Merge pull request #19 from NodeFactoryIo/feature/ik-handshake-flow

Integrate IK handshake flow
This commit is contained in:
Belma Gutlic 2020-01-24 11:51:36 +01:00 committed by GitHub
commit 13ba253edd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 462 additions and 38 deletions

View File

@ -52,6 +52,7 @@
"chai": "^4.2.0", "chai": "^4.2.0",
"eslint": "^6.6.0", "eslint": "^6.6.0",
"mocha": "^6.2.2", "mocha": "^6.2.2",
"sinon": "^8.1.0",
"typescript": "^3.6.4" "typescript": "^3.6.4"
}, },
"dependencies": { "dependencies": {

View File

@ -14,6 +14,8 @@ export const uint16BEDecode = data => {
}; };
uint16BEDecode.bytes = 2; uint16BEDecode.bytes = 2;
// Note: IK and XX encoder usage is opposite (XX uses in stages encode0 where IK uses encode1)
export function encode0(message: MessageBuffer): bytes { export function encode0(message: MessageBuffer): bytes {
return Buffer.concat([message.ne, message.ciphertext]); return Buffer.concat([message.ne, message.ciphertext]);
} }

10
src/errors.ts Normal file
View File

@ -0,0 +1,10 @@
export class FailedIKError extends Error {
public initialMsg;
constructor(initialMsg, message?: string) {
super(message);
this.initialMsg = initialMsg;
this.name = "FailedIKhandshake";
}
};

View File

@ -5,6 +5,10 @@ import {bytes, bytes32} from "./@types/basic";
import {KeyPair, PeerId} from "./@types/libp2p"; import {KeyPair, PeerId} from "./@types/libp2p";
import {IHandshake} from "./@types/handshake-interface"; import {IHandshake} from "./@types/handshake-interface";
import {Buffer} from "buffer"; import {Buffer} from "buffer";
import {decode0, decode1, encode0, encode1} from "./encoder";
import {verifySignedPayload} from "./utils";
import {FailedIKError} from "./errors";
import {logger} from "./logger";
export class IKHandshake implements IHandshake { export class IKHandshake implements IHandshake {
public isInitiator: boolean; public isInitiator: boolean;
@ -28,7 +32,7 @@ export class IKHandshake implements IHandshake {
handshake?: IK, handshake?: IK,
) { ) {
this.isInitiator = isInitiator; this.isInitiator = isInitiator;
this.payload = payload; this.payload = Buffer.from(payload);
this.prologue = prologue; this.prologue = prologue;
this.staticKeypair = staticKeypair; this.staticKeypair = staticKeypair;
this.connection = connection; this.connection = connection;
@ -38,6 +42,52 @@ export class IKHandshake implements IHandshake {
this.session = this.ik.initSession(this.isInitiator, this.prologue, this.staticKeypair, remoteStaticKey); this.session = this.ik.initSession(this.isInitiator, this.prologue, this.staticKeypair, remoteStaticKey);
} }
public async stage0(): Promise<void> {
if (this.isInitiator) {
logger("IK Stage 0 - Initiator sending message...");
const messageBuffer = this.ik.sendMessage(this.session, this.payload);
this.connection.writeLP(encode1(messageBuffer));
logger("IK Stage 0 - Initiator sent message.");
} else {
logger("IK Stage 0 - Responder receiving message...");
const receivedMsg = await this.connection.readLP();
try {
const receivedMessageBuffer = decode1(receivedMsg);
const plaintext = this.ik.recvMessage(this.session, receivedMessageBuffer);
logger("IK Stage 0 - Responder got message, going to verify payload.");
await verifySignedPayload(receivedMessageBuffer.ns, plaintext, this.remotePeer.id);
logger("IK Stage 0 - Responder successfully verified payload!");
} catch (e) {
logger("Responder breaking up with IK handshake in stage 0.");
throw new FailedIKError(receivedMsg, `Error occurred while verifying initiator's signed payload: ${e.message}`);
}
}
}
public async stage1(): Promise<void> {
if (this.isInitiator) {
logger("IK Stage 1 - Initiator receiving message...");
const receivedMsg = (await this.connection.readLP()).slice();
const receivedMessageBuffer = decode0(Buffer.from(receivedMsg));
const plaintext = this.ik.recvMessage(this.session, receivedMessageBuffer);
logger("IK Stage 1 - Initiator got message, going to verify payload.");
try {
await verifySignedPayload(receivedMessageBuffer.ns, plaintext, this.remotePeer.id);
logger("IK Stage 1 - Initiator successfully verified payload!");
} catch (e) {
logger("Initiator breaking up with IK handshake in stage 1.");
throw new FailedIKError(receivedMsg, `Error occurred while verifying responder's signed payload: ${e.message}`);
}
} else {
logger("IK Stage 1 - Responder sending message...");
const messageBuffer = this.ik.sendMessage(this.session, this.payload);
this.connection.writeLP(encode0(messageBuffer));
logger("IK Stage 1 - Responder sent message...");
}
}
public decrypt(ciphertext: Buffer, session: NoiseSession): Buffer { public decrypt(ciphertext: Buffer, session: NoiseSession): Buffer {
const cs = this.getCS(session, false); const cs = this.getCS(session, false);
return this.ik.decryptWithAd(cs, Buffer.alloc(0), ciphertext); return this.ik.decryptWithAd(cs, Buffer.alloc(0), ciphertext);

View File

@ -39,37 +39,35 @@ export class XXFallbackHandshake extends XXHandshake {
this.xx.sendMessage(this.session, Buffer.alloc(0), this.ephemeralKeys); this.xx.sendMessage(this.session, Buffer.alloc(0), this.ephemeralKeys);
logger("XX Fallback Stage 0 - Initialized state as the first message was sent by initiator."); logger("XX Fallback Stage 0 - Initialized state as the first message was sent by initiator.");
} else { } else {
logger("XX Fallback Stage 0 - Responder waiting to receive first message..."); logger("XX Fallback Stage 0 - Responder decoding initial msg from IK.")
const receivedMessageBuffer = decode0(this.initialMsg); const receivedMessageBuffer = decode0(this.initialMsg);
this.xx.recvMessage(this.session, { this.xx.recvMessage(this.session, {
ne: receivedMessageBuffer.ne, ne: receivedMessageBuffer.ne,
ns: Buffer.alloc(0), ns: Buffer.alloc(0),
ciphertext: Buffer.alloc(0), ciphertext: Buffer.alloc(0),
}); });
logger("XX Fallback Stage 0 - Responder received first message."); logger("XX Fallback Stage 0 - Responder used received message from IK.");
} }
} }
// stage 1 // stage 1
public async exchange(): Promise<void> { public async exchange(): Promise<void> {
if (this.isInitiator) { if (this.isInitiator) {
logger('XX Fallback Stage 1 - Initiator waiting to receive first message from responder...');
const receivedMessageBuffer = decode1(this.initialMsg); const receivedMessageBuffer = decode1(this.initialMsg);
const plaintext = this.xx.recvMessage(this.session, receivedMessageBuffer); const plaintext = this.xx.recvMessage(this.session, receivedMessageBuffer);
logger('XX Fallback Stage 1 - Initiator received the message. Got remote\'s static key.'); logger('XX Fallback Stage 1 - Initiator used received message from IK.');
logger("Initiator going to check remote's signature..."); logger("Initiator going to check remote's signature...");
try { try {
await verifySignedPayload(receivedMessageBuffer.ns, plaintext, this.remotePeer.id); await verifySignedPayload(receivedMessageBuffer.ns, plaintext, this.remotePeer.id);
} catch (e) { } catch (e) {
throw new Error(`Error occurred while verifying signed payload: ${e.message}`); throw new Error(`Error occurred while verifying signed payload from responder: ${e.message}`);
} }
logger("All good with the signature!"); logger("All good with the signature!");
} else { } else {
logger('XX Fallback Stage 1 - Responder sending out first message with signed payload and static key.'); logger("XX Fallback Stage 1 - Responder start");
const messageBuffer = this.xx.sendMessage(this.session, this.payload); super.exchange();
this.connection.writeLP(encode1(messageBuffer)); logger("XX Fallback Stage 1 - Responder end");
logger('XX Fallback Stage 1 - Responder sent the second handshake message with signed payload.')
} }
} }
} }

