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

229 lines (228 loc) • 12.4 kB
import { asArray } from "../../utils/array.mjs"; import { equalsIgnoreOrder } from "../../utils/deepEquality.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 { omitUndefinedFields } from "./proof-processing/normalization.mjs"; import { W3cDataIntegrityProcessingError, createInvalidResult, createIssue } from "./W3cDataIntegrityError.mjs"; import { W3cDataIntegrityCryptosuiteRegistry } from "./W3cDataIntegrityCryptosuiteRegistry.mjs"; import { assertCreatedProofPostconditions, validateProofFieldFormats, validateProofRequiredMembers } from "./proof-processing/proofValidation.mjs"; import { assertMultiProofDocument, assertSingleProofDocument, createW3cDataIntegrityProofOptions } from "./W3cDataIntegrityProof.mjs"; import { parseW3cDataIntegrityProofDocument } from "./proof-processing/parsing.mjs"; import { validateProofPurposeVerificationRelationship } from "./proof-processing/proofPurposeValidation.mjs"; import { validateProofChainStructure } from "./proof-processing/proofSetValidation.mjs"; //#region src/modules/w3c-di/W3cDataIntegrityProofService.ts let W3cDataIntegrityProofService = class W3cDataIntegrityProofService { constructor(dataIntegrityCryptosuiteRegistry) { this.supportedMediaTypes = [ "application/json", "application/ld+json", "application/vc+ld+json" ]; this.dataIntegrityCryptosuiteRegistry = dataIntegrityCryptosuiteRegistry; } /** * Implements VC Data Integrity v1.0 §4.2 "Add Proof" orchestration. * Delegates cryptographic suite proof creation and enforces postconditions. */ async createProof(agentContext, options) { const normalizedUnsecuredDocument = omitUndefinedFields(options.unsecuredDocument); let cryptosuite; try { cryptosuite = this.dataIntegrityCryptosuiteRegistry.createByCryptosuite(agentContext, options.cryptosuite); } catch (error) { if (!(error instanceof Error)) throw error; return { created: false, proof: null, errors: [error instanceof W3cDataIntegrityProcessingError ? error.issue : createIssue("https://w3id.org/security#PROOF_GENERATION_ERROR", "Error creating Data Integrity proof", error.message)] }; } const proofOptions = createW3cDataIntegrityProofOptions(omitUndefinedFields({ cryptosuite: cryptosuite.cryptosuite, verificationMethod: options.verificationMethod, proofPurpose: options.proofPurpose, created: options.created, expires: options.expires, challenge: options.challenge, domain: options.domain, nonce: options.nonce, previousProof: options.previousProof })); try { const proof = await cryptosuite.createProof(normalizedUnsecuredDocument, proofOptions); assertCreatedProofPostconditions(proof, { ...proofOptions, verificationMethod: options.verificationMethod }, cryptosuite.cryptosuite); return { created: true, proof }; } catch (error) { if (!(error instanceof Error)) throw error; return { created: false, proof: null, errors: [error instanceof W3cDataIntegrityProcessingError ? error.issue : createIssue("https://w3id.org/security#PROOF_GENERATION_ERROR", "Error creating Data Integrity proof", error.message)] }; } } /** * Implements VC Data Integrity v1.0 §4.4 "Verify Proof" algorithm. * Steps 4-8 are enforced through required-member gate, policy checks, * field-format validation, and cryptosuite verification. */ async verifyProof(agentContext, securedDocument, options = {}) { try { const normalizedSecuredDocument = omitUndefinedFields(securedDocument); assertSingleProofDocument(normalizedSecuredDocument); return await this.verifySingleProofCore(agentContext, normalizedSecuredDocument, options); } catch (error) { if (!(error instanceof Error)) throw error; const issue = error instanceof W3cDataIntegrityProcessingError ? error.issue : createIssue("https://w3id.org/security#PROOF_VERIFICATION_ERROR", "Error verifying Data Integrity proof", error.message); return createInvalidResult(issue); } } /** * Implements VC Data Integrity v1.0 §4.5 "Verify Proof Sets and Chains" algorithm. * Performs chain-structure validation, then verifies each proof against * reconstructed dependency-proof input. */ async verifyProofSetAndChain(agentContext, securedDocument, options = {}) { try { const normalizedSecuredDocument = omitUndefinedFields(securedDocument); assertMultiProofDocument(normalizedSecuredDocument); const proofs = normalizedSecuredDocument.proof; const requiredMemberIssues = proofs.map((proof, index) => { const requiredMemberValidationError = validateProofRequiredMembers(proof); if (!requiredMemberValidationError) return void 0; return createIssue("https://w3id.org/security#PROOF_VERIFICATION_ERROR", `Proof at index ${index} has invalid required members`, requiredMemberValidationError); }).filter((issue) => issue !== void 0); if (requiredMemberIssues.length > 0) return createInvalidResult(requiredMemberIssues[0], requiredMemberIssues.slice(1)); const proofChainIssues = validateProofChainStructure(proofs); if (proofChainIssues.length > 0) return createInvalidResult(proofChainIssues[0], proofChainIssues.slice(1)); const proofPolicyIssues = proofs.map((proof, index) => { const proofPolicyIssue = this.validateProofPolicy(proof, options); if (!proofPolicyIssue) return void 0; return createIssue(proofPolicyIssue.type, `Proof at index ${index} failed verification policy pre-check`, proofPolicyIssue.detail ?? proofPolicyIssue.title); }).filter((issue) => issue !== void 0); if (proofPolicyIssues.length > 0) return createInvalidResult(proofPolicyIssues[0], proofPolicyIssues.slice(1)); const proofFieldFormatIssues = proofs.map((proof, index) => { const proofFieldFormatError = validateProofFieldFormats(proof); if (!proofFieldFormatError) return void 0; return createIssue("https://w3id.org/security#PROOF_VERIFICATION_ERROR", `Proof at index ${index} has invalid field formats`, proofFieldFormatError.errors[0]?.detail ?? proofFieldFormatError.errors[0]?.title); }).filter((issue) => issue !== void 0); if (proofFieldFormatIssues.length > 0) return createInvalidResult(proofFieldFormatIssues[0], proofFieldFormatIssues.slice(1)); const proofIdToIndex = this.createProofIdToIndexMap(proofs); const { proof: _, ...unsecuredDocument } = normalizedSecuredDocument; for (const [_index, proof] of proofs.entries()) { const matchingProofs = this.getMatchingProofIndices(proof, proofIdToIndex).map((matchingProofIndex) => proofs[matchingProofIndex]); const singleProofSecuredDocument = { ...unsecuredDocument, proof }; const chainInputDocument = this.reconstructProofInputDocument(unsecuredDocument, matchingProofs); const verificationResult = await this.verifySingleProofCore(agentContext, singleProofSecuredDocument, options, chainInputDocument); if (!verificationResult.verified) return verificationResult; } return { verified: true, verifiedDocument: unsecuredDocument, mediaType: null }; } catch (error) { if (!(error instanceof Error)) throw error; const issue = error instanceof W3cDataIntegrityProcessingError ? error.issue : createIssue("https://w3id.org/security#PROOF_VERIFICATION_ERROR", "Error verifying Data Integrity proof set/chain", error.message); return createInvalidResult(issue); } } /** * Parses a secured document and dispatches to §4.4 or §4.5 verification paths. */ async verifyProofDocument(agentContext, options) { const parseResult = parseW3cDataIntegrityProofDocument(options, this.supportedMediaTypes); if (!parseResult.ok) return parseResult.result; const { normalizedMediaType, securedDocument, verifyOptions } = parseResult.value; const verificationResult = Array.isArray(securedDocument.proof) ? await this.verifyProofSetAndChain(agentContext, securedDocument, verifyOptions) : await this.verifyProof(agentContext, securedDocument, verifyOptions); if (!verificationResult.verified) return verificationResult; return { ...verificationResult, mediaType: normalizedMediaType }; } /** * Implements VC Data Integrity v1.0 §4.4 steps 5-8 for a single-proof input. * Runs proof checks, field-format checks, relationship validation, and cryptosuite verification. */ async verifySingleProofCore(agentContext, securedDocument, options, unsecuredDocument = (() => { const { proof: _, ...unsecuredDocument } = securedDocument; return unsecuredDocument; })()) { const proof = securedDocument.proof; const proofPolicyIssue = this.validateProofPolicy(proof, options); if (proofPolicyIssue) return createInvalidResult(proofPolicyIssue); const proofFieldFormatError = validateProofFieldFormats(proof); if (proofFieldFormatError) return proofFieldFormatError; const proofPurposeValidationError = await validateProofPurposeVerificationRelationship(agentContext, proof); if (proofPurposeValidationError) return proofPurposeValidationError; const cryptosuite = this.dataIntegrityCryptosuiteRegistry.createByCryptosuite(agentContext, proof.cryptosuite); const proofVerificationInput = { unsecuredDocument, proof }; const verificationResult = await cryptosuite.verifyProof(proofVerificationInput); if (!verificationResult.verified || verificationResult.verifiedDocument === null) return createInvalidResult(createIssue("https://w3id.org/security#PROOF_VERIFICATION_ERROR", "Cryptosuite proof verification failed")); return { verified: true, verifiedDocument: verificationResult.verifiedDocument, mediaType: null }; } /** * Implements VC Data Integrity v1.0 §4.4 steps 5-7 verification policy checks. * Validates expected proofPurpose, domain, and challenge when provided. */ validateProofPolicy(proof, options) { if (options.expectedProofPurpose && proof.proofPurpose !== options.expectedProofPurpose) return createIssue("https://w3id.org/security#PROOF_VERIFICATION_ERROR", "Proof purpose does not match expected proof purpose", `Expected '${options.expectedProofPurpose}', received '${proof.proofPurpose}'`); if (options.domain) { const expectedDomain = [...new Set(asArray(options.domain))]; const proofDomain = [...new Set(asArray(proof.domain))]; if (!equalsIgnoreOrder(expectedDomain, proofDomain)) return createIssue("https://w3id.org/security#INVALID_DOMAIN_ERROR", "Proof domain does not match expected domain"); } if (options.challenge && proof.challenge !== options.challenge) return createIssue("https://w3id.org/security#INVALID_CHALLENGE_ERROR", "Proof challenge does not match expected challenge", `Expected '${options.challenge}', received '${proof.challenge ?? "undefined"}'`); } createProofIdToIndexMap(proofs) { const proofIdToIndex = /* @__PURE__ */ new Map(); for (const [index, proof] of proofs.entries()) { if (typeof proof.id !== "string") continue; proofIdToIndex.set(proof.id, index); } return proofIdToIndex; } getMatchingProofIndices(proof, proofIdToIndex) { const previousProofReferences = proof.previousProof ? Array.isArray(proof.previousProof) ? proof.previousProof : [proof.previousProof] : []; const matchingProofIndices = []; for (const previousProofReference of previousProofReferences) { const matchingProofIndex = proofIdToIndex.get(previousProofReference); if (matchingProofIndex !== void 0) matchingProofIndices.push(matchingProofIndex); } return matchingProofIndices; } /** * Implements VC Data Integrity v1.0 §4.5 step 3.3 input reconstruction. * Adds matching dependency proofs into the unsecured document for per-proof verification. */ reconstructProofInputDocument(unsecuredDocument, matchingProofs) { if (matchingProofs.length === 0) return unsecuredDocument; return { ...unsecuredDocument, proof: matchingProofs.length === 1 ? matchingProofs[0] : matchingProofs }; } }; W3cDataIntegrityProofService = __decorate([injectable(), __decorateMetadata("design:paramtypes", [typeof W3cDataIntegrityCryptosuiteRegistry === "undefined" ? Object : W3cDataIntegrityCryptosuiteRegistry])], W3cDataIntegrityProofService); //#endregion export { W3cDataIntegrityProofService }; //# sourceMappingURL=W3cDataIntegrityProofService.mjs.map