diff --git a/.gitignore b/.gitignore index 640ac6f20..3081abfaa 100644 --- a/.gitignore +++ b/.gitignore @@ -39,6 +39,7 @@ README.html lib/* !lib/socket.io.js +!lib/sjcl.js js/copayBundle.js js/copayMain.js diff --git a/config.js b/config.js index 6bc3b5f7a..37972a7c6 100644 --- a/config.js +++ b/config.js @@ -42,7 +42,7 @@ var defaultConfig = { // local encryption/security config passphraseConfig: { - iterations: 100, + iterations: 1000, storageSalt: 'mjuBtGybi/4=', }, @@ -55,7 +55,7 @@ var defaultConfig = { plugins: { //LocalStorage: true, -// EncryptedLocalStorage: true, + // EncryptedLocalStorage: true, //GoogleDrive: true, //InsightStorage: true EncryptedInsightStorage: true, diff --git a/js/controllers/import.js b/js/controllers/import.js index 7b898bf09..6d83e62a5 100644 --- a/js/controllers/import.js +++ b/js/controllers/import.js @@ -27,7 +27,7 @@ angular.module('copayApp.controllers').controller('ImportController', if ($scope.skipTxProposals) skipFields.push('txProposals'); - $rootScope.iden.importEncryptedWallet(encryptedObj, password, skipFields, function(err, w) { + $rootScope.iden.importEncryptedWallet(encryptedObj, password, skipFields, opts, function(err, w) { if (!w) { $scope.loading = false; notification.error('Error', err || 'Wrong password'); @@ -67,9 +67,11 @@ angular.module('copayApp.controllers').controller('ImportController', reader.onloadend = function(evt) { if (evt.target.readyState == FileReader.DONE) { // DONE == 2 var encryptedObj = evt.target.result; + copay.Compatibility.importEncryptedWallet($rootScope.iden, encryptedObj, $scope.password, {}, - function(err, wallet){ + function(err, wallet) { if (err) { + $scope.loading = false; notification.error('Error', 'Could not read wallet. Please check your password'); } else { controllerUtils.installWalletHandlers($scope, wallet); @@ -109,23 +111,22 @@ angular.module('copayApp.controllers').controller('ImportController', if (backupFile) { reader.readAsBinaryString(backupFile); - } - else { - copay.Compatibility.importEncryptedWallet($rootScope.iden, backupText, $scope.password, {}, - function(err, wallet){ - if (err) { - notification.error('Error', 'Could not read wallet. Please check your password'); - } else { - copay.Compatibility.deleteOldWallet(backupOldWallet); - controllerUtils.installWalletHandlers($scope, wallet); - controllerUtils.setFocusedWallet(wallet); - return; - } + } else { + copay.Compatibility.importEncryptedWallet($rootScope.iden, backupText, $scope.password, {}, + function(err, wallet) { + if (err) { + notification.error('Error', 'Could not read wallet. Please check your password'); + } else { + copay.Compatibility.deleteOldWallet(backupOldWallet); + controllerUtils.installWalletHandlers($scope, wallet); + controllerUtils.setFocusedWallet(wallet); + return; } - ); + } + ); try { _importBackup(backupText); - } catch(e) { + } catch (e) { copay.Compatibility.importEncryptedWallet(backupText, $scope.password, $scope.skipPublicKeyRing, $scope.skipTxProposals); } } diff --git a/js/controllers/importProfile.js b/js/controllers/importProfile.js new file mode 100644 index 000000000..dd900ec6f --- /dev/null +++ b/js/controllers/importProfile.js @@ -0,0 +1,83 @@ +'use strict'; + +angular.module('copayApp.controllers').controller('ImportProfileController', + function($scope, $rootScope, $location, controllerUtils, notification, isMobile, pluginManager) { + controllerUtils.redirIfLogged(); + + $scope.title = 'Import a backup'; + $scope.importStatus = 'Importing wallet - Reading backup...'; + $scope.hideAdv = true; + $scope.is_iOS = isMobile.iOS(); + + var reader = new FileReader(); + + var updateStatus = function(status) { + $scope.importStatus = status; + $scope.$digest(); + } + + var _importBackup = function(str) { + var password = $scope.password; + updateStatus('Importing profile - Setting things up...'); + + copay.Identity.importFromEncryptedFullJson(str, password, { + pluginManager: pluginManager, + network: config.network, + networkName: config.networkName, + walletDefaults: config.wallet, + passphraseConfig: config.passphraseConfig, + }, function(err, iden) { + if (err && !iden) { + controllerUtils.onErrorDigest( + $scope, (err.toString() || '').match('BADSTR') ? 'Bad password or corrupt profile file' : 'Unknown error'); + } else { + notification.info('Success', 'Profile imported successfully'); + $location.path('/'); + } + }); + }; + + $scope.openFileDialog = function() { + if (window.cshell) { + return cshell.send('backup:import'); + } + $scope.choosefile = !$scope.choosefile; + }; + + $scope.getFile = function() { + // If we use onloadend, we need to check the readyState. + reader.onloadend = function(evt) { + if (evt.target.readyState == FileReader.DONE) { // DONE == 2 + var encryptedObj = evt.target.result; + _importBackup(encryptedObj); + } + }; + }; + + $scope.import = function(form) { + $scope.loading = true; + + if (form.$invalid) { + $scope.loading = false; + notification.error('Error', 'There is an error in the form.'); + return; + } + + var backupFile = $scope.file; + var backupText = form.backupText.$modelValue; + var password = form.password.$modelValue; + + if (!backupFile && !backupText) { + $scope.loading = false; + notification.error('Error', 'Please, select your backup file'); + $scope.loading = false; + return; + } + + if (backupFile) { + reader.readAsBinaryString(backupFile); + } else { + _importBackup(backupText); + } + }; + }); diff --git a/js/models/Compatibility.js b/js/models/Compatibility.js index f003b9f63..32254f768 100644 --- a/js/models/Compatibility.js +++ b/js/models/Compatibility.js @@ -47,6 +47,7 @@ Compatibility._getWalletIds = function(cb) { Compatibility.importLegacy = function(encryptedWallet, password) { var passphrase = this.kdf(password); var ret = Compatibility._decrypt(encryptedWallet, passphrase); + if (!ret) return null; return ret; }; @@ -195,19 +196,19 @@ Compatibility.readWalletPre8 = function(walletId, password, cb) { Compatibility.importEncryptedWallet = function(identity, cypherText, password, opts, cb) { var crypto = (opts && opts.cryptoUtil) || cryptoUtils; - var key = crypto.kdf(password); - var obj = crypto.decrypt(key, cypherText); + + var obj = crypto.decrypt(password, cypherText); if (!obj) { log.info("Could not decrypt, trying legacy.."); obj = Compatibility.importLegacy(cypherText, password); if (!obj) { - return cb(new Error('Could not decrypt')) + return cb('Could not decrypt', null); } }; try { obj = JSON.parse(obj); } catch (e) { - return cb(new Error('Could not read encrypted wallet')); + return cb('Could not read encrypted wallet', null); } return identity.importWalletFromObj(obj, opts, cb); }; @@ -236,7 +237,7 @@ Compatibility.kdf = function(password) { }; Compatibility.deleteOldWallet = function(walletObj) { - localStorage.removeItem('wallet::'+walletObj.id+'_'+walletObj.name); + localStorage.removeItem('wallet::' + walletObj.id + '_' + walletObj.name); log.info('Old wallet ' + walletObj.name + ' deleted: ' + walletObj.id); }; diff --git a/js/models/Identity.js b/js/models/Identity.js index bf1985258..536c90274 100644 --- a/js/models/Identity.js +++ b/js/models/Identity.js @@ -225,8 +225,7 @@ Identity.prototype.toObj = function() { Identity.prototype.exportEncryptedWithWalletInfo = function(opts) { var crypto = opts.cryptoUtil || cryptoUtil; - var key = crypto.kdf(this.password); - return crypto.encrypt(key, this.exportWithWalletInfo(opts)); + return crypto.encrypt(this.password, this.exportWithWalletInfo(opts)); }; Identity.prototype.exportWithWalletInfo = function(opts) { @@ -287,11 +286,8 @@ Identity.prototype.close = function(cb) { * @return {Wallet} */ Identity.prototype.importEncryptedWallet = function(cypherText, password, opts, cb) { - var crypto = opts.cryptoUtil || cryptoUtil; - // TODO set iter and salt using config.js - var key = crypto.kdf(password); - var obj = crypto.decrypt(key, cypherText); + var obj = crypto.decrypt(password, cypherText); if (!obj) return cb(new Error('Could not decrypt')); try { obj = JSON.parse(obj); @@ -344,8 +340,12 @@ Identity.prototype.closeWallet = function(wallet, cb) { Identity.importFromEncryptedFullJson = function(str, password, opts, cb) { var crypto = opts.cryptoUtil || cryptoUtil; - var key = crypto.kdf(password); - return Identity.importFromFullJson(crypto.decript(key, str)); + + var str = crypto.decrypt(password, str); + if (!str) { + return cb('BADSTR'); + } + return Identity.importFromFullJson(str, password, opts, cb); }; Identity.importFromFullJson = function(str, password, opts, cb) { @@ -354,18 +354,21 @@ Identity.importFromFullJson = function(str, password, opts, cb) { try { json = JSON.parse(str); } catch (e) { - return cb('Unable to retrieve json from string', str); + return cb('BADSTR: Unable to retrieve json from string', str); } - if (!_.isNumber(json.iterations)) - return cb('BADSTR: Missing iterations'); - var email = json.email; - var iden = new Identity(email, password, opts); + + opts.email = email; + opts.password = password; + + var iden = new Identity(opts); json.wallets = json.wallets || {}; + async.map(json.wallets, function(walletData, callback) { - iden.importEncryptedWallet(wstr, password, opts, function(err, w) { + + iden.importWalletFromObj(walletData, opts, function(err, w) { if (err) return callback(err); log.debug('Wallet ' + w.getId() + ' imported'); callback(); diff --git a/js/models/Wallet.js b/js/models/Wallet.js index 1f961434e..f68f67abd 100644 --- a/js/models/Wallet.js +++ b/js/models/Wallet.js @@ -2967,8 +2967,7 @@ Wallet.prototype.getTransactionHistory = function(cb) { Wallet.prototype.exportEncrypted = function(password, opts) { opts = opts || {}; var crypto = opts.cryptoUtil || cryptoUtil; - var key = crypto.kdf(password); - return crypto.encrypt(key, this.toObj()); + return crypto.encrypt(password, this.toObj()); }; module.exports = Wallet; diff --git a/js/routes.js b/js/routes.js index a4ab4f01e..c179876f7 100644 --- a/js/routes.js +++ b/js/routes.js @@ -34,6 +34,9 @@ angular templateUrl: 'views/import.html', logged: true }) + .when('/importProfile', { + templateUrl: 'views/importProfile.html', + }) .when('/create', { templateUrl: 'views/create.html', logged: true @@ -109,7 +112,6 @@ angular $location.path('unsupported'); } else { if (!$rootScope.iden && next.logged) { - console.log('not logged... redirecting') $idle.unwatch(); $location.path('/'); } diff --git a/js/util/crypto.js b/js/util/crypto.js index 46da407df..ccd5f9935 100644 --- a/js/util/crypto.js +++ b/js/util/crypto.js @@ -4,9 +4,10 @@ var sjcl = require('sjcl'); var log = require('../log.js'); var _ = require('lodash'); +var config = require('../../config'); -var defaultSalt = 'mjuBtGybi/4='; -var defaultIterations = 100; +var defaultSalt = (config && config.passphraseConfig && config.passphraseConfig.storageSalt) || 'mjuBtGybi/4='; +var defaultIterations = (config && config.passphraseConfig && config.passphraseConfig.iterations) || 1000; module.exports = { @@ -50,6 +51,8 @@ module.exports = { if (!_.isString(message)) { message = JSON.stringify(message); } + sjcl.json.defaults.salt = defaultSalt; + sjcl.json.defaults.iter = defaultIterations; return sjcl.encrypt(key, message); }, diff --git a/karma.conf.js b/karma.conf.js index 64334a0f7..babd35e39 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -46,7 +46,6 @@ module.exports = function(config) { 'js/log.js', 'js/routes.js', 'js/services/*.js', - 'js/util/*.js', 'js/directives.js', 'js/filters.js', 'js/controllers/*.js', diff --git a/test/Compatibility.js b/test/Compatibility.js index f90035a07..00bd976e0 100644 --- a/test/Compatibility.js +++ b/test/Compatibility.js @@ -1,4 +1,3 @@ - var compat = require('../js/models/Compatibility'); describe('Compatibility', function() { @@ -9,12 +8,12 @@ describe('Compatibility', function() { should.not.exist(wo); }); - it('should generate passphrases acording to old algorightm', function() { + it('should generate passphrases acording to old algorithm', function() { var passphrase = compat.kdf(legacyPassword1); passphrase.should.equal(legacyPassphrase1); }); - + it('should be able to decrypt an old backup', function() { var str = compat.importLegacy(encryptedLegacy1, legacyPassword1); @@ -45,8 +44,8 @@ describe('Compatibility', function() { var iden = sinon.stub(); iden.importWalletFromObj = sinon.stub().yields(null); - compat.importEncryptedWallet(iden, encryptedLegacy1, legacyPassword1, {}, function(err){ - var s = iden.importWalletFromObj; + compat.importEncryptedWallet(iden, encryptedLegacy1, legacyPassword1, {}, function(err) { + var s = iden.importWalletFromObj; s.getCall(0).args[0].opts.id.should.equal('48ba2f1ffdfe9708'); done(); }); diff --git a/test/Identity.js b/test/Identity.js index 0fb900f29..0fc31d795 100644 --- a/test/Identity.js +++ b/test/Identity.js @@ -260,8 +260,7 @@ describe('Identity model', function() { sinon.stub(iden, 'importWalletFromObj').yields(null); iden.importEncryptedWallet(123, 'password', opts, function(err) { should.not.exist(err); - fakeCrypto.kdf.getCall(0).args[0].should.equal('password'); - fakeCrypto.decrypt.getCall(0).args[0].should.equal('passphrase'); + fakeCrypto.decrypt.getCall(0).args[0].should.equal('password'); fakeCrypto.decrypt.getCall(0).args[1].should.equal(123); done(); }); diff --git a/views/home.html b/views/home.html index 808573bb6..e069a4d33 100644 --- a/views/home.html +++ b/views/home.html @@ -58,12 +58,18 @@