Updated tests.

Removed libloki-protocol.
This commit is contained in:
Mikunj 2019-01-10 10:26:43 +11:00
parent 070d18b514
commit f9147663d5
5 changed files with 112 additions and 271 deletions

View file

@ -1,167 +0,0 @@
/* global window, libsignal, textsecure, StringView, log */
// eslint-disable-next-line func-names
(function () {
window.libloki = window.libloki || {};
class FallBackDecryptionError extends Error { }
const IV_LENGTH = 16;
class FallBackSessionCipher {
constructor(address) {
this.identityKeyString = address.getName();
this.pubKey = StringView.hexToArrayBuffer(address.getName());
}
async encrypt(plaintext) {
const myKeyPair = await textsecure.storage.protocol.getIdentityKeyPair();
const myPrivateKey = myKeyPair.privKey;
const symmetricKey = libsignal.Curve.calculateAgreement(this.pubKey, myPrivateKey);
const iv = libsignal.crypto.getRandomBytes(IV_LENGTH);
const ciphertext = await libsignal.crypto.encrypt(symmetricKey, plaintext, iv);
const ivAndCiphertext = new Uint8Array(iv.byteLength + ciphertext.byteLength);
ivAndCiphertext.set(new Uint8Array(iv));
ivAndCiphertext.set(new Uint8Array(ciphertext), iv.byteLength);
return {
type: textsecure.protobuf.Envelope.Type.FRIEND_REQUEST,
body: ivAndCiphertext,
registrationId: null,
};
}
async decrypt(ivAndCiphertext) {
const iv = ivAndCiphertext.slice(0, IV_LENGTH);
const cipherText = ivAndCiphertext.slice(IV_LENGTH);
const myKeyPair = await textsecure.storage.protocol.getIdentityKeyPair();
const myPrivateKey = myKeyPair.privKey;
const symmetricKey = libsignal.Curve.calculateAgreement(this.pubKey, myPrivateKey);
try {
return await libsignal.crypto.decrypt(symmetricKey, cipherText, iv);
}
catch (e) {
throw new FallBackDecryptionError(`Could not decrypt message from ${this.identityKeyString} using FallBack encryption.`);
}
}
}
async function getPreKeyBundleForContact(pubKey) {
const myKeyPair = await textsecure.storage.protocol.getIdentityKeyPair();
const identityKey = myKeyPair.pubKey;
// Retrieve ids. The ids stored are always the latest generated + 1
const signedKeyId = textsecure.storage.get('signedKeyId', 2) - 1;
const [signedKey, preKey] = await Promise.all([
textsecure.storage.protocol.loadSignedPreKey(signedKeyId),
new Promise(async resolve => {
// retrieve existing prekey if we already generated one for that recipient
const storedPreKey = await textsecure.storage.protocol.loadPreKeyForContact(
pubKey
);
if (storedPreKey) {
resolve({ pubKey: storedPreKey.pubKey, keyId: storedPreKey.keyId });
} else {
// generate and store new prekey
const preKeyId = textsecure.storage.get('maxPreKeyId', 1);
textsecure.storage.put('maxPreKeyId', preKeyId + 1);
const newPreKey = await libsignal.KeyHelper.generatePreKey(preKeyId);
await textsecure.storage.protocol.storePreKey(
newPreKey.keyId,
newPreKey.keyPair,
pubKey
);
resolve({ pubKey: newPreKey.keyPair.pubKey, keyId: preKeyId });
}
}),
]);
return {
identityKey: new Uint8Array(identityKey),
deviceId: 1, // TODO: fetch from somewhere
preKeyId: preKey.keyId,
signedKeyId,
preKey: new Uint8Array(preKey.pubKey),
signedKey: new Uint8Array(signedKey.pubKey),
signature: new Uint8Array(signedKey.signature),
};
}
async function saveContactPreKeyBundle({
pubKey,
preKeyId,
preKey,
signedKeyId,
signedKey,
signature,
}) {
const signedPreKey = {
keyId: signedKeyId,
publicKey: signedKey,
signature,
};
const signedKeyPromise = textsecure.storage.protocol.storeContactSignedPreKey(
pubKey,
signedPreKey
);
const preKeyObject = {
publicKey: preKey,
keyId: preKeyId,
};
const preKeyPromise = textsecure.storage.protocol.storeContactPreKey(
pubKey,
preKeyObject
);
await Promise.all([signedKeyPromise, preKeyPromise]);
}
async function removeContactPreKeyBundle(pubKey) {
await Promise.all([
textsecure.storage.protocol.removeContactPreKey(pubKey),
textsecure.storage.protocol.removeContactSignedPreKey(pubKey),
]);
}
async function sendFriendRequestAccepted(pubKey) {
return sendEmptyMessage(pubKey);
}
async function sendEmptyMessage(pubKey) {
// empty content message
const content = new textsecure.protobuf.Content();
// will be called once the transmission succeeded or failed
const callback = res => {
if (res.errors.length > 0) {
res.errors.forEach(error => log.error(error));
} else {
log.info('empty message sent successfully');
}
};
const options = {};
// send an empty message. The logic in ougoing_message will attach the prekeys.
const outgoingMessage = new textsecure.OutgoingMessage(
null, // server
Date.now(), // timestamp,
[pubKey], // numbers
content, // message
true, // silent
callback, // callback
options
);
await outgoingMessage.sendToNumber(pubKey);
}
window.libloki.FallBackSessionCipher = FallBackSessionCipher;
window.libloki.getPreKeyBundleForContact = getPreKeyBundleForContact;
window.libloki.FallBackDecryptionError = FallBackDecryptionError;
window.libloki.saveContactPreKeyBundle = saveContactPreKeyBundle;
window.libloki.removeContactPreKeyBundle = removeContactPreKeyBundle;
window.libloki.sendFriendRequestAccepted = sendFriendRequestAccepted;
window.libloki.sendEmptyMessage = sendEmptyMessage;
})();

