From 42de35dd72e115b2eb9195d00ba5cdcb1b819c81 Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Fri, 17 Jan 2014 01:08:33 -0500 Subject: [PATCH] Random fixes, stuff --- background.js | 2 +- helpers.js | 526 +++++++++++++++++++++++++------------------------- options.html | 7 +- options.js | 86 ++++++--- 4 files changed, 333 insertions(+), 288 deletions(-) diff --git a/background.js b/background.js index f94a30a6a..6697a8b39 100644 --- a/background.js +++ b/background.js @@ -1,4 +1,4 @@ -if (localStorage.getItem('first_install_ran')) { +if (!localStorage.getItem('first_install_ran')) { localStorage.setItem('first_install_ran', 1); chrome.tabs.create({url: "options.html"}); } else { diff --git a/helpers.js b/helpers.js index 2f128319c..5cec15a24 100644 --- a/helpers.js +++ b/helpers.js @@ -34,13 +34,6 @@ function base64DecToArr (sBase64, nBlocksSize) { return taBytes; } -/**************************** - *** Forward declarations *** - ****************************/ -var crypto = {}; -crypto._storage = {}; -var storage = {}; - /********************************* *** Type conversion utilities *** *********************************/ @@ -71,7 +64,7 @@ function decodeIncomingPushMessageProtobuf(string) { } var PushMessageContentProtobuf = dcodeIO.ProtoBuf.loadProtoFile("IncomingPushMessageSignal.proto").build("textsecure.PushMessageContent"); -function decodePPushMessageContentProtobuf(string) { +function decodePushMessageContentProtobuf(string) { return PushMessageContentProtobuf.decode(btoa(string)); } @@ -133,6 +126,8 @@ function objectContainsKeys(object) { /************************************************ *** Utilities to store data in local storage *** ************************************************/ +var storage = {}; + storage.putEncrypted = function(key, value) { //TODO if (value === undefined) @@ -171,6 +166,8 @@ storage.removeUnencrypted = function(key) { function registrationDone() { storage.putUnencrypted("registration_done", ""); + //TODO: Fix dirty hack: + chrome.runtime.reload(); } function isRegistrationDone() { @@ -256,272 +253,278 @@ function getRandomBytes(size) { } } -crypto._createNewKeyPair = function() { - //TODO - var pubKey = "BRTJzsHPUWRRBxyo5MoaBRidMk2fwDlfqvU91b6pzbED"; - var privKey = ""; - return { pubKey: pubKey, privKey: privKey }; -} - -crypto._storage.getNewPubKeySTORINGPrivKey = function(keyName) { - var keyPair = _createNewKeyPair(); - storage.putEncrypted("25519Key" + keyName, keyPair); - return keyPair.pubKey; -} - -crypto._storage.getStoredPubKey = function(keyName) { - return storage.getEncrypted("25519Key" + keyName, { pubKey: undefined }).pubKey; -} - -crypto._storage.getStoredKeyPair = function(keyName) { - return storage.getEncrypted("25519Key" + keyName); -} - -crypto._storage.getAndRemoveStoredKeyPair = function(keyName) { - var keyPair = getStoredKeyPair(keyName); - storage.removeEncrypted("25519Key" + keyName); - return keyPair; -} - -crypto._storage.getAndRemovePreKeyPair = function(keyId) { - return getAndRemoveStoredKeyPair("preKey" + keyId); -} - -crypto._storage.getIdentityPrivKey = function() { - return getStoredKeyPair("identityKey").privKey; -} - -crypto._storage.saveSession = function(encodedNumber, session) { - storage.putEncrypted("session" + getEncodedNumber(encodedNumber), session); -} - -crypto._storage.getSession = function(encodedNumber) { - return storage.getEncrypted("session" + getEncodedNumber(encodedNumber)); -} - - -/***************************** - *** Internal Crypto stuff *** - *****************************/ -crypto._ECDHE = function(pubKey, privKey) { - return "ECDHE";//TODO -} - -crypto._HKDF = function(input, salt, info) { - var hkdf = "HKDF(" + input + ", " + salt + ", " + info + ")"; //TODO - return [ hkdf.substring(0, 32), hkdf.substring(32, 64) ]; -} - -crypto._HMACSHA256 = function(input, key) { - //TODO: NativeA - //TODO: return string - return CryptoJS.HmacSHA256(input, CryptoJS.enc.Latin1.parse(getString(key))); -} - -crypto._verifyMACWithVersionByte = function(data, key, mac) { - var calculated_mac = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA256, key); - calculated_mac.update(CryptoJS.enc.Latin1.parse(String.fromCharCode(1))); - calculated_mac.update(CryptoJS.enc.Latin1.parse(getString(data))); - calculated_mac = calculated_mac.finalize(); - - if (btoa(calculated_mac.toString(CryptoJS.enc.Base64)).substring(0, mac.length) != mac) { - console.log("Got message with bad MAC"); - throw "Bad MAC"; - } -} - -/****************************** - *** Ratchet implementation *** - ******************************/ -crypto._initSession = function(isInitiator, theirIdentityPubKey, ourEphemeralPrivKey, theirEphemeralPubKey) { - var ourIdentityPrivKey = _storage.getIdentityPrivKey(); - - var sharedSecret = _ECDHE(theirEphemeralPubKey, ourIdentityPrivKey); - if (isInitiator) - sharedSecret = sharedSecret + _ECDHE(theirIdentityPubKey, ourEphemeralPrivKey); - else - sharedSecret = _ECDHE(theirIdentityPubKey, ourEphemeralPrivKey) + sharedSecret; - sharedSecret += _ECDHE(theirEphemeralPubKey, ourEphemeralPrivKey); - - var masterKey = _HKDF(sharedSecret, '', "WhisperText"); - return { rootKey: masterKey[0], chainKey: masterKey[1] }; -} - -crypto._initSessionFromPreKeyWhisperMessage = function(encodedNumber, message) { - //TODO: Check remote identity key matches known-good key - - var preKeyPair = _storage.getAndRemovePreKeyPair(preKeyProto.preKeyId); - if (preKeyPair === undefined) - throw "Missing preKey for PreKeyWhisperMessage"; - - var firstRatchet = _initSession(false, message.identityKey, preKeyPair.privKey, message.baseKey); - - var session = {currentRatchet: { rootKey: firstRatchet.rootKey, ephemeralKeyPair: preKeyPair, - lastRemoteEphemeralKey: message.baseKey }, - oldRatchetList: [] - }; - session[preKeyPair.pubKey] = { messageKeys: {}, chainKey: { counter: 0, key: firstRatchet.chainKey } }; - _storage.saveSession(encodedNumber, session); -} - -crypto._fillMessageKeys = function(chain, counter) { - var messageKeys = chain.messageKeys; - var key = chain.chainKey.key; - for (var i = chain.chainKey.counter; i < counter; i++) { - messageKeys[counter] = _HMACSHA256(key, String.fromCharCode(1)); - key = _HMACSHA256(key, String.fromCharCode(2)); - } - chain.chainKey.key = key; - chain.chainKey.counter = counter; -} - -crypto._maybeStepRatchet = function(session, remoteKey, previousCounter) { - if (sesion[remoteKey] !== undefined) //TODO: null??? - return; - - var ratchet = session.currentRatchet; - - var previousRatchet = session[ratchet.lastRemoteEphemeralKey]; - _fillMessageKeys(previousRatchet, previousCounter); - if (!objectContainsKeys(previousRatchet.messageKeys)) - delete session[ratchet.lastRemoteEphemeralKey]; - else - session.oldRatchetList[session.oldRatchetList.length] = { added: new Date().getTime(), ephemeralKey: ratchet.lastRemoteEphemeralKey }; - - delete session[ratchet.ephemeralKeyPair.pubKey]; - - var masterKey = _HKDF(_ECDHE(remoteKey, ratchet.ephemeralKeyPair.privKey), ratchet.rootKey, "WhisperRatchet"); - session[remoteKey] = { messageKeys: {}, chainKey: { counter: 0, key: masterKey.substring(32, 64) } }; - - ratchet.ephemeralKeyPair = _createNewKeyPair(); - masterKey = _HKDF(_ECDHE(remoteKey, ratchet.ephemeralKeyPair.privKey), masterKey.substring(0, 32), "WhisperRatchet"); - ratchet.rootKey = masterKey.substring(0, 32); - session[nextRatchet.ephemeralKeyPair.pubKey] = { messageKeys: {}, chainKey: { counter: 0, key: masterKey.substring(32, 64) } }; - - ratchet.lastRemoteEphemeralKey = remoteKey; -} - -crypto._doDecryptWhisperMessage = function(ciphertext, mac, messageKey, counter) { - //TODO keys swapped? - var keys = _HKDF(messageKey, /* all 0x00 bytes????? */ '', "WhisperMessageKeys"); - _verifyMACWithVersionByte(ciphertext, keys[0], mac); - - return AES_CTR_NOPADDING(keys[1], CTR = counter, ciphertext); -} - -// returns decrypted protobuf -crypto._decryptWhisperMessage = function(encodedNumber, messageBytes) { - var session = _storage.getSession(encodedNumber); - if (session === undefined) - throw "No session currently open with " + encodedNumber; - - if (messageBytes[0] != String.fromCharCode(1)) - throw "Bad version number on WhisperMessage"; - - var messageProto = messageBytes.substring(1, messageBytes.length - 8); - var mac = messageBytes.substring(messageBytes.length - 8, messageBytes.length); - - var message = decodeWhisperMessageProtobuf(messageProto); - - _maybeStepRatchet(session, getString(message.ephemeralKey), message.previousCounter); - var chain = session[getString(message.ephemeralKey)]; - - _fillMessageKeys(chain, message.counter); - - var plaintext = _doDecryptWhisperMessage(message.ciphertext, mac, chain.messageKeys[message.counter], message.counter); - delete chain.messageKeys[message.counter]; - - _removeOldChains(session); - - _storage.saveSession(encodedNumber, session); - return decodeWhisperMessage(atob(plaintext)); -} - -/************************* - *** Public crypto API *** - *************************/ -// Decrypts message into a raw string -crypto.decryptWebsocketMessage = function(message) { - //TODO: Use a native AES impl (so I dont feel so bad about side-channels) - var signaling_key = storage.getEncrypted("signaling_key"); //TODO: in crypto._storage - var aes_key = CryptoJS.enc.Latin1.parse(signaling_key.substring(0, 32));//TODO: UTF8 breaks this????? - var mac_key = CryptoJS.enc.Latin1.parse(signaling_key.substring(32, 32 + 20)); - - //TODO: Can we drop the uint8array in favor of raw strings? - var decodedMessage = base64ToUint8Array(message); - if (decodedMessage[0] != 1) { - console.log("Got bad version number: " + decodedMessage[0]); - return; - } - var iv = CryptoJS.lib.WordArray.create(decodedMessage.subarray(1, 1 + 16)); - var ciphertext = decodedMessage.subarray(1 + 16, decodedMessage.length - 10); - var mac = CryptoJS.lib.WordArray.create(decodedMessage.subarray(decodedMessage.length - 10, decodedMessage.length)); - - var calculated_mac = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA256, mac_key); - calculated_mac.update(CryptoJS.enc.Latin1.parse(String.fromCharCode(1))); - calculated_mac.update(iv); - calculated_mac.update(CryptoJS.lib.WordArray.create(ciphertext)); - calculated_mac = calculated_mac.finalize(); - - if (calculated_mac.toString(CryptoJS.enc.Hex).substring(0, 20) != mac.toString(CryptoJS.enc.Hex)) { - console.log("Got message with bad MAC"); - throw "Bad MAC"; +(function(crypto, $, undefined) { + var createNewKeyPair = function() { + //TODO + var pubKey = "BRTJzsHPUWRRBxyo5MoaBRidMk2fwDlfqvU91b6pzbED"; + var privKey = ""; + return { pubKey: pubKey, privKey: privKey }; } - var plaintext = CryptoJS.AES.decrypt(btoa(getString(ciphertext)), aes_key, {iv: iv});//TODO: Does this throw on invalid padding (seems not...) + var crypto_storage = {}; - return atob(plaintext.toString(CryptoJS.enc.Base64)); -} - -crypto.handleIncomingPushMessageProto = function(proto) { - switch(proto.type) { - case 0: //TYPE_MESSAGE_PLAINTEXT - proto.message = decodePushMessageContent(toString(proto.message)); - break; - case 1: //TYPE_MESSAGE_CIPHERTEXT - proto.message = _decryptWhisperMessage(proto.source, toString(proto.message)); - break; - case 3: //TYPE_MESSAGE_PREKEY_BUNDLE - var preKeyProto = decodePreKeyWhisperMessageProtobuf(toString(proto.message)); - _initSessionFromPreKeyWhisperMessage(proto.source, preKeyProto); - proto.message = _decryptWhisperMessage(proto.source, toString(preKeyProto.message)); - break; + crypto_storage.getNewPubKeySTORINGPrivKey = function(keyName) { + var keyPair = crypto._createNewKeyPair(); + storage.putEncrypted("25519Key" + keyName, keyPair); + return keyPair.pubKey; } -} -crypto.encryptMessageFor = function(deviceObject, message) { - return message + " encrypted to " + deviceObject.encodedNumber + " with relay " + deviceObject.relay + - " with identityKey " + deviceObject.identityKey + " and public key " + deviceObject.publicKey; //TODO -} + crypto_storage.getStoredPubKey = function(keyName) { + return storage.getEncrypted("25519Key" + keyName, { pubKey: undefined }).pubKey; + } -var GENERATE_KEYS_KEYS_GENERATED = 100; -crypto.generateKeys = function() { - var identityKey = _storage.getStoredPubKey("identityKey"); - if (identityKey === undefined) - identityKey = _storage.getNewPubKeySTORINGPrivKey("identityKey"); //TODO: should probably just throw? + crypto_storage.getStoredKeyPair = function(keyName) { + return storage.getEncrypted("25519Key" + keyName); + } - var firstKeyId = storage.getEncrypted("maxPreKeyId", -1) + 1; - storage.putEncrypted("maxPreKeyId", firstKeyId + GENERATE_KEYS_KEYS_GENERATED); + crypto_storage.getAndRemoveStoredKeyPair = function(keyName) { + var keyPair = this.getStoredKeyPair(keyName); + storage.removeEncrypted("25519Key" + keyName); + return keyPair; + } - if (firstKeyId > 16777000) - throw "You crazy motherfucker"; + crypto_storage.getAndRemovePreKeyPair = function(keyId) { + return this.getAndRemoveStoredKeyPair("preKey" + keyId); + } - var keys = {}; - keys.keys = []; - for (var i = firstKeyId; i < firstKeyId + GENERATE_KEYS_KEYS_GENERATED; i++) - keys.keys[i] = {keyId: i, publicKey: _storage.getNewPubKeySTORINGPrivKey("preKey" + i), identityKey: identityKey}; - // 0xFFFFFF == 16777215 - keys.lastResortKey = {keyId: 16777215, publicKey: _storage.getStoredPubKey("preKey16777215"), identityKey: identityKey};//TODO: Rotate lastResortKey - if (keys.lastResortKey.publicKey === undefined) - keys.lastResortKey.publicKey = _storage.getNewPubKeySTORINGPrivKey("preKey16777215"); - return keys; -} + crypto_storage.getIdentityPrivKey = function() { + return this.getStoredKeyPair("identityKey").privKey; + } + + crypto_storage.saveSession = function(encodedNumber, session) { + storage.putEncrypted("session" + getEncodedNumber(encodedNumber), session); + } + + crypto_storage.getSession = function(encodedNumber) { + return storage.getEncrypted("session" + getEncodedNumber(encodedNumber)); + } + + + /***************************** + *** Internal Crypto stuff *** + *****************************/ + var ECDHE = function(pubKey, privKey) { + return "ECDHE";//TODO + } + + var HKDF = function(input, salt, info) { + var hkdf = "HKDF(" + input + ", " + salt + ", " + info + ")"; //TODO + return [ hkdf.substring(0, 32), hkdf.substring(32, 64) ]; + } + + var HMACSHA256 = function(input, key) { + //TODO: NativeA + //TODO: return string + return CryptoJS.HmacSHA256(input, CryptoJS.enc.Latin1.parse(getString(key))); + } + + var verifyMACWithVersionByte = function(data, key, mac) { + var calculated_mac = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA256, key); + calculated_mac.update(CryptoJS.enc.Latin1.parse(String.fromCharCode(1))); + calculated_mac.update(CryptoJS.enc.Latin1.parse(getString(data))); + calculated_mac = calculated_mac.finalize(); + + if (btoa(calculated_mac.toString(CryptoJS.enc.Base64)).substring(0, mac.length) != mac) { + console.log("Got message with bad MAC"); + throw "Bad MAC"; + } + } + + /****************************** + *** Ratchet implementation *** + ******************************/ + var initSession = function(isInitiator, theirIdentityPubKey, ourEphemeralPrivKey, theirEphemeralPubKey) { + var ourIdentityPrivKey = crypto_storage.getIdentityPrivKey(); + + var sharedSecret = ECDHE(theirEphemeralPubKey, ourIdentityPrivKey); + if (isInitiator) + sharedSecret = sharedSecret + ECDHE(theirIdentityPubKey, ourEphemeralPrivKey); + else + sharedSecret = ECDHE(theirIdentityPubKey, ourEphemeralPrivKey) + sharedSecret; + sharedSecret += ECDHE(theirEphemeralPubKey, ourEphemeralPrivKey); + + var masterKey = HKDF(sharedSecret, '', "WhisperText"); + return { rootKey: masterKey[0], chainKey: masterKey[1] }; + } + + var initSessionFromPreKeyWhisperMessage = function(encodedNumber, message) { + //TODO: Check remote identity key matches known-good key + + var preKeyPair = crypto_storage.getAndRemovePreKeyPair(preKeyProto.preKeyId); + if (preKeyPair === undefined) + throw "Missing preKey for PreKeyWhisperMessage"; + + var firstRatchet = initSession(false, message.identityKey, preKeyPair.privKey, message.baseKey); + + var session = {currentRatchet: { rootKey: firstRatchet.rootKey, ephemeralKeyPair: preKeyPair, + lastRemoteEphemeralKey: message.baseKey }, + oldRatchetList: [] + }; + session[preKeyPair.pubKey] = { messageKeys: {}, chainKey: { counter: 0, key: firstRatchet.chainKey } }; + storage.saveSession(encodedNumber, session); + } + + var fillMessageKeys = function(chain, counter) { + var messageKeys = chain.messageKeys; + var key = chain.chainKey.key; + for (var i = chain.chainKey.counter; i < counter; i++) { + messageKeys[counter] = HMACSHA256(key, String.fromCharCode(1)); + key = HMACSHA256(key, String.fromCharCode(2)); + } + chain.chainKey.key = key; + chain.chainKey.counter = counter; + } + + var maybeStepRatchet = function(session, remoteKey, previousCounter) { + if (sesion[remoteKey] !== undefined) //TODO: null??? + return; + + var ratchet = session.currentRatchet; + + var previousRatchet = session[ratchet.lastRemoteEphemeralKey]; + fillMessageKeys(previousRatchet, previousCounter); + if (!objectContainsKeys(previousRatchet.messageKeys)) + delete session[ratchet.lastRemoteEphemeralKey]; + else + session.oldRatchetList[session.oldRatchetList.length] = { added: new Date().getTime(), ephemeralKey: ratchet.lastRemoteEphemeralKey }; + + delete session[ratchet.ephemeralKeyPair.pubKey]; + + var masterKey = HKDF(ECDHE(remoteKey, ratchet.ephemeralKeyPair.privKey), ratchet.rootKey, "WhisperRatchet"); + session[remoteKey] = { messageKeys: {}, chainKey: { counter: 0, key: masterKey.substring(32, 64) } }; + + ratchet.ephemeralKeyPair = _createNewKeyPair(); + masterKey = HKDF(ECDHE(remoteKey, ratchet.ephemeralKeyPair.privKey), masterKey.substring(0, 32), "WhisperRatchet"); + ratchet.rootKey = masterKey.substring(0, 32); + session[nextRatchet.ephemeralKeyPair.pubKey] = { messageKeys: {}, chainKey: { counter: 0, key: masterKey.substring(32, 64) } }; + + ratchet.lastRemoteEphemeralKey = remoteKey; + } + + var doDecryptWhisperMessage = function(ciphertext, mac, messageKey, counter) { + //TODO keys swapped? + var keys = HKDF(messageKey, /* all 0x00 bytes????? */ '', "WhisperMessageKeys"); + verifyMACWithVersionByte(ciphertext, keys[0], mac); + + return AES_CTR_NOPADDING(keys[1], CTR = counter, ciphertext); + } + + // returns decrypted protobuf + var decryptWhisperMessage = function(encodedNumber, messageBytes) { + var session = crypto_storage.getSession(encodedNumber); + if (session === undefined) + throw "No session currently open with " + encodedNumber; + + if (messageBytes[0] != String.fromCharCode(1)) + throw "Bad version number on WhisperMessage"; + + var messageProto = messageBytes.substring(1, messageBytes.length - 8); + var mac = messageBytes.substring(messageBytes.length - 8, messageBytes.length); + + var message = decodeWhisperMessageProtobuf(messageProto); + + maybeStepRatchet(session, getString(message.ephemeralKey), message.previousCounter); + var chain = session[getString(message.ephemeralKey)]; + + fillMessageKeys(chain, message.counter); + + var plaintext = doDecryptWhisperMessage(message.ciphertext, mac, chain.messageKeys[message.counter], message.counter); + delete chain.messageKeys[message.counter]; + + removeOldChains(session); + + crypto_storage.saveSession(encodedNumber, session); + return decodePushMessageContentProtobuf(atob(plaintext)); + } + + /************************* + *** Public crypto API *** + *************************/ + // Decrypts message into a raw string + crypto.decryptWebsocketMessage = function(message) { + //TODO: Use a native AES impl (so I dont feel so bad about side-channels) + var signaling_key = storage.getEncrypted("signaling_key"); //TODO: in crypto_storage + var aes_key = CryptoJS.enc.Latin1.parse(signaling_key.substring(0, 32));//TODO: UTF8 breaks this????? + var mac_key = CryptoJS.enc.Latin1.parse(signaling_key.substring(32, 32 + 20)); + + //TODO: Can we drop the uint8array in favor of raw strings? + var decodedMessage = base64ToUint8Array(message); + if (decodedMessage[0] != 1) { + console.log("Got bad version number: " + decodedMessage[0]); + return; + } + var iv = CryptoJS.lib.WordArray.create(decodedMessage.subarray(1, 1 + 16)); + var ciphertext = decodedMessage.subarray(1 + 16, decodedMessage.length - 10); + var mac = CryptoJS.lib.WordArray.create(decodedMessage.subarray(decodedMessage.length - 10, decodedMessage.length)); + + var calculated_mac = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA256, mac_key); + calculated_mac.update(CryptoJS.enc.Latin1.parse(String.fromCharCode(1))); + calculated_mac.update(iv); + calculated_mac.update(CryptoJS.lib.WordArray.create(ciphertext)); + calculated_mac = calculated_mac.finalize(); + + if (calculated_mac.toString(CryptoJS.enc.Hex).substring(0, 20) != mac.toString(CryptoJS.enc.Hex)) { + console.log("Got message with bad MAC"); + throw "Bad MAC"; + } + + var plaintext = CryptoJS.AES.decrypt(btoa(getString(ciphertext)), aes_key, {iv: iv});//TODO: Does this throw on invalid padding (seems not...) + + return atob(plaintext.toString(CryptoJS.enc.Base64)); + } + + crypto.handleIncomingPushMessageProto = function(proto) { + switch(proto.type) { + case 0: //TYPE_MESSAGE_PLAINTEXT + proto.message = decodePushMessageContentProtobuf(getString(proto.message)); + break; + case 1: //TYPE_MESSAGE_CIPHERTEXT + proto.message = decryptWhisperMessage(proto.source, getString(proto.message)); + break; + case 3: //TYPE_MESSAGE_PREKEY_BUNDLE + var preKeyProto = decodePreKeyWhisperMessageProtobuf(getString(proto.message)); + initSessionFromPreKeyWhisperMessage(proto.source, preKeyProto); + proto.message = decryptWhisperMessage(proto.source, getString(preKeyProto.message)); + break; + } + } + + crypto.encryptMessageFor = function(deviceObject, message) { + return message + " encrypted to " + deviceObject.encodedNumber + " with relay " + deviceObject.relay + + " with identityKey " + deviceObject.identityKey + " and public key " + deviceObject.publicKey; //TODO + } + + var GENERATE_KEYS_KEYS_GENERATED = 100; + crypto.generateKeys = function() { + var identityKey = crypto_storage.getStoredPubKey("identityKey"); + if (identityKey === undefined) + identityKey = crypto_storage.getNewPubKeySTORINGPrivKey("identityKey"); //TODO: should probably just throw? + + var firstKeyId = storage.getEncrypted("maxPreKeyId", -1) + 1; + storage.putEncrypted("maxPreKeyId", firstKeyId + GENERATE_KEYS_KEYS_GENERATED); + + if (firstKeyId > 16777000) + throw "You crazy motherfucker"; + + var keys = {}; + keys.keys = []; + for (var i = firstKeyId; i < firstKeyId + GENERATE_KEYS_KEYS_GENERATED; i++) + keys.keys[i] = {keyId: i, publicKey: crypto_storage.getNewPubKeySTORINGPrivKey("preKey" + i), identityKey: identityKey}; + // 0xFFFFFF == 16777215 + keys.lastResortKey = {keyId: 16777215, publicKey: crypto_storage.getStoredPubKey("preKey16777215"), identityKey: identityKey};//TODO: Rotate lastResortKey + if (keys.lastResortKey.publicKey === undefined) + keys.lastResortKey.publicKey = crypto_storage.getNewPubKeySTORINGPrivKey("preKey16777215"); + return keys; + } + +}( window.crypto = window.crypto || {}, jQuery )); /************************************************ *** Utilities to communicate with the server *** ************************************************/ var URL_BASE = "http://textsecure-test.herokuapp.com"; var URL_CALLS = {}; +URL_CALLS['accounts'] = "/v1/accounts"; URL_CALLS['devices'] = "/v1/devices"; URL_CALLS['keys'] = "/v1/keys"; URL_CALLS['push'] = "/v1/messagesocket"; @@ -562,6 +565,11 @@ function doAjax(param) { }, error: function(jqXHR, textStatus, errorThrown) { var code = jqXHR.status; + if (code == 200) {// happens sometimes when we get no response + if (param.success_callback !== undefined) + param.success_callback(null); + return; + } if (code > 999 || code < 100) code = -1; if (param.error_callback !== undefined) diff --git a/options.html b/options.html index 5a1613aeb..9252af09b 100644 --- a/options.html +++ b/options.html @@ -8,14 +8,15 @@