@hpke/dhkem-x448
Version:
A Hybrid Public Key Encryption (HPKE) module extension for X448
184 lines (183 loc) • 5.76 kB
JavaScript
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);
}
});
}
}