mirror of
https://github.com/fluencelabs/js-libp2p
synced 2025-06-23 05:51:37 +00:00
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:
67
src/pnet/README.md
Normal file
67
src/pnet/README.md
Normal 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
98
src/pnet/crypto.js
Normal 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
7
src/pnet/errors.js
Normal 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
70
src/pnet/index.js
Normal 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
22
src/pnet/key-generator.js
Normal 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
5
src/pnet/package.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "libp2p-pnet",
|
||||
"description": "Private Network protection implementation",
|
||||
"main": "./index.js"
|
||||
}
|
110
src/pnet/state.js
Normal file
110
src/pnet/state.js
Normal 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
|
Reference in New Issue
Block a user