UNPKG

@sphereon/oid4vc-common

Version:

OpenID 4 Verifiable Credentials Common

473 lines (463 loc) 17.5 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // lib/index.ts var index_exports = {}; __export(index_exports, { BASE64_URL_REGEX: () => BASE64_URL_REGEX, SigningAlgo: () => SigningAlgo, VCI_LOGGERS: () => VCI_LOGGERS, VCI_LOG_COMMON: () => VCI_LOG_COMMON, base64ToHexString: () => base64ToHexString, base64urlEncodeBuffer: () => base64urlEncodeBuffer, base64urlToString: () => base64urlToString, calculateJwkThumbprint: () => calculateJwkThumbprint, calculateJwkThumbprintUri: () => calculateJwkThumbprintUri, checkExp: () => checkExp, createDPoP: () => createDPoP, decodeJwt: () => decodeJwt, decodeProtectedHeader: () => decodeProtectedHeader, defaultHasher: () => defaultHasher, dpopTokenRequestNonceError: () => dpopTokenRequestNonceError, epochTime: () => epochTime, fromBase64: () => fromBase64, getCreateDPoPOptions: () => getCreateDPoPOptions, getDidJwtVerifier: () => getDidJwtVerifier, getDigestAlgorithmFromJwkThumbprintUri: () => getDigestAlgorithmFromJwkThumbprintUri, getJwkVerifier: () => getJwkVerifier, getJwtVerifierWithContext: () => getJwtVerifierWithContext, getNowSkewed: () => getNowSkewed, getX5cVerifier: () => getX5cVerifier, isJwe: () => isJwe, isJws: () => isJws, parseJWT: () => parseJWT, uuidv4: () => import_uuid2.v4, verifyDPoP: () => verifyDPoP, verifyResourceDPoP: () => verifyResourceDPoP }); module.exports = __toCommonJS(index_exports); var import_ssi_types2 = require("@sphereon/ssi-types"); // lib/jwt/Jwt.types.ts var SigningAlgo = /* @__PURE__ */ (function(SigningAlgo2) { SigningAlgo2["EDDSA"] = "EdDSA"; SigningAlgo2["RS256"] = "RS256"; SigningAlgo2["PS256"] = "PS256"; SigningAlgo2["ES256"] = "ES256"; SigningAlgo2["ES256K"] = "ES256K"; return SigningAlgo2; })({}); // lib/jwt/JwkThumbprint.ts var u8a = __toESM(require("uint8arrays"), 1); // lib/hasher.ts var import_ssi_types = require("@sphereon/ssi-types"); var defaultHasher = /* @__PURE__ */ __name((data, algorithm) => { return (0, import_ssi_types.shaHasher)(data, algorithm); }, "defaultHasher"); // lib/jwt/JwkThumbprint.ts var { toString } = u8a; var check = /* @__PURE__ */ __name((value, description) => { if (typeof value !== "string" || !value) { throw Error(`${description} missing or invalid`); } }, "check"); async function calculateJwkThumbprint(jwk, digestAlgorithm) { if (!jwk || typeof jwk !== "object") { throw new TypeError("JWK must be an object"); } const algorithm = digestAlgorithm ?? "sha256"; if (algorithm !== "sha256" && algorithm !== "sha384" && algorithm !== "sha512") { throw new TypeError('digestAlgorithm must one of "sha256", "sha384", or "sha512"'); } let components; switch (jwk.kty) { case "EC": check(jwk.crv, '"crv" (Curve) Parameter'); check(jwk.x, '"x" (X Coordinate) Parameter'); check(jwk.y, '"y" (Y Coordinate) Parameter'); components = { crv: jwk.crv, kty: jwk.kty, x: jwk.x, y: jwk.y }; break; case "OKP": check(jwk.crv, '"crv" (Subtype of Key Pair) Parameter'); check(jwk.x, '"x" (Public Key) Parameter'); components = { crv: jwk.crv, kty: jwk.kty, x: jwk.x }; break; case "RSA": check(jwk.e, '"e" (Exponent) Parameter'); check(jwk.n, '"n" (Modulus) Parameter'); components = { e: jwk.e, kty: jwk.kty, n: jwk.n }; break; case "oct": check(jwk.k, '"k" (Key Value) Parameter'); components = { k: jwk.k, kty: jwk.kty }; break; default: throw Error('"kty" (Key Type) Parameter missing or unsupported'); } return toString(defaultHasher(JSON.stringify(components), algorithm), "base64url"); } __name(calculateJwkThumbprint, "calculateJwkThumbprint"); async function getDigestAlgorithmFromJwkThumbprintUri(uri) { const match = uri.match(/^urn:ietf:params:oauth:jwk-thumbprint:sha-(\w+):/); if (!match) { throw new Error(`Invalid JWK thumbprint URI structure ${uri}`); } const algorithm = `sha${match[1]}`; if (algorithm !== "sha256" && algorithm !== "sha384" && algorithm !== "sha512") { throw new Error(`Invalid JWK thumbprint URI digest algorithm ${uri}`); } return algorithm; } __name(getDigestAlgorithmFromJwkThumbprintUri, "getDigestAlgorithmFromJwkThumbprintUri"); async function calculateJwkThumbprintUri(jwk, digestAlgorithm = "sha256") { const thumbprint = await calculateJwkThumbprint(jwk, digestAlgorithm); return `urn:ietf:params:oauth:jwk-thumbprint:sha-${digestAlgorithm.slice(-3)}:${thumbprint}`; } __name(calculateJwkThumbprintUri, "calculateJwkThumbprintUri"); // lib/jwt/JwtVerifier.ts var getDidJwtVerifier = /* @__PURE__ */ __name((jwt, options) => { const { type } = options; if (!jwt.header.kid) throw new Error(`Received an invalid JWT. Missing kid header.`); if (!jwt.header.alg) throw new Error(`Received an invalid JWT. Missing alg header.`); if (!jwt.header.kid.includes("#")) { throw new Error(`Received an invalid JWT.. '${type}' contains an invalid kid header.`); } return { method: "did", didUrl: jwt.header.kid, type, alg: jwt.header.alg }; }, "getDidJwtVerifier"); var getIssuer = /* @__PURE__ */ __name((type, payload) => { if (type === "request-object") { if (!payload.client_id) { throw new Error("Missing required field client_id in request object JWT"); } return payload.client_id; } if (typeof payload.iss !== "string") { throw new Error(`Received an invalid JWT. '${type}' contains an invalid iss claim or it is missing.`); } return payload.iss; }, "getIssuer"); var getX5cVerifier = /* @__PURE__ */ __name((jwt, options) => { const { type } = options; if (!jwt.header.x5c) throw new Error(`Received an invalid JWT. Missing x5c header.`); if (!jwt.header.alg) throw new Error(`Received an invalid JWT. Missing alg header.`); if (!Array.isArray(jwt.header.x5c) || jwt.header.x5c.length === 0 || !jwt.header.x5c.every((cert) => typeof cert === "string")) { throw new Error(`Received an invalid JWT.. '${type}' contains an invalid x5c header.`); } return { method: "x5c", x5c: jwt.header.x5c, issuer: getIssuer(type, jwt.payload), type, alg: jwt.header.alg }; }, "getX5cVerifier"); var getJwkVerifier = /* @__PURE__ */ __name(async (jwt, options) => { const { type } = options; if (!jwt.header.jwk) throw new Error(`Received an invalid JWT. Missing jwk header.`); if (!jwt.header.alg) throw new Error(`Received an invalid JWT. Missing alg header.`); if (typeof jwt.header.jwk !== "object") { throw new Error(`Received an invalid JWT. '${type}' contains an invalid jwk header.`); } return { method: "jwk", type, jwk: jwt.header.jwk, alg: jwt.header.alg }; }, "getJwkVerifier"); var getJwtVerifierWithContext = /* @__PURE__ */ __name(async (jwt, options) => { const { header, payload } = jwt; if (header.kid?.startsWith("did:")) return getDidJwtVerifier({ header, payload }, options); else if (jwt.header.x5c) return getX5cVerifier({ header, payload }, options); else if (jwt.header.jwk) return getJwkVerifier({ header, payload }, options); return { method: "custom", type: options.type }; }, "getJwtVerifierWithContext"); // lib/jwt/jwtUtils.ts var import_jwt_decode = require("jwt-decode"); function parseJWT(jwt) { const header = (0, import_jwt_decode.jwtDecode)(jwt, { header: true }); const payload = (0, import_jwt_decode.jwtDecode)(jwt, { header: false }); if (!payload || !header) { throw new Error("Jwt Payload and/or Header could not be parsed"); } return { header, payload }; } __name(parseJWT, "parseJWT"); var DEFAULT_SKEW_TIME = 60; function getNowSkewed(now, skewTime) { const _now = now ? now : epochTime(); const _skewTime = skewTime ? skewTime : DEFAULT_SKEW_TIME; return { nowSkewedPast: _now - _skewTime, nowSkewedFuture: _now + _skewTime }; } __name(getNowSkewed, "getNowSkewed"); function epochTime() { return Math.floor(Date.now() / 1e3); } __name(epochTime, "epochTime"); var BASE64_URL_REGEX = /^([0-9a-zA-Z-_]{4})*(([0-9a-zA-Z-_]{2}(==)?)|([0-9a-zA-Z-_]{3}(=)?))?$/; var isJws = /* @__PURE__ */ __name((jws) => { const jwsParts = jws.split("."); return jwsParts.length === 3 && jwsParts.every((part) => BASE64_URL_REGEX.test(part)); }, "isJws"); var isJwe = /* @__PURE__ */ __name((jwe) => { const jweParts = jwe.split("."); return jweParts.length === 5 && jweParts.every((part) => BASE64_URL_REGEX.test(part)); }, "isJwe"); var decodeProtectedHeader = /* @__PURE__ */ __name((jwt) => { return (0, import_jwt_decode.jwtDecode)(jwt, { header: true }); }, "decodeProtectedHeader"); var decodeJwt = /* @__PURE__ */ __name((jwt) => { return (0, import_jwt_decode.jwtDecode)(jwt, { header: false }); }, "decodeJwt"); var checkExp = /* @__PURE__ */ __name((input) => { const { exp, now, clockSkew } = input; return exp < (now ?? Date.now() / 1e3) - (clockSkew ?? 120); }, "checkExp"); // lib/dpop/DPoP.ts var import_jwt_decode2 = require("jwt-decode"); var u8a2 = __toESM(require("uint8arrays"), 1); var import_uuid = require("uuid"); var { toString: toString2 } = u8a2; var dpopTokenRequestNonceError = "use_dpop_nonce"; function getCreateDPoPOptions(createDPoPClientOpts, endPointUrl, resourceRequestOpts) { const htu = endPointUrl.split("?")[0].split("#")[0]; return { ...createDPoPClientOpts, jwtPayloadProps: { ...createDPoPClientOpts.jwtPayloadProps, htu, htm: "POST", ...resourceRequestOpts && { accessToken: resourceRequestOpts.accessToken } } }; } __name(getCreateDPoPOptions, "getCreateDPoPOptions"); async function createDPoP(options) { const { createJwtCallback, jwtIssuer, jwtPayloadProps, dPoPSigningAlgValuesSupported } = options; if (jwtPayloadProps.accessToken && (jwtPayloadProps.accessToken?.startsWith("DPoP ") || jwtPayloadProps.accessToken?.startsWith("Bearer "))) { throw new Error("expected access token without scheme"); } const ath = jwtPayloadProps.accessToken ? toString2(defaultHasher(jwtPayloadProps.accessToken, "sha256"), "base64url") : void 0; return createJwtCallback({ method: "jwk", type: "dpop", alg: jwtIssuer.alg, jwk: jwtIssuer.jwk, dPoPSigningAlgValuesSupported }, { header: { ...jwtIssuer, typ: "dpop+jwt", alg: jwtIssuer.alg, jwk: jwtIssuer.jwk }, payload: { ...jwtPayloadProps, iat: epochTime(), jti: (0, import_uuid.v4)(), ...ath && { ath } } }); } __name(createDPoP, "createDPoP"); async function verifyDPoP(request, options) { const dpop = request.headers["dpop"]; if (!dpop || typeof dpop !== "string") { throw new Error("missing or invalid dpop header. Expected compact JWT"); } const { header: dPoPHeader, payload: dPoPPayload } = parseJWT(dpop); if (dPoPHeader.typ !== "dpop+jwt" || !dPoPHeader.alg || !dPoPHeader.jwk || typeof dPoPHeader.jwk !== "object" || dPoPHeader.jwk.d) { throw new Error("invalid_dpop_proof. Invalid header claims"); } if (!dPoPPayload.htm || !dPoPPayload.htu || !dPoPPayload.iat || !dPoPPayload.jti) { throw new Error("invalid_dpop_proof. Missing required claims"); } if (options?.acceptedAlgorithms && !options.acceptedAlgorithms.includes(dPoPHeader.alg)) { throw new Error(`invalid_dpop_proof. Invalid 'alg' claim '${dPoPHeader.alg}'. Only ${options.acceptedAlgorithms.join(", ")} are supported.`); } if (options?.expectedNonce && !dPoPPayload.nonce || dPoPPayload.nonce !== options.expectedNonce) { throw new Error("invalid_dpop_proof. Nonce mismatch"); } try { const verificationResult = await options.jwtVerifyCallback({ method: "jwk", type: "dpop", jwk: dPoPHeader.jwk, alg: dPoPHeader.alg }, { header: dPoPHeader, payload: dPoPPayload, raw: dpop }); if (!verificationResult) { throw new Error("invalid_dpop_proof. Invalid JWT signature"); } } catch (error) { throw new Error("invalid_dpop_proof. Invalid JWT signature. " + (error instanceof Error ? error.message : "Unknown error")); } if (dPoPPayload.htm !== request.method) { throw new Error(`invalid_dpop_proof. Invalid htm claim. Must match request method '${request.method}'`); } const currentUri = request.fullUrl.split("?")[0].split("#")[0]; if (dPoPPayload.htu !== currentUri) { throw new Error("invalid_dpop_proof. Invalid htu claim"); } if (options.expectedNonce && dPoPPayload.nonce !== options.expectedNonce || !options.expectedNonce && dPoPPayload.nonce) { throw new Error("invalid_dpop_proof. Nonce mismatch"); } const { nowSkewedPast, nowSkewedFuture } = getNowSkewed(options.now); if ( // iat claim is too far in the future nowSkewedPast - (options.maxIatAgeInSeconds ?? 60) > dPoPPayload.iat || // iat claim is too old nowSkewedFuture + (options.maxIatAgeInSeconds ?? 60) < dPoPPayload.iat ) { throw new Error("invalid_dpop_proof. Invalid iat claim"); } const authorizationHeader = request.headers.authorization; if (!options.expectAccessToken && authorizationHeader) { throw new Error("invalid_dpop_proof. Received an unexpected authorization header."); } if (options.expectAccessToken) { if (!dPoPPayload.ath) { throw new Error("invalid_dpop_proof. Missing expected ath claim."); } if (!authorizationHeader || typeof authorizationHeader !== "string" || !authorizationHeader.startsWith("DPoP ")) { throw new Error("invalid_dpop_proof. Invalid authorization header."); } const accessToken = authorizationHeader.replace("DPoP ", ""); const expectedAth = toString2(defaultHasher(accessToken, "sha256"), "base64url"); if (dPoPPayload.ath !== expectedAth) { throw new Error("invalid_dpop_proof. Invalid ath claim"); } const accessTokenPayload = (0, import_jwt_decode2.jwtDecode)(accessToken, { header: false }); if (!accessTokenPayload.cnf?.jkt) { throw new Error("invalid_dpop_proof. Access token is missing the jkt claim"); } const thumprint = await calculateJwkThumbprint(dPoPHeader.jwk, "sha256"); if (accessTokenPayload.cnf?.jkt !== thumprint) { throw new Error("invalid_dpop_proof. JwkThumbprint mismatch"); } } return dPoPHeader.jwk; } __name(verifyDPoP, "verifyDPoP"); async function verifyResourceDPoP(request, options) { if (!request.headers.authorization || typeof request.headers.authorization !== "string") { throw new Error("Received an invalid resource request. Missing authorization header."); } const tokenPayload = (0, import_jwt_decode2.jwtDecode)(request.headers.authorization, { header: false }); const tokenType = tokenPayload.token_type; if (tokenType !== "DPoP") { return; } return verifyDPoP(request, { ...options, expectAccessToken: true }); } __name(verifyResourceDPoP, "verifyResourceDPoP"); // lib/helpers/Encodings.ts var u8a3 = __toESM(require("uint8arrays"), 1); var { fromString, toString: toString3 } = u8a3; function base64ToHexString(input, encoding) { return toString3(fromString(input, encoding ?? "base64url"), "base16"); } __name(base64ToHexString, "base64ToHexString"); function fromBase64(base64) { return base64.replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_"); } __name(fromBase64, "fromBase64"); function base64urlEncodeBuffer(buf) { return fromBase64(buf.toString("base64")); } __name(base64urlEncodeBuffer, "base64urlEncodeBuffer"); function base64urlToString(base64url) { const uint8array = fromString(base64url, "base64url"); return toString3(uint8array, "ascii"); } __name(base64urlToString, "base64urlToString"); // lib/index.ts var import_uuid2 = require("uuid"); var VCI_LOGGERS = import_ssi_types2.Loggers.DEFAULT; var VCI_LOG_COMMON = VCI_LOGGERS.get("sphereon:oid4vci:common"); //# sourceMappingURL=index.cjs.map