UNPKG

@gaonengwww/jose

Version:

JWA, JWS, JWE, JWT, JWK, JWKS for Node.js, Browser, Cloudflare Workers, Deno, Bun, and other Web-interoperable runtimes

356 lines (348 loc) 10.4 kB
// src/lib/buffer_utils.ts var encoder = new TextEncoder(); var decoder = new TextDecoder(); var MAX_INT32 = 2 ** 32; // src/lib/base64.ts function decodeBase64(encoded) { if (Uint8Array.fromBase64) { return Uint8Array.fromBase64(encoded); } const binary = atob(encoded); const bytes = new Uint8Array(binary.length); for (let i = 0; i < binary.length; i++) { bytes[i] = binary.charCodeAt(i); } return bytes; } // src/util/base64url.ts function decode(input) { if (Uint8Array.fromBase64) { return Uint8Array.fromBase64(typeof input === "string" ? input : decoder.decode(input), { alphabet: "base64url" }); } let encoded = input; if (encoded instanceof Uint8Array) { encoded = decoder.decode(encoded); } encoded = encoded.replace(/-/g, "+").replace(/_/g, "/").replace(/\s/g, ""); try { return decodeBase64(encoded); } catch { throw new TypeError("The input to be decoded is not correctly encoded."); } } // src/util/errors.ts var JOSEError = class extends Error { /** * A unique error code for the particular error subclass. * * @ignore */ static code = "ERR_JOSE_GENERIC"; /** A unique error code for {@link JOSEError}. */ code = "ERR_JOSE_GENERIC"; /** @ignore */ constructor(message, options) { super(message, options); this.name = this.constructor.name; Error.captureStackTrace?.(this, this.constructor); } }; var JOSENotSupported = class extends JOSEError { /** @ignore */ static code = "ERR_JOSE_NOT_SUPPORTED"; /** A unique error code for {@link JOSENotSupported}. */ code = "ERR_JOSE_NOT_SUPPORTED"; }; var JWKSInvalid = class extends JOSEError { /** @ignore */ static code = "ERR_JWKS_INVALID"; /** A unique error code for {@link JWKSInvalid}. */ code = "ERR_JWKS_INVALID"; }; var JWKSNoMatchingKey = class extends JOSEError { /** @ignore */ static code = "ERR_JWKS_NO_MATCHING_KEY"; /** A unique error code for {@link JWKSNoMatchingKey}. */ code = "ERR_JWKS_NO_MATCHING_KEY"; /** @ignore */ constructor(message = "no applicable key found in the JSON Web Key Set", options) { super(message, options); } }; var JWKSMultipleMatchingKeys = class extends JOSEError { /** @ignore */ [Symbol.asyncIterator]; /** @ignore */ static code = "ERR_JWKS_MULTIPLE_MATCHING_KEYS"; /** A unique error code for {@link JWKSMultipleMatchingKeys}. */ code = "ERR_JWKS_MULTIPLE_MATCHING_KEYS"; /** @ignore */ constructor(message = "multiple matching keys found in the JSON Web Key Set", options) { super(message, options); } }; // src/lib/jwk_to_key.ts function subtleMapping(jwk) { let algorithm; let keyUsages; switch (jwk.kty) { case "RSA": { switch (jwk.alg) { case "PS256": case "PS384": case "PS512": algorithm = { name: "RSA-PSS", hash: `SHA-${jwk.alg.slice(-3)}` }; keyUsages = jwk.d ? ["sign"] : ["verify"]; break; case "RS256": case "RS384": case "RS512": algorithm = { name: "RSASSA-PKCS1-v1_5", hash: `SHA-${jwk.alg.slice(-3)}` }; keyUsages = jwk.d ? ["sign"] : ["verify"]; break; case "RSA-OAEP": case "RSA-OAEP-256": case "RSA-OAEP-384": case "RSA-OAEP-512": algorithm = { name: "RSA-OAEP", hash: `SHA-${parseInt(jwk.alg.slice(-3), 10) || 1}` }; keyUsages = jwk.d ? ["decrypt", "unwrapKey"] : ["encrypt", "wrapKey"]; break; default: throw new JOSENotSupported('Invalid or unsupported JWK "alg" (Algorithm) Parameter value'); } break; } case "EC": { switch (jwk.alg) { case "ES256": algorithm = { name: "ECDSA", namedCurve: "P-256" }; keyUsages = jwk.d ? ["sign"] : ["verify"]; break; case "ES384": algorithm = { name: "ECDSA", namedCurve: "P-384" }; keyUsages = jwk.d ? ["sign"] : ["verify"]; break; case "ES512": algorithm = { name: "ECDSA", namedCurve: "P-521" }; keyUsages = jwk.d ? ["sign"] : ["verify"]; break; case "ECDH-ES": case "ECDH-ES+A128KW": case "ECDH-ES+A192KW": case "ECDH-ES+A256KW": algorithm = { name: "ECDH", namedCurve: jwk.crv }; keyUsages = jwk.d ? ["deriveBits"] : []; break; default: throw new JOSENotSupported('Invalid or unsupported JWK "alg" (Algorithm) Parameter value'); } break; } case "OKP": { switch (jwk.alg) { case "Ed25519": // Fall through case "EdDSA": algorithm = { name: "Ed25519" }; keyUsages = jwk.d ? ["sign"] : ["verify"]; break; case "ECDH-ES": case "ECDH-ES+A128KW": case "ECDH-ES+A192KW": case "ECDH-ES+A256KW": algorithm = { name: jwk.crv }; keyUsages = jwk.d ? ["deriveBits"] : []; break; default: throw new JOSENotSupported('Invalid or unsupported JWK "alg" (Algorithm) Parameter value'); } break; } default: throw new JOSENotSupported('Invalid or unsupported JWK "kty" (Key Type) Parameter value'); } return { algorithm, keyUsages }; } var jwk_to_key_default = async (jwk) => { if (!jwk.alg) { throw new TypeError('"alg" argument is required when "jwk.alg" is not present'); } const { algorithm, keyUsages } = subtleMapping(jwk); const keyData = { ...jwk }; delete keyData.alg; delete keyData.use; return crypto.subtle.importKey( "jwk", keyData, algorithm, jwk.ext ?? (jwk.d ? false : true), jwk.key_ops ?? keyUsages ); }; // src/lib/is_object.ts function isObjectLike(value) { return typeof value === "object" && value !== null; } var is_object_default = (input) => { if (!isObjectLike(input) || Object.prototype.toString.call(input) !== "[object Object]") { return false; } if (Object.getPrototypeOf(input) === null) { return true; } let proto = input; while (Object.getPrototypeOf(proto) !== null) { proto = Object.getPrototypeOf(proto); } return Object.getPrototypeOf(input) === proto; }; // src/key/import.ts async function importJWK(jwk, alg, options) { if (!is_object_default(jwk)) { throw new TypeError("JWK must be an object"); } let ext; alg ??= jwk.alg; ext ??= options?.extractable ?? jwk.ext; switch (jwk.kty) { case "oct": if (typeof jwk.k !== "string" || !jwk.k) { throw new TypeError('missing "k" (Key Value) Parameter value'); } return decode(jwk.k); case "RSA": if ("oth" in jwk && jwk.oth !== void 0) { throw new JOSENotSupported( 'RSA JWK "oth" (Other Primes Info) Parameter value is not supported' ); } case "EC": case "OKP": return jwk_to_key_default({ ...jwk, alg, ext }); default: throw new JOSENotSupported('Unsupported "kty" (Key Type) Parameter value'); } } // src/jwks/local.ts function getKtyFromAlg(alg) { switch (typeof alg === "string" && alg.slice(0, 2)) { case "RS": case "PS": return "RSA"; case "ES": return "EC"; case "Ed": return "OKP"; default: throw new JOSENotSupported('Unsupported "alg" value for a JSON Web Key Set'); } } function isJWKSLike(jwks) { return jwks && typeof jwks === "object" && // @ts-expect-error Array.isArray(jwks.keys) && // @ts-expect-error jwks.keys.every(isJWKLike); } function isJWKLike(key) { return is_object_default(key); } var LocalJWKSet = class { #jwks; #cached = /* @__PURE__ */ new WeakMap(); constructor(jwks) { if (!isJWKSLike(jwks)) { throw new JWKSInvalid("JSON Web Key Set malformed"); } this.#jwks = structuredClone(jwks); } jwks() { return this.#jwks; } async getKey(protectedHeader, token) { const { alg, kid } = { ...protectedHeader, ...token?.header }; const kty = getKtyFromAlg(alg); const candidates = this.#jwks.keys.filter((jwk2) => { let candidate = kty === jwk2.kty; if (candidate && typeof kid === "string") { candidate = kid === jwk2.kid; } if (candidate && typeof jwk2.alg === "string") { candidate = alg === jwk2.alg; } if (candidate && typeof jwk2.use === "string") { candidate = jwk2.use === "sig"; } if (candidate && Array.isArray(jwk2.key_ops)) { candidate = jwk2.key_ops.includes("verify"); } if (candidate) { switch (alg) { case "ES256": candidate = jwk2.crv === "P-256"; break; case "ES384": candidate = jwk2.crv === "P-384"; break; case "ES512": candidate = jwk2.crv === "P-521"; break; case "Ed25519": // Fall through case "EdDSA": candidate = jwk2.crv === "Ed25519"; break; } } return candidate; }); const { 0: jwk, length } = candidates; if (length === 0) { throw new JWKSNoMatchingKey(); } if (length !== 1) { const error = new JWKSMultipleMatchingKeys(); const _cached = this.#cached; error[Symbol.asyncIterator] = async function* () { for (const jwk2 of candidates) { try { yield await importWithAlgCache(_cached, jwk2, alg); } catch { } } }; throw error; } return importWithAlgCache(this.#cached, jwk, alg); } }; async function importWithAlgCache(cache, jwk, alg) { const cached = cache.get(jwk) || cache.set(jwk, {}).get(jwk); if (cached[alg] === void 0) { const key = await importJWK({ ...jwk, ext: true }, alg); if (key instanceof Uint8Array || key.type !== "public") { throw new JWKSInvalid("JSON Web Key Set members must be public keys"); } cached[alg] = key; } return cached[alg]; } function createLocalJWKSet(jwks) { const set = new LocalJWKSet(jwks); const localJWKSet = async (protectedHeader, token) => set.getKey(protectedHeader, token); Object.defineProperties(localJWKSet, { jwks: { value: () => structuredClone(set.jwks()), enumerable: false, configurable: false, writable: false } }); return localJWKSet; } export { createLocalJWKSet };