js-libp2p-noise/src/noise.ts

124 lines
4.3 KiB
TypeScript
Raw Normal View History

2019-11-11 15:39:09 +01:00
import { x25519 } from 'bcrypto';
2019-11-11 21:58:04 +01:00
import { Buffer } from "buffer";
2019-11-20 15:21:53 +01:00
import Wrap from 'it-pb-rpc';
2019-11-25 13:27:55 +01:00
import DuplexPair from 'it-pair/duplex';
import ensureBuffer from 'it-buffer';
import pipe from 'it-pipe';
import lp from 'it-length-prefixed';
2019-11-08 14:03:34 +01:00
2020-01-03 14:53:14 +01:00
import { Handshake } from "./handshake-xx";
2019-12-03 15:15:46 +01:00
import { generateKeypair } from "./utils";
2019-12-29 18:23:43 +01:00
import { uint16BEDecode, uint16BEEncode } from "./encoder";
2019-11-25 13:09:40 +01:00
import { decryptStream, encryptStream } from "./crypto";
2019-11-20 15:21:53 +01:00
import { bytes } from "./@types/basic";
import { NoiseConnection, PeerId, KeyPair, SecureOutbound } from "./@types/libp2p";
import { Duplex } from "./@types/it-pair";
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
2019-11-11 21:58:04 +01:00
export class Noise implements NoiseConnection {
2019-11-20 13:23:36 +01:00
public protocol = "/noise";
2019-11-11 15:39:09 +01:00
private readonly privateKey: bytes;
2019-11-21 14:43:12 +01:00
private readonly staticKeys: KeyPair;
private readonly earlyData?: bytes;
2019-11-11 15:39:09 +01:00
2019-11-08 14:03:34 +01:00
constructor(privateKey: bytes, staticNoiseKey?: bytes, earlyData?: bytes) {
2019-11-11 15:39:09 +01:00
this.privateKey = privateKey;
2019-11-26 10:52:30 +01:00
this.earlyData = earlyData || Buffer.alloc(0);
2019-11-08 14:03:34 +01:00
2019-11-11 15:39:09 +01:00
if (staticNoiseKey) {
2019-11-28 17:32:46 +01:00
const publicKey = x25519.publicKeyCreate(staticNoiseKey); // TODO: verify this
2019-11-11 15:39:09 +01:00
this.staticKeys = {
privateKey: staticNoiseKey,
publicKey,
}
2019-11-12 14:02:59 +01:00
} else {
2019-11-21 14:43:12 +01: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)
* @param {PeerId} localPeer - PeerId of the receiving peer
* @param connection - streaming iterable duplex that will be encrypted
* @param {PeerId} remotePeer - PeerId of the remote peer. Used to validate the integrity of the remote peer.
* @returns {Promise<SecureOutbound>}
*/
2019-11-28 17:53:27 +01:00
public async secureOutbound(localPeer: PeerId, connection: any, remotePeer: PeerId): Promise<SecureOutbound> {
2019-11-20 15:21:53 +01:00
const wrappedConnection = Wrap(connection);
2019-12-03 13:39:33 +01:00
const libp2pPublicKey = localPeer.marshalPubKey();
2020-01-03 14:53:14 +01:00
const handshake = await this.performXXHandshake(wrappedConnection, true, libp2pPublicKey, remotePeer);
2019-11-28 17:32:46 +01:00
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,
2019-11-20 13:23:36 +01:00
remotePeer,
2019-11-11 21:58:04 +01:00
}
}
2019-11-20 13:23:36 +01:00
/**
* Decrypt incoming data (handshake as responder).
* @param {PeerId} localPeer - PeerId of the receiving peer.
* @param connection - streaming iterable duplex that will be encryption.
* @param {PeerId} remotePeer - optional PeerId of the initiating peer, if known. This may only exist during transport upgrades.
* @returns {Promise<SecureOutbound>}
*/
2019-11-28 17:53:27 +01:00
public async secureInbound(localPeer: PeerId, connection: any, remotePeer: PeerId): Promise<SecureOutbound> {
2019-11-25 14:03:23 +01:00
const wrappedConnection = Wrap(connection);
2019-12-03 13:39:33 +01:00
const libp2pPublicKey = localPeer.marshalPubKey();
2020-01-03 14:53:14 +01:00
const handshake = await this.performXXHandshake(wrappedConnection, false, libp2pPublicKey, remotePeer);
2019-11-28 17:32:46 +01: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,
remotePeer,
};
2019-11-11 21:58:04 +01:00
}
2020-01-03 14:53:14 +01:00
private async performXXHandshake(
2019-11-21 14:43:12 +01:00
connection: WrappedConnection,
2019-11-11 21:58:04 +01:00
isInitiator: boolean,
2019-11-28 17:32:46 +01:00
libp2pPublicKey: bytes,
2019-12-02 13:18:31 +01:00
remotePeer: PeerId,
2019-11-28 17:32:46 +01:00
): Promise<Handshake> {
2019-11-20 13:23:36 +01:00
const prologue = Buffer.from(this.protocol);
2019-12-03 13:39:33 +01:00
const handshake = new Handshake(isInitiator, this.privateKey, libp2pPublicKey, prologue, this.staticKeys, connection, remotePeer);
2019-11-21 13:38:39 +01:00
try {
await handshake.propose();
await handshake.exchange();
await handshake.finish(this.earlyData);
} catch (e) {
throw new Error(`Error occurred during handshake: ${e.message}`);
}
2019-11-28 17:32:46 +01:00
return handshake;
}
2019-11-11 21:58:04 +01:00
2019-11-28 17:32:46 +01:00
private async createSecureConnection(
connection: WrappedConnection,
handshake: Handshake,
2019-11-28 17:53:27 +01:00
): Promise<Duplex> {
2019-11-25 13:09:40 +01:00
// Create encryption box/unbox wrapper
2019-11-25 13:27:55 +01:00
const [secure, user] = DuplexPair();
const network = connection.unwrap();
pipe(
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
2019-12-29 18:23:43 +01:00
lp.encode({ lengthEncoder: uint16BEEncode }), // prefix with message length
2019-11-25 13:27:55 +01:00
network, // send to the remote peer
2019-12-29 18:23:43 +01:00
lp.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
);
return user;
2019-11-08 14:03:34 +01:00
}
2019-11-11 21:58:04 +01:00
2019-11-08 14:03:34 +01:00
}