@hpke/common
Version:
A Hybrid Public Key Encryption (HPKE) internal-use common module for @hpke family modules.
416 lines (415 loc) • 17 kB
JavaScript
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "../../algorithm.js", "../../consts.js", "../../kdfs/hkdf.js", "../../errors.js", "../../identifiers.js", "../../interfaces/dhkemPrimitives.js", "../../utils/bignum.js", "../../utils/misc.js"], factory);
}
})(function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Ec = void 0;
const algorithm_js_1 = require("../../algorithm.js");
const consts_js_1 = require("../../consts.js");
const hkdf_js_1 = require("../../kdfs/hkdf.js");
const errors_js_1 = require("../../errors.js");
const identifiers_js_1 = require("../../identifiers.js");
const dhkemPrimitives_js_1 = require("../../interfaces/dhkemPrimitives.js");
const bignum_js_1 = require("../../utils/bignum.js");
const misc_js_1 = require("../../utils/misc.js");
// b"candidate"
// deno-fmt-ignore
const LABEL_CANDIDATE = /* @__PURE__ */ new Uint8Array([
99, 97, 110, 100, 105, 100, 97, 116, 101,
]);
// the order of the curve being used.
// deno-fmt-ignore
const ORDER_P_256 = /* @__PURE__ */ new Uint8Array([
0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xbc, 0xe6, 0xfa, 0xad, 0xa7, 0x17, 0x9e, 0x84,
0xf3, 0xb9, 0xca, 0xc2, 0xfc, 0x63, 0x25, 0x51,
]);
// deno-fmt-ignore
const ORDER_P_384 = /* @__PURE__ */ new Uint8Array([
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xc7, 0x63, 0x4d, 0x81, 0xf4, 0x37, 0x2d, 0xdf,
0x58, 0x1a, 0x0d, 0xb2, 0x48, 0xb0, 0xa7, 0x7a,
0xec, 0xec, 0x19, 0x6a, 0xcc, 0xc5, 0x29, 0x73,
]);
// deno-fmt-ignore
const ORDER_P_521 = /* @__PURE__ */ new Uint8Array([
0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xfa, 0x51, 0x86, 0x87, 0x83, 0xbf, 0x2f,
0x96, 0x6b, 0x7f, 0xcc, 0x01, 0x48, 0xf7, 0x09,
0xa5, 0xd0, 0x3b, 0xb5, 0xc9, 0xb8, 0x89, 0x9c,
0x47, 0xae, 0xbb, 0x6f, 0xb7, 0x1e, 0x91, 0x38,
0x64, 0x09,
]);
// deno-fmt-ignore
const PKCS8_ALG_ID_P_256 = /* @__PURE__ */ new Uint8Array([
48, 65, 2, 1, 0, 48, 19, 6, 7, 42,
134, 72, 206, 61, 2, 1, 6, 8, 42, 134,
72, 206, 61, 3, 1, 7, 4, 39, 48, 37,
2, 1, 1, 4, 32,
]);
// deno-fmt-ignore
const PKCS8_ALG_ID_P_384 = /* @__PURE__ */ new Uint8Array([
48, 78, 2, 1, 0, 48, 16, 6, 7, 42,
134, 72, 206, 61, 2, 1, 6, 5, 43, 129,
4, 0, 34, 4, 55, 48, 53, 2, 1, 1,
4, 48,
]);
// deno-fmt-ignore
const PKCS8_ALG_ID_P_521 = /* @__PURE__ */ new Uint8Array([
48, 96, 2, 1, 0, 48, 16, 6, 7, 42,
134, 72, 206, 61, 2, 1, 6, 5, 43, 129,
4, 0, 35, 4, 73, 48, 71, 2, 1, 1,
4, 66,
]);
const EC_P_256_PARAMS = {
p: 0xffffffff00000001000000000000000000000000ffffffffffffffffffffffffn,
b: 0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604bn,
gx: 0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296n,
gy: 0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5n,
coordinateSize: 32,
};
const EC_P_384_PARAMS = {
p: 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000ffffffffn,
b: 0xb3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8edd3ec2aefn,
gx: 0xaa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab7n,
gy: 0x3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5fn,
coordinateSize: 48,
};
const EC_P_521_PARAMS = {
p: (1n << 521n) - 1n,
b: 0x0051953eb9618e1c9a1f929a21a0b68540eea2da725b99b315f3b8b489918ef109e156193951ec7e937b1652c0bd3bb1bf073573df883d2c34f1ef451fd46b503f00n,
gx: 0x00c6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f828af606b4d3dbaa14b5e77efe75928fe1dc127a2ffa8de3348b3c1856a429bf97e7e31c2e5bd66n,
gy: 0x011839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd17273e662c97ee72995ef42640c550b9013fad0761353c7086a272c24088be94769fd16650n,
coordinateSize: 66,
};
function mod(a, p) {
const r = a % p;
return r >= 0n ? r : r + p;
}
function modPow(base, exponent, p) {
let result = 1n;
let b = mod(base, p);
let e = exponent;
while (e > 0n) {
if ((e & 1n) === 1n) {
result = mod(result * b, p);
}
b = mod(b * b, p);
e >>= 1n;
}
return result;
}
function modSqrt(rhs, p) {
// P-256/P-384/P-521 primes satisfy p % 4 == 3.
const y = modPow(rhs, (p + 1n) >> 2n, p);
if (mod(y * y, p) !== mod(rhs, p)) {
throw new Error("Invalid ECDH point");
}
return y;
}
function bytesToBigInt(bytes) {
let v = 0n;
for (const b of bytes) {
v = (v << 8n) | consts_js_1.BYTE_TO_BIGINT_256[b];
}
return v;
}
function bigIntToBytes(v, len) {
const out = new Uint8Array(len);
let n = v;
for (let i = len - 1; i >= 0; i--) {
out[i] = Number(n & 0xffn);
n >>= 8n;
}
if (n !== 0n) {
throw new Error("Invalid coordinate length");
}
return out;
}
function buildRawUncompressedPublicKey(x, y, coordinateSize) {
const out = new Uint8Array(1 + coordinateSize * 2);
out[0] = 0x04;
out.set(bigIntToBytes(x, coordinateSize), 1);
out.set(bigIntToBytes(y, coordinateSize), 1 + coordinateSize);
return out;
}
class Ec extends algorithm_js_1.NativeAlgorithm {
constructor(kem, hkdf) {
super();
Object.defineProperty(this, "_hkdf", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "_alg", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "_nPk", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "_nSk", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "_nDh", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
// EC specific arguments for deriving key pair.
Object.defineProperty(this, "_order", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "_bitmask", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "_pkcs8AlgId", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "_curveParams", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
this._hkdf = hkdf;
switch (kem) {
case identifiers_js_1.KemId.DhkemP256HkdfSha256:
this._alg = { name: "ECDH", namedCurve: "P-256" };
this._nPk = 65;
this._nSk = 32;
this._nDh = 32;
this._order = ORDER_P_256;
this._bitmask = 0xFF;
this._pkcs8AlgId = PKCS8_ALG_ID_P_256;
this._curveParams = EC_P_256_PARAMS;
break;
case identifiers_js_1.KemId.DhkemP384HkdfSha384:
this._alg = { name: "ECDH", namedCurve: "P-384" };
this._nPk = 97;
this._nSk = 48;
this._nDh = 48;
this._order = ORDER_P_384;
this._bitmask = 0xFF;
this._pkcs8AlgId = PKCS8_ALG_ID_P_384;
this._curveParams = EC_P_384_PARAMS;
break;
default:
// case KemId.DhkemP521HkdfSha512:
this._alg = { name: "ECDH", namedCurve: "P-521" };
this._nPk = 133;
this._nSk = 66;
this._nDh = 66;
this._order = ORDER_P_521;
this._bitmask = 0x01;
this._pkcs8AlgId = PKCS8_ALG_ID_P_521;
this._curveParams = EC_P_521_PARAMS;
break;
}
}
async serializePublicKey(key) {
await this._setup();
try {
return await this._api.exportKey("raw", key);
}
catch (e) {
throw new errors_js_1.SerializeError(e);
}
}
async deserializePublicKey(key) {
await this._setup();
try {
return await this._importRawKey((0, hkdf_js_1.toArrayBuffer)(key), true);
}
catch (e) {
throw new errors_js_1.DeserializeError(e);
}
}
async serializePrivateKey(key) {
await this._setup();
try {
const jwk = await this._api.exportKey("jwk", key);
if (!("d" in jwk)) {
throw new Error("Not private key");
}
return (0, misc_js_1.base64UrlToBytes)(jwk["d"]).buffer;
}
catch (e) {
throw new errors_js_1.SerializeError(e);
}
}
async deserializePrivateKey(key) {
await this._setup();
try {
return await this._importRawKey((0, hkdf_js_1.toArrayBuffer)(key), false);
}
catch (e) {
throw new errors_js_1.DeserializeError(e);
}
}
async importKey(format, key, isPublic) {
await this._setup();
try {
if (format === "raw") {
return await this._importRawKey(key, isPublic);
}
// jwk
if (key instanceof ArrayBuffer) {
throw new Error("Invalid jwk key format");
}
return await this._importJWK(key, isPublic);
}
catch (e) {
throw new errors_js_1.DeserializeError(e);
}
}
async generateKeyPair() {
await this._setup();
try {
return await this._api.generateKey(this._alg, true, dhkemPrimitives_js_1.KEM_USAGES);
}
catch (e) {
throw new errors_js_1.NotSupportedError(e);
}
}
async deriveKeyPair(ikm) {
await this._setup();
try {
const rawIkm = (0, hkdf_js_1.toArrayBuffer)(ikm);
const dkpPrk = await this._hkdf.labeledExtract(consts_js_1.EMPTY, dhkemPrimitives_js_1.LABEL_DKP_PRK, new Uint8Array(rawIkm));
const bn = new bignum_js_1.Bignum(this._nSk);
for (let counter = 0; bn.isZero() || !bn.lessThan(this._order); counter++) {
if (counter > 255) {
throw new Error("Faild to derive a key pair");
}
const bytes = new Uint8Array(await this._hkdf.labeledExpand(dkpPrk, LABEL_CANDIDATE, (0, misc_js_1.i2Osp)(counter, 1), this._nSk));
bytes[0] = bytes[0] & this._bitmask;
bn.set(bytes);
}
const sk = await this._deserializePkcs8Key(bn.val());
bn.reset();
return {
privateKey: sk,
publicKey: await this.derivePublicKey(sk),
};
}
catch (e) {
throw new errors_js_1.DeriveKeyPairError(e);
}
}
async derivePublicKey(key) {
await this._setup();
try {
const jwk = await this._api.exportKey("jwk", key);
delete jwk["d"];
delete jwk["key_ops"];
return await this._api.importKey("jwk", jwk, this._alg, true, []);
}
catch {
try {
// Firefox fails to export JWK from some imported ECDH private keys.
return await this._derivePublicKeyWithoutJwkExport(key);
}
catch (e) {
throw new errors_js_1.DeserializeError(e);
}
}
}
async dh(sk, pk) {
try {
await this._setup();
const bits = await this._api.deriveBits({
name: "ECDH",
public: pk,
}, sk, this._nDh * 8);
return bits;
}
catch (e) {
throw new errors_js_1.SerializeError(e);
}
}
async _importRawKey(key, isPublic) {
if (isPublic && key.byteLength !== this._nPk) {
throw new Error("Invalid public key for the ciphersuite");
}
if (!isPublic && key.byteLength !== this._nSk) {
throw new Error("Invalid private key for the ciphersuite");
}
if (isPublic) {
return await this._api.importKey("raw", key, this._alg, true, []);
}
return await this._deserializePkcs8Key(new Uint8Array(key));
}
async _importJWK(key, isPublic) {
if (typeof key.crv === "undefined" || key.crv !== this._alg.namedCurve) {
throw new Error(`Invalid crv: ${key.crv}`);
}
if (isPublic) {
if (typeof key.d !== "undefined") {
throw new Error("Invalid key: `d` should not be set");
}
return await this._api.importKey("jwk", key, this._alg, true, []);
}
if (typeof key.d === "undefined") {
throw new Error("Invalid key: `d` not found");
}
return await this._api.importKey("jwk", key, this._alg, true, dhkemPrimitives_js_1.KEM_USAGES);
}
async _deserializePkcs8Key(k) {
const pkcs8Key = new Uint8Array(this._pkcs8AlgId.length + k.length);
pkcs8Key.set(this._pkcs8AlgId, 0);
pkcs8Key.set(k, this._pkcs8AlgId.length);
return await this._api.importKey("pkcs8", pkcs8Key, this._alg, true, dhkemPrimitives_js_1.KEM_USAGES);
}
async _derivePublicKeyWithoutJwkExport(key) {
const basePointRaw = buildRawUncompressedPublicKey(this._curveParams.gx, this._curveParams.gy, this._curveParams.coordinateSize);
const basePoint = await this._api.importKey("raw", basePointRaw.buffer, this._alg, true, []);
const xBytes = new Uint8Array(await this._api.deriveBits({
name: "ECDH",
public: basePoint,
}, key, this._nDh * 8));
const p = this._curveParams.p;
const x = bytesToBigInt(xBytes);
const rhs = mod(modPow(x, 3n, p) - 3n * x + this._curveParams.b, p);
let y = modSqrt(rhs, p);
// Canonicalize sign so the encoded point is deterministic.
if ((y & 1n) === 1n) {
y = p - y;
}
const pubRaw = buildRawUncompressedPublicKey(x, y, this._curveParams.coordinateSize);
return await this._api.importKey("raw", pubRaw.buffer, this._alg, true, []);
}
}
exports.Ec = Ec;
});