add authentication via new Message core class
This commit is contained in:
parent
a1fe7b3d7d
commit
543c42a6a8
9 changed files with 226 additions and 23 deletions
82
js/models/core/Message.js
Normal file
82
js/models/core/Message.js
Normal 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);
|
||||||
|
|
@ -32,9 +32,17 @@ PrivateKey.prototype.getIdPriv = function() {
|
||||||
return this.idpriv;
|
return this.idpriv;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
PrivateKey.prototype.getIdKey = function() {
|
||||||
|
if (!this.idkey) {
|
||||||
|
this.cacheId();
|
||||||
|
}
|
||||||
|
return this.idkey;
|
||||||
|
};
|
||||||
|
|
||||||
PrivateKey.prototype.cacheId = function() {
|
PrivateKey.prototype.cacheId = function() {
|
||||||
var path = Structure.IdFullBranch;
|
var path = Structure.IdFullBranch;
|
||||||
var idhk = this.bip.derive(path);
|
var idhk = this.bip.derive(path);
|
||||||
|
this.idkey = idhk.eckey;
|
||||||
this.id = idhk.eckey.public.toString('hex');
|
this.id = idhk.eckey.public.toString('hex');
|
||||||
this.idpriv = idhk.eckey.private.toString('hex');
|
this.idpriv = idhk.eckey.private.toString('hex');
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -279,6 +279,7 @@ Wallet.prototype.netStart = function(callback) {
|
||||||
|
|
||||||
var myId = self.getMyCopayerId();
|
var myId = self.getMyCopayerId();
|
||||||
var myIdPriv = self.getMyCopayerIdPriv();
|
var myIdPriv = self.getMyCopayerIdPriv();
|
||||||
|
|
||||||
var startOpts = {
|
var startOpts = {
|
||||||
copayerId: myId,
|
copayerId: myId,
|
||||||
privkey: myIdPriv,
|
privkey: myIdPriv,
|
||||||
|
|
|
||||||
|
|
@ -222,7 +222,8 @@ WalletFactory.prototype.joinCreateSession = function(secret, nickname, passphras
|
||||||
this.log('\t### PrivateKey Initialized');
|
this.log('\t### PrivateKey Initialized');
|
||||||
var opts = {
|
var opts = {
|
||||||
copayerId: privateKey.getId(),
|
copayerId: privateKey.getId(),
|
||||||
privkey: privateKey.getIdPriv()
|
privkey: privateKey.getIdPriv(),
|
||||||
|
key: privateKey.getIdKey()
|
||||||
};
|
};
|
||||||
self.network.cleanUp();
|
self.network.cleanUp();
|
||||||
self.network.start(opts, function() {
|
self.network.start(opts, function() {
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ var EventEmitter = imports.EventEmitter || require('events').EventEmitter;
|
||||||
var bitcore = require('bitcore');
|
var bitcore = require('bitcore');
|
||||||
var util = bitcore.util;
|
var util = bitcore.util;
|
||||||
var extend = require('util')._extend;
|
var extend = require('util')._extend;
|
||||||
|
var Message = require('../core/Message');
|
||||||
/*
|
/*
|
||||||
* Emits
|
* Emits
|
||||||
* 'connect'
|
* 'connect'
|
||||||
|
|
@ -45,6 +46,7 @@ Network.prototype.cleanUp = function() {
|
||||||
this.connectedPeers = [];
|
this.connectedPeers = [];
|
||||||
this.peerId = null;
|
this.peerId = null;
|
||||||
this.privkey = null; //TODO: hide privkey in a closure
|
this.privkey = null; //TODO: hide privkey in a closure
|
||||||
|
this.key = null;
|
||||||
this.copayerId = null;
|
this.copayerId = null;
|
||||||
this.signingKey = null;
|
this.signingKey = null;
|
||||||
this.allowedCopayerIds = null;
|
this.allowedCopayerIds = null;
|
||||||
|
|
@ -151,16 +153,30 @@ Network.prototype._addConnectedCopayer = function(copayerId, isInbound) {
|
||||||
this.emit('connect', copayerId);
|
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) {
|
Network.prototype._onData = function(enchex, isInbound, peerId) {
|
||||||
var sig, payload;
|
var sig, payload;
|
||||||
var encUint8Array = new Uint8Array(enchex);
|
var encUint8Array = new Uint8Array(enchex);
|
||||||
var encbuf = new Buffer(encUint8Array);
|
var encbuf = new Buffer(encUint8Array);
|
||||||
|
var encstr = encbuf.toString();
|
||||||
|
|
||||||
var privkey = this.privkey;
|
var privkey = this.privkey;
|
||||||
|
var key = this.getKey();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var data = this._decrypt(privkey, encbuf);
|
var encoded = JSON.parse(encstr);
|
||||||
payload = JSON.parse(data);
|
var databuf = this._decode(key, encoded);
|
||||||
|
var datastr = databuf.toString();
|
||||||
|
payload = JSON.parse(datastr);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this._deletePeer(peerId);
|
this._deletePeer(peerId);
|
||||||
return;
|
return;
|
||||||
|
|
@ -349,18 +365,19 @@ Network.prototype.getPeer = function() {
|
||||||
return this.peer;
|
return this.peer;
|
||||||
};
|
};
|
||||||
|
|
||||||
Network.prototype._encrypt = function(pubkey, payload) {
|
Network.prototype._encode = function(topubkey, fromkey, payload) {
|
||||||
var encrypted = bitcore.ECIES.encrypt(pubkey, payload);
|
var encoded = Message.encode(topubkey, fromkey, payload);
|
||||||
return encrypted;
|
return encoded;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
Network.prototype._decrypt = function(privkey, encrypted) {
|
Network.prototype._decode = function(key, encoded) {
|
||||||
var decrypted = bitcore.ECIES.decrypt(privkey, encrypted);
|
var payload = Message.decode(key, encoded);
|
||||||
return decrypted;
|
return payload;
|
||||||
};
|
};
|
||||||
|
|
||||||
Network.prototype._sendToOne = function(copayerId, payload, sig, cb) {
|
Network.prototype._sendToOne = function(copayerId, payload, sig, cb) {
|
||||||
|
console.log('payload: ' + payload);
|
||||||
var peerId = this.peerFromCopayer(copayerId);
|
var peerId = this.peerFromCopayer(copayerId);
|
||||||
if (peerId !== this.peerId) {
|
if (peerId !== this.peerId) {
|
||||||
var dataConn = this.connections[peerId];
|
var dataConn = this.connections[peerId];
|
||||||
|
|
@ -391,7 +408,7 @@ Network.prototype.send = function(copayerIds, payload, cb) {
|
||||||
var i = 0;
|
var i = 0;
|
||||||
copayerIds.forEach(function(copayerId) {
|
copayerIds.forEach(function(copayerId) {
|
||||||
var copayerIdBuf = new Buffer(copayerId, 'hex');
|
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() {
|
self._sendToOne(copayerId, encPayload, sig, function() {
|
||||||
if (++i === l && typeof cb === 'function') cb();
|
if (++i === l && typeof cb === 'function') cb();
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
<script src="../lib/bitcore/browser/bundle.js"></script>
|
<script src="../lib/bitcore/browser/bundle.js"></script>
|
||||||
<script src="../js/copayBundle.js"></script>
|
<script src="../js/copayBundle.js"></script>
|
||||||
<script src="test.blockchain.Insight.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.network.WebRTC.js"></script>
|
||||||
<script src="test.PrivateKey.js"></script>
|
<script src="test.PrivateKey.js"></script>
|
||||||
<script src="test.PublicKeyRing.js"></script>
|
<script src="test.PublicKeyRing.js"></script>
|
||||||
|
|
|
||||||
85
test/test.Message.js
Normal file
85
test/test.Message.js
Normal file
|
|
@ -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);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
@ -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 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 privkeystr = new bitcore.Buffer('test privkey');
|
||||||
var privkey = bitcore.util.sha256(privkeystr);
|
var privkey = bitcore.util.sha256(privkeystr);
|
||||||
var key = new bitcore.Key();
|
var key = new bitcore.Key();
|
||||||
key.private = privkey;
|
key.private = privkey;
|
||||||
key.regenerateSync();
|
key.regenerateSync();
|
||||||
var encrypted = n._encrypt(key.public, data);
|
var encoded = n._encode(key.public, key, data);
|
||||||
encrypted.length.should.not.equal(0);
|
should.exist(encoded);
|
||||||
encrypted.length.should.equal(145);
|
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 n = new WebRTC();
|
||||||
var data = new bitcore.Buffer('my data to encrypt');
|
var data = new bitcore.Buffer('my data to encrypt');
|
||||||
var privkeystr = new bitcore.Buffer('test privkey');
|
var privkeystr = new bitcore.Buffer('test privkey');
|
||||||
|
|
@ -73,10 +75,10 @@ describe('Network / WebRTC', function() {
|
||||||
var key = new bitcore.Key();
|
var key = new bitcore.Key();
|
||||||
key.private = privkey;
|
key.private = privkey;
|
||||||
key.regenerateSync();
|
key.regenerateSync();
|
||||||
var encrypted = n._encrypt(key.public, data);
|
var encoded = n._encode(key.public, key, data);
|
||||||
var decrypted = n._decrypt(key.private, encrypted);
|
var decoded = n._decode(key, encoded);
|
||||||
encrypted.length.should.not.equal(0);
|
encoded.sig.should.not.equal(0);
|
||||||
decrypted.toString().should.equal('my data to encrypt');
|
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) {
|
it('should call _sendToOne for a copayer', function(done) {
|
||||||
var n = new WebRTC();
|
var n = new WebRTC();
|
||||||
|
n.privkey = bitcore.util.sha256('test');
|
||||||
|
|
||||||
var data = new bitcore.Buffer('my data to send');
|
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) {
|
it('should call _sendToOne with encrypted data for a copayer', function(done) {
|
||||||
var n = new WebRTC();
|
var n = new WebRTC();
|
||||||
|
n.privkey = bitcore.util.sha256('test');
|
||||||
|
|
||||||
var data = new bitcore.Buffer('my data to send');
|
var data = new bitcore.Buffer('my data to send');
|
||||||
|
|
||||||
|
|
@ -118,7 +122,7 @@ describe('Network / WebRTC', function() {
|
||||||
|
|
||||||
var copayerId = key.public.toString('hex');
|
var copayerId = key.public.toString('hex');
|
||||||
n._sendToOne = function(a1, encPayload, a3, cb) {
|
n._sendToOne = function(a1, encPayload, a3, cb) {
|
||||||
encPayload.length.should.be.greaterThan(0);
|
encPayload.sig.length.should.be.greaterThan(0);
|
||||||
cb();
|
cb();
|
||||||
};
|
};
|
||||||
var sig = undefined;
|
var sig = undefined;
|
||||||
|
|
@ -130,6 +134,7 @@ describe('Network / WebRTC', function() {
|
||||||
|
|
||||||
it('should call _sendToOne for a list of copayers', function(done) {
|
it('should call _sendToOne for a list of copayers', function(done) {
|
||||||
var n = new WebRTC();
|
var n = new WebRTC();
|
||||||
|
n.privkey = bitcore.util.sha256('test');
|
||||||
|
|
||||||
var data = new bitcore.Buffer('my data to send');
|
var data = new bitcore.Buffer('my data to send');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -84,6 +84,9 @@ var createBundle = function(opts) {
|
||||||
b.require('./js/models/core/Passphrase', {
|
b.require('./js/models/core/Passphrase', {
|
||||||
expose: '../js/models/core/Passphrase'
|
expose: '../js/models/core/Passphrase'
|
||||||
});
|
});
|
||||||
|
b.require('./js/models/core/Message', {
|
||||||
|
expose: '../js/models/core/Message'
|
||||||
|
});
|
||||||
|
|
||||||
if (opts.dontminify) {
|
if (opts.dontminify) {
|
||||||
//include dev dependencies
|
//include dev dependencies
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue