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
JavaScript
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
};