diff --git a/js/modules/metadata/CiphertextMessage.js b/js/modules/metadata/CiphertextMessage.js deleted file mode 100644 index d38bdd077..000000000 --- a/js/modules/metadata/CiphertextMessage.js +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2018-2020 Signal Messenger, LLC -// SPDX-License-Identifier: AGPL-3.0-only - -module.exports = { - CURRENT_VERSION: 3, - - // This matches Envelope.Type.CIPHERTEXT - WHISPER_TYPE: 1, - // This matches Envelope.Type.PREKEY_BUNDLE - PREKEY_TYPE: 3, - - SENDERKEY_TYPE: 4, - SENDERKEY_DISTRIBUTION_TYPE: 5, - - ENCRYPTED_MESSAGE_OVERHEAD: 53, -}; diff --git a/js/modules/signal.js b/js/modules/signal.js index 1daad5214..ddc13530a 100644 --- a/js/modules/signal.js +++ b/js/modules/signal.js @@ -21,7 +21,6 @@ const Stickers = require('./stickers'); const Settings = require('./settings'); const RemoteConfig = require('../../ts/RemoteConfig'); const Util = require('../../ts/util'); -const Metadata = require('./metadata/SecretSessionCipher'); const RefreshSenderCertificate = require('./refresh_sender_certificate'); const LinkPreviews = require('./link_previews'); const AttachmentDownloads = require('./attachment_downloads'); @@ -428,7 +427,6 @@ exports.setup = (options = {}) => { GroupChange, IndexedDB, LinkPreviews, - Metadata, Migrations, Notifications, OS, diff --git a/test/index.html b/test/index.html index f79d1d116..7c179b89d 100644 --- a/test/index.html +++ b/test/index.html @@ -360,8 +360,6 @@ - - diff --git a/ts/libsignal.d.ts b/ts/libsignal.d.ts index a9f581b99..09894b9a4 100644 --- a/ts/libsignal.d.ts +++ b/ts/libsignal.d.ts @@ -223,6 +223,7 @@ export declare class SessionCipherClass { body: string; }>; getRecord: () => Promise; + getSessionVersion: () => Promise; getRemoteRegistrationId: () => Promise; hasOpenSession: () => Promise; } diff --git a/ts/metadata/CiphertextMessage.ts b/ts/metadata/CiphertextMessage.ts new file mode 100644 index 000000000..9f7e84b22 --- /dev/null +++ b/ts/metadata/CiphertextMessage.ts @@ -0,0 +1,14 @@ +// Copyright 2018-2020 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +export const CURRENT_VERSION = 3; + +// This matches Envelope.Type.CIPHERTEXT +export const WHISPER_TYPE = 1; +// This matches Envelope.Type.PREKEY_BUNDLE +export const PREKEY_TYPE = 3; + +export const SENDERKEY_TYPE = 4; +export const SENDERKEY_DISTRIBUTION_TYPE = 5; + +export const ENCRYPTED_MESSAGE_OVERHEAD = 53; diff --git a/js/modules/metadata/SecretSessionCipher.js b/ts/metadata/SecretSessionCipher.ts similarity index 55% rename from js/modules/metadata/SecretSessionCipher.js rename to ts/metadata/SecretSessionCipher.ts index b4c468475..a092d0e5b 100644 --- a/js/modules/metadata/SecretSessionCipher.js +++ b/ts/metadata/SecretSessionCipher.ts @@ -1,12 +1,10 @@ -// Copyright 2018-2020 Signal Messenger, LLC +// Copyright 2018-2021 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -/* global libsignal, textsecure */ +/* eslint-disable class-methods-use-this */ -/* eslint-disable no-bitwise */ - -const CiphertextMessage = require('./CiphertextMessage'); -const { +import * as CiphertextMessage from './CiphertextMessage'; +import { bytesFromString, concatenateBytes, constantTimeEqual, @@ -20,44 +18,111 @@ const { intsToByteHighAndLow, splitBytes, trimBytes, -} = require('../../../ts/Crypto'); +} from '../Crypto'; -const REVOKED_CERTIFICATES = []; - -function SecretSessionCipher(storage, options) { - this.storage = storage; - - // We do this on construction because libsignal won't be available when this file loads - const { SessionCipher } = libsignal; - this.SessionCipher = SessionCipher; - - this.options = options || {}; -} +import { SignalProtocolAddressClass } from '../libsignal.d'; +const REVOKED_CERTIFICATES: Array = []; const CIPHERTEXT_VERSION = 1; const UNIDENTIFIED_DELIVERY_PREFIX = 'UnidentifiedDelivery'; +type MeType = { + number?: string; + uuid?: string; + deviceId: number; +}; + +type ValidatorType = { + validate( + certificate: SenderCertificateType, + validationTime: number + ): Promise; +}; + +export type SerializedCertificateType = { + serialized: ArrayBuffer; +}; + +type ServerCertificateType = { + id: number; + key: ArrayBuffer; +}; + +type ServerCertificateWrapperType = { + certificate: ArrayBuffer; + signature: ArrayBuffer; +}; + +type SenderCertificateType = { + sender?: string; + senderUuid?: string; + senderDevice: number; + expires: number; + identityKey: ArrayBuffer; + signer: ServerCertificateType; +}; + +type SenderCertificateWrapperType = { + certificate: ArrayBuffer; + signature: ArrayBuffer; +}; + +type MessageType = { + ephemeralPublic: ArrayBuffer; + encryptedStatic: ArrayBuffer; + encryptedMessage: ArrayBuffer; +}; + +type InnerMessageType = { + type: number; + senderCertificate: SenderCertificateWrapperType; + content: ArrayBuffer; +}; + +export type ExplodedServerCertificateType = ServerCertificateType & + ServerCertificateWrapperType & + SerializedCertificateType; + +export type ExplodedSenderCertificateType = SenderCertificateType & + SenderCertificateWrapperType & + SerializedCertificateType & { + signer: ExplodedServerCertificateType; + }; + +type ExplodedMessageType = MessageType & + SerializedCertificateType & { version: number }; + +type ExplodedInnerMessageType = InnerMessageType & + SerializedCertificateType & { + senderCertificate: ExplodedSenderCertificateType; + }; + // public CertificateValidator(ECPublicKey trustRoot) -function createCertificateValidator(trustRoot) { +export function createCertificateValidator( + trustRoot: ArrayBuffer +): ValidatorType { return { // public void validate(SenderCertificate certificate, long validationTime) - async validate(certificate, validationTime) { + async validate( + certificate: ExplodedSenderCertificateType, + validationTime: number + ): Promise { const serverCertificate = certificate.signer; - await libsignal.Curve.async.verifySignature( + await window.libsignal.Curve.async.verifySignature( trustRoot, serverCertificate.certificate, serverCertificate.signature ); - const serverCertId = serverCertificate.certificate.id; + const serverCertId = serverCertificate.id; if (REVOKED_CERTIFICATES.includes(serverCertId)) { throw new Error( `Server certificate id ${serverCertId} has been revoked` ); } - await libsignal.Curve.async.verifySignature( + await window.libsignal.Curve.async.verifySignature( serverCertificate.key, certificate.certificate, certificate.signature @@ -70,24 +135,28 @@ function createCertificateValidator(trustRoot) { }; } -function _decodePoint(serialized, offset = 0) { +function _decodePoint(serialized: ArrayBuffer, offset = 0): ArrayBuffer { const view = offset > 0 ? getViewOfArrayBuffer(serialized, offset, serialized.byteLength) : serialized; - return libsignal.Curve.validatePubKeyFormat(view); + return window.libsignal.Curve.validatePubKeyFormat(view); } // public ServerCertificate(byte[] serialized) -function _createServerCertificateFromBuffer(serialized) { - const wrapper = textsecure.protobuf.ServerCertificate.decode(serialized); +export function _createServerCertificateFromBuffer( + serialized: ArrayBuffer +): ExplodedServerCertificateType { + const wrapper = window.textsecure.protobuf.ServerCertificate.decode( + serialized + ); if (!wrapper.certificate || !wrapper.signature) { throw new Error('Missing fields'); } - const certificate = textsecure.protobuf.ServerCertificate.Certificate.decode( + const certificate = window.textsecure.protobuf.ServerCertificate.Certificate.decode( wrapper.certificate.toArrayBuffer() ); @@ -106,54 +175,70 @@ function _createServerCertificateFromBuffer(serialized) { } // public SenderCertificate(byte[] serialized) -function _createSenderCertificateFromBuffer(serialized) { - const wrapper = textsecure.protobuf.SenderCertificate.decode(serialized); +export function _createSenderCertificateFromBuffer( + serialized: ArrayBuffer +): ExplodedSenderCertificateType { + const wrapper = window.textsecure.protobuf.SenderCertificate.decode( + serialized + ); - if (!wrapper.signature || !wrapper.certificate) { + const { signature, certificate } = wrapper; + + if (!signature || !certificate) { throw new Error('Missing fields'); } - const certificate = textsecure.protobuf.SenderCertificate.Certificate.decode( + const senderCertificate = window.textsecure.protobuf.SenderCertificate.Certificate.decode( wrapper.certificate.toArrayBuffer() ); + const { + signer, + identityKey, + senderDevice, + expires, + sender, + senderUuid, + } = senderCertificate; + if ( - !certificate.signer || - !certificate.identityKey || - !certificate.senderDevice || - !certificate.expires || - !(certificate.sender || certificate.senderUuid) + !signer || + !identityKey || + !senderDevice || + !expires || + !(sender || senderUuid) ) { throw new Error('Missing fields'); } return { - sender: certificate.sender, - senderUuid: certificate.senderUuid, - senderDevice: certificate.senderDevice, - expires: certificate.expires.toNumber(), - identityKey: certificate.identityKey.toArrayBuffer(), - signer: _createServerCertificateFromBuffer( - certificate.signer.toArrayBuffer() - ), + sender, + senderUuid, + senderDevice, + expires: expires.toNumber(), + identityKey: identityKey.toArrayBuffer(), + signer: _createServerCertificateFromBuffer(signer.toArrayBuffer()), - certificate: wrapper.certificate.toArrayBuffer(), - signature: wrapper.signature.toArrayBuffer(), + certificate: certificate.toArrayBuffer(), + signature: signature.toArrayBuffer(), serialized, }; } // public UnidentifiedSenderMessage(byte[] serialized) -function _createUnidentifiedSenderMessageFromBuffer(serialized) { - const version = highBitsToInt(serialized[0]); +function _createUnidentifiedSenderMessageFromBuffer( + serialized: ArrayBuffer +): ExplodedMessageType { + const uintArray = new Uint8Array(serialized); + const version = highBitsToInt(uintArray[0]); if (version > CIPHERTEXT_VERSION) { - throw new Error(`Unknown version: ${this.version}`); + throw new Error(`Unknown version: ${version}`); } const view = getViewOfArrayBuffer(serialized, 1, serialized.byteLength); - const unidentifiedSenderMessage = textsecure.protobuf.UnidentifiedSenderMessage.decode( + const unidentifiedSenderMessage = window.textsecure.protobuf.UnidentifiedSenderMessage.decode( view ); @@ -179,20 +264,20 @@ function _createUnidentifiedSenderMessageFromBuffer(serialized) { // public UnidentifiedSenderMessage( // ECPublicKey ephemeral, byte[] encryptedStatic, byte[] encryptedMessage) { function _createUnidentifiedSenderMessage( - ephemeralPublic, - encryptedStatic, - encryptedMessage -) { + ephemeralPublic: ArrayBuffer, + encryptedStatic: ArrayBuffer, + encryptedMessage: ArrayBuffer +): ExplodedMessageType { const versionBytes = new Uint8Array([ intsToByteHighAndLow(CIPHERTEXT_VERSION, CIPHERTEXT_VERSION), ]); - const unidentifiedSenderMessage = new textsecure.protobuf.UnidentifiedSenderMessage(); + const unidentifiedSenderMessage = new window.textsecure.protobuf.UnidentifiedSenderMessage(); unidentifiedSenderMessage.encryptedMessage = encryptedMessage; unidentifiedSenderMessage.encryptedStatic = encryptedStatic; unidentifiedSenderMessage.ephemeralPublic = ephemeralPublic; - const messageBytes = unidentifiedSenderMessage.encode().toArrayBuffer(); + const messageBytes = unidentifiedSenderMessage.toArrayBuffer(); return { version: CIPHERTEXT_VERSION, @@ -206,10 +291,13 @@ function _createUnidentifiedSenderMessage( } // public UnidentifiedSenderMessageContent(byte[] serialized) -function _createUnidentifiedSenderMessageContentFromBuffer(serialized) { - const TypeEnum = textsecure.protobuf.UnidentifiedSenderMessage.Message.Type; +function _createUnidentifiedSenderMessageContentFromBuffer( + serialized: ArrayBuffer +): ExplodedInnerMessageType { + const TypeEnum = + window.textsecure.protobuf.UnidentifiedSenderMessage.Message.Type; - const message = textsecure.protobuf.UnidentifiedSenderMessage.Message.decode( + const message = window.textsecure.protobuf.UnidentifiedSenderMessage.Message.decode( serialized ); @@ -241,8 +329,9 @@ function _createUnidentifiedSenderMessageContentFromBuffer(serialized) { } // private int getProtoType(int type) -function _getProtoMessageType(type) { - const TypeEnum = textsecure.protobuf.UnidentifiedSenderMessage.Message.Type; +function _getProtoMessageType(type: number): number { + const TypeEnum = + window.textsecure.protobuf.UnidentifiedSenderMessage.Message.Type; switch (type) { case CiphertextMessage.WHISPER_TYPE: @@ -257,39 +346,53 @@ function _getProtoMessageType(type) { // public UnidentifiedSenderMessageContent( // int type, SenderCertificate senderCertificate, byte[] content) function _createUnidentifiedSenderMessageContent( - type, - senderCertificate, - content -) { - const innerMessage = new textsecure.protobuf.UnidentifiedSenderMessage.Message(); + type: number, + senderCertificate: SerializedCertificateType, + content: ArrayBuffer +): ArrayBuffer { + const innerMessage = new window.textsecure.protobuf.UnidentifiedSenderMessage.Message(); innerMessage.type = _getProtoMessageType(type); - innerMessage.senderCertificate = textsecure.protobuf.SenderCertificate.decode( + innerMessage.senderCertificate = window.textsecure.protobuf.SenderCertificate.decode( senderCertificate.serialized ); innerMessage.content = content; - return { - type, - senderCertificate, - content, - - serialized: innerMessage.encode().toArrayBuffer(), - }; + return innerMessage.toArrayBuffer(); } -SecretSessionCipher.prototype = { +export class SecretSessionCipher { + storage: typeof window.textsecure.storage.protocol; + + options: { messageKeysLimit?: number | boolean }; + + SessionCipher: typeof window.libsignal.SessionCipher; + + constructor( + storage: typeof window.textsecure.storage.protocol, + options?: { messageKeysLimit?: number | boolean } + ) { + this.storage = storage; + + // Do this on construction because libsignal won't be available when this file loads + const { SessionCipher } = window.libsignal; + this.SessionCipher = SessionCipher; + + this.options = options || {}; + } + // public byte[] encrypt( // SignalProtocolAddress destinationAddress, // SenderCertificate senderCertificate, // byte[] paddedPlaintext // ) - async encrypt(destinationAddress, senderCertificate, paddedPlaintext) { + async encrypt( + destinationAddress: SignalProtocolAddressClass, + senderCertificate: SerializedCertificateType, + paddedPlaintext: ArrayBuffer + ): Promise { // Capture this.xxx variables to replicate Java's implicit this syntax const { SessionCipher } = this; const signalProtocolStore = this.storage; - const _calculateEphemeralKeys = this._calculateEphemeralKeys.bind(this); - const _encryptWithSecretKeys = this._encryptWithSecretKeys.bind(this); - const _calculateStaticKeys = this._calculateStaticKeys.bind(this); const sessionCipher = new SessionCipher( signalProtocolStore, @@ -299,22 +402,31 @@ SecretSessionCipher.prototype = { const message = await sessionCipher.encrypt(paddedPlaintext); const ourIdentity = await signalProtocolStore.getIdentityKeyPair(); - const theirIdentity = fromEncodedBinaryToArrayBuffer( - await signalProtocolStore.loadIdentityKey(destinationAddress.getName()) + const theirIdentityData = await signalProtocolStore.loadIdentityKey( + destinationAddress.getName() ); + if (!theirIdentityData) { + throw new Error( + 'SecretSessionCipher.encrypt: No identity data for recipient!' + ); + } + const theirIdentity = + typeof theirIdentityData === 'string' + ? fromEncodedBinaryToArrayBuffer(theirIdentityData) + : theirIdentityData; - const ephemeral = await libsignal.Curve.async.generateKeyPair(); + const ephemeral = await window.libsignal.Curve.async.generateKeyPair(); const ephemeralSalt = concatenateBytes( bytesFromString(UNIDENTIFIED_DELIVERY_PREFIX), theirIdentity, ephemeral.pubKey ); - const ephemeralKeys = await _calculateEphemeralKeys( + const ephemeralKeys = await this._calculateEphemeralKeys( theirIdentity, ephemeral.privKey, ephemeralSalt ); - const staticKeyCiphertext = await _encryptWithSecretKeys( + const staticKeyCiphertext = await this._encryptWithSecretKeys( ephemeralKeys.cipherKey, ephemeralKeys.macKey, ourIdentity.pubKey @@ -324,20 +436,20 @@ SecretSessionCipher.prototype = { ephemeralKeys.chainKey, staticKeyCiphertext ); - const staticKeys = await _calculateStaticKeys( + const staticKeys = await this._calculateStaticKeys( theirIdentity, ourIdentity.privKey, staticSalt ); - const content = _createUnidentifiedSenderMessageContent( + const serializedMessage = _createUnidentifiedSenderMessageContent( message.type, senderCertificate, fromEncodedBinaryToArrayBuffer(message.body) ); - const messageBytes = await _encryptWithSecretKeys( + const messageBytes = await this._encryptWithSecretKeys( staticKeys.cipherKey, staticKeys.macKey, - content.serialized + serializedMessage ); const unidentifiedSenderMessage = _createUnidentifiedSenderMessage( @@ -347,20 +459,22 @@ SecretSessionCipher.prototype = { ); return unidentifiedSenderMessage.serialized; - }, + } // public Pair decrypt( // CertificateValidator validator, byte[] ciphertext, long timestamp) - async decrypt(validator, ciphertext, timestamp, me = {}) { - // Capture this.xxx variables to replicate Java's implicit this syntax + async decrypt( + validator: ValidatorType, + ciphertext: ArrayBuffer, + timestamp: number, + me?: MeType + ): Promise<{ + isMe?: boolean; + sender?: SignalProtocolAddressClass; + senderUuid?: SignalProtocolAddressClass; + content?: ArrayBuffer; + }> { const signalProtocolStore = this.storage; - const _calculateEphemeralKeys = this._calculateEphemeralKeys.bind(this); - const _calculateStaticKeys = this._calculateStaticKeys.bind(this); - const _decryptWithUnidentifiedSenderMessage = this._decryptWithUnidentifiedSenderMessage.bind( - this - ); - const _decryptWithSecretKeys = this._decryptWithSecretKeys.bind(this); - const ourIdentity = await signalProtocolStore.getIdentityKeyPair(); const wrapper = _createUnidentifiedSenderMessageFromBuffer(ciphertext); const ephemeralSalt = concatenateBytes( @@ -368,12 +482,12 @@ SecretSessionCipher.prototype = { ourIdentity.pubKey, wrapper.ephemeralPublic ); - const ephemeralKeys = await _calculateEphemeralKeys( + const ephemeralKeys = await this._calculateEphemeralKeys( wrapper.ephemeralPublic, ourIdentity.privKey, ephemeralSalt ); - const staticKeyBytes = await _decryptWithSecretKeys( + const staticKeyBytes = await this._decryptWithSecretKeys( ephemeralKeys.cipherKey, ephemeralKeys.macKey, wrapper.encryptedStatic @@ -384,12 +498,12 @@ SecretSessionCipher.prototype = { ephemeralKeys.chainKey, wrapper.encryptedStatic ); - const staticKeys = await _calculateStaticKeys( + const staticKeys = await this._calculateStaticKeys( staticKey, ourIdentity.privKey, staticSalt ); - const messageBytes = await _decryptWithSecretKeys( + const messageBytes = await this._decryptWithSecretKeys( staticKeys.cipherKey, staticKeys.macKey, wrapper.encryptedMessage @@ -410,6 +524,7 @@ SecretSessionCipher.prototype = { const { sender, senderUuid, senderDevice } = content.senderCertificate; if ( + me && ((sender && me.number && sender === me.number) || (senderUuid && me.uuid && senderUuid === me.uuid)) && senderDevice === me.deviceId @@ -418,20 +533,21 @@ SecretSessionCipher.prototype = { isMe: true, }; } - const addressE164 = - sender && new libsignal.SignalProtocolAddress(sender, senderDevice); - const addressUuid = - senderUuid && - new libsignal.SignalProtocolAddress( - senderUuid.toLowerCase(), - senderDevice - ); + const addressE164 = sender + ? new window.libsignal.SignalProtocolAddress(sender, senderDevice) + : undefined; + const addressUuid = senderUuid + ? new window.libsignal.SignalProtocolAddress( + senderUuid.toLowerCase(), + senderDevice + ) + : undefined; try { return { sender: addressE164, senderUuid: addressUuid, - content: await _decryptWithUnidentifiedSenderMessage(content), + content: await this._decryptWithUnidentifiedSenderMessage(content), }; } catch (error) { if (!error) { @@ -444,10 +560,12 @@ SecretSessionCipher.prototype = { throw error; } - }, + } // public int getSessionVersion(SignalProtocolAddress remoteAddress) { - getSessionVersion(remoteAddress) { + getSessionVersion( + remoteAddress: SignalProtocolAddressClass + ): Promise { const { SessionCipher } = this; const signalProtocolStore = this.storage; @@ -458,10 +576,12 @@ SecretSessionCipher.prototype = { ); return cipher.getSessionVersion(); - }, + } // public int getRemoteRegistrationId(SignalProtocolAddress remoteAddress) { - getRemoteRegistrationId(remoteAddress) { + getRemoteRegistrationId( + remoteAddress: SignalProtocolAddressClass + ): Promise { const { SessionCipher } = this; const signalProtocolStore = this.storage; @@ -472,10 +592,12 @@ SecretSessionCipher.prototype = { ); return cipher.getRemoteRegistrationId(); - }, + } // Used by outgoing_message.js - closeOpenSessionForDevice(remoteAddress) { + closeOpenSessionForDevice( + remoteAddress: SignalProtocolAddressClass + ): Promise { const { SessionCipher } = this; const signalProtocolStore = this.storage; @@ -486,19 +608,27 @@ SecretSessionCipher.prototype = { ); return cipher.closeOpenSessionForDevice(); - }, + } // private EphemeralKeys calculateEphemeralKeys( // ECPublicKey ephemeralPublic, ECPrivateKey ephemeralPrivate, byte[] salt) - async _calculateEphemeralKeys(ephemeralPublic, ephemeralPrivate, salt) { - const ephemeralSecret = await libsignal.Curve.async.calculateAgreement( + private async _calculateEphemeralKeys( + ephemeralPublic: ArrayBuffer, + ephemeralPrivate: ArrayBuffer, + salt: ArrayBuffer + ): Promise<{ + chainKey: ArrayBuffer; + cipherKey: ArrayBuffer; + macKey: ArrayBuffer; + }> { + const ephemeralSecret = await window.libsignal.Curve.async.calculateAgreement( ephemeralPublic, ephemeralPrivate ); - const ephemeralDerivedParts = await libsignal.HKDF.deriveSecrets( + const ephemeralDerivedParts = await window.libsignal.HKDF.deriveSecrets( ephemeralSecret, salt, - new ArrayBuffer() + new ArrayBuffer(0) ); // private EphemeralKeys(byte[] chainKey, byte[] cipherKey, byte[] macKey) @@ -507,19 +637,23 @@ SecretSessionCipher.prototype = { cipherKey: ephemeralDerivedParts[1], macKey: ephemeralDerivedParts[2], }; - }, + } // private StaticKeys calculateStaticKeys( // ECPublicKey staticPublic, ECPrivateKey staticPrivate, byte[] salt) - async _calculateStaticKeys(staticPublic, staticPrivate, salt) { - const staticSecret = await libsignal.Curve.async.calculateAgreement( + private async _calculateStaticKeys( + staticPublic: ArrayBuffer, + staticPrivate: ArrayBuffer, + salt: ArrayBuffer + ): Promise<{ cipherKey: ArrayBuffer; macKey: ArrayBuffer }> { + const staticSecret = await window.libsignal.Curve.async.calculateAgreement( staticPublic, staticPrivate ); - const staticDerivedParts = await libsignal.HKDF.deriveSecrets( + const staticDerivedParts = await window.libsignal.HKDF.deriveSecrets( staticSecret, salt, - new ArrayBuffer() + new ArrayBuffer(0) ); // private StaticKeys(byte[] cipherKey, byte[] macKey) @@ -527,39 +661,59 @@ SecretSessionCipher.prototype = { cipherKey: staticDerivedParts[1], macKey: staticDerivedParts[2], }; - }, + } // private byte[] decrypt(UnidentifiedSenderMessageContent message) - _decryptWithUnidentifiedSenderMessage(message) { + private _decryptWithUnidentifiedSenderMessage( + message: ExplodedInnerMessageType + ): Promise { const { SessionCipher } = this; const signalProtocolStore = this.storage; - const sender = new libsignal.SignalProtocolAddress( - message.senderCertificate.senderUuid || message.senderCertificate.sender, - message.senderCertificate.senderDevice + if (!message.senderCertificate) { + throw new Error( + '_decryptWithUnidentifiedSenderMessage: Message had no senderCertificate' + ); + } + + const { senderUuid, sender, senderDevice } = message.senderCertificate; + const target = senderUuid || sender; + if (!senderDevice || !target) { + throw new Error( + '_decryptWithUnidentifiedSenderMessage: Missing sender information in senderCertificate' + ); + } + + const address = new window.libsignal.SignalProtocolAddress( + target, + senderDevice ); switch (message.type) { case CiphertextMessage.WHISPER_TYPE: return new SessionCipher( signalProtocolStore, - sender, + address, this.options ).decryptWhisperMessage(message.content); case CiphertextMessage.PREKEY_TYPE: return new SessionCipher( signalProtocolStore, - sender, + address, this.options ).decryptPreKeyWhisperMessage(message.content); default: throw new Error(`Unknown type: ${message.type}`); } - }, + } // private byte[] encrypt( // SecretKeySpec cipherKey, SecretKeySpec macKey, byte[] plaintext) - async _encryptWithSecretKeys(cipherKey, macKey, plaintext) { + private async _encryptWithSecretKeys( + cipherKey: ArrayBuffer, + macKey: ArrayBuffer, + plaintext: ArrayBuffer + ): Promise { // Cipher const cipher = Cipher.getInstance('AES/CTR/NoPadding'); // cipher.init(Cipher.ENCRYPT_MODE, cipherKey, new IvParameterSpec(new byte[16])); @@ -574,11 +728,15 @@ SecretSessionCipher.prototype = { const ourMac = trimBytes(ourFullMac, 10); return concatenateBytes(ciphertext, ourMac); - }, + } // private byte[] decrypt( // SecretKeySpec cipherKey, SecretKeySpec macKey, byte[] ciphertext) - async _decryptWithSecretKeys(cipherKey, macKey, ciphertext) { + private async _decryptWithSecretKeys( + cipherKey: ArrayBuffer, + macKey: ArrayBuffer, + ciphertext: ArrayBuffer + ): Promise { if (ciphertext.byteLength < 10) { throw new Error('Ciphertext not long enough for MAC!'); } @@ -598,7 +756,7 @@ SecretSessionCipher.prototype = { const theirMac = ciphertextParts[1]; if (!constantTimeEqual(ourMac, theirMac)) { - throw new Error('Bad mac!'); + throw new Error('SecretSessionCipher/_decryptWithSecretKeys: Bad MAC!'); } // Cipher const cipher = Cipher.getInstance('AES/CTR/NoPadding'); @@ -606,12 +764,5 @@ SecretSessionCipher.prototype = { // return cipher.doFinal(ciphertextParts[0]); return decryptAesCtr(cipherKey, ciphertextParts[0], getZeroes(16)); - }, -}; - -module.exports = { - SecretSessionCipher, - createCertificateValidator, - _createServerCertificateFromBuffer, - _createSenderCertificateFromBuffer, -}; + } +} diff --git a/test/metadata/SecretSessionCipher_test.js b/ts/test-electron/metadata/SecretSessionCipher_test.ts similarity index 65% rename from test/metadata/SecretSessionCipher_test.js rename to ts/test-electron/metadata/SecretSessionCipher_test.ts index 58b62b8f2..a107082d2 100644 --- a/test/metadata/SecretSessionCipher_test.js +++ b/ts/test-electron/metadata/SecretSessionCipher_test.ts @@ -1,46 +1,48 @@ -// Copyright 2018-2020 Signal Messenger, LLC +// Copyright 2018-2021 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -/* global libsignal, textsecure */ +/* eslint-disable @typescript-eslint/no-explicit-any */ -'use strict'; +import { assert } from 'chai'; -const { +import { + ExplodedSenderCertificateType, SecretSessionCipher, createCertificateValidator, _createSenderCertificateFromBuffer, _createServerCertificateFromBuffer, -} = window.Signal.Metadata; -const { +} from '../../metadata/SecretSessionCipher'; +import { bytesFromString, stringFromBytes, arrayBufferToBase64, -} = window.Signal.Crypto; +} from '../../Crypto'; +import { KeyPairType } from '../../libsignal.d'; -function InMemorySignalProtocolStore() { - this.store = {}; -} - -function toString(thing) { +function toString(thing: string | ArrayBuffer): string { if (typeof thing === 'string') { return thing; } return arrayBufferToBase64(thing); } -InMemorySignalProtocolStore.prototype = { - Direction: { +class InMemorySignalProtocolStore { + store: Record = {}; + + Direction = { SENDING: 1, RECEIVING: 2, - }, + }; - getIdentityKeyPair() { + getIdentityKeyPair(): Promise<{ privKey: ArrayBuffer; pubKey: ArrayBuffer }> { return Promise.resolve(this.get('identityKey')); - }, - getLocalRegistrationId() { + } + + getLocalRegistrationId(): Promise { return Promise.resolve(this.get('registrationId')); - }, - put(key, value) { + } + + put(key: string, value: any): void { if ( key === undefined || value === undefined || @@ -50,8 +52,9 @@ InMemorySignalProtocolStore.prototype = { throw new Error('Tried to store undefined/null'); } this.store[key] = value; - }, - get(key, defaultValue) { + } + + get(key: string, defaultValue?: any): any { if (key === null || key === undefined) { throw new Error('Tried to get value for undefined/null key'); } @@ -60,15 +63,19 @@ InMemorySignalProtocolStore.prototype = { } return defaultValue; - }, - remove(key) { + } + + remove(key: string): void { if (key === null || key === undefined) { throw new Error('Tried to remove value for undefined/null key'); } delete this.store[key]; - }, + } - isTrustedIdentity(identifier, identityKey) { + isTrustedIdentity( + identifier: string, + identityKey: ArrayBuffer + ): Promise { if (identifier === null || identifier === undefined) { throw new Error('tried to check identity key for undefined/null key'); } @@ -80,18 +87,22 @@ InMemorySignalProtocolStore.prototype = { return Promise.resolve(true); } return Promise.resolve(toString(identityKey) === toString(trusted)); - }, - loadIdentityKey(identifier) { + } + + loadIdentityKey(identifier: string): any { if (identifier === null || identifier === undefined) { throw new Error('Tried to get identity key for undefined/null key'); } return Promise.resolve(this.get(`identityKey${identifier}`)); - }, - saveIdentity(identifier, identityKey) { + } + + saveIdentity(identifier: string, identityKey: ArrayBuffer): any { if (identifier === null || identifier === undefined) { throw new Error('Tried to put identity key for undefined/null key'); } - const address = libsignal.SignalProtocolAddress.fromString(identifier); + const address = window.libsignal.SignalProtocolAddress.fromString( + identifier + ); const existing = this.get(`identityKey${address.getName()}`); this.put(`identityKey${address.getName()}`, identityKey); @@ -101,48 +112,55 @@ InMemorySignalProtocolStore.prototype = { } return Promise.resolve(false); - }, + } /* Returns a prekeypair object or undefined */ - loadPreKey(keyId) { + loadPreKey(keyId: number): any { let res = this.get(`25519KeypreKey${keyId}`); if (res !== undefined) { res = { pubKey: res.pubKey, privKey: res.privKey }; } return Promise.resolve(res); - }, - storePreKey(keyId, keyPair) { + } + + storePreKey(keyId: number, keyPair: any): Promise { return Promise.resolve(this.put(`25519KeypreKey${keyId}`, keyPair)); - }, - removePreKey(keyId) { + } + + removePreKey(keyId: number): Promise { return Promise.resolve(this.remove(`25519KeypreKey${keyId}`)); - }, + } /* Returns a signed keypair object or undefined */ - loadSignedPreKey(keyId) { + loadSignedPreKey(keyId: number): any { let res = this.get(`25519KeysignedKey${keyId}`); if (res !== undefined) { res = { pubKey: res.pubKey, privKey: res.privKey }; } return Promise.resolve(res); - }, - storeSignedPreKey(keyId, keyPair) { - return Promise.resolve(this.put(`25519KeysignedKey${keyId}`, keyPair)); - }, - removeSignedPreKey(keyId) { - return Promise.resolve(this.remove(`25519KeysignedKey${keyId}`)); - }, + } - loadSession(identifier) { + storeSignedPreKey(keyId: number, keyPair: any): Promise { + return Promise.resolve(this.put(`25519KeysignedKey${keyId}`, keyPair)); + } + + removeSignedPreKey(keyId: number): Promise { + return Promise.resolve(this.remove(`25519KeysignedKey${keyId}`)); + } + + loadSession(identifier: string): Promise { return Promise.resolve(this.get(`session${identifier}`)); - }, - storeSession(identifier, record) { + } + + storeSession(identifier: string, record: any): Promise { return Promise.resolve(this.put(`session${identifier}`, record)); - }, - removeSession(identifier) { + } + + removeSession(identifier: string): Promise { return Promise.resolve(this.remove(`session${identifier}`)); - }, - removeAllSessions(identifier) { + } + + removeAllSessions(identifier: string): Promise { // eslint-disable-next-line no-restricted-syntax for (const id in this.store) { if (id.startsWith(`session${identifier}`)) { @@ -150,8 +168,8 @@ InMemorySignalProtocolStore.prototype = { } } return Promise.resolve(); - }, -}; + } +} describe('SecretSessionCipher', () => { it('successfully roundtrips', async function thisNeeded() { @@ -164,7 +182,7 @@ describe('SecretSessionCipher', () => { const aliceIdentityKey = await aliceStore.getIdentityKeyPair(); - const trustRoot = await libsignal.Curve.async.generateKeyPair(); + const trustRoot = await window.libsignal.Curve.async.generateKeyPair(); const senderCertificate = await _createSenderCertificateFor( trustRoot, '+14151111111', @@ -172,15 +190,15 @@ describe('SecretSessionCipher', () => { aliceIdentityKey.pubKey, 31337 ); - const aliceCipher = new SecretSessionCipher(aliceStore); + const aliceCipher = new SecretSessionCipher(aliceStore as any); const ciphertext = await aliceCipher.encrypt( - new libsignal.SignalProtocolAddress('+14152222222', 1), + new window.libsignal.SignalProtocolAddress('+14152222222', 1), { serialized: senderCertificate.serialized }, bytesFromString('smert za smert') ); - const bobCipher = new SecretSessionCipher(bobStore); + const bobCipher = new SecretSessionCipher(bobStore as any); const decryptResult = await bobCipher.decrypt( createCertificateValidator(trustRoot.pubKey), @@ -188,6 +206,13 @@ describe('SecretSessionCipher', () => { 31335 ); + if (!decryptResult.content) { + throw new Error('decryptResult.content is null!'); + } + if (!decryptResult.sender) { + throw new Error('decryptResult.sender is null!'); + } + assert.strictEqual( stringFromBytes(decryptResult.content), 'smert za smert' @@ -205,8 +230,8 @@ describe('SecretSessionCipher', () => { const aliceIdentityKey = await aliceStore.getIdentityKeyPair(); - const trustRoot = await libsignal.Curve.async.generateKeyPair(); - const falseTrustRoot = await libsignal.Curve.async.generateKeyPair(); + const trustRoot = await window.libsignal.Curve.async.generateKeyPair(); + const falseTrustRoot = await window.libsignal.Curve.async.generateKeyPair(); const senderCertificate = await _createSenderCertificateFor( falseTrustRoot, '+14151111111', @@ -214,15 +239,15 @@ describe('SecretSessionCipher', () => { aliceIdentityKey.pubKey, 31337 ); - const aliceCipher = new SecretSessionCipher(aliceStore); + const aliceCipher = new SecretSessionCipher(aliceStore as any); const ciphertext = await aliceCipher.encrypt( - new libsignal.SignalProtocolAddress('+14152222222', 1), + new window.libsignal.SignalProtocolAddress('+14152222222', 1), { serialized: senderCertificate.serialized }, bytesFromString('и вот я') ); - const bobCipher = new SecretSessionCipher(bobStore); + const bobCipher = new SecretSessionCipher(bobStore as any); try { await bobCipher.decrypt( @@ -246,7 +271,7 @@ describe('SecretSessionCipher', () => { const aliceIdentityKey = await aliceStore.getIdentityKeyPair(); - const trustRoot = await libsignal.Curve.async.generateKeyPair(); + const trustRoot = await window.libsignal.Curve.async.generateKeyPair(); const senderCertificate = await _createSenderCertificateFor( trustRoot, '+14151111111', @@ -254,15 +279,15 @@ describe('SecretSessionCipher', () => { aliceIdentityKey.pubKey, 31337 ); - const aliceCipher = new SecretSessionCipher(aliceStore); + const aliceCipher = new SecretSessionCipher(aliceStore as any); const ciphertext = await aliceCipher.encrypt( - new libsignal.SignalProtocolAddress('+14152222222', 1), + new window.libsignal.SignalProtocolAddress('+14152222222', 1), { serialized: senderCertificate.serialized }, bytesFromString('и вот я') ); - const bobCipher = new SecretSessionCipher(bobStore); + const bobCipher = new SecretSessionCipher(bobStore as any); try { await bobCipher.decrypt( @@ -284,8 +309,8 @@ describe('SecretSessionCipher', () => { await _initializeSessions(aliceStore, bobStore); - const trustRoot = await libsignal.Curve.async.generateKeyPair(); - const randomKeyPair = await libsignal.Curve.async.generateKeyPair(); + const trustRoot = await window.libsignal.Curve.async.generateKeyPair(); + const randomKeyPair = await window.libsignal.Curve.async.generateKeyPair(); const senderCertificate = await _createSenderCertificateFor( trustRoot, '+14151111111', @@ -293,15 +318,15 @@ describe('SecretSessionCipher', () => { randomKeyPair.pubKey, 31337 ); - const aliceCipher = new SecretSessionCipher(aliceStore); + const aliceCipher = new SecretSessionCipher(aliceStore as any); const ciphertext = await aliceCipher.encrypt( - new libsignal.SignalProtocolAddress('+14152222222', 1), + new window.libsignal.SignalProtocolAddress('+14152222222', 1), { serialized: senderCertificate.serialized }, bytesFromString('smert za smert') ); - const bobCipher = new SecretSessionCipher(bobStore); + const bobCipher = new SecretSessionCipher(bobStore as any); try { await bobCipher.decrypt( @@ -326,93 +351,99 @@ describe('SecretSessionCipher', () => { // long expires // ) async function _createSenderCertificateFor( - trustRoot, - sender, - deviceId, - identityKey, - expires - ) { - const serverKey = await libsignal.Curve.async.generateKeyPair(); + trustRoot: KeyPairType, + sender: string, + deviceId: number, + identityKey: ArrayBuffer, + expires: number + ): Promise { + const serverKey = await window.libsignal.Curve.async.generateKeyPair(); - const serverCertificateCertificateProto = new textsecure.protobuf.ServerCertificate.Certificate(); + const serverCertificateCertificateProto = new window.textsecure.protobuf.ServerCertificate.Certificate(); serverCertificateCertificateProto.id = 1; serverCertificateCertificateProto.key = serverKey.pubKey; - const serverCertificateCertificateBytes = serverCertificateCertificateProto - .encode() - .toArrayBuffer(); + const serverCertificateCertificateBytes = serverCertificateCertificateProto.toArrayBuffer(); - const serverCertificateSignature = await libsignal.Curve.async.calculateSignature( + const serverCertificateSignature = await window.libsignal.Curve.async.calculateSignature( trustRoot.privKey, serverCertificateCertificateBytes ); - const serverCertificateProto = new textsecure.protobuf.ServerCertificate(); + const serverCertificateProto = new window.textsecure.protobuf.ServerCertificate(); serverCertificateProto.certificate = serverCertificateCertificateBytes; serverCertificateProto.signature = serverCertificateSignature; const serverCertificate = _createServerCertificateFromBuffer( - serverCertificateProto.encode().toArrayBuffer() + serverCertificateProto.toArrayBuffer() ); - const senderCertificateCertificateProto = new textsecure.protobuf.SenderCertificate.Certificate(); + const senderCertificateCertificateProto = new window.textsecure.protobuf.SenderCertificate.Certificate(); senderCertificateCertificateProto.sender = sender; senderCertificateCertificateProto.senderDevice = deviceId; senderCertificateCertificateProto.identityKey = identityKey; senderCertificateCertificateProto.expires = expires; - senderCertificateCertificateProto.signer = textsecure.protobuf.ServerCertificate.decode( + senderCertificateCertificateProto.signer = window.textsecure.protobuf.ServerCertificate.decode( serverCertificate.serialized ); - const senderCertificateBytes = senderCertificateCertificateProto - .encode() - .toArrayBuffer(); + const senderCertificateBytes = senderCertificateCertificateProto.toArrayBuffer(); - const senderCertificateSignature = await libsignal.Curve.async.calculateSignature( + const senderCertificateSignature = await window.libsignal.Curve.async.calculateSignature( serverKey.privKey, senderCertificateBytes ); - const senderCertificateProto = new textsecure.protobuf.SenderCertificate(); + const senderCertificateProto = new window.textsecure.protobuf.SenderCertificate(); senderCertificateProto.certificate = senderCertificateBytes; senderCertificateProto.signature = senderCertificateSignature; return _createSenderCertificateFromBuffer( - senderCertificateProto.encode().toArrayBuffer() + senderCertificateProto.toArrayBuffer() ); } // private void _initializeSessions( // SignalProtocolStore aliceStore, SignalProtocolStore bobStore) - async function _initializeSessions(aliceStore, bobStore) { - const aliceAddress = new libsignal.SignalProtocolAddress('+14152222222', 1); + async function _initializeSessions( + aliceStore: InMemorySignalProtocolStore, + bobStore: InMemorySignalProtocolStore + ): Promise { + const aliceAddress = new window.libsignal.SignalProtocolAddress( + '+14152222222', + 1 + ); await aliceStore.put( 'identityKey', - await libsignal.Curve.generateKeyPair() + await window.libsignal.Curve.generateKeyPair() + ); + await bobStore.put( + 'identityKey', + await window.libsignal.Curve.generateKeyPair() ); - await bobStore.put('identityKey', await libsignal.Curve.generateKeyPair()); await aliceStore.put('registrationId', 57); await bobStore.put('registrationId', 58); - const bobPreKey = await libsignal.Curve.async.generateKeyPair(); + const bobPreKey = await window.libsignal.Curve.async.generateKeyPair(); const bobIdentityKey = await bobStore.getIdentityKeyPair(); - const bobSignedPreKey = await libsignal.KeyHelper.generateSignedPreKey( + const bobSignedPreKey = await window.libsignal.KeyHelper.generateSignedPreKey( bobIdentityKey, 2 ); const bobBundle = { + deviceId: 3, identityKey: bobIdentityKey.pubKey, registrationId: 1, - preKey: { - keyId: 1, - publicKey: bobPreKey.pubKey, - }, signedPreKey: { keyId: 2, publicKey: bobSignedPreKey.keyPair.pubKey, signature: bobSignedPreKey.signature, }, + preKey: { + keyId: 1, + publicKey: bobPreKey.pubKey, + }, }; - const aliceSessionBuilder = new libsignal.SessionBuilder( - aliceStore, + const aliceSessionBuilder = new window.libsignal.SessionBuilder( + aliceStore as any, aliceAddress ); await aliceSessionBuilder.processPreKey(bobBundle); diff --git a/ts/textsecure.d.ts b/ts/textsecure.d.ts index 5e94fc1fd..33a6c607d 100644 --- a/ts/textsecure.d.ts +++ b/ts/textsecure.d.ts @@ -216,6 +216,12 @@ type SubProtocolProtobufTypes = { WebSocketResponseMessage: typeof WebSocketResponseMessageClass; }; +type UnidentifiedDeliveryTypes = { + ServerCertificate: typeof ServerCertificateClass; + SenderCertificate: typeof SenderCertificateClass; + UnidentifiedSenderMessage: typeof UnidentifiedSenderMessageClass; +}; + type ProtobufCollectionType = { onLoad: (callback: () => unknown) => void; } & DeviceMessagesProtobufTypes & @@ -223,7 +229,8 @@ type ProtobufCollectionType = { GroupsProtobufTypes & SignalServiceProtobufTypes & SignalStorageProtobufTypes & - SubProtocolProtobufTypes; + SubProtocolProtobufTypes & + UnidentifiedDeliveryTypes; // Note: there are a lot of places in the code that overwrite a field like this // with a type that the app can use. Being more rigorous with these @@ -1354,3 +1361,90 @@ export declare class WebSocketResponseMessageClass { } export { CallingMessageClass }; + +// UnidentifiedDelivery.proto + +export declare class ServerCertificateClass { + static decode: ( + data: ArrayBuffer | ByteBufferClass, + encoding?: string + ) => ServerCertificateClass; + toArrayBuffer: () => ArrayBuffer; + + certificate?: ProtoBinaryType; + signature?: ProtoBinaryType; +} + +export declare namespace ServerCertificateClass { + class Certificate { + static decode: ( + data: ArrayBuffer | ByteBufferClass, + encoding?: string + ) => Certificate; + toArrayBuffer: () => ArrayBuffer; + + id?: number; + key?: ProtoBinaryType; + } +} + +export declare class SenderCertificateClass { + static decode: ( + data: ArrayBuffer | ByteBufferClass, + encoding?: string + ) => SenderCertificateClass; + toArrayBuffer: () => ArrayBuffer; + + certificate?: ProtoBinaryType; + signature?: ProtoBinaryType; +} + +export declare namespace SenderCertificateClass { + class Certificate { + static decode: ( + data: ArrayBuffer | ByteBufferClass, + encoding?: string + ) => Certificate; + toArrayBuffer: () => ArrayBuffer; + + sender?: string; + senderUuid?: string; + senderDevice?: number; + expires?: ProtoBigNumberType; + identityKey?: ProtoBinaryType; + signer?: SenderCertificateClass; + } +} + +export declare class UnidentifiedSenderMessageClass { + static decode: ( + data: ArrayBuffer | ByteBufferClass, + encoding?: string + ) => UnidentifiedSenderMessageClass; + toArrayBuffer: () => ArrayBuffer; + + ephemeralPublic?: ProtoBinaryType; + encryptedStatic?: ProtoBinaryType; + encryptedMessage?: ProtoBinaryType; +} + +export declare namespace UnidentifiedSenderMessageClass { + class Message { + static decode: ( + data: ArrayBuffer | ByteBufferClass, + encoding?: string + ) => Message; + toArrayBuffer: () => ArrayBuffer; + + type?: number; + senderCertificate?: SenderCertificateClass; + content?: ProtoBinaryType; + } +} + +export declare namespace UnidentifiedSenderMessageClass.Message { + class Type { + static PREKEY_MESSAGE: number; + static MESSAGE: number; + } +} diff --git a/ts/textsecure/MessageReceiver.ts b/ts/textsecure/MessageReceiver.ts index 4cb559315..bf38c4942 100644 --- a/ts/textsecure/MessageReceiver.ts +++ b/ts/textsecure/MessageReceiver.ts @@ -27,6 +27,10 @@ import Crypto from './Crypto'; import { deriveMasterKeyFromGroupV1 } from '../Crypto'; import { ContactBuffer, GroupBuffer } from './ContactsParser'; import { IncomingIdentityKeyError } from './Errors'; +import { + createCertificateValidator, + SecretSessionCipher, +} from '../metadata/SecretSessionCipher'; import { AttachmentPointerClass, @@ -946,7 +950,7 @@ class MessageReceiverInner extends EventTarget { address, options ); - const secretSessionCipher = new window.Signal.Metadata.SecretSessionCipher( + const secretSessionCipher = new SecretSessionCipher( window.textsecure.storage.protocol, options ); @@ -979,7 +983,7 @@ class MessageReceiverInner extends EventTarget { window.log.info('received unidentified sender message'); promise = secretSessionCipher .decrypt( - window.Signal.Metadata.createCertificateValidator(serverTrustRoot), + createCertificateValidator(serverTrustRoot), ciphertext.toArrayBuffer(), Math.min(envelope.serverTimestamp || Date.now(), Date.now()), me @@ -1028,6 +1032,12 @@ class MessageReceiverInner extends EventTarget { originalSource || originalSourceUuid ); + if (!content) { + throw new Error( + 'MessageReceiver.decrypt: Content returned was falsey!' + ); + } + // Return just the content because that matches the signature of the other // decrypt methods used above. return this.unpad(content); diff --git a/ts/textsecure/OutgoingMessage.ts b/ts/textsecure/OutgoingMessage.ts index 11f9813bc..bb372fd2f 100644 --- a/ts/textsecure/OutgoingMessage.ts +++ b/ts/textsecure/OutgoingMessage.ts @@ -26,6 +26,10 @@ import { UnregisteredUserError, } from './Errors'; import { isValidNumber } from '../types/PhoneNumber'; +import { + SecretSessionCipher, + SerializedCertificateType, +} from '../metadata/SecretSessionCipher'; type OutgoingMessageOptionsType = SendOptionsType & { online?: boolean; @@ -58,7 +62,7 @@ export default class OutgoingMessage { sendMetadata?: SendMetadataType; - senderCertificate?: ArrayBuffer; + senderCertificate?: SerializedCertificateType; online?: boolean; @@ -384,8 +388,8 @@ export default class OutgoingMessage { options.messageKeysLimit = false; } - if (sealedSender) { - const secretSessionCipher = new window.Signal.Metadata.SecretSessionCipher( + if (sealedSender && senderCertificate) { + const secretSessionCipher = new SecretSessionCipher( window.textsecure.storage.protocol ); ciphers[address.getDeviceId()] = secretSessionCipher; diff --git a/ts/textsecure/SendMessage.ts b/ts/textsecure/SendMessage.ts index 9b5c7e48d..05caaf9df 100644 --- a/ts/textsecure/SendMessage.ts +++ b/ts/textsecure/SendMessage.ts @@ -46,6 +46,7 @@ import { LinkPreviewImage, LinkPreviewMetadata, } from '../linkPreviews/linkPreviewFetch'; +import { SerializedCertificateType } from '../metadata/SecretSessionCipher'; function stringToArrayBuffer(str: string): ArrayBuffer { if (typeof str !== 'string') { @@ -66,7 +67,7 @@ export type SendMetadataType = { }; export type SendOptionsType = { - senderCertificate?: ArrayBuffer; + senderCertificate?: SerializedCertificateType; sendMetadata?: SendMetadataType; online?: boolean; };