From 74f6fd8ab33eceb64a80bd6a280f5931946813b1 Mon Sep 17 00:00:00 2001 From: David Dias Date: Sun, 7 Jan 2018 13:58:30 +0000 Subject: [PATCH] test: check if we dialed to the right peer --- .aegir.js | 7 ++--- README.md | 12 ++++----- benchmarks/send.js | 8 +++--- package.json | 1 + src/handshake/crypto.js | 10 ++++++- src/handshake/exchange.js | 4 +-- src/handshake/index.js | 9 ++++--- src/index.js | 54 ++++++++++++++++++++++++------------- src/state.js | 57 +++++++++++---------------------------- src/support.js | 4 +-- test/browser.js | 17 ++++++++---- test/secio.spec.js | 50 +++++++++++++++++++++++++++++----- 12 files changed, 138 insertions(+), 95 deletions(-) diff --git a/.aegir.js b/.aegir.js index cafdc3b..d77e70f 100644 --- a/.aegir.js +++ b/.aegir.js @@ -15,17 +15,18 @@ module.exports = { hooks: { browser: { pre: (done) => { - PeerId.createFromJSON(peerNodeJSON, (err, id) => { + PeerId.createFromJSON(peerNodeJSON, (err, peerId) => { if (err) { throw err } const ws = new WS() listener = ws.createListener((conn) => { - const encrypted = secio.encrypt(id, id._privKey, conn, (err) => { + const encryptedConn = secio.encrypt(peerId, conn, undefined, (err) => { if (err) { throw err } }) - pull(encrypted, encrypted) + // echo + pull(encryptedConn, encryptedConn) }) listener.listen(ma, done) diff --git a/README.md b/README.md index 5a70cb7..446b792 100644 --- a/README.md +++ b/README.md @@ -43,12 +43,12 @@ const secio = require('libp2p-secio') The current `secio` tag, usable in `multistream`. -### `const encryptedConnection = secio.encrypt(id, key, plainTextConnection [, callback])` +### `const encryptedConnection = secio.encrypt(localPeerId, plainTextConnection [, remotePeerId] [, callback])` -- `id: PeerId` - The id of the node we are doing the SECIO handshake with. -- `key: RSAPrivateKey` - The private key of our node node. -- `insecure: PullStream` - The insecure connection. -- `callback: Function` - Called if an error happens during the initialization. +- `localPeerId: PeerId` - A PeerId object containing the Private, Public and Id of our node. +- `plainTextConnection: Connection` - The insecure connection to be secured. +- `remotePeerId: PeerId` - A PeerId object containing the Public and/or Id of the node we are doing the SECIO handshake with. +- `callback: Function` - Optional, Called if an error happens during the initialization. Returns an encrypted [Connection object](https://github.com/libp2p/interface-connection) that is the upgraded `plainTextConnection` with now having every byte encripted. @@ -78,8 +78,6 @@ const nodeStreamInstance = pullToStream(pullStreamInstance) To learn more about this utility, visit https://pull-stream.github.io/#pull-stream-to-stream. - - ## Contribute Feel free to join in. All welcome. Open an [issue](https://github.com/libp2p/js-libp2p-secio/issues)! diff --git a/benchmarks/send.js b/benchmarks/send.js index 3109ff8..27a7734 100644 --- a/benchmarks/send.js +++ b/benchmarks/send.js @@ -53,8 +53,8 @@ suite.add('establish an encrypted channel', (deferred) => { const peerA = peers[0] const peerB = peers[1] - const aToB = secio.encrypt(peerB, peerA.privKey, p[0], (err) => { throw err }) - const bToA = secio.encrypt(peerA, peerB.privKey, p[1], (err) => { throw err }) + const aToB = secio.encrypt(peerA, p[0], peerB, (err) => { throw err }) + const bToA = secio.encrypt(peerB, p[1], peerA, (err) => { throw err }) sendData(aToB, bToA, {}, deferred) }, { defer: true }) @@ -83,8 +83,8 @@ cases.forEach((el) => { const peerA = peers[0] const peerB = peers[1] - const aToB = secio.encrypt(peerB, peerA.privKey, p[0], (err) => { throw err }) - const bToA = secio.encrypt(peerA, peerB.privKey, p[1], (err) => { throw err }) + const aToB = secio.encrypt(peerA, p[0], peerB, (err) => { throw err }) + const bToA = secio.encrypt(peerB, p[1], peerA, (err) => { throw err }) sendData(aToB, bToA, { times: times, size: size }, deferred) }, { defer: true }) diff --git a/package.json b/package.json index be98930..df393d6 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "libp2p-crypto": "~0.11.0", "multihashing-async": "~0.4.7", "peer-id": "~0.10.4", + "peer-info": "^0.11.4", "protons": "^1.0.1", "pull-defer": "^0.2.2", "pull-handshake": "^1.1.4", diff --git a/src/handshake/crypto.js b/src/handshake/crypto.js index 9920fae..0d2f9a2 100644 --- a/src/handshake/crypto.js +++ b/src/handshake/crypto.js @@ -68,12 +68,20 @@ exports.identify = (state, msg, callback) => { const pubkey = state.proposal.in.pubkey state.key.remote = crypto.keys.unmarshalPublicKey(pubkey) + PeerId.createFromPubKey(pubkey.toString('base64'), (err, remoteId) => { if (err) { return callback(err) } - state.id.remote = remoteId + // If we know who we are dialing to, double check + if (state.id.remote) { + if (state.id.remote.toB58String() !== remoteId.toB58String()) { + return callback(new Error('dialed to the wrong peer, Ids do not match')) + } + } else { + state.id.remote = remoteId + } log('1.1 identify - %s - identified remote peer as %s', state.id.local.toB58String(), state.id.remote.toB58String()) callback() diff --git a/src/handshake/exchange.js b/src/handshake/exchange.js index 4ed867e..b4cd10b 100644 --- a/src/handshake/exchange.js +++ b/src/handshake/exchange.js @@ -27,9 +27,7 @@ module.exports = function exchange (state, cb) { }, (cb) => crypto.generateKeys(state, cb) ], (err) => { - if (err) { - return cb(err) - } + if (err) { return cb(err) } log('2. exchange - finish') cb() diff --git a/src/handshake/index.js b/src/handshake/index.js index 75ad78e..b73a1e2 100644 --- a/src/handshake/index.js +++ b/src/handshake/index.js @@ -6,9 +6,9 @@ const propose = require('./propose') const exchange = require('./exchange') const finish = require('./finish') -// Performs initial communication over insecure channel to share -// keys, IDs, and initiate communication, assigning all necessary params. -module.exports = function handshake (state) { +// Performs initial communication over insecure channel to share keys, IDs, +// and initiate communication, assigning all necessary params. +module.exports = function handshake (state, callback) { series([ (cb) => propose(state, cb), (cb) => exchange(state, cb), @@ -22,6 +22,9 @@ module.exports = function handshake (state) { } state.shake.abort(err) } + + // signal when the handshake is finished so that plumbing can happen + callback() }) return state.stream diff --git a/src/index.js b/src/index.js index db6fed6..a7e480f 100644 --- a/src/index.js +++ b/src/index.js @@ -2,41 +2,57 @@ const pull = require('pull-stream') const Connection = require('interface-connection').Connection +const assert = require('assert') +const PeerInfo = require('peer-info') +const debug = require('debug') +const once = require('once') +const log = debug('libp2p:secio') +log.error = debug('libp2p:secio:error') const handshake = require('./handshake') const State = require('./state') module.exports = { tag: '/secio/1.0.0', - encrypt (local, key, insecure, callback) { - if (!local) { - throw new Error('no local id provided') + encrypt (localId, conn, remoteId, callback) { + assert(localId, 'no local private key provided') + assert(conn, 'no connection for the handshake provided') + + if (typeof remoteId === 'function') { + callback = remoteId + remoteId = undefined } - if (!key) { - throw new Error('no local private key provided') - } + callback = once(callback || function (err) { + if (err) { log.error(err) } + }) - if (!insecure) { - throw new Error('no insecure stream provided') - } + const timeout = 60 * 1000 * 5 - if (!callback) { - callback = (err) => { - if (err) { - console.error(err) + const state = new State(localId, remoteId, timeout, callback) + + function finish (err) { + if (err) { return callback(err) } + + conn.getPeerInfo((err, peerInfo) => { + encryptedConnection.setInnerConn(state.secure) + + if (err) { // no peerInfo yet, means I'm the receiver + encryptedConnection.setPeerInfo(new PeerInfo(state.id.remote)) } - } + + callback() + }) } - const state = new State(local, key, 60 * 1000 * 5, callback) + const encryptedConnection = new Connection(undefined, conn) pull( - insecure, - handshake(state), - insecure + conn, + handshake(state, finish), + conn ) - return new Connection(state.secure, insecure) + return encryptedConnection } } diff --git a/src/state.js b/src/state.js index 47c333f..7b63414 100644 --- a/src/state.js +++ b/src/state.js @@ -4,37 +4,31 @@ const handshake = require('pull-handshake') const deferred = require('pull-defer') class State { - constructor (id, key, timeout, cb) { + constructor (localId, remoteId, timeout, callback) { if (typeof timeout === 'function') { - cb = timeout + callback = timeout timeout = undefined } this.setup() - this.id.local = id - this.key.local = key + + this.id.local = localId + // TODO use remoteId to verify PeersIdentity + this.id.remote = remoteId + this.key.local = localId.privKey this.timeout = timeout || 60 * 1000 - cb = cb || (() => {}) + callback = callback || (() => {}) this.secure = deferred.duplex() - this.stream = handshake({timeout: this.timeout}, cb) + this.stream = handshake({ timeout: this.timeout }, callback) this.shake = this.stream.handshake delete this.stream.handshake } setup () { - this.id = { - local: null, - remote: null - } - - this.key = { - local: null, - remote: null - } - + this.id = { local: null, remote: null } + this.key = { local: null, remote: null } this.shake = null - this.cleanSecrets() } @@ -42,30 +36,11 @@ class State { cleanSecrets () { this.shared = {} - this.ephemeralKey = { - local: null, - remote: null - } - - this.proposal = { - in: null, - out: null - } - - this.proposalEncoded = { - in: null, - out: null - } - - this.protocols = { - local: null, - remote: null - } - - this.exchange = { - in: null, - out: null - } + this.ephemeralKey = { local: null, remote: null } + this.proposal = { in: null, out: null } + this.proposalEncoded = { in: null, out: null } + this.protocols = { local: null, remote: null } + this.exchange = { in: null, out: null } } } diff --git a/src/support.js b/src/support.js index 55d317f..75cb578 100644 --- a/src/support.js +++ b/src/support.js @@ -116,9 +116,7 @@ exports.digest = (buf, cb) => { exports.write = function write (state, msg, cb) { cb = cb || (() => {}) pull( - pull.values([ - msg - ]), + pull.values([msg]), lp.encode({fixed: true, bytes: 4}), pull.collect((err, res) => { if (err) { diff --git a/test/browser.js b/test/browser.js index 2aff8ee..cb52f08 100644 --- a/test/browser.js +++ b/test/browser.js @@ -12,7 +12,9 @@ const WS = require('libp2p-websockets') const PeerId = require('peer-id') const parallel = require('async/parallel') +const peerNodeJSON = require('./fixtures/peer-node.json') const peerBrowserJSON = require('./fixtures/peer-browser.json') + const secio = require('../src') describe('secio between browser <-> nodejs through websockets', () => { @@ -22,17 +24,22 @@ describe('secio between browser <-> nodejs through websockets', () => { before((done) => { parallel([ + (cb) => PeerId.createFromJSON(peerNodeJSON, cb), (cb) => PeerId.createFromJSON(peerBrowserJSON, cb), + (cb) => { const ws = new WS() conn = ws.dial(ma, cb) } ], (err, res) => { - if (err) { - return done(err) - } + expect(err).to.not.exist() - encryptedConn = secio.encrypt(res[0], res[0]._privKey, conn) + const peerIdNode = res[0] + const peerIdBrowser = res[1] + + encryptedConn = secio.encrypt(peerIdBrowser, conn, peerIdNode, (err) => { + expect(err).to.not.exist() + }) done() }) }) @@ -44,7 +51,7 @@ describe('secio between browser <-> nodejs through websockets', () => { source: pull.values([message]), sink: pull.collect((err, results) => { expect(err).to.not.exist() - expect(results).to.be.eql([message]) + expect(results).to.eql([message]) done() }) }, 'GoodBye') diff --git a/test/secio.spec.js b/test/secio.spec.js index 34f598b..a4b2579 100644 --- a/test/secio.spec.js +++ b/test/secio.spec.js @@ -8,7 +8,7 @@ const dirtyChai = require('dirty-chai') const expect = chai.expect chai.use(dirtyChai) const PeerId = require('peer-id') -const crypto = require('libp2p-crypto') +const Connection = require('interface-connection').Connection const parallel = require('async/parallel') const series = require('async/series') const Buffer = require('safe-buffer').Buffer @@ -45,8 +45,8 @@ describe('secio', () => { it('upgrades a connection', (done) => { const p = pair() - const aToB = secio.encrypt(peerB, peerA.privKey, p[0], (err) => expect(err).to.not.exist()) - const bToA = secio.encrypt(peerA, peerB.privKey, p[1], (err) => expect(err).to.not.exist()) + const aToB = secio.encrypt(peerA, new Connection(p[0]), peerB, (err) => expect(err).to.not.exist()) + const bToA = secio.encrypt(peerB, new Connection(p[1]), peerA, (err) => expect(err).to.not.exist()) pull( pull.values([Buffer.from('hello world')]), @@ -76,7 +76,7 @@ describe('secio', () => { ], cb), (cb) => { listener.addHandler('/banana/1.0.0', (protocol, conn) => { - const bToA = secio.encrypt(peerA, peerB.privKey, conn, (err) => expect(err).to.not.exist()) + const bToA = secio.encrypt(peerB, conn, peerA, (err) => expect(err).to.not.exist()) pull( bToA, @@ -93,7 +93,7 @@ describe('secio', () => { (cb) => dialer.select('/banana/1.0.0', (err, conn) => { expect(err).to.not.exist() - const aToB = secio.encrypt(peerB, peerA.privKey, conn, (err) => expect(err).to.not.exist()) + const aToB = secio.encrypt(peerA, conn, peerB, (err) => expect(err).to.not.exist()) pull( pull.values([Buffer.from('hello world')]), @@ -104,6 +104,44 @@ describe('secio', () => { ]) }) - it.skip('fails if we dialed to the wrong peer', (done) => { + it('establishes the connection even if the receiver does not know who is dialing', (done) => { + const p = pair() + + const aToB = secio.encrypt(peerA, new Connection(p[0]), peerB, (err) => expect(err).to.not.exist()) + const bToA = secio.encrypt(peerB, new Connection(p[1]), undefined, (err) => expect(err).to.not.exist()) + + pull( + pull.values([Buffer.from('hello world')]), + aToB + ) + + pull( + bToA, + pull.collect((err, chunks) => { + expect(err).to.not.exist() + + expect(chunks).to.eql([Buffer.from('hello world')]) + + bToA.getPeerInfo((err, PeerInfo) => { + expect(err).to.not.exist() + expect(PeerInfo.id.toB58String()).to.equal(peerA.toB58String()) + done() + }) + }) + ) + }) + + it('fails if we dialed to the wrong peer', (done) => { + const p = pair() + let count = 0 + + function check (err) { + expect(err).to.exist() + if (++count === 2) { done() } + } + + // we are using peerC Id on purpose to fail + secio.encrypt(peerA, p[0], peerC, check) + secio.encrypt(peerB, p[1], peerA, check) }) })