diff --git a/.aegir.js b/.aegir.js deleted file mode 100644 index d77e70f..0000000 --- a/.aegir.js +++ /dev/null @@ -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) - } - } - } -} diff --git a/.travis.yml b/.travis.yml index 264f189..88040e7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,7 @@ stages: node_js: - '10' + - '12' os: - linux diff --git a/README.md b/README.md index c850977..efe024a 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/benchmarks/send.js b/benchmarks/send.js index e6dab48..04e649a 100644 --- a/benchmarks/send.js +++ b/benchmarks/send.js @@ -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', (deferred) => { - const p = pair() + } +}) +suite.add('establish an encrypted channel', { + defer: true, + fn: async (deferred) => { + const p = DuplexPair() - const peerA = peers[0] - const peerB = peers[1] + const peerA = peers[0] + const peerB = peers[1] - const aToB = secio.encrypt(peerA, new Connection(p[0]), peerB, ifErr) - const bToA = secio.encrypt(peerB, new Connection(p[1]), peerA, ifErr) + const [aToB, bToA] = await Promise.all([ + secio.secureInbound(peerA, p[0], peerB), + secio.secureOutbound(peerB, p[1], peerA) + ]) - 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) => { diff --git a/package.json b/package.json index 2908aef..7f05ba4 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/etm.js b/src/etm.js index 16a1418..6645bba 100644 --- a/src/etm.js +++ b/src/etm.js @@ -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')}`)) - } - - // all good, decrypt - decipher.decrypt(data, (err, decrypted) => { - if (err) { - return cb(err) - } + if (!macd.equals(expected)) { + throw new InvalidCryptoTransmissionError(`MAC Invalid: ${macd.toString('hex')} != ${expected.toString('hex')}`) + } - cb(null, decrypted) - }) - }) - }) - ) -} + const decrypted = await decipher.decrypt(data) -function ensureBuffer () { - return map((c) => { - if (typeof c === 'string') { - return Buffer.from(c, 'utf-8') + yield decrypted } - - return c - }) + } } diff --git a/src/handshake/crypto.js b/src/handshake/crypto.js index 13fb06c..91cd16c 100644 --- a/src/handshake/crypto.js +++ b/src/handshake/crypto.js @@ -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 + + // Gather corpus to sign. + const selectionOut = Buffer.concat([ + state.proposalEncoded.out, + state.proposalEncoded.in, + state.ephemeralKey.local + ]) - 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 - ]) - - state.key.local.sign(selectionOut, (err, sig) => { - if (err) { - return callback(err) - } - - state.exchange.out = { - epubkey: state.ephemeralKey.local, - signature: sig - } - - callback(null, pbm.Exchange.encode(state.exchange.out)) - }) - }) + const sig = await state.key.local.sign(selectionOut) + + state.exchange.out = { + epubkey: state.ephemeralKey.local, + signature: sig + } + + 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 - - 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')) - } - - log('2.3. mac + cipher') - - parallel([ - (_cb) => support.makeMacAndCipher(state.protocols.local, _cb), - (_cb) => support.makeMacAndCipher(state.protocols.remote, _cb) - ], cb) - } - ], callback) + const secret = await state.shared.generate(state.exchange.in.epubkey) + + state.shared.secret = secret + + const keys = await crypto.keys.keyStretcher( + state.protocols.local.cipherT, + state.protocols.local.hashT, + state.shared.secret) + + // 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) => { diff --git a/src/handshake/exchange.js b/src/handshake/exchange.js index ecc3ca3..19f3b99 100644 --- a/src/handshake/exchange.js +++ b/src/handshake/exchange.js @@ -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) + + await wrapped.writeLP(ex) + const msg = await wrapped.readLP() + + log('2. exchange - reading exchange') + await crypto.verify(state, msg.slice()) - log('2. exchange - finish') - callback() - }) + await crypto.generateKeys(state) + log('2. exchange - finish') } diff --git a/src/handshake/finish.js b/src/handshake/finish.js index 7411a53..f356169 100644 --- a/src/handshake/finish.js +++ b/src/handshake/finish.js @@ -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) - } - - if (err) return fail(err) - - 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() - }) + // 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()) + + log('3. finish - finish') + + // Awesome that's all folks. + state.secure = shake.unwrap() } diff --git a/src/handshake/index.js b/src/handshake/index.js index 521e4d3..ca571fe 100644 --- a/src/handshake/index.js +++ b/src/handshake/index.js @@ -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() - - 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) - }) +module.exports = async function handshake (state, wrapped) { + await propose(state, wrapped) + await exchange(state, wrapped) + await finish(state, wrapped) - return state.stream + state.cleanSecrets() } diff --git a/src/handshake/propose.js b/src/handshake/propose.js index 709027f..06e665c 100644 --- a/src/handshake/propose.js +++ b/src/handshake/propose.js @@ -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) + + await wrapped.write(lp.encode.single(prop, { lengthEncoder: int32BEEncode })) + + log('1. propose - reading proposal') + const msg = (await wrapped.readLP()).slice() + log('1. propose - read proposal', msg) - 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 crypto.identify(state, msg) + await crypto.selectProtocols(state) - log('1. propose - finish') - callback() - }) + log('1. propose - finish') } diff --git a/src/handshake/secio.proto.js b/src/handshake/secio.proto.js index cf1b972..e72b32f 100644 --- a/src/handshake/secio.proto.js +++ b/src/handshake/secio.proto.js @@ -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; -}` +}`) diff --git a/src/index.js b/src/index.js index 73a28f5..dca8e49 100644 --- a/src/index.js +++ b/src/index.js @@ -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') - - if (typeof remoteId === 'function') { - callback = remoteId - remoteId = undefined - } - - 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) } +async function secure (localPeer, duplex, remotePeer) { // returns duplex + assert(localPeer, 'no local private key provided') + assert(duplex, 'no connection for the handshake provided') - conn.getPeerInfo((err, peerInfo) => { - encryptedConnection.setInnerConn(new Connection(state.secure, conn)) + const state = new State(localPeer, remotePeer) + const wrapped = Wrap(duplex, { lengthDecoder: int32BEDecode, lengthEncoder: int32BEEncode }) + await handshake(state, wrapped) - if (err) { // no peerInfo yet, means I'm the receiver - encryptedConnection.setPeerInfo(new PeerInfo(state.id.remote)) - } - - callback() - }) - } - - const encryptedConnection = new Connection(undefined, conn) + return { + conn: state.secure, + remotePeer: state.id.remote + } +} - pull( - conn, - handshake(state, finish), - conn - ) +module.exports = { + protocol: '/secio/1.0.0', - return encryptedConnection - } + // since SECIO is symetric, we only need one function here + secureInbound: secure, + secureOutbound: secure } diff --git a/src/state.js b/src/state.js index 7b63414..f12e0f3 100644 --- a/src/state.js +++ b/src/state.js @@ -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 () { diff --git a/src/support.js b/src/support.js index 284b2ba..ca7ff15 100644 --- a/src/support.js +++ b/src/support.js @@ -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) - } - - exports.digest(Buffer.concat([ - local.pubKeyBytes, - remote.nonce - ]), (err, oh2) => { - if (err) { - return cb(err) - } - - const order = Buffer.compare(oh1, oh2) + ])) + const oh2 = await exports.digest(Buffer.concat([ + local.pubKeyBytes, + remote.nonce + ])) - 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 - }) - }) - }) -} + const order = Buffer.compare(oh1, oh2) -exports.digest = (buf, cb) => { - mh.digest(buf, 'sha2-256', buf.length, cb) -} + if (order === 0) { + throw new InvalidCryptoExchangeError('you are trying to talk to yourself') + } -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() - }) - ) + 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.read = function read (reader, cb) { - lp.decodeFromReader(reader, { fixed: true, bytes: 4 }, cb) +exports.digest = (buf) => { + return mh.digest(buf, 'sha2-256', buf.length) } diff --git a/test/browser.js b/test/browser.js deleted file mode 100644 index cb52f08..0000000 --- a/test/browser.js +++ /dev/null @@ -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 - ) - }) -}) diff --git a/test/crypto.spec.js b/test/crypto.spec.js new file mode 100644 index 0000000..fe80258 --- /dev/null +++ b/test/crypto.spec.js @@ -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 + } +}) diff --git a/test/fixtures/peer-a.json b/test/fixtures/peer-a.json deleted file mode 100644 index 107c894..0000000 --- a/test/fixtures/peer-a.json +++ /dev/null @@ -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=" -} diff --git a/test/fixtures/peer-b.json b/test/fixtures/peer-b.json deleted file mode 100644 index 4c52c6e..0000000 --- a/test/fixtures/peer-b.json +++ /dev/null @@ -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=" -} diff --git a/test/fixtures/peer-browser.json b/test/fixtures/peer-browser.json deleted file mode 100644 index 976c60a..0000000 --- a/test/fixtures/peer-browser.json +++ /dev/null @@ -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=" -} diff --git a/test/fixtures/peer-c.json b/test/fixtures/peer-c.json deleted file mode 100644 index ba43f60..0000000 --- a/test/fixtures/peer-c.json +++ /dev/null @@ -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=" -} diff --git a/test/fixtures/peer-node.json b/test/fixtures/peer-node.json deleted file mode 100644 index 09e1635..0000000 --- a/test/fixtures/peer-node.json +++ /dev/null @@ -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=" -} diff --git a/test/fixtures/peer.js b/test/fixtures/peer.js new file mode 100644 index 0000000..7000796 --- /dev/null +++ b/test/fixtures/peer.js @@ -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])) + ) +} diff --git a/test/secio.spec.js b/test/secio.spec.js index b499730..0188a23 100644 --- a/test/secio.spec.js +++ b/test/secio.spec.js @@ -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 - - 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() - }) - }) - - it('exports a secio multicodec', () => { - expect(secio.tag).to.equal('/secio/1.0.0') - }) + let remotePeer + let localPeer - it('upgrades a connection', (done) => { - const p = pair() - - const aToB = secio.encrypt(peerA, new Connection(p[0]), peerB, (err) => expect(err).to.not.exist()) - const bToA = secio.encrypt(peerB, new Connection(p[1]), peerA, (err) => expect(err).to.not.exist()) - - pull( - pull.values([Buffer.from('hello world')]), - aToB - ) - - pull( - bToA, - pull.collect((err, chunks) => { - expect(err).to.not.exist() - expect(chunks).to.eql([Buffer.from('hello world')]) - done() - }) - ) + before(async () => { + [remotePeer, localPeer] = await createPeerIdsFromFixtures(2) }) - it('works over multistream-select', (done) => { - const p = pair() - - const listener = new Listener() - const dialer = new Dialer() - - 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()) - - pull( - bToA, - pull.collect((err, chunks) => { - expect(err).to.not.exist() - expect(chunks).to.eql([Buffer.from('hello world')]) - done() - }) - ) - }) - - cb() - }, - (cb) => dialer.select('/banana/1.0.0', (err, conn) => { - expect(err).to.not.exist() - - const aToB = secio.encrypt(peerA, conn, peerB, (err) => expect(err).to.not.exist()) - - pull( - pull.values([Buffer.from('hello world')]), - aToB - ) - cb() - }) + 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) + + // Create our proposal + const proposal = createProposal(state) + + // Send our proposal + const proposalLength = Buffer.allocUnsafe(4) + proposalLength.writeInt32BE(proposal.length, 0) + wrap.write(Buffer.concat([proposalLength, proposal])) + + // 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') + + // 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' }) + + // Create our exchange + const exchange = await createExchange(state) + + // Send our exchange + const exchangeLength = Buffer.allocUnsafe(4) + exchangeLength.writeInt32BE(exchange.length, 0) + wrap.write(Buffer.concat([exchangeLength, exchange])) + + // Read their exchange + let theirExchangeRaw = (await wrap.read()).slice() + dataLength = theirExchangeRaw.readInt32BE(0) + theirExchangeRaw = theirExchangeRaw.slice(4, dataLength + 4) + await verify(state, theirExchangeRaw) + + // Generate the crypto keys + await generateKeys(state) + + // 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) + + // 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()])) + + // 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() - - const aToB = secio.encrypt(peerA, new Connection(p[0]), peerB, (err) => expect(err).to.not.exist()) - const bToA = secio.encrypt(peerB, new Connection(p[1]), undefined, (err) => expect(err).to.not.exist()) - - pull( - pull.values([Buffer.from('hello world')]), - aToB - ) - - pull( - bToA, - pull.collect((err, chunks) => { - expect(err).to.not.exist() - - expect(chunks).to.eql([Buffer.from('hello world')]) - - bToA.getPeerInfo((err, PeerInfo) => { - expect(err).to.not.exist() - expect(PeerInfo.id.toB58String()).to.equal(peerA.toB58String()) - done() - }) - }) - ) - }) - - it('fails if we dialed to the wrong peer', (done) => { - const p = pair() - let count = 0 - - function check (err) { - expect(err).to.exist() - if (++count === 2) { done() } - } - - // we are using peerC Id on purpose to fail - secio.encrypt(peerA, new Connection(p[0]), peerC, check) - secio.encrypt(peerB, new Connection(p[1]), peerA, check) - }) - - 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]) - - function finish (err) { - expect(err).to.exist() - done() - } - - pull( - connA, - handshake(stateA, finish), - connA - ) - - pull( - connB, - handshake(stateB, finish), - connB - ) + 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) + + // Create our proposal + const proposal = createProposal(state) + + // Send our proposal + const proposalLength = Buffer.allocUnsafe(4) + proposalLength.writeInt32BE(proposal.length, 0) + wrap.write(Buffer.concat([proposalLength, proposal])) + + // 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') + + // 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' }) + + // Create our exchange + const exchange = await createExchange(state) + + // Send our exchange + const exchangeLength = Buffer.allocUnsafe(4) + exchangeLength.writeInt32BE(exchange.length, 0) + wrap.write(Buffer.concat([exchangeLength, exchange])) + + // Read their exchange + let theirExchangeRaw = (await wrap.read()).slice() + dataLength = theirExchangeRaw.readInt32BE(0) + theirExchangeRaw = theirExchangeRaw.slice(4, dataLength + 4) + await verify(state, theirExchangeRaw) + + // Generate the crypto keys + await generateKeys(state) + + // 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) + + // 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()])) + + // 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) + })() + ]) }) })