js-libp2p-noise/src/noise.ts

252 lines
8.0 KiB
TypeScript
Raw Normal View History

2020-06-19 12:49:40 +02:00
import x25519 from 'bcrypto/lib/js/x25519'
import { Buffer } from 'buffer'
import Wrap from 'it-pb-rpc'
import DuplexPair from 'it-pair/duplex'
import ensureBuffer from 'it-buffer'
import pipe from 'it-pipe'
import { encode, decode } from 'it-length-prefixed'
import { XXHandshake } from './handshake-xx'
import { IKHandshake } from './handshake-ik'
import { XXFallbackHandshake } from './handshake-xx-fallback'
import { generateKeypair, getPayload } from './utils'
import { uint16BEDecode, uint16BEEncode } from './encoder'
import { decryptStream, encryptStream } from './crypto'
import { bytes } from './@types/basic'
import { INoiseConnection, KeyPair, SecureOutbound } from './@types/libp2p'
import { Duplex } from 'it-pair'
import { IHandshake } from './@types/handshake-interface'
import { KeyCache } from './keycache'
import { logger } from './logger'
import PeerId from 'peer-id'
import { NOISE_MSG_MAX_LENGTH_BYTES } from './constants'
2019-11-11 21:58:04 +01:00
2019-11-22 12:52:59 +01:00
export type WrappedConnection = ReturnType<typeof Wrap>;
2019-11-21 14:43:12 +01:00
2020-01-05 19:00:16 +01:00
type HandshakeParams = {
connection: WrappedConnection;
isInitiator: boolean;
2020-01-11 20:20:57 +01:00
localPeer: PeerId;
2020-02-07 20:21:27 +01:00
remotePeer?: PeerId;
2020-01-05 19:00:16 +01:00
};
2020-01-11 20:27:26 +01:00
export class Noise implements INoiseConnection {
2020-06-19 12:49:40 +02:00
public protocol = '/noise';
2019-11-20 13:23:36 +01:00
2020-01-29 19:48:14 +01:00
private readonly prologue = Buffer.alloc(0);
2019-11-21 14:43:12 +01:00
private readonly staticKeys: KeyPair;
private readonly earlyData?: bytes;
2020-01-13 15:38:14 +01:00
private useNoisePipes: boolean;
2019-11-11 15:39:09 +01:00
2020-02-07 12:59:52 +01:00
/**
*
2021-01-25 11:30:58 +01:00
* @param {bytes} staticNoiseKey - x25519 private key, reuse for faster handshakes
2020-06-19 13:06:31 +02:00
* @param {bytes} earlyData
2020-02-07 12:59:52 +01:00
*/
2020-06-19 12:49:40 +02:00
constructor (staticNoiseKey?: bytes, earlyData?: bytes) {
this.earlyData = earlyData || Buffer.alloc(0)
// disabled until properly specked
this.useNoisePipes = false
2019-11-08 14:03:34 +01:00
2019-11-11 15:39:09 +01:00
if (staticNoiseKey) {
2020-06-19 12:49:40 +02:00
const publicKey = x25519.publicKeyCreate(staticNoiseKey)
2019-11-11 15:39:09 +01:00
this.staticKeys = {
privateKey: staticNoiseKey,
2020-06-19 12:49:40 +02:00
publicKey
2019-11-11 15:39:09 +01:00
}
2019-11-12 14:02:59 +01:00
} else {
2020-06-19 12:49:40 +02:00
this.staticKeys = generateKeypair()
2019-11-11 15:39:09 +01:00
}
2019-11-08 14:03:34 +01:00
}
2019-11-20 13:23:36 +01:00
/**
* Encrypt outgoing data to the remote party (handshake as initiator)
2021-01-25 11:30:58 +01:00
*
2019-11-20 13:23:36 +01:00
* @param {PeerId} localPeer - PeerId of the receiving peer
2020-06-19 13:06:31 +02:00
* @param {any} connection - streaming iterable duplex that will be encrypted
2019-11-20 13:23:36 +01:00
* @param {PeerId} remotePeer - PeerId of the remote peer. Used to validate the integrity of the remote peer.
* @returns {Promise<SecureOutbound>}
*/
2020-06-19 12:49:40 +02:00
public async secureOutbound (localPeer: PeerId, connection: any, remotePeer: PeerId): Promise<SecureOutbound> {
2020-02-13 22:51:36 +01:00
const wrappedConnection = Wrap(
connection,
{
// wrong types in repo
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
2020-02-13 22:51:36 +01:00
lengthEncoder: uint16BEEncode,
lengthDecoder: uint16BEDecode,
maxDataLength: NOISE_MSG_MAX_LENGTH_BYTES
}
2020-06-19 12:49:40 +02:00
)
2020-01-05 19:00:16 +01:00
const handshake = await this.performHandshake({
connection: wrappedConnection,
isInitiator: true,
localPeer,
2020-06-19 12:49:40 +02:00
remotePeer
})
const conn = await this.createSecureConnection(wrappedConnection, handshake)
2019-11-11 21:58:04 +01:00
2019-11-20 13:23:36 +01:00
return {
2019-11-25 14:03:23 +01:00
conn,
2020-04-17 11:07:58 +02:00
remoteEarlyData: handshake.remoteEarlyData,
2020-06-19 12:49:40 +02:00
remotePeer: handshake.remotePeer
2019-11-11 21:58:04 +01:00
}
}
2019-11-20 13:23:36 +01:00
/**
* Decrypt incoming data (handshake as responder).
2021-01-25 11:30:58 +01:00
*
2019-11-20 13:23:36 +01:00
* @param {PeerId} localPeer - PeerId of the receiving peer.
2020-06-19 13:06:31 +02:00
* @param {any} connection - streaming iterable duplex that will be encryption.
2019-11-20 13:23:36 +01:00
* @param {PeerId} remotePeer - optional PeerId of the initiating peer, if known. This may only exist during transport upgrades.
* @returns {Promise<SecureOutbound>}
*/
2020-06-19 12:49:40 +02:00
public async secureInbound (localPeer: PeerId, connection: any, remotePeer?: PeerId): Promise<SecureOutbound> {
2020-02-13 22:51:36 +01:00
const wrappedConnection = Wrap(
connection,
{
// wrong types in repo
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
2020-02-13 22:51:36 +01:00
lengthEncoder: uint16BEEncode,
lengthDecoder: uint16BEDecode,
maxDataLength: NOISE_MSG_MAX_LENGTH_BYTES
}
2020-06-19 12:49:40 +02:00
)
2020-01-05 19:00:16 +01:00
const handshake = await this.performHandshake({
connection: wrappedConnection,
isInitiator: false,
localPeer,
2020-01-05 19:00:16 +01:00
remotePeer
2020-06-19 12:49:40 +02:00
})
const conn = await this.createSecureConnection(wrappedConnection, handshake)
2019-11-25 14:03:23 +01:00
2019-11-21 14:43:12 +01:00
return {
2019-11-25 14:03:23 +01:00
conn,
2020-04-17 11:04:50 +02:00
remoteEarlyData: handshake.remoteEarlyData,
2020-02-07 20:21:27 +01:00
remotePeer: handshake.remotePeer
2020-06-19 12:49:40 +02:00
}
2019-11-11 21:58:04 +01:00
}
2020-01-03 15:07:46 +01:00
/**
* If Noise pipes supported, tries IK handshake first with XX as fallback if it fails.
2020-01-13 16:40:42 +01:00
* If noise pipes disabled or remote peer static key is unknown, use XX.
2021-01-25 11:30:58 +01:00
*
2020-06-19 13:06:31 +02:00
* @param {HandshakeParams} params
2020-01-03 15:07:46 +01:00
*/
2020-06-19 12:49:40 +02:00
private async performHandshake (params: HandshakeParams): Promise<IHandshake> {
const payload = await getPayload(params.localPeer, this.staticKeys.publicKey, this.earlyData)
let tryIK = this.useNoisePipes
if (params.isInitiator && KeyCache.load(params.remotePeer) === null) {
// if we are initiator and remote static key is unknown, don't try IK
tryIK = false
2020-02-07 20:21:27 +01:00
}
2020-01-15 17:27:32 +01:00
// Try IK if acting as responder or initiator that has remote's static key.
2020-02-07 20:21:27 +01:00
if (tryIK) {
2020-01-13 16:33:58 +01:00
// Try IK first
2020-06-19 12:49:40 +02:00
const { remotePeer, connection, isInitiator } = params
2020-02-07 20:21:27 +01:00
const ikHandshake = new IKHandshake(
isInitiator,
payload,
this.prologue,
this.staticKeys,
connection,
2020-06-19 12:49:40 +02:00
// safe to cast as we did checks
2020-02-07 20:21:27 +01:00
KeyCache.load(params.remotePeer) || Buffer.alloc(32),
2020-06-19 12:49:40 +02:00
remotePeer as PeerId
)
2020-01-15 17:27:32 +01:00
2020-01-05 19:00:16 +01:00
try {
2020-06-19 12:49:40 +02:00
return await this.performIKHandshake(ikHandshake)
2020-01-05 19:00:16 +01:00
} catch (e) {
2020-01-13 16:40:42 +01:00
// IK failed, go to XX fallback
2020-06-19 12:49:40 +02:00
let ephemeralKeys
2020-02-08 11:21:51 +01:00
if (params.isInitiator) {
2020-06-19 12:49:40 +02:00
ephemeralKeys = ikHandshake.getLocalEphemeralKeys()
2020-01-15 17:27:32 +01:00
}
2020-06-19 12:49:40 +02:00
return await this.performXXFallbackHandshake(params, payload, e.initialMsg, ephemeralKeys)
2020-01-05 19:00:16 +01:00
}
2020-01-03 15:07:46 +01:00
} else {
2020-02-07 20:21:27 +01:00
// run XX handshake
2020-06-19 12:49:40 +02:00
return await this.performXXHandshake(params, payload)
2020-01-03 15:07:46 +01:00
}
}
2020-06-19 12:49:40 +02:00
private async performXXFallbackHandshake (
2020-01-05 19:00:16 +01:00
params: HandshakeParams,
payload: bytes,
2020-01-05 19:00:16 +01:00
initialMsg: bytes,
2020-06-19 12:49:40 +02:00
ephemeralKeys?: KeyPair
2020-01-07 13:34:45 +01:00
): Promise<XXFallbackHandshake> {
2020-06-19 12:49:40 +02:00
const { isInitiator, remotePeer, connection } = params
2020-01-05 19:00:16 +01:00
const handshake =
2020-06-19 12:49:40 +02:00
new XXFallbackHandshake(isInitiator, payload, this.prologue, this.staticKeys, connection, initialMsg, remotePeer, ephemeralKeys)
2020-01-05 19:00:16 +01:00
try {
2020-06-19 12:49:40 +02:00
await handshake.propose()
await handshake.exchange()
await handshake.finish()
2020-01-05 19:00:16 +01:00
} catch (e) {
2020-06-19 12:49:40 +02:00
logger(e)
throw new Error(`Error occurred during XX Fallback handshake: ${e.message}`)
2020-01-05 19:00:16 +01:00
}
2020-06-19 12:49:40 +02:00
return handshake
2020-01-05 19:00:16 +01:00
}
2020-06-19 12:49:40 +02:00
private async performXXHandshake (
2020-01-05 19:00:16 +01:00
params: HandshakeParams,
2020-06-19 12:49:40 +02:00
payload: bytes
2020-01-07 13:34:45 +01:00
): Promise<XXHandshake> {
2020-06-19 12:49:40 +02:00
const { isInitiator, remotePeer, connection } = params
const handshake = new XXHandshake(isInitiator, payload, this.prologue, this.staticKeys, connection, remotePeer)
2020-01-03 17:28:13 +01:00
try {
2020-06-19 12:49:40 +02:00
await handshake.propose()
await handshake.exchange()
await handshake.finish()
2020-01-13 15:38:14 +01:00
2020-02-08 12:11:16 +01:00
if (this.useNoisePipes && handshake.remotePeer) {
2020-06-19 12:49:40 +02:00
KeyCache.store(handshake.remotePeer, handshake.getRemoteStaticKey())
2020-01-13 15:38:14 +01:00
}
} catch (e) {
2020-06-19 12:49:40 +02:00
throw new Error(`Error occurred during XX handshake: ${e.message}`)
}
2019-11-28 17:32:46 +01:00
2020-06-19 12:49:40 +02:00
return handshake
2019-11-28 17:32:46 +01:00
}
2019-11-11 21:58:04 +01:00
2020-06-19 12:49:40 +02:00
private async performIKHandshake (
handshake: IKHandshake
2020-01-07 13:34:45 +01:00
): Promise<IKHandshake> {
2020-06-19 12:49:40 +02:00
await handshake.stage0()
await handshake.stage1()
2020-01-15 11:32:40 +01:00
2020-06-19 12:49:40 +02:00
return handshake
2020-01-05 19:00:16 +01:00
}
2020-06-19 12:49:40 +02:00
private async createSecureConnection (
2019-11-28 17:32:46 +01:00
connection: WrappedConnection,
2020-06-19 12:49:40 +02:00
handshake: IHandshake
2019-11-28 17:53:27 +01:00
): Promise<Duplex> {
2019-11-25 13:09:40 +01:00
// Create encryption box/unbox wrapper
2020-06-19 12:49:40 +02:00
const [secure, user] = DuplexPair()
const network = connection.unwrap()
2019-11-25 13:27:55 +01:00
2020-06-19 12:49:10 +02:00
await pipe(
2019-11-25 13:27:55 +01:00
secure, // write to wrapper
ensureBuffer, // ensure any type of data is converted to buffer
2019-11-29 16:23:24 +01:00
encryptStream(handshake), // data is encrypted
2020-02-13 22:51:36 +01:00
encode({ lengthEncoder: uint16BEEncode }), // prefix with message length
2019-11-25 13:27:55 +01:00
network, // send to the remote peer
2020-06-19 12:49:40 +02:00
decode({ lengthDecoder: uint16BEDecode }), // read message length prefix
2019-11-25 13:27:55 +01:00
ensureBuffer, // ensure any type of data is converted to buffer
2019-11-29 16:23:24 +01:00
decryptStream(handshake), // decrypt the incoming data
2019-11-25 13:27:55 +01:00
secure // pipe to the wrapper
2020-06-19 12:49:40 +02:00
)
2019-11-25 13:27:55 +01:00
2020-06-19 12:49:40 +02:00
return user
2019-11-08 14:03:34 +01:00
}
}