jose
Version:
JWA, JWS, JWE, JWT, JWK, JWKS for Node.js, Browser, Cloudflare Workers, Deno, Bun, and other Web-interoperable runtimes
61 lines (60 loc) • 2.85 kB
JavaScript
import { jwkToKey, snapshotJwk } from "../lib/key.js";
import { JWS } from "../lib/jws_algorithms.js";
import { JWKSInvalid, JOSENotSupported, JWKSNoMatchingKey, JWKSMultipleMatchingKeys } from "../util/errors.js";
import { isJwkSet } from "../lib/validate.js";
function isUsableJWK(jwk, entry, alg, kid) {
const { kty, key_ops: keyOps, ext, kid: jwkKid, alg: jwkAlg, use, crv } = jwk;
return (ext === void 0 || typeof ext == "boolean") && (keyOps === void 0 || Array.isArray(keyOps) && keyOps.every((operation, index) => typeof operation == "string" && keyOps.indexOf(operation) === index) && keyOps.includes("verify")) && entry.kty.includes(kty) && (kid === void 0 || typeof kid == "string" && kid === jwkKid) && (jwkAlg === void 0 ? kty !== "AKP" : alg === jwkAlg) && (use === void 0 || use === "sig") && (!entry.crv || crv === entry.crv);
}
async function importWithAlgCache(cache, jwk, entry) {
const cached = cache.get(jwk) || cache.set(jwk, {}).get(jwk), { alg } = entry;
if (cached[alg] === void 0) {
const pending = jwkToKey(entry, jwk, !0).then((key) => {
if (key.type !== "public")
throw new JWKSInvalid("JSON Web Key Set members must be public keys");
return cached[alg] = key, key;
}).catch((error) => {
throw cached[alg] === pending && delete cached[alg], error;
});
cached[alg] = pending;
}
return cached[alg];
}
function createLocalJWKSet(jwks) {
let snapshot;
try {
snapshot = structuredClone(jwks);
} catch (cause) {
throw new JWKSInvalid("JSON Web Key Set malformed", { cause });
}
if (!isJwkSet(snapshot))
throw new JWKSInvalid("JSON Web Key Set malformed");
const metadata = snapshot.keys.map((jwk) => {
const normalized = snapshotJwk(jwk);
return Array.isArray(normalized.key_ops) && (normalized.key_ops = [...normalized.key_ops]), normalized;
}), cached = /* @__PURE__ */ new WeakMap();
return Object.defineProperty(async (protectedHeader, token) => {
const { alg, kid } = { ...protectedHeader, ...token?.header }, entry = typeof alg == "string" ? JWS[alg] : void 0;
if (!entry || entry.secret)
throw new JOSENotSupported('Unsupported "alg" value for a JSON Web Key Set');
const candidates = snapshot.keys.filter((_, index) => isUsableJWK(metadata[index], entry, alg, kid)), { 0: jwk, length } = candidates;
if (!length)
throw new JWKSNoMatchingKey();
if (length !== 1) {
const error = new JWKSMultipleMatchingKeys();
throw error[Symbol.asyncIterator] = async function* () {
for (const jwk2 of candidates)
try {
yield await importWithAlgCache(cached, jwk2, entry);
} catch {
}
}, error;
}
return importWithAlgCache(cached, jwk, entry);
}, "jwks", {
value: () => structuredClone(snapshot)
});
}
export {
createLocalJWKSet
};