refactor: add core modules to libp2p (#400)

* refactor: add js-libp2p-connection-manager to repo

Co-authored-by: David Dias <daviddias.p@gmail.com>
Co-authored-by: Jacob Heun <jacobheun@gmail.com>
Co-authored-by: Pedro Teixeira <i@pgte.me>
Co-authored-by: Vasco Santos <vasco.santos@ua.pt>

* test(conn-mgr): only run in node

* refactor: add js-libp2p-identify to repo

Co-authored-by: David Dias <daviddias.p@gmail.com>
Co-authored-by: Friedel Ziegelmayer <dignifiedquire@gmail.com>
Co-authored-by: Hugo Dias <hugomrdias@gmail.com>
Co-authored-by: Jacob Heun <jacobheun@gmail.com>
Co-authored-by: Maciej Krüger <mkg20001@gmail.com>
Co-authored-by: Richard Littauer <richard.littauer@gmail.com>
Co-authored-by: Vasco Santos <vasco.santos@moxy.studio>
Co-authored-by: Yusef Napora <yusef@protocol.ai>
Co-authored-by: ᴠɪᴄᴛᴏʀ ʙᴊᴇʟᴋʜᴏʟᴍ <victorbjelkholm@gmail.com>

* refactor: add libp2p-pnet to repo

Co-authored-by: Jacob Heun <jacobheun@gmail.com>
Co-authored-by: Vasco Santos <vasco.santos@moxy.studio>

* refactor: add libp2p-ping to repo

Co-authored-by: David Dias <daviddias.p@gmail.com>
Co-authored-by: Francisco Baio Dias <xicombd@gmail.com>
Co-authored-by: Friedel Ziegelmayer <dignifiedquire@gmail.com>
Co-authored-by: Hugo Dias <mail@hugodias.me>
Co-authored-by: Jacob Heun <jacobheun@gmail.com>
Co-authored-by: João Antunes <j.goncalo.antunes@gmail.com>
Co-authored-by: Richard Littauer <richard.littauer@gmail.com>
Co-authored-by: Vasco Santos <vasco.santos@moxy.studio>
Co-authored-by: Vasco Santos <vasco.santos@ua.pt>
Co-authored-by: ᴠɪᴄᴛᴏʀ ʙᴊᴇʟᴋʜᴏʟᴍ <victorbjelkholm@gmail.com>

* refactor: add libp2p-circuit to repo

Co-authored-by: David Dias <daviddias.p@gmail.com>
Co-authored-by: Dmitriy Ryajov <dryajov@gmail.com>
Co-authored-by: Friedel Ziegelmayer <dignifiedquire@gmail.com>
Co-authored-by: Hugo Dias <mail@hugodias.me>
Co-authored-by: Jacob Heun <jacobheun@gmail.com>
Co-authored-by: Maciej Krüger <mkg20001@gmail.com>
Co-authored-by: Oli Evans <oli@tableflip.io>
Co-authored-by: Pedro Teixeira <i@pgte.me>
Co-authored-by: Vasco Santos <vasco.santos@ua.pt>
Co-authored-by: Victor Bjelkholm <victorbjelkholm@gmail.com>
Co-authored-by: Yusef Napora <yusef@napora.org>
Co-authored-by: dirkmc <dirk@mccormick.cx>

* test(switch): avoid using instanceof

* chore(switch): update bignumber dep

* refactor(circuit): clean up tests

* refactor(switch): consolidate get peer utils

* test(identify): do deep checks of addresses

* test(identify): bump timeout for identify test

* test(switch): tidy up limit dialer test

* refactor(switch): remove redundant circuit tests

* chore: add coverage script

* refactor(circuit): consolidate get peer info

* docs: reference original repositories in each sub readme

* docs: fix comment

* refactor: clean up sub package.json files and readmes
This commit is contained in:
Jacob Heun
2019-08-16 17:30:03 +02:00
committed by GitHub
parent d92306f222
commit b294301456
87 changed files with 5399 additions and 750 deletions

67
src/pnet/README.md Normal file
View File

@ -0,0 +1,67 @@
js-libp2p-pnet
==================
> Connection protection management for libp2p leveraging PSK encryption via XSalsa20.
**Note**: git history prior to merging into js-libp2p can be found in the original repository, https://github.com/libp2p/js-libp2p-pnet.
## Table of Contents
- [Usage](#usage)
- [Examples](#examples)
- [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) => { })
```
### Examples
[Private Networks with IPFS](./examples/pnet-ipfs)
### 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 ./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)
```

98
src/pnet/crypto.js Normal file
View File

@ -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
src/pnet/errors.js Normal file
View File

@ -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
src/pnet/index.js Normal file
View File

@ -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')

22
src/pnet/key-generator.js Normal file
View File

@ -0,0 +1,22 @@
'use strict'
const crypto = require('crypto')
const KEY_LENGTH = 32
/**
* Generates a PSK that can be used in a libp2p-pnet private network
* @param {Writer} writer An object containing a `write` method
* @returns {void}
*/
function generate (writer) {
const psk = crypto.randomBytes(KEY_LENGTH).toString('hex')
writer.write('/key/swarm/psk/1.0.0/\n/base16/\n' + psk)
}
module.exports = generate
module.exports.NONCE_LENGTH = 24
module.exports.KEY_LENGTH = KEY_LENGTH
if (require.main === module) {
generate(process.stdout)
}

5
src/pnet/package.json Normal file
View File

@ -0,0 +1,5 @@
{
"name": "libp2p-pnet",
"description": "Private Network protection implementation",
"main": "./index.js"
}

110
src/pnet/state.js Normal file
View File

@ -0,0 +1,110 @@
'use strict'
const crypto = require('crypto')
const debug = require('debug')
const pair = require('pull-pair')
const Reader = require('pull-reader')
const cat = require('pull-cat')
const pull = require('pull-stream')
const deferred = require('pull-defer')
const cryptoStreams = require('./crypto')
const NONCE_LENGTH = require('./key-generator').NONCE_LENGTH
const log = debug('libp2p:pnet')
log.err = debug('libp2p:pnet:err')
log.trace = debug('libp2p:pnet:trace')
/**
* Keeps track of the state of a given connection, such as the local psk
* and local and remote nonces for encryption/decryption
*/
class State {
/**
* @param {Buffer} psk The key buffer used for encryption
* @constructor
*/
constructor (psk) {
this.local = {
nonce: Buffer.from(
crypto.randomBytes(NONCE_LENGTH)
),
psk: psk
}
this.remote = { nonce: null }
this.rawReader = Reader(60e3)
this.encryptedReader = Reader(60e3)
this.rawPairStream = pair()
this.encryptedPairStream = pair()
// The raw, pair stream
this.innerRawStream = null
this.outerRawStream = {
sink: this.rawReader,
source: cat([
pull.values([
this.local.nonce
]),
this.rawPairStream.source
])
}
// The encrypted, pair stream
this.innerEncryptedStream = {
sink: this.encryptedReader,
source: this.encryptedPairStream.source
}
this.outerEncryptedStream = null
}
/**
* Creates encryption streams for the given state
*
* @param {function(Error, Connection)} callback
* @returns {void}
*/
encrypt (callback) {
// The outer stream needs to be returned before we setup the
// rest of the streams, so we're delaying the execution
setTimeout(() => {
// Read the nonce first, once we have it resolve the
// deferred source, so we keep reading
const deferredSource = deferred.source()
this.rawReader.read(NONCE_LENGTH, (err, data) => {
if (err) {
log.err('There was an error attempting to read the nonce', err)
}
log.trace('remote nonce received')
this.remote.nonce = data
deferredSource.resolve(this.rawReader.read())
})
this.innerRawStream = {
sink: this.rawPairStream.sink,
source: deferredSource
}
// Create the pull exchange between the two inner streams
pull(
this.innerRawStream,
cryptoStreams.createUnboxStream(this.remote, this.local.psk),
this.innerEncryptedStream,
cryptoStreams.createBoxStream(this.local.nonce, this.local.psk),
this.innerRawStream
)
this.outerEncryptedStream = {
sink: this.encryptedPairStream.sink,
source: this.encryptedReader.read()
}
callback(null, this.outerEncryptedStream)
}, 0)
return this.outerRawStream
}
}
module.exports = State