js-libp2p-noise/src/handshakes/abstract-handshake.ts

181 lines
5.1 KiB
TypeScript
Raw Normal View History

2019-12-24 21:15:38 +01:00
import {Buffer} from "buffer";
2020-04-03 09:24:58 +02:00
import AEAD from 'bcrypto/lib/js/aead';
import x25519 from 'bcrypto/lib/js/x25519';
import SHA256 from 'bcrypto/lib/js/sha256';
2019-12-24 21:15:38 +01:00
import {bytes, bytes32, uint32} from "../@types/basic";
2019-12-25 18:32:19 +01:00
import {CipherState, MessageBuffer, SymmetricState} from "../@types/handshake";
2019-12-24 21:15:38 +01:00
import {getHkdf} from "../utils";
2020-03-01 19:05:53 +01:00
import {logger} from "../logger";
2019-12-24 21:15:38 +01:00
2019-12-30 09:59:59 +01:00
export const MIN_NONCE = 0;
2019-12-24 21:15:38 +01:00
2019-12-29 18:23:43 +01:00
export abstract class AbstractHandshake {
2019-12-25 11:11:07 +01:00
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;
}
2020-03-01 19:05:53 +01:00
public decryptWithAd(cs: CipherState, ad: bytes, ciphertext: bytes): {plaintext: bytes; valid: boolean} {
const {plaintext, valid} = this.decrypt(cs.k, cs.n, ad, ciphertext);
2019-12-25 11:11:07 +01:00
this.setNonce(cs, this.incrementNonce(cs.n));
2020-03-01 19:05:53 +01:00
return {plaintext, valid};
2019-12-25 11:11:07 +01:00
}
// Cipher state related
protected hasKey(cs: CipherState): boolean {
return !this.isEmptyKey(cs.k);
}
protected setNonce(cs: CipherState, nonce: uint32): void {
cs.n = nonce;
}
protected createEmptyKey(): bytes32 {
return Buffer.alloc(32);
}
protected isEmptyKey(k: bytes32): boolean {
const emptyKey = this.createEmptyKey();
return emptyKey.equals(k);
}
2019-12-24 21:15:38 +01:00
protected incrementNonce(n: uint32): uint32 {
return n + 1;
}
protected nonceToBytes(n: uint32): bytes {
const nonce = Buffer.alloc(12);
nonce.writeUInt32LE(n, 4);
return nonce;
}
protected encrypt(k: bytes32, n: uint32, ad: bytes, plaintext: bytes): bytes {
const nonce = this.nonceToBytes(n);
const ctx = new AEAD();
2020-02-17 12:11:55 +01:00
plaintext = Buffer.from(plaintext);
2019-12-24 21:15:38 +01:00
ctx.init(k, nonce);
ctx.aad(ad);
ctx.encrypt(plaintext);
// Encryption is done on the sent reference
2020-02-17 12:11:55 +01:00
return Buffer.concat([plaintext, ctx.final()]);
2019-12-24 21:15:38 +01:00
}
2019-12-25 11:11:07 +01:00
protected encryptAndHash(ss: SymmetricState, plaintext: bytes): bytes {
let ciphertext;
if (this.hasKey(ss.cs)) {
ciphertext = this.encryptWithAd(ss.cs, ss.h, plaintext);
} else {
ciphertext = plaintext;
}
this.mixHash(ss, ciphertext);
return ciphertext;
}
2020-03-01 19:05:53 +01:00
protected decrypt(k: bytes32, n: uint32, ad: bytes, ciphertext: bytes): {plaintext: bytes; valid: boolean} {
2019-12-25 11:11:07 +01:00
const nonce = this.nonceToBytes(n);
const ctx = new AEAD();
2020-02-17 12:11:55 +01:00
ciphertext = Buffer.from(ciphertext);
2020-03-01 19:05:53 +01:00
const tag = ciphertext.slice(ciphertext.length - 16);
2020-02-17 12:11:55 +01:00
ciphertext = ciphertext.slice(0, ciphertext.length - 16);
2019-12-25 11:11:07 +01:00
ctx.init(k, nonce);
ctx.aad(ad);
ctx.decrypt(ciphertext);
// Decryption is done on the sent reference
2020-03-01 19:05:53 +01:00
return {plaintext: ciphertext, valid: ctx.verify(tag)};
2019-12-25 11:11:07 +01:00
}
2020-03-01 19:05:53 +01:00
protected decryptAndHash(ss: SymmetricState, ciphertext: bytes): {plaintext: bytes; valid: boolean} {
let plaintext: bytes, valid = true;
2019-12-25 11:11:07 +01:00
if (this.hasKey(ss.cs)) {
2020-03-01 19:05:53 +01:00
({plaintext, valid} = this.decryptWithAd(ss.cs, ss.h, ciphertext));
2019-12-25 11:11:07 +01:00
} else {
plaintext = ciphertext;
}
this.mixHash(ss, ciphertext);
2020-03-01 19:05:53 +01:00
return {plaintext, valid};
2019-12-25 11:11:07 +01:00
}
2019-12-24 21:15:38 +01:00
protected dh(privateKey: bytes32, publicKey: bytes32): bytes32 {
2020-03-01 19:05:53 +01:00
try {
const derived = x25519.derive(publicKey, privateKey);
const result = Buffer.alloc(32);
derived.copy(result);
return result;
} catch (e) {
logger(e.message);
return Buffer.alloc(32);
}
2019-12-24 21:15:38 +01:00
}
protected mixHash(ss: SymmetricState, data: bytes): void {
ss.h = this.getHash(ss.h, data);
}
protected getHash(a: bytes, b: bytes): bytes32 {
return SHA256.digest(Buffer.from([...a, ...b]));
}
protected mixKey(ss: SymmetricState, ikm: bytes32): void {
const [ ck, tempK ] = getHkdf(ss.ck, ikm);
ss.cs = this.initializeKey(tempK) as CipherState;
ss.ck = ck;
}
protected initializeKey(k: bytes32): CipherState {
2019-12-30 09:59:59 +01:00
const n = MIN_NONCE;
2019-12-24 21:15:38 +01:00
return { k, n };
}
2019-12-25 11:11:07 +01:00
// Symmetric state related
protected initializeSymmetric(protocolName: string): SymmetricState {
const protocolNameBytes: bytes = Buffer.from(protocolName, 'utf-8');
const h = this.hashProtocolName(protocolNameBytes);
const ck = h;
const key = this.createEmptyKey();
const cs: CipherState = this.initializeKey(key);
return { cs, ck, h };
}
protected hashProtocolName(protocolName: bytes): bytes32 {
if (protocolName.length <= 32) {
const h = Buffer.alloc(32);
protocolName.copy(h);
return h;
} else {
return this.getHash(protocolName, Buffer.alloc(0));
}
}
2019-12-25 18:32:19 +01:00
protected split(ss: SymmetricState) {
2019-12-25 11:11:07 +01:00
const [ tempk1, tempk2 ] = getHkdf(ss.ck, Buffer.alloc(0));
const cs1 = this.initializeKey(tempk1);
const cs2 = this.initializeKey(tempk2);
return { cs1, cs2 };
}
2019-12-25 18:32:19 +01:00
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 };
}
2020-03-01 19:05:53 +01:00
protected readMessageRegular(cs: CipherState, message: MessageBuffer): {plaintext: bytes; valid: boolean} {
2019-12-25 18:32:19 +01:00
return this.decryptWithAd(cs, Buffer.alloc(0), message.ciphertext);
}
2019-12-24 21:15:38 +01:00
}