signal-desktop/ts/textsecure/ProvisioningCipher.ts
Scott Nonnenberg b7d56def82 Moves libtextsecure to Typescript
* Starting to work through lint errors

* libsignal-protocol: Update changes for primary repo compatibility

* Step 1: task_with_timeout rename

* Step 2: Apply the changes to TaskWithTimeout.ts

* Step 1: All to-be-converted libtextsecure/*.js files moved

* Step 2: No Typescript errors!

* Get libtextsecure tests passing again

* TSLint errors down to 1

* Compilation succeeds, no lint errors or test failures

* WebSocketResources - update import for case-sensitive filesystems

* Fixes for lint-deps

* Remove unnecessary @ts-ignore

* Fix inability to message your own contact after link

* Add log message for the end of migration 20

* lint fix
2020-04-15 14:45:11 -07:00

111 lines
3.3 KiB
TypeScript

// tslint:disable no-default-export
import { KeyPairType } from '../libsignal.d';
import { ProvisionEnvelopeClass } from '../textsecure.d';
type ProvisionDecryptResult = {
identityKeyPair: KeyPairType;
number?: string;
uuid?: string;
provisioningCode?: string;
userAgent?: string;
readReceipts?: boolean;
profileKey?: ArrayBuffer;
};
class ProvisioningCipherInner {
keyPair?: KeyPairType;
async decrypt(
provisionEnvelope: ProvisionEnvelopeClass
): Promise<ProvisionDecryptResult> {
const masterEphemeral = provisionEnvelope.publicKey.toArrayBuffer();
const message = provisionEnvelope.body.toArrayBuffer();
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!');
}
return window.libsignal.Curve.async
.calculateAgreement(masterEphemeral, this.keyPair.privKey)
.then(async ecRes =>
window.libsignal.HKDF.deriveSecrets(
ecRes,
new ArrayBuffer(32),
'TextSecure Provisioning Message'
)
)
.then(async keys =>
window.libsignal.crypto
.verifyMAC(ivAndCiphertext, keys[1], mac, 32)
.then(async () =>
window.libsignal.crypto.decrypt(keys[0], ciphertext, iv)
)
)
.then(async plaintext => {
const provisionMessage = window.textsecure.protobuf.ProvisionMessage.decode(
plaintext
);
const privKey = provisionMessage.identityKeyPrivate.toArrayBuffer();
return window.libsignal.Curve.async
.createKeyPair(privKey)
.then(keyPair => {
const ret: ProvisionDecryptResult = {
identityKeyPair: keyPair,
number: provisionMessage.number,
provisioningCode: provisionMessage.provisioningCode,
userAgent: provisionMessage.userAgent,
readReceipts: provisionMessage.readReceipts,
};
if (provisionMessage.profileKey) {
ret.profileKey = provisionMessage.profileKey.toArrayBuffer();
}
return ret;
});
});
}
async getPublicKey(): Promise<ArrayBuffer> {
return Promise.resolve()
.then(async () => {
if (!this.keyPair) {
return window.libsignal.Curve.async
.generateKeyPair()
.then(keyPair => {
this.keyPair = keyPair;
});
}
return null;
})
.then(() => {
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: (
provisionEnvelope: ProvisionEnvelopeClass
) => Promise<ProvisionDecryptResult>;
getPublicKey: () => Promise<ArrayBuffer>;
}