From 6fd8ea20c7ffe1d8cd9e21a7d8a01b77ad54f77d Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Thu, 5 Nov 2020 17:48:17 +1100 Subject: [PATCH] fix restoring of session after restore from seed --- js/models/conversations.d.ts | 2 +- js/models/conversations.js | 8 ------ js/signal_protocol_store.js | 3 +-- js/views/conversation_view.js | 4 --- libtextsecure/errors.js | 13 ++++++++++ libtextsecure/index.d.ts | 1 + .../conversation/SessionConversation.tsx | 2 +- ts/receiver/contentMessage.ts | 25 +++++++++++++++---- ts/receiver/dataMessage.ts | 8 +++--- ts/receiver/sessionHandling.ts | 4 ++- .../outgoing/content/SessionRequestMessage.ts | 12 ++++++++- 11 files changed, 56 insertions(+), 26 deletions(-) diff --git a/js/models/conversations.d.ts b/js/models/conversations.d.ts index 049731bff..746e5dd29 100644 --- a/js/models/conversations.d.ts +++ b/js/models/conversations.d.ts @@ -92,7 +92,7 @@ export interface ConversationModel makeQuote: any; unblock: any; deleteContact: any; - endSession: any; + endSession: () => Promise; block: any; copyPublicKey: any; getAvatar: any; diff --git a/js/models/conversations.js b/js/models/conversations.js index 77362074c..3acac23a8 100644 --- a/js/models/conversations.js +++ b/js/models/conversations.js @@ -1547,14 +1547,6 @@ }, async onSessionResetReceived() { await this.setSessionResetStatus(SessionResetEnum.request_received); - // send empty message, this will trigger the new session to propagate - // to the reset initiator. - const user = new libsession.Types.PubKey(this.id); - - const sessionEstablished = new window.libsession.Messages.Outgoing.SessionEstablishedMessage( - { timestamp: Date.now() } - ); - await libsession.getMessageQueue().send(user, sessionEstablished); }, isSessionResetReceived() { diff --git a/js/signal_protocol_store.js b/js/signal_protocol_store.js index 3b045afe5..8229525e4 100644 --- a/js/signal_protocol_store.js +++ b/js/signal_protocol_store.js @@ -232,8 +232,7 @@ }; } - window.log.error('Failed to fetch prekey:', keyId); - return undefined; + throw new textsecure.PreKeyMissing(); }, async loadPreKeyForContact(contactPubKey) { const key = await window.Signal.Data.getPreKeyByRecipient(contactPubKey); diff --git a/js/views/conversation_view.js b/js/views/conversation_view.js index 44eb16c08..13ccccc35 100644 --- a/js/views/conversation_view.js +++ b/js/views/conversation_view.js @@ -928,10 +928,6 @@ } }, - endSession() { - this.model.endSession(); - }, - setDisappearingMessages(seconds) { if (seconds > 0) { this.model.updateExpirationTimer(seconds); diff --git a/libtextsecure/errors.js b/libtextsecure/errors.js index 1b627c3ed..bb1e0e3a1 100644 --- a/libtextsecure/errors.js +++ b/libtextsecure/errors.js @@ -226,6 +226,18 @@ } } + function PreKeyMissing() { + this.name = 'PreKeyMissing'; + + Error.call(this, this.name); + + // Maintains proper stack trace, where our error was thrown (only available on V8) + // via https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error + if (Error.captureStackTrace) { + Error.captureStackTrace(this); + } + } + window.textsecure.SendMessageNetworkError = SendMessageNetworkError; window.textsecure.IncomingIdentityKeyError = IncomingIdentityKeyError; window.textsecure.OutgoingIdentityKeyError = OutgoingIdentityKeyError; @@ -243,4 +255,5 @@ window.textsecure.PublicChatError = PublicChatError; window.textsecure.PublicTokenError = PublicTokenError; window.textsecure.SenderKeyMissing = SenderKeyMissing; + window.textsecure.PreKeyMissing = PreKeyMissing; })(); diff --git a/libtextsecure/index.d.ts b/libtextsecure/index.d.ts index 191adf711..ad65c1594 100644 --- a/libtextsecure/index.d.ts +++ b/libtextsecure/index.d.ts @@ -21,5 +21,6 @@ export interface LibTextsecure { PublicChatError: any; PublicTokenError: any; SenderKeyMissing: any; + PreKeyMissing: any; createTaskWithTimeout(task: any, id: any, options?: any): Promise; } diff --git a/ts/components/session/conversation/SessionConversation.tsx b/ts/components/session/conversation/SessionConversation.tsx index 543ae159c..5abed9c8c 100644 --- a/ts/components/session/conversation/SessionConversation.tsx +++ b/ts/components/session/conversation/SessionConversation.tsx @@ -447,7 +447,7 @@ export class SessionConversation extends React.Component { }, onDeleteContact: () => conversation.deleteContact(), onResetSession: () => { - conversation.endSession(); + void conversation.endSession(); }, onShowSafetyNumber: () => { diff --git a/ts/receiver/contentMessage.ts b/ts/receiver/contentMessage.ts index c8757ae0b..2cea00c9b 100644 --- a/ts/receiver/contentMessage.ts +++ b/ts/receiver/contentMessage.ts @@ -6,7 +6,10 @@ import { removeFromCache, updateCache } from './cache'; import { SignalService } from '../protobuf'; import * as Lodash from 'lodash'; import * as libsession from '../session'; -import { handleSessionRequestMessage } from './sessionHandling'; +import { + handleEndSession, + handleSessionRequestMessage, +} from './sessionHandling'; import { handlePairingAuthorisationMessage } from './multidevice'; import { MediumGroupRequestKeysMessage } from '../session/messages/outgoing'; import { MultiDeviceProtocol, SessionProtocol } from '../session/protocols'; @@ -169,16 +172,16 @@ async function decryptUnidentifiedSender( window.log.info( 'Dropping blocked message with error after sealed sender decryption' ); + await removeFromCache(envelope); return null; } - // eslint-disable-next-line no-param-reassign + // eslint-disable no-param-reassign envelope.source = source.getName(); - // eslint-disable-next-line no-param-reassign envelope.sourceDevice = source.getDeviceId(); - // eslint-disable-next-line no-param-reassign envelope.unidentifiedDeliveryReceived = !originalSource; - + // eslint-enable no-param-reassign + await removeFromCache(envelope); throw error; } @@ -257,6 +260,7 @@ async function doDecrypt( } } +// tslint:disable-next-line: max-func-body-length async function decrypt( envelope: EnvelopePlus, ciphertext: ArrayBuffer @@ -309,6 +313,17 @@ async function decrypt( return; } + } else if (error instanceof window.textsecure.PreKeyMissing) { + const convo = window.ConversationController.get(envelope.source); + if (!convo) { + window.console.warn( + 'PreKeyMissing but convo is missing too. Dropping...' + ); + return; + } + void convo.endSession(); + + return; } let errorToThrow = error; diff --git a/ts/receiver/dataMessage.ts b/ts/receiver/dataMessage.ts index 6e29ed11d..37ade63e9 100644 --- a/ts/receiver/dataMessage.ts +++ b/ts/receiver/dataMessage.ts @@ -286,14 +286,16 @@ export async function handleDataMessage( return; } - // tslint:disable-next-line no-bitwise + // tslint:disable no-bitwise if ( dataMessage.flags && dataMessage.flags & SignalService.DataMessage.Flags.END_SESSION ) { await handleEndSession(envelope.source); - return; + return removeFromCache(envelope); } + // tslint:enable no-bitwise + const message = await processDecrypted(envelope, dataMessage); const ourPubKey = window.textsecure.storage.user.getNumber(); const senderPubKey = envelope.senderIdentity || envelope.source; @@ -302,7 +304,7 @@ export async function handleDataMessage( const { UNPAIRING_REQUEST } = SignalService.DataMessage.Flags; - // eslint-disable-next-line no-bitwise + // tslint:disable-next-line: no-bitwise const isUnpairingRequest = Boolean(message.flags & UNPAIRING_REQUEST); if (isUnpairingRequest) { diff --git a/ts/receiver/sessionHandling.ts b/ts/receiver/sessionHandling.ts index 1dd04c571..11350972f 100644 --- a/ts/receiver/sessionHandling.ts +++ b/ts/receiver/sessionHandling.ts @@ -14,6 +14,8 @@ export async function handleEndSession(number: string): Promise { try { const conversation = ConversationController.get(number); if (conversation) { + // this just marks the conversation as being waiting for a new session + // it does trigger a message to be sent. (the message is sent from handleSessionRequestMessage()) await conversation.onSessionResetReceived(); } else { throw new Error(); @@ -27,7 +29,7 @@ export async function handleSessionRequestMessage( envelope: EnvelopePlus, preKeyBundleMessage: SignalService.IPreKeyBundleMessage ) { - const { libsignal, libloki, StringView, textsecure, dcodeIO, log } = window; + const { libsignal, StringView, textsecure, dcodeIO, log } = window; window.console.log( `Received SESSION_REQUEST from source: ${envelope.source}` diff --git a/ts/session/messages/outgoing/content/SessionRequestMessage.ts b/ts/session/messages/outgoing/content/SessionRequestMessage.ts index 57d2c9007..575fd323d 100644 --- a/ts/session/messages/outgoing/content/SessionRequestMessage.ts +++ b/ts/session/messages/outgoing/content/SessionRequestMessage.ts @@ -2,6 +2,7 @@ import { ContentMessage } from './ContentMessage'; import { SignalService } from '../../../../protobuf'; import { MessageParams } from '../Message'; import { Constants } from '../../..'; +import * as crypto from 'crypto'; export interface PreKeyBundleType { identityKey: Uint8Array; @@ -19,10 +20,19 @@ interface SessionRequestParams extends MessageParams { export class SessionRequestMessage extends ContentMessage { private readonly preKeyBundle: PreKeyBundleType; + private readonly padding: Buffer; constructor(params: SessionRequestParams) { super({ timestamp: params.timestamp, identifier: params.identifier }); this.preKeyBundle = params.preKeyBundle; + // Generate a random int from 1 and 512 + const buffer = crypto.randomBytes(1); + + // tslint:disable-next-line: no-bitwise + const paddingLength = (new Uint8Array(buffer)[0] & 0x1ff) + 1; + + // Generate a random padding buffer of the chosen size + this.padding = crypto.randomBytes(paddingLength); } public ttl(): number { @@ -36,7 +46,7 @@ export class SessionRequestMessage extends ContentMessage { protected contentProto(): SignalService.Content { const nullMessage = new SignalService.NullMessage({}); const preKeyBundleMessage = this.getPreKeyBundleMessage(); - + nullMessage.padding = this.padding; return new SignalService.Content({ nullMessage, preKeyBundleMessage,