Finish porting IK handshake

This commit is contained in:
Belma Gutlic 2019-12-25 18:32:19 +01:00
parent 7b9118e15b
commit b98c5b4513
3 changed files with 162 additions and 15 deletions

View File

@ -2,7 +2,7 @@ import {Buffer} from "buffer";
import { AEAD, x25519, HKDF, SHA256 } from 'bcrypto'; import { AEAD, x25519, HKDF, SHA256 } from 'bcrypto';
import {bytes, bytes32, uint32} from "../@types/basic"; import {bytes, bytes32, uint32} from "../@types/basic";
import {CipherState, SymmetricState} from "../@types/handshake"; import {CipherState, MessageBuffer, SymmetricState} from "../@types/handshake";
import {getHkdf} from "../utils"; import {getHkdf} from "../utils";
export class AbstractHandshake { 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 [ tempk1, tempk2 ] = getHkdf(ss.ck, Buffer.alloc(0));
const cs1 = this.initializeKey(tempk1); const cs1 = this.initializeKey(tempk1);
const cs2 = this.initializeKey(tempk2); const cs2 = this.initializeKey(tempk2);
return { cs1, cs2 }; 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);
}
} }

View File

@ -1,12 +1,94 @@
import {Buffer} from "buffer"; 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 {bytes, bytes32} from "../@types/basic";
import {generateKeypair, getHkdf} from "../utils"; import {generateKeypair, getHkdf} from "../utils";
import {AbstractHandshake} from "./abstract-handshake"; import {AbstractHandshake} from "./abstract-handshake";
import {KeyPair} from "../@types/libp2p";
import {BN} from "bn.js";
export class IKHandshake extends AbstractHandshake { 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 { private writeMessageA(hs: HandshakeState, payload: bytes): MessageBuffer {
hs.e = generateKeypair(); hs.e = generateKeypair();
const ne = hs.e.publicKey; const ne = hs.e.publicKey;
@ -21,5 +103,70 @@ export class IKHandshake extends AbstractHandshake {
return { ne, ns, ciphertext }; 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 };
}
} }

View File

@ -67,14 +67,6 @@ export class XXHandshake extends AbstractHandshake {
return { h: hs.ss.h, messageBuffer, cs1, cs2 }; 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 { private readMessageA(hs: HandshakeState, message: MessageBuffer): bytes {
if (x25519.publicKeyVerify(message.ne)) { if (x25519.publicKeyVerify(message.ne)) {
hs.re = message.ne; hs.re = message.ne;
@ -119,10 +111,6 @@ export class XXHandshake extends AbstractHandshake {
return { h: hs.ss.h, plaintext, cs1, cs2 }; 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 { public initSession(initiator: boolean, prologue: bytes32, s: KeyPair): NoiseSession {
const psk = this.createEmptyKey(); const psk = this.createEmptyKey();
const rs = Buffer.alloc(32); // no static key yet const rs = Buffer.alloc(32); // no static key yet