From aec36468bc605f094d4a2217e772aa6f5d00b3bd Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Wed, 28 May 2014 01:14:16 +0200 Subject: [PATCH] Request new keys (largely untested) --- js/api.js | 3 + js/chromium.js | 4 +- js/crypto.js | 5 ++ js/helpers.js | 145 ++++++++++++++++++++++++++----------------------- 4 files changed, 88 insertions(+), 69 deletions(-) diff --git a/js/api.js b/js/api.js index 0c107e588..842cc0975 100644 --- a/js/api.js +++ b/js/api.js @@ -218,8 +218,11 @@ window.textsecure.api = function() { var code = jqXHR.status; if (code > 999 || code < 100) code = -1; + var e = new Error(code); e.name = "HTTPError"; + if (jqXHR.responseJSON) + e.response = jqXHR.responseJSON; reject(e); } }); diff --git a/js/chromium.js b/js/chromium.js index e4c835c7f..8036f4e25 100644 --- a/js/chromium.js +++ b/js/chromium.js @@ -17,11 +17,11 @@ // Random shared utilities that are used only by chromium things function registrationDone() { - textsecure.storage.putUnencrypted("registration_done", ""); + localStorage.setItem("chromiumRegistrationDone", ""); //TODO: Fix dirty hack: chrome.runtime.reload(); } function isRegistrationDone() { - return textsecure.storage.getUnencrypted("registration_done") !== undefined; + return localStorage.getItem("chromiumRegistrationDone") !== null; } diff --git a/js/crypto.js b/js/crypto.js index d2b7777dd..8571cac7b 100644 --- a/js/crypto.js +++ b/js/crypto.js @@ -231,6 +231,11 @@ window.textsecure.crypto = new function() { throw new Error("Datastore inconsistency: session was stored without identity key"); } + // Used when device keys change - we assume key compromise so refuse all new messages + self.forceRemoveAllSessions = function(encodedNumber) { + textsecure.storage.removeEncrypted("session" + encodedNumber); + } + /***************************** *** Internal Crypto stuff *** diff --git a/js/helpers.js b/js/helpers.js index 9c56ad34e..dc77dba49 100644 --- a/js/helpers.js +++ b/js/helpers.js @@ -334,58 +334,46 @@ window.textsecure.storage = function() { *** Device Storage *** **********************/ self.devices = function() { - //TODO: Just store numbers with the device list in the object, not a list of pointers... var self = {}; - self.getDeviceObject = function(encodedNumber) { - return textsecure.storage.getEncrypted("deviceObject" + encodedNumber); - } - - var getDeviceIdListFromNumber = function(number) { - return textsecure.storage.getEncrypted("deviceIdList" + number, []); - } - - var addDeviceIdForNumber = function(number, deviceId) { - var deviceIdList = getDeviceIdListFromNumber(number); - for (var i = 0; i < deviceIdList.length; i++) { - if (deviceIdList[i] == deviceId) - return; - } - deviceIdList[deviceIdList.length] = deviceId; - textsecure.storage.putEncrypted("deviceIdList" + number, deviceIdList); - } - - var getDeviceId = function(encodedNumber) { - var split = encodedNumber.split("."); - if (split.length > 1) - return split[1]; - return 1; - } - - // throws "Identity key mismatch" self.saveDeviceObject = function(deviceObject) { - var existing = this.getDeviceObject(deviceObject.encodedNumber); - if (existing === undefined) - existing = {encodedNumber: deviceObject.encodedNumber}; - for (key in deviceObject) { - if (key == "encodedNumber") - continue; + var number = textsecure.utils.unencodeNumber(deviceObject.encodedNumber); + var map = textsecure.storage.getEncrypted("devices" + number); - if (key == "identityKey" && deviceObject.identityKey != deviceObject.identityKey) - throw new Error("Identity key mismatch"); + if (map === undefined) + map = { devices: [deviceObject], identityKey: deviceObject.identityKey }; + else if (map.identityKey != getString(deviceObject.identityKey)) + throw new Error("Identity key changed"); - existing[key] = deviceObject[key]; - } - textsecure.storage.putEncrypted("deviceObject" + deviceObject.encodedNumber, existing); - addDeviceIdForNumber(textsecure.utils.unencodeNumber(deviceObject.encodedNumber), getDeviceId(deviceObject.encodedNumber)); + textsecure.storage.putEncrypted("devices" + number, map); } - self.getDeviceObjectListFromNumber = function(number) { - var deviceObjectList = []; - var deviceIdList = getDeviceIdListFromNumber(number); - for (var i = 0; i < deviceIdList.length; i++) - deviceObjectList[deviceObjectList.length] = self.getDeviceObject(number + "." + deviceIdList[i]); - return deviceObjectList; + self.getDeviceObjectsForNumber = function(number) { + var map = textsecure.storage.getEncrypted("devices" + number); + return map === undefined ? [] : map.devices; + } + + self.removeDeviceIdsForNumber = function(number, deviceIdsToRemove) { + var map = textsecure.storage.getEncrypted("devices" + number); + if (map === undefined) + throw new Error("Tried to remove device for unknown number"); + + var newDevices = []; + var devicesRemoved = 0; + for (device in map.devices) { + var keep = true; + for (idToRemove in deviceIdsToRemove) + if (device.deviceId == idToRemove) + keep = false; + + if (keep) + newDevices.push(device); + else + devicesRemoved++; + } + + if (devicesRemoved != deviceIdsToRemove.length) + throw new Error("Tried to remove unknown device"); } return self; @@ -519,18 +507,29 @@ window.textsecure.subscribeToPush = function() { // sendMessage(numbers = [], message = PushMessageContentProto, callback(success/failure map)) window.textsecure.sendMessage = function() { - function getKeysForNumber(number) { + function getKeysForNumber(number, updateDevices) { return textsecure.api.getKeysForNumber(number).then(function(response) { - for (var i = 0; i < response.length; i++) { - textsecure.storage.devices.saveDeviceObject({ - encodedNumber: number + "." + response[i].deviceId, - identityKey: response[i].identityKey, - publicKey: response[i].publicKey, - preKeyId: response[i].keyId, - registrationId: response[i].registrationId - }); + var identityKey = getString(response[0].identityKey); + for (device in response) + if (getString(device.identityKey) != identityKey) + throw new Error("Identity key changed"); + + for (device in response) { + var updateDevice = (updateDevices === undefined); + if (!updateDevice) + for (deviceId in updateDevices) + if (deviceId == device.deviceId) + updateDevice = true; + + if (updateDevice) + textsecure.storage.devices.saveDeviceObject({ + encodedNumber: number + "." + device.deviceId, + identityKey: device.identityKey, + publicKey: device.publicKey, + preKeyId: device.keyId, + registrationId: device.registrationId + }); } - return response[0].identityKey; }); } @@ -542,7 +541,7 @@ window.textsecure.sendMessage = function() { var promises = []; var addEncryptionFor = function(i) { - return new Promise(function(resolve) { // Wrap in a promise for the throws + return new Promise(function(resolve) { // Wrap in a promise for the direct throws if (deviceObjectList[i].relay !== undefined) { if (relay === undefined) relay = deviceObjectList[i].relay; @@ -601,19 +600,36 @@ window.textsecure.sendMessage = function() { numberCompleted(); }).catch(function(error) { if (error instanceof Error && error.name == "HTTPError" && (error.message == 410 || error.message == 409)) { - //TODO: Re-request keys for number here - } - registerError(number, "Failed to create or send message", error); + var resetDevices = ((error.message == 410) ? error.response.staleDevices : error.response.missingDevices); + getKeysForNumber(number, resetDevices).then(function() { + if (error.message == 409) + resetDevices = resetDevices.concat(error.response.extraDevices); + + textsecure.storage.devices.removeDeviceIdsForNumber(number, resetDevices); + for (deviceId in resetDevices) + textsecure.crypto.forceRemoveAllSessions(number + "." + resetDevices); + + //TODO: Try again + }).catch(function(error) { + if (error.message !== "Identity key changed") + registerError(number, "Failed to reload device keys", error); + else { + // TODO: Identity key changed, check which devices it changed for and get upset + registerError(number, "Identity key changed!!!!", error); + } + }); + } else + registerError(number, "Failed to create or send message", error); }); } for (var i = 0; i < numbers.length; i++) { var number = numbers[i]; - var devicesForNumber = textsecure.storage.devices.getDeviceObjectListFromNumber(number); + var devicesForNumber = textsecure.storage.devices.getDeviceObjectsForNumber(number); if (devicesForNumber.length == 0) { - getKeysForNumber(number).then(function(identity_key) { - devicesForNumber = textsecure.storage.devices.getDeviceObjectListFromNumber(number); + getKeysForNumber(number).then(function() { + devicesForNumber = textsecure.storage.devices.getDeviceObjectsForNumber(number); if (devicesForNumber.length == 0) registerError(number, "Failed to retreive new device keys for number " + number, null); else @@ -661,8 +677,3 @@ window.textsecure.register = function() { }); } }(); - -function requestIdentityPrivKeyFromMasterDevice(number, identityKey) { - //TODO -} -