2020-10-30 20:34:04 +00:00
|
|
|
// Copyright 2014-2020 Signal Messenger, LLC
|
|
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
|
2018-12-13 19:12:33 +00:00
|
|
|
/* global Signal, textsecure, libsignal */
|
2018-11-02 18:02:53 +00:00
|
|
|
|
2018-03-19 19:42:12 +00:00
|
|
|
'use strict';
|
|
|
|
|
2018-10-18 01:01:21 +00:00
|
|
|
describe('Crypto', () => {
|
|
|
|
describe('accessKey/profileKey', () => {
|
|
|
|
it('verification roundtrips', async () => {
|
|
|
|
const profileKey = await Signal.Crypto.getRandomBytes(32);
|
|
|
|
const accessKey = await Signal.Crypto.deriveAccessKey(profileKey);
|
2018-03-19 19:42:12 +00:00
|
|
|
|
2018-10-18 01:01:21 +00:00
|
|
|
const verifier = await Signal.Crypto.getAccessKeyVerifier(accessKey);
|
|
|
|
|
|
|
|
const correct = await Signal.Crypto.verifyAccessKey(accessKey, verifier);
|
2018-03-19 19:42:12 +00:00
|
|
|
|
2018-10-18 01:01:21 +00:00
|
|
|
assert.strictEqual(correct, true);
|
|
|
|
});
|
2018-03-19 19:42:12 +00:00
|
|
|
});
|
|
|
|
|
2018-10-18 01:01:21 +00:00
|
|
|
describe('symmetric encryption', () => {
|
|
|
|
it('roundtrips', async () => {
|
2018-11-02 18:02:53 +00:00
|
|
|
const message = 'this is my message';
|
|
|
|
const plaintext = dcodeIO.ByteBuffer.wrap(
|
2018-10-18 01:01:21 +00:00
|
|
|
message,
|
|
|
|
'binary'
|
|
|
|
).toArrayBuffer();
|
2018-11-02 18:02:53 +00:00
|
|
|
const key = textsecure.crypto.getRandomBytes(32);
|
2018-10-18 01:01:21 +00:00
|
|
|
|
2018-11-02 18:02:53 +00:00
|
|
|
const encrypted = await Signal.Crypto.encryptSymmetric(key, plaintext);
|
|
|
|
const decrypted = await Signal.Crypto.decryptSymmetric(key, encrypted);
|
2018-10-18 01:01:21 +00:00
|
|
|
|
2018-11-02 18:02:53 +00:00
|
|
|
const equal = Signal.Crypto.constantTimeEqual(plaintext, decrypted);
|
2018-10-18 01:01:21 +00:00
|
|
|
if (!equal) {
|
|
|
|
throw new Error('The output and input did not match!');
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
it('roundtrip fails if nonce is modified', async () => {
|
2018-11-02 18:02:53 +00:00
|
|
|
const message = 'this is my message';
|
|
|
|
const plaintext = dcodeIO.ByteBuffer.wrap(
|
2018-10-18 01:01:21 +00:00
|
|
|
message,
|
|
|
|
'binary'
|
|
|
|
).toArrayBuffer();
|
2018-11-02 18:02:53 +00:00
|
|
|
const key = textsecure.crypto.getRandomBytes(32);
|
2018-10-18 01:01:21 +00:00
|
|
|
|
2018-11-02 18:02:53 +00:00
|
|
|
const encrypted = await Signal.Crypto.encryptSymmetric(key, plaintext);
|
|
|
|
const uintArray = new Uint8Array(encrypted);
|
2018-12-13 21:41:42 +00:00
|
|
|
uintArray[2] += 2;
|
2018-10-18 01:01:21 +00:00
|
|
|
|
|
|
|
try {
|
2018-11-02 18:02:53 +00:00
|
|
|
await Signal.Crypto.decryptSymmetric(key, uintArray.buffer);
|
2018-10-18 01:01:21 +00:00
|
|
|
} catch (error) {
|
|
|
|
assert.strictEqual(
|
|
|
|
error.message,
|
|
|
|
'decryptSymmetric: Failed to decrypt; MAC verification failed'
|
|
|
|
);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
throw new Error('Expected error to be thrown');
|
|
|
|
});
|
|
|
|
|
|
|
|
it('roundtrip fails if mac is modified', async () => {
|
2018-11-02 18:02:53 +00:00
|
|
|
const message = 'this is my message';
|
|
|
|
const plaintext = dcodeIO.ByteBuffer.wrap(
|
2018-10-18 01:01:21 +00:00
|
|
|
message,
|
|
|
|
'binary'
|
|
|
|
).toArrayBuffer();
|
2018-11-02 18:02:53 +00:00
|
|
|
const key = textsecure.crypto.getRandomBytes(32);
|
2018-10-18 01:01:21 +00:00
|
|
|
|
2018-11-02 18:02:53 +00:00
|
|
|
const encrypted = await Signal.Crypto.encryptSymmetric(key, plaintext);
|
|
|
|
const uintArray = new Uint8Array(encrypted);
|
2018-12-13 21:41:42 +00:00
|
|
|
uintArray[uintArray.length - 3] += 2;
|
2018-10-18 01:01:21 +00:00
|
|
|
|
|
|
|
try {
|
2018-11-02 18:02:53 +00:00
|
|
|
await Signal.Crypto.decryptSymmetric(key, uintArray.buffer);
|
2018-10-18 01:01:21 +00:00
|
|
|
} catch (error) {
|
|
|
|
assert.strictEqual(
|
|
|
|
error.message,
|
|
|
|
'decryptSymmetric: Failed to decrypt; MAC verification failed'
|
|
|
|
);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
throw new Error('Expected error to be thrown');
|
|
|
|
});
|
|
|
|
|
|
|
|
it('roundtrip fails if encrypted contents are modified', async () => {
|
2018-11-02 18:02:53 +00:00
|
|
|
const message = 'this is my message';
|
|
|
|
const plaintext = dcodeIO.ByteBuffer.wrap(
|
2018-10-18 01:01:21 +00:00
|
|
|
message,
|
|
|
|
'binary'
|
|
|
|
).toArrayBuffer();
|
2018-11-02 18:02:53 +00:00
|
|
|
const key = textsecure.crypto.getRandomBytes(32);
|
2018-10-18 01:01:21 +00:00
|
|
|
|
2018-11-02 18:02:53 +00:00
|
|
|
const encrypted = await Signal.Crypto.encryptSymmetric(key, plaintext);
|
|
|
|
const uintArray = new Uint8Array(encrypted);
|
2018-12-13 21:41:42 +00:00
|
|
|
uintArray[35] += 9;
|
2018-10-18 01:01:21 +00:00
|
|
|
|
|
|
|
try {
|
2018-11-02 18:02:53 +00:00
|
|
|
await Signal.Crypto.decryptSymmetric(key, uintArray.buffer);
|
2018-10-18 01:01:21 +00:00
|
|
|
} catch (error) {
|
|
|
|
assert.strictEqual(
|
|
|
|
error.message,
|
|
|
|
'decryptSymmetric: Failed to decrypt; MAC verification failed'
|
|
|
|
);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
throw new Error('Expected error to be thrown');
|
|
|
|
});
|
2018-03-19 19:42:12 +00:00
|
|
|
});
|
2018-12-13 19:12:33 +00:00
|
|
|
|
|
|
|
describe('encrypted device name', () => {
|
|
|
|
it('roundtrips', async () => {
|
|
|
|
const deviceName = 'v1.19.0 on Windows 10';
|
|
|
|
const identityKey = await libsignal.KeyHelper.generateIdentityKeyPair();
|
|
|
|
|
|
|
|
const encrypted = await Signal.Crypto.encryptDeviceName(
|
|
|
|
deviceName,
|
|
|
|
identityKey.pubKey
|
|
|
|
);
|
|
|
|
const decrypted = await Signal.Crypto.decryptDeviceName(
|
|
|
|
encrypted,
|
|
|
|
identityKey.privKey
|
|
|
|
);
|
|
|
|
|
|
|
|
assert.strictEqual(decrypted, deviceName);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('fails if iv is changed', async () => {
|
|
|
|
const deviceName = 'v1.19.0 on Windows 10';
|
|
|
|
const identityKey = await libsignal.KeyHelper.generateIdentityKeyPair();
|
|
|
|
|
|
|
|
const encrypted = await Signal.Crypto.encryptDeviceName(
|
|
|
|
deviceName,
|
|
|
|
identityKey.pubKey
|
|
|
|
);
|
|
|
|
encrypted.syntheticIv = Signal.Crypto.getRandomBytes(16);
|
|
|
|
try {
|
|
|
|
await Signal.Crypto.decryptDeviceName(encrypted, identityKey.privKey);
|
|
|
|
} catch (error) {
|
|
|
|
assert.strictEqual(
|
|
|
|
error.message,
|
|
|
|
'decryptDeviceName: synthetic IV did not match'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
2018-12-13 21:41:42 +00:00
|
|
|
|
|
|
|
describe('attachment encryption', () => {
|
|
|
|
it('roundtrips', async () => {
|
|
|
|
const staticKeyPair = await libsignal.KeyHelper.generateIdentityKeyPair();
|
|
|
|
const message = 'this is my message';
|
|
|
|
const plaintext = Signal.Crypto.bytesFromString(message);
|
|
|
|
const path =
|
|
|
|
'fa/facdf99c22945b1c9393345599a276f4b36ad7ccdc8c2467f5441b742c2d11fa';
|
|
|
|
|
|
|
|
const encrypted = await Signal.Crypto.encryptAttachment(
|
|
|
|
staticKeyPair.pubKey.slice(1),
|
|
|
|
path,
|
|
|
|
plaintext
|
|
|
|
);
|
|
|
|
const decrypted = await Signal.Crypto.decryptAttachment(
|
|
|
|
staticKeyPair.privKey,
|
|
|
|
path,
|
|
|
|
encrypted
|
|
|
|
);
|
|
|
|
|
|
|
|
const equal = Signal.Crypto.constantTimeEqual(plaintext, decrypted);
|
|
|
|
if (!equal) {
|
|
|
|
throw new Error('The output and input did not match!');
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
2020-11-13 19:57:55 +00:00
|
|
|
|
|
|
|
describe('uuidToArrayBuffer', () => {
|
|
|
|
const { uuidToArrayBuffer } = Signal.Crypto;
|
|
|
|
|
|
|
|
it('converts valid UUIDs to ArrayBuffers', () => {
|
|
|
|
const expectedResult = new Uint8Array([
|
|
|
|
0x22,
|
|
|
|
0x6e,
|
|
|
|
0x44,
|
|
|
|
0x02,
|
|
|
|
0x7f,
|
|
|
|
0xfc,
|
|
|
|
0x45,
|
|
|
|
0x43,
|
|
|
|
0x85,
|
|
|
|
0xc9,
|
|
|
|
0x46,
|
|
|
|
0x22,
|
|
|
|
0xc5,
|
|
|
|
0x0a,
|
|
|
|
0x5b,
|
|
|
|
0x14,
|
|
|
|
]).buffer;
|
|
|
|
|
|
|
|
assert.deepEqual(
|
|
|
|
uuidToArrayBuffer('226e4402-7ffc-4543-85c9-4622c50a5b14'),
|
|
|
|
expectedResult
|
|
|
|
);
|
|
|
|
assert.deepEqual(
|
|
|
|
uuidToArrayBuffer('226E4402-7FFC-4543-85C9-4622C50A5B14'),
|
|
|
|
expectedResult
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('returns an empty ArrayBuffer for strings of the wrong length', () => {
|
|
|
|
assert.deepEqual(uuidToArrayBuffer(''), new ArrayBuffer(0));
|
|
|
|
assert.deepEqual(uuidToArrayBuffer('abc'), new ArrayBuffer(0));
|
|
|
|
assert.deepEqual(
|
|
|
|
uuidToArrayBuffer('032deadf0d5e4ee78da28e75b1dfb284'),
|
|
|
|
new ArrayBuffer(0)
|
|
|
|
);
|
|
|
|
assert.deepEqual(
|
|
|
|
uuidToArrayBuffer('deaed5eb-d983-456a-a954-9ad7a006b271aaaaaaaaaa'),
|
|
|
|
new ArrayBuffer(0)
|
|
|
|
);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('arrayBufferToUuid', () => {
|
|
|
|
const { arrayBufferToUuid } = Signal.Crypto;
|
|
|
|
|
|
|
|
it('converts valid ArrayBuffers to UUID strings', () => {
|
|
|
|
const buf = new Uint8Array([
|
|
|
|
0x22,
|
|
|
|
0x6e,
|
|
|
|
0x44,
|
|
|
|
0x02,
|
|
|
|
0x7f,
|
|
|
|
0xfc,
|
|
|
|
0x45,
|
|
|
|
0x43,
|
|
|
|
0x85,
|
|
|
|
0xc9,
|
|
|
|
0x46,
|
|
|
|
0x22,
|
|
|
|
0xc5,
|
|
|
|
0x0a,
|
|
|
|
0x5b,
|
|
|
|
0x14,
|
|
|
|
]).buffer;
|
|
|
|
|
|
|
|
assert.deepEqual(
|
|
|
|
arrayBufferToUuid(buf),
|
|
|
|
'226e4402-7ffc-4543-85c9-4622c50a5b14'
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('returns undefined if passed an all-zero buffer', () => {
|
|
|
|
assert.isUndefined(arrayBufferToUuid(new ArrayBuffer(16)));
|
|
|
|
});
|
|
|
|
|
|
|
|
it('returns undefined if passed the wrong number of bytes', () => {
|
|
|
|
assert.isUndefined(arrayBufferToUuid(new ArrayBuffer(0)));
|
|
|
|
assert.isUndefined(arrayBufferToUuid(new Uint8Array([0x22]).buffer));
|
|
|
|
assert.isUndefined(
|
|
|
|
arrayBufferToUuid(new Uint8Array(Array(17).fill(0x22)).buffer)
|
|
|
|
);
|
|
|
|
});
|
|
|
|
});
|
2018-03-19 19:42:12 +00:00
|
|
|
});
|