Another step forward

This commit is contained in:
Matt Corallo 2014-05-02 11:59:01 -04:00
parent d9e7e59c3a
commit 87c19c0c94
2 changed files with 202 additions and 78 deletions

View file

@ -575,17 +575,19 @@ var crypto_tests = {};
var decryptAESCTR = function(ciphertext, key, counter) { var decryptAESCTR = function(ciphertext, key, counter) {
//TODO: Waaayyyy less type conversion here (probably just means replacing CryptoJS) //TODO: Waaayyyy less type conversion here (probably just means replacing CryptoJS)
var iv = String.fromCharCode(counter & 0xff000000) + String.fromCharCode(counter & 0xff0000) + String.fromCharCode(counter & 0xff00) + String.fromCharCode(counter & 0xff);
return CryptoJS.AES.decrypt(btoa(getString(ciphertext)), return CryptoJS.AES.decrypt(btoa(getString(ciphertext)),
CryptoJS.enc.Latin1.parse(getString(key)), CryptoJS.enc.Latin1.parse(getString(key)),
{mode: CryptoJS.mode.CTR, iv: CryptoJS.enc.Latin1.parse(""), padding: CryptoJS.pad.NoPadding}) {mode: CryptoJS.mode.CTR, iv: CryptoJS.enc.Latin1.parse(iv), padding: CryptoJS.pad.NoPadding})
.toString(CryptoJS.enc.Latin1); .toString(CryptoJS.enc.Latin1);
} }
var encryptAESCTR = function(plaintext, key, counter) { var encryptAESCTR = function(plaintext, key, counter) {
//TODO: Waaayyyy less type conversion here (probably just means replacing CryptoJS) //TODO: Waaayyyy less type conversion here (probably just means replacing CryptoJS)
var iv = String.fromCharCode(counter & 0xff000000) + String.fromCharCode(counter & 0xff0000) + String.fromCharCode(counter & 0xff00) + String.fromCharCode(counter & 0xff);
return CryptoJS.AES.encrypt(CryptoJS.enc.Latin1.parse(getString(plaintext)), return CryptoJS.AES.encrypt(CryptoJS.enc.Latin1.parse(getString(plaintext)),
CryptoJS.enc.Latin1.parse(getString(key)), CryptoJS.enc.Latin1.parse(getString(key)),
{mode: CryptoJS.mode.CTR, iv: CryptoJS.enc.Latin1.parse(""), padding: CryptoJS.pad.NoPadding}) {mode: CryptoJS.mode.CTR, iv: CryptoJS.enc.Latin1.parse(iv), padding: CryptoJS.pad.NoPadding})
.ciphertext.toString(CryptoJS.enc.Latin1); .ciphertext.toString(CryptoJS.enc.Latin1);
} }
@ -610,6 +612,20 @@ var crypto_tests = {};
/****************************** /******************************
*** Ratchet implementation *** *** Ratchet implementation ***
******************************/ ******************************/
var calculateRatchet = function(session, remoteKey, sending, callback) {
var ratchet = session.currentRatchet;
ECDHE(remoteKey, ratchet.ephemeralKeyPair.privKey, function(sharedSecret) {
var masterKey = HKDF(sharedSecret, ratchet.rootKey, "WhisperRatchet");
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];
callback();
});
}
var initSession = function(isInitiator, ourEphemeralKey, encodedNumber, theirIdentityPubKey, theirEphemeralPubKey, callback) { var initSession = function(isInitiator, ourEphemeralKey, encodedNumber, theirIdentityPubKey, theirEphemeralPubKey, callback) {
var ourIdentityPrivKey = crypto_storage.getIdentityPrivKey(); var ourIdentityPrivKey = crypto_storage.getIdentityPrivKey();
@ -622,16 +638,19 @@ var crypto_tests = {};
sharedSecret += getString(ecRes); sharedSecret += getString(ecRes);
var masterKey = HKDF(sharedSecret, '', "WhisperText"); var masterKey = HKDF(sharedSecret, '', "WhisperText");
var session = {currentRatchet: { rootKey: masterKey[0], ephemeralKeyPair: ourEphemeralKey, // Create new ephemeral key for sending
lastRemoteEphemeralKey: theirEphemeralPubKey }, createNewKeyPair(false, function(ourSendingEphemeralKey) {
oldRatchetList: [] var session = {currentRatchet: { rootKey: masterKey[0], ephemeralKeyPair: ourSendingEphemeralKey,
}; lastRemoteEphemeralKey: theirEphemeralPubKey },
session[getString(ourEphemeralKey.pubKey)] = { messageKeys: {}, chainKey: { counter: -1, key: masterKey[1] } }; oldRatchetList: []
// This isnt an actual ratchet, its just here to make maybeStepRatchet work };
session[getString(theirEphemeralPubKey)] = { messageKeys: {}, chainKey: { counter: 0xffffffff, key: '' } };
crypto_storage.saveSession(encodedNumber, session);
callback(); calculateRatchet(session, theirEphemeralPubKey, true, function() {
crypto_storage.saveSession(encodedNumber, session);
callback();
});
});
}); });
} }
@ -662,6 +681,9 @@ var crypto_tests = {};
} }
var fillMessageKeys = function(chain, counter) { var fillMessageKeys = function(chain, counter) {
if (chain.chainKey.counter + 1000 < counter)
return; // Stalker, much?
var messageKeys = chain.messageKeys; var messageKeys = chain.messageKeys;
var key = chain.chainKey.key; var key = chain.chainKey.key;
for (var i = chain.chainKey.counter; i < counter; i++) { for (var i = chain.chainKey.counter; i < counter; i++) {
@ -673,34 +695,30 @@ var crypto_tests = {};
} }
var maybeStepRatchet = function(session, remoteKey, previousCounter, callback) { var maybeStepRatchet = function(session, remoteKey, previousCounter, callback) {
if (session[getString(remoteKey)] !== undefined) { //TODO: null??? if (session[getString(remoteKey)] !== undefined) {
callback();//TODO: This is happening in tests as alice (when bob is checking), probably shouldn't? callback();
return; return;
} }
var ratchet = session.currentRatchet; var ratchet = session.currentRatchet;
var previousRatchet = session[getString(ratchet.lastRemoteEphemeralKey)]; var previousRatchet = session[getString(ratchet.lastRemoteEphemeralKey)];
fillMessageKeys(previousRatchet, previousCounter); if (previousRatchet !== undefined) {
if (!objectContainsKeys(previousRatchet.messageKeys)) fillMessageKeys(previousRatchet, previousCounter);
delete session[getString(ratchet.lastRemoteEphemeralKey)]; if (!objectContainsKeys(previousRatchet.messageKeys))
else delete session[getString(ratchet.lastRemoteEphemeralKey)];
session.oldRatchetList[session.oldRatchetList.length] = { added: new Date().getTime(), ephemeralKey: ratchet.lastRemoteEphemeralKey }; else
session.oldRatchetList[session.oldRatchetList.length] = { added: new Date().getTime(), ephemeralKey: ratchet.lastRemoteEphemeralKey };
}
delete session[ratchet.ephemeralKeyPair.pubKey]; calculateRatchet(session, remoteKey, false, function() {
// Now swap the ephemeral key and calculate the new sending chain
ECDHE(remoteKey, ratchet.ephemeralKeyPair.privKey, function(sharedSecret) { ratchet.previousCounter = session[getString(ratchet.ephemeralKeyPair.pubKey)].chainKey.counter;
var masterKey = HKDF(sharedSecret, ratchet.rootKey, "WhisperRatchet"); delete session[getString(ratchet.ephemeralKeyPair.pubKey)];
session[getString(remoteKey)] = { messageKeys: {}, chainKey: { counter: -1, key: masterKey[1] } };
createNewKeyPair(false, function(keyPair) { createNewKeyPair(false, function(keyPair) {
ratchet.ephemeralKeyPair = keyPair; ratchet.ephemeralKeyPair = keyPair;
calculateRatchet(session, remoteKey, true, function() {
ECDHE(remoteKey, ratchet.ephemeralKeyPair.privKey, function(sharedSecret) {
masterKey = HKDF(sharedSecret, masterKey[0], "WhisperRatchet");
ratchet.rootKey = masterKey[0];
session[getString(ratchet.ephemeralKeyPair.pubKey)] = { messageKeys: {}, chainKey: { counter: -1, key: masterKey[1] } };
ratchet.lastRemoteEphemeralKey = remoteKey; ratchet.lastRemoteEphemeralKey = remoteKey;
callback(); callback();
}); });
@ -733,6 +751,7 @@ var crypto_tests = {};
var plaintext = decryptAESCTR(message.ciphertext, keys[0], message.counter); var plaintext = decryptAESCTR(message.ciphertext, keys[0], message.counter);
//TODO: removeOldChains(session); //TODO: removeOldChains(session);
delete session['pendingPreKey'];
crypto_storage.saveSession(encodedNumber, session); crypto_storage.saveSession(encodedNumber, session);
callback(decodePushMessageContentProtobuf(plaintext)); callback(decodePushMessageContentProtobuf(plaintext));
@ -796,15 +815,14 @@ var crypto_tests = {};
msg.ephemeralKey = toArrayBuffer(session.currentRatchet.ephemeralKeyPair.pubKey); msg.ephemeralKey = toArrayBuffer(session.currentRatchet.ephemeralKeyPair.pubKey);
var chain = session[getString(msg.ephemeralKey)]; var chain = session[getString(msg.ephemeralKey)];
fillMessageKeys(chain, chain.counter + 1); fillMessageKeys(chain, chain.chainKey.counter + 1);
var keys = HKDF(chain.messageKeys[chain.counter], '', "WhisperMessageKeys"); var keys = HKDF(chain.messageKeys[chain.chainKey.counter], '', "WhisperMessageKeys");
delete chain.messageKeys[chain.counter];
msg.counter = chain.counter;
//TODO delete chain.messageKeys[chain.chainKey.counter];
msg.previousCounter = 1; msg.counter = chain.chainKey.counter;
msg.previousCounter = session.currentRatchet.previousCounter;
msg.ciphertext = toArrayBuffer(encryptAESCTR(plaintext, keys[0], chain.counter)); msg.ciphertext = toArrayBuffer(encryptAESCTR(plaintext, keys[0], chain.chainKey.counter));
var encodedMsg = getString(msg.encode()); var encodedMsg = getString(msg.encode());
var mac = calculateMACWithVersionByte(encodedMsg, keys[1], (2 << 4) | 2); var mac = calculateMACWithVersionByte(encodedMsg, keys[1], (2 << 4) | 2);
@ -814,16 +832,18 @@ var crypto_tests = {};
callback(result); callback(result);
} }
var preKeyMsg = new PreKeyWhisperMessageProtobuf();
preKeyMsg.identityKey = toArrayBuffer(crypto_storage.getStoredPubKey("identityKey"));
preKeyMsg.preKeyId = deviceObject.preKeyId;
preKeyMsg.registrationId = deviceObject.registrationId;
if (session === undefined) { if (session === undefined) {
var preKeyMsg = new PreKeyWhisperMessageProtobuf();
preKeyMsg.identityKey = toArrayBuffer(crypto_storage.getStoredPubKey("identityKey"));
createNewKeyPair(false, function(baseKey) { createNewKeyPair(false, function(baseKey) {
preKeyMsg.baseKey = toArrayBuffer(baseKey.pubKey); preKeyMsg.baseKey = toArrayBuffer(baseKey.pubKey);
preKeyMsg.preKeyId = deviceObject.preKeyId;
initSession(true, baseKey, deviceObject.encodedNumber, deviceObject.identityKey, deviceObject.publicKey, function() { initSession(true, baseKey, deviceObject.encodedNumber, deviceObject.identityKey, deviceObject.publicKey, function() {
//TODO: Delete preKey info now? //TODO: Delete preKey info on first message received back
session = crypto_storage.getSession(deviceObject.encodedNumber); session = crypto_storage.getSession(deviceObject.encodedNumber);
//TODO: We need to step ratchet here, I think session.pendingPreKey = baseKey.pubKey;
doEncryptPushMessageContent(function(message) { doEncryptPushMessageContent(function(message) {
preKeyMsg.message = toArrayBuffer(message); preKeyMsg.message = toArrayBuffer(message);
var result = String.fromCharCode((2 << 4) | 2) + getString(preKeyMsg.encode()); var result = String.fromCharCode((2 << 4) | 2) + getString(preKeyMsg.encode());
@ -833,7 +853,13 @@ var crypto_tests = {};
}); });
} else } else
doEncryptPushMessageContent(function(message) { doEncryptPushMessageContent(function(message) {
callback({type: 1, body: getString(message)}); if (session.pendingPreKey !== undefined) {
preKeyMsg.baseKey = toArrayBuffer(session.pendingPreKey);
preKeyMsg.message = toArrayBuffer(message);
var result = String.fromCharCode((2 << 4) | 2) + getString(preKeyMsg.encode());
callback({type: 3, body: result});
} else
callback({type: 1, body: getString(message)});
}); });
} }
@ -956,7 +982,8 @@ function getKeysForNumber(number, success_callback, error_callback) {
encodedNumber: number + "." + response[i].deviceId, encodedNumber: number + "." + response[i].deviceId,
identityKey: response[i].identityKey, identityKey: response[i].identityKey,
publicKey: response[i].publicKey, publicKey: response[i].publicKey,
preKeyId: response[i].keyId preKeyId: response[i].keyId,
registrationId: response[i].registrationId
}); });
} catch (e) { } catch (e) {
error_callback(e); error_callback(e);
@ -991,6 +1018,7 @@ function sendMessageToDevices(number, deviceObjectList, message, success_callbac
jsonData[i] = { jsonData[i] = {
type: encryptedMsg.type, type: encryptedMsg.type,
destination: deviceObjectList[i].encodedNumber, destination: deviceObjectList[i].encodedNumber,
destinationRegistrationId: deviceObjectList[i].registrationId,
body: encryptedMsg.body, body: encryptedMsg.body,
timestamp: new Date().getTime() timestamp: new Date().getTime()
}; };

View file

@ -184,7 +184,7 @@ registerOnLoadFunction(function() {
}, "HMAC RFC5869 Test vectors");*/ }, "HMAC RFC5869 Test vectors");*/
var simpleAxolotlTestVectors = { /*var simpleAxolotlTestVectors = {
aliceIdentityPriv: hexToArrayBuffer("08ebc1e1fdbbc88d1a833a9d8c287328d4f749b7b7eb20afda0957dc05efc258"), aliceIdentityPriv: hexToArrayBuffer("08ebc1e1fdbbc88d1a833a9d8c287328d4f749b7b7eb20afda0957dc05efc258"),
aliceIdentityPub: hexToArrayBuffer("05b9c152cb9fefb0a12df319ae50c728c7909a8a080fcf22d5e1842352186d3870"), aliceIdentityPub: hexToArrayBuffer("05b9c152cb9fefb0a12df319ae50c728c7909a8a080fcf22d5e1842352186d3870"),
bobIdentityPriv: hexToArrayBuffer("08491ea8a9aff03a724cfb44411502f3e974010e62b6db2703b9506a2e18554e"), bobIdentityPriv: hexToArrayBuffer("08491ea8a9aff03a724cfb44411502f3e974010e62b6db2703b9506a2e18554e"),
@ -246,42 +246,42 @@ encryptedMessage: hexToArrayBuffer("415a326e6f457937756a6c5355785876342f6b585634
TEST(function(callback) { TEST(function(callback) {
simpleAxolotlTestVectorsAsBob(simpleAxolotlTestVectors, callback); simpleAxolotlTestVectorsAsBob(simpleAxolotlTestVectors, callback);
}, "Simple Axolotl test vectors as bob", true); }, "Simple Axolotl test vectors as bob", true);*/
var axolotlTwoPartyTestVectorsAlice = [ var axolotlTwoPartyTestVectorsAlice = [
["sendMessage", ["sendMessage",
{ {
smsText: "A", smsText: "A ",
ourBaseKey: hexToArrayBuffer('91918ad75986b7fa7546dcf9f36baa4997c2ab4c5f0f0e3a1274907e06dd027d'), ourBaseKey: hexToArrayBuffer('192b4892aa2e4cff1293999dc7c367874456c4d920aae7d9d42e5e62c965546c'),
ourEphemeralKey: hexToArrayBuffer('11cd8695564c84153543ab4b47b7da6f89dca070249f734f08ab6f10062b857f'), ourEphemeralKey: hexToArrayBuffer('f12704787bab04a3cf544ebd9d421b6fe36147519eb5afa7c90e3fb67c141e64'),
ourIdentityKey: hexToArrayBuffer('0806209dfac61e8d4e99a855908f86bf1eb874851223902718eed7a54d672960'), ourIdentityKey: hexToArrayBuffer('a05fd14abb42ff393004eee526e3167441ee51021c6d801b784720c15637747c'),
theirPreKey: hexToArrayBuffer('05522fdfff7ded73e9006096be562eb2d99847fba6fb870300db82ccf9a557a534'), theirPreKey: hexToArrayBuffer('05fee424a5b6ccb717d85ef2207e2057ab1144c40afe89cdc80e9c424dd90c146e'),
theirPreKeyId: 16728523, theirPreKeyId: 13845842,
theirRegistrationId: 9072,//TODO: Figure out wtf this is for theirRegistrationId: 11593,//TODO: Figure out wtf this is for
theirIdentityKey: hexToArrayBuffer('05ae90af12c5cb02daa6bc29ac95082d7e66c96bf60215b33215079f0231a00763'), theirIdentityKey: hexToArrayBuffer('05276e4df34557386f67df38b708eeddb1a8924e0428b9eefdc9213c3e8927cc7d'),
//expectedPlaintext: hexToArrayBuffer('0a0e4120202020202020202020202020'), //expectedPlaintext: hexToArrayBuffer('0a0e4120202020202020202020202020'),
//expectedCounter: 0, //expectedCounter: 0,
expectedCiphertext: hexToArrayBuffer('2208cb83fd071221057f5f3adf3b11525967a6c7d9f4fb34df4c79f40b4827cced8d4cd9c0c85d7e601a210582d1fb7ab4f8f0ecfdea447de66a4e19cd2c3502f2b04b8829dd5bb36bd0d6242242220a21059f20776ec3d0975ebe54f8efaf847fdf227ffb2cc057de845c2e963984e5a878100018002210f5dd335e2d1ec1e7335a11c9e1c2a159a0f432040b064abe28f046'), expectedCiphertext: hexToArrayBuffer('2208d28acd061221059ab4e844771bfeb96382edac5f80e757a1109b5611c770b2ba9f28b363d7c2541a2105bd61aea7fa5304f4dc914892bc3795812cda8bb90b73de9920e22c609cf0ec4e2242220a21058c0c357a3a25e6da46b0186d93fec31d5b86a4ac4973742012d8e9de2346be161000180022104bd27ab87ee151d71cdfe89828050ef4b05bddfb56da491728c95a'),
}], }],
/*["sendMessage", ["sendMessage",
{ {
smsText: "B", smsText: "B ",
//expectedPlaintext: hexToArrayBuffer('0a0e4220202020202020202020202020'), //expectedPlaintext: hexToArrayBuffer('0a0e4220202020202020202020202020'),
//expectedCounter: 1, //expectedCounter: 1,
expectedCiphertext: hexToArrayBuffer('2208cb83fd071221057f5f3adf3b11525967a6c7d9f4fb34df4c79f40b4827cced8d4cd9c0c85d7e601a210582d1fb7ab4f8f0ecfdea447de66a4e19cd2c3502f2b04b8829dd5bb36bd0d6242242220a21059f20776ec3d0975ebe54f8efaf847fdf227ffb2cc057de845c2e963984e5a878100118002210efa08202abe3ad52828a9a782f3aa5fc18ea1a709fcc6e6828f046'), expectedCiphertext: hexToArrayBuffer('2208d28acd061221059ab4e844771bfeb96382edac5f80e757a1109b5611c770b2ba9f28b363d7c2541a2105bd61aea7fa5304f4dc914892bc3795812cda8bb90b73de9920e22c609cf0ec4e2242220a21058c0c357a3a25e6da46b0186d93fec31d5b86a4ac4973742012d8e9de2346be16100118002210b40da85e4998984b4bac1748045b3661f46657badd576b4128c95a'),
}], }],
["receiveMessage", ["receiveMessage",
{ {
message: hexToArrayBuffer('0801120c2b313333333333333333333328fec18dd4d3283239220a21051ac83687a0dc5acf4595e12411358fe7efeb068b7deb96f2ae6d25f2a4231f2e100018ffffffff0f220321deef7e214d158ebbd10c3801'), message: hexToArrayBuffer('220a2105edf6feeea8ee95f4b227453d5942d15731cb7a962eff6d04706860a4d577476f100018ffffffff0f22032a53da435b5477336965c6'),
newEphemeralKey: hexToArrayBuffer('21ba1f11c4e9be34da2af78345cb7a04143c67dc084c37b6bd9c3dbc68c86a58'), newEphemeralKey: hexToArrayBuffer('a92a28cf21fb48745ebf68b425a1811476fed69f8623ff5941fd4e547ee4027c'),
//expectedPlaintext: hexToArrayBuffer('220a21051ac83687a0dc5acf4595e12411358fe7efeb068b7deb96f2ae6d25f2a4231f2e100018ffffffff0f220321deef7e214d158ebbd10c'), //expectedPlaintext: hexToArrayBuffer(''),
expectedSmsText: "C", expectedSmsText: "C",
}], }],
["receiveMessage", ["receiveMessage",
{ {
message: hexToArrayBuffer('0801120c2b313333333333333333333328b3da8dd4d3283239220a21051ac83687a0dc5acf4595e12411358fe7efeb068b7deb96f2ae6d25f2a4231f2e100118ffffffff0f220388e968d2d102a298b2c92a3801'), message: hexToArrayBuffer('220a2105edf6feeea8ee95f4b227453d5942d15731cb7a962eff6d04706860a4d577476f100118ffffffff0f2203a439d199228e124820c28b'),
//expectedPlaintext: hexToArrayBuffer('220a21051ac83687a0dc5acf4595e12411358fe7efeb068b7deb96f2ae6d25f2a4231f2e100118ffffffff0f220388e968d2d102a298b2c92a'), //expectedPlaintext: hexToArrayBuffer(''),
expectedSmsText: "D", expectedSmsText: "D",
}], }],
["sendMessage", ["sendMessage",
@ -289,24 +289,24 @@ encryptedMessage: hexToArrayBuffer("415a326e6f457937756a6c5355785876342f6b585634
smsText: "E", smsText: "E",
//expectedPlaintext: hexToArrayBuffer('0a0145'), //expectedPlaintext: hexToArrayBuffer('0a0145'),
//expectedCounter: 0, //expectedCounter: 0,
expectedCiphertext: hexToArrayBuffer('220a2105f4465a6be68def783cb36b26fa4d2b6eb80b6fa7678a58181f0492121a760b48100018012203bf89479b777a890a700a36'), expectedCiphertext: hexToArrayBuffer('220a2105f94173eeb7ff19ab9196461d596324385611fadef0ca29592cc182d92eb653281000180122031b67b3e2c43b9a672c9cb0'),
}],*/ }],
]; ];
var axolotlTwoPartyTestVectorsBob = [ var axolotlTwoPartyTestVectorsBob = [
["receiveMessage", ["receiveMessage",
{ {
message: hexToArrayBuffer('0803120c2b313232323232323232323228b3e48cd4d3283293012208cb83fd071221057f5f3adf3b11525967a6c7d9f4fb34df4c79f40b4827cced8d4cd9c0c85d7e601a210582d1fb7ab4f8f0ecfdea447de66a4e19cd2c3502f2b04b8829dd5bb36bd0d6242242220a21059f20776ec3d0975ebe54f8efaf847fdf227ffb2cc057de845c2e963984e5a878100018002210f5dd335e2d1ec1e7335a11c9e1c2a159a0f432040b064abe28f0463801'), message: hexToArrayBuffer(''),
ourPreKey: hexToArrayBuffer('71f2d216192d22de45290135bb2cc9b140c864d9b92d8f238577f4fddb2fcb4d'), ourPreKey: hexToArrayBuffer(''),
ourIdentityKey: hexToArrayBuffer('08b66c917e27319f8f9a69791d7571ce47175e7e3db5fc85a51e264b31cdf56b'), ourIdentityKey: hexToArrayBuffer(''),
newEphemeralKey: hexToArrayBuffer('416f63ac4939c0bdd0d9e3b589e1ffde4906f54629742f189e6028c08a5d2666'), newEphemeralKey: hexToArrayBuffer(''),
//expectedPlaintext: hexToArrayBuffer('220a21059f20776ec3d0975ebe54f8efaf847fdf227ffb2cc057de845c2e963984e5a878100018002210f5dd335e2d1ec1e7335a11c9e1c2a159a0f432040b064abe'), //expectedPlaintext: hexToArrayBuffer(''),
expectedSmsText: "A", expectedSmsText: "A",
}], }],
["receiveMessage", ["receiveMessage",
{ {
message: hexToArrayBuffer('0803120c2b313232323232323232323228e9898dd4d3283293012208cb83fd071221057f5f3adf3b11525967a6c7d9f4fb34df4c79f40b4827cced8d4cd9c0c85d7e601a210582d1fb7ab4f8f0ecfdea447de66a4e19cd2c3502f2b04b8829dd5bb36bd0d6242242220a21059f20776ec3d0975ebe54f8efaf847fdf227ffb2cc057de845c2e963984e5a878100118002210efa08202abe3ad52828a9a782f3aa5fc18ea1a709fcc6e6828f0463801'), message: hexToArrayBuffer(''),
//expectedPlaintext: hexToArrayBuffer('220a21059f20776ec3d0975ebe54f8efaf847fdf227ffb2cc057de845c2e963984e5a878100118002210efa08202abe3ad52828a9a782f3aa5fc18ea1a709fcc6e68'), //expectedPlaintext: hexToArrayBuffer(''),
expectedSmsText: "B", expectedSmsText: "B",
}], }],
["sendMessage", ["sendMessage",
@ -314,27 +314,123 @@ encryptedMessage: hexToArrayBuffer("415a326e6f457937756a6c5355785876342f6b585634
smsText: "C", smsText: "C",
//expectedPlaintext: hexToArrayBuffer('0a0143'), //expectedPlaintext: hexToArrayBuffer('0a0143'),
//expectedCounter: 0, //expectedCounter: 0,
expectedCiphertext: hexToArrayBuffer('220a21051ac83687a0dc5acf4595e12411358fe7efeb068b7deb96f2ae6d25f2a4231f2e100018ffffffff0f220321deef7e214d158ebbd10c'), expectedCiphertext: hexToArrayBuffer(''),
}], }],
["sendMessage", ["sendMessage",
{ {
smsText: "D", smsText: "D",
//expectedPlaintext: hexToArrayBuffer('0a0144'), //expectedPlaintext: hexToArrayBuffer('0a0144'),
//expectedCounter: 1, //expectedCounter: 1,
expectedCiphertext: hexToArrayBuffer('220a21051ac83687a0dc5acf4595e12411358fe7efeb068b7deb96f2ae6d25f2a4231f2e100118ffffffff0f220388e968d2d102a298b2c92a'), expectedCiphertext: hexToArrayBuffer(''),
}], }],
["receiveMessage", ["receiveMessage",
{ {
message: hexToArrayBuffer('0801120c2b313232323232323232323228e1ad8ed4d3283235220a2105f4465a6be68def783cb36b26fa4d2b6eb80b6fa7678a58181f0492121a760b48100018012203bf89479b777a890a700a363801'), message: hexToArrayBuffer(''),
newEphemeralKey: hexToArrayBuffer('31a5281df2a88cf8ddea3f34eb4ac2c7c55eb91cdf052c5f87d788b4649bae47'), newEphemeralKey: hexToArrayBuffer(''),
//expectedPlaintext: hexToArrayBuffer('220a2105f4465a6be68def783cb36b26fa4d2b6eb80b6fa7678a58181f0492121a760b48100018012203bf89479b777a890a700a36'), //expectedPlaintext: hexToArrayBuffer(''),
expectedSmsText: "E", expectedSmsText: "E",
}], }],
]; ];
TEST(function(callback) { TEST(function(callback) {
axolotlTestVectorsAsBob(axolotlTestVectors, callback); var v = axolotlTwoPartyTestVectorsAlice;
}, "Axolotl test vectors as bob", true); var origCreateNewKeyPair = crypto_tests.createNewKeyPair;
var remoteDevice = { encodedNumber: "BOB" };
var doStep;
var stepDone = function(res) {
if (!res || privKeyQueue.length != 0) {
crypto_tests.createNewKeyPair = origCreateNewKeyPair;
callback(false);
} else if (step == v.length) {
crypto_tests.createNewKeyPair = origCreateNewKeyPair;
callback(true);
} else
doStep();
}
var privKeyQueue = [];
crypto_tests.createNewKeyPair = function(isIdentity, callback) {
if (privKeyQueue.length == 0 || isIdentity)
stepDone(false);
else {
var privKey = privKeyQueue.shift();
crypto_tests.privToPub(privKey, false, function(keyPair) {
var a = btoa(getString(keyPair.privKey)); var b = btoa(getString(privKey));
if (getString(keyPair.privKey) != getString(privKey))
stepDone(false);
else
callback(keyPair);
});
}
}
var step = 0;
var doStep = function() {
var data = v[step][1];
switch(v[step++][0]) {
case "receiveMessage":
if (data.newEphemeralKey !== undefined)
privKeyQueue.push(data.newEphemeralKey);
var message = new IncomingPushMessageProtobuf();
message.type = 1;
message.source = "BOB";
message.message = data.message;
crypto.handleIncomingPushMessageProto(decodeIncomingPushMessageProtobuf(getString(message.encode())), function(res) {
stepDone(res.message.body == data.expectedSmsText);
});
break;
case "sendMessage":
var postLocalKeySetup = function() {
if (data.theirIdentityKey !== undefined)
remoteDevice.identityKey = data.theirIdentityKey;
if (data.theirPreKey !== undefined) {
remoteDevice.publicKey = data.theirPreKey;
remoteDevice.preKeyId = data.theirPreKeyId;
remoteDevice.registrationId = data.theirRegistrationId;
}
var message = new PushMessageContentProtobuf();
message.body = data.smsText;
crypto.encryptMessageFor(remoteDevice, message, function(res) {
if (res.type == 1) { //XXX: This should be used for everything...
stepDone(getString(data.expectedCiphertext) == getString(res.body));
return;
}
// Reencode expectedCiphertext...?
var decoded = decodePreKeyWhisperMessageProtobuf(getString(data.expectedCiphertext).substr(1));
var reencoded = String.fromCharCode((2<<4) | 2) + getString(decoded.encode());
stepDone(getString(data.expectedCiphertext)[0] == String.fromCharCode((2 << 4) | 2) &&
reencoded == res.body);
});
}
if (data.ourBaseKey !== undefined)
privKeyQueue.push(data.ourBaseKey);
if (data.ourEphemeralKey !== undefined)
privKeyQueue.push(data.ourEphemeralKey);
if (data.ourIdentityKey !== undefined)
crypto_tests.privToPub(data.ourIdentityKey, true, function(keyPair) {
storage.putEncrypted("25519KeyidentityKey", keyPair);
postLocalKeySetup();
});
else
postLocalKeySetup();
break;
default:
stepDone(false);
}
}
doStep();
}, "Standard Axolotl Test Vectors as Alice", true);
// Setup test timeouts (note that this will only work if things are actually // Setup test timeouts (note that this will only work if things are actually
// being run async, ie in the case of NaCL) // being run async, ie in the case of NaCL)