UNPKG

@credo-ts/core

Version:

<p align="center"> <br /> <img alt="Credo Logo" src="https://github.com/openwallet-foundation/credo-ts/blob/c7886cb8377ceb8ee4efe8d264211e561a75072d/images/credo-logo.png" height="250px" /> </p> <h1 align="center"><b>Credo - Core</b></h1

268 lines (267 loc) • 14.9 kB
import { CredoError } from "../../error/CredoError.mjs"; import "../../error/index.mjs"; import { asArray } from "../../utils/array.mjs"; import { injectable } from "../../plugins/index.mjs"; import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.151.0/helpers/esm/decorateMetadata.mjs"; import __decorate from "../../_virtual/_@oxc-project_runtime@0.151.0/helpers/esm/decorate.mjs"; import "./models/ClaimFormat.mjs"; import { W3cV2JwtVerifiableCredential } from "./jwt-vc/W3cV2JwtVerifiableCredential.mjs"; import { W3cV2DataIntegrityVerifiableCredential } from "./data-integrity/W3cV2DataIntegrityVerifiableCredential.mjs"; import { W3cV2DataIntegrityVerifiablePresentation } from "./data-integrity/W3cV2DataIntegrityVerifiablePresentation.mjs"; import { W3cV2SdJwtVerifiableCredential } from "./sd-jwt-vc/W3cV2SdJwtVerifiableCredential.mjs"; import { W3cV2EnvelopedVerifiableCredential } from "./models/credential/W3cV2EnvelopedVerifiableCredential.mjs"; import { W3cV2EnvelopedVerifiablePresentation } from "./models/presentation/W3cV2EnvelopedVerifiablePresentation.mjs"; import { W3cV2JwtVerifiablePresentation } from "./jwt-vc/W3cV2JwtVerifiablePresentation.mjs"; import { W3cV2SdJwtVerifiablePresentation } from "./sd-jwt-vc/W3cV2SdJwtVerifiablePresentation.mjs"; import { getVerificationMethodForJwt } from "./v2-jwt-utils.mjs"; import { W3cV2JwtCredentialService } from "./jwt-vc/W3cV2JwtCredentialService.mjs"; import "./jwt-vc/index.mjs"; import { getSignerForDataIntegrityProof } from "./data-integrity/v2-di-utils.mjs"; import { W3cV2DataIntegrityCredentialService } from "./data-integrity/W3cV2DataIntegrityCredentialService.mjs"; import "./data-integrity/index.mjs"; import { W3cV2SdJwtCredentialService } from "./sd-jwt-vc/W3cV2SdJwtCredentialService.mjs"; import "./sd-jwt-vc/index.mjs"; import { decodeW3cV2VerifiableCredential } from "./models/credential/W3cV2VerifiableCredential.mjs"; import { decodeW3cV2VerifiablePresentation } from "./models/presentation/W3cV2VerifiablePresentation.mjs"; import "./models/index.mjs"; import { W3cV2CredentialRepository } from "./repository/W3cV2CredentialRepository.mjs"; import "./repository/index.mjs"; //#region src/modules/vc/W3cV2CredentialService.ts let W3cV2CredentialService = class W3cV2CredentialService { constructor(w3cV2CredentialRepository, w3cV2SdJwtCredentialService, w3cV2JwtCredentialService, w3cV2DataIntegrityCredentialService) { this.w3cV2CredentialRepository = w3cV2CredentialRepository; this.w3cV2SdJwtCredentialService = w3cV2SdJwtCredentialService; this.w3cV2JwtCredentialService = w3cV2JwtCredentialService; this.w3cV2DataIntegrityCredentialService = w3cV2DataIntegrityCredentialService; } /** * Signs a credential * * @param credential the credential to be signed * @returns the signed credential */ async signCredential(agentContext, options) { if (options.format === "vc+jwt") return await this.w3cV2JwtCredentialService.signCredential(agentContext, options); if (options.format === "vc+sd-jwt") return await this.w3cV2SdJwtCredentialService.signCredential(agentContext, options); if (options.format === "di_vc") return await this.w3cV2DataIntegrityCredentialService.signCredential(agentContext, options); throw new CredoError(`Unsupported format in options. Format must be either 'vc+jwt', 'vc+sd-jwt', or 'di_vc'`); } /** * Verifies the signature(s) of a credential */ async verifyCredential(agentContext, options) { const credential = typeof options.credential === "string" ? decodeW3cV2VerifiableCredential(options.credential) : options.credential; if (credential instanceof W3cV2JwtVerifiableCredential) return this.w3cV2JwtCredentialService.verifyCredential(agentContext, { ...options, credential }); if (credential instanceof W3cV2SdJwtVerifiableCredential) return this.w3cV2SdJwtCredentialService.verifyCredential(agentContext, { ...options, credential }); if (credential instanceof W3cV2DataIntegrityVerifiableCredential) return this.w3cV2DataIntegrityCredentialService.verifyCredential(agentContext, { ...options, credential }); throw new CredoError("Unsupported credential type in options. Credential must be either a W3cV2JwtVerifiableCredential, a W3cV2SdJwtVerifiableCredential, or a W3cV2DataIntegrityVerifiableCredential"); } /** * Signs a presentation including the credentials it includes * * @param presentation the presentation to be signed * @returns the signed presentation */ async signPresentation(agentContext, options) { if (options.format === "vp+jwt") return await this.w3cV2JwtCredentialService.signPresentation(agentContext, options); if (options.format === "vp+sd-jwt") return await this.w3cV2SdJwtCredentialService.signPresentation(agentContext, options); if (options.format === "di_vp") return await this.w3cV2DataIntegrityCredentialService.signPresentation(agentContext, options); throw new CredoError(`Unsupported format in options. Format must be either 'vp+jwt', 'vp+sd-jwt', or 'di_vp'`); } /** * Verifies a W3C V2 verifiable presentation and each enclosed credential entry. * * Flow: * 1. Verifies the outer VP using the format-specific service (`vp+jwt` or `vp+sd-jwt`). * 2. Derives the presenter identifier (holder id, or JWT verification method controller fallback). * 3. Recursively walks enclosed entries and verifies each credential entry with the matching * credential verifier (`vc+jwt` or `vc+sd-jwt`) based on the actual enclosed format. * 4. Adds `credentialSubjectAuthentication` validation to each credential result by checking * whether the authenticated presenter matches at least one credentialSubject id. * * Current support and constraints: * - Compact string presentations are accepted for `vp+jwt` and `vp+sd-jwt` and normalized * to typed presentation instances before routing. * - `di_vp` routes through the DI credential service for outer-VP verification. * - `di_vc` entries route through the DI credential service for entry verification. * - Nested VP entries are treated as invalid. VC 2.0 verifiable presentations support VC entries, * not VP-within-VP entries. * * Return semantics: * - `presentation` contains the outer VP verification result. * - `credentialEntries` contains one result per traversed credential entry, including unsupported * entries as invalid results. * - Top-level `isValid` is true only if the outer VP and all credential entry validations are valid. * * @param agentContext The framework agent context used for resolution and cryptographic checks. * @param options Verification input containing the presentation to verify. * @returns Aggregated presentation and credential-entry verification result. */ async verifyPresentation(agentContext, options) { const presentation = typeof options.presentation === "string" ? decodeW3cV2VerifiablePresentation(options.presentation) : options.presentation; const validationResults = { isValid: false, presentation: { isValid: false, validations: {} }, credentialEntries: [] }; let entries = []; let signerId; if (presentation instanceof W3cV2JwtVerifiablePresentation) { const presentationResult = await this.w3cV2JwtCredentialService.verifyPresentation(agentContext, { ...options, presentation }); validationResults.presentation = presentationResult.presentation; if (!presentationResult.presentation.isValid) { validationResults.isValid = false; return validationResults; } const holderId = presentation.resolvedPresentation.holderId; if (holderId) signerId = holderId; else try { signerId = (await getVerificationMethodForJwt(agentContext, presentation, ["authentication"])).controller; } catch (error) { validationResults.isValid = false; validationResults.error = error; return validationResults; } entries = this.extractCredentialEntriesFromPresentation(presentation); } else if (presentation instanceof W3cV2SdJwtVerifiablePresentation) { const presentationResult = await this.w3cV2SdJwtCredentialService.verifyPresentation(agentContext, { ...options, presentation }); validationResults.presentation = presentationResult.presentation; if (!presentationResult.presentation.isValid) { validationResults.isValid = false; return validationResults; } const holderId = presentation.resolvedPresentation.holderId; if (holderId) signerId = holderId; else try { signerId = (await getVerificationMethodForJwt(agentContext, presentation, ["authentication"])).controller; } catch (error) { validationResults.isValid = false; validationResults.error = error; return validationResults; } entries = this.extractCredentialEntriesFromPresentation(presentation); } else if (presentation instanceof W3cV2DataIntegrityVerifiablePresentation) { const presentationResult = await this.w3cV2DataIntegrityCredentialService.verifyPresentation(agentContext, { ...options, presentation }); validationResults.presentation = presentationResult.presentation; if (!presentationResult.presentation.isValid) { validationResults.isValid = false; return validationResults; } signerId = presentation.resolvedPresentation.holderId; if (!signerId) try { signerId = await getSignerForDataIntegrityProof(agentContext, presentation.securedPresentation, "authentication"); } catch (error) { validationResults.isValid = false; validationResults.error = error; return validationResults; } entries = this.extractCredentialEntriesFromPresentation(presentation); } else throw new CredoError("Unsupported credential type in options. Presentation must be either a W3cV2JwtVerifiablePresentation, a W3cV2SdJwtVerifiablePresentation, or a W3cV2DataIntegrityVerifiablePresentation"); validationResults.credentialEntries = (await Promise.all(entries.map((entry) => this.verifyPresentationEntry(agentContext, entry, signerId)))).flat(); validationResults.isValid = validationResults.presentation.isValid && validationResults.credentialEntries.every((entry) => entry.isValid); return validationResults; } async verifyPresentationEntry(agentContext, entry, signerId) { if (entry instanceof W3cV2DataIntegrityVerifiablePresentation) return [this.createInvalidCredentialEntryResult(new CredoError("Nested verifiable presentation entries are not supported in VC 2.0 presentations."))]; if (entry instanceof W3cV2DataIntegrityVerifiableCredential) { const credentialResult = await this.w3cV2DataIntegrityCredentialService.verifyCredential(agentContext, { credential: entry }); return [this.mergeCredentialSubjectAuthenticationValidation(credentialResult, signerId, entry.resolvedCredential?.credentialSubjectIds ?? [])]; } if (entry instanceof W3cV2EnvelopedVerifiablePresentation) return [this.createInvalidCredentialEntryResult(new CredoError("Nested verifiable presentation entries are not supported in VC 2.0 presentations."))]; if (!(entry instanceof W3cV2EnvelopedVerifiableCredential)) return [this.createInvalidCredentialEntryResult(new CredoError("Unsupported credential entry type in presentation."))]; const enclosed = entry.envelopedCredential; let credentialResult; if (enclosed instanceof W3cV2JwtVerifiableCredential) credentialResult = await this.w3cV2JwtCredentialService.verifyCredential(agentContext, { credential: enclosed }); else if (enclosed instanceof W3cV2SdJwtVerifiableCredential) credentialResult = await this.w3cV2SdJwtCredentialService.verifyCredential(agentContext, { credential: enclosed }); else return [this.createInvalidCredentialEntryResult(new CredoError(`Credential entry uses '${this.getClaimFormat(enclosed) ?? "an unsupported enclosed credential format"}'.`))]; return [this.mergeCredentialSubjectAuthenticationValidation(credentialResult, signerId, entry.resolvedCredential.credentialSubjectIds)]; } extractCredentialEntriesFromPresentation(presentation) { return this.extractCredentialEntriesFromResolvedPresentation(presentation.resolvedPresentation); } extractCredentialEntriesFromResolvedPresentation(resolvedPresentation) { return asArray(resolvedPresentation.verifiableCredential); } createInvalidCredentialEntryResult(error) { return { isValid: false, error, validations: {} }; } getClaimFormat(value) { if (!value || typeof value !== "object" || Array.isArray(value)) return void 0; const candidate = value; return typeof candidate.claimFormat === "string" ? candidate.claimFormat : void 0; } mergeCredentialSubjectAuthenticationValidation(credentialResult, signerId, credentialSubjectIds) { const presentationAuthenticatesCredentialSubject = credentialSubjectIds.some((subjectId) => signerId === subjectId); const credentialSubjectAuthentication = credentialSubjectIds.length > 0 && !presentationAuthenticatesCredentialSubject ? { isValid: false, error: new CredoError("Credential has one or more credentialSubject ids, but presentation does not authenticate credential subject") } : { isValid: true }; return { ...credentialResult, isValid: credentialResult.isValid && credentialSubjectAuthentication.isValid, validations: { ...credentialResult.validations, credentialSubjectAuthentication } }; } /** * Writes a credential to storage * * @param record the credential to be stored * @returns the credential record that was written to storage */ async storeCredential(agentContext, options) { await this.w3cV2CredentialRepository.save(agentContext, options.record); return options.record; } async removeCredentialRecord(agentContext, id) { await this.w3cV2CredentialRepository.deleteById(agentContext, id); } async getAllCredentialRecords(agentContext) { return await this.w3cV2CredentialRepository.getAll(agentContext); } async getCredentialRecordById(agentContext, id) { return await this.w3cV2CredentialRepository.getById(agentContext, id); } async findCredentialsByQuery(agentContext, query, queryOptions) { return (await this.w3cV2CredentialRepository.findByQuery(agentContext, query, queryOptions)).map((record) => record.firstCredential); } async findCredentialRecordByQuery(agentContext, query) { return (await this.w3cV2CredentialRepository.findSingleByQuery(agentContext, query))?.firstCredential; } }; W3cV2CredentialService = __decorate([injectable(), __decorateMetadata("design:paramtypes", [ typeof W3cV2CredentialRepository === "undefined" ? Object : W3cV2CredentialRepository, typeof W3cV2SdJwtCredentialService === "undefined" ? Object : W3cV2SdJwtCredentialService, typeof W3cV2JwtCredentialService === "undefined" ? Object : W3cV2JwtCredentialService, typeof W3cV2DataIntegrityCredentialService === "undefined" ? Object : W3cV2DataIntegrityCredentialService ])], W3cV2CredentialService); //#endregion export { W3cV2CredentialService }; //# sourceMappingURL=W3cV2CredentialService.mjs.map