Merge pull request #125 from Mikunj/refactor-lib-loki

Refactor libloki protocol
pull/129/head
Beaudan Campbell-Brown 6 years ago committed by GitHub
commit 80077325e1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -101,8 +101,10 @@ module.exports = grunt => {
},
libloki: {
src: [
'libloki/libloki-protocol.js',
'libloki/api.js',
'libloki/crypto.js',
'libloki/service_nodes.js',
'libloki/storage.js',
],
dest: 'js/libloki.js',
},

@ -709,7 +709,6 @@
<script type='text/javascript' src='js/storage.js'></script>
<script type='text/javascript' src='js/legacy_storage.js'></script>
<script type='text/javascript' src='js/signal_protocol_store.js'></script>
<script type='text/javascript' src='js/loki_protocol_store.js'></script>
<script type='text/javascript' src='js/libtextsecure.js'></script>
<script type='text/javascript' src='js/libloki.js'></script>

@ -620,7 +620,7 @@
response: 'declined',
direction: 'incoming',
});
await window.libloki.removeContactPreKeyBundle(this.id);
await window.libloki.storage.removeContactPreKeyBundle(this.id);
},
// We have accepted an incoming friend request
async onAcceptFriendRequest() {
@ -630,7 +630,7 @@
response: 'accepted',
direction: 'incoming',
});
window.libloki.sendFriendRequestAccepted(this.id);
window.libloki.api.sendFriendRequestAccepted(this.id);
}
},
// Our outgoing friend request has been accepted
@ -1548,7 +1548,7 @@
await this.setSessionResetStatus(SessionResetEnum.request_received);
// send empty message, this will trigger the new session to propagate
// to the reset initiator.
await window.libloki.sendEmptyMessage(this.id);
await window.libloki.api.sendEmptyMessage(this.id);
},
isSessionResetReceived() {
@ -1582,7 +1582,7 @@
async onNewSessionAdopted() {
if (this.get('sessionResetStatus') === SessionResetEnum.initiated) {
// send empty message to confirm that we have adopted the new session
await window.libloki.sendEmptyMessage(this.id);
await window.libloki.api.sendEmptyMessage(this.id);
}
await this.createAndStoreEndSessionMessage({ type: 'incoming', endSessionType: 'done' });
await this.setSessionResetStatus(SessionResetEnum.none);

@ -1407,7 +1407,7 @@
autoAccept = true;
message.set({ friendStatus: 'accepted' });
await conversation.onFriendRequestAccepted();
window.libloki.sendFriendRequestAccepted(message.get('source'));
window.libloki.api.sendFriendRequestAccepted(message.get('source'));
} else if (!conversation.isFriend()) {
await conversation.onFriendRequestReceived();
}

@ -0,0 +1,41 @@
/* global window, textsecure, log */
// eslint-disable-next-line func-names
(function () {
window.libloki = window.libloki || {};
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.api = {
sendFriendRequestAccepted,
sendEmptyMessage,
};
})();

@ -0,0 +1,53 @@
/* global window, libsignal, textsecure, StringView */
// 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.`);
}
}
}
window.libloki.crypto = {
FallBackSessionCipher,
FallBackDecryptionError,
};
})();

@ -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;
})();

@ -3,7 +3,6 @@
// eslint-disable-next-line func-names
(function () {
window.libloki = window.libloki || {};
window.libloki.serviceNodes = window.libloki.serviceNodes || {};
function consolidateLists(lists, threshold = 1){
if (typeof threshold !== 'number') {
@ -31,5 +30,7 @@
.map(keyValue => keyValue[0]);
}
window.libloki.serviceNodes.consolidateLists = consolidateLists;
window.libloki.serviceNodes = {
consolidateLists,
};
})();

@ -1,6 +1,97 @@
/* global window, libsignal, textsecure */
// eslint-disable-next-line func-names
(function() {
'use strict';
(function () {
window.libloki = window.libloki || {};
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),
]);
}
window.libloki.storage = {
getPreKeyBundleForContact,
saveContactPreKeyBundle,
removeContactPreKeyBundle,
};
// Libloki protocol store
const store = window.SignalProtocolStore.prototype;
@ -116,5 +207,4 @@
store.clearContactSignedPreKeysStore = async () => {
await window.Signal.Data.removeAllContactSignedPreKeys();
};
})();

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

