From 7b03a3df3bd5fa99a01a79073322b922021dd59e Mon Sep 17 00:00:00 2001 From: morrigan Date: Mon, 2 Dec 2019 12:53:00 +0100 Subject: [PATCH 01/12] Write functions for verification --- package.json | 2 +- src/handshake.ts | 10 +++++++--- src/utils.ts | 16 ++++++++++++++++ 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 62758b8..e3d47e9 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,6 @@ "eslint": "^6.6.0", "libp2p-crypto": "^0.17.1", "mocha": "^6.2.2", - "peer-id": "^0.13.5", "typescript": "^3.6.4" }, "babel": { @@ -61,6 +60,7 @@ "it-pair": "^1.0.0", "it-pb-rpc": "^0.1.3", "it-pipe": "^1.1.0", + "peer-id": "^0.13.5", "protobufjs": "~6.8.8" } } diff --git a/src/handshake.ts b/src/handshake.ts index 5cbe50f..1c2bae6 100644 --- a/src/handshake.ts +++ b/src/handshake.ts @@ -9,7 +9,7 @@ import { encodeMessageBuffer, getHandshakePayload, logger, signEarlyDataPayload, - signPayload, + signPayload, verifySignedPayload, } from "./utils"; import { WrappedConnection } from "./noise"; @@ -70,13 +70,17 @@ export class Handshake { } // stage 1 - async exchange(): Promise { + async exchange(libp2pRemotekey?: bytes): Promise { if (this.isInitiator) { logger('Stage 1 - Initiator waiting to receive first message from responder...'); const receivedMessageBuffer = decodeMessageBuffer((await this.connection.readLP()).slice()); const plaintext = await this.xx.recvMessage(this.session, receivedMessageBuffer); - // TODO: Verify payload logger('Stage 1 - Initiator received the message. Got remote\'s static key.'); + + if (!libp2pRemotekey) { + throw new Error("Missing remote's libp2p public key, can't verify signature."); + } + verifySignedPayload(receivedMessageBuffer.ns, plaintext, libp2pRemotekey); } else { logger('Stage 1 - Responder sending out first message with signed payload and static key.'); const signedPayload = signPayload(this.libp2pPrivateKey, getHandshakePayload(this.staticKeys.publicKey)); diff --git a/src/utils.ts b/src/utils.ts index f65da02..c133651 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -2,6 +2,7 @@ import { x25519, ed25519 } from 'bcrypto'; import protobuf from "protobufjs"; import { Buffer } from "buffer"; import debug from "debug"; +import PeerId from "peer-id"; import { KeyPair } from "./@types/libp2p"; import { bytes } from "./@types/basic"; @@ -82,6 +83,21 @@ export function decodeMessageBuffer(message: bytes): MessageBuffer { } } +export async function verifyPeerId(peerId: bytes, publicKey: bytes) { + const generatedPeerId = await PeerId.createFromPubKey(publicKey); + if (!generatedPeerId.equals(peerId)) { + Promise.reject("Peer ID doesn't match libp2p public key."); + } +} + +export function verifySignedPayload(noiseStaticKey: bytes, plaintext: bytes, libp2pPublicKey: bytes) { + const generatedPayload = getHandshakePayload(noiseStaticKey); + + if (!ed25519.verify(generatedPayload, signature, libp2pPublicKey)) { + throw new Error("Static key doesn't match to peer that signed payload!"); + } +} + export const int16BEEncode = (value, target, offset) => { target = target || Buffer.allocUnsafe(2); return target.writeInt16BE(value, offset); From bf9ae90a5e2ffa1808e707bcf03865ed2b62ef5b Mon Sep 17 00:00:00 2001 From: morrigan Date: Mon, 2 Dec 2019 13:18:31 +0100 Subject: [PATCH 02/12] Verify payload in stage 1 --- src/handshake.ts | 2 +- src/noise.ts | 7 ++++--- src/utils.ts | 8 +++++--- test/handshake.test.ts | 2 +- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/handshake.ts b/src/handshake.ts index 1c2bae6..f897029 100644 --- a/src/handshake.ts +++ b/src/handshake.ts @@ -80,7 +80,7 @@ export class Handshake { if (!libp2pRemotekey) { throw new Error("Missing remote's libp2p public key, can't verify signature."); } - verifySignedPayload(receivedMessageBuffer.ns, plaintext, libp2pRemotekey); + await verifySignedPayload(receivedMessageBuffer.ns, plaintext, libp2pRemotekey); } else { logger('Stage 1 - Responder sending out first message with signed payload and static key.'); const signedPayload = signPayload(this.libp2pPrivateKey, getHandshakePayload(this.staticKeys.publicKey)); diff --git a/src/noise.ts b/src/noise.ts index fe33aed..8221a41 100644 --- a/src/noise.ts +++ b/src/noise.ts @@ -47,7 +47,7 @@ export class Noise implements NoiseConnection { public async secureOutbound(localPeer: PeerId, connection: any, remotePeer: PeerId): Promise { const wrappedConnection = Wrap(connection); const libp2pPublicKey = localPeer.pubKey.marshal(); - const handshake = await this.performHandshake(wrappedConnection, true, libp2pPublicKey); + const handshake = await this.performHandshake(wrappedConnection, true, libp2pPublicKey, remotePeer); const conn = await this.createSecureConnection(wrappedConnection, handshake); return { @@ -66,7 +66,7 @@ export class Noise implements NoiseConnection { public async secureInbound(localPeer: PeerId, connection: any, remotePeer: PeerId): Promise { const wrappedConnection = Wrap(connection); const libp2pPublicKey = localPeer.pubKey.marshal(); - const handshake = await this.performHandshake(wrappedConnection, false, libp2pPublicKey); + const handshake = await this.performHandshake(wrappedConnection, false, libp2pPublicKey, remotePeer); const conn = await this.createSecureConnection(wrappedConnection, handshake); return { @@ -79,12 +79,13 @@ export class Noise implements NoiseConnection { connection: WrappedConnection, isInitiator: boolean, libp2pPublicKey: bytes, + remotePeer: PeerId, ): Promise { const prologue = Buffer.from(this.protocol); const handshake = new Handshake(isInitiator, this.privateKey, libp2pPublicKey, prologue, this.staticKeys, connection); await handshake.propose(this.earlyData); - await handshake.exchange(); + await handshake.exchange(remotePeer.pubKey.marshal()); await handshake.finish(); return handshake; diff --git a/src/utils.ts b/src/utils.ts index c133651..107819d 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -90,11 +90,13 @@ export async function verifyPeerId(peerId: bytes, publicKey: bytes) { } } -export function verifySignedPayload(noiseStaticKey: bytes, plaintext: bytes, libp2pPublicKey: bytes) { +export async function verifySignedPayload(noiseStaticKey: bytes, plaintext: bytes, libp2pPublicKey: bytes) { + const NoiseHandshakePayload = await loadPayloadProto(); + const receivedPayload = NoiseHandshakePayload.toObject(NoiseHandshakePayload.decode(plaintext)); const generatedPayload = getHandshakePayload(noiseStaticKey); - if (!ed25519.verify(generatedPayload, signature, libp2pPublicKey)) { - throw new Error("Static key doesn't match to peer that signed payload!"); + if (!ed25519.verify(generatedPayload, receivedPayload.noiseStaticKeySignature, libp2pPublicKey)) { + Promise.reject("Static key doesn't match to peer that signed payload!"); } } diff --git a/test/handshake.test.ts b/test/handshake.test.ts index 7166f49..927bc2b 100644 --- a/test/handshake.test.ts +++ b/test/handshake.test.ts @@ -31,7 +31,7 @@ describe("Handshake", () => { await handshakeResponder.propose(); await handshakeResponder.exchange(); - await handshakeInitator.exchange(); + await handshakeInitator.exchange(peerB.pubKey.marshal()); await handshakeInitator.finish(); await handshakeResponder.finish(); From 6bb36f1663b82ec2ed456bcfd0b193b9d8f05b8e Mon Sep 17 00:00:00 2001 From: morrigan Date: Mon, 2 Dec 2019 15:24:49 +0100 Subject: [PATCH 03/12] Update timing of initiator payloading sending to verify that payload --- src/handshake.ts | 104 ++++++++++++++++++++++------------------- src/noise.ts | 10 ++-- src/utils.ts | 8 ++-- test/handshake.test.ts | 68 ++++++++++++++------------- test/noise.test.ts | 66 ++++++++++++++------------ 5 files changed, 140 insertions(+), 116 deletions(-) diff --git a/src/handshake.ts b/src/handshake.ts index f897029..6684d8a 100644 --- a/src/handshake.ts +++ b/src/handshake.ts @@ -45,9 +45,55 @@ export class Handshake { } // stage 0 - async propose(earlyData?: bytes): Promise { + async propose(): Promise { if (this.isInitiator) { logger("Stage 0 - Initiator starting to send first message."); + const messageBuffer = await this.xx.sendMessage(this.session, Buffer.alloc(0)); + this.connection.writeLP(encodeMessageBuffer(messageBuffer)); + logger("Stage 0 - Initiator finished sending first message."); + } else { + logger("Stage 0 - Responder waiting to receive first message..."); + const receivedMessageBuffer = decodeMessageBuffer((await this.connection.readLP()).slice()); + await this.xx.recvMessage(this.session, receivedMessageBuffer); + logger("Stage 0 - Responder received first message."); + } + } + + // stage 1 + async exchange(): Promise { + if (this.isInitiator) { + logger('Stage 1 - Initiator waiting to receive first message from responder...'); + const receivedMessageBuffer = decodeMessageBuffer((await this.connection.readLP()).slice()); + const plaintext = await this.xx.recvMessage(this.session, receivedMessageBuffer); + logger('Stage 1 - Initiator received the message. Got remote\'s static key.'); + + // if (!libp2pRemotekey) { + // throw new Error("Missing remote's libp2p public key, can't verify peer ID."); + // } + logger("Initiator going to check remote's signature..."); + await verifySignedPayload(receivedMessageBuffer.ns, plaintext); + logger("All good with the signature!"); + } else { + logger('Stage 1 - Responder sending out first message with signed payload and static key.'); + const signedPayload = signPayload(this.libp2pPrivateKey, getHandshakePayload(this.staticKeys.publicKey)); + const signedEarlyDataPayload = signEarlyDataPayload(this.libp2pPrivateKey, Buffer.alloc(0)); + const handshakePayload = await createHandshakePayload( + this.libp2pPublicKey, + this.libp2pPrivateKey, + signedPayload, + signedEarlyDataPayload, + ); + + const messageBuffer = await this.xx.sendMessage(this.session, handshakePayload); + this.connection.writeLP(encodeMessageBuffer(messageBuffer)); + logger('Stage 1 - Responder sent the second handshake message with signed payload.') + } + } + + // stage 2 + async finish(earlyData?: bytes): Promise { + if (this.isInitiator) { + logger('Stage 2 - Initiator sending third handshake message.'); const signedPayload = signPayload(this.libp2pPrivateKey, getHandshakePayload(this.staticKeys.publicKey)); const signedEarlyDataPayload = signEarlyDataPayload(this.libp2pPrivateKey, earlyData || Buffer.alloc(0)); const handshakePayload = await createHandshakePayload( @@ -58,56 +104,18 @@ export class Handshake { ); const messageBuffer = await this.xx.sendMessage(this.session, handshakePayload); this.connection.writeLP(encodeMessageBuffer(messageBuffer)); - - logger("Stage 0 - Initiator finished proposing, sent signed NoiseHandshake payload and static public key."); - } else { - logger("Stage 0 - Responder waiting to receive first message..."); - const receivedMessageBuffer = decodeMessageBuffer((await this.connection.readLP()).slice()); - const plaintext = await this.xx.recvMessage(this.session, receivedMessageBuffer); - // TODO: Verify payload - logger("Stage 0 - Responder received first message."); - } - } - - // stage 1 - async exchange(libp2pRemotekey?: bytes): Promise { - if (this.isInitiator) { - logger('Stage 1 - Initiator waiting to receive first message from responder...'); - const receivedMessageBuffer = decodeMessageBuffer((await this.connection.readLP()).slice()); - const plaintext = await this.xx.recvMessage(this.session, receivedMessageBuffer); - logger('Stage 1 - Initiator received the message. Got remote\'s static key.'); - - if (!libp2pRemotekey) { - throw new Error("Missing remote's libp2p public key, can't verify signature."); - } - await verifySignedPayload(receivedMessageBuffer.ns, plaintext, libp2pRemotekey); - } else { - logger('Stage 1 - Responder sending out first message with signed payload and static key.'); - const signedPayload = signPayload(this.libp2pPrivateKey, getHandshakePayload(this.staticKeys.publicKey)); - const handshakePayload = await createHandshakePayload( - this.libp2pPublicKey, - this.libp2pPrivateKey, - signedPayload, - ); - - const messageBuffer = await this.xx.sendMessage(this.session, handshakePayload); - this.connection.writeLP(encodeMessageBuffer(messageBuffer)); - logger('Stage 1 - Responder sent the second handshake message.') - } - } - - // stage 2 - async finish(): Promise { - if (this.isInitiator) { - logger('Stage 2 - Initiator sending third handshake message.'); - const messageBuffer = await this.xx.sendMessage(this.session, Buffer.alloc(0)); - this.connection.writeLP(encodeMessageBuffer(messageBuffer)); - logger('Stage 2 - Initiator sent message.'); + logger('Stage 2 - Initiator sent message with signed payload.'); } else { logger('Stage 2 - Responder waiting for third handshake message...'); - const receivedMessageBuffer = (await this.connection.readLP()).slice(); - const plaintext = await this.xx.recvMessage(this.session, decodeMessageBuffer(receivedMessageBuffer)); + const receivedMessageBuffer = decodeMessageBuffer((await this.connection.readLP()).slice()); + const plaintext = await this.xx.recvMessage(this.session, receivedMessageBuffer); logger('Stage 2 - Responder received the message, finished handshake. Got remote\'s static key.'); + + // if (!libp2pRemotekey) { + // throw new Error("Missing remote's libp2p public key, can't verify signature."); + // } + + await verifySignedPayload(receivedMessageBuffer.ns, plaintext); } } diff --git a/src/noise.ts b/src/noise.ts index 8221a41..ec82433 100644 --- a/src/noise.ts +++ b/src/noise.ts @@ -84,9 +84,13 @@ export class Noise implements NoiseConnection { const prologue = Buffer.from(this.protocol); const handshake = new Handshake(isInitiator, this.privateKey, libp2pPublicKey, prologue, this.staticKeys, connection); - await handshake.propose(this.earlyData); - await handshake.exchange(remotePeer.pubKey.marshal()); - await handshake.finish(); + try { + await handshake.propose(); + await handshake.exchange(); + await handshake.finish(this.earlyData); + } catch (e) { + throw new Error(`Error occurred during handshake: ${e.message}`); + } return handshake; } diff --git a/src/utils.ts b/src/utils.ts index 107819d..4936708 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -86,17 +86,17 @@ export function decodeMessageBuffer(message: bytes): MessageBuffer { export async function verifyPeerId(peerId: bytes, publicKey: bytes) { const generatedPeerId = await PeerId.createFromPubKey(publicKey); if (!generatedPeerId.equals(peerId)) { - Promise.reject("Peer ID doesn't match libp2p public key."); + throw new Error("Peer ID doesn't match libp2p public key."); } } -export async function verifySignedPayload(noiseStaticKey: bytes, plaintext: bytes, libp2pPublicKey: bytes) { +export async function verifySignedPayload(noiseStaticKey: bytes, plaintext: bytes) { const NoiseHandshakePayload = await loadPayloadProto(); const receivedPayload = NoiseHandshakePayload.toObject(NoiseHandshakePayload.decode(plaintext)); const generatedPayload = getHandshakePayload(noiseStaticKey); - if (!ed25519.verify(generatedPayload, receivedPayload.noiseStaticKeySignature, libp2pPublicKey)) { - Promise.reject("Static key doesn't match to peer that signed payload!"); + if (!ed25519.verify(generatedPayload, receivedPayload.noiseStaticKeySignature, receivedPayload.libp2pKey)) { + throw new Error("Static key doesn't match to peer that signed payload!"); } } diff --git a/test/handshake.test.ts b/test/handshake.test.ts index 927bc2b..792e0d2 100644 --- a/test/handshake.test.ts +++ b/test/handshake.test.ts @@ -10,46 +10,50 @@ import {createPeerIds} from "./fixtures/peer"; describe("Handshake", () => { it("should propose, exchange and finish handshake", async() => { - const duplex = Duplex(); - const connectionFrom = Wrap(duplex[0]); - const connectionTo = Wrap(duplex[1]); + 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 [peerA, peerB] = await createPeerIds(2); + const prologue = Buffer.from('/noise'); + const staticKeysInitiator = generateKeypair(); + const staticKeysResponder = generateKeypair(); + const [peerA, peerB] = await createPeerIds(2); - const initiatorPrivKey = peerA.privKey.marshal().slice(0, 32); - const initiatorPubKey = peerA.pubKey.marshal(); - const handshakeInitator = new Handshake(true, initiatorPrivKey, initiatorPubKey, prologue, staticKeysInitiator, connectionFrom); + const initiatorPrivKey = peerA.privKey.marshal().slice(0, 32); + const initiatorPubKey = peerA.pubKey.marshal(); + const handshakeInitator = new Handshake(true, initiatorPrivKey, initiatorPubKey, prologue, staticKeysInitiator, connectionFrom); - const responderPrivKey = peerB.privKey.marshal().slice(0, 32); - const responderPubKey = peerB.pubKey.marshal(); - const handshakeResponder = new Handshake(false, responderPrivKey, responderPubKey, prologue, staticKeysResponder, connectionTo); + const responderPrivKey = peerB.privKey.marshal().slice(0, 32); + const responderPubKey = peerB.pubKey.marshal(); + const handshakeResponder = new Handshake(false, responderPrivKey, responderPubKey, prologue, staticKeysResponder, connectionTo); - await handshakeInitator.propose(); - await handshakeResponder.propose(); + await handshakeInitator.propose(); + await handshakeResponder.propose(); - await handshakeResponder.exchange(); - await handshakeInitator.exchange(peerB.pubKey.marshal()); + await handshakeResponder.exchange(); + await handshakeInitator.exchange(); - await handshakeInitator.finish(); - await handshakeResponder.finish(); + await handshakeInitator.finish(); + await handshakeResponder.finish(); - const sessionInitator = handshakeInitator.session; - const sessionResponder = handshakeResponder.session; + const sessionInitator = handshakeInitator.session; + const sessionResponder = handshakeResponder.session; - // Test shared key - if (sessionInitator.cs1 && sessionResponder.cs1 && sessionInitator.cs2 && sessionResponder.cs2) { - assert(sessionInitator.cs1.k.equals(sessionResponder.cs1.k)); - assert(sessionInitator.cs2.k.equals(sessionResponder.cs2.k)); - } else { - assert(false); + // Test shared key + if (sessionInitator.cs1 && sessionResponder.cs1 && sessionInitator.cs2 && sessionResponder.cs2) { + assert(sessionInitator.cs1.k.equals(sessionResponder.cs1.k)); + assert(sessionInitator.cs2.k.equals(sessionResponder.cs2.k)); + } else { + assert(false); + } + + // Test encryption and decryption + const encrypted = handshakeInitator.encrypt(Buffer.from("encryptthis"), handshakeInitator.session); + const decrypted = handshakeResponder.decrypt(encrypted, handshakeResponder.session); + assert(decrypted.equals(Buffer.from("encryptthis"))); + } catch (e) { + assert(false, e.message); } - - // Test encryption and decryption - const encrypted = handshakeInitator.encrypt(Buffer.from("encryptthis"), handshakeInitator.session); - const decrypted = handshakeResponder.decrypt(encrypted, handshakeResponder.session); - assert(decrypted.equals(Buffer.from("encryptthis"))); }); }); diff --git a/test/noise.test.ts b/test/noise.test.ts index 22f5578..a1c3bfe 100644 --- a/test/noise.test.ts +++ b/test/noise.test.ts @@ -2,7 +2,6 @@ import { expect, assert } from "chai"; import DuplexPair from 'it-pair/duplex'; import { Noise } from "../src"; -import { generateEd25519Keys } from "./utils"; import {createPeerIds, createPeerIdsFromFixtures} from "./fixtures/peer"; import Wrap from "it-pb-rpc"; import {Handshake} from "../src/handshake"; @@ -11,7 +10,7 @@ import { decodeMessageBuffer, encodeMessageBuffer, generateKeypair, - getHandshakePayload, + getHandshakePayload, signEarlyDataPayload, signPayload } from "../src/utils"; import {XXHandshake} from "../src/xx"; @@ -25,28 +24,32 @@ describe("Noise", () => { }); it("should communicate through encrypted streams", async() => { - const libp2pInitPrivKey = localPeer.privKey.marshal().slice(0, 32); - const libp2pRespPrivKey = remotePeer.privKey.marshal().slice(0, 32); + try { + const libp2pInitPrivKey = localPeer.privKey.marshal().slice(0, 32); + const libp2pRespPrivKey = remotePeer.privKey.marshal().slice(0, 32); - const noiseInit = new Noise(libp2pInitPrivKey); - const noiseResp = new Noise(libp2pRespPrivKey); + const noiseInit = new Noise(libp2pInitPrivKey); + const noiseResp = new Noise(libp2pRespPrivKey); - 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); + 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")); - const response = await wrappedInbound.readLP(); - expect(response.toString()).equal("test"); + wrappedOutbound.writeLP(Buffer.from("test")); + const response = await wrappedInbound.readLP(); + expect(response.toString()).equal("test"); + } catch (e) { + assert(false, e.message); + } }); it("should test that secureOutbound is spec compliant", async() => { - const libp2pPrivKey = localPeer.privKey.marshal().slice(0, 32); - const noiseInit = new Noise(libp2pPrivKey); + const libp2pInitPrivKey = localPeer.privKey.marshal().slice(0, 32); + const noiseInit = new Noise(libp2pInitPrivKey); const [inboundConnection, outboundConnection] = DuplexPair(); const [outbound, { wrapped, handshake }] = await Promise.all([ @@ -56,7 +59,8 @@ describe("Noise", () => { const prologue = Buffer.from('/noise'); const staticKeys = generateKeypair(); const xx = new XXHandshake(); - const libp2pPubKey = remotePeer.pubKey.marshal().slice(32, 64); + const libp2pPubKey = remotePeer.pubKey.marshal(); + const libp2pPrivKey = remotePeer.privKey.marshal().slice(0, 32); const handshake = new Handshake(false, libp2pPrivKey, libp2pPubKey, prologue, staticKeys, wrapped, xx); let receivedMessageBuffer = decodeMessageBuffer((await wrapped.readLP()).slice()); @@ -74,19 +78,23 @@ describe("Noise", () => { // Stage 2 - finish handshake receivedMessageBuffer = decodeMessageBuffer((await wrapped.readLP()).slice()); await xx.recvMessage(handshake.session, receivedMessageBuffer); - return { wrapped, handshake }; + return {wrapped, handshake}; })(), ]); - const wrappedOutbound = Wrap(outbound.conn); - wrappedOutbound.write(Buffer.from("test")); + try { + const wrappedOutbound = Wrap(outbound.conn); + wrappedOutbound.write(Buffer.from("test")); - // Check that noise message is prefixed with 16-bit big-endian unsigned integer - const receivedEncryptedPayload = (await wrapped.read()).slice(); - const dataLength = receivedEncryptedPayload.readInt16BE(0); - const data = receivedEncryptedPayload.slice(2, dataLength + 2); - const decrypted = handshake.decrypt(data, handshake.session); - // Decrypted data should match - assert(decrypted.equals(Buffer.from("test"))); + // Check that noise message is prefixed with 16-bit big-endian unsigned integer + const receivedEncryptedPayload = (await wrapped.read()).slice(); + const dataLength = receivedEncryptedPayload.readInt16BE(0); + const data = receivedEncryptedPayload.slice(2, dataLength + 2); + const decrypted = handshake.decrypt(data, handshake.session); + // Decrypted data should match + assert(decrypted.equals(Buffer.from("test"))); + } catch (e) { + assert(false, e.message); + } }) }); From bb303a568e6984106c571d54ba5822797def8c99 Mon Sep 17 00:00:00 2001 From: morrigan Date: Mon, 2 Dec 2019 15:28:59 +0100 Subject: [PATCH 04/12] Fix eslint --- src/crypto.ts | 3 +-- test/noise.test.ts | 6 +++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/crypto.ts b/src/crypto.ts index fb8308a..0d33d33 100644 --- a/src/crypto.ts +++ b/src/crypto.ts @@ -1,4 +1,3 @@ -import { Duplex } from "it-pair"; import { Handshake } from "./handshake"; import { Buffer } from "buffer"; @@ -7,7 +6,7 @@ interface ReturnEncryptionWrapper { } // Returns generator that encrypts payload from the user - export function encryptStream(handshake: Handshake): ReturnEncryptionWrapper { +export function encryptStream(handshake: Handshake): ReturnEncryptionWrapper { return async function * (source) { for await (const chunk of source) { const chunkBuffer = Buffer.from(chunk); diff --git a/test/noise.test.ts b/test/noise.test.ts index a1c3bfe..44e974f 100644 --- a/test/noise.test.ts +++ b/test/noise.test.ts @@ -2,7 +2,7 @@ import { expect, assert } from "chai"; import DuplexPair from 'it-pair/duplex'; import { Noise } from "../src"; -import {createPeerIds, createPeerIdsFromFixtures} from "./fixtures/peer"; +import {createPeerIdsFromFixtures} from "./fixtures/peer"; import Wrap from "it-pb-rpc"; import {Handshake} from "../src/handshake"; import { @@ -10,7 +10,7 @@ import { decodeMessageBuffer, encodeMessageBuffer, generateKeypair, - getHandshakePayload, signEarlyDataPayload, + getHandshakePayload, signPayload } from "../src/utils"; import {XXHandshake} from "../src/xx"; @@ -20,7 +20,7 @@ describe("Noise", () => { let remotePeer, localPeer; before(async () => { - [localPeer, remotePeer] = await createPeerIds(2); + [localPeer, remotePeer] = await createPeerIdsFromFixtures(2); }); it("should communicate through encrypted streams", async() => { From e0806499ff769a3f60c24164cfbdede6233c0d32 Mon Sep 17 00:00:00 2001 From: morrigan Date: Tue, 3 Dec 2019 13:39:33 +0100 Subject: [PATCH 05/12] Verify peer id --- package.json | 2 +- src/@types/libp2p.ts | 2 ++ src/handshake.ts | 30 ++++++++++++++++++------------ src/noise.ts | 6 +++--- src/utils.ts | 21 +++++++++++++-------- test/handshake.test.ts | 8 ++++---- test/noise.test.ts | 4 ++-- 7 files changed, 43 insertions(+), 30 deletions(-) diff --git a/package.json b/package.json index e3d47e9..77a9cfc 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,6 @@ "bn.js-typings": "^1.0.1", "chai": "^4.2.0", "eslint": "^6.6.0", - "libp2p-crypto": "^0.17.1", "mocha": "^6.2.2", "typescript": "^3.6.4" }, @@ -60,6 +59,7 @@ "it-pair": "^1.0.0", "it-pb-rpc": "^0.1.3", "it-pipe": "^1.1.0", + "libp2p-crypto": "^0.17.1", "peer-id": "^0.13.5", "protobufjs": "~6.8.8" } diff --git a/src/@types/libp2p.ts b/src/@types/libp2p.ts index 46bf95f..58ecd87 100644 --- a/src/@types/libp2p.ts +++ b/src/@types/libp2p.ts @@ -14,6 +14,8 @@ export type PeerId = { pubKey: { marshal(): bytes; }; + marshalPubKey(): bytes; + marshalPrivKey(): bytes; }; export interface NoiseConnection { diff --git a/src/handshake.ts b/src/handshake.ts index 6684d8a..bd27605 100644 --- a/src/handshake.ts +++ b/src/handshake.ts @@ -2,14 +2,16 @@ import { Buffer } from "buffer"; import { bytes, bytes32 } from "./@types/basic"; import { NoiseSession, XXHandshake } from "./xx"; -import { KeyPair } from "./@types/libp2p"; +import { KeyPair, PeerId } from "./@types/libp2p"; import { createHandshakePayload, decodeMessageBuffer, encodeMessageBuffer, getHandshakePayload, - logger, signEarlyDataPayload, - signPayload, verifySignedPayload, + logger, + signEarlyDataPayload, + signPayload, + verifySignedPayload, } from "./utils"; import { WrappedConnection } from "./noise"; @@ -22,6 +24,7 @@ export class Handshake { private prologue: bytes32; private staticKeys: KeyPair; private connection: WrappedConnection; + private remotePeer: PeerId; private xx: XXHandshake; constructor( @@ -31,6 +34,7 @@ export class Handshake { prologue: bytes32, staticKeys: KeyPair, connection: WrappedConnection, + remotePeer: PeerId, handshake?: XXHandshake, ) { this.isInitiator = isInitiator; @@ -39,6 +43,7 @@ export class Handshake { this.prologue = prologue; this.staticKeys = staticKeys; this.connection = connection; + this.remotePeer = remotePeer; this.xx = handshake || new XXHandshake(); this.session = this.xx.initSession(this.isInitiator, this.prologue, this.staticKeys); @@ -67,11 +72,12 @@ export class Handshake { const plaintext = await this.xx.recvMessage(this.session, receivedMessageBuffer); logger('Stage 1 - Initiator received the message. Got remote\'s static key.'); - // if (!libp2pRemotekey) { - // throw new Error("Missing remote's libp2p public key, can't verify peer ID."); - // } logger("Initiator going to check remote's signature..."); - await verifySignedPayload(receivedMessageBuffer.ns, plaintext); + try { + await verifySignedPayload(receivedMessageBuffer.ns, plaintext, this.remotePeer.id); + } catch (e) { + throw new Error(`Error occurred while verifying signed payload: ${e.message}`); + } logger("All good with the signature!"); } else { logger('Stage 1 - Responder sending out first message with signed payload and static key.'); @@ -111,11 +117,11 @@ export class Handshake { const plaintext = await this.xx.recvMessage(this.session, receivedMessageBuffer); logger('Stage 2 - Responder received the message, finished handshake. Got remote\'s static key.'); - // if (!libp2pRemotekey) { - // throw new Error("Missing remote's libp2p public key, can't verify signature."); - // } - - await verifySignedPayload(receivedMessageBuffer.ns, plaintext); + try { + await verifySignedPayload(receivedMessageBuffer.ns, plaintext, this.remotePeer.id); + } catch (e) { + throw new Error(`Error occurred while verifying signed payload: ${e.message}`); + } } } diff --git a/src/noise.ts b/src/noise.ts index ec82433..179389c 100644 --- a/src/noise.ts +++ b/src/noise.ts @@ -46,7 +46,7 @@ export class Noise implements NoiseConnection { */ public async secureOutbound(localPeer: PeerId, connection: any, remotePeer: PeerId): Promise { const wrappedConnection = Wrap(connection); - const libp2pPublicKey = localPeer.pubKey.marshal(); + const libp2pPublicKey = localPeer.marshalPubKey(); const handshake = await this.performHandshake(wrappedConnection, true, libp2pPublicKey, remotePeer); const conn = await this.createSecureConnection(wrappedConnection, handshake); @@ -65,7 +65,7 @@ export class Noise implements NoiseConnection { */ public async secureInbound(localPeer: PeerId, connection: any, remotePeer: PeerId): Promise { const wrappedConnection = Wrap(connection); - const libp2pPublicKey = localPeer.pubKey.marshal(); + const libp2pPublicKey = localPeer.marshalPubKey(); const handshake = await this.performHandshake(wrappedConnection, false, libp2pPublicKey, remotePeer); const conn = await this.createSecureConnection(wrappedConnection, handshake); @@ -82,7 +82,7 @@ export class Noise implements NoiseConnection { remotePeer: PeerId, ): Promise { const prologue = Buffer.from(this.protocol); - const handshake = new Handshake(isInitiator, this.privateKey, libp2pPublicKey, prologue, this.staticKeys, connection); + const handshake = new Handshake(isInitiator, this.privateKey, libp2pPublicKey, prologue, this.staticKeys, connection, remotePeer); try { await handshake.propose(); diff --git a/src/utils.ts b/src/utils.ts index 4936708..8ea0752 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -3,6 +3,7 @@ import protobuf from "protobufjs"; import { Buffer } from "buffer"; import debug from "debug"; import PeerId from "peer-id"; +import * as crypto from 'libp2p-crypto'; import { KeyPair } from "./@types/libp2p"; import { bytes } from "./@types/basic"; @@ -83,19 +84,23 @@ export function decodeMessageBuffer(message: bytes): MessageBuffer { } } -export async function verifyPeerId(peerId: bytes, publicKey: bytes) { - const generatedPeerId = await PeerId.createFromPubKey(publicKey); - if (!generatedPeerId.equals(peerId)) { - throw new Error("Peer ID doesn't match libp2p public key."); - } +async function isValidPeerId(peerId: bytes, publicKeyProtobuf: bytes) { + const generatedPeerId = await PeerId.createFromPubKey(publicKeyProtobuf); + return generatedPeerId.id.equals(peerId); } -export async function verifySignedPayload(noiseStaticKey: bytes, plaintext: bytes) { +export async function verifySignedPayload(noiseStaticKey: bytes, plaintext: bytes, peerId: bytes) { const NoiseHandshakePayload = await loadPayloadProto(); const receivedPayload = NoiseHandshakePayload.toObject(NoiseHandshakePayload.decode(plaintext)); - const generatedPayload = getHandshakePayload(noiseStaticKey); - if (!ed25519.verify(generatedPayload, receivedPayload.noiseStaticKeySignature, receivedPayload.libp2pKey)) { + if (!(await isValidPeerId(peerId, receivedPayload.libp2pKey)) ) { + throw new Error("Peer ID doesn't match libp2p public key."); + } + + const generatedPayload = getHandshakePayload(noiseStaticKey); + // Unmarshaling from PublicKey protobuf and taking key buffer only. + const publicKey = crypto.keys.unmarshalPublicKey(receivedPayload.libp2pKey).marshal(); + if (!ed25519.verify(generatedPayload, receivedPayload.noiseStaticKeySignature, publicKey)) { throw new Error("Static key doesn't match to peer that signed payload!"); } } diff --git a/test/handshake.test.ts b/test/handshake.test.ts index 792e0d2..ec56010 100644 --- a/test/handshake.test.ts +++ b/test/handshake.test.ts @@ -21,12 +21,12 @@ describe("Handshake", () => { const [peerA, peerB] = await createPeerIds(2); const initiatorPrivKey = peerA.privKey.marshal().slice(0, 32); - const initiatorPubKey = peerA.pubKey.marshal(); - const handshakeInitator = new Handshake(true, initiatorPrivKey, initiatorPubKey, prologue, staticKeysInitiator, connectionFrom); + const initiatorPubKey = peerA.marshalPubKey(); + const handshakeInitator = new Handshake(true, initiatorPrivKey, initiatorPubKey, prologue, staticKeysInitiator, connectionFrom, peerB); const responderPrivKey = peerB.privKey.marshal().slice(0, 32); - const responderPubKey = peerB.pubKey.marshal(); - const handshakeResponder = new Handshake(false, responderPrivKey, responderPubKey, prologue, staticKeysResponder, connectionTo); + const responderPubKey = peerB.marshalPubKey(); + const handshakeResponder = new Handshake(false, responderPrivKey, responderPubKey, prologue, staticKeysResponder, connectionTo, peerA); await handshakeInitator.propose(); await handshakeResponder.propose(); diff --git a/test/noise.test.ts b/test/noise.test.ts index 44e974f..f8fecfe 100644 --- a/test/noise.test.ts +++ b/test/noise.test.ts @@ -59,9 +59,9 @@ describe("Noise", () => { const prologue = Buffer.from('/noise'); const staticKeys = generateKeypair(); const xx = new XXHandshake(); - const libp2pPubKey = remotePeer.pubKey.marshal(); + const libp2pPubKey = remotePeer.marshalPubKey(); const libp2pPrivKey = remotePeer.privKey.marshal().slice(0, 32); - const handshake = new Handshake(false, libp2pPrivKey, libp2pPubKey, prologue, staticKeys, wrapped, xx); + const handshake = new Handshake(false, libp2pPrivKey, libp2pPubKey, prologue, staticKeys, wrapped, localPeer, xx); let receivedMessageBuffer = decodeMessageBuffer((await wrapped.readLP()).slice()); // The first handshake message contains the initiator's ephemeral public key From c4d7e95c08ccd980a88e203bc08386120583f473 Mon Sep 17 00:00:00 2001 From: morrigan Date: Tue, 3 Dec 2019 13:52:44 +0100 Subject: [PATCH 06/12] Write tests --- test/handshake.test.ts | 77 ++++++++++++++++++++++++++++++++++++++---- test/noise.test.ts | 12 +++---- test/utils.ts | 8 +++++ test/xx.test.ts | 1 + 4 files changed, 85 insertions(+), 13 deletions(-) diff --git a/test/handshake.test.ts b/test/handshake.test.ts index ec56010..6019711 100644 --- a/test/handshake.test.ts +++ b/test/handshake.test.ts @@ -1,14 +1,21 @@ -import {assert} from "chai"; +import {assert, expect} from "chai"; import Duplex from 'it-pair/duplex'; import {Buffer} from "buffer"; import Wrap from "it-pb-rpc"; import {Handshake} from "../src/handshake"; import {generateKeypair} from "../src/utils"; -import {createPeerIds} from "./fixtures/peer"; +import {createPeerIdsFromFixtures} from "./fixtures/peer"; +import {getKeyPairFromPeerId} from "./utils"; describe("Handshake", () => { + let peerA, peerB, fakePeer; + + before(async () => { + [peerA, peerB, fakePeer] = await createPeerIdsFromFixtures(3); + }); + it("should propose, exchange and finish handshake", async() => { try { const duplex = Duplex(); @@ -18,14 +25,11 @@ describe("Handshake", () => { const prologue = Buffer.from('/noise'); const staticKeysInitiator = generateKeypair(); const staticKeysResponder = generateKeypair(); - const [peerA, peerB] = await createPeerIds(2); - const initiatorPrivKey = peerA.privKey.marshal().slice(0, 32); - const initiatorPubKey = peerA.marshalPubKey(); + const { privateKey: initiatorPrivKey, publicKey: initiatorPubKey } = getKeyPairFromPeerId(peerA); const handshakeInitator = new Handshake(true, initiatorPrivKey, initiatorPubKey, prologue, staticKeysInitiator, connectionFrom, peerB); - const responderPrivKey = peerB.privKey.marshal().slice(0, 32); - const responderPubKey = peerB.marshalPubKey(); + const { privateKey: responderPrivKey, publicKey: responderPubKey } = getKeyPairFromPeerId(peerB); const handshakeResponder = new Handshake(false, responderPrivKey, responderPubKey, prologue, staticKeysResponder, connectionTo, peerA); await handshakeInitator.propose(); @@ -56,4 +60,63 @@ describe("Handshake", () => { assert(false, e.message); } }); + + it("Initiator should fail to exchange handshake if given wrong public key in payload", 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 { privateKey: initiatorPrivKey, publicKey: initiatorPubKey } = getKeyPairFromPeerId(peerA); + const handshakeInitator = new Handshake(true, initiatorPrivKey, initiatorPubKey, prologue, staticKeysInitiator, connectionFrom, fakePeer); + + const { privateKey: responderPrivKey, publicKey: responderPubKey } = getKeyPairFromPeerId(peerB); + const handshakeResponder = new Handshake(false, responderPrivKey, responderPubKey, prologue, staticKeysResponder, connectionTo, peerA); + + await handshakeInitator.propose(); + await handshakeResponder.propose(); + + await handshakeResponder.exchange(); + await handshakeInitator.exchange(); + + assert(false, "Should throw exception"); + } catch (e) { + expect(e.message).equals("Error occurred while verifying signed payload: Peer ID doesn't match libp2p public key.") + } + }); + + it("Responder should fail to exchange handshake if given wrong public key in payload", 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 { privateKey: initiatorPrivKey, publicKey: initiatorPubKey } = getKeyPairFromPeerId(peerA); + const handshakeInitator = new Handshake(true, initiatorPrivKey, initiatorPubKey, prologue, staticKeysInitiator, connectionFrom, peerB); + + const { privateKey: responderPrivKey, publicKey: responderPubKey } = getKeyPairFromPeerId(peerB); + const handshakeResponder = new Handshake(false, responderPrivKey, responderPubKey, prologue, staticKeysResponder, connectionTo, fakePeer); + + await handshakeInitator.propose(); + await handshakeResponder.propose(); + + await handshakeResponder.exchange(); + await handshakeInitator.exchange(); + + await handshakeInitator.finish(); + await handshakeResponder.finish(); + + assert(false, "Should throw exception"); + } catch (e) { + expect(e.message).equals("Error occurred while verifying signed payload: Peer ID doesn't match libp2p public key.") + } + }); }); diff --git a/test/noise.test.ts b/test/noise.test.ts index f8fecfe..b409568 100644 --- a/test/noise.test.ts +++ b/test/noise.test.ts @@ -15,6 +15,7 @@ import { } from "../src/utils"; import {XXHandshake} from "../src/xx"; import {Buffer} from "buffer"; +import {getKeyPairFromPeerId} from "./utils"; describe("Noise", () => { let remotePeer, localPeer; @@ -25,9 +26,8 @@ describe("Noise", () => { it("should communicate through encrypted streams", async() => { try { - const libp2pInitPrivKey = localPeer.privKey.marshal().slice(0, 32); - const libp2pRespPrivKey = remotePeer.privKey.marshal().slice(0, 32); - + const { privateKey: libp2pInitPrivKey } = getKeyPairFromPeerId(localPeer); + const { privateKey: libp2pRespPrivKey } = getKeyPairFromPeerId(remotePeer); const noiseInit = new Noise(libp2pInitPrivKey); const noiseResp = new Noise(libp2pRespPrivKey); @@ -48,7 +48,7 @@ describe("Noise", () => { }); it("should test that secureOutbound is spec compliant", async() => { - const libp2pInitPrivKey = localPeer.privKey.marshal().slice(0, 32); + const { privateKey: libp2pInitPrivKey } = getKeyPairFromPeerId(localPeer); const noiseInit = new Noise(libp2pInitPrivKey); const [inboundConnection, outboundConnection] = DuplexPair(); @@ -59,8 +59,8 @@ describe("Noise", () => { const prologue = Buffer.from('/noise'); const staticKeys = generateKeypair(); const xx = new XXHandshake(); - const libp2pPubKey = remotePeer.marshalPubKey(); - const libp2pPrivKey = remotePeer.privKey.marshal().slice(0, 32); + const { privateKey: libp2pPrivKey, publicKey: libp2pPubKey } = getKeyPairFromPeerId(remotePeer); + const handshake = new Handshake(false, libp2pPrivKey, libp2pPubKey, prologue, staticKeys, wrapped, localPeer, xx); let receivedMessageBuffer = decodeMessageBuffer((await wrapped.readLP()).slice()); diff --git a/test/utils.ts b/test/utils.ts index 6a6580a..2ac5b7b 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -1,5 +1,13 @@ import * as crypto from 'libp2p-crypto'; +import {KeyPair, PeerId} from "../src/@types/libp2p"; export async function generateEd25519Keys() { return await crypto.keys.generateKeyPair('ed25519'); } + +export function getKeyPairFromPeerId(peerId: PeerId): KeyPair { + return { + privateKey: peerId.privKey.marshal().slice(0, 32), + publicKey: peerId.marshalPubKey(), + } +} diff --git a/test/xx.test.ts b/test/xx.test.ts index 9ff5c6b..75c3bc0 100644 --- a/test/xx.test.ts +++ b/test/xx.test.ts @@ -57,6 +57,7 @@ describe("Index", () => { // initiator creates payload const libp2pInitPrivKey = libp2pInitKeys.marshal().slice(0, 32); const libp2pInitPubKey = libp2pInitKeys.marshal().slice(32, 64); + const payloadInitEnc = await createHandshakePayload(libp2pInitPubKey, libp2pInitPrivKey, initSignedPayload); // initiator sends message From ac950d14199417cc14bff35a38c54e631a0309b4 Mon Sep 17 00:00:00 2001 From: morrigan Date: Tue, 3 Dec 2019 15:12:55 +0100 Subject: [PATCH 07/12] Remove promises from xx --- src/handshake.ts | 12 ++++----- src/xx.ts | 62 +++++++++++++++++++++++----------------------- test/noise.test.ts | 6 ++--- test/xx.test.ts | 16 ++++++------ 4 files changed, 48 insertions(+), 48 deletions(-) diff --git a/src/handshake.ts b/src/handshake.ts index bd27605..255327e 100644 --- a/src/handshake.ts +++ b/src/handshake.ts @@ -53,13 +53,13 @@ export class Handshake { async propose(): Promise { if (this.isInitiator) { logger("Stage 0 - Initiator starting to send first message."); - const messageBuffer = await this.xx.sendMessage(this.session, Buffer.alloc(0)); + const messageBuffer = this.xx.sendMessage(this.session, Buffer.alloc(0)); this.connection.writeLP(encodeMessageBuffer(messageBuffer)); logger("Stage 0 - Initiator finished sending first message."); } else { logger("Stage 0 - Responder waiting to receive first message..."); const receivedMessageBuffer = decodeMessageBuffer((await this.connection.readLP()).slice()); - await this.xx.recvMessage(this.session, receivedMessageBuffer); + this.xx.recvMessage(this.session, receivedMessageBuffer); logger("Stage 0 - Responder received first message."); } } @@ -69,7 +69,7 @@ export class Handshake { if (this.isInitiator) { logger('Stage 1 - Initiator waiting to receive first message from responder...'); const receivedMessageBuffer = decodeMessageBuffer((await this.connection.readLP()).slice()); - const plaintext = await 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("Initiator going to check remote's signature..."); @@ -90,7 +90,7 @@ export class Handshake { signedEarlyDataPayload, ); - const messageBuffer = await this.xx.sendMessage(this.session, handshakePayload); + const messageBuffer = this.xx.sendMessage(this.session, handshakePayload); this.connection.writeLP(encodeMessageBuffer(messageBuffer)); logger('Stage 1 - Responder sent the second handshake message with signed payload.') } @@ -108,13 +108,13 @@ export class Handshake { signedPayload, signedEarlyDataPayload ); - const messageBuffer = await this.xx.sendMessage(this.session, handshakePayload); + const messageBuffer = this.xx.sendMessage(this.session, handshakePayload); this.connection.writeLP(encodeMessageBuffer(messageBuffer)); logger('Stage 2 - Initiator sent message with signed payload.'); } else { logger('Stage 2 - Responder waiting for third handshake message...'); const receivedMessageBuffer = decodeMessageBuffer((await this.connection.readLP()).slice()); - const plaintext = await 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.'); try { diff --git a/src/xx.ts b/src/xx.ts index 95aa619..d4b479a 100644 --- a/src/xx.ts +++ b/src/xx.ts @@ -191,7 +191,7 @@ export class XXHandshake { return SHA256.digest(Buffer.from([...a, ...b])); } - private async encryptAndHash(ss: SymmetricState, plaintext: bytes): Promise { + private encryptAndHash(ss: SymmetricState, plaintext: bytes): bytes { let ciphertext; if (this.hasKey(ss.cs)) { ciphertext = this.encryptWithAd(ss.cs, ss.h, plaintext); @@ -203,7 +203,7 @@ export class XXHandshake { return ciphertext; } - private async decryptAndHash(ss: SymmetricState, ciphertext: bytes): Promise { + private decryptAndHash(ss: SymmetricState, ciphertext: bytes): bytes { let plaintext; if (this.hasKey(ss.cs)) { plaintext = this.decryptWithAd(ss.cs, ss.h, ciphertext); @@ -223,38 +223,38 @@ export class XXHandshake { return { cs1, cs2 }; } - private async writeMessageA(hs: HandshakeState, payload: bytes): Promise { + private writeMessageA(hs: HandshakeState, payload: bytes): MessageBuffer { const ns = Buffer.alloc(0); hs.e = generateKeypair(); const ne = hs.e.publicKey; this.mixHash(hs.ss, ne); - const ciphertext = await this.encryptAndHash(hs.ss, payload); + const ciphertext = this.encryptAndHash(hs.ss, payload); return {ne, ns, ciphertext}; } - private async writeMessageB(hs: HandshakeState, payload: bytes): Promise { + private writeMessageB(hs: HandshakeState, payload: bytes): MessageBuffer { hs.e = generateKeypair(); const ne = hs.e.publicKey; this.mixHash(hs.ss, ne); this.mixKey(hs.ss, this.dh(hs.e.privateKey, hs.re)); const spk = Buffer.from(hs.s.publicKey); - const ns = await this.encryptAndHash(hs.ss, spk); + const ns = this.encryptAndHash(hs.ss, spk); this.mixKey(hs.ss, this.dh(hs.s.privateKey, hs.re)); - const ciphertext = await this.encryptAndHash(hs.ss, payload); + const ciphertext = this.encryptAndHash(hs.ss, payload); return { ne, ns, ciphertext }; } - private async writeMessageC(hs: HandshakeState, payload: bytes) { + private writeMessageC(hs: HandshakeState, payload: bytes) { const spk = Buffer.from(hs.s.publicKey); - const ns = await this.encryptAndHash(hs.ss, spk); + const ns = this.encryptAndHash(hs.ss, spk); this.mixKey(hs.ss, this.dh(hs.s.privateKey, hs.re)); - const ciphertext = await this.encryptAndHash(hs.ss, payload); + const ciphertext = this.encryptAndHash(hs.ss, payload); const ne = this.createEmptyKey(); const messageBuffer: MessageBuffer = {ne, ns, ciphertext}; const { cs1, cs2 } = this.split(hs.ss); @@ -262,7 +262,7 @@ export class XXHandshake { return { h: hs.ss.h, messageBuffer, cs1, cs2 }; } - private async writeMessageRegular(cs: CipherState, payload: bytes): Promise { + private writeMessageRegular(cs: CipherState, payload: bytes): MessageBuffer { const ciphertext = this.encryptWithAd(cs, Buffer.alloc(0), payload); const ne = this.createEmptyKey(); const ns = Buffer.alloc(0); @@ -270,16 +270,16 @@ export class XXHandshake { return { ne, ns, ciphertext }; } - private async readMessageA(hs: HandshakeState, message: MessageBuffer): Promise { + private readMessageA(hs: HandshakeState, message: MessageBuffer): bytes { if (x25519.publicKeyVerify(message.ne)) { hs.re = message.ne; } this.mixHash(hs.ss, hs.re); - return await this.decryptAndHash(hs.ss, message.ciphertext); + return this.decryptAndHash(hs.ss, message.ciphertext); } - private async readMessageB(hs: HandshakeState, message: MessageBuffer): Promise { + private readMessageB(hs: HandshakeState, message: MessageBuffer): bytes { if (x25519.publicKeyVerify(message.ne)) { hs.re = message.ne; } @@ -289,16 +289,16 @@ export class XXHandshake { throw new Error("Handshake state `e` param is missing."); } this.mixKey(hs.ss, this.dh(hs.e.privateKey, hs.re)); - const ns = await this.decryptAndHash(hs.ss, message.ns); + const ns = this.decryptAndHash(hs.ss, message.ns); if (ns.length === 32 && x25519.publicKeyVerify(message.ns)) { hs.rs = ns; } this.mixKey(hs.ss, this.dh(hs.e.privateKey, hs.rs)); - return await this.decryptAndHash(hs.ss, message.ciphertext); + return this.decryptAndHash(hs.ss, message.ciphertext); } - private async readMessageC(hs: HandshakeState, message: MessageBuffer) { - const ns = await this.decryptAndHash(hs.ss, message.ns); + private readMessageC(hs: HandshakeState, message: MessageBuffer) { + const ns = this.decryptAndHash(hs.ss, message.ns); if (ns.length === 32 && x25519.publicKeyVerify(message.ns)) { hs.rs = ns; } @@ -308,7 +308,7 @@ export class XXHandshake { } this.mixKey(hs.ss, this.dh(hs.e.privateKey, hs.rs)); - const plaintext = await this.decryptAndHash(hs.ss, message.ciphertext); + const plaintext = this.decryptAndHash(hs.ss, message.ciphertext); const { cs1, cs2 } = this.split(hs.ss); return { h: hs.ss.h, plaintext, cs1, cs2 }; @@ -336,14 +336,14 @@ export class XXHandshake { }; } - public async sendMessage(session: NoiseSession, message: bytes): Promise { + public sendMessage(session: NoiseSession, message: bytes): MessageBuffer { let messageBuffer: MessageBuffer; if (session.mc.eqn(0)) { - messageBuffer = await this.writeMessageA(session.hs, message); + messageBuffer = this.writeMessageA(session.hs, message); } else if (session.mc.eqn(1)) { - messageBuffer = await this.writeMessageB(session.hs, message); + messageBuffer = this.writeMessageB(session.hs, message); } else if (session.mc.eqn(2)) { - const { h, messageBuffer: resultingBuffer, cs1, cs2 } = await this.writeMessageC(session.hs, message); + const { h, messageBuffer: resultingBuffer, cs1, cs2 } = this.writeMessageC(session.hs, message); messageBuffer = resultingBuffer; session.h = h; session.cs1 = cs1; @@ -354,13 +354,13 @@ export class XXHandshake { throw new Error("CS1 (cipher state) is not defined") } - messageBuffer = await this.writeMessageRegular(session.cs1, message); + messageBuffer = this.writeMessageRegular(session.cs1, message); } else { if (!session.cs2) { throw new Error("CS2 (cipher state) is not defined") } - messageBuffer = await this.writeMessageRegular(session.cs2, message); + messageBuffer = this.writeMessageRegular(session.cs2, message); } } else { throw new Error("Session invalid.") @@ -370,14 +370,14 @@ export class XXHandshake { return messageBuffer; } - public async recvMessage(session: NoiseSession, message: MessageBuffer): Promise { + public recvMessage(session: NoiseSession, message: MessageBuffer): bytes { let plaintext: bytes; if (session.mc.eqn(0)) { - plaintext = await this.readMessageA(session.hs, message); + plaintext = this.readMessageA(session.hs, message); } else if (session.mc.eqn(1)) { - plaintext = await this.readMessageB(session.hs, message); + plaintext = this.readMessageB(session.hs, message); } else if (session.mc.eqn(2)) { - const { h, plaintext: resultingPlaintext, cs1, cs2 } = await this.readMessageC(session.hs, message); + const { h, plaintext: resultingPlaintext, cs1, cs2 } = this.readMessageC(session.hs, message); plaintext = resultingPlaintext; session.h = h; session.cs1 = cs1; @@ -387,12 +387,12 @@ export class XXHandshake { if (!session.cs2) { throw new Error("CS1 (cipher state) is not defined") } - plaintext = await this.readMessageRegular(session.cs2, message); + plaintext = this.readMessageRegular(session.cs2, message); } else { if (!session.cs1) { throw new Error("CS1 (cipher state) is not defined") } - plaintext = await this.readMessageRegular(session.cs1, message); + plaintext = this.readMessageRegular(session.cs1, message); } } else { throw new Error("Session invalid."); diff --git a/test/noise.test.ts b/test/noise.test.ts index b409568..3acd8e2 100644 --- a/test/noise.test.ts +++ b/test/noise.test.ts @@ -66,18 +66,18 @@ describe("Noise", () => { let receivedMessageBuffer = decodeMessageBuffer((await wrapped.readLP()).slice()); // The first handshake message contains the initiator's ephemeral public key expect(receivedMessageBuffer.ne.length).equal(32); - await xx.recvMessage(handshake.session, receivedMessageBuffer); + xx.recvMessage(handshake.session, receivedMessageBuffer); // Stage 1 const signedPayload = signPayload(libp2pPrivKey, getHandshakePayload(staticKeys.publicKey)); const handshakePayload = await createHandshakePayload(libp2pPubKey, libp2pPrivKey, signedPayload); - const messageBuffer = await xx.sendMessage(handshake.session, handshakePayload); + const messageBuffer = xx.sendMessage(handshake.session, handshakePayload); wrapped.writeLP(encodeMessageBuffer(messageBuffer)); // Stage 2 - finish handshake receivedMessageBuffer = decodeMessageBuffer((await wrapped.readLP()).slice()); - await xx.recvMessage(handshake.session, receivedMessageBuffer); + xx.recvMessage(handshake.session, receivedMessageBuffer); return {wrapped, handshake}; })(), ]); diff --git a/test/xx.test.ts b/test/xx.test.ts index 75c3bc0..1eca6b4 100644 --- a/test/xx.test.ts +++ b/test/xx.test.ts @@ -48,9 +48,9 @@ describe("Index", () => { const respSignedPayload = await libp2pRespKeys.sign(getHandshakePayload(kpResp.publicKey)); // initiator: new XX noise session - const nsInit = await xx.initSession(true, prologue, kpInit); + const nsInit = xx.initSession(true, prologue, kpInit); // responder: new XX noise session - const nsResp = await xx.initSession(false, prologue, kpResp); + const nsResp = xx.initSession(false, prologue, kpResp); /* STAGE 0 */ @@ -62,12 +62,12 @@ describe("Index", () => { // initiator sends message const message = Buffer.concat([Buffer.alloc(0), payloadInitEnc]); - const messageBuffer = await xx.sendMessage(nsInit, message); + const messageBuffer = xx.sendMessage(nsInit, message); expect(messageBuffer.ne.length).not.equal(0); // responder receives message - const plaintext = await xx.recvMessage(nsResp, messageBuffer); + const plaintext = xx.recvMessage(nsResp, messageBuffer); console.log("Stage 0 responder payload: ", plaintext); /* STAGE 1 */ @@ -78,22 +78,22 @@ describe("Index", () => { const payloadRespEnc = await createHandshakePayload(libp2pRespPubKey, libp2pRespPrivKey, respSignedPayload); const message1 = Buffer.concat([message, payloadRespEnc]); - const messageBuffer2 = await xx.sendMessage(nsResp, message1); + const messageBuffer2 = xx.sendMessage(nsResp, message1); expect(messageBuffer2.ne.length).not.equal(0); expect(messageBuffer2.ns.length).not.equal(0); // initiator receive payload - const plaintext2 = await xx.recvMessage(nsInit, messageBuffer2); + const plaintext2 = xx.recvMessage(nsInit, messageBuffer2); console.log("Stage 1 responder payload: ", plaintext2); /* STAGE 2 */ // initiator send message - const messageBuffer3 = await xx.sendMessage(nsInit, Buffer.alloc(0)); + const messageBuffer3 = xx.sendMessage(nsInit, Buffer.alloc(0)); // responder receive message - const plaintext3 = await xx.recvMessage(nsResp, messageBuffer3); + const plaintext3 = xx.recvMessage(nsResp, messageBuffer3); console.log("Stage 2 responder payload: ", plaintext3); assert(nsInit.cs1.k.equals(nsResp.cs1.k)); From acb9b8753768da05957634057362831b29a2d3e0 Mon Sep 17 00:00:00 2001 From: morrigan Date: Tue, 3 Dec 2019 15:15:46 +0100 Subject: [PATCH 08/12] Split encoder functions from utils --- src/encoder.ts | 27 +++++++++++++++++++++++++++ src/handshake.ts | 3 +-- src/noise.ts | 3 ++- src/utils.ts | 25 ------------------------- test/noise.test.ts | 3 +-- 5 files changed, 31 insertions(+), 30 deletions(-) create mode 100644 src/encoder.ts diff --git a/src/encoder.ts b/src/encoder.ts new file mode 100644 index 0000000..ea946ef --- /dev/null +++ b/src/encoder.ts @@ -0,0 +1,27 @@ +import {Buffer} from "buffer"; +import {bytes} from "./@types/basic"; +import {MessageBuffer} from "./xx"; + +export const int16BEEncode = (value, target, offset) => { + target = target || Buffer.allocUnsafe(2); + return target.writeInt16BE(value, offset); +}; +int16BEEncode.bytes = 2; + +export const int16BEDecode = data => { + if (data.length < 2) throw RangeError('Could not decode int16BE'); + return data.readInt16BE(0); +}; +int16BEDecode.bytes = 2; + +export function encodeMessageBuffer(message: MessageBuffer): bytes { + return Buffer.concat([message.ne, message.ns, message.ciphertext]); +} + +export function decodeMessageBuffer(message: bytes): MessageBuffer { + return { + ne: message.slice(0, 32), + ns: message.slice(32, 64), + ciphertext: message.slice(64, message.length), + } +} diff --git a/src/handshake.ts b/src/handshake.ts index 255327e..88eaaeb 100644 --- a/src/handshake.ts +++ b/src/handshake.ts @@ -5,14 +5,13 @@ import { NoiseSession, XXHandshake } from "./xx"; import { KeyPair, PeerId } from "./@types/libp2p"; import { createHandshakePayload, - decodeMessageBuffer, - encodeMessageBuffer, getHandshakePayload, logger, signEarlyDataPayload, signPayload, verifySignedPayload, } from "./utils"; +import { decodeMessageBuffer, encodeMessageBuffer } from "./encoder"; import { WrappedConnection } from "./noise"; export class Handshake { diff --git a/src/noise.ts b/src/noise.ts index 179389c..83b313e 100644 --- a/src/noise.ts +++ b/src/noise.ts @@ -7,7 +7,8 @@ import pipe from 'it-pipe'; import lp from 'it-length-prefixed'; import { Handshake } from "./handshake"; -import { generateKeypair, int16BEDecode, int16BEEncode } from "./utils"; +import { generateKeypair } from "./utils"; +import { int16BEDecode, int16BEEncode } from "./encoder"; import { decryptStream, encryptStream } from "./crypto"; import { bytes } from "./@types/basic"; import { NoiseConnection, PeerId, KeyPair, SecureOutbound } from "./@types/libp2p"; diff --git a/src/utils.ts b/src/utils.ts index 8ea0752..8e8faf6 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -7,7 +7,6 @@ import * as crypto from 'libp2p-crypto'; import { KeyPair } from "./@types/libp2p"; import { bytes } from "./@types/basic"; -import { MessageBuffer } from "./xx"; export const logger = debug('libp2p:noise'); @@ -72,18 +71,6 @@ export const getHandshakePayload = (publicKey: bytes ) => Buffer.concat([Buffer. export const getEarlyDataPayload = (earlyData: bytes) => Buffer.concat([Buffer.from("noise-libp2p-early-data:"), earlyData]); -export function encodeMessageBuffer(message: MessageBuffer): bytes { - return Buffer.concat([message.ne, message.ns, message.ciphertext]); -} - -export function decodeMessageBuffer(message: bytes): MessageBuffer { - return { - ne: message.slice(0, 32), - ns: message.slice(32, 64), - ciphertext: message.slice(64, message.length), - } -} - async function isValidPeerId(peerId: bytes, publicKeyProtobuf: bytes) { const generatedPeerId = await PeerId.createFromPubKey(publicKeyProtobuf); return generatedPeerId.id.equals(peerId); @@ -104,15 +91,3 @@ export async function verifySignedPayload(noiseStaticKey: bytes, plaintext: byte throw new Error("Static key doesn't match to peer that signed payload!"); } } - -export const int16BEEncode = (value, target, offset) => { - target = target || Buffer.allocUnsafe(2); - return target.writeInt16BE(value, offset); -}; -int16BEEncode.bytes = 2; - -export const int16BEDecode = data => { - if (data.length < 2) throw RangeError('Could not decode int16BE'); - return data.readInt16BE(0); -}; -int16BEDecode.bytes = 2; diff --git a/test/noise.test.ts b/test/noise.test.ts index 3acd8e2..6d42cd5 100644 --- a/test/noise.test.ts +++ b/test/noise.test.ts @@ -7,12 +7,11 @@ import Wrap from "it-pb-rpc"; import {Handshake} from "../src/handshake"; import { createHandshakePayload, - decodeMessageBuffer, - encodeMessageBuffer, generateKeypair, getHandshakePayload, signPayload } from "../src/utils"; +import { decodeMessageBuffer, encodeMessageBuffer } from "../src/encoder"; import {XXHandshake} from "../src/xx"; import {Buffer} from "buffer"; import {getKeyPairFromPeerId} from "./utils"; From c8c03b5093d31827203148b6f02f20c234a4c945 Mon Sep 17 00:00:00 2001 From: morrigan Date: Tue, 3 Dec 2019 15:19:08 +0100 Subject: [PATCH 09/12] Separate logger from utils --- src/handshake.ts | 2 +- src/logger.ts | 2 ++ src/utils.ts | 3 --- 3 files changed, 3 insertions(+), 4 deletions(-) create mode 100644 src/logger.ts diff --git a/src/handshake.ts b/src/handshake.ts index 88eaaeb..cb76381 100644 --- a/src/handshake.ts +++ b/src/handshake.ts @@ -6,11 +6,11 @@ import { KeyPair, PeerId } from "./@types/libp2p"; import { createHandshakePayload, getHandshakePayload, - logger, signEarlyDataPayload, signPayload, verifySignedPayload, } from "./utils"; +import { logger } from "./logger"; import { decodeMessageBuffer, encodeMessageBuffer } from "./encoder"; import { WrappedConnection } from "./noise"; diff --git a/src/logger.ts b/src/logger.ts new file mode 100644 index 0000000..150e061 --- /dev/null +++ b/src/logger.ts @@ -0,0 +1,2 @@ +import debug from "debug"; +export const logger = debug('libp2p:noise'); diff --git a/src/utils.ts b/src/utils.ts index 8e8faf6..65b7939 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,15 +1,12 @@ import { x25519, ed25519 } from 'bcrypto'; import protobuf from "protobufjs"; import { Buffer } from "buffer"; -import debug from "debug"; import PeerId from "peer-id"; import * as crypto from 'libp2p-crypto'; import { KeyPair } from "./@types/libp2p"; import { bytes } from "./@types/basic"; -export const logger = debug('libp2p:noise'); - export async function loadPayloadProto () { const payloadProtoBuf = await protobuf.load("protos/payload.proto"); return payloadProtoBuf.lookupType("pb.NoiseHandshakePayload"); From 013de0c3cdf55fecd279beee799dc690d3d2d851 Mon Sep 17 00:00:00 2001 From: morrigan Date: Tue, 3 Dec 2019 15:22:24 +0100 Subject: [PATCH 10/12] Update readme --- README.md | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 519c887..8a1835e 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,21 @@ This repository contains TypeScript implementation of noise protocol, an encrypt ## Usage -TBD +TBD (package not yet published) ## API -TBD +This module exposes a crypto interface, as defined in the repository [js-interfaces](https://github.com/libp2p/js-interfaces). + +[ยป API Docs](https://github.com/libp2p/js-interfaces/tree/master/src/crypto#api) + + +## Contribute + +Feel free to join in. All welcome. Open an issue! + +[![](https://cdn.rawgit.com/jbenet/contribute-ipfs-gif/master/img/contribute.gif)](https://github.com/ipfs/community/blob/master/contributing.md) + +## License + +[MIT](LICENSE) From 8e036d9e5224531cb48b6e0a61ecac781d65938d Mon Sep 17 00:00:00 2001 From: morrigan Date: Tue, 3 Dec 2019 16:09:46 +0100 Subject: [PATCH 11/12] Update package.json with keywords --- package.json | 39 ++++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/package.json b/package.json index 77a9cfc..2babf57 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,11 @@ "repository": "git@github.com:NodeFactoryIo/js-libp2p-noise.git", "author": "NodeFactory ", "license": "MIT", + "keywords": [ + "libp2p", + "noise", + "crypto" + ], "scripts": { "prebuild": "rm -rf lib", "build": "babel src -x .ts -d lib --source-maps", @@ -13,6 +18,23 @@ "pretest": "yarn check-types", "test": "DEBUG=libp2p:noise mocha -r ./babel-register.js \"test/**/*.test.ts\"" }, + "babel": { + "presets": [ + [ + "@babel/preset-env", + { + "targets": { + "node": "12" + } + } + ], + "@babel/preset-typescript" + ], + "plugins": [ + "@babel/plugin-proposal-object-rest-spread", + "@babel/plugin-proposal-async-generator-functions" + ] + }, "devDependencies": { "@babel/cli": "^7.6.4", "@babel/core": "^7.6.4", @@ -32,23 +54,6 @@ "mocha": "^6.2.2", "typescript": "^3.6.4" }, - "babel": { - "presets": [ - [ - "@babel/preset-env", - { - "targets": { - "node": "12" - } - } - ], - "@babel/preset-typescript" - ], - "plugins": [ - "@babel/plugin-proposal-object-rest-spread", - "@babel/plugin-proposal-async-generator-functions" - ] - }, "dependencies": { "bcrypto": "^4.2.3", "bn.js": "^5.0.0", From db3f0acb6fd4c876ec8063da766617c456e5e204 Mon Sep 17 00:00:00 2001 From: morrigan Date: Tue, 3 Dec 2019 18:22:05 +0100 Subject: [PATCH 12/12] Update readme --- README.md | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8a1835e..b7d6a7e 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,10 @@ [![](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](https://libp2p.io/) ![](https://img.shields.io/github/issues-raw/nodefactoryio/js-libp2p-noise) ![](https://img.shields.io/github/license/nodefactoryio/js-libp2p-noise) +[![Build Status](https://travis-ci.com/NodeFactoryIo/js-libp2p-noise.svg?branch=master)](https://travis-ci.com/NodeFactoryIo/js-libp2p-noise) +![](https://img.shields.io/badge/yarn-%3E%3D1.17.0-orange.svg?style=flat-square) +![](https://img.shields.io/badge/Node.js-%3E%3D12.4.0-orange.svg?style=flat-square) +[![Discourse posts](https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg)](https://discuss.libp2p.io) > Noise libp2p handshake for js-libp2p @@ -10,7 +14,25 @@ This repository contains TypeScript implementation of noise protocol, an encrypt ## Usage -TBD (package not yet published) +When published, package should be imported as: `import { Noise } from 'libp2p-noise'`. + +Example of instantiating noise and passing it to the libp2p config: +``` +const NOISE = new Noise(privateKey); + +const libp2p = new Libp2p({ + modules: { + connEncryption: [NOISE], + }, +}); +``` + +Where parameters for Noise constructor are: + - *private key* - required parameter (32 bytes libp2p peer private key) + - *static Noise key* - (optional) existing private Noise static key + - *early data* - (optional) an early data payload to be sent in handshake messages + + ## API