This commit is contained in:
Marin Petrunić 2019-11-09 15:46:13 +01:00
parent 94a990a479
commit 6d7be49f55
No known key found for this signature in database
GPG Key ID: 834D07135E110DA5

110
src/xx.ts
View File

@ -4,54 +4,54 @@ import { AEAD, x25519, HKDF, SHA256 } from 'bcrypto';
import { BN } from 'bn.js'; import { BN } from 'bn.js';
export interface KeyPair { export interface KeyPair {
publicKey: bytes32, publicKey: bytes32;
privateKey: bytes32, privateKey: bytes32;
} }
interface MessageBuffer { interface MessageBuffer {
ne: bytes32, ne: bytes32;
ns: bytes, ns: bytes;
ciphertext: bytes ciphertext: bytes;
} }
type CipherState = { type CipherState = {
k: bytes32, k: bytes32;
n: uint32, n: uint32;
} }
type SymmetricState = { type SymmetricState = {
cs: CipherState, cs: CipherState;
ck: bytes32, // chaining key ck: bytes32; // chaining key
h: bytes32, // handshake hash h: bytes32; // handshake hash
} }
type HandshakeState = { type HandshakeState = {
ss: SymmetricState, ss: SymmetricState;
s: KeyPair, s: KeyPair;
e?: KeyPair, e?: KeyPair;
rs: bytes32, rs: bytes32;
re: bytes32, re: bytes32;
psk: bytes32, psk: bytes32;
} }
type NoiseSession = { type NoiseSession = {
hs: HandshakeState, hs: HandshakeState;
h?: bytes32, h?: bytes32;
cs1?: CipherState, cs1?: CipherState;
cs2?: CipherState, cs2?: CipherState;
mc: uint64, mc: uint64;
i: boolean, i: boolean;
} }
export type Hkdf = [bytes, bytes, bytes]; export type Hkdf = [bytes, bytes, bytes];
const minNonce = 0; const minNonce = 0;
export class XXHandshake { export class XXHandshake {
private createEmptyKey() : bytes32 { private createEmptyKey(): bytes32 {
return Buffer.alloc(32); return Buffer.alloc(32);
} }
private async initializeInitiator(prologue: bytes32, s: KeyPair, rs: bytes32, psk: bytes32) : Promise<HandshakeState> { private async initializeInitiator(prologue: bytes32, s: KeyPair, rs: bytes32, psk: bytes32): Promise<HandshakeState> {
const name = "Noise_XX_25519_ChaChaPoly_SHA256"; const name = "Noise_XX_25519_ChaChaPoly_SHA256";
const ss = await this.initializeSymmetric(name); const ss = await this.initializeSymmetric(name);
this.mixHash(ss, prologue); this.mixHash(ss, prologue);
@ -60,7 +60,7 @@ export class XXHandshake {
return { ss, s, rs, psk, re }; return { ss, s, rs, psk, re };
} }
private async initializeResponder(prologue: bytes32, s: KeyPair, rs: bytes32, psk: bytes32) : Promise<HandshakeState> { private async initializeResponder(prologue: bytes32, s: KeyPair, rs: bytes32, psk: bytes32): Promise<HandshakeState> {
const name = "Noise_XX_25519_ChaChaPoly_SHA256"; const name = "Noise_XX_25519_ChaChaPoly_SHA256";
const ss = await this.initializeSymmetric(name); const ss = await this.initializeSymmetric(name);
this.mixHash(ss, prologue); this.mixHash(ss, prologue);
@ -69,25 +69,25 @@ export class XXHandshake {
return { ss, s, rs, psk, re }; return { ss, s, rs, psk, re };
} }
private incrementNonce(n: uint32) : uint32 { private incrementNonce(n: uint32): uint32 {
return n + 1; return n + 1;
} }
private dh(privateKey: bytes32, publicKey: bytes32) : bytes32 { private dh(privateKey: bytes32, publicKey: bytes32): bytes32 {
const derived = x25519.derive(publicKey, privateKey); const derived = x25519.derive(publicKey, privateKey);
const result = Buffer.alloc(32); const result = Buffer.alloc(32);
derived.copy(result); derived.copy(result);
return result; return result;
} }
private nonceToBytes(n: uint32) : bytes { private nonceToBytes(n: uint32): bytes {
const nonce = Buffer.alloc(12); const nonce = Buffer.alloc(12);
nonce.writeUInt32LE(n, 4); nonce.writeUInt32LE(n, 4);
return nonce; return nonce;
} }
private encrypt(k: bytes32, n: uint32, ad: bytes, plaintext: bytes) : bytes { private encrypt(k: bytes32, n: uint32, ad: bytes, plaintext: bytes): bytes {
const nonce = this.nonceToBytes(n); const nonce = this.nonceToBytes(n);
const ctx = new AEAD(); const ctx = new AEAD();
@ -99,7 +99,7 @@ export class XXHandshake {
return plaintext; return plaintext;
} }
private decrypt(k: bytes32, n: uint32, ad: bytes, ciphertext: bytes) : bytes { private decrypt(k: bytes32, n: uint32, ad: bytes, ciphertext: bytes): bytes {
const nonce = this.nonceToBytes(n); const nonce = this.nonceToBytes(n);
const ctx = new AEAD(); const ctx = new AEAD();
@ -111,33 +111,33 @@ export class XXHandshake {
return ciphertext; return ciphertext;
} }
private isEmptyKey(k: bytes32) : boolean { private isEmptyKey(k: bytes32): boolean {
const emptyKey = this.createEmptyKey(); const emptyKey = this.createEmptyKey();
return emptyKey.equals(k); return emptyKey.equals(k);
} }
// Cipher state related // Cipher state related
private initializeKey(k: bytes32) : CipherState { private initializeKey(k: bytes32): CipherState {
const n = minNonce; const n = minNonce;
return { k, n }; return { k, n };
} }
private hasKey(cs: CipherState) : boolean { private hasKey(cs: CipherState): boolean {
return !this.isEmptyKey(cs.k); return !this.isEmptyKey(cs.k);
} }
private setNonce(cs: CipherState, nonce: uint32) : void { private setNonce(cs: CipherState, nonce: uint32): void {
cs.n = nonce; cs.n = nonce;
} }
public encryptWithAd(cs: CipherState, ad: bytes, plaintext: bytes) : bytes { public encryptWithAd(cs: CipherState, ad: bytes, plaintext: bytes): bytes {
const e = this.encrypt(cs.k, cs.n, ad, plaintext); const e = this.encrypt(cs.k, cs.n, ad, plaintext);
this.setNonce(cs, this.incrementNonce(cs.n)); this.setNonce(cs, this.incrementNonce(cs.n));
return e; return e;
} }
public decryptWithAd(cs: CipherState, ad: bytes, ciphertext: bytes) : bytes { public decryptWithAd(cs: CipherState, ad: bytes, ciphertext: bytes): bytes {
const plaintext = this.decrypt(cs.k, cs.n, ad, ciphertext); const plaintext = this.decrypt(cs.k, cs.n, ad, ciphertext);
this.setNonce(cs, this.incrementNonce(cs.n)); this.setNonce(cs, this.incrementNonce(cs.n));
@ -146,13 +146,13 @@ export class XXHandshake {
// Symmetric state related // Symmetric state related
private async initializeSymmetric(protocolName: string) : Promise<SymmetricState> { private async initializeSymmetric(protocolName: string): Promise<SymmetricState> {
const protocolNameBytes: bytes = Buffer.from(protocolName, 'utf-8'); const protocolNameBytes: bytes = Buffer.from(protocolName, 'utf-8');
const h = await this.hashProtocolName(protocolNameBytes); const h = await this.hashProtocolName(protocolNameBytes);
const ck = h; const ck = h;
const key = this.createEmptyKey(); const key = this.createEmptyKey();
const cs:CipherState = this.initializeKey(key); const cs: CipherState = this.initializeKey(key);
return { cs, ck, h }; return { cs, ck, h };
} }
@ -163,9 +163,9 @@ export class XXHandshake {
ss.ck = ck; ss.ck = ck;
} }
private async hashProtocolName(protocolName: bytes) : Promise<bytes32> { private async hashProtocolName(protocolName: bytes): Promise<bytes32> {
if (protocolName.length <= 32) { if (protocolName.length <= 32) {
let h = Buffer.alloc(32); const h = Buffer.alloc(32);
protocolName.copy(h); protocolName.copy(h);
return h; return h;
} else { } else {
@ -173,7 +173,7 @@ export class XXHandshake {
} }
} }
public getHkdf(ck: bytes32, ikm: bytes) : Hkdf { public getHkdf(ck: bytes32, ikm: bytes): Hkdf {
const info = Buffer.alloc(0); const info = Buffer.alloc(0);
const prk = HKDF.extract(SHA256, ikm, ck); const prk = HKDF.extract(SHA256, ikm, ck);
const okm = HKDF.expand(SHA256, prk, info, 96); const okm = HKDF.expand(SHA256, prk, info, 96);
@ -189,11 +189,11 @@ export class XXHandshake {
ss.h = this.getHash(ss.h, data); ss.h = this.getHash(ss.h, data);
} }
private getHash(a: bytes, b: bytes) : bytes32 { private getHash(a: bytes, b: bytes): bytes32 {
return SHA256.digest(Buffer.from([...a, ...b])); return SHA256.digest(Buffer.from([...a, ...b]));
} }
private async encryptAndHash(ss: SymmetricState, plaintext: bytes) : Promise<bytes> { private async encryptAndHash(ss: SymmetricState, plaintext: bytes): Promise<bytes> {
let ciphertext; let ciphertext;
if (this.hasKey(ss.cs)) { if (this.hasKey(ss.cs)) {
ciphertext = this.encryptWithAd(ss.cs, ss.h, plaintext); ciphertext = this.encryptWithAd(ss.cs, ss.h, plaintext);
@ -205,7 +205,7 @@ export class XXHandshake {
return ciphertext; return ciphertext;
} }
private async decryptAndHash(ss: SymmetricState, ciphertext: bytes) : Promise<bytes> { private async decryptAndHash(ss: SymmetricState, ciphertext: bytes): Promise<bytes> {
let plaintext; let plaintext;
if (this.hasKey(ss.cs)) { if (this.hasKey(ss.cs)) {
plaintext = this.decryptWithAd(ss.cs, ss.h, ciphertext); plaintext = this.decryptWithAd(ss.cs, ss.h, ciphertext);
@ -225,8 +225,8 @@ export class XXHandshake {
return { cs1, cs2 }; return { cs1, cs2 };
} }
private async writeMessageA(hs: HandshakeState, payload: bytes) : Promise<MessageBuffer> { private async writeMessageA(hs: HandshakeState, payload: bytes): Promise<MessageBuffer> {
let ns = Buffer.alloc(0); const ns = Buffer.alloc(0);
hs.e = await this.generateKeypair(); hs.e = await this.generateKeypair();
const ne = hs.e.publicKey; const ne = hs.e.publicKey;
@ -236,7 +236,7 @@ export class XXHandshake {
return {ne, ns, ciphertext}; return {ne, ns, ciphertext};
} }
private async writeMessageB(hs: HandshakeState, payload: bytes) : Promise<MessageBuffer> { private async writeMessageB(hs: HandshakeState, payload: bytes): Promise<MessageBuffer> {
hs.e = await this.generateKeypair(); hs.e = await this.generateKeypair();
const ne = hs.e.publicKey; const ne = hs.e.publicKey;
this.mixHash(hs.ss, ne); this.mixHash(hs.ss, ne);
@ -263,7 +263,7 @@ export class XXHandshake {
return { h: hs.ss.h, messageBuffer, cs1, cs2 }; return { h: hs.ss.h, messageBuffer, cs1, cs2 };
} }
private async writeMessageRegular(cs: CipherState, payload: bytes) : Promise<MessageBuffer> { private async writeMessageRegular(cs: CipherState, payload: bytes): Promise<MessageBuffer> {
const ciphertext = this.encryptWithAd(cs, Buffer.alloc(0), payload); const ciphertext = this.encryptWithAd(cs, Buffer.alloc(0), payload);
const ne = this.createEmptyKey(); const ne = this.createEmptyKey();
const ns = Buffer.alloc(0); const ns = Buffer.alloc(0);
@ -271,7 +271,7 @@ export class XXHandshake {
return { ne, ns, ciphertext }; return { ne, ns, ciphertext };
} }
private async readMessageA(hs: HandshakeState, message: MessageBuffer) : Promise<bytes> { private async readMessageA(hs: HandshakeState, message: MessageBuffer): Promise<bytes> {
if (x25519.publicKeyVerify(message.ne)) { if (x25519.publicKeyVerify(message.ne)) {
hs.re = message.ne; hs.re = message.ne;
} }
@ -280,7 +280,7 @@ export class XXHandshake {
return await this.decryptAndHash(hs.ss, message.ciphertext); return await this.decryptAndHash(hs.ss, message.ciphertext);
} }
private async readMessageB(hs: HandshakeState, message: MessageBuffer) : Promise<bytes> { private async readMessageB(hs: HandshakeState, message: MessageBuffer): Promise<bytes> {
if (x25519.publicKeyVerify(message.ne)) { if (x25519.publicKeyVerify(message.ne)) {
hs.re = message.ne; hs.re = message.ne;
} }
@ -314,11 +314,11 @@ export class XXHandshake {
return { h: hs.ss.h, plaintext, cs1, cs2 }; return { h: hs.ss.h, plaintext, cs1, cs2 };
} }
private readMessageRegular(cs: CipherState, message: MessageBuffer) : bytes { private readMessageRegular(cs: CipherState, message: MessageBuffer): bytes {
return this.decryptWithAd(cs, Buffer.alloc(0), message.ciphertext); return this.decryptWithAd(cs, Buffer.alloc(0), message.ciphertext);
} }
public async generateKeypair() : Promise<KeyPair> { public async generateKeypair(): Promise<KeyPair> {
const privateKey = x25519.privateKeyGenerate(); const privateKey = x25519.privateKeyGenerate();
const publicKey = x25519.publicKeyCreate(privateKey); const publicKey = x25519.publicKeyCreate(privateKey);
@ -328,7 +328,7 @@ export class XXHandshake {
} }
} }
public async initSession(initiator: boolean, prologue: bytes32, s: KeyPair, rs: bytes32) : Promise<NoiseSession> { public async initSession(initiator: boolean, prologue: bytes32, s: KeyPair, rs: bytes32): Promise<NoiseSession> {
const psk = this.createEmptyKey(); const psk = this.createEmptyKey();
let hs; let hs;
@ -345,7 +345,7 @@ export class XXHandshake {
}; };
} }
public async sendMessage(session: NoiseSession, message: bytes) : Promise<MessageBuffer> { public async sendMessage(session: NoiseSession, message: bytes): Promise<MessageBuffer> {
let messageBuffer: MessageBuffer; let messageBuffer: MessageBuffer;
if (session.mc.eqn(0)) { if (session.mc.eqn(0)) {
messageBuffer = await this.writeMessageA(session.hs, message); messageBuffer = await this.writeMessageA(session.hs, message);
@ -379,7 +379,7 @@ export class XXHandshake {
return messageBuffer; return messageBuffer;
} }
public async recvMessage(session: NoiseSession, message: MessageBuffer) : Promise<bytes> { public async recvMessage(session: NoiseSession, message: MessageBuffer): Promise<bytes> {
let plaintext: bytes; let plaintext: bytes;
if (session.mc.eqn(0)) { if (session.mc.eqn(0)) {
plaintext = await this.readMessageA(session.hs, message); plaintext = await this.readMessageA(session.hs, message);