signal-desktop/ts/Curve.ts

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;
}