@hpke/common
Version:
A Hybrid Public Key Encryption (HPKE) internal-use common module for @hpke family modules.
197 lines (196 loc) • 7.26 kB
JavaScript
import { EMPTY, INPUT_LENGTH_LIMIT } from "../consts.js";
import { DecapError, EncapError, InvalidParamError } from "../errors.js";
import { SUITE_ID_HEADER_KEM } from "../interfaces/kemInterface.js";
import { toArrayBuffer } from "../kdfs/hkdf.js";
import { concat, i2Osp, isCryptoKeyPair } from "../utils/misc.js";
// b"eae_prk"
const LABEL_EAE_PRK = /* @__PURE__ */ new Uint8Array([
101,
97,
101,
95,
112,
114,
107,
]);
// b"shared_secret"
// deno-fmt-ignore
const LABEL_SHARED_SECRET = /* @__PURE__ */ new Uint8Array([
115, 104, 97, 114, 101, 100, 95, 115, 101, 99,
114, 101, 116,
]);
function concat3(a, b, c) {
const ret = new Uint8Array(a.length + b.length + c.length);
ret.set(a, 0);
ret.set(b, a.length);
ret.set(c, a.length + b.length);
return ret;
}
export class Dhkem {
constructor(id, prim, kdf) {
Object.defineProperty(this, "id", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "secretSize", {
enumerable: true,
configurable: true,
writable: true,
value: 0
});
Object.defineProperty(this, "encSize", {
enumerable: true,
configurable: true,
writable: true,
value: 0
});
Object.defineProperty(this, "publicKeySize", {
enumerable: true,
configurable: true,
writable: true,
value: 0
});
Object.defineProperty(this, "privateKeySize", {
enumerable: true,
configurable: true,
writable: true,
value: 0
});
Object.defineProperty(this, "_prim", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "_kdf", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
this.id = id;
this._prim = prim;
this._kdf = kdf;
const suiteId = new Uint8Array(SUITE_ID_HEADER_KEM);
suiteId.set(i2Osp(this.id, 2), 3);
this._kdf.init(suiteId);
}
async serializePublicKey(key) {
return await this._prim.serializePublicKey(key);
}
async deserializePublicKey(key) {
return await this._prim.deserializePublicKey(toArrayBuffer(key));
}
async serializePrivateKey(key) {
return await this._prim.serializePrivateKey(key);
}
async deserializePrivateKey(key) {
return await this._prim.deserializePrivateKey(toArrayBuffer(key));
}
async importKey(format, key, isPublic = true) {
return await this._prim.importKey(format, key, isPublic);
}
async generateKeyPair() {
return await this._prim.generateKeyPair();
}
async deriveKeyPair(ikm) {
const rawIkm = toArrayBuffer(ikm);
if (rawIkm.byteLength > INPUT_LENGTH_LIMIT) {
throw new InvalidParamError("Too long ikm");
}
return await this._prim.deriveKeyPair(rawIkm);
}
async encap(params) {
let ke;
if (params.ekm === undefined) {
ke = await this.generateKeyPair();
}
else if (isCryptoKeyPair(params.ekm)) {
// params.ekm is only used for testing.
ke = params.ekm;
}
else {
// params.ekm is only used for testing.
ke = await this.deriveKeyPair(params.ekm);
}
const enc = await this._prim.serializePublicKey(ke.publicKey);
const pkrm = await this._prim.serializePublicKey(params.recipientPublicKey);
try {
let dh;
if (params.senderKey === undefined) {
dh = new Uint8Array(await this._prim.dh(ke.privateKey, params.recipientPublicKey));
}
else {
const sks = isCryptoKeyPair(params.senderKey)
? params.senderKey.privateKey
: params.senderKey;
const dh1 = new Uint8Array(await this._prim.dh(ke.privateKey, params.recipientPublicKey));
const dh2 = new Uint8Array(await this._prim.dh(sks, params.recipientPublicKey));
dh = concat(dh1, dh2);
}
let kemContext;
if (params.senderKey === undefined) {
kemContext = concat(new Uint8Array(enc), new Uint8Array(pkrm));
}
else {
const pks = isCryptoKeyPair(params.senderKey)
? params.senderKey.publicKey
: await this._prim.derivePublicKey(params.senderKey);
const pksm = await this._prim.serializePublicKey(pks);
kemContext = concat3(new Uint8Array(enc), new Uint8Array(pkrm), new Uint8Array(pksm));
}
const sharedSecret = await this._generateSharedSecret(dh, kemContext);
return {
enc: enc,
sharedSecret: sharedSecret,
};
}
catch (e) {
throw new EncapError(e);
}
}
async decap(params) {
const enc = toArrayBuffer(params.enc);
const pke = await this._prim.deserializePublicKey(enc);
const skr = isCryptoKeyPair(params.recipientKey)
? params.recipientKey.privateKey
: params.recipientKey;
const pkr = isCryptoKeyPair(params.recipientKey)
? params.recipientKey.publicKey
: await this._prim.derivePublicKey(params.recipientKey);
const pkrm = await this._prim.serializePublicKey(pkr);
try {
let dh;
if (params.senderPublicKey === undefined) {
dh = new Uint8Array(await this._prim.dh(skr, pke));
}
else {
const dh1 = new Uint8Array(await this._prim.dh(skr, pke));
const dh2 = new Uint8Array(await this._prim.dh(skr, params.senderPublicKey));
dh = concat(dh1, dh2);
}
let kemContext;
if (params.senderPublicKey === undefined) {
kemContext = concat(new Uint8Array(enc), new Uint8Array(pkrm));
}
else {
const pksm = await this._prim.serializePublicKey(params.senderPublicKey);
kemContext = new Uint8Array(enc.byteLength + pkrm.byteLength + pksm.byteLength);
kemContext.set(new Uint8Array(enc), 0);
kemContext.set(new Uint8Array(pkrm), enc.byteLength);
kemContext.set(new Uint8Array(pksm), enc.byteLength + pkrm.byteLength);
}
return await this._generateSharedSecret(dh, kemContext);
}
catch (e) {
throw new DecapError(e);
}
}
async _generateSharedSecret(dh, kemContext) {
const labeledIkm = this._kdf.buildLabeledIkm(LABEL_EAE_PRK, dh);
const labeledInfo = this._kdf.buildLabeledInfo(LABEL_SHARED_SECRET, kemContext, this.secretSize);
return await this._kdf.extractAndExpand(EMPTY, labeledIkm, labeledInfo, this.secretSize);
}
}