fix: harden Protobuf Deserialization and Refactor Utilities (#1820)
* refactor: reorganize browser utility functions and improve buffer handling * fix: harden protobuf deserialization and clean up code * refactor: simplify data handling in GroupCipher and SenderKey classes * fix: Ensure consistent Buffer hydration and remove redundant code * refactor: update decodeAndHydrate calls to use proto types for improved type safety
This commit is contained in:
committed by
GitHub
parent
202c39f3fe
commit
3b46d43beb
@@ -19,7 +19,7 @@ export class GroupCipher {
|
||||
this.senderKeyName = senderKeyName
|
||||
}
|
||||
|
||||
public async encrypt(paddedPlaintext: Uint8Array | string): Promise<Uint8Array> {
|
||||
public async encrypt(paddedPlaintext: Uint8Array): Promise<Uint8Array> {
|
||||
const record = await this.senderKeyStore.loadSenderKey(this.senderKeyName)
|
||||
if (!record) {
|
||||
throw new Error('No SenderKeyRecord found for encryption')
|
||||
@@ -107,16 +107,9 @@ export class GroupCipher {
|
||||
}
|
||||
}
|
||||
|
||||
private async getCipherText(
|
||||
iv: Uint8Array | string,
|
||||
key: Uint8Array | string,
|
||||
plaintext: Uint8Array | string
|
||||
): Promise<Buffer> {
|
||||
private async getCipherText(iv: Uint8Array, key: Uint8Array, plaintext: Uint8Array): Promise<Buffer> {
|
||||
try {
|
||||
const ivBuffer = typeof iv === 'string' ? Buffer.from(iv, 'base64') : iv
|
||||
const keyBuffer = typeof key === 'string' ? Buffer.from(key, 'base64') : key
|
||||
const plaintextBuffer = typeof plaintext === 'string' ? Buffer.from(plaintext) : plaintext
|
||||
return encrypt(keyBuffer, plaintextBuffer, ivBuffer)
|
||||
return encrypt(key, plaintext, iv)
|
||||
} catch (e) {
|
||||
throw new Error('InvalidMessageException')
|
||||
}
|
||||
|
||||
@@ -9,16 +9,7 @@ export class SenderChainKey {
|
||||
|
||||
constructor(iteration: number, chainKey: Uint8Array | Buffer) {
|
||||
this.iteration = iteration
|
||||
if (Buffer.isBuffer(chainKey)) {
|
||||
this.chainKey = chainKey
|
||||
} else if (chainKey instanceof Uint8Array) {
|
||||
this.chainKey = Buffer.from(chainKey)
|
||||
} else if (chainKey && typeof chainKey === 'object') {
|
||||
// backported from @MartinSchere (#1741)
|
||||
this.chainKey = Buffer.from(Object.values(chainKey)) // temp fix // backported from @MartinSchere (#1741)
|
||||
} else {
|
||||
this.chainKey = Buffer.alloc(0)
|
||||
}
|
||||
this.chainKey = Buffer.from(chainKey)
|
||||
}
|
||||
|
||||
public getIteration(): number {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { proto } from '../../../WAProto/index.js'
|
||||
import { decodeAndHydrate } from '../../Utils/proto-utils'
|
||||
import { CiphertextMessage } from './ciphertext-message'
|
||||
|
||||
export class SenderKeyDistributionMessage extends CiphertextMessage {
|
||||
@@ -20,19 +21,13 @@ export class SenderKeyDistributionMessage extends CiphertextMessage {
|
||||
if (serialized) {
|
||||
try {
|
||||
const message = serialized.slice(1)
|
||||
const distributionMessage = proto.SenderKeyDistributionMessage.decode(message)
|
||||
const distributionMessage = decodeAndHydrate(proto.SenderKeyDistributionMessage, message)
|
||||
|
||||
this.serialized = serialized
|
||||
this.id = distributionMessage.id
|
||||
this.iteration = distributionMessage.iteration
|
||||
this.chainKey =
|
||||
typeof distributionMessage.chainKey === 'string'
|
||||
? Buffer.from(distributionMessage.chainKey, 'base64')
|
||||
: distributionMessage.chainKey
|
||||
this.signatureKey =
|
||||
typeof distributionMessage.signingKey === 'string'
|
||||
? Buffer.from(distributionMessage.signingKey, 'base64')
|
||||
: distributionMessage.signingKey
|
||||
this.chainKey = distributionMessage.chainKey
|
||||
this.signatureKey = distributionMessage.signingKey
|
||||
} catch (e) {
|
||||
throw new Error(String(e))
|
||||
}
|
||||
@@ -73,11 +68,11 @@ export class SenderKeyDistributionMessage extends CiphertextMessage {
|
||||
}
|
||||
|
||||
public getChainKey(): Uint8Array {
|
||||
return typeof this.chainKey === 'string' ? Buffer.from(this.chainKey, 'base64') : this.chainKey
|
||||
return this.chainKey
|
||||
}
|
||||
|
||||
public getSignatureKey(): Uint8Array {
|
||||
return typeof this.signatureKey === 'string' ? Buffer.from(this.signatureKey, 'base64') : this.signatureKey
|
||||
return this.signatureKey
|
||||
}
|
||||
|
||||
public getId(): number {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { calculateSignature, verifySignature } from 'libsignal/src/curve'
|
||||
import { proto } from '../../../WAProto/index.js'
|
||||
import { decodeAndHydrate } from '../../Utils/proto-utils'
|
||||
import { CiphertextMessage } from './ciphertext-message'
|
||||
|
||||
export class SenderKeyMessage extends CiphertextMessage {
|
||||
@@ -24,16 +25,13 @@ export class SenderKeyMessage extends CiphertextMessage {
|
||||
const version = serialized[0]!
|
||||
const message = serialized.slice(1, serialized.length - this.SIGNATURE_LENGTH)
|
||||
const signature = serialized.slice(-1 * this.SIGNATURE_LENGTH)
|
||||
const senderKeyMessage = proto.SenderKeyMessage.decode(message)
|
||||
const senderKeyMessage = decodeAndHydrate(proto.SenderKeyMessage, message)
|
||||
|
||||
this.serialized = serialized
|
||||
this.messageVersion = (version & 0xff) >> 4
|
||||
this.keyId = senderKeyMessage.id
|
||||
this.iteration = senderKeyMessage.iteration
|
||||
this.ciphertext =
|
||||
typeof senderKeyMessage.ciphertext === 'string'
|
||||
? Buffer.from(senderKeyMessage.ciphertext, 'base64')
|
||||
: senderKeyMessage.ciphertext
|
||||
this.ciphertext = senderKeyMessage.ciphertext
|
||||
this.signature = signature
|
||||
} else {
|
||||
const version = (((this.CURRENT_VERSION << 4) | this.CURRENT_VERSION) & 0xff) % 256
|
||||
|
||||
@@ -61,17 +61,9 @@ export class SenderKeyRecord {
|
||||
public serialize(): SenderKeyStateStructure[] {
|
||||
return this.senderKeyStates.map(state => state.getStructure())
|
||||
}
|
||||
static deserialize(data: Uint8Array | string | SenderKeyStateStructure[]): SenderKeyRecord {
|
||||
let parsed: SenderKeyStateStructure[]
|
||||
if (typeof data === 'string') {
|
||||
parsed = JSON.parse(data, BufferJSON.reviver)
|
||||
} else if (data instanceof Uint8Array) {
|
||||
const str = Buffer.from(data).toString('utf-8')
|
||||
parsed = JSON.parse(str, BufferJSON.reviver)
|
||||
} else {
|
||||
parsed = data
|
||||
}
|
||||
|
||||
static deserialize(data: Uint8Array): SenderKeyRecord {
|
||||
const str = Buffer.from(data).toString('utf-8')
|
||||
const parsed = JSON.parse(str, BufferJSON.reviver)
|
||||
return new SenderKeyRecord(parsed)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,13 +36,6 @@ export class SenderKeyState {
|
||||
signatureKeyPrivate?: Uint8Array | string | null,
|
||||
senderKeyStateStructure?: SenderKeyStateStructure | null
|
||||
) {
|
||||
const toBuffer = (val: any) => {
|
||||
if (!val) return Buffer.alloc(0)
|
||||
if (typeof val === 'string') return Buffer.from(val, 'base64')
|
||||
if (val instanceof Uint8Array || Array.isArray(val)) return Buffer.from(val)
|
||||
return Buffer.alloc(0)
|
||||
}
|
||||
|
||||
if (senderKeyStateStructure) {
|
||||
this.senderKeyStateStructure = {
|
||||
...senderKeyStateStructure,
|
||||
@@ -56,22 +49,16 @@ export class SenderKeyState {
|
||||
signatureKeyPrivate = signatureKeyPair.private
|
||||
}
|
||||
|
||||
chainKey = typeof chainKey === 'string' ? Buffer.from(chainKey, 'base64') : chainKey
|
||||
|
||||
const senderChainKeyStructure: SenderChainKeyStructure = {
|
||||
iteration: iteration || 0,
|
||||
seed: chainKey ? toBuffer(chainKey) : Buffer.alloc(0)
|
||||
}
|
||||
|
||||
const signingKeyStructure: SenderSigningKeyStructure = {
|
||||
public: toBuffer(signatureKeyPublic),
|
||||
private: signatureKeyPrivate ? toBuffer(signatureKeyPrivate) : undefined
|
||||
}
|
||||
|
||||
this.senderKeyStateStructure = {
|
||||
senderKeyId: id || 0,
|
||||
senderChainKey: senderChainKeyStructure,
|
||||
senderSigningKey: signingKeyStructure,
|
||||
senderChainKey: {
|
||||
iteration: iteration || 0,
|
||||
seed: Buffer.from(chainKey || [])
|
||||
},
|
||||
senderSigningKey: {
|
||||
public: Buffer.from(signatureKeyPublic || []),
|
||||
private: Buffer.from(signatureKeyPrivate || [])
|
||||
},
|
||||
senderMessageKeys: []
|
||||
}
|
||||
}
|
||||
@@ -96,22 +83,7 @@ export class SenderKeyState {
|
||||
}
|
||||
|
||||
public getSigningKeyPublic(): Buffer {
|
||||
let key = this.senderKeyStateStructure.senderSigningKey.public
|
||||
|
||||
// normalize into Buffer
|
||||
if (!Buffer.isBuffer(key)) {
|
||||
if (key instanceof Uint8Array) {
|
||||
key = Buffer.from(key)
|
||||
} else if (typeof key === 'string') {
|
||||
key = Buffer.from(key, 'base64')
|
||||
} else if (key && typeof key === 'object') {
|
||||
return Buffer.from(Object.values(key)) // temp fix // inspired by @MartinSchere 's #1741
|
||||
} else {
|
||||
key = Buffer.from(key || [])
|
||||
}
|
||||
}
|
||||
|
||||
const publicKey = key as Buffer
|
||||
const publicKey = Buffer.from(this.senderKeyStateStructure.senderSigningKey.public)
|
||||
|
||||
if (publicKey.length === 32) {
|
||||
const fixed = Buffer.alloc(33)
|
||||
@@ -125,19 +97,6 @@ export class SenderKeyState {
|
||||
|
||||
public getSigningKeyPrivate(): Buffer | undefined {
|
||||
const privateKey = this.senderKeyStateStructure.senderSigningKey.private
|
||||
if (!privateKey) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
if (Buffer.isBuffer(privateKey)) {
|
||||
return privateKey
|
||||
} else if (privateKey instanceof Uint8Array) {
|
||||
return Buffer.from(privateKey)
|
||||
} else if (privateKey && typeof privateKey === 'object') {
|
||||
return Buffer.from(Object.values(privateKey)) // temp fix // inspired by @MartinSchere 's #1741
|
||||
} else if (typeof privateKey === 'string') {
|
||||
return Buffer.from(privateKey, 'base64')
|
||||
}
|
||||
|
||||
return Buffer.from(privateKey || [])
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user