From ce7f4118c2cd9c3c8f6c6b0f0c6a1eae70707403 Mon Sep 17 00:00:00 2001 From: Belma Gutlic Date: Mon, 13 Jan 2020 15:38:14 +0100 Subject: [PATCH 1/5] Create key cache --- package.json | 1 + src/keycache.ts | 40 ++++++++++++++++++++++++++++++++++++++++ src/noise.ts | 8 +++++++- test/keycache.test.ts | 23 +++++++++++++++++++++++ yarn.lock | 5 +++++ 5 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 src/keycache.ts create mode 100644 test/keycache.test.ts diff --git a/package.json b/package.json index 2babf57..da6919f 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ "typescript": "^3.6.4" }, "dependencies": { + "async-mutex": "^0.1.4", "bcrypto": "^4.2.3", "bn.js": "^5.0.0", "buffer": "^5.4.3", diff --git a/src/keycache.ts b/src/keycache.ts new file mode 100644 index 0000000..8c0241f --- /dev/null +++ b/src/keycache.ts @@ -0,0 +1,40 @@ +import {Mutex} from 'async-mutex'; +import {PeerId} from "./@types/libp2p"; +import {bytes, bytes32} from "./@types/basic"; + + +class Keycache { + private mutex: Mutex; + private storage = new Map(); + + constructor() { + this.mutex = new Mutex(); + } + + public async store(peerId: PeerId, key: bytes32): Promise { + const release = await this.mutex.acquire(); + try { + this.storage.set(peerId.id, key); + } finally { + release(); + } + } + + public async load(peerId: PeerId): Promise { + const release = await this.mutex.acquire(); + let key; + try { + key = this.storage.get(peerId.id); + } finally { + release(); + } + + return key; + } + +} + +const KeyCache = new Keycache(); +export { + KeyCache, +} diff --git a/src/noise.ts b/src/noise.ts index 676beb3..0810f67 100644 --- a/src/noise.ts +++ b/src/noise.ts @@ -32,9 +32,11 @@ export class Noise implements INoiseConnection { private readonly prologue = Buffer.from(this.protocol); private readonly staticKeys: KeyPair; private readonly earlyData?: bytes; + private useNoisePipes: boolean; - constructor(staticNoiseKey?: bytes, earlyData?: bytes) { + constructor(staticNoiseKey?: bytes, earlyData?: bytes, useNoisePipes = true) { this.earlyData = earlyData || Buffer.alloc(0); + this.useNoisePipes = useNoisePipes; if (staticNoiseKey) { const publicKey = x25519.publicKeyCreate(staticNoiseKey); // TODO: verify this @@ -152,6 +154,10 @@ export class Noise implements INoiseConnection { await handshake.propose(); await handshake.exchange(); await handshake.finish(); + + if (this.useNoisePipes) { + + } } catch (e) { throw new Error(`Error occurred during XX handshake: ${e.message}`); } diff --git a/test/keycache.test.ts b/test/keycache.test.ts new file mode 100644 index 0000000..4bd75bd --- /dev/null +++ b/test/keycache.test.ts @@ -0,0 +1,23 @@ +import { expect, assert } from "chai"; +import { KeyCache } from "../src/keycache"; +import {createPeerIdsFromFixtures} from "./fixtures/peer"; + +describe("KeyCache", () => { + let peerA, peerB; + + before(async () => { + [peerA, peerB] = await createPeerIdsFromFixtures(2); + }); + + it("should store and load same key successfully", async() => { + try { + const key = Buffer.from("this is id 007"); + await KeyCache.store(peerA, key); + const result = await KeyCache.load(peerA); + assert(result.equals(key), "Stored and loaded key are not the same"); + } catch (e) { + console.error(e); + assert(false, `Test failed - ${e.message}`) + } + }); +}); diff --git a/yarn.lock b/yarn.lock index 63ec7c9..ed561c7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1086,6 +1086,11 @@ async-each@^1.0.1: resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ== +async-mutex@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/async-mutex/-/async-mutex-0.1.4.tgz#a47d1eebf584f7dcdd760e3642dc2c58613bef5c" + integrity sha512-zVWTmAnxxHaeB2B1te84oecI8zTDJ/8G49aVBblRX6be0oq6pAybNcUSxwfgVOmOjSCvN4aYZAqwtyNI8e1YGw== + atob@^2.1.1: version "2.1.2" resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" From 90af03ab025cc6931d08cb081ec9b5dfb359ee77 Mon Sep 17 00:00:00 2001 From: Belma Gutlic Date: Mon, 13 Jan 2020 16:18:10 +0100 Subject: [PATCH 2/5] Add missing key test --- test/keycache.test.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/test/keycache.test.ts b/test/keycache.test.ts index 4bd75bd..f9b9cd0 100644 --- a/test/keycache.test.ts +++ b/test/keycache.test.ts @@ -1,6 +1,6 @@ import { expect, assert } from "chai"; import { KeyCache } from "../src/keycache"; -import {createPeerIdsFromFixtures} from "./fixtures/peer"; +import {createPeerIds, createPeerIdsFromFixtures} from "./fixtures/peer"; describe("KeyCache", () => { let peerA, peerB; @@ -20,4 +20,15 @@ describe("KeyCache", () => { assert(false, `Test failed - ${e.message}`) } }); + + it("should return undefined if key not found", async() => { + try { + const [newPeer] = await createPeerIds(1); + const result = await KeyCache.load(newPeer); + assert(!result); + } catch (e) { + console.error(e); + assert(false, `Test failed - ${e.message}`) + } + }); }); From 7d22967197790bc3473749e4e61c8710f23b7b87 Mon Sep 17 00:00:00 2001 From: Belma Gutlic Date: Mon, 13 Jan 2020 16:33:58 +0100 Subject: [PATCH 3/5] Use static key caching --- src/handshake-ik.ts | 7 ++----- src/handshake-xx.ts | 4 ++++ src/handshakes/xx.ts | 1 - src/keycache.ts | 4 ++-- src/noise.ts | 26 ++++++++++++++++---------- test/noise.test.ts | 10 +++++----- 6 files changed, 29 insertions(+), 23 deletions(-) diff --git a/src/handshake-ik.ts b/src/handshake-ik.ts index f13e711..9ce370a 100644 --- a/src/handshake-ik.ts +++ b/src/handshake-ik.ts @@ -24,6 +24,7 @@ export class IKHandshake implements IHandshake { staticKeypair: KeyPair, connection: WrappedConnection, remotePeer: PeerId, + remoteStaticKey: bytes, handshake?: IK, ) { this.isInitiator = isInitiator; @@ -34,11 +35,7 @@ export class IKHandshake implements IHandshake { this.remotePeer = remotePeer; this.ik = handshake || new IK(); - - // Dummy data - // TODO: Load remote static keys if found - const remoteStaticKeys = this.staticKeypair; - this.session = this.ik.initSession(this.isInitiator, this.prologue, this.staticKeypair, remoteStaticKeys.publicKey); + this.session = this.ik.initSession(this.isInitiator, this.prologue, this.staticKeypair, remoteStaticKey); } public decrypt(ciphertext: Buffer, session: NoiseSession): Buffer { diff --git a/src/handshake-xx.ts b/src/handshake-xx.ts index 7f6f1bf..5f42238 100644 --- a/src/handshake-xx.ts +++ b/src/handshake-xx.ts @@ -114,6 +114,10 @@ export class XXHandshake implements IHandshake { return this.xx.decryptWithAd(cs, Buffer.alloc(0), ciphertext); } + public getRemoteStaticKey(): bytes { + return this.session.hs.rs; + } + private getCS(session: NoiseSession, encryption = true) { if (!session.cs1 || !session.cs2) { throw new Error("Handshake not completed properly, cipher state does not exist."); diff --git a/src/handshakes/xx.ts b/src/handshakes/xx.ts index 110dc7e..6362bab 100644 --- a/src/handshakes/xx.ts +++ b/src/handshakes/xx.ts @@ -145,7 +145,6 @@ export class XX extends AbstractHandshake { session.h = h; session.cs1 = cs1; session.cs2 = cs2; - delete session.hs; } else if (session.mc.gtn(2)) { if (session.i) { if (!session.cs1) { diff --git a/src/keycache.ts b/src/keycache.ts index 8c0241f..ea65a94 100644 --- a/src/keycache.ts +++ b/src/keycache.ts @@ -20,11 +20,11 @@ class Keycache { } } - public async load(peerId: PeerId): Promise { + public async load(peerId: PeerId): Promise { const release = await this.mutex.acquire(); let key; try { - key = this.storage.get(peerId.id); + key = this.storage.get(peerId.id) || null; } finally { release(); } diff --git a/src/noise.ts b/src/noise.ts index 0810f67..0a2a7a0 100644 --- a/src/noise.ts +++ b/src/noise.ts @@ -16,6 +16,8 @@ import { bytes } from "./@types/basic"; import { INoiseConnection, PeerId, KeyPair, SecureOutbound } from "./@types/libp2p"; import { Duplex } from "./@types/it-pair"; import {IHandshake} from "./@types/handshake-interface"; +import {KeyCache} from "./keycache"; +import {logger} from "./logger"; export type WrappedConnection = ReturnType; @@ -104,14 +106,21 @@ export class Noise implements INoiseConnection { * @param remotePeer */ private async performHandshake(params: HandshakeParams): Promise { - // TODO: Implement noise pipes const payload = await getPayload(params.localPeer, this.staticKeys.publicKey, this.earlyData); - if (false) { - let IKhandshake; + 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}`) + } + + if (foundRemoteStaticKey) { + // Try IK first + const { remotePeer, connection, isInitiator } = params; + const IKhandshake = new IKHandshake(isInitiator, payload, this.prologue, this.staticKeys, connection, remotePeer, foundRemoteStaticKey); try { - IKhandshake = await this.performIKHandshake(params, payload); - return IKhandshake; + return await this.performIKHandshake(IKhandshake, payload); } catch (e) { // XX fallback const ephemeralKeys = IKhandshake.getRemoteEphemeralKeys(); @@ -156,7 +165,7 @@ export class Noise implements INoiseConnection { await handshake.finish(); if (this.useNoisePipes) { - + await KeyCache.store(remotePeer, handshake.getRemoteStaticKey()); } } catch (e) { throw new Error(`Error occurred during XX handshake: ${e.message}`); @@ -166,12 +175,9 @@ export class Noise implements INoiseConnection { } private async performIKHandshake( - params: HandshakeParams, + handshake: IKHandshake, payload: bytes, ): Promise { - const { isInitiator, remotePeer, connection } = params; - const handshake = new IKHandshake(isInitiator, payload, this.prologue, this.staticKeys, connection, remotePeer); - // TODO return handshake; diff --git a/test/noise.test.ts b/test/noise.test.ts index dcf790f..872f825 100644 --- a/test/noise.test.ts +++ b/test/noise.test.ts @@ -26,8 +26,8 @@ describe("Noise", () => { it("should communicate through encrypted streams", async() => { try { - const noiseInit = new Noise(); - const noiseResp = new Noise(); + const noiseInit = new Noise(undefined, undefined, false); + const noiseResp = new Noise(undefined, undefined, false); const [inboundConnection, outboundConnection] = DuplexPair(); const [outbound, inbound] = await Promise.all([ @@ -46,7 +46,7 @@ describe("Noise", () => { }); it("should test that secureOutbound is spec compliant", async() => { - const noiseInit = new Noise(); + const noiseInit = new Noise(undefined, undefined, false); const [inboundConnection, outboundConnection] = DuplexPair(); const [outbound, { wrapped, handshake }] = await Promise.all([ @@ -99,8 +99,8 @@ describe("Noise", () => { it("should test large payloads", async() => { try { - const noiseInit = new Noise(); - const noiseResp = new Noise(); + const noiseInit = new Noise(undefined, undefined, false); + const noiseResp = new Noise(undefined, undefined, false); const [inboundConnection, outboundConnection] = DuplexPair(); const [outbound, inbound] = await Promise.all([ From 52195afe18ad9a4f1bd4570c108da972c02589a0 Mon Sep 17 00:00:00 2001 From: Belma Gutlic Date: Mon, 13 Jan 2020 16:40:42 +0100 Subject: [PATCH 4/5] Add comments --- src/keycache.ts | 8 +++++++- src/noise.ts | 5 +++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/keycache.ts b/src/keycache.ts index ea65a94..2d1028d 100644 --- a/src/keycache.ts +++ b/src/keycache.ts @@ -2,7 +2,9 @@ import {Mutex} from 'async-mutex'; import {PeerId} from "./@types/libp2p"; import {bytes, bytes32} from "./@types/basic"; - +/** + * Storage for static keys of previously connected peers. + */ class Keycache { private mutex: Mutex; private storage = new Map(); @@ -32,6 +34,10 @@ class Keycache { return key; } + public resetStorage(): void { + this.storage.clear(); + } + } const KeyCache = new Keycache(); diff --git a/src/noise.ts b/src/noise.ts index 0a2a7a0..cebdec9 100644 --- a/src/noise.ts +++ b/src/noise.ts @@ -99,7 +99,7 @@ export class Noise implements INoiseConnection { /** * If Noise pipes supported, tries IK handshake first with XX as fallback if it fails. - * If remote peer static key is unknown, use XX. + * If noise pipes disabled or remote peer static key is unknown, use XX. * @param connection * @param isInitiator * @param libp2pPublicKey @@ -122,11 +122,12 @@ export class Noise implements INoiseConnection { try { return await this.performIKHandshake(IKhandshake, payload); } catch (e) { - // XX fallback + // IK failed, go to XX fallback const ephemeralKeys = IKhandshake.getRemoteEphemeralKeys(); return await this.performXXFallbackHandshake(params, payload, ephemeralKeys, e.initialMsg); } } else { + // Noise pipes not supported, use XX return await this.performXXHandshake(params, payload); } } From 6ee5c70af9cdba34814f5de2224e71e721da6c8e Mon Sep 17 00:00:00 2001 From: Belma Gutlic Date: Mon, 13 Jan 2020 18:25:28 +0100 Subject: [PATCH 5/5] Address PR comment --- package.json | 1 - src/keycache.ts | 23 ++--------------------- yarn.lock | 5 ----- 3 files changed, 2 insertions(+), 27 deletions(-) diff --git a/package.json b/package.json index da6919f..2babf57 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,6 @@ "typescript": "^3.6.4" }, "dependencies": { - "async-mutex": "^0.1.4", "bcrypto": "^4.2.3", "bn.js": "^5.0.0", "buffer": "^5.4.3", diff --git a/src/keycache.ts b/src/keycache.ts index 2d1028d..bf96f5e 100644 --- a/src/keycache.ts +++ b/src/keycache.ts @@ -1,4 +1,3 @@ -import {Mutex} from 'async-mutex'; import {PeerId} from "./@types/libp2p"; import {bytes, bytes32} from "./@types/basic"; @@ -6,32 +5,14 @@ import {bytes, bytes32} from "./@types/basic"; * Storage for static keys of previously connected peers. */ class Keycache { - private mutex: Mutex; private storage = new Map(); - constructor() { - this.mutex = new Mutex(); - } - public async store(peerId: PeerId, key: bytes32): Promise { - const release = await this.mutex.acquire(); - try { - this.storage.set(peerId.id, key); - } finally { - release(); - } + this.storage.set(peerId.id, key); } public async load(peerId: PeerId): Promise { - const release = await this.mutex.acquire(); - let key; - try { - key = this.storage.get(peerId.id) || null; - } finally { - release(); - } - - return key; + return this.storage.get(peerId.id) || null; } public resetStorage(): void { diff --git a/yarn.lock b/yarn.lock index ed561c7..63ec7c9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1086,11 +1086,6 @@ async-each@^1.0.1: resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ== -async-mutex@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/async-mutex/-/async-mutex-0.1.4.tgz#a47d1eebf584f7dcdd760e3642dc2c58613bef5c" - integrity sha512-zVWTmAnxxHaeB2B1te84oecI8zTDJ/8G49aVBblRX6be0oq6pAybNcUSxwfgVOmOjSCvN4aYZAqwtyNI8e1YGw== - atob@^2.1.1: version "2.1.2" resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"