From 0228cd51157f20840a05e19b261e1f94cbf27e19 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Fri, 5 Mar 2021 16:55:25 +1100 Subject: [PATCH 1/2] add attachment padding --- ts/receiver/attachments.ts | 14 ++++++-- ts/session/utils/Attachments.ts | 57 +++++++++++++++++++++++++++++++-- 2 files changed, 66 insertions(+), 5 deletions(-) diff --git a/ts/receiver/attachments.ts b/ts/receiver/attachments.ts index c67497594..598436dec 100644 --- a/ts/receiver/attachments.ts +++ b/ts/receiver/attachments.ts @@ -3,6 +3,7 @@ import _ from 'lodash'; import { MessageModel } from '../models/message'; import { saveMessage } from '../../ts/data/data'; import { fromBase64ToArrayBuffer } from '../session/utils/String'; +import { AttachmentUtils } from '../session/utils'; export async function downloadAttachment(attachment: any) { const serverUrl = new URL(attachment.url).origin; @@ -70,9 +71,16 @@ export async function downloadAttachment(attachment: any) { ); if (!size || size !== data.byteLength) { - throw new Error( - `downloadAttachment: Size ${size} did not match downloaded attachment 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 { + throw new Error( + `downloadAttachment: Size ${size} did not match downloaded attachment size ${data.byteLength}` + ); + } } } diff --git a/ts/session/utils/Attachments.ts b/ts/session/utils/Attachments.ts index f81ec0625..97e99b4e5 100644 --- a/ts/session/utils/Attachments.ts +++ b/ts/session/utils/Attachments.ts @@ -14,6 +14,7 @@ interface UploadParams { openGroup?: OpenGroup; isAvatar?: boolean; isRaw?: boolean; + shouldPad?: boolean; } interface RawPreview { @@ -37,6 +38,8 @@ interface RawQuote { // tslint:disable-next-line: no-unnecessary-class export class AttachmentUtils { + public static readonly PADDING_BYTE = 0; + private constructor() {} public static getDefaultServer(): LokiAppDotNetServerInterface { @@ -44,7 +47,13 @@ export class AttachmentUtils { } public static async upload(params: UploadParams): Promise { - const { attachment, openGroup, isAvatar = false, isRaw = false } = params; + const { + attachment, + openGroup, + isAvatar = false, + isRaw = false, + shouldPad = false, + } = params; if (typeof attachment !== 'object' || attachment == null) { throw new Error('Invalid attachment passed.'); } @@ -83,8 +92,12 @@ export class AttachmentUtils { server = this.getDefaultServer(); pointer.key = new Uint8Array(crypto.randomBytes(64)); const iv = new Uint8Array(crypto.randomBytes(16)); + + const dataToEncrypt = !shouldPad + ? attachment.data + : AttachmentUtils.addAttachmentPadding(attachment.data); const data = await window.textsecure.crypto.encryptAttachment( - attachment.data, + dataToEncrypt, pointer.key.buffer, iv.buffer ); @@ -126,6 +139,7 @@ export class AttachmentUtils { this.upload({ attachment, openGroup, + shouldPad: true, }) ); @@ -181,4 +195,43 @@ 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; + } + + private 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; + } } From 099c9a6f8c03d4e74bc022f4c82ec91eaa1e589e Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Tue, 16 Mar 2021 11:49:10 +1100 Subject: [PATCH 2/2] autobind class with a lot of ...bind(this) --- AUDRICTOCLEAN.txt | 1 - ts/components/session/SessionSeedModal.tsx | 5 +-- .../conversation/SessionCompositionBox.tsx | 36 +--------------- .../conversation/SessionConversation.tsx | 43 +------------------ .../conversation/SessionMessagesList.tsx | 9 +--- .../session/conversation/SessionRecording.tsx | 23 +--------- ts/models/message.ts | 2 - 7 files changed, 9 insertions(+), 110 deletions(-) diff --git a/AUDRICTOCLEAN.txt b/AUDRICTOCLEAN.txt index e877bf1d0..765fd7239 100644 --- a/AUDRICTOCLEAN.txt +++ b/AUDRICTOCLEAN.txt @@ -27,7 +27,6 @@ sendSyncMessageOnly to fix indexedDB initializeAttachmentMetadata=> -schemaVersion for messages to put as what needs to be set run_migration diff --git a/ts/components/session/SessionSeedModal.tsx b/ts/components/session/SessionSeedModal.tsx index e88cd2e43..0b7000c31 100644 --- a/ts/components/session/SessionSeedModal.tsx +++ b/ts/components/session/SessionSeedModal.tsx @@ -118,10 +118,7 @@ class SessionSeedModalInner extends React.Component { const bgColor = '#FFFFFF'; const fgColor = '#1B1B1B'; - const hexEncodedSeed = window.mnemonic.mn_decode( - this.state.recoveryPhrase, - 'english' - ); + const hexEncodedSeed = mn_decode(this.state.recoveryPhrase, 'english'); return ( <> diff --git a/ts/components/session/conversation/SessionCompositionBox.tsx b/ts/components/session/conversation/SessionCompositionBox.tsx index ae77ed217..9f352976e 100644 --- a/ts/components/session/conversation/SessionCompositionBox.tsx +++ b/ts/components/session/conversation/SessionCompositionBox.tsx @@ -30,6 +30,7 @@ import { DefaultTheme } from 'styled-components'; import { ConversationController } from '../../../session/conversations'; import { ConversationType } from '../../../state/ducks/conversations'; import { SessionMemberListItem } from '../SessionMemberListItem'; +import autoBind from 'auto-bind'; export interface ReplyingToMessageProps { convoId: string; @@ -142,41 +143,8 @@ export class SessionCompositionBox extends React.Component { // Emojis this.emojiPanel = null; + autoBind(this); this.toggleEmojiPanel = debounce(this.toggleEmojiPanel.bind(this), 100); - this.hideEmojiPanel = this.hideEmojiPanel.bind(this); - this.onEmojiClick = this.onEmojiClick.bind(this); - this.handleClick = this.handleClick.bind(this); - - this.renderRecordingView = this.renderRecordingView.bind(this); - this.renderCompositionView = this.renderCompositionView.bind(this); - this.renderTextArea = this.renderTextArea.bind(this); - this.renderQuotedMessage = this.renderQuotedMessage.bind(this); - - this.renderStagedLinkPreview = this.renderStagedLinkPreview.bind(this); - this.renderAttachmentsStaged = this.renderAttachmentsStaged.bind(this); - - // Recording view functions - this.sendVoiceMessage = this.sendVoiceMessage.bind(this); - this.onLoadVoiceNoteView = this.onLoadVoiceNoteView.bind(this); - this.onExitVoiceNoteView = this.onExitVoiceNoteView.bind(this); - - // Attachments - this.onChoseAttachment = this.onChoseAttachment.bind(this); - this.onChooseAttachment = this.onChooseAttachment.bind(this); - this.onClickAttachment = this.onClickAttachment.bind(this); - this.renderCaptionEditor = this.renderCaptionEditor.bind(this); - this.abortLinkPreviewFetch = this.abortLinkPreviewFetch.bind(this); - - // On Sending - this.onSendMessage = this.onSendMessage.bind(this); - - // Events - this.onKeyDown = this.onKeyDown.bind(this); - this.onKeyUp = this.onKeyUp.bind(this); - this.onChange = this.onChange.bind(this); - this.focusCompositionBox = this.focusCompositionBox.bind(this); - - this.fetchUsersForGroup = this.fetchUsersForGroup.bind(this); } public componentDidMount() { diff --git a/ts/components/session/conversation/SessionConversation.tsx b/ts/components/session/conversation/SessionConversation.tsx index 437cc7119..55fc17355 100644 --- a/ts/components/session/conversation/SessionConversation.tsx +++ b/ts/components/session/conversation/SessionConversation.tsx @@ -35,6 +35,7 @@ import { getMessageById, getPubkeysInPublicConversation, } from '../../../data/data'; +import autoBind from 'auto-bind'; interface State { // Message sending progress @@ -106,47 +107,7 @@ export class SessionConversation extends React.Component { this.messageContainerRef = React.createRef(); this.dragCounter = 0; - // Group settings panel - this.toggleRightPanel = this.toggleRightPanel.bind(this); - this.getRightPanelProps = this.getRightPanelProps.bind(this); - - // Recording view - this.onLoadVoiceNoteView = this.onLoadVoiceNoteView.bind(this); - this.onExitVoiceNoteView = this.onExitVoiceNoteView.bind(this); - - // Messages - this.loadInitialMessages = this.loadInitialMessages.bind(this); - this.selectMessage = this.selectMessage.bind(this); - this.resetSelection = this.resetSelection.bind(this); - this.updateSendingProgress = this.updateSendingProgress.bind(this); - this.resetSendingProgress = this.resetSendingProgress.bind(this); - this.onMessageSending = this.onMessageSending.bind(this); - this.onMessageSuccess = this.onMessageSuccess.bind(this); - this.onMessageFailure = this.onMessageFailure.bind(this); - this.deleteSelectedMessages = this.deleteSelectedMessages.bind(this); - - this.replyToMessage = this.replyToMessage.bind(this); - this.showMessageDetails = this.showMessageDetails.bind(this); - this.deleteMessage = this.deleteMessage.bind(this); - this.onClickAttachment = this.onClickAttachment.bind(this); - this.downloadAttachment = this.downloadAttachment.bind(this); - - // Keyboard navigation - this.onKeyDown = this.onKeyDown.bind(this); - - this.renderLightBox = this.renderLightBox.bind(this); - - // attachments - this.clearAttachments = this.clearAttachments.bind(this); - this.addAttachments = this.addAttachments.bind(this); - this.removeAttachment = this.removeAttachment.bind(this); - this.onChoseAttachments = this.onChoseAttachments.bind(this); - this.handleDragIn = this.handleDragIn.bind(this); - this.handleDragOut = this.handleDragOut.bind(this); - this.handleDrag = this.handleDrag.bind(this); - this.handleDrop = this.handleDrop.bind(this); - - this.updateMemberList = this.updateMemberList.bind(this); + autoBind(this); } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/ts/components/session/conversation/SessionMessagesList.tsx b/ts/components/session/conversation/SessionMessagesList.tsx index 60b366e8b..64d9ebab1 100644 --- a/ts/components/session/conversation/SessionMessagesList.tsx +++ b/ts/components/session/conversation/SessionMessagesList.tsx @@ -18,6 +18,7 @@ import { ConversationController } from '../../../session/conversations'; import { MessageModel } from '../../../models/message'; import { MessageRegularProps } from '../../../models/messageType'; import { getMessagesBySentAt } from '../../../data/data'; +import autoBind from 'auto-bind'; interface State { showScrollButton: boolean; @@ -59,13 +60,7 @@ export class SessionMessagesList extends React.Component { this.state = { showScrollButton: false, }; - this.renderMessage = this.renderMessage.bind(this); - this.handleScroll = this.handleScroll.bind(this); - this.scrollToUnread = this.scrollToUnread.bind(this); - this.scrollToBottom = this.scrollToBottom.bind(this); - this.scrollToQuoteMessage = this.scrollToQuoteMessage.bind(this); - this.getScrollOffsetBottomPx = this.getScrollOffsetBottomPx.bind(this); - this.displayUnreadBannerIndex = this.displayUnreadBannerIndex.bind(this); + autoBind(this); this.messageContainerRef = this.props.messageContainerRef; this.ignoreScrollEvents = true; diff --git a/ts/components/session/conversation/SessionRecording.tsx b/ts/components/session/conversation/SessionRecording.tsx index 065740865..61806d783 100644 --- a/ts/components/session/conversation/SessionRecording.tsx +++ b/ts/components/session/conversation/SessionRecording.tsx @@ -13,6 +13,7 @@ import { import { Constants } from '../../../session'; import { ToastUtils } from '../../../session/utils'; import { DefaultTheme, withTheme } from 'styled-components'; +import autoBind from 'auto-bind'; interface Props { onExitVoiceNoteView: any; @@ -64,33 +65,13 @@ class SessionRecordingInner extends React.Component { constructor(props: Props) { super(props); - // Mouse interaction - this.handleHoverActions = this.handleHoverActions.bind(this); - this.handleUnhoverActions = this.handleUnhoverActions.bind(this); - - // Component actions - this.playAudio = this.playAudio.bind(this); - this.pauseAudio = this.pauseAudio.bind(this); - this.stopRecording = this.stopRecording.bind(this); - - // Voice message actions - this.onSendVoiceMessage = this.onSendVoiceMessage.bind(this); - this.onDeleteVoiceMessage = this.onDeleteVoiceMessage.bind(this); - - // Stream monitors - this.timerUpdate = this.timerUpdate.bind(this); - this.onRecordingStream = this.onRecordingStream.bind(this); - this.stopRecordingStream = this.stopRecordingStream.bind(this); + autoBind(this); // Refs this.visualisationRef = React.createRef(); this.visualisationCanvas = React.createRef(); this.playbackCanvas = React.createRef(); - // Listeners - this.onKeyDown = this.onKeyDown.bind(this); - this.updateCanvasDimensions = this.updateCanvasDimensions.bind(this); - const now = getTimestamp(); const updateTimerInterval = global.setInterval(this.timerUpdate, 500); diff --git a/ts/models/message.ts b/ts/models/message.ts index 74bbfef42..8d6124762 100644 --- a/ts/models/message.ts +++ b/ts/models/message.ts @@ -669,8 +669,6 @@ export class MessageModel extends Backbone.Model { } public async getPropsForMessageDetail() { - const newIdentity = window.i18n('newIdentity'); - // We include numbers we didn't successfully send to so we can display errors. // Older messages don't have the recipients included on the message, so we fall // back to the conversation's current recipients