diff --git a/js/modules/metadata/CiphertextMessage.js b/js/modules/metadata/CiphertextMessage.js
deleted file mode 100644
index d38bdd077155..000000000000
--- 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 1daad5214ee0..ddc13530a5a4 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 f79d1d116904..7c179b89d0ed 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 a9f581b99b06..09894b9a4367 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 000000000000..9f7e84b22f0a
--- /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 b4c4684757af..a092d0e5bb6c 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 58b62b8f2ba0..a107082d2b8e 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 5e94fc1fd940..33a6c607d551 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 4cb559315537..bf38c49424d5 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 11f9813bc781..bb372fd2fb2d 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 9b5c7e48da26..05caaf9dfc0c 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;
};