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