add nopasswd

This commit is contained in:
Matias Alejo Garcia 2015-02-23 21:02:26 -03:00
parent 531a794e96
commit 2c88e49e96
8 changed files with 182 additions and 41 deletions

View File

@ -8,6 +8,7 @@ program = utils.configureCommander(program);
program
.option('-t, --testnet', 'Create a Testnet Wallet')
.option('-n, --nopasswd [level]', 'Set access for no password usage: none(default), readonly, readwrite, full', 'none')
.usage('[options] <walletName> <m-n> [copayerName]')
.parse(process.argv);

View File

@ -8,6 +8,7 @@ program = utils.configureCommander(program);
program
.option('-t, --testnet', 'Create a Testnet Extended Private Key')
.option('-n, --nopasswd [level]', 'Set access for no password usage: none(default), readonly, readwrite, full', 'none')
.parse(process.argv);
var args = program.args;

View File

@ -7,6 +7,7 @@ program = utils.configureCommander(program);
program
.usage('[options] <secret> [copayerName]')
.option('-n, --nopasswd [level]', 'Set access for no password usage: none(default), readonly, readwrite, full', 'none')
.parse(process.argv);
var args = program.args;

View File

@ -1,5 +1,6 @@
var _ = require('lodash');
var Client = require('../lib/client');
var read = require('read')
var Utils = function() {};
@ -38,12 +39,27 @@ Utils.getClient = function(args) {
var storage = new Client.FileStorage({
filename: args.file || process.env['BIT_FILE'],
});
return new Client({
var c = new Client({
storage: storage,
baseUrl: args.host || process.env['BIT_HOST'],
verbose: args.verbose
password: args.password,
verbose: args.verbose,
});
if (args.nopasswd)
c.setNopasswdAccess(args.nopasswd);
c.on('needPassword', function(cb) {
if (args.password) {
return cb(args.password);
} else {
read({ prompt: 'Password: ', silent: true }, function(er, password) {
return cb(password);
})
}
});
return c;
}
Utils.findOneTxProposal = function(txps, id) {

View File

@ -6,6 +6,7 @@ var util = require('util');
var async = require('async');
var log = require('npmlog');
var request = require('request')
var events = require('events');
log.debug = log.verbose;
var Bitcore = require('bitcore')
@ -111,6 +112,7 @@ function API(opts) {
this.request = request || opts.request;
this.baseUrl = opts.baseUrl || BASE_URL;
this.basePath = this.baseUrl.replace(/http.?:\/\/[a-zA-Z0-9:-]*\//, '/');
this.noPasswdAccess = opts.noPasswdAccess || 'full';
if (this.verbose) {
log.level = 'debug';
} else {
@ -118,7 +120,7 @@ function API(opts) {
}
};
util.inherits(API, events.EventEmitter);
API.prototype._tryToCompleteFromServer = function(wcd, cb) {
@ -143,7 +145,7 @@ API.prototype._tryToCompleteFromServer = function(wcd, cb) {
wcd.publicKeyRing = _.pluck(wallet.copayers, 'xPubKey')
self.storage.save(wcd, function(err) {
self.save(wcd, function(err) {
return cb(err, wcd);
});
});
@ -163,7 +165,7 @@ API.prototype._tryToCompleteFromData = function(wcd, toComplete, cb) {
return cb(ex);
}
this.storage.save(wcd, function(err) {
this.save(wcd, function(err) {
return cb(err, wcd);
});
};
@ -202,8 +204,16 @@ API.prototype._processWcdAfterRead = function(rawData, requiredAccess, cb) {
// 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);
try {
rawData = WU.decryptWallet(rawData, password);
} catch (e) {};
if (!rawData)
return cb('NOTAUTH');
access = WU.accessFromData(rawData);
accessLevel = WU.accessNameToLevel(access);
// Is the data available?
if (requiredAccessLevel <= accessLevel)
@ -213,16 +223,25 @@ API.prototype._processWcdAfterRead = function(rawData, requiredAccess, cb) {
});
};
API.prototype._processWcdBeforeWrite = function(wcd, accessWithoutEncrytion, cb) {
API.prototype.setNopasswdAccess = function(noPasswdAccess) {
if (!_.contains(['none', 'readonly', 'readwrite', 'full'], noPasswdAccess))
throw new Error('Bad nopasswd access:' + noPasswdAccess);
this.noPasswdAccess = noPasswdAccess;
};
API.prototype._processWcdBeforeWrite = function(wcd, cb) {
var self = this;
// 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);
});
if (this.noPasswdAccess == 'full') {
return cb(null, wcd);
} else {
return rawdata;
this.emit('needPassword', function(password) {
if (!password) return cb('No password given');
var ewcd = WalletUtils.encryptWallet(wcd, self.noPasswdAccess, password);
return cb(null, ewcd);
});
}
};
@ -326,6 +345,18 @@ API.prototype._doJoinWallet = function(walletId, walletPrivKey, xPubKey, copayer
});
};
API.prototype.save = function(inWcd, cb) {
var self = this;
self._processWcdBeforeWrite(inWcd, function(err, wcd) {
if (err) return cb(err);
self.storage.save(wcd, function(err) {
return cb(err, null);
});
});
}
API.prototype.generateKey = function(network, cb) {
var self = this;
network = network || 'livenet';
@ -337,7 +368,8 @@ API.prototype.generateKey = function(network, cb) {
return cb(self.storage.getName() + ' already contains a wallet');
var wcd = _initWcd(network);
self.storage.save(wcd, function(err) {
self.save(wcd, function(err) {
return cb(err, null);
});
});
@ -378,7 +410,7 @@ API.prototype.createWallet = function(walletName, copayerName, m, n, network, cb
self._doJoinWallet(walletId, walletPrivKey, wcd.publicKeyRing[0], copayerName,
function(err, wallet) {
if (err) return cb(err);
self.storage.save(wcd, function(err) {
self.save(wcd, function(err) {
return cb(err, n > 1 ? secret : null);
});
});
@ -439,7 +471,7 @@ API.prototype.joinWallet = function(secret, copayerName, cb) {
function(err, joinedWallet) {
if (err) return cb(err);
_addWalletToWcd(wcd, secretData.walletPrivKey, joinedWallet.m, joinedWallet.n);
self.storage.save(wcd, cb);
self.save(wcd, cb);
});
});
};
@ -639,7 +671,7 @@ API.prototype.import = function(str, cb) {
return cb('Invalid source wallet');
wcd.network = wcd.publicKeyRing[0].substr(0, 4) == 'tpub' ? 'testnet' : 'livenet';
self.storage.save(wcd, function(err) {
self.save(wcd, function(err) {
return cb(err, WalletUtils.accessFromData(wcd));
});
});

View File

@ -40,7 +40,10 @@ WalletUtils.accessFromData = function(data) {
if (data.rwPrivKey)
return 'readwrite';
return 'readonly';
if (data.roPrivKey)
return 'readonly';
return 'none';
};
WalletUtils.accessNameToLevel = function(name) {
@ -51,13 +54,12 @@ WalletUtils.accessNameToLevel = function(name) {
return 20;
if (name === 'readonly')
return 10;
if (name === 'none')
return 0;
throw new Error('Bad access name:' + name);
};
WalletUtils.isAccessEncrypted = function(name) {};
WalletUtils.verifyMessage = function(text, signature, pubKey) {
$.checkArgument(text);
$.checkArgument(pubKey);
@ -160,7 +162,8 @@ WalletUtils.privateKeyToAESKey = function(privKey) {
WalletUtils.decryptWallet = function(data, password) {
$.checkArgument(data.enc);
var extraFields = sjcl.decrypt(password, data.enc);
var extraFields = JSON.parse(sjcl.decrypt(password, data.enc));
delete data.enc;
return _.extend(data, extraFields);
};
@ -171,24 +174,26 @@ WalletUtils.sjclOpts = {
WalletUtils.encryptWallet = function(data, accessWithoutEncrytion, password) {
var toEncryptByLevel = {
readwrite: [],
readonly: [],
// Fields to encrypt, given the NOPASSWD access level
var fieldsEncryptByLevel = {
none: _.keys(data),
readonly: ['xPrivKey', 'rwPrivKey', 'publicKeyRing' ],
readwrite: ['xPrivKey', ],
full: [],
};
var toEncrypt = whatToEncryptByLevel[accessWithoutEncrytion];
var fieldsEncrypt = fieldsEncryptByLevel[accessWithoutEncrytion];
$.checkState(!_.isUndefined(fieldsEncrypt));
if (!_.every(toEncrypt, function(k) {
if (!_.every(fieldsEncrypt, function(k) {
return data[k];
})) throw new Error('Wallet does not contain necesary info to encrypt');
var toEncrypt = _.pick(data, whatToEncrypt);
var toEncrypt = _.pick(data, fieldsEncrypt);
var enc = sjcl.encrypt(password, JSON.stringify(toEncrypt), WalletUtils.sjclOpts);
var ret = _.omit(data, toEncrypt);
var ret = _.omit(data, fieldsEncrypt);
ret.enc = enc;
console.log('[walletutils.js.191:ret:]',ret); //TODO
return ret;
};

View File

@ -33,6 +33,7 @@
"npmlog": "^0.1.1",
"preconditions": "^1.0.7",
"qr-image": "*",
"read": "^1.0.5",
"request": "^2.53.0",
"sjcl": "^1.0.2",
"uuid": "*"

View File

@ -221,20 +221,105 @@ describe('client API ', function() {
});
describe.skip('Storage Encryption', function() {
it('should check balance in a 1-1 ', function(done) {
describe('Storage Encryption', function() {
beforeEach(function() {
_.each(_.range(3), function(i) {
clients[i].on('needPassword', function(cb) {
return cb('1234#$@#%F,./.**');
});
});
});
it('full encryption roundtrip', function(done) {
clients[0].setNopasswdAccess('none');
helpers.createAndJoinWallet(clients, 1, 1, function(err) {
should.not.exist(err);
clients[0].getBalance(function(err, x) {
// Load it
var wcd = JSON.parse(fsmock._get('client0'));
fsmock._set('client1', wcd);
clients[1].getBalance(function(err, bal0) {
should.not.exist(err);
var wcd = JSON.parse(fsmock._get('client0'));
console.log('[clientApi.js.236]', wcd); //TODO
done();
})
});
});
});
it('should fail if wrong password', function(done) {
clients[0].setNopasswdAccess('none');
helpers.createAndJoinWallet(clients, 1, 1, function(err) {
should.not.exist(err);
// Load it
var wcd = JSON.parse(fsmock._get('client0'));
fsmock._set('client4', wcd);
clients[4].on('needPassword', function(cb) {
return cb('1');
});
clients[4].getBalance(function(err, bal0) {
err.should.equal('NOTAUTH');
done();
});
});
});
it('should encrypt everything', function(done) {
clients[0].setNopasswdAccess('none');
helpers.createAndJoinWallet(clients, 1, 1, function(err) {
should.not.exist(err);
var wcd = JSON.parse(fsmock._get('client0'));
_.keys(wcd).should.deep.equal(['enc']);
done();
});
});
it('should encrypt xpriv access', function(done) {
clients[0].setNopasswdAccess('readwrite');
helpers.createAndJoinWallet(clients, 1, 1, function(err) {
should.not.exist(err);
var wcd = JSON.parse(fsmock._get('client0'));
should.exist(wcd.enc);
should.not.exist(wcd.xpriv);
done();
});
});
it('should encrypt rwkey', function(done) {
clients[0].setNopasswdAccess('readonly');
helpers.createAndJoinWallet(clients, 1, 1, function(err) {
should.not.exist(err);
var wcd = JSON.parse(fsmock._get('client0'));
should.exist(wcd.enc);
should.not.exist(wcd.xpriv);
should.not.exist(wcd.rwPrivKey);
done();
});
});
_.each(['full', 'readwrite', 'readonly', 'none'], function(k) {
it('full encryption roundtrip: type:' + k, function(done) {
clients[0].setNopasswdAccess(k);
helpers.createAndJoinWallet(clients, 1, 1, function(err) {
should.not.exist(err);
// Load it
var wcd = JSON.parse(fsmock._get('client0'));
fsmock._set('client1', wcd);
clients[1].getBalance(function(err, bal0) {
should.not.exist(err);
done();
});
});
});
});
it.skip('should not ask for password if not needed (readonly)', function(done) {});
it.skip('should not ask for password if not needed (readwrite)', function(done) {});
});
@ -460,7 +545,6 @@ describe('client API ', function() {
};
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);