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

184 lines (183 loc) • 10.1 kB
import { CredoError } from "../../../error/CredoError.mjs"; import "../../../error/index.mjs"; import { MessageValidator } from "../../../utils/MessageValidator.mjs"; import { JsonTransformer } from "../../../utils/JsonTransformer.mjs"; import "../../../utils/index.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 { CREDENTIALS_CONTEXT_V2_URL } from "../constants.mjs"; import { validateVc2ContextBaseline, validateVc2CredentialStatus } from "../validators.mjs"; import { W3cV2Credential } from "../models/credential/W3cV2Credential.mjs"; import { W3cV2DataIntegrityVerifiableCredential } from "./W3cV2DataIntegrityVerifiableCredential.mjs"; import { W3cV2DataIntegrityVerifiablePresentation } from "./W3cV2DataIntegrityVerifiablePresentation.mjs"; import { W3cV2Presentation } from "../models/presentation/W3cV2Presentation.mjs"; import { createW3cDataIntegrityCredoError } from "../../w3c-di/W3cDataIntegrityError.mjs"; import { W3cDataIntegrityProofService } from "../../w3c-di/W3cDataIntegrityProofService.mjs"; import "../../w3c-di/internal.mjs"; import { W3cV2DataIntegrityContextValidator } from "./W3cV2DataIntegrityContextValidator.mjs"; import { getVerificationMethodForDataIntegrityProof } from "./v2-di-utils.mjs"; //#region src/modules/vc/data-integrity/W3cV2DataIntegrityCredentialService.ts let W3cV2DataIntegrityCredentialService = class W3cV2DataIntegrityCredentialService { constructor(dataIntegrityProofService, contextValidator) { this.dataIntegrityProofService = dataIntegrityProofService; this.contextValidator = contextValidator; } async signCredential(agentContext, options) { const unsecuredCredential = JsonTransformer.toJSON(options.credential); if (unsecuredCredential["@context"] === void 0 || unsecuredCredential["@context"] === null) unsecuredCredential["@context"] = [CREDENTIALS_CONTEXT_V2_URL]; MessageValidator.validateSync(JsonTransformer.fromJSON(unsecuredCredential, W3cV2Credential, { validate: false })); const contextValidation = validateVc2ContextBaseline(unsecuredCredential["@context"]); if (!contextValidation.isValid) throw contextValidation.error ?? new CredoError("VC2 credential @context validation failed"); const proofResult = await this.dataIntegrityProofService.createProof(agentContext, { unsecuredDocument: unsecuredCredential, verificationMethod: options.verificationMethod, proofPurpose: "assertionMethod", cryptosuite: options.cryptosuite }); if (!proofResult.created) throw createW3cDataIntegrityCredoError(proofResult.errors); return new W3cV2DataIntegrityVerifiableCredential({ securedCredential: { ...unsecuredCredential, proof: proofResult.proof } }); } async verifyCredential(agentContext, options) { const securedCredential = options.credential.securedCredential; const verificationResult = Array.isArray(securedCredential.proof) ? await this.dataIntegrityProofService.verifyProofSetAndChain(agentContext, securedCredential, { expectedProofPurpose: "assertionMethod" }) : await this.dataIntegrityProofService.verifyProof(agentContext, securedCredential, { expectedProofPurpose: "assertionMethod" }); if (!verificationResult.verified) return this.invalidResult(verificationResult.errors, "credential"); const contextResult = await this.contextValidator.validate(agentContext, securedCredential); if (!contextResult.validated) return this.invalidResult(contextResult.errors, "credential"); const credentialStatus = validateVc2CredentialStatus({ credentialStatus: securedCredential.credentialStatus, credentialFormat: "DI", verifyCredentialStatus: options.verifyCredentialStatus }); const issuerIsSigner = await this.verifySignerIsExpectedParty(agentContext, securedCredential, "assertionMethod", options.credential.resolvedCredential.issuerId, (verificationMethodId, issuerId) => `Credential is signed using verification method ${verificationMethodId}, while the issuer of the credential is '${issuerId}'`); return { isValid: credentialStatus.isValid && issuerIsSigner.isValid, validations: { dataModel: { isValid: true }, signature: { isValid: true }, credentialStatus, issuerIsSigner } }; } async signPresentation(agentContext, options) { const unsecuredPresentation = JsonTransformer.toJSON(options.presentation); if (unsecuredPresentation["@context"] === void 0 || unsecuredPresentation["@context"] === null) unsecuredPresentation["@context"] = [CREDENTIALS_CONTEXT_V2_URL]; MessageValidator.validateSync(JsonTransformer.fromJSON(unsecuredPresentation, W3cV2Presentation, { validate: false })); const contextValidation = validateVc2ContextBaseline(unsecuredPresentation["@context"]); if (!contextValidation.isValid) throw contextValidation.error ?? new CredoError("VC2 presentation @context validation failed"); const proofResult = await this.dataIntegrityProofService.createProof(agentContext, { unsecuredDocument: unsecuredPresentation, verificationMethod: options.verificationMethod, proofPurpose: "authentication", cryptosuite: options.cryptosuite, challenge: options.challenge, domain: options.domain }); if (!proofResult.created) throw createW3cDataIntegrityCredoError(proofResult.errors); const securedPresentation = { ...unsecuredPresentation, proof: proofResult.proof }; return new W3cV2DataIntegrityVerifiablePresentation({ securedPresentation, resolvedPresentation: JsonTransformer.fromJSON(securedPresentation, W3cV2Presentation, { validate: false }) }); } async verifyPresentation(agentContext, options) { const securedPresentation = options.presentation.securedPresentation; const verificationResult = Array.isArray(securedPresentation.proof) ? await this.dataIntegrityProofService.verifyProofSetAndChain(agentContext, securedPresentation, { expectedProofPurpose: "authentication", challenge: options.challenge, domain: options.domain }) : await this.dataIntegrityProofService.verifyProof(agentContext, securedPresentation, { expectedProofPurpose: "authentication", challenge: options.challenge, domain: options.domain }); if (!verificationResult.verified) return this.invalidResult(verificationResult.errors, "presentation"); const presentationContextResult = await this.contextValidator.validate(agentContext, securedPresentation); if (!presentationContextResult.validated) return this.invalidResult(presentationContextResult.errors, "presentation"); const holderIsSigner = await this.verifySignerIsExpectedParty(agentContext, options.presentation.securedPresentation, "authentication", options.presentation.resolvedPresentation.holderId, (verificationMethodId, holderId) => `Presentation is signed using verification method ${verificationMethodId}, while the holder of the presentation is '${holderId}'`); return { isValid: holderIsSigner.isValid, presentation: { isValid: holderIsSigner.isValid, validations: { dataModel: { isValid: true }, holderIsSigner, presentationSignature: { isValid: true } } }, credentialEntries: [] }; } /** * Checks that a secured document was signed by the identity it names as responsible for it — * the `issuer` of a credential, or the `holder` of a presentation. * * Mirrors the equivalent checks in the JWT and SD-JWT services — though those compare key * fingerprints for the issuer, whereas Data Integrity proofs name a verification method, so the * comparison here is by controller for both roles. It implements the association described in * VC-DATA-INTEGRITY §"Relationship to Verifiable Credentials": that the `controller` of the * proof's verification method matches the identifier used for the `issuer` or `holder`, and that * the verification method is expressed under a verification relationship acceptable for the * proof's purpose. The latter is enforced by dereferencing the key with `purpose`. * * Verifying the proof only establishes that *someone* signed; without this comparison a document * could name one party and be signed by an unrelated one. * * @param expectedSigner the `issuer` or `holder`. `Holder` is optional in * VCDM 2.0 so when undefined there is no claim to contradict, so the validation passes. */ async verifySignerIsExpectedParty(agentContext, securedDocument, purpose, expectedSigner, describeMismatch) { if (!expectedSigner) return { isValid: true }; try { const verificationMethod = await getVerificationMethodForDataIntegrityProof(agentContext, securedDocument, purpose, expectedSigner); if (verificationMethod.controller !== expectedSigner) return { isValid: false, error: new CredoError(describeMismatch(verificationMethod.id, expectedSigner)) }; return { isValid: true }; } catch (error) { return { isValid: false, error }; } } invalidResult(errors, target) { const invalidValidation = this.invalidValidation(errors); if (target === "credential") return { isValid: false, validations: { signature: invalidValidation } }; return this.invalidPresentationResult("presentationSignature", invalidValidation.error); } invalidValidation(errors) { return { isValid: false, error: createW3cDataIntegrityCredoError(errors) }; } invalidPresentationResult(field, error) { return { isValid: false, presentation: { isValid: false, validations: { [field]: { isValid: false, error } } }, credentialEntries: [] }; } }; W3cV2DataIntegrityCredentialService = __decorate([injectable(), __decorateMetadata("design:paramtypes", [typeof W3cDataIntegrityProofService === "undefined" ? Object : W3cDataIntegrityProofService, typeof W3cV2DataIntegrityContextValidator === "undefined" ? Object : W3cV2DataIntegrityContextValidator])], W3cV2DataIntegrityCredentialService); //#endregion export { W3cV2DataIntegrityCredentialService }; //# sourceMappingURL=W3cV2DataIntegrityCredentialService.mjs.map