From 543c42a6a8b42e05c990aed7b06647cc80acead5 Mon Sep 17 00:00:00 2001 From: "Ryan X. Charles" Date: Thu, 26 Jun 2014 08:49:22 -0700 Subject: [PATCH] add authentication via new Message core class --- js/models/core/Message.js | 82 +++++++++++++++++++++++++++++++ js/models/core/PrivateKey.js | 8 ++++ js/models/core/Wallet.js | 1 + js/models/core/WalletFactory.js | 3 +- js/models/network/WebRTC.js | 35 ++++++++++---- test/index.html | 1 + test/test.Message.js | 85 +++++++++++++++++++++++++++++++++ test/test.network.WebRTC.js | 31 +++++++----- util/build.js | 3 ++ 9 files changed, 226 insertions(+), 23 deletions(-) create mode 100644 js/models/core/Message.js create mode 100644 test/test.Message.js diff --git a/js/models/core/Message.js b/js/models/core/Message.js new file mode 100644 index 000000000..b281eecd8 --- /dev/null +++ b/js/models/core/Message.js @@ -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); diff --git a/js/models/core/PrivateKey.js b/js/models/core/PrivateKey.js index ff8b70e79..b1149f28b 100644 --- a/js/models/core/PrivateKey.js +++ b/js/models/core/PrivateKey.js @@ -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'); }; diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 0bfa3dcf4..eb84b1f8c 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -279,6 +279,7 @@ Wallet.prototype.netStart = function(callback) { var myId = self.getMyCopayerId(); var myIdPriv = self.getMyCopayerIdPriv(); + var startOpts = { copayerId: myId, privkey: myIdPriv, diff --git a/js/models/core/WalletFactory.js b/js/models/core/WalletFactory.js index 03d59e2ea..b7ccc60e0 100644 --- a/js/models/core/WalletFactory.js +++ b/js/models/core/WalletFactory.js @@ -222,7 +222,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() { diff --git a/js/models/network/WebRTC.js b/js/models/network/WebRTC.js index 98e660a7a..e56134c14 100644 --- a/js/models/network/WebRTC.js +++ b/js/models/network/WebRTC.js @@ -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,30 @@ Network.prototype._addConnectedCopayer = function(copayerId, isInbound) { this.emit('connect', copayerId); }; +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(enchex, isInbound, peerId) { var sig, payload; var encUint8Array = new Uint8Array(enchex); 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(); + payload = JSON.parse(datastr); } catch (e) { this._deletePeer(peerId); return; @@ -349,18 +365,19 @@ 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) { + console.log('payload: ' + payload); var peerId = this.peerFromCopayer(copayerId); if (peerId !== this.peerId) { var dataConn = this.connections[peerId]; @@ -391,7 +408,7 @@ 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); + var encPayload = self._encode(copayerIdBuf, self.getKey(), payloadBuf); self._sendToOne(copayerId, encPayload, sig, function() { if (++i === l && typeof cb === 'function') cb(); }); diff --git a/test/index.html b/test/index.html index 127df85f3..120a98b2b 100644 --- a/test/index.html +++ b/test/index.html @@ -15,6 +15,7 @@ + diff --git a/test/test.Message.js b/test/test.Message.js new file mode 100644 index 000000000..cd71458ca --- /dev/null +++ b/test/test.Message.js @@ -0,0 +1,85 @@ +'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() { + var message = new Buffer('message'); + var messagehex = message.toString('hex'); + var encoded = Message.encode(key2.public, key, message); + + it('should decode an encoded message', function() { + var decoded = Message.decode(key2, encoded); + decoded.toString('hex').should.equal(messagehex); + }); + + }); + + 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); + }); + + }); + +}); diff --git a/test/test.network.WebRTC.js b/test/test.network.WebRTC.js index d880d006d..8297762bf 100644 --- a/test/test.network.WebRTC.js +++ b/test/test.network.WebRTC.js @@ -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'); @@ -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'); @@ -118,7 +122,7 @@ describe('Network / WebRTC', function() { var copayerId = key.public.toString('hex'); n._sendToOne = function(a1, encPayload, a3, cb) { - encPayload.length.should.be.greaterThan(0); + encPayload.sig.length.should.be.greaterThan(0); cb(); }; var sig = undefined; @@ -130,6 +134,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'); diff --git a/util/build.js b/util/build.js index 2af0ca892..e7cf06fa5 100755 --- a/util/build.js +++ b/util/build.js @@ -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