diff --git a/bit-wallet/cli-utils.js b/bit-wallet/cli-utils.js index f9abc22..4c63f05 100644 --- a/bit-wallet/cli-utils.js +++ b/bit-wallet/cli-utils.js @@ -42,6 +42,7 @@ Utils.getClient = function(args) { storage: storage, baseUrl: args.host || process.env['BIT_HOST'], verbose: args.verbose + password: args.password, }); } diff --git a/lib/client/api.js b/lib/client/api.js index db61edb..17c02df 100644 --- a/lib/client/api.js +++ b/lib/client/api.js @@ -118,6 +118,8 @@ function API(opts) { } }; + + API.prototype._tryToCompleteFromServer = function(wcd, cb) { if (!wcd.walletPrivKey) @@ -176,15 +178,65 @@ API.prototype._tryToComplete = function(opts, wcd, cb) { }; +// access: 'full' > 'readwrite' > readonly' +API.prototype._processWcdAfterRead = function(rawData, requiredAccess, cb) { + var WU = WalletUtils; + requiredAccess = requiredAccess || 'full'; + + if (!rawData) + return cb(null, rawData); + + var requiredAccessLevel = WU.accessNameToLevel(requiredAccess); + + var access = WU.accessFromData(rawData); + var accessLevel = WU.accessNameToLevel(access); + + // Is the data available? + if (requiredAccessLevel <= accessLevel) + return cb(null, rawData); + + // Has any encrypted info? + if (!rawData.enc) + return cb('NOTAUTH'); + + // Decrypt it and try again + this.emit('needPassword', function(password) { + if (!password) return cb('No password'); + rawData = WE.decryptWallet(rawData, password); + var access = WU.accessFromData(rawData); + + // Is the data available? + if (requiredAccessLevel <= accessLevel) + return cb(null, rawData); + + return cb('NOTAUTH'); + }); +}; + +API.prototype._processWcdBeforeWrite = function(wcd, accessWithoutEncrytion, cb) { + // Is any encrypted? + if (encryptedAccess) { + this.emit('needPassword', function(password) { + if (!password) return cb('No password'); + rawdata = WE.encryptWallet(wcd, accessWithoutEncrytion, password); + return cb(null, rawdata); + }); + } else { + return rawdata; + } +}; + + -API.prototype._load = function(cb) { +API.prototype._load = function(opts, cb) { var self = this; + $.shouldBeFunction(cb); - this.storage.load(function(err, wcd) { - if (err || !wcd) { + this.storage.load(function(err, rawdata) { + if (err || !rawdata) { return cb(err || 'wcd file not found.'); } - return cb(null, wcd); + self._processWcdAfterRead(rawdata, opts.requiredAccess, cb); }); }; @@ -197,7 +249,7 @@ API.prototype._load = function(cb) { API.prototype._loadAndCheck = function(opts, cb) { var self = this; - this._load(function(err, wcd) { + this._load(opts, function(err, wcd) { if (err) return cb(err); if (!wcd.n || (wcd.n > 1 && wcd.publicKeyRing.length != wcd.n)) { @@ -337,7 +389,9 @@ API.prototype.createWallet = function(walletName, copayerName, m, n, network, cb API.prototype.reCreateWallet = function(walletName, cb) { var self = this; - this._loadAndCheck({}, function(err, wcd) { + this._loadAndCheck({ + requiredAccess: 'readonly', + }, function(err, wcd) { if (err) return cb(err); var walletPrivKey = new Bitcore.PrivateKey(); @@ -393,7 +447,9 @@ API.prototype.joinWallet = function(secret, copayerName, cb) { API.prototype.getStatus = function(cb) { var self = this; - this._load(function(err, wcd) { + this._load({ + requiredAccess: 'readonly' + }, function(err, wcd) { if (err) return cb(err); var url = '/v1/wallets/'; @@ -418,7 +474,9 @@ API.prototype.sendTxProposal = function(opts, cb) { var self = this; - this._loadAndCheck({}, function(err, wcd) { + this._loadAndCheck({ + requiredAccess: 'readonly', + }, function(err, wcd) { if (err) return cb(err); if (!wcd.rwPrivKey) @@ -441,7 +499,9 @@ API.prototype.sendTxProposal = function(opts, cb) { API.prototype.createAddress = function(cb) { var self = this; - this._loadAndCheck({}, function(err, wcd) { + this._loadAndCheck({ + requiredAccess: 'readwrite', + }, function(err, wcd) { if (err) return cb(err); var url = '/v1/addresses/'; @@ -463,7 +523,9 @@ API.prototype.createAddress = function(cb) { API.prototype.getMainAddresses = function(opts, cb) { var self = this; - this._loadAndCheck({}, function(err, wcd) { + this._loadAndCheck({ + requiredAccess: 'readonly', + }, function(err, wcd) { if (err) return cb(err); var url = '/v1/addresses/'; @@ -489,7 +551,9 @@ API.prototype.history = function(limit, cb) { API.prototype.getBalance = function(cb) { var self = this; - this._loadAndCheck({}, function(err, wcd) { + this._loadAndCheck({ + requiredAccess: 'readonly', + }, function(err, wcd) { if (err) return cb(err); var url = '/v1/balance/'; self._doGetRequest(url, wcd, cb); @@ -508,7 +572,9 @@ API.prototype.export = function(opts, cb) { opts = opts || {}; var access = opts.access || 'full'; - this._load(function(err, wcd) { + this._load({ + requiredAccess: access, + }, function(err, wcd) { if (err) return cb(err); var v = []; @@ -587,6 +653,7 @@ API.prototype.parseTxProposals = function(txData, cb) { var self = this; this._loadAndCheck({ + requiredAccess: 'readonly', toComplete: txData.toComplete }, function(err, wcd) { if (err) return cb(err); @@ -617,7 +684,9 @@ API.prototype.parseTxProposals = function(txData, cb) { API.prototype.getTxProposals = function(opts, cb) { var self = this; - this._loadAndCheck({}, function(err, wcd) { + this._loadAndCheck({ + requiredAccess: 'readonly' + }, function(err, wcd) { if (err) return cb(err); var url = '/v1/txproposals/'; self._doGetRequest(url, wcd, function(err, txps) { @@ -681,7 +750,9 @@ API.prototype.getSignatures = function(txp, cb) { $.checkArgument(txp.creatorId); var self = this; - this._loadAndCheck({}, function(err, wcd) { + this._loadAndCheck({ + requiredAccess: 'full' + }, function(err, wcd) { if (err) return cb(err); if (!Verifier.checkTxProposal(wcd, txp)) { @@ -695,7 +766,9 @@ API.prototype.getSignatures = function(txp, cb) { API.prototype.getEncryptedWalletData = function(cb) { var self = this; - this._loadAndCheck({}, function(err, wcd) { + this._loadAndCheck({ + requiredAccess: 'readonly' + }, function(err, wcd) { if (err) return cb(err); var toComplete = JSON.stringify(_.pick(wcd, WALLET_AIRGAPPED_TOCOMPLETE)); return cb(null, _encryptMessage(toComplete, WalletUtils.privateKeyToAESKey(wcd.roPrivKey))); @@ -709,7 +782,9 @@ API.prototype.signTxProposal = function(txp, cb) { var self = this; - this._loadAndCheck({}, function(err, wcd) { + this._loadAndCheck({ + requiredAccess: txp.signatures ? 'readwrite' : 'full' + }, function(err, wcd) { if (err) return cb(err); if (!Verifier.checkTxProposal(wcd, txp)) { @@ -732,7 +807,9 @@ API.prototype.rejectTxProposal = function(txp, reason, cb) { var self = this; - this._loadAndCheck({}, + this._loadAndCheck({ + requiredAccess: 'readwrite' + }, function(err, wcd) { if (err) return cb(err); @@ -747,7 +824,9 @@ API.prototype.rejectTxProposal = function(txp, reason, cb) { API.prototype.broadcastTxProposal = function(txp, cb) { var self = this; - this._loadAndCheck({}, + this._loadAndCheck({ + requiredAccess: 'readwrite' + }, function(err, wcd) { if (err) return cb(err); @@ -760,7 +839,9 @@ API.prototype.broadcastTxProposal = function(txp, cb) { API.prototype.removeTxProposal = function(txp, cb) { var self = this; - this._loadAndCheck({}, + this._loadAndCheck({ + requiredAccess: 'readwrite' + }, function(err, wcd) { if (err) return cb(err); var url = '/v1/txproposals/' + txp.id; diff --git a/lib/walletutils.js b/lib/walletutils.js index d99fdf9..53b6eb6 100644 --- a/lib/walletutils.js +++ b/lib/walletutils.js @@ -43,6 +43,21 @@ WalletUtils.accessFromData = function(data) { return 'readonly'; }; +WalletUtils.accessNameToLevel = function(name) { + + if (name === 'full') + return 30; + if (name === 'readwrite') + return 20; + if (name === 'readonly') + return 10; + + throw new Error('Bad access name:' + name); +}; + + +WalletUtils.isAccessEncrypted = function(name) {}; + WalletUtils.verifyMessage = function(text, signature, pubKey) { $.checkArgument(text); $.checkArgument(pubKey); @@ -143,4 +158,39 @@ WalletUtils.privateKeyToAESKey = function(privKey) { return Bitcore.crypto.Hash.sha256(pk.toBuffer()).slice(0, 16).toString('base64'); }; +WalletUtils.decryptWallet = function(data, password) { + $.checkArgument(data.enc); + var extraFields = sjcl.decrypt(password, data.enc); + return _.extend(data, extraFields); +}; + + +WalletUtils.sjclOpts = { + iter: 5000, +}; + +WalletUtils.encryptWallet = function(data, accessWithoutEncrytion, password) { + + var toEncryptByLevel = { + readwrite: [], + readonly: [], + full: [], + }; + + var toEncrypt = whatToEncryptByLevel[accessWithoutEncrytion]; + + if (!_.every(toEncrypt, function(k) { + return data[k]; + })) throw new Error('Wallet does not contain necesary info to encrypt'); + + var toEncrypt = _.pick(data, whatToEncrypt); + var enc = sjcl.encrypt(password, JSON.stringify(toEncrypt), WalletUtils.sjclOpts); + + var ret = _.omit(data, toEncrypt); + ret.enc = enc; +console.log('[walletutils.js.191:ret:]',ret); //TODO + return ret; +}; + + module.exports = WalletUtils; diff --git a/test/integration/clientApi.js b/test/integration/clientApi.js index ae4413b..4c38943 100644 --- a/test/integration/clientApi.js +++ b/test/integration/clientApi.js @@ -221,6 +221,23 @@ describe('client API ', function() { }); + describe.skip('Storage Encryption', function() { + it('should check balance in a 1-1 ', function(done) { + helpers.createAndJoinWallet(clients, 1, 1, function(err) { + should.not.exist(err); + + clients[0].getBalance(function(err, x) { + should.not.exist(err); + + var wcd = JSON.parse(fsmock._get('client0')); + console.log('[clientApi.js.236]', wcd); //TODO + done(); + }) + }); + }); + }); + + describe('Wallet Creation', function() { it('should check balance in a 1-1 ', function(done) { helpers.createAndJoinWallet(clients, 1, 1, function(err) { @@ -281,7 +298,7 @@ describe('client API ', function() { should.not.exist(err); // Get right response - clients[0]._load(function(err, data) { + clients[0]._load({}, function(err, data) { var url = '/v1/wallets/'; clients[0]._doGetRequest(url, data, function(err, x) { @@ -305,7 +322,7 @@ describe('client API ', function() { should.not.exist(err); // Get right response - var data = clients[0]._load(function(err, data) { + var data = clients[0]._load({}, function(err, data) { var url = '/v1/wallets/'; clients[0]._doGetRequest(url, data, function(err, x) { @@ -330,7 +347,7 @@ describe('client API ', function() { should.not.exist(err); // Get right response - var data = clients[0]._load(function(err, data) { + var data = clients[0]._load({}, function(err, data) { var url = '/v1/wallets/'; clients[0]._doGetRequest(url, data, function(err, x) { @@ -362,6 +379,12 @@ describe('client API ', function() { delete data.rwPrivKey; fsmock._set('client0', JSON.stringify(data)); data.rwPrivKey = null; + + // Overwrite client's API auth checks + clients[0]._processWcdAfterRead = function(rawData, xx, cb) { + return cb(null, rawData); + }; + clients[0].createAddress(function(err, x0) { err.code.should.equal('NOTAUTHORIZED'); done(); @@ -378,6 +401,11 @@ describe('client API ', function() { clients[1].import(str, function(err, wallet) { should.not.exist(err); + // Overwrite client's API auth checks + clients[1]._processWcdAfterRead = function(rawData, xx, cb) { + return cb(null, rawData); + }; + clients[1].createAddress(function(err, x0) { err.code.should.equal('NOTAUTHORIZED'); clients[0].createAddress(function(err, x0) { @@ -425,7 +453,14 @@ describe('client API ', function() { }; clients[1].sendTxProposal(opts, function(err, x) { should.not.exist(err); + + // Overwrite client's API auth checks + clients[1]._processWcdAfterRead = function(rawData, xx, cb) { + return cb(null, rawData); + }; + clients[1].signTxProposal(x, function(err, tx) { +console.log('[clientApi.js.456:err:]',err); //TODO err.code.should.be.equal('BADSIGNATURES'); clients[1].getTxProposals({}, function(err, txs) { should.not.exist(err); @@ -656,7 +691,7 @@ describe('client API ', function() { should.not.exist(err); // Get right response - clients[0]._load(function(err, data) { + clients[0]._load({}, function(err, data) { var url = '/v1/addresses/'; clients[0]._doPostRequest(url, {}, data, function(err, address) { @@ -680,7 +715,7 @@ describe('client API ', function() { should.not.exist(err); // Get right response - clients[0]._load(function(err, data) { + clients[0]._load({}, function(err, data) { var url = '/v1/addresses/'; clients[0]._doPostRequest(url, {}, data, function(err, address) { @@ -865,7 +900,7 @@ describe('client API ', function() { // Get right response - clients[0]._load(function(err, data) { + clients[0]._load({}, function(err, data) { var url = '/v1/txproposals/'; clients[0]._doGetRequest(url, data, function(err, txps) { @@ -904,7 +939,7 @@ describe('client API ', function() { // Get right response - clients[0]._load(function(err, data) { + clients[0]._load({}, function(err, data) { var url = '/v1/txproposals/'; clients[0]._doGetRequest(url, data, function(err, txps) { @@ -943,7 +978,7 @@ describe('client API ', function() { // Get right response - clients[0]._load(function(err, data) { + clients[0]._load({}, function(err, data) { var url = '/v1/txproposals/'; clients[0]._doGetRequest(url, data, function(err, txps) { // Tamper data