UNPKG

@chainsafe/libp2p-noise

Version:
192 lines 8.16 kB
import { publicKeyFromProtobuf } from '@libp2p/crypto/keys'; import { InvalidCryptoExchangeError, serviceCapabilities } from '@libp2p/interface'; import { peerIdFromPublicKey } from '@libp2p/peer-id'; import { decode } from 'it-length-prefixed'; import { lpStream } from 'it-length-prefixed-stream'; import { duplexPair } from 'it-pair/duplex'; import { pipe } from 'it-pipe'; import { alloc as uint8ArrayAlloc } from 'uint8arrays/alloc'; import { NOISE_MSG_MAX_LENGTH_BYTES } from './constants.js'; import { defaultCrypto } from './crypto/index.js'; import { wrapCrypto } from './crypto.js'; import { uint16BEDecode, uint16BEEncode } from './encoder.js'; import { registerMetrics } from './metrics.js'; import { performHandshakeInitiator, performHandshakeResponder } from './performHandshake.js'; import { decryptStream, encryptStream } from './streaming.js'; export class Noise { protocol = '/noise'; crypto; prologue; staticKey; extensions; metrics; components; constructor(components, init = {}) { const { staticNoiseKey, extensions, crypto, prologueBytes } = init; const { metrics } = components; this.components = components; const _crypto = crypto ?? defaultCrypto; this.crypto = wrapCrypto(_crypto); this.extensions = { webtransportCerthashes: [], ...extensions }; this.metrics = metrics ? registerMetrics(metrics) : undefined; if (staticNoiseKey) { // accepts x25519 private key of length 32 this.staticKey = _crypto.generateX25519KeyPairFromSeed(staticNoiseKey); } else { this.staticKey = _crypto.generateX25519KeyPair(); } this.prologue = prologueBytes ?? uint8ArrayAlloc(0); } [Symbol.toStringTag] = '@chainsafe/libp2p-noise'; [serviceCapabilities] = [ '@libp2p/connection-encryption', '@chainsafe/libp2p-noise' ]; /** * Encrypt outgoing data to the remote party (handshake as initiator) * * @param connection - streaming iterable duplex that will be encrypted * @param options * @param options.remotePeer - PeerId of the remote peer. Used to validate the integrity of the remote peer * @param options.signal - Used to abort the operation */ async secureOutbound(connection, options) { const wrappedConnection = lpStream(connection, { lengthEncoder: uint16BEEncode, lengthDecoder: uint16BEDecode, maxDataLength: NOISE_MSG_MAX_LENGTH_BYTES }); const handshake = await this.performHandshakeInitiator(wrappedConnection, this.components.privateKey, options?.remotePeer?.publicKey, options); const conn = await this.createSecureConnection(wrappedConnection, handshake); connection.source = conn.source; connection.sink = conn.sink; const publicKey = publicKeyFromProtobuf(handshake.payload.identityKey); return { conn: connection, remoteExtensions: handshake.payload.extensions, remotePeer: peerIdFromPublicKey(publicKey), streamMuxer: options?.skipStreamMuxerNegotiation === true ? undefined : this.getStreamMuxer(handshake.payload.extensions?.streamMuxers) }; } getStreamMuxer(protocols) { if (protocols == null || protocols.length === 0) { return; } const streamMuxers = this.components.upgrader.getStreamMuxers(); if (streamMuxers != null) { for (const protocol of protocols) { const streamMuxer = streamMuxers.get(protocol); if (streamMuxer != null) { return streamMuxer; } } } if (protocols.length) { throw new InvalidCryptoExchangeError('Early muxer negotiation was requested but the initiator and responder had no common muxers'); } } /** * Decrypt incoming data (handshake as responder). * * @param connection - streaming iterable duplex that will be encrypted * @param options * @param options.remotePeer - PeerId of the remote peer. Used to validate the integrity of the remote peer * @param options.signal - Used to abort the operation */ async secureInbound(connection, options) { const wrappedConnection = lpStream(connection, { lengthEncoder: uint16BEEncode, lengthDecoder: uint16BEDecode, maxDataLength: NOISE_MSG_MAX_LENGTH_BYTES }); const handshake = await this.performHandshakeResponder(wrappedConnection, this.components.privateKey, options?.remotePeer?.publicKey, options); const conn = await this.createSecureConnection(wrappedConnection, handshake); connection.source = conn.source; connection.sink = conn.sink; const publicKey = publicKeyFromProtobuf(handshake.payload.identityKey); return { conn: connection, remoteExtensions: handshake.payload.extensions, remotePeer: peerIdFromPublicKey(publicKey), streamMuxer: options?.skipStreamMuxerNegotiation === true ? undefined : this.getStreamMuxer(handshake.payload.extensions?.streamMuxers) }; } /** * Perform XX handshake as initiator. */ async performHandshakeInitiator(connection, // TODO: pass private key in noise constructor via Components privateKey, remoteIdentityKey, options) { let result; const streamMuxers = options?.skipStreamMuxerNegotiation === true ? [] : [...this.components.upgrader.getStreamMuxers().keys()]; try { result = await performHandshakeInitiator({ connection, privateKey, remoteIdentityKey, log: this.components.logger.forComponent('libp2p:noise:xxhandshake'), crypto: this.crypto, prologue: this.prologue, s: this.staticKey, extensions: { streamMuxers, webtransportCerthashes: [], ...this.extensions } }, options); this.metrics?.xxHandshakeSuccesses.increment(); } catch (e) { this.metrics?.xxHandshakeErrors.increment(); throw e; } return result; } /** * Perform XX handshake as responder. */ async performHandshakeResponder(connection, privateKey, remoteIdentityKey, options) { let result; const streamMuxers = options?.skipStreamMuxerNegotiation === true ? [] : [...this.components.upgrader.getStreamMuxers().keys()]; try { result = await performHandshakeResponder({ connection, privateKey, remoteIdentityKey, log: this.components.logger.forComponent('libp2p:noise:xxhandshake'), crypto: this.crypto, prologue: this.prologue, s: this.staticKey, extensions: { streamMuxers, webtransportCerthashes: [], ...this.extensions } }, options); this.metrics?.xxHandshakeSuccesses.increment(); } catch (e) { this.metrics?.xxHandshakeErrors.increment(); throw e; } return result; } async createSecureConnection(connection, handshake) { // Create encryption box/unbox wrapper const [secure, user] = duplexPair(); const network = connection.unwrap(); await pipe(secure, // write to wrapper encryptStream(handshake, this.metrics), // encrypt data + prefix with message length network, // send to the remote peer (source) => decode(source, { lengthDecoder: uint16BEDecode }), // read message length prefix decryptStream(handshake, this.metrics), // decrypt the incoming data secure // pipe to the wrapper ); return user; } } //# sourceMappingURL=noise.js.map