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

Initial development #1

Merged
merged 1 commit into from
Jul 18, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
yarn.lock
package-lock.json
**/node_modules/
**/*.log
test/repo-tests*

# Logs
logs
*.log

coverage
.nyc_output/

# Runtime data
pids
*.pid
*.seed

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage

# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# node-waf configuration
.lock-wscript

build

# Dependency directory
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
node_modules

lib
dist
docs
81 changes: 79 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,79 @@
# js-libp2p-pnet
A module providing swarm protection for libp2p
js-libp2p-pnet
==================

Connection protection management for libp2p leveraging PSK encryption via XSalsa20.

## Lead Maintainer

[Jacob Heun](https://github.com/jacobheun)

## Table of Contents

- [Usage](#usage)
- [Private Shared Keys (PSK)](#private-shared-keys)
- [PSK Generation](#psk-generation)
- [Contribute](#contribute)
- [License](#license)

## Usage

```js
const Protector = require('libp2p-pnet')
const protector = new Protector(swarmKeyBuffer)
const privateConnection = protector.protect(myPublicConnection, (err) => { })
```

### Private Shared Keys

Private Shared Keys are expected to be in the following format:

```
/key/swarm/psk/1.0.0/
/base16/
dffb7e3135399a8b1612b2aaca1c36a3a8ac2cd0cca51ceeb2ced87d308cac6d
```

### PSK Generation

A utility method has been created to generate a key for your private network. You can
use one of the methods below to generate your key.

#### From libp2p-pnet

If you have libp2p-pnet locally, you can run the following from the projects root.

```sh
node src/key-generator.js > swarm.key
```

#### From a module using libp2p

If you have a module locally that depends on libp2p-pnet, you can run the following from
that project, assuming the node_modules are installed.

```sh
node -e "require('libp2p-pnet').generate(process.stdout)" > swarm.key
```

#### Programmatically

```js
const writeKey = require('libp2p-pnet').generate
const swarmKey = Buffer.alloc(95)
writeKey(swarmKey)
fs.writeFileSync('swarm.key', swarmKey)
```

## Contribute

There are some ways you can make this module better:

- Consult our [open issues](https://github.com/libp2p/js-libp2p-pnet/issues) and take on one of them

This repository falls under the IPFS [Code of Conduct](https://github.com/ipfs/community/blob/master/code-of-conduct.md).

[![](https://cdn.rawgit.com/jbenet/contribute-ipfs-gif/master/img/contribute.gif)](https://github.com/ipfs/community/blob/master/contributing.md)

## License

[MIT](LICENSE)
2 changes: 2 additions & 0 deletions ci/Jenkinsfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// 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.
javascript()
21 changes: 21 additions & 0 deletions circle.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# 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.
machine:
node:
version: stable

test:
pre:
- npm run lint
post:
- npm run coverage -- --upload --providers coveralls

dependencies:
pre:
- google-chrome --version
- curl -L -o google-chrome.deb https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
- sudo dpkg -i google-chrome.deb || true
- sudo apt-get update
- sudo apt-get install -f
- sudo apt-get install --only-upgrade lsb-base
- sudo dpkg -i google-chrome.deb
- google-chrome --version
65 changes: 65 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
{
"name": "libp2p-pnet",
"version": "1.0.0",
"description": "Private Network protection implementation",
"leadMaintainer": "Jacob Heun <[email protected]>",
"main": "src/index.js",
"scripts": {
"lint": "aegir lint",
"build": "aegir build",
"test": "aegir test",
"test:node": "aegir test -t node",
"test:browser": "aegir test -t browser",
"release": "aegir release -t node browser",
"release-minor": "aegir release --type minor -t node browser",
"release-major": "aegir release --type major -t node browser",
"coverage": "aegir coverage",
"coverage-publish": "aegir coverage publish",
"benchmark": "node benchmarks/send.js"
},
"keywords": [
"IPFS",
"libp2p",
"crypto",
"psk"
],
"license": "MIT",
"dependencies": {
"debug": "^3.1.0",
"interface-connection": "^0.3.2",
"pull-cat": "^1.1.11",
"pull-defer": "^0.2.2",
"pull-handshake": "^1.1.4",
"pull-reader": "^1.2.9",
"pull-stream": "^3.6.7",
"xsalsa20": "^1.0.2"
},
"devDependencies": {
"aegir": "^13.1.0",
"async": "^2.6.1",
"chai": "^4.1.2",
"dirty-chai": "^2.0.1",
"peer-id": "^0.10.7",
"pre-commit": "^1.2.2",
"pull-pair": "^1.1.0"
},
"pre-commit": [
"lint",
"test"
],
"engines": {
"node": ">=6.0.0",
"npm": ">=3.0.0"
},
"repository": {
"type": "git",
"url": "https://github.com/ipfs/js-libp2p-pnet.git"
},
"bugs": {
"url": "https://github.com/ipfs/js-libp2p-pnet/issues"
},
"homepage": "https://github.com/ipfs/js-libp2p-pnet",
"contributors": [
"Jacob Heun <[email protected]>"
]
}
98 changes: 98 additions & 0 deletions src/crypto.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
'use strict'

const pull = require('pull-stream')
const debug = require('debug')
const Errors = require('./errors')
const xsalsa20 = require('xsalsa20')
const KEY_LENGTH = require('./key-generator').KEY_LENGTH

const log = debug('libp2p:pnet')
log.trace = debug('libp2p:pnet:trace')
log.err = debug('libp2p:pnet:err')

/**
* Creates a pull stream to encrypt messages in a private network
*
* @param {Buffer} nonce The nonce to use in encryption
* @param {Buffer} psk The private shared key to use in encryption
* @returns {PullStream} a through stream
*/
module.exports.createBoxStream = (nonce, psk) => {
const xor = xsalsa20(nonce, psk)
return pull(
ensureBuffer(),
pull.map((chunk) => {
return xor.update(chunk, chunk)
})
)
}

