From c063848167aa4435342f9bf69a4c6fb881cbfc17 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Fri, 7 May 2021 10:56:14 +1000 Subject: [PATCH] pad and unpad message everywhere attachment not padded for opengroup only --- ts/receiver/attachments.ts | 20 ++-- ts/receiver/contentMessage.ts | 2 +- ts/receiver/receiver.ts | 2 +- ts/session/crypto/BufferPadding.ts | 94 +++++++++++++++++++ ts/session/crypto/MessageEncrypter.ts | 2 +- ts/session/crypto/MessagePadding.ts | 45 --------- ts/session/sending/MessageSender.ts | 2 +- ts/session/utils/Attachments.ts | 39 +------- ts/session/utils/AttachmentsV2.ts | 10 +- .../unit/crypto/MessageEncrypter_test.ts | 2 +- 10 files changed, 116 insertions(+), 102 deletions(-) create mode 100644 ts/session/crypto/BufferPadding.ts delete mode 100644 ts/session/crypto/MessagePadding.ts diff --git a/ts/receiver/attachments.ts b/ts/receiver/attachments.ts index edbe253b1..7430f0f64 100644 --- a/ts/receiver/attachments.ts +++ b/ts/receiver/attachments.ts @@ -11,6 +11,7 @@ import { } from '../opengroup/opengroupV2/OpenGroupAPIV2'; import { OpenGroupRequestCommonType } from '../opengroup/opengroupV2/ApiUtil'; import { FSv2 } from '../fileserver'; +import { getUnpaddedAttachment } from '../session/crypto/BufferPadding'; export async function downloadAttachment(attachment: any) { const serverUrl = new URL(attachment.url).origin; @@ -69,14 +70,13 @@ export async function downloadAttachment(attachment: any) { if (!size || size !== data.byteLength) { // we might have padding, check that all the remaining bytes are padding bytes // otherwise we have an error. - if (AttachmentUtils.isLeftOfBufferPaddingOnly(data, size)) { - // we can safely remove the padding - data = data.slice(0, size); - } else { + const unpaddedData = getUnpaddedAttachment(data, size); + if (!unpaddedData) { throw new Error( `downloadAttachment: Size ${size} did not match downloaded attachment size ${data.byteLength}` ); } + data = unpaddedData; } } @@ -120,17 +120,17 @@ export async function downloadAttachmentOpenGroupV2( if (attachment.size !== dataUint.length) { // we might have padding, check that all the remaining bytes are padding bytes // otherwise we have an error. - if (AttachmentUtils.isLeftOfBufferPaddingOnly(dataUint.buffer, attachment.size)) { - // we can safely remove the padding - data = data.slice(0, attachment.size); - } else { + const unpaddedData = getUnpaddedAttachment(dataUint.buffer, attachment.size); + if (!unpaddedData) { throw new Error( `downloadAttachment: Size ${attachment.size} did not match downloaded attachment size ${data.byteLength}` ); } + data = new Uint8Array(unpaddedData); } else { - // nothing to do, the attachment has already the correct size. There is just no padding included, which is bas - window.log.warn('Received opengroupv2 unpadded attachment'); + // nothing to do, the attachment has already the correct size. + // There is just no padding included, which is what we agreed on + window.log.info('Received opengroupv2 unpadded attachment'); } return { diff --git a/ts/receiver/contentMessage.ts b/ts/receiver/contentMessage.ts index 06666d7e0..85058a6de 100644 --- a/ts/receiver/contentMessage.ts +++ b/ts/receiver/contentMessage.ts @@ -17,7 +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'; +import { removeMessagePadding } from '../session/crypto/BufferPadding'; export async function handleContentMessage(envelope: EnvelopePlus) { try { diff --git a/ts/receiver/receiver.ts b/ts/receiver/receiver.ts index fe85f4309..c37e8ecb7 100644 --- a/ts/receiver/receiver.ts +++ b/ts/receiver/receiver.ts @@ -36,7 +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'; +import { removeMessagePadding } from '../session/crypto/BufferPadding'; // TODO: check if some of these exports no longer needed diff --git a/ts/session/crypto/BufferPadding.ts b/ts/session/crypto/BufferPadding.ts new file mode 100644 index 000000000..2c998b0e9 --- /dev/null +++ b/ts/session/crypto/BufferPadding.ts @@ -0,0 +1,94 @@ +/** + * This file is used to pad message buffer and attachments + */ +const PADDING_BYTE = 0x00; + +/** + * 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); + window.log.info('Removing message padding...'); + 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] !== PADDING_BYTE) { + 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 { + window.log.info('Adding message padding...'); + + 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; +} + +/** + * + * If the attachment has padding, remove the padding and return the unpad attachment + + */ +export function getUnpaddedAttachment( + data: ArrayBuffer, + unpaddedExpectedSize: number +): ArrayBuffer | null { + window.log.info('Removing attachment padding...'); + + // to have a padding we must have a strictly longer length expected + if (data.byteLength <= unpaddedExpectedSize) { + return null; + } + const dataUint = new Uint8Array(data); + for (let i = unpaddedExpectedSize; i < data.byteLength; i++) { + if (dataUint[i] !== PADDING_BYTE) { + return null; + } + } + + return data.slice(0, unpaddedExpectedSize); +} + +export function addAttachmentPadding(data: ArrayBuffer): ArrayBuffer { + const originalUInt = new Uint8Array(data); + window.log.info('Adding attchment padding...'); + + const paddedSize = Math.max( + 541, + Math.floor(Math.pow(1.05, Math.ceil(Math.log(originalUInt.length) / Math.log(1.05)))) + ); + const paddedData = new ArrayBuffer(paddedSize); + const paddedUInt = new Uint8Array(paddedData); + + paddedUInt.fill(PADDING_BYTE, originalUInt.length); + paddedUInt.set(originalUInt); + + return paddedUInt.buffer; +} diff --git a/ts/session/crypto/MessageEncrypter.ts b/ts/session/crypto/MessageEncrypter.ts index 63b177dd1..8e5f9045b 100644 --- a/ts/session/crypto/MessageEncrypter.ts +++ b/ts/session/crypto/MessageEncrypter.ts @@ -6,7 +6,7 @@ import { fromHexToArray } from '../utils/String'; export { concatUInt8Array, getSodium }; import { getLatestClosedGroupEncryptionKeyPair } from '../../../ts/data/data'; import { UserUtils } from '../utils'; -import { addMessagePadding } from './MessagePadding'; +import { addMessagePadding } from './BufferPadding'; type EncryptResult = { envelopeType: SignalService.Envelope.Type; diff --git a/ts/session/crypto/MessagePadding.ts b/ts/session/crypto/MessagePadding.ts deleted file mode 100644 index f8e6868ab..000000000 --- a/ts/session/crypto/MessagePadding.ts +++ /dev/null @@ -1,45 +0,0 @@ -/** - * 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 c9fe43183..caf483d94 100644 --- a/ts/session/sending/MessageSender.ts +++ b/ts/session/sending/MessageSender.ts @@ -13,7 +13,7 @@ import { OpenGroupMessageV2 } from '../../opengroup/opengroupV2/OpenGroupMessage import { fromUInt8ArrayToBase64 } from '../utils/String'; import { OpenGroupVisibleMessage } from '../messages/outgoing/visibleMessage/OpenGroupVisibleMessage'; import * as LokiMessageApi from './LokiMessageApi'; -import { addMessagePadding } from '../crypto/MessagePadding'; +import { addMessagePadding } from '../crypto/BufferPadding'; // ================ Regular ================ diff --git a/ts/session/utils/Attachments.ts b/ts/session/utils/Attachments.ts index 212f38537..060f387c9 100644 --- a/ts/session/utils/Attachments.ts +++ b/ts/session/utils/Attachments.ts @@ -9,6 +9,7 @@ import { } from '../messages/outgoing/visibleMessage/VisibleMessage'; import { OpenGroup } from '../../opengroup/opengroupV1/OpenGroup'; import { FSv2 } from '../../fileserver'; +import { addAttachmentPadding } from '../crypto/BufferPadding'; interface UploadParams { attachment: Attachment; @@ -39,8 +40,6 @@ interface RawQuote { // tslint:disable-next-line: no-unnecessary-class export class AttachmentUtils { - public static readonly PADDING_BYTE = 0; - private constructor() {} public static async uploadV1(params: UploadParams): Promise { @@ -85,7 +84,7 @@ export class AttachmentUtils { const dataToEncrypt = !shouldPad || !window.lokiFeatureFlags.padOutgoingAttachments ? attachment.data - : AttachmentUtils.addAttachmentPadding(attachment.data); + : addAttachmentPadding(attachment.data); const data = await window.textsecure.crypto.encryptAttachment( dataToEncrypt, pointer.key.buffer, @@ -196,38 +195,4 @@ export class AttachmentUtils { attachments, }; } - - public static isLeftOfBufferPaddingOnly( - data: ArrayBuffer, - unpaddedExpectedSize: number - ): boolean { - // to have a padding we must have a strictly longer length expected - if (data.byteLength <= unpaddedExpectedSize) { - return false; - } - const dataUint = new Uint8Array(data); - for (let i = unpaddedExpectedSize; i < data.byteLength; i++) { - if (dataUint[i] !== this.PADDING_BYTE) { - return false; - } - } - - return true; - } - - public static addAttachmentPadding(data: ArrayBuffer): ArrayBuffer { - const originalUInt = new Uint8Array(data); - - const paddedSize = Math.max( - 541, - Math.floor(Math.pow(1.05, Math.ceil(Math.log(originalUInt.length) / Math.log(1.05)))) - ); - const paddedData = new ArrayBuffer(paddedSize); - const paddedUInt = new Uint8Array(paddedData); - - paddedUInt.fill(AttachmentUtils.PADDING_BYTE, originalUInt.length); - paddedUInt.set(originalUInt); - - return paddedUInt.buffer; - } } diff --git a/ts/session/utils/AttachmentsV2.ts b/ts/session/utils/AttachmentsV2.ts index e877ae0c9..e5023d42a 100644 --- a/ts/session/utils/AttachmentsV2.ts +++ b/ts/session/utils/AttachmentsV2.ts @@ -10,6 +10,7 @@ import { } from '../messages/outgoing/visibleMessage/VisibleMessage'; import { AttachmentUtils } from './Attachments'; import { uploadFileOpenGroupV2 } from '../../opengroup/opengroupV2/OpenGroupAPIV2'; +import { addAttachmentPadding } from '../crypto/BufferPadding'; interface UploadParamsV2 { attachment: Attachment; @@ -35,8 +36,6 @@ interface RawQuote { attachments?: Array; } -const PADDING_BYTE = 0; - export async function uploadV2(params: UploadParamsV2): Promise { const { attachment, openGroup } = params; if (typeof attachment !== 'object' || attachment == null) { @@ -57,9 +56,10 @@ export async function uploadV2(params: UploadParamsV2): Promise