Skip to content
This repository was archived by the owner on Aug 23, 2019. It is now read-only.

Commit 9f5038d

Browse files
author
Jacob Heun
authored
Merge pull request #1 from libp2p/feat/initial
Initial development
2 parents 4eb199b + 5652e1a commit 9f5038d

File tree

13 files changed

+628
-2
lines changed

13 files changed

+628
-2
lines changed

.gitignore

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
yarn.lock
2+
package-lock.json
3+
**/node_modules/
4+
**/*.log
5+
test/repo-tests*
6+
7+
# Logs
8+
logs
9+
*.log
10+
11+
coverage
12+
.nyc_output/
13+
14+
# Runtime data
15+
pids
16+
*.pid
17+
*.seed
18+
19+
# Directory for instrumented libs generated by jscoverage/JSCover
20+
lib-cov
21+
22+
# Coverage directory used by tools like istanbul
23+
coverage
24+
25+
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
26+
.grunt
27+
28+
# node-waf configuration
29+
.lock-wscript
30+
31+
build
32+
33+
# Dependency directory
34+
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
35+
node_modules
36+
37+
lib
38+
dist
39+
docs

README.md

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,79 @@
1-
# js-libp2p-pnet
2-
A module providing swarm protection for libp2p
1+
js-libp2p-pnet
2+
==================
3+
4+
Connection protection management for libp2p leveraging PSK encryption via XSalsa20.
5+
6+
## Lead Maintainer
7+
8+
[Jacob Heun](https://github.com/jacobheun)
9+
10+
## Table of Contents
11+
12+
- [Usage](#usage)
13+
- [Private Shared Keys (PSK)](#private-shared-keys)
14+
- [PSK Generation](#psk-generation)
15+
- [Contribute](#contribute)
16+
- [License](#license)
17+
18+
## Usage
19+
20+
```js
21+
const Protector = require('libp2p-pnet')
22+
const protector = new Protector(swarmKeyBuffer)
23+
const privateConnection = protector.protect(myPublicConnection, (err) => { })
24+
```
25+
26+
### Private Shared Keys
27+
28+
Private Shared Keys are expected to be in the following format:
29+
30+
```
31+
/key/swarm/psk/1.0.0/
32+
/base16/
33+
dffb7e3135399a8b1612b2aaca1c36a3a8ac2cd0cca51ceeb2ced87d308cac6d
34+
```
35+
36+
### PSK Generation
37+
38+
A utility method has been created to generate a key for your private network. You can
39+
use one of the methods below to generate your key.
40+
41+
#### From libp2p-pnet
42+
43+
If you have libp2p-pnet locally, you can run the following from the projects root.
44+
45+
```sh
46+
node src/key-generator.js > swarm.key
47+
```
48+
49+
#### From a module using libp2p
50+
51+
If you have a module locally that depends on libp2p-pnet, you can run the following from
52+
that project, assuming the node_modules are installed.
53+
54+
```sh
55+
node -e "require('libp2p-pnet').generate(process.stdout)" > swarm.key
56+
```
57+
58+
#### Programmatically
59+
60+
```js
61+
const writeKey = require('libp2p-pnet').generate
62+
const swarmKey = Buffer.alloc(95)
63+
writeKey(swarmKey)
64+
fs.writeFileSync('swarm.key', swarmKey)
65+
```
66+
67+
## Contribute
68+
69+
There are some ways you can make this module better:
70+
71+
- Consult our [open issues](https://github.com/libp2p/js-libp2p-pnet/issues) and take on one of them
72+
73+
This repository falls under the IPFS [Code of Conduct](https://github.com/ipfs/community/blob/master/code-of-conduct.md).
74+
75+
[![](https://cdn.rawgit.com/jbenet/contribute-ipfs-gif/master/img/contribute.gif)](https://github.com/ipfs/community/blob/master/contributing.md)
76+
77+
## License
78+
79+
[MIT](LICENSE)

ci/Jenkinsfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// Warning: This file is automatically synced from https://github.com/ipfs/ci-sync so if you want to change it, please change it there and ask someone to sync all repositories.
2+
javascript()

circle.yml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Warning: This file is automatically synced from https://github.com/ipfs/ci-sync so if you want to change it, please change it there and ask someone to sync all repositories.
2+
machine:
3+
node:
4+
version: stable
5+
6+
test:
7+
pre:
8+
- npm run lint
9+
post:
10+
- npm run coverage -- --upload --providers coveralls
11+
12+
dependencies:
13+
pre:
14+
- google-chrome --version
15+
- curl -L -o google-chrome.deb https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
16+
- sudo dpkg -i google-chrome.deb || true
17+
- sudo apt-get update
18+
- sudo apt-get install -f
19+
- sudo apt-get install --only-upgrade lsb-base
20+
- sudo dpkg -i google-chrome.deb
21+
- google-chrome --version

package.json

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
{
2+
"name": "libp2p-pnet",
3+
"version": "1.0.0",
4+
"description": "Private Network protection implementation",
5+
"leadMaintainer": "Jacob Heun <[email protected]>",
6+
"main": "src/index.js",
7+
"scripts": {
8+
"lint": "aegir lint",
9+
"build": "aegir build",
10+
"test": "aegir test",
11+
"test:node": "aegir test -t node",
12+
"test:browser": "aegir test -t browser",
13+
"release": "aegir release -t node browser",
14+
"release-minor": "aegir release --type minor -t node browser",
15+
"release-major": "aegir release --type major -t node browser",
16+
"coverage": "aegir coverage",
17+
"coverage-publish": "aegir coverage publish",
18+
"benchmark": "node benchmarks/send.js"
19+
},
20+
"keywords": [
21+
"IPFS",
22+
"libp2p",
23+
"crypto",
24+
"psk"
25+
],
26+
"license": "MIT",
27+
"dependencies": {
28+
"debug": "^3.1.0",
29+
"interface-connection": "^0.3.2",
30+
"pull-cat": "^1.1.11",
31+
"pull-defer": "^0.2.2",
32+
"pull-handshake": "^1.1.4",
33+
"pull-reader": "^1.2.9",
34+
"pull-stream": "^3.6.7",
35+
"xsalsa20": "^1.0.2"
36+
},
37+
"devDependencies": {
38+
"aegir": "^13.1.0",
39+
"async": "^2.6.1",
40+
"chai": "^4.1.2",
41+
"dirty-chai": "^2.0.1",
42+
"peer-id": "^0.10.7",
43+
"pre-commit": "^1.2.2",
44+
"pull-pair": "^1.1.0"
45+
},
46+
"pre-commit": [
47+
"lint",
48+
"test"
49+
],
50+
"engines": {
51+
"node": ">=6.0.0",
52+
"npm": ">=3.0.0"
53+
},
54+
"repository": {
55+
"type": "git",
56+
"url": "https://github.com/ipfs/js-libp2p-pnet.git"
57+
},
58+
"bugs": {
59+
"url": "https://github.com/ipfs/js-libp2p-pnet/issues"
60+
},
61+
"homepage": "https://github.com/ipfs/js-libp2p-pnet",
62+
"contributors": [
63+
"Jacob Heun <[email protected]>"
64+
]
65+
}

src/crypto.js

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
'use strict'
2+
3+
const pull = require('pull-stream')
4+
const debug = require('debug')
5+
const Errors = require('./errors')
6+
const xsalsa20 = require('xsalsa20')
7+
const KEY_LENGTH = require('./key-generator').KEY_LENGTH
8+
9+
const log = debug('libp2p:pnet')
10+
log.trace = debug('libp2p:pnet:trace')
11+
log.err = debug('libp2p:pnet:err')
12+
13+
/**
14+
* Creates a pull stream to encrypt messages in a private network
15+
*
16+
* @param {Buffer} nonce The nonce to use in encryption
17+
* @param {Buffer} psk The private shared key to use in encryption
18+
* @returns {PullStream} a through stream
19+
*/
20+
module.exports.createBoxStream = (nonce, psk) => {
21+
const xor = xsalsa20(nonce, psk)
22+
return pull(
23+
ensureBuffer(),
24+
pull.map((chunk) => {
25+
return xor.update(chunk, chunk)
26+
})
27+
)
28+
}
29+
30+
/**
31+
* Creates a pull stream to decrypt messages in a private network
32+
*
33+
* @param {Object} remote Holds the nonce of the peer
34+
* @param {Buffer} psk The private shared key to use in decryption
35+
* @returns {PullStream} a through stream
36+
*/
37+
module.exports.createUnboxStream = (remote, psk) => {
38+
let xor
39+
return pull(
40+
ensureBuffer(),
41+
pull.map((chunk) => {
42+
if (!xor) {
43+
xor = xsalsa20(remote.nonce, psk)
44+
log.trace('Decryption enabled')
45+
}
46+
47+
return xor.update(chunk, chunk)
48+
})
49+
)
50+
}
51+
52+
/**
53+
* Decode the version 1 psk from the given Buffer
54+
*
55+
* @param {Buffer} pskBuffer
56+
* @throws {INVALID_PSK}
57+
* @returns {Object} The PSK metadata (tag, codecName, psk)
58+
*/
59+
module.exports.decodeV1PSK = (pskBuffer) => {
60+
try {
61+
// This should pull from multibase/multicodec to allow for
62+
// more encoding flexibility. Ideally we'd consume the codecs
63+
// from the buffer line by line to evaluate the next line
64+
// programatically instead of making assumptions about the
65+
// encodings of each line.
66+
const metadata = pskBuffer.toString().split(/(?:\r\n|\r|\n)/g)
67+
const pskTag = metadata.shift()
68+
const codec = metadata.shift()
69+
const psk = Buffer.from(metadata.shift(), 'hex')
70+
71+
if (psk.byteLength !== KEY_LENGTH) {
72+
throw new Error(Errors.INVALID_PSK)
73+
}
74+
75+
return {
76+
tag: pskTag,
77+
codecName: codec,
78+
psk: psk
79+
}
80+
} catch (err) {
81+
throw new Error(Errors.INVALID_PSK)
82+
}
83+
}
84+
85+
/**
86+
* Returns a through pull-stream that ensures the passed chunks
87+
* are buffers instead of strings
88+
* @returns {PullStream} a through stream
89+
*/
90+
function ensureBuffer () {
91+
return pull.map((chunk) => {
92+
if (typeof chunk === 'string') {
93+
return Buffer.from(chunk, 'utf-8')
94+
}
95+
96+
return chunk
97+
})
98+
}

