From b98c5b4513978992d7eafae33a3302a51c9aa68e Mon Sep 17 00:00:00 2001 From: Belma Gutlic Date: Wed, 25 Dec 2019 18:32:19 +0100 Subject: [PATCH] Finish porting IK handshake --- src/handshakes/abstract-handshake.ts | 16 ++- src/handshakes/ik.ts | 149 ++++++++++++++++++++++++++- src/handshakes/xx.ts | 12 --- 3 files changed, 162 insertions(+), 15 deletions(-) diff --git a/src/handshakes/abstract-handshake.ts b/src/handshakes/abstract-handshake.ts index 8b99f74..c3fb26f 100644 --- a/src/handshakes/abstract-handshake.ts +++ b/src/handshakes/abstract-handshake.ts @@ -2,7 +2,7 @@ import {Buffer} from "buffer"; import { AEAD, x25519, HKDF, SHA256 } from 'bcrypto'; import {bytes, bytes32, uint32} from "../@types/basic"; -import {CipherState, SymmetricState} from "../@types/handshake"; +import {CipherState, MessageBuffer, SymmetricState} from "../@types/handshake"; import {getHkdf} from "../utils"; export class AbstractHandshake { @@ -149,11 +149,23 @@ export class AbstractHandshake { } } - protected split (ss: SymmetricState) { + protected split(ss: SymmetricState) { const [ tempk1, tempk2 ] = getHkdf(ss.ck, Buffer.alloc(0)); const cs1 = this.initializeKey(tempk1); const cs2 = this.initializeKey(tempk2); return { cs1, cs2 }; } + + protected writeMessageRegular(cs: CipherState, payload: bytes): MessageBuffer { + const ciphertext = this.encryptWithAd(cs, Buffer.alloc(0), payload); + const ne = this.createEmptyKey(); + const ns = Buffer.alloc(0); + + return { ne, ns, ciphertext }; + } + + protected readMessageRegular(cs: CipherState, message: MessageBuffer): bytes { + return this.decryptWithAd(cs, Buffer.alloc(0), message.ciphertext); + } } diff --git a/src/handshakes/ik.ts b/src/handshakes/ik.ts index bf46def..2f53bf1 100644 --- a/src/handshakes/ik.ts +++ b/src/handshakes/ik.ts @@ -1,12 +1,94 @@ import {Buffer} from "buffer"; +import {x25519} from "bcrypto"; -import {CipherState, HandshakeState, MessageBuffer, SymmetricState} from "../@types/handshake"; +import {CipherState, HandshakeState, MessageBuffer, NoiseSession, SymmetricState} from "../@types/handshake"; import {bytes, bytes32} from "../@types/basic"; import {generateKeypair, getHkdf} from "../utils"; import {AbstractHandshake} from "./abstract-handshake"; +import {KeyPair} from "../@types/libp2p"; +import {BN} from "bn.js"; export class IKHandshake extends AbstractHandshake { + public initSession(initiator: boolean, prologue: bytes32, s: KeyPair, rs: bytes32): NoiseSession { + const psk = this.createEmptyKey(); + + let hs; + if (initiator) { + hs = this.initializeInitiator(prologue, s, rs, psk); + } else { + hs = this.initializeResponder(prologue, s, rs, psk); + } + + return { + hs, + i: initiator, + mc: new BN(0), + }; + } + + public sendMessage(session: NoiseSession, message: bytes): MessageBuffer { + let messageBuffer: MessageBuffer; + if (session.mc.eqn(0)) { + messageBuffer = this.writeMessageA(session.hs, message); + } else if (session.mc.eqn(1)) { + const { messageBuffer: mb, h, cs1, cs2 } = this.writeMessageB(session.hs, message); + messageBuffer = mb; + session.h = h; + session.cs1 = cs1; + session.cs2 = cs2; + } else if (session.mc.gtn(1)) { + if (session.i) { + if (!session.cs1) { + throw new Error("CS1 (cipher state) is not defined") + } + + messageBuffer = this.writeMessageRegular(session.cs1, message); + } else { + if (!session.cs2) { + throw new Error("CS2 (cipher state) is not defined") + } + + messageBuffer = this.writeMessageRegular(session.cs2, message); + } + } else { + throw new Error("Session invalid.") + } + + session.mc = session.mc.add(new BN(1)); + return messageBuffer; + } + + public recvMessage(session: NoiseSession, message: MessageBuffer): bytes { + let plaintext: bytes; + if (session.mc.eqn(0)) { + plaintext = this.readMessageA(session.hs, message); + } else if (session.mc.eqn(1)) { + const { plaintext: pt, h, cs1, cs2 } = this.readMessageB(session.hs, message); + plaintext = pt; + session.h = h; + session.cs1 = cs1; + session.cs2 = cs2; + } else if (session.mc.gtn(1)) { + if (session.i) { + if (!session.cs2) { + throw new Error("CS1 (cipher state) is not defined") + } + plaintext = this.readMessageRegular(session.cs2, message); + } else { + if (!session.cs1) { + throw new Error("CS1 (cipher state) is not defined") + } + plaintext = this.readMessageRegular(session.cs1, message); + } + } else { + throw new Error("Session invalid."); + } + + session.mc = session.mc.add(new BN(1)); + return plaintext; + } + private writeMessageA(hs: HandshakeState, payload: bytes): MessageBuffer { hs.e = generateKeypair(); const ne = hs.e.publicKey; @@ -21,5 +103,70 @@ export class IKHandshake extends AbstractHandshake { return { ne, ns, ciphertext }; } + private writeMessageB(hs: HandshakeState, payload: bytes) { + hs.e = generateKeypair(); + const ne = hs.e.publicKey; + this.mixHash(hs.ss, ne); + this.mixKey(hs.ss, this.dh(hs.e.privateKey, hs.re)); + this.mixKey(hs.ss, this.dh(hs.e.privateKey, hs.rs)); + const ciphertext = this.encryptAndHash(hs.ss, payload); + const ns = this.createEmptyKey(); + const messageBuffer: MessageBuffer = {ne, ns, ciphertext}; + const { cs1, cs2 } = this.split(hs.ss); + + return { messageBuffer, cs1, cs2, h: hs.ss.h } + } + + private readMessageA(hs: HandshakeState, message: MessageBuffer): bytes { + if (x25519.publicKeyVerify(message.ne)) { + hs.re = message.ne; + } + + this.mixHash(hs.ss, hs.re); + this.mixKey(hs.ss, this.dh(hs.s.privateKey, hs.re)); + 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.s.privateKey, hs.rs)); + return this.decryptAndHash(hs.ss, message.ciphertext); + } + + private readMessageB(hs: HandshakeState, message: MessageBuffer) { + if (x25519.publicKeyVerify(message.ne)) { + hs.re = message.ne; + } + + this.mixHash(hs.ss, hs.re); + if (!hs.e) { + throw new Error("Handshake state should contain ephemeral key by now."); + } + this.mixKey(hs.ss, this.dh(hs.e.privateKey, hs.re)); + this.mixKey(hs.ss, this.dh(hs.s.privateKey, hs.re)); + const plaintext = this.decryptAndHash(hs.ss, message.ciphertext); + const { cs1, cs2 } = this.split(hs.ss); + + return { h: hs.ss.h, plaintext, cs1, cs2 }; + } + + private initializeInitiator(prologue: bytes32, s: KeyPair, rs: bytes32, psk: bytes32): HandshakeState { + const name = "Noise_IK_25519_ChaChaPoly_SHA256"; + const ss = this.initializeSymmetric(name); + this.mixHash(ss, prologue); + this.mixHash(ss, rs); + const re = Buffer.alloc(32); + + return { ss, s, rs, re, psk }; + } + + private initializeResponder(prologue: bytes32, s: KeyPair, rs: bytes32, psk: bytes32): HandshakeState { + const name = "Noise_IK_25519_ChaChaPoly_SHA256"; + const ss = this.initializeSymmetric(name); + this.mixHash(ss, prologue); + this.mixHash(ss, s.publicKey); + const re = Buffer.alloc(32); + + return { ss, s, rs, re, psk }; + } } diff --git a/src/handshakes/xx.ts b/src/handshakes/xx.ts index 6e95ccc..3bee635 100644 --- a/src/handshakes/xx.ts +++ b/src/handshakes/xx.ts @@ -67,14 +67,6 @@ export class XXHandshake extends AbstractHandshake { return { h: hs.ss.h, messageBuffer, cs1, cs2 }; } - 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); - - return { ne, ns, ciphertext }; - } - private readMessageA(hs: HandshakeState, message: MessageBuffer): bytes { if (x25519.publicKeyVerify(message.ne)) { hs.re = message.ne; @@ -119,10 +111,6 @@ export class XXHandshake extends AbstractHandshake { return { h: hs.ss.h, plaintext, cs1, cs2 }; } - private readMessageRegular(cs: CipherState, message: MessageBuffer): bytes { - return this.decryptWithAd(cs, Buffer.alloc(0), message.ciphertext); - } - public initSession(initiator: boolean, prologue: bytes32, s: KeyPair): NoiseSession { const psk = this.createEmptyKey(); const rs = Buffer.alloc(32); // no static key yet