From eeaaa8cc843498e8230f009a086b3fc54cf35969 Mon Sep 17 00:00:00 2001 From: Belma Gutlic Date: Wed, 15 Jan 2020 11:32:40 +0100 Subject: [PATCH 01/11] Write and test IK stages --- src/handshake-ik.ts | 34 ++++++++++++++++++++++++ src/noise.ts | 4 ++- test/ik-handshake.test.ts | 56 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 test/ik-handshake.test.ts diff --git a/src/handshake-ik.ts b/src/handshake-ik.ts index 9ce370a..76d9e6a 100644 --- a/src/handshake-ik.ts +++ b/src/handshake-ik.ts @@ -5,6 +5,8 @@ import {bytes, bytes32} from "./@types/basic"; import {KeyPair, PeerId} from "./@types/libp2p"; import {IHandshake} from "./@types/handshake-interface"; import {Buffer} from "buffer"; +import {decode0, decode1, encode0, encode1} from "./encoder"; +import {verifySignedPayload} from "./utils"; export class IKHandshake implements IHandshake { public isInitiator: boolean; @@ -38,6 +40,38 @@ export class IKHandshake implements IHandshake { this.session = this.ik.initSession(this.isInitiator, this.prologue, this.staticKeypair, remoteStaticKey); } + public async stage0(): Promise { + if (this.isInitiator) { + const messageBuffer = this.ik.sendMessage(this.session, this.payload); + this.connection.writeLP(encode0(messageBuffer)); + } else { + const receivedMessageBuffer = decode0(await this.connection.readLP()); + const plaintext = this.ik.recvMessage(this.session, receivedMessageBuffer); + + try { + await verifySignedPayload(receivedMessageBuffer.ns, plaintext, this.remotePeer.id); + } catch (e) { + throw new Error(`Error occurred while verifying signed payload: ${e.message}`); + } + } + } + + public async stage1(): Promise { + if (this.isInitiator) { + const receivedMessageBuffer = decode1(await this.connection.readLP()); + const plaintext = this.ik.recvMessage(this.session, receivedMessageBuffer); + + try { + await verifySignedPayload(receivedMessageBuffer.ns, plaintext, this.remotePeer.id); + } catch (e) { + throw new Error(`Error occurred while verifying signed payload: ${e.message}`); + } + } else { + const messageBuffer = this.ik.sendMessage(this.session, this.payload); + this.connection.writeLP(encode1(messageBuffer)); + } + } + public decrypt(ciphertext: Buffer, session: NoiseSession): Buffer { const cs = this.getCS(session, false); return this.ik.decryptWithAd(cs, Buffer.alloc(0), ciphertext); diff --git a/src/noise.ts b/src/noise.ts index cebdec9..752e2a7 100644 --- a/src/noise.ts +++ b/src/noise.ts @@ -179,7 +179,9 @@ export class Noise implements INoiseConnection { handshake: IKHandshake, payload: bytes, ): Promise { - // TODO + + await handshake.stage0(); + await handshake.stage1(); return handshake; } diff --git a/test/ik-handshake.test.ts b/test/ik-handshake.test.ts new file mode 100644 index 0000000..1201d3b --- /dev/null +++ b/test/ik-handshake.test.ts @@ -0,0 +1,56 @@ +import Wrap from "it-pb-rpc"; +import Duplex from 'it-pair/duplex'; +import {Buffer} from "buffer"; + +import {createPeerIdsFromFixtures} from "./fixtures/peer"; +import {generateKeypair, getPayload} from "../src/utils"; +import {IKHandshake} from "../src/handshake-ik"; +import {assert} from "chai"; + +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); + } + }); +}); From 26cb0c5a17fa6b3b8b5e556bcac3ab096dd9d62e Mon Sep 17 00:00:00 2001 From: Belma Gutlic Date: Wed, 15 Jan 2020 11:40:32 +0100 Subject: [PATCH 02/11] Test negative output --- src/handshake-ik.ts | 4 ++-- test/ik-handshake.test.ts | 26 +++++++++++++++++++++++++- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/handshake-ik.ts b/src/handshake-ik.ts index 76d9e6a..63a8ffc 100644 --- a/src/handshake-ik.ts +++ b/src/handshake-ik.ts @@ -51,7 +51,7 @@ export class IKHandshake implements IHandshake { try { await verifySignedPayload(receivedMessageBuffer.ns, plaintext, this.remotePeer.id); } catch (e) { - throw new Error(`Error occurred while verifying signed payload: ${e.message}`); + throw new Error(`Error occurred while verifying initiator's signed payload: ${e.message}`); } } } @@ -64,7 +64,7 @@ export class IKHandshake implements IHandshake { try { await verifySignedPayload(receivedMessageBuffer.ns, plaintext, this.remotePeer.id); } catch (e) { - throw new Error(`Error occurred while verifying signed payload: ${e.message}`); + throw new Error(`Error occurred while verifying responder's signed payload: ${e.message}`); } } else { const messageBuffer = this.ik.sendMessage(this.session, this.payload); diff --git a/test/ik-handshake.test.ts b/test/ik-handshake.test.ts index 1201d3b..3083441 100644 --- a/test/ik-handshake.test.ts +++ b/test/ik-handshake.test.ts @@ -1,11 +1,11 @@ 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"; -import {assert} from "chai"; describe("IK Handshake", () => { let peerA, peerB, fakePeer; @@ -53,4 +53,28 @@ describe("IK Handshake", () => { 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"); + } + }); }); From 93f33028b4e08afeed21f14d1126b729b480a46e Mon Sep 17 00:00:00 2001 From: Belma Gutlic Date: Wed, 15 Jan 2020 11:54:49 +0100 Subject: [PATCH 03/11] Remove unused vars --- src/handshakes/ik.ts | 2 +- src/noise.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/handshakes/ik.ts b/src/handshakes/ik.ts index 8d41e49..75d646e 100644 --- a/src/handshakes/ik.ts +++ b/src/handshakes/ik.ts @@ -3,7 +3,7 @@ import {BN} from "bn.js"; import {HandshakeState, MessageBuffer, NoiseSession} from "../@types/handshake"; import {bytes, bytes32} from "../@types/basic"; -import {generateKeypair, getHkdf, isValidPublicKey} from "../utils"; +import {generateKeypair, isValidPublicKey} from "../utils"; import {AbstractHandshake} from "./abstract-handshake"; import {KeyPair} from "../@types/libp2p"; diff --git a/src/noise.ts b/src/noise.ts index 752e2a7..4d708dc 100644 --- a/src/noise.ts +++ b/src/noise.ts @@ -177,7 +177,6 @@ export class Noise implements INoiseConnection { private async performIKHandshake( handshake: IKHandshake, - payload: bytes, ): Promise { await handshake.stage0(); From 33708db8b8a1fb7db00753a89b0f99fd688a90ec Mon Sep 17 00:00:00 2001 From: Belma Gutlic Date: Wed, 15 Jan 2020 17:27:32 +0100 Subject: [PATCH 04/11] Test and fix failed IK handshakes --- src/errors.ts | 11 +++++++++ src/handshake-ik.ts | 11 +++++---- src/handshakes/ik.ts | 1 - src/keycache.ts | 4 ++-- src/noise.ts | 39 ++++++++++++++++++++++---------- src/utils.ts | 9 ++++++-- test/noise.test.ts | 53 +++++++++++++++++++++++++++++++++++++++++++- 7 files changed, 106 insertions(+), 22 deletions(-) create mode 100644 src/errors.ts diff --git a/src/errors.ts b/src/errors.ts new file mode 100644 index 0000000..09d2b10 --- /dev/null +++ b/src/errors.ts @@ -0,0 +1,11 @@ +export class FailedIKError extends Error { + public initialMsg; + + constructor(initialMsg, message?: string) { + super(message); + + this.initialMsg = initialMsg; + this.name = "FailedIKhandshake"; + this.stack = new Error().stack; + } +}; diff --git a/src/handshake-ik.ts b/src/handshake-ik.ts index 63a8ffc..5683c99 100644 --- a/src/handshake-ik.ts +++ b/src/handshake-ik.ts @@ -7,6 +7,7 @@ import {IHandshake} from "./@types/handshake-interface"; import {Buffer} from "buffer"; import {decode0, decode1, encode0, encode1} from "./encoder"; import {verifySignedPayload} from "./utils"; +import {FailedIKError} from "./errors"; export class IKHandshake implements IHandshake { public isInitiator: boolean; @@ -45,26 +46,28 @@ export class IKHandshake implements IHandshake { const messageBuffer = this.ik.sendMessage(this.session, this.payload); this.connection.writeLP(encode0(messageBuffer)); } else { - const receivedMessageBuffer = decode0(await this.connection.readLP()); + const receivedMsg = await this.connection.readLP(); + const receivedMessageBuffer = decode0(receivedMsg); const plaintext = this.ik.recvMessage(this.session, receivedMessageBuffer); try { await verifySignedPayload(receivedMessageBuffer.ns, plaintext, this.remotePeer.id); } catch (e) { - throw new Error(`Error occurred while verifying initiator's signed payload: ${e.message}`); + throw new FailedIKError(receivedMsg, `Error occurred while verifying initiator's signed payload: ${e.message}`); } } } public async stage1(): Promise { if (this.isInitiator) { - const receivedMessageBuffer = decode1(await this.connection.readLP()); + const receivedMsg = await this.connection.readLP(); + const receivedMessageBuffer = decode1(receivedMsg); const plaintext = this.ik.recvMessage(this.session, receivedMessageBuffer); try { await verifySignedPayload(receivedMessageBuffer.ns, plaintext, this.remotePeer.id); } catch (e) { - throw new Error(`Error occurred while verifying responder's signed payload: ${e.message}`); + throw new FailedIKError(receivedMsg, `Error occurred while verifying responder's signed payload: ${e.message}`); } } else { const messageBuffer = this.ik.sendMessage(this.session, this.payload); diff --git a/src/handshakes/ik.ts b/src/handshakes/ik.ts index 75d646e..793aea3 100644 --- a/src/handshakes/ik.ts +++ b/src/handshakes/ik.ts @@ -68,7 +68,6 @@ export class IK extends AbstractHandshake { session.h = h; session.cs1 = cs1; session.cs2 = cs2; - delete session.hs; } else if (session.mc.gtn(1)) { if (session.i) { if (!session.cs2) { diff --git a/src/keycache.ts b/src/keycache.ts index bf96f5e..fec7ef3 100644 --- a/src/keycache.ts +++ b/src/keycache.ts @@ -11,8 +11,8 @@ class Keycache { this.storage.set(peerId.id, key); } - public async load(peerId: PeerId): Promise { - return this.storage.get(peerId.id) || null; + public async load(peerId: PeerId): Promise { + return this.storage.get(peerId.id); } public resetStorage(): void { diff --git a/src/noise.ts b/src/noise.ts index 4d708dc..c678042 100644 --- a/src/noise.ts +++ b/src/noise.ts @@ -108,23 +108,32 @@ export class Noise implements INoiseConnection { private async performHandshake(params: HandshakeParams): Promise { const payload = await getPayload(params.localPeer, this.staticKeys.publicKey, this.earlyData); - let foundRemoteStaticKey: bytes|null = null; - if (this.useNoisePipes && params.isInitiator) { - logger("Initiator using noise pipes. Going to load cached static key..."); - foundRemoteStaticKey = await KeyCache.load(params.remotePeer); - logger(`Static key has been found: ${!!foundRemoteStaticKey}`) + let tryIK = this.useNoisePipes; + const foundRemoteStaticKey = await KeyCache.load(params.remotePeer); + if (tryIK && params.isInitiator && !foundRemoteStaticKey) { + tryIK = false; + logger(`Static key not found.`) } - if (foundRemoteStaticKey) { + // Try IK if acting as responder or initiator that has remote's static key. + if (tryIK) { // Try IK first const { remotePeer, connection, isInitiator } = params; + if (!foundRemoteStaticKey) { + // TODO: Recheck. Possible that responder should not have it here. + throw new Error("Remote static key should be initialized."); + } + const IKhandshake = new IKHandshake(isInitiator, payload, this.prologue, this.staticKeys, connection, remotePeer, foundRemoteStaticKey); try { - return await this.performIKHandshake(IKhandshake, payload); + return await this.performIKHandshake(IKhandshake); } catch (e) { // IK failed, go to XX fallback - const ephemeralKeys = IKhandshake.getRemoteEphemeralKeys(); - return await this.performXXFallbackHandshake(params, payload, ephemeralKeys, e.initialMsg); + let ephemeralKeys; + if (params.isInitiator) { + ephemeralKeys = IKhandshake.getRemoteEphemeralKeys(); + } + return await this.performXXFallbackHandshake(params, payload, e.initialMsg, ephemeralKeys); } } else { // Noise pipes not supported, use XX @@ -135,8 +144,8 @@ export class Noise implements INoiseConnection { private async performXXFallbackHandshake( params: HandshakeParams, payload: bytes, - ephemeralKeys: KeyPair, initialMsg: bytes, + ephemeralKeys?: KeyPair, ): Promise { const { isInitiator, remotePeer, connection } = params; const handshake = @@ -147,6 +156,7 @@ export class Noise implements INoiseConnection { await handshake.exchange(); await handshake.finish(); } catch (e) { + logger(e); throw new Error(`Error occurred during XX Fallback handshake: ${e.message}`); } @@ -179,8 +189,13 @@ export class Noise implements INoiseConnection { handshake: IKHandshake, ): Promise { - await handshake.stage0(); - await handshake.stage1(); + try { + await handshake.stage0(); + await handshake.stage1(); + } catch (e) { + console.error("Error in IK handshake: ", e); + throw e; + } return handshake; } diff --git a/src/utils.ts b/src/utils.ts index 003159c..6469215 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -89,8 +89,13 @@ async function isValidPeerId(peerId: bytes, publicKeyProtobuf: bytes) { } export async function verifySignedPayload(noiseStaticKey: bytes, plaintext: bytes, peerId: bytes) { - const NoiseHandshakePayload = await loadPayloadProto(); - const receivedPayload = NoiseHandshakePayload.toObject(NoiseHandshakePayload.decode(plaintext)); + let receivedPayload; + 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)) ) { throw new Error("Peer ID doesn't match libp2p public key."); diff --git a/test/noise.test.ts b/test/noise.test.ts index 872f825..2678d9e 100644 --- a/test/noise.test.ts +++ b/test/noise.test.ts @@ -16,6 +16,7 @@ import {decode0, decode1, encode1} from "../src/encoder"; import {XX} from "../src/handshakes/xx"; import {Buffer} from "buffer"; import {getKeyPairFromPeerId} from "./utils"; +import {KeyCache} from "../src/keycache"; describe("Noise", () => { let remotePeer, localPeer; @@ -24,7 +25,7 @@ describe("Noise", () => { [localPeer, remotePeer] = await createPeerIdsFromFixtures(2); }); - it("should communicate through encrypted streams", async() => { + it("should communicate through encrypted streams without noise pipes", async() => { try { const noiseInit = new Noise(undefined, undefined, false); const noiseResp = new Noise(undefined, undefined, false); @@ -120,4 +121,54 @@ describe("Noise", () => { 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 + await KeyCache.store(localPeer, staticKeysInitiator.publicKey); + await 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 v2")); + const response = await wrappedInbound.readLP(); + expect(response.toString()).equal("test v2"); + } catch (e) { + console.error(e); + assert(false, e.message); + } + }); + + it("should switch to XX fallback because of invalid remote static key", async() => { + try { + const staticKeysInitiator = generateKeypair(); + const noiseInit = new Noise(staticKeysInitiator.privateKey); + const noiseResp = new Noise(); + + // Prepare key cache for noise pipes + await KeyCache.store(localPeer, staticKeysInitiator.publicKey); + await 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), + ]); + assert(false, "Should throw error"); + } catch (e) { + console.error(e); + assert(true, e.message); + } + }); }); From 34bec6e1f4e0b0358d0f7a32f371e153d4b1a6be Mon Sep 17 00:00:00 2001 From: Belma Gutlic Date: Thu, 16 Jan 2020 17:49:41 +0100 Subject: [PATCH 05/11] Add sinon to better test with spies --- package.json | 1 + test/noise.test.ts | 23 +++++++++- yarn.lock | 102 ++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 123 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 2babf57..332e555 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ "chai": "^4.2.0", "eslint": "^6.6.0", "mocha": "^6.2.2", + "sinon": "^8.1.0", "typescript": "^3.6.4" }, "dependencies": { diff --git a/test/noise.test.ts b/test/noise.test.ts index 2678d9e..4281874 100644 --- a/test/noise.test.ts +++ b/test/noise.test.ts @@ -5,6 +5,7 @@ import { Noise } from "../src"; import {createPeerIdsFromFixtures} from "./fixtures/peer"; import Wrap from "it-pb-rpc"; import { random } from "bcrypto"; +import sinon from "sinon"; import {XXHandshake} from "../src/handshake-xx"; import { createHandshakePayload, @@ -17,14 +18,20 @@ import {XX} from "../src/handshakes/xx"; import {Buffer} from "buffer"; import {getKeyPairFromPeerId} from "./utils"; import {KeyCache} from "../src/keycache"; +import {XXFallbackHandshake} from "../src/handshake-xx-fallback"; describe("Noise", () => { let remotePeer, localPeer; + let sandbox = sinon.createSandbox(); before(async () => { [localPeer, remotePeer] = await createPeerIdsFromFixtures(2); }); + afterEach(function() { + sandbox.restore(); + }); + it("should communicate through encrypted streams without noise pipes", async() => { try { const noiseInit = new Noise(undefined, undefined, false); @@ -133,6 +140,9 @@ describe("Noise", () => { await KeyCache.store(localPeer, staticKeysInitiator.publicKey); await 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), @@ -144,6 +154,9 @@ describe("Noise", () => { 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); @@ -165,10 +178,16 @@ describe("Noise", () => { noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer), noiseResp.secureInbound(remotePeer, inboundConnection, localPeer), ]); - assert(false, "Should throw error"); + + 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"); } catch (e) { console.error(e); - assert(true, e.message); + assert(false, e.message); } }); }); diff --git a/yarn.lock b/yarn.lock index 63ec7c9..77d83a0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -869,6 +869,35 @@ resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" 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": version "4.2.4" 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" 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: version "3.0.0" 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" 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: version "1.0.0" 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" 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: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" @@ -2560,6 +2604,11 @@ json5@^2.1.0: dependencies: 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: version "1.0.1" 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" 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: version "4.0.1" resolved "https://registry.yarnpkg.com/lodash.unescape/-/lodash.unescape-4.0.1.tgz#bf2249886ce514cda112fae9218cdc065211fc9c" @@ -2660,6 +2714,13 @@ log-symbols@2.2.0: dependencies: 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: version "4.0.0" 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" 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: version "1.0.5" 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" 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: version "1.1.0" resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.0.tgz#b942e6d4bde653005ef6b71361def8727d0645e0" @@ -3621,6 +3701,19 @@ signed-varint@^2.0.1: dependencies: 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: version "2.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44" @@ -3829,6 +3922,13 @@ supports-color@^5.3.0: dependencies: 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: version "5.4.6" resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e" @@ -3923,7 +4023,7 @@ type-check@~0.3.2: dependencies: 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" resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== From 47e295add55f69badf76209bd08ace40259bbb95 Mon Sep 17 00:00:00 2001 From: Belma Gutlic Date: Fri, 17 Jan 2020 23:40:51 +0100 Subject: [PATCH 06/11] Add IK -> XX fallback tests and fix things --- src/encoder.ts | 2 ++ src/handshake-ik.ts | 15 ++++++++------ src/handshake-xx-fallback.ts | 15 ++++++-------- src/handshake-xx.ts | 2 +- src/noise.ts | 11 +++-------- test/noise.test.ts | 38 ++++++++++++++++++++++++++++++++++++ 6 files changed, 59 insertions(+), 24 deletions(-) diff --git a/src/encoder.ts b/src/encoder.ts index 4f869e6..d8c93f7 100644 --- a/src/encoder.ts +++ b/src/encoder.ts @@ -14,6 +14,8 @@ export const uint16BEDecode = data => { }; 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 { return Buffer.concat([message.ne, message.ciphertext]); } diff --git a/src/handshake-ik.ts b/src/handshake-ik.ts index 5683c99..62a02f7 100644 --- a/src/handshake-ik.ts +++ b/src/handshake-ik.ts @@ -8,6 +8,7 @@ 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 { public isInitiator: boolean; @@ -44,15 +45,16 @@ export class IKHandshake implements IHandshake { public async stage0(): Promise { if (this.isInitiator) { const messageBuffer = this.ik.sendMessage(this.session, this.payload); - this.connection.writeLP(encode0(messageBuffer)); + this.connection.writeLP(encode1(messageBuffer)); } else { - const receivedMsg = await this.connection.readLP(); - const receivedMessageBuffer = decode0(receivedMsg); + const receivedMsg = (await this.connection.readLP()).slice(); + const receivedMessageBuffer = decode1(Buffer.from(receivedMsg)); const plaintext = this.ik.recvMessage(this.session, receivedMessageBuffer); try { await verifySignedPayload(receivedMessageBuffer.ns, plaintext, this.remotePeer.id); } 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}`); } } @@ -60,18 +62,19 @@ export class IKHandshake implements IHandshake { public async stage1(): Promise { if (this.isInitiator) { - const receivedMsg = await this.connection.readLP(); - const receivedMessageBuffer = decode1(receivedMsg); + const receivedMsg = (await this.connection.readLP()).slice(); + const receivedMessageBuffer = decode0(Buffer.from(receivedMsg)); const plaintext = this.ik.recvMessage(this.session, receivedMessageBuffer); try { await verifySignedPayload(receivedMessageBuffer.ns, plaintext, this.remotePeer.id); } 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 { const messageBuffer = this.ik.sendMessage(this.session, this.payload); - this.connection.writeLP(encode1(messageBuffer)); + this.connection.writeLP(encode0(messageBuffer)); } } diff --git a/src/handshake-xx-fallback.ts b/src/handshake-xx-fallback.ts index c01539b..ed14f4d 100644 --- a/src/handshake-xx-fallback.ts +++ b/src/handshake-xx-fallback.ts @@ -39,37 +39,34 @@ export class XXFallbackHandshake extends XXHandshake { 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."); } else { - logger("XX Fallback Stage 0 - Responder waiting to receive first message..."); const receivedMessageBuffer = decode0(this.initialMsg); this.xx.recvMessage(this.session, { ne: receivedMessageBuffer.ne, ns: 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 public async exchange(): Promise { if (this.isInitiator) { - logger('XX Fallback Stage 1 - Initiator waiting to receive first message from responder...'); const receivedMessageBuffer = decode1(this.initialMsg); 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..."); try { await verifySignedPayload(receivedMessageBuffer.ns, plaintext, this.remotePeer.id); } 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!"); } else { - logger('XX Fallback Stage 1 - Responder sending out first message with signed payload and static key.'); - const messageBuffer = this.xx.sendMessage(this.session, this.payload); - this.connection.writeLP(encode1(messageBuffer)); - logger('XX Fallback Stage 1 - Responder sent the second handshake message with signed payload.') + logger("XX Fallback Stage 1 - Responder start"); + super.exchange(); + logger("XX Fallback Stage 1 - Responder end"); } } } diff --git a/src/handshake-xx.ts b/src/handshake-xx.ts index 5f42238..ed65093 100644 --- a/src/handshake-xx.ts +++ b/src/handshake-xx.ts @@ -63,7 +63,7 @@ export class XXHandshake implements IHandshake { public async exchange(): Promise { if (this.isInitiator) { 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); logger('Stage 1 - Initiator received the message. Got remote\'s static key.'); diff --git a/src/noise.ts b/src/noise.ts index c678042..b0d65ef 100644 --- a/src/noise.ts +++ b/src/noise.ts @@ -124,7 +124,7 @@ export class Noise implements INoiseConnection { throw new Error("Remote static key should be initialized."); } - const IKhandshake = new IKHandshake(isInitiator, payload, this.prologue, this.staticKeys, connection, remotePeer, foundRemoteStaticKey); + const IKhandshake = new IKHandshake(isInitiator, Buffer.from(payload), this.prologue, this.staticKeys, connection, remotePeer, foundRemoteStaticKey); try { return await this.performIKHandshake(IKhandshake); } catch (e) { @@ -189,13 +189,8 @@ export class Noise implements INoiseConnection { handshake: IKHandshake, ): Promise { - try { - await handshake.stage0(); - await handshake.stage1(); - } catch (e) { - console.error("Error in IK handshake: ", e); - throw e; - } + await handshake.stage0(); + await handshake.stage1(); return handshake; } diff --git a/test/noise.test.ts b/test/noise.test.ts index 4281874..da52381 100644 --- a/test/noise.test.ts +++ b/test/noise.test.ts @@ -168,6 +168,7 @@ describe("Noise", () => { 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 await KeyCache.store(localPeer, staticKeysInitiator.publicKey); @@ -185,9 +186,46 @@ describe("Noise", () => { 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("XX fallback with XX as responder has noise pipes disabled", async() => { + try { + const staticKeysInitiator = generateKeypair(); + const noiseInit = new Noise(staticKeysInitiator.privateKey); + const staticKeysResponder = generateKeypair(); + console.log("staticKeysInitiator: ", staticKeysInitiator) + console.log("staticKeysResponder: ", staticKeysResponder) + const noiseResp = new Noise(staticKeysResponder.privateKey, undefined, false); + const xxSpy = sandbox.spy(noiseInit, "performXXFallbackHandshake"); + + // Prepare key cache for noise pipes + await KeyCache.store(localPeer, staticKeysInitiator.publicKey); + await 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); + } + }); }); From 244348c596a9e7fe0024e6e8cdaa5e2d27a95ce2 Mon Sep 17 00:00:00 2001 From: Belma Gutlic Date: Fri, 17 Jan 2020 23:50:41 +0100 Subject: [PATCH 07/11] Add another test + logger --- src/handshake-ik.ts | 16 ++++++++++++--- src/handshake-xx-fallback.ts | 1 + test/noise.test.ts | 38 ++++++++++++++++++++++++++++++++++-- 3 files changed, 50 insertions(+), 5 deletions(-) diff --git a/src/handshake-ik.ts b/src/handshake-ik.ts index 62a02f7..8b3d315 100644 --- a/src/handshake-ik.ts +++ b/src/handshake-ik.ts @@ -44,15 +44,20 @@ export class IKHandshake implements IHandshake { public async stage0(): Promise { 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()).slice(); - const receivedMessageBuffer = decode1(Buffer.from(receivedMsg)); - const plaintext = this.ik.recvMessage(this.session, receivedMessageBuffer); - try { + const receivedMessageBuffer = decode1(Buffer.from(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}`); @@ -62,19 +67,24 @@ export class IKHandshake implements IHandshake { public async stage1(): Promise { 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..."); } } diff --git a/src/handshake-xx-fallback.ts b/src/handshake-xx-fallback.ts index ed14f4d..23cef09 100644 --- a/src/handshake-xx-fallback.ts +++ b/src/handshake-xx-fallback.ts @@ -39,6 +39,7 @@ export class XXFallbackHandshake extends XXHandshake { 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."); } else { + logger("XX Fallback Stage 0 - Responder decoding initial msg from IK.") const receivedMessageBuffer = decode0(this.initialMsg); this.xx.recvMessage(this.session, { ne: receivedMessageBuffer.ne, diff --git a/test/noise.test.ts b/test/noise.test.ts index da52381..ebafad4 100644 --- a/test/noise.test.ts +++ b/test/noise.test.ts @@ -199,8 +199,7 @@ describe("Noise", () => { const staticKeysInitiator = generateKeypair(); const noiseInit = new Noise(staticKeysInitiator.privateKey); const staticKeysResponder = generateKeypair(); - console.log("staticKeysInitiator: ", staticKeysInitiator) - console.log("staticKeysResponder: ", staticKeysResponder) + const noiseResp = new Noise(staticKeysResponder.privateKey, undefined, false); const xxSpy = sandbox.spy(noiseInit, "performXXFallbackHandshake"); @@ -228,4 +227,39 @@ describe("Noise", () => { assert(false, e.message); } }); + + it("Initiator starts with XX (pipes disabled) responder has 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 + await 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); + } + }); }); From 15db3b53ce230d3b93e8de9d2a9161d9f2c5424d Mon Sep 17 00:00:00 2001 From: Belma Gutlic Date: Sat, 18 Jan 2020 16:31:05 +0100 Subject: [PATCH 08/11] Remove async from keycache --- src/handshake-ik.ts | 2 +- src/keycache.ts | 4 ++-- src/noise.ts | 6 +++--- test/noise.test.ts | 14 +++++++------- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/handshake-ik.ts b/src/handshake-ik.ts index 8b3d315..5cdf52a 100644 --- a/src/handshake-ik.ts +++ b/src/handshake-ik.ts @@ -32,7 +32,7 @@ export class IKHandshake implements IHandshake { handshake?: IK, ) { this.isInitiator = isInitiator; - this.payload = payload; + this.payload = Buffer.from(payload); this.prologue = prologue; this.staticKeypair = staticKeypair; this.connection = connection; diff --git a/src/keycache.ts b/src/keycache.ts index fec7ef3..24cbcc0 100644 --- a/src/keycache.ts +++ b/src/keycache.ts @@ -7,11 +7,11 @@ import {bytes, bytes32} from "./@types/basic"; class Keycache { private storage = new Map(); - public async store(peerId: PeerId, key: bytes32): Promise { + public store(peerId: PeerId, key: bytes32): void { this.storage.set(peerId.id, key); } - public async load(peerId: PeerId): Promise { + public load(peerId: PeerId): bytes32|undefined { return this.storage.get(peerId.id); } diff --git a/src/noise.ts b/src/noise.ts index b0d65ef..73c6edd 100644 --- a/src/noise.ts +++ b/src/noise.ts @@ -109,7 +109,7 @@ export class Noise implements INoiseConnection { const payload = await getPayload(params.localPeer, this.staticKeys.publicKey, this.earlyData); let tryIK = this.useNoisePipes; - const foundRemoteStaticKey = await KeyCache.load(params.remotePeer); + const foundRemoteStaticKey = KeyCache.load(params.remotePeer); if (tryIK && params.isInitiator && !foundRemoteStaticKey) { tryIK = false; logger(`Static key not found.`) @@ -124,7 +124,7 @@ export class Noise implements INoiseConnection { throw new Error("Remote static key should be initialized."); } - const IKhandshake = new IKHandshake(isInitiator, Buffer.from(payload), this.prologue, this.staticKeys, connection, remotePeer, foundRemoteStaticKey); + const IKhandshake = new IKHandshake(isInitiator, payload, this.prologue, this.staticKeys, connection, remotePeer, foundRemoteStaticKey); try { return await this.performIKHandshake(IKhandshake); } catch (e) { @@ -176,7 +176,7 @@ export class Noise implements INoiseConnection { await handshake.finish(); if (this.useNoisePipes) { - await KeyCache.store(remotePeer, handshake.getRemoteStaticKey()); + KeyCache.store(remotePeer, handshake.getRemoteStaticKey()); } } catch (e) { throw new Error(`Error occurred during XX handshake: ${e.message}`); diff --git a/test/noise.test.ts b/test/noise.test.ts index ebafad4..dca824c 100644 --- a/test/noise.test.ts +++ b/test/noise.test.ts @@ -137,8 +137,8 @@ describe("Noise", () => { const noiseResp = new Noise(staticKeysResponder.privateKey); // Prepare key cache for noise pipes - await KeyCache.store(localPeer, staticKeysInitiator.publicKey); - await KeyCache.store(remotePeer, staticKeysResponder.publicKey); + KeyCache.store(localPeer, staticKeysInitiator.publicKey); + KeyCache.store(remotePeer, staticKeysResponder.publicKey); const xxSpy = sandbox.spy(noiseInit, "performXXHandshake"); const xxFallbackSpy = sandbox.spy(noiseInit, "performXXFallbackHandshake"); @@ -171,8 +171,8 @@ describe("Noise", () => { const xxSpy = sandbox.spy(noiseInit, "performXXFallbackHandshake"); // Prepare key cache for noise pipes - await KeyCache.store(localPeer, staticKeysInitiator.publicKey); - await KeyCache.store(remotePeer, generateKeypair().publicKey); + KeyCache.store(localPeer, staticKeysInitiator.publicKey); + KeyCache.store(remotePeer, generateKeypair().publicKey); const [inboundConnection, outboundConnection] = DuplexPair(); const [outbound, inbound] = await Promise.all([ @@ -204,8 +204,8 @@ describe("Noise", () => { const xxSpy = sandbox.spy(noiseInit, "performXXFallbackHandshake"); // Prepare key cache for noise pipes - await KeyCache.store(localPeer, staticKeysInitiator.publicKey); - await KeyCache.store(remotePeer, staticKeysResponder.publicKey); + KeyCache.store(localPeer, staticKeysInitiator.publicKey); + KeyCache.store(remotePeer, staticKeysResponder.publicKey); const [inboundConnection, outboundConnection] = DuplexPair(); @@ -239,7 +239,7 @@ describe("Noise", () => { const xxRespSpy = sandbox.spy(noiseResp, "performXXFallbackHandshake"); // Prepare key cache for noise pipes - await KeyCache.store(localPeer, staticKeysInitiator.publicKey); + KeyCache.store(localPeer, staticKeysInitiator.publicKey); const [inboundConnection, outboundConnection] = DuplexPair(); From 231c721d121f1cc6a60b554f418c69aa15d0caeb Mon Sep 17 00:00:00 2001 From: Belma Gutlic Date: Sat, 18 Jan 2020 17:00:31 +0100 Subject: [PATCH 09/11] Add test and improve --- src/errors.ts | 1 - src/noise.ts | 16 +++------------- test/noise.test.ts | 46 +++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 44 insertions(+), 19 deletions(-) diff --git a/src/errors.ts b/src/errors.ts index 09d2b10..46a5c0a 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -6,6 +6,5 @@ export class FailedIKError extends Error { this.initialMsg = initialMsg; this.name = "FailedIKhandshake"; - this.stack = new Error().stack; } }; diff --git a/src/noise.ts b/src/noise.ts index 73c6edd..7d88c73 100644 --- a/src/noise.ts +++ b/src/noise.ts @@ -108,23 +108,13 @@ export class Noise implements INoiseConnection { private async performHandshake(params: HandshakeParams): Promise { const payload = await getPayload(params.localPeer, this.staticKeys.publicKey, this.earlyData); - let tryIK = this.useNoisePipes; - const foundRemoteStaticKey = KeyCache.load(params.remotePeer); - if (tryIK && params.isInitiator && !foundRemoteStaticKey) { - tryIK = false; - logger(`Static key not found.`) - } - + const remoteStaticKey = KeyCache.load(params.remotePeer); // Try IK if acting as responder or initiator that has remote's static key. - if (tryIK) { + if (this.useNoisePipes && remoteStaticKey) { // Try IK first const { remotePeer, connection, isInitiator } = params; - if (!foundRemoteStaticKey) { - // TODO: Recheck. Possible that responder should not have it here. - throw new Error("Remote static key should be initialized."); - } + const IKhandshake = new IKHandshake(isInitiator, payload, this.prologue, this.staticKeys, connection, remotePeer, remoteStaticKey); - const IKhandshake = new IKHandshake(isInitiator, payload, this.prologue, this.staticKeys, connection, remotePeer, foundRemoteStaticKey); try { return await this.performIKHandshake(IKhandshake); } catch (e) { diff --git a/test/noise.test.ts b/test/noise.test.ts index dca824c..f40346e 100644 --- a/test/noise.test.ts +++ b/test/noise.test.ts @@ -163,7 +163,7 @@ describe("Noise", () => { } }); - it("should switch to XX fallback because of invalid remote static key", async() => { + it("IK -> XX fallback: initiator has invalid remote static key", async() => { try { const staticKeysInitiator = generateKeypair(); const noiseInit = new Noise(staticKeysInitiator.privateKey); @@ -171,6 +171,7 @@ describe("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); @@ -194,12 +195,12 @@ describe("Noise", () => { } }); - it("XX fallback with XX as responder has noise pipes disabled", async() => { + it("IK -> XX fallback: responder has disabled noise pipes", async() => { try { const staticKeysInitiator = generateKeypair(); const noiseInit = new Noise(staticKeysInitiator.privateKey); - const staticKeysResponder = generateKeypair(); + const staticKeysResponder = generateKeypair(); const noiseResp = new Noise(staticKeysResponder.privateKey, undefined, false); const xxSpy = sandbox.spy(noiseInit, "performXXFallbackHandshake"); @@ -208,7 +209,6 @@ describe("Noise", () => { 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), @@ -228,7 +228,7 @@ describe("Noise", () => { } }); - it("Initiator starts with XX (pipes disabled) responder has noise pipes", async() => { + 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); @@ -262,4 +262,40 @@ describe("Noise", () => { 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); + } + }); }); From 737dabdce0e8d87d94e36b36d5ee0d9fa9d91d18 Mon Sep 17 00:00:00 2001 From: Belma Gutlic Date: Tue, 21 Jan 2020 11:04:34 +0100 Subject: [PATCH 10/11] Remove slice where not necessary --- src/handshake-ik.ts | 4 ++-- src/handshake-xx.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/handshake-ik.ts b/src/handshake-ik.ts index 5cdf52a..9e50a0e 100644 --- a/src/handshake-ik.ts +++ b/src/handshake-ik.ts @@ -50,9 +50,9 @@ export class IKHandshake implements IHandshake { logger("IK Stage 0 - Initiator sent message."); } else { logger("IK Stage 0 - Responder receiving message..."); - const receivedMsg = (await this.connection.readLP()).slice(); + const receivedMsg = await this.connection.readLP(); try { - const receivedMessageBuffer = decode1(Buffer.from(receivedMsg)); + const receivedMessageBuffer = decode1(receivedMsg); const plaintext = this.ik.recvMessage(this.session, receivedMessageBuffer); logger("IK Stage 0 - Responder got message, going to verify payload."); diff --git a/src/handshake-xx.ts b/src/handshake-xx.ts index ed65093..1453e95 100644 --- a/src/handshake-xx.ts +++ b/src/handshake-xx.ts @@ -53,7 +53,7 @@ export class XXHandshake implements IHandshake { logger("Stage 0 - Initiator finished sending first message."); } else { 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); logger("Stage 0 - Responder received first message."); } @@ -91,7 +91,7 @@ export class XXHandshake implements IHandshake { logger('Stage 2 - Initiator sent message with signed payload.'); } else { 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); logger('Stage 2 - Responder received the message, finished handshake. Got remote\'s static key.'); From cca8b362cbf3bb211c17c9a84835c33371b2f1c3 Mon Sep 17 00:00:00 2001 From: Belma Gutlic Date: Tue, 21 Jan 2020 11:20:00 +0100 Subject: [PATCH 11/11] Address PR comment --- src/noise.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/noise.ts b/src/noise.ts index 7d88c73..d40f2d3 100644 --- a/src/noise.ts +++ b/src/noise.ts @@ -113,15 +113,15 @@ export class Noise implements INoiseConnection { if (this.useNoisePipes && remoteStaticKey) { // Try IK first const { remotePeer, connection, isInitiator } = params; - const IKhandshake = new IKHandshake(isInitiator, payload, this.prologue, this.staticKeys, connection, remotePeer, remoteStaticKey); + const ikHandshake = new IKHandshake(isInitiator, payload, this.prologue, this.staticKeys, connection, remotePeer, remoteStaticKey); try { - return await this.performIKHandshake(IKhandshake); + return await this.performIKHandshake(ikHandshake); } catch (e) { // IK failed, go to XX fallback let ephemeralKeys; if (params.isInitiator) { - ephemeralKeys = IKhandshake.getRemoteEphemeralKeys(); + ephemeralKeys = ikHandshake.getRemoteEphemeralKeys(); } return await this.performXXFallbackHandshake(params, payload, e.initialMsg, ephemeralKeys); }