203 lines
5.2 KiB
TypeScript
203 lines
5.2 KiB
TypeScript
// Copyright 2021 Signal Messenger, LLC
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
import * as client from '@signalapp/libsignal-client';
|
|
import type { KyberPreKeyRecord } from '@signalapp/libsignal-client';
|
|
|
|
import * as Bytes from './Bytes';
|
|
import { constantTimeEqual } from './Crypto';
|
|
import type {
|
|
KeyPairType,
|
|
CompatPreKeyType,
|
|
CompatSignedPreKeyType,
|
|
} from './textsecure/Types.d';
|
|
import * as log from './logging/log';
|
|
|
|
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 Uint8Array) ||
|
|
identityKeyPair.privKey.byteLength !== 32 ||
|
|
!(identityKeyPair.pubKey instanceof Uint8Array) ||
|
|
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 generateKyberPreKey(
|
|
identityKeyPair: KeyPairType,
|
|
keyId: number
|
|
): KyberPreKeyRecord {
|
|
if (!isNonNegativeInteger(keyId)) {
|
|
throw new TypeError(
|
|
`generateKyberPreKey: Invalid argument for keyId: ${keyId}`
|
|
);
|
|
}
|
|
|
|
if (
|
|
!(identityKeyPair.privKey instanceof Uint8Array) ||
|
|
identityKeyPair.privKey.byteLength !== 32 ||
|
|
!(identityKeyPair.pubKey instanceof Uint8Array) ||
|
|
identityKeyPair.pubKey.byteLength !== 33
|
|
) {
|
|
throw new TypeError(
|
|
'generateKyberPreKey: Invalid argument for identityKeyPair'
|
|
);
|
|
}
|
|
|
|
const keyPair = client.KEMKeyPair.generate();
|
|
const signature = calculateSignature(
|
|
identityKeyPair.privKey,
|
|
keyPair.getPublicKey().serialize()
|
|
);
|
|
return client.KyberPreKeyRecord.new(
|
|
keyId,
|
|
Date.now(),
|
|
keyPair,
|
|
Buffer.from(signature)
|
|
);
|
|
}
|
|
|
|
export function generateKeyPair(): KeyPairType {
|
|
const privKey = client.PrivateKey.generate();
|
|
const pubKey = privKey.getPublicKey();
|
|
|
|
return {
|
|
privKey: privKey.serialize(),
|
|
pubKey: pubKey.serialize(),
|
|
};
|
|
}
|
|
|
|
export function createKeyPair(incomingKey: Uint8Array): KeyPairType {
|
|
const copy = new Uint8Array(incomingKey);
|
|
clampPrivateKey(copy);
|
|
if (!constantTimeEqual(copy, incomingKey)) {
|
|
log.warn('createKeyPair: incoming private key was not clamped!');
|
|
}
|
|
|
|
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: privKey.serialize(),
|
|
pubKey: pubKey.serialize(),
|
|
};
|
|
}
|
|
|
|
export function prefixPublicKey(pubKey: Uint8Array): Uint8Array {
|
|
return Bytes.concatenate([
|
|
new Uint8Array([0x05]),
|
|
validatePubKeyFormat(pubKey),
|
|
]);
|
|
}
|
|
|
|
export function calculateAgreement(
|
|
pubKey: Uint8Array,
|
|
privKey: Uint8Array
|
|
): Uint8Array {
|
|
const privKeyBuffer = Buffer.from(privKey);
|
|
|
|
const pubKeyObj = client.PublicKey.deserialize(
|
|
Buffer.from(prefixPublicKey(pubKey))
|
|
);
|
|
const privKeyObj = client.PrivateKey.deserialize(privKeyBuffer);
|
|
const sharedSecret = privKeyObj.agree(pubKeyObj);
|
|
return sharedSecret;
|
|
}
|
|
|
|
export function verifySignature(
|
|
pubKey: Uint8Array,
|
|
message: Uint8Array,
|
|
signature: Uint8Array
|
|
): 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: Uint8Array,
|
|
plaintext: Uint8Array
|
|
): Uint8Array {
|
|
const privKeyBuffer = Buffer.from(privKey);
|
|
const plaintextBuffer = Buffer.from(plaintext);
|
|
|
|
const privKeyObj = client.PrivateKey.deserialize(privKeyBuffer);
|
|
const signature = privKeyObj.sign(plaintextBuffer);
|
|
return signature;
|
|
}
|
|
|
|
function validatePubKeyFormat(pubKey: Uint8Array): Uint8Array {
|
|
if (
|
|
pubKey === undefined ||
|
|
((pubKey.byteLength !== 33 || 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: Uint8Array): void {
|
|
// eslint-disable-next-line no-param-reassign
|
|
publicKey[0] = 5;
|
|
}
|
|
|
|
export function clampPrivateKey(privateKey: Uint8Array): void {
|
|
// eslint-disable-next-line no-bitwise, no-param-reassign
|
|
privateKey[0] &= 248;
|
|
// eslint-disable-next-line no-bitwise, no-param-reassign
|
|
privateKey[31] &= 127;
|
|
// eslint-disable-next-line no-bitwise, no-param-reassign
|
|
privateKey[31] |= 64;
|
|
}
|