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

1 lines • 10.1 kB
{"version":3,"file":"W3cV2DataIntegrityContextValidator.mjs","names":["jsonld"],"sources":["../../../../src/modules/vc/data-integrity/W3cV2DataIntegrityContextValidator.ts"],"sourcesContent":["import type { AgentContext } from '../../../agent/context'\nimport { injectable } from '../../../plugins'\nimport {\n createProofVerificationIssue,\n type W3cDataIntegrityProcessingIssue as DataIntegrityProcessingIssue,\n type W3cDataIntegrityUnsecuredDocument as DataIntegrityUnsecuredDocument,\n} from '../../w3c-di/internal'\nimport { omitUndefinedFields } from '../../w3c-di/proof-processing/normalization'\nimport { CREDENTIALS_CONTEXT_V2_URL } from '../constants'\nimport { DEFAULT_CONTEXTS, DI_SPEC_CONTEXT_HASHES } from '../jsonld/contexts'\nimport { JsonLdModuleConfig } from '../jsonld/JsonLdModuleConfig'\nimport jsonld from '../jsonld/jsonld'\nimport { W3cV2CredentialsModuleConfig } from '../W3cV2CredentialsModuleConfig'\n\n/**\n * Output of the VC Data Integrity §4.6 Context Validation algorithm.\n */\nexport interface W3cV2DataIntegrityContextValidationResult {\n validated: boolean\n validatedDocument: DataIntegrityUnsecuredDocument | null\n warnings: DataIntegrityProcessingIssue[]\n errors: DataIntegrityProcessingIssue[]\n}\n\n/**\n * Implements the VC Data Integrity §4.6 Context Validation algorithm.\n *\n * Algorithm inputs:\n * - inputDocument: the secured document (after proof verification)\n * - recompactInvalidContexts: whether to run JSON-LD compaction when §4.6 step 3 trigger conditions are detected\n *\n * Recompaction delegates context resolution to the shared\n * {@link JsonLdModuleConfig}, so a custom `documentLoader` configured on\n * `JsonLdModule`/`W3cCredentialsModule` is honoured consistently with VC 1.1\n * JSON-LD processing. Context hash verification (§2.4) only trusts Credo's\n * bundled context copies; it does not fetch remote content over the network.\n */\n@injectable()\nexport class W3cV2DataIntegrityContextValidator {\n public constructor(\n private readonly jsonLdModuleConfig: JsonLdModuleConfig,\n private readonly w3cV2CredentialsModuleConfig: W3cV2CredentialsModuleConfig\n ) {}\n\n public async validate(\n agentContext: AgentContext,\n inputDocument: DataIntegrityUnsecuredDocument\n ): Promise<W3cV2DataIntegrityContextValidationResult> {\n const normalisedInputDocument = omitUndefinedFields(inputDocument)\n const validatedDocument = { ...normalisedInputDocument }\n\n // §4.6, step 1: initialise result\n const result: W3cV2DataIntegrityContextValidationResult = {\n validated: false,\n validatedDocument,\n warnings: [],\n errors: [],\n }\n\n // §4.6, step 2: get contextValue\n const contextValue = normaliseContext(validatedDocument['@context'])\n\n // §2.4.2 Context Injection: context injection is only for securing, not verification.\n // A conforming verifier MUST NOT accept a document without top-level @context.\n if (validatedDocument['@context'] === undefined || validatedDocument['@context'] === null) {\n result.errors.push(\n createProofVerificationIssue(\n 'Missing top-level @context in secured document',\n 'Verification must not perform context injection (§2.4.2). Document must have explicit @context.'\n )\n )\n result.validated = false\n result.validatedDocument = null\n return result\n }\n\n // §4.6, step 3: detect trigger conditions\n const triggerErrors: DataIntegrityProcessingIssue[] = []\n\n // 3a: the mandatory VC 2.0 context must be first; additional contexts are permitted.\n if (contextValue[0] !== CREDENTIALS_CONTEXT_V2_URL) {\n triggerErrors.push(\n createProofVerificationIssue(\n '@context does not start with the required VC 2.0 context',\n `Document @context must start with '${CREDENTIALS_CONTEXT_V2_URL}'`\n )\n )\n }\n\n // 3b: any subtree contains @context (no exemptions per spec §4.6)\n const nestedContextPaths = collectAllNestedContextPaths(validatedDocument)\n if (nestedContextPaths.length > 0) {\n triggerErrors.push(\n createProofVerificationIssue(\n 'Nested @context detected in document',\n `Nested @context found at path(s): ${nestedContextPaths.join(', ')}`\n )\n )\n }\n\n // 3c: flag spec-pinned contexts that are not available as bundled, trusted contexts (§2.4)\n for (const contextEntry of contextValue) {\n if (typeof contextEntry !== 'string') continue\n\n if (contextEntry === CREDENTIALS_CONTEXT_V2_URL) continue\n if (!(contextEntry in DI_SPEC_CONTEXT_HASHES)) continue\n\n const bundledContextIssue = this.verifyContextIsBundled(contextEntry)\n if (bundledContextIssue) {\n triggerErrors.push(bundledContextIssue)\n break\n }\n }\n\n if (triggerErrors.length > 0) {\n if (this.w3cV2CredentialsModuleConfig.recompactInvalidContexts) {\n try {\n result.validatedDocument = (await jsonld.compact(normalisedInputDocument, [CREDENTIALS_CONTEXT_V2_URL], {\n documentLoader: this.jsonLdModuleConfig.documentLoader(agentContext),\n compactToRelative: false,\n })) as DataIntegrityUnsecuredDocument\n\n // Preserve trigger-condition visibility when recompaction succeeds.\n result.warnings.push(...triggerErrors)\n } catch (error) {\n result.errors.push(\n createProofVerificationIssue(\n 'Context recompaction failed (§4.6 step 3.1)',\n error instanceof Error ? error.message : 'JSON-LD compaction failed'\n )\n )\n }\n } else {\n result.errors.push(...triggerErrors)\n }\n }\n\n // §4.6, step 4: finalise\n if (result.errors.length === 0) {\n result.validated = true\n } else {\n result.validated = false\n result.validatedDocument = null\n }\n\n // §4.6, step 5: return\n return result\n }\n\n /**\n * Verifies a spec-pinned context URI (§2.4). The §2.4 hash-pinning mechanism exists for\n * processors that don't vendor the canonical contexts and must instead fetch and hash-verify\n * them over the network; Credo vendors all spec-required contexts, so a bundled copy is\n * trusted as canonical and no network fetch or hash comparison is performed.\n */\n private verifyContextIsBundled(contextUri: string): DataIntegrityProcessingIssue | undefined {\n if (hasBundledContext(contextUri)) {\n return undefined\n }\n\n return createProofVerificationIssue(\n 'Unable to verify context hash (§2.4)',\n `Context '${contextUri}' is not available as a bundled context and cannot be hash-verified`\n )\n }\n}\n\nfunction normaliseContext(context: unknown): unknown[] {\n if (context === undefined || context === null) return []\n return Array.isArray(context) ? context : [context]\n}\n\nfunction collectAllNestedContextPaths(value: unknown, path: string[] = []): string[] {\n if (value === null || typeof value !== 'object') return []\n\n if (Array.isArray(value)) {\n return value.flatMap((item, i) => collectAllNestedContextPaths(item, [...path, String(i)]))\n }\n\n const obj = value as Record<string, unknown>\n const paths: string[] = []\n\n for (const [key, entry] of Object.entries(obj)) {\n const nextPath = [...path, key]\n const isTopLevelContext = path.length === 0 && key === '@context'\n\n if (key === '@context' && !isTopLevelContext) {\n paths.push(nextPath.join('.'))\n }\n\n paths.push(...collectAllNestedContextPaths(entry, nextPath))\n }\n\n return paths\n}\n\nfunction hasBundledContext(contextUrl: string): boolean {\n if (contextUrl in DEFAULT_CONTEXTS) return true\n const withoutFragment = contextUrl.split('#')[0]\n return withoutFragment in DEFAULT_CONTEXTS\n}\n"],"mappings":"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"}