From 08a741d880ed8e825873221b4cf5cace4446708a Mon Sep 17 00:00:00 2001 From: "Ryan X. Charles" Date: Thu, 26 Jun 2014 13:58:45 -0700 Subject: [PATCH] confirm peerId matches claimed pubkey to prevent MITM attacks --- js/models/network/WebRTC.js | 20 ++++++---- test/test.Message.js | 26 +++++++++++-- test/test.network.WebRTC.js | 75 +++++++++++++++++++++++++++++++++++-- 3 files changed, 106 insertions(+), 15 deletions(-) diff --git a/js/models/network/WebRTC.js b/js/models/network/WebRTC.js index e56134c14..86f86d26e 100644 --- a/js/models/network/WebRTC.js +++ b/js/models/network/WebRTC.js @@ -163,9 +163,8 @@ Network.prototype.getKey = function() { return this.key; }; -Network.prototype._onData = function(enchex, isInbound, peerId) { - var sig, payload; - var encUint8Array = new Uint8Array(enchex); +Network.prototype._onData = function(enc, isInbound, peerId) { + var encUint8Array = new Uint8Array(enc); var encbuf = new Buffer(encUint8Array); var encstr = encbuf.toString(); @@ -176,7 +175,7 @@ Network.prototype._onData = function(enchex, isInbound, peerId) { var encoded = JSON.parse(encstr); var databuf = this._decode(key, encoded); var datastr = databuf.toString(); - payload = JSON.parse(datastr); + var payload = JSON.parse(datastr); } catch (e) { this._deletePeer(peerId); return; @@ -185,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; @@ -376,8 +382,7 @@ Network.prototype._decode = function(key, encoded) { return payload; }; -Network.prototype._sendToOne = function(copayerId, payload, sig, cb) { - console.log('payload: ' + payload); +Network.prototype._sendToOne = function(copayerId, payload, cb) { var peerId = this.peerFromCopayer(copayerId); if (peerId !== this.peerId) { var dataConn = this.connections[peerId]; @@ -400,7 +405,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); @@ -409,7 +413,7 @@ Network.prototype.send = function(copayerIds, payload, cb) { copayerIds.forEach(function(copayerId) { var copayerIdBuf = new Buffer(copayerId, 'hex'); var encPayload = self._encode(copayerIdBuf, self.getKey(), payloadBuf); - self._sendToOne(copayerId, encPayload, sig, function() { + self._sendToOne(copayerId, encPayload, function() { if (++i === l && typeof cb === 'function') cb(); }); }); diff --git a/test/test.Message.js b/test/test.Message.js index cd71458ca..6abe1e3e3 100644 --- a/test/test.Message.js +++ b/test/test.Message.js @@ -29,15 +29,33 @@ describe('Message model', function() { }); 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 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() { diff --git a/test/test.network.WebRTC.js b/test/test.network.WebRTC.js index 8297762bf..a818937e7 100644 --- a/test/test.network.WebRTC.js +++ b/test/test.network.WebRTC.js @@ -98,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; @@ -121,7 +121,7 @@ describe('Network / WebRTC', function() { key.regenerateSync(); var copayerId = key.public.toString('hex'); - n._sendToOne = function(a1, encPayload, a3, cb) { + n._sendToOne = function(a1, encPayload, cb) { encPayload.sig.length.should.be.greaterThan(0); cb(); }; @@ -145,7 +145,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; @@ -156,4 +156,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'); + }); + + }); + });