src/errors.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
'use strict'
2+
3+
module.exports.INVALID_PEER = 'Not a valid peer connection'
4+
module.exports.INVALID_PSK = 'Your private shared key is invalid'
5+
module.exports.NO_LOCAL_ID = 'No local private key provided'
6+
module.exports.NO_HANDSHAKE_CONNECTION = 'No connection for the handshake provided'
7+
module.exports.STREAM_ENDED = 'Stream ended prematurely'

src/index.js

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
'use strict'
2+
3+
const pull = require('pull-stream')
4+
const Connection = require('interface-connection').Connection
5+
const assert = require('assert')
6+
7+
const Errors = require('./errors')
8+
const State = require('./state')
9+
const decodeV1PSK = require('./crypto').decodeV1PSK
10+
const debug = require('debug')
11+
const log = debug('libp2p:pnet')
12+
log.err = debug('libp2p:pnet:err')
13+
14+
/**
15+
* Takes a Private Shared Key (psk) and provides a `protect` method
16+
* for wrapping existing connections in a private encryption stream
17+
*/
18+
class Protector {
19+
/**
20+
* @param {Buffer} keyBuffer The private shared key buffer
21+
* @constructor
22+
*/
23+
constructor (keyBuffer) {
24+
const decodedPSK = decodeV1PSK(keyBuffer)
25+
this.psk = decodedPSK.psk
26+
this.tag = decodedPSK.tag
27+
}
28+
29+
/**
30+
* Takes a given Connection and creates a privaste encryption stream
31+
* between its two peers from the PSK the Protector instance was
32+
* created with.
33+
*
34+
* @param {Connection} connection The connection to protect
35+
* @param {function(Error)} callback
36+
* @returns {Connection} The protected connection
37+
*/
38+
protect (connection, callback) {
39+
assert(connection, Errors.NO_HANDSHAKE_CONNECTION)
40+
41+
const protectedConnection = new Connection(undefined, connection)
42+
const state = new State(this.psk)
43+
44+
log('protecting the connection')
45+
46+
// Run the connection through an encryptor
47+
pull(
48+
connection,
49+
state.encrypt((err, encryptedOuterStream) => {
50+
if (err) {
51+
log.err('There was an error attempting to protect the connection', err)
52+
return callback(err)
53+
}
54+
55+
connection.getPeerInfo(() => {
56+
protectedConnection.setInnerConn(new Connection(encryptedOuterStream, connection))
57+
log('the connection has been successfully wrapped by the protector')
58+
callback()
59+
})
60+
}),
61+
connection
62+
)
63+
64+
return protectedConnection
65+
}
66+
}
67+
68+
module.exports = Protector
69+
module.exports.errors = Errors
70+
module.exports.generate = require('./key-generator')

0 commit comments

Comments
 (0)