refactor: use async await (#108)

* chore: upgrade deps

* feat: first iteration of the idea

* feat: iterate a layer deeper

* feat: rewrite handshake

* feat: rewrite propose

* feat: rewrite finish

* feat: rewrite exchange

* feat: rewrite low-level stuff

* feat: work on rewriting tests

* refactor: browser tests

* refactor: .aegir.js

* feat: refactor benchmarks

* fix: try to make it work

* fix: lint

* refactor: move tests

* refactor: switch deps

* refactor: entry file

* refactor: a bit more

* fix: tests

* feat: inital iterables refactor

* refactor: streaming

* refactor: cleanup

* fix: turn bufferlist into buffer

* fix: use errors from interfaces

* refactor: etm

* fix: typo

* fix: .read error

* fix: satisfy output expectations

* fix: it works - WARNING: using varint instead of fixed lp, tests lie

* fix: use errors

* refactor: benchmarks

* fix: add suggestions from review

Co-Authored-By: Jacob Heun <jacobheun@gmail.com>

* fix: upgrade deps and use correct lp-encoder

* refactor: apply changes from review

* refactor: apply changes from review

* refactor: apply changes from review

* chore: remove old tests

test: add support tests back

* test: fix async benchmarks

* chore: clean up deps

* fix: use fixed encoding/decoding everywhere

fix: exchange final nonce handshake over encryption

* test: add verify inbound and outbound secio

* test: verify nonces are boxed

* chore: add node 12 to ci
This commit is contained in:
Maciej Krüger 2019-11-22 21:18:17 +01:00 committed by Jacob Heun
parent 774267f08e
commit 8ad4c15562
24 changed files with 468 additions and 787 deletions

View File

@ -1,40 +0,0 @@
'use strict'
const multiaddr = require('multiaddr')
const pull = require('pull-stream')
const WS = require('libp2p-websockets')
const PeerId = require('peer-id')
const secio = require('./src')
const peerNodeJSON = require('./test/fixtures/peer-node.json')
const ma = multiaddr('/ip4/127.0.0.1/tcp/9090/ws')
let listener
module.exports = {
hooks: {
browser: {
pre: (done) => {
PeerId.createFromJSON(peerNodeJSON, (err, peerId) => {
if (err) { throw err }
const ws = new WS()
listener = ws.createListener((conn) => {
const encryptedConn = secio.encrypt(peerId, conn, undefined, (err) => {
if (err) { throw err }
})
// echo
pull(encryptedConn, encryptedConn)
})
listener.listen(ma, done)
})
},
post: (done) => {
listener.close(done)
}
}
}
}

View File

@ -9,6 +9,7 @@ stages:
node_js:
- '10'
- '12'
os:
- linux

View File

@ -42,44 +42,9 @@ const secio = require('libp2p-secio')
## API
### `.tag`
This module exposes a crypto interface, as defined in the [js-interfaces](https://github.com/libp2p/js-interfaces)
The current `secio` tag, usable in `multistream`.
### `const encryptedConnection = secio.encrypt(localPeerId, plainTextConnection [, remotePeerId] [, callback])`
- `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 encrypted.
Both plainTextConnection and encryptedConnection are at their base, PullStreams.
### This module uses `pull-streams`
We expose a streaming interface based on `pull-streams`, rather then on the Node.js core streams implementation (aka Node.js streams). `pull-streams` offers us a better mechanism for error handling and flow control guarantees. If you would like to know more about why we did this, see the discussion at this [issue](https://github.com/ipfs/js-ipfs/issues/362).
You can learn more about pull-streams at:
- [The history of Node.js streams, nodebp April 2014](https://www.youtube.com/watch?v=g5ewQEuXjsQ)
- [The history of streams, 2016](http://dominictarr.com/post/145135293917/history-of-streams)
- [pull-streams, the simple streaming primitive](http://dominictarr.com/post/149248845122/pull-streams-pull-streams-are-a-very-simple)
- [pull-streams documentation](https://pull-stream.github.io/)
#### Converting `pull-streams` to Node.js Streams
If you are a Node.js streams user, you can convert a pull-stream to a Node.js stream using the module [`pull-stream-to-stream`](https://github.com/pull-stream/pull-stream-to-stream), giving you an instance of a Node.js stream that is linked to the pull-stream. For example:
```js
const pullToStream = require('pull-stream-to-stream')
const nodeStreamInstance = pullToStream(pullStreamInstance)
// nodeStreamInstance is an instance of a Node.js Stream
```
To learn more about this utility, visit https://pull-stream.github.io/#pull-stream-to-stream.
[ » API Docs ](https://github.com/libp2p/js-interfaces/tree/master/src/crypto#api)
## Contribute

View File

@ -1,73 +1,70 @@
'use strict'
/* eslint-disable no-console */
const Benchmark = require('benchmark')
const pull = require('pull-stream/pull')
const infinite = require('pull-stream/sources/infinite')
const take = require('pull-stream/throughs/take')
const drain = require('pull-stream/sinks/drain')
const Connection = require('interface-connection').Connection
const parallel = require('async/parallel')
const pair = require('pull-pair/duplex')
const PeerId = require('peer-id')
const secio = require('../src')
const pipe = require('it-pipe')
const { reduce } = require('streaming-iterables')
const DuplexPair = require('it-pair/duplex')
const secio = require('..')
const suite = new Benchmark.Suite('secio')
let peers
function sendData (a, b, opts, finish) {
async function sendData (a, b, opts) {
opts = Object.assign({ times: 1, size: 100 }, opts)
pull(
infinite(() => Buffer.allocUnsafe(opts.size)),
take(opts.times),
let i = opts.times
pipe(
function * () {
while (i--) {
yield Buffer.allocUnsafe(opts.size)
}
},
a
)
let length = 0
pull(
const res = await pipe(
b,
drain((data) => {
length += data.length
}, () => {
if (length !== opts.times * opts.size) {
throw new Error('Did not receive enough chunks')
}
finish.resolve()
})
reduce((acc, val) => acc + val.length, 0)
)
}
function ifErr (err) {
if (err) {
throw err
if (res !== opts.times * opts.size) {
throw new Error('Did not receive enough chunks')
}
}
suite.add('create peers for test', (deferred) => {
parallel([
(cb) => PeerId.createFromJSON(require('./peer-a'), cb),
(cb) => PeerId.createFromJSON(require('./peer-b'), cb)
], (err, _peers) => {
if (err) { throw err }
peers = _peers
suite.add('create peers for test', {
defer: true,
fn: async (deferred) => {
peers = await Promise.all([
PeerId.createFromJSON(require('./peer-a')),
PeerId.createFromJSON(require('./peer-b'))
])
deferred.resolve()
})
}, { defer: true })
}
})
suite.add('establish an encrypted channel', {
defer: true,
fn: async (deferred) => {
const p = DuplexPair()
suite.add('establish an encrypted channel', (deferred) => {
const p = pair()
const peerA = peers[0]
const peerB = peers[1]
const peerA = peers[0]
const peerB = peers[1]
const [aToB, bToA] = await Promise.all([
secio.secureInbound(peerA, p[0], peerB),
secio.secureOutbound(peerB, p[1], peerA)
])
const aToB = secio.encrypt(peerA, new Connection(p[0]), peerB, ifErr)
const bToA = secio.encrypt(peerB, new Connection(p[1]), peerA, ifErr)
sendData(aToB, bToA, {}, deferred)
}, { defer: true })
await sendData(aToB.conn, bToA.conn, {})
deferred.resolve()
}
})
const cases = [
[10, 262144],
@ -81,23 +78,32 @@ cases.forEach((el) => {
const times = el[0]
const size = el[1]
suite.add(`send plaintext ${times} x ${size} bytes`, (deferred) => {
const p = pair()
suite.add(`send plaintext ${times} x ${size} bytes`, {
defer: true,
fn: async (deferred) => {
const p = DuplexPair()
await sendData(p[0], p[1], { times: times, size: size })
deferred.resolve()
}
})
sendData(p[0], p[1], { times: times, size: size }, deferred)
}, { defer: true })
suite.add(`send encrypted ${times} x ${size} bytes`, {
defer: true,
fn: async (deferred) => {
const p = DuplexPair()
suite.add(`send encrypted ${times} x ${size} bytes`, (deferred) => {
const p = pair()
const peerA = peers[0]
const peerB = peers[1]
const peerA = peers[0]
const peerB = peers[1]
const [aToB, bToA] = await Promise.all([
secio.secureInbound(peerA, p[0], peerB),
secio.secureOutbound(peerB, p[1], peerA)
])
const aToB = secio.encrypt(peerA, new Connection(p[0]), peerB, ifErr)
const bToA = secio.encrypt(peerB, new Connection(p[1]), peerA, ifErr)
sendData(aToB, bToA, { times: times, size: size }, deferred)
}, { defer: true })
await sendData(aToB.conn, bToA.conn, { times: times, size: size })
deferred.resolve()
}
})
})
suite.on('cycle', (event) => {

View File

@ -25,31 +25,26 @@
],
"license": "MIT",
"dependencies": {
"async": "^2.6.2",
"bl": "^4.0.0",
"debug": "^4.1.1",
"interface-connection": "~0.3.3",
"libp2p-crypto": "~0.16.1",
"multiaddr": "^6.0.6",
"multihashing-async": "~0.6.0",
"once": "^1.4.0",
"peer-id": "~0.12.2",
"peer-info": "~0.15.1",
"protons": "^1.0.1",
"pull-defer": "~0.2.3",
"pull-handshake": "^1.1.4",
"pull-length-prefixed": "^1.3.2",
"pull-stream": "^3.6.9",
"safe-buffer": "^5.1.2"
"it-buffer": "^0.1.1",
"it-length-prefixed": "^3.0.0",
"it-pair": "^1.0.0",
"it-pb-rpc": "^0.1.4",
"it-pipe": "^1.1.0",
"libp2p-crypto": "~0.17.1",
"libp2p-interfaces": "~0.1.3",
"multiaddr": "^7.2.1",
"multihashing-async": "~0.8.0",
"peer-id": "~0.13.5",
"protons": "^1.0.1"
},
"devDependencies": {
"aegir": "^18.2.2",
"aegir": "^20.4.1",
"benchmark": "^2.1.4",
"chai": "^4.2.0",
"dirty-chai": "^2.0.1",
"libp2p-websockets": "~0.12.2",
"multistream-select": "~0.14.4",
"pull-goodbye": "~0.0.2",
"pull-pair": "^1.1.0"
"streaming-iterables": "^4.1.1"
},
"engines": {
"node": ">=6.0.0",

View File

@ -1,81 +1,41 @@
'use strict'
const pull = require('pull-stream/pull')
const map = require('pull-stream/throughs/map')
const asyncMap = require('pull-stream/throughs/async-map')
const lp = require('pull-length-prefixed')
const lpOpts = {
fixed: true,
bytes: 4
}
const BufferList = require('bl/BufferList')
const { InvalidCryptoTransmissionError } = require('libp2p-interfaces/src/crypto/errors')
exports.createBoxStream = (cipher, mac) => {
return pull(
ensureBuffer(),
asyncMap((chunk, cb) => {
cipher.encrypt(chunk, (err, data) => {
if (err) {
return cb(err)
}
mac.digest(data, (err, digest) => {
if (err) {
return cb(err)
}
cb(null, Buffer.concat([data, digest]))
})
})
}),
lp.encode(lpOpts)
)
return async function * (source) {
for await (const chunk of source) {
const data = await cipher.encrypt(chunk)
const digest = await mac.digest(data)
yield new BufferList([data, digest])
}
}
}
exports.createUnboxStream = (decipher, mac) => {
return pull(
ensureBuffer(),
lp.decode(lpOpts),
asyncMap((chunk, cb) => {
return async function * (source) {
for await (const chunk of source) {
const l = chunk.length
const macSize = mac.length
if (l < macSize) {
return cb(new Error(`buffer (${l}) shorter than MAC size (${macSize})`))
throw new InvalidCryptoTransmissionError(`buffer (${l}) shorter than MAC size (${macSize})`)
}
const mark = l - macSize
const data = chunk.slice(0, mark)
const macd = chunk.slice(mark)
mac.digest(data, (err, expected) => {
if (err) {
return cb(err)
}
const expected = await mac.digest(data)
if (!macd.equals(expected)) {
return cb(new Error(`MAC Invalid: ${macd.toString('hex')} != ${expected.toString('hex')}`))
}
if (!macd.equals(expected)) {
throw new InvalidCryptoTransmissionError(`MAC Invalid: ${macd.toString('hex')} != ${expected.toString('hex')}`)
}
// all good, decrypt
decipher.decrypt(data, (err, decrypted) => {
if (err) {
return cb(err)
}
const decrypted = await decipher.decrypt(data)
cb(null, decrypted)
})
})
})
)
}
function ensureBuffer () {
return map((c) => {
if (typeof c === 'string') {
return Buffer.from(c, 'utf-8')
yield decrypted
}
return c
})
}
}

View File

@ -1,18 +1,17 @@
'use strict'
const protons = require('protons')
const PeerId = require('peer-id')
const crypto = require('libp2p-crypto')
const parallel = require('async/parallel')
const waterfall = require('async/waterfall')
const debug = require('debug')
const log = debug('libp2p:secio')
log.error = debug('libp2p:secio:error')
const pbm = protons(require('./secio.proto'))
const pbm = require('./secio.proto')
const support = require('../support')
const { UnexpectedPeerError } = require('libp2p-interfaces/src/crypto/errors')
// nonceSize is the size of our nonces (in bytes)
const nonceSize = 16
@ -29,38 +28,30 @@ exports.createProposal = (state) => {
return state.proposalEncoded.out
}
exports.createExchange = (state, callback) => {
crypto.keys.generateEphemeralKeyPair(state.protocols.local.curveT, (err, res) => {
if (err) {
return callback(err)
}
exports.createExchange = async (state) => {
const res = await crypto.keys.generateEphemeralKeyPair(state.protocols.local.curveT)
state.ephemeralKey.local = res.key
state.shared.generate = res.genSharedKey
state.ephemeralKey.local = res.key
state.shared.generate = res.genSharedKey
// Gather corpus to sign.
const selectionOut = Buffer.concat([
state.proposalEncoded.out,
state.proposalEncoded.in,
state.ephemeralKey.local
])
// Gather corpus to sign.
const selectionOut = Buffer.concat([
state.proposalEncoded.out,
state.proposalEncoded.in,
state.ephemeralKey.local
])
state.key.local.sign(selectionOut, (err, sig) => {
if (err) {
return callback(err)
}
const sig = await state.key.local.sign(selectionOut)
state.exchange.out = {
epubkey: state.ephemeralKey.local,
signature: sig
}
state.exchange.out = {
epubkey: state.ephemeralKey.local,
signature: sig
}
callback(null, pbm.Exchange.encode(state.exchange.out))
})
})
return pbm.Exchange.encode(state.exchange.out)
}
exports.identify = (state, msg, callback) => {
exports.identify = async (state, msg) => {
log('1.1 identify')
state.proposalEncoded.in = msg
@ -69,26 +60,21 @@ exports.identify = (state, msg, callback) => {
state.key.remote = crypto.keys.unmarshalPublicKey(pubkey)
PeerId.createFromPubKey(pubkey.toString('base64'), (err, remoteId) => {
if (err) {
return callback(err)
}
const remoteId = await PeerId.createFromPubKey(pubkey.toString('base64'))
// 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
// If we know who we are dialing to, double check
if (state.id.remote) {
if (state.id.remote.toString() !== remoteId.toString()) {
throw new UnexpectedPeerError('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()
})
log('1.1 identify - %s - identified remote peer as %s', state.id.local.toB58String(), state.id.remote.toB58String())
}
exports.selectProtocols = (state, callback) => {
exports.selectProtocols = async (state) => {
log('1.2 selection')
const local = {
@ -107,30 +93,26 @@ exports.selectProtocols = (state, callback) => {
nonce: state.proposal.in.rand
}
support.selectBest(local, remote, (err, selected) => {
if (err) {
return callback(err)
}
// we use the same params for both directions (must choose same curve)
// WARNING: if they dont SelectBest the same way, this won't work...
state.protocols.remote = {
order: selected.order,
curveT: selected.curveT,
cipherT: selected.cipherT,
hashT: selected.hashT
}
const selected = await support.selectBest(local, remote)
state.protocols.local = {
order: selected.order,
curveT: selected.curveT,
cipherT: selected.cipherT,
hashT: selected.hashT
}
callback()
})
// we use the same params for both directions (must choose same curve)
// WARNING: if they dont SelectBest the same way, this won't work...
state.protocols.remote = {
order: selected.order,
curveT: selected.curveT,
cipherT: selected.cipherT,
hashT: selected.hashT
}
state.protocols.local = {
order: selected.order,
curveT: selected.curveT,
cipherT: selected.cipherT,
hashT: selected.hashT
}
}
exports.verify = (state, msg, callback) => {
exports.verify = async (state, msg) => {
log('2.1. verify')
state.exchange.in = pbm.Exchange.decode(msg)
@ -142,57 +124,43 @@ exports.verify = (state, msg, callback) => {
state.ephemeralKey.remote
])
state.key.remote.verify(selectionIn, state.exchange.in.signature, (err, sigOk) => {
if (err) {
return callback(err)
}
const sigOk = await state.key.remote.verify(selectionIn, state.exchange.in.signature)
if (!sigOk) {
return callback(new Error('Bad signature'))
}
if (!sigOk) {
throw new Error('Bad signature')
}
log('2.1. verify - signature verified')
callback()
})
log('2.1. verify - signature verified')
}
exports.generateKeys = (state, callback) => {
exports.generateKeys = async (state) => {
log('2.2. keys')
waterfall([
(cb) => state.shared.generate(state.exchange.in.epubkey, cb),
(secret, cb) => {
state.shared.secret = secret
const secret = await state.shared.generate(state.exchange.in.epubkey)
crypto.keys.keyStretcher(
state.protocols.local.cipherT,
state.protocols.local.hashT,
state.shared.secret,
cb
)
},
(keys, cb) => {
// use random nonces to decide order.
if (state.protocols.local.order > 0) {
state.protocols.local.keys = keys.k1
state.protocols.remote.keys = keys.k2
} else if (state.protocols.local.order < 0) {
// swap
state.protocols.local.keys = keys.k2
state.protocols.remote.keys = keys.k1
} else {
// we should've bailed before state. but if not, bail here.
return cb(new Error('you are trying to talk to yourself'))
}
state.shared.secret = secret
log('2.3. mac + cipher')
const keys = await crypto.keys.keyStretcher(
state.protocols.local.cipherT,
state.protocols.local.hashT,
state.shared.secret)
parallel([
(_cb) => support.makeMacAndCipher(state.protocols.local, _cb),
(_cb) => support.makeMacAndCipher(state.protocols.remote, _cb)
], cb)
}
], callback)
// use random nonces to decide order.
if (state.protocols.local.order > 0) {
state.protocols.local.keys = keys.k1
state.protocols.remote.keys = keys.k2
} else if (state.protocols.local.order < 0) {
// swap
state.protocols.local.keys = keys.k2
state.protocols.remote.keys = keys.k1
} else {
// we should've bailed before state. but if not, bail here.
throw new Error('you are trying to talk to yourself')
}
log('2.3. mac + cipher')
await Promise.all([state.protocols.local, state.protocols.remote].map(data => support.makeMacAndCipher(data)))
}
exports.verifyNonce = (state, n2) => {

View File

@ -1,35 +1,25 @@
'use strict'
const debug = require('debug')
const waterfall = require('async/waterfall')
const support = require('../support')
const crypto = require('./crypto')
const debug = require('debug')
const log = debug('libp2p:secio')
log.error = debug('libp2p:secio:error')
// step 2. Exchange
// -- exchange (signed) ephemeral keys. verify signatures.
module.exports = function exchange (state, callback) {
module.exports = async function exchange (state, wrapped) {
log('2. exchange - start')
log('2. exchange - writing exchange')
waterfall([
(cb) => crypto.createExchange(state, cb),
(ex, cb) => {
support.write(state, ex)
support.read(state.shake, cb)
},
(msg, cb) => {
log('2. exchange - reading exchange')
crypto.verify(state, msg, cb)
},
(cb) => crypto.generateKeys(state, cb)
], (err) => {
if (err) { return callback(err) }
const ex = await crypto.createExchange(state)
log('2. exchange - finish')
callback()
})
await wrapped.writeLP(ex)
const msg = await wrapped.readLP()
log('2. exchange - reading exchange')
await crypto.verify(state, msg.slice())
await crypto.generateKeys(state)
log('2. exchange - finish')
}

View File

@ -1,61 +1,49 @@
'use strict'
const pull = require('pull-stream/pull')
const pullError = require('pull-stream/sources/error')
const handshake = require('pull-handshake')
const debug = require('debug')
const log = debug('libp2p:secio')
log.error = debug('libp2p:secio:error')
const DuplexPair = require('it-pair/duplex')
const pipe = require('it-pipe')
const lp = require('it-length-prefixed')
const Wrap = require('it-pb-rpc')
const { int32BEEncode, int32BEDecode } = lp
const ensureBuffer = require('it-buffer')
const etm = require('../etm')
const crypto = require('./crypto')
// step 3. Finish
// -- send expected message to verify encryption works (send local nonce)
module.exports = function finish (state, callback) {
module.exports = async function finish (state, wrapped) {
log('3. finish - start')
const proto = state.protocols
const stream = state.shake.rest()
const shake = handshake({ timeout: state.timeout }, (err) => {
if (err) {
throw err
}
})
pull(
stream,
etm.createUnboxStream(proto.remote.cipher, proto.remote.mac),
shake,
const [secure, user] = DuplexPair()
const network = wrapped.unwrap()
pipe(
secure, // this is FROM the user
ensureBuffer,
etm.createBoxStream(proto.local.cipher, proto.local.mac),
stream
lp.encode({ lengthEncoder: int32BEEncode }),
network, // and gets piped INTO and FROM the network
lp.decode({ lengthDecoder: int32BEDecode }),
ensureBuffer,
etm.createUnboxStream(proto.remote.cipher, proto.remote.mac),
secure // and gets piped TO the user
)
shake.handshake.write(state.proposal.in.rand)
shake.handshake.read(state.proposal.in.rand.length, (err, nonceBack) => {
const fail = (err) => {
log.error(err)
state.secure.resolve({
source: pullError(err),
sink (read) {
}
})
callback(err)
}
// Exchange nonces over the encrypted stream for final verification
const shake = Wrap(user)
shake.write(state.proposal.in.rand)
const nonceBack = await shake.read(state.proposal.in.rand.length)
crypto.verifyNonce(state, nonceBack.slice())
if (err) return fail(err)
log('3. finish - finish')
try {
crypto.verifyNonce(state, nonceBack)
} catch (err) {
return fail(err)
}
log('3. finish - finish')
// Awesome that's all folks.
state.secure.resolve(shake.handshake.rest())
callback()
})
// Awesome that's all folks.
state.secure = shake.unwrap()
}

View File

@ -1,31 +1,15 @@
'use strict'
const series = require('async/series')
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, callback) {
series([
(cb) => propose(state, cb),
(cb) => exchange(state, cb),
(cb) => finish(state, cb)
], (err) => {
state.cleanSecrets()
module.exports = async function handshake (state, wrapped) {
await propose(state, wrapped)
await exchange(state, wrapped)
await finish(state, wrapped)
if (err) {
if (err === true) {
err = new Error('Stream ended prematurely')
}
state.shake.abort(err)
}
// signal when the handshake is finished so that plumbing can happen
callback(err)
})
return state.stream
state.cleanSecrets()
}

View File

@ -1,35 +1,28 @@
'use strict'
const debug = require('debug')
const waterfall = require('async/waterfall')
const support = require('../support')
const crypto = require('./crypto')
const lp = require('it-length-prefixed')
const { int32BEEncode } = lp
const debug = require('debug')
const log = debug('libp2p:secio')
log.error = debug('libp2p:secio:error')
// step 1. Propose
// -- propose cipher suite + send pubkeys + nonce
module.exports = function propose (state, callback) {
module.exports = async function propose (state, wrapped) {
log('1. propose - start')
log('1. propose - writing proposal')
support.write(state, crypto.createProposal(state))
const prop = crypto.createProposal(state)
log('1. propose - writing proposal', prop)
waterfall([
(cb) => support.read(state.shake, cb),
(msg, cb) => {
log('1. propose - reading proposal', msg)
crypto.identify(state, msg, cb)
},
(cb) => crypto.selectProtocols(state, cb)
], (err) => {
if (err) {
return callback(err)
}
await wrapped.write(lp.encode.single(prop, { lengthEncoder: int32BEEncode }))
log('1. propose - finish')
callback()
})
log('1. propose - reading proposal')
const msg = (await wrapped.readLP()).slice()
log('1. propose - read proposal', msg)
await crypto.identify(state, msg)
await crypto.selectProtocols(state)
log('1. propose - finish')
}

View File

@ -1,6 +1,8 @@
'use strict'
module.exports = `message Propose {
const protons = require('protons')
module.exports = protons(`message Propose {
optional bytes rand = 1;
optional bytes pubkey = 2;
optional string exchanges = 3;
@ -11,4 +13,4 @@ module.exports = `message Propose {
message Exchange {
optional bytes epubkey = 1;
optional bytes signature = 2;
}`
}`)

View File

@ -1,58 +1,33 @@
'use strict'
const pull = require('pull-stream/pull')
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')
const Wrap = require('it-pb-rpc')
const { int32BEDecode, int32BEEncode } = require('it-length-prefixed')
module.exports = {
tag: '/secio/1.0.0',
encrypt (localId, conn, remoteId, callback) {
assert(localId, 'no local private key provided')
assert(conn, 'no connection for the handshake provided')
async function secure (localPeer, duplex, remotePeer) { // returns duplex
assert(localPeer, 'no local private key provided')
assert(duplex, 'no connection for the handshake provided')
if (typeof remoteId === 'function') {
callback = remoteId
remoteId = undefined
}
const state = new State(localPeer, remotePeer)
const wrapped = Wrap(duplex, { lengthDecoder: int32BEDecode, lengthEncoder: int32BEEncode })
await handshake(state, wrapped)
callback = once(callback || function (err) {
if (err) { log.error(err) }
})
const timeout = 60 * 1000 * 5
const state = new State(localId, remoteId, timeout, callback)
function finish (err) {
if (err) { return callback(err) }
conn.getPeerInfo((err, peerInfo) => {
encryptedConnection.setInnerConn(new Connection(state.secure, conn))
if (err) { // no peerInfo yet, means I'm the receiver
encryptedConnection.setPeerInfo(new PeerInfo(state.id.remote))
}
callback()
})
}
const encryptedConnection = new Connection(undefined, conn)
pull(
conn,
handshake(state, finish),
conn
)
return encryptedConnection
return {
conn: state.secure,
remotePeer: state.id.remote
}
}
module.exports = {
protocol: '/secio/1.0.0',
// since SECIO is symetric, we only need one function here
secureInbound: secure,
secureOutbound: secure
}

View File

@ -1,28 +1,12 @@
'use strict'
const handshake = require('pull-handshake')
const deferred = require('pull-defer')
class State {
constructor (localId, remoteId, timeout, callback) {
if (typeof timeout === 'function') {
callback = timeout
timeout = undefined
}
constructor (localId, remoteId) {
this.setup()
this.id.local = localId
// TODO use remoteId to verify PeersIdentity
this.id.remote = remoteId
this.key.local = localId.privKey
this.timeout = timeout || 60 * 1000
callback = callback || (() => {})
this.secure = deferred.duplex()
this.stream = handshake({ timeout: this.timeout }, callback)
this.shake = this.stream.handshake
delete this.stream.handshake
}
setup () {

View File

@ -1,12 +1,9 @@
'use strict'
const mh = require('multihashing-async')
const lp = require('pull-length-prefixed')
const pull = require('pull-stream/pull')
const values = require('pull-stream/sources/values')
const collect = require('pull-stream/sinks/collect')
const crypto = require('libp2p-crypto')
const parallel = require('async/parallel')
const { InvalidCryptoExchangeError } = require('libp2p-interfaces/src/crypto/errors')
exports.exchanges = [
'P-256',
@ -39,97 +36,61 @@ exports.theBest = (order, p1, p2) => {
return p1[0]
}
for (let firstCandidate of first) {
for (let secondCandidate of second) {
for (const firstCandidate of first) {
for (const secondCandidate of second) {
if (firstCandidate === secondCandidate) {
return firstCandidate
}
}
}
throw new Error('No algorithms in common!')
throw new InvalidCryptoExchangeError('No algorithms in common!')
}
exports.makeMacAndCipher = (target, callback) => {
parallel([
(cb) => makeMac(target.hashT, target.keys.macKey, cb),
(cb) => makeCipher(target.cipherT, target.keys.iv, target.keys.cipherKey, cb)
], (err, macAndCipher) => {
if (err) {
return callback(err)
}
target.mac = macAndCipher[0]
target.cipher = macAndCipher[1]
callback()
})
exports.makeMacAndCipher = async (target) => {
[target.mac, target.cipher] = await Promise.all([
makeMac(target.hashT, target.keys.macKey),
makeCipher(target.cipherT, target.keys.iv, target.keys.cipherKey)
])
}
function makeMac (hash, key, callback) {
crypto.hmac.create(hash, key, callback)
function makeMac (hash, key) {
return crypto.hmac.create(hash, key)
}
function makeCipher (cipherType, iv, key, callback) {
function makeCipher (cipherType, iv, key) {
if (cipherType === 'AES-128' || cipherType === 'AES-256') {
return crypto.aes.create(key, iv, callback)
return crypto.aes.create(key, iv)
}
// TODO: figure out if Blowfish is needed and if so find a library for it.
callback(new Error(`unrecognized cipher type: ${cipherType}`))
throw new InvalidCryptoExchangeError(`unrecognized cipher type: ${cipherType}`)
}
exports.selectBest = (local, remote, cb) => {
exports.digest(Buffer.concat([
exports.selectBest = async (local, remote) => {
const oh1 = await exports.digest(Buffer.concat([
remote.pubKeyBytes,
local.nonce
]), (err, oh1) => {
if (err) {
return cb(err)
}
]))
const oh2 = await exports.digest(Buffer.concat([
local.pubKeyBytes,
remote.nonce
]))
exports.digest(Buffer.concat([
local.pubKeyBytes,
remote.nonce
]), (err, oh2) => {
if (err) {
return cb(err)
}
const order = Buffer.compare(oh1, oh2)
const order = Buffer.compare(oh1, oh2)
if (order === 0) {
throw new InvalidCryptoExchangeError('you are trying to talk to yourself')
}
if (order === 0) {
return cb(new Error('you are trying to talk to yourself'))
}
cb(null, {
curveT: exports.theBest(order, local.exchanges, remote.exchanges),
cipherT: exports.theBest(order, local.ciphers, remote.ciphers),
hashT: exports.theBest(order, local.hashes, remote.hashes),
order
})
})
})
return {
curveT: exports.theBest(order, local.exchanges, remote.exchanges),
cipherT: exports.theBest(order, local.ciphers, remote.ciphers),
hashT: exports.theBest(order, local.hashes, remote.hashes),
order
}
}
exports.digest = (buf, cb) => {
mh.digest(buf, 'sha2-256', buf.length, cb)
}
exports.write = function write (state, msg, cb) {
cb = cb || (() => {})
pull(
values([msg]),
lp.encode({ fixed: true, bytes: 4 }),
collect((err, res) => {
if (err) {
return cb(err)
}
state.shake.write(res[0])
cb()
})
)
}
exports.read = function read (reader, cb) {
lp.decodeFromReader(reader, { fixed: true, bytes: 4 }, cb)
exports.digest = (buf) => {
return mh.digest(buf, 'sha2-256', buf.length)
}

View File

@ -1,68 +0,0 @@
/* eslint-env mocha */
'use strict'
const chai = require('chai')
const dirtyChai = require('dirty-chai')
const expect = chai.expect
chai.use(dirtyChai)
const multiaddr = require('multiaddr')
const pull = require('pull-stream')
const pullGoodbye = require('pull-goodbye')
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', () => {
const ma = multiaddr('/ip4/127.0.0.1/tcp/9090/ws')
let conn
let encryptedConn
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) => {
expect(err).to.not.exist()
const peerIdNode = res[0]
const peerIdBrowser = res[1]
encryptedConn = secio.encrypt(peerIdBrowser, conn, peerIdNode, (err) => {
expect(err).to.not.exist()
})
done()
})
})
it('echo', (done) => {
const message = 'Hello World!'
const s = pullGoodbye({
source: pull.values([message]),
sink: pull.collect((err, results) => {
expect(err).to.not.exist()
expect(results).to.eql([message])
done()
})
}, 'GoodBye')
pull(
s,
encryptedConn,
// Need to convert to a string as goodbye only understands strings
pull.map((msg) => msg.toString()),
s
)
})
})

14
test/crypto.spec.js Normal file
View File

@ -0,0 +1,14 @@
'use strict'
const tests = require('libp2p-interfaces/src/crypto/tests')
const SECIO = require('..')
tests({
setup () {
// Set up your crypto if needed, then return it
return SECIO
},
teardown () {
// Clean up your crypto if needed
}
})

View File

@ -1,5 +0,0 @@
{
"id": "QmeS1ou3mrjCFGoFtRx3MwrGDzqKD6xbuYJU1CKtMrtFFu",
"privKey": "CAASqAkwggSkAgEAAoIBAQChwzYwCNIyUkzEK3sILqq9ChAKZ9eU+ribY+B/xwAwDKPfvuqHq0hjauJBMcjiQyKAWz9xEBR3WupOM7h9M8oU+/e0xJUTt/CDOrtJ0PCgUXeBLkqsApbBoXW3yomHEDHxYttKzrtoTimiP1bhrxurcpVNC4CUYD+q8gw3sRZlsrqpeYYAfU04kS0BM75W/sUT90znnHvOxFXrEdMMdenEFhZOsDyEK9ENzwhkKgOGb18MBY4kN5DoW4bVd4ItfZnNwdkQtpP/X99tMWJxO4yqpngbywZGnkfirLeuRwt/xRGFVbLOigjBpTVpcbBqe1t2Flhuf/bfWYX4FbyElA5FAgMBAAECggEAJnDTcbrG6LpyD7QdeqZMYLwBb9eZfYfPUu37LaJGwyRd1Q/zf+YOP8HonoGMMWiuzD3i56Vgl7R9NbRIxUgHX9E43jZRDuyJNUZBt5r1c8OoWIR9rj63QLBz3wc8g2Iv3CMX5cEW/ASHFE1lAiCwvJ9wJ2zyU1BEEQWQLbPhlKzw7SLhr4fee45/7pnrKZMllt5vwC9pM6lrpIkICO5gUu0OWu5wfzzlTvfmCgfTb11VqKESEPbDBMUtpJibRqegE4xvipLklJ8VV8jz7NFs9bhgCpNM74Ngt5vGHcddeqtj//86UsClEw5YgWAdRe29ZjMApWvKIkginLjZEO8eiQKBgQDoDWii0rmlgBl1/8fENUSWxYvknGmWO7eWjVqMjDvA+waWUVDpTE+eHT1QAaPofM+nFz5PG+SpB55o4rXdxDesq+DqnaRAI9WtSHdgRtjgETyqoBAiahQ0zGWmSEYHGDB+xGctTMr8GxdhZxqZjjfyptp6oXXqZkmxgcogrx+WTwKBgQCydNDmCDpeH0kSvhAPxaNx5c9WkFEFSA0OCZOx57Y+Mt0MVamRILFrUrcMz095w8BQZkjlHjSHfsRgKa/b2eOd+3BhoMLZVtxRqBdpdqq1KTAcRRG4yA2KA39rttpVzaTV5SPfdDf3tsVlBtV784W63gVpN9gNfajyyrpeffiBKwKBgDnDrLprbl8uZigjhdznza0ie9JqxTXqo6bMhS/bcLx3QIqGr3eD0YXwjWSvI9gpyZ80gAQ9U0xoYxyE4vTTdXB8UL7Wgx6cTQKXuW+z8yTD5bArrBiFA4apItyjvRrjAJ9t0KlMJnNfYxCSE+MJrg+vTU+dhbbVw552SpScQ2atAoGBAKMu3rb6XyUiRpe05MsHVuYX1vi5Dt1dfVKQv1W3JJbLvAZDbsMeuh4BjRFRoMMflQPwBEg+zpn3+WpVtFG9dL5J5gHgF0zWeLDSnFX8BS2TdELlhccKaBcEC8hbdFtxqIFO/vaeN2902hv/m8e0b1zpGNmWDyKG/a7GYpV1a3/xAoGBAJtgGANDVk6qqcWGEVk56FH1ZksvgF3SPXWaXpzbZ5KLCcV5ooRyhowylKUZBBPowMeZ46tem2xwJbraB5kDg6WiSjBsXcbN95ivb8AuoRa6gDqAszjokQUSdpY7FTgMaL046AuihrKsQSly1jrQqbQu8JBgmnnBzus3s77inL/j",
"pubKey": "CAASpgIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQChwzYwCNIyUkzEK3sILqq9ChAKZ9eU+ribY+B/xwAwDKPfvuqHq0hjauJBMcjiQyKAWz9xEBR3WupOM7h9M8oU+/e0xJUTt/CDOrtJ0PCgUXeBLkqsApbBoXW3yomHEDHxYttKzrtoTimiP1bhrxurcpVNC4CUYD+q8gw3sRZlsrqpeYYAfU04kS0BM75W/sUT90znnHvOxFXrEdMMdenEFhZOsDyEK9ENzwhkKgOGb18MBY4kN5DoW4bVd4ItfZnNwdkQtpP/X99tMWJxO4yqpngbywZGnkfirLeuRwt/xRGFVbLOigjBpTVpcbBqe1t2Flhuf/bfWYX4FbyElA5FAgMBAAE="
}

View File

@ -1,5 +0,0 @@
{
"id": "QmYWHGZ9y1Bzx59bBzn85JsJxwmpBy5bpXDWDfwMfsHsxz",
"privKey": "CAASqQkwggSlAgEAAoIBAQDLVaPqWFA8WgK6ixuPvhTHeQfBblmEFLEmraLlIDSWbMUPva6aJ1V/hi2I5QLXNeeiig5sco+nF+RKhGnzQ9NpgHRVZ7Ze+LWq3Q4YxONdzFeNUjTvJrDSKgkubA5EKC/LI6pU33WZbjyKkomGo+Gzuqvlj4Rx1dLVXRIOjxUYcIQw3vpLQgwPpiz52eWCeoCpzn06DcsF6aNPjhlp9uJRZCRxZ4yeiwh/A0xxiQtnB4fdZuUPmia1r62+oaxrDl4hUwR7kzHYl0YGfXxAW9GT17KGtjES2yO4kAUgquelNh0hgBKZRvny9imwsObG7ntw5ZG7H62sP7UySIUJqoNRAgMBAAECggEBAKLVU25BCQg7wQGokwra2wMfPoG+IDuw4mkqFlBNKS/prSo86c2TgFmel2qQk2TLS1OUIZbha38RmAXA4qQohe5wKzmV06tcmwdY/YgCbF5aXSbUVYXLQ0Ea3r1pVUdps1SHnElZpnCXoi4Kyc2kAgSPkkdFVnhfFvc9EE/Ob8NgMkdFhlosE5WVNqm4BKQ+mqONddSz4JDbDOApPs/rRpgYm7pJKc3vkrYwniPjyQGYb5EoSbSWuu31RzIcn3Bhte3wKtfMMlpn8MMpPiYo2WJ2eVG6hlUOxhHgS93Y6czCfAgsDtD3C2JpteewuBjg8N0d6WRArKxny83J34q0qy0CgYEA6YSo5UDEq1TF8sbtSVYg6MKSX92NO5MQI/8fTjU4tEwxn/yxpGsnqUu0WGYIc2qVaZuxtcnk2CQxEilxQTbWSIxKuTt7qofEcpSjLLQ4f4chk4DpPsba+S8zSUdWdjthPHZT9IYzobylGBLfbPxyXXiYn1VuqAJfFy8iV9XqmdcCgYEA3ukROQQZCJcgsNTc5uFAKUeQvzv1iae3fGawgJmIJW3Bl8+4dSm1diqG3ZXP1WU31no2aX50PqOZjoIpbl1ggT76cnBDuu3pItR3dNJFQyMEpQOWOjO+NBWF7sRswCvlqbyjofWkzsdd0BioL7vWMjPftiusyyAFA55HRoeStxcCgYEA0tP7rKdSKKFr6inhl+GT6rGod7bOSSgYXXd7qx9v55AXCauaMqiv8TAxTdIo9RMYfHWd91OlMeNTDmOuJcO9qVhIKn5iw266VPyPac/4ZmL5VHQBobTlhC4yLomirTIlMvJeEBmNygtIPrjjUUGGe49itA/szPD/Ky5Z4lV27pcCgYAWU3mqIELxnVFk5K0LYtwuRkC1Jqg9FVNHXnGnL7l3JjsRnXh4I6lNII1JfEvIr86b6LmybzvtWi1zHI5Rw4B68XfcJmpiOpnzJxyf0r+lLci1Tlqpka0nQlCbzYim5r6l9YLeIeBT5Zv7z7xoq4OUm6V4dX9lCNv3tM6mvcVwGQKBgQC9hhjD64/VKXL8wYKZyTAOVO5xYCcqylrpI39qdzl+sS8oqmLUbXnKsGY4If9U61XdULld41BJCRlv6CsKreynm6ZN41j9YRuWWLu8STJcniV9Ef9uVl1M1zo8kfnCHMCym9LkTfJY+Ow/kYhqPukJJL6ve1CVmIuA4rnZlshjbg==",
"pubKey": "CAASpgIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDLVaPqWFA8WgK6ixuPvhTHeQfBblmEFLEmraLlIDSWbMUPva6aJ1V/hi2I5QLXNeeiig5sco+nF+RKhGnzQ9NpgHRVZ7Ze+LWq3Q4YxONdzFeNUjTvJrDSKgkubA5EKC/LI6pU33WZbjyKkomGo+Gzuqvlj4Rx1dLVXRIOjxUYcIQw3vpLQgwPpiz52eWCeoCpzn06DcsF6aNPjhlp9uJRZCRxZ4yeiwh/A0xxiQtnB4fdZuUPmia1r62+oaxrDl4hUwR7kzHYl0YGfXxAW9GT17KGtjES2yO4kAUgquelNh0hgBKZRvny9imwsObG7ntw5ZG7H62sP7UySIUJqoNRAgMBAAE="
}

View File

@ -1,5 +0,0 @@
{
"id": "QmWDLy5Q31n9TbPsoE49NjagV8uJbNbixQ4JbyB2WcnNmv",
"privKey": "CAASpwkwggSjAgEAAoIBAQCi3wtY+s70DLgeMKk7BhQq0Xmpkl1Z9YHaP+8ERM2slqPYSAnG1zaOP4iy+O0AAji+7/pIRGVYyY2oBmCM7Uo2jZZZHEAcJ0Ih4vIT7jwnqdOUrEkkiHxgVCq2+yxgY85LpiIDzJIme3Atc90roPfJi5wpgxM8Yvxwh/Jpr0it4VrNxWSQmGgxfrK9La5ezfFSs/MrU+aA8Qh+vMkRQHsBVyr30pzK1bD2l3kkg5hC48wClduoYHB1IGAOgx14Hsu+4qX/tpu7c3HXL9mPvjHsddqeKiHIs1u2kkXKw+myDyIJr/FVPHQxxpuOnA+GmPVQI5d+INLVzCV88CfzqKwpAgMBAAECggEATYj/HvHvWbFAaWbi+W1QZn3ofDhoZm4AzkSHZbHXc+UWxNyugtFrcFaAnirwsINePk+CB6s/z//LhwTaK9y+6q+Gto9DWeO6kOU4NxK/4mXviqRmAZVUGIuY9hkmrBB3Yf4JzWMy5Ez5PzocPSvZKkJjKkPzAVliMbQWTAedAuE1eq9b5Nb5MQJq/1a9RYFdFm0kYy7FgwQbMsoXc3Uv5s+TJGwXzhsUFH6MpNZGniUsS6wiwWPLjdGKebUrf/pD8Z7nwscSQBKzkyrHVBGfImOtNcgM+BwesN+WUFqyAJRvPv0SNVu+KwkVx3jEqAaIf5vxO62TNok+TuBxQP8HrQKBgQDai5dWEy08EZOyuM0MlU3fXhpUFczSrtxw1Ty4a4ma+jQitcaCnUdgA1keQhTpLYoSqE0NL8uykTC3FcRF2AQf0jTGZ94smbu5cJkWWh5NoIW2EnkSQwfPMjHGKjwKhltIbiAxlnbxmpLd3CTTx7eVXB5a5WgVih1fYOThoXzKTwKBgQC+yNLQ7FkxsCW3wRQ6ANk/6mQEYpuX6ap2TtxykvwCwuWbadufeRAZb52gbiNUgClqT7h0Kper6mBqhs7ubU55DVJMUTLKqWLQVkHdkQIoau5uXaYoX/1/bfc65BgHg61aUF93hJAYzv0NuEEX2UaLAXcpYfXe2Liv8AYvr9mcBwKBgDPDqpXdtvIqa6SCdzcKykYlAPF3tGsWteoX5+j6t744k07BZYKchEJuqJYtKszMV47xxEZiUso576L+Cd5NOzTaUlvIUGyaAUf8LpaHw/O5GNK2b2zu6ZOfHQEGEfCgQFDYnNGCBSxW44CfWy26eXZsOlhnTA8GBs0Ho076NBerAoGAV/5iFZBdFyjKTLVV8ebATNV7qfYdE3TndUesL4ARkeh4ZDTv4d7BiSnMxtjlnKy77Ve1mIaoi9c+/wMMYDW0EusNATwWNBjqBXMzT9D48NFZBThWUZrsXaDHfbcESjr2cohNb9+JYpfdaT2JcEl7WtOjNUgEUfMdQ7Bt+gKeWHMCgYEApoS8oknM4AtveP/w3YYSLSILbNMx2sYDNJ6HGDdg9ajjpTez4PS+BgqhcMkzMd/oVB7VD2n8As35LxppfHtSEPW1II/h3Phtd//HT8Wsu2L0BRoY/JIMLCbTwTSbtVr2+w9vWz7pg2Q2r0uScYadYNpMNF2uXC6vd6TFyCc17ic=",
"pubKey": "CAASpgIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCi3wtY+s70DLgeMKk7BhQq0Xmpkl1Z9YHaP+8ERM2slqPYSAnG1zaOP4iy+O0AAji+7/pIRGVYyY2oBmCM7Uo2jZZZHEAcJ0Ih4vIT7jwnqdOUrEkkiHxgVCq2+yxgY85LpiIDzJIme3Atc90roPfJi5wpgxM8Yvxwh/Jpr0it4VrNxWSQmGgxfrK9La5ezfFSs/MrU+aA8Qh+vMkRQHsBVyr30pzK1bD2l3kkg5hC48wClduoYHB1IGAOgx14Hsu+4qX/tpu7c3HXL9mPvjHsddqeKiHIs1u2kkXKw+myDyIJr/FVPHQxxpuOnA+GmPVQI5d+INLVzCV88CfzqKwpAgMBAAE="
}

View File

@ -1,5 +0,0 @@
{
"id": "QmYsRYcrQAftGS5U6XRsHdxiVDdGowAfCCYZLgxgL5JGpu",
"privKey": "CAASqAkwggSkAgEAAoIBAQCoYkBQ0acKleSMT58n3IgxuCAoY4iGC8WMR5L5ZOeDGF1L8j31OwOXVP2AzxUMtBbhuqbaH6n0zXUDdCvXy+LSAE6IT+iDBMN9MhWv9XY8NBLmNJjoL6a5fWBbTmz3oFjnTLvSrmjFa7GuTFF9vFdYAf7ArFTAbwJxgofnfg+A2Vc572sh6ZFedeYWHC6lTe8f+RRAtlRnQsbvDuTRwj9s9EWT5WaKp+jGEoGHR2X8tekfHC6eDMju+Hb8325HZox1K1Tn7IAIZ0V0PfbqjdeyDtPaTWvBdTyhCrIwYIokMG3o8fn2PtyzBisStObXDUDH9ZBrypQ5wB5qJUsTZOEjAgMBAAECggEAJQ/jKibLRpgxgvSvil2PGKp3YYxu61pB9Bt47S3Zi1BckK/UFVsU8kVBuw1Zi79D3hFwUkhv5zMl00nJsKju1tw8bOJxwgzVXoQco+s6SAwKMONY2JpMbz16bP55G5QOnXKVUM0UwIdwx3WC5lMDQprF0PDdgrGsbwGVGk3z6Z8RV0CUIKsegv2njZs5y0ykSJsd1ZP4b0Kp2MButB2588K+7auR07WLTqlmZGPXBOzjgugkMrIjoEy6vhiRNqyD+V0xLxbRfHUX1JU7DTA40kSjomfB8Mv1kcGKT4rmqUhV5HARpPxW5EBitLV1nreYgl3z4gBHUzG7UERZLEmzOQKBgQDgidrTCBi6SHCjF0u8U6lFH7GlC8zc8mPJwYbJTL08vkHtM8YlTj4MhKdZJuxPNvoDD663mAwbKdUKaB33o8f3+qYVEXl3N/xfTSvcGT/fuDDkALYCXiqqXOugQ9E7CjrJ+ZlNRCCrHBuehazgqJVk2ePgroNEprt37ioejan2NQKBgQC/+iPVAQlg+l4qEvF3cD++lUsQkUnVGmm7nPpIjTqR0h/90rSWNBnyC4/N42m9naUswAQd+NieQF3z8LcKXFEjc0bb6tKlFsqxKnogT8zS5Niq9Jhqx7AKwVwZxEnF00qJ7AK5fYiyCBVtLC7m4/ZtK/rlGvZBMNvSw1R4BkGE9wKBgQCv0aIkPqHGM/Tm79Xg6I2Rz8h1WTEYDUCv+bbGJy3Cw6OxzimoioSxnqm/eGfvezf3b686mXdZSb9Ev9Q/VeK+PWkQ9DbtcExQf4gJd0f5sSuvTRM39W5pKgti2dea6F6ySpyM0PzXn1HNJhUBcWBRsP/rrhqhhgEjXr1iYUElxQKBgDvgsDZqqVs3HBGxcOEZALjX5d7YAhhWsa3Ty3i/nAoJaPMJOkwzGfiVxJeqG7wJpJAtn9sdV6scVnGUnfK8Uo5lES/csTDOyA+0OP1FBpSsziucOMHtYKoqHyjmQUBTo8ca9sqWLgGhZdV0V3iql5z7f7jfzPkOXTICvKDnCp6HAoGBAJ+xBhd/dFvH12MmPsW1m2bPDXY3Arc3xW/qUAAh7zivK5dFVHYIcVjRSiG0agyWuzz1ig1evwk9qhs+mvGmTPj4Z3mkh+lCdH1SMzi3IygX6kYnvZZcGd892vQFjNVzAxUUb8MRm5aoznzRvwzpSgdKT5lO5dXrPIsH8eK3RIY9",
"pubKey": "CAASpgIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCoYkBQ0acKleSMT58n3IgxuCAoY4iGC8WMR5L5ZOeDGF1L8j31OwOXVP2AzxUMtBbhuqbaH6n0zXUDdCvXy+LSAE6IT+iDBMN9MhWv9XY8NBLmNJjoL6a5fWBbTmz3oFjnTLvSrmjFa7GuTFF9vFdYAf7ArFTAbwJxgofnfg+A2Vc572sh6ZFedeYWHC6lTe8f+RRAtlRnQsbvDuTRwj9s9EWT5WaKp+jGEoGHR2X8tekfHC6eDMju+Hb8325HZox1K1Tn7IAIZ0V0PfbqjdeyDtPaTWvBdTyhCrIwYIokMG3o8fn2PtyzBisStObXDUDH9ZBrypQ5wB5qJUsTZOEjAgMBAAE="
}

View File

@ -1,5 +0,0 @@
{
"id": "QmY2cwDxD3B2n1Tv9YiBKZXgKkKCkcS5r2sCJMu9oBcGBq",
"privKey": "CAASqAkwggSkAgEAAoIBAQCfBFkPbclTBzE4W4hhuteYrK4qR3eQnfbwfR1LUPdNmZKZxnTuQmL86ZCQCBzolqbJVFWb+phs2y3+0jCt/pdwSAjWIO8A9PJBcavJzYhi9JLLg8wl96xIHqHHQ8JsD8np1RLOYFQ1w2jNrXkDO0RQXb62+7xSoSyG4Y8UrhT/n65fv3aGlhqcYh1Tz4DV1bWdXzEOfg3R+KGmMcbJT4eC0x04gvxZXlTOmI2YMW+DunK/RN8fmt9YJqqu/f2bed+cuGsmwqfX/grDjVRjSxK2QECGYXjxU7WIZMrqBvlkBc7sWFyp3KHFU8bZYvP6a6K/dgoTbQAcz/UJswGpE9j3AgMBAAECggEAFZNFci53Lmi/aOIicwAi2Hg1eU8RDfIg2yhenSVzKHg5x9uBagJf4+jc0G4JXhVys8ZZhzxNChgC+ZwTNshgS7+6UzNMuliBthdyM4NLigGdGTfcrxoXqgFd+edbrcXGo49hadbwFgtZYO60iJe2ASF3CuTE/IEZxYM9IpUsDDKqZ8/fToLQk80XQuupiqwx4X6Yon/XTSLnNBSMA/hnhuwjJjw90DQQs+OER4ixbS7ENCFrMvV2omtwHh+bo2bfFhdyzSrV2wiRIyQiP6cOT5k7AsHkz2xzU7novNAW3D0bXDYhlBpTWEvn3yoKKkYuYptP9lyA2hcGedoVCR1wYQKBgQD1zWUdgnf3BfpYB0E3gItAEAgy1dvGny39XGvRtFZCWfykNKCyjOe48lXntNAjQq22Leo4U4LQWb/PWkYlO3mm2Zt1Posyx6lBbFS6h+5dCFDPbC7zvf1QD5kyAtS93Xds8gX4YWQVd0jj3ov7f3FghlTjnc6udTPD1oWiaekeqwKBgQClnTo+WHf9ZPQtZzBNht/rqEYA1p6KiedsAR1cXYMhernafLQ5FnT4czp3gZTcnEAsTfBg4mp3j+dqxmN/YpPI6hRMQlbsi7Zv3ro7FivTUHBYdWC2OHu+fDbpqras8s6nRBilKzkcaMf5WPw+fQpcr+zor4fFM01oGxfBClo+5QKBgQAI6kc1l8rUGdJnqPOzmKT0UOCLP3h2LsXTP6vlcj4CsBLavdHqR/QLoDZ/be5yqPN1/RpWqqi+99JeKe8LYKnb5F6gFQGleNpptg0oqs95bljH/SuCyaxLYBV1W+btb//p4qlWxemEYcwx/5tiJtAs6RJhIxMg/r0+6CP2rRK4ewKBgQCWNPQOd87cVCPiyiRVLG8LHaPgPsesf0cV/izTCT1VsCnAsDoFTQjqDhiJK04IiO7rQAU02iYWKr6JaUX000OWhjfCsqiEAnOFI01lKca18c7zbAI7Qx94tNBZPixQ0Cf+LRTtOTajPaWh0cN2KZKsXiNRJ2LMyKr8MRZqTylqwQKBgCb6022/mnnJQQue3NpgOnm9hSnnmm+zNU74DhQbzf5Uod+hzlpQsQnH7q935KAZj7VQGxkb0w4ctwyggoQnljCtuT+8kNLmZd5bFmx9JfY5uqzhvOueC/AKJdQ0S61NVs5YqEUrmLPRvSG5TXH3swWcHt7ofOgTsdZTKPqduj5x",
"pubKey": "CAASpgIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCfBFkPbclTBzE4W4hhuteYrK4qR3eQnfbwfR1LUPdNmZKZxnTuQmL86ZCQCBzolqbJVFWb+phs2y3+0jCt/pdwSAjWIO8A9PJBcavJzYhi9JLLg8wl96xIHqHHQ8JsD8np1RLOYFQ1w2jNrXkDO0RQXb62+7xSoSyG4Y8UrhT/n65fv3aGlhqcYh1Tz4DV1bWdXzEOfg3R+KGmMcbJT4eC0x04gvxZXlTOmI2YMW+DunK/RN8fmt9YJqqu/f2bed+cuGsmwqfX/grDjVRjSxK2QECGYXjxU7WIZMrqBvlkBc7sWFyp3KHFU8bZYvP6a6K/dgoTbQAcz/UJswGpE9j3AgMBAAE="
}

23
test/fixtures/peer.js vendored Normal file
View File

@ -0,0 +1,23 @@
'use strict'
const PeerId = require('peer-id')
const peers = [{
id: 'QmeS1ou3mrjCFGoFtRx3MwrGDzqKD6xbuYJU1CKtMrtFFu',
privKey: 'CAASqAkwggSkAgEAAoIBAQChwzYwCNIyUkzEK3sILqq9ChAKZ9eU+ribY+B/xwAwDKPfvuqHq0hjauJBMcjiQyKAWz9xEBR3WupOM7h9M8oU+/e0xJUTt/CDOrtJ0PCgUXeBLkqsApbBoXW3yomHEDHxYttKzrtoTimiP1bhrxurcpVNC4CUYD+q8gw3sRZlsrqpeYYAfU04kS0BM75W/sUT90znnHvOxFXrEdMMdenEFhZOsDyEK9ENzwhkKgOGb18MBY4kN5DoW4bVd4ItfZnNwdkQtpP/X99tMWJxO4yqpngbywZGnkfirLeuRwt/xRGFVbLOigjBpTVpcbBqe1t2Flhuf/bfWYX4FbyElA5FAgMBAAECggEAJnDTcbrG6LpyD7QdeqZMYLwBb9eZfYfPUu37LaJGwyRd1Q/zf+YOP8HonoGMMWiuzD3i56Vgl7R9NbRIxUgHX9E43jZRDuyJNUZBt5r1c8OoWIR9rj63QLBz3wc8g2Iv3CMX5cEW/ASHFE1lAiCwvJ9wJ2zyU1BEEQWQLbPhlKzw7SLhr4fee45/7pnrKZMllt5vwC9pM6lrpIkICO5gUu0OWu5wfzzlTvfmCgfTb11VqKESEPbDBMUtpJibRqegE4xvipLklJ8VV8jz7NFs9bhgCpNM74Ngt5vGHcddeqtj//86UsClEw5YgWAdRe29ZjMApWvKIkginLjZEO8eiQKBgQDoDWii0rmlgBl1/8fENUSWxYvknGmWO7eWjVqMjDvA+waWUVDpTE+eHT1QAaPofM+nFz5PG+SpB55o4rXdxDesq+DqnaRAI9WtSHdgRtjgETyqoBAiahQ0zGWmSEYHGDB+xGctTMr8GxdhZxqZjjfyptp6oXXqZkmxgcogrx+WTwKBgQCydNDmCDpeH0kSvhAPxaNx5c9WkFEFSA0OCZOx57Y+Mt0MVamRILFrUrcMz095w8BQZkjlHjSHfsRgKa/b2eOd+3BhoMLZVtxRqBdpdqq1KTAcRRG4yA2KA39rttpVzaTV5SPfdDf3tsVlBtV784W63gVpN9gNfajyyrpeffiBKwKBgDnDrLprbl8uZigjhdznza0ie9JqxTXqo6bMhS/bcLx3QIqGr3eD0YXwjWSvI9gpyZ80gAQ9U0xoYxyE4vTTdXB8UL7Wgx6cTQKXuW+z8yTD5bArrBiFA4apItyjvRrjAJ9t0KlMJnNfYxCSE+MJrg+vTU+dhbbVw552SpScQ2atAoGBAKMu3rb6XyUiRpe05MsHVuYX1vi5Dt1dfVKQv1W3JJbLvAZDbsMeuh4BjRFRoMMflQPwBEg+zpn3+WpVtFG9dL5J5gHgF0zWeLDSnFX8BS2TdELlhccKaBcEC8hbdFtxqIFO/vaeN2902hv/m8e0b1zpGNmWDyKG/a7GYpV1a3/xAoGBAJtgGANDVk6qqcWGEVk56FH1ZksvgF3SPXWaXpzbZ5KLCcV5ooRyhowylKUZBBPowMeZ46tem2xwJbraB5kDg6WiSjBsXcbN95ivb8AuoRa6gDqAszjokQUSdpY7FTgMaL046AuihrKsQSly1jrQqbQu8JBgmnnBzus3s77inL/j',
pubKey: 'CAASpgIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQChwzYwCNIyUkzEK3sILqq9ChAKZ9eU+ribY+B/xwAwDKPfvuqHq0hjauJBMcjiQyKAWz9xEBR3WupOM7h9M8oU+/e0xJUTt/CDOrtJ0PCgUXeBLkqsApbBoXW3yomHEDHxYttKzrtoTimiP1bhrxurcpVNC4CUYD+q8gw3sRZlsrqpeYYAfU04kS0BM75W/sUT90znnHvOxFXrEdMMdenEFhZOsDyEK9ENzwhkKgOGb18MBY4kN5DoW4bVd4ItfZnNwdkQtpP/X99tMWJxO4yqpngbywZGnkfirLeuRwt/xRGFVbLOigjBpTVpcbBqe1t2Flhuf/bfWYX4FbyElA5FAgMBAAE='
}, {
id: 'QmYWHGZ9y1Bzx59bBzn85JsJxwmpBy5bpXDWDfwMfsHsxz',
privKey: 'CAASqQkwggSlAgEAAoIBAQDLVaPqWFA8WgK6ixuPvhTHeQfBblmEFLEmraLlIDSWbMUPva6aJ1V/hi2I5QLXNeeiig5sco+nF+RKhGnzQ9NpgHRVZ7Ze+LWq3Q4YxONdzFeNUjTvJrDSKgkubA5EKC/LI6pU33WZbjyKkomGo+Gzuqvlj4Rx1dLVXRIOjxUYcIQw3vpLQgwPpiz52eWCeoCpzn06DcsF6aNPjhlp9uJRZCRxZ4yeiwh/A0xxiQtnB4fdZuUPmia1r62+oaxrDl4hUwR7kzHYl0YGfXxAW9GT17KGtjES2yO4kAUgquelNh0hgBKZRvny9imwsObG7ntw5ZG7H62sP7UySIUJqoNRAgMBAAECggEBAKLVU25BCQg7wQGokwra2wMfPoG+IDuw4mkqFlBNKS/prSo86c2TgFmel2qQk2TLS1OUIZbha38RmAXA4qQohe5wKzmV06tcmwdY/YgCbF5aXSbUVYXLQ0Ea3r1pVUdps1SHnElZpnCXoi4Kyc2kAgSPkkdFVnhfFvc9EE/Ob8NgMkdFhlosE5WVNqm4BKQ+mqONddSz4JDbDOApPs/rRpgYm7pJKc3vkrYwniPjyQGYb5EoSbSWuu31RzIcn3Bhte3wKtfMMlpn8MMpPiYo2WJ2eVG6hlUOxhHgS93Y6czCfAgsDtD3C2JpteewuBjg8N0d6WRArKxny83J34q0qy0CgYEA6YSo5UDEq1TF8sbtSVYg6MKSX92NO5MQI/8fTjU4tEwxn/yxpGsnqUu0WGYIc2qVaZuxtcnk2CQxEilxQTbWSIxKuTt7qofEcpSjLLQ4f4chk4DpPsba+S8zSUdWdjthPHZT9IYzobylGBLfbPxyXXiYn1VuqAJfFy8iV9XqmdcCgYEA3ukROQQZCJcgsNTc5uFAKUeQvzv1iae3fGawgJmIJW3Bl8+4dSm1diqG3ZXP1WU31no2aX50PqOZjoIpbl1ggT76cnBDuu3pItR3dNJFQyMEpQOWOjO+NBWF7sRswCvlqbyjofWkzsdd0BioL7vWMjPftiusyyAFA55HRoeStxcCgYEA0tP7rKdSKKFr6inhl+GT6rGod7bOSSgYXXd7qx9v55AXCauaMqiv8TAxTdIo9RMYfHWd91OlMeNTDmOuJcO9qVhIKn5iw266VPyPac/4ZmL5VHQBobTlhC4yLomirTIlMvJeEBmNygtIPrjjUUGGe49itA/szPD/Ky5Z4lV27pcCgYAWU3mqIELxnVFk5K0LYtwuRkC1Jqg9FVNHXnGnL7l3JjsRnXh4I6lNII1JfEvIr86b6LmybzvtWi1zHI5Rw4B68XfcJmpiOpnzJxyf0r+lLci1Tlqpka0nQlCbzYim5r6l9YLeIeBT5Zv7z7xoq4OUm6V4dX9lCNv3tM6mvcVwGQKBgQC9hhjD64/VKXL8wYKZyTAOVO5xYCcqylrpI39qdzl+sS8oqmLUbXnKsGY4If9U61XdULld41BJCRlv6CsKreynm6ZN41j9YRuWWLu8STJcniV9Ef9uVl1M1zo8kfnCHMCym9LkTfJY+Ow/kYhqPukJJL6ve1CVmIuA4rnZlshjbg==',
pubKey: 'CAASpgIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDLVaPqWFA8WgK6ixuPvhTHeQfBblmEFLEmraLlIDSWbMUPva6aJ1V/hi2I5QLXNeeiig5sco+nF+RKhGnzQ9NpgHRVZ7Ze+LWq3Q4YxONdzFeNUjTvJrDSKgkubA5EKC/LI6pU33WZbjyKkomGo+Gzuqvlj4Rx1dLVXRIOjxUYcIQw3vpLQgwPpiz52eWCeoCpzn06DcsF6aNPjhlp9uJRZCRxZ4yeiwh/A0xxiQtnB4fdZuUPmia1r62+oaxrDl4hUwR7kzHYl0YGfXxAW9GT17KGtjES2yO4kAUgquelNh0hgBKZRvny9imwsObG7ntw5ZG7H62sP7UySIUJqoNRAgMBAAE='
}, {
id: 'QmYsRYcrQAftGS5U6XRsHdxiVDdGowAfCCYZLgxgL5JGpu',
privKey: 'CAASqAkwggSkAgEAAoIBAQCoYkBQ0acKleSMT58n3IgxuCAoY4iGC8WMR5L5ZOeDGF1L8j31OwOXVP2AzxUMtBbhuqbaH6n0zXUDdCvXy+LSAE6IT+iDBMN9MhWv9XY8NBLmNJjoL6a5fWBbTmz3oFjnTLvSrmjFa7GuTFF9vFdYAf7ArFTAbwJxgofnfg+A2Vc572sh6ZFedeYWHC6lTe8f+RRAtlRnQsbvDuTRwj9s9EWT5WaKp+jGEoGHR2X8tekfHC6eDMju+Hb8325HZox1K1Tn7IAIZ0V0PfbqjdeyDtPaTWvBdTyhCrIwYIokMG3o8fn2PtyzBisStObXDUDH9ZBrypQ5wB5qJUsTZOEjAgMBAAECggEAJQ/jKibLRpgxgvSvil2PGKp3YYxu61pB9Bt47S3Zi1BckK/UFVsU8kVBuw1Zi79D3hFwUkhv5zMl00nJsKju1tw8bOJxwgzVXoQco+s6SAwKMONY2JpMbz16bP55G5QOnXKVUM0UwIdwx3WC5lMDQprF0PDdgrGsbwGVGk3z6Z8RV0CUIKsegv2njZs5y0ykSJsd1ZP4b0Kp2MButB2588K+7auR07WLTqlmZGPXBOzjgugkMrIjoEy6vhiRNqyD+V0xLxbRfHUX1JU7DTA40kSjomfB8Mv1kcGKT4rmqUhV5HARpPxW5EBitLV1nreYgl3z4gBHUzG7UERZLEmzOQKBgQDgidrTCBi6SHCjF0u8U6lFH7GlC8zc8mPJwYbJTL08vkHtM8YlTj4MhKdZJuxPNvoDD663mAwbKdUKaB33o8f3+qYVEXl3N/xfTSvcGT/fuDDkALYCXiqqXOugQ9E7CjrJ+ZlNRCCrHBuehazgqJVk2ePgroNEprt37ioejan2NQKBgQC/+iPVAQlg+l4qEvF3cD++lUsQkUnVGmm7nPpIjTqR0h/90rSWNBnyC4/N42m9naUswAQd+NieQF3z8LcKXFEjc0bb6tKlFsqxKnogT8zS5Niq9Jhqx7AKwVwZxEnF00qJ7AK5fYiyCBVtLC7m4/ZtK/rlGvZBMNvSw1R4BkGE9wKBgQCv0aIkPqHGM/Tm79Xg6I2Rz8h1WTEYDUCv+bbGJy3Cw6OxzimoioSxnqm/eGfvezf3b686mXdZSb9Ev9Q/VeK+PWkQ9DbtcExQf4gJd0f5sSuvTRM39W5pKgti2dea6F6ySpyM0PzXn1HNJhUBcWBRsP/rrhqhhgEjXr1iYUElxQKBgDvgsDZqqVs3HBGxcOEZALjX5d7YAhhWsa3Ty3i/nAoJaPMJOkwzGfiVxJeqG7wJpJAtn9sdV6scVnGUnfK8Uo5lES/csTDOyA+0OP1FBpSsziucOMHtYKoqHyjmQUBTo8ca9sqWLgGhZdV0V3iql5z7f7jfzPkOXTICvKDnCp6HAoGBAJ+xBhd/dFvH12MmPsW1m2bPDXY3Arc3xW/qUAAh7zivK5dFVHYIcVjRSiG0agyWuzz1ig1evwk9qhs+mvGmTPj4Z3mkh+lCdH1SMzi3IygX6kYnvZZcGd892vQFjNVzAxUUb8MRm5aoznzRvwzpSgdKT5lO5dXrPIsH8eK3RIY9',
pubKey: 'CAASpgIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCoYkBQ0acKleSMT58n3IgxuCAoY4iGC8WMR5L5ZOeDGF1L8j31OwOXVP2AzxUMtBbhuqbaH6n0zXUDdCvXy+LSAE6IT+iDBMN9MhWv9XY8NBLmNJjoL6a5fWBbTmz3oFjnTLvSrmjFa7GuTFF9vFdYAf7ArFTAbwJxgofnfg+A2Vc572sh6ZFedeYWHC6lTe8f+RRAtlRnQsbvDuTRwj9s9EWT5WaKp+jGEoGHR2X8tekfHC6eDMju+Hb8325HZox1K1Tn7IAIZ0V0PfbqjdeyDtPaTWvBdTyhCrIwYIokMG3o8fn2PtyzBisStObXDUDH9ZBrypQ5wB5qJUsTZOEjAgMBAAE='
}]
module.exports.createPeerIdsFromFixtures = (length) => {
return Promise.all(
Array.from({ length }).map((_, i) => PeerId.createFromJSON(peers[i]))
)
}

View File

@ -1,175 +1,180 @@
/* eslint max-nested-callbacks: ["error", 8] */
/* eslint-env mocha */
'use strict'
const pair = require('pull-pair/duplex')
const chai = require('chai')
const dirtyChai = require('dirty-chai')
const expect = chai.expect
chai.use(dirtyChai)
const PeerId = require('peer-id')
const Connection = require('interface-connection').Connection
const parallel = require('async/parallel')
const series = require('async/series')
const Buffer = require('safe-buffer').Buffer
const ms = require('multistream-select')
const pull = require('pull-stream')
const Listener = ms.Listener
const Dialer = ms.Dialer
const secio = require('../src')
const duplexPair = require('it-pair/duplex')
const Handshake = require('it-pb-rpc')
const Secio = require('../src')
const { createPeerIdsFromFixtures } = require('./fixtures/peer')
const {
createExchange,
createProposal,
generateKeys,
identify,
selectProtocols,
verify
} = require('../src/handshake/crypto')
const { createBoxStream, createUnboxStream } = require('../src/etm')
const State = require('../src/state')
const handshake = require('../src/handshake')
const { Propose } = require('../src/handshake/secio.proto')
describe('secio', () => {
let peerA
let peerB
let peerC
let remotePeer
let localPeer
before((done) => {
parallel([
(cb) => PeerId.createFromJSON(require('./fixtures/peer-a'), cb),
(cb) => PeerId.createFromJSON(require('./fixtures/peer-b'), cb),
(cb) => PeerId.createFromJSON(require('./fixtures/peer-c'), cb)
], (err, peers) => {
expect(err).to.not.exist()
peerA = peers[0]
peerB = peers[1]
peerC = peers[2]
done()
})
before(async () => {
[remotePeer, localPeer] = await createPeerIdsFromFixtures(2)
})
it('exports a secio multicodec', () => {
expect(secio.tag).to.equal('/secio/1.0.0')
})
it('performs a spec compliant inbound exchange', async () => {
const [inboundConnection, outboundConnection] = duplexPair()
await Promise.all([
Secio.secureInbound(remotePeer, inboundConnection, null),
(async () => {
const wrap = Handshake(outboundConnection)
const state = new State(localPeer, remotePeer)
it('upgrades a connection', (done) => {
const p = pair()
// Create our proposal
const proposal = createProposal(state)
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())
// Send our proposal
const proposalLength = Buffer.allocUnsafe(4)
proposalLength.writeInt32BE(proposal.length, 0)
wrap.write(Buffer.concat([proposalLength, proposal]))
pull(
pull.values([Buffer.from('hello world')]),
aToB
)
// Read their proposal
let theirProposalRaw = (await wrap.read()).slice()
let dataLength = theirProposalRaw.readInt32BE(0)
theirProposalRaw = theirProposalRaw.slice(4, dataLength + 4)
const theirProposal = Propose.decode(theirProposalRaw)
expect(theirProposal.rand).to.have.length(16)
expect(theirProposal.pubkey).to.eql(remotePeer.pubKey.bytes)
expect(theirProposal.exchanges).to.equal('P-256,P-384,P-521')
expect(theirProposal.ciphers).to.equal('AES-256,AES-128')
expect(theirProposal.hashes).to.equal('SHA256,SHA512')
pull(
bToA,
pull.collect((err, chunks) => {
expect(err).to.not.exist()
expect(chunks).to.eql([Buffer.from('hello world')])
done()
})
)
})
// Select protocols
identify(state, theirProposalRaw)
await selectProtocols(state)
expect(state.protocols.local).to.include({ curveT: 'P-256', cipherT: 'AES-256', hashT: 'SHA256' })
expect(state.protocols.remote).to.include({ curveT: 'P-256', cipherT: 'AES-256', hashT: 'SHA256' })
it('works over multistream-select', (done) => {
const p = pair()
// Create our exchange
const exchange = await createExchange(state)
const listener = new Listener()
const dialer = new Dialer()
// Send our exchange
const exchangeLength = Buffer.allocUnsafe(4)
exchangeLength.writeInt32BE(exchange.length, 0)
wrap.write(Buffer.concat([exchangeLength, exchange]))
series([
(cb) => parallel([
(cb) => listener.handle(p[0], cb),
(cb) => dialer.handle(p[1], cb)
], cb),
(cb) => {
listener.addHandler('/banana/1.0.0', (protocol, conn) => {
const bToA = secio.encrypt(peerB, conn, peerA, (err) => expect(err).to.not.exist())
// Read their exchange
let theirExchangeRaw = (await wrap.read()).slice()
dataLength = theirExchangeRaw.readInt32BE(0)
theirExchangeRaw = theirExchangeRaw.slice(4, dataLength + 4)
await verify(state, theirExchangeRaw)
pull(
bToA,
pull.collect((err, chunks) => {
expect(err).to.not.exist()
expect(chunks).to.eql([Buffer.from('hello world')])
done()
})
)
})
// Generate the crypto keys
await generateKeys(state)
cb()
},
(cb) => dialer.select('/banana/1.0.0', (err, conn) => {
expect(err).to.not.exist()
// Create the crypto stream
const box = createBoxStream(state.protocols.local.cipher, state.protocols.local.mac)
const unbox = createUnboxStream(state.protocols.remote.cipher, state.protocols.remote.mac)
const aToB = secio.encrypt(peerA, conn, peerB, (err) => expect(err).to.not.exist())
// Send back their nonce over the crypto stream
const { value: nonce } = await box([state.proposal.in.rand]).next()
expect(nonce.slice()).to.not.eql(state.proposal.in.rand) // The nonce should be encrypted
const nonceLength = Buffer.allocUnsafe(4)
nonceLength.writeInt32BE(nonce.length, 0)
wrap.write(Buffer.concat([nonceLength, nonce.slice()]))
pull(
pull.values([Buffer.from('hello world')]),
aToB
)
cb()
})
// Read our nonce from the crypto stream
let ourNonceRaw = (await wrap.read())
dataLength = ourNonceRaw.readInt32BE(0)
ourNonceRaw = ourNonceRaw.shallowSlice(4, dataLength + 4) // Unbox expects a BufferList, so shallow slice here
expect(ourNonceRaw.slice()).to.not.eql(state.proposal.out.rand) // The nonce should be encrypted
const { value: ourNonce } = await unbox([ourNonceRaw]).next()
// Verify our nonce is correct
expect(ourNonce.slice()).to.eql(state.proposal.out.rand)
})()
])
})
it('establishes the connection even if the receiver does not know who is dialing', (done) => {
const p = pair()
it('performs a spec compliant outbound exchange', async () => {
const [inboundConnection, outboundConnection] = duplexPair()
await Promise.all([
Secio.secureOutbound(localPeer, outboundConnection, remotePeer),
(async () => {
const wrap = Handshake(inboundConnection)
const state = new State(remotePeer, localPeer)
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())
// Create our proposal
const proposal = createProposal(state)
pull(
pull.values([Buffer.from('hello world')]),
aToB
)
// Send our proposal
const proposalLength = Buffer.allocUnsafe(4)
proposalLength.writeInt32BE(proposal.length, 0)
wrap.write(Buffer.concat([proposalLength, proposal]))
pull(
bToA,
pull.collect((err, chunks) => {
expect(err).to.not.exist()
// Read their proposal
let theirProposalRaw = (await wrap.read()).slice()
let dataLength = theirProposalRaw.readInt32BE(0)
theirProposalRaw = theirProposalRaw.slice(4, dataLength + 4)
const theirProposal = Propose.decode(theirProposalRaw)
expect(theirProposal.rand).to.have.length(16)
expect(theirProposal.pubkey).to.eql(localPeer.pubKey.bytes)
expect(theirProposal.exchanges).to.equal('P-256,P-384,P-521')
expect(theirProposal.ciphers).to.equal('AES-256,AES-128')
expect(theirProposal.hashes).to.equal('SHA256,SHA512')
expect(chunks).to.eql([Buffer.from('hello world')])
// Select protocols
identify(state, theirProposalRaw)
await selectProtocols(state)
expect(state.protocols.local).to.include({ curveT: 'P-256', cipherT: 'AES-256', hashT: 'SHA256' })
expect(state.protocols.remote).to.include({ curveT: 'P-256', cipherT: 'AES-256', hashT: 'SHA256' })
bToA.getPeerInfo((err, PeerInfo) => {
expect(err).to.not.exist()
expect(PeerInfo.id.toB58String()).to.equal(peerA.toB58String())
done()
})
})
)
})
// Create our exchange
const exchange = await createExchange(state)
it('fails if we dialed to the wrong peer', (done) => {
const p = pair()
let count = 0
// Send our exchange
const exchangeLength = Buffer.allocUnsafe(4)
exchangeLength.writeInt32BE(exchange.length, 0)
wrap.write(Buffer.concat([exchangeLength, exchange]))
function check (err) {
expect(err).to.exist()
if (++count === 2) { done() }
}
// Read their exchange
let theirExchangeRaw = (await wrap.read()).slice()
dataLength = theirExchangeRaw.readInt32BE(0)
theirExchangeRaw = theirExchangeRaw.slice(4, dataLength + 4)
await verify(state, theirExchangeRaw)
// we are using peerC Id on purpose to fail
secio.encrypt(peerA, new Connection(p[0]), peerC, check)
secio.encrypt(peerB, new Connection(p[1]), peerA, check)
})
// Generate the crypto keys
await generateKeys(state)
it('bubbles errors from handshake failures properly', (done) => {
const p = pair()
const timeout = 60 * 1000 * 5
const stateA = new State(peerA, peerC, timeout, () => { })
const stateB = new State(peerB, peerA, timeout, () => { })
const connA = new Connection(p[0])
const connB = new Connection(p[1])
// Create the crypto stream
const box = createBoxStream(state.protocols.local.cipher, state.protocols.local.mac)
const unbox = createUnboxStream(state.protocols.remote.cipher, state.protocols.remote.mac)
function finish (err) {
expect(err).to.exist()
done()
}
// Send back their nonce over the crypto stream
const { value: nonce } = await box([state.proposal.in.rand]).next()
expect(nonce.slice()).to.not.eql(state.proposal.in.rand) // The nonce should be encrypted
const nonceLength = Buffer.allocUnsafe(4)
nonceLength.writeInt32BE(nonce.length, 0)
wrap.write(Buffer.concat([nonceLength, nonce.slice()]))
pull(
connA,
handshake(stateA, finish),
connA
)
// Read our nonce from the crypto stream
let ourNonceRaw = (await wrap.read())
dataLength = ourNonceRaw.readInt32BE(0)
ourNonceRaw = ourNonceRaw.shallowSlice(4, dataLength + 4) // Unbox expects a BufferList, so shallow slice here
expect(ourNonceRaw.slice()).to.not.eql(state.proposal.out.rand) // The nonce should be encrypted
const { value: ourNonce } = await unbox([ourNonceRaw]).next()
pull(
connB,
handshake(stateB, finish),
connB
)
// Verify our nonce is correct
expect(ourNonce.slice()).to.eql(state.proposal.out.rand)
})()
])
})
})