import type { EbsiEnvConfiguration, EbsiIssuer, } from "@europeum-ebsi/verifiable-credential"; import type { SetRequired } from "type-fest"; import { createJWT } from "@europeum-ebsi/did-jwt"; import { getResolver as getEbsiDidResolver } from "@europeum-ebsi/ebsi-did-resolver"; import { getResolver as getKeyDidResolver } from "@europeum-ebsi/key-did-resolver"; import { AXIOS_TIMEOUT, validateConfig, validateDid, validateHeaderJwk, } from "@europeum-ebsi/verifiable-credential"; import { Resolver } from "did-resolver"; import type { CreateVerifiablePresentationJwtOptions, Schemas, VerifyPresentationJwtOptions, VpJwtHeader, VpJwtPayload, } from "./types.ts"; import { validateContext, validateCredentials, validateEbsiVerifiablePresentation, validateHolder, validateJwtProps, validateSignature, validateType, validateVpJwtClaims, validateVpJwtHeader, } from "./validators.ts"; export * from "./types.ts"; export * from "./validators.ts"; /** * Creates a Verifiable Presentation JWT given a `Schemas["Presentation"]` and an `EbsiIssuer`. * * This method transforms the presentation payload into the [JWT encoding](https://www.w3.org/TR/2025/REC-vc-jose-cose-20250515/#securing-vps-with-jose) * described in the [W3C VC spec](https://www.w3.org/TR/vc-data-model-2.0/) and then validated to conform to the EBSI * required spec. * * The `holder` is then used to assign an algorithm and sign the JWT. * @param presentation - `Schemas["Presentation"]` * @param holder - `EbsiIssuer` of the Presentation JWT (holder of the VC), signer and algorithm that will sign the token * @param audience - represents the identity of the intended audience (DID, URI) * @param config - EBSI environment configuration * @param options - `CreateVerifiablePresentationOptions` allows to pass additional values to the resulting JWT payload * @returns a `Promise` that resolves to the JWT encoded verifiable presentation or rejects with `ValidationError` if the * `payload` is not W3C compliant */ export async function createVerifiablePresentationJwt( presentation: Schemas["Presentation"], holder: EbsiIssuer, audience: string, config: EbsiEnvConfiguration, options?: CreateVerifiablePresentationJwtOptions, ): Promise; export async function createVerifiablePresentationJwt( presentation: unknown, holder: EbsiIssuer, audience: string, config: EbsiEnvConfiguration, options?: CreateVerifiablePresentationJwtOptions, ): Promise { validateConfig(config); // Set default options const opts: Required = { axiosHeaders: {}, header: {}, payload: {}, proofPurpose: "authentication", skipHolderDidResolutionValidation: false, skipValidation: false, timeout: AXIOS_TIMEOUT, // Let the VC library define the default values verifyCredentialOptions: {}, ...options, }; // Always verify that the payload is conform to the the Verifiable Presentation schema validateEbsiVerifiablePresentation(presentation, config, opts.timeout); const iat = Math.floor(Date.now() / 1000); if (opts.skipValidation) { validateDid(holder.did); } else { validateContext(presentation["@context"]); validateType(presentation.type); const resolver = new Resolver({ ...getEbsiDidResolver({ // TODO: implement fallback registry: `https://${config.hosts[0]!}/did-registry/${config.services["did-registry"]}/identifiers`, }), ...getKeyDidResolver(), }); // Validate VP holder await validateHolder( presentation, holder.did, holder.kid, holder.alg, iat, resolver, opts.proofPurpose, opts.timeout, opts.skipHolderDidResolutionValidation, opts.axiosHeaders, ); // Validate VC JWTs await validateCredentials(presentation, holder.did, config, { axiosHeaders: opts.axiosHeaders, timeout: opts.timeout, ...opts.verifyCredentialOptions, }); validateVpJwtHeader(opts.header, true); validateVpJwtClaims(opts.payload, presentation, audience); } const { alg, kid, signer } = holder; const vpJwtPayload = { holder: holder.did, ...presentation, ...opts.payload, aud: audience, } satisfies VpJwtPayload; const vpJwtHeader = { alg, kid, typ: "vp+jwt", ...opts.header, } satisfies VpJwtHeader; return createJWT(vpJwtPayload, { signer }, vpJwtHeader); } /** * Verifies and validates an EBSI Verifiable Presentation that is encoded as a JWT according to the EBSI and W3C specs. * @param presentation - the presentation to be verified. Currently only the JWT encoding is supported by this library * @param audience - represents the identity of the intended audience (DID, URI) * @param config - EBSI environment configuration * @param options - optional verification options that need to be satisfied * @returns a `Promise` that resolves to a Verifiable Presentation object or rejects with `ValidationError` if the input is * not EBSI compliant or the VerifyPresentationJwtOptions are not satisfied. */ export async function verifyPresentationJwt( presentation: string, audience: string, config: EbsiEnvConfiguration, options?: VerifyPresentationJwtOptions, ): Promise> { validateConfig(config); // Set default options const opts: Required = { axiosHeaders: {}, clockSkew: 0, proofPurpose: "authentication", skipHolderDidResolutionValidation: false, skipSignatureValidation: false, timeout: AXIOS_TIMEOUT, validAt: Math.floor(Date.now() / 1000), // Let the VC library define the default values verifyCredentialOptions: {}, ...options, }; const { header, payload } = validateJwtProps( presentation, audience, config, opts, ); const resolver = new Resolver({ ...getEbsiDidResolver({ // TODO: implement fallback registry: `https://${config.hosts[0]!}/did-registry/${config.services["did-registry"]}/identifiers`, }), ...getKeyDidResolver(), }); const { alg, kid } = header; // eslint-disable-next-line @typescript-eslint/no-deprecated const { aud, exp, iat, iss, jti, nbf, sub, ...vp } = payload; // Validate VP holder const didAuthenticator = await validateHolder( vp, // eslint-disable-next-line @typescript-eslint/no-deprecated payload.iss, kid, alg, iat, resolver, opts.proofPurpose, opts.timeout, opts.skipHolderDidResolutionValidation, opts.axiosHeaders, ); // Validate VC JWTs const holder = typeof vp.holder === "string" ? vp.holder : vp.holder.id; await validateCredentials(vp, holder, config, { axiosHeaders: opts.axiosHeaders, clockSkew: opts.clockSkew, timeout: opts.timeout, validAt: opts.validAt, ...opts.verifyCredentialOptions, }); // Validate signature if (didAuthenticator && !opts.skipSignatureValidation) { const resultVerifyJWT = await validateSignature( presentation, audience, resolver, didAuthenticator, ); if (header.jwk) { validateHeaderJwk(header.jwk, resultVerifyJWT.signer.publicKeyJwk); } } return vp; }