Move libaxolotl out-of-tree

This commit is contained in:
Matt Corallo 2015-03-17 14:31:33 -07:00
parent e2e06b2d3c
commit d3c158f4cf
26 changed files with 884 additions and 64545 deletions

View file

@ -10,11 +10,6 @@ module.exports = function(grunt) {
components.push('components/' + bower.concat.app[i] + '/**/*.js');
}
var libaxolotlcomponents = [];
for (i in bower.concat.libaxolotl) {
libaxolotlcomponents.push('components/' + bower.concat.libaxolotl[i] + '/**/*.js');
}
var libtextsecurecomponents = [];
for (i in bower.concat.libtextsecure) {
libtextsecurecomponents.push('components/' + bower.concat.libtextsecure[i] + '/**/*.js');
@ -27,43 +22,10 @@ module.exports = function(grunt) {
src: components,
dest: 'js/components.js',
},
libaxolotlcomponents: {
src: libaxolotlcomponents,
dest: 'libaxolotl/components.js',
},
libtextsecurecomponents: {
src: libtextsecurecomponents,
dest: 'libtextsecure/components.js',
},
curve25519: {
src: [
'build/curve25519_compiled.js',
'build/curve25519.js',
],
dest: 'libaxolotl/curve25519_concat.js',
options: {
banner: ';(function(){\n',
footer: '\n})();'
}
},
webcrypto: {
src: [
'components/cryptojs/src/core.js',
'components/cryptojs/src/sha256.js',
'components/cryptojs/src/hmac.js',
'components/cryptojs/src/enc-base64.js',
'components/cryptojs/src/md5.js',
'components/cryptojs/src/evpkdf.js',
'components/cryptojs/src/cipher-core.js',
'components/cryptojs/src/aes.js',
'build/webcrypto.js'
],
dest: 'libaxolotl/webcrypto_concat.js',
options: {
banner: ';(function(){\n',
footer: '\n})();'
}
},
test: {
src: [
'components/mocha/mocha.js',
@ -72,25 +34,12 @@ module.exports = function(grunt) {
],
dest: 'test/test.js',
},
libaxolotl: {
src: [
'libaxolotl/curve25519_concat.js',
'libaxolotl/webcrypto_concat.js',
'libaxolotl/components.js',
'libaxolotl/crypto.js',
'libaxolotl/protocol.js',
'libaxolotl/protobufs.js',
'libaxolotl/session_storage.js',
],
dest: 'libtextsecure/libaxolotl_concat.js',
},
//TODO: Move errors back down?
libtextsecure: {
src: [
'libtextsecure/errors.js',
'libtextsecure/libaxolotl.js',
'libtextsecure/axolotl_wrapper.js',
'libtextsecure/libaxolotl_concat.js',
'libtextsecure/crypto.js',
'libtextsecure/storage.js',
@ -113,15 +62,6 @@ module.exports = function(grunt) {
'libtextsecure/test/_test.js'
],
dest: 'libtextsecure/test/test.js',
},
libaxolotltest: {
src: [
'components/mocha/mocha.js',
'components/chai/chai.js',
'components/jquery/**/*.js',
'libaxolotl/test/_test.js'
],
dest: 'libaxolotl/test/test.js',
}
},
sass: {
@ -206,7 +146,6 @@ module.exports = function(grunt) {
urls: [
'http://127.0.0.1:9999/test/index.html',
'http://127.0.0.1:9999/libtextsecure/test/index.html',
'http://127.0.0.1:9999/libaxolotl/test/index.html'
],
build: process.env.TRAVIS_JOB_ID,
browsers: [

View file

@ -134,11 +134,6 @@
"long",
"bytebuffer",
"protobuf"
],
"libaxolotl": [
"long",
"bytebuffer",
"protobuf"
]
}
}

File diff suppressed because it is too large Load diff

View file

@ -26,7 +26,7 @@
} else {
$('#init-setup').show().addClass('in');
$('#status').text("Connecting...");
axolotl.protocol.createIdentityKeyRecvSocket().then(function(cryptoInfo) {
textsecure.protocol_wrapper.createIdentityKeyRecvSocket().then(function(cryptoInfo) {
var qrCode = new QRCode(document.getElementById('qr'));
var socket = textsecure.api.getTempWebsocket();
new WebSocketResource(socket, function(request) {

File diff suppressed because it is too large Load diff

View file

@ -1,134 +0,0 @@
/* vim: ts=4:sw=4
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
;(function() {
window.axolotl = window.axolotl || {};
/*
* axolotl.crypto
* glues together various implementations into a single interface
* for all low-level crypto operations,
*/
window.axolotl.crypto = {
getRandomBytes: function(size) {
var array = new Uint8Array(size);
window.crypto.getRandomValues(array);
return array.buffer;
},
encrypt: function(key, data, iv) {
return window.crypto.subtle.importKey('raw', key, {name: 'AES-CBC'}, false, ['encrypt']).then(function(key) {
return window.crypto.subtle.encrypt({name: 'AES-CBC', iv: new Uint8Array(iv)}, key, data);
});
},
decrypt: function(key, data, iv) {
return window.crypto.subtle.importKey('raw', key, {name: 'AES-CBC'}, false, ['decrypt']).then(function(key) {
return window.crypto.subtle.decrypt({name: 'AES-CBC', iv: new Uint8Array(iv)}, key, data);
});
},
sign: function(key, data) {
return window.crypto.subtle.importKey('raw', key, {name: 'HMAC', hash: {name: 'SHA-256'}}, false, ['sign']).then(function(key) {
return window.crypto.subtle.sign( {name: 'HMAC', hash: 'SHA-256'}, key, data);
});
},
HKDF: function(input, salt, info) {
// Specific implementation of RFC 5869 that only returns the first 3 32-byte chunks
// TODO: We dont always need the third chunk, we might skip it
return window.axolotl.crypto.sign(salt, input).then(function(PRK) {
var infoBuffer = new ArrayBuffer(info.byteLength + 1 + 32);
var infoArray = new Uint8Array(infoBuffer);
infoArray.set(new Uint8Array(info), 32);
infoArray[infoArray.length - 1] = 1;
return window.axolotl.crypto.sign(PRK, infoBuffer.slice(32)).then(function(T1) {
infoArray.set(new Uint8Array(T1));
infoArray[infoArray.length - 1] = 2;
return window.axolotl.crypto.sign(PRK, infoBuffer).then(function(T2) {
infoArray.set(new Uint8Array(T2));
infoArray[infoArray.length - 1] = 3;
return window.axolotl.crypto.sign(PRK, infoBuffer).then(function(T3) {
return [ T1, T2, T3 ];
});
});
});
});
},
// Curve 25519 crypto
createKeyPair: function(privKey) {
if (privKey === undefined) {
privKey = axolotl.crypto.getRandomBytes(32);
}
if (privKey.byteLength != 32) {
throw new Error("Invalid private key");
}
return window.curve25519.keyPair(privKey).then(function(raw_keys) {
// prepend version byte
var origPub = new Uint8Array(raw_keys.pubKey);
var pub = new Uint8Array(33);
pub.set(origPub, 1);
pub[0] = 5;
return { pubKey: pub.buffer, privKey: raw_keys.privKey };
});
},
ECDHE: function(pubKey, privKey) {
pubKey = validatePubKeyFormat(pubKey);
if (privKey === undefined || privKey.byteLength != 32)
throw new Error("Invalid private key");
if (pubKey === undefined || pubKey.byteLength != 32)
throw new Error("Invalid public key");
return window.curve25519.sharedSecret(pubKey, privKey);
},
Ed25519Sign: function(privKey, message) {
if (privKey === undefined || privKey.byteLength != 32)
throw new Error("Invalid private key");
if (message === undefined)
throw new Error("Invalid message");
return window.curve25519.sign(privKey, message);
},
Ed25519Verify: function(pubKey, msg, sig) {
pubKey = validatePubKeyFormat(pubKey);
if (pubKey === undefined || pubKey.byteLength != 32)
throw new Error("Invalid public key");
if (msg === undefined)
throw new Error("Invalid message");
if (sig === undefined || sig.byteLength != 64)
throw new Error("Invalid signature");
return window.curve25519.verify(pubKey, msg, sig);
}
};
var validatePubKeyFormat = function(pubKey) {
if (pubKey === undefined || ((pubKey.byteLength != 33 || new Uint8Array(pubKey)[0] != 5) && pubKey.byteLength != 32))
throw new Error("Invalid public key");
if (pubKey.byteLength == 33) {
return pubKey.slice(1);
} else {
console.error("WARNING: Expected pubkey of length 33, please report the ST and client that generated the pubkey");
return pubKey;
}
};
})();

File diff suppressed because one or more lines are too long

View file

@ -1,18 +0,0 @@
;(function() {
function loadProtoBufs(filename) {
return dcodeIO.ProtoBuf.loadProtoFile({root: 'protos', file: filename}).build('textsecure');
};
var protocolMessages = loadProtoBufs('WhisperTextProtocol.proto');
var deviceMessages = loadProtoBufs('DeviceMessages.proto');
window.axolotl = window.axolotl || {};
window.axolotl.protobuf = {
WhisperMessage : protocolMessages.WhisperMessage,
PreKeyWhisperMessage : protocolMessages.PreKeyWhisperMessage,
DeviceInit : deviceMessages.DeviceInit,
IdentityKey : deviceMessages.IdentityKey,
DeviceControl : deviceMessages.DeviceControl,
ProvisionMessage : deviceMessages.ProvisionMessage,
};
})();

View file

@ -1,743 +0,0 @@
/* vim: ts=4:sw=4
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
;(function() {
'use strict';
window.axolotl = window.axolotl || {};
window.axolotl.protocol = function() {
var self = {};
/******************************
*** Random constants/utils ***
******************************/
// We consider messages lost after a week and might throw away keys at that point
// (also the time between signedPreKey regenerations)
var MESSAGE_LOST_THRESHOLD_MS = 1000*60*60*24*7;
function objectContainsKeys(object) {
var count = 0;
for (var key in object) {
count++;
break;
}
return count != 0;
}
/***************************
*** Key/session storage ***
***************************/
var crypto_storage = {};
crypto_storage.putKeyPair = function(keyName, keyPair) {
axolotl.api.storage.put("25519Key" + keyName, keyPair);
}
crypto_storage.getNewStoredKeyPair = function(keyName) {
return axolotl.crypto.createKeyPair().then(function(keyPair) {
crypto_storage.putKeyPair(keyName, keyPair);
return keyPair;
});
}
crypto_storage.getStoredKeyPair = function(keyName) {
var res = axolotl.api.storage.get("25519Key" + keyName);
if (res === undefined)
return undefined;
return { pubKey: toArrayBuffer(res.pubKey), privKey: toArrayBuffer(res.privKey) };
}
crypto_storage.removeStoredKeyPair = function(keyName) {
axolotl.api.storage.remove("25519Key" + keyName);
}
crypto_storage.getIdentityKey = function() {
return this.getStoredKeyPair("identityKey");
}
crypto_storage.saveSession = function(encodedNumber, session, registrationId) {
var record = axolotl.api.storage.sessions.get(encodedNumber);
if (record === undefined) {
if (registrationId === undefined)
throw new Error("Tried to save a session for an existing device that didn't exist");
else
record = new axolotl.sessions.RecipientRecord(session.indexInfo.remoteIdentityKey, registrationId);
}
var sessions = record._sessions;
if (record.identityKey === null)
record.identityKey = session.indexInfo.remoteIdentityKey;
if (getString(record.identityKey) !== getString(session.indexInfo.remoteIdentityKey))
throw new Error("Identity key changed at session save time");
var doDeleteSession = false;
if (session.indexInfo.closed != -1) {
doDeleteSession = (session.indexInfo.closed < (new Date().getTime() - MESSAGE_LOST_THRESHOLD_MS));
if (!doDeleteSession) {
var keysLeft = false;
for (var key in session) {
if (key != "indexInfo" && key != "oldRatchetList" && key != "currentRatchet") {
keysLeft = true;
break;
}
}
doDeleteSession = !keysLeft;
console.log((doDeleteSession ? "Deleting " : "Not deleting ") + "closed session which has not yet timed out");
} else
console.log("Deleting closed session due to timeout (created at " + session.indexInfo.closed + ")");
}
if (doDeleteSession)
delete sessions[getString(session.indexInfo.baseKey)];
else
sessions[getString(session.indexInfo.baseKey)] = session;
var openSessionRemaining = false;
for (var key in sessions)
if (sessions[key].indexInfo.closed == -1)
openSessionRemaining = true;
if (!openSessionRemaining) // Used as a flag to get new pre keys for the next session
record.registrationId = null;
else if (record.registrationId === null && registrationId !== undefined)
record.registrationId = registrationId;
else if (record.registrationId === null)
throw new Error("Had open sessions on a record that had no registrationId set");
var identityKey = axolotl.api.storage.identityKeys.get(encodedNumber);
if (identityKey === undefined)
axolotl.api.storage.identityKeys.put(encodedNumber, record.identityKey);
else if (getString(identityKey) !== getString(record.identityKey))
throw new Error("Tried to change identity key at save time");
axolotl.api.storage.sessions.put(encodedNumber, record);
}
var getSessions = function(encodedNumber) {
var record = axolotl.api.storage.sessions.get(encodedNumber);
if (record === undefined)
return undefined;
return record._sessions;
}
crypto_storage.getOpenSession = function(encodedNumber) {
var sessions = getSessions(encodedNumber);
if (sessions === undefined)
return undefined;
for (var key in sessions)
if (sessions[key].indexInfo.closed == -1)
return sessions[key];
return undefined;
}
crypto_storage.getSessionByRemoteEphemeralKey = function(encodedNumber, remoteEphemeralKey) {
var sessions = getSessions(encodedNumber);
if (sessions === undefined)
return undefined;
var searchKey = getString(remoteEphemeralKey);
var openSession = undefined;
for (var key in sessions) {
if (sessions[key].indexInfo.closed == -1) {
if (openSession !== undefined)
throw new Error("Datastore inconsistensy: multiple open sessions for " + encodedNumber);
openSession = sessions[key];
}
if (sessions[key][searchKey] !== undefined)
return sessions[key];
}
if (openSession !== undefined)
return openSession;
return undefined;
}
crypto_storage.getSessionOrIdentityKeyByBaseKey = function(encodedNumber, baseKey) {
var record = axolotl.api.storage.sessions.get(encodedNumber);
if (record === undefined) {
var identityKey = axolotl.api.storage.identityKeys.get(encodedNumber);
if (identityKey === undefined)
return undefined;
return { indexInfo: { remoteIdentityKey: identityKey } };
}
var sessions = record._sessions;
var preferredSession = record._sessions[getString(baseKey)];
if (preferredSession !== undefined)
return preferredSession;
if (record.identityKey !== undefined)
return { indexInfo: { remoteIdentityKey: record.identityKey } };
throw new Error("Datastore inconsistency: device was stored without identity key");
}
/*****************************
*** Internal Crypto stuff ***
*****************************/
var HKDF = function(input, salt, info) {
// HKDF for TextSecure has a bit of additional handling - salts always end up being 32 bytes
if (salt == '')
salt = new ArrayBuffer(32);
if (salt.byteLength != 32)
throw new Error("Got salt of incorrect length");
info = toArrayBuffer(info); // TODO: maybe convert calls?
return axolotl.crypto.HKDF(input, salt, info);
}
var verifyMAC = function(data, key, mac) {
return axolotl.crypto.sign(key, data).then(function(calculated_mac) {
if (!isEqual(calculated_mac, mac, true))
throw new Error("Bad MAC");
});
}
/******************************
*** Ratchet implementation ***
******************************/
var calculateRatchet = function(session, remoteKey, sending) {
var ratchet = session.currentRatchet;
return axolotl.crypto.ECDHE(remoteKey, toArrayBuffer(ratchet.ephemeralKeyPair.privKey)).then(function(sharedSecret) {
return HKDF(sharedSecret, toArrayBuffer(ratchet.rootKey), "WhisperRatchet").then(function(masterKey) {
if (sending)
session[getString(ratchet.ephemeralKeyPair.pubKey)] = { messageKeys: {}, chainKey: { counter: -1, key: masterKey[1] } };
else
session[getString(remoteKey)] = { messageKeys: {}, chainKey: { counter: -1, key: masterKey[1] } };
ratchet.rootKey = masterKey[0];
});
});
}
var initSession = function(isInitiator, ourEphemeralKey, ourSignedKey, encodedNumber, theirIdentityPubKey, theirEphemeralPubKey, theirSignedPubKey) {
var ourIdentityKey = crypto_storage.getIdentityKey();
if (isInitiator) {
if (ourSignedKey !== undefined)
throw new Error("Invalid call to initSession");
ourSignedKey = ourEphemeralKey;
} else {
if (theirSignedPubKey !== undefined)
throw new Error("Invalid call to initSession");
theirSignedPubKey = theirEphemeralPubKey;
}
var sharedSecret;
if (ourEphemeralKey === undefined || theirEphemeralPubKey === undefined)
sharedSecret = new Uint8Array(32 * 4);
else
sharedSecret = new Uint8Array(32 * 5);
for (var i = 0; i < 32; i++)
sharedSecret[i] = 0xff;
return axolotl.crypto.ECDHE(theirSignedPubKey, ourIdentityKey.privKey).then(function(ecRes1) {
function finishInit() {
return axolotl.crypto.ECDHE(theirSignedPubKey, ourSignedKey.privKey).then(function(ecRes) {
sharedSecret.set(new Uint8Array(ecRes), 32 * 3);
return HKDF(sharedSecret.buffer, '', "WhisperText").then(function(masterKey) {
var session = {currentRatchet: { rootKey: masterKey[0], lastRemoteEphemeralKey: theirSignedPubKey, previousCounter: 0 },
indexInfo: { remoteIdentityKey: theirIdentityPubKey, closed: -1 },
oldRatchetList: []
};
if (!isInitiator)
session.indexInfo.baseKey = theirEphemeralPubKey;
else
session.indexInfo.baseKey = ourEphemeralKey.pubKey;
// If we're initiating we go ahead and set our first sending ephemeral key now,
// otherwise we figure it out when we first maybeStepRatchet with the remote's ephemeral key
if (isInitiator) {
return axolotl.crypto.createKeyPair().then(function(ourSendingEphemeralKey) {
session.currentRatchet.ephemeralKeyPair = ourSendingEphemeralKey;
return calculateRatchet(session, theirSignedPubKey, true).then(function() {
return session;
});
});
} else {
session.currentRatchet.ephemeralKeyPair = ourSignedKey;
return session;
}
});
});
}
var promise;
if (ourEphemeralKey === undefined || theirEphemeralPubKey === undefined)
promise = Promise.resolve(new ArrayBuffer(0));
else
promise = axolotl.crypto.ECDHE(theirEphemeralPubKey, ourEphemeralKey.privKey);
return promise.then(function(ecRes4) {
sharedSecret.set(new Uint8Array(ecRes4), 32 * 4);
if (isInitiator)
return axolotl.crypto.ECDHE(theirIdentityPubKey, ourSignedKey.privKey).then(function(ecRes2) {
sharedSecret.set(new Uint8Array(ecRes1), 32);
sharedSecret.set(new Uint8Array(ecRes2), 32 * 2);
}).then(finishInit);
else
return axolotl.crypto.ECDHE(theirIdentityPubKey, ourSignedKey.privKey).then(function(ecRes2) {
sharedSecret.set(new Uint8Array(ecRes1), 32 * 2);
sharedSecret.set(new Uint8Array(ecRes2), 32)
}).then(finishInit);
});
});
}
var removeOldChains = function(session) {
// Sending ratchets are always removed when we step because we never need them again
// Receiving ratchets are either removed if we step with all keys used up to previousCounter
// and are otherwise added to the oldRatchetList, which we parse here and remove ratchets
// older than a week (we assume the message was lost and move on with our lives at that point)
var newList = [];
for (var i = 0; i < session.oldRatchetList.length; i++) {
var entry = session.oldRatchetList[i];
var ratchet = getString(entry.ephemeralKey);
console.log("Checking old chain with added time " + (entry.added/1000));
if ((!objectContainsKeys(session[ratchet].messageKeys) && (session[ratchet].chainKey === undefined || session[ratchet].chainKey.key === undefined))
|| entry.added < new Date().getTime() - MESSAGE_LOST_THRESHOLD_MS) {
delete session[ratchet];
console.log("...deleted");
} else
newList[newList.length] = entry;
}
session.oldRatchetList = newList;
}
var closeSession = function(session, sessionClosedByRemote) {
if (session.indexInfo.closed > -1)
return;
// After this has run, we can still receive messages on ratchet chains which
// were already open (unless we know we dont need them),
// but we cannot send messages or step the ratchet
// Delete current sending ratchet
delete session[getString(session.currentRatchet.ephemeralKeyPair.pubKey)];
// Move all receive ratchets to the oldRatchetList to mark them for deletion
for (var i in session) {
if (session[i].chainKey !== undefined && session[i].chainKey.key !== undefined) {
if (!sessionClosedByRemote)
session.oldRatchetList[session.oldRatchetList.length] = { added: new Date().getTime(), ephemeralKey: i };
else
delete session[i].chainKey.key;
}
}
// Delete current root key and our ephemeral key pair to disallow ratchet stepping
delete session.currentRatchet['rootKey'];
delete session.currentRatchet['ephemeralKeyPair'];
session.indexInfo.closed = new Date().getTime();
removeOldChains(session);
}
self.closeOpenSessionForDevice = function(encodedNumber) {
var session = crypto_storage.getOpenSession(encodedNumber);
if (session === undefined)
return;
closeSession(session);
crypto_storage.saveSession(encodedNumber, session);
}
var refreshPreKeys;
var initSessionFromPreKeyWhisperMessage = function(encodedNumber, message) {
var preKeyPair = crypto_storage.getStoredKeyPair("preKey" + message.preKeyId);
var signedPreKeyPair = crypto_storage.getStoredKeyPair("signedKey" + message.signedPreKeyId);
//TODO: Call refreshPreKeys when it looks like all our prekeys are used up?
var session = crypto_storage.getSessionOrIdentityKeyByBaseKey(encodedNumber, toArrayBuffer(message.baseKey));
var open_session = crypto_storage.getOpenSession(encodedNumber);
if (signedPreKeyPair === undefined) {
// Session may or may not be the right one, but if its not, we can't do anything about it
// ...fall through and let decryptWhisperMessage handle that case
if (session !== undefined && session.currentRatchet !== undefined)
return Promise.resolve([session, undefined]);
else
throw new Error("Missing Signed PreKey for PreKeyWhisperMessage");
}
if (session !== undefined) {
// Duplicate PreKeyMessage for session:
if (isEqual(session.indexInfo.baseKey, message.baseKey, false))
return Promise.resolve([session, undefined]);
// We already had a session/known identity key:
if (isEqual(session.indexInfo.remoteIdentityKey, message.identityKey, false)) {
// If the identity key matches the previous one, close the previous one and use the new one
if (open_session !== undefined)
closeSession(open_session); // To be returned and saved later
} else {
throw new Error('Unknown identity key');
}
}
return initSession(false, preKeyPair, signedPreKeyPair, encodedNumber, toArrayBuffer(message.identityKey), toArrayBuffer(message.baseKey), undefined)
.then(function(new_session) {
// Note that the session is not actually saved until the very end of decryptWhisperMessage
// ... to ensure that the sender actually holds the private keys for all reported pubkeys
return [new_session, function() {
if (open_session !== undefined)
crypto_storage.saveSession(encodedNumber, open_session);
crypto_storage.removeStoredKeyPair("preKey" + message.preKeyId);
}];
});;
}
var fillMessageKeys = function(chain, counter) {
if (chain.chainKey.counter + 1000 < counter) //TODO: maybe 1000 is too low/high in some cases?
return Promise.resolve(); // Stalker, much?
if (chain.chainKey.counter >= counter)
return Promise.resolve(); // Already calculated
if (chain.chainKey.key === undefined)
throw new Error("Got invalid request to extend chain after it was already closed");
var key = toArrayBuffer(chain.chainKey.key);
var byteArray = new Uint8Array(1);
byteArray[0] = 1;
return axolotl.crypto.sign(key, byteArray.buffer).then(function(mac) {
byteArray[0] = 2;
return axolotl.crypto.sign(key, byteArray.buffer).then(function(key) {
chain.messageKeys[chain.chainKey.counter + 1] = mac;
chain.chainKey.key = key
chain.chainKey.counter += 1;
return fillMessageKeys(chain, counter);
});
});
}
var maybeStepRatchet = function(session, remoteKey, previousCounter) {
if (session[getString(remoteKey)] !== undefined)
return Promise.resolve();
var ratchet = session.currentRatchet;
var finish = function() {
return calculateRatchet(session, remoteKey, false).then(function() {
// Now swap the ephemeral key and calculate the new sending chain
var previousRatchet = getString(ratchet.ephemeralKeyPair.pubKey);
if (session[previousRatchet] !== undefined) {
ratchet.previousCounter = session[previousRatchet].chainKey.counter;
delete session[previousRatchet];
}
return axolotl.crypto.createKeyPair().then(function(keyPair) {
ratchet.ephemeralKeyPair = keyPair;
return calculateRatchet(session, remoteKey, true).then(function() {
ratchet.lastRemoteEphemeralKey = remoteKey;
});
});
});
}
var previousRatchet = session[getString(ratchet.lastRemoteEphemeralKey)];
if (previousRatchet !== undefined) {
return fillMessageKeys(previousRatchet, previousCounter).then(function() {
delete previousRatchet.chainKey.key;
if (!objectContainsKeys(previousRatchet.messageKeys))
delete session[getString(ratchet.lastRemoteEphemeralKey)];
else
session.oldRatchetList[session.oldRatchetList.length] = { added: new Date().getTime(), ephemeralKey: ratchet.lastRemoteEphemeralKey };
}).then(finish);
} else
return finish();
}
var doDecryptWhisperMessage = function(encodedNumber, messageBytes, session, registrationId) {
if (messageBytes[0] != String.fromCharCode((3 << 4) | 3))
throw new Error("Bad version number on WhisperMessage");
var messageProto = messageBytes.substring(1, messageBytes.length - 8);
var mac = messageBytes.substring(messageBytes.length - 8, messageBytes.length);
var message = axolotl.protobuf.WhisperMessage.decode(messageProto, 'binary');
var remoteEphemeralKey = toArrayBuffer(message.ephemeralKey);
if (session === undefined) {
var session = crypto_storage.getSessionByRemoteEphemeralKey(encodedNumber, remoteEphemeralKey);
if (session === undefined)
throw new Error("No session found to decrypt message from " + encodedNumber);
}
return maybeStepRatchet(session, remoteEphemeralKey, message.previousCounter).then(function() {
var chain = session[getString(message.ephemeralKey)];
return fillMessageKeys(chain, message.counter).then(function() {
return HKDF(toArrayBuffer(chain.messageKeys[message.counter]), '', "WhisperMessageKeys").then(function(keys) {
delete chain.messageKeys[message.counter];
var messageProtoArray = toArrayBuffer(messageProto);
var macInput = new Uint8Array(messageProtoArray.byteLength + 33*2 + 1);
macInput.set(new Uint8Array(toArrayBuffer(session.indexInfo.remoteIdentityKey)));
macInput.set(new Uint8Array(toArrayBuffer(crypto_storage.getIdentityKey().pubKey)), 33);
macInput[33*2] = (3 << 4) | 3;
macInput.set(new Uint8Array(messageProtoArray), 33*2 + 1);
return verifyMAC(macInput.buffer, keys[1], mac).then(function() {
return window.axolotl.crypto.decrypt(keys[0], toArrayBuffer(message.ciphertext), keys[2].slice(0, 16))
.then(function(paddedPlaintext) {
paddedPlaintext = new Uint8Array(paddedPlaintext);
var plaintext;
for (var i = paddedPlaintext.length - 1; i >= 0; i--) {
if (paddedPlaintext[i] == 0x80) {
plaintext = new Uint8Array(i);
plaintext.set(paddedPlaintext.subarray(0, i));
plaintext = plaintext.buffer;
break;
} else if (paddedPlaintext[i] != 0x00)
throw new Error('Invalid padding');
}
delete session['pendingPreKey'];
removeOldChains(session);
crypto_storage.saveSession(encodedNumber, session, registrationId);
return [plaintext, function() {
closeSession(session, true);
removeOldChains(session);
crypto_storage.saveSession(encodedNumber, session);
}];
});
});
});
});
});
}
/*************************
*** Public crypto API ***
*************************/
//TODO: SHARP EDGE HERE
//XXX: Also, you MUST call the session close function before processing another message....except its a promise...so you literally cant!
// returns decrypted plaintext and a function that must be called if the message indicates session close
self.decryptWhisperMessage = function(encodedNumber, messageBytes, session) {
return doDecryptWhisperMessage(encodedNumber, messageBytes, session);
}
// Inits a session (maybe) and then decrypts the message
self.handlePreKeyWhisperMessage = function(from, encodedMessage) {
var preKeyProto = axolotl.protobuf.PreKeyWhisperMessage.decode(encodedMessage, 'binary');
return initSessionFromPreKeyWhisperMessage(from, preKeyProto).then(function(sessions) {
return doDecryptWhisperMessage(from, getString(preKeyProto.message), sessions[0], preKeyProto.registrationId).then(function(result) {
if (sessions[1] !== undefined)
sessions[1]();
return result;
});
});
}
// return Promise(encoded [PreKey]WhisperMessage)
self.encryptMessageFor = function(deviceObject, pushMessageContent) {
var session = crypto_storage.getOpenSession(deviceObject.encodedNumber);
var hadSession = session !== undefined;
var doEncryptPushMessageContent = function() {
var msg = new axolotl.protobuf.WhisperMessage();
var plaintext = toArrayBuffer(pushMessageContent.encode());
var paddedPlaintext = new Uint8Array(Math.ceil((plaintext.byteLength + 1) / 160.0) * 160 - 1);
paddedPlaintext.set(new Uint8Array(plaintext));
paddedPlaintext[plaintext.byteLength] = 0x80;
msg.ephemeralKey = toArrayBuffer(session.currentRatchet.ephemeralKeyPair.pubKey);
var chain = session[getString(msg.ephemeralKey)];
return fillMessageKeys(chain, chain.chainKey.counter + 1).then(function() {
return HKDF(toArrayBuffer(chain.messageKeys[chain.chainKey.counter]), '', "WhisperMessageKeys").then(function(keys) {
delete chain.messageKeys[chain.chainKey.counter];
msg.counter = chain.chainKey.counter;
msg.previousCounter = session.currentRatchet.previousCounter;
return window.axolotl.crypto.encrypt(keys[0], paddedPlaintext.buffer, keys[2].slice(0, 16)).then(function(ciphertext) {
msg.ciphertext = ciphertext;
var encodedMsg = toArrayBuffer(msg.encode());
var macInput = new Uint8Array(encodedMsg.byteLength + 33*2 + 1);
macInput.set(new Uint8Array(toArrayBuffer(crypto_storage.getIdentityKey().pubKey)));
macInput.set(new Uint8Array(toArrayBuffer(session.indexInfo.remoteIdentityKey)), 33);
macInput[33*2] = (3 << 4) | 3;
macInput.set(new Uint8Array(encodedMsg), 33*2 + 1);
return axolotl.crypto.sign(keys[1], macInput.buffer).then(function(mac) {
var result = new Uint8Array(encodedMsg.byteLength + 9);
result[0] = (3 << 4) | 3;
result.set(new Uint8Array(encodedMsg), 1);
result.set(new Uint8Array(mac, 0, 8), encodedMsg.byteLength + 1);
removeOldChains(session);
crypto_storage.saveSession(deviceObject.encodedNumber, session, !hadSession ? deviceObject.registrationId : undefined);
return result;
});
});
});
});
}
var preKeyMsg = new axolotl.protobuf.PreKeyWhisperMessage();
preKeyMsg.identityKey = toArrayBuffer(crypto_storage.getIdentityKey().pubKey);
preKeyMsg.registrationId = axolotl.api.getMyRegistrationId();
if (session === undefined) {
var deviceIdentityKey = toArrayBuffer(deviceObject.identityKey);
var deviceSignedKey = toArrayBuffer(deviceObject.signedKey);
return axolotl.crypto.Ed25519Verify(deviceIdentityKey, deviceSignedKey, toArrayBuffer(deviceObject.signedKeySignature)).then(function() {
return axolotl.crypto.createKeyPair().then(function(baseKey) {
preKeyMsg.preKeyId = deviceObject.preKeyId;
preKeyMsg.signedPreKeyId = deviceObject.signedKeyId;
preKeyMsg.baseKey = toArrayBuffer(baseKey.pubKey);
return initSession(true, baseKey, undefined, deviceObject.encodedNumber,
deviceIdentityKey, toArrayBuffer(deviceObject.preKey), deviceSignedKey)
.then(function(new_session) {
session = new_session;
session.pendingPreKey = { preKeyId: deviceObject.preKeyId, signedKeyId: deviceObject.signedKeyId, baseKey: baseKey.pubKey };
return doEncryptPushMessageContent().then(function(message) {
preKeyMsg.message = message;
var result = String.fromCharCode((3 << 4) | 3) + getString(preKeyMsg.encode());
return {type: 3, body: result};
});
});
});
});
} else
return doEncryptPushMessageContent().then(function(message) {
if (session.pendingPreKey !== undefined) {
preKeyMsg.baseKey = toArrayBuffer(session.pendingPreKey.baseKey);
preKeyMsg.preKeyId = session.pendingPreKey.preKeyId;
preKeyMsg.signedPreKeyId = session.pendingPreKey.signedKeyId;
preKeyMsg.message = message;
var result = String.fromCharCode((3 << 4) | 3) + getString(preKeyMsg.encode());
return {type: 3, body: result};
} else
return {type: 1, body: getString(message)};
});
}
var GENERATE_KEYS_KEYS_GENERATED = 100;
self.generateKeys = function() {
var identityKeyPair = crypto_storage.getIdentityKey();
var identityKeyCalculated = function(identityKeyPair) {
var firstPreKeyId = axolotl.api.storage.get("maxPreKeyId", 0);
axolotl.api.storage.put("maxPreKeyId", firstPreKeyId + GENERATE_KEYS_KEYS_GENERATED);
var signedKeyId = axolotl.api.storage.get("signedKeyId", 0);
axolotl.api.storage.put("signedKeyId", signedKeyId + 1);
var keys = {};
keys.identityKey = identityKeyPair.pubKey;
keys.preKeys = [];
var generateKey = function(keyId) {
return crypto_storage.getNewStoredKeyPair("preKey" + keyId, false).then(function(keyPair) {
keys.preKeys[keyId] = {keyId: keyId, publicKey: keyPair.pubKey};
});
};
var promises = [];
for (var i = firstPreKeyId; i < firstPreKeyId + GENERATE_KEYS_KEYS_GENERATED; i++)
promises[i] = generateKey(i);
promises[firstPreKeyId + GENERATE_KEYS_KEYS_GENERATED] = crypto_storage.getNewStoredKeyPair("signedKey" + signedKeyId).then(function(keyPair) {
return axolotl.crypto.Ed25519Sign(identityKeyPair.privKey, keyPair.pubKey).then(function(sig) {
keys.signedPreKey = {keyId: signedKeyId, publicKey: keyPair.pubKey, signature: sig};
});
});
//TODO: Process by date added and agressively call generateKeys when we get near maxPreKeyId in a message
crypto_storage.removeStoredKeyPair("signedKey" + (signedKeyId - 2));
return Promise.all(promises).then(function() {
axolotl.api.storage.put("lastPreKeyUpdate", Date.now());
return keys;
});
}
if (identityKeyPair === undefined)
return crypto_storage.getNewStoredKeyPair("identityKey").then(function(keyPair) { return identityKeyCalculated(keyPair); });
else
return identityKeyCalculated(identityKeyPair);
}
refreshPreKeys = function() {
self.generateKeys().then(function(keys) {
console.log("Pre Keys updated!");
return axolotl.api.updateKeys(keys);
}).catch(function(e) {
//TODO: Notify the user somehow???
console.error(e);
});
}
window.setInterval(function() {
// Note that this will not ever run until generateKeys has been called at least once
if (axolotl.api.storage.get("lastPreKeyUpdate", Date.now()) < Date.now() - MESSAGE_LOST_THRESHOLD_MS)
refreshPreKeys();
}, 60 * 1000);
self.createIdentityKeyRecvSocket = function() {
var socketInfo = {};
var keyPair;
socketInfo.decryptAndHandleDeviceInit = function(deviceInit) {
var masterEphemeral = toArrayBuffer(deviceInit.publicKey);
var message = toArrayBuffer(deviceInit.body);
return axolotl.crypto.ECDHE(masterEphemeral, keyPair.privKey).then(function(ecRes) {
return HKDF(ecRes, '', "TextSecure Provisioning Message").then(function(keys) {
if (new Uint8Array(message)[0] != 1)
throw new Error("Bad version number on ProvisioningMessage");
var iv = message.slice(1, 16 + 1);
var mac = message.slice(message.byteLength - 32, message.byteLength);
var ivAndCiphertext = message.slice(0, message.byteLength - 32);
var ciphertext = message.slice(16 + 1, message.byteLength - 32);
return verifyMAC(ivAndCiphertext, keys[1], mac).then(function() {
return window.axolotl.crypto.decrypt(keys[0], ciphertext, iv).then(function(plaintext) {
var identityKeyMsg = axolotl.protobuf.ProvisionMessage.decode(plaintext);
return axolotl.crypto.createKeyPair(toArrayBuffer(identityKeyMsg.identityKeyPrivate)).then(function(identityKeyPair) {
if (crypto_storage.getStoredKeyPair("identityKey") !== undefined)
throw new Error("Tried to overwrite identity key");
crypto_storage.putKeyPair("identityKey", identityKeyPair);
identityKeyMsg.identityKeyPrivate = null;
return identityKeyMsg;
});
});
});
});
});
}
return axolotl.crypto.createKeyPair().then(function(newKeyPair) {
keyPair = newKeyPair;
socketInfo.pubKey = keyPair.pubKey;
return socketInfo;
});
}
return self;
}();
})();

View file

@ -1,53 +0,0 @@
/* vim: ts=4:sw=4
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
;(function() {
'use strict';
window.axolotl = window.axolotl || {};
var RecipientRecord = function(identityKey, registrationId) {
this._sessions = {};
this.identityKey = identityKey !== undefined ? getString(identityKey) : null;
this.registrationId = registrationId;
if (this.registrationId === undefined || typeof this.registrationId !== "number")
this.registrationId = null;
};
RecipientRecord.prototype.serialize = function() {
return textsecure.utils.jsonThing({sessions: this._sessions, registrationId: this.registrationId, identityKey: this.identityKey});
}
RecipientRecord.prototype.deserialize = function(serialized) {
var data = JSON.parse(serialized);
this._sessions = data.sessions;
if (this._sessions === undefined || this._sessions === null || typeof this._sessions !== "object" || Array.isArray(this._sessions))
throw new Error("Error deserializing RecipientRecord");
this.identityKey = data.identityKey;
this.registrationId = data.registrationId;
if (this.identityKey === undefined || this.registrationId === undefined)
throw new Error("Error deserializing RecipientRecord");
}
RecipientRecord.prototype.haveOpenSession = function() {
return this.registrationId !== null;
}
window.axolotl.sessions = {
RecipientRecord: RecipientRecord,
};
})();

View file

@ -1,69 +0,0 @@
/*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
mocha.setup("bdd");
window.assert = chai.assert;
(function() {
var OriginalReporter = mocha._reporter;
var SauceReporter = function(runner) {
var failedTests = [];
runner.on('end', function() {
window.mochaResults = runner.stats;
window.mochaResults.reports = failedTests;
});
runner.on('fail', function(test, err) {
var flattenTitles = function(test) {
var titles = [];
while (test.parent.title) {
titles.push(test.parent.title);
test = test.parent;
}
return titles.reverse();
};
failedTests.push({
name: test.title,
result: false,
message: err.message,
stack: err.stack,
titles: flattenTitles(test)
});
});
new OriginalReporter(runner);
};
SauceReporter.prototype = OriginalReporter.prototype;
mocha.reporter(SauceReporter);
}());
/*
* global helpers for tests
*/
function assertEqualArrayBuffers(ab1, ab2) {
assert.deepEqual(new Uint8Array(ab1), new Uint8Array(ab2));
};
function hexToArrayBuffer(str) {
var ret = new ArrayBuffer(str.length / 2);
var array = new Uint8Array(ret);
for (var i = 0; i < str.length/2; i++)
array[i] = parseInt(str.substr(i*2, 2), 16);
return ret;
};

File diff suppressed because one or more lines are too long

View file

@ -1,158 +0,0 @@
/* vim: ts=4:sw=4
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
'use strict';
window.assert = chai.assert;
describe("Crypto", function() {
describe("Encrypt AES-CBC", function() {
it('works', function(done) {
var key = hexToArrayBuffer('603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4');
var iv = hexToArrayBuffer('000102030405060708090a0b0c0d0e0f');
var plaintext = hexToArrayBuffer('6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710');
var ciphertext = hexToArrayBuffer('f58c4c04d6e5f1ba779eabfb5f7bfbd69cfc4e967edb808d679f777bc6702c7d39f23369a9d9bacfa530e26304231461b2eb05e2c39be9fcda6c19078c6a9d1b3f461796d6b0d6b2e0c2a72b4d80e644');
window.axolotl.crypto.encrypt(key, plaintext, iv).then(function(result) {
assertEqualArrayBuffers(result, ciphertext);
}).then(done).catch(done);
});
});
describe("Decrypt AES-CBC", function() {
it('works', function(done) {
var key = hexToArrayBuffer('603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4');
var iv = hexToArrayBuffer('000102030405060708090a0b0c0d0e0f');
var plaintext = hexToArrayBuffer('6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710');
var ciphertext = hexToArrayBuffer('f58c4c04d6e5f1ba779eabfb5f7bfbd69cfc4e967edb808d679f777bc6702c7d39f23369a9d9bacfa530e26304231461b2eb05e2c39be9fcda6c19078c6a9d1b3f461796d6b0d6b2e0c2a72b4d80e644');
window.axolotl.crypto.decrypt(key, ciphertext, iv).then(function(result) {
assertEqualArrayBuffers(result, plaintext);
}).then(done).catch(done);
});
});
describe("HMAC SHA-256", function() {
it("works", function(done) {
var key = hexToArrayBuffer('6f35628d65813435534b5d67fbdb54cb33403d04e843103e6399f806cb5df95febbdd61236f33245');
var input = hexToArrayBuffer('752cff52e4b90768558e5369e75d97c69643509a5e5904e0a386cbe4d0970ef73f918f675945a9aefe26daea27587e8dc909dd56fd0468805f834039b345f855cfe19c44b55af241fff3ffcd8045cd5c288e6c4e284c3720570b58e4d47b8feeedc52fd1401f698a209fccfa3b4c0d9a797b046a2759f82a54c41ccd7b5f592b');
var mac = hexToArrayBuffer('05d1243e6465ed9620c9aec1c351a186');
window.axolotl.crypto.sign(key, input).then(function(result) {
assertEqualArrayBuffers(result.slice(0, mac.byteLength), mac);
}).then(done).catch(done);
});
});
describe("HKDF", function() {
it('works', function(done) {
// HMAC RFC5869 Test vectors
var T1 = hexToArrayBuffer("3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf");
var T2 = hexToArrayBuffer("34007208d5b887185865");
var IKM = new Uint8Array(new ArrayBuffer(22));
for (var i = 0; i < 22; i++)
IKM[i] = 11;
var salt = new Uint8Array(new ArrayBuffer(13));
for (var i = 0; i < 13; i++)
salt[i] = i;
var info = new Uint8Array(new ArrayBuffer(10));
for (var i = 0; i < 10; i++)
info[i] = 240 + i;
return axolotl.crypto.HKDF(IKM.buffer, salt.buffer, info.buffer).then(function(OKM){
assertEqualArrayBuffers(OKM[0], T1);
assertEqualArrayBuffers(OKM[1].slice(0, 10), T2);
}).then(done).catch(done);
});
});
var alice_bytes = hexToArrayBuffer("77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a");
var alice_priv = hexToArrayBuffer("70076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c6a");
var alice_pub = hexToArrayBuffer("058520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a");
var bob_bytes = hexToArrayBuffer("5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb");
var bob_priv = hexToArrayBuffer("58ab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e06b");
var bob_pub = hexToArrayBuffer("05de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f");
var shared_sec = hexToArrayBuffer("4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742");
describe("createKeyPair", function() {
it ('converts alice private keys to a keypair', function(done) {
axolotl.crypto.createKeyPair(alice_bytes).then(function(keypair) {
assertEqualArrayBuffers(keypair.privKey, alice_priv);
assertEqualArrayBuffers(keypair.pubKey, alice_pub);
done();
}).catch(done);
});
it ('converts bob private keys to a keypair', function(done) {
axolotl.crypto.createKeyPair(bob_bytes).then(function(keypair) {
assertEqualArrayBuffers(keypair.privKey, bob_priv);
assertEqualArrayBuffers(keypair.pubKey, bob_pub);
done();
}).catch(done);
});
it ('generates a key if one is not provided', function(done) {
axolotl.crypto.createKeyPair().then(function(keypair) {
assert.strictEqual(keypair.privKey.byteLength, 32);
assert.strictEqual(keypair.pubKey.byteLength, 33);
assert.strictEqual(new Uint8Array(keypair.pubKey)[0], 5);
done();
}).catch(done);
});
});
describe("ECDHE", function() {
it("computes the shared secret for alice", function(done) {
axolotl.crypto.ECDHE(bob_pub, alice_priv).then(function(secret) {
assertEqualArrayBuffers(shared_sec, secret);
done();
}).catch(done);
});
it("computes the shared secret for bob", function(done) {
axolotl.crypto.ECDHE(alice_pub, bob_priv).then(function(secret) {
assertEqualArrayBuffers(shared_sec, secret);
done();
}).catch(done);
});
});
var priv = hexToArrayBuffer("48a8892cc4e49124b7b57d94fa15becfce071830d6449004685e387c62409973");
var pub = hexToArrayBuffer("0555f1bfede27b6a03e0dd389478ffb01462e5c52dbbac32cf870f00af1ed9af3a");
var msg = hexToArrayBuffer("617364666173646661736466");
var sig = hexToArrayBuffer("2bc06c745acb8bae10fbc607ee306084d0c28e2b3bb819133392473431291fd0dfa9c7f11479996cf520730d2901267387e08d85bbf2af941590e3035a545285");
describe("Ed25519Sign", function() {
// Some self-generated test vectors
it('works', function(done) {
return axolotl.crypto.Ed25519Sign(priv, msg).then(function(sigCalc) {
assertEqualArrayBuffers(sig, sigCalc);
}).then(done).catch(done);
});
});
describe("Ed25519Verify", function() {
it("throws on bad signature", function(done) {
var badsig = sig.slice(0);
new Uint8Array(badsig).set([0], 0);
axolotl.crypto.Ed25519Verify(pub, msg, badsig).catch(function(e) {
if (e.message === 'Invalid signature') {
done();
} else { throw e; }
}).catch(done);
});
it("does not throw on good signature", function(done) {
return axolotl.crypto.Ed25519Verify(pub, msg, sig).then(done).catch(done);
});
});
});

View file

@ -1,53 +0,0 @@
'use strict';
var testSessionMap = {};
var testIdentityKeysMap = {};
;(function() {
window.axolotl = window.axolotl || {};
window.axolotl.api = {
getMyRegistrationId: function() {
return window.myRegistrationId;
},
storage: {
put: function(key, value) {
if (value === undefined)
throw new Error("Tried to store undefined");
localStorage.setItem(key, textsecure.utils.jsonThing(value));
},
get: function(key, defaultValue) {
var value = localStorage.getItem(key);
if (value === null)
return defaultValue;
return JSON.parse(value);
},
remove: function(key) {
localStorage.removeItem(key);
},
identityKeys: {
get: function(identifier) {
return testIdentityKeysMap[identifier];
},
put: function(identifier, identityKey) {
testIdentityKeysMap[identifier] = identityKey;
},
},
sessions: {
get: function(identifier) {
return testSessionMap[identifier];
},
put: function(identifier, record) {
testSessionMap[identifier] = record;
}
}
},
updateKeys: function(keys) {
return textsecure.api.registerKeys(keys).catch(function(e) {
//TODO: Notify the user somehow?
console.error(e);
});
},
};
})();

View file

@ -1,50 +0,0 @@
<!--This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<html>
<head>
<title>libTextSecure test runner</title>
<link rel="stylesheet" href="../../components/mocha/mocha.css" />
</head>
<body>
<h2>Run this out of the chrome-plugin:// namespace (and expect plugin state to be cleared/corrupted), not file://</h2>
<div id="mocha">
</div>
<div id="tests">
</div>
<script type="text/javascript" src="test.js"></script>
<script type="text/javascript" src="blanket_mocha.js"></script>
<script type="text/javascript" src="../curve25519_concat.js"></script>
<script type="text/javascript" src="../webcrypto_concat.js"></script>
<script type="text/javascript" src="../components.js"></script>
<!-- TODO: Remove the following -->
<script type="text/javascript" src="temp_helpers.js"></script>
<script type="text/javascript" src="../../libtextsecure/protobufs.js"></script>
<script type="text/javascript" src="../crypto.js" data-cover></script>
<script type="text/javascript" src="../protocol.js" data-cover></script>
<script type="text/javascript" src="../protobufs.js" data-cover></script>
<script type="text/javascript" src="../session_storage.js" data-cover></script>
<script type="text/javascript" src="fake_api.js"></script>
<script type="text/javascript" src="crypto_test.js"></script>
<script type="text/javascript" src="testvectors.js"></script>
<script type="text/javascript" src="protocol_test.js"></script>
</body>
</html>

View file

@ -1,237 +0,0 @@
/* vim: ts=4:sw=4
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
'use strict';
describe('Protocol', function() {
describe("Identity and Pre Key Creation", function() {
this.timeout(200000);
before(function() { localStorage.clear(); });
after(function() { localStorage.clear(); });
it ('works', function(done) {
localStorage.clear();
return axolotl.protocol.generateKeys().then(function() {
assert.isDefined(axolotl.api.storage.get("25519KeyidentityKey"));
assert.isDefined(axolotl.api.storage.get("25519KeysignedKey0"));
for (var i = 0; i < 100; i++) {
assert.isDefined(axolotl.api.storage.get("25519KeypreKey" + i));
}
var origIdentityKey = getString(axolotl.api.storage.get("25519KeyidentityKey").privKey);
return axolotl.protocol.generateKeys().then(function() {
assert.isDefined(axolotl.api.storage.get("25519KeyidentityKey"));
assert.equal(getString(axolotl.api.storage.get("25519KeyidentityKey").privKey), origIdentityKey);
assert.isDefined(axolotl.api.storage.get("25519KeysignedKey0"));
assert.isDefined(axolotl.api.storage.get("25519KeysignedKey1"));
for (var i = 0; i < 200; i++) {
assert.isDefined(axolotl.api.storage.get("25519KeypreKey" + i));
}
return axolotl.protocol.generateKeys().then(function() {
assert.isDefined(axolotl.api.storage.get("25519KeyidentityKey"));
assert.equal(getString(axolotl.api.storage.get("25519KeyidentityKey").privKey), origIdentityKey);
assert.isUndefined(axolotl.api.storage.get("25519KeysignedKey0"));
assert.isDefined(axolotl.api.storage.get("25519KeysignedKey1"));
assert.isDefined(axolotl.api.storage.get("25519KeysignedKey2"));
for (var i = 0; i < 300; i++) {
assert.isDefined(axolotl.api.storage.get("25519KeypreKey" + i));
}
});
});
}).then(done).catch(done);
});
});
describe("Axolotl", function() {
var runAxolotlTest = function(v) {
var origCreateKeyPair = axolotl.crypto.createKeyPair;
var doStep;
var stepDone;
stepDone = function(res) {
if (!res || privKeyQueue.length != 0) {
axolotl.crypto.createKeyPair = origCreateKeyPair;
return false;
} else if (step == v.length) {
axolotl.crypto.createKeyPair = origCreateKeyPair;
return true;
} else
return doStep().then(stepDone);
}
var privKeyQueue = [];
axolotl.crypto.createKeyPair = function(privKey) {
if (privKey !== undefined)
return origCreateKeyPair(privKey);
if (privKeyQueue.length == 0)
throw new Error('Out of private keys');
else {
var privKey = privKeyQueue.shift();
return axolotl.crypto.createKeyPair(privKey).then(function(keyPair) {
var a = btoa(getString(keyPair.privKey)); var b = btoa(getString(privKey));
if (getString(keyPair.privKey) != getString(privKey))
throw new Error('Failed to rederive private key!');
else
return keyPair;
});
}
}
var deviceObject = {encodedNumber: "SNOWDEN.1"};
var step = 0;
var doStep = function() {
var data = v[step][1];
switch(v[step++][0]) {
case "receiveMessage":
var postLocalKeySetup = function() {
if (data.newEphemeralKey !== undefined)
privKeyQueue.push(data.newEphemeralKey);
try {
var checkResult = function(res) {
res = textsecure.protobuf.PushMessageContent.decode(res[0]);
//TODO: Handle END_SESSION here (just like libtextsecure.protocol_wrapper
if (data.expectTerminateSession)
return res.flags == textsecure.protobuf.PushMessageContent.Flags.END_SESSION;
return res.body == data.expectedSmsText;
}
var checkException = function(e) {
if (data.expectException)
return true;
throw e;
}
if (data.type == textsecure.protobuf.IncomingPushMessageSignal.Type.CIPHERTEXT)
return axolotl.protocol.decryptWhisperMessage("SNOWDEN.1", getString(data.message)).then(checkResult).catch(checkException);
else if (data.type == textsecure.protobuf.IncomingPushMessageSignal.Type.PREKEY_BUNDLE) {
if (getString(data.message).charCodeAt(0) != ((3 << 4) | 3))
throw new Error("Bad version byte");
return axolotl.protocol.handlePreKeyWhisperMessage("SNOWDEN.1", getString(data.message).substring(1)).then(checkResult).catch(checkException);
} else
return Promise.reject(new Error("Unknown data type in test vector"));
} catch(e) {
if (data.expectException)
return Promise.resolve(true);
throw e;
}
}
if (data.ourIdentityKey !== undefined)
return axolotl.crypto.createKeyPair(data.ourIdentityKey).then(function(keyPair) {
axolotl.api.storage.put("25519KeyidentityKey", keyPair);
return axolotl.crypto.createKeyPair(data.ourSignedPreKey).then(function(keyPair) {
axolotl.api.storage.put("25519KeysignedKey" + data.signedPreKeyId, keyPair);
if (data.ourPreKey !== undefined)
return axolotl.crypto.createKeyPair(data.ourPreKey).then(function(keyPair) {
axolotl.api.storage.put("25519KeypreKey" + data.preKeyId, keyPair);
return postLocalKeySetup();
});
else
return postLocalKeySetup();
});
});
else
return postLocalKeySetup();
case "sendMessage":
var postLocalKeySetup = function() {
if (data.registrationId !== undefined)
window.myRegistrationId = data.registrationId;
if (data.getKeys !== undefined) {
deviceObject = {encodedNumber: "SNOWDEN.1",
identityKey: data.getKeys.identityKey,
preKey: data.getKeys.devices[0].preKey.publicKey,
preKeyId: data.getKeys.devices[0].preKey.keyId,
signedKey: data.getKeys.devices[0].signedPreKey.publicKey,
signedKeyId: data.getKeys.devices[0].signedPreKey.keyId,
signedKeySignature: data.getKeys.devices[0].signedPreKey.signature,
registrationId: data.getKeys.devices[0].signedPreKey.keyId
};
}
var checkMessage = function(msg) {
//XXX: This should be all we do: isEqual(data.expectedCiphertext, encryptedMsg, false);
var encryptedMsg = msg.body;
if (msg.type == 1) {
var expected = getString(data.expectedCiphertext);
var decoded = axolotl.protobuf.WhisperMessage.decode(expected.substring(1, expected.length - 8), 'binary');
var result = getString(encryptedMsg);
return getString(decoded.encode()) == result.substring(1, result.length - 8);
} else {
var decoded = axolotl.protobuf.PreKeyWhisperMessage.decode(getString(data.expectedCiphertext).substr(1), 'binary');
var result = getString(encryptedMsg).substring(1);
return getString(decoded.encode()) == result;
}
}
var proto = new textsecure.protobuf.PushMessageContent();
if (data.endSession) {
proto.flags = textsecure.protobuf.PushMessageContent.Flags.END_SESSION;
proto.body = "TERMINATE";
} else
proto.body = data.smsText;
return axolotl.protocol.encryptMessageFor(deviceObject, proto).then(checkMessage)
.then(function(res) {
if (data.endSession)
axolotl.protocol.closeOpenSessionForDevice("SNOWDEN.1");
return res;
});
}
if (data.ourBaseKey !== undefined)
privKeyQueue.push(data.ourBaseKey);
if (data.ourEphemeralKey !== undefined)
privKeyQueue.push(data.ourEphemeralKey);
if (data.ourIdentityKey !== undefined)
return axolotl.crypto.createKeyPair(data.ourIdentityKey).then(function(keyPair) {
axolotl.api.storage.put("25519KeyidentityKey", keyPair);
return postLocalKeySetup();
});
else
return postLocalKeySetup();
default:
return Promise.resolve(false);
}
}
return doStep().then(stepDone);
};
describe("test vectors", function() {
function defineTest(i) {
it(axolotlTestVectors[i].name, function(done) {
localStorage.clear();
testSessionMap = {};
testIdentityKeysMap = {};
return runAxolotlTest(axolotlTestVectors[i].vectors).then(function(res) {
assert(res);
}).then(done).catch(done);
});
}
for (var i in axolotlTestVectors)
defineTest(i);
});
});
});

View file

@ -1 +0,0 @@
../../protos/

View file

@ -1,85 +0,0 @@
var StaticByteBufferProto = new dcodeIO.ByteBuffer().__proto__;
var StaticArrayBufferProto = new ArrayBuffer().__proto__;
var StaticUint8ArrayProto = new Uint8Array().__proto__;
function getString(thing) {
if (thing === Object(thing)) {
if (thing.__proto__ == StaticUint8ArrayProto)
return String.fromCharCode.apply(null, thing);
if (thing.__proto__ == StaticArrayBufferProto)
return getString(new Uint8Array(thing));
if (thing.__proto__ == StaticByteBufferProto)
return thing.toString("binary");
}
return thing;
}
function getStringable(thing) {
return (typeof thing == "string" || typeof thing == "number" || typeof thing == "boolean" ||
(thing === Object(thing) &&
(thing.__proto__ == StaticArrayBufferProto ||
thing.__proto__ == StaticUint8ArrayProto ||
thing.__proto__ == StaticByteBufferProto)));
}
function isEqual(a, b, mayBeShort) {
// TODO: Special-case arraybuffers, etc
if (a === undefined || b === undefined)
return false;
a = getString(a);
b = getString(b);
var maxLength = mayBeShort ? Math.min(a.length, b.length) : Math.max(a.length, b.length);
if (maxLength < 5)
throw new Error("a/b compare too short");
return a.substring(0, Math.min(maxLength, a.length)) == b.substring(0, Math.min(maxLength, b.length));
}
function toArrayBuffer(thing) {
//TODO: Optimize this for specific cases
if (thing === undefined)
return undefined;
if (thing === Object(thing) && thing.__proto__ == StaticArrayBufferProto)
return thing;
if (thing instanceof Array) {
// Assuming Uint16Array from curve25519
var res = new ArrayBuffer(thing.length * 2);
var uint = new Uint16Array(res);
for (var i = 0; i < thing.length; i++)
uint[i] = thing[i];
return res;
}
if (!getStringable(thing))
throw new Error("Tried to convert a non-stringable thing of type " + typeof thing + " to an array buffer");
var str = getString(thing);
var res = new ArrayBuffer(str.length);
var uint = new Uint8Array(res);
for (var i = 0; i < str.length; i++)
uint[i] = str.charCodeAt(i);
return res;
}
function ensureStringed(thing) {
if (getStringable(thing))
return getString(thing);
else if (thing instanceof Array) {
var res = [];
for (var i = 0; i < thing.length; i++)
res[i] = ensureStringed(thing[i]);
return res;
} else if (thing === Object(thing)) {
var res = {};
for (var key in thing)
res[key] = ensureStringed(thing[key]);
return res;
}
throw new Error("unsure of how to jsonify object of type " + typeof thing);
}
window.textsecure = {
utils: {
jsonThing: function(thing) {
return JSON.stringify(ensureStringed(thing));
}
}
};

File diff suppressed because it is too large Load diff

View file

@ -1,472 +0,0 @@
// Copyright (c) 2014 Matt Corallo
// Distributed under the X11 software license, see the accompanying
// file MIT
axolotlTestVectors = function() {
// We're gonna throw the finalized tests in here:
var tests = [];
// The common-case ALICE test vectors themselves...
var axolotlTwoPartyTestVectorsAlice = [
["sendMessage",
{
smsText: "A",
ourBaseKey: hexToArrayBuffer('2060fe31b041d28127ac35cbfe790e2a25f92d2e21eb2251690ae75e732f5c4d'),
ourEphemeralKey: hexToArrayBuffer('082e6391deb7154bd0375df3fc07f87020a3b0fd7a8c6c90e73f0e054bc2bf5d'),
ourIdentityKey: hexToArrayBuffer('d83d8141aad5f1d62d78a1af09ffbe61f2d3458eeb887a047a58a07565d24463'),
registrationId: 10290,
getKeys: {identityKey: hexToArrayBuffer('059c2197be51bae703ae2edd26b6ff2b03d589ef4851be33a3f8d923ad86a6b439'),
devices: [{
deviceId: 1,
preKey: {keyId: 4611143, publicKey: hexToArrayBuffer('052cd5004a4c31dd7b89b7fc80cc3e62abcf9cf1af014c93ec4589f7ca3e79e65c')},
signedPreKey: {keyId: 14983230, publicKey: hexToArrayBuffer('05a9ecf666ec55fc27988ecc417db0d62dd5e1fa751da1f7a2dd2eca0d14c8bd46'), signature: hexToArrayBuffer('0b46fdb238f1e2df7b28a94ba575e58b0aa1d377bb843602cc8c2a7cd33770fdd741f65a240f7c3086f00f31dc4f3b8ceeab498356f8d5e4bfe6f2dd3eeca98f')},
registrationId: 0xd00d
}]
},
expectedCiphertext: hexToArrayBuffer('3308c7b899021221058a49fa8a94224aaa8f5873404e01710ff9ef02169a75f90af4fbbc600796e0521a21050a6cf5e075c9970f14862db8a703a6c761f50b5182d17874908940556a22372222d301330a2105883ab58b3eb6db93b32bf91899a5b5175e7b21e96fff2cec02c83dff16ba1b271000180022a0013c5d070d1b75c418cef769bd7378a58969537a00e0ff60cbb99defb486fcfb43384264da4ea9821c1336f02d988da38944453331c4b30181704cbcec5a792ab87c5ccff256e0b4d61ba6a30a6964783875018882e66bfbd9445ac44fee9dc67edc2ad9de78adbe0eb7e9cb990272183ce5fac682ee5106f67d732cd16dfb731239590ba67dc827e849c49a9fb5ed8eed41d85d5e6de3294e74f3524c6489c2f25482ff52f9ea29c928b25030bec09207'),
}],
["sendMessage",
{
smsText: "B",
expectedCiphertext: hexToArrayBuffer('3308c7b899021221058a49fa8a94224aaa8f5873404e01710ff9ef02169a75f90af4fbbc600796e0521a21050a6cf5e075c9970f14862db8a703a6c761f50b5182d17874908940556a22372222d301330a2105883ab58b3eb6db93b32bf91899a5b5175e7b21e96fff2cec02c83dff16ba1b271001180022a001256aae85babf8c0808f75e08bf10a63f7f3aea97324c2583d777f609df493d7d45232c8883c3e1118fbc29b6318a3091ae57fed4f1c54458c6bb832fbb35f24933cb79765d00f4a161e2877a5a21a26592cdb0aa8a2f70f5fbe8c601ecdff0bef1b733d7fd0cb7b7d8fc1e45f79c016c8f90449239ca1a04b374538f2760eef43127ddc9a6439c6ceca5faf5962fb26d7248257d4d5ee3fe4cf8795acc555718558e5317f618828328b25030bec09207'),
}],
["receiveMessage",
{
message: hexToArrayBuffer('330a2105bc81f1348a1d065b2bd2776edb9f29bc4150399db35c1d87dc258b94894bc57a1000180022a001c93af1107634d9eaa1516a4f8e95c6a454c27313b38830709eb863608f08f2f3a598ff8f558645427f7b6ea8e182e40f7b4a92ce0325f2e22f76f36f6954f6f391dd21d2cad12e5b620e75b991e69df8821ab0e826e3cb2ae1c7a1fb8ed72213e36fc508ca1f0a92ebe2089535b5d5e1b34eae5f91497bd072de47de3291ba78a6fd67d3f8f3f20d04ab3a1159df8f36ef7e4696847e32ce6be07edb93763a2226c87feff8cc4827'),
type: 1,
newEphemeralKey: hexToArrayBuffer('d04f334799ea1272eff64c5267e28274f54b91b3b11372879303eb7a8cd52763'),
expectedSmsText: "C",
}],
["receiveMessage",
{
message: hexToArrayBuffer('330a2105bc81f1348a1d065b2bd2776edb9f29bc4150399db35c1d87dc258b94894bc57a1001180022a001eb52c72c7bb6b8878c96398cc05810382d29fc17644f88bdc8d57509e8a734626620ae243cb740466806ee3c64bbf12957d5ac0452a17aba6c0e10e2a82626a986df0c4e5cadebb9ce824f1af4fac85cf7d1b9b7cf37f5df06d77b901d0e2aaa772b49f838ec92a67d13b4d7908cf91f7e0a54ad031b2aa4a954180b652f0696350e4f286592e24cc83091b196f2d48397241e33acaf6f65be27af12f1a8af91fd1daf2c01bdfaaa'),
type: 1,
expectedSmsText: "D",
}],
["sendMessage",
{
smsText: "E",
expectedCiphertext: hexToArrayBuffer('330a2105576f3c29717db75ffd19a37154d4d6beba8d796a26c4244793132f7e6cb180491000180122a001bd139a95021d34d9df74d99aa897981aa6718fd6b72d8567891afff92c6e3534ded0de80be7e7c58730a001f2acc1f1e6447f9ca0a99681f3f65d9a4072f3a1fb978740918d3db5c346170edb3bf8fec2b52362edf7138f93cb23a3f17b0f40bf9769e01273955b14c20b6212cbb1f665d1a7e5e770437a53b1727c13bcd639bf5beba71893b8de435244acddc42c3ba592b7debdacdc4dea12dc7e4e670753419be0455e0043f91'),
}],
];
// Now change the order and make 2 tests out of them:
tests[tests.length] = {name: "Standard Axolotl Test Vectors as Alice", vectors: axolotlTwoPartyTestVectorsAlice};
tests[tests.length] = function() {
var test = [];
test[0] = axolotlTwoPartyTestVectorsAlice[0];
test[1] = axolotlTwoPartyTestVectorsAlice[1];
test[2] = ["receiveMessage", { message: axolotlTwoPartyTestVectorsAlice[3][1].message,
type: axolotlTwoPartyTestVectorsAlice[3][1].type,
expectedSmsText: axolotlTwoPartyTestVectorsAlice[3][1].expectedSmsText,
newEphemeralKey: axolotlTwoPartyTestVectorsAlice[2][1].newEphemeralKey }] ;
test[3] = ["receiveMessage", { message: axolotlTwoPartyTestVectorsAlice[2][1].message,
type: axolotlTwoPartyTestVectorsAlice[2][1].type,
expectedSmsText: axolotlTwoPartyTestVectorsAlice[2][1].expectedSmsText }];
test[4] = axolotlTwoPartyTestVectorsAlice[4];
return {name: "Shuffled Axolotl Test Vectors as Alice", vectors: test};
}();
// The common-case BOB test vectors themselves...
var axolotlTwoPartyTestVectorsBob = [
["receiveMessage",
{
message: hexToArrayBuffer('3308c7b899021221058a49fa8a94224aaa8f5873404e01710ff9ef02169a75f90af4fbbc600796e0521a21050a6cf5e075c9970f14862db8a703a6c761f50b5182d17874908940556a22372222d301330a2105883ab58b3eb6db93b32bf91899a5b5175e7b21e96fff2cec02c83dff16ba1b271000180022a0013c5d070d1b75c418cef769bd7378a58969537a00e0ff60cbb99defb486fcfb43384264da4ea9821c1336f02d988da38944453331c4b30181704cbcec5a792ab87c5ccff256e0b4d61ba6a30a6964783875018882e66bfbd9445ac44fee9dc67edc2ad9de78adbe0eb7e9cb990272183ce5fac682ee5106f67d732cd16dfb731239590ba67dc827e849c49a9fb5ed8eed41d85d5e6de3294e74f3524c6489c2f25482ff52f9ea29c928b25030bec09207'),
type: 3,
ourPreKey: hexToArrayBuffer('88d9a12e7b03afdac42e49ec9d4e5488e1b1e6d48c6eef6029e45dec09a9d562'),
preKeyId: 4611143,
ourSignedPreKey: hexToArrayBuffer('888b3f14aff80e36bb2d2cc26a72da2e1a99330962f5066c7c1dded1262ca665'),
signedPreKeyId: 14983230,
ourIdentityKey: hexToArrayBuffer('58c9fb2ec2c6b13e279e7db57ce837c02aac1531504f71130d167cc8fb25a857'),
newEphemeralKey: hexToArrayBuffer('f0b66ac79b6f4ae997636bc8ed622a184dbe00603b2c657ac18800122523d142'),
expectedSmsText: "A",
}],
["receiveMessage",
{
message: hexToArrayBuffer('3308c7b899021221058a49fa8a94224aaa8f5873404e01710ff9ef02169a75f90af4fbbc600796e0521a21050a6cf5e075c9970f14862db8a703a6c761f50b5182d17874908940556a22372222d301330a2105883ab58b3eb6db93b32bf91899a5b5175e7b21e96fff2cec02c83dff16ba1b271001180022a001256aae85babf8c0808f75e08bf10a63f7f3aea97324c2583d777f609df493d7d45232c8883c3e1118fbc29b6318a3091ae57fed4f1c54458c6bb832fbb35f24933cb79765d00f4a161e2877a5a21a26592cdb0aa8a2f70f5fbe8c601ecdff0bef1b733d7fd0cb7b7d8fc1e45f79c016c8f90449239ca1a04b374538f2760eef43127ddc9a6439c6ceca5faf5962fb26d7248257d4d5ee3fe4cf8795acc555718558e5317f618828328b25030bec09207'),
type: 3,
expectedSmsText: "B",
}],
["sendMessage",
{
smsText: "C",
expectedCiphertext: hexToArrayBuffer('330a2105bc81f1348a1d065b2bd2776edb9f29bc4150399db35c1d87dc258b94894bc57a1000180022a001c93af1107634d9eaa1516a4f8e95c6a454c27313b38830709eb863608f08f2f3a598ff8f558645427f7b6ea8e182e40f7b4a92ce0325f2e22f76f36f6954f6f391dd21d2cad12e5b620e75b991e69df8821ab0e826e3cb2ae1c7a1fb8ed72213e36fc508ca1f0a92ebe2089535b5d5e1b34eae5f91497bd072de47de3291ba78a6fd67d3f8f3f20d04ab3a1159df8f36ef7e4696847e32ce6be07edb93763a2226c87feff8cc4827'),
}],
["sendMessage",
{
smsText: "D",
expectedCiphertext: hexToArrayBuffer('330a2105bc81f1348a1d065b2bd2776edb9f29bc4150399db35c1d87dc258b94894bc57a1001180022a001eb52c72c7bb6b8878c96398cc05810382d29fc17644f88bdc8d57509e8a734626620ae243cb740466806ee3c64bbf12957d5ac0452a17aba6c0e10e2a82626a986df0c4e5cadebb9ce824f1af4fac85cf7d1b9b7cf37f5df06d77b901d0e2aaa772b49f838ec92a67d13b4d7908cf91f7e0a54ad031b2aa4a954180b652f0696350e4f286592e24cc83091b196f2d48397241e33acaf6f65be27af12f1a8af91fd1daf2c01bdfaaa'),
}],
["receiveMessage",
{
message: hexToArrayBuffer('330a2105576f3c29717db75ffd19a37154d4d6beba8d796a26c4244793132f7e6cb180491000180122a001bd139a95021d34d9df74d99aa897981aa6718fd6b72d8567891afff92c6e3534ded0de80be7e7c58730a001f2acc1f1e6447f9ca0a99681f3f65d9a4072f3a1fb978740918d3db5c346170edb3bf8fec2b52362edf7138f93cb23a3f17b0f40bf9769e01273955b14c20b6212cbb1f665d1a7e5e770437a53b1727c13bcd639bf5beba71893b8de435244acddc42c3ba592b7debdacdc4dea12dc7e4e670753419be0455e0043f91'),
type: 1,
newEphemeralKey: hexToArrayBuffer('98bee5f861b528816888d45c2ca40125b111d2c03e483e57e6886c82dd758467'),
expectedSmsText: "E",
}],
];
// Now change the order and make 5 tests out of them:
tests[tests.length] = {name: "Standard Axolotl Test Vectors as Bob", vectors: axolotlTwoPartyTestVectorsBob};
var axolotlTwoPartyTestVectorsBobCopy = function() {
var orig = axolotlTwoPartyTestVectorsBob;
var v = [];
for (var i = 0; i < axolotlTwoPartyTestVectorsBob.length; i++) {
v[i] = [];
v[i][0] = orig[i][0];
v[i][1] = orig[i][1];
}
return v;
}
tests[tests.length] = function() {
// Copy axolotlTwoPartyTestVectorsBob into v
var v = axolotlTwoPartyTestVectorsBobCopy();
var orig = axolotlTwoPartyTestVectorsBob;
// Swap first and second received prekey messages
v[0][1] = { message: orig[1][1].message, type: orig[1][1].type, expectedSmsText: orig[1][1].expectedSmsText };
v[0][1].ourPreKey = orig[0][1].ourPreKey;
v[0][1].preKeyId = orig[0][1].preKeyId;
v[0][1].ourSignedPreKey = orig[0][1].ourSignedPreKey;
v[0][1].signedPreKeyId = orig[0][1].signedPreKeyId;
v[0][1].registrationId = orig[0][1].registrationId;
v[0][1].ourIdentityKey = orig[0][1].ourIdentityKey;
v[0][1].newEphemeralKey = orig[0][1].newEphemeralKey;
v[1][1] = { message: orig[0][1].message, type: orig[0][1].type, expectedSmsText: orig[0][1].expectedSmsText };
return {name: "Shuffled Axolotl Test Vectors as Bob I", vectors: v};
}();
tests[tests.length] = function() {
// Copy axolotlTwoPartyTestVectorsBob into v
var v = axolotlTwoPartyTestVectorsBobCopy();
var orig = axolotlTwoPartyTestVectorsBob;
// Swap second received prekey msg with the first send
v[1] = orig[2];
v[2] = orig[1];
return {name: "Shuffled Axolotl Test Vectors as Bob II", vectors: v};
}();
tests[tests.length] = function() {
// Copy axolotlTwoPartyTestVectorsBob into v
var v = axolotlTwoPartyTestVectorsBobCopy();
var orig = axolotlTwoPartyTestVectorsBob;
// Move second received prekey msg to the end (incl after the first received message in the second chain)
v[4] = orig[1];
v[1] = orig[2];
v[2] = orig[3];
v[3] = orig[4];
return {name: "Shuffled Axolotl Test Vectors as Bob III", vectors: v};
}();
tests[tests.length] = function() {
// Copy axolotlTwoPartyTestVectorsBob into v
var v = axolotlTwoPartyTestVectorsBobCopy();
var orig = axolotlTwoPartyTestVectorsBob;
// Move first received prekey msg to the end (incl after the first received message in the second chain)
// ... by first swapping first and second received prekey msg
v[0][1] = { message: orig[1][1].message, type: orig[1][1].type, expectedSmsText: orig[1][1].expectedSmsText };
v[0][1].ourPreKey = orig[0][1].ourPreKey;
v[0][1].preKeyId = orig[0][1].preKeyId;
v[0][1].ourSignedPreKey = orig[0][1].ourSignedPreKey;
v[0][1].signedPreKeyId = orig[0][1].signedPreKeyId;
v[0][1].registrationId = orig[0][1].registrationId;
v[0][1].ourIdentityKey = orig[0][1].ourIdentityKey;
v[0][1].newEphemeralKey = orig[0][1].newEphemeralKey;
v[1][1] = { message: orig[0][1].message, type: orig[0][1].type, expectedSmsText: orig[0][1].expectedSmsText };
// ... then moving the (now-second) message to the end
v[4] = v[1];
v[1] = orig[2];
v[2] = orig[3];
v[3] = orig[4];
return {name: "Shuffled Axolotl Test Vectors as Bob IV", vectors: v};
}();
// Test vectors around an end-session
/*var axolotlEndSessionTestVectorsBob = [
["receiveMessage",
{
message: hexToArrayBuffer('3308d49d980512210513595fc079c0170ea2e849ae4d63d5f5828bb804770a441032b6184ddfe79f7c1a21053841429a37322af3f786be4df3dd8cea5403a79f258e254d4738970acbbe633422d301330a2105fde2b4eca4b7a66a53cd838a45b4ce5847684b3ac2fcb966afabd7e160ada5701000180022a0014949fd0f39d5990f2d1954c84ce626ab4a10149df50daf06f033ec16680d32c479dff81d3782b9042e44d0e3d57096c8b36199360dd1b985afa70033d9c918a4a4355c7d314256225bbc66ace964ddc24d1cd6927a4a878dd97353dd07c298b2c440027009ce28c2cff7b42f7cfd3a1fc16d1e586c9319f011651efe41d98dd585314c7859c1f334aa0f3083f93940757debf1760642954d23b50ed96b0b13f277033dc529eefc3728ac3930a0b68d01'),
type: 3,
ourPreKey: hexToArrayBuffer('89cca67d7e79ad337735876666b284e4f0aa4ccc7a1a9a1d3d6432898923d179'),
preKeyId: 10882772,
ourSignedPreKey: hexToArrayBuffer('b1899b87fa3f6e84894cffded76843ef339f41474ec1ccf1a1c068046c18fb61'),
signedPreKeyId: 2317088,
ourIdentityKey: hexToArrayBuffer('18a3c6d4e4522e8f224c0b1efffdc1d91e6aced52f9fa18e14b888eec462394b'),
newEphemeralKey: hexToArrayBuffer('21daf6374126ee4c2a15bcaa57eb869ece53f6020d55ad20809eb9fe8917457e'),
expectedSmsText: "A",
}],
["receiveMessage",
{
message: hexToArrayBuffer('3308d49d980512210513595fc079c0170ea2e849ae4d63d5f5828bb804770a441032b6184ddfe79f7c1a21053841429a37322af3f786be4df3dd8cea5403a79f258e254d4738970acbbe633422d301330a2105fde2b4eca4b7a66a53cd838a45b4ce5847684b3ac2fcb966afabd7e160ada5701001180022a0010f4a39c86969af983c89b38aa9f968046fdac3e8b1bb59befc998a734bb5f91d457b26eadb7b54ccc07e16a236581fddf73cdcc0c19ca59fd0e261c23ba9d22d351c51aa307cf69e8446ada5d4131cb58f4324e183d059797dcba4d7cc0babd7beda6e25327907d97c39e24d4270c0bdb593da09fdc955f50b228d918b6e03d178737bb42b81b6f4080f92db80a5e0c07dcca1220014f71631401734ac97b723e366e771a85b942128ac3930a0b68d01'),
type: 3,
expectedSmsText: "B",
}],
["receiveMessage",
{
message: hexToArrayBuffer('3308d49d980512210513595fc079c0170ea2e849ae4d63d5f5828bb804770a441032b6184ddfe79f7c1a21053841429a37322af3f786be4df3dd8cea5403a79f258e254d4738970acbbe633422d301330a2105fde2b4eca4b7a66a53cd838a45b4ce5847684b3ac2fcb966afabd7e160ada5701002180022a001b75b0124e0a077de4ceae3addfb722469638fad82577680afe7060da2f7c6dbc9ffe924af37952141ef6f5a78138ba93f2e71102432ee1ed0f1b05cfed069c7cace2103432c92160785ac07d111fbaf535b2a740bc73391a2e370a05db41c883592d7f12129e7c1ee12ac99793b0d9fb38a696e5410d9e3f2df45de2f72ee7812cc66b6770c37e5c882a193b42236563968974ce57c0f9d73631790dce3460f16712a351c428853d28ac3930a0b68d01'),
type: 3,
expectTerminateSession: true,
}],
["receiveMessage",
{
message: hexToArrayBuffer('3308d59d98051221052338a4fb6cbb6d39936ab8b623802d684871c4c4eaf285f2afb7692b5183132c1a21053841429a37322af3f786be4df3dd8cea5403a79f258e254d4738970acbbe633422d301330a2105418a8942643b5278746f1740e05a8ad403380bfaff87a7a8f9b6cbc5db6f38381000180022a00165ffb1279d2d0c25e3f4c3e51e8b6e480724e5858f9078e15346df33b84d61ca33a88fe883c312e93cd560e0d76975aa09fac2c6f348051f6e2a035f08960e3c94001037fb3e6e2328471ac24cc35d6d19f568e27de3d091ff5d7fb861c81ed21ca8da5ece8d4dab705391a03072fbd34b30b1614c4083e0679a66bb487364d36e8b21bd40ba9271c9cc31414e925971b5936000d191724bbfaf6d653f3ecfd0d6f201dbafe17c0d28ac3930a0b68d01'),
type: 3,
ourPreKey: hexToArrayBuffer('e187c9f9a1d6b46e12df13c80b8b51f02cdc8859ff65a222968c1f3ab2484440'),
preKeyId: 10882773,
ourSignedPreKey: hexToArrayBuffer('b1899b87fa3f6e84894cffded76843ef339f41474ec1ccf1a1c068046c18fb61'),
signedPreKeyId: 2317088,
ourIdentityKey: hexToArrayBuffer('18a3c6d4e4522e8f224c0b1efffdc1d91e6aced52f9fa18e14b888eec462394b'),
newEphemeralKey: hexToArrayBuffer('5108fbaaf136969412c691dfbf6108c64cde5235fc30015c5b62d2188784887b'),
expectedSmsText: "C",
}],
["sendMessage",
{
smsText: "D",
expectedCiphertext: hexToArrayBuffer('330a21051b6216eb6bb717294b6140f129f1c706b073e80a57f9c44a912be90489f5214f1000180022a0011b73670e71cc82d5f4d487bfbdf0c6210eacefdb45fe4a4b7aeda5b390873d0e66aa9b2b968d74bbed4dfff9c3ce2c3613afc6cd6711d68335e3b07929ae92c2083ebbb7e212a6af4a799a8f5245806c96dc7f73e99e8b45a6ee81be3fbe2ddc52b1eee7c888e29070ffacbb8adcb4eca9165a7a7036acd5adda63a1a7fb5ee45f9fefb697e96c205f069a2e0d7b005a1255c4ca5fc0c6d263920dd2657835bc7888c173a441b9f4'),
}],
];
// Now shuffle them around and make 6 tests
tests[tests.length] = {name: "Axolotl End Session Test Vectors as Bob", vectors: axolotlEndSessionTestVectorsBob};
var axolotlEndSessionTestVectorsBobCopy = function() {
var orig = axolotlEndSessionTestVectorsBob;
var v = [];
for (var i = 0; i < axolotlEndSessionTestVectorsBob.length; i++) {
v[i] = [];
v[i][0] = orig[i][0];
v[i][1] = orig[i][1];
}
return v;
}
tests[tests.length] = function() {
// Copy axolotlTwoPartyTestVectorsBob into v
var v = axolotlEndSessionTestVectorsBobCopy();
var orig = axolotlEndSessionTestVectorsBob;
// Swap message 2 and 3, moving 2 after its session close
var tmp = v[2][1];
v[2][1] = v[1][1];
v[1][1] = tmp;
return {name: "Shuffled End Session Axolotl Test Vectors as Bob I", vectors: v};
}();
tests[tests.length] = function() {
// Copy axolotlTwoPartyTestVectorsBob into v
var v = axolotlEndSessionTestVectorsBobCopy();
var orig = axolotlEndSessionTestVectorsBob;
// Swap message 2 and 4, moving 2 after the new session
var tmp = v[3][1];
v[3][1] = v[1][1];
v[1][1] = tmp;
return {name: "Shuffled End Session Axolotl Test Vectors as Bob II", vectors: v};
}();
tests[tests.length] = function() {
// Copy axolotlTwoPartyTestVectorsBob into v
var v = axolotlEndSessionTestVectorsBobCopy();
var orig = axolotlEndSessionTestVectorsBob;
// Swap message 3 and 4, starting a new session before closing the last
var tmp = v[3][1];
v[3][1] = v[2][1];
v[2][1] = tmp;
return {name: "Shuffled End Session Axolotl Test Vectors as Bob III", vectors: v};
}();
tests[tests.length] = function() {
// Copy axolotlTwoPartyTestVectorsBob into v
var v = axolotlEndSessionTestVectorsBobCopy();
var orig = axolotlEndSessionTestVectorsBob;
// Swap message 3 and 4, starting a new session before closing the last
var tmp = v[3][1];
v[3][1] = v[2][1];
v[2][1] = tmp;
//...and also swap 4 and 5, sending before the last is closed
tmp = v[3][1];
v[3] = ["sendMessage", v[4][1]];
v[4] = ["receiveMessage", tmp];
return {name: "Shuffled End Session Axolotl Test Vectors as Bob IV", vectors: v};
}();
tests[tests.length] = function() {
// Copy axolotlTwoPartyTestVectorsBob into v
var v = axolotlEndSessionTestVectorsBobCopy();
var orig = axolotlEndSessionTestVectorsBob;
// Put the end session message before all the cooresponding messages
var tmp = v[0][1];
v[0][1] = { message: orig[2][1].message, type: orig[2][1].type, expectTerminateSession: orig[2][1].expectTerminateSession };
v[0][1].ourPreKey = orig[0][1].ourPreKey;
v[0][1].preKeyId = orig[0][1].preKeyId;
v[0][1].ourSignedPreKey = orig[0][1].ourSignedPreKey;
v[0][1].signedPreKeyId = orig[0][1].signedPreKeyId;
v[0][1].registrationId = orig[0][1].registrationId;
v[0][1].ourIdentityKey = orig[0][1].ourIdentityKey;
v[0][1].newEphemeralKey = orig[0][1].newEphemeralKey;
v[2][1] = { message: tmp.message, type: tmp.type, expectedSmsText: tmp.expectedSmsText };
return {name: "Shuffled End Session Axolotl Test Vectors as Bob V", vectors: v};
}();
tests[tests.length] = function() {
// Copy axolotlTwoPartyTestVectorsBob into v
var v = axolotlEndSessionTestVectorsBobCopy();
var orig = axolotlEndSessionTestVectorsBob;
// Put the end session message before all the cooresponding messages
var tmp = v[0][1];
v[0][1] = { message: orig[2][1].message, type: orig[2][1].type, expectTerminateSession: orig[2][1].expectTerminateSession };
v[0][1].ourPreKey = orig[0][1].ourPreKey;
v[0][1].preKeyId = orig[0][1].preKeyId;
v[0][1].ourSignedPreKey = orig[0][1].ourSignedPreKey;
v[0][1].signedPreKeyId = orig[0][1].signedPreKeyId;
v[0][1].registrationId = orig[0][1].registrationId;
v[0][1].ourIdentityKey = orig[0][1].ourIdentityKey;
v[0][1].newEphemeralKey = orig[0][1].newEphemeralKey;
v[2][1] = { message: tmp.message, type: tmp.type, expectedSmsText: tmp.expectedSmsText };
// ... and also open a new session before receiving the pending messages
tmp = v[3][1];
v[3][1] = v[2][1];
v[2][1] = tmp;
return {name: "Shuffled End Session Axolotl Test Vectors as Bob VI", vectors: v};
}();
// Nearly same as above except as Alice
var axolotlEndSessionTestVectorsAlice = [
["sendMessage",
{
smsText: "A",
ourBaseKey: hexToArrayBuffer('b9f458404bb8d9a50b4c58fd373ec109f83dc820ae410d6f933c6f9a72e35e4c'),
ourEphemeralKey: hexToArrayBuffer('9133b17c81c14cdf89b3cd449c7b2ad9c91c223a2e627cc9619e20fbac1b8b6a'),
ourIdentityKey: hexToArrayBuffer('a898043b1b447cfae63e2633e34c49d91cfbad8562c815e300c879e10d4c3456'),
registrationId: 5405,
getKeys: {identityKey: hexToArrayBuffer('056c8e7e99343ae057d3962465a42f9b493e35d06c29140fb95bf01bf8b185852f'),
devices: [{
deviceId: 1,
preKey: {keyId: 6598766, publicKey: hexToArrayBuffer('05cbb234552f2b607fc0b08d76d78dd8ce1f6fc7e2dab8dc5103747cfb398b990d')},
signedPreKey: {keyId: 1564819, publicKey: hexToArrayBuffer('05ac707620d65fe630483f17b43f281d6310d43c3a8d2a27d870300a992f241b5e')},
registrationId: 2966
}]
},
expectedCiphertext: hexToArrayBuffer('3308eee09203122105266863a2585d725c244d440ef03a4ffee9a194a454f92b48500210342cf47e171a2105a028c496fa0850958a9ff1a1dfa528f75fa26a763b168de533f01be99b6b971422d301330a2105cbacb784b46fff7eed25243c96d280306b6336ffb6072b425f3fad2d3f9a1c581000180022a001efa8f1802e2e407754ec82aa7cfc18285733dce8d1bdd9ef934599c816b6d942949391184a74f2f1f156d515a91d9b09352d4116bdb023704c7d5d45b1ed7c9a2555d272fd81d871b9a1c8946ea84d094bb44e184ba03a0fd46c8ac827a05e682c6adb10626cfd98d8e267d6bb7daba7dff77affea1d090592fbe6929736154c16c4648da485b3a5996c8e3536b25844fb2763b2c62fbbcecd21608252e27b34dfd3eb6c618c284b289d2a3093c15f'),
}],
["sendMessage",
{
smsText: "B",
expectedCiphertext: hexToArrayBuffer('3308eee09203122105266863a2585d725c244d440ef03a4ffee9a194a454f92b48500210342cf47e171a2105a028c496fa0850958a9ff1a1dfa528f75fa26a763b168de533f01be99b6b971422d301330a2105cbacb784b46fff7eed25243c96d280306b6336ffb6072b425f3fad2d3f9a1c581001180022a001744576061111ffb4e2df634cf2b155e1cc6d252d3f72cd5d7bad5cb68dc46fc7822176975087abddc65b34d5dc2f644314b4be4deb01e050904ff2c067491324736305c8fc8ce7527d1b6e1c20a08f2d3b3208eacb6e6ce0b8af80f941cc7de1b2d625ae8cdd2e40a2ab27aafe13377bc2a407014492a3a9f21cbf0207997873697d02cd7eea97981860a574333e098c4f55094742c24cfbc73da2640609dc2566e9ed7834240ac5289d2a3093c15f'),
}],
["sendMessage",
{
endSession: true,
expectedCiphertext: hexToArrayBuffer('3308eee09203122105266863a2585d725c244d440ef03a4ffee9a194a454f92b48500210342cf47e171a2105a028c496fa0850958a9ff1a1dfa528f75fa26a763b168de533f01be99b6b971422d301330a2105cbacb784b46fff7eed25243c96d280306b6336ffb6072b425f3fad2d3f9a1c581002180022a001e0b7b0679fa466e677a3e18a28b574c286c59ac48dbf5b5e24e289b7222a2353726ad190aa4ab1cc57d8ac50711adb32ebbed369214bde90a66bcc0b042970224206cb05dd02fab534f12e07e7c909fbbf77e678fb282b81298bc01eae024db13eba6b915651487a06a9b62606c844406496c0c878c6c3422d709d8b08db4d22a7c09a036a3aed6479e0ad07da2f6dfc0b9ee58b11a46d72fe38b662e1c09604a76358b7856dadd7289d2a3093c15f'),
}],
["sendMessage",
{
smsText: "C",
ourBaseKey: hexToArrayBuffer('49a4bb5a4da5ddd29697ff77f787177cd9da36007e456e77bc9107a9f4392b66'),
ourEphemeralKey: hexToArrayBuffer('a189e070781266fbc55e27180a6654e496e98f47e98b0a9e9c4e5e66219dd56e'),
ourIdentityKey: hexToArrayBuffer('a898043b1b447cfae63e2633e34c49d91cfbad8562c815e300c879e10d4c3456'),
registrationId: 5405,
getKeys: {identityKey: hexToArrayBuffer('056c8e7e99343ae057d3962465a42f9b493e35d06c29140fb95bf01bf8b185852f'),
devices: [{
deviceId: 1,
preKey: {keyId: 6598767, publicKey: hexToArrayBuffer('054508c2343459b6a0085f216885096ffa7b8312d073b9bcd1748423b1bfc1ab42')},
signedPreKey: {keyId: 1564819, publicKey: hexToArrayBuffer('05ac707620d65fe630483f17b43f281d6310d43c3a8d2a27d870300a992f241b5e')},
registrationId: 2966
}]
},
expectedCiphertext: hexToArrayBuffer('3308efe092031221054057ff80fb53953c149baf1628fb91b8fcd7df883bf63e94b1bab4037d20966a1a2105a028c496fa0850958a9ff1a1dfa528f75fa26a763b168de533f01be99b6b971422d301330a210591ce9658f1587e42d16b76bfc5035837becde75d630802353c5a215612b385431000180022a001cfd82605cf03277ea76d0c65c9c906a0c4568e312ae9c869ebfcca8c5fe4fe2e80e8eb8d674da589cc45522431903fd0540d4c84bc296332273c165ccbb443859fa697a809a33009a7df03a6f32ac9621807433a456227020e209eec06898af1291e5acf2285ea77aeb04c416464b1e5345a4bf237c3004a0b6f8d334c5783599ea4c1e68d2198872cda7e4e224b24a8fac5e17ce641763f4b14a45a48cc7bcf14e69b2a9272a156289d2a3093c15f'),
}],
];
tests[tests.length] = {name: "Standard End Session Axolotl Test Vectors as Alice", vectors: axolotlEndSessionTestVectorsAlice};
// Nearly same as above except as Alice
var axolotlNoPreKeyEndSessionTestVectorsBob = [
["receiveMessage",
{
message: hexToArrayBuffer('3308ffffffff0f1221050a1fe3a769c05c50e8f09747969099d072f4c343b09ceae56543391349b5bc701a21053841429a37322af3f786be4df3dd8cea5403a79f258e254d4738970acbbe633422d301330a2105896760e61f619db748eb761225b49890aa4e5b286ff8d0575a06660158e40d4e1000180022a0010ced8428b53359fcf2f3dbb6f8be97e77309481df1013a86db4bd41aebc94f7c9d0077c81f53b96501caeece31bd8171f25255ebfe774a981f007849aa38da51904c57a1334a5a11d983205c4cb49e9dd7f308678e34734e6eb9a9297cf03abc8bdd1b1a07c9445474136656ac38cf5ddf41606cf511e20c002fd74bf4b1f8cec738c380b8d4dae0afa0ffc7e091ef5382787eb678b2d9c61dd6fa4ec146c8c30aade6666ee4325228ac3930a0b68d01'),
type: 3,
//ourPreKey: hexToArrayBuffer(''),
//preKeyId: -1,
ourSignedPreKey: hexToArrayBuffer('b1899b87fa3f6e84894cffded76843ef339f41474ec1ccf1a1c068046c18fb61'),
signedPreKeyId: 2317088,
ourIdentityKey: hexToArrayBuffer('18a3c6d4e4522e8f224c0b1efffdc1d91e6aced52f9fa18e14b888eec462394b'),
newEphemeralKey: hexToArrayBuffer('d10237bd4906b68aa3c9105376747a30fb71ef8a2de9f4f5121f4ca458347355'),
expectedSmsText: "A",
}],
["receiveMessage",
{
message: hexToArrayBuffer('3308ffffffff0f1221050a1fe3a769c05c50e8f09747969099d072f4c343b09ceae56543391349b5bc701a21053841429a37322af3f786be4df3dd8cea5403a79f258e254d4738970acbbe633422d301330a2105896760e61f619db748eb761225b49890aa4e5b286ff8d0575a06660158e40d4e1001180022a0019ac9ba3b905c2a92bef7ece4d3c741cedcd05d15dd848be7fa5034db6de7835414f803b40301cf5b8c144b13582322d81dfbe3cf5db237595d16706b1cf2258bcf75b5ac69174341eb931c65a52130825c1f1f97641a7cc1c90c530e7cde0c09919ceb0ada3ea8d987295884f4d42561d793129035b8d298cab1fcba8f7a0bec75a1fe4a3440d59dd48c18f0372ab6952da75bf7f350d28132900e8c48210795aaf4296255be120428ac3930a0b68d01'),
type: 3,
expectedSmsText: "B",
}],
["sendMessage",
{
smsText: "C",
expectedCiphertext: hexToArrayBuffer('330a21051b4f7303e61b8e0f08dca7b31ce01151831d572e31270b3d291214a6e193b27b1000180022a0015b2f37c95192845d947febe6be26ded465f6d98ccef660216d17887dc32d32609ba7a91d3a332539faf483315952c79383fdd5b9768d4b42c665f5c117e2e1f82e10e07a61f63e8318ff687b3e3704336a9ed76565e088706704b680a6931f9adeacb7320c69c043b72db6b3d19646d67be2112be53e782e3b0f4523c6a019f4a693d1ced9d2379763e867ab2d7a03eb222948e1ce86a515d2da519336f7be53bc19af1c68326b3b'),
}],
["sendMessage",
{
smsText: "D",
expectedCiphertext: hexToArrayBuffer('330a21051b4f7303e61b8e0f08dca7b31ce01151831d572e31270b3d291214a6e193b27b1001180022a0016637465b06e81e2bf100cc7ff5dada7c837374b6a51123e6770d7c2ef032436255cdf866487da20de412efa5b99633aa76d833f8542d6d93d21cd2672904783079e4908a126708dfdfb087f48053bc16e3e28e8ac913d55fc25fb59e9bb3f6009a6938aaa86dbb911984d1425f4b4c959e71faeb85a0a017662d5d5a315b341966baf6dc8fa2e9736655d82249741fdcdb93a432346e218b153e5fbef5f064d9e6f6211cb9a6af36'),
}],
["receiveMessage",
{
message: hexToArrayBuffer('330a21056076ed503123ff2662f1c3fce3f0d49084351d0a25fc08d67115a336e8d4be5c1000180122a0012cc6305372c347f141f7690ecc7cf2cc3a47c2c12d3a492e3be7fd6e2723e29e5e858378781d45eb795f32d47f8539987687db4b54e420b06980700b9c5bfe1780445a097c8a47f94080a4e8d88fe12a2c37e04bcb22e23685b7b955391f99ac2da52fbbb25d83269b6584c68de3b61f7f37ffda8c7350a15e798ca59891dd8f62f59afe3544c4a99118edddda322f4aa516536a64dce05e091b125fb06a9c37501e344b993f2a8b'),
type: 1,
expectTerminateSession: true,
newEphemeralKey: hexToArrayBuffer('3131dd7adb8c2eb01e10d6441ede57e499b929354740cea99f6e79fea0eadf58'),
}],
["receiveMessage",
{
message: hexToArrayBuffer('330a21056076ed503123ff2662f1c3fce3f0d49084351d0a25fc08d67115a336e8d4be5c1001180122a001f0898aaaaa4f7928793c4c14b16256e5b797e99a55e12b69242ed4086fb5c1f71982b683f2324305ebccb2eaae146ee783b23f8cebb0aa970e209e554b4ae6140ef30f2f0d83b73ca3f74075194574a9c260ad0e1d08df218aa334ead582efa9a8e705ff17a8e22994a4ac91359cadf9b9cf6853eae12a4bd9c5e5bcad4b8ca991005f0699a5960d09244fa2f01e9f0fb50e85f7318556b314358bfd0fbbc8055dc1090c7d214d83'),
type: 1,
expectException: true,
}],
["receiveMessage",
{
message: hexToArrayBuffer('3308ffffffff0f122105018086d77ab095075239bc2e54a24355114985c8c897b1a56d253d3449ba416b1a21053841429a37322af3f786be4df3dd8cea5403a79f258e254d4738970acbbe633422d301330a2105578d8b0420b0b68fe817772d4dd4f5eea2f786da22f33a109b57adb7ad084c6f1000180022a001cb7303e83ac80b6cd251a93107061aa96ad7bd9b2983a597ba500b0d3402e93af6bcc9304f1ca3a37e9e5a26743ec50dea620c474cec8101a5439cb357c1a4479bb50b33061405fbfddae119edead07ff4fd292f5d6666fc94b8d36cd96ef6fd58fc70d478b182f3cf15a8f1be6a51e560671f901e09fa8b2376462a4cc953751ddc027e15cd0a92f86bb40d3b199b2dab1e0c2e208b104a2594220ff6129f0650ca8aff90c6e06228ac3930a0b68d01'),
type: 3,
//ourPreKey: hexToArrayBuffer(''),
//preKeyId: -1,
ourSignedPreKey: hexToArrayBuffer('b1899b87fa3f6e84894cffded76843ef339f41474ec1ccf1a1c068046c18fb61'),
signedPreKeyId: 2317088,
ourIdentityKey: hexToArrayBuffer('18a3c6d4e4522e8f224c0b1efffdc1d91e6aced52f9fa18e14b888eec462394b'),
newEphemeralKey: hexToArrayBuffer('c9411ca8636f8462308135ae6aff6ec30338ae2c87808b6ee35ef21530971070'),
expectedSmsText: "F",
}],
];
tests[tests.length] = {name: "No-PreKey fake end-session test as Bob", vectors: axolotlNoPreKeyEndSessionTestVectorsBob};
*/
//TODO: GROUPS
//TODO: Sender changes identity key?
return tests;
}();

File diff suppressed because it is too large Load diff

View file

@ -3,12 +3,10 @@
'use strict';
;(function() {
window.axolotl = window.axolotl || {};
window.axolotl.api = {
getMyRegistrationId: function() {
return textsecure.storage.getUnencrypted("registrationId");
},
storage: {
var axolotlInstance = axolotl.protocol({
getMyRegistrationId: function() {
return textsecure.storage.getUnencrypted("registrationId");
},
put: function(key, value) {
return textsecure.storage.putEncrypted(key, value);
},
@ -50,13 +48,12 @@
}
}
},
updateKeys: function(keys) {
function(keys) {
return textsecure.api.registerKeys(keys).catch(function(e) {
//TODO: Notify the user somehow?
console.error(e);
});
},
};
});
var decodeMessageContents = function(res) {
var finalMessage = textsecure.protobuf.PushMessageContent.decode(res[0]);
@ -71,7 +68,7 @@
var handlePreKeyWhisperMessage = function(from, message) {
try {
return axolotl.protocol.handlePreKeyWhisperMessage(from, message);
return textsecure.protocol_wrapper.handlePreKeyWhisperMessage(from, message);
} catch(e) {
if (e.message === 'Unknown identity key') {
// create an error that the UI will pick up and ask the
@ -90,7 +87,7 @@
return Promise.resolve(textsecure.protobuf.PushMessageContent.decode(proto.message));
case textsecure.protobuf.IncomingPushMessageSignal.Type.CIPHERTEXT:
var from = proto.source + "." + (proto.sourceDevice == null ? 0 : proto.sourceDevice);
return axolotl.protocol.decryptWhisperMessage(from, getString(proto.message)).then(decodeMessageContents);
return textsecure.protocol_wrapper.decryptWhisperMessage(from, getString(proto.message)).then(decodeMessageContents);
case textsecure.protobuf.IncomingPushMessageSignal.Type.PREKEY_BUNDLE:
if (proto.message.readUint8() != ((3 << 4) | 3))
throw new Error("Bad version byte");
@ -101,6 +98,24 @@
default:
return new Promise(function(resolve, reject) { reject(new Error("Unknown message type")); });
}
},
closeOpenSessionForDevice: function(encodedNumber) {
return axolotlInstance.closeOpenSessionForDevice(encodedNumber)
},
decryptWhisperMessage: function(encodedNumber, messageBytes, session) {
return axolotlInstance.decryptWhisperMessage(encodedNumber, messageBytes, session);
},
handlePreKeyWhisperMessage: function(from, encodedMessage) {
return axolotlInstance.handlePreKeyWhisperMessage(from, encodedMessage);
},
encryptMessageFor: function(deviceObject, pushMessageContent) {
return axolotlInstance.encryptMessageFor(deviceObject, pushMessageContent);
},
generateKeys: function() {
return axolotlInstance.generateKeys();
},
createIdentityKeyRecvSocket: function() {
return axolotlInstance.createIdentityKeyRecvSocket();
}
};
@ -108,7 +123,7 @@
// Wipe identity key!
textsecure.storage.devices.removeIdentityKeyForNumber(from.split('.')[0]);
//TODO: Probably breaks with a devicecontrol message
return axolotl.protocol.handlePreKeyWhisperMessage(from, encodedMessage).then(decodeMessageContents).then(
return textsecure.protocol_wrapper.handlePreKeyWhisperMessage(from, encodedMessage).then(decodeMessageContents).then(
function(pushMessageContent) {
extension.trigger('message:decrypted', {
message_id : message_id,

View file

@ -271,7 +271,7 @@ window.textsecure.registerSingleDevice = function(number, verificationCode, step
textsecure.storage.putUnencrypted("regionCode", libphonenumber.util.getRegionCodeForNumber(number));
stepDone(1);
return axolotl.protocol.generateKeys().then(function(keys) {
return textsecure.protocol_wrapper.generateKeys().then(function(keys) {
stepDone(2);
return textsecure.api.registerKeys(keys).then(function() {
stepDone(3);
@ -302,7 +302,7 @@ window.textsecure.registerSecondDevice = function(encodedProvisionEnvelope, cryp
textsecure.storage.putUnencrypted("regionCode", libphonenumber.util.getRegionCodeForNumber(identityKey.number));
stepDone(2);
return axolotl.protocol.generateKeys().then(function(keys) {
return textsecure.protocol_wrapper.generateKeys().then(function(keys) {
stepDone(3);
return textsecure.api.registerKeys(keys).then(function() {
stepDone(4);

View file

@ -67,7 +67,7 @@ window.textsecure.messaging = function() {
return new Promise(function() { throw new Error("Mismatched relays for number " + number); });
}
return axolotl.protocol.encryptMessageFor(deviceObjectList[i], message).then(function(encryptedMsg) {
return textsecure.protocol_wrapper.encryptMessageFor(deviceObjectList[i], message).then(function(encryptedMsg) {
textsecure.storage.devices.removeTempKeysFromDevice(deviceObjectList[i].encodedNumber);
var registrationId = deviceObjectList[i].registrationId || deviceObjectList[i].sessions.registrationId;
@ -313,7 +313,7 @@ window.textsecure.messaging = function() {
return sendIndividualProto(number, proto, Date.now()).then(function(res) {
var devices = textsecure.storage.devices.getDeviceObjectsForNumber(number);
for (var i in devices)
axolotl.protocol.closeOpenSessionForDevice(devices[i].encodedNumber);
textsecure.protocol_wrapper.closeOpenSessionForDevice(devices[i].encodedNumber);
return res;
});

View file

@ -34,8 +34,8 @@
<script type="text/javascript" src="../protobufs.js" data-cover></script>
<script type="text/javascript" src="../errors.js" data-cover></script>
<script type="text/javascript" src="../storage.js" data-cover></script>
<script type="text/javascript" src="../libaxolotl.js"></script>
<script type="text/javascript" src="../axolotl_wrapper.js" data-cover></script>
<script type="text/javascript" src="../libaxolotl_concat.js"></script>
<script type="text/javascript" src="../websocket.js" data-cover></script>
<script type="text/javascript" src="../websocket-resources.js" data-cover></script>