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: node_js:
- '10' - '10'
- '12'
os: os:
- linux - linux

View File

@ -42,44 +42,9 @@ const secio = require('libp2p-secio')
## API ## 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`. [ » API Docs ](https://github.com/libp2p/js-interfaces/tree/master/src/crypto#api)
### `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.
## Contribute ## Contribute

View File

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

View File

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

View File

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

View File

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

View File

@ -1,35 +1,25 @@
'use strict' 'use strict'
const debug = require('debug')
const waterfall = require('async/waterfall')
const support = require('../support')
const crypto = require('./crypto') const crypto = require('./crypto')
const debug = require('debug')
const log = debug('libp2p:secio') const log = debug('libp2p:secio')
log.error = debug('libp2p:secio:error') log.error = debug('libp2p:secio:error')
// step 2. Exchange // step 2. Exchange
// -- exchange (signed) ephemeral keys. verify signatures. // -- 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 - start')
log('2. exchange - writing exchange') log('2. exchange - writing exchange')
waterfall([ const ex = await crypto.createExchange(state)
(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) }
log('2. exchange - finish') await wrapped.writeLP(ex)
callback() 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' '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 debug = require('debug')
const log = debug('libp2p:secio') const log = debug('libp2p:secio')
log.error = debug('libp2p:secio:error') 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 etm = require('../etm')
const crypto = require('./crypto') const crypto = require('./crypto')
// step 3. Finish // step 3. Finish
// -- send expected message to verify encryption works (send local nonce) // -- 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') log('3. finish - start')
const proto = state.protocols const proto = state.protocols
const stream = state.shake.rest()
const shake = handshake({ timeout: state.timeout }, (err) => {
if (err) {
throw err
}
})
pull( const [secure, user] = DuplexPair()
stream, const network = wrapped.unwrap()
etm.createUnboxStream(proto.remote.cipher, proto.remote.mac),
shake, pipe(
secure, // this is FROM the user
ensureBuffer,
etm.createBoxStream(proto.local.cipher, proto.local.mac), 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) // Exchange nonces over the encrypted stream for final verification
shake.handshake.read(state.proposal.in.rand.length, (err, nonceBack) => { const shake = Wrap(user)
const fail = (err) => { shake.write(state.proposal.in.rand)
log.error(err) const nonceBack = await shake.read(state.proposal.in.rand.length)
state.secure.resolve({ crypto.verifyNonce(state, nonceBack.slice())
source: pullError(err),
sink (read) {
}
})
callback(err)
}
if (err) return fail(err) log('3. finish - finish')
try { // Awesome that's all folks.
crypto.verifyNonce(state, nonceBack) state.secure = shake.unwrap()
} catch (err) {
return fail(err)
}
log('3. finish - finish')
// Awesome that's all folks.
state.secure.resolve(shake.handshake.rest())
callback()
})
} }

View File

@ -1,31 +1,15 @@
'use strict' 'use strict'
const series = require('async/series')
const propose = require('./propose') 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 keys, IDs, // Performs initial communication over insecure channel to share keys, IDs,
// and initiate communication, assigning all necessary params. // and initiate communication, assigning all necessary params.
module.exports = function handshake (state, callback) { module.exports = async function handshake (state, wrapped) {
series([ await propose(state, wrapped)
(cb) => propose(state, cb), await exchange(state, wrapped)
(cb) => exchange(state, cb), await finish(state, wrapped)
(cb) => finish(state, cb)
], (err) => {
state.cleanSecrets()
if (err) { state.cleanSecrets()
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
} }

View File

@ -1,35 +1,28 @@
'use strict' 'use strict'
const debug = require('debug')
const waterfall = require('async/waterfall')
const support = require('../support')
const crypto = require('./crypto') const crypto = require('./crypto')
const lp = require('it-length-prefixed')
const { int32BEEncode } = lp
const debug = require('debug')
const log = debug('libp2p:secio') const log = debug('libp2p:secio')
log.error = debug('libp2p:secio:error') log.error = debug('libp2p:secio:error')
// step 1. Propose // step 1. Propose
// -- propose cipher suite + send pubkeys + nonce // -- 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 - start')
log('1. propose - writing proposal') const prop = crypto.createProposal(state)
support.write(state, crypto.createProposal(state)) log('1. propose - writing proposal', prop)
waterfall([ await wrapped.write(lp.encode.single(prop, { lengthEncoder: int32BEEncode }))
(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)
}
log('1. propose - finish') log('1. propose - reading proposal')
callback() 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' 'use strict'
module.exports = `message Propose { const protons = require('protons')
module.exports = protons(`message Propose {
optional bytes rand = 1; optional bytes rand = 1;
optional bytes pubkey = 2; optional bytes pubkey = 2;
optional string exchanges = 3; optional string exchanges = 3;
@ -11,4 +13,4 @@ module.exports = `message Propose {
message Exchange { message Exchange {
optional bytes epubkey = 1; optional bytes epubkey = 1;
optional bytes signature = 2; optional bytes signature = 2;
}` }`)

View File

@ -1,58 +1,33 @@
'use strict' 'use strict'
const pull = require('pull-stream/pull')
const Connection = require('interface-connection').Connection
const assert = require('assert') const assert = require('assert')
const PeerInfo = require('peer-info')
const debug = require('debug') const debug = require('debug')
const once = require('once')
const log = debug('libp2p:secio') const log = debug('libp2p:secio')
log.error = debug('libp2p:secio:error') log.error = debug('libp2p:secio:error')
const handshake = require('./handshake') const handshake = require('./handshake')
const State = require('./state') const State = require('./state')
const Wrap = require('it-pb-rpc')
const { int32BEDecode, int32BEEncode } = require('it-length-prefixed')
module.exports = { async function secure (localPeer, duplex, remotePeer) { // returns duplex
tag: '/secio/1.0.0', assert(localPeer, 'no local private key provided')
encrypt (localId, conn, remoteId, callback) { assert(duplex, 'no connection for the handshake provided')
assert(localId, 'no local private key provided')
assert(conn, 'no connection for the handshake provided')
if (typeof remoteId === 'function') { const state = new State(localPeer, remotePeer)
callback = remoteId const wrapped = Wrap(duplex, { lengthDecoder: int32BEDecode, lengthEncoder: int32BEEncode })
remoteId = undefined await handshake(state, wrapped)
}
callback = once(callback || function (err) { return {
if (err) { log.error(err) } conn: state.secure,
}) remotePeer: state.id.remote
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
} }
} }
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' 'use strict'
const handshake = require('pull-handshake')
const deferred = require('pull-defer')
class State { class State {
constructor (localId, remoteId, timeout, callback) { constructor (localId, remoteId) {
if (typeof timeout === 'function') {
callback = timeout
timeout = undefined
}
this.setup() this.setup()
this.id.local = localId this.id.local = localId
// TODO use remoteId to verify PeersIdentity
this.id.remote = remoteId this.id.remote = remoteId
this.key.local = localId.privKey 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 () { setup () {

View File

@ -1,12 +1,9 @@
'use strict' 'use strict'
const mh = require('multihashing-async') 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 crypto = require('libp2p-crypto')
const parallel = require('async/parallel')
const { InvalidCryptoExchangeError } = require('libp2p-interfaces/src/crypto/errors')
exports.exchanges = [ exports.exchanges = [
'P-256', 'P-256',
@ -39,97 +36,61 @@ exports.theBest = (order, p1, p2) => {
return p1[0] return p1[0]
} }
for (let firstCandidate of first) { for (const firstCandidate of first) {
for (let secondCandidate of second) { for (const secondCandidate of second) {
if (firstCandidate === secondCandidate) { if (firstCandidate === secondCandidate) {
return firstCandidate return firstCandidate
} }
} }
} }
throw new Error('No algorithms in common!') throw new InvalidCryptoExchangeError('No algorithms in common!')
} }
exports.makeMacAndCipher = (target, callback) => { exports.makeMacAndCipher = async (target) => {
parallel([ [target.mac, target.cipher] = await Promise.all([
(cb) => makeMac(target.hashT, target.keys.macKey, cb), makeMac(target.hashT, target.keys.macKey),
(cb) => makeCipher(target.cipherT, target.keys.iv, target.keys.cipherKey, cb) makeCipher(target.cipherT, target.keys.iv, target.keys.cipherKey)
], (err, macAndCipher) => { ])
if (err) {
return callback(err)
}
target.mac = macAndCipher[0]
target.cipher = macAndCipher[1]
callback()
})
} }
function makeMac (hash, key, callback) { function makeMac (hash, key) {
crypto.hmac.create(hash, key, callback) return crypto.hmac.create(hash, key)
} }
function makeCipher (cipherType, iv, key, callback) { function makeCipher (cipherType, iv, key) {
if (cipherType === 'AES-128' || cipherType === 'AES-256') { 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. // 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.selectBest = async (local, remote) => {
exports.digest(Buffer.concat([ const oh1 = await exports.digest(Buffer.concat([
remote.pubKeyBytes, remote.pubKeyBytes,
local.nonce local.nonce
]), (err, oh1) => { ]))
if (err) { const oh2 = await exports.digest(Buffer.concat([
return cb(err) local.pubKeyBytes,
} remote.nonce
]))
exports.digest(Buffer.concat([ const order = Buffer.compare(oh1, oh2)
local.pubKeyBytes,
remote.nonce
]), (err, oh2) => {
if (err) {
return cb(err)
}
const order = Buffer.compare(oh1, oh2) if (order === 0) {
throw new InvalidCryptoExchangeError('you are trying to talk to yourself')
}
if (order === 0) { return {
return cb(new Error('you are trying to talk to yourself')) curveT: exports.theBest(order, local.exchanges, remote.exchanges),
} cipherT: exports.theBest(order, local.ciphers, remote.ciphers),
hashT: exports.theBest(order, local.hashes, remote.hashes),
cb(null, { order
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) => { exports.digest = (buf) => {
mh.digest(buf, 'sha2-256', buf.length, cb) return mh.digest(buf, 'sha2-256', buf.length)
}
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)
} }

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 */ /* eslint-env mocha */
'use strict' 'use strict'
const pair = require('pull-pair/duplex')
const chai = require('chai') const chai = require('chai')
const dirtyChai = require('dirty-chai') 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 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 State = require('../src/state')
const handshake = require('../src/handshake') const { Propose } = require('../src/handshake/secio.proto')
describe('secio', () => { describe('secio', () => {
let peerA let remotePeer
let peerB let localPeer
let peerC
before((done) => { before(async () => {
parallel([ [remotePeer, localPeer] = await createPeerIdsFromFixtures(2)
(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()
})
}) })
it('exports a secio multicodec', () => { it('performs a spec compliant inbound exchange', async () => {
expect(secio.tag).to.equal('/secio/1.0.0') 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) => { // Create our proposal
const p = pair() const proposal = createProposal(state)
const aToB = secio.encrypt(peerA, new Connection(p[0]), peerB, (err) => expect(err).to.not.exist()) // Send our proposal
const bToA = secio.encrypt(peerB, new Connection(p[1]), peerA, (err) => expect(err).to.not.exist()) const proposalLength = Buffer.allocUnsafe(4)
proposalLength.writeInt32BE(proposal.length, 0)
wrap.write(Buffer.concat([proposalLength, proposal]))
pull( // Read their proposal
pull.values([Buffer.from('hello world')]), let theirProposalRaw = (await wrap.read()).slice()
aToB 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( // Select protocols
bToA, identify(state, theirProposalRaw)
pull.collect((err, chunks) => { await selectProtocols(state)
expect(err).to.not.exist() expect(state.protocols.local).to.include({ curveT: 'P-256', cipherT: 'AES-256', hashT: 'SHA256' })
expect(chunks).to.eql([Buffer.from('hello world')]) expect(state.protocols.remote).to.include({ curveT: 'P-256', cipherT: 'AES-256', hashT: 'SHA256' })
done()
})
)
})
it('works over multistream-select', (done) => { // Create our exchange
const p = pair() const exchange = await createExchange(state)
const listener = new Listener() // Send our exchange
const dialer = new Dialer() const exchangeLength = Buffer.allocUnsafe(4)
exchangeLength.writeInt32BE(exchange.length, 0)
wrap.write(Buffer.concat([exchangeLength, exchange]))
series([ // Read their exchange
(cb) => parallel([ let theirExchangeRaw = (await wrap.read()).slice()
(cb) => listener.handle(p[0], cb), dataLength = theirExchangeRaw.readInt32BE(0)
(cb) => dialer.handle(p[1], cb) theirExchangeRaw = theirExchangeRaw.slice(4, dataLength + 4)
], cb), await verify(state, theirExchangeRaw)
(cb) => {
listener.addHandler('/banana/1.0.0', (protocol, conn) => {
const bToA = secio.encrypt(peerB, conn, peerA, (err) => expect(err).to.not.exist())
pull( // Generate the crypto keys
bToA, await generateKeys(state)
pull.collect((err, chunks) => {
expect(err).to.not.exist()
expect(chunks).to.eql([Buffer.from('hello world')])
done()
})
)
})
cb() // Create the crypto stream
}, const box = createBoxStream(state.protocols.local.cipher, state.protocols.local.mac)
(cb) => dialer.select('/banana/1.0.0', (err, conn) => { const unbox = createUnboxStream(state.protocols.remote.cipher, state.protocols.remote.mac)
expect(err).to.not.exist()
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( // Read our nonce from the crypto stream
pull.values([Buffer.from('hello world')]), let ourNonceRaw = (await wrap.read())
aToB dataLength = ourNonceRaw.readInt32BE(0)
) ourNonceRaw = ourNonceRaw.shallowSlice(4, dataLength + 4) // Unbox expects a BufferList, so shallow slice here
cb() 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) => { it('performs a spec compliant outbound exchange', async () => {
const p = pair() 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()) // Create our proposal
const bToA = secio.encrypt(peerB, new Connection(p[1]), undefined, (err) => expect(err).to.not.exist()) const proposal = createProposal(state)
pull( // Send our proposal
pull.values([Buffer.from('hello world')]), const proposalLength = Buffer.allocUnsafe(4)
aToB proposalLength.writeInt32BE(proposal.length, 0)
) wrap.write(Buffer.concat([proposalLength, proposal]))
pull( // Read their proposal
bToA, let theirProposalRaw = (await wrap.read()).slice()
pull.collect((err, chunks) => { let dataLength = theirProposalRaw.readInt32BE(0)
expect(err).to.not.exist() 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) => { // Create our exchange
expect(err).to.not.exist() const exchange = await createExchange(state)
expect(PeerInfo.id.toB58String()).to.equal(peerA.toB58String())
done()
})
})
)
})
it('fails if we dialed to the wrong peer', (done) => { // Send our exchange
const p = pair() const exchangeLength = Buffer.allocUnsafe(4)
let count = 0 exchangeLength.writeInt32BE(exchange.length, 0)
wrap.write(Buffer.concat([exchangeLength, exchange]))
function check (err) { // Read their exchange
expect(err).to.exist() let theirExchangeRaw = (await wrap.read()).slice()
if (++count === 2) { done() } dataLength = theirExchangeRaw.readInt32BE(0)
} theirExchangeRaw = theirExchangeRaw.slice(4, dataLength + 4)
await verify(state, theirExchangeRaw)
// we are using peerC Id on purpose to fail // Generate the crypto keys
secio.encrypt(peerA, new Connection(p[0]), peerC, check) await generateKeys(state)
secio.encrypt(peerB, new Connection(p[1]), peerA, check)
})
it('bubbles errors from handshake failures properly', (done) => { // Create the crypto stream
const p = pair() const box = createBoxStream(state.protocols.local.cipher, state.protocols.local.mac)
const timeout = 60 * 1000 * 5 const unbox = createUnboxStream(state.protocols.remote.cipher, state.protocols.remote.mac)
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])
function finish (err) { // Send back their nonce over the crypto stream
expect(err).to.exist() const { value: nonce } = await box([state.proposal.in.rand]).next()
done() 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( // Read our nonce from the crypto stream
connA, let ourNonceRaw = (await wrap.read())
handshake(stateA, finish), dataLength = ourNonceRaw.readInt32BE(0)
connA 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( // Verify our nonce is correct
connB, expect(ourNonce.slice()).to.eql(state.proposal.out.rand)
handshake(stateB, finish), })()
connB ])
)
}) })
}) })