UNPKG

jose

Version:

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

139 lines (138 loc) • 5.87 kB
import { invalidKeyInput, isCryptoKey, isKeyObject, validateExtractableOption } from "./key.js"; import { encodeBase64, decodeBase64 } from "./buffer_utils.js"; import { JOSENotSupported } from "../util/errors.js"; import { keyAlgorithm, unsupportedAlg, algArgument } from "./key_algorithm.js"; const formatPEM = (b64, descriptor) => { const newlined = (b64.match(/.{1,64}/g) || []).join(` `); return `-----BEGIN ${descriptor}----- ${newlined} -----END ${descriptor}-----`; }, genericExport = async (keyType, keyFormat, key) => { if (isKeyObject(key)) { if (key.type !== keyType) throw new TypeError(`key is not a ${keyType} key`); return key.export({ format: "pem", type: keyFormat }); } if (!isCryptoKey(key)) throw new TypeError(invalidKeyInput(key, "CryptoKey", "KeyObject")); if (!key.extractable) throw new TypeError("CryptoKey is not extractable"); if (key.type !== keyType) throw new TypeError(`key is not a ${keyType} key`); return formatPEM(encodeBase64(new Uint8Array(await crypto.subtle.exportKey(keyFormat, key))), `${keyType.toUpperCase()} KEY`); }, toSPKI = (key) => genericExport("public", "spki", key), toPKCS8 = (key) => genericExport("private", "pkcs8", key), bytesEqual = (a, b) => { if (a.byteLength !== b.length) return !1; for (let i = 0; i < a.byteLength; i++) if (a[i] !== b[i]) return !1; return !0; }, createASN1State = (data) => ({ data, pos: 0 }), readByte = (state) => { const byte = state.data[state.pos++]; if (byte === void 0) throw new Error("Unexpected end of ASN.1 input"); return byte; }, parseLength = (state) => { const first = readByte(state); if (first & 128) { const lengthOfLen = first & 127; let length = 0; for (let i = 0; i < lengthOfLen; i++) length = length << 8 | readByte(state); return length; } return first; }, skipElement = (state, count = 1) => { for (; count-- > 0; ) { state.pos++; const length = parseLength(state); state.pos += length; } }, expectTag = (state, expectedTag, errorMessage) => { if (readByte(state) !== expectedTag) throw new Error(errorMessage); }, getSubarray = (state, length) => { if (length < 0 || state.pos + length > state.data.length) throw new Error("Unexpected end of ASN.1 input"); const result = state.data.subarray(state.pos, state.pos + length); return state.pos += length, result; }, parseAlgorithmOID = (state) => { expectTag(state, 6, "Expected algorithm OID"); const oidLen = parseLength(state); return getSubarray(state, oidLen); }; function parseKeyHeader(state, keyFormat) { if (expectTag(state, 48, `Invalid ${keyFormat === "spki" ? "SPKI" : "PKCS#8"} structure`), parseLength(state), keyFormat === "pkcs8") { expectTag(state, 2, "Expected version field"); const length = parseLength(state); state.pos += length; } expectTag(state, 48, "Expected algorithm identifier"), parseLength(state); } const parseECAlgorithmIdentifier = (state) => { const algOid = parseAlgorithmOID(state); if (bytesEqual(algOid, [43, 101, 110])) return "X25519"; if (!bytesEqual(algOid, [42, 134, 72, 206, 61, 2, 1])) throw new Error("Unsupported key algorithm"); expectTag(state, 6, "Expected curve OID"); const curveOidLen = parseLength(state), curveOid = getSubarray(state, curveOidLen); if (bytesEqual(curveOid, [42, 134, 72, 206, 61, 3, 1, 7])) return "P-256"; if (bytesEqual(curveOid, [43, 129, 4, 0, 34])) return "P-384"; if (bytesEqual(curveOid, [43, 129, 4, 0, 35])) return "P-521"; throw new Error("Unsupported named curve"); }, genericImport = async (keyFormat, keyData, alg, options) => { const extractable = validateExtractableOption(options?.extractable), entry = keyAlgorithm(alg, algArgument); entry.secret && unsupportedAlg(algArgument); const isPublic = keyFormat === "spki"; let algorithm; if (entry.resolve) try { const state = createASN1State(keyData); parseKeyHeader(state, keyFormat), algorithm = entry.resolve({ crv: parseECAlgorithmIdentifier(state) }); } catch (cause) { throw new JOSENotSupported("Invalid or unsupported key format", { cause }); } else algorithm = entry.subtle; return crypto.subtle.importKey(keyFormat, keyData, algorithm, extractable ?? isPublic, entry.usages[isPublic ? 0 : 1]); }, processPEMData = (pem, pattern) => decodeBase64(pem.replace(pattern, "")), fromPKCS8 = (pem, alg, options) => { const keyData = processPEMData(pem, /(?:-----(?:BEGIN|END) PRIVATE KEY-----|\s)/g); return genericImport("pkcs8", keyData, alg, options); }, fromSPKI = (pem, alg, options) => { const keyData = processPEMData(pem, /(?:-----(?:BEGIN|END) PUBLIC KEY-----|\s)/g); return genericImport("spki", keyData, alg, options); }; function spkiFromX509(buf) { const state = createASN1State(buf); expectTag(state, 48, "Invalid certificate structure"); const certificateLength = parseLength(state); if (certificateLength < 0 || state.pos + certificateLength > state.data.length) throw new Error("Unexpected end of ASN.1 input"); expectTag(state, 48, "Invalid tbsCertificate structure"), parseLength(state), buf[state.pos] === 160 ? skipElement(state, 6) : skipElement(state, 5); const spkiStart = state.pos; expectTag(state, 48, "Invalid SPKI structure"); const spkiContentLen = parseLength(state); return buf.subarray(spkiStart, spkiStart + spkiContentLen + (state.pos - spkiStart)); } const fromX509 = (pem, alg, options) => { let spki; try { const certificate = processPEMData(pem, /(?:-----(?:BEGIN|END) CERTIFICATE-----|\s)/g); spki = spkiFromX509(certificate); } catch (cause) { throw new TypeError("Failed to parse the X.509 certificate", { cause }); } return genericImport("spki", spki, alg, options); }; export { fromPKCS8, fromSPKI, fromX509, toPKCS8, toSPKI };