@ -24,12 +24,15 @@
<script type="text/javascript" src="../../libtextsecure/protobufs.js" data-cover></script>
<script type="text/javascript" src="../../libtextsecure/stringview.js" data-cover></script>
<script type="text/javascript" src="../libloki-protocol.js" data-cover></script>
<script type="text/javascript" src="../api.js" data-cover></script>
<script type="text/javascript" src="../crypto.js" data-cover></script>
<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( -->

@ -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.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.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.getPreKeyBundleForContact(pubKeyString);
const bundle2 = await libloki.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.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);
});
});

@ -8,88 +8,98 @@ const {
greaterThan,
} = pow;
describe('Proof of Work Worker', () => {
it('should increment a Uint8Array nonce correctly', () => {
const arr1Before = new Uint8Array([0,0,0,0,0,0,0,0]);
const arr1After = incrementNonce(arr1Before);
assert.strictEqual(arr1After[0], 0);
assert.strictEqual(arr1After[1], 0);
assert.strictEqual(arr1After[2], 0);
assert.strictEqual(arr1After[3], 0);
assert.strictEqual(arr1After[4], 0);
assert.strictEqual(arr1After[5], 0);
assert.strictEqual(arr1After[6], 0);
assert.strictEqual(arr1After[7], 1);
});
describe('Proof of Work', () => {
describe('#incrementNonce', () => {
it('should increment a Uint8Array nonce correctly', () => {
const arr1Before = new Uint8Array([0,0,0,0,0,0,0,0]);
const arr1After = incrementNonce(arr1Before);
assert.strictEqual(arr1After[0], 0);
assert.strictEqual(arr1After[1], 0);
assert.strictEqual(arr1After[2], 0);
assert.strictEqual(arr1After[3], 0);
assert.strictEqual(arr1After[4], 0);
assert.strictEqual(arr1After[5], 0);
assert.strictEqual(arr1After[6], 0);
assert.strictEqual(arr1After[7], 1);
});
it('should increment a Uint8Array nonce correctly', () => {
let arr = new Uint8Array([0,0,0,0,0,0,0,0]);
assert.deepEqual(incrementNonce(arr), new Uint8Array([0,0,0,0,0,0,0,1]));
arr = new Uint8Array([0,0,0,0,0,0,0,0]);
for(let i = 0; i <= 255; i += 1) {
arr = incrementNonce(arr);
}
assert.deepEqual(arr, new Uint8Array([0,0,0,0,0,0,1,0]));
arr = new Uint8Array([255,255,255,255,255,255,255,255]);
assert.deepEqual(incrementNonce(arr), new Uint8Array([0,0,0,0,0,0,0,0]));
it('should increment a Uint8Array nonce correctly in a loop', () => {
let arr = new Uint8Array([0,0,0,0,0,0,0,0]);
assert.deepEqual(incrementNonce(arr), new Uint8Array([0,0,0,0,0,0,0,1]));
arr = new Uint8Array([0,0,0,0,0,0,0,0]);
for(let i = 0; i <= 255; i += 1) {
arr = incrementNonce(arr);
}
assert.deepEqual(arr, new Uint8Array([0,0,0,0,0,0,1,0]));
arr = new Uint8Array([255,255,255,255,255,255,255,255]);
assert.deepEqual(incrementNonce(arr), new Uint8Array([0,0,0,0,0,0,0,0]));
});
});
it('should calculate a correct difficulty target', () => {
// These values will need to be updated if we adjust the difficulty settings
let payloadLen = 625;
const ttl = 86400;
let expectedTarget = new Uint8Array([0,4,119,164,35,224,222,64]);
describe('#calcTarget', () => {
it('should calculate a correct difficulty target', () => {
// These values will need to be updated if we adjust the difficulty settings
let payloadLen = 625;
const ttl = 86400;
let expectedTarget = new Uint8Array([0,4,119,164,35,224,222,64]);
let actualTarget = calcTarget(ttl, payloadLen, 10);
assert.deepEqual(actualTarget, expectedTarget);
payloadLen = 6597;
expectedTarget = new Uint8Array([0,0,109,145,174,146,124,3]);
actualTarget = calcTarget(ttl, payloadLen, 10);
assert.deepEqual(actualTarget, expectedTarget);
let actualTarget = calcTarget(ttl, payloadLen, 10);
assert.deepEqual(actualTarget, expectedTarget);
payloadLen = 6597;
expectedTarget = new Uint8Array([0,0,109,145,174,146,124,3]);
actualTarget = calcTarget(ttl, payloadLen, 10);
assert.deepEqual(actualTarget, expectedTarget);
});
});
it('should correclty compare two Uint8Arrays', () => {
let arr1 = new Uint8Array([0,0,0,0,0,0,0,0,0,1]);
let arr2 = new Uint8Array([0,0,0,0,0,0,0,0,0,1]);
assert.isFalse(greaterThan(arr1, arr2))
arr1 = new Uint8Array([0,0,0,0,0,0,0,0,0,2]);
arr2 = new Uint8Array([0,0,0,0,0,0,0,0,0,1]);
assert.isTrue(greaterThan(arr1, arr2))
arr1 = new Uint8Array([255,255,255,255,255,255,255,255,255,255]);
arr2 = new Uint8Array([255,255,255,255,255,255,255,255,255,254]);
assert.isTrue(greaterThan(arr1, arr2))
arr1 = new Uint8Array([254,255,255,255,255,255,255,255,255,255]);
arr2 = new Uint8Array([255,255,255,255,255,255,255,255,255,255]);
assert.isFalse(greaterThan(arr1, arr2));
arr1 = new Uint8Array([0]);
arr2 = new Uint8Array([0,0]);
assert.isFalse(greaterThan(arr1, arr2))
describe('#greaterThan', () => {
it('should correclty compare two Uint8Arrays', () => {
let arr1 = new Uint8Array([0,0,0,0,0,0,0,0,0,1]);
let arr2 = new Uint8Array([0,0,0,0,0,0,0,0,0,1]);
assert.isFalse(greaterThan(arr1, arr2))
arr1 = new Uint8Array([0,0,0,0,0,0,0,0,0,2]);
arr2 = new Uint8Array([0,0,0,0,0,0,0,0,0,1]);
assert.isTrue(greaterThan(arr1, arr2))
arr1 = new Uint8Array([255,255,255,255,255,255,255,255,255,255]);
arr2 = new Uint8Array([255,255,255,255,255,255,255,255,255,254]);
assert.isTrue(greaterThan(arr1, arr2))
arr1 = new Uint8Array([254,255,255,255,255,255,255,255,255,255]);
arr2 = new Uint8Array([255,255,255,255,255,255,255,255,255,255]);
assert.isFalse(greaterThan(arr1, arr2));
arr1 = new Uint8Array([0]);
arr2 = new Uint8Array([0,0]);
assert.isFalse(greaterThan(arr1, arr2))
});
});
it('should correclty convert a Uint8Array to a base64 string', () => {
let arr = new Uint8Array([1,2,3]);
let expected = 'AQID';
assert.strictEqual(bufferToBase64(arr), expected);
arr = new Uint8Array([123,25,3,121,45,87,24,111]);
expected = 'exkDeS1XGG8=';
assert.strictEqual(bufferToBase64(arr), expected);
arr = new Uint8Array([]);
expected = '';
assert.strictEqual(bufferToBase64(arr), expected);
describe('#bufferToBase64', () => {
it('should correclty convert a Uint8Array to a base64 string', () => {
let arr = new Uint8Array([1,2,3]);
let expected = 'AQID';
assert.strictEqual(bufferToBase64(arr), expected);
arr = new Uint8Array([123,25,3,121,45,87,24,111]);
expected = 'exkDeS1XGG8=';
assert.strictEqual(bufferToBase64(arr), expected);
arr = new Uint8Array([]);
expected = '';
assert.strictEqual(bufferToBase64(arr), expected);
});
});
it('should correclty convert a BigInteger to a Uint8Array', () => {
let bigInt = JSBI.BigInt(Number.MAX_SAFE_INTEGER);
let expected = new Uint8Array([0, 31, 255, 255, 255, 255, 255, 255]);
assert.deepEqual(bigIntToUint8Array(bigInt), expected);
bigInt = JSBI.BigInt('0');
expected = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0]);
assert.deepEqual(bigIntToUint8Array(bigInt), expected);
bigInt = JSBI.BigInt('255');
expected = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 255]);
assert.deepEqual(bigIntToUint8Array(bigInt), expected);
bigInt = JSBI.BigInt('256');
expected = new Uint8Array([0, 0, 0, 0, 0, 0, 1, 0]);
assert.deepEqual(bigIntToUint8Array(bigInt), expected);
describe('#bigIntToUint8Array', () => {
it('should correclty convert a BigInteger to a Uint8Array', () => {
let bigInt = JSBI.BigInt(Number.MAX_SAFE_INTEGER);
let expected = new Uint8Array([0, 31, 255, 255, 255, 255, 255, 255]);
assert.deepEqual(bigIntToUint8Array(bigInt), expected);
bigInt = JSBI.BigInt('0');
expected = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0]);
assert.deepEqual(bigIntToUint8Array(bigInt), expected);
bigInt = JSBI.BigInt('255');
expected = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 255]);
assert.deepEqual(bigIntToUint8Array(bigInt), expected);
bigInt = JSBI.BigInt('256');
expected = new Uint8Array([0, 0, 0, 0, 0, 0, 1, 0]);
assert.deepEqual(bigIntToUint8Array(bigInt), expected);
});
});
});

@ -1,40 +1,50 @@
/* global libloki, chai */
/* global libloki, assert */
describe('ServiceNodes', () => {
describe('#consolidateLists', () => {
it('should throw when provided a non-iterable list', () => {
chai.expect(() => libloki.serviceNodes.consolidateLists(null, 1)).to.throw();
assert.throws(() => libloki.serviceNodes.consolidateLists(null, 1), Error);
});
it('should throw when provided a non-iterable item in the list', () => {
chai.expect(() => libloki.serviceNodes.consolidateLists([1, 2, 3], 1)).to.throw();
assert.throws(() => libloki.serviceNodes.consolidateLists([1, 2, 3], 1), Error);
});
it('should throw when provided a non-number threshold', () => {
chai.expect(() => libloki.serviceNodes.consolidateLists([], 'a')).to.throw();
assert.throws(
() => libloki.serviceNodes.consolidateLists([], 'a'),
'Provided threshold is not a number'
);
});
it('should return an empty array when the input is an empty array', () => {
const result = libloki.serviceNodes.consolidateLists([]);
chai.expect(result).to.deep.equal([]);
assert.deepEqual(result, []);
});
it('should return the input when only 1 list is provided', () => {
const result = libloki.serviceNodes.consolidateLists([['a', 'b', 'c']]);
chai.expect(result).to.deep.equal(['a', 'b', 'c']);
assert.deepEqual(result, ['a', 'b', 'c']);
});
it('should return the union of all lists when threshold is 0', () => {
const result = libloki.serviceNodes.consolidateLists([
['a', 'b', 'c', 'h'],
['d', 'e', 'f', 'g'],
['g', 'h'],
], 0);
chai.expect(result.sort()).to.deep.equal(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']);
assert.deepEqual(result.sort(), ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']);
});
it('should return the intersection of all lists when threshold is 1', () => {
const result = libloki.serviceNodes.consolidateLists([
['a', 'b', 'c', 'd'],
['a', 'e', 'f', 'g'],
['a', 'h'],
], 1);
chai.expect(result).to.deep.equal(['a']);
assert.deepEqual(result, ['a']);
});
it('should return the elements that have an occurence >= the provided threshold', () => {
const result = libloki.serviceNodes.consolidateLists([
['a', 'b', 'c', 'd', 'e', 'f', 'g'],
@ -42,8 +52,9 @@ describe('ServiceNodes', () => {
['a', 'b', 'c', 'd', 'e', 'f', 'g'],
['a', 'b', 'c', 'd', 'e', 'g', 'h'],
], 3/4);
chai.expect(result).to.deep.equal(['a', 'b', 'c', 'd', 'e', 'f', 'g']);
assert.deepEqual(result, ['a', 'b', 'c', 'd', 'e', 'f', 'g']);
});
it('should work with sets as well', () => {
const result = libloki.serviceNodes.consolidateLists(new Set([
new Set(['a', 'b', 'c', 'd', 'e', 'f', 'g']),
@ -51,7 +62,7 @@ describe('ServiceNodes', () => {
new Set(['a', 'b', 'c', 'd', 'e', 'f', 'g']),
new Set(['a', 'b', 'c', 'd', 'e', 'g', 'h']),
]), 3/4);
chai.expect(result).to.deep.equal(['a', 'b', 'c', 'd', 'e', 'f', 'g']);
assert.deepEqual(result, ['a', 'b', 'c', 'd', 'e', 'f', 'g']);
});
});
});

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

@ -605,7 +605,7 @@ MessageReceiver.prototype.extend({
textsecure.storage.protocol
);
const fallBackSessionCipher = new libloki.FallBackSessionCipher(
const fallBackSessionCipher = new libloki.crypto.FallBackSessionCipher(
address
);
@ -1238,7 +1238,7 @@ MessageReceiver.prototype.extend({
);
}
await libloki.saveContactPreKeyBundle({
await libloki.storage.saveContactPreKeyBundle({
pubKey,
preKeyId,
signedKeyId,

@ -121,7 +121,7 @@ OutgoingMessage.prototype = {
}
return builder.processPreKey(device).then(async () => {
// TODO: only remove the keys that were used above!
await window.libloki.removeContactPreKeyBundle(number);
await window.libloki.storage.removeContactPreKeyBundle(number);
return true;
}
).catch(error => {
@ -288,7 +288,7 @@ OutgoingMessage.prototype = {
const address = new libsignal.SignalProtocolAddress(number, deviceId);
const ourKey = textsecure.storage.user.getNumber();
const options = {};
const fallBackCipher = new libloki.FallBackSessionCipher(address);
const fallBackCipher = new libloki.crypto.FallBackSessionCipher(address);
// Check if we need to attach the preKeys
let sessionCipher;
@ -297,7 +297,7 @@ OutgoingMessage.prototype = {
const isEndSession = flags === textsecure.protobuf.DataMessage.Flags.END_SESSION;
if (isFriendRequest || isEndSession) {
// Encrypt them with the fallback
const pkb = await libloki.getPreKeyBundleForContact(number);
const pkb = await libloki.storage.getPreKeyBundleForContact(number);
const preKeyBundleMessage = new textsecure.protobuf.PreKeyBundleMessage(pkb);
this.message.preKeyBundleMessage = preKeyBundleMessage;
window.log.info('attaching prekeys to outgoing message');

Loading…
Cancel
Save