mirror of
https://github.com/fluencelabs/js-libp2p-secio
synced 2025-05-10 05:27:13 +00:00
feat(secio): implement with pull-streams, ensure interop with go
This commit is contained in:
parent
b948ad62dd
commit
10a4cf0337
52
README.md
52
README.md
@ -4,14 +4,14 @@
|
|||||||
[](http://ipfs.io/)
|
[](http://ipfs.io/)
|
||||||
[](http://webchat.freenode.net/?channels=%23ipfs)
|
[](http://webchat.freenode.net/?channels=%23ipfs)
|
||||||
[](https://github.com/RichardLitt/standard-readme)
|
[](https://github.com/RichardLitt/standard-readme)
|
||||||
[](https://coveralls.io/github/ipfs/js-libp2p-secio?branch=master)
|
[](https://coveralls.io/github/libp2p/js-libp2p-secio?branch=master)
|
||||||
[](https://travis-ci.org/ipfs/js-libp2p-secio)
|
[](https://travis-ci.org/libp2p/js-libp2p-secio)
|
||||||
[](https://circleci.com/gh/ipfs/js-libp2p-secio)
|
[](https://circleci.com/gh/libp2p/js-libp2p-secio)
|
||||||
[](https://david-dm.org/ipfs/js-libp2p-secio) [](https://github.com/feross/standard)
|
[](https://david-dm.org/libp2p/js-libp2p-secio) [](https://github.com/feross/standard)
|
||||||
|
|
||||||
> Secio implementation in JavaScript
|
> Secio implementation in JavaScript
|
||||||
|
|
||||||
This repo contains the JavaScript implementation of secio, an encryption protocol used in libp2p. This is based on this [go implementation](https://github.com/ipfs/go-libp2p-secio).
|
This repo contains the JavaScript implementation of secio, an encryption protocol used in libp2p. This is based on this [go implementation](https://github.com/libp2p/go-libp2p-secio).
|
||||||
|
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
|
|
||||||
@ -24,17 +24,55 @@ This repo contains the JavaScript implementation of secio, an encryption protoco
|
|||||||
## Install
|
## Install
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
npm install --save libp2p-secio
|
npm install libp2p-secio
|
||||||
```
|
```
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const libp2pSecio = require('libp2p-secio')
|
const secio = require('libp2p-secio')
|
||||||
```
|
```
|
||||||
|
|
||||||
## API
|
## API
|
||||||
|
|
||||||
|
### `SecureSession`
|
||||||
|
|
||||||
|
#### `constructor(id, key, insecure)`
|
||||||
|
|
||||||
|
- `id: PeerId` - The id of the node.
|
||||||
|
- `key: RSAPrivateKey` - The private key of the node.
|
||||||
|
- `insecure: PullStream` - The insecure connection.
|
||||||
|
|
||||||
|
### `.secure`
|
||||||
|
|
||||||
|
Returns the `insecure` connection provided, wrapped with secio. This is a pull-stream.
|
||||||
|
|
||||||
|
### 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/dominictarr/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
|
||||||
|
|
||||||
Feel free to join in. All welcome. Open an [issue](https://github.com/ipfs/js-libp2p-secio/issues)!
|
Feel free to join in. All welcome. Open an [issue](https://github.com/ipfs/js-libp2p-secio/issues)!
|
||||||
|
26
package.json
26
package.json
@ -25,27 +25,27 @@
|
|||||||
"author": "Friedel Ziegelmayer <dignifiedqurie@gmail.com>",
|
"author": "Friedel Ziegelmayer <dignifiedqurie@gmail.com>",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"async-buffered-reader": "^1.2.1",
|
|
||||||
"debug": "^2.2.0",
|
"debug": "^2.2.0",
|
||||||
"duplexify": "^3.4.3",
|
"interface-connection": "^0.2.1",
|
||||||
"length-prefixed-stream": "^1.5.0",
|
|
||||||
"libp2p-crypto": "^0.5.0",
|
"libp2p-crypto": "^0.5.0",
|
||||||
"multihashing": "^0.2.1",
|
"multihashing": "^0.2.1",
|
||||||
"node-forge": "^0.6.39",
|
"node-forge": "^0.6.42",
|
||||||
"peer-id": "^0.7.0",
|
"peer-id": "^0.7.0",
|
||||||
"protocol-buffers": "^3.1.6",
|
"protocol-buffers": "^3.1.6",
|
||||||
"readable-stream": "2.1.4",
|
"pull-defer": "^0.2.2",
|
||||||
"run-series": "^1.1.4",
|
"pull-handshake": "^1.1.4",
|
||||||
"through2": "^2.0.1"
|
"pull-length-prefixed": "^1.2.0",
|
||||||
|
"pull-stream": "^3.4.3",
|
||||||
|
"pull-through": "^1.0.18",
|
||||||
|
"run-series": "^1.1.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"aegir": "^6.0.0",
|
"aegir": "^8.0.0",
|
||||||
"bl": "^1.1.2",
|
|
||||||
"chai": "^3.5.0",
|
"chai": "^3.5.0",
|
||||||
"multistream-select": "^0.10.0",
|
"multistream-select": "^0.11.0",
|
||||||
"pre-commit": "^1.1.3",
|
"pre-commit": "^1.1.3",
|
||||||
"run-parallel": "^1.1.6",
|
"pull-pair": "^1.1.0",
|
||||||
"stream-pair": "^1.0.3"
|
"run-parallel": "^1.1.6"
|
||||||
},
|
},
|
||||||
"pre-commit": [
|
"pre-commit": [
|
||||||
"lint",
|
"lint",
|
||||||
@ -65,4 +65,4 @@
|
|||||||
"contributors": [
|
"contributors": [
|
||||||
"dignifiedquire <dignifiedquire@gmail.com>"
|
"dignifiedquire <dignifiedquire@gmail.com>"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
53
src/etm.js
53
src/etm.js
@ -1,41 +1,44 @@
|
|||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
const through = require('through2')
|
const through = require('pull-through')
|
||||||
const lpm = require('length-prefixed-stream')
|
const pull = require('pull-stream')
|
||||||
|
const lp = require('pull-length-prefixed')
|
||||||
|
|
||||||
const toForgeBuffer = require('./support').toForgeBuffer
|
const toForgeBuffer = require('./support').toForgeBuffer
|
||||||
|
|
||||||
exports.writer = function etmWriter (insecure, cipher, mac) {
|
const lpOpts = {
|
||||||
const encode = lpm.encode()
|
fixed: true,
|
||||||
const pt = through(function (chunk, enc, cb) {
|
bytes: 4
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.createBoxStream = (cipher, mac) => {
|
||||||
|
const pt = through(function (chunk) {
|
||||||
cipher.update(toForgeBuffer(chunk))
|
cipher.update(toForgeBuffer(chunk))
|
||||||
|
|
||||||
if (cipher.output.length() > 0) {
|
if (cipher.output.length() > 0) {
|
||||||
const data = new Buffer(cipher.output.getBytes(), 'binary')
|
const data = new Buffer(cipher.output.getBytes(), 'binary')
|
||||||
mac.update(data)
|
mac.update(data.toString('binary'))
|
||||||
const macBuffer = new Buffer(mac.getMac().getBytes(), 'binary')
|
const macBuffer = new Buffer(mac.digest().getBytes(), 'binary')
|
||||||
|
|
||||||
this.push(Buffer.concat([data, macBuffer]))
|
this.queue(Buffer.concat([data, macBuffer]))
|
||||||
// reset hmac
|
// reset hmac
|
||||||
mac.start(null, null)
|
mac.start(null, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
cb()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
pt.pipe(encode).pipe(insecure)
|
return pull(
|
||||||
|
pt,
|
||||||
return pt
|
lp.encode(lpOpts)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.reader = function etmReader (insecure, decipher, mac) {
|
exports.createUnboxStream = (decipher, mac) => {
|
||||||
const decode = lpm.decode()
|
const pt = through(function (chunk) {
|
||||||
const pt = through(function (chunk, enc, cb) {
|
|
||||||
const l = chunk.length
|
const l = chunk.length
|
||||||
const macSize = mac.getMac().length()
|
const macSize = mac.getMac().length()
|
||||||
|
|
||||||
if (l < macSize) {
|
if (l < macSize) {
|
||||||
return cb(new Error(`buffer (${l}) shorter than MAC size (${macSize})`))
|
return this.emit('error', new Error(`buffer (${l}) shorter than MAC size (${macSize})`))
|
||||||
}
|
}
|
||||||
|
|
||||||
const mark = l - macSize
|
const mark = l - macSize
|
||||||
@ -45,12 +48,13 @@ exports.reader = function etmReader (insecure, decipher, mac) {
|
|||||||
// Clear out any previous data
|
// Clear out any previous data
|
||||||
mac.start(null, null)
|
mac.start(null, null)
|
||||||
|
|
||||||
mac.update(data)
|
mac.update(data.toString('binary'))
|
||||||
const expected = new Buffer(mac.getMac().getBytes(), 'binary')
|
const expected = new Buffer(mac.getMac().getBytes(), 'binary')
|
||||||
|
|
||||||
// reset hmac
|
// reset hmac
|
||||||
mac.start(null, null)
|
mac.start(null, null)
|
||||||
if (!macd.equals(expected)) {
|
if (!macd.equals(expected)) {
|
||||||
return cb(new Error(`MAC Invalid: ${macd.toString('hex')} != ${expected.toString('hex')}`))
|
return this.emit('error', new Error(`MAC Invalid: ${macd.toString('hex')} != ${expected.toString('hex')}`))
|
||||||
}
|
}
|
||||||
|
|
||||||
// all good, decrypt
|
// all good, decrypt
|
||||||
@ -58,13 +62,12 @@ exports.reader = function etmReader (insecure, decipher, mac) {
|
|||||||
|
|
||||||
if (decipher.output.length() > 0) {
|
if (decipher.output.length() > 0) {
|
||||||
const data = new Buffer(decipher.output.getBytes(), 'binary')
|
const data = new Buffer(decipher.output.getBytes(), 'binary')
|
||||||
this.push(data)
|
this.queue(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
cb()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
insecure.pipe(decode).pipe(pt)
|
return pull(
|
||||||
|
lp.decode(lpOpts),
|
||||||
return pt
|
pt
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
163
src/handshake/crypto.js
Normal file
163
src/handshake/crypto.js
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
'use strict'
|
||||||
|
|
||||||
|
const protobuf = require('protocol-buffers')
|
||||||
|
const path = require('path')
|
||||||
|
const fs = require('fs')
|
||||||
|
const PeerId = require('peer-id')
|
||||||
|
const crypto = require('libp2p-crypto')
|
||||||
|
const debug = require('debug')
|
||||||
|
const log = debug('libp2p:secio')
|
||||||
|
log.error = debug('libp2p:secio:error')
|
||||||
|
|
||||||
|
const pbm = protobuf(fs.readFileSync(path.join(__dirname, 'secio.proto')))
|
||||||
|
|
||||||
|
const support = require('../support')
|
||||||
|
|
||||||
|
// nonceSize is the size of our nonces (in bytes)
|
||||||
|
const nonceSize = 16
|
||||||
|
|
||||||
|
exports.createProposal = (state) => {
|
||||||
|
state.proposal.out = {
|
||||||
|
rand: support.randomBytes(nonceSize),
|
||||||
|
pubkey: state.key.local.public.bytes,
|
||||||
|
exchanges: support.exchanges.join(','),
|
||||||
|
ciphers: support.ciphers.join(','),
|
||||||
|
hashes: support.hashes.join(',')
|
||||||
|
}
|
||||||
|
|
||||||
|
state.proposalEncoded.out = pbm.Propose.encode(state.proposal.out)
|
||||||
|
return state.proposalEncoded.out
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.createExchange = (state) => {
|
||||||
|
const res = crypto.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.exchange.out = {
|
||||||
|
epubkey: state.ephemeralKey.local,
|
||||||
|
signature: new Buffer(state.key.local.sign(selectionOut), 'binary')
|
||||||
|
}
|
||||||
|
|
||||||
|
return pbm.Exchange.encode(state.exchange.out)
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.identify = (state, msg) => {
|
||||||
|
log('1.1 identify')
|
||||||
|
|
||||||
|
state.proposalEncoded.in = msg
|
||||||
|
state.proposal.in = pbm.Propose.decode(msg)
|
||||||
|
const pubkey = state.proposal.in.pubkey
|
||||||
|
|
||||||
|
console.log(state.proposal.in)
|
||||||
|
|
||||||
|
state.key.remote = crypto.unmarshalPublicKey(pubkey)
|
||||||
|
state.id.remote = PeerId.createFromPubKey(pubkey.toString('base64'))
|
||||||
|
|
||||||
|
log('1.1 identify - %s - identified remote peer as %s', state.id.local.toB58String(), state.id.remote.toB58String())
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.selectProtocols = (state) => {
|
||||||
|
log('1.2 selection')
|
||||||
|
|
||||||
|
const local = {
|
||||||
|
pubKeyBytes: state.key.local.public.bytes,
|
||||||
|
exchanges: support.exchanges,
|
||||||
|
hashes: support.hashes,
|
||||||
|
ciphers: support.ciphers,
|
||||||
|
nonce: state.proposal.out.rand
|
||||||
|
}
|
||||||
|
|
||||||
|
const remote = {
|
||||||
|
pubKeyBytes: state.proposal.in.pubkey,
|
||||||
|
exchanges: state.proposal.in.exchanges.split(','),
|
||||||
|
hashes: state.proposal.in.hashes.split(','),
|
||||||
|
ciphers: state.proposal.in.ciphers.split(','),
|
||||||
|
nonce: state.proposal.in.rand
|
||||||
|
}
|
||||||
|
|
||||||
|
let selected = support.selectBest(local, remote)
|
||||||
|
// 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) => {
|
||||||
|
log('2.1. verify')
|
||||||
|
|
||||||
|
state.exchange.in = pbm.Exchange.decode(msg)
|
||||||
|
state.ephemeralKey.remote = state.exchange.in.epubkey
|
||||||
|
|
||||||
|
const selectionIn = Buffer.concat([
|
||||||
|
state.proposalEncoded.in,
|
||||||
|
state.proposalEncoded.out,
|
||||||
|
state.ephemeralKey.remote
|
||||||
|
])
|
||||||
|
|
||||||
|
const sigOk = state.key.remote.verify(selectionIn, state.exchange.in.signature)
|
||||||
|
|
||||||
|
if (!sigOk) {
|
||||||
|
throw new Error('Bad signature')
|
||||||
|
}
|
||||||
|
|
||||||
|
log('2.1. verify - signature verified')
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.generateKeys = (state) => {
|
||||||
|
log('2.2. keys')
|
||||||
|
|
||||||
|
state.shared.secret = state.shared.generate(state.exchange.in.epubkey)
|
||||||
|
|
||||||
|
const keys = crypto.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')
|
||||||
|
|
||||||
|
support.makeMacAndCipher(state.protocols.local)
|
||||||
|
support.makeMacAndCipher(state.protocols.remote)
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.verifyNonce = (state, n2) => {
|
||||||
|
const n1 = state.proposal.out.rand
|
||||||
|
|
||||||
|
if (n1.equals(n2)) return
|
||||||
|
|
||||||
|
throw new Error(
|
||||||
|
`Failed to read our encrypted nonce: ${n1.toString('hex')} != ${n2.toString('hex')}`
|
||||||
|
)
|
||||||
|
}
|
@ -1,43 +1,30 @@
|
|||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
const crypto = require('libp2p-crypto')
|
|
||||||
const debug = require('debug')
|
const debug = require('debug')
|
||||||
const fs = require('fs')
|
|
||||||
const path = require('path')
|
const support = require('../support')
|
||||||
const protobuf = require('protocol-buffers')
|
const crypto = require('./crypto')
|
||||||
|
|
||||||
const log = debug('libp2p:secio')
|
const log = debug('libp2p:secio')
|
||||||
log.error = debug('libp2p:secio:error')
|
log.error = debug('libp2p:secio:error')
|
||||||
|
|
||||||
const pbm = protobuf(fs.readFileSync(path.join(__dirname, '../secio.proto')))
|
|
||||||
|
|
||||||
const support = require('../support')
|
|
||||||
|
|
||||||
// step 2. Exchange
|
// step 2. Exchange
|
||||||
// -- exchange (signed) ephemeral keys. verify signatures.
|
// -- exchange (signed) ephemeral keys. verify signatures.
|
||||||
module.exports = function exchange (session, cb) {
|
module.exports = function exchange (state, cb) {
|
||||||
log('2. exchange - start')
|
log('2. exchange - start')
|
||||||
|
|
||||||
let genSharedKey
|
log('2. exchange - writing exchange')
|
||||||
let exchangeOut
|
support.write(state, crypto.createExchange(state))
|
||||||
|
support.read(state.shake, (err, msg) => {
|
||||||
|
if (err) {
|
||||||
|
return cb(err)
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
log('2. exchange - reading exchange')
|
||||||
const eResult = crypto.generateEphemeralKeyPair(session.local.curveT)
|
|
||||||
session.local.ephemeralPubKey = eResult.key
|
|
||||||
genSharedKey = eResult.genSharedKey
|
|
||||||
exchangeOut = makeExchange(session)
|
|
||||||
} catch (err) {
|
|
||||||
return cb(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
session.insecureLp.write(exchangeOut)
|
|
||||||
session.insecureLp.once('data', (chunk) => {
|
|
||||||
const exchangeIn = pbm.Exchange.decode(chunk)
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
verify(session, exchangeIn)
|
crypto.verify(state, msg)
|
||||||
keys(session, exchangeIn, genSharedKey)
|
crypto.generateKeys(state)
|
||||||
macAndCipher(session)
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return cb(err)
|
return cb(err)
|
||||||
}
|
}
|
||||||
@ -46,63 +33,3 @@ module.exports = function exchange (session, cb) {
|
|||||||
cb()
|
cb()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeExchange (session) {
|
|
||||||
// Gather corpus to sign.
|
|
||||||
const selectionOut = Buffer.concat([
|
|
||||||
session.proposal.out,
|
|
||||||
session.proposal.in,
|
|
||||||
session.local.ephemeralPubKey
|
|
||||||
])
|
|
||||||
|
|
||||||
const epubkey = session.local.ephemeralPubKey
|
|
||||||
const signature = new Buffer(session.localKey.sign(selectionOut), 'binary')
|
|
||||||
log('out', {epubkey, signature})
|
|
||||||
return pbm.Exchange.encode({epubkey, signature})
|
|
||||||
}
|
|
||||||
|
|
||||||
function verify (session, exchangeIn) {
|
|
||||||
log('2.1. verify', exchangeIn)
|
|
||||||
|
|
||||||
session.remote.ephemeralPubKey = exchangeIn.epubkey
|
|
||||||
const selectionIn = Buffer.concat([
|
|
||||||
session.proposal.in,
|
|
||||||
session.proposal.out,
|
|
||||||
session.remote.ephemeralPubKey
|
|
||||||
])
|
|
||||||
const sigOk = session.remote.permanentPubKey.verify(selectionIn, exchangeIn.signature)
|
|
||||||
|
|
||||||
if (!sigOk) {
|
|
||||||
throw new Error('Bad signature')
|
|
||||||
}
|
|
||||||
|
|
||||||
log('2.1. verify - signature verified')
|
|
||||||
}
|
|
||||||
|
|
||||||
function keys (session, exchangeIn, genSharedKey) {
|
|
||||||
log('2.2. keys')
|
|
||||||
|
|
||||||
session.sharedSecret = genSharedKey(exchangeIn.epubkey)
|
|
||||||
|
|
||||||
const keys = crypto.keyStretcher(session.local.cipherT, session.local.hashT, session.sharedSecret)
|
|
||||||
|
|
||||||
// use random nonces to decide order.
|
|
||||||
if (session.proposal.order > 0) {
|
|
||||||
session.local.keys = keys.k1
|
|
||||||
session.remote.keys = keys.k2
|
|
||||||
} else if (session.proposal.order < 0) {
|
|
||||||
// swap
|
|
||||||
session.local.keys = keys.k2
|
|
||||||
session.remote.keys = keys.k1
|
|
||||||
} else {
|
|
||||||
// we should've bailed before this. but if not, bail here.
|
|
||||||
throw new Error('you are trying to talk to yourself')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function macAndCipher (session) {
|
|
||||||
log('2.3. mac + cipher')
|
|
||||||
|
|
||||||
support.makeMacAndCipher(session.local)
|
|
||||||
support.makeMacAndCipher(session.remote)
|
|
||||||
}
|
|
||||||
|
@ -1,35 +1,56 @@
|
|||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
const duplexify = require('duplexify')
|
const pull = require('pull-stream')
|
||||||
|
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 read = require('async-buffered-reader')
|
|
||||||
|
|
||||||
const etm = require('../etm')
|
const etm = require('../etm')
|
||||||
|
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 (session, cb) {
|
module.exports = function finish (state, cb) {
|
||||||
log('3. finish - start')
|
log('3. finish - start')
|
||||||
|
|
||||||
const w = etm.writer(session.insecure, session.local.cipher, session.local.mac)
|
const proto = state.protocols
|
||||||
const r = etm.reader(session.insecure, session.remote.cipher, session.remote.mac)
|
const stream = state.shake.rest()
|
||||||
session.secure = duplexify(w, r)
|
const shake = handshake({timeout: state.timeout})
|
||||||
session.secure.write(session.proposal.randIn)
|
|
||||||
|
|
||||||
// read our nonce back
|
pull(
|
||||||
read(session.secure, 16, (nonceOut2) => {
|
stream,
|
||||||
const nonceOut = session.proposal.nonceOut
|
etm.createUnboxStream(proto.remote.cipher, proto.remote.mac),
|
||||||
if (!nonceOut.equals(nonceOut2)) {
|
shake,
|
||||||
const err = new Error(`Failed to read our encrypted nonce: ${nonceOut.toString('hex')} != ${nonceOut2.toString('hex')}`)
|
etm.createBoxStream(proto.local.cipher, proto.local.mac),
|
||||||
|
stream
|
||||||
|
)
|
||||||
|
|
||||||
|
shake.handshake.write(state.proposal.in.rand)
|
||||||
|
shake.handshake.read(state.proposal.in.rand.length, (err, nonceBack) => {
|
||||||
|
const fail = (err) => {
|
||||||
log.error(err)
|
log.error(err)
|
||||||
return cb(err)
|
state.secure.resolve({
|
||||||
|
source: pull.error(err),
|
||||||
|
sink (read) {
|
||||||
|
}
|
||||||
|
})
|
||||||
|
cb(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log('3. finish - finish', nonceOut.toString('hex'), nonceOut2.toString('hex'))
|
if (err) return fail(err)
|
||||||
|
|
||||||
|
try {
|
||||||
|
crypto.verifyNonce(state, nonceBack)
|
||||||
|
} catch (err) {
|
||||||
|
return fail(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log('3. finish - finish')
|
||||||
|
|
||||||
// Awesome that's all folks.
|
// Awesome that's all folks.
|
||||||
|
state.secure.resolve(shake.handshake.rest())
|
||||||
cb()
|
cb()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,25 @@
|
|||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
const debug = require('debug')
|
|
||||||
const series = require('run-series')
|
const series = require('run-series')
|
||||||
|
|
||||||
const log = debug('libp2p:secio')
|
|
||||||
log.error = debug('libp2p:secio:error')
|
|
||||||
|
|
||||||
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
|
// Performs initial communication over insecure channel to share
|
||||||
// keys, IDs, and initiate communication, assigning all necessary params.
|
// keys, IDs, and initiate communication, assigning all necessary params.
|
||||||
module.exports = function handshake (session, cb) {
|
module.exports = function handshake (state) {
|
||||||
series([
|
series([
|
||||||
(cb) => propose(session, cb),
|
(cb) => propose(state, cb),
|
||||||
(cb) => exchange(session, cb),
|
(cb) => exchange(state, cb),
|
||||||
(cb) => finish(session, cb)
|
(cb) => finish(state, cb)
|
||||||
], cb)
|
], (err) => {
|
||||||
|
state.cleanSecrets()
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
state.shake.abort(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return state.stream
|
||||||
}
|
}
|
||||||
|
@ -1,52 +1,30 @@
|
|||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
const forge = require('node-forge')
|
|
||||||
const debug = require('debug')
|
const debug = require('debug')
|
||||||
const protobuf = require('protocol-buffers')
|
|
||||||
const path = require('path')
|
const support = require('../support')
|
||||||
const fs = require('fs')
|
const crypto = require('./crypto')
|
||||||
const PeerId = require('peer-id')
|
|
||||||
const mh = require('multihashing')
|
|
||||||
const crypto = require('libp2p-crypto')
|
|
||||||
|
|
||||||
const log = debug('libp2p:secio')
|
const log = debug('libp2p:secio')
|
||||||
log.error = debug('libp2p:secio:error')
|
log.error = debug('libp2p:secio:error')
|
||||||
|
|
||||||
// nonceSize is the size of our nonces (in bytes)
|
|
||||||
const nonceSize = 16
|
|
||||||
|
|
||||||
const pbm = protobuf(fs.readFileSync(path.join(__dirname, '../secio.proto')))
|
|
||||||
const support = require('../support')
|
|
||||||
|
|
||||||
// step 1. Propose
|
// step 1. Propose
|
||||||
// -- propose cipher suite + send pubkeys + nonce
|
// -- propose cipher suite + send pubkeys + nonce
|
||||||
module.exports = function propose (session, cb) {
|
module.exports = function propose (state, cb) {
|
||||||
log('1. propose - start')
|
log('1. propose - start')
|
||||||
|
|
||||||
const nonceOut = new Buffer(forge.random.getBytesSync(nonceSize), 'binary')
|
log('1. propose - writing proposal')
|
||||||
const proposeOut = makeProposal(session, nonceOut)
|
support.write(state, crypto.createProposal(state))
|
||||||
|
support.read(state.shake, (err, msg) => {
|
||||||
session.proposal.out = proposeOut
|
if (err) {
|
||||||
session.proposal.nonceOut = nonceOut
|
|
||||||
|
|
||||||
log('1. propse - writing proposal')
|
|
||||||
session.insecureLp.write(proposeOut)
|
|
||||||
session.insecureLp.once('data', (chunk) => {
|
|
||||||
log('1. propse - reading proposal')
|
|
||||||
|
|
||||||
let proposeIn
|
|
||||||
|
|
||||||
try {
|
|
||||||
proposeIn = readProposal(chunk)
|
|
||||||
session.proposal.in = chunk
|
|
||||||
session.proposal.randIn = proposeIn.rand
|
|
||||||
identify(session, proposeIn)
|
|
||||||
} catch (err) {
|
|
||||||
return cb(err)
|
return cb(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log('1. propose - reading proposal', msg)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
selection(session, nonceOut, proposeIn)
|
crypto.identify(state, msg)
|
||||||
|
crypto.selectProtocols(state)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return cb(err)
|
return cb(err)
|
||||||
}
|
}
|
||||||
@ -56,91 +34,3 @@ module.exports = function propose (session, cb) {
|
|||||||
cb()
|
cb()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate and send Hello packet.
|
|
||||||
// Hello = (rand, PublicKey, Supported)
|
|
||||||
function makeProposal (session, nonceOut) {
|
|
||||||
session.local.permanentPubKey = session.localKey.public
|
|
||||||
const myPubKeyBytes = session.local.permanentPubKey.bytes
|
|
||||||
|
|
||||||
return pbm.Propose.encode({
|
|
||||||
rand: nonceOut,
|
|
||||||
pubkey: myPubKeyBytes,
|
|
||||||
exchanges: support.exchanges.join(','),
|
|
||||||
ciphers: support.ciphers.join(','),
|
|
||||||
hashes: support.hashes.join(',')
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function readProposal (bytes) {
|
|
||||||
return pbm.Propose.decode(bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
function identify (session, proposeIn) {
|
|
||||||
log('1.1 identify')
|
|
||||||
|
|
||||||
session.remote.permanentPubKey = crypto.unmarshalPublicKey(proposeIn.pubkey)
|
|
||||||
session.remotePeer = PeerId.createFromPubKey(proposeIn.pubkey.toString('base64'))
|
|
||||||
|
|
||||||
log('1.1 identify - %s - identified remote peer as %s', session.localPeer.toB58String(), session.remotePeer.toB58String())
|
|
||||||
}
|
|
||||||
|
|
||||||
function selection (session, nonceOut, proposeIn) {
|
|
||||||
log('1.2 selection')
|
|
||||||
|
|
||||||
const local = {
|
|
||||||
pubKeyBytes: session.local.permanentPubKey.bytes,
|
|
||||||
exchanges: support.exchanges,
|
|
||||||
hashes: support.hashes,
|
|
||||||
ciphers: support.ciphers,
|
|
||||||
nonce: nonceOut
|
|
||||||
}
|
|
||||||
|
|
||||||
const remote = {
|
|
||||||
pubKeyBytes: proposeIn.pubkey,
|
|
||||||
exchanges: proposeIn.exchanges.split(','),
|
|
||||||
hashes: proposeIn.hashes.split(','),
|
|
||||||
ciphers: proposeIn.ciphers.split(','),
|
|
||||||
nonce: proposeIn.rand
|
|
||||||
}
|
|
||||||
|
|
||||||
let selected = selectBest(local, remote)
|
|
||||||
session.proposal.order = selected.order
|
|
||||||
|
|
||||||
session.local.curveT = selected.curveT
|
|
||||||
session.local.cipherT = selected.cipherT
|
|
||||||
session.local.hashT = selected.hashT
|
|
||||||
|
|
||||||
// we use the same params for both directions (must choose same curve)
|
|
||||||
// WARNING: if they dont SelectBest the same way, this won't work...
|
|
||||||
session.remote.curveT = session.local.curveT
|
|
||||||
session.remote.cipherT = session.local.cipherT
|
|
||||||
session.remote.hashT = session.local.hashT
|
|
||||||
}
|
|
||||||
|
|
||||||
function selectBest (local, remote) {
|
|
||||||
const oh1 = digest(Buffer.concat([
|
|
||||||
remote.pubKeyBytes,
|
|
||||||
local.nonce
|
|
||||||
]))
|
|
||||||
const oh2 = digest(Buffer.concat([
|
|
||||||
local.pubKeyBytes,
|
|
||||||
remote.nonce
|
|
||||||
]))
|
|
||||||
const order = Buffer.compare(oh1, oh2)
|
|
||||||
|
|
||||||
if (order === 0) {
|
|
||||||
throw new Error('you are trying to talk to yourself')
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
curveT: support.theBest(order, local.exchanges, remote.exchanges),
|
|
||||||
cipherT: support.theBest(order, local.ciphers, remote.ciphers),
|
|
||||||
hashT: support.theBest(order, local.hashes, remote.hashes),
|
|
||||||
order
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function digest (buf) {
|
|
||||||
return mh.digest(buf, 'sha2-256', buf.length)
|
|
||||||
}
|
|
||||||
|
115
src/index.js
115
src/index.js
@ -1,119 +1,36 @@
|
|||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
const duplexify = require('duplexify')
|
const pull = require('pull-stream')
|
||||||
const lpstream = require('length-prefixed-stream')
|
const Connection = require('interface-connection').Connection
|
||||||
const PassThrough = require('readable-stream').PassThrough
|
|
||||||
|
|
||||||
const handshake = require('./handshake')
|
const handshake = require('./handshake')
|
||||||
|
const State = require('./state')
|
||||||
|
|
||||||
exports.SecureSession = class SecureSession {
|
exports.SecureSession = class SecureSession {
|
||||||
constructor (local, key, insecure) {
|
constructor (local, key, insecure) {
|
||||||
this.localKey = key
|
if (!local) {
|
||||||
this.localPeer = local
|
|
||||||
this.sharedSecret = null
|
|
||||||
this.local = {}
|
|
||||||
this.remote = {}
|
|
||||||
this.proposal = {}
|
|
||||||
this.insecure = insecure
|
|
||||||
this.secure = null
|
|
||||||
const e = lpstream.encode()
|
|
||||||
const d = lpstream.decode()
|
|
||||||
this.insecureLp = duplexify(e, d)
|
|
||||||
|
|
||||||
e.pipe(this.insecure)
|
|
||||||
this.insecure.pipe(d)
|
|
||||||
|
|
||||||
if (!this.localPeer) {
|
|
||||||
throw new Error('no local id provided')
|
throw new Error('no local id provided')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.localKey) {
|
if (!key) {
|
||||||
throw new Error('no local private key provided')
|
throw new Error('no local private key provided')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enable when implemented in js-peer-id
|
|
||||||
// if (!this.localPeer.matchesPrivateKey(this.localKey)) {
|
|
||||||
// throw new Error('peer.ID does not match privateKey')
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (!insecure) {
|
if (!insecure) {
|
||||||
throw new Error('no insecure stream provided')
|
throw new Error('no insecure stream provided')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.state = new State(local, key)
|
||||||
|
this.insecure = insecure
|
||||||
|
|
||||||
|
pull(
|
||||||
|
this.insecure,
|
||||||
|
handshake(this.state),
|
||||||
|
this.insecure
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
secureStream () {
|
get secure () {
|
||||||
let handshaked = false
|
return new Connection(this.state.secure, this.insecure)
|
||||||
const reader = new PassThrough()
|
|
||||||
const writer = new PassThrough()
|
|
||||||
const dp = duplexify(writer, reader)
|
|
||||||
const originalRead = reader.read.bind(reader)
|
|
||||||
const originalWrite = writer.write.bind(writer)
|
|
||||||
|
|
||||||
const doHandshake = () => {
|
|
||||||
if (handshaked) return
|
|
||||||
|
|
||||||
handshaked = true
|
|
||||||
|
|
||||||
// Restore methods to avoid overhead
|
|
||||||
reader.read = originalRead
|
|
||||||
writer.write = originalWrite
|
|
||||||
|
|
||||||
this.handshake((err) => {
|
|
||||||
if (err) {
|
|
||||||
dp.emit('error', err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pipe things together
|
|
||||||
writer.pipe(this.secure)
|
|
||||||
this.secure.pipe(reader)
|
|
||||||
|
|
||||||
dp.uncork()
|
|
||||||
dp.resume()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// patch to detect first read
|
|
||||||
reader.read = (size) => {
|
|
||||||
doHandshake()
|
|
||||||
originalRead(size)
|
|
||||||
}
|
|
||||||
|
|
||||||
// patch to detect first write
|
|
||||||
writer.write = (chunk, encoding, callback) => {
|
|
||||||
doHandshake()
|
|
||||||
originalWrite(chunk, encoding, callback)
|
|
||||||
}
|
|
||||||
|
|
||||||
dp.cork()
|
|
||||||
dp.pause()
|
|
||||||
|
|
||||||
return dp
|
|
||||||
}
|
|
||||||
|
|
||||||
handshake (cb) {
|
|
||||||
// TODO: figure out how to best handle the handshake timeout
|
|
||||||
if (this._handshakeLock) {
|
|
||||||
return cb(new Error('handshake already in progress'))
|
|
||||||
}
|
|
||||||
|
|
||||||
this._handshakeLock = true
|
|
||||||
|
|
||||||
const finish = (err) => {
|
|
||||||
this._handshakeLock = false
|
|
||||||
cb(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this._handshakeDone) {
|
|
||||||
return finish()
|
|
||||||
}
|
|
||||||
|
|
||||||
handshake(this, (err) => {
|
|
||||||
if (err) {
|
|
||||||
return finish(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
this._handshakeDone = true
|
|
||||||
finish()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
67
src/state.js
Normal file
67
src/state.js
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
'use strict'
|
||||||
|
|
||||||
|
const handshake = require('pull-handshake')
|
||||||
|
const deferred = require('pull-defer')
|
||||||
|
|
||||||
|
class State {
|
||||||
|
constructor (id, key, timeout, cb) {
|
||||||
|
this.setup()
|
||||||
|
this.id.local = id
|
||||||
|
this.key.local = key
|
||||||
|
this.timeout = timeout || 60 * 1000
|
||||||
|
cb = cb || (() => {})
|
||||||
|
|
||||||
|
this.secure = deferred.duplex()
|
||||||
|
this.stream = handshake({timeout: this.timeout}, cb)
|
||||||
|
this.shake = this.stream.handshake
|
||||||
|
delete this.stream.handshake
|
||||||
|
}
|
||||||
|
|
||||||
|
setup () {
|
||||||
|
this.id = {
|
||||||
|
local: null,
|
||||||
|
remote: null
|
||||||
|
}
|
||||||
|
|
||||||
|
this.key = {
|
||||||
|
local: null,
|
||||||
|
remote: null
|
||||||
|
}
|
||||||
|
|
||||||
|
this.shake = null
|
||||||
|
|
||||||
|
this.cleanSecrets()
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove all data from the handshake that is not needed anymore
|
||||||
|
cleanSecrets () {
|
||||||
|
this.shared = {}
|
||||||
|
|
||||||
|
this.ephemeralKey = {
|
||||||
|
local: null,
|
||||||
|
remote: null
|
||||||
|
}
|
||||||
|
|
||||||
|
this.proposal = {
|
||||||
|
in: null,
|
||||||
|
out: null
|
||||||
|
}
|
||||||
|
|
||||||
|
this.proposalEncoded = {
|
||||||
|
in: null,
|
||||||
|
out: null
|
||||||
|
}
|
||||||
|
|
||||||
|
this.protocols = {
|
||||||
|
local: null,
|
||||||
|
remote: null
|
||||||
|
}
|
||||||
|
|
||||||
|
this.exchange = {
|
||||||
|
in: null,
|
||||||
|
out: null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = State
|
@ -1,6 +1,9 @@
|
|||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
|
const mh = require('multihashing')
|
||||||
const forge = require('node-forge')
|
const forge = require('node-forge')
|
||||||
|
const lp = require('pull-length-prefixed')
|
||||||
|
const pull = require('pull-stream')
|
||||||
|
|
||||||
exports.exchanges = [
|
exports.exchanges = [
|
||||||
'P-256',
|
'P-256',
|
||||||
@ -86,3 +89,55 @@ function makeCipher (cipherType, iv, key) {
|
|||||||
|
|
||||||
throw new Error(`unrecognized cipher type: ${cipherType}`)
|
throw new Error(`unrecognized cipher type: ${cipherType}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exports.randomBytes = (nonceSize) => {
|
||||||
|
return new Buffer(forge.random.getBytesSync(nonceSize), 'binary')
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.selectBest = (local, remote) => {
|
||||||
|
const oh1 = exports.digest(Buffer.concat([
|
||||||
|
remote.pubKeyBytes,
|
||||||
|
local.nonce
|
||||||
|
]))
|
||||||
|
const oh2 = exports.digest(Buffer.concat([
|
||||||
|
local.pubKeyBytes,
|
||||||
|
remote.nonce
|
||||||
|
]))
|
||||||
|
const order = Buffer.compare(oh1, oh2)
|
||||||
|
|
||||||
|
if (order === 0) {
|
||||||
|
throw new Error('you are trying to talk to yourself')
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
curveT: exports.theBest(order, local.exchanges, remote.exchanges),
|
||||||
|
cipherT: exports.theBest(order, local.ciphers, remote.ciphers),
|
||||||
|
hashT: exports.theBest(order, local.hashes, remote.hashes),
|
||||||
|
order
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.digest = (buf) => {
|
||||||
|
return mh.digest(buf, 'sha2-256', buf.length)
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.write = function write (state, msg, cb) {
|
||||||
|
cb = cb || (() => {})
|
||||||
|
pull(
|
||||||
|
pull.values([
|
||||||
|
msg
|
||||||
|
]),
|
||||||
|
lp.encode({fixed: true, bytes: 4}),
|
||||||
|
pull.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)
|
||||||
|
}
|
||||||
|
@ -1,92 +1,45 @@
|
|||||||
/* eslint-env mocha */
|
/* eslint-env mocha */
|
||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
|
const pair = require('pull-pair/duplex')
|
||||||
const expect = require('chai').expect
|
const expect = require('chai').expect
|
||||||
const through = require('through2')
|
|
||||||
const bl = require('bl')
|
|
||||||
const PeerId = require('peer-id')
|
const PeerId = require('peer-id')
|
||||||
const crypto = require('libp2p-crypto')
|
const crypto = require('libp2p-crypto')
|
||||||
const streamPair = require('stream-pair')
|
|
||||||
const parallel = require('run-parallel')
|
const parallel = require('run-parallel')
|
||||||
const series = require('run-series')
|
const series = require('run-series')
|
||||||
const ms = require('multistream-select')
|
const ms = require('multistream-select')
|
||||||
|
const pull = require('pull-stream')
|
||||||
const Listener = ms.Listener
|
const Listener = ms.Listener
|
||||||
const Dialer = ms.Dialer
|
const Dialer = ms.Dialer
|
||||||
|
|
||||||
const SecureSession = require('../src').SecureSession
|
const SecureSession = require('../src').SecureSession
|
||||||
|
|
||||||
describe('libp2p-secio', () => {
|
describe('libp2p-secio', () => {
|
||||||
describe('insecure length prefixed stream', () => {
|
|
||||||
it('encodes', (done) => {
|
|
||||||
const id = PeerId.create({bits: 64})
|
|
||||||
const key = {}
|
|
||||||
const insecure = through()
|
|
||||||
const s = new SecureSession(id, key, insecure)
|
|
||||||
|
|
||||||
// encoded on raw
|
|
||||||
s.insecure.pipe(bl((err, res) => {
|
|
||||||
expect(err).to.not.exist
|
|
||||||
expect(res.toString()).to.be.eql('\u0005hello\u0005world')
|
|
||||||
done()
|
|
||||||
}))
|
|
||||||
|
|
||||||
s.insecureLp.write('hello')
|
|
||||||
s.insecureLp.write('world')
|
|
||||||
insecure.end()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('decodes', (done) => {
|
|
||||||
const id = PeerId.create({bits: 64})
|
|
||||||
const key = {}
|
|
||||||
const insecure = through()
|
|
||||||
const s = new SecureSession(id, key, insecure)
|
|
||||||
|
|
||||||
// encoded on raw
|
|
||||||
s.insecureLp.pipe(bl((err, res) => {
|
|
||||||
expect(err).to.not.exist
|
|
||||||
expect(res.toString()).to.be.eql('helloworld')
|
|
||||||
done()
|
|
||||||
}))
|
|
||||||
|
|
||||||
s.insecure.write('\u0005hello')
|
|
||||||
s.insecure.write('\u0005world')
|
|
||||||
s.insecureLp.end()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('all together now', (done) => {
|
|
||||||
const pair = streamPair.create()
|
|
||||||
|
|
||||||
const local = createSession(pair)
|
|
||||||
const remote = createSession(pair.other)
|
|
||||||
remote.session.insecureLp.pipe(bl((err, res) => {
|
|
||||||
if (err) throw err
|
|
||||||
expect(res.toString()).to.be.eql('hello world')
|
|
||||||
done()
|
|
||||||
}))
|
|
||||||
|
|
||||||
local.session.insecureLp.write('hello ')
|
|
||||||
local.session.insecureLp.write('world')
|
|
||||||
pair.end()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('upgrades a connection', (done) => {
|
it('upgrades a connection', (done) => {
|
||||||
const pair = streamPair.create()
|
const p = pair()
|
||||||
|
|
||||||
const local = createSession(pair)
|
const local = createSession(p[0])
|
||||||
const remote = createSession(pair.other)
|
const remote = createSession(p[1])
|
||||||
const localSecure = local.session.secureStream()
|
const localSecure = local.session.secure
|
||||||
localSecure.write('hello world')
|
|
||||||
|
|
||||||
const remoteSecure = remote.session.secureStream()
|
pull(
|
||||||
remoteSecure.once('data', (chunk) => {
|
pull.values(['hello world']),
|
||||||
expect(chunk.toString()).to.be.eql('hello world')
|
localSecure
|
||||||
done()
|
)
|
||||||
})
|
|
||||||
|
const remoteSecure = remote.session.secure
|
||||||
|
pull(
|
||||||
|
remoteSecure,
|
||||||
|
pull.collect((err, chunks) => {
|
||||||
|
expect(err).to.not.exist
|
||||||
|
expect(chunks).to.be.eql([new Buffer('hello world')])
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('works over multistream', (done) => {
|
it('works over multistream', (done) => {
|
||||||
const pair = streamPair.create()
|
const p = pair()
|
||||||
|
|
||||||
const listener = new Listener()
|
const listener = new Listener()
|
||||||
const dialer = new Dialer()
|
const dialer = new Dialer()
|
||||||
@ -94,22 +47,29 @@ describe('libp2p-secio', () => {
|
|||||||
let remote
|
let remote
|
||||||
series([
|
series([
|
||||||
(cb) => parallel([
|
(cb) => parallel([
|
||||||
(cb) => listener.handle(pair, cb),
|
(cb) => listener.handle(p[0], cb),
|
||||||
(cb) => dialer.handle(pair.other, cb)
|
(cb) => dialer.handle(p[1], cb)
|
||||||
], cb),
|
], cb),
|
||||||
(cb) => {
|
(cb) => {
|
||||||
listener.addHandler('/banana/1.0.0', (conn) => {
|
listener.addHandler('/banana/1.0.0', (conn) => {
|
||||||
local = createSession(conn).session.secureStream()
|
local = createSession(conn).session.secure
|
||||||
local.once('data', (res) => {
|
pull(
|
||||||
expect(res.toString()).to.be.eql('hello world')
|
local,
|
||||||
done()
|
pull.collect((err, chunks) => {
|
||||||
})
|
expect(err).to.not.exist
|
||||||
|
expect(chunks).to.be.eql([new Buffer('hello world')])
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
)
|
||||||
})
|
})
|
||||||
cb()
|
cb()
|
||||||
},
|
},
|
||||||
(cb) => dialer.select('/banana/1.0.0', (err, conn) => {
|
(cb) => dialer.select('/banana/1.0.0', (err, conn) => {
|
||||||
remote = createSession(conn).session.secureStream()
|
remote = createSession(conn).session.secure
|
||||||
remote.write('hello world')
|
pull(
|
||||||
|
pull.values(['hello world']),
|
||||||
|
remote
|
||||||
|
)
|
||||||
cb(err)
|
cb(err)
|
||||||
})
|
})
|
||||||
], (err) => {
|
], (err) => {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user