Merge pull request #252 from colkito/feature/password-web-encryption

Feature/password web encryption // tons of good job!!
This commit is contained in:
Gustavo Maximiliano Cortez 2014-05-01 17:12:51 -03:00
commit 493f5c3427
14 changed files with 217 additions and 96 deletions

View file

@ -42,7 +42,9 @@ var config = {
port: 3001
},
verbose: 1,
themes: ['default']
themes: ['default'],
iterations: 1000,
storageSalt: 'mjuBtGybi/4=', // choose your own salt (base64)
};
var log = function () {

View file

@ -3,6 +3,8 @@
module.exports.PublicKeyRing = require('./js/models/core/PublicKeyRing');
module.exports.TxProposals = require('./js/models/core/TxProposals');
module.exports.PrivateKey = require('./js/models/core/PrivateKey');
module.exports.Passphrase = require('./js/models/core/Passphrase');
// components
var WebRTC = module.exports.WebRTC = require('./js/models/network/WebRTC');
@ -13,7 +15,7 @@ var StorageLocalEncrypted = module.exports.StorageLocalEncrypted = require('./js
var WalletFactory = require('soop').load('./js/models/core/WalletFactory',{
Network: WebRTC,
Blockchain: Insight,
Storage: StorageLocalPlain,
Storage: StorageLocalEncrypted,
});
module.exports.WalletFactory = WalletFactory;

View file

@ -356,15 +356,15 @@ hr { margin: 2.25rem 0;}
.box-setup-copayers:after {
border-color: rgba(255, 255, 255, 0);
border-bottom-color: #ffffff;
border-width: 30px;
margin-left: -30px;
border-width: 20px;
margin-left: -20px;
}
.box-setup-copayers:before {
border-color: rgba(238, 238, 238, 0);
border-bottom-color: #eee;
border-width: 33px;
margin-left: -33px;
border-width: 23px;
margin-left: -23px;
}
.box-setup-copayers-fix {

View file

@ -142,34 +142,31 @@
Looking for peers...
</div>
<div ng-show="!loading">
<div class="row">
<div class="large-6 columns">
<div class="box-signin">
<h3>Join a Wallet in Creation</h3>
<input type="text" class="form-control" placeholder="Paste wallet secret here"
ng-model="connectionId" required autofocus>
<input type="text" class="form-control" placeholder="Your name (optional)"
ng-model="nickname">
<button class="button primary expand radius"
ng-click="join(connectionId,nickname)" ng-disabled="loading" loading="Joining">Join</button>
<h3>Join a Wallet in Creation</h3>
<input type="text" class="form-control" placeholder="Paste wallet secret here" ng-model="connectionId" required autofocus>
<input type="password" class="form-control" placeholder="Your wallet password" ng-model="joinPassword">
<input type="text" class="form-control" placeholder="Your name (optional)" ng-model="nickname">
<button class="button primary expand radius" ng-click="join()" ng-disabled="loading" loading="Joining">Join</button>
</div>
</div>
<div class="large-6 columns">
<div class="box-signin">
<div ng-show="wallets.length">
<h3>Open Wallet</h3>
<select class="form-control" ng-model="selectedWalletId"
ng-options="w.id as w.show for w in wallets">
</select>
<button class="button secondary expand radius" type="button"
ng-click="open(selectedWalletId)" ng-disabled="loading" loading="Opening">Open</button>
</div>
<div ng-show="!wallets.length">
<h3>Create a new wallet</h3>
<input type="text" class="form-control" ng-model="walletName" placeholder="Wallet name (optional)">
<button class="button secondary expand radius" ng-click="create(walletName)" ng-disabled="loading" loading="Creating">Create</button>
</div>
<div ng-show="wallets.length">
<h3>Open Wallet</h3>
<select class="form-control" ng-model="selectedWalletId" ng-options="w.id as w.show for w in wallets">
</select>
<input type="password" class="form-control" placeholder="Your wallet password" ng-model="openPassword">
<button class="button secondary expand radius" type="button" ng-click="open()" ng-disabled="loading" loading="Opening">Open</button>
</div>
<div ng-show="!wallets.length">
<h3>Create a new wallet</h3>
<input type="text" class="form-control" ng-model="walletName" placeholder="Wallet name (optional)">
<input type="password" class="form-control" placeholder="Your wallet password" ng-model="createPassword">
<button class="button secondary expand radius" ng-click="create()" ng-disabled="loading" loading="Creating">Create</button>
</div>
</div>
</div>
</div>
@ -182,8 +179,6 @@
<a ng-href="#import">Import from file</a>
</div>
</div>
</div> <!-- End !loading -->
</div>
</script>
@ -191,7 +186,6 @@
<script type="text/ng-template" id="import.html">
<div ng-controller="ImportController">
<h3>{{title}}</h3>
<div class="large-6 columns">
<input type="file" class="form-control" placeholder="Select a backup file" ng-model="backupFile" autofocus="" ng-file-select>
</div>
@ -206,18 +200,16 @@
<div ng-show="!loading">
<div class="row">
<div class="small-12 medium-8 medium-centered large-8 large-centered columns box-setup">
<div class="large-6 columns line-dashed-v">
<h6>Select total number of copayers</h6>
<select ng-model="totalCopayers"
ng-options="totalCopayers as totalCopayers for totalCopayers in TCValues">
</select>
</div>
<div class="large-6 columns">
<h6>Select required number of signatures</h6>
<select ng-model="requiredCopayers"
ng-options="requiredCopayers as requiredCopayers for requiredCopayers in RCValues">
</select>
</div>
<div class="large-6 columns line-dashed-v">
<h6>Select total number of copayers</h6>
<select ng-model="totalCopayers" ng-options="totalCopayers as totalCopayers for totalCopayers in TCValues">
</select>
</div>
<div class="large-6 columns">
<h6>Select required number of signatures</h6>
<select ng-model="requiredCopayers" ng-options="requiredCopayers as requiredCopayers for requiredCopayers in RCValues">
</select>
</div>
</div>
</div>
<div class="row">
@ -228,23 +220,23 @@
</div>
</div>
</div>
</div>
<div class="row">
<div class="small-12 medium-6 medium-centered large-6 large-centered columns m30v">
<h6>Wallet name (optional)</h6>
<input type="text" class="form-control" ng-model="walletName" placeholder="Enter wallet name">
<h6>Wallet Password</h6>
<input type="password" class="form-control" ng-model="walletPassword" required>
</div>
<div class="small-12 medium-6 medium-centered large-6 large-centered columns m30v">
<h6>Wallet name <small>(optional)</small></h6>
<input type="text" class="form-control" ng-model="walletName">
</div>
<div class="large-6 large-centered columns m30v">
<h6>Your name (optional)</h6>
<input ng-model="myNickname" placeholder="" class="size-24" style="width:100%">
<h6>Your name <small>(optional)</small></h6>
<input type="text" class="form-control" ng-model="myNickname">
</div>
</div>
<div class="row">
<div class="large-12 columns line-dashed">
<button class="button primary radius right" type="button"
ng-click="create(totalCopayers, requiredCopayers, walletName, myNickname)">
ng-click="create()">
Create {{requiredCopayers}}-of-{{totalCopayers}} wallet
</button>
<a class="button secondary radius" href="#signin">Go back</a>
@ -292,7 +284,7 @@
</div>
</div>
</script>
<!-- TRANSACTIONS -->
<script type="text/ng-template" id="transactions.html">
<div class="transactions" data-ng-controller="TransactionsController">
@ -537,6 +529,8 @@
<script src="lib/angular-foundation/mm-foundation-tpls.min.js"></script>
<script src="lib/peerjs/peer.js"></script>
<script src="lib/bitcore.js"></script>
<script src="lib/crypto-js/rollups/sha256.js"></script>
<script src="lib/crypto-js/rollups/pbkdf2.js"></script>
<script src="lib/crypto-js/rollups/aes.js"></script>
<script src="lib/file-saver/FileSaver.js"></script>
<script src="lib/socket.io.js"></script>
@ -551,6 +545,7 @@
<script src="js/services/video.js"></script>
<script src="js/services/walletFactory.js"></script>
<script src="js/services/controllerUtils.js"></script>
<script src="js/services/passphrase.js"></script>
<script src="js/controllers/header.js"></script>
<script src="js/controllers/footer.js"></script>

View file

@ -20,7 +20,8 @@ var copayApp = window.copayApp = angular.module('copay',[
'copay.setup',
'copay.directives',
'copay.video',
'copay.import'
'copay.import',
'copay.passphrase'
]);
angular.module('copay.header', []);
@ -37,4 +38,5 @@ angular.module('copay.socket', []);
angular.module('copay.directives', []);
angular.module('copay.video', []);
angular.module('copay.import', []);
angular.module('copay.passphrase', []);

View file

@ -1,9 +1,10 @@
'use strict';
angular.module('copay.setup').controller('SetupController',
function($scope, $rootScope, $location, walletFactory, controllerUtils) {
function($scope, $rootScope, $location, walletFactory, controllerUtils, Passphrase) {
$scope.loading = false;
$scope.walletPassword = $rootScope.walletPassword;
// ng-repeat defined number of times instead of repeating over array?
$scope.getNumber = function(num) {
@ -31,15 +32,18 @@ angular.module('copay.setup').controller('SetupController',
updateRCSelect(tc);
});
$scope.create = function(totalCopayers, requiredCopayers, walletName, myNickname) {
$scope.create = function() {
$scope.loading = true;
var passphrase = Passphrase.getBase64($scope.walletPassword);
var opts = {
requiredCopayers: requiredCopayers,
totalCopayers: totalCopayers,
name: walletName,
nickname: myNickname,
requiredCopayers: $scope.requiredCopayers,
totalCopayers: $scope.totalCopayers,
name: $scope.walletName,
nickname: $scope.myNickname,
passphrase: passphrase,
};
console.log('[setup.js.31:opts:]',opts); //TODO
var w = walletFactory.create(opts);
controllerUtils.startNetwork(w);
};

View file

@ -1,43 +1,54 @@
'use strict';
angular.module('copay.signin').controller('SigninController',
function($scope, $rootScope, $location, walletFactory, controllerUtils) {
function($scope, $rootScope, $location, walletFactory, controllerUtils, Passphrase) {
$scope.loading = false;
$scope.wallets = walletFactory.getWallets();
$scope.selectedWalletId = $scope.wallets.length ? $scope.wallets[0].id : null;
$scope.openPassword = '';
$scope.create = function(walletName) {
$scope.create = function() {
$scope.loading = true;
$rootScope.walletName = walletName;
$rootScope.walletName = $scope.walletName;
$rootScope.walletPassword = $scope.createPassword;
$location.path('setup');
};
$scope.open = function(walletId, opts) {
$scope.loading = true;
var w = walletFactory.open(walletId, opts);
controllerUtils.startNetwork(w);
$scope.open = function() {
if ($scope.openPassword != '') {
$scope.loading = true;
var passphrase = Passphrase.getBase64($scope.openPassword);
var w = walletFactory.open($scope.selectedWalletId, { passphrase: passphrase});
controllerUtils.startNetwork(w);
}
};
$scope.join = function(secret, nickname ) {
$scope.join = function() {
$scope.loading = true;
walletFactory.network.on('badSecret', function() {
});
walletFactory.joinCreateSession(secret, nickname, function(err,w) {
walletFactory.joinCreateSession($scope.connectionId, $scope.nickname, function(err,w) {
$scope.loading = false;
if (err || !w) {
if (err || !w || !$scope.joinPassword) {
if (err === 'joinError')
$rootScope.flashMessage = { message: 'Can not find peer'};
else if (err === 'badSecret')
$rootScope.flashMessage = { message: 'Bad secret secret string', type: 'error'};
else if (!$scope.joinPassword)
$rootScope.flashMessage = { message: 'Enter your wallet password', type: 'error' };
else
$rootScope.flashMessage = { message: 'Unknown error', type: 'error'};
controllerUtils.onErrorDigest();
}
else
} else {
var passphrase = Passphrase.getBase64($scope.joinPassword);
w.storage._setPassphrase(passphrase);
controllerUtils.startNetwork(w);
}
});
};
});

View file

@ -0,0 +1,24 @@
'use strict';
function Passphrase(config) {
config = config || {};
this.salt = config.storageSalt;
this.iterations = config.iterations || 1000;
};
Passphrase.prototype.get = function(password) {
var hash = CryptoJS.SHA256(CryptoJS.SHA256(password));
var salt = CryptoJS.enc.Base64.parse(this.salt);
var key512 = CryptoJS.PBKDF2(hash, salt, { keySize: 512/32, iterations: this.iterations });
return key512;
};
Passphrase.prototype.getBase64 = function(password) {
var key512 = this.get(password);
var keyBase64 = key512.toString(CryptoJS.enc.Base64);
return keyBase64;
};
module.exports = Passphrase;

View file

@ -86,7 +86,6 @@ WalletFactory.prototype.read = function(walletId) {
};
WalletFactory.prototype.create = function(opts) {
var s = WalletFactory.storage;
opts = opts || {};
this.log('### CREATING NEW WALLET.' +
(opts.id ? ' USING ID: ' + opts.id : ' NEW ID') +
@ -112,6 +111,8 @@ WalletFactory.prototype.create = function(opts) {
});
this.log('\t### TxProposals Initialized');
this.storage._setPassphrase(opts.passphrase);
opts.storage = this.storage;
opts.network = this.network;
opts.blockchain = this.blockchain;
@ -126,12 +127,15 @@ WalletFactory.prototype.create = function(opts) {
};
WalletFactory.prototype.open = function(walletId, opts) {
this.log('Opening walletId:' + walletId);
opts = opts || {};
opts.id = walletId;
opts.verbose = this.verbose;
this.storage._setPassphrase(opts.passphrase);
var w = this.read(walletId) || this.create(opts);
w.store();
return w;
};

View file

@ -1,8 +1,6 @@
'use strict';
var imports = require('soop').imports();
//var buffertools = imports.buffertools || require('buffertools');
var parent = imports.parent || require('./LocalPlain');
var id = 0;
function Storage(opts) {
@ -13,8 +11,6 @@ function Storage(opts) {
if (opts.password)
this._setPassphrase(opts.password);
}
Storage.parent = parent;
var pps = {};
Storage.prototype._getPassphrase = function() {
@ -58,38 +54,105 @@ Storage.prototype._read = function(k) {
console.log('Error while decrypting: '+e);
throw e;
};
return ret;
};
Storage.prototype._write = function(k,v) {
v = JSON.stringify(v);
v = this._encrypt(v);
localStorage.setItem(k, v);
};
// get value by key
Storage.prototype.getGlobal = function(k) {
return localStorage.getItem(k);
};
// set value for key
Storage.prototype.setGlobal = function(k,v) {
localStorage.setItem(k, JSON.stringify(v));
};
// remove value for key
Storage.prototype.removeGlobal = function(k) {
localStorage.removeItem(k);
};
Storage.prototype._key = function(walletId, k) {
return walletId + '::' + k;
};
// get value by key
Storage.prototype.get = function(walletId, k) {
var ret = this._read(this._key(walletId,k));
return ret;
};
// set value for key
Storage.prototype.set = function(walletId, k,v) {
this._write(this._key(walletId,k), v);
};
// remove value for key
Storage.prototype.remove = function(walletId, k) {
this.removeGlobal(this._key(walletId,k));
};
Storage.prototype.setName = function(walletId, name) {
this.setGlobal('nameFor::'+walletId, name);
};
Storage.prototype.getName = function(walletId) {
return this.getGlobal('nameFor::'+walletId);
};
Storage.prototype.getWalletIds = function() {
var walletIds = [];
var uniq = {};
for (var i = 0; i < localStorage.length; i++) {
var key = localStorage.key(i);
var split = key.split('::');
if (split.length == 2) {
var walletId = split[0];
if (walletId === 'nameFor') continue;
if (typeof uniq[walletId] === 'undefined' ) {
walletIds.push(walletId);
uniq[walletId] = 1;
}
}
}
return walletIds;
};
Storage.prototype.getWallets = function() {
var wallets = [];
var uniq = {};
var ids = this.getWalletIds();
for (var i in ids){
wallets.push({
id:ids[i],
name: this.getName(ids[i]),
});
}
return wallets;
};
//obj contains keys to be set
Storage.prototype.setFromObj = function(walletId, obj) {
for (var i in keys) {
var key = keys[0];
obj[key] = this.get(walletId, key);
for (var k in obj) {
this.set(walletId, k, obj[k]);
}
this.setName(walletId, obj.opts.name);
};
Storage.prototype.setFromEncryptedObj = function(walletId, base64) {
};
Storage.prototype.getEncryptedObj = function(walletId) {
var keys = this._getWalletKeys();
var obj = {};
for (var i in keys) {
var key = keys[0];
obj[key] = this.get(walletId, key);
}
var str = JSON.stringify(obj);
var base64 = this._encrypt(str).toString();
return base64;
// remove all values
Storage.prototype.clearAll = function() {
localStorage.clear();
};
module.exports = require('soop')(Storage);

View file

@ -0,0 +1,7 @@
'use strict';
var passphrase;
angular.module('copay.passphrase').factory('Passphrase', function($rootScope) {
passphrase = passphrase || new copay.Passphrase(config);
return passphrase;
});

View file

@ -3,6 +3,10 @@ var FakeStorage = function(){
this.storage = {};
};
FakeStorage.prototype._setPassphrase = function (password) {
this.storage.passphrase = password;
};
FakeStorage.prototype.setGlobal = function (id, payload) {
this.storage[id] = payload;
};

View file

@ -32,6 +32,7 @@ describe('WalletFactory model', function() {
port: 80
},
networkName: 'testnet',
passphrase: 'test',
};
it('should create the factory', function() {

View file

@ -68,7 +68,9 @@ var createBundle = function(opts) {
b.require('./js/models/core/PublicKeyRing', {
expose: '../js/models/core/PublicKeyRing'
});
b.require('./js/models/core/Passphrase', {
expose: '../js/models/core/Passphrase'
});
if (!opts.dontminify) {
b.transform({