mirror of
https://github.com/fluencelabs/js-libp2p
synced 2025-06-23 14:01:35 +00:00
refactor: crypto and pnet (#469)
* feat: add initial plaintext 2 module * refactor: initial refactor of pnet * chore: fix lint * fix: update plaintext api usage * test: use plaintext for test crypto * chore: update deps test: update dialer suite scope * feat: add connection protection to the upgrader * refactor: cleanup and lint fix * chore: remove unncessary transforms * chore: temporarily disable bundlesize * chore: add missing dep * fix: use it-handshake to prevent overreading * chore(fix): PR feedback updates * chore: apply suggestions from code review Co-Authored-By: Vasco Santos <vasco.santos@moxy.studio>
This commit is contained in:
@ -1,6 +1,5 @@
|
||||
'use strict'
|
||||
|
||||
const pull = require('pull-stream')
|
||||
const debug = require('debug')
|
||||
const Errors = require('./errors')
|
||||
const xsalsa20 = require('xsalsa20')
|
||||
@ -8,45 +7,40 @@ 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')
|
||||
log.error = debug('libp2p:pnet:err')
|
||||
|
||||
/**
|
||||
* Creates a pull stream to encrypt messages in a private network
|
||||
* Creates a stream iterable 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
|
||||
* @returns {*} a through iterable
|
||||
*/
|
||||
module.exports.createBoxStream = (nonce, psk) => {
|
||||
const xor = xsalsa20(nonce, psk)
|
||||
return pull(
|
||||
ensureBuffer(),
|
||||
pull.map((chunk) => {
|
||||
return xor.update(chunk, chunk)
|
||||
})
|
||||
)
|
||||
return (source) => (async function * () {
|
||||
for await (const chunk of source) {
|
||||
yield Buffer.from(xor.update(chunk.slice()))
|
||||
}
|
||||
})()
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a pull stream to decrypt messages in a private network
|
||||
* Creates a stream iterable to decrypt messages in a private network
|
||||
*
|
||||
* @param {Object} remote Holds the nonce of the peer
|
||||
* @param {Buffer} nonce The nonce of the remote peer
|
||||
* @param {Buffer} psk The private shared key to use in decryption
|
||||
* @returns {PullStream} a through stream
|
||||
* @returns {*} a through iterable
|
||||
*/
|
||||
module.exports.createUnboxStream = (remote, psk) => {
|
||||
let xor
|
||||
return pull(
|
||||
ensureBuffer(),
|
||||
pull.map((chunk) => {
|
||||
if (!xor) {
|
||||
xor = xsalsa20(remote.nonce, psk)
|
||||
log.trace('Decryption enabled')
|
||||
}
|
||||
module.exports.createUnboxStream = (nonce, psk) => {
|
||||
return (source) => (async function * () {
|
||||
const xor = xsalsa20(nonce, psk)
|
||||
log.trace('Decryption enabled')
|
||||
|
||||
return xor.update(chunk, chunk)
|
||||
})
|
||||
)
|
||||
for await (const chunk of source) {
|
||||
yield Buffer.from(xor.update(chunk.slice()))
|
||||
}
|
||||
})()
|
||||
}
|
||||
|
||||
/**
|
||||
@ -61,7 +55,7 @@ module.exports.decodeV1PSK = (pskBuffer) => {
|
||||
// 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
|
||||
// programmatically instead of making assumptions about the
|
||||
// encodings of each line.
|
||||
const metadata = pskBuffer.toString().split(/(?:\r\n|\r|\n)/g)
|
||||
const pskTag = metadata.shift()
|
||||
@ -78,21 +72,7 @@ module.exports.decodeV1PSK = (pskBuffer) => {
|
||||
psk: psk
|
||||
}
|
||||
} catch (err) {
|
||||
log.error(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
|
||||
})
|
||||
}
|
||||
|
@ -1,12 +1,17 @@
|
||||
'use strict'
|
||||
|
||||
const pull = require('pull-stream')
|
||||
const { Connection } = require('libp2p-interfaces/src/connection')
|
||||
const pipe = require('it-pipe')
|
||||
const assert = require('assert')
|
||||
|
||||
const duplexPair = require('it-pair/duplex')
|
||||
const crypto = require('libp2p-crypto')
|
||||
const Errors = require('./errors')
|
||||
const State = require('./state')
|
||||
const decodeV1PSK = require('./crypto').decodeV1PSK
|
||||
const {
|
||||
createBoxStream,
|
||||
createUnboxStream,
|
||||
decodeV1PSK
|
||||
} = require('./crypto')
|
||||
const handshake = require('it-handshake')
|
||||
const { NONCE_LENGTH } = require('./key-generator')
|
||||
const debug = require('debug')
|
||||
const log = debug('libp2p:pnet')
|
||||
log.err = debug('libp2p:pnet:err')
|
||||
@ -27,41 +32,41 @@ class Protector {
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a given Connection and creates a privaste encryption stream
|
||||
* Takes a given Connection and creates a private 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
|
||||
* @returns {*} A protected duplex iterable
|
||||
*/
|
||||
protect (connection, callback) {
|
||||
async protect (connection) {
|
||||
assert(connection, Errors.NO_HANDSHAKE_CONNECTION)
|
||||
|
||||
const protectedConnection = new Connection(undefined, connection)
|
||||
const state = new State(this.psk)
|
||||
|
||||
// Exchange nonces
|
||||
log('protecting the connection')
|
||||
const localNonce = crypto.randomBytes(NONCE_LENGTH)
|
||||
|
||||
// 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)
|
||||
}
|
||||
const shake = handshake(connection)
|
||||
shake.write(localNonce)
|
||||
|
||||
connection.getPeerInfo(() => {
|
||||
protectedConnection.setInnerConn(new Connection(encryptedOuterStream, connection))
|
||||
log('the connection has been successfully wrapped by the protector')
|
||||
callback()
|
||||
})
|
||||
}),
|
||||
connection
|
||||
const result = await shake.reader.next(NONCE_LENGTH)
|
||||
const remoteNonce = result.value.slice()
|
||||
shake.rest()
|
||||
|
||||
// Create the boxing/unboxing pipe
|
||||
log('exchanged nonces')
|
||||
const [internal, external] = duplexPair()
|
||||
pipe(
|
||||
external,
|
||||
// Encrypt all outbound traffic
|
||||
createBoxStream(localNonce, this.psk),
|
||||
shake.stream,
|
||||
// Decrypt all inbound traffic
|
||||
createUnboxStream(remoteNonce, this.psk),
|
||||
external
|
||||
)
|
||||
|
||||
return protectedConnection
|
||||
return internal
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,110 +0,0 @@
|
||||
'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