UNPKG

@gaonengwww/jose

Version:

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

436 lines (428 loc) 12.9 kB
// src/lib/buffer_utils.ts var encoder = new TextEncoder(); var decoder = new TextDecoder(); var MAX_INT32 = 2 ** 32; // src/lib/base64.ts function encodeBase64(input) { if (Uint8Array.prototype.toBase64) { return input.toBase64(); } const CHUNK_SIZE = 32768; const arr = []; for (let i = 0; i < input.length; i += CHUNK_SIZE) { arr.push(String.fromCharCode.apply(null, input.subarray(i, i + CHUNK_SIZE))); } return btoa(arr.join("")); } 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"; }; // src/lib/asn1.ts var formatPEM = (b64, descriptor) => { const newlined = (b64.match(/.{1,64}/g) || []).join("\n"); return `-----BEGIN ${descriptor}----- ${newlined} -----END ${descriptor}-----`; }; var findOid = (keyData, oid, from = 0) => { if (from === 0) { oid.unshift(oid.length); oid.unshift(6); } const i = keyData.indexOf(oid[0], from); if (i === -1) return false; const sub = keyData.subarray(i, i + oid.length); if (sub.length !== oid.length) return false; return sub.every((value, index) => value === oid[index]) || findOid(keyData, oid, i + 1); }; var getNamedCurve = (keyData) => { switch (true) { case findOid(keyData, [42, 134, 72, 206, 61, 3, 1, 7]): return "P-256"; case findOid(keyData, [43, 129, 4, 0, 34]): return "P-384"; case findOid(keyData, [43, 129, 4, 0, 35]): return "P-521"; default: return void 0; } }; var genericImport = async (replace, keyFormat, pem, alg, options) => { let algorithm; let keyUsages; const keyData = new Uint8Array( atob(pem.replace(replace, "")).split("").map((c) => c.charCodeAt(0)) ); const isPublic = keyFormat === "spki"; switch (alg) { case "PS256": case "PS384": case "PS512": algorithm = { name: "RSA-PSS", hash: `SHA-${alg.slice(-3)}` }; keyUsages = isPublic ? ["verify"] : ["sign"]; break; case "RS256": case "RS384": case "RS512": algorithm = { name: "RSASSA-PKCS1-v1_5", hash: `SHA-${alg.slice(-3)}` }; keyUsages = isPublic ? ["verify"] : ["sign"]; break; case "RSA-OAEP": case "RSA-OAEP-256": case "RSA-OAEP-384": case "RSA-OAEP-512": algorithm = { name: "RSA-OAEP", hash: `SHA-${parseInt(alg.slice(-3), 10) || 1}` }; keyUsages = isPublic ? ["encrypt", "wrapKey"] : ["decrypt", "unwrapKey"]; break; case "ES256": algorithm = { name: "ECDSA", namedCurve: "P-256" }; keyUsages = isPublic ? ["verify"] : ["sign"]; break; case "ES384": algorithm = { name: "ECDSA", namedCurve: "P-384" }; keyUsages = isPublic ? ["verify"] : ["sign"]; break; case "ES512": algorithm = { name: "ECDSA", namedCurve: "P-521" }; keyUsages = isPublic ? ["verify"] : ["sign"]; break; case "ECDH-ES": case "ECDH-ES+A128KW": case "ECDH-ES+A192KW": case "ECDH-ES+A256KW": { const namedCurve = getNamedCurve(keyData); algorithm = namedCurve?.startsWith("P-") ? { name: "ECDH", namedCurve } : { name: "X25519" }; keyUsages = isPublic ? [] : ["deriveBits"]; break; } case "Ed25519": // Fall through case "EdDSA": algorithm = { name: "Ed25519" }; keyUsages = isPublic ? ["verify"] : ["sign"]; break; default: throw new JOSENotSupported('Invalid or unsupported "alg" (Algorithm) value'); } return crypto.subtle.importKey( keyFormat, keyData, algorithm, options?.extractable ?? (isPublic ? true : false), keyUsages ); }; var fromPKCS8 = (pem, alg, options) => { return genericImport(/(?:-----(?:BEGIN|END) PRIVATE KEY-----|\s)/g, "pkcs8", pem, alg, options); }; var fromSPKI = (pem, alg, options) => { return genericImport(/(?:-----(?:BEGIN|END) PUBLIC KEY-----|\s)/g, "spki", pem, alg, options); }; function getElement(seq) { const result = []; let next = 0; while (next < seq.length) { const nextPart = parseElement(seq.subarray(next)); result.push(nextPart); next += nextPart.byteLength; } return result; } function parseElement(bytes) { let position = 0; let tag = bytes[0] & 31; position++; if (tag === 31) { tag = 0; while (bytes[position] >= 128) { tag = tag * 128 + bytes[position] - 128; position++; } tag = tag * 128 + bytes[position] - 128; position++; } let length = 0; if (bytes[position] < 128) { length = bytes[position]; position++; } else if (length === 128) { length = 0; while (bytes[position + length] !== 0 || bytes[position + length + 1] !== 0) { if (length > bytes.byteLength) { throw new TypeError("invalid indefinite form length"); } length++; } const byteLength2 = position + length + 2; return { byteLength: byteLength2, contents: bytes.subarray(position, position + length), raw: bytes.subarray(0, byteLength2) }; } else { const numberOfDigits = bytes[position] & 127; position++; length = 0; for (let i = 0; i < numberOfDigits; i++) { length = length * 256 + bytes[position]; position++; } } const byteLength = position + length; return { byteLength, contents: bytes.subarray(position, byteLength), raw: bytes.subarray(0, byteLength) }; } function spkiFromX509(buf) { const tbsCertificate = getElement(getElement(parseElement(buf).contents)[0].contents); return encodeBase64(tbsCertificate[tbsCertificate[0].raw[0] === 160 ? 6 : 5].raw); } var createPublicKey; function getSPKI(x509) { try { createPublicKey ??= globalThis.process?.getBuiltinModule?.("node:crypto")?.createPublicKey; } catch { createPublicKey = 0; } if (createPublicKey) { try { return new createPublicKey(x509).export({ format: "pem", type: "spki" }); } catch { } } const pem = x509.replace(/(?:-----(?:BEGIN|END) CERTIFICATE-----|\s)/g, ""); const raw = decodeBase64(pem); return formatPEM(spkiFromX509(raw), "PUBLIC KEY"); } var fromX509 = (pem, alg, options) => { let spki; try { spki = getSPKI(pem); } catch (cause) { throw new TypeError("Failed to parse the X.509 certificate", { cause }); } return fromSPKI(spki, alg, 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 importSPKI(spki, alg, options) { if (typeof spki !== "string" || spki.indexOf("-----BEGIN PUBLIC KEY-----") !== 0) { throw new TypeError('"spki" must be SPKI formatted string'); } return fromSPKI(spki, alg, options); } async function importX509(x509, alg, options) { if (typeof x509 !== "string" || x509.indexOf("-----BEGIN CERTIFICATE-----") !== 0) { throw new TypeError('"x509" must be X.509 formatted string'); } return fromX509(x509, alg, options); } async function importPKCS8(pkcs8, alg, options) { if (typeof pkcs8 !== "string" || pkcs8.indexOf("-----BEGIN PRIVATE KEY-----") !== 0) { throw new TypeError('"pkcs8" must be PKCS#8 formatted string'); } return fromPKCS8(pkcs8, alg, options); } 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'); } } export { importJWK, importPKCS8, importSPKI, importX509 };