mirror of https://github.com/BTCPrivate/copay.git
confirm peerId matches claimed pubkey to prevent MITM attacks
This commit is contained in:
parent
543c42a6a8
commit
08a741d880
|
@ -163,9 +163,8 @@ Network.prototype.getKey = function() {
|
||||||
return this.key;
|
return this.key;
|
||||||
};
|
};
|
||||||
|
|
||||||
Network.prototype._onData = function(enchex, isInbound, peerId) {
|
Network.prototype._onData = function(enc, isInbound, peerId) {
|
||||||
var sig, payload;
|
var encUint8Array = new Uint8Array(enc);
|
||||||
var encUint8Array = new Uint8Array(enchex);
|
|
||||||
var encbuf = new Buffer(encUint8Array);
|
var encbuf = new Buffer(encUint8Array);
|
||||||
var encstr = encbuf.toString();
|
var encstr = encbuf.toString();
|
||||||
|
|
||||||
|
@ -176,7 +175,7 @@ Network.prototype._onData = function(enchex, isInbound, peerId) {
|
||||||
var encoded = JSON.parse(encstr);
|
var encoded = JSON.parse(encstr);
|
||||||
var databuf = this._decode(key, encoded);
|
var databuf = this._decode(key, encoded);
|
||||||
var datastr = databuf.toString();
|
var datastr = databuf.toString();
|
||||||
payload = JSON.parse(datastr);
|
var payload = JSON.parse(datastr);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this._deletePeer(peerId);
|
this._deletePeer(peerId);
|
||||||
return;
|
return;
|
||||||
|
@ -185,6 +184,13 @@ Network.prototype._onData = function(enchex, isInbound, peerId) {
|
||||||
if (isInbound && payload.type === 'hello') {
|
if (isInbound && payload.type === 'hello') {
|
||||||
var payloadStr = JSON.stringify(payload);
|
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]) {
|
if (this.allowedCopayerIds && !this.allowedCopayerIds[payload.copayerId]) {
|
||||||
this._deletePeer(peerId);
|
this._deletePeer(peerId);
|
||||||
return;
|
return;
|
||||||
|
@ -376,8 +382,7 @@ Network.prototype._decode = function(key, encoded) {
|
||||||
return payload;
|
return payload;
|
||||||
};
|
};
|
||||||
|
|
||||||
Network.prototype._sendToOne = function(copayerId, payload, sig, cb) {
|
Network.prototype._sendToOne = function(copayerId, payload, 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];
|
||||||
|
@ -400,7 +405,6 @@ Network.prototype.send = function(copayerIds, payload, cb) {
|
||||||
if (typeof copayerIds === 'string')
|
if (typeof copayerIds === 'string')
|
||||||
copayerIds = [copayerIds];
|
copayerIds = [copayerIds];
|
||||||
|
|
||||||
var sig;
|
|
||||||
var payloadStr = JSON.stringify(payload);
|
var payloadStr = JSON.stringify(payload);
|
||||||
var payloadBuf = new Buffer(payloadStr);
|
var payloadBuf = new Buffer(payloadStr);
|
||||||
|
|
||||||
|
@ -409,7 +413,7 @@ Network.prototype.send = function(copayerIds, payload, cb) {
|
||||||
copayerIds.forEach(function(copayerId) {
|
copayerIds.forEach(function(copayerId) {
|
||||||
var copayerIdBuf = new Buffer(copayerId, 'hex');
|
var copayerIdBuf = new Buffer(copayerId, 'hex');
|
||||||
var encPayload = self._encode(copayerIdBuf, self.getKey(), payloadBuf);
|
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();
|
if (++i === l && typeof cb === 'function') cb();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -29,15 +29,33 @@ describe('Message model', function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#decode', function() {
|
describe('#decode', function() {
|
||||||
|
|
||||||
|
it('should decode an encoded message', function() {
|
||||||
var message = new Buffer('message');
|
var message = new Buffer('message');
|
||||||
var messagehex = message.toString('hex');
|
var messagehex = message.toString('hex');
|
||||||
var encoded = Message.encode(key2.public, key, message);
|
var encoded = Message.encode(key2.public, key, message);
|
||||||
|
|
||||||
it('should decode an encoded message', function() {
|
|
||||||
var decoded = Message.decode(key2, encoded);
|
var decoded = Message.decode(key2, encoded);
|
||||||
decoded.toString('hex').should.equal(messagehex);
|
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() {
|
describe('#_encrypt', function() {
|
||||||
|
|
|
@ -98,7 +98,7 @@ describe('Network / WebRTC', function() {
|
||||||
key.regenerateSync();
|
key.regenerateSync();
|
||||||
|
|
||||||
var copayerId = key.public.toString('hex');
|
var copayerId = key.public.toString('hex');
|
||||||
n._sendToOne = function(a1, a2, a3, cb) {
|
n._sendToOne = function(a1, a2, cb) {
|
||||||
cb();
|
cb();
|
||||||
};
|
};
|
||||||
var sig = undefined;
|
var sig = undefined;
|
||||||
|
@ -121,7 +121,7 @@ describe('Network / WebRTC', function() {
|
||||||
key.regenerateSync();
|
key.regenerateSync();
|
||||||
|
|
||||||
var copayerId = key.public.toString('hex');
|
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);
|
encPayload.sig.length.should.be.greaterThan(0);
|
||||||
cb();
|
cb();
|
||||||
};
|
};
|
||||||
|
@ -145,7 +145,7 @@ describe('Network / WebRTC', function() {
|
||||||
key.regenerateSync();
|
key.regenerateSync();
|
||||||
|
|
||||||
var copayerIds = [key.public.toString('hex')];
|
var copayerIds = [key.public.toString('hex')];
|
||||||
n._sendToOne = function(a1, a2, a3, cb) {
|
n._sendToOne = function(a1, a2, cb) {
|
||||||
cb();
|
cb();
|
||||||
};
|
};
|
||||||
var sig = undefined;
|
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');
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue