From 7b96aba1bf2a9e98897ed81baa0c5082e7612f1e Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Fri, 7 May 2021 10:16:15 +1000 Subject: [PATCH] padd Message buffer for all outgoing messages (even opengroupv2) --- app/tray_icon.js | 8 +++- .../session/SessionPasswordModal.tsx | 4 +- ts/receiver/contentMessage.ts | 23 ++-------- ts/receiver/receiver.ts | 6 ++- ts/session/crypto/MessageEncrypter.ts | 26 +---------- ts/session/crypto/MessagePadding.ts | 45 +++++++++++++++++++ ts/session/sending/MessageSender.ts | 5 ++- ts/session/utils/Attachments.ts | 1 + .../unit/crypto/MessageEncrypter_test.ts | 3 +- 9 files changed, 69 insertions(+), 52 deletions(-) create mode 100644 ts/session/crypto/MessagePadding.ts diff --git a/app/tray_icon.js b/app/tray_icon.js index 503174882..66489e6fd 100644 --- a/app/tray_icon.js +++ b/app/tray_icon.js @@ -9,7 +9,13 @@ let tray = null; function createTrayIcon(getMainWindow, messages) { // A smaller icon is needed on macOS const iconSize = process.platform === 'darwin' ? '16' : '256'; - const iconNoNewMessages = path.join(__dirname, '..', 'images', 'session', `session_icon_${iconSize}.png`); + const iconNoNewMessages = path.join( + __dirname, + '..', + 'images', + 'session', + `session_icon_${iconSize}.png` + ); tray = new Tray(iconNoNewMessages); diff --git a/ts/components/session/SessionPasswordModal.tsx b/ts/components/session/SessionPasswordModal.tsx index bbe1bde16..24c2dd72a 100644 --- a/ts/components/session/SessionPasswordModal.tsx +++ b/ts/components/session/SessionPasswordModal.tsx @@ -208,9 +208,7 @@ class SessionPasswordModalInner extends React.Component { return; } - const isValidWithStoredInDB = Boolean( - await this.validatePasswordHash(oldPassword) - ); + const isValidWithStoredInDB = Boolean(await this.validatePasswordHash(oldPassword)); if (!isValidWithStoredInDB) { this.setState({ error: window.i18n('changePasswordInvalid'), diff --git a/ts/receiver/contentMessage.ts b/ts/receiver/contentMessage.ts index 137cb4dc2..06666d7e0 100644 --- a/ts/receiver/contentMessage.ts +++ b/ts/receiver/contentMessage.ts @@ -17,6 +17,7 @@ import { KeyPairRequestManager } from './keyPairRequestManager'; import { requestEncryptionKeyPair } from '../session/group'; import { handleConfigurationMessage } from './configMessage'; import { ConversationTypeEnum } from '../models/conversation'; +import { removeMessagePadding } from '../session/crypto/MessagePadding'; export async function handleContentMessage(envelope: EnvelopePlus) { try { @@ -94,7 +95,7 @@ async function decryptForClosedGroup(envelope: EnvelopePlus, ciphertext: ArrayBu } window.log.info('ClosedGroup Message decrypted successfully with keyIndex:', keyIndex); - return unpad(decryptedContent); + return removeMessagePadding(decryptedContent); } catch (e) { /** * If an error happened during the decoding, @@ -130,7 +131,7 @@ async function decryptForClosedGroup(envelope: EnvelopePlus, ciphertext: ArrayBu * or a message sent to a closed group. * * We do not unpad the result here, as in the case of the keypair wrapper, there is not padding. - * Instead, it is the called who needs to unpad() the content. + * Instead, it is the called who needs to removeMessagePadding() the content. */ export async function decryptWithSessionProtocol( envelope: EnvelopePlus, @@ -191,22 +192,6 @@ export async function decryptWithSessionProtocol( return plaintext; } -export function unpad(paddedData: ArrayBuffer): ArrayBuffer { - const paddedPlaintext = new Uint8Array(paddedData); - - for (let i = paddedPlaintext.length - 1; i >= 0; i -= 1) { - if (paddedPlaintext[i] === 0x80) { - const plaintext = new Uint8Array(i); - plaintext.set(paddedPlaintext.subarray(0, i)); - return plaintext.buffer; - } else if (paddedPlaintext[i] !== 0x00) { - throw new Error('Invalid padding'); - } - } - - throw new Error('Invalid padding'); -} - export async function isBlocked(number: string) { return BlockedNumberController.isBlockedAsync(number); } @@ -227,7 +212,7 @@ async function decryptUnidentifiedSender( ); // keep the await so the try catch works as expected const retSessionProtocol = await decryptWithSessionProtocol(envelope, ciphertext, ecKeyPair); - return unpad(retSessionProtocol); + return removeMessagePadding(retSessionProtocol); } catch (e) { window.log.warn('decryptWithSessionProtocol for unidentified message throw:', e); return null; diff --git a/ts/receiver/receiver.ts b/ts/receiver/receiver.ts index b4590e6dc..fe85f4309 100644 --- a/ts/receiver/receiver.ts +++ b/ts/receiver/receiver.ts @@ -9,7 +9,7 @@ import { processMessage } from '../session/snode_api/swarmPolling'; import { onError } from './errors'; // innerHandleContentMessage is only needed because of code duplication in handleDecryptedEnvelope... -import { handleContentMessage, innerHandleContentMessage, unpad } from './contentMessage'; +import { handleContentMessage, innerHandleContentMessage } from './contentMessage'; import _, { noop } from 'lodash'; export { processMessage }; @@ -36,6 +36,7 @@ import { OpenGroupMessageV2 } from '../opengroup/opengroupV2/OpenGroupMessageV2' import { OpenGroupRequestCommonType } from '../opengroup/opengroupV2/ApiUtil'; import { handleMessageJob } from './queuedJob'; import { fromBase64ToArray } from '../session/utils/String'; +import { removeMessagePadding } from '../session/crypto/MessagePadding'; // TODO: check if some of these exports no longer needed @@ -315,7 +316,8 @@ export async function handleOpenGroupV2Message( return; } - const dataUint = new Uint8Array(unpad(fromBase64ToArray(base64EncodedData))); + // Note: opengroup messages are not padded + const dataUint = new Uint8Array(removeMessagePadding(fromBase64ToArray(base64EncodedData))); const decoded = SignalService.Content.decode(dataUint); diff --git a/ts/session/crypto/MessageEncrypter.ts b/ts/session/crypto/MessageEncrypter.ts index a3cf2a988..63b177dd1 100644 --- a/ts/session/crypto/MessageEncrypter.ts +++ b/ts/session/crypto/MessageEncrypter.ts @@ -6,29 +6,7 @@ import { fromHexToArray } from '../utils/String'; export { concatUInt8Array, getSodium }; import { getLatestClosedGroupEncryptionKeyPair } from '../../../ts/data/data'; import { UserUtils } from '../utils'; - -/** - * Add padding to a message buffer - * @param messageBuffer The buffer to add padding to. - */ -export function padPlainTextBuffer(messageBuffer: Uint8Array): Uint8Array { - const plaintext = new Uint8Array(getPaddedMessageLength(messageBuffer.byteLength + 1) - 1); - plaintext.set(new Uint8Array(messageBuffer)); - plaintext[messageBuffer.byteLength] = 0x80; - - return plaintext; -} - -function getPaddedMessageLength(originalLength: number): number { - const messageLengthWithTerminator = originalLength + 1; - let messagePartCount = Math.floor(messageLengthWithTerminator / 160); - - if (messageLengthWithTerminator % 160 !== 0) { - messagePartCount += 1; - } - - return messagePartCount * 160; -} +import { addMessagePadding } from './MessagePadding'; type EncryptResult = { envelopeType: SignalService.Envelope.Type; @@ -53,7 +31,7 @@ export async function encrypt( throw new Error(`Invalid encryption type:${encryptionType}`); } const encryptForClosedGroup = encryptionType === EncryptionType.ClosedGroup; - const plainText = padPlainTextBuffer(plainTextBuffer); + const plainText = addMessagePadding(plainTextBuffer); if (encryptForClosedGroup) { window?.log?.info( diff --git a/ts/session/crypto/MessagePadding.ts b/ts/session/crypto/MessagePadding.ts new file mode 100644 index 000000000..f8e6868ab --- /dev/null +++ b/ts/session/crypto/MessagePadding.ts @@ -0,0 +1,45 @@ +/** + * Unpad the buffer from its padding. + * An error is thrown if there is no padding. + * A padded buffer is + * * whatever at start + * * ends with 0x80 and any number of 0x00 until the end + */ +export function removeMessagePadding(paddedData: ArrayBuffer): ArrayBuffer { + const paddedPlaintext = new Uint8Array(paddedData); + + for (let i = paddedPlaintext.length - 1; i >= 0; i -= 1) { + if (paddedPlaintext[i] === 0x80) { + const plaintext = new Uint8Array(i); + plaintext.set(paddedPlaintext.subarray(0, i)); + return plaintext.buffer; + } else if (paddedPlaintext[i] !== 0x00) { + throw new Error('Invalid padding'); + } + } + + throw new Error('Invalid padding'); +} + +/** + * Add padding to a message buffer + * @param messageBuffer The buffer to add padding to. + */ +export function addMessagePadding(messageBuffer: Uint8Array): Uint8Array { + const plaintext = new Uint8Array(getPaddedMessageLength(messageBuffer.byteLength + 1) - 1); + plaintext.set(new Uint8Array(messageBuffer)); + plaintext[messageBuffer.byteLength] = 0x80; + + return plaintext; +} + +function getPaddedMessageLength(originalLength: number): number { + const messageLengthWithTerminator = originalLength + 1; + let messagePartCount = Math.floor(messageLengthWithTerminator / 160); + + if (messageLengthWithTerminator % 160 !== 0) { + messagePartCount += 1; + } + + return messagePartCount * 160; +} diff --git a/ts/session/sending/MessageSender.ts b/ts/session/sending/MessageSender.ts index 481aabbc4..c9fe43183 100644 --- a/ts/session/sending/MessageSender.ts +++ b/ts/session/sending/MessageSender.ts @@ -10,10 +10,10 @@ import { UserUtils } from '../utils'; import { OpenGroupRequestCommonType } from '../../opengroup/opengroupV2/ApiUtil'; import { postMessage } from '../../opengroup/opengroupV2/OpenGroupAPIV2'; import { OpenGroupMessageV2 } from '../../opengroup/opengroupV2/OpenGroupMessageV2'; -import { padPlainTextBuffer } from '../crypto/MessageEncrypter'; import { fromUInt8ArrayToBase64 } from '../utils/String'; import { OpenGroupVisibleMessage } from '../messages/outgoing/visibleMessage/OpenGroupVisibleMessage'; import * as LokiMessageApi from './LokiMessageApi'; +import { addMessagePadding } from '../crypto/MessagePadding'; // ================ Regular ================ @@ -136,7 +136,8 @@ export async function sendToOpenGroupV2( rawMessage: OpenGroupVisibleMessage, roomInfos: OpenGroupRequestCommonType ): Promise { - const paddedBody = padPlainTextBuffer(rawMessage.plainTextBuffer()); + // we agreed to pad message for opengroupv2 + const paddedBody = addMessagePadding(rawMessage.plainTextBuffer()); const v2Message = new OpenGroupMessageV2({ sentTimestamp: Date.now(), sender: UserUtils.getOurPubKeyStrFromCache(), diff --git a/ts/session/utils/Attachments.ts b/ts/session/utils/Attachments.ts index 63a39301f..212f38537 100644 --- a/ts/session/utils/Attachments.ts +++ b/ts/session/utils/Attachments.ts @@ -74,6 +74,7 @@ export class AttachmentUtils { let attachmentData: ArrayBuffer; + // We don't pad attachments for opengroup as they are unencrypted if (isRaw || openGroup) { attachmentData = attachment.data; } else { diff --git a/ts/test/session/unit/crypto/MessageEncrypter_test.ts b/ts/test/session/unit/crypto/MessageEncrypter_test.ts index b5697e913..9c6603f49 100644 --- a/ts/test/session/unit/crypto/MessageEncrypter_test.ts +++ b/ts/test/session/unit/crypto/MessageEncrypter_test.ts @@ -11,6 +11,7 @@ import { StringUtils, UserUtils } from '../../../../session/utils'; import chaiBytes from 'chai-bytes'; import { PubKey } from '../../../../session/types'; import { fromHex, toHex } from '../../../../session/utils/String'; +import { addMessagePadding } from '../../../../session/crypto/MessagePadding'; chai.use(chaiBytes); @@ -187,7 +188,7 @@ describe('MessageEncrypter', () => { const spy = sinon.spy(MessageEncrypter, 'encryptUsingSessionProtocol'); await MessageEncrypter.encrypt(TestUtils.generateFakePubKey(), data, EncryptionType.Fallback); chai.expect(spy.callCount).to.be.equal(1); - const paddedData = MessageEncrypter.padPlainTextBuffer(data); + const paddedData = addMessagePadding(data); const firstArgument = new Uint8Array(spy.args[0][1]); chai.expect(firstArgument).to.deep.equal(paddedData); spy.restore();