signal-desktop/ts/textsecure/ProvisioningCipher.ts

123 lines
3.7 KiB
TypeScript
Raw Normal View History

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
/* eslint-disable max-classes-per-file */
import type { KeyPairType } from './Types.d';
2021-09-24 00:49:05 +00:00
import * as Bytes from '../Bytes';
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';
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;
number?: string;
2023-09-27 23:14:55 +00:00
aci?: string;
untaggedPni?: string;
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
}>;
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(
provisionEnvelope.publicKey,
'Missing publicKey in ProvisionEnvelope'
2021-07-02 19:21:24 +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;
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);
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-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');
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');
2024-08-30 00:02:48 +00:00
return {
2022-03-01 23:01:21 +00:00
aciKeyPair,
pniKeyPair,
number: dropNull(provisionMessage.number),
2023-09-27 23:14:55 +00:00
aci,
untaggedPni: pni,
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,
};
}
2024-08-30 00:02:48 +00:00
getPublicKey(): Uint8Array {
if (!this.keyPair) {
this.keyPair = generateKeyPair();
}
if (!this.keyPair) {
throw new Error('ProvisioningCipher.decrypt: No keypair!');
}
return this.keyPair.pubKey;
}
}
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;
2024-08-30 00:02:48 +00:00
getPublicKey: () => Uint8Array;
}