UNPKG

jose

Version:

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

156 lines (155 loc) • 7.86 kB
import { isObject } from "./validate.js"; import { decode } from "../util/base64url.js"; import { JOSENotSupported } from "../util/errors.js"; const tag = (key) => key[Symbol.toStringTag], jwkMatchesOp = (entry, key, usage) => { const { alg } = entry; if (key.use !== void 0) { const expected = usage === "sign" || usage === "verify" ? "sig" : "enc"; if (key.use !== expected) throw new TypeError(`Invalid key for this operation, its "use" must be "${expected}" when present`); } if (key.alg !== void 0 && key.alg !== alg) throw new TypeError(`Invalid key for this operation, its "alg" must be "${alg}" when present`); if (Array.isArray(key.key_ops)) { const expectedKeyOp = usage === "encrypt" || usage === "decrypt" ? entry.ops?.[usage === "encrypt" ? 0 : 1] : usage; if (expectedKeyOp && !key.key_ops.includes(expectedKeyOp)) throw new TypeError(`Invalid key for this operation, its "key_ops" must include "${expectedKeyOp}" when present`); } }; async function prepareKey(entry, key, usage) { const { alg, secret } = entry, requiresPrivateKey = usage === "decrypt" || usage === "sign"; if (secret && key instanceof Uint8Array) return key; let normalized, keyObject; if (isObject(key)) { if (normalized = normalizeJwk(key), typeof normalized.kty != "string") throw invalidKeyType(alg, key, secret); if (!(secret ? normalized.kty === "oct" && typeof normalized.k == "string" : normalized.kty !== "oct" && (requiresPrivateKey ? normalized.kty === "AKP" && typeof normalized.priv == "string" || typeof normalized.d == "string" : normalized.d === void 0 && normalized.priv === void 0))) throw new TypeError(secret ? 'JSON Web Key for symmetric algorithms must have JWK "kty" (Key Type) equal to "oct" and the JWK "k" (Key Value) present' : `JSON Web Key for this operation must be a ${requiresPrivateKey ? "private" : "public"} JWK`); if (jwkMatchesOp(entry, normalized, usage), normalized.kty === "oct") return decode(normalized.k); if (!Object.isFrozen(key)) { const { key_ops } = key; Array.isArray(key_ops) && Object.freeze(key_ops), Object.freeze(key); } } else { if (!isKeyLike(key)) throw invalidKeyType(alg, key, secret); const expectedType = secret ? "secret" : requiresPrivateKey ? "private" : "public"; if (key.type !== expectedType && (secret || ["secret", "public", "private"].includes(key.type))) throw new TypeError(`${tag(key)} instances must be of type "${expectedType}" for the ${alg} algorithm`); if (isCryptoKey(key)) return key; if (keyObject = key, keyObject.type === "secret") return keyObject.export(); } cache ||= /* @__PURE__ */ new WeakMap(); const cacheKey = key; let cached = cache.get(cacheKey); if (cached?.[alg]) return cached[alg]; if (cached || cache.set(cacheKey, cached = {}), keyObject && typeof keyObject.toCryptoKey == "function") { const isPublic = keyObject.type === "public", crv = nist[keyObject.asymmetricKeyDetails?.namedCurve], params = entry.resolve?.({ crv, asymmetricKeyType: keyObject.asymmetricKeyType }) ?? entry.subtle; return cached[alg] = keyObject.toCryptoKey(params, isPublic, entry.usages[isPublic ? 0 : 1]); } return normalized ??= keyObject.export({ format: "jwk" }), normalized.alg = alg, cached[alg] = await jwkToKey(entry, normalized); } let cache; const nist = { __proto__: null, prime256v1: "P-256", secp384r1: "P-384", secp521r1: "P-521" }; function assertCryptoKey(key) { if (!isCryptoKey(key)) throw new Error("CryptoKey instance expected"); } const isCryptoKey = (key) => { if (key?.[Symbol.toStringTag] === "CryptoKey") return !0; try { return key instanceof CryptoKey; } catch { return !1; } }, isKeyObject = (key) => key?.[Symbol.toStringTag] === "KeyObject", isKeyLike = (key) => isCryptoKey(key) || isKeyObject(key); function message(msg, actual, ...types) { if (types.length > 2) { const last = types.pop(); msg += `one of type ${types.join(", ")}, or ${last}.`; } else types.length === 2 ? msg += `one of type ${types[0]} or ${types[1]}.` : msg += `of type ${types[0]}.`; return actual == null ? msg += ` Received ${actual}` : typeof actual == "function" && actual.name ? msg += ` Received function ${actual.name}` : typeof actual == "object" && actual != null && actual.constructor?.name && (msg += ` Received an instance of ${actual.constructor.name}`), msg; } const invalidKeyInput = (actual, ...types) => message("Key must be ", actual, ...types); function invalidKeyType(alg, actual, secret) { const types = ["CryptoKey", "KeyObject", "JSON Web Key"]; return secret && types.push("Uint8Array"), new TypeError(message(`Key for the ${alg} algorithm must be `, actual, ...types)); } const unusable = (name, prop = "algorithm.name") => new TypeError(`CryptoKey does not support this operation, its ${prop} must be ${name}`); function checkUsage(key, usage) { if (usage && !key.usages.includes(usage)) throw new TypeError(`CryptoKey does not support this operation, its usages must include ${usage}.`); } function checkModulusLength(alg, key) { const { modulusLength } = key.algorithm; if (typeof modulusLength != "number" || modulusLength < 2048) throw new TypeError(`${alg} requires key modulusLength to be 2048 bits or larger`); } function checkCryptoKey(key, expected, usage) { const algorithm = key.algorithm; if (algorithm.name !== expected.name) throw unusable(expected.name); if (expected.hash && algorithm.hash?.name !== expected.hash) throw unusable(expected.hash, "algorithm.hash"); if (expected.namedCurve && algorithm.namedCurve !== expected.namedCurve) throw unusable(expected.namedCurve, "algorithm.namedCurve"); if (expected.length !== void 0 && algorithm.length !== expected.length) throw unusable(expected.length, "algorithm.length"); checkUsage(key, usage); } function snapshotJwk(jwk) { return { __proto__: null, ...jwk }; } function normalizeJwk(jwk) { const normalized = snapshotJwk(jwk); if (normalized.ext !== void 0 && typeof normalized.ext != "boolean") throw new TypeError('"ext" (Extractable) Parameter must be a boolean'); if (normalized.key_ops !== void 0) { const value = normalized.key_ops, keyOps = Array.isArray(value) ? [...value] : void 0; if (!keyOps || keyOps.some((operation) => typeof operation != "string") || new Set(keyOps).size !== keyOps.length) throw new TypeError('"key_ops" (Key Operations) Parameter must be an array of unique strings'); normalized.key_ops = keyOps; } return normalized; } function validateExtractableOption(extractable) { if (extractable !== void 0 && typeof extractable != "boolean") throw new TypeError('"extractable" option must be a boolean'); return extractable; } async function jwkToKey(entry, jwk, extractable) { if (!entry.kty.includes(jwk.kty)) throw new JOSENotSupported('Invalid or unsupported JWK "alg" (Algorithm) Parameter value'); const algorithm = entry.resolve?.({ kty: jwk.kty, crv: jwk.crv }) ?? entry.subtle, isPrivate = !!(jwk.d || jwk.priv), keyData = { ...jwk, ext: extractable ?? jwk.ext }; return keyData.kty !== "AKP" && delete keyData.alg, delete keyData.use, crypto.subtle.importKey("jwk", keyData, algorithm, keyData.ext ?? !isPrivate, jwk.key_ops ?? entry.usages[isPrivate ? 1 : 0]); } async function rawKey(key, expected, usage, extractable = !1) { return key instanceof Uint8Array && (key = await crypto.subtle.importKey("raw", key, expected, extractable, [usage])), checkCryptoKey(key, expected, usage), key; } export { assertCryptoKey, checkCryptoKey, checkModulusLength, checkUsage, invalidKeyInput, isCryptoKey, isKeyLike, isKeyObject, jwkToKey, normalizeJwk, prepareKey, rawKey, snapshotJwk, validateExtractableOption };