View File

@ -53,7 +53,7 @@ export class XXHandshake implements IHandshake {
logger("Stage 0 - Initiator finished sending first message."); logger("Stage 0 - Initiator finished sending first message.");
} else { } else {
logger("Stage 0 - Responder waiting to receive first message..."); logger("Stage 0 - Responder waiting to receive first message...");
const receivedMessageBuffer = decode0((await this.connection.readLP()).slice()); const receivedMessageBuffer = decode0(await this.connection.readLP());
this.xx.recvMessage(this.session, receivedMessageBuffer); this.xx.recvMessage(this.session, receivedMessageBuffer);
logger("Stage 0 - Responder received first message."); logger("Stage 0 - Responder received first message.");
} }
@ -63,7 +63,7 @@ export class XXHandshake implements IHandshake {
public async exchange(): Promise<void> { public async exchange(): Promise<void> {
if (this.isInitiator) { if (this.isInitiator) {
logger('Stage 1 - Initiator waiting to receive first message from responder...'); logger('Stage 1 - Initiator waiting to receive first message from responder...');
const receivedMessageBuffer = decode1((await this.connection.readLP()).slice()); const receivedMessageBuffer = decode1(await this.connection.readLP());
const plaintext = this.xx.recvMessage(this.session, receivedMessageBuffer); const plaintext = this.xx.recvMessage(this.session, receivedMessageBuffer);
logger('Stage 1 - Initiator received the message. Got remote\'s static key.'); logger('Stage 1 - Initiator received the message. Got remote\'s static key.');
@ -91,7 +91,7 @@ export class XXHandshake implements IHandshake {
logger('Stage 2 - Initiator sent message with signed payload.'); logger('Stage 2 - Initiator sent message with signed payload.');
} else { } else {
logger('Stage 2 - Responder waiting for third handshake message...'); logger('Stage 2 - Responder waiting for third handshake message...');
const receivedMessageBuffer = decode1((await this.connection.readLP()).slice()); const receivedMessageBuffer = decode1(await this.connection.readLP());
const plaintext = this.xx.recvMessage(this.session, receivedMessageBuffer); const plaintext = this.xx.recvMessage(this.session, receivedMessageBuffer);
logger('Stage 2 - Responder received the message, finished handshake. Got remote\'s static key.'); logger('Stage 2 - Responder received the message, finished handshake. Got remote\'s static key.');

View File

@ -3,7 +3,7 @@ import {BN} from "bn.js";
import {HandshakeState, MessageBuffer, NoiseSession} from "../@types/handshake"; import {HandshakeState, MessageBuffer, NoiseSession} from "../@types/handshake";
import {bytes, bytes32} from "../@types/basic"; import {bytes, bytes32} from "../@types/basic";
import {generateKeypair, getHkdf, isValidPublicKey} from "../utils"; import {generateKeypair, isValidPublicKey} from "../utils";
import {AbstractHandshake} from "./abstract-handshake"; import {AbstractHandshake} from "./abstract-handshake";
import {KeyPair} from "../@types/libp2p"; import {KeyPair} from "../@types/libp2p";
@ -68,7 +68,6 @@ export class IK extends AbstractHandshake {
session.h = h; session.h = h;
session.cs1 = cs1; session.cs1 = cs1;
session.cs2 = cs2; session.cs2 = cs2;
delete session.hs;
} else if (session.mc.gtn(1)) { } else if (session.mc.gtn(1)) {
if (session.i) { if (session.i) {
if (!session.cs2) { if (!session.cs2) {

View File

@ -7,12 +7,12 @@ import {bytes, bytes32} from "./@types/basic";
class Keycache { class Keycache {
private storage = new Map<bytes, bytes32>(); private storage = new Map<bytes, bytes32>();
public async store(peerId: PeerId, key: bytes32): Promise<void> { public store(peerId: PeerId, key: bytes32): void {
this.storage.set(peerId.id, key); this.storage.set(peerId.id, key);
} }
public async load(peerId: PeerId): Promise<bytes32|null> { public load(peerId: PeerId): bytes32|undefined {
return this.storage.get(peerId.id) || null; return this.storage.get(peerId.id);
} }
public resetStorage(): void { public resetStorage(): void {

View File

@ -108,23 +108,22 @@ export class Noise implements INoiseConnection {
private async performHandshake(params: HandshakeParams): Promise<IHandshake> { private async performHandshake(params: HandshakeParams): Promise<IHandshake> {
const payload = await getPayload(params.localPeer, this.staticKeys.publicKey, this.earlyData); const payload = await getPayload(params.localPeer, this.staticKeys.publicKey, this.earlyData);
let foundRemoteStaticKey: bytes|null = null; const remoteStaticKey = KeyCache.load(params.remotePeer);
if (this.useNoisePipes && params.isInitiator) { // Try IK if acting as responder or initiator that has remote's static key.
logger("Initiator using noise pipes. Going to load cached static key..."); if (this.useNoisePipes && remoteStaticKey) {
foundRemoteStaticKey = await KeyCache.load(params.remotePeer);
logger(`Static key has been found: ${!!foundRemoteStaticKey}`)
}
if (foundRemoteStaticKey) {
// Try IK first // Try IK first
const { remotePeer, connection, isInitiator } = params; const { remotePeer, connection, isInitiator } = params;
const IKhandshake = new IKHandshake(isInitiator, payload, this.prologue, this.staticKeys, connection, remotePeer, foundRemoteStaticKey); const ikHandshake = new IKHandshake(isInitiator, payload, this.prologue, this.staticKeys, connection, remotePeer, remoteStaticKey);
try { try {
return await this.performIKHandshake(IKhandshake, payload); return await this.performIKHandshake(ikHandshake);
} catch (e) { } catch (e) {
// IK failed, go to XX fallback // IK failed, go to XX fallback
const ephemeralKeys = IKhandshake.getRemoteEphemeralKeys(); let ephemeralKeys;
return await this.performXXFallbackHandshake(params, payload, ephemeralKeys, e.initialMsg); if (params.isInitiator) {
ephemeralKeys = ikHandshake.getRemoteEphemeralKeys();
}
return await this.performXXFallbackHandshake(params, payload, e.initialMsg, ephemeralKeys);
} }
} else { } else {
// Noise pipes not supported, use XX // Noise pipes not supported, use XX
@ -135,8 +134,8 @@ export class Noise implements INoiseConnection {
private async performXXFallbackHandshake( private async performXXFallbackHandshake(
params: HandshakeParams, params: HandshakeParams,
payload: bytes, payload: bytes,
ephemeralKeys: KeyPair,
initialMsg: bytes, initialMsg: bytes,
ephemeralKeys?: KeyPair,
): Promise<XXFallbackHandshake> { ): Promise<XXFallbackHandshake> {
const { isInitiator, remotePeer, connection } = params; const { isInitiator, remotePeer, connection } = params;
const handshake = const handshake =
@ -147,6 +146,7 @@ export class Noise implements INoiseConnection {
await handshake.exchange(); await handshake.exchange();
await handshake.finish(); await handshake.finish();
} catch (e) { } catch (e) {
logger(e);
throw new Error(`Error occurred during XX Fallback handshake: ${e.message}`); throw new Error(`Error occurred during XX Fallback handshake: ${e.message}`);
} }
@ -166,7 +166,7 @@ export class Noise implements INoiseConnection {
await handshake.finish(); await handshake.finish();
if (this.useNoisePipes) { if (this.useNoisePipes) {
await KeyCache.store(remotePeer, handshake.getRemoteStaticKey()); KeyCache.store(remotePeer, handshake.getRemoteStaticKey());
} }
} catch (e) { } catch (e) {
throw new Error(`Error occurred during XX handshake: ${e.message}`); throw new Error(`Error occurred during XX handshake: ${e.message}`);
@ -177,9 +177,10 @@ export class Noise implements INoiseConnection {
private async performIKHandshake( private async performIKHandshake(
handshake: IKHandshake, handshake: IKHandshake,
payload: bytes,
): Promise<IKHandshake> { ): Promise<IKHandshake> {
// TODO
await handshake.stage0();
await handshake.stage1();
return handshake; return handshake;
} }

View File

@ -89,8 +89,13 @@ async function isValidPeerId(peerId: bytes, publicKeyProtobuf: bytes) {
} }
export async function verifySignedPayload(noiseStaticKey: bytes, plaintext: bytes, peerId: bytes) { export async function verifySignedPayload(noiseStaticKey: bytes, plaintext: bytes, peerId: bytes) {
const NoiseHandshakePayload = await loadPayloadProto(); let receivedPayload;
const receivedPayload = NoiseHandshakePayload.toObject(NoiseHandshakePayload.decode(plaintext)); try {
const NoiseHandshakePayload = await loadPayloadProto();
receivedPayload = NoiseHandshakePayload.toObject(NoiseHandshakePayload.decode(plaintext));
} catch (e) {
throw new Error("Failed to decode received payload.");
}
if (!(await isValidPeerId(peerId, receivedPayload.libp2pKey)) ) { if (!(await isValidPeerId(peerId, receivedPayload.libp2pKey)) ) {
throw new Error("Peer ID doesn't match libp2p public key."); throw new Error("Peer ID doesn't match libp2p public key.");

80
test/ik-handshake.test.ts Normal file
View File

@ -0,0 +1,80 @@
import Wrap from "it-pb-rpc";
import Duplex from 'it-pair/duplex';
import {Buffer} from "buffer";
import {assert, expect} from "chai";
import {createPeerIdsFromFixtures} from "./fixtures/peer";
import {generateKeypair, getPayload} from "../src/utils";
import {IKHandshake} from "../src/handshake-ik";
describe("IK Handshake", () => {
let peerA, peerB, fakePeer;
before(async () => {
[peerA, peerB, fakePeer] = await createPeerIdsFromFixtures(3);
});
it("should finish both stages as initiator and responder", async() => {
try {
const duplex = Duplex();
const connectionFrom = Wrap(duplex[0]);
const connectionTo = Wrap(duplex[1]);
const prologue = Buffer.from('/noise');
const staticKeysInitiator = generateKeypair();
const staticKeysResponder = generateKeypair();
const initPayload = await getPayload(peerA, staticKeysInitiator.publicKey);
const handshakeInit = new IKHandshake(true, initPayload, prologue, staticKeysInitiator, connectionFrom, peerB, staticKeysResponder.publicKey);
const respPayload = await getPayload(peerB, staticKeysResponder.publicKey);
const handshakeResp = new IKHandshake(false, respPayload, prologue, staticKeysResponder, connectionTo, peerA, staticKeysInitiator.publicKey);
await handshakeInit.stage0();
await handshakeResp.stage0();
await handshakeResp.stage1();
await handshakeInit.stage1();
// Test shared key
if (handshakeInit.session.cs1 && handshakeResp.session.cs1 && handshakeInit.session.cs2 && handshakeResp.session.cs2) {
assert(handshakeInit.session.cs1.k.equals(handshakeResp.session.cs1.k));
assert(handshakeInit.session.cs2.k.equals(handshakeResp.session.cs2.k));
} else {
assert(false);
}
// Test encryption and decryption
const encrypted = handshakeInit.encrypt(Buffer.from("encryptthis"), handshakeInit.session);
const decrypted = handshakeResp.decrypt(encrypted, handshakeResp.session);
assert(decrypted.equals(Buffer.from("encryptthis")));
} catch (e) {
console.error(e);
assert(false, e.message);
}
});
it("should throw error if responder's static key changed", async() => {
try {
const duplex = Duplex();
const connectionFrom = Wrap(duplex[0]);
const connectionTo = Wrap(duplex[1]);
const prologue = Buffer.from('/noise');
const staticKeysInitiator = generateKeypair();
const staticKeysResponder = generateKeypair();
const oldScammyKeys = generateKeypair();
const initPayload = await getPayload(peerA, staticKeysInitiator.publicKey);
const handshakeInit = new IKHandshake(true, initPayload, prologue, staticKeysInitiator, connectionFrom, peerB, oldScammyKeys.publicKey);
const respPayload = await getPayload(peerB, staticKeysResponder.publicKey);
const handshakeResp = new IKHandshake(false, respPayload, prologue, staticKeysResponder, connectionTo, peerA, staticKeysInitiator.publicKey);
await handshakeInit.stage0();
await handshakeResp.stage0();
} catch (e) {
expect(e.message).to.include("Error occurred while verifying initiator's signed payload");
}
});
});

View File

@ -5,6 +5,7 @@ import { Noise } from "../src";
import {createPeerIdsFromFixtures} from "./fixtures/peer"; import {createPeerIdsFromFixtures} from "./fixtures/peer";
import Wrap from "it-pb-rpc"; import Wrap from "it-pb-rpc";
import { random } from "bcrypto"; import { random } from "bcrypto";
import sinon from "sinon";
import {XXHandshake} from "../src/handshake-xx"; import {XXHandshake} from "../src/handshake-xx";
import { import {
createHandshakePayload, createHandshakePayload,
@ -16,15 +17,22 @@ import {decode0, decode1, encode1} from "../src/encoder";
import {XX} from "../src/handshakes/xx"; import {XX} from "../src/handshakes/xx";
import {Buffer} from "buffer"; import {Buffer} from "buffer";
import {getKeyPairFromPeerId} from "./utils"; import {getKeyPairFromPeerId} from "./utils";
import {KeyCache} from "../src/keycache";
import {XXFallbackHandshake} from "../src/handshake-xx-fallback";
describe("Noise", () => { describe("Noise", () => {
let remotePeer, localPeer; let remotePeer, localPeer;
let sandbox = sinon.createSandbox();
before(async () => { before(async () => {
[localPeer, remotePeer] = await createPeerIdsFromFixtures(2); [localPeer, remotePeer] = await createPeerIdsFromFixtures(2);
}); });
it("should communicate through encrypted streams", async() => { afterEach(function() {
sandbox.restore();
});
it("should communicate through encrypted streams without noise pipes", async() => {
try { try {
const noiseInit = new Noise(undefined, undefined, false); const noiseInit = new Noise(undefined, undefined, false);
const noiseResp = new Noise(undefined, undefined, false); const noiseResp = new Noise(undefined, undefined, false);
@ -120,4 +128,174 @@ describe("Noise", () => {
assert(false, e.message); assert(false, e.message);
} }
}); });
it("should communicate through encrypted streams with noise pipes", async() => {
try {
const staticKeysInitiator = generateKeypair();
const noiseInit = new Noise(staticKeysInitiator.privateKey);
const staticKeysResponder = generateKeypair();
const noiseResp = new Noise(staticKeysResponder.privateKey);
// Prepare key cache for noise pipes
KeyCache.store(localPeer, staticKeysInitiator.publicKey);
KeyCache.store(remotePeer, staticKeysResponder.publicKey);
const xxSpy = sandbox.spy(noiseInit, "performXXHandshake");
const xxFallbackSpy = sandbox.spy(noiseInit, "performXXFallbackHandshake");
const [inboundConnection, outboundConnection] = DuplexPair();
const [outbound, inbound] = await Promise.all([
noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer),
noiseResp.secureInbound(remotePeer, inboundConnection, localPeer),
]);
const wrappedInbound = Wrap(inbound.conn);
const wrappedOutbound = Wrap(outbound.conn);
wrappedOutbound.writeLP(Buffer.from("test v2"));
const response = await wrappedInbound.readLP();
expect(response.toString()).equal("test v2");
assert(xxSpy.notCalled);
assert(xxFallbackSpy.notCalled);
} catch (e) {
console.error(e);
assert(false, e.message);
}
});
it("IK -> XX fallback: initiator has invalid remote static key", async() => {
try {
const staticKeysInitiator = generateKeypair();
const noiseInit = new Noise(staticKeysInitiator.privateKey);
const noiseResp = new Noise();
const xxSpy = sandbox.spy(noiseInit, "performXXFallbackHandshake");
// Prepare key cache for noise pipes
KeyCache.resetStorage();
KeyCache.store(localPeer, staticKeysInitiator.publicKey);
KeyCache.store(remotePeer, generateKeypair().publicKey);
const [inboundConnection, outboundConnection] = DuplexPair();
const [outbound, inbound] = await Promise.all([
noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer),
noiseResp.secureInbound(remotePeer, inboundConnection, localPeer),
]);
const wrappedInbound = Wrap(inbound.conn);
const wrappedOutbound = Wrap(outbound.conn);
wrappedOutbound.writeLP(Buffer.from("test fallback"));
const response = await wrappedInbound.readLP();
expect(response.toString()).equal("test fallback");
assert(xxSpy.calledOnce, "XX Fallback method was never called.");
} catch (e) {
console.error(e);
assert(false, e.message);
}
});
it("IK -> XX fallback: responder has disabled noise pipes", async() => {
try {
const staticKeysInitiator = generateKeypair();
const noiseInit = new Noise(staticKeysInitiator.privateKey);
const staticKeysResponder = generateKeypair();
const noiseResp = new Noise(staticKeysResponder.privateKey, undefined, false);
const xxSpy = sandbox.spy(noiseInit, "performXXFallbackHandshake");
// Prepare key cache for noise pipes
KeyCache.store(localPeer, staticKeysInitiator.publicKey);
KeyCache.store(remotePeer, staticKeysResponder.publicKey);
const [inboundConnection, outboundConnection] = DuplexPair();
const [outbound, inbound] = await Promise.all([
noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer),
noiseResp.secureInbound(remotePeer, inboundConnection, localPeer),
]);
const wrappedInbound = Wrap(inbound.conn);
const wrappedOutbound = Wrap(outbound.conn);
wrappedOutbound.writeLP(Buffer.from("test fallback"));
const response = await wrappedInbound.readLP();
expect(response.toString()).equal("test fallback");
assert(xxSpy.calledOnce, "XX Fallback method was never called.");
} catch (e) {
console.error(e);
assert(false, e.message);
}
});
it("Initiator starts with XX (pipes disabled), responder has enabled noise pipes", async() => {
try {
const staticKeysInitiator = generateKeypair();
const noiseInit = new Noise(staticKeysInitiator.privateKey, undefined, false);
const staticKeysResponder = generateKeypair();
const noiseResp = new Noise(staticKeysResponder.privateKey);
const xxInitSpy = sandbox.spy(noiseInit, "performXXHandshake");
const xxRespSpy = sandbox.spy(noiseResp, "performXXFallbackHandshake");
// Prepare key cache for noise pipes
KeyCache.store(localPeer, staticKeysInitiator.publicKey);
const [inboundConnection, outboundConnection] = DuplexPair();
const [outbound, inbound] = await Promise.all([
noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer),
noiseResp.secureInbound(remotePeer, inboundConnection, localPeer),
]);
const wrappedInbound = Wrap(inbound.conn);
const wrappedOutbound = Wrap(outbound.conn);
wrappedOutbound.writeLP(Buffer.from("test fallback"));
const response = await wrappedInbound.readLP();
expect(response.toString()).equal("test fallback");
assert(xxInitSpy.calledOnce, "XX method was never called.");
assert(xxRespSpy.calledOnce, "XX Fallback method was never called.");
} catch (e) {
console.error(e);
assert(false, e.message);
}
});
it("IK -> XX Fallback: responder has no remote static key", async() => {
try {
const staticKeysInitiator = generateKeypair();
const noiseInit = new Noise(staticKeysInitiator.privateKey);
const staticKeysResponder = generateKeypair();
const noiseResp = new Noise(staticKeysResponder.privateKey);
const xxFallbackInitSpy = sandbox.spy(noiseInit, "performXXFallbackHandshake");
const xxRespSpy = sandbox.spy(noiseResp, "performXXHandshake");
// Prepare key cache for noise pipes
KeyCache.resetStorage();
KeyCache.store(remotePeer, staticKeysResponder.publicKey);
const [inboundConnection, outboundConnection] = DuplexPair();
const [outbound, inbound] = await Promise.all([
noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer),
noiseResp.secureInbound(remotePeer, inboundConnection, localPeer),
]);
const wrappedInbound = Wrap(inbound.conn);
const wrappedOutbound = Wrap(outbound.conn);
wrappedOutbound.writeLP(Buffer.from("test fallback"));
const response = await wrappedInbound.readLP();
expect(response.toString()).equal("test fallback");
assert(xxFallbackInitSpy.calledOnce, "XX Fallback method was not called.");
assert(xxRespSpy.calledOnce, "XX method was not called.");
} catch (e) {
console.error(e);
assert(false, e.message);
}
});
}); });

102
yarn.lock
View File

@ -869,6 +869,35 @@
resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570"
integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA= integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=
"@sinonjs/commons@^1", "@sinonjs/commons@^1.6.0", "@sinonjs/commons@^1.7.0":
version "1.7.0"
resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.7.0.tgz#f90ffc52a2e519f018b13b6c4da03cbff36ebed6"
integrity sha512-qbk9AP+cZUsKdW1GJsBpxPKFmCJ0T8swwzVje3qFd+AkQb74Q/tiuzrdfFg8AD2g5HH/XbE/I8Uc1KYHVYWfhg==
dependencies:
type-detect "4.0.8"
"@sinonjs/formatio@^4.0.1":
version "4.0.1"
resolved "https://registry.yarnpkg.com/@sinonjs/formatio/-/formatio-4.0.1.tgz#50ac1da0c3eaea117ca258b06f4f88a471668bdb"
integrity sha512-asIdlLFrla/WZybhm0C8eEzaDNNrzymiTqHMeJl6zPW2881l3uuVRpm0QlRQEjqYWv6CcKMGYME3LbrLJsORBw==
dependencies:
"@sinonjs/commons" "^1"
"@sinonjs/samsam" "^4.2.0"
"@sinonjs/samsam@^4.2.0", "@sinonjs/samsam@^4.2.2":
version "4.2.2"
resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-4.2.2.tgz#0f6cb40e467865306d8a20a97543a94005204e23"
integrity sha512-z9o4LZUzSD9Hl22zV38aXNykgFeVj8acqfFabCY6FY83n/6s/XwNJyYYldz6/9lBJanpno9h+oL6HTISkviweA==
dependencies:
"@sinonjs/commons" "^1.6.0"
lodash.get "^4.4.2"
type-detect "^4.0.8"
"@sinonjs/text-encoding@^0.7.1":
version "0.7.1"
resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz#8da5c6530915653f3a1f38fd5f101d8c3f8079c5"
integrity sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==
"@types/chai@^4.2.4": "@types/chai@^4.2.4":
version "4.2.4" version "4.2.4"
resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.2.4.tgz#8936cffad3c96ec470a2dc26a38c3ba8b9b6f619" resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.2.4.tgz#8936cffad3c96ec470a2dc26a38c3ba8b9b6f619"
@ -1623,6 +1652,11 @@ diff@3.5.0:
resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==
diff@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
doctrine@^3.0.0: doctrine@^3.0.0:
version "3.0.0" version "3.0.0"
resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961"
@ -2093,6 +2127,11 @@ has-flag@^3.0.0:
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0=
has-flag@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
has-symbols@^1.0.0: has-symbols@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44"
@ -2421,6 +2460,11 @@ is-windows@^1.0.2:
resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==
isarray@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=
isarray@1.0.0, isarray@~1.0.0: isarray@1.0.0, isarray@~1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
@ -2560,6 +2604,11 @@ json5@^2.1.0:
dependencies: dependencies:
minimist "^1.2.0" minimist "^1.2.0"
just-extend@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-4.0.2.tgz#f3f47f7dfca0f989c55410a7ebc8854b07108afc"
integrity sha512-FrLwOgm+iXrPV+5zDU6Jqu4gCRXbWEQg2O3SKONsWE4w7AXFRkryS53bpWdaL9cNol+AmR3AEYz6kn+o0fCPnw==
keypair@^1.0.1: keypair@^1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/keypair/-/keypair-1.0.1.tgz#7603719270afb6564ed38a22087a06fc9aa4ea1b" resolved "https://registry.yarnpkg.com/keypair/-/keypair-1.0.1.tgz#7603719270afb6564ed38a22087a06fc9aa4ea1b"
@ -2643,6 +2692,11 @@ locate-path@^3.0.0:
p-locate "^3.0.0" p-locate "^3.0.0"
path-exists "^3.0.0" path-exists "^3.0.0"
lodash.get@^4.4.2:
version "4.4.2"
resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=
lodash.unescape@4.0.1: lodash.unescape@4.0.1:
version "4.0.1" version "4.0.1"
resolved "https://registry.yarnpkg.com/lodash.unescape/-/lodash.unescape-4.0.1.tgz#bf2249886ce514cda112fae9218cdc065211fc9c" resolved "https://registry.yarnpkg.com/lodash.unescape/-/lodash.unescape-4.0.1.tgz#bf2249886ce514cda112fae9218cdc065211fc9c"
@ -2660,6 +2714,13 @@ log-symbols@2.2.0:
dependencies: dependencies:
chalk "^2.0.1" chalk "^2.0.1"
lolex@^5.0.1, lolex@^5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/lolex/-/lolex-5.1.2.tgz#953694d098ce7c07bc5ed6d0e42bc6c0c6d5a367"
integrity sha512-h4hmjAvHTmd+25JSwrtTIuwbKdwg5NzZVRMLn9saij4SZaepCrTCxPr35H/3bjwfMJtN+t3CX8672UIkglz28A==
dependencies:
"@sinonjs/commons" "^1.7.0"
long@^4.0.0: long@^4.0.0:
version "4.0.0" version "4.0.0"
resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28"
@ -2923,6 +2984,18 @@ nice-try@^1.0.4:
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
nise@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/nise/-/nise-3.0.1.tgz#0659982af515e5aac15592226246243e8da0013d"
integrity sha512-fYcH9y0drBGSoi88kvhpbZEsenX58Yr+wOJ4/Mi1K4cy+iGP/a73gNoyNhu5E9QxPdgTlVChfIaAlnyOy/gHUA==
dependencies:
"@sinonjs/commons" "^1.7.0"
"@sinonjs/formatio" "^4.0.1"
"@sinonjs/text-encoding" "^0.7.1"
just-extend "^4.0.2"
lolex "^5.0.1"
path-to-regexp "^1.7.0"
node-environment-flags@1.0.5: node-environment-flags@1.0.5:
version "1.0.5" version "1.0.5"
resolved "https://registry.yarnpkg.com/node-environment-flags/-/node-environment-flags-1.0.5.tgz#fa930275f5bf5dae188d6192b24b4c8bbac3d76a" resolved "https://registry.yarnpkg.com/node-environment-flags/-/node-environment-flags-1.0.5.tgz#fa930275f5bf5dae188d6192b24b4c8bbac3d76a"
@ -3202,6 +3275,13 @@ path-parse@^1.0.6:
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c"
integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==
path-to-regexp@^1.7.0:
version "1.8.0"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a"
integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==
dependencies:
isarray "0.0.1"
pathval@^1.1.0: pathval@^1.1.0:
version "1.1.0" version "1.1.0"
resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.0.tgz#b942e6d4bde653005ef6b71361def8727d0645e0" resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.0.tgz#b942e6d4bde653005ef6b71361def8727d0645e0"
@ -3621,6 +3701,19 @@ signed-varint@^2.0.1:
dependencies: dependencies:
varint "~5.0.0" varint "~5.0.0"
sinon@^8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/sinon/-/sinon-8.1.0.tgz#30f82371ac6e7d4dccb77fa1525f5ea748c636ff"
integrity sha512-6/05TR+8QhEgTbyMWaConm8iPL609Eno7SqToPq63wC/jS/6NMEI4NxqtzlLkk3r/KcZT9xPXQodH0oJ917Hbg==
dependencies:
"@sinonjs/commons" "^1.7.0"
"@sinonjs/formatio" "^4.0.1"
"@sinonjs/samsam" "^4.2.2"
diff "^4.0.2"
lolex "^5.1.2"
nise "^3.0.1"
supports-color "^7.1.0"
slash@^2.0.0: slash@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44" resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44"
@ -3829,6 +3922,13 @@ supports-color@^5.3.0:
dependencies: dependencies:
has-flag "^3.0.0" has-flag "^3.0.0"
supports-color@^7.1.0:
version "7.1.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1"
integrity sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==
dependencies:
has-flag "^4.0.0"
table@^5.2.3: table@^5.2.3:
version "5.4.6" version "5.4.6"
resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e" resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e"
@ -3923,7 +4023,7 @@ type-check@~0.3.2:
dependencies: dependencies:
prelude-ls "~1.1.2" prelude-ls "~1.1.2"
type-detect@^4.0.0, type-detect@^4.0.5: type-detect@4.0.8, type-detect@^4.0.0, type-detect@^4.0.5, type-detect@^4.0.8:
version "4.0.8" version "4.0.8"
resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==