2021-04-16 23:13:13 +00:00
|
|
|
// Copyright 2021 Signal Messenger, LLC
|
|
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
|
2021-05-14 01:18:43 +00:00
|
|
|
import * as client from '@signalapp/signal-client';
|
2021-04-16 23:13:13 +00:00
|
|
|
|
|
|
|
import { constantTimeEqual, typedArrayToArrayBuffer } from './Crypto';
|
|
|
|
import {
|
|
|
|
KeyPairType,
|
|
|
|
CompatPreKeyType,
|
|
|
|
CompatSignedPreKeyType,
|
|
|
|
} from './textsecure/Types.d';
|
2021-09-17 18:27:53 +00:00
|
|
|
import * as log from './logging/log';
|
2021-04-16 23:13:13 +00:00
|
|
|
|
|
|
|
export function isNonNegativeInteger(n: unknown): n is number {
|
|
|
|
return typeof n === 'number' && n % 1 === 0 && n >= 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function generateSignedPreKey(
|
|
|
|
identityKeyPair: KeyPairType,
|
|
|
|
keyId: number
|
|
|
|
): CompatSignedPreKeyType {
|
|
|
|
if (!isNonNegativeInteger(keyId)) {
|
|
|
|
throw new TypeError(
|
|
|
|
`generateSignedPreKey: Invalid argument for keyId: ${keyId}`
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (
|
|
|
|
!(identityKeyPair.privKey instanceof ArrayBuffer) ||
|
|
|
|
identityKeyPair.privKey.byteLength !== 32 ||
|
|
|
|
!(identityKeyPair.pubKey instanceof ArrayBuffer) ||
|
|
|
|
identityKeyPair.pubKey.byteLength !== 33
|
|
|
|
) {
|
|
|
|
throw new TypeError(
|
|
|
|
'generateSignedPreKey: Invalid argument for identityKeyPair'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
const keyPair = generateKeyPair();
|
|
|
|
const signature = calculateSignature(identityKeyPair.privKey, keyPair.pubKey);
|
|
|
|
|
|
|
|
return {
|
|
|
|
keyId,
|
|
|
|
keyPair,
|
|
|
|
signature,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
export function generatePreKey(keyId: number): CompatPreKeyType {
|
|
|
|
if (!isNonNegativeInteger(keyId)) {
|
|
|
|
throw new TypeError(`generatePreKey: Invalid argument for keyId: ${keyId}`);
|
|
|
|
}
|
|
|
|
|
|
|
|
const keyPair = generateKeyPair();
|
|
|
|
|
|
|
|
return {
|
|
|
|
keyId,
|
|
|
|
keyPair,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
export function generateKeyPair(): KeyPairType {
|
|
|
|
const privKey = client.PrivateKey.generate();
|
|
|
|
const pubKey = privKey.getPublicKey();
|
|
|
|
|
|
|
|
return {
|
|
|
|
privKey: typedArrayToArrayBuffer(privKey.serialize()),
|
|
|
|
pubKey: typedArrayToArrayBuffer(pubKey.serialize()),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
export function copyArrayBuffer(source: ArrayBuffer): ArrayBuffer {
|
|
|
|
const sourceArray = new Uint8Array(source);
|
|
|
|
|
|
|
|
const target = new ArrayBuffer(source.byteLength);
|
|
|
|
const targetArray = new Uint8Array(target);
|
|
|
|
|
|
|
|
targetArray.set(sourceArray, 0);
|
|
|
|
|
|
|
|
return target;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function createKeyPair(incomingKey: ArrayBuffer): KeyPairType {
|
|
|
|
const copy = copyArrayBuffer(incomingKey);
|
|
|
|
clampPrivateKey(copy);
|
|
|
|
if (!constantTimeEqual(copy, incomingKey)) {
|
2021-09-17 18:27:53 +00:00
|
|
|
log.warn('createKeyPair: incoming private key was not clamped!');
|
2021-04-16 23:13:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const incomingKeyBuffer = Buffer.from(incomingKey);
|
|
|
|
|
|
|
|
if (incomingKeyBuffer.length !== 32) {
|
|
|
|
throw new Error('key must be 32 bytes long');
|
|
|
|
}
|
|
|
|
|
|
|
|
const privKey = client.PrivateKey.deserialize(incomingKeyBuffer);
|
|
|
|
const pubKey = privKey.getPublicKey();
|
|
|
|
|
|
|
|
return {
|
|
|
|
privKey: typedArrayToArrayBuffer(privKey.serialize()),
|
|
|
|
pubKey: typedArrayToArrayBuffer(pubKey.serialize()),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
export function calculateAgreement(
|
|
|
|
pubKey: ArrayBuffer,
|
|
|
|
privKey: ArrayBuffer
|
|
|
|
): ArrayBuffer {
|
|
|
|
const privKeyBuffer = Buffer.from(privKey);
|
|
|
|
|
|
|
|
const pubKeyObj = client.PublicKey.deserialize(
|
|
|
|
Buffer.concat([
|
|
|
|
Buffer.from([0x05]),
|
|
|
|
Buffer.from(validatePubKeyFormat(pubKey)),
|
|
|
|
])
|
|
|
|
);
|
|
|
|
const privKeyObj = client.PrivateKey.deserialize(privKeyBuffer);
|
|
|
|
const sharedSecret = privKeyObj.agree(pubKeyObj);
|
|
|
|
return typedArrayToArrayBuffer(sharedSecret);
|
|
|
|
}
|
|
|
|
|
|
|
|
export function verifySignature(
|
|
|
|
pubKey: ArrayBuffer,
|
|
|
|
message: ArrayBuffer,
|
|
|
|
signature: ArrayBuffer
|
|
|
|
): boolean {
|
|
|
|
const pubKeyBuffer = Buffer.from(pubKey);
|
|
|
|
const messageBuffer = Buffer.from(message);
|
|
|
|
const signatureBuffer = Buffer.from(signature);
|
|
|
|
|
|
|
|
const pubKeyObj = client.PublicKey.deserialize(pubKeyBuffer);
|
|
|
|
const result = pubKeyObj.verify(messageBuffer, signatureBuffer);
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function calculateSignature(
|
|
|
|
privKey: ArrayBuffer,
|
|
|
|
plaintext: ArrayBuffer
|
|
|
|
): ArrayBuffer {
|
|
|
|
const privKeyBuffer = Buffer.from(privKey);
|
|
|
|
const plaintextBuffer = Buffer.from(plaintext);
|
|
|
|
|
|
|
|
const privKeyObj = client.PrivateKey.deserialize(privKeyBuffer);
|
|
|
|
const signature = privKeyObj.sign(plaintextBuffer);
|
|
|
|
return typedArrayToArrayBuffer(signature);
|
|
|
|
}
|
|
|
|
|
|
|
|
export function validatePubKeyFormat(pubKey: ArrayBuffer): ArrayBuffer {
|
|
|
|
if (
|
|
|
|
pubKey === undefined ||
|
|
|
|
((pubKey.byteLength !== 33 || new Uint8Array(pubKey)[0] !== 5) &&
|
|
|
|
pubKey.byteLength !== 32)
|
|
|
|
) {
|
|
|
|
throw new Error('Invalid public key');
|
|
|
|
}
|
|
|
|
if (pubKey.byteLength === 33) {
|
|
|
|
return pubKey.slice(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
return pubKey;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function setPublicKeyTypeByte(publicKey: ArrayBuffer): void {
|
|
|
|
const byteArray = new Uint8Array(publicKey);
|
|
|
|
byteArray[0] = 5;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function clampPrivateKey(privateKey: ArrayBuffer): void {
|
|
|
|
const byteArray = new Uint8Array(privateKey);
|
|
|
|
|
|
|
|
// eslint-disable-next-line no-bitwise
|
|
|
|
byteArray[0] &= 248;
|
|
|
|
// eslint-disable-next-line no-bitwise
|
|
|
|
byteArray[31] &= 127;
|
|
|
|
// eslint-disable-next-line no-bitwise
|
|
|
|
byteArray[31] |= 64;
|
|
|
|
}
|