UNPKG

@sphereon/oid4vc-common

Version:

OpenID 4 Verifiable Credentials Common

444 lines (434 loc) 15.1 kB
var __defProp = Object.defineProperty; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); // lib/index.ts import { Loggers } from "@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 import * as u8a from "uint8arrays"; // lib/hasher.ts import { shaHasher } from "@sphereon/ssi-types"; var defaultHasher = /* @__PURE__ */ __name((data, algorithm) => { return 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 import { jwtDecode } from "jwt-decode"; function parseJWT(jwt) { const header = jwtDecode(jwt, { header: true }); const payload = 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 jwtDecode(jwt, { header: true }); }, "decodeProtectedHeader"); var decodeJwt = /* @__PURE__ */ __name((jwt) => { return 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 import { jwtDecode as jwtDecode2 } from "jwt-decode"; import * as u8a2 from "uint8arrays"; import { v4 as uuidv4 } from "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: uuidv4(), ...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 = jwtDecode2(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 = jwtDecode2(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 import * as u8a3 from "uint8arrays"; 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 import { v4 } from "uuid"; var VCI_LOGGERS = Loggers.DEFAULT; var VCI_LOG_COMMON = VCI_LOGGERS.get("sphereon:oid4vci:common"); export { BASE64_URL_REGEX, SigningAlgo, VCI_LOGGERS, VCI_LOG_COMMON, base64ToHexString, base64urlEncodeBuffer, base64urlToString, calculateJwkThumbprint, calculateJwkThumbprintUri, checkExp, createDPoP, decodeJwt, decodeProtectedHeader, defaultHasher, dpopTokenRequestNonceError, epochTime, fromBase64, getCreateDPoPOptions, getDidJwtVerifier, getDigestAlgorithmFromJwkThumbprintUri, getJwkVerifier, getJwtVerifierWithContext, getNowSkewed, getX5cVerifier, isJwe, isJws, parseJWT, v4 as uuidv4, verifyDPoP, verifyResourceDPoP }; //# sourceMappingURL=index.js.map