/**
* Creates a pull stream to decrypt messages in a private network
*
* @param {Object} remote Holds the nonce of the peer
* @param {Buffer} psk The private shared key to use in decryption
* @returns {PullStream} a through stream
*/
module.exports.createUnboxStream = (remote, psk) => {
let xor
return pull(
ensureBuffer(),
pull.map((chunk) => {
if (!xor) {
xor = xsalsa20(remote.nonce, psk)
log.trace('Decryption enabled')
}

return xor.update(chunk, chunk)
})
)
}

/**
* Decode the version 1 psk from the given Buffer
*
* @param {Buffer} pskBuffer
* @throws {INVALID_PSK}
* @returns {Object} The PSK metadata (tag, codecName, psk)
*/
module.exports.decodeV1PSK = (pskBuffer) => {
try {
// This should pull from multibase/multicodec to allow for
// more encoding flexibility. Ideally we'd consume the codecs
// from the buffer line by line to evaluate the next line
// programatically instead of making assumptions about the
// encodings of each line.
const metadata = pskBuffer.toString().split(/(?:\r\n|\r|\n)/g)
const pskTag = metadata.shift()
const codec = metadata.shift()
const psk = Buffer.from(metadata.shift(), 'hex')

if (psk.byteLength !== KEY_LENGTH) {
throw new Error(Errors.INVALID_PSK)
}

return {
tag: pskTag,
codecName: codec,
psk: psk
}
} catch (err) {
throw new Error(Errors.INVALID_PSK)
}
}

/**
* Returns a through pull-stream that ensures the passed chunks
* are buffers instead of strings
* @returns {PullStream} a through stream
*/
function ensureBuffer () {
return pull.map((chunk) => {
if (typeof chunk === 'string') {
return Buffer.from(chunk, 'utf-8')
}

return chunk
})
}
7 changes: 7 additions & 0 deletions src/errors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
'use strict'

module.exports.INVALID_PEER = 'Not a valid peer connection'
module.exports.INVALID_PSK = 'Your private shared key is invalid'
module.exports.NO_LOCAL_ID = 'No local private key provided'
module.exports.NO_HANDSHAKE_CONNECTION = 'No connection for the handshake provided'
module.exports.STREAM_ENDED = 'Stream ended prematurely'
70 changes: 70 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
'use strict'

const pull = require('pull-stream')
const Connection = require('interface-connection').Connection
const assert = require('assert')

const Errors = require('./errors')
const State = require('./state')
const decodeV1PSK = require('./crypto').decodeV1PSK
const debug = require('debug')
const log = debug('libp2p:pnet')
log.err = debug('libp2p:pnet:err')

/**
* Takes a Private Shared Key (psk) and provides a `protect` method
* for wrapping existing connections in a private encryption stream
*/
class Protector {
/**
* @param {Buffer} keyBuffer The private shared key buffer
* @constructor
*/
constructor (keyBuffer) {
const decodedPSK = decodeV1PSK(keyBuffer)
this.psk = decodedPSK.psk
this.tag = decodedPSK.tag
}

/**
* Takes a given Connection and creates a privaste encryption stream
* between its two peers from the PSK the Protector instance was
* created with.
*
* @param {Connection} connection The connection to protect
* @param {function(Error)} callback
* @returns {Connection} The protected connection
*/
protect (connection, callback) {
assert(connection, Errors.NO_HANDSHAKE_CONNECTION)

const protectedConnection = new Connection(undefined, connection)
const state = new State(this.psk)

log('protecting the connection')

// Run the connection through an encryptor
pull(
connection,
state.encrypt((err, encryptedOuterStream) => {
if (err) {
log.err('There was an error attempting to protect the connection', err)
return callback(err)
}

connection.getPeerInfo(() => {
protectedConnection.setInnerConn(new Connection(encryptedOuterStream, connection))
log('the connection has been successfully wrapped by the protector')
callback()
})
}),
connection
)

return protectedConnection
}
}

module.exports = Protector
module.exports.errors = Errors
module.exports.generate = require('./key-generator')
Loading