UNPKG

@hpke/dhkem-x448

Version:

A Hybrid Public Key Encryption (HPKE) module extension for X448

184 lines (183 loc) 5.76 kB
import { x448 } from "@noble/curves/ed448"; import { base64UrlToBytes, DeriveKeyPairError, DeserializeError, EMPTY, KEM_USAGES, LABEL_DKP_PRK, LABEL_SK, NotSupportedError, SerializeError, XCryptoKey, } from "@hpke/common"; const ALG_NAME = "X448"; export class X448 { constructor(hkdf) { Object.defineProperty(this, "_hkdf", { 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 }); this._hkdf = hkdf; this._nPk = 56; this._nSk = 56; } async serializePublicKey(key) { try { return await this._serializePublicKey(key); } catch (e) { throw new SerializeError(e); } } async deserializePublicKey(key) { try { return await this._importRawKey(key, true); } catch (e) { throw new DeserializeError(e); } } async serializePrivateKey(key) { try { return await this._serializePrivateKey(key); } catch (e) { throw new SerializeError(e); } } async deserializePrivateKey(key) { try { return await this._importRawKey(key, false); } catch (e) { throw new DeserializeError(e); } } async importKey(format, key, isPublic) { 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 DeserializeError(e); } } async generateKeyPair() { try { const rawSk = x448.utils.randomPrivateKey(); const sk = new XCryptoKey(ALG_NAME, rawSk, "private", KEM_USAGES); const pk = await this.derivePublicKey(sk); return { publicKey: pk, privateKey: sk }; } catch (e) { throw new NotSupportedError(e); } } async deriveKeyPair(ikm) { try { const dkpPrk = await this._hkdf.labeledExtract(EMPTY.buffer, LABEL_DKP_PRK, new Uint8Array(ikm)); const rawSk = await this._hkdf.labeledExpand(dkpPrk, LABEL_SK, EMPTY, this._nSk); const sk = new XCryptoKey(ALG_NAME, new Uint8Array(rawSk), "private", KEM_USAGES); return { privateKey: sk, publicKey: await this.derivePublicKey(sk), }; } catch (e) { throw new DeriveKeyPairError(e); } } async derivePublicKey(key) { try { return await this._derivePublicKey(key); } catch (e) { throw new DeserializeError(e); } } async dh(sk, pk) { try { return await this._dh(sk, pk); } catch (e) { throw new SerializeError(e); } } _serializePublicKey(k) { return new Promise((resolve) => { resolve(k.key.buffer); }); } _serializePrivateKey(k) { return new Promise((resolve) => { resolve(k.key.buffer); }); } _importRawKey(key, isPublic) { return new Promise((resolve, reject) => { if (isPublic && key.byteLength !== this._nPk) { reject(new Error("Invalid length of the key")); } if (!isPublic && (key.byteLength !== this._nSk)) { reject(new Error("Invalid length of the key")); } resolve(new XCryptoKey(ALG_NAME, new Uint8Array(key), isPublic ? "public" : "private", isPublic ? [] : KEM_USAGES)); }); } _importJWK(key, isPublic) { return new Promise((resolve, reject) => { if (key.kty !== "OKP") { reject(new Error(`Invalid kty: ${key.kty}`)); } if (key.crv !== "X448") { reject(new Error(`Invalid crv: ${key.crv}`)); } if (isPublic) { if (typeof key.d !== "undefined") { reject(new Error("Invalid key: `d` should not be set")); } if (typeof key.x !== "string") { reject(new Error("Invalid key: `x` not found")); } resolve(new XCryptoKey(ALG_NAME, base64UrlToBytes(key.x), "public")); } else { if (typeof key.d !== "string") { reject(new Error("Invalid key: `d` not found")); } resolve(new XCryptoKey(ALG_NAME, base64UrlToBytes(key.d), "private", KEM_USAGES)); } }); } _derivePublicKey(k) { return new Promise((resolve, reject) => { try { const pk = x448.getPublicKey(k.key); resolve(new XCryptoKey(ALG_NAME, pk, "public")); } catch (e) { reject(e); } }); } _dh(sk, pk) { return new Promise((resolve, reject) => { try { resolve(x448.getSharedSecret(sk.key, pk.key).buffer); } catch (e) { reject(e); } }); } }