View file

@ -0,0 +1,38 @@
/* global libsignal, libloki, textsecure, StringView */
'use strict';
describe('Crypto', () => {
describe('FallBackSessionCipher', () => {
let fallbackCipher;
let identityKey;
let address;
const store = textsecure.storage.protocol;
before(async () => {
clearDatabase();
identityKey = await libsignal.KeyHelper.generateIdentityKeyPair();
store.put('identityKey', identityKey);
const key = libsignal.crypto.getRandomBytes(32);
const pubKeyString = StringView.arrayBufferToHex(key);
address = new libsignal.SignalProtocolAddress(
pubKeyString,
1
);
fallbackCipher = new libloki.crypto.FallBackSessionCipher(address);
});
it('should encrypt fallback cipher messages as friend requests', async () => {
const buffer = new ArrayBuffer(10);
const { type } = await fallbackCipher.encrypt(buffer);
assert.strictEqual(type, textsecure.protobuf.Envelope.Type.FRIEND_REQUEST);
});
it('should encrypt and then decrypt a message with the same result', async () => {
const arr = new Uint8Array([1,2,3,4,5]);
const { body } = await fallbackCipher.encrypt(arr.buffer);
const result = await fallbackCipher.decrypt(body);
assert.deepEqual(result, arr.buffer);
});
});
});

View file

@ -29,9 +29,10 @@
<script type="text/javascript" src="../service_nodes.js" data-cover></script>
<script type="text/javascript" src="../storage.js" data-cover></script>
<script type="text/javascript" src="crypto_test.js"></script>
<script type="text/javascript" src="proof-of-work_test.js"></script>
<script type="text/javascript" src="libloki-protocol_test.js"></script>
<script type="text/javascript" src="service_nodes_test.js"></script>
<script type="text/javascript" src="storage_test.js"></script>
<!-- Comment out to turn off code coverage. Useful for getting real callstacks. -->
<!-- NOTE: blanket doesn't support modern syntax and will choke until we find a replacement. :0( -->

