Merge remote-tracking branch 'remotes/origin/master' into morrigan/encrypt-stream

This commit is contained in:
morrigan 2019-11-25 10:16:16 +01:00
commit b1a848cd47
2 changed files with 64 additions and 58 deletions

12
.travis.yml Normal file
View File

@ -0,0 +1,12 @@
language: node_js
cache: false
install:
- yarn install --frozen-lockfile --network-timeout 1000000
script:
set -e;
yarn run lint;
yarn run check-types;
yarn run build;

110
src/xx.ts
View File

@ -8,49 +8,49 @@ import { generateKeypair } from './utils';
export interface MessageBuffer {
ne: bytes32,
ns: bytes,
ciphertext: bytes
ne: bytes32;
ns: bytes;
ciphertext: bytes;
}
type CipherState = {
k: bytes32,
n: uint32,
k: bytes32;
n: uint32;
}
type SymmetricState = {
cs: CipherState,
ck: bytes32, // chaining key
h: bytes32, // handshake hash
cs: CipherState;
ck: bytes32; // chaining key
h: bytes32; // handshake hash
}
type HandshakeState = {
ss: SymmetricState,
s: KeyPair,
e?: KeyPair,
rs: bytes32,
re: bytes32,
psk: bytes32,
ss: SymmetricState;
s: KeyPair;
e?: KeyPair;
rs: bytes32;
re: bytes32;
psk: bytes32;
}
export type NoiseSession = {
hs: HandshakeState,
h?: bytes32,
cs1?: CipherState,
cs2?: CipherState,
mc: uint64,
i: boolean,
hs: HandshakeState;
h?: bytes32;
cs1?: CipherState;
cs2?: CipherState;
mc: uint64;
i: boolean;
}
export type Hkdf = [bytes, bytes, bytes];
const minNonce = 0;
export class XXHandshake {
private createEmptyKey() : bytes32 {
private createEmptyKey(): bytes32 {
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 ss = await this.initializeSymmetric(name);
this.mixHash(ss, prologue);
@ -59,7 +59,7 @@ export class XXHandshake {
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 ss = await this.initializeSymmetric(name);
this.mixHash(ss, prologue);
@ -68,25 +68,25 @@ export class XXHandshake {
return { ss, s, rs, psk, re };
}
private incrementNonce(n: uint32) : uint32 {
private incrementNonce(n: uint32): uint32 {
return n + 1;
}
private dh(privateKey: bytes32, publicKey: bytes32) : bytes32 {
private dh(privateKey: bytes32, publicKey: bytes32): bytes32 {
const derived = x25519.derive(publicKey, privateKey);
const result = Buffer.alloc(32);
derived.copy(result);
return result;
}
private nonceToBytes(n: uint32) : bytes {
private nonceToBytes(n: uint32): bytes {
const nonce = Buffer.alloc(12);
nonce.writeUInt32LE(n, 4);
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 ctx = new AEAD();
@ -98,7 +98,7 @@ export class XXHandshake {
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 ctx = new AEAD();
@ -110,33 +110,33 @@ export class XXHandshake {
return ciphertext;
}
private isEmptyKey(k: bytes32) : boolean {
private isEmptyKey(k: bytes32): boolean {
const emptyKey = this.createEmptyKey();
return emptyKey.equals(k);
}
// Cipher state related
private initializeKey(k: bytes32) : CipherState {
private initializeKey(k: bytes32): CipherState {
const n = minNonce;
return { k, n };
}
private hasKey(cs: CipherState) : boolean {
private hasKey(cs: CipherState): boolean {
return !this.isEmptyKey(cs.k);
}
private setNonce(cs: CipherState, nonce: uint32) : void {
private setNonce(cs: CipherState, nonce: uint32): void {
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);
this.setNonce(cs, this.incrementNonce(cs.n));
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);
this.setNonce(cs, this.incrementNonce(cs.n));
@ -145,7 +145,7 @@ export class XXHandshake {
// 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 h = await this.hashProtocolName(protocolNameBytes);
@ -162,9 +162,9 @@ export class XXHandshake {
ss.ck = ck;
}
private async hashProtocolName(protocolName: bytes) : Promise<bytes32> {
private async hashProtocolName(protocolName: bytes): Promise<bytes32> {
if (protocolName.length <= 32) {
let h = Buffer.alloc(32);
const h = Buffer.alloc(32);
protocolName.copy(h);
return h;
} else {
@ -172,7 +172,7 @@ export class XXHandshake {
}
}
public getHkdf(ck: bytes32, ikm: bytes) : Hkdf {
public getHkdf(ck: bytes32, ikm: bytes): Hkdf {
const info = Buffer.alloc(0);
const prk = HKDF.extract(SHA256, ikm, ck);
const okm = HKDF.expand(SHA256, prk, info, 96);
@ -188,11 +188,11 @@ export class XXHandshake {
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]));
}
private async encryptAndHash(ss: SymmetricState, plaintext: bytes) : Promise<bytes> {
private async encryptAndHash(ss: SymmetricState, plaintext: bytes): Promise<bytes> {
let ciphertext;
if (this.hasKey(ss.cs)) {
ciphertext = this.encryptWithAd(ss.cs, ss.h, plaintext);
@ -204,7 +204,7 @@ export class XXHandshake {
return ciphertext;
}
private async decryptAndHash(ss: SymmetricState, ciphertext: bytes) : Promise<bytes> {
private async decryptAndHash(ss: SymmetricState, ciphertext: bytes): Promise<bytes> {
let plaintext;
if (this.hasKey(ss.cs)) {
plaintext = this.decryptWithAd(ss.cs, ss.h, ciphertext);
@ -224,12 +224,10 @@ export class XXHandshake {
return { cs1, cs2 };
}
private async writeMessageA(hs: HandshakeState, payload: bytes) : Promise<MessageBuffer> {
let ns = Buffer.alloc(0);
private async writeMessageA(hs: HandshakeState, payload: bytes): Promise<MessageBuffer> {
const ns = Buffer.alloc(0);
hs.e = generateKeypair();
if (!hs.e) {
throw new Error("Handshake state has keypair missing.");
}
const ne = hs.e.publicKey;
this.mixHash(hs.ss, ne);
@ -238,11 +236,8 @@ export class XXHandshake {
return {ne, ns, ciphertext};
}
private async writeMessageB(hs: HandshakeState, payload: bytes) : Promise<MessageBuffer> {
private async writeMessageB(hs: HandshakeState, payload: bytes): Promise<MessageBuffer> {
hs.e = generateKeypair();
if (!hs.e) {
throw new Error("Handshake state has keypair missing.");
}
const ne = hs.e.publicKey;
this.mixHash(hs.ss, ne);
@ -268,7 +263,7 @@ export class XXHandshake {
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 ne = this.createEmptyKey();
const ns = Buffer.alloc(0);
@ -276,8 +271,7 @@ export class XXHandshake {
return { ne, ns, ciphertext };
}
private async readMessageA(hs: HandshakeState, message: MessageBuffer) : Promise<bytes> {
console.log("publci key: ", message.ne)
private async readMessageA(hs: HandshakeState, message: MessageBuffer): Promise<bytes> {
if (x25519.publicKeyVerify(message.ne)) {
hs.re = message.ne;
}
@ -286,7 +280,7 @@ export class XXHandshake {
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)) {
hs.re = message.ne;
}
@ -320,11 +314,11 @@ export class XXHandshake {
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);
}
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();
let hs;
@ -341,7 +335,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;
if (session.mc.eqn(0)) {
messageBuffer = await this.writeMessageA(session.hs, message);
@ -375,7 +369,7 @@ export class XXHandshake {
return messageBuffer;
}
public async recvMessage(session: NoiseSession, message: MessageBuffer) : Promise<bytes> {
public async recvMessage(session: NoiseSession, message: MessageBuffer): Promise<bytes> {
let plaintext: bytes;
if (session.mc.eqn(0)) {
plaintext = await this.readMessageA(session.hs, message);