2023-01-03 19:55:46 +00:00
|
|
|
// Copyright 2020 Signal Messenger, LLC
|
2020-10-30 20:34:04 +00:00
|
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
|
2020-09-24 21:53:21 +00:00
|
|
|
/* eslint-disable max-classes-per-file */
|
2020-04-13 17:37:29 +00:00
|
|
|
|
2021-10-26 19:15:33 +00:00
|
|
|
import type { KeyPairType } from './Types.d';
|
2021-09-24 00:49:05 +00:00
|
|
|
import * as Bytes from '../Bytes';
|
2021-04-16 23:13:13 +00:00
|
|
|
import {
|
|
|
|
decryptAes256CbcPkcsPadding,
|
|
|
|
deriveSecrets,
|
|
|
|
verifyHmacSha256,
|
|
|
|
} from '../Crypto';
|
|
|
|
import { calculateAgreement, createKeyPair, generateKeyPair } from '../Curve';
|
2021-07-02 19:21:24 +00:00
|
|
|
import { SignalService as Proto } from '../protobuf';
|
2021-07-09 19:36:10 +00:00
|
|
|
import { strictAssert } from '../util/assert';
|
2023-11-07 21:31:59 +00:00
|
|
|
import { dropNull } from '../util/dropNull';
|
2021-07-02 19:21:24 +00:00
|
|
|
|
2024-08-30 00:02:48 +00:00
|
|
|
export type ProvisionDecryptResult = Readonly<{
|
2022-03-01 23:01:21 +00:00
|
|
|
aciKeyPair: KeyPairType;
|
|
|
|
pniKeyPair?: KeyPairType;
|
2020-04-13 17:37:29 +00:00
|
|
|
number?: string;
|
2023-09-27 23:14:55 +00:00
|
|
|
aci?: string;
|
|
|
|
untaggedPni?: string;
|
2020-04-13 17:37:29 +00:00
|
|
|
provisioningCode?: string;
|
|
|
|
userAgent?: string;
|
|
|
|
readReceipts?: boolean;
|
2021-09-24 00:49:05 +00:00
|
|
|
profileKey?: Uint8Array;
|
2023-10-30 20:31:39 +00:00
|
|
|
masterKey?: Uint8Array;
|
2024-08-30 00:02:48 +00:00
|
|
|
}>;
|
2020-04-13 17:37:29 +00:00
|
|
|
|
|
|
|
class ProvisioningCipherInner {
|
|
|
|
keyPair?: KeyPairType;
|
|
|
|
|
2024-08-30 00:02:48 +00:00
|
|
|
decrypt(provisionEnvelope: Proto.ProvisionEnvelope): ProvisionDecryptResult {
|
2021-07-09 19:36:10 +00:00
|
|
|
strictAssert(
|
2023-11-07 21:31:59 +00:00
|
|
|
provisionEnvelope.publicKey,
|
|
|
|
'Missing publicKey in ProvisionEnvelope'
|
2021-07-02 19:21:24 +00:00
|
|
|
);
|
2023-11-07 21:31:59 +00:00
|
|
|
strictAssert(provisionEnvelope.body, 'Missing body in ProvisionEnvelope');
|
2021-07-02 19:21:24 +00:00
|
|
|
const masterEphemeral = provisionEnvelope.publicKey;
|
|
|
|
const message = provisionEnvelope.body;
|
2020-04-13 17:37:29 +00:00
|
|
|
if (new Uint8Array(message)[0] !== 1) {
|
|
|
|
throw new Error('Bad version number on ProvisioningMessage');
|
|
|
|
}
|
|
|
|
|
|
|
|
const iv = message.slice(1, 16 + 1);
|
|
|
|
const mac = message.slice(message.byteLength - 32, message.byteLength);
|
|
|
|
const ivAndCiphertext = message.slice(0, message.byteLength - 32);
|
|
|
|
const ciphertext = message.slice(16 + 1, message.byteLength - 32);
|
|
|
|
|
|
|
|
if (!this.keyPair) {
|
|
|
|
throw new Error('ProvisioningCipher.decrypt: No keypair!');
|
|
|
|
}
|
|
|
|
|
2021-09-24 00:49:05 +00:00
|
|
|
const ecRes = calculateAgreement(masterEphemeral, this.keyPair.privKey);
|
2021-04-16 23:13:13 +00:00
|
|
|
const keys = deriveSecrets(
|
|
|
|
ecRes,
|
2021-09-24 00:49:05 +00:00
|
|
|
new Uint8Array(32),
|
|
|
|
Bytes.fromString('TextSecure Provisioning Message')
|
2021-07-02 19:21:24 +00:00
|
|
|
);
|
2021-09-24 00:49:05 +00:00
|
|
|
verifyHmacSha256(ivAndCiphertext, keys[1], mac, 32);
|
2021-04-16 23:13:13 +00:00
|
|
|
|
2021-09-24 00:49:05 +00:00
|
|
|
const plaintext = decryptAes256CbcPkcsPadding(keys[0], ciphertext, iv);
|
|
|
|
const provisionMessage = Proto.ProvisionMessage.decode(plaintext);
|
2022-03-01 23:01:21 +00:00
|
|
|
const aciPrivKey = provisionMessage.aciIdentityKeyPrivate;
|
|
|
|
const pniPrivKey = provisionMessage.pniIdentityKeyPrivate;
|
|
|
|
strictAssert(aciPrivKey, 'Missing aciKeyPrivate in ProvisionMessage');
|
2021-04-16 23:13:13 +00:00
|
|
|
|
2022-03-01 23:01:21 +00:00
|
|
|
const aciKeyPair = createKeyPair(aciPrivKey);
|
|
|
|
const pniKeyPair = pniPrivKey?.length
|
|
|
|
? createKeyPair(pniPrivKey)
|
|
|
|
: undefined;
|
2021-07-09 19:36:10 +00:00
|
|
|
|
2022-03-01 23:01:21 +00:00
|
|
|
const { aci, pni } = provisionMessage;
|
|
|
|
strictAssert(aci, 'Missing aci in provisioning message');
|
2023-09-27 23:14:55 +00:00
|
|
|
strictAssert(pni, 'Missing pni in provisioning message');
|
2021-04-16 23:13:13 +00:00
|
|
|
|
2024-08-30 00:02:48 +00:00
|
|
|
return {
|
2022-03-01 23:01:21 +00:00
|
|
|
aciKeyPair,
|
|
|
|
pniKeyPair,
|
2023-11-07 21:31:59 +00:00
|
|
|
number: dropNull(provisionMessage.number),
|
2023-09-27 23:14:55 +00:00
|
|
|
aci,
|
|
|
|
untaggedPni: pni,
|
2023-11-07 21:31:59 +00:00
|
|
|
provisioningCode: dropNull(provisionMessage.provisioningCode),
|
|
|
|
userAgent: dropNull(provisionMessage.userAgent),
|
|
|
|
readReceipts: provisionMessage.readReceipts ?? false,
|
2024-08-30 00:02:48 +00:00
|
|
|
profileKey: Bytes.isNotEmpty(provisionMessage.profileKey)
|
|
|
|
? provisionMessage.profileKey
|
|
|
|
: undefined,
|
|
|
|
masterKey: Bytes.isNotEmpty(provisionMessage.masterKey)
|
|
|
|
? provisionMessage.masterKey
|
|
|
|
: undefined,
|
2021-04-16 23:13:13 +00:00
|
|
|
};
|
2020-04-13 17:37:29 +00:00
|
|
|
}
|
2020-09-24 21:53:21 +00:00
|
|
|
|
2024-08-30 00:02:48 +00:00
|
|
|
getPublicKey(): Uint8Array {
|
2021-04-16 23:13:13 +00:00
|
|
|
if (!this.keyPair) {
|
|
|
|
this.keyPair = generateKeyPair();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!this.keyPair) {
|
|
|
|
throw new Error('ProvisioningCipher.decrypt: No keypair!');
|
|
|
|
}
|
|
|
|
|
|
|
|
return this.keyPair.pubKey;
|
2020-04-13 17:37:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export default class ProvisioningCipher {
|
|
|
|
constructor() {
|
|
|
|
const inner = new ProvisioningCipherInner();
|
|
|
|
|
|
|
|
this.decrypt = inner.decrypt.bind(inner);
|
|
|
|
this.getPublicKey = inner.getPublicKey.bind(inner);
|
|
|
|
}
|
|
|
|
|
|
|
|
decrypt: (
|
2021-07-02 19:21:24 +00:00
|
|
|
provisionEnvelope: Proto.ProvisionEnvelope
|
2024-08-30 00:02:48 +00:00
|
|
|
) => ProvisionDecryptResult;
|
2020-09-24 21:53:21 +00:00
|
|
|
|
2024-08-30 00:02:48 +00:00
|
|
|
getPublicKey: () => Uint8Array;
|
2020-04-13 17:37:29 +00:00
|
|
|
}
|