diff --git a/Gruntfile.js b/Gruntfile.js index 91a97030bbfd..47decfd62a51 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -56,6 +56,7 @@ module.exports = function(grunt) { 'libtextsecure/helpers.js', 'libtextsecure/stringview.js', 'libtextsecure/api.js', + 'libtextsecure/account_manager.js', 'libtextsecure/message_receiver.js', 'libtextsecure/sendmessage.js', ], diff --git a/js/libtextsecure.js b/js/libtextsecure.js index 9d43f61011f1..e963e8ab2836 100644 --- a/js/libtextsecure.js +++ b/js/libtextsecure.js @@ -38913,116 +38913,6 @@ textsecure.processDecrypted = function(decrypted, source) { }); }; -window.textsecure.refreshPreKeys = function() { - return textsecure.api.getMyKeys().then(function(preKeyCount) { - if (preKeyCount < 10) { - generateKeys(); - } - }); -}; - -function createAccount(number, verificationCode, identityKeyPair, single_device) { - textsecure.storage.put('identityKey', identityKeyPair); - - var signalingKey = textsecure.crypto.getRandomBytes(32 + 20); - textsecure.storage.put('signaling_key', signalingKey); - - var password = btoa(getString(textsecure.crypto.getRandomBytes(16))); - password = password.substring(0, password.length - 2); - textsecure.storage.put("password", password); - - var registrationId = axolotl.util.generateRegistrationId(); - textsecure.storage.put("registrationId", registrationId); - - return textsecure.api.confirmCode( - number, verificationCode, password, signalingKey, registrationId, single_device - ).then(function(response) { - textsecure.storage.user.setNumberAndDeviceId(number, response.deviceId || 1); - textsecure.storage.put("regionCode", libphonenumber.util.getRegionCodeForNumber(number)); - - return textsecure.protocol_wrapper.generateKeys().then(textsecure.registration.done); - }); -} - -function generateKeys(count, progressCallback) { - if (count === undefined) { - throw TypeError('generateKeys: count is undefined'); - } - if (typeof progressCallback !== 'function') { - progressCallback = undefined; - } - var store = textsecure.storage.axolotl; - var identityKey = store.getMyIdentityKey(); - var result = { preKeys: [], identityKey: identityKey.pubKey }; - var promises = []; - - var startId = textsecure.storage.get('maxPreKeyId', 1); - var signedKeyId = textsecure.storage.get('signedKeyId', 1); - - for (var keyId = startId; keyId < startId+count; ++keyId) { - promises.push( - axolotl.util.generatePreKey(keyId).then(function(res) { - store.putPreKey(res.keyId, res.keyPair); - result.preKeys.push({ - keyId : res.keyId, - publicKey : res.keyPair.pubKey - }); - if (progressCallback) { progressCallback(); } - }) - ); - } - promises.push( - axolotl.util.generateSignedPreKey(identityKey, signedKeyId).then(function(res) { - store.putSignedPreKey(res.keyId, res.keyPair); - result.signedPreKey = { - keyId : res.keyId, - publicKey : res.keyPair.pubKey, - signature : res.signature - }; - }) - ); - - store.removeSignedPreKey(signedKeyId - 2); - textsecure.storage.put('maxPreKeyId', startId + count); - textsecure.storage.put('signedKeyId', signedKeyId + 1); - - return Promise.all(promises).then(function() { - return result; - }); -}; - -window.textsecure.registerSecondDevice = function(setProvisioningUrl, confirmNumber, progressCallback) { - return textsecure.protocol_wrapper.createIdentityKeyRecvSocket().then(function(cryptoInfo) { - return new Promise(function(resolve) { - new WebSocketResource(textsecure.api.getTempWebsocket(), function(request) { - if (request.path == "/v1/address" && request.verb == "PUT") { - var proto = textsecure.protobuf.ProvisioningUuid.decode(request.body); - setProvisioningUrl([ - 'tsdevice:/?uuid=', proto.uuid, '&pub_key=', - encodeURIComponent(btoa(getString(cryptoInfo.pubKey))) - ].join('')); - request.respond(200, 'OK'); - } else if (request.path == "/v1/message" && request.verb == "PUT") { - var envelope = textsecure.protobuf.ProvisionEnvelope.decode(request.body, 'binary'); - request.respond(200, 'OK'); - resolve(cryptoInfo.decryptAndHandleDeviceInit(envelope).then(function(provisionMessage) { - return confirmNumber(provisionMessage.number).then(function() { - return createAccount( - provisionMessage.number, - provisionMessage.provisioningCode, - provisionMessage.identityKeyPair, - false - ); - }); - })); - } else { - console.log('Unknown websocket message', request.path); - } - }); - }); - }); -}; - /* vim: ts=4:sw=4:expandtab * * This program is free software: you can redistribute it and/or modify @@ -39480,6 +39370,167 @@ window.textsecure.api = function () { return self; }(); +/* vim: ts=4:sw=4:expandtab + * + * 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 . + */ + + +;(function () { + 'use strict'; + window.textsecure = window.textsecure || {}; + + function AccountManager() { + } + + AccountManager.prototype = { + constructor: AccountManager, + requestVoiceVerification: function(number) { + return TextSecureServer.requestVerificationVoice(number); + }, + requestSMSVerification: function(number) { + return TextSecureServer.requestVerificationSMS(number); + }, + registerSingleDevice: function(number, verificationCode) { + return axolotl.util.generateIdentityKeyPair().then(function(identityKeyPair) { + return createAccount(number, verificationCode, identityKeyPair, true). + then(function() { return generateKeys(100); }). + then(TextSecureServer.registerKeys). + then(textsecure.registration.done); + }); + }, + registerSecondDevice: function(setProvisioningUrl, confirmNumber, progressCallback) { + return textsecure.protocol_wrapper.createIdentityKeyRecvSocket().then(function(cryptoInfo) { + return new Promise(function(resolve) { + new WebSocketResource(textsecure.api.getTempWebsocket(), function(request) { + if (request.path == "/v1/address" && request.verb == "PUT") { + var proto = textsecure.protobuf.ProvisioningUuid.decode(request.body); + setProvisioningUrl([ + 'tsdevice:/?uuid=', proto.uuid, '&pub_key=', + encodeURIComponent(btoa(getString(cryptoInfo.pubKey))) + ].join('')); + request.respond(200, 'OK'); + } else if (request.path == "/v1/message" && request.verb == "PUT") { + var envelope = textsecure.protobuf.ProvisionEnvelope.decode(request.body, 'binary'); + request.respond(200, 'OK'); + resolve(cryptoInfo.decryptAndHandleDeviceInit(envelope).then(function(provisionMessage) { + return confirmNumber(provisionMessage.number).then(function() { + return createAccount( + provisionMessage.number, + provisionMessage.provisioningCode, + provisionMessage.identityKeyPair, + false + ); + }); + })); + } else { + console.log('Unknown websocket message', request.path); + } + }); + }); + }).then(function() { + return generateKeys(100, progressCallback); + }).then(TextSecureServer.registerKeys).then(textsecure.registration.done); + }, + refreshPreKeys: function() { + return textsecure.api.getMyKeys().then(function(preKeyCount) { + if (preKeyCount < 10) { + return generateKeys(100); + } + }); + } + }; + + function createAccount(number, verificationCode, identityKeyPair, single_device) { + textsecure.storage.put('identityKey', identityKeyPair); + + var signalingKey = textsecure.crypto.getRandomBytes(32 + 20); + textsecure.storage.put('signaling_key', signalingKey); + + var password = btoa(getString(textsecure.crypto.getRandomBytes(16))); + password = password.substring(0, password.length - 2); + textsecure.storage.put("password", password); + + var registrationId = axolotl.util.generateRegistrationId(); + textsecure.storage.put("registrationId", registrationId); + + return textsecure.api.confirmCode( + number, verificationCode, password, signalingKey, registrationId, single_device + ).then(function(response) { + textsecure.storage.user.setNumberAndDeviceId(number, response.deviceId || 1); + textsecure.storage.put("regionCode", libphonenumber.util.getRegionCodeForNumber(number)); + }); + } + + textsecure.AccountManager = AccountManager; + +}()); + +function generateKeys(count, progressCallback) { + if (typeof progressCallback !== 'function') { + progressCallback = undefined; + } + var startId = textsecure.storage.get('maxPreKeyId', 1); + var signedKeyId = textsecure.storage.get('signedKeyId', 1); + + if (typeof startId != 'number') { + throw new Error('Invalid maxPreKeyId'); + } + if (typeof signedKeyId != 'number') { + throw new Error('Invalid signedKeyId'); + } + + textsecure.protocol_wrapper.startWorker(); + + var store = textsecure.storage.axolotl; + var identityKey = store.getMyIdentityKey(); + var result = { preKeys: [], identityKey: identityKey.pubKey }; + var promises = []; + + for (var keyId = startId; keyId < startId+count; ++keyId) { + promises.push( + axolotl.util.generatePreKey(keyId).then(function(res) { + store.putPreKey(res.keyId, res.keyPair); + result.preKeys.push({ + keyId : res.keyId, + publicKey : res.keyPair.pubKey + }); + if (progressCallback) { progressCallback(); } + }) + ); + } + + promises.push( + axolotl.util.generateSignedPreKey(identityKey, signedKeyId).then(function(res) { + store.putSignedPreKey(res.keyId, res.keyPair); + result.signedPreKey = { + keyId : res.keyId, + publicKey : res.keyPair.pubKey, + signature : res.signature + }; + }) + ); + + store.removeSignedPreKey(signedKeyId - 2); + textsecure.storage.put('maxPreKeyId', startId + count); + textsecure.storage.put('signedKeyId', signedKeyId + 1); + return Promise.all(promises).then(function() { + textsecure.protocol_wrapper.stopWorker(); + return result; + }); +} + /* vim: ts=4:sw=4:expandtab * * This program is free software: you can redistribute it and/or modify diff --git a/js/options.js b/js/options.js index 15a629dde4d4..e716092c610e 100644 --- a/js/options.js +++ b/js/options.js @@ -52,7 +52,7 @@ e.stopPropagation(); $('.confirmation-dialog').hide(); $('.progress-dialog').show(); - $('.progress-dialog .status').text('Registering new device...'); + $('.progress-dialog .status').text('Generating Keys'); resolve(); }); $('.modal-container').show(); @@ -78,7 +78,8 @@ $('#init-setup').show().addClass('in'); $('#status').text("Connecting..."); - bg.textsecure.registerSecondDevice(setProvisioningUrl, confirmNumber, incrementCounter).then(function() { + var accountManager = new bg.textsecure.AccountManager(); + accountManager.registerSecondDevice(setProvisioningUrl, confirmNumber, incrementCounter).then(function() { $('.modal-container').hide(); $('#init-setup').hide(); $('#setup-complete').show().addClass('in'); diff --git a/js/register.js b/js/register.js index 306da4344a16..ee7c00cb8bd6 100644 --- a/js/register.js +++ b/js/register.js @@ -17,6 +17,7 @@ ;(function() { 'use strict'; var bg = extension.windows.getBackground(); + var accountManager = new bg.textsecure.AccountManager(); function log(s) { console.log(s); @@ -61,7 +62,7 @@ $('#error').hide(); var number = phoneView.validateNumber(); if (number) { - bg.textsecure.api.requestVerificationVoice(number).catch(displayError); + accountManager.requestVoiceVerification(number).catch(displayError); $('#step2').addClass('in').fadeIn(); } else { $('#number-container').addClass('invalid'); @@ -72,7 +73,7 @@ $('#error').hide(); var number = phoneView.validateNumber(); if (number) { - bg.textsecure.api.requestVerificationSMS(number).catch(displayError); + accountManager.requestSMSVerification(number).catch(displayError); $('#step2').addClass('in').fadeIn(); } else { $('#number-container').addClass('invalid'); @@ -86,7 +87,7 @@ localStorage.clear(); localStorage.setItem('first_install_ran', 1); - bg.textsecure.registerSingleDevice(number, verificationCode).then(function() { + accountManager.registerSingleDevice(number, verificationCode).then(function() { extension.navigator.tabs.create("options.html"); window.close(); }).catch(function(e) { diff --git a/libtextsecure/account_manager.js b/libtextsecure/account_manager.js new file mode 100644 index 000000000000..6915791f7c9c --- /dev/null +++ b/libtextsecure/account_manager.js @@ -0,0 +1,157 @@ +/* vim: ts=4:sw=4:expandtab + * + * 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 . + */ + + +;(function () { + 'use strict'; + window.textsecure = window.textsecure || {}; + + function AccountManager() { + } + + AccountManager.prototype = { + constructor: AccountManager, + requestVoiceVerification: function(number) { + return TextSecureServer.requestVerificationVoice(number); + }, + requestSMSVerification: function(number) { + return TextSecureServer.requestVerificationSMS(number); + }, + registerSingleDevice: function(number, verificationCode) { + return axolotl.util.generateIdentityKeyPair().then(function(identityKeyPair) { + return createAccount(number, verificationCode, identityKeyPair, true). + then(function() { return generateKeys(100); }). + then(TextSecureServer.registerKeys). + then(textsecure.registration.done); + }); + }, + registerSecondDevice: function(setProvisioningUrl, confirmNumber, progressCallback) { + return textsecure.protocol_wrapper.createIdentityKeyRecvSocket().then(function(cryptoInfo) { + return new Promise(function(resolve) { + new WebSocketResource(textsecure.api.getTempWebsocket(), function(request) { + if (request.path == "/v1/address" && request.verb == "PUT") { + var proto = textsecure.protobuf.ProvisioningUuid.decode(request.body); + setProvisioningUrl([ + 'tsdevice:/?uuid=', proto.uuid, '&pub_key=', + encodeURIComponent(btoa(getString(cryptoInfo.pubKey))) + ].join('')); + request.respond(200, 'OK'); + } else if (request.path == "/v1/message" && request.verb == "PUT") { + var envelope = textsecure.protobuf.ProvisionEnvelope.decode(request.body, 'binary'); + request.respond(200, 'OK'); + resolve(cryptoInfo.decryptAndHandleDeviceInit(envelope).then(function(provisionMessage) { + return confirmNumber(provisionMessage.number).then(function() { + return createAccount( + provisionMessage.number, + provisionMessage.provisioningCode, + provisionMessage.identityKeyPair, + false + ); + }); + })); + } else { + console.log('Unknown websocket message', request.path); + } + }); + }); + }).then(function() { + return generateKeys(100, progressCallback); + }).then(TextSecureServer.registerKeys).then(textsecure.registration.done); + }, + refreshPreKeys: function() { + return textsecure.api.getMyKeys().then(function(preKeyCount) { + if (preKeyCount < 10) { + return generateKeys(100); + } + }); + } + }; + + function createAccount(number, verificationCode, identityKeyPair, single_device) { + textsecure.storage.put('identityKey', identityKeyPair); + + var signalingKey = textsecure.crypto.getRandomBytes(32 + 20); + textsecure.storage.put('signaling_key', signalingKey); + + var password = btoa(getString(textsecure.crypto.getRandomBytes(16))); + password = password.substring(0, password.length - 2); + textsecure.storage.put("password", password); + + var registrationId = axolotl.util.generateRegistrationId(); + textsecure.storage.put("registrationId", registrationId); + + return textsecure.api.confirmCode( + number, verificationCode, password, signalingKey, registrationId, single_device + ).then(function(response) { + textsecure.storage.user.setNumberAndDeviceId(number, response.deviceId || 1); + textsecure.storage.put("regionCode", libphonenumber.util.getRegionCodeForNumber(number)); + }); + } + + textsecure.AccountManager = AccountManager; + +}()); + +function generateKeys(count, progressCallback) { + if (typeof progressCallback !== 'function') { + progressCallback = undefined; + } + var startId = textsecure.storage.get('maxPreKeyId', 1); + var signedKeyId = textsecure.storage.get('signedKeyId', 1); + + if (typeof startId != 'number') { + throw new Error('Invalid maxPreKeyId'); + } + if (typeof signedKeyId != 'number') { + throw new Error('Invalid signedKeyId'); + } + + var store = textsecure.storage.axolotl; + var identityKey = store.getMyIdentityKey(); + var result = { preKeys: [], identityKey: identityKey.pubKey }; + var promises = []; + + for (var keyId = startId; keyId < startId+count; ++keyId) { + promises.push( + axolotl.util.generatePreKey(keyId).then(function(res) { + store.putPreKey(res.keyId, res.keyPair); + result.preKeys.push({ + keyId : res.keyId, + publicKey : res.keyPair.pubKey + }); + if (progressCallback) { progressCallback(); } + }) + ); + } + + promises.push( + axolotl.util.generateSignedPreKey(identityKey, signedKeyId).then(function(res) { + store.putSignedPreKey(res.keyId, res.keyPair); + result.signedPreKey = { + keyId : res.keyId, + publicKey : res.keyPair.pubKey, + signature : res.signature + }; + }) + ); + + store.removeSignedPreKey(signedKeyId - 2); + textsecure.storage.put('maxPreKeyId', startId + count); + textsecure.storage.put('signedKeyId', signedKeyId + 1); + return Promise.all(promises).then(function() { + return result; + }); +} diff --git a/libtextsecure/helpers.js b/libtextsecure/helpers.js index b881dc26163d..ada38b17e65b 100644 --- a/libtextsecure/helpers.js +++ b/libtextsecure/helpers.js @@ -247,113 +247,3 @@ textsecure.processDecrypted = function(decrypted, source) { return decrypted; }); }; - -window.textsecure.refreshPreKeys = function() { - return textsecure.api.getMyKeys().then(function(preKeyCount) { - if (preKeyCount < 10) { - generateKeys(); - } - }); -}; - -function createAccount(number, verificationCode, identityKeyPair, single_device) { - textsecure.storage.put('identityKey', identityKeyPair); - - var signalingKey = textsecure.crypto.getRandomBytes(32 + 20); - textsecure.storage.put('signaling_key', signalingKey); - - var password = btoa(getString(textsecure.crypto.getRandomBytes(16))); - password = password.substring(0, password.length - 2); - textsecure.storage.put("password", password); - - var registrationId = axolotl.util.generateRegistrationId(); - textsecure.storage.put("registrationId", registrationId); - - return textsecure.api.confirmCode( - number, verificationCode, password, signalingKey, registrationId, single_device - ).then(function(response) { - textsecure.storage.user.setNumberAndDeviceId(number, response.deviceId || 1); - textsecure.storage.put("regionCode", libphonenumber.util.getRegionCodeForNumber(number)); - - return textsecure.protocol_wrapper.generateKeys().then(textsecure.registration.done); - }); -} - -function generateKeys(count, progressCallback) { - if (count === undefined) { - throw TypeError('generateKeys: count is undefined'); - } - if (typeof progressCallback !== 'function') { - progressCallback = undefined; - } - var store = textsecure.storage.axolotl; - var identityKey = store.getMyIdentityKey(); - var result = { preKeys: [], identityKey: identityKey.pubKey }; - var promises = []; - - var startId = textsecure.storage.get('maxPreKeyId', 1); - var signedKeyId = textsecure.storage.get('signedKeyId', 1); - - for (var keyId = startId; keyId < startId+count; ++keyId) { - promises.push( - axolotl.util.generatePreKey(keyId).then(function(res) { - store.putPreKey(res.keyId, res.keyPair); - result.preKeys.push({ - keyId : res.keyId, - publicKey : res.keyPair.pubKey - }); - if (progressCallback) { progressCallback(); } - }) - ); - } - promises.push( - axolotl.util.generateSignedPreKey(identityKey, signedKeyId).then(function(res) { - store.putSignedPreKey(res.keyId, res.keyPair); - result.signedPreKey = { - keyId : res.keyId, - publicKey : res.keyPair.pubKey, - signature : res.signature - }; - }) - ); - - store.removeSignedPreKey(signedKeyId - 2); - textsecure.storage.put('maxPreKeyId', startId + count); - textsecure.storage.put('signedKeyId', signedKeyId + 1); - - return Promise.all(promises).then(function() { - return result; - }); -}; - -window.textsecure.registerSecondDevice = function(setProvisioningUrl, confirmNumber, progressCallback) { - return textsecure.protocol_wrapper.createIdentityKeyRecvSocket().then(function(cryptoInfo) { - return new Promise(function(resolve) { - new WebSocketResource(textsecure.api.getTempWebsocket(), function(request) { - if (request.path == "/v1/address" && request.verb == "PUT") { - var proto = textsecure.protobuf.ProvisioningUuid.decode(request.body); - setProvisioningUrl([ - 'tsdevice:/?uuid=', proto.uuid, '&pub_key=', - encodeURIComponent(btoa(getString(cryptoInfo.pubKey))) - ].join('')); - request.respond(200, 'OK'); - } else if (request.path == "/v1/message" && request.verb == "PUT") { - var envelope = textsecure.protobuf.ProvisionEnvelope.decode(request.body, 'binary'); - request.respond(200, 'OK'); - resolve(cryptoInfo.decryptAndHandleDeviceInit(envelope).then(function(provisionMessage) { - return confirmNumber(provisionMessage.number).then(function() { - return createAccount( - provisionMessage.number, - provisionMessage.provisioningCode, - provisionMessage.identityKeyPair, - false - ); - }); - })); - } else { - console.log('Unknown websocket message', request.path); - } - }); - }); - }); -}; diff --git a/libtextsecure/test/index.html b/libtextsecure/test/index.html index 5e8c26e9eb64..782583e06b2e 100644 --- a/libtextsecure/test/index.html +++ b/libtextsecure/test/index.html @@ -45,6 +45,7 @@ +