2020-02-07 20:21:27 +01:00
|
|
|
import {x25519} from 'bcrypto';
|
|
|
|
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';
|
2020-02-13 22:51:36 +01:00
|
|
|
import {encode, decode} from 'it-length-prefixed';
|
2019-11-08 14:03:34 +01:00
|
|
|
|
2020-02-07 20:21:27 +01:00
|
|
|
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";
|
2020-02-10 13:53:10 +01:00
|
|
|
import {bytes} from "./@types/basic";
|
2020-02-07 20:21:27 +01:00
|
|
|
import {INoiseConnection, KeyPair, SecureOutbound} from "./@types/libp2p";
|
2020-04-03 13:20:15 +02:00
|
|
|
import {Duplex} from "it-pair";
|
2020-01-07 13:34:45 +01:00
|
|
|
import {IHandshake} from "./@types/handshake-interface";
|
2020-01-13 16:33:58 +01:00
|
|
|
import {KeyCache} from "./keycache";
|
|
|
|
import {logger} from "./logger";
|
2020-02-07 12:59:52 +01:00
|
|
|
import PeerId from "peer-id";
|
2020-02-13 22:51:36 +01:00
|
|
|
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 {
|
2019-11-20 13:23:36 +01:00
|
|
|
public protocol = "/noise";
|
|
|
|
|
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
|
|
|
/**
|
|
|
|
*
|
2020-02-07 13:06:42 +01:00
|
|
|
* @param staticNoiseKey x25519 private key, reuse for faster handshakes
|
2020-02-07 12:59:52 +01:00
|
|
|
* @param earlyData
|
|
|
|
*/
|
2020-03-04 09:02:59 +01:00
|
|
|
constructor(staticNoiseKey?: bytes, earlyData?: bytes) {
|
2019-11-26 10:52:30 +01:00
|
|
|
this.earlyData = earlyData || Buffer.alloc(0);
|
2020-03-04 09:02:59 +01:00
|
|
|
//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-02-07 12:59:52 +01:00
|
|
|
const publicKey = x25519.publicKeyCreate(staticNoiseKey);
|
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> {
|
2020-02-13 22:51:36 +01:00
|
|
|
const wrappedConnection = Wrap(
|
|
|
|
connection,
|
|
|
|
{
|
|
|
|
lengthEncoder: uint16BEEncode,
|
|
|
|
lengthDecoder: uint16BEDecode,
|
|
|
|
maxDataLength: NOISE_MSG_MAX_LENGTH_BYTES
|
|
|
|
}
|
|
|
|
);
|
2020-01-05 19:00:16 +01:00
|
|
|
const handshake = await this.performHandshake({
|
|
|
|
connection: wrappedConnection,
|
|
|
|
isInitiator: true,
|
2020-01-11 15:24:33 +01:00
|
|
|
localPeer,
|
2020-01-05 19:00:16 +01:00
|
|
|
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,
|
2020-04-17 10:36:29 +02:00
|
|
|
earlyData: handshake.earlyData,
|
2020-03-11 09:08:46 +01: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).
|
|
|
|
* @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>}
|
|
|
|
*/
|
2020-02-07 20:21:27 +01:00
|
|
|
public async secureInbound(localPeer: PeerId, connection: any, remotePeer?: PeerId): Promise<SecureOutbound> {
|
2020-02-13 22:51:36 +01:00
|
|
|
const wrappedConnection = Wrap(
|
|
|
|
connection,
|
|
|
|
{
|
|
|
|
lengthEncoder: uint16BEEncode,
|
|
|
|
lengthDecoder: uint16BEDecode,
|
|
|
|
maxDataLength: NOISE_MSG_MAX_LENGTH_BYTES
|
|
|
|
}
|
|
|
|
);
|
2020-01-05 19:00:16 +01:00
|
|
|
const handshake = await this.performHandshake({
|
|
|
|
connection: wrappedConnection,
|
|
|
|
isInitiator: false,
|
2020-01-11 15:24:33 +01:00
|
|
|
localPeer,
|
2020-01-05 19:00:16 +01:00
|
|
|
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,
|
2020-04-17 11:04:50 +02:00
|
|
|
remoteEarlyData: handshake.remoteEarlyData,
|
2020-02-07 20:21:27 +01:00
|
|
|
remotePeer: handshake.remotePeer
|
2019-11-25 14:03:23 +01: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.
|
2020-02-07 12:59:52 +01:00
|
|
|
* @param params
|
2020-01-03 15:07:46 +01:00
|
|
|
*/
|
2020-01-07 13:34:45 +01:00
|
|
|
private async performHandshake(params: HandshakeParams): Promise<IHandshake> {
|
2020-01-11 15:24:33 +01:00
|
|
|
const payload = await getPayload(params.localPeer, this.staticKeys.publicKey, this.earlyData);
|
2020-02-07 20:21:27 +01:00
|
|
|
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-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
|
|
|
|
const { remotePeer, connection, isInitiator } = params;
|
2020-02-07 20:21:27 +01:00
|
|
|
const ikHandshake = new IKHandshake(
|
|
|
|
isInitiator,
|
|
|
|
payload,
|
|
|
|
this.prologue,
|
|
|
|
this.staticKeys,
|
|
|
|
connection,
|
|
|
|
//safe to cast as we did checks
|
|
|
|
KeyCache.load(params.remotePeer) || Buffer.alloc(32),
|
|
|
|
remotePeer as PeerId,
|
|
|
|
);
|
2020-01-15 17:27:32 +01:00
|
|
|
|
2020-01-05 19:00:16 +01:00
|
|
|
try {
|
2020-01-21 11:20:00 +01: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-01-15 17:27:32 +01:00
|
|
|
let ephemeralKeys;
|
2020-02-08 11:21:51 +01:00
|
|
|
if (params.isInitiator) {
|
|
|
|
ephemeralKeys = ikHandshake.getLocalEphemeralKeys();
|
2020-01-15 17:27:32 +01: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-01-11 15:24:33 +01:00
|
|
|
return await this.performXXHandshake(params, payload);
|
2020-01-03 15:07:46 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-05 19:00:16 +01:00
|
|
|
private async performXXFallbackHandshake(
|
|
|
|
params: HandshakeParams,
|
2020-01-11 15:24:33 +01:00
|
|
|
payload: bytes,
|
2020-01-05 19:00:16 +01:00
|
|
|
initialMsg: bytes,
|
2020-01-15 17:27:32 +01:00
|
|
|
ephemeralKeys?: KeyPair,
|
2020-01-07 13:34:45 +01:00
|
|
|
): Promise<XXFallbackHandshake> {
|
2020-01-11 20:20:57 +01:00
|
|
|
const { isInitiator, remotePeer, connection } = params;
|
2020-01-05 19:00:16 +01:00
|
|
|
const handshake =
|
2020-02-07 20:21:27 +01:00
|
|
|
new XXFallbackHandshake(isInitiator, payload, this.prologue, this.staticKeys, connection, initialMsg, remotePeer, ephemeralKeys);
|
2020-01-05 19:00:16 +01:00
|
|
|
|
|
|
|
try {
|
|
|
|
await handshake.propose();
|
|
|
|
await handshake.exchange();
|
2020-01-07 17:01:52 +01:00
|
|
|
await handshake.finish();
|
2020-01-05 19:00:16 +01:00
|
|
|
} catch (e) {
|
2020-01-15 17:27:32 +01:00
|
|
|
logger(e);
|
2020-01-05 19:00:16 +01:00
|
|
|
throw new Error(`Error occurred during XX Fallback handshake: ${e.message}`);
|
|
|
|
}
|
|
|
|
|
|
|
|
return handshake;
|
|
|
|
}
|
|
|
|
|
2020-01-03 14:53:14 +01:00
|
|
|
private async performXXHandshake(
|
2020-01-05 19:00:16 +01:00
|
|
|
params: HandshakeParams,
|
2020-01-11 15:24:33 +01:00
|
|
|
payload: bytes,
|
2020-01-07 13:34:45 +01:00
|
|
|
): Promise<XXHandshake> {
|
2020-01-11 20:20:57 +01: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
|
|
|
|
2019-12-02 15:24:49 +01:00
|
|
|
try {
|
|
|
|
await handshake.propose();
|
|
|
|
await handshake.exchange();
|
2020-01-11 15:24:33 +01:00
|
|
|
await handshake.finish();
|
2020-01-13 15:38:14 +01:00
|
|
|
|
2020-02-08 12:11:16 +01:00
|
|
|
if (this.useNoisePipes && handshake.remotePeer) {
|
|
|
|
KeyCache.store(handshake.remotePeer, handshake.getRemoteStaticKey());
|
2020-01-13 15:38:14 +01:00
|
|
|
}
|
2019-12-02 15:24:49 +01:00
|
|
|
} catch (e) {
|
2020-01-05 19:00:16 +01:00
|
|
|
throw new Error(`Error occurred during XX handshake: ${e.message}`);
|
2019-12-02 15:24:49 +01:00
|
|
|
}
|
2019-11-28 17:32:46 +01:00
|
|
|
|
|
|
|
return handshake;
|
|
|
|
}
|
2019-11-11 21:58:04 +01:00
|
|
|
|
2020-01-05 19:00:16 +01:00
|
|
|
private async performIKHandshake(
|
2020-01-13 16:33:58 +01:00
|
|
|
handshake: IKHandshake,
|
2020-01-07 13:34:45 +01:00
|
|
|
): Promise<IKHandshake> {
|
2020-01-15 11:32:40 +01:00
|
|
|
|
2020-01-17 23:40:51 +01:00
|
|
|
await handshake.stage0();
|
|
|
|
await handshake.stage1();
|
2020-01-05 19:00:16 +01:00
|
|
|
|
|
|
|
return handshake;
|
|
|
|
}
|
|
|
|
|
2019-11-28 17:32:46 +01:00
|
|
|
private async createSecureConnection(
|
|
|
|
connection: WrappedConnection,
|
2020-01-07 13:34:45 +01: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
|
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
|
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-02-17 12:11:55 +01: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
|
|
|
|
);
|
|
|
|
|
|
|
|
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
|
|
|
}
|