encryption schema
This commit is contained in:
parent
a266ec94e2
commit
531a794e96
|
@ -42,6 +42,7 @@ Utils.getClient = function(args) {
|
|||
storage: storage,
|
||||
baseUrl: args.host || process.env['BIT_HOST'],
|
||||
verbose: args.verbose
|
||||
password: args.password,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
||||
API.prototype._load = function(cb) {
|
||||
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(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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue