Merge pull request #768 from ryanxcharles/feature/authentication

Authentication
This commit is contained in:
Matias Alejo Garcia 2014-06-26 23:12:46 -03:00
commit c16531dfb7
9 changed files with 330 additions and 32 deletions

82
js/models/core/Message.js Normal file
View File

@ -0,0 +1,82 @@
'use strict';
var imports = require('soop').imports();
var bitcore = require('bitcore');
/* Encrypted, authenticated messages to be shared between copayers */
var Message = function() {
};
Message.encode = function(topubkey, fromkey, payload) {
var version = new Buffer([0]);
var toencrypt = Buffer.concat([version, payload]);
var encrypted = Message._encrypt(topubkey, toencrypt);
var sig = Message._sign(fromkey, encrypted);
var encoded = {
pubkey: fromkey.public.toString('hex'),
sig: sig.toString('hex'),
encrypted: encrypted.toString('hex')
};
return encoded;
};
Message.decode = function(key, encoded) {
try {
var frompubkey = new Buffer(encoded.pubkey, 'hex');
} catch (e) {
throw new Error('Error decoding public key: ' + e);
}
try {
var sig = new Buffer(encoded.sig, 'hex');
var encrypted = new Buffer(encoded.encrypted, 'hex');
} catch (e) {
throw new Error('Error decoding data: ' + e);
}
try {
var v = Message._verify(frompubkey, sig, encrypted);
} catch (e) {
throw new Error('Error verifying signature: ' + e);
}
if (!v)
throw new Error('Invalid signature');
try {
var decrypted = Message._decrypt(key.private, encrypted);
} catch (e) {
throw new Error('Cannot decrypt data: ' + e);
}
if (decrypted[0] !== 0)
throw new Error('Invalid version number');
if (decrypted.length === 0)
throw new Error('No data present');
var payload = decrypted.slice(1);
return payload;
};
Message._encrypt = function(topubkey, payload, r, iv) {
var encrypted = bitcore.ECIES.encrypt(topubkey, payload, r, iv);
return encrypted;
};
Message._decrypt = function(privkey, encrypted) {
var decrypted = bitcore.ECIES.decrypt(privkey, encrypted);
return decrypted;
};
Message._sign = function(key, payload) {
var sig = bitcore.Message.sign(payload, key);
return sig;
};
Message._verify = function(pubkey, signature, payload) {
var v = bitcore.Message.verifyWithPubKey(pubkey, payload, signature);
return v;
};
module.exports = require('soop')(Message);

View File

@ -32,9 +32,17 @@ PrivateKey.prototype.getIdPriv = function() {
return this.idpriv;
};
PrivateKey.prototype.getIdKey = function() {
if (!this.idkey) {
this.cacheId();
}
return this.idkey;
};
PrivateKey.prototype.cacheId = function() {
var path = Structure.IdFullBranch;
var idhk = this.bip.derive(path);
this.idkey = idhk.eckey;
this.id = idhk.eckey.public.toString('hex');
this.idpriv = idhk.eckey.private.toString('hex');
};

View File

@ -280,6 +280,7 @@ Wallet.prototype.netStart = function(callback) {
var myId = self.getMyCopayerId();
var myIdPriv = self.getMyCopayerIdPriv();
var startOpts = {
copayerId: myId,
privkey: myIdPriv,

View File

@ -224,7 +224,8 @@ WalletFactory.prototype.joinCreateSession = function(secret, nickname, passphras
this.log('\t### PrivateKey Initialized');
var opts = {
copayerId: privateKey.getId(),
privkey: privateKey.getIdPriv()
privkey: privateKey.getIdPriv(),
key: privateKey.getIdKey()
};
self.network.cleanUp();
self.network.start(opts, function() {

View File

@ -5,6 +5,7 @@ var EventEmitter = imports.EventEmitter || require('events').EventEmitter;
var bitcore = require('bitcore');
var util = bitcore.util;
var extend = require('util')._extend;
var Message = require('../core/Message');
/*
* Emits
* 'connect'
@ -45,6 +46,7 @@ Network.prototype.cleanUp = function() {
this.connectedPeers = [];
this.peerId = null;
this.privkey = null; //TODO: hide privkey in a closure
this.key = null;
this.copayerId = null;
this.signingKey = null;
this.allowedCopayerIds = null;
@ -151,16 +153,29 @@ Network.prototype._addConnectedCopayer = function(copayerId, isInbound) {
this.emit('connect', copayerId);
};
Network.prototype._onData = function(enchex, isInbound, peerId) {
var sig, payload;
var encUint8Array = new Uint8Array(enchex);
Network.prototype.getKey = function() {
if (!this.key) {
var key = new bitcore.Key();
key.private = new Buffer(this.privkey, 'hex');
key.regenerateSync();
this.key = key;
}
return this.key;
};
Network.prototype._onData = function(enc, isInbound, peerId) {
var encUint8Array = new Uint8Array(enc);
var encbuf = new Buffer(encUint8Array);
var encstr = encbuf.toString();
var privkey = this.privkey;
var key = this.getKey();
try {
var data = this._decrypt(privkey, encbuf);
payload = JSON.parse(data);
var encoded = JSON.parse(encstr);
var databuf = this._decode(key, encoded);
var datastr = databuf.toString();
var payload = JSON.parse(datastr);
} catch (e) {
this._deletePeer(peerId);
return;
@ -169,6 +184,13 @@ Network.prototype._onData = function(enchex, isInbound, peerId) {
if (isInbound && payload.type === 'hello') {
var payloadStr = JSON.stringify(payload);
//ensure claimed public key is actually the public key of the peer
//e.g., their public key should hash to be their peerId
if (peerId.toString() !== this.peerFromCopayer(payload.copayerId) || peerId.toString() !== this.peerFromCopayer(encoded.pubkey)) {
this._deletePeer(peerId, 'incorrect pubkey for peerId');
return;
}
if (this.allowedCopayerIds && !this.allowedCopayerIds[payload.copayerId]) {
this._deletePeer(peerId);
return;
@ -349,18 +371,20 @@ Network.prototype.getPeer = function() {
return this.peer;
};
Network.prototype._encrypt = function(pubkey, payload) {
var encrypted = bitcore.ECIES.encrypt(pubkey, payload);
return encrypted;
Network.prototype._encode = function(topubkey, fromkey, payload) {
var encoded = Message.encode(topubkey, fromkey, payload);
return encoded;
};
Network.prototype._decrypt = function(privkey, encrypted) {
var decrypted = bitcore.ECIES.decrypt(privkey, encrypted);
return decrypted;
Network.prototype._decode = function(key, encoded) {
var payload = Message.decode(key, encoded);
return payload;
};
Network.prototype._sendToOne = function(copayerId, payload, sig, cb) {
Network.prototype._sendToOne = function(copayerId, payload, cb) {
if (!Buffer.isBuffer(payload))
throw new Error('payload must be a buffer');
var peerId = this.peerFromCopayer(copayerId);
if (peerId !== this.peerId) {
var dataConn = this.connections[peerId];
@ -383,7 +407,6 @@ Network.prototype.send = function(copayerIds, payload, cb) {
if (typeof copayerIds === 'string')
copayerIds = [copayerIds];
var sig;
var payloadStr = JSON.stringify(payload);
var payloadBuf = new Buffer(payloadStr);
@ -391,8 +414,9 @@ Network.prototype.send = function(copayerIds, payload, cb) {
var i = 0;
copayerIds.forEach(function(copayerId) {
var copayerIdBuf = new Buffer(copayerId, 'hex');
var encPayload = self._encrypt(copayerIdBuf, payloadBuf);
self._sendToOne(copayerId, encPayload, sig, function() {
var encPayload = self._encode(copayerIdBuf, self.getKey(), payloadBuf);
var enc = new Buffer(JSON.stringify(encPayload));
self._sendToOne(copayerId, enc, function() {
if (++i === l && typeof cb === 'function') cb();
});
});

View File

@ -15,6 +15,7 @@
<script src="../lib/bitcore/browser/bundle.js"></script>
<script src="../js/copayBundle.js"></script>
<script src="test.blockchain.Insight.js"></script>
<script src="test.Message.js"></script>
<script src="test.network.WebRTC.js"></script>
<script src="test.PrivateKey.js"></script>
<script src="test.PublicKeyRing.js"></script>

103
test/test.Message.js Normal file
View File

@ -0,0 +1,103 @@
'use strict';
var chai = chai || require('chai');
var should = chai.should();
var sinon = require('sinon');
var Message = require('../js/models/core/Message');
var bitcore = bitcore || require('bitcore');
var Buffer = bitcore.Buffer;
describe('Message model', function() {
var key = new bitcore.Key();
key.private = bitcore.util.sha256(new Buffer('test'));
key.regenerateSync();
var key2 = new bitcore.Key();
key2.private = bitcore.util.sha256(new Buffer('test 2'));
key2.regenerateSync();
describe('#encode', function() {
it('should encode a message', function() {
var message = new Buffer('message');
var encoded = Message.encode(key2.public, key, message);
should.exist(encoded.pubkey);
should.exist(encoded.sig);
should.exist(encoded.encrypted);
});
});
describe('#decode', function() {
it('should decode an encoded message', function() {
var message = new Buffer('message');
var messagehex = message.toString('hex');
var encoded = Message.encode(key2.public, key, message);
var decoded = Message.decode(key2, encoded);
decoded.toString('hex').should.equal(messagehex);
});
it('should fail if the version number is incorrect', function() {
var payload = new Buffer('message');
var fromkey = key;
var topubkey = key2.public;
var version = new Buffer([1]);
var toencrypt = Buffer.concat([version, payload]);
var encrypted = Message._encrypt(topubkey, toencrypt);
var sig = Message._sign(fromkey, encrypted);
var encoded = {
pubkey: fromkey.public.toString('hex'),
sig: sig.toString('hex'),
encrypted: encrypted.toString('hex')
};
(function() {Message.decode(key2, encoded);}).should.throw('Invalid version number');
});
});
describe('#_encrypt', function() {
it('should encrypt data', function() {
var payload = new Buffer('payload');
var encrypted = Message._encrypt(key.public, payload);
encrypted.length.should.equal(129);
});
});
describe('#_decrypt', function() {
var payload = new Buffer('payload');
var payloadhex = payload.toString('hex');
it('should decrypt encrypted data', function() {
var encrypted = Message._encrypt(key.public, payload);
var decrypted = Message._decrypt(key.private, encrypted);
decrypted.toString('hex').should.equal(payloadhex);
});
});
describe('#_sign', function() {
it('should sign data', function() {
var payload = new Buffer('payload');
var sig = Message._sign(key, payload);
sig.length.should.be.greaterThan(60);
});
});
describe('#_verify', function() {
var payload = new Buffer('payload');
var sig = Message._sign(key, payload);
it('should verify signed data', function() {
Message._verify(key.public, sig, payload).should.equal(true);
});
});
});

View File

@ -46,26 +46,28 @@ describe('Network / WebRTC', function() {
});
describe('#_encrypt', function() {
describe('#_encode', function() {
it('should encrypt data successfully', function() {
it('should encode data successfully', function() {
var n = new WebRTC();
var data = new bitcore.Buffer('my data to encrypt');
var data = new bitcore.Buffer('my data to encode');
var privkeystr = new bitcore.Buffer('test privkey');
var privkey = bitcore.util.sha256(privkeystr);
var key = new bitcore.Key();
key.private = privkey;
key.regenerateSync();
var encrypted = n._encrypt(key.public, data);
encrypted.length.should.not.equal(0);
encrypted.length.should.equal(145);
var encoded = n._encode(key.public, key, data);
should.exist(encoded);
encoded.sig.length.should.not.equal(0);
encoded.pubkey.length.should.not.equal(0);
encoded.encrypted.length.should.not.equal(0);
});
});
describe('#_decrypt', function() {
describe('#_decode', function() {
it('should decrypt that which was encrypted', function() {
it('should decode that which was encoded', function() {
var n = new WebRTC();
var data = new bitcore.Buffer('my data to encrypt');
var privkeystr = new bitcore.Buffer('test privkey');
@ -73,10 +75,10 @@ describe('Network / WebRTC', function() {
var key = new bitcore.Key();
key.private = privkey;
key.regenerateSync();
var encrypted = n._encrypt(key.public, data);
var decrypted = n._decrypt(key.private, encrypted);
encrypted.length.should.not.equal(0);
decrypted.toString().should.equal('my data to encrypt');
var encoded = n._encode(key.public, key, data);
var decoded = n._decode(key, encoded);
encoded.sig.should.not.equal(0);
decoded.toString().should.equal('my data to encrypt');
});
});
@ -85,6 +87,7 @@ describe('Network / WebRTC', function() {
it('should call _sendToOne for a copayer', function(done) {
var n = new WebRTC();
n.privkey = bitcore.util.sha256('test');
var data = new bitcore.Buffer('my data to send');
@ -95,7 +98,7 @@ describe('Network / WebRTC', function() {
key.regenerateSync();
var copayerId = key.public.toString('hex');
n._sendToOne = function(a1, a2, a3, cb) {
n._sendToOne = function(a1, a2, cb) {
cb();
};
var sig = undefined;
@ -107,6 +110,7 @@ describe('Network / WebRTC', function() {
it('should call _sendToOne with encrypted data for a copayer', function(done) {
var n = new WebRTC();
n.privkey = bitcore.util.sha256('test');
var data = new bitcore.Buffer('my data to send');
@ -117,8 +121,9 @@ describe('Network / WebRTC', function() {
key.regenerateSync();
var copayerId = key.public.toString('hex');
n._sendToOne = function(a1, encPayload, a3, cb) {
encPayload.length.should.be.greaterThan(0);
n._sendToOne = function(a1, enc, cb) {
var encPayload = JSON.parse(enc.toString());
encPayload.sig.length.should.be.greaterThan(0);
cb();
};
var sig = undefined;
@ -130,6 +135,7 @@ describe('Network / WebRTC', function() {
it('should call _sendToOne for a list of copayers', function(done) {
var n = new WebRTC();
n.privkey = bitcore.util.sha256('test');
var data = new bitcore.Buffer('my data to send');
@ -140,7 +146,7 @@ describe('Network / WebRTC', function() {
key.regenerateSync();
var copayerIds = [key.public.toString('hex')];
n._sendToOne = function(a1, a2, a3, cb) {
n._sendToOne = function(a1, a2, cb) {
cb();
};
var sig = undefined;
@ -151,4 +157,73 @@ describe('Network / WebRTC', function() {
});
});
describe('#_onData', function() {
var privkey1 = bitcore.util.sha256('test privkey 1');
var privkey2 = bitcore.util.sha256('test privkey 2');
var privkey3 = bitcore.util.sha256('test privkey 2');
var key1 = new bitcore.Key();
key1.private = privkey1;
key1.regenerateSync();
var key2 = new bitcore.Key();
key2.private = privkey2;
key2.regenerateSync();
var key3 = new bitcore.Key();
key3.private = privkey3;
key3.regenerateSync();
it('should not reject data sent from a peer with hijacked pubkey', function() {
var n = new WebRTC();
n.privkey = key2.private.toString('hex');
var message = {
type: 'hello',
copayerId: key1.public.toString('hex')
};
var messagestr = JSON.stringify(message);
var messagebuf = new Buffer(messagestr);
var encoded = n._encode(key2.public, key1, messagebuf);
var encodedstr = JSON.stringify(encoded);
var encodeduint = new Buffer(encodedstr);
var isInbound = true;
var peerId = new bitcore.SIN(key1.public);
n._deletePeer = sinon.spy();
n._onData(encodeduint, isInbound, peerId);
n._deletePeer.calledOnce.should.equal(false);
});
it('should reject data sent from a peer with hijacked pubkey', function() {
var n = new WebRTC();
n.privkey = key2.private.toString('hex');
var message = {
type: 'hello',
copayerId: key3.public.toString('hex') //MITM pubkey 3
};
var messagestr = JSON.stringify(message);
var messagebuf = new Buffer(messagestr);
var encoded = n._encode(key2.public, key1, messagebuf);
var encodedstr = JSON.stringify(encoded);
var encodeduint = new Buffer(encodedstr);
var isInbound = true;
var peerId = new bitcore.SIN(key1.public);
n._deletePeer = sinon.spy();
n._onData(encodeduint, isInbound, peerId);
n._deletePeer.calledOnce.should.equal(true);
n._deletePeer.getCall(0).args[0].should.equal(peerId);
n._deletePeer.getCall(0).args[1].should.equal('incorrect pubkey for peerId');
});
});
});

View File

@ -84,6 +84,9 @@ var createBundle = function(opts) {
b.require('./js/models/core/Passphrase', {
expose: '../js/models/core/Passphrase'
});
b.require('./js/models/core/Message', {
expose: '../js/models/core/Message'
});
if (opts.dontminify) {
//include dev dependencies