From c427da04f0e82fce3de9da4c2286e6dbbafe2eb9 Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Thu, 24 Jul 2014 20:15:27 -0400 Subject: [PATCH] Add closeSession tests as Alice and fix re-requesting prekeys --- js/api.js | 7 ++++-- js/crypto.js | 41 ++++++++++++++++++++++++++++------- js/fake_api.js | 2 +- js/helpers.js | 22 ++++++++++++++++--- js/sendmessage.js | 53 ++++++++++++++++++++++++++++++++-------------- js/test.js | 10 +++++++-- js/testvectors.js | 54 +++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 157 insertions(+), 32 deletions(-) diff --git a/js/api.js b/js/api.js index fc2d4b4e..fd65d16b 100644 --- a/js/api.js +++ b/js/api.js @@ -164,12 +164,15 @@ window.textsecure.api = function() { }); }; - self.getKeysForNumber = function(number) { + self.getKeysForNumber = function(number, deviceId) { + if (deviceId === undefined) + deviceId = "*"; + return doAjax({ call : 'keys', httpType : 'GET', do_auth : true, - urlParameters : "/" + number + "/*", + urlParameters : "/" + number + "/" + deviceId, }).then(function(res) { var promises = []; res.identityKey = base64DecToArr(res.identityKey); diff --git a/js/crypto.js b/js/crypto.js index a8051006..3bdb3a21 100644 --- a/js/crypto.js +++ b/js/crypto.js @@ -140,12 +140,16 @@ window.textsecure.crypto = function() { var device = textsecure.storage.devices.getDeviceObject(encodedNumber); if (device === undefined) device = { sessions: {}, encodedNumber: encodedNumber }; - if (device.sessions === undefined) - device.sessions = {}; if (registrationId !== undefined) device.registrationId = registrationId; + crypto_storage.saveSessionAndDevice(device, session); + } + + crypto_storage.saveSessionAndDevice = function(device, session) { + if (device.sessions === undefined) + device.sessions = {}; var sessions = device.sessions; var doDeleteSession = false; @@ -172,6 +176,15 @@ window.textsecure.crypto = function() { 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) + try { + delete device['registrationId']; + } catch(_) {} + textsecure.storage.devices.saveDeviceObject(device); } @@ -678,7 +691,6 @@ window.textsecure.crypto = function() { throw new Error('Invalid padding'); } - removeOldChains(session); delete session['pendingPreKey']; var finalMessage = textsecure.protos.decodePushMessageContentProtobuf(getString(plaintext)); @@ -687,6 +699,8 @@ window.textsecure.crypto = function() { == textsecure.protos.PushMessageContentProtobuf.Flags.END_SESSION) closeSession(session); + removeOldChains(session); + crypto_storage.saveSession(encodedNumber, session, registrationId); return finalMessage; }); @@ -805,7 +819,15 @@ window.textsecure.crypto = function() { result[0] = (3 << 4) | 3; result.set(new Uint8Array(encodedMsg), 1); result.set(new Uint8Array(mac, 0, 8), encodedMsg.byteLength + 1); - crypto_storage.saveSession(deviceObject.encodedNumber, session); + + try { + delete deviceObject['signedKey']; + delete deviceObject['signedKeyId']; + delete deviceObject['preKey']; + delete deviceObject['preKeyId']; + } catch(_) {} + + crypto_storage.saveSessionAndDevice(deviceObject, session); return result; }); }); @@ -815,18 +837,18 @@ window.textsecure.crypto = function() { var preKeyMsg = new textsecure.protos.PreKeyWhisperMessageProtobuf(); preKeyMsg.identityKey = toArrayBuffer(crypto_storage.getIdentityKey().pubKey); - preKeyMsg.preKeyId = deviceObject.preKeyId; - preKeyMsg.signedPreKeyId = deviceObject.signedKeyId; preKeyMsg.registrationId = textsecure.storage.getUnencrypted("registrationId"); if (session === undefined) { return createNewKeyPair(false).then(function(baseKey) { + preKeyMsg.preKeyId = deviceObject.preKeyId; + preKeyMsg.signedPreKeyId = deviceObject.signedKeyId; preKeyMsg.baseKey = toArrayBuffer(baseKey.pubKey); return initSession(true, baseKey, undefined, deviceObject.encodedNumber, toArrayBuffer(deviceObject.identityKey), toArrayBuffer(deviceObject.preKey), toArrayBuffer(deviceObject.signedKey)) .then(function(new_session) { session = new_session; - session.pendingPreKey = baseKey.pubKey; + 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()); @@ -837,8 +859,11 @@ window.textsecure.crypto = function() { } else return doEncryptPushMessageContent().then(function(message) { if (session.pendingPreKey !== undefined) { - preKeyMsg.baseKey = toArrayBuffer(session.pendingPreKey); + 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 diff --git a/js/fake_api.js b/js/fake_api.js index ecf09ecd..2f2e8aea 100644 --- a/js/fake_api.js +++ b/js/fake_api.js @@ -15,7 +15,7 @@ */ var getKeysForNumberMap = {}; -textsecure.api.getKeysForNumber = function(number) { +textsecure.api.getKeysForNumber = function(number, deviceId) { var res = getKeysForNumberMap[number]; if (res !== undefined) { delete getKeysForNumberMap[number]; diff --git a/js/helpers.js b/js/helpers.js index f72776b4..8f276461 100644 --- a/js/helpers.js +++ b/js/helpers.js @@ -340,8 +340,8 @@ window.textsecure.storage = function() { self.devices = function() { var self = {}; - self.saveDeviceObject = function(deviceObject) { - if (deviceObject.identityKey === undefined || deviceObject.registrationId === undefined || deviceObject.encodedNumber === undefined) + var internalSaveDeviceObject = function(deviceObject, onlyKeys) { + if (deviceObject.identityKey === undefined || deviceObject.encodedNumber === undefined) throw new Error("Tried to store invalid deviceObject"); var number = textsecure.utils.unencodeNumber(deviceObject.encodedNumber)[0]; @@ -355,7 +355,15 @@ window.textsecure.storage = function() { var updated = false; for (var i in map.devices) { if (map.devices[i].encodedNumber == deviceObject.encodedNumber) { - map.devices[i] = deviceObject; + if (!onlyKeys) + map.devices[i] = deviceObject; + else { + map.devices[i].preKey = deviceObject.preKey; + map.devices[i].preKeyId = deviceObject.preKeyId; + map.devices[i].signedKey = deviceObject.signedKey; + map.devices[i].signedKeyId = deviceObject.signedKeyId; + map.devices[i].registrationId = deviceObject.registrationId; + } updated = true; } } @@ -367,6 +375,14 @@ window.textsecure.storage = function() { textsecure.storage.putEncrypted("devices" + number, map); } + self.saveDeviceObject = function(deviceObject) { + return internalSaveDeviceObject(deviceObject, false); + } + + self.saveKeysToDeviceObject = function(deviceObject) { + return internalSaveDeviceObject(deviceObject, true); + } + self.getDeviceObjectsForNumber = function(number) { var map = textsecure.storage.getEncrypted("devices" + number); return map === undefined ? [] : map.devices; diff --git a/js/sendmessage.js b/js/sendmessage.js index d7edc5a3..d9b07bc9 100644 --- a/js/sendmessage.js +++ b/js/sendmessage.js @@ -5,10 +5,10 @@ window.textsecure.messaging = function() { var self = {}; function getKeysForNumber(number, updateDevices) { - return textsecure.api.getKeysForNumber(number).then(function(response) { + var handleResult = function(response) { for (var i in response.devices) { if (updateDevices === undefined || updateDevices.indexOf(response.devices[i].deviceId) > -1) - textsecure.storage.devices.saveDeviceObject({ + textsecure.storage.devices.saveKeysToDeviceObject({ encodedNumber: number + "." + response.devices[i].deviceId, identityKey: response.identityKey, preKey: response.devices[i].preKey.publicKey, @@ -18,7 +18,16 @@ window.textsecure.messaging = function() { registrationId: response.devices[i].registrationId }); } - }); + }; + + var promises = []; + if (updateDevices !== undefined) + for (var i in updateDevices) + promises[promises.length] = textsecure.api.getKeysForNumber(number, updateDevices[i]).then(handleResult); + else + return textsecure.api.getKeysForNumber(number).then(handleResult); + + return Promise.all(promises); } // success_callback(server success/failure map), error_callback(error_msg) @@ -165,14 +174,23 @@ window.textsecure.messaging = function() { var number = numbers[i]; var devicesForNumber = textsecure.storage.devices.getDeviceObjectsForNumber(number); - if (devicesForNumber.length == 0) { - getKeysForNumber(number) - .then(reloadDevicesAndSend(number, true)) - .catch(function(error) { - registerError(number, "Failed to retreive new device keys for number " + number, error); - }); - } else - doSendMessage(number, devicesForNumber, true); + var promises = []; + for (var i in devicesForNumber) + if (devicesForNumber[i].registrationId === undefined) + promises[promises.length] = getKeysForNumber(number, [parseInt(textsecure.utils.unencodeNumber(devicesForNumber[i].encodedNumber)[1])]); + + Promise.all(promises).then(function() { + devicesForNumber = textsecure.storage.devices.getDeviceObjectsForNumber(number); + + if (devicesForNumber.length == 0) { + getKeysForNumber(number) + .then(reloadDevicesAndSend(number, true)) + .catch(function(error) { + registerError(number, "Failed to retreive new device keys for number " + number, error); + }); + } else + doSendMessage(number, devicesForNumber, true); + }); } } @@ -229,13 +247,16 @@ window.textsecure.messaging = function() { } self.closeSession = function(number) { - var devices = textsecure.storage.devices.getDeviceObjectsForNumber(number); - for (var i in devices) - textsecure.crypto.closeOpenSessionForDevice(devices[i].encodedNumber); - var proto = new textsecure.protos.PushMessageContentProtobuf(); + proto.body = "TERMINATE"; proto.flags = textsecure.protos.PushMessageContentProtobuf.Flags.END_SESSION; - return sendIndividualProto(number, proto); + return sendIndividualProto(number, proto).then(function(res) { + var devices = textsecure.storage.devices.getDeviceObjectsForNumber(number); + for (var i in devices) + textsecure.crypto.closeOpenSessionForDevice(devices[i].encodedNumber); + + return res; + }); } self.sendMessageToGroup = function(groupId, messageText, attachments) { diff --git a/js/test.js b/js/test.js index c7f0a5b4..5f1e0469 100644 --- a/js/test.js +++ b/js/test.js @@ -339,7 +339,7 @@ textsecure.registerOnLoadFunction(function() { if (data.getKeys !== undefined) getKeysForNumberMap["SNOWDEN"] = data.getKeys; - return textsecure.messaging.sendMessageToNumber("SNOWDEN", data.smsText, []).then(function() { + var checkMessage = function() { var msg = messagesSentMap["SNOWDEN.1"]; delete messagesSentMap["SNOWDEN.1"]; //XXX: This should be all we do: isEqual(data.expectedCiphertext, msg.body, false); @@ -353,7 +353,12 @@ textsecure.registerOnLoadFunction(function() { var result = getString(msg.body).substring(1); return getString(decoded.encode()) == result; } - }); + } + + if (data.endSession) + return textsecure.messaging.closeSession("SNOWDEN").then(checkMessage); + else + return textsecure.messaging.sendMessageToNumber("SNOWDEN", data.smsText, []).then(checkMessage); } if (data.ourBaseKey !== undefined) @@ -435,6 +440,7 @@ textsecure.registerOnLoadFunction(function() { } delete testsOutstanding[i]; } + printTestsDone(); startNextExclusiveTest(); }, 10000); diff --git a/js/testvectors.js b/js/testvectors.js index b02284da..a87dce5b 100644 --- a/js/testvectors.js +++ b/js/testvectors.js @@ -236,6 +236,8 @@ axolotlTestVectors = function() { expectedSmsText: "C", }], ]; + + // Now shuffle them around and make 5 tests tests[tests.length] = {name: "Axolotl End Session Test Vectors as Bob", vectors: axolotlEndSessionTestVectorsBob}; var axolotlEndSessionTestVectorsBobCopy = function() { @@ -333,5 +335,57 @@ axolotlTestVectors = function() { return {name: "Shuffled End Session Axolotl Test Vectors as Bob V", vectors: v}; }(); + // 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}; + + + return tests; }();