2021-04-16 23:13:13 +00:00
|
|
|
// Copyright 2021 Signal Messenger, LLC
|
|
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
|
|
|
|
import { assert } from 'chai';
|
|
|
|
|
|
|
|
import {
|
|
|
|
arrayBufferToHex,
|
|
|
|
constantTimeEqual,
|
|
|
|
getRandomBytes,
|
|
|
|
hexToArrayBuffer,
|
|
|
|
typedArrayToArrayBuffer,
|
|
|
|
} from '../Crypto';
|
|
|
|
import {
|
|
|
|
calculateSignature,
|
|
|
|
clampPrivateKey,
|
|
|
|
createKeyPair,
|
|
|
|
copyArrayBuffer,
|
2021-04-27 00:42:12 +00:00
|
|
|
calculateAgreement,
|
2021-04-16 23:13:13 +00:00
|
|
|
generateKeyPair,
|
|
|
|
generatePreKey,
|
|
|
|
generateSignedPreKey,
|
|
|
|
isNonNegativeInteger,
|
|
|
|
verifySignature,
|
|
|
|
} from '../Curve';
|
|
|
|
|
|
|
|
describe('Curve', () => {
|
|
|
|
it('verifySignature roundtrip', () => {
|
|
|
|
const message = typedArrayToArrayBuffer(Buffer.from('message'));
|
|
|
|
const { pubKey, privKey } = generateKeyPair();
|
|
|
|
const signature = calculateSignature(privKey, message);
|
|
|
|
const verified = verifySignature(pubKey, message, signature);
|
|
|
|
|
|
|
|
assert.isTrue(verified);
|
|
|
|
});
|
|
|
|
|
2021-04-27 00:42:12 +00:00
|
|
|
it('calculateAgreement roundtrip', () => {
|
|
|
|
const alice = generateKeyPair();
|
|
|
|
const bob = generateKeyPair();
|
|
|
|
|
|
|
|
const sharedSecretAlice = calculateAgreement(bob.pubKey, alice.privKey);
|
|
|
|
const sharedSecretBob = calculateAgreement(alice.pubKey, bob.privKey);
|
|
|
|
|
|
|
|
assert.isTrue(constantTimeEqual(sharedSecretAlice, sharedSecretBob));
|
|
|
|
});
|
|
|
|
|
2021-04-16 23:13:13 +00:00
|
|
|
describe('#isNonNegativeInteger', () => {
|
|
|
|
it('returns false for -1, Infinity, NaN, a string, etc.', () => {
|
|
|
|
assert.isFalse(isNonNegativeInteger(-1));
|
|
|
|
assert.isFalse(isNonNegativeInteger(NaN));
|
|
|
|
assert.isFalse(isNonNegativeInteger(Infinity));
|
|
|
|
assert.isFalse(isNonNegativeInteger('woo!'));
|
|
|
|
});
|
|
|
|
it('returns true for 0 and positive integgers', () => {
|
|
|
|
assert.isTrue(isNonNegativeInteger(0));
|
|
|
|
assert.isTrue(isNonNegativeInteger(1));
|
|
|
|
assert.isTrue(isNonNegativeInteger(3));
|
|
|
|
assert.isTrue(isNonNegativeInteger(400_000));
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('#generateSignedPrekey', () => {
|
|
|
|
it('geernates proper signature for created signed prekeys', () => {
|
|
|
|
const keyId = 4;
|
|
|
|
const identityKeyPair = generateKeyPair();
|
|
|
|
const signedPreKey = generateSignedPreKey(identityKeyPair, keyId);
|
|
|
|
|
|
|
|
assert.equal(keyId, signedPreKey.keyId);
|
|
|
|
|
|
|
|
const verified = verifySignature(
|
|
|
|
identityKeyPair.pubKey,
|
|
|
|
signedPreKey.keyPair.pubKey,
|
|
|
|
signedPreKey.signature
|
|
|
|
);
|
|
|
|
|
|
|
|
assert.isTrue(verified);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('#generatePrekey', () => {
|
|
|
|
it('returns keys of the right length', () => {
|
|
|
|
const keyId = 7;
|
|
|
|
const preKey = generatePreKey(keyId);
|
|
|
|
|
|
|
|
assert.equal(keyId, preKey.keyId);
|
|
|
|
assert.equal(33, preKey.keyPair.pubKey.byteLength);
|
|
|
|
assert.equal(32, preKey.keyPair.privKey.byteLength);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('#copyArrayBuffer', () => {
|
|
|
|
it('copy matches original', () => {
|
|
|
|
const data = getRandomBytes(200);
|
|
|
|
const dataHex = arrayBufferToHex(data);
|
|
|
|
const copy = copyArrayBuffer(data);
|
|
|
|
|
|
|
|
assert.equal(data.byteLength, copy.byteLength);
|
|
|
|
assert.isTrue(constantTimeEqual(data, copy));
|
|
|
|
|
|
|
|
assert.equal(dataHex, arrayBufferToHex(data));
|
|
|
|
assert.equal(dataHex, arrayBufferToHex(copy));
|
|
|
|
});
|
|
|
|
|
|
|
|
it('copies into new memory location', () => {
|
|
|
|
const data = getRandomBytes(200);
|
|
|
|
const dataHex = arrayBufferToHex(data);
|
|
|
|
const copy = copyArrayBuffer(data);
|
|
|
|
|
|
|
|
const view = new Uint8Array(copy);
|
|
|
|
view[0] += 1;
|
|
|
|
view[1] -= 1;
|
|
|
|
|
|
|
|
assert.equal(data.byteLength, copy.byteLength);
|
|
|
|
assert.isFalse(constantTimeEqual(data, copy));
|
|
|
|
|
|
|
|
assert.equal(dataHex, arrayBufferToHex(data));
|
|
|
|
assert.notEqual(dataHex, arrayBufferToHex(copy));
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('#createKeyPair', () => {
|
|
|
|
it('does not modify unclamped private key', () => {
|
|
|
|
const initialHex =
|
|
|
|
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
|
|
|
|
const privateKey = hexToArrayBuffer(initialHex);
|
|
|
|
const copyOfPrivateKey = copyArrayBuffer(privateKey);
|
|
|
|
|
|
|
|
assert.isTrue(
|
|
|
|
constantTimeEqual(privateKey, copyOfPrivateKey),
|
|
|
|
'initial copy check'
|
|
|
|
);
|
|
|
|
|
|
|
|
const keyPair = createKeyPair(privateKey);
|
|
|
|
|
|
|
|
assert.equal(32, keyPair.privKey.byteLength);
|
|
|
|
assert.equal(33, keyPair.pubKey.byteLength);
|
|
|
|
|
|
|
|
// The original incoming key is not modified
|
|
|
|
assert.isTrue(
|
|
|
|
constantTimeEqual(privateKey, copyOfPrivateKey),
|
|
|
|
'second copy check'
|
|
|
|
);
|
|
|
|
|
|
|
|
// But the keypair that comes out has indeed been updated
|
|
|
|
assert.notEqual(
|
|
|
|
initialHex,
|
|
|
|
arrayBufferToHex(keyPair.privKey),
|
|
|
|
'keypair check'
|
|
|
|
);
|
|
|
|
assert.isFalse(
|
|
|
|
constantTimeEqual(keyPair.privKey, privateKey),
|
|
|
|
'keypair vs incoming value'
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('does not modify clamped private key', () => {
|
|
|
|
const initialHex =
|
|
|
|
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
|
|
|
|
const privateKey = hexToArrayBuffer(initialHex);
|
|
|
|
clampPrivateKey(privateKey);
|
|
|
|
const postClampHex = arrayBufferToHex(privateKey);
|
|
|
|
const copyOfPrivateKey = copyArrayBuffer(privateKey);
|
|
|
|
|
|
|
|
assert.notEqual(postClampHex, initialHex, 'initial clamp check');
|
|
|
|
assert.isTrue(
|
|
|
|
constantTimeEqual(privateKey, copyOfPrivateKey),
|
|
|
|
'initial copy check'
|
|
|
|
);
|
|
|
|
|
|
|
|
const keyPair = createKeyPair(privateKey);
|
|
|
|
|
|
|
|
assert.equal(32, keyPair.privKey.byteLength);
|
|
|
|
assert.equal(33, keyPair.pubKey.byteLength);
|
|
|
|
|
|
|
|
// The original incoming key is not modified
|
|
|
|
assert.isTrue(
|
|
|
|
constantTimeEqual(privateKey, copyOfPrivateKey),
|
|
|
|
'second copy check'
|
|
|
|
);
|
|
|
|
|
|
|
|
// The keypair that comes out hasn't been updated either
|
|
|
|
assert.equal(
|
|
|
|
postClampHex,
|
|
|
|
arrayBufferToHex(keyPair.privKey),
|
|
|
|
'keypair check'
|
|
|
|
);
|
|
|
|
assert.isTrue(
|
|
|
|
constantTimeEqual(privateKey, keyPair.privKey),
|
|
|
|
'keypair vs incoming value'
|
|
|
|
);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|