View file

@ -1,103 +0,0 @@
/* global libsignal, libloki, textsecure, StringView */
'use strict';
describe('FallBackSessionCipher', () => {
let fallbackCipher;
let identityKey;
let address;
const store = textsecure.storage.protocol;
before(async () => {
clearDatabase();
identityKey = await libsignal.KeyHelper.generateIdentityKeyPair();
store.put('identityKey', identityKey);
const key = libsignal.crypto.getRandomBytes(32);
const pubKeyString = StringView.arrayBufferToHex(key);
address = new libsignal.SignalProtocolAddress(
pubKeyString,
1
);
fallbackCipher = new libloki.crypto.FallBackSessionCipher(address);
});
it('should encrypt fallback cipher messages as friend requests', async () => {
const buffer = new ArrayBuffer(10);
const { type } = await fallbackCipher.encrypt(buffer);
assert.strictEqual(type, textsecure.protobuf.Envelope.Type.FRIEND_REQUEST);
});
it('should encrypt and then decrypt a message with the same result', async () => {
const arr = new Uint8Array([1,2,3,4,5]);
const { body } = await fallbackCipher.encrypt(arr.buffer);
const result = await fallbackCipher.decrypt(body);
assert.deepEqual(result, arr.buffer);
});
});
describe('LibLoki Protocol', () => {
let testKey;
const store = textsecure.storage.protocol;
beforeEach(async () => {
clearDatabase();
testKey = {
pubKey: libsignal.crypto.getRandomBytes(33),
privKey: libsignal.crypto.getRandomBytes(32),
};
textsecure.storage.put('signedKeyId', 2);
await store.storeSignedPreKey(1, testKey);
});
it('should generate a new prekey bundle for a new contact', async () => {
const pubKey = libsignal.crypto.getRandomBytes(32);
const pubKeyString = StringView.arrayBufferToHex(pubKey);
const preKeyIdBefore = textsecure.storage.get('maxPreKeyId', 1);
const newBundle = await libloki.storage.getPreKeyBundleForContact(pubKeyString);
const preKeyIdAfter = textsecure.storage.get('maxPreKeyId', 1);
assert.strictEqual(preKeyIdAfter, preKeyIdBefore + 1);
const testKeyArray = new Uint8Array(testKey.pubKey);
assert.isDefined(newBundle);
assert.isDefined(newBundle.identityKey);
assert.isDefined(newBundle.deviceId);
assert.isDefined(newBundle.preKeyId);
assert.isDefined(newBundle.signedKeyId);
assert.isDefined(newBundle.preKey);
assert.isDefined(newBundle.signedKey);
assert.isDefined(newBundle.signature);
assert.strictEqual(testKeyArray.byteLength, newBundle.signedKey.byteLength);
for (let i = 0 ; i !== testKeyArray.byteLength; i += 1)
assert.strictEqual(testKeyArray[i], newBundle.signedKey[i]);
});
it('should return the same prekey bundle after creating a contact', async () => {
const pubKey = libsignal.crypto.getRandomBytes(32);
const pubKeyString = StringView.arrayBufferToHex(pubKey);
const bundle1 = await libloki.storage.getPreKeyBundleForContact(pubKeyString);
const bundle2 = await libloki.storage.getPreKeyBundleForContact(pubKeyString);
assert.isDefined(bundle1);
assert.isDefined(bundle2);
assert.deepEqual(bundle1, bundle2);
});
it('should save the signed keys and prekeys from a bundle', async () => {
const pubKey = libsignal.crypto.getRandomBytes(32);
const pubKeyString = StringView.arrayBufferToHex(pubKey);
const preKeyIdBefore = textsecure.storage.get('maxPreKeyId', 1);
const newBundle = await libloki.storage.getPreKeyBundleForContact(pubKeyString);
const preKeyIdAfter = textsecure.storage.get('maxPreKeyId', 1);
assert.strictEqual(preKeyIdAfter, preKeyIdBefore + 1);
const testKeyArray = new Uint8Array(testKey.pubKey);
assert.isDefined(newBundle);
assert.isDefined(newBundle.identityKey);
assert.isDefined(newBundle.deviceId);
assert.isDefined(newBundle.preKeyId);
assert.isDefined(newBundle.signedKeyId);
assert.isDefined(newBundle.preKey);
assert.isDefined(newBundle.signedKey);
assert.isDefined(newBundle.signature);
assert.deepEqual(testKeyArray, newBundle.signedKey);
});
});

View file

@ -0,0 +1,72 @@
/* global libsignal, libloki, textsecure, StringView */
'use strict';
describe('Storage', () => {
let testKey;
const store = textsecure.storage.protocol;
describe('#getPreKeyBundleForContact', () => {
beforeEach(async () => {
clearDatabase();
testKey = {
pubKey: libsignal.crypto.getRandomBytes(33),
privKey: libsignal.crypto.getRandomBytes(32),
};
textsecure.storage.put('signedKeyId', 2);
await store.storeSignedPreKey(1, testKey);
});
it('should generate a new prekey bundle for a new contact', async () => {
const pubKey = libsignal.crypto.getRandomBytes(32);
const pubKeyString = StringView.arrayBufferToHex(pubKey);
const preKeyIdBefore = textsecure.storage.get('maxPreKeyId', 1);
const newBundle = await libloki.storage.getPreKeyBundleForContact(pubKeyString);
const preKeyIdAfter = textsecure.storage.get('maxPreKeyId', 1);
assert.strictEqual(preKeyIdAfter, preKeyIdBefore + 1);
const testKeyArray = new Uint8Array(testKey.pubKey);
assert.isDefined(newBundle);
assert.isDefined(newBundle.identityKey);
assert.isDefined(newBundle.deviceId);
assert.isDefined(newBundle.preKeyId);
assert.isDefined(newBundle.signedKeyId);
assert.isDefined(newBundle.preKey);
assert.isDefined(newBundle.signedKey);
assert.isDefined(newBundle.signature);
assert.strictEqual(testKeyArray.byteLength, newBundle.signedKey.byteLength);
for (let i = 0 ; i !== testKeyArray.byteLength; i += 1)
assert.strictEqual(testKeyArray[i], newBundle.signedKey[i]);
});
it('should return the same prekey bundle after creating a contact', async () => {
const pubKey = libsignal.crypto.getRandomBytes(32);
const pubKeyString = StringView.arrayBufferToHex(pubKey);
const bundle1 = await libloki.storage.getPreKeyBundleForContact(pubKeyString);
const bundle2 = await libloki.storage.getPreKeyBundleForContact(pubKeyString);
assert.isDefined(bundle1);
assert.isDefined(bundle2);
assert.deepEqual(bundle1, bundle2);
});
it('should save the signed keys and prekeys from a bundle', async () => {
const pubKey = libsignal.crypto.getRandomBytes(32);
const pubKeyString = StringView.arrayBufferToHex(pubKey);
const preKeyIdBefore = textsecure.storage.get('maxPreKeyId', 1);
const newBundle = await libloki.storage.getPreKeyBundleForContact(pubKeyString);
const preKeyIdAfter = textsecure.storage.get('maxPreKeyId', 1);
assert.strictEqual(preKeyIdAfter, preKeyIdBefore + 1);
const testKeyArray = new Uint8Array(testKey.pubKey);
assert.isDefined(newBundle);
assert.isDefined(newBundle.identityKey);
assert.isDefined(newBundle.deviceId);
assert.isDefined(newBundle.preKeyId);
assert.isDefined(newBundle.signedKeyId);
assert.isDefined(newBundle.preKey);
assert.isDefined(newBundle.signedKey);
assert.isDefined(newBundle.signature);
assert.deepEqual(testKeyArray, newBundle.signedKey);
});
});
});