Skip to content
This repository was archived by the owner on Feb 24, 2021. It is now read-only.

Commit f32239f

Browse files
authored
Merge pull request #8 from libp2p/pull
[WIP] Refactor to use pull streams
2 parents b948ad6 + 10a4cf0 commit f32239f

File tree

13 files changed

+493
-448
lines changed

13 files changed

+493
-448
lines changed

README.md

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@
44
[![](https://img.shields.io/badge/project-IPFS-blue.svg?style=flat-square)](http://ipfs.io/)
55
[![](https://img.shields.io/badge/freenode-%23ipfs-blue.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23ipfs)
66
[![standard-readme compliant](https://img.shields.io/badge/standard--readme-OK-green.svg?style=flat-square)](https://github.com/RichardLitt/standard-readme)
7-
[![Coverage Status](https://coveralls.io/repos/github/ipfs/js-libp2p-secio/badge.svg?branch=master)](https://coveralls.io/github/ipfs/js-libp2p-secio?branch=master)
8-
[![Travis CI](https://travis-ci.org/ipfs/js-libp2p-secio.svg?branch=master)](https://travis-ci.org/ipfs/js-libp2p-secio)
9-
[![Circle CI](https://circleci.com/gh/ipfs/js-libp2p-secio.svg?style=svg)](https://circleci.com/gh/ipfs/js-libp2p-secio)
10-
[![Dependency Status](https://david-dm.org/ipfs/js-libp2p-secio.svg?style=flat-square)](https://david-dm.org/ipfs/js-libp2p-secio) [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/feross/standard)
7+
[![Coverage Status](https://coveralls.io/repos/github/libp2p/js-libp2p-secio/badge.svg?branch=master)](https://coveralls.io/github/libp2p/js-libp2p-secio?branch=master)
8+
[![Travis CI](https://travis-ci.org/libp2p/js-libp2p-secio.svg?branch=master)](https://travis-ci.org/libp2p/js-libp2p-secio)
9+
[![Circle CI](https://circleci.com/gh/libp2p/js-libp2p-secio.svg?style=svg)](https://circleci.com/gh/libp2p/js-libp2p-secio)
10+
[![Dependency Status](https://david-dm.org/libp2p/js-libp2p-secio.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-secio) [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/feross/standard)
1111

1212
> Secio implementation in JavaScript
1313
14-
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).
14+
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).
1515

1616
## Table of Contents
1717

@@ -24,17 +24,55 @@ This repo contains the JavaScript implementation of secio, an encryption protoco
2424
## Install
2525

2626
```sh
27-
npm install --save libp2p-secio
27+
npm install libp2p-secio
2828
```
2929

3030
## Usage
3131

3232
```js
33-
const libp2pSecio = require('libp2p-secio')
33+
const secio = require('libp2p-secio')
3434
```
3535

3636
## API
3737

38+
### `SecureSession`
39+
40+
#### `constructor(id, key, insecure)`
41+
42+
- `id: PeerId` - The id of the node.
43+
- `key: RSAPrivateKey` - The private key of the node.
44+
- `insecure: PullStream` - The insecure connection.
45+
46+
### `.secure`
47+
48+
Returns the `insecure` connection provided, wrapped with secio. This is a pull-stream.
49+
50+
### This module uses `pull-streams`
51+
52+
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).
53+
54+
You can learn more about pull-streams at:
55+
56+
- [The history of Node.js streams, nodebp April 2014](https://www.youtube.com/watch?v=g5ewQEuXjsQ)
57+
- [The history of streams, 2016](http://dominictarr.com/post/145135293917/history-of-streams)
58+
- [pull-streams, the simple streaming primitive](http://dominictarr.com/post/149248845122/pull-streams-pull-streams-are-a-very-simple)
59+
- [pull-streams documentation](https://pull-stream.github.io/)
60+
61+
#### Converting `pull-streams` to Node.js Streams
62+
63+
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:
64+
65+
```js
66+
const pullToStream = require('pull-stream-to-stream')
67+
68+
const nodeStreamInstance = pullToStream(pullStreamInstance)
69+
// nodeStreamInstance is an instance of a Node.js Stream
70+
```
71+
72+
To learn more about this utility, visit https://pull-stream.github.io/#pull-stream-to-stream.
73+
74+
75+
3876
## Contribute
3977

4078
Feel free to join in. All welcome. Open an [issue](https://github.com/ipfs/js-libp2p-secio/issues)!

package.json

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,27 +25,27 @@
2525
"author": "Friedel Ziegelmayer <dignifiedqurie@gmail.com>",
2626
"license": "MIT",
2727
"dependencies": {
28-
"async-buffered-reader": "^1.2.1",
2928
"debug": "^2.2.0",
30-
"duplexify": "^3.4.3",
31-
"length-prefixed-stream": "^1.5.0",
29+
"interface-connection": "^0.2.1",
3230
"libp2p-crypto": "^0.5.0",
3331
"multihashing": "^0.2.1",
34-
"node-forge": "^0.6.39",
32+
"node-forge": "^0.6.42",
3533
"peer-id": "^0.7.0",
3634
"protocol-buffers": "^3.1.6",
37-
"readable-stream": "2.1.4",
38-
"run-series": "^1.1.4",
39-
"through2": "^2.0.1"
35+
"pull-defer": "^0.2.2",
36+
"pull-handshake": "^1.1.4",
37+
"pull-length-prefixed": "^1.2.0",
38+
"pull-stream": "^3.4.3",
39+
"pull-through": "^1.0.18",
40+
"run-series": "^1.1.4"
4041
},
4142
"devDependencies": {
42-
"aegir": "^6.0.0",
43-
"bl": "^1.1.2",
43+
"aegir": "^8.0.0",
4444
"chai": "^3.5.0",
45-
"multistream-select": "^0.10.0",
45+
"multistream-select": "^0.11.0",
4646
"pre-commit": "^1.1.3",
47-
"run-parallel": "^1.1.6",
48-
"stream-pair": "^1.0.3"
47+
"pull-pair": "^1.1.0",
48+
"run-parallel": "^1.1.6"
4949
},
5050
"pre-commit": [
5151
"lint",
@@ -65,4 +65,4 @@
6565
"contributors": [
6666
"dignifiedquire <dignifiedquire@gmail.com>"
6767
]
68-
}
68+
}

src/etm.js

Lines changed: 28 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,44 @@
11
'use strict'
22

3-
const through = require('through2')
4-
const lpm = require('length-prefixed-stream')
3+
const through = require('pull-through')
4+
const pull = require('pull-stream')
5+
const lp = require('pull-length-prefixed')
56

67
const toForgeBuffer = require('./support').toForgeBuffer
78

8-
exports.writer = function etmWriter (insecure, cipher, mac) {
9-
const encode = lpm.encode()
10-
const pt = through(function (chunk, enc, cb) {
9+
const lpOpts = {
10+
fixed: true,
11+
bytes: 4
12+
}
13+
14+
exports.createBoxStream = (cipher, mac) => {
15+
const pt = through(function (chunk) {
1116
cipher.update(toForgeBuffer(chunk))
1217

1318
if (cipher.output.length() > 0) {
1419
const data = new Buffer(cipher.output.getBytes(), 'binary')
15-
mac.update(data)
16-
const macBuffer = new Buffer(mac.getMac().getBytes(), 'binary')
20+
mac.update(data.toString('binary'))
21+
const macBuffer = new Buffer(mac.digest().getBytes(), 'binary')
1722

18-
this.push(Buffer.concat([data, macBuffer]))
23+
this.queue(Buffer.concat([data, macBuffer]))
1924
// reset hmac
2025
mac.start(null, null)
2126
}
22-
23-
cb()
2427
})
2528

26-
pt.pipe(encode).pipe(insecure)
27-
28-
return pt
29+
return pull(
30+
pt,
31+
lp.encode(lpOpts)
32+
)
2933
}
3034

31-
exports.reader = function etmReader (insecure, decipher, mac) {
32-
const decode = lpm.decode()
33-
const pt = through(function (chunk, enc, cb) {
35+
exports.createUnboxStream = (decipher, mac) => {
36+
const pt = through(function (chunk) {
3437
const l = chunk.length
3538
const macSize = mac.getMac().length()
3639

3740
if (l < macSize) {
38-
return cb(new Error(`buffer (${l}) shorter than MAC size (${macSize})`))
41+
return this.emit('error', new Error(`buffer (${l}) shorter than MAC size (${macSize})`))
3942
}
4043

4144
const mark = l - macSize
@@ -45,26 +48,26 @@ exports.reader = function etmReader (insecure, decipher, mac) {
4548
// Clear out any previous data
4649
mac.start(null, null)
4750

48-
mac.update(data)
51+
mac.update(data.toString('binary'))
4952
const expected = new Buffer(mac.getMac().getBytes(), 'binary')
53+
5054
// reset hmac
5155
mac.start(null, null)
5256
if (!macd.equals(expected)) {
53-
return cb(new Error(`MAC Invalid: ${macd.toString('hex')} != ${expected.toString('hex')}`))
57+
return this.emit('error', new Error(`MAC Invalid: ${macd.toString('hex')} != ${expected.toString('hex')}`))
5458
}
5559

5660
// all good, decrypt
5761
decipher.update(toForgeBuffer(data))
5862

5963
if (decipher.output.length() > 0) {
6064
const data = new Buffer(decipher.output.getBytes(), 'binary')
61-
this.push(data)
65+
this.queue(data)
6266
}
63-
64-
cb()
6567
})
6668

67-
insecure.pipe(decode).pipe(pt)
68-
69-
return pt
69+
return pull(
70+
lp.decode(lpOpts),
71+
pt
72+
)
7073
}

src/handshake/crypto.js

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
'use strict'
2+
3+
const protobuf = require('protocol-buffers')
4+
const path = require('path')
5+
const fs = require('fs')
6+
const PeerId = require('peer-id')
7+
const crypto = require('libp2p-crypto')
8+
const debug = require('debug')
9+
const log = debug('libp2p:secio')
10+
log.error = debug('libp2p:secio:error')
11+
12+
const pbm = protobuf(fs.readFileSync(path.join(__dirname, 'secio.proto')))
13+
14+
const support = require('../support')
15+
16+
// nonceSize is the size of our nonces (in bytes)
17+
const nonceSize = 16
18+
19+
exports.createProposal = (state) => {
20+
state.proposal.out = {
21+
rand: support.randomBytes(nonceSize),
22+
pubkey: state.key.local.public.bytes,
23+
exchanges: support.exchanges.join(','),
24+
ciphers: support.ciphers.join(','),
25+
hashes: support.hashes.join(',')
26+
}
27+
28+
state.proposalEncoded.out = pbm.Propose.encode(state.proposal.out)
29+
return state.proposalEncoded.out
30+
}
31+
32+
exports.createExchange = (state) => {
33+
const res = crypto.generateEphemeralKeyPair(state.protocols.local.curveT)
34+
state.ephemeralKey.local = res.key
35+
state.shared.generate = res.genSharedKey
36+
37+
// Gather corpus to sign.
38+
const selectionOut = Buffer.concat([
39+
state.proposalEncoded.out,
40+
state.proposalEncoded.in,
41+
state.ephemeralKey.local
42+
])
43+
44+
state.exchange.out = {
45+
epubkey: state.ephemeralKey.local,
46+
signature: new Buffer(state.key.local.sign(selectionOut), 'binary')
47+
}
48+
49+
return pbm.Exchange.encode(state.exchange.out)
50+
}
51+
52+
exports.identify = (state, msg) => {
53+
log('1.1 identify')
54+
55+
state.proposalEncoded.in = msg
56+
state.proposal.in = pbm.Propose.decode(msg)
57+
const pubkey = state.proposal.in.pubkey
58+
59+
console.log(state.proposal.in)
60+
61+
state.key.remote = crypto.unmarshalPublicKey(pubkey)
62+
state.id.remote = PeerId.createFromPubKey(pubkey.toString('base64'))
63+
64+
log('1.1 identify - %s - identified remote peer as %s', state.id.local.toB58String(), state.id.remote.toB58String())
65+
}
66+
67+
exports.selectProtocols = (state) => {
68+
log('1.2 selection')
69+
70+
const local = {
71+
pubKeyBytes: state.key.local.public.bytes,
72+
exchanges: support.exchanges,
73+
hashes: support.hashes,
74+
ciphers: support.ciphers,
75+
nonce: state.proposal.out.rand
76+
}
77+
78+
const remote = {
79+
pubKeyBytes: state.proposal.in.pubkey,
80+
exchanges: state.proposal.in.exchanges.split(','),
81+
hashes: state.proposal.in.hashes.split(','),
82+
ciphers: state.proposal.in.ciphers.split(','),
83+
nonce: state.proposal.in.rand
84+
}
85+
86+
let selected = support.selectBest(local, remote)
87+
// we use the same params for both directions (must choose same curve)
88+
// WARNING: if they dont SelectBest the same way, this won't work...
89+
state.protocols.remote = {
90+
order: selected.order,
91+
curveT: selected.curveT,
92+
cipherT: selected.cipherT,
93+
hashT: selected.hashT
94+
}
95+
96+
state.protocols.local = {
97+
order: selected.order,
98+
curveT: selected.curveT,
99+
cipherT: selected.cipherT,
100+
hashT: selected.hashT
101+
}
102+
}
103+
104+
exports.verify = (state, msg) => {
105+
log('2.1. verify')
106+
107+
state.exchange.in = pbm.Exchange.decode(msg)
108+
state.ephemeralKey.remote = state.exchange.in.epubkey
109+
110+
const selectionIn = Buffer.concat([
111+
state.proposalEncoded.in,
112+
state.proposalEncoded.out,
113+
state.ephemeralKey.remote
114+
])
115+
116+
const sigOk = state.key.remote.verify(selectionIn, state.exchange.in.signature)
117+
118+
if (!sigOk) {
119+
throw new Error('Bad signature')
120+
}
121+
122+
log('2.1. verify - signature verified')
123+
}
124+
125+
exports.generateKeys = (state) => {
126+
log('2.2. keys')
127+
128+
state.shared.secret = state.shared.generate(state.exchange.in.epubkey)
129+
130+
const keys = crypto.keyStretcher(
131+
state.protocols.local.cipherT,
132+
state.protocols.local.hashT,
133+
state.shared.secret
134+
)
135+
136+
// use random nonces to decide order.
137+
if (state.protocols.local.order > 0) {
138+
state.protocols.local.keys = keys.k1
139+
state.protocols.remote.keys = keys.k2
140+
} else if (state.protocols.local.order < 0) {
141+
// swap
142+
state.protocols.local.keys = keys.k2
143+
state.protocols.remote.keys = keys.k1
144+
} else {
145+
// we should've bailed before state. but if not, bail here.
146+
throw new Error('you are trying to talk to yourself')
147+
}
148+
149+
log('2.3. mac + cipher')
150+
151+
support.makeMacAndCipher(state.protocols.local)
152+
support.makeMacAndCipher(state.protocols.remote)
153+
}
154+
155+
exports.verifyNonce = (state, n2) => {
156+
const n1 = state.proposal.out.rand
157+
158+
if (n1.equals(n2)) return
159+
160+
throw new Error(
161+
`Failed to read our encrypted nonce: ${n1.toString('hex')} != ${n2.toString('hex')}`
162+
)
163+
}

0 commit comments

Comments
 (0)