2019-10-31 17:35:18 +01:00
|
|
|
import {bytes32, bytes16, uint32, uint64, bytes} from './types/basic'
|
|
|
|
import { Buffer } from 'buffer';
|
|
|
|
import * as crypto from 'libp2p-crypto';
|
2019-11-04 17:06:31 +01:00
|
|
|
import AEAD from 'bcrypto';
|
2019-10-31 17:35:18 +01:00
|
|
|
|
2019-11-04 17:06:31 +01:00
|
|
|
export interface KeyPair {
|
2019-10-31 17:35:18 +01:00
|
|
|
publicKey: bytes32,
|
|
|
|
privateKey: bytes32,
|
|
|
|
}
|
|
|
|
|
|
|
|
type CipherState = {
|
|
|
|
k: bytes32,
|
|
|
|
n: uint32,
|
|
|
|
}
|
|
|
|
|
|
|
|
type SymmetricState = {
|
|
|
|
cs: CipherState,
|
|
|
|
ck: bytes32,
|
|
|
|
h: bytes32,
|
|
|
|
}
|
|
|
|
|
|
|
|
type HandshakeState = {
|
|
|
|
ss: SymmetricState,
|
|
|
|
s: KeyPair,
|
|
|
|
e: KeyPair,
|
|
|
|
rs: bytes32,
|
|
|
|
re: bytes32,
|
|
|
|
psk: bytes32,
|
|
|
|
}
|
|
|
|
|
|
|
|
type NoiseSession = {
|
|
|
|
hs: HandshakeState,
|
|
|
|
h: bytes32,
|
|
|
|
cs1: CipherState,
|
|
|
|
c2: CipherState,
|
|
|
|
mc: uint64,
|
|
|
|
i: boolean,
|
|
|
|
}
|
|
|
|
|
|
|
|
const minNonce = 0;
|
|
|
|
|
2019-11-04 15:19:40 +01:00
|
|
|
export class XXHandshake {
|
2019-11-04 15:10:14 +01:00
|
|
|
private createEmptyKey() : bytes32 {
|
|
|
|
return Buffer.alloc(32);
|
|
|
|
}
|
|
|
|
|
2019-11-04 14:31:58 +01:00
|
|
|
private async initializeInitiator(prologue: bytes32, s: KeyPair, rs: bytes32, psk: bytes32) : Promise<HandshakeState> {
|
2019-11-04 15:10:14 +01:00
|
|
|
let e: KeyPair;
|
|
|
|
let re: bytes32;
|
2019-10-31 17:35:18 +01:00
|
|
|
const name = "Noise_XX_25519_ChaChaPoly_SHA256";
|
|
|
|
const ss = await this.initializeSymmetric(name);
|
|
|
|
await this.mixHash(ss, prologue);
|
|
|
|
|
2019-11-04 17:06:31 +01:00
|
|
|
// @ts-ignore-next-line
|
2019-10-31 17:35:18 +01:00
|
|
|
return {ss, s, e, rs, re, psk};
|
|
|
|
}
|
|
|
|
|
2019-11-04 14:38:01 +01:00
|
|
|
private async initializeResponder(prologue: bytes32, s: KeyPair, rs: bytes32, psk: bytes32) : Promise<HandshakeState> {
|
2019-11-04 15:10:14 +01:00
|
|
|
let e: KeyPair;
|
|
|
|
let re: bytes32;
|
2019-10-31 17:35:18 +01:00
|
|
|
const name = "Noise_XX_25519_ChaChaPoly_SHA256";
|
|
|
|
const ss = await this.initializeSymmetric(name);
|
|
|
|
await this.mixHash(ss, prologue);
|
|
|
|
|
2019-11-04 17:06:31 +01:00
|
|
|
// @ts-ignore-next-line
|
2019-10-31 17:35:18 +01:00
|
|
|
return {ss, s, e, rs, re, psk};
|
|
|
|
}
|
|
|
|
|
2019-11-04 14:38:01 +01:00
|
|
|
private incrementNonce(n: uint32) : uint32 {
|
2019-11-01 11:30:28 +01:00
|
|
|
return n + 1;
|
|
|
|
}
|
|
|
|
|
2019-11-04 15:19:40 +01:00
|
|
|
private convertNonce(n: uint32) : bytes {
|
2019-11-04 14:31:58 +01:00
|
|
|
const nonce = Buffer.alloc(12);
|
|
|
|
nonce.writeUInt32LE(n, 4);
|
2019-11-04 15:19:40 +01:00
|
|
|
|
|
|
|
return nonce;
|
|
|
|
}
|
|
|
|
|
|
|
|
private encrypt(k: bytes32, n: uint32, ad: bytes, plaintext: bytes) : bytes {
|
|
|
|
const nonce = this.convertNonce(n);
|
2019-11-04 14:31:58 +01:00
|
|
|
const ctx = new AEAD();
|
|
|
|
ctx.init(k, nonce);
|
|
|
|
ctx.aad(ad);
|
|
|
|
ctx.encrypt(plaintext);
|
|
|
|
|
|
|
|
return ctx.final();
|
2019-11-01 11:30:28 +01:00
|
|
|
}
|
|
|
|
|
2019-11-04 15:19:40 +01:00
|
|
|
private decrypt(k: bytes32, n: uint32, ad: bytes, ciphertext: bytes) : bytes {
|
|
|
|
const nonce = this.convertNonce(n);
|
|
|
|
const ctx = new AEAD();
|
|
|
|
|
|
|
|
ctx.init(k, nonce);
|
|
|
|
ctx.aad(ad);
|
|
|
|
ctx.decrypt(ciphertext);
|
|
|
|
|
|
|
|
return ctx.final();
|
|
|
|
}
|
|
|
|
|
2019-11-01 11:30:28 +01:00
|
|
|
// Cipher state related
|
2019-11-04 14:38:01 +01:00
|
|
|
private initializeKey(k: bytes32) : CipherState {
|
2019-10-31 17:35:18 +01:00
|
|
|
const n = minNonce;
|
|
|
|
return { k, n };
|
|
|
|
}
|
|
|
|
|
2019-11-04 14:38:01 +01:00
|
|
|
private setNonce(cs: CipherState, nonce: uint32) {
|
2019-11-01 11:30:28 +01:00
|
|
|
cs.n = nonce;
|
|
|
|
}
|
|
|
|
|
2019-11-04 14:38:01 +01:00
|
|
|
private encryptWithAd(cs: CipherState, ad: bytes, plaintext: bytes) : bytes {
|
2019-11-01 11:30:28 +01:00
|
|
|
const e = this.encrypt(cs.k, cs.n, ad, plaintext);
|
|
|
|
this.setNonce(cs, this.incrementNonce(cs.n));
|
|
|
|
return e;
|
|
|
|
}
|
|
|
|
|
2019-11-04 15:19:40 +01:00
|
|
|
private decryptWithAd(cs: CipherState, ad: bytes, ciphertext: bytes) : bytes {
|
|
|
|
const plaintext = this.decrypt(cs.k, cs.n, ad, ciphertext);
|
|
|
|
this.setNonce(cs, this.incrementNonce(cs.n));
|
|
|
|
|
|
|
|
return plaintext;
|
|
|
|
}
|
|
|
|
|
2019-11-01 11:30:28 +01:00
|
|
|
// Symmetric state related
|
|
|
|
|
2019-11-04 14:38:01 +01:00
|
|
|
private async initializeSymmetric(protocolName: string) : Promise<SymmetricState> {
|
2019-11-04 15:10:14 +01:00
|
|
|
const protocolNameBytes: bytes = Buffer.from(protocolName, 'utf-8');
|
|
|
|
const h = await this.hashProtocolName(protocolNameBytes);
|
2019-10-31 17:35:18 +01:00
|
|
|
const ck = h;
|
2019-11-04 15:10:14 +01:00
|
|
|
const key = this.createEmptyKey();
|
|
|
|
const cs = this.initializeKey(key);
|
2019-10-31 17:35:18 +01:00
|
|
|
|
|
|
|
return { cs, ck, h };
|
|
|
|
}
|
|
|
|
|
2019-11-04 15:10:14 +01:00
|
|
|
private async hashProtocolName(protocolName: bytes) : Promise<bytes32> {
|
2019-10-31 17:35:18 +01:00
|
|
|
if (protocolName.length <= 32) {
|
|
|
|
return new Promise(resolve => {
|
2019-11-04 14:38:01 +01:00
|
|
|
const h = Buffer.alloc(32);
|
2019-11-04 15:10:14 +01:00
|
|
|
protocolName.copy(h);
|
2019-10-31 17:35:18 +01:00
|
|
|
resolve(h)
|
|
|
|
});
|
|
|
|
} else {
|
2019-11-04 15:10:14 +01:00
|
|
|
return await this.getHash(protocolName, Buffer.from([]));
|
2019-10-31 17:35:18 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-04 14:38:01 +01:00
|
|
|
private async mixHash(ss: SymmetricState, data: bytes) {
|
2019-10-31 17:35:18 +01:00
|
|
|
ss.h = await this.getHash(ss.h, data);
|
|
|
|
}
|
|
|
|
|
2019-11-04 14:38:01 +01:00
|
|
|
private async getHash(a: bytes, b: bytes) : Promise<bytes32> {
|
2019-10-31 17:35:18 +01:00
|
|
|
return await crypto.hmac.create('sha256', Buffer.from([...a, ...b]))
|
|
|
|
}
|
|
|
|
|
2019-11-04 17:06:31 +01:00
|
|
|
public async initSession(initiator: boolean, prologue: bytes32, s: KeyPair, rs: bytes32) : Promise<NoiseSession> {
|
|
|
|
// TODO: Create noisesession object/class
|
|
|
|
// @ts-ignore-next-line
|
|
|
|
let session: NoiseSession = {};
|
2019-11-04 15:10:14 +01:00
|
|
|
const psk = this.createEmptyKey();
|
2019-10-31 17:35:18 +01:00
|
|
|
|
|
|
|
if (initiator) {
|
|
|
|
session.hs = await this.initializeInitiator(prologue, s, rs, psk);
|
|
|
|
} else {
|
|
|
|
session.hs = await this.initializeResponder(prologue, s, rs, psk);
|
|
|
|
}
|
|
|
|
|
|
|
|
session.i = initiator;
|
|
|
|
session.mc = 0;
|
|
|
|
return session;
|
|
|
|
}
|
|
|
|
}
|