test: check if we dialed to the right peer

This commit is contained in:
David Dias 2018-01-07 13:58:30 +00:00
parent ee1f64c98e
commit 74f6fd8ab3
12 changed files with 138 additions and 95 deletions

View File

@ -15,17 +15,18 @@ module.exports = {
hooks: { hooks: {
browser: { browser: {
pre: (done) => { pre: (done) => {
PeerId.createFromJSON(peerNodeJSON, (err, id) => { PeerId.createFromJSON(peerNodeJSON, (err, peerId) => {
if (err) { throw err } if (err) { throw err }
const ws = new WS() const ws = new WS()
listener = ws.createListener((conn) => { 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 } if (err) { throw err }
}) })
pull(encrypted, encrypted) // echo
pull(encryptedConn, encryptedConn)
}) })
listener.listen(ma, done) listener.listen(ma, done)

View File

@ -43,12 +43,12 @@ const secio = require('libp2p-secio')
The current `secio` tag, usable in `multistream`. 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. - `localPeerId: PeerId` - A PeerId object containing the Private, Public and Id of our node.
- `key: RSAPrivateKey` - The private key of our node node. - `plainTextConnection: Connection` - The insecure connection to be secured.
- `insecure: PullStream` - The insecure connection. - `remotePeerId: PeerId` - A PeerId object containing the Public and/or Id of the node we are doing the SECIO handshake with.
- `callback: Function` - Called if an error happens during the initialization. - `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. 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. To learn more about this utility, visit https://pull-stream.github.io/#pull-stream-to-stream.
## Contribute ## Contribute
Feel free to join in. All welcome. Open an [issue](https://github.com/libp2p/js-libp2p-secio/issues)! Feel free to join in. All welcome. Open an [issue](https://github.com/libp2p/js-libp2p-secio/issues)!

View File

@ -53,8 +53,8 @@ suite.add('establish an encrypted channel', (deferred) => {
const peerA = peers[0] const peerA = peers[0]
const peerB = peers[1] const peerB = peers[1]
const aToB = secio.encrypt(peerB, peerA.privKey, p[0], (err) => { throw err }) const aToB = secio.encrypt(peerA, p[0], peerB, (err) => { throw err })
const bToA = secio.encrypt(peerA, peerB.privKey, p[1], (err) => { throw err }) const bToA = secio.encrypt(peerB, p[1], peerA, (err) => { throw err })
sendData(aToB, bToA, {}, deferred) sendData(aToB, bToA, {}, deferred)
}, { defer: true }) }, { defer: true })
@ -83,8 +83,8 @@ cases.forEach((el) => {
const peerA = peers[0] const peerA = peers[0]
const peerB = peers[1] const peerB = peers[1]
const aToB = secio.encrypt(peerB, peerA.privKey, p[0], (err) => { throw err }) const aToB = secio.encrypt(peerA, p[0], peerB, (err) => { throw err })
const bToA = secio.encrypt(peerA, peerB.privKey, p[1], (err) => { throw err }) const bToA = secio.encrypt(peerB, p[1], peerA, (err) => { throw err })
sendData(aToB, bToA, { times: times, size: size }, deferred) sendData(aToB, bToA, { times: times, size: size }, deferred)
}, { defer: true }) }, { defer: true })

View File

@ -31,6 +31,7 @@
"libp2p-crypto": "~0.11.0", "libp2p-crypto": "~0.11.0",
"multihashing-async": "~0.4.7", "multihashing-async": "~0.4.7",
"peer-id": "~0.10.4", "peer-id": "~0.10.4",
"peer-info": "^0.11.4",
"protons": "^1.0.1", "protons": "^1.0.1",
"pull-defer": "^0.2.2", "pull-defer": "^0.2.2",
"pull-handshake": "^1.1.4", "pull-handshake": "^1.1.4",

View File

@ -68,12 +68,20 @@ exports.identify = (state, msg, callback) => {
const pubkey = state.proposal.in.pubkey const pubkey = state.proposal.in.pubkey
state.key.remote = crypto.keys.unmarshalPublicKey(pubkey) state.key.remote = crypto.keys.unmarshalPublicKey(pubkey)
PeerId.createFromPubKey(pubkey.toString('base64'), (err, remoteId) => { PeerId.createFromPubKey(pubkey.toString('base64'), (err, remoteId) => {
if (err) { if (err) {
return callback(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()) log('1.1 identify - %s - identified remote peer as %s', state.id.local.toB58String(), state.id.remote.toB58String())
callback() callback()

View File

@ -27,9 +27,7 @@ module.exports = function exchange (state, cb) {
}, },
(cb) => crypto.generateKeys(state, cb) (cb) => crypto.generateKeys(state, cb)
], (err) => { ], (err) => {
if (err) { if (err) { return cb(err) }
return cb(err)
}
log('2. exchange - finish') log('2. exchange - finish')
cb() cb()

View File

@ -6,9 +6,9 @@ const propose = require('./propose')
const exchange = require('./exchange') const exchange = require('./exchange')
const finish = require('./finish') const finish = require('./finish')
// Performs initial communication over insecure channel to share // Performs initial communication over insecure channel to share keys, IDs,
// keys, IDs, and initiate communication, assigning all necessary params. // and initiate communication, assigning all necessary params.
module.exports = function handshake (state) { module.exports = function handshake (state, callback) {
series([ series([
(cb) => propose(state, cb), (cb) => propose(state, cb),
(cb) => exchange(state, cb), (cb) => exchange(state, cb),
@ -22,6 +22,9 @@ module.exports = function handshake (state) {
} }
state.shake.abort(err) state.shake.abort(err)
} }
// signal when the handshake is finished so that plumbing can happen
callback()
}) })
return state.stream return state.stream

View File

@ -2,41 +2,57 @@
const pull = require('pull-stream') const pull = require('pull-stream')
const Connection = require('interface-connection').Connection 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 handshake = require('./handshake')
const State = require('./state') const State = require('./state')
module.exports = { module.exports = {
tag: '/secio/1.0.0', tag: '/secio/1.0.0',
encrypt (local, key, insecure, callback) { encrypt (localId, conn, remoteId, callback) {
if (!local) { assert(localId, 'no local private key provided')
throw new Error('no local id provided') assert(conn, 'no connection for the handshake provided')
if (typeof remoteId === 'function') {
callback = remoteId
remoteId = undefined
} }
if (!key) { callback = once(callback || function (err) {
throw new Error('no local private key provided') if (err) { log.error(err) }
} })
if (!insecure) { const timeout = 60 * 1000 * 5
throw new Error('no insecure stream provided')
}
if (!callback) { const state = new State(localId, remoteId, timeout, callback)
callback = (err) => {
if (err) { function finish (err) {
console.error(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( pull(
insecure, conn,
handshake(state), handshake(state, finish),
insecure conn
) )
return new Connection(state.secure, insecure) return encryptedConnection
} }
} }

View File

@ -4,37 +4,31 @@ const handshake = require('pull-handshake')
const deferred = require('pull-defer') const deferred = require('pull-defer')
class State { class State {
constructor (id, key, timeout, cb) { constructor (localId, remoteId, timeout, callback) {
if (typeof timeout === 'function') { if (typeof timeout === 'function') {
cb = timeout callback = timeout
timeout = undefined timeout = undefined
} }
this.setup() 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 this.timeout = timeout || 60 * 1000
cb = cb || (() => {}) callback = callback || (() => {})
this.secure = deferred.duplex() this.secure = deferred.duplex()
this.stream = handshake({timeout: this.timeout}, cb) this.stream = handshake({ timeout: this.timeout }, callback)
this.shake = this.stream.handshake this.shake = this.stream.handshake
delete this.stream.handshake delete this.stream.handshake
} }
setup () { setup () {
this.id = { this.id = { local: null, remote: null }
local: null, this.key = { local: null, remote: null }
remote: null
}
this.key = {
local: null,
remote: null
}
this.shake = null this.shake = null
this.cleanSecrets() this.cleanSecrets()
} }
@ -42,30 +36,11 @@ class State {
cleanSecrets () { cleanSecrets () {
this.shared = {} this.shared = {}
this.ephemeralKey = { this.ephemeralKey = { local: null, remote: null }
local: null, this.proposal = { in: null, out: null }
remote: null this.proposalEncoded = { in: null, out: null }
} this.protocols = { local: null, remote: null }
this.exchange = { in: null, out: 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
}
} }
} }

View File

@ -116,9 +116,7 @@ exports.digest = (buf, cb) => {
exports.write = function write (state, msg, cb) { exports.write = function write (state, msg, cb) {
cb = cb || (() => {}) cb = cb || (() => {})
pull( pull(
pull.values([ pull.values([msg]),
msg
]),
lp.encode({fixed: true, bytes: 4}), lp.encode({fixed: true, bytes: 4}),
pull.collect((err, res) => { pull.collect((err, res) => {
if (err) { if (err) {

View File

@ -12,7 +12,9 @@ const WS = require('libp2p-websockets')
const PeerId = require('peer-id') const PeerId = require('peer-id')
const parallel = require('async/parallel') const parallel = require('async/parallel')
const peerNodeJSON = require('./fixtures/peer-node.json')
const peerBrowserJSON = require('./fixtures/peer-browser.json') const peerBrowserJSON = require('./fixtures/peer-browser.json')
const secio = require('../src') const secio = require('../src')
describe('secio between browser <-> nodejs through websockets', () => { describe('secio between browser <-> nodejs through websockets', () => {
@ -22,17 +24,22 @@ describe('secio between browser <-> nodejs through websockets', () => {
before((done) => { before((done) => {
parallel([ parallel([
(cb) => PeerId.createFromJSON(peerNodeJSON, cb),
(cb) => PeerId.createFromJSON(peerBrowserJSON, cb), (cb) => PeerId.createFromJSON(peerBrowserJSON, cb),
(cb) => { (cb) => {
const ws = new WS() const ws = new WS()
conn = ws.dial(ma, cb) conn = ws.dial(ma, cb)
} }
], (err, res) => { ], (err, res) => {
if (err) { expect(err).to.not.exist()
return done(err)
}
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() done()
}) })
}) })
@ -44,7 +51,7 @@ describe('secio between browser <-> nodejs through websockets', () => {
source: pull.values([message]), source: pull.values([message]),
sink: pull.collect((err, results) => { sink: pull.collect((err, results) => {
expect(err).to.not.exist() expect(err).to.not.exist()
expect(results).to.be.eql([message]) expect(results).to.eql([message])
done() done()
}) })
}, 'GoodBye') }, 'GoodBye')

View File

@ -8,7 +8,7 @@ const dirtyChai = require('dirty-chai')
const expect = chai.expect const expect = chai.expect
chai.use(dirtyChai) chai.use(dirtyChai)
const PeerId = require('peer-id') const PeerId = require('peer-id')
const crypto = require('libp2p-crypto') const Connection = require('interface-connection').Connection
const parallel = require('async/parallel') const parallel = require('async/parallel')
const series = require('async/series') const series = require('async/series')
const Buffer = require('safe-buffer').Buffer const Buffer = require('safe-buffer').Buffer
@ -45,8 +45,8 @@ describe('secio', () => {
it('upgrades a connection', (done) => { it('upgrades a connection', (done) => {
const p = pair() const p = pair()
const aToB = secio.encrypt(peerB, peerA.privKey, p[0], (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(peerA, peerB.privKey, p[1], (err) => expect(err).to.not.exist()) const bToA = secio.encrypt(peerB, new Connection(p[1]), peerA, (err) => expect(err).to.not.exist())
pull( pull(
pull.values([Buffer.from('hello world')]), pull.values([Buffer.from('hello world')]),
@ -76,7 +76,7 @@ describe('secio', () => {
], cb), ], cb),
(cb) => { (cb) => {
listener.addHandler('/banana/1.0.0', (protocol, conn) => { 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( pull(
bToA, bToA,
@ -93,7 +93,7 @@ describe('secio', () => {
(cb) => dialer.select('/banana/1.0.0', (err, conn) => { (cb) => dialer.select('/banana/1.0.0', (err, conn) => {
expect(err).to.not.exist() 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(
pull.values([Buffer.from('hello world')]), 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)
}) })
}) })