import { getResolver as getEbsiResolver } from "@cef-ebsi/ebsi-did-resolver"; import { getResolver as getKeyResolver } from "@cef-ebsi/key-did-resolver"; import { createJWT } from "did-jwt"; import { Resolver } from "did-resolver"; import type { CreateVerifiableCredentialOptions, EbsiEnvConfiguration, EbsiIssuer, EbsiVerifiableAttestation, VcJwtPayload, VerifyCredentialOptions, } from "./types.ts"; import { validateConfig } from "./config.ts"; import { isSelfAttestationForTrustChain, validateAccreditations, validateContext, validateCredentialSchema, validateCredentialStatus, validateCredentialSubject, validateDates, validateEbsiVerifiableAttestation, validateIssuer, validateType, verifyVcJwt, } from "./validators.ts"; export * from "./config.ts"; export * from "./errors/index.ts"; export * from "./types.ts"; export * from "./utils.ts"; export * from "./validators.ts"; /** * Creates a VerifiableCredential given an EBSI Verifiable Attestation and an `EbsiIssuer`. * * This method transforms the payload into the [JWT encoding](https://www.w3.org/TR/vc-data-model/#jwt-encoding) * described in the [W3C VC spec](https://www.w3.org/TR/vc-data-model). * * The `issuer` is then used to assign an algorithm, and then sign the JWT. * @param payload - `EbsiVerifiableAttestation` * @param issuer - `EbsiIssuer`: the DID, kid, signer and algorithm that will sign the token * @param config - EBSI environment configuration * @param options - `CreateVerifiableCredentialOptions` object containing additional options. * @returns a `Promise` that resolves to the JWT encoded verifiable credential or rejects with * `ValidationError` if the `payload` is not EBSI compliant */ export async function createVerifiableCredentialJwt( payload: EbsiVerifiableAttestation, issuer: EbsiIssuer, config: EbsiEnvConfiguration, options?: CreateVerifiableCredentialOptions, ): Promise; export async function createVerifiableCredentialJwt( payload: unknown, issuer: EbsiIssuer, config: EbsiEnvConfiguration, options?: CreateVerifiableCredentialOptions, ): Promise { validateConfig(config); // Always verify that the payload is conform to the the Verifiable Attestation schema validateEbsiVerifiableAttestation(payload, config, options?.timeout); if (!options?.skipValidation) { validateContext(payload["@context"]); validateType(payload.type); validateDates(payload, options?.validAt, options?.clockSkew); if (!options?.skipCredentialSubjectValidation) { validateCredentialSubject(payload, options?.sub); } const resolver = new Resolver({ ...getEbsiResolver({ // TODO: implement fallback registry: `https://${config.hosts[0]!}/did-registry/${config.services["did-registry"]}/identifiers`, }), ...getKeyResolver(), }); await validateIssuer( payload, issuer.did, issuer.kid, issuer.alg, resolver, config, isSelfAttestationForTrustChain(payload), options?.timeout, options?.axiosHeaders, ); await validateCredentialSchema( payload, config, options?.timeout, options?.extraCredentialSchemaTypes, ); if (!options?.skipAccreditationsValidation) { await validateAccreditations(payload, config, options); } if (!options?.skipStatusValidation) { await validateCredentialStatus(payload, resolver, config, options); } } const credentialIssuer = typeof payload.issuer === "string" ? payload.issuer : payload.issuer.id; const subject = Array.isArray(payload.credentialSubject) ? options?.sub : payload.credentialSubject.id; const validFrom = payload.validFrom || payload.issuanceDate; const { expirationDate } = payload; // Prepare payload // See https://www.w3.org/TR/vc-data-model/#jwt-encoding const vcJwtPayload = { /** * `jti` MUST represent the `id` property of the verifiable credential. */ jti: payload.id, /** * `sub` MUST represent the `id` property contained in the verifiable credential subject. */ ...(subject && { sub: subject, }), /** * `iss` MUST represent the issuer property of a verifiable credential. */ iss: credentialIssuer, /** * `nbf` MUST represent `issuanceDate`, encoded as a UNIX timestamp. * * Note: since `validFrom` will replace `issuanceDate` in the future, we support both. */ ...(validFrom && { nbf: Math.floor(Date.parse(validFrom) / 1000), }), /** * `exp` MUST represent the `expirationDate` property, encoded as a UNIX timestamp. */ ...(expirationDate && { exp: Math.floor(Date.parse(expirationDate) / 1000), }), /** * Time at which the VC JWT was issued. */ iat: Math.floor(Date.parse(payload.issued) / 1000), /** * vc: JSON object, which MUST be present in a JWT verifiable credential. * The object contains the verifiable credential according to this specification. */ vc: payload, } as const satisfies VcJwtPayload; // Add kid to JWT header const { alg, did, kid, signer } = issuer; // Create JWT const vcJwt = await createJWT( vcJwtPayload, { issuer: did, signer }, { alg, kid, typ: "JWT", ...options?.header }, ); return vcJwt; } /** * Verifies and validates a VerifiableCredential that is encoded as a JWT according to the W3C and EBSI specs. * @param vc - the credential to be verified. * @param config - EBSI environment configuration * @param options - `VerifyCredentialOptions` object containing additional options. * @returns a `Promise` that resolves to a `EbsiVerifiableAttestation`, or rejects with `ValidationError` if the input is not EBSI compliant */ export async function verifyCredentialJwt( vc: string, config: EbsiEnvConfiguration, options?: VerifyCredentialOptions, ): Promise { validateConfig(config); const resolver = new Resolver({ ...getEbsiResolver({ // TODO: implement fallback registry: `https://${config.hosts[0]!}/did-registry/${config.services["did-registry"]}/identifiers`, }), ...getKeyResolver(), }); return verifyVcJwt(vc, resolver, config, options); }