@credo-ts/core 0.7.3 → 0.7.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/agent/TrustedIssuerContext.d.mts +11 -1
- package/build/agent/TrustedIssuerContext.d.mts.map +1 -1
- package/build/agent/TrustedIssuerContext.mjs +7 -0
- package/build/agent/TrustedIssuerContext.mjs.map +1 -1
- package/build/agent/TrustedIssuersForVerification.d.mts +11 -1
- package/build/agent/TrustedIssuersForVerification.d.mts.map +1 -1
- package/build/index.d.mts +3 -3
- package/build/index.mjs +2 -2
- package/build/modules/dcql/DcqlService.d.mts.map +1 -1
- package/build/modules/dcql/DcqlService.mjs +24 -12
- package/build/modules/dcql/DcqlService.mjs.map +1 -1
- package/build/modules/dids/domain/verificationMethod/JsonWebKey2020.d.mts +24 -24
- package/build/modules/dids/methods/jwk/DidJwk.d.mts +24 -24
- package/build/modules/dids/services/DidResolverService.d.mts +4 -0
- package/build/modules/dids/services/DidResolverService.d.mts.map +1 -1
- package/build/modules/dids/services/DidResolverService.mjs +21 -5
- package/build/modules/dids/services/DidResolverService.mjs.map +1 -1
- package/build/modules/kms/KeyManagementApi.d.mts +14 -14
- package/build/modules/kms/jwk/jwa.d.mts +1 -1
- package/build/modules/kms/jwk/knownJwk.d.mts +4 -4
- package/build/modules/kms/jwk/kty/ec/P256PublicJwk.d.mts +1 -1
- package/build/modules/kms/jwk/kty/ec/P384PublicJwk.d.mts +1 -1
- package/build/modules/kms/jwk/kty/ec/P521PublicJwk.d.mts +1 -1
- package/build/modules/kms/jwk/kty/ec/Secp256k1PublicJwk.d.mts +1 -1
- package/build/modules/kms/jwk/kty/okp/Ed25519PublicJwk.d.mts +2 -2
- package/build/modules/kms/jwk/kty/okp/X25519PublicJwk.d.mts +1 -1
- package/build/modules/kms/jwk/kty/okp/okpJwk.d.mts +2 -2
- package/build/modules/kms/options/KmsCreateKeyOptions.d.mts +3 -3
- package/build/modules/kms/options/KmsSignOptions.d.mts +1 -1
- package/build/modules/kms/options/KmsVerifyOptions.d.mts +2 -2
- package/build/modules/mdoc/repository/MdocRecord.d.mts +1 -1
- package/build/modules/sd-jwt-vc/repository/SdJwtVcRecord.d.mts +1 -1
- package/build/modules/sd-jwt-vc/utils.mjs +4 -4
- package/build/modules/sd-jwt-vc/utils.mjs.map +1 -1
- package/build/modules/vc/W3cCredentialServiceOptions.d.mts +5 -5
- package/build/modules/vc/W3cCredentialServiceOptions.d.mts.map +1 -1
- package/build/modules/vc/W3cV2CredentialService.d.mts +5 -1
- package/build/modules/vc/W3cV2CredentialService.d.mts.map +1 -1
- package/build/modules/vc/W3cV2CredentialService.mjs +72 -26
- package/build/modules/vc/W3cV2CredentialService.mjs.map +1 -1
- package/build/modules/vc/W3cV2CredentialServiceOptions.d.mts +14 -6
- package/build/modules/vc/W3cV2CredentialServiceOptions.d.mts.map +1 -1
- package/build/modules/vc/data-integrity/W3cV2DataIntegrityCredentialService.d.mts +0 -18
- package/build/modules/vc/data-integrity/W3cV2DataIntegrityCredentialService.d.mts.map +1 -1
- package/build/modules/vc/data-integrity/W3cV2DataIntegrityCredentialService.mjs +38 -36
- package/build/modules/vc/data-integrity/W3cV2DataIntegrityCredentialService.mjs.map +1 -1
- package/build/modules/vc/data-integrity/v2-di-utils.mjs +17 -35
- package/build/modules/vc/data-integrity/v2-di-utils.mjs.map +1 -1
- package/build/modules/vc/jwt-vc/W3cJwtCredentialService.d.mts +0 -23
- package/build/modules/vc/jwt-vc/W3cJwtCredentialService.d.mts.map +1 -1
- package/build/modules/vc/jwt-vc/W3cJwtCredentialService.mjs +17 -68
- package/build/modules/vc/jwt-vc/W3cJwtCredentialService.mjs.map +1 -1
- package/build/modules/vc/jwt-vc/W3cV2JwtCredentialService.d.mts.map +1 -1
- package/build/modules/vc/jwt-vc/W3cV2JwtCredentialService.mjs +11 -17
- package/build/modules/vc/jwt-vc/W3cV2JwtCredentialService.mjs.map +1 -1
- package/build/modules/vc/linked-data-proofs/W3cJsonLdCredentialService.d.mts +5 -0
- package/build/modules/vc/linked-data-proofs/W3cJsonLdCredentialService.d.mts.map +1 -1
- package/build/modules/vc/linked-data-proofs/W3cJsonLdCredentialService.mjs +18 -4
- package/build/modules/vc/linked-data-proofs/W3cJsonLdCredentialService.mjs.map +1 -1
- package/build/modules/vc/models/W3cV2VerifyResult.d.mts +6 -0
- package/build/modules/vc/models/W3cV2VerifyResult.d.mts.map +1 -1
- package/build/modules/vc/resolveSigner.mjs +74 -0
- package/build/modules/vc/resolveSigner.mjs.map +1 -0
- package/build/modules/vc/sd-jwt-vc/W3cV2SdJwtCredentialService.d.mts.map +1 -1
- package/build/modules/vc/sd-jwt-vc/W3cV2SdJwtCredentialService.mjs +16 -18
- package/build/modules/vc/sd-jwt-vc/W3cV2SdJwtCredentialService.mjs.map +1 -1
- package/build/modules/vc/sd-jwt-vc/disclosureFrame.d.mts +1 -1
- package/build/modules/vc/sd-jwt-vc/disclosureFrame.d.mts.map +1 -1
- package/build/modules/vc/sd-jwt-vc/disclosureFrame.mjs +2 -1
- package/build/modules/vc/sd-jwt-vc/disclosureFrame.mjs.map +1 -1
- package/build/modules/vc/v2-jwt-utils.mjs +54 -55
- package/build/modules/vc/v2-jwt-utils.mjs.map +1 -1
- package/build/modules/x509/X509ModuleConfig.d.mts +5 -13
- package/build/modules/x509/X509ModuleConfig.d.mts.map +1 -1
- package/build/modules/x509/X509ModuleConfig.mjs +2 -1
- package/build/modules/x509/X509ModuleConfig.mjs.map +1 -1
- package/build/modules/x509/X509RevocationService.d.mts.map +1 -1
- package/build/modules/x509/X509RevocationService.mjs +17 -20
- package/build/modules/x509/X509RevocationService.mjs.map +1 -1
- package/build/modules/x509/X509Service.mjs +1 -1
- package/build/modules/x509/X509Service.mjs.map +1 -1
- package/build/modules/x509/X509ServiceOptions.d.mts +4 -6
- package/build/modules/x509/X509ServiceOptions.d.mts.map +1 -1
- package/build/modules/x509/X509ValidationOptions.d.mts +19 -2
- package/build/modules/x509/X509ValidationOptions.d.mts.map +1 -1
- package/build/modules/x509/X509ValidationOptions.mjs +21 -1
- package/build/modules/x509/X509ValidationOptions.mjs.map +1 -1
- package/build/modules/x509/index.d.mts +2 -2
- package/build/modules/x509/index.mjs +2 -2
- package/build/modules/x509/utils/crlFetcher.mjs +1 -11
- package/build/modules/x509/utils/crlFetcher.mjs.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"X509RevocationService.mjs","names":[],"sources":["../../../src/modules/x509/X509RevocationService.ts"],"sourcesContent":["import type { AgentContext } from '../../agent'\nimport { CredoWebCrypto } from '../../crypto/webcrypto'\nimport { injectable } from '../../plugins'\nimport { validateCriticalCrlExtensionIds } from './utils/criticalExtensions'\nimport { fetchCrl, getCachedCrlSummary, setCachedCrlSummary } from './utils/crlFetcher'\nimport {\n buildCrlSummary,\n CrlSummaryVerifiedCrl,\n computeCrlSha256,\n computeIssuerNameSha256,\n computeIssuerPublicJwkThumbprint,\n type VerifiedCrl,\n type X509CrlSummary,\n} from './utils/crlSummary'\nimport { X509Certificate, X509KeyUsage } from './X509Certificate'\nimport { X509CertificateRevocationList, type X509CertificateRevocationListEntry } from './X509CertificateRevocationList'\nimport { ALL_REVOCATION_REASONS_MASK, X509RevocationReason } from './X509CrlDistributionPoint'\nimport { X509CrlUnavailableError, X509Error } from './X509Error'\nimport { X509ModuleConfig } from './X509ModuleConfig'\nimport type {\n X509CheckCertificateRevocationOptions,\n X509FetchCertificateRevocationListOptions,\n} from './X509ServiceOptions'\nimport { X509RevocationCheckMode, type X509RevocationCheckOptions } from './X509ValidationOptions'\nimport type { X509CertificateSingleValidationResult } from './X509ValidationResult'\n\nexport interface X509RevocationCheckResult {\n isValid: boolean\n isRevoked?: boolean\n error?: Error\n details?: string\n method?: 'crl'\n}\n\n/**\n * Default freshness period for a cached verified CRL summary (1 hour): how long it is served\n * without being revalidated against freshly fetched CRL bytes.\n */\nconst DEFAULT_CRL_CACHE_EXPIRY_SECONDS = 3600\n\ninterface FetchCrlOptions {\n url: string\n issuerCertificate: X509Certificate\n verificationDate: Date\n options: { timeoutMs?: number; maxCrlSizeBytes?: number; crlCacheExpirySeconds?: number }\n useCache: boolean\n}\n\n@injectable()\n// biome-ignore lint/complexity/noStaticOnlyClass: Service class pattern used in Credo\nexport class X509RevocationService {\n /**\n * Check the revocation status of a single certificate using CRL.\n */\n public static async checkCertificateRevocation(\n agentContext: AgentContext,\n { certificate, issuerCertificate, revocationCheckOptions }: X509CheckCertificateRevocationOptions\n ): Promise<X509RevocationCheckResult> {\n const parsedCertificate =\n certificate instanceof X509Certificate ? certificate : X509Certificate.fromEncodedCertificate(certificate)\n const parsedIssuerCertificate =\n issuerCertificate instanceof X509Certificate\n ? issuerCertificate\n : X509Certificate.fromEncodedCertificate(issuerCertificate)\n\n const options = revocationCheckOptions ??\n agentContext.dependencyManager.resolve(X509ModuleConfig).revocationCheck ?? {\n mode: X509RevocationCheckMode.SoftFail,\n }\n\n const mode = options.mode ?? X509RevocationCheckMode.SoftFail\n if (mode === X509RevocationCheckMode.Disabled) {\n return { isValid: true, details: 'Revocation checking disabled' }\n }\n\n try {\n return await X509RevocationService.checkCrl(agentContext, parsedCertificate, parsedIssuerCertificate, options)\n } catch (error) {\n return X509RevocationService.handleRevocationError(error, mode)\n }\n }\n\n /**\n * Check the revocation status of every certificate in a chain (the leaf and, unless\n * `checkFullChain` is disabled, the intermediate CAs). The root is skipped as it is self-issued and\n * not covered by a CRL we can fetch and verify.\n *\n * The chain must be ordered from root (index 0) to leaf (last index); each certificate's issuer is\n * the certificate that precedes it in the chain.\n *\n * The same CRL handling limitations as {@link checkCertificateRevocation} apply (CRL only; delta\n * and indirect CRLs rejected; no IDP distribution-point-name matching; no `onlySomeReasons`\n * cross-check; Freshest CRL / AIA and `invalidityDate` not processed).\n */\n public static async checkCertificateChainRevocation(\n agentContext: AgentContext,\n certificateChain: X509Certificate[],\n config: X509ModuleConfig,\n verificationDate: Date\n ): Promise<X509CertificateSingleValidationResult> {\n const configuredRevocationCheck = config.revocationCheck\n if (!configuredRevocationCheck || configuredRevocationCheck.mode === X509RevocationCheckMode.Disabled) {\n return { isValid: true, details: 'Revocation checking disabled' }\n }\n\n // Use the chain's verificationDate for CRL validity (thisUpdate/nextUpdate) checks\n // unless the revocation config explicitly overrides it.\n const revocationConfig: X509RevocationCheckOptions = {\n ...configuredRevocationCheck,\n verificationDate: configuredRevocationCheck.verificationDate ?? verificationDate,\n }\n\n // The chain is ordered from root (index 0) to leaf (last index). A certificate's issuer is the\n // certificate that precedes it in the chain. The root certificate (index 0) is skipped.\n const checkFullChain = revocationConfig.checkFullChain ?? true\n const startIndex = checkFullChain ? 1 : certificateChain.length - 1\n\n for (let i = startIndex; i < certificateChain.length; i++) {\n const certificate = certificateChain[i]\n // The issuer is the preceding certificate in the chain.\n const issuerCertificate = certificateChain[i - 1]\n\n if (!issuerCertificate) {\n // No issuer available (e.g. a single self-signed certificate); nothing to check.\n continue\n }\n\n const result = await X509RevocationService.checkCertificateRevocation(agentContext, {\n certificate,\n issuerCertificate,\n revocationCheckOptions: revocationConfig,\n })\n\n if (!result.isValid) {\n return {\n isValid: false,\n error: result.error,\n details: result.details,\n }\n }\n }\n\n return { isValid: true, details: 'No certificates are revoked' }\n }\n\n /**\n * Checks the revocation status of a certificate using CRL with proper reason partitioning\n * Based on RFC 5280 Section 5.2.5\n */\n private static async checkCrl(\n agentContext: AgentContext,\n certificate: X509Certificate,\n issuerCertificate: X509Certificate,\n options: X509RevocationCheckOptions\n ): Promise<X509RevocationCheckResult> {\n // Get CRL distribution points from certificate\n const distributionPoints = certificate.crlDistributionPoints\n if (distributionPoints.length === 0) {\n return {\n isValid: true,\n details: 'No CRL distribution points in certificate',\n }\n }\n\n const verificationDate = options.verificationDate ?? new Date()\n\n // Step 1: Check if there's a \"full\" distribution point (no reasons specified). Indirect CRLs\n // (those with a crlIssuer) are excluded here - we can't verify them, so they fall through to the\n // partitioned path below where they are tracked as an unsupported feature instead of being\n // fetched and failing verification against the wrong issuer.\n const fullDistributionPoint = distributionPoints.find(\n (dp) => dp.reasons === undefined && dp.crlIssuer === undefined\n )\n\n if (fullDistributionPoint) {\n // Full DP covers all reasons - just fetch and check this one CRL\n return await X509RevocationService.checkDistributionPoint(\n agentContext,\n certificate,\n issuerCertificate,\n fullDistributionPoint,\n verificationDate,\n options\n )\n }\n\n // Step 2: No full DP - need to ensure required reasons are covered by fetching multiple CRLs\n // Calculate which reasons we need to cover\n // Default: require all reasons that are actually present in the certificate's DPs\n // (no point requiring reasons the issuer never published DPs for)\n const requiredReasonsMask = options.requiredReasons\n ? X509RevocationService.calculateReasonMask(options.requiredReasons)\n : X509RevocationService.calculateAllReasonsInDPs(distributionPoints)\n\n let coveredReasons = 0 // Bitmask of covered reason codes\n const coveredCrls: VerifiedCrl[] = []\n const fetchedDPs: string[] = []\n const fetchErrors: string[] = [] // Track errors for DPs we couldn't fetch\n const unsupportedDPs: Array<{ reasons: number; feature: string }> = [] // Track DPs we can't process\n\n // Whether any DP downloaded a CRL that then failed verification/validity\n let integrityFailure = false\n\n for (const dp of distributionPoints) {\n // Calculate which reasons this DP covers\n const dpReasonMask = X509RevocationService.calculateReasonMask(dp.reasons)\n\n // Check if this DP adds any new coverage for reasons we care about\n const newCoverage = dpReasonMask & requiredReasonsMask & ~coveredReasons\n if (newCoverage === 0) {\n // This DP doesn't add any new reason coverage for required reasons, skip it\n continue\n }\n\n // Check for unsupported features\n if (dp.crlIssuer !== undefined) {\n // This DP uses an indirect CRL (different issuer) which we don't support\n // Track it but don't fail immediately - we might cover these reasons with other DPs\n unsupportedDPs.push({ reasons: dpReasonMask, feature: `crlIssuer (${dp.crlIssuer})` })\n continue\n }\n\n // Try to fetch CRL from this distribution point\n const result = await X509RevocationService.fetchAndVerifyCrl(\n agentContext,\n certificate,\n dp.urls,\n issuerCertificate,\n verificationDate,\n options\n )\n\n if (!result.success) {\n // CRL fetch failed - track the error but don't fail immediately\n // Another DP might cover the same reasons\n fetchErrors.push(`URLs ${dp.urls.join(', ')}: ${result.error}`)\n\n // A downloaded-but-rejected CRL is an integrity failure\n if (result.reachable) integrityFailure = true\n\n continue\n }\n\n // Successfully fetched CRL - mark these reasons as covered\n coveredReasons |= dpReasonMask\n fetchedDPs.push(result.usedUrl)\n\n // Keep the verified CRL so we can look up the certificate using the same\n // (format-normalizing) matching logic as the single distribution point path.\n coveredCrls.push(result.crl)\n\n // Check if we've covered all required reasons\n if ((coveredReasons & requiredReasonsMask) === requiredReasonsMask) {\n break\n }\n }\n\n // Step 3: Check if the certificate is revoked in any CRL we DID collect. Presence on a scoped\n // CRL is a definitive revocation regardless of whether every required reason partition was\n // obtained, so this must be checked BEFORE concluding anything about coverage completeness -\n // otherwise an unreachable partition could mask a revocation we already have proof of.\n // Use the CRL's own findRevoked so serial number formats are normalized consistently with the\n // single distribution point path.\n let revokedEntry: X509CertificateRevocationListEntry | null = null\n for (const crl of coveredCrls) {\n revokedEntry = crl.findRevoked(certificate)\n if (revokedEntry) break\n }\n\n if (revokedEntry) {\n return {\n isValid: false,\n isRevoked: true,\n error: new X509Error(\n `Certificate '${certificate.subject}' has been revoked. ` +\n `Revocation date: ${revokedEntry.revocationDate.toISOString()}` +\n (revokedEntry.reason !== undefined ? `, reason: ${revokedEntry.reason}` : '')\n ),\n details: `Revoked on ${revokedEntry.revocationDate.toISOString()}`,\n method: 'crl',\n }\n }\n\n // Step 4: The certificate was not found revoked. We can only conclude it is NOT revoked if we\n // actually covered all required revocation reasons; otherwise the status is indeterminate.\n if ((coveredReasons & requiredReasonsMask) !== requiredReasonsMask) {\n const missingReasons = requiredReasonsMask & ~coveredReasons\n const coveredNames = X509RevocationService.reasonMaskToNames(coveredReasons)\n const requiredNames = X509RevocationService.reasonMaskToNames(requiredReasonsMask)\n const missingNames = X509RevocationService.reasonMaskToNames(missingReasons)\n\n // Build detailed error message\n let errorMsg =\n `Unable to cover all required revocation reasons. ` +\n `Covered: [${coveredNames.join(', ')}], ` +\n `Required: [${requiredNames.join(', ')}], ` +\n `Missing: [${missingNames.join(', ')}]. ` +\n `Certificate revocation status cannot be fully determined.`\n\n // Include unsupported DP info if any missing reasons could have been covered by them\n let unsupportedCoversMissing = false\n for (const unsupported of unsupportedDPs) {\n if (unsupported.reasons & missingReasons) {\n unsupportedCoversMissing = true\n const unsupportedReasons = X509RevocationService.reasonMaskToNames(unsupported.reasons & missingReasons)\n errorMsg += ` Unsupported CRL feature ${unsupported.feature} covers missing reasons: [${unsupportedReasons.join(', ')}].`\n }\n }\n\n if (fetchErrors.length > 0) {\n errorMsg += ` Fetch errors: ${fetchErrors.join('; ')}`\n }\n\n // Check if the missing reasons are covered by any DP (even if we couldn't fetch its CRL):\n // if not, it's a structural gap that can't be soft-failed.\n const reasonsPublishedByAnyDp = X509RevocationService.calculateAllReasonsInDPs(distributionPoints)\n const structuralGap = (missingReasons & ~reasonsPublishedByAnyDp) !== 0\n\n // The coverage gap is soft-failable only when it is purely caused by CRLs we could not reach;\n // an integrity failure, an unsupported feature, or a structural gap is never soft-failed.\n const availabilityOnly = !integrityFailure && !unsupportedCoversMissing && !structuralGap\n\n throw availabilityOnly ? new X509CrlUnavailableError(errorMsg) : new X509Error(errorMsg)\n }\n\n // Certificate not found in any CRL and all required reasons covered - it's valid\n return {\n isValid: true,\n isRevoked: false,\n details: `Certificate not found in CRLs from: ${fetchedDPs.join(', ')}`,\n method: 'crl',\n }\n }\n\n /**\n * Calculate bitmask of revocation reasons covered by a distribution point\n */\n private static calculateReasonMask(reasons: number[] | undefined): number {\n if (!reasons || reasons.length === 0) {\n // No reasons specified means this DP covers ALL reasons\n return ALL_REVOCATION_REASONS_MASK\n }\n\n // Convert array of reason codes to bitmask\n let mask = 0\n for (const reason of reasons) {\n if (reason >= 0 && reason <= 8) {\n mask |= 1 << reason\n }\n }\n return mask\n }\n\n /**\n * Calculate the union of all reasons present in distribution points\n * Used to determine the default required reasons (no point requiring reasons the issuer never published)\n */\n private static calculateAllReasonsInDPs(\n distributionPoints: Array<{ urls: string[]; reasons?: number[]; crlIssuer?: string }>\n ): number {\n let allReasons = 0\n for (const dp of distributionPoints) {\n allReasons |= X509RevocationService.calculateReasonMask(dp.reasons)\n }\n return allReasons\n }\n\n /**\n * Convert a bitmask of revocation reasons to human-readable names\n */\n private static reasonMaskToNames(mask: number): string[] {\n const names: string[] = []\n const reasonNames: Record<number, string> = {\n [X509RevocationReason.Unused]: 'unused',\n [X509RevocationReason.KeyCompromise]: 'keyCompromise',\n [X509RevocationReason.CACompromise]: 'cACompromise',\n [X509RevocationReason.AffiliationChanged]: 'affiliationChanged',\n [X509RevocationReason.Superseded]: 'superseded',\n [X509RevocationReason.CessationOfOperation]: 'cessationOfOperation',\n [X509RevocationReason.CertificateHold]: 'certificateHold',\n [X509RevocationReason.PrivilegeWithdrawn]: 'privilegeWithdrawn',\n [X509RevocationReason.AACompromise]: 'aACompromise',\n }\n\n for (let i = 0; i <= 8; i++) {\n if (mask & (1 << i)) {\n names.push(reasonNames[i] ?? `unknown(${i})`)\n }\n }\n\n return names\n }\n\n /**\n * Check revocation status using a single distribution point (typically a \"full\" DP)\n */\n private static async checkDistributionPoint(\n agentContext: AgentContext,\n certificate: X509Certificate,\n issuerCertificate: X509Certificate,\n distributionPoint: { urls: string[]; reasons?: number[] },\n verificationDate: Date,\n options: X509RevocationCheckOptions\n ): Promise<X509RevocationCheckResult> {\n const result = await X509RevocationService.fetchAndVerifyCrl(\n agentContext,\n certificate,\n distributionPoint.urls,\n issuerCertificate,\n verificationDate,\n options\n )\n\n if (!result.success) {\n const message =\n `Failed to fetch CRL from distribution point (URLs: ${distributionPoint.urls.join(', ')}). ` +\n `Error: ${result.error}`\n\n throw result.reachable ? new X509Error(message) : new X509CrlUnavailableError(message)\n }\n\n // Check if certificate is in the CRL\n const revokedEntry = result.crl.findRevoked(certificate)\n\n if (revokedEntry) {\n return {\n isValid: false,\n isRevoked: true,\n error: new X509Error(\n `Certificate '${certificate.subject}' has been revoked. ` +\n `Revocation date: ${revokedEntry.revocationDate.toISOString()}` +\n (revokedEntry.reason !== undefined ? `, reason: ${revokedEntry.reason}` : '')\n ),\n details: `Revoked on ${revokedEntry.revocationDate.toISOString()}`,\n method: 'crl',\n }\n }\n\n // Certificate not found in CRL - it's valid\n return {\n isValid: true,\n isRevoked: false,\n details: `Certificate not found in CRL from ${result.usedUrl}`,\n method: 'crl',\n }\n }\n\n /**\n * Determine whether a verified CRL can be used to authoritatively determine the revocation status\n * of `certificate`. Uses the CRL's own extensions (RFC 5280 §5.2.4 Delta CRL Indicator and §5.2.5\n * Issuing Distribution Point) to reject CRLs we cannot correctly interpret as a complete, direct\n * CRL covering this certificate. Returns a human-readable reason when the CRL must be rejected.\n */\n private static checkCrlApplicability(\n crl: VerifiedCrl,\n certificate: X509Certificate\n ): { usable: true } | { usable: false; reason: string } {\n // RFC 5280 §6.3.3: a CRL bearing a critical extension we do not recognize MUST NOT be used, as\n // that extension may change the CRL's meaning (e.g. its scope) in ways we cannot account for.\n const criticalExtensions = validateCriticalCrlExtensionIds(crl.criticalExtensionIds)\n if (!criticalExtensions.isValid) {\n return {\n usable: false,\n reason: criticalExtensions.error?.message ?? 'CRL contains unrecognized critical extensions',\n }\n }\n\n // A delta CRL only lists changes relative to a base CRL. We do not process delta CRLs, so a\n // certificate's absence from one is not proof it is unrevoked: treating it as a complete CRL\n // could let a certificate revoked on the base CRL appear valid.\n if (crl.deltaCrlIndicator !== undefined) {\n return { usable: false, reason: 'CRL is a delta CRL, which is not supported for revocation checking' }\n }\n\n const issuingDistributionPoint = crl.issuingDistributionPoint\n if (issuingDistributionPoint) {\n // An indirect CRL may revoke certificates from issuers other than the one that signed it (via\n // the per-entry certificateIssuer extension), so its entries cannot be reliably attributed to\n // this certificate's issuer. We do not process indirect CRLs.\n if (issuingDistributionPoint.indirectCRL) {\n return { usable: false, reason: 'CRL is an indirect CRL, which is not supported for revocation checking' }\n }\n\n // This CRL only covers attribute certificates, never the public-key certificate being checked.\n if (issuingDistributionPoint.onlyContainsAttributeCerts) {\n return { usable: false, reason: 'CRL only covers attribute certificates' }\n }\n\n // The CRL is scoped to a certificate category that excludes the certificate being checked, so\n // its (non-)presence says nothing about this certificate's revocation status.\n const isCertificateAuthority = certificate.isCertificateAuthority\n if (issuingDistributionPoint.onlyContainsUserCerts && isCertificateAuthority) {\n return {\n usable: false,\n reason: 'CRL only covers end-entity certificates but the certificate is a CA certificate',\n }\n }\n if (issuingDistributionPoint.onlyContainsCACerts && !isCertificateAuthority) {\n return {\n usable: false,\n reason: 'CRL only covers CA certificates but the certificate is an end-entity certificate',\n }\n }\n }\n\n return { usable: true }\n }\n\n /**\n * Fetch and verify a CRL from a list of mirror URLs\n * Tries each URL in order until one succeeds\n */\n private static async fetchAndVerifyCrl(\n agentContext: AgentContext,\n certificate: X509Certificate,\n urls: string[],\n issuerCertificate: X509Certificate,\n verificationDate: Date,\n options: X509RevocationCheckOptions\n ): Promise<\n { success: true; crl: VerifiedCrl; usedUrl: string } | { success: false; error: string; reachable: boolean }\n > {\n let lastError: string | undefined\n\n // Whether at least one URL produced a downloaded CRL\n let reachable = false\n\n const webCrypto = new CredoWebCrypto(agentContext)\n\n // Try each URL (these are mirrors) until one succeeds\n for (const url of urls) {\n const result = await X509RevocationService.fetchVerifyAndCacheCrl(\n agentContext,\n { url, issuerCertificate, verificationDate, options, useCache: true },\n webCrypto\n )\n\n if (result.success) {\n // A verified CRL is only authoritative for this certificate if it is a complete, direct CRL\n // whose scope covers the certificate. Otherwise its (non-)presence proves nothing about the\n // certificate's revocation status, so it must not be used.\n const applicability = X509RevocationService.checkCrlApplicability(result.crl, certificate)\n if (applicability.usable) return { success: true, crl: result.crl, usedUrl: url }\n\n // The CRL was downloaded and verified, but cannot be used for this certificate. Treat it as\n // reachable so the coverage gap is classified as an integrity (hard) failure rather than a\n // soft-failable availability failure - we did obtain a CRL, it is just not one we can use.\n lastError = `CRL from ${url} cannot be used: ${applicability.reason}`\n reachable = true\n continue\n }\n\n lastError = result.error\n if (result.reachable) reachable = true\n }\n\n // All URLs failed\n return {\n success: false,\n error: lastError ?? `All CRL URLs failed: ${urls.join(', ')}`,\n reachable,\n }\n }\n\n /**\n * Obtain a verified CRL for a URL, preferring the cached verified summary.\n *\n * A summary is only reused when the provided issuer certificate carries the same subject name and\n * public key the summary was verified against, its key usage (when present) permits cRLSign, and\n * the summary's validity window covers `verificationDate` — together these reproduce exactly the\n * issuer-dependent checks of {@link X509CertificateRevocationList.verify}, so a renewed or\n * cross-certified issuer certificate (same name and key) reuses the summary directly. A reusable\n * summary that is still fresh (`staleAt` in the future) is then trusted from the\n * (application-controlled) cache without any network I/O; a stale one is revalidated on the full\n * path, which reuses it without re-parsing when the freshly fetched bytes are unchanged.\n *\n * Anything else — a different key or subject name, a key usage without cRLSign, an out-of-window,\n * missing or malformed summary — falls through to the full fetch-parse-verify path, which\n * produces all failure results (an unreachable host is then an availability failure like any\n * other fetch, also when revalidating a stale summary). On success the full path overwrites the\n * summary with the new issuer binding.\n */\n private static async fetchVerifyAndCacheCrl(\n agentContext: AgentContext,\n fetchOptions: FetchCrlOptions,\n webCrypto: CredoWebCrypto\n ): Promise<{ success: true; crl: VerifiedCrl } | { success: false; error: string; reachable: boolean }> {\n const { url, issuerCertificate, verificationDate, useCache } = fetchOptions\n\n if (useCache) {\n const summary = await getCachedCrlSummary(agentContext, url)\n const verificationTime = verificationDate.getTime()\n const issuerKeyUsage = issuerCertificate.keyUsage\n if (\n summary &&\n summary.issuerNameSha256 === computeIssuerNameSha256(issuerCertificate) &&\n summary.issuerPublicJwkThumbprint === computeIssuerPublicJwkThumbprint(issuerCertificate) &&\n // Mirror verify()'s cRLSign check: key usage, when present and non-empty, must include cRLSign\n !(issuerKeyUsage && issuerKeyUsage.length > 0 && !issuerKeyUsage.includes(X509KeyUsage.CrlSign)) &&\n // Mirror verify()'s validity window checks (isNotYetValid/isExpired use strict comparisons)\n verificationTime >= summary.thisUpdate &&\n (summary.nextUpdate === undefined || verificationTime <= summary.nextUpdate)\n ) {\n if (summary.staleAt > verificationTime) {\n return { success: true, crl: new CrlSummaryVerifiedCrl(summary) }\n }\n\n // Stale but otherwise reusable: revalidate on the full path. Only a summary that passed\n // every reuse gate may be revalidated by bytes hash — hash-based reuse must not resurrect\n // a summary bound to a different issuer or an uncovered validity window.\n return X509RevocationService.fetchParseVerifyAndCacheCrl(\n agentContext,\n { ...fetchOptions, staleSummary: summary },\n webCrypto\n )\n }\n }\n\n return X509RevocationService.fetchParseVerifyAndCacheCrl(agentContext, fetchOptions, webCrypto)\n }\n\n /**\n * Download a single CRL, parse it, and verify it against the issuer. When `useCache` is enabled,\n * a successful verification also caches a derived summary so subsequent checks can skip the\n * download and the (potentially very expensive) parse via {@link fetchVerifyAndCacheCrl}.\n *\n * `staleSummary` is a cached summary that passed all reuse gates but whose `staleAt` has passed:\n * when the fetched bytes hash to its `crlSha256`, the parse and verification are skipped and the\n * summary is re-cached with a refreshed `staleAt`.\n */\n private static async fetchParseVerifyAndCacheCrl(\n agentContext: AgentContext,\n {\n url,\n issuerCertificate,\n verificationDate,\n options,\n useCache,\n staleSummary,\n }: FetchCrlOptions & { staleSummary?: X509CrlSummary },\n webCrypto: CredoWebCrypto\n ): Promise<{ success: true; crl: VerifiedCrl } | { success: false; error: string; reachable: boolean }> {\n // Whether the CRL bytes were downloaded successfully.\n let reachable = false\n\n try {\n const crlData = await fetchCrl({\n url,\n timeoutMs: options.timeoutMs,\n maxSizeBytes: options.maxCrlSizeBytes,\n agentContext,\n })\n\n reachable = true\n\n // The globally configured expiry applies even when per-call revocation options omit it; the\n // hardcoded default only kicks in when neither is set.\n const crlCacheExpirySeconds =\n options.crlCacheExpirySeconds ??\n agentContext.dependencyManager.resolve(X509ModuleConfig).revocationCheck?.crlCacheExpirySeconds ??\n DEFAULT_CRL_CACHE_EXPIRY_SECONDS\n const staleAt = verificationDate.getTime() + crlCacheExpirySeconds * 1000\n\n if (staleSummary && computeCrlSha256(crlData) === staleSummary.crlSha256) {\n // Unchanged bytes: the stale summary was verified against exactly these bytes, so only its\n // freshness deadline needs refreshing.\n const refreshed: X509CrlSummary = { ...staleSummary, staleAt }\n await setCachedCrlSummary(\n agentContext,\n url,\n refreshed,\n X509RevocationService.computeCacheTtlSeconds(staleSummary.nextUpdate, verificationDate, crlCacheExpirySeconds)\n )\n\n return { success: true, crl: new CrlSummaryVerifiedCrl(refreshed) }\n }\n\n const crl = X509CertificateRevocationList.fromRaw(crlData)\n\n // Verify the CRL signature, issuer-name binding, and validity window against the issuer.\n const verifyResult = await crl.verify({ issuerCertificate, verificationDate }, webCrypto)\n if (!verifyResult.isValid) {\n return { success: false, error: verifyResult.error?.message ?? 'CRL verification failed', reachable }\n }\n\n if (useCache) {\n // The entry lives until the CRL's own `nextUpdate` so an expired CRL is never served from\n // the cache; freshness within that window is governed by the summary's `staleAt`.\n const ttlSeconds = X509RevocationService.computeCacheTtlSeconds(\n crl.nextUpdate?.getTime(),\n verificationDate,\n crlCacheExpirySeconds\n )\n\n // Cache the derived summary of every successfully verified CRL (fresh fetch or\n // re-verification against a different issuer certificate). Best-effort: a malformed CRL\n // (e.g. duplicate extensions) throws while deriving the summary; skip caching it so such\n // CRLs keep surfacing that error at applicability time.\n try {\n await setCachedCrlSummary(agentContext, url, buildCrlSummary(crl, issuerCertificate, staleAt), ttlSeconds)\n } catch {\n // the summary cache is an optimization only\n }\n }\n\n return { success: true, crl }\n } catch (error) {\n return { success: false, error: error instanceof Error ? error.message : String(error), reachable }\n }\n }\n\n /**\n * Compute the cache TTL (in seconds) for a verified CRL summary: the full time until its\n * `nextUpdate` (freshness within that window is governed by the summary's `staleAt`). Returns 0\n * when the CRL is already at/after `nextUpdate` so it is not cached. A CRL without `nextUpdate`\n * gets the freshness period itself as TTL, so its entry expires instead of being revalidated.\n */\n private static computeCacheTtlSeconds(\n nextUpdate: number | undefined,\n verificationDate: Date,\n crlCacheExpirySeconds: number\n ): number {\n if (nextUpdate === undefined) return crlCacheExpirySeconds\n\n return Math.max(0, Math.floor((nextUpdate - verificationDate.getTime()) / 1000))\n }\n\n /**\n * Handles errors during revocation checking based on the configured mode\n */\n private static handleRevocationError(error: unknown, mode: X509RevocationCheckMode): X509RevocationCheckResult {\n const errorMessage = error instanceof Error ? error.message : String(error)\n\n if (mode === X509RevocationCheckMode.Disabled) {\n return {\n isValid: true,\n details: 'Revocation checking disabled',\n }\n }\n\n // SoftFail only tolerates an availability failure (the CRL could not be obtained)\n if (mode === X509RevocationCheckMode.SoftFail && error instanceof X509CrlUnavailableError) {\n return {\n isValid: true,\n details: `CRL could not be obtained but SoftFail mode allowed it: ${errorMessage}`,\n }\n }\n\n // Remaining cases result in validation failure\n return {\n isValid: false,\n error: new X509Error(`Required revocation check failed: ${errorMessage}`, {\n cause: error instanceof Error ? error : undefined,\n }),\n }\n }\n\n /**\n * Fetch a CRL from a URL and parse it into an {@link X509CertificateRevocationList}.\n *\n * When `issuerCertificate` is provided, the CRL's signature, issuer name, issuer `cRLSign` key\n * usage, and validity window are verified, and an error is thrown on failure. When omitted, the CRL\n * is returned unverified.\n *\n * Note: this is a raw fetch+parse helper. Unlike {@link checkCertificateRevocation}, it does not\n * apply revocation-scope/applicability checks (delta/indirect/IDP scope), so it will return a delta\n * or scoped CRL as-is for inspection.\n */\n public static async fetchCertificateRevocationList(\n agentContext: AgentContext,\n {\n url,\n issuerCertificate,\n timeoutMs,\n maxCrlSizeBytes,\n verificationDate = new Date(),\n }: X509FetchCertificateRevocationListOptions\n ): Promise<X509CertificateRevocationList> {\n // Without an issuer certificate we cannot verify the CRL; just fetch and parse it.\n if (!issuerCertificate) {\n const crlData = await fetchCrl({ url, timeoutMs, maxSizeBytes: maxCrlSizeBytes, agentContext })\n return X509CertificateRevocationList.fromRaw(crlData)\n }\n\n const parsedIssuerCertificate =\n issuerCertificate instanceof X509Certificate\n ? issuerCertificate\n : X509Certificate.fromEncodedCertificate(issuerCertificate)\n\n const result = await X509RevocationService.fetchParseVerifyAndCacheCrl(\n agentContext,\n {\n url,\n issuerCertificate: parsedIssuerCertificate,\n verificationDate,\n options: { timeoutMs, maxCrlSizeBytes },\n // This is an explicit, one-off fetch; don't read from or populate the revocation cache.\n useCache: false,\n },\n new CredoWebCrypto(agentContext)\n )\n\n if (!result.success) {\n // Distinguish an availability failure (could not download) from an integrity failure\n // (downloaded but rejected), mirroring the revocation engine's classification.\n throw result.reachable\n ? new X509Error(`CRL from ${url} could not be verified: ${result.error}`)\n : new X509CrlUnavailableError(result.error)\n }\n\n // Without `staleSummary` (and with `useCache: false`) the full path always returns the freshly\n // parsed CRL, never a cached-summary view.\n return result.crl as X509CertificateRevocationList\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"X509RevocationService.mjs","names":[],"sources":["../../../src/modules/x509/X509RevocationService.ts"],"sourcesContent":["import type { AgentContext } from '../../agent'\nimport { CredoWebCrypto } from '../../crypto/webcrypto'\nimport { injectable } from '../../plugins'\nimport { validateCriticalCrlExtensionIds } from './utils/criticalExtensions'\nimport { fetchCrl, getCachedCrlSummary, setCachedCrlSummary } from './utils/crlFetcher'\nimport {\n buildCrlSummary,\n CrlSummaryVerifiedCrl,\n computeCrlSha256,\n computeIssuerNameSha256,\n computeIssuerPublicJwkThumbprint,\n type VerifiedCrl,\n type X509CrlSummary,\n} from './utils/crlSummary'\nimport { X509Certificate, X509KeyUsage } from './X509Certificate'\nimport { X509CertificateRevocationList, type X509CertificateRevocationListEntry } from './X509CertificateRevocationList'\nimport { ALL_REVOCATION_REASONS_MASK, X509RevocationReason } from './X509CrlDistributionPoint'\nimport { X509CrlUnavailableError, X509Error } from './X509Error'\nimport { X509ModuleConfig } from './X509ModuleConfig'\nimport type {\n X509CheckCertificateRevocationOptions,\n X509FetchCertificateRevocationListOptions,\n} from './X509ServiceOptions'\nimport {\n withX509RevocationCheckDefaults,\n X509RevocationCheckMode,\n type X509RevocationCheckOptionsWithDefaults,\n} from './X509ValidationOptions'\nimport type { X509CertificateSingleValidationResult } from './X509ValidationResult'\n\nexport interface X509RevocationCheckResult {\n isValid: boolean\n isRevoked?: boolean\n error?: Error\n details?: string\n method?: 'crl'\n}\n\ninterface FetchCrlOptions {\n url: string\n issuerCertificate: X509Certificate\n verificationDate: Date\n options: Pick<X509RevocationCheckOptionsWithDefaults, 'timeoutMs' | 'maxCrlSizeBytes' | 'crlCacheExpirySeconds'>\n useCache: boolean\n}\n\n@injectable()\n// biome-ignore lint/complexity/noStaticOnlyClass: Service class pattern used in Credo\nexport class X509RevocationService {\n /**\n * Check the revocation status of a single certificate using CRL.\n */\n public static async checkCertificateRevocation(\n agentContext: AgentContext,\n { certificate, issuerCertificate, revocationCheckOptions }: X509CheckCertificateRevocationOptions\n ): Promise<X509RevocationCheckResult> {\n const parsedCertificate =\n certificate instanceof X509Certificate ? certificate : X509Certificate.fromEncodedCertificate(certificate)\n const parsedIssuerCertificate =\n issuerCertificate instanceof X509Certificate\n ? issuerCertificate\n : X509Certificate.fromEncodedCertificate(issuerCertificate)\n\n const options = withX509RevocationCheckDefaults(\n revocationCheckOptions,\n agentContext.dependencyManager.resolve(X509ModuleConfig).revocationCheck\n )\n\n const mode = options.mode\n if (mode === X509RevocationCheckMode.Disabled) {\n return { isValid: true, details: 'Revocation checking disabled' }\n }\n\n try {\n return await X509RevocationService.checkCrl(\n agentContext,\n parsedCertificate,\n parsedIssuerCertificate,\n options,\n options.verificationDate ?? new Date()\n )\n } catch (error) {\n return X509RevocationService.handleRevocationError(error, mode)\n }\n }\n\n /**\n * Check the revocation status of every certificate in a chain (the leaf and, unless\n * `checkFullChain` is disabled, the intermediate CAs). The root is skipped as it is self-issued and\n * not covered by a CRL we can fetch and verify.\n *\n * The chain must be ordered from root (index 0) to leaf (last index); each certificate's issuer is\n * the certificate that precedes it in the chain.\n *\n * The same CRL handling limitations as {@link checkCertificateRevocation} apply (CRL only; delta\n * and indirect CRLs rejected; no IDP distribution-point-name matching; no `onlySomeReasons`\n * cross-check; Freshest CRL / AIA and `invalidityDate` not processed).\n */\n public static async checkCertificateChainRevocation(\n agentContext: AgentContext,\n certificateChain: X509Certificate[],\n config: X509ModuleConfig,\n verificationDate: Date\n ): Promise<X509CertificateSingleValidationResult> {\n const configuredRevocationCheck = config.revocationCheck\n if (configuredRevocationCheck.mode === X509RevocationCheckMode.Disabled) {\n return { isValid: true, details: 'Revocation checking disabled' }\n }\n\n // Use the chain's verificationDate for CRL validity (thisUpdate/nextUpdate) checks\n // unless the revocation config explicitly overrides it.\n const revocationConfig: X509RevocationCheckOptionsWithDefaults = {\n ...configuredRevocationCheck,\n verificationDate: configuredRevocationCheck.verificationDate ?? verificationDate,\n }\n\n // The chain is ordered from root (index 0) to leaf (last index). A certificate's issuer is the\n // certificate that precedes it in the chain. The root certificate (index 0) is skipped.\n const checkFullChain = revocationConfig.checkFullChain\n const startIndex = checkFullChain ? 1 : certificateChain.length - 1\n\n for (let i = startIndex; i < certificateChain.length; i++) {\n const certificate = certificateChain[i]\n // The issuer is the preceding certificate in the chain.\n const issuerCertificate = certificateChain[i - 1]\n\n if (!issuerCertificate) {\n // No issuer available (e.g. a single self-signed certificate); nothing to check.\n continue\n }\n\n const result = await X509RevocationService.checkCertificateRevocation(agentContext, {\n certificate,\n issuerCertificate,\n revocationCheckOptions: revocationConfig,\n })\n\n if (!result.isValid) {\n return {\n isValid: false,\n error: result.error,\n details: result.details,\n }\n }\n }\n\n return { isValid: true, details: 'No certificates are revoked' }\n }\n\n /**\n * Checks the revocation status of a certificate using CRL with proper reason partitioning\n * Based on RFC 5280 Section 5.2.5\n */\n private static async checkCrl(\n agentContext: AgentContext,\n certificate: X509Certificate,\n issuerCertificate: X509Certificate,\n options: X509RevocationCheckOptionsWithDefaults,\n verificationDate: Date\n ): Promise<X509RevocationCheckResult> {\n // Get CRL distribution points from certificate\n const distributionPoints = certificate.crlDistributionPoints\n if (distributionPoints.length === 0) {\n return {\n isValid: true,\n details: 'No CRL distribution points in certificate',\n }\n }\n\n // Step 1: Check if there's a \"full\" distribution point (no reasons specified). Indirect CRLs\n // (those with a crlIssuer) are excluded here - we can't verify them, so they fall through to the\n // partitioned path below where they are tracked as an unsupported feature instead of being\n // fetched and failing verification against the wrong issuer.\n const fullDistributionPoint = distributionPoints.find(\n (dp) => dp.reasons === undefined && dp.crlIssuer === undefined\n )\n\n if (fullDistributionPoint) {\n // Full DP covers all reasons - just fetch and check this one CRL\n return await X509RevocationService.checkDistributionPoint(\n agentContext,\n certificate,\n issuerCertificate,\n fullDistributionPoint,\n verificationDate,\n options\n )\n }\n\n // Step 2: No full DP - need to ensure required reasons are covered by fetching multiple CRLs\n // Calculate which reasons we need to cover\n // Default: require all reasons that are actually present in the certificate's DPs\n // (no point requiring reasons the issuer never published DPs for)\n const requiredReasonsMask = options.requiredReasons\n ? X509RevocationService.calculateReasonMask(options.requiredReasons)\n : X509RevocationService.calculateAllReasonsInDPs(distributionPoints)\n\n let coveredReasons = 0 // Bitmask of covered reason codes\n const coveredCrls: VerifiedCrl[] = []\n const fetchedDPs: string[] = []\n const fetchErrors: string[] = [] // Track errors for DPs we couldn't fetch\n const unsupportedDPs: Array<{ reasons: number; feature: string }> = [] // Track DPs we can't process\n\n // Whether any DP downloaded a CRL that then failed verification/validity\n let integrityFailure = false\n\n for (const dp of distributionPoints) {\n // Calculate which reasons this DP covers\n const dpReasonMask = X509RevocationService.calculateReasonMask(dp.reasons)\n\n // Check if this DP adds any new coverage for reasons we care about\n const newCoverage = dpReasonMask & requiredReasonsMask & ~coveredReasons\n if (newCoverage === 0) {\n // This DP doesn't add any new reason coverage for required reasons, skip it\n continue\n }\n\n // Check for unsupported features\n if (dp.crlIssuer !== undefined) {\n // This DP uses an indirect CRL (different issuer) which we don't support\n // Track it but don't fail immediately - we might cover these reasons with other DPs\n unsupportedDPs.push({ reasons: dpReasonMask, feature: `crlIssuer (${dp.crlIssuer})` })\n continue\n }\n\n // Try to fetch CRL from this distribution point\n const result = await X509RevocationService.fetchAndVerifyCrl(\n agentContext,\n certificate,\n dp.urls,\n issuerCertificate,\n verificationDate,\n options\n )\n\n if (!result.success) {\n // CRL fetch failed - track the error but don't fail immediately\n // Another DP might cover the same reasons\n fetchErrors.push(`URLs ${dp.urls.join(', ')}: ${result.error}`)\n\n // A downloaded-but-rejected CRL is an integrity failure\n if (result.reachable) integrityFailure = true\n\n continue\n }\n\n // Successfully fetched CRL - mark these reasons as covered\n coveredReasons |= dpReasonMask\n fetchedDPs.push(result.usedUrl)\n\n // Keep the verified CRL so we can look up the certificate using the same\n // (format-normalizing) matching logic as the single distribution point path.\n coveredCrls.push(result.crl)\n\n // Check if we've covered all required reasons\n if ((coveredReasons & requiredReasonsMask) === requiredReasonsMask) {\n break\n }\n }\n\n // Step 3: Check if the certificate is revoked in any CRL we DID collect. Presence on a scoped\n // CRL is a definitive revocation regardless of whether every required reason partition was\n // obtained, so this must be checked BEFORE concluding anything about coverage completeness -\n // otherwise an unreachable partition could mask a revocation we already have proof of.\n // Use the CRL's own findRevoked so serial number formats are normalized consistently with the\n // single distribution point path.\n let revokedEntry: X509CertificateRevocationListEntry | null = null\n for (const crl of coveredCrls) {\n revokedEntry = crl.findRevoked(certificate)\n if (revokedEntry) break\n }\n\n if (revokedEntry) {\n return {\n isValid: false,\n isRevoked: true,\n error: new X509Error(\n `Certificate '${certificate.subject}' has been revoked. ` +\n `Revocation date: ${revokedEntry.revocationDate.toISOString()}` +\n (revokedEntry.reason !== undefined ? `, reason: ${revokedEntry.reason}` : '')\n ),\n details: `Revoked on ${revokedEntry.revocationDate.toISOString()}`,\n method: 'crl',\n }\n }\n\n // Step 4: The certificate was not found revoked. We can only conclude it is NOT revoked if we\n // actually covered all required revocation reasons; otherwise the status is indeterminate.\n if ((coveredReasons & requiredReasonsMask) !== requiredReasonsMask) {\n const missingReasons = requiredReasonsMask & ~coveredReasons\n const coveredNames = X509RevocationService.reasonMaskToNames(coveredReasons)\n const requiredNames = X509RevocationService.reasonMaskToNames(requiredReasonsMask)\n const missingNames = X509RevocationService.reasonMaskToNames(missingReasons)\n\n // Build detailed error message\n let errorMsg =\n `Unable to cover all required revocation reasons. ` +\n `Covered: [${coveredNames.join(', ')}], ` +\n `Required: [${requiredNames.join(', ')}], ` +\n `Missing: [${missingNames.join(', ')}]. ` +\n `Certificate revocation status cannot be fully determined.`\n\n // Include unsupported DP info if any missing reasons could have been covered by them\n let unsupportedCoversMissing = false\n for (const unsupported of unsupportedDPs) {\n if (unsupported.reasons & missingReasons) {\n unsupportedCoversMissing = true\n const unsupportedReasons = X509RevocationService.reasonMaskToNames(unsupported.reasons & missingReasons)\n errorMsg += ` Unsupported CRL feature ${unsupported.feature} covers missing reasons: [${unsupportedReasons.join(', ')}].`\n }\n }\n\n if (fetchErrors.length > 0) {\n errorMsg += ` Fetch errors: ${fetchErrors.join('; ')}`\n }\n\n // Check if the missing reasons are covered by any DP (even if we couldn't fetch its CRL):\n // if not, it's a structural gap that can't be soft-failed.\n const reasonsPublishedByAnyDp = X509RevocationService.calculateAllReasonsInDPs(distributionPoints)\n const structuralGap = (missingReasons & ~reasonsPublishedByAnyDp) !== 0\n\n // The coverage gap is soft-failable only when it is purely caused by CRLs we could not reach;\n // an integrity failure, an unsupported feature, or a structural gap is never soft-failed.\n const availabilityOnly = !integrityFailure && !unsupportedCoversMissing && !structuralGap\n\n throw availabilityOnly ? new X509CrlUnavailableError(errorMsg) : new X509Error(errorMsg)\n }\n\n // Certificate not found in any CRL and all required reasons covered - it's valid\n return {\n isValid: true,\n isRevoked: false,\n details: `Certificate not found in CRLs from: ${fetchedDPs.join(', ')}`,\n method: 'crl',\n }\n }\n\n /**\n * Calculate bitmask of revocation reasons covered by a distribution point\n */\n private static calculateReasonMask(reasons: number[] | undefined): number {\n if (!reasons || reasons.length === 0) {\n // No reasons specified means this DP covers ALL reasons\n return ALL_REVOCATION_REASONS_MASK\n }\n\n // Convert array of reason codes to bitmask\n let mask = 0\n for (const reason of reasons) {\n if (reason >= 0 && reason <= 8) {\n mask |= 1 << reason\n }\n }\n return mask\n }\n\n /**\n * Calculate the union of all reasons present in distribution points\n * Used to determine the default required reasons (no point requiring reasons the issuer never published)\n */\n private static calculateAllReasonsInDPs(\n distributionPoints: Array<{ urls: string[]; reasons?: number[]; crlIssuer?: string }>\n ): number {\n let allReasons = 0\n for (const dp of distributionPoints) {\n allReasons |= X509RevocationService.calculateReasonMask(dp.reasons)\n }\n return allReasons\n }\n\n /**\n * Convert a bitmask of revocation reasons to human-readable names\n */\n private static reasonMaskToNames(mask: number): string[] {\n const names: string[] = []\n const reasonNames: Record<number, string> = {\n [X509RevocationReason.Unused]: 'unused',\n [X509RevocationReason.KeyCompromise]: 'keyCompromise',\n [X509RevocationReason.CACompromise]: 'cACompromise',\n [X509RevocationReason.AffiliationChanged]: 'affiliationChanged',\n [X509RevocationReason.Superseded]: 'superseded',\n [X509RevocationReason.CessationOfOperation]: 'cessationOfOperation',\n [X509RevocationReason.CertificateHold]: 'certificateHold',\n [X509RevocationReason.PrivilegeWithdrawn]: 'privilegeWithdrawn',\n [X509RevocationReason.AACompromise]: 'aACompromise',\n }\n\n for (let i = 0; i <= 8; i++) {\n if (mask & (1 << i)) {\n names.push(reasonNames[i] ?? `unknown(${i})`)\n }\n }\n\n return names\n }\n\n /**\n * Check revocation status using a single distribution point (typically a \"full\" DP)\n */\n private static async checkDistributionPoint(\n agentContext: AgentContext,\n certificate: X509Certificate,\n issuerCertificate: X509Certificate,\n distributionPoint: { urls: string[]; reasons?: number[] },\n verificationDate: Date,\n options: X509RevocationCheckOptionsWithDefaults\n ): Promise<X509RevocationCheckResult> {\n const result = await X509RevocationService.fetchAndVerifyCrl(\n agentContext,\n certificate,\n distributionPoint.urls,\n issuerCertificate,\n verificationDate,\n options\n )\n\n if (!result.success) {\n const message =\n `Failed to fetch CRL from distribution point (URLs: ${distributionPoint.urls.join(', ')}). ` +\n `Error: ${result.error}`\n\n throw result.reachable ? new X509Error(message) : new X509CrlUnavailableError(message)\n }\n\n // Check if certificate is in the CRL\n const revokedEntry = result.crl.findRevoked(certificate)\n\n if (revokedEntry) {\n return {\n isValid: false,\n isRevoked: true,\n error: new X509Error(\n `Certificate '${certificate.subject}' has been revoked. ` +\n `Revocation date: ${revokedEntry.revocationDate.toISOString()}` +\n (revokedEntry.reason !== undefined ? `, reason: ${revokedEntry.reason}` : '')\n ),\n details: `Revoked on ${revokedEntry.revocationDate.toISOString()}`,\n method: 'crl',\n }\n }\n\n // Certificate not found in CRL - it's valid\n return {\n isValid: true,\n isRevoked: false,\n details: `Certificate not found in CRL from ${result.usedUrl}`,\n method: 'crl',\n }\n }\n\n /**\n * Determine whether a verified CRL can be used to authoritatively determine the revocation status\n * of `certificate`. Uses the CRL's own extensions (RFC 5280 §5.2.4 Delta CRL Indicator and §5.2.5\n * Issuing Distribution Point) to reject CRLs we cannot correctly interpret as a complete, direct\n * CRL covering this certificate. Returns a human-readable reason when the CRL must be rejected.\n */\n private static checkCrlApplicability(\n crl: VerifiedCrl,\n certificate: X509Certificate\n ): { usable: true } | { usable: false; reason: string } {\n // RFC 5280 §6.3.3: a CRL bearing a critical extension we do not recognize MUST NOT be used, as\n // that extension may change the CRL's meaning (e.g. its scope) in ways we cannot account for.\n const criticalExtensions = validateCriticalCrlExtensionIds(crl.criticalExtensionIds)\n if (!criticalExtensions.isValid) {\n return {\n usable: false,\n reason: criticalExtensions.error?.message ?? 'CRL contains unrecognized critical extensions',\n }\n }\n\n // A delta CRL only lists changes relative to a base CRL. We do not process delta CRLs, so a\n // certificate's absence from one is not proof it is unrevoked: treating it as a complete CRL\n // could let a certificate revoked on the base CRL appear valid.\n if (crl.deltaCrlIndicator !== undefined) {\n return { usable: false, reason: 'CRL is a delta CRL, which is not supported for revocation checking' }\n }\n\n const issuingDistributionPoint = crl.issuingDistributionPoint\n if (issuingDistributionPoint) {\n // An indirect CRL may revoke certificates from issuers other than the one that signed it (via\n // the per-entry certificateIssuer extension), so its entries cannot be reliably attributed to\n // this certificate's issuer. We do not process indirect CRLs.\n if (issuingDistributionPoint.indirectCRL) {\n return { usable: false, reason: 'CRL is an indirect CRL, which is not supported for revocation checking' }\n }\n\n // This CRL only covers attribute certificates, never the public-key certificate being checked.\n if (issuingDistributionPoint.onlyContainsAttributeCerts) {\n return { usable: false, reason: 'CRL only covers attribute certificates' }\n }\n\n // The CRL is scoped to a certificate category that excludes the certificate being checked, so\n // its (non-)presence says nothing about this certificate's revocation status.\n const isCertificateAuthority = certificate.isCertificateAuthority\n if (issuingDistributionPoint.onlyContainsUserCerts && isCertificateAuthority) {\n return {\n usable: false,\n reason: 'CRL only covers end-entity certificates but the certificate is a CA certificate',\n }\n }\n if (issuingDistributionPoint.onlyContainsCACerts && !isCertificateAuthority) {\n return {\n usable: false,\n reason: 'CRL only covers CA certificates but the certificate is an end-entity certificate',\n }\n }\n }\n\n return { usable: true }\n }\n\n /**\n * Fetch and verify a CRL from a list of mirror URLs\n * Tries each URL in order until one succeeds\n */\n private static async fetchAndVerifyCrl(\n agentContext: AgentContext,\n certificate: X509Certificate,\n urls: string[],\n issuerCertificate: X509Certificate,\n verificationDate: Date,\n options: X509RevocationCheckOptionsWithDefaults\n ): Promise<\n { success: true; crl: VerifiedCrl; usedUrl: string } | { success: false; error: string; reachable: boolean }\n > {\n let lastError: string | undefined\n\n // Whether at least one URL produced a downloaded CRL\n let reachable = false\n\n const webCrypto = new CredoWebCrypto(agentContext)\n\n // Try each URL (these are mirrors) until one succeeds\n for (const url of urls) {\n const result = await X509RevocationService.fetchVerifyAndCacheCrl(\n agentContext,\n { url, issuerCertificate, verificationDate, options, useCache: true },\n webCrypto\n )\n\n if (result.success) {\n // A verified CRL is only authoritative for this certificate if it is a complete, direct CRL\n // whose scope covers the certificate. Otherwise its (non-)presence proves nothing about the\n // certificate's revocation status, so it must not be used.\n const applicability = X509RevocationService.checkCrlApplicability(result.crl, certificate)\n if (applicability.usable) return { success: true, crl: result.crl, usedUrl: url }\n\n // The CRL was downloaded and verified, but cannot be used for this certificate. Treat it as\n // reachable so the coverage gap is classified as an integrity (hard) failure rather than a\n // soft-failable availability failure - we did obtain a CRL, it is just not one we can use.\n lastError = `CRL from ${url} cannot be used: ${applicability.reason}`\n reachable = true\n continue\n }\n\n lastError = result.error\n if (result.reachable) reachable = true\n }\n\n // All URLs failed\n return {\n success: false,\n error: lastError ?? `All CRL URLs failed: ${urls.join(', ')}`,\n reachable,\n }\n }\n\n /**\n * Obtain a verified CRL for a URL, preferring the cached verified summary.\n *\n * A summary is only reused when the provided issuer certificate carries the same subject name and\n * public key the summary was verified against, its key usage (when present) permits cRLSign, and\n * the summary's validity window covers `verificationDate` — together these reproduce exactly the\n * issuer-dependent checks of {@link X509CertificateRevocationList.verify}, so a renewed or\n * cross-certified issuer certificate (same name and key) reuses the summary directly. A reusable\n * summary that is still fresh (`staleAt` in the future) is then trusted from the\n * (application-controlled) cache without any network I/O; a stale one is revalidated on the full\n * path, which reuses it without re-parsing when the freshly fetched bytes are unchanged.\n *\n * Anything else — a different key or subject name, a key usage without cRLSign, an out-of-window,\n * missing or malformed summary — falls through to the full fetch-parse-verify path, which\n * produces all failure results (an unreachable host is then an availability failure like any\n * other fetch, also when revalidating a stale summary). On success the full path overwrites the\n * summary with the new issuer binding.\n */\n private static async fetchVerifyAndCacheCrl(\n agentContext: AgentContext,\n fetchOptions: FetchCrlOptions,\n webCrypto: CredoWebCrypto\n ): Promise<{ success: true; crl: VerifiedCrl } | { success: false; error: string; reachable: boolean }> {\n const { url, issuerCertificate, verificationDate, useCache } = fetchOptions\n\n if (useCache) {\n const summary = await getCachedCrlSummary(agentContext, url)\n const verificationTime = verificationDate.getTime()\n const issuerKeyUsage = issuerCertificate.keyUsage\n if (\n summary &&\n summary.issuerNameSha256 === computeIssuerNameSha256(issuerCertificate) &&\n summary.issuerPublicJwkThumbprint === computeIssuerPublicJwkThumbprint(issuerCertificate) &&\n // Mirror verify()'s cRLSign check: key usage, when present and non-empty, must include cRLSign\n !(issuerKeyUsage && issuerKeyUsage.length > 0 && !issuerKeyUsage.includes(X509KeyUsage.CrlSign)) &&\n // Mirror verify()'s validity window checks (isNotYetValid/isExpired use strict comparisons)\n verificationTime >= summary.thisUpdate &&\n (summary.nextUpdate === undefined || verificationTime <= summary.nextUpdate)\n ) {\n if (summary.staleAt > verificationTime) {\n return { success: true, crl: new CrlSummaryVerifiedCrl(summary) }\n }\n\n // Stale but otherwise reusable: revalidate on the full path. Only a summary that passed\n // every reuse gate may be revalidated by bytes hash — hash-based reuse must not resurrect\n // a summary bound to a different issuer or an uncovered validity window.\n return X509RevocationService.fetchParseVerifyAndCacheCrl(\n agentContext,\n { ...fetchOptions, staleSummary: summary },\n webCrypto\n )\n }\n }\n\n return X509RevocationService.fetchParseVerifyAndCacheCrl(agentContext, fetchOptions, webCrypto)\n }\n\n /**\n * Download a single CRL, parse it, and verify it against the issuer. When `useCache` is enabled,\n * a successful verification also caches a derived summary so subsequent checks can skip the\n * download and the (potentially very expensive) parse via {@link fetchVerifyAndCacheCrl}.\n *\n * `staleSummary` is a cached summary that passed all reuse gates but whose `staleAt` has passed:\n * when the fetched bytes hash to its `crlSha256`, the parse and verification are skipped and the\n * summary is re-cached with a refreshed `staleAt`.\n */\n private static async fetchParseVerifyAndCacheCrl(\n agentContext: AgentContext,\n {\n url,\n issuerCertificate,\n verificationDate,\n options,\n useCache,\n staleSummary,\n }: FetchCrlOptions & { staleSummary?: X509CrlSummary },\n webCrypto: CredoWebCrypto\n ): Promise<{ success: true; crl: VerifiedCrl } | { success: false; error: string; reachable: boolean }> {\n // Whether the CRL bytes were downloaded successfully.\n let reachable = false\n\n try {\n const crlData = await fetchCrl({\n url,\n timeoutMs: options.timeoutMs,\n maxSizeBytes: options.maxCrlSizeBytes,\n agentContext,\n })\n\n reachable = true\n\n const crlCacheExpirySeconds = options.crlCacheExpirySeconds\n const staleAt = verificationDate.getTime() + crlCacheExpirySeconds * 1000\n\n if (staleSummary && computeCrlSha256(crlData) === staleSummary.crlSha256) {\n // Unchanged bytes: the stale summary was verified against exactly these bytes, so only its\n // freshness deadline needs refreshing.\n const refreshed: X509CrlSummary = { ...staleSummary, staleAt }\n await setCachedCrlSummary(\n agentContext,\n url,\n refreshed,\n X509RevocationService.computeCacheTtlSeconds(staleSummary.nextUpdate, verificationDate, crlCacheExpirySeconds)\n )\n\n return { success: true, crl: new CrlSummaryVerifiedCrl(refreshed) }\n }\n\n const crl = X509CertificateRevocationList.fromRaw(crlData)\n\n // Verify the CRL signature, issuer-name binding, and validity window against the issuer.\n const verifyResult = await crl.verify({ issuerCertificate, verificationDate }, webCrypto)\n if (!verifyResult.isValid) {\n return { success: false, error: verifyResult.error?.message ?? 'CRL verification failed', reachable }\n }\n\n if (useCache) {\n // The entry lives until the CRL's own `nextUpdate` so an expired CRL is never served from\n // the cache; freshness within that window is governed by the summary's `staleAt`.\n const ttlSeconds = X509RevocationService.computeCacheTtlSeconds(\n crl.nextUpdate?.getTime(),\n verificationDate,\n crlCacheExpirySeconds\n )\n\n // Cache the derived summary of every successfully verified CRL (fresh fetch or\n // re-verification against a different issuer certificate). Best-effort: a malformed CRL\n // (e.g. duplicate extensions) throws while deriving the summary; skip caching it so such\n // CRLs keep surfacing that error at applicability time.\n try {\n await setCachedCrlSummary(agentContext, url, buildCrlSummary(crl, issuerCertificate, staleAt), ttlSeconds)\n } catch {\n // the summary cache is an optimization only\n }\n }\n\n return { success: true, crl }\n } catch (error) {\n return { success: false, error: error instanceof Error ? error.message : String(error), reachable }\n }\n }\n\n /**\n * Compute the cache TTL (in seconds) for a verified CRL summary: the full time until its\n * `nextUpdate` (freshness within that window is governed by the summary's `staleAt`). Returns 0\n * when the CRL is already at/after `nextUpdate` so it is not cached. A CRL without `nextUpdate`\n * gets the freshness period itself as TTL, so its entry expires instead of being revalidated.\n */\n private static computeCacheTtlSeconds(\n nextUpdate: number | undefined,\n verificationDate: Date,\n crlCacheExpirySeconds: number\n ): number {\n if (nextUpdate === undefined) return crlCacheExpirySeconds\n\n return Math.max(0, Math.floor((nextUpdate - verificationDate.getTime()) / 1000))\n }\n\n /**\n * Handles errors during revocation checking based on the configured mode\n */\n private static handleRevocationError(error: unknown, mode: X509RevocationCheckMode): X509RevocationCheckResult {\n const errorMessage = error instanceof Error ? error.message : String(error)\n\n if (mode === X509RevocationCheckMode.Disabled) {\n return {\n isValid: true,\n details: 'Revocation checking disabled',\n }\n }\n\n // SoftFail only tolerates an availability failure (the CRL could not be obtained)\n if (mode === X509RevocationCheckMode.SoftFail && error instanceof X509CrlUnavailableError) {\n return {\n isValid: true,\n details: `CRL could not be obtained but SoftFail mode allowed it: ${errorMessage}`,\n }\n }\n\n // Remaining cases result in validation failure\n return {\n isValid: false,\n error: new X509Error(`Required revocation check failed: ${errorMessage}`, {\n cause: error instanceof Error ? error : undefined,\n }),\n }\n }\n\n /**\n * Fetch a CRL from a URL and parse it into an {@link X509CertificateRevocationList}.\n *\n * When `issuerCertificate` is provided, the CRL's signature, issuer name, issuer `cRLSign` key\n * usage, and validity window are verified, and an error is thrown on failure. When omitted, the CRL\n * is returned unverified.\n *\n * Note: this is a raw fetch+parse helper. Unlike {@link checkCertificateRevocation}, it does not\n * apply revocation-scope/applicability checks (delta/indirect/IDP scope), so it will return a delta\n * or scoped CRL as-is for inspection.\n */\n public static async fetchCertificateRevocationList(\n agentContext: AgentContext,\n {\n url,\n issuerCertificate,\n timeoutMs,\n maxCrlSizeBytes,\n verificationDate = new Date(),\n }: X509FetchCertificateRevocationListOptions\n ): Promise<X509CertificateRevocationList> {\n const config = agentContext.dependencyManager.resolve(X509ModuleConfig)\n const options = {\n timeoutMs: timeoutMs ?? config.revocationCheck.timeoutMs,\n maxCrlSizeBytes: maxCrlSizeBytes ?? config.revocationCheck.maxCrlSizeBytes,\n crlCacheExpirySeconds: config.revocationCheck.crlCacheExpirySeconds,\n }\n\n // Without an issuer certificate we cannot verify the CRL; just fetch and parse it.\n if (!issuerCertificate) {\n const crlData = await fetchCrl({\n url,\n timeoutMs: options.timeoutMs,\n maxSizeBytes: options.maxCrlSizeBytes,\n agentContext,\n })\n return X509CertificateRevocationList.fromRaw(crlData)\n }\n\n const parsedIssuerCertificate =\n issuerCertificate instanceof X509Certificate\n ? issuerCertificate\n : X509Certificate.fromEncodedCertificate(issuerCertificate)\n\n const result = await X509RevocationService.fetchParseVerifyAndCacheCrl(\n agentContext,\n {\n url,\n issuerCertificate: parsedIssuerCertificate,\n verificationDate,\n options,\n // This is an explicit, one-off fetch; don't read from or populate the revocation cache.\n useCache: false,\n },\n new CredoWebCrypto(agentContext)\n )\n\n if (!result.success) {\n // Distinguish an availability failure (could not download) from an integrity failure\n // (downloaded but rejected), mirroring the revocation engine's classification.\n throw result.reachable\n ? new X509Error(`CRL from ${url} could not be verified: ${result.error}`)\n : new X509CrlUnavailableError(result.error)\n }\n\n // Without `staleSummary` (and with `useCache: false`) the full path always returns the freshly\n // parsed CRL, never a cached-summary view.\n return result.crl as X509CertificateRevocationList\n }\n}\n"],"mappings":"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"}
|
|
@@ -126,7 +126,7 @@ let X509Service = _X509Service = class X509Service {
|
|
|
126
126
|
validations,
|
|
127
127
|
error: validations.criticalExtensions.error
|
|
128
128
|
});
|
|
129
|
-
if (config.revocationCheck
|
|
129
|
+
if (config.revocationCheck.mode !== "Disabled") {
|
|
130
130
|
validations.revocationStatus = await X509RevocationService.checkCertificateChainRevocation(agentContext, parsedChain, config, verificationDate);
|
|
131
131
|
if (!validations.revocationStatus.isValid) throw new X509ValidationError(validations.revocationStatus.error?.message ?? "Revocation check failed", {
|
|
132
132
|
isValid: false,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"X509Service.mjs","names":[],"sources":["../../../src/modules/x509/X509Service.ts"],"sourcesContent":["import * as x509 from '@peculiar/x509'\nimport { AgentContext } from '../../agent'\nimport { CredoWebCrypto } from '../../crypto/webcrypto'\nimport { injectable } from '../../plugins'\nimport { CertificateSigningRequest } from './CertificateSigningRequest'\nimport { X509ExtensionIdentifier } from './utils'\nimport { validateCriticalExtensionsForChain } from './utils/criticalExtensions'\nimport { X509Certificate, X509KeyUsage } from './X509Certificate'\nimport { X509CertificateRevocationList } from './X509CertificateRevocationList'\nimport { X509Error, X509ValidationError } from './X509Error'\nimport { X509ModuleConfig } from './X509ModuleConfig'\nimport { X509RevocationService } from './X509RevocationService'\nimport type {\n X509CreateCertificateOptions,\n X509CreateCertificateRevocationListOptions,\n X509CreateCertificateSigningRequestOptions,\n X509GetLeafCertificateOptions,\n X509ParseCertificateOptions,\n X509ParseCertificateSigningRequestOptions,\n X509ValidateCertificateChainOptions,\n} from './X509ServiceOptions'\nimport { X509RevocationCheckMode } from './X509ValidationOptions'\nimport type { X509CertificateSingleValidationResult, X509ValidationResult } from './X509ValidationResult'\n\n@injectable()\n// biome-ignore lint/complexity/noStaticOnlyClass: no explanation\nexport class X509Service {\n /**\n *\n * Validate a chain of X.509 certificates according to RFC 5280\n *\n * This function requires a list of base64 encoded certificates and, optionally, a certificate that should be found in the chain.\n * If no certificate is provided, it will just assume the leaf certificate\n *\n * The leaf certificate should be the 0th index and the root the last\n *\n * The Issuer of the certificate is found with the following algorithm:\n * - Check if there is an AuthorityKeyIdentifierExtension\n * - Go through all the other certificates and see if the SubjectKeyIdentifier is equal to the AuthorityKeyIdentifier\n * - If they are equal, the certificate signature is verified and returned as the issuer\n *\n * Validation checks performed:\n * - Chain validation (structure, signatures, validity, trust anchor)\n * - Basic constraints are valid for CA certificates\n * - Key usage of issuing CA certificates includes `keyCertSign` (when the extension is present)\n * - Path length constraints are satisfied\n * - All critical extensions are understood and valid\n * - Certificates have not been revoked (CRL) - if enabled\n *\n * Limitations (RFC 5280 features not enforced here):\n * - Revocation is checked via CRL only; OCSP is not supported.\n * - Name Constraints (§4.2.1.10), Policy Constraints (§4.2.1.11), and Certificate Policies\n * (§4.2.1.4) are not enforced. A certificate carrying one of these as a *critical* extension is\n * rejected (unrecognized critical extension); a non-critical occurrence is ignored.\n * - Extended Key Usage is parsed but not enforced.\n * - Key Usage is only enforced when the extension is present (it is optional; absent means\n * unrestricted).\n * - Subject/Issuer Alternative Names only surface the URI and DNS GeneralName forms.\n */\n public static async validateCertificateChain(\n agentContext: AgentContext,\n {\n certificateChain,\n certificate = certificateChain[0],\n verificationDate = new Date(),\n trustedCertificates,\n allowNonRootTrustedCertificate = true,\n }: X509ValidateCertificateChainOptions\n ) {\n const validations: X509ValidationResult['validations'] = {}\n const config = agentContext.dependencyManager.resolve(X509ModuleConfig)\n\n let parsedChain: X509Certificate[]\n\n // Phase 1: Chain validation (structure + signatures + validity + trust)\n try {\n if (certificateChain.length === 0) {\n throw new X509Error('Certificate chain is empty')\n }\n const webCrypto = new CredoWebCrypto(agentContext)\n\n let parsedLeafCertificate: x509.X509Certificate\n let certificatesToBuildChain: x509.X509Certificate[]\n try {\n parsedLeafCertificate = new x509.X509Certificate(certificate)\n certificatesToBuildChain = [...certificateChain, ...(trustedCertificates ?? [])].map(\n (c) => new x509.X509Certificate(c)\n )\n } catch (error) {\n throw new X509Error('Error during parsing of x509 certificate', { cause: error })\n }\n\n const certificateChainBuilder = new x509.X509ChainBuilder({\n certificates: certificatesToBuildChain,\n })\n\n const chain = await certificateChainBuilder.build(parsedLeafCertificate, webCrypto)\n\n // The chain is reversed here as the `x5c` header (the expected input),\n // has the leaf certificate as the first entry, while the `x509` library expects this as the last\n parsedChain = chain.map((c) => X509Certificate.fromRawCertificate(new Uint8Array(c.rawData))).reverse()\n\n // We allow longer parsed chain, in case the root cert was not part of the chain, but in the\n // list of trusted certificates\n if (parsedChain.length < certificateChain.length) {\n throw new X509Error('Could not parse the full chain. Likely due to incorrect ordering')\n }\n\n let previousCertificate: X509Certificate | undefined\n\n if (trustedCertificates) {\n const parsedTrustedCertificates = trustedCertificates.map((trustedCertificate) =>\n X509Certificate.fromEncodedCertificate(trustedCertificate)\n )\n\n const trustedCertificateIndex = parsedChain.findIndex((cert) =>\n parsedTrustedCertificates.some((tCert) => cert.equal(tCert))\n )\n\n if (trustedCertificateIndex === -1) {\n throw new X509Error('No trusted certificate was found while validating the X.509 chain')\n }\n\n if (trustedCertificateIndex > 0) {\n // When we trust a certificate other than the first certificate in the provided chain we keep a reference to the\n // previous certificate as we need the key of this certificate to verify the first certificate in the chain as\n // it's not self-signed.\n previousCertificate = parsedChain[trustedCertificateIndex - 1]\n\n // Pop everything off before the index of the trusted certificate (those are more root) as it is not relevant for validation\n parsedChain = parsedChain.slice(trustedCertificateIndex)\n }\n }\n\n // Verify signatures and validity for all certificates in the chain\n for (let i = 0; i < parsedChain.length; i++) {\n const cert = parsedChain[i]\n const publicJwk = previousCertificate ? previousCertificate.publicJwk : undefined\n\n // When a trusted certificate is the first in the chain and we don't have its parent certificate's public key,\n // we may need to skip signature verification based on configuration.\n //\n // Scenario: For ISO 18013-5 mDL, the root cert MUST NOT be in the chain. If the intermediate/signer\n // certificate is directly trusted, we can't verify its signature without the root certificate.\n // See also https://github.com/openid/OpenID4VCI/issues/62\n //\n // If allowNonRootTrustedCertificate is false, we require the signature to be verifiable.\n const skipSignatureVerification = i === 0 && trustedCertificates && !publicJwk && allowNonRootTrustedCertificate\n\n // If we don't allow non-root trusted certificates and can't verify the signature, throw an error\n if (i === 0 && trustedCertificates && !publicJwk && !allowNonRootTrustedCertificate) {\n throw new X509Error(\n 'Unable to verify the certificate chain. A trusted non-self-signed certificate is the first certificate in the chain, ' +\n 'but no parent certificate was found to verify its signature. Either provide the parent certificate in trustedCertificates, ' +\n 'or set allowNonRootTrustedCertificate to true to allow trusting intermediate certificates without signature verification.'\n )\n }\n\n await cert.verify(\n {\n publicJwk,\n verificationDate,\n skipSignatureVerification,\n },\n webCrypto\n )\n previousCertificate = cert\n }\n\n validations.chain = { isValid: true }\n } catch (error) {\n const x509Error = error instanceof X509Error ? error : new X509Error('Chain validation failed', { cause: error })\n validations.chain = { isValid: false, error: x509Error }\n throw new X509ValidationError(x509Error.message, { isValid: false, validations, error: x509Error })\n }\n\n // Phase 2: Basic constraints validation for CA certificates\n validations.basicConstraints = X509Service.validateBasicConstraintsForChain(parsedChain)\n if (!validations.basicConstraints.isValid) {\n throw new X509ValidationError(\n validations.basicConstraints.error?.message ?? 'Basic constraints validation failed',\n { isValid: false, validations, error: validations.basicConstraints.error }\n )\n }\n\n // Phase 2.5: Key usage validation for issuing CA certificates\n validations.keyUsage = X509Service.validateKeyUsageForChain(parsedChain)\n if (!validations.keyUsage.isValid) {\n throw new X509ValidationError(validations.keyUsage.error?.message ?? 'Key usage validation failed', {\n isValid: false,\n validations,\n error: validations.keyUsage.error,\n })\n }\n\n // Phase 3: Path length constraint validation\n validations.pathLength = X509Service.validatePathLengthConstraints(parsedChain)\n if (!validations.pathLength.isValid) {\n throw new X509ValidationError(\n validations.pathLength.error?.message ?? 'Path length constraint validation failed',\n { isValid: false, validations, error: validations.pathLength.error }\n )\n }\n\n // Phase 4: Critical extension validation\n const peculiarChain = parsedChain.map((cert) => new x509.X509Certificate(cert.rawCertificate))\n validations.criticalExtensions = validateCriticalExtensionsForChain(peculiarChain)\n if (!validations.criticalExtensions.isValid) {\n throw new X509ValidationError(\n validations.criticalExtensions.error?.message ?? 'Critical extension validation failed',\n { isValid: false, validations, error: validations.criticalExtensions.error }\n )\n }\n\n // Phase 5: Revocation checking (if enabled)\n if (config.revocationCheck && config.revocationCheck.mode !== X509RevocationCheckMode.Disabled) {\n validations.revocationStatus = await X509RevocationService.checkCertificateChainRevocation(\n agentContext,\n parsedChain,\n config,\n verificationDate\n )\n if (!validations.revocationStatus.isValid) {\n throw new X509ValidationError(validations.revocationStatus.error?.message ?? 'Revocation check failed', {\n isValid: false,\n validations,\n error: validations.revocationStatus.error,\n })\n }\n } else {\n validations.revocationStatus = { isValid: true, details: 'Revocation checking disabled' }\n }\n\n return parsedChain\n }\n\n /**\n *\n * Parses a base64-encoded X.509 certificate into a {@link X509Certificate}\n *\n */\n public static parseCertificate(\n _agentContext: AgentContext,\n { encodedCertificate }: X509ParseCertificateOptions\n ): X509Certificate {\n const certificate = X509Certificate.fromEncodedCertificate(encodedCertificate)\n\n return certificate\n }\n\n public static getLeafCertificate(\n _agentContext: AgentContext,\n { certificateChain }: X509GetLeafCertificateOptions\n ): X509Certificate {\n if (certificateChain.length === 0) throw new X509Error('Certificate chain is empty')\n\n const certificate = X509Certificate.fromEncodedCertificate(certificateChain[0])\n\n return certificate\n }\n\n public static async createCertificate(agentContext: AgentContext, options: X509CreateCertificateOptions) {\n const webCrypto = new CredoWebCrypto(agentContext)\n\n const certificate = await X509Certificate.create(options, webCrypto)\n\n return certificate\n }\n\n public static async createCertificateSigningRequest(\n agentContext: AgentContext,\n options: X509CreateCertificateSigningRequestOptions\n ) {\n const webCrypto = new CredoWebCrypto(agentContext)\n\n const csr = await CertificateSigningRequest.create(options, webCrypto)\n\n return csr\n }\n\n /**\n * Creates and signs a X.509 Certificate Revocation List (CRL).\n *\n * Supported CRL extensions: CRL Number (§5.2.3), Delta CRL Indicator (§5.2.4), Authority Key\n * Identifier (§5.2.1) and Issuing Distribution Point (§5.2.5), plus the per-entry\n * `certificateIssuer` extension (§5.3.3) for indirect CRLs. Other CRL extensions are not produced.\n *\n * Note: issuer/subject names built from a structured object only support the `CN`, `C`, `OU` and\n * `ST` distinguished-name attributes; pass a raw DN string to use any other attribute.\n */\n public static async createCertificateRevocationList(\n agentContext: AgentContext,\n options: X509CreateCertificateRevocationListOptions\n ) {\n const webCrypto = new CredoWebCrypto(agentContext)\n\n const crl = await X509CertificateRevocationList.create(options, webCrypto)\n\n return crl\n }\n\n public static parseCertificateSigningRequest({\n encodedCertificateSigningRequest,\n }: X509ParseCertificateSigningRequestOptions) {\n const csr = CertificateSigningRequest.fromEncodedCertificateRequest(encodedCertificateSigningRequest)\n\n return csr\n }\n\n /**\n * Validates path length constraints for a certificate chain\n * Per RFC 5280 Section 4.2.1.9\n */\n private static validatePathLengthConstraints(\n certificateChain: X509Certificate[]\n ): X509CertificateSingleValidationResult {\n let currentPathLength = 0\n\n // The chain is ordered root-first (index 0) to leaf (last index). Iterate from the leaf upward,\n // counting the intermediate CAs encountered so each CA's pathLenConstraint can be checked against\n // the number of CAs that follow it toward the leaf.\n for (let i = certificateChain.length - 1; i >= 0; i--) {\n const cert = certificateChain[i]\n const peculiarCert = new x509.X509Certificate(cert.rawCertificate)\n const basicConstraintsExt = peculiarCert.getExtension(X509ExtensionIdentifier.BasicConstraints)\n\n if (!basicConstraintsExt) {\n // Leaf certificates don't need basicConstraints\n if (i === 0) continue\n // But CA certificates should have it\n continue\n }\n\n const basicConstraints = new x509.BasicConstraintsExtension(basicConstraintsExt.rawData)\n\n // Skip leaf certificates (not CA)\n if (!basicConstraints.ca) {\n continue\n }\n\n // Check if pathLength constraint is violated\n if (basicConstraints.pathLength !== undefined) {\n const remainingCAsInChain = currentPathLength\n if (remainingCAsInChain > basicConstraints.pathLength) {\n return {\n isValid: false,\n error: new X509Error(\n `Path length constraint violated. Certificate '${cert.subject}' has pathLength=${basicConstraints.pathLength} ` +\n `but ${remainingCAsInChain} intermediate CA(s) follow it in the chain.`\n ),\n details: `pathLength=${basicConstraints.pathLength}, actual=${remainingCAsInChain}`,\n }\n }\n }\n\n currentPathLength++\n }\n\n return { isValid: true }\n }\n\n /**\n * Validates basic constraints for CA certificates in the chain\n * Per RFC 5280 Section 4.2.1.9\n */\n private static validateBasicConstraintsForChain(\n certificateChain: X509Certificate[]\n ): X509CertificateSingleValidationResult {\n // Check all certificates except the last one (which is typically the end-entity/leaf)\n // In a properly ordered chain, CA certificates will be before the leaf\n for (let i = 0; i < certificateChain.length - 1; i++) {\n const cert = certificateChain[i]\n const peculiarCert = new x509.X509Certificate(cert.rawCertificate)\n const basicConstraintsExt = peculiarCert.getExtension(X509ExtensionIdentifier.BasicConstraints)\n\n if (!basicConstraintsExt) {\n return {\n isValid: false,\n error: new X509Error(\n `Certificate '${cert.subject}' is used as a CA but does not have a basicConstraints extension`\n ),\n }\n }\n\n const basicConstraints = new x509.BasicConstraintsExtension(basicConstraintsExt.rawData)\n\n // Intermediate and root CAs MUST have ca=true\n if (!basicConstraints.ca) {\n return {\n isValid: false,\n error: new X509Error(\n `Certificate '${cert.subject}' is used as a CA but does not have basicConstraints with ca=true`\n ),\n }\n }\n }\n\n return { isValid: true }\n }\n\n /**\n * Validates the Key Usage extension of the issuing CA certificates in the chain.\n * Per RFC 5280 §4.2.1.3 / §6.1.4(n): a certificate used to sign other certificates MUST, when it\n * asserts a Key Usage extension, include the `keyCertSign` bit.\n *\n * Key Usage is optional in a certificate; when the extension is absent, all usages are permitted\n * and no constraint is enforced. Only the issuing certificates (every certificate except the leaf)\n * are checked.\n */\n private static validateKeyUsageForChain(certificateChain: X509Certificate[]): X509CertificateSingleValidationResult {\n // Every certificate except the last one (the leaf/end-entity) issues the next certificate in the\n // chain, mirroring the set checked by validateBasicConstraintsForChain.\n for (let i = 0; i < certificateChain.length - 1; i++) {\n const cert = certificateChain[i]\n const keyUsage = cert.keyUsage\n\n // An empty result means no Key Usage extension is present, so the key is unrestricted and there\n // is nothing to enforce (a well-formed Key Usage extension always asserts at least one bit).\n if (keyUsage && keyUsage.length > 0 && !keyUsage.includes(X509KeyUsage.KeyCertSign)) {\n return {\n isValid: false,\n error: new X509Error(\n `Certificate '${cert.subject}' is used as a CA but its key usage does not include keyCertSign`\n ),\n details: 'Missing keyCertSign key usage',\n }\n }\n }\n\n return { isValid: true }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"X509Service.mjs","names":[],"sources":["../../../src/modules/x509/X509Service.ts"],"sourcesContent":["import * as x509 from '@peculiar/x509'\nimport { AgentContext } from '../../agent'\nimport { CredoWebCrypto } from '../../crypto/webcrypto'\nimport { injectable } from '../../plugins'\nimport { CertificateSigningRequest } from './CertificateSigningRequest'\nimport { X509ExtensionIdentifier } from './utils'\nimport { validateCriticalExtensionsForChain } from './utils/criticalExtensions'\nimport { X509Certificate, X509KeyUsage } from './X509Certificate'\nimport { X509CertificateRevocationList } from './X509CertificateRevocationList'\nimport { X509Error, X509ValidationError } from './X509Error'\nimport { X509ModuleConfig } from './X509ModuleConfig'\nimport { X509RevocationService } from './X509RevocationService'\nimport type {\n X509CreateCertificateOptions,\n X509CreateCertificateRevocationListOptions,\n X509CreateCertificateSigningRequestOptions,\n X509GetLeafCertificateOptions,\n X509ParseCertificateOptions,\n X509ParseCertificateSigningRequestOptions,\n X509ValidateCertificateChainOptions,\n} from './X509ServiceOptions'\nimport { X509RevocationCheckMode } from './X509ValidationOptions'\nimport type { X509CertificateSingleValidationResult, X509ValidationResult } from './X509ValidationResult'\n\n@injectable()\n// biome-ignore lint/complexity/noStaticOnlyClass: no explanation\nexport class X509Service {\n /**\n *\n * Validate a chain of X.509 certificates according to RFC 5280\n *\n * This function requires a list of base64 encoded certificates and, optionally, a certificate that should be found in the chain.\n * If no certificate is provided, it will just assume the leaf certificate\n *\n * The leaf certificate should be the 0th index and the root the last\n *\n * The Issuer of the certificate is found with the following algorithm:\n * - Check if there is an AuthorityKeyIdentifierExtension\n * - Go through all the other certificates and see if the SubjectKeyIdentifier is equal to the AuthorityKeyIdentifier\n * - If they are equal, the certificate signature is verified and returned as the issuer\n *\n * Validation checks performed:\n * - Chain validation (structure, signatures, validity, trust anchor)\n * - Basic constraints are valid for CA certificates\n * - Key usage of issuing CA certificates includes `keyCertSign` (when the extension is present)\n * - Path length constraints are satisfied\n * - All critical extensions are understood and valid\n * - Certificates have not been revoked (CRL) - if enabled\n *\n * Limitations (RFC 5280 features not enforced here):\n * - Revocation is checked via CRL only; OCSP is not supported.\n * - Name Constraints (§4.2.1.10), Policy Constraints (§4.2.1.11), and Certificate Policies\n * (§4.2.1.4) are not enforced. A certificate carrying one of these as a *critical* extension is\n * rejected (unrecognized critical extension); a non-critical occurrence is ignored.\n * - Extended Key Usage is parsed but not enforced.\n * - Key Usage is only enforced when the extension is present (it is optional; absent means\n * unrestricted).\n * - Subject/Issuer Alternative Names only surface the URI and DNS GeneralName forms.\n */\n public static async validateCertificateChain(\n agentContext: AgentContext,\n {\n certificateChain,\n certificate = certificateChain[0],\n verificationDate = new Date(),\n trustedCertificates,\n allowNonRootTrustedCertificate = true,\n }: X509ValidateCertificateChainOptions\n ) {\n const validations: X509ValidationResult['validations'] = {}\n const config = agentContext.dependencyManager.resolve(X509ModuleConfig)\n\n let parsedChain: X509Certificate[]\n\n // Phase 1: Chain validation (structure + signatures + validity + trust)\n try {\n if (certificateChain.length === 0) {\n throw new X509Error('Certificate chain is empty')\n }\n const webCrypto = new CredoWebCrypto(agentContext)\n\n let parsedLeafCertificate: x509.X509Certificate\n let certificatesToBuildChain: x509.X509Certificate[]\n try {\n parsedLeafCertificate = new x509.X509Certificate(certificate)\n certificatesToBuildChain = [...certificateChain, ...(trustedCertificates ?? [])].map(\n (c) => new x509.X509Certificate(c)\n )\n } catch (error) {\n throw new X509Error('Error during parsing of x509 certificate', { cause: error })\n }\n\n const certificateChainBuilder = new x509.X509ChainBuilder({\n certificates: certificatesToBuildChain,\n })\n\n const chain = await certificateChainBuilder.build(parsedLeafCertificate, webCrypto)\n\n // The chain is reversed here as the `x5c` header (the expected input),\n // has the leaf certificate as the first entry, while the `x509` library expects this as the last\n parsedChain = chain.map((c) => X509Certificate.fromRawCertificate(new Uint8Array(c.rawData))).reverse()\n\n // We allow longer parsed chain, in case the root cert was not part of the chain, but in the\n // list of trusted certificates\n if (parsedChain.length < certificateChain.length) {\n throw new X509Error('Could not parse the full chain. Likely due to incorrect ordering')\n }\n\n let previousCertificate: X509Certificate | undefined\n\n if (trustedCertificates) {\n const parsedTrustedCertificates = trustedCertificates.map((trustedCertificate) =>\n X509Certificate.fromEncodedCertificate(trustedCertificate)\n )\n\n const trustedCertificateIndex = parsedChain.findIndex((cert) =>\n parsedTrustedCertificates.some((tCert) => cert.equal(tCert))\n )\n\n if (trustedCertificateIndex === -1) {\n throw new X509Error('No trusted certificate was found while validating the X.509 chain')\n }\n\n if (trustedCertificateIndex > 0) {\n // When we trust a certificate other than the first certificate in the provided chain we keep a reference to the\n // previous certificate as we need the key of this certificate to verify the first certificate in the chain as\n // it's not self-signed.\n previousCertificate = parsedChain[trustedCertificateIndex - 1]\n\n // Pop everything off before the index of the trusted certificate (those are more root) as it is not relevant for validation\n parsedChain = parsedChain.slice(trustedCertificateIndex)\n }\n }\n\n // Verify signatures and validity for all certificates in the chain\n for (let i = 0; i < parsedChain.length; i++) {\n const cert = parsedChain[i]\n const publicJwk = previousCertificate ? previousCertificate.publicJwk : undefined\n\n // When a trusted certificate is the first in the chain and we don't have its parent certificate's public key,\n // we may need to skip signature verification based on configuration.\n //\n // Scenario: For ISO 18013-5 mDL, the root cert MUST NOT be in the chain. If the intermediate/signer\n // certificate is directly trusted, we can't verify its signature without the root certificate.\n // See also https://github.com/openid/OpenID4VCI/issues/62\n //\n // If allowNonRootTrustedCertificate is false, we require the signature to be verifiable.\n const skipSignatureVerification = i === 0 && trustedCertificates && !publicJwk && allowNonRootTrustedCertificate\n\n // If we don't allow non-root trusted certificates and can't verify the signature, throw an error\n if (i === 0 && trustedCertificates && !publicJwk && !allowNonRootTrustedCertificate) {\n throw new X509Error(\n 'Unable to verify the certificate chain. A trusted non-self-signed certificate is the first certificate in the chain, ' +\n 'but no parent certificate was found to verify its signature. Either provide the parent certificate in trustedCertificates, ' +\n 'or set allowNonRootTrustedCertificate to true to allow trusting intermediate certificates without signature verification.'\n )\n }\n\n await cert.verify(\n {\n publicJwk,\n verificationDate,\n skipSignatureVerification,\n },\n webCrypto\n )\n previousCertificate = cert\n }\n\n validations.chain = { isValid: true }\n } catch (error) {\n const x509Error = error instanceof X509Error ? error : new X509Error('Chain validation failed', { cause: error })\n validations.chain = { isValid: false, error: x509Error }\n throw new X509ValidationError(x509Error.message, { isValid: false, validations, error: x509Error })\n }\n\n // Phase 2: Basic constraints validation for CA certificates\n validations.basicConstraints = X509Service.validateBasicConstraintsForChain(parsedChain)\n if (!validations.basicConstraints.isValid) {\n throw new X509ValidationError(\n validations.basicConstraints.error?.message ?? 'Basic constraints validation failed',\n { isValid: false, validations, error: validations.basicConstraints.error }\n )\n }\n\n // Phase 2.5: Key usage validation for issuing CA certificates\n validations.keyUsage = X509Service.validateKeyUsageForChain(parsedChain)\n if (!validations.keyUsage.isValid) {\n throw new X509ValidationError(validations.keyUsage.error?.message ?? 'Key usage validation failed', {\n isValid: false,\n validations,\n error: validations.keyUsage.error,\n })\n }\n\n // Phase 3: Path length constraint validation\n validations.pathLength = X509Service.validatePathLengthConstraints(parsedChain)\n if (!validations.pathLength.isValid) {\n throw new X509ValidationError(\n validations.pathLength.error?.message ?? 'Path length constraint validation failed',\n { isValid: false, validations, error: validations.pathLength.error }\n )\n }\n\n // Phase 4: Critical extension validation\n const peculiarChain = parsedChain.map((cert) => new x509.X509Certificate(cert.rawCertificate))\n validations.criticalExtensions = validateCriticalExtensionsForChain(peculiarChain)\n if (!validations.criticalExtensions.isValid) {\n throw new X509ValidationError(\n validations.criticalExtensions.error?.message ?? 'Critical extension validation failed',\n { isValid: false, validations, error: validations.criticalExtensions.error }\n )\n }\n\n // Phase 5: Revocation checking (if enabled)\n if (config.revocationCheck.mode !== X509RevocationCheckMode.Disabled) {\n validations.revocationStatus = await X509RevocationService.checkCertificateChainRevocation(\n agentContext,\n parsedChain,\n config,\n verificationDate\n )\n if (!validations.revocationStatus.isValid) {\n throw new X509ValidationError(validations.revocationStatus.error?.message ?? 'Revocation check failed', {\n isValid: false,\n validations,\n error: validations.revocationStatus.error,\n })\n }\n } else {\n validations.revocationStatus = { isValid: true, details: 'Revocation checking disabled' }\n }\n\n return parsedChain\n }\n\n /**\n *\n * Parses a base64-encoded X.509 certificate into a {@link X509Certificate}\n *\n */\n public static parseCertificate(\n _agentContext: AgentContext,\n { encodedCertificate }: X509ParseCertificateOptions\n ): X509Certificate {\n const certificate = X509Certificate.fromEncodedCertificate(encodedCertificate)\n\n return certificate\n }\n\n public static getLeafCertificate(\n _agentContext: AgentContext,\n { certificateChain }: X509GetLeafCertificateOptions\n ): X509Certificate {\n if (certificateChain.length === 0) throw new X509Error('Certificate chain is empty')\n\n const certificate = X509Certificate.fromEncodedCertificate(certificateChain[0])\n\n return certificate\n }\n\n public static async createCertificate(agentContext: AgentContext, options: X509CreateCertificateOptions) {\n const webCrypto = new CredoWebCrypto(agentContext)\n\n const certificate = await X509Certificate.create(options, webCrypto)\n\n return certificate\n }\n\n public static async createCertificateSigningRequest(\n agentContext: AgentContext,\n options: X509CreateCertificateSigningRequestOptions\n ) {\n const webCrypto = new CredoWebCrypto(agentContext)\n\n const csr = await CertificateSigningRequest.create(options, webCrypto)\n\n return csr\n }\n\n /**\n * Creates and signs a X.509 Certificate Revocation List (CRL).\n *\n * Supported CRL extensions: CRL Number (§5.2.3), Delta CRL Indicator (§5.2.4), Authority Key\n * Identifier (§5.2.1) and Issuing Distribution Point (§5.2.5), plus the per-entry\n * `certificateIssuer` extension (§5.3.3) for indirect CRLs. Other CRL extensions are not produced.\n *\n * Note: issuer/subject names built from a structured object only support the `CN`, `C`, `OU` and\n * `ST` distinguished-name attributes; pass a raw DN string to use any other attribute.\n */\n public static async createCertificateRevocationList(\n agentContext: AgentContext,\n options: X509CreateCertificateRevocationListOptions\n ) {\n const webCrypto = new CredoWebCrypto(agentContext)\n\n const crl = await X509CertificateRevocationList.create(options, webCrypto)\n\n return crl\n }\n\n public static parseCertificateSigningRequest({\n encodedCertificateSigningRequest,\n }: X509ParseCertificateSigningRequestOptions) {\n const csr = CertificateSigningRequest.fromEncodedCertificateRequest(encodedCertificateSigningRequest)\n\n return csr\n }\n\n /**\n * Validates path length constraints for a certificate chain\n * Per RFC 5280 Section 4.2.1.9\n */\n private static validatePathLengthConstraints(\n certificateChain: X509Certificate[]\n ): X509CertificateSingleValidationResult {\n let currentPathLength = 0\n\n // The chain is ordered root-first (index 0) to leaf (last index). Iterate from the leaf upward,\n // counting the intermediate CAs encountered so each CA's pathLenConstraint can be checked against\n // the number of CAs that follow it toward the leaf.\n for (let i = certificateChain.length - 1; i >= 0; i--) {\n const cert = certificateChain[i]\n const peculiarCert = new x509.X509Certificate(cert.rawCertificate)\n const basicConstraintsExt = peculiarCert.getExtension(X509ExtensionIdentifier.BasicConstraints)\n\n if (!basicConstraintsExt) {\n // Leaf certificates don't need basicConstraints\n if (i === 0) continue\n // But CA certificates should have it\n continue\n }\n\n const basicConstraints = new x509.BasicConstraintsExtension(basicConstraintsExt.rawData)\n\n // Skip leaf certificates (not CA)\n if (!basicConstraints.ca) {\n continue\n }\n\n // Check if pathLength constraint is violated\n if (basicConstraints.pathLength !== undefined) {\n const remainingCAsInChain = currentPathLength\n if (remainingCAsInChain > basicConstraints.pathLength) {\n return {\n isValid: false,\n error: new X509Error(\n `Path length constraint violated. Certificate '${cert.subject}' has pathLength=${basicConstraints.pathLength} ` +\n `but ${remainingCAsInChain} intermediate CA(s) follow it in the chain.`\n ),\n details: `pathLength=${basicConstraints.pathLength}, actual=${remainingCAsInChain}`,\n }\n }\n }\n\n currentPathLength++\n }\n\n return { isValid: true }\n }\n\n /**\n * Validates basic constraints for CA certificates in the chain\n * Per RFC 5280 Section 4.2.1.9\n */\n private static validateBasicConstraintsForChain(\n certificateChain: X509Certificate[]\n ): X509CertificateSingleValidationResult {\n // Check all certificates except the last one (which is typically the end-entity/leaf)\n // In a properly ordered chain, CA certificates will be before the leaf\n for (let i = 0; i < certificateChain.length - 1; i++) {\n const cert = certificateChain[i]\n const peculiarCert = new x509.X509Certificate(cert.rawCertificate)\n const basicConstraintsExt = peculiarCert.getExtension(X509ExtensionIdentifier.BasicConstraints)\n\n if (!basicConstraintsExt) {\n return {\n isValid: false,\n error: new X509Error(\n `Certificate '${cert.subject}' is used as a CA but does not have a basicConstraints extension`\n ),\n }\n }\n\n const basicConstraints = new x509.BasicConstraintsExtension(basicConstraintsExt.rawData)\n\n // Intermediate and root CAs MUST have ca=true\n if (!basicConstraints.ca) {\n return {\n isValid: false,\n error: new X509Error(\n `Certificate '${cert.subject}' is used as a CA but does not have basicConstraints with ca=true`\n ),\n }\n }\n }\n\n return { isValid: true }\n }\n\n /**\n * Validates the Key Usage extension of the issuing CA certificates in the chain.\n * Per RFC 5280 §4.2.1.3 / §6.1.4(n): a certificate used to sign other certificates MUST, when it\n * asserts a Key Usage extension, include the `keyCertSign` bit.\n *\n * Key Usage is optional in a certificate; when the extension is absent, all usages are permitted\n * and no constraint is enforced. Only the issuing certificates (every certificate except the leaf)\n * are checked.\n */\n private static validateKeyUsageForChain(certificateChain: X509Certificate[]): X509CertificateSingleValidationResult {\n // Every certificate except the last one (the leaf/end-entity) issues the next certificate in the\n // chain, mirroring the set checked by validateBasicConstraintsForChain.\n for (let i = 0; i < certificateChain.length - 1; i++) {\n const cert = certificateChain[i]\n const keyUsage = cert.keyUsage\n\n // An empty result means no Key Usage extension is present, so the key is unrestricted and there\n // is nothing to enforce (a well-formed Key Usage extension always asserts at least one bit).\n if (keyUsage && keyUsage.length > 0 && !keyUsage.includes(X509KeyUsage.KeyCertSign)) {\n return {\n isValid: false,\n error: new X509Error(\n `Certificate '${cert.subject}' is used as a CA but its key usage does not include keyCertSign`\n ),\n details: 'Missing keyCertSign key usage',\n }\n }\n }\n\n return { isValid: true }\n }\n}\n"],"mappings":"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"}
|
|
@@ -65,8 +65,8 @@ export interface X509CheckCertificateRevocationOptions {
|
|
|
65
65
|
*/
|
|
66
66
|
issuerCertificate: EncodedX509Certificate | X509Certificate;
|
|
67
67
|
/**
|
|
68
|
-
* Options controlling how revocation is checked.
|
|
69
|
-
* `revocationCheck` options
|
|
68
|
+
* Options controlling how revocation is checked. Options that are not set use the module's
|
|
69
|
+
* `revocationCheck` options.
|
|
70
70
|
*/
|
|
71
71
|
revocationCheckOptions?: X509RevocationCheckOptions;
|
|
72
72
|
}
|
|
@@ -84,13 +84,11 @@ export interface X509FetchCertificateRevocationListOptions {
|
|
|
84
84
|
*/
|
|
85
85
|
issuerCertificate?: EncodedX509Certificate | X509Certificate;
|
|
86
86
|
/**
|
|
87
|
-
* Timeout in milliseconds for the CRL fetch.
|
|
88
|
-
* @default 5000
|
|
87
|
+
* Timeout in milliseconds for the CRL fetch. Defaults to the module's `revocationCheck.timeoutMs`.
|
|
89
88
|
*/
|
|
90
89
|
timeoutMs?: number;
|
|
91
90
|
/**
|
|
92
|
-
* Maximum size in bytes for the CRL download.
|
|
93
|
-
* @default 10485760 (10 MB)
|
|
91
|
+
* Maximum size in bytes for the CRL download. Defaults to the module's `revocationCheck.maxCrlSizeBytes`.
|
|
94
92
|
*/
|
|
95
93
|
maxCrlSizeBytes?: number;
|
|
96
94
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"X509ServiceOptions.d.mts","names":[],"sources":["../../../src/modules/x509/X509ServiceOptions.ts"],"mappings":";;;;;;;;KAOK,kBAAkB,UAAU,eAAe,4BAA4B,OACzE,WAAW,IAAI,EAAE;EAChB;;;;;YAOQ;iBAEK;EACf,kBAAkB;EAElB,cAAc;;;;;;;;;EAUd,mBAAmB;EAEnB,sBAAsB;;;;;;;;;;;;;;;;;;;;;EAsBtB;;iBAGe;EACf,kBAAkB;;iBAGH;EACf;;iBAGe;;;;EAIf,aAAa,yBAAyB;;;;;EAMtC,mBAAmB,yBAAyB;;;;;EAM5C,yBAAyB;;iBAGV;;;;EAIf;;;;;;;;EASA,oBAAoB,yBAAyB
|
|
1
|
+
{"version":3,"file":"X509ServiceOptions.d.mts","names":[],"sources":["../../../src/modules/x509/X509ServiceOptions.ts"],"mappings":";;;;;;;;KAOK,kBAAkB,UAAU,eAAe,4BAA4B,OACzE,WAAW,IAAI,EAAE;EAChB;;;;;YAOQ;iBAEK;EACf,kBAAkB;EAElB,cAAc;;;;;;;;;EAUd,mBAAmB;EAEnB,sBAAsB;;;;;;;;;;;;;;;;;;;;;EAsBtB;;iBAGe;EACf,kBAAkB;;iBAGH;EACf;;iBAGe;;;;EAIf,aAAa,yBAAyB;;;;;EAMtC,mBAAmB,yBAAyB;;;;;EAM5C,yBAAyB;;iBAGV;;;;EAIf;;;;;;;;EASA,oBAAoB,yBAAyB;;;;EAK7C;;;;EAKA;;;;;;EAOA,mBAAmB;;iBAGJ;;;;EAIf;;iBAGe;EACf,cAAc,MAAM;EACpB;;YAGU,mCAAmC;EAC7C;IACE;;EAEF;IACE,QAAQ,MAAM;;EAEhB;IACE,QAAQ,MAAM;;EAEhB;IACE;;EAEF;IACE,MAAM;MAAQ,MAAM;MAAiB;;;EAEvC;IACE,MAAM;MAAQ,MAAM;MAAiB;;;EAEvC;IACE;IACA;;EAEF;IACE,MAAM;;;;;;;IAQN,UAAU,MAAM;;;;;;;;;IAUhB;;;YAIQ,iDAAiD,KAC3D;iBAIe;EACf;EACA;EACA;EACA;;iBAGe;;;;;;EAMf;;;;;;EAOA,cAAc;;;;;;;;;EAUd,mBAAmB;;;;;;EAOnB,iBAAiB;;;;;;;;;EAUjB,mBAAmB;;;;;;EAOnB;;;;;;IAME,YAAY;;;;;;IAOZ,WAAW;;;;;;;EAQb,aAAa;;iBAGE;;;;;;;EAOf,kBAAkB;;;;EAKlB,kBAAkB;;;;EAKlB,aAAa;;iBAGE;EACf;;iBAGe;;;;;;EAMf;;;;;;EAOA,cAAc;;;;;;EAOd,iBAAiB;;;;EAKjB,SAAS;;;;;;EAOT,kBAAkB;;YAGR,iDAAiD;;;;;;EAM3D;IACE;;;;;;;;EASF;IACE;;;;;;EAOF;IACE;;;;;;;EAQF;;IAEE;IACA;IACA;;IAEA,kBAAkB;;IAElB;IACA;;;iBAIa;;;;EAIf,cAAc;;;;EAKd,iBAAiB;;;;EAKjB;;;;;;IAME,aAAa;;;;IAKb,aAAa;;;;;EAMf,UAAU,MAAM;;;;EAKhB,aAAa"}
|
|
@@ -54,8 +54,7 @@ export interface X509RevocationCheckOptions {
|
|
|
54
54
|
* distribution point. After this period the CRL bytes are fetched again: when unchanged (same
|
|
55
55
|
* SHA-256) the cached CRL is reused without re-parsing or re-verifying, otherwise it is fully
|
|
56
56
|
* re-verified. Independently of this period, a cached CRL is never served past its own
|
|
57
|
-
* `nextUpdate`.
|
|
58
|
-
* module's `revocationCheck` applies.
|
|
57
|
+
* `nextUpdate`.
|
|
59
58
|
*
|
|
60
59
|
* @default 3600 (1 hour)
|
|
61
60
|
*/
|
|
@@ -82,5 +81,23 @@ export interface X509RevocationCheckOptions {
|
|
|
82
81
|
*/
|
|
83
82
|
requiredReasons?: X509RevocationReason[];
|
|
84
83
|
}
|
|
84
|
+
/**
|
|
85
|
+
* Revocation check options where every option that has a default is set.
|
|
86
|
+
*/
|
|
87
|
+
export type X509RevocationCheckOptionsWithDefaults = X509RevocationCheckOptions & Required<Pick<X509RevocationCheckOptions, 'mode' | 'checkFullChain' | 'timeoutMs' | 'maxCrlSizeBytes' | 'crlCacheExpirySeconds'>>;
|
|
88
|
+
/**
|
|
89
|
+
* The revocation check options used for any option that is not configured.
|
|
90
|
+
*/
|
|
91
|
+
export declare const defaultX509RevocationCheckOptions: {
|
|
92
|
+
readonly mode: X509RevocationCheckMode.SoftFail;
|
|
93
|
+
readonly checkFullChain: true;
|
|
94
|
+
readonly timeoutMs: 5000;
|
|
95
|
+
readonly maxCrlSizeBytes: number;
|
|
96
|
+
readonly crlCacheExpirySeconds: 3600;
|
|
97
|
+
};
|
|
98
|
+
/**
|
|
99
|
+
* Takes the options that are not set in `options` from `base`.
|
|
100
|
+
*/
|
|
101
|
+
export declare function withX509RevocationCheckDefaults(options?: X509RevocationCheckOptions, base?: X509RevocationCheckOptionsWithDefaults): X509RevocationCheckOptionsWithDefaults;
|
|
85
102
|
//#endregion
|
|
86
103
|
//# sourceMappingURL=X509ValidationOptions.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"X509ValidationOptions.d.mts","names":[],"sources":["../../../src/modules/x509/X509ValidationOptions.ts"],"mappings":";;;;;oBAKY;;;;EAIV;;;;;;EAOA;;;;;EAMA;;iBAGe;;;;;;;;EAQf,OAAO;;;;;;;;;;EAWP;;;;;EAMA;;;;;EAMA
|
|
1
|
+
{"version":3,"file":"X509ValidationOptions.d.mts","names":[],"sources":["../../../src/modules/x509/X509ValidationOptions.ts"],"mappings":";;;;;oBAKY;;;;EAIV;;;;;;EAOA;;;;;EAMA;;iBAGe;;;;;;;;EAQf,OAAO;;;;;;;;;;EAWP;;;;;EAMA;;;;;EAMA;;;;;;;;;;EAWA;;;;;;EAOA,mBAAmB;;;;;;;;;;;;;;;EAgBnB,kBAAkB;;;;;YAMR,yCAAyC,6BACnD,SACE,KACE;;;;qBAQO;iBAAA,wBAAA;;;;;;;;;wBAWG,gCACd,UAAS,4BACT,OAAM,yCACL"}
|
|
@@ -20,7 +20,27 @@ let X509RevocationCheckMode = /* @__PURE__ */ function(X509RevocationCheckMode)
|
|
|
20
20
|
X509RevocationCheckMode["Require"] = "Require";
|
|
21
21
|
return X509RevocationCheckMode;
|
|
22
22
|
}({});
|
|
23
|
+
/**
|
|
24
|
+
* The revocation check options used for any option that is not configured.
|
|
25
|
+
*/
|
|
26
|
+
const defaultX509RevocationCheckOptions = {
|
|
27
|
+
mode: "SoftFail",
|
|
28
|
+
checkFullChain: true,
|
|
29
|
+
timeoutMs: 5e3,
|
|
30
|
+
maxCrlSizeBytes: 10485760,
|
|
31
|
+
crlCacheExpirySeconds: 3600
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Takes the options that are not set in `options` from `base`.
|
|
35
|
+
*/
|
|
36
|
+
function withX509RevocationCheckDefaults(options = {}, base = defaultX509RevocationCheckOptions) {
|
|
37
|
+
const setOptions = Object.fromEntries(Object.entries(options).filter(([, value]) => value !== void 0));
|
|
38
|
+
return {
|
|
39
|
+
...base,
|
|
40
|
+
...setOptions
|
|
41
|
+
};
|
|
42
|
+
}
|
|
23
43
|
//#endregion
|
|
24
|
-
export { X509RevocationCheckMode };
|
|
44
|
+
export { X509RevocationCheckMode, defaultX509RevocationCheckOptions, withX509RevocationCheckDefaults };
|
|
25
45
|
|
|
26
46
|
//# sourceMappingURL=X509ValidationOptions.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"X509ValidationOptions.mjs","names":[],"sources":["../../../src/modules/x509/X509ValidationOptions.ts"],"sourcesContent":["import type { X509RevocationReason } from './X509CrlDistributionPoint'\n\n/**\n * Revocation checking mode based on Java PKIXRevocationChecker.Option patterns\n */\nexport enum X509RevocationCheckMode {\n /**\n * Completely disable revocation checking\n */\n Disabled = 'Disabled',\n\n /**\n * Check revocation status but allow validation to succeed if:\n * - CRL cannot be obtained due to network error\n * This is the RECOMMENDED default for production systems\n */\n SoftFail = 'SoftFail',\n\n /**\n * Require successful revocation check - fail if network unavailable or service errors\n * Use with caution in production as network failures will block certificate validation\n */\n Require = 'Require',\n}\n\nexport interface X509RevocationCheckOptions {\n /**\n * Mode for revocation checking\n * @default {@link X509RevocationCheckMode.SoftFail}\n *\n * Note: Currently defaults to 'SoftFail' to provide enhanced security\n * without breaking existing functionality in network failure scenarios\n */\n mode?: X509RevocationCheckMode\n\n /**\n * Whether to check revocation for all certificates in the chain (except the root).\n *\n * Defaults to `true`, so every certificate in the chain (the end-entity and any intermediate CAs)\n * is checked, as RFC 5280 expects. Set to `false` to only check the end-entity/leaf certificate,\n * which is faster but weaker.\n *\n * @default true\n */\n checkFullChain?: boolean\n\n /**\n * Timeout in milliseconds for CRL fetches\n * @default 5000\n */\n timeoutMs?: number\n\n /**\n * Maximum size in bytes for CRL downloads\n * @default 10485760 (10 MB)\n */\n maxCrlSizeBytes?: number\n\n /**\n * How long (in seconds) a verified CRL is served from the cache without contacting the\n * distribution point. After this period the CRL bytes are fetched again: when unchanged (same\n * SHA-256) the cached CRL is reused without re-parsing or re-verifying, otherwise it is fully\n * re-verified. Independently of this period, a cached CRL is never served past its own\n * `nextUpdate
|
|
1
|
+
{"version":3,"file":"X509ValidationOptions.mjs","names":[],"sources":["../../../src/modules/x509/X509ValidationOptions.ts"],"sourcesContent":["import type { X509RevocationReason } from './X509CrlDistributionPoint'\n\n/**\n * Revocation checking mode based on Java PKIXRevocationChecker.Option patterns\n */\nexport enum X509RevocationCheckMode {\n /**\n * Completely disable revocation checking\n */\n Disabled = 'Disabled',\n\n /**\n * Check revocation status but allow validation to succeed if:\n * - CRL cannot be obtained due to network error\n * This is the RECOMMENDED default for production systems\n */\n SoftFail = 'SoftFail',\n\n /**\n * Require successful revocation check - fail if network unavailable or service errors\n * Use with caution in production as network failures will block certificate validation\n */\n Require = 'Require',\n}\n\nexport interface X509RevocationCheckOptions {\n /**\n * Mode for revocation checking\n * @default {@link X509RevocationCheckMode.SoftFail}\n *\n * Note: Currently defaults to 'SoftFail' to provide enhanced security\n * without breaking existing functionality in network failure scenarios\n */\n mode?: X509RevocationCheckMode\n\n /**\n * Whether to check revocation for all certificates in the chain (except the root).\n *\n * Defaults to `true`, so every certificate in the chain (the end-entity and any intermediate CAs)\n * is checked, as RFC 5280 expects. Set to `false` to only check the end-entity/leaf certificate,\n * which is faster but weaker.\n *\n * @default true\n */\n checkFullChain?: boolean\n\n /**\n * Timeout in milliseconds for CRL fetches\n * @default 5000\n */\n timeoutMs?: number\n\n /**\n * Maximum size in bytes for CRL downloads\n * @default 10485760 (10 MB)\n */\n maxCrlSizeBytes?: number\n\n /**\n * How long (in seconds) a verified CRL is served from the cache without contacting the\n * distribution point. After this period the CRL bytes are fetched again: when unchanged (same\n * SHA-256) the cached CRL is reused without re-parsing or re-verifying, otherwise it is fully\n * re-verified. Independently of this period, a cached CRL is never served past its own\n * `nextUpdate`.\n *\n * @default 3600 (1 hour)\n */\n crlCacheExpirySeconds?: number\n\n /**\n * The date/time to use for revocation checking\n * Used to verify CRL validity (thisUpdate/nextUpdate) and revocation dates\n * @default new Date() (current time)\n */\n verificationDate?: Date\n\n /**\n * Specific revocation reasons that must be covered by CRLs\n * If a certificate has no reasons specified in its CRL distribution points (a \"full\" DP),\n * it covers all reasons automatically.\n *\n * If a certificate only has distribution points with specific reasons, this array determines\n * which reasons must be covered for the check to be considered complete.\n *\n * @default The reasons actually published across the certificate's CRL distribution points.\n * There is no point requiring coverage of reasons the issuer never published a distribution point\n * for, so by default only the reasons present in the certificate's DPs are required.\n *\n * Set to a subset to only require coverage of specific reasons.\n */\n requiredReasons?: X509RevocationReason[]\n}\n\n/**\n * Revocation check options where every option that has a default is set.\n */\nexport type X509RevocationCheckOptionsWithDefaults = X509RevocationCheckOptions &\n Required<\n Pick<\n X509RevocationCheckOptions,\n 'mode' | 'checkFullChain' | 'timeoutMs' | 'maxCrlSizeBytes' | 'crlCacheExpirySeconds'\n >\n >\n\n/**\n * The revocation check options used for any option that is not configured.\n */\nexport const defaultX509RevocationCheckOptions = {\n mode: X509RevocationCheckMode.SoftFail,\n checkFullChain: true,\n timeoutMs: 5000,\n maxCrlSizeBytes: 10 * 1024 * 1024,\n crlCacheExpirySeconds: 3600,\n} as const satisfies X509RevocationCheckOptionsWithDefaults\n\n/**\n * Takes the options that are not set in `options` from `base`.\n */\nexport function withX509RevocationCheckDefaults(\n options: X509RevocationCheckOptions = {},\n base: X509RevocationCheckOptionsWithDefaults = defaultX509RevocationCheckOptions\n): X509RevocationCheckOptionsWithDefaults {\n const setOptions = Object.fromEntries(Object.entries(options).filter(([, value]) => value !== undefined))\n return { ...base, ...setOptions }\n}\n"],"mappings":";;;;AAKA,IAAY,0BAAL,yBAAA,yBAAA;;;;CAIL,wBAAA,cAAA;;;;;;CAOA,wBAAA,cAAA;;;;;CAMA,wBAAA,aAAA;;AACF,EAAA,CAAA,CAAA;;;;AAoFA,MAAa,oCAAoC;CAC/C,MAAA;CACA,gBAAgB;CAChB,WAAW;CACX,iBAAiB;CACjB,uBAAuB;AACzB;;;;AAKA,SAAgB,gCACd,UAAsC,CAAC,GACvC,OAA+C,mCACP;CACxC,MAAM,aAAa,OAAO,YAAY,OAAO,QAAQ,OAAO,CAAC,CAAC,QAAQ,GAAG,WAAW,UAAU,KAAA,CAAS,CAAC;CACxG,OAAO;EAAE,GAAG;EAAM,GAAG;CAAW;AAClC"}
|
|
@@ -2,7 +2,7 @@ import { convertLegacyTrustedCertificates, legacyTrustedCertificatesToTrustedIss
|
|
|
2
2
|
import { ALL_REVOCATION_REASONS_MASK, X509CrlDistributionPoint, X509RevocationReason } from "./X509CrlDistributionPoint.mjs";
|
|
3
3
|
import { X509Certificate, X509CertificateOptions, X509ExtendedKeyUsage, X509KeyUsage } from "./X509Certificate.mjs";
|
|
4
4
|
import { X509CertificateRevocationList, X509CertificateRevocationListEntry, X509CertificateRevocationListEntryReason, X509IssuingDistributionPoint } from "./X509CertificateRevocationList.mjs";
|
|
5
|
-
import { X509RevocationCheckMode, X509RevocationCheckOptions } from "./X509ValidationOptions.mjs";
|
|
5
|
+
import { X509RevocationCheckMode, X509RevocationCheckOptions, X509RevocationCheckOptionsWithDefaults, defaultX509RevocationCheckOptions, withX509RevocationCheckDefaults } from "./X509ValidationOptions.mjs";
|
|
6
6
|
import { EncodedX509Certificate, X509CertificateExtensionsOptions, X509CertificateIssuerAndSubjectOptions, X509CertificateRevocationListEntryOptions, X509CertificateRevocationListExtensionsOptions, X509CertificateSigningRequestExtensionsOptions, X509CheckCertificateRevocationOptions, X509CreateCertificateChainOptions, X509CreateCertificateOptions, X509CreateCertificateRevocationListOptions, X509CreateCertificateSigningRequestOptions, X509FetchCertificateRevocationListOptions, X509GetLeafCertificateOptions, X509ParseCertificateOptions, X509ParseCertificateRevocationListOptions, X509ParseCertificateSigningRequestOptions, X509ValidateCertificateChainOptions } from "./X509ServiceOptions.mjs";
|
|
7
7
|
import { X509CrlExtensionIdentifier, X509ExtensionIdentifier, createAuthorityKeyIdentifierExtension, createBasicConstraintsExtension, createCrlDistributionPointsExtension, createCrlEntryCertificateIssuerExtension, createCrlNumberExtension, createDeltaCrlIndicatorExtension, createExtendedKeyUsagesExtension, createIssuerAlternativeNameExtension, createIssuingDistributionPointExtension, createKeyUsagesExtension, createSubjectAlternativeNameExtension, createSubjectKeyIdentifierExtension } from "./utils/extensions.mjs";
|
|
8
8
|
import { convertName } from "./utils/nameConversion.mjs";
|
|
@@ -17,4 +17,4 @@ import { X509CrlUnavailableError, X509Error, X509ValidationError } from "./X509E
|
|
|
17
17
|
import { X509Module } from "./X509Module.mjs";
|
|
18
18
|
import { X509Service } from "./X509Service.mjs";
|
|
19
19
|
import { X509ModuleConfig, X509ModuleConfigOptions, X509VerificationContext, X509VerificationTrustedCertificates, X509VerificationTypeCredential, X509VerificationTypeMdocReaderAuth, X509VerificationTypeOauth2ClientAttestation, X509VerificationTypeOauth2SecuredAuthorizationRequest, X509VerificationTypeOpenId4VciCredentialIssuerMetadata, X509VerificationTypeOpenId4VciKeyAttestation } from "./X509ModuleConfig.mjs";
|
|
20
|
-
export { ALL_REVOCATION_REASONS_MASK, CertificateSigningRequest, CertificateSigningRequestOptions, EncodedX509Certificate, X509Api, X509Certificate, X509CertificateExtensionsOptions, X509CertificateIssuerAndSubjectOptions, X509CertificateOptions, X509CertificateRevocationList, X509CertificateRevocationListEntry, X509CertificateRevocationListEntryOptions, X509CertificateRevocationListEntryReason, X509CertificateRevocationListExtensionsOptions, X509CertificateSigningRequestExtensionsOptions, X509CertificateSingleValidationResult, X509CertificateValidations, X509CheckCertificateRevocationOptions, X509CreateCertificateChainOptions, X509CreateCertificateOptions, X509CreateCertificateRevocationListOptions, X509CreateCertificateSigningRequestOptions, X509CrlDistributionPoint, X509CrlExtensionIdentifier, X509CrlUnavailableError, X509Error, X509ExtendedKeyUsage, X509ExtensionIdentifier, X509FetchCertificateRevocationListOptions, X509GetLeafCertificateOptions, X509IssuingDistributionPoint, X509KeyUsage, X509Module, X509ModuleConfig, X509ModuleConfigOptions, X509ParseCertificateOptions, X509ParseCertificateRevocationListOptions, X509ParseCertificateSigningRequestOptions, X509RevocationCheckMode, X509RevocationCheckOptions, X509RevocationCheckResult, X509RevocationReason, X509RevocationService, X509Service, X509ValidateCertificateChainOptions, X509ValidationError, X509ValidationResult, X509VerificationContext, X509VerificationTrustedCertificates, X509VerificationTypeCredential, X509VerificationTypeMdocReaderAuth, X509VerificationTypeOauth2ClientAttestation, X509VerificationTypeOauth2SecuredAuthorizationRequest, X509VerificationTypeOpenId4VciCredentialIssuerMetadata, X509VerificationTypeOpenId4VciKeyAttestation, convertLegacyTrustedCertificates, convertName, createAuthorityKeyIdentifierExtension, createBasicConstraintsExtension, createCrlDistributionPointsExtension, createCrlEntryCertificateIssuerExtension, createCrlNumberExtension, createDeltaCrlIndicatorExtension, createExtendedKeyUsagesExtension, createIssuerAlternativeNameExtension, createIssuingDistributionPointExtension, createKeyUsagesExtension, createSubjectAlternativeNameExtension, createSubjectKeyIdentifierExtension, extractX509CertificatesFromJwt, legacyTrustedCertificatesToTrustedIssuers, x509SignatureAlgorithmToJwa };
|
|
20
|
+
export { ALL_REVOCATION_REASONS_MASK, CertificateSigningRequest, CertificateSigningRequestOptions, EncodedX509Certificate, X509Api, X509Certificate, X509CertificateExtensionsOptions, X509CertificateIssuerAndSubjectOptions, X509CertificateOptions, X509CertificateRevocationList, X509CertificateRevocationListEntry, X509CertificateRevocationListEntryOptions, X509CertificateRevocationListEntryReason, X509CertificateRevocationListExtensionsOptions, X509CertificateSigningRequestExtensionsOptions, X509CertificateSingleValidationResult, X509CertificateValidations, X509CheckCertificateRevocationOptions, X509CreateCertificateChainOptions, X509CreateCertificateOptions, X509CreateCertificateRevocationListOptions, X509CreateCertificateSigningRequestOptions, X509CrlDistributionPoint, X509CrlExtensionIdentifier, X509CrlUnavailableError, X509Error, X509ExtendedKeyUsage, X509ExtensionIdentifier, X509FetchCertificateRevocationListOptions, X509GetLeafCertificateOptions, X509IssuingDistributionPoint, X509KeyUsage, X509Module, X509ModuleConfig, X509ModuleConfigOptions, X509ParseCertificateOptions, X509ParseCertificateRevocationListOptions, X509ParseCertificateSigningRequestOptions, X509RevocationCheckMode, X509RevocationCheckOptions, X509RevocationCheckOptionsWithDefaults, X509RevocationCheckResult, X509RevocationReason, X509RevocationService, X509Service, X509ValidateCertificateChainOptions, X509ValidationError, X509ValidationResult, X509VerificationContext, X509VerificationTrustedCertificates, X509VerificationTypeCredential, X509VerificationTypeMdocReaderAuth, X509VerificationTypeOauth2ClientAttestation, X509VerificationTypeOauth2SecuredAuthorizationRequest, X509VerificationTypeOpenId4VciCredentialIssuerMetadata, X509VerificationTypeOpenId4VciKeyAttestation, convertLegacyTrustedCertificates, convertName, createAuthorityKeyIdentifierExtension, createBasicConstraintsExtension, createCrlDistributionPointsExtension, createCrlEntryCertificateIssuerExtension, createCrlNumberExtension, createDeltaCrlIndicatorExtension, createExtendedKeyUsagesExtension, createIssuerAlternativeNameExtension, createIssuingDistributionPointExtension, createKeyUsagesExtension, createSubjectAlternativeNameExtension, createSubjectKeyIdentifierExtension, defaultX509RevocationCheckOptions, extractX509CertificatesFromJwt, legacyTrustedCertificatesToTrustedIssuers, withX509RevocationCheckDefaults, x509SignatureAlgorithmToJwa };
|
|
@@ -5,7 +5,7 @@ import { X509CrlExtensionIdentifier, X509ExtensionIdentifier, createAuthorityKey
|
|
|
5
5
|
import { x509SignatureAlgorithmToJwa } from "./utils/signatureAlgorithm.mjs";
|
|
6
6
|
import "./utils/index.mjs";
|
|
7
7
|
import { X509Certificate, X509ExtendedKeyUsage, X509KeyUsage } from "./X509Certificate.mjs";
|
|
8
|
-
import { X509RevocationCheckMode } from "./X509ValidationOptions.mjs";
|
|
8
|
+
import { X509RevocationCheckMode, defaultX509RevocationCheckOptions, withX509RevocationCheckDefaults } from "./X509ValidationOptions.mjs";
|
|
9
9
|
import { X509ModuleConfig } from "./X509ModuleConfig.mjs";
|
|
10
10
|
import { CertificateSigningRequest } from "./CertificateSigningRequest.mjs";
|
|
11
11
|
import { X509CertificateRevocationList, X509CertificateRevocationListEntryReason } from "./X509CertificateRevocationList.mjs";
|
|
@@ -16,4 +16,4 @@ import "./X509ServiceOptions.mjs";
|
|
|
16
16
|
import { extractX509CertificatesFromJwt } from "./extraction.mjs";
|
|
17
17
|
import { X509Api } from "./X509Api.mjs";
|
|
18
18
|
import { X509Module } from "./X509Module.mjs";
|
|
19
|
-
export { ALL_REVOCATION_REASONS_MASK, CertificateSigningRequest, X509Api, X509Certificate, X509CertificateRevocationList, X509CertificateRevocationListEntryReason, X509CrlExtensionIdentifier, X509CrlUnavailableError, X509Error, X509ExtendedKeyUsage, X509ExtensionIdentifier, X509KeyUsage, X509Module, X509ModuleConfig, X509RevocationCheckMode, X509RevocationReason, X509RevocationService, X509Service, X509ValidationError, convertLegacyTrustedCertificates, convertName, createAuthorityKeyIdentifierExtension, createBasicConstraintsExtension, createCrlDistributionPointsExtension, createCrlEntryCertificateIssuerExtension, createCrlNumberExtension, createDeltaCrlIndicatorExtension, createExtendedKeyUsagesExtension, createIssuerAlternativeNameExtension, createIssuingDistributionPointExtension, createKeyUsagesExtension, createSubjectAlternativeNameExtension, createSubjectKeyIdentifierExtension, extractX509CertificatesFromJwt, legacyTrustedCertificatesToTrustedIssuers, x509SignatureAlgorithmToJwa };
|
|
19
|
+
export { ALL_REVOCATION_REASONS_MASK, CertificateSigningRequest, X509Api, X509Certificate, X509CertificateRevocationList, X509CertificateRevocationListEntryReason, X509CrlExtensionIdentifier, X509CrlUnavailableError, X509Error, X509ExtendedKeyUsage, X509ExtensionIdentifier, X509KeyUsage, X509Module, X509ModuleConfig, X509RevocationCheckMode, X509RevocationReason, X509RevocationService, X509Service, X509ValidationError, convertLegacyTrustedCertificates, convertName, createAuthorityKeyIdentifierExtension, createBasicConstraintsExtension, createCrlDistributionPointsExtension, createCrlEntryCertificateIssuerExtension, createCrlNumberExtension, createDeltaCrlIndicatorExtension, createExtendedKeyUsagesExtension, createIssuerAlternativeNameExtension, createIssuingDistributionPointExtension, createKeyUsagesExtension, createSubjectAlternativeNameExtension, createSubjectKeyIdentifierExtension, defaultX509RevocationCheckOptions, extractX509CertificatesFromJwt, legacyTrustedCertificatesToTrustedIssuers, withX509RevocationCheckDefaults, x509SignatureAlgorithmToJwa };
|
|
@@ -4,16 +4,6 @@ import { CacheModuleConfig } from "../../cache/CacheModuleConfig.mjs";
|
|
|
4
4
|
import "../../cache/index.mjs";
|
|
5
5
|
import { isX509CrlSummary } from "./crlSummary.mjs";
|
|
6
6
|
//#region src/modules/x509/utils/crlFetcher.ts
|
|
7
|
-
/**
|
|
8
|
-
* Default maximum CRL size in bytes (10 MB)
|
|
9
|
-
* This is a reasonable limit as most CRLs are much smaller.
|
|
10
|
-
* Very large CRLs (>10 MB) are uncommon and may indicate issues.
|
|
11
|
-
*/
|
|
12
|
-
const DEFAULT_MAX_CRL_SIZE_BYTES = 10485760;
|
|
13
|
-
/**
|
|
14
|
-
* Default CRL fetch timeout in milliseconds (5 seconds)
|
|
15
|
-
*/
|
|
16
|
-
const DEFAULT_TIMEOUT_MILLISECONDS = 5e3;
|
|
17
7
|
function crlSummaryCacheKey(url) {
|
|
18
8
|
return `x509:crl-summary:v1:${url}`;
|
|
19
9
|
}
|
|
@@ -27,7 +17,7 @@ function crlSummaryCacheKey(url) {
|
|
|
27
17
|
* @throws {X509Error} If the fetch fails, returns a non-OK status, or exceeds the size limit
|
|
28
18
|
*/
|
|
29
19
|
async function fetchCrl(options) {
|
|
30
|
-
const { url, timeoutMs
|
|
20
|
+
const { url, timeoutMs, agentContext, maxSizeBytes } = options;
|
|
31
21
|
try {
|
|
32
22
|
const response = await fetchWithTimeout(agentContext.config.agentDependencies.fetch, url, {
|
|
33
23
|
timeoutMs,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"crlFetcher.mjs","names":[],"sources":["../../../../src/modules/x509/utils/crlFetcher.ts"],"sourcesContent":["import type { AgentContext } from '../../../agent'\nimport { fetchWithTimeout } from '../../../utils/fetch'\nimport { CacheModuleConfig } from '../../cache'\nimport { X509Error } from '../X509Error'\nimport { isX509CrlSummary, type X509CrlSummary } from './crlSummary'\n\nexport interface CrlFetchOptions {\n url: string\n agentContext: AgentContext\n\n /**\n * Maximum CRL fetch time after which request will be aborted\n
|
|
1
|
+
{"version":3,"file":"crlFetcher.mjs","names":[],"sources":["../../../../src/modules/x509/utils/crlFetcher.ts"],"sourcesContent":["import type { AgentContext } from '../../../agent'\nimport { fetchWithTimeout } from '../../../utils/fetch'\nimport { CacheModuleConfig } from '../../cache'\nimport { X509Error } from '../X509Error'\nimport { isX509CrlSummary, type X509CrlSummary } from './crlSummary'\n\nexport interface CrlFetchOptions {\n url: string\n agentContext: AgentContext\n\n /**\n * Maximum CRL fetch time after which request will be aborted\n */\n timeoutMs: number\n\n /**\n * Maximum size in bytes for CRL downloads\n */\n maxSizeBytes: number\n}\n\n// Versioned so future summary shape changes can bump the key. Distinct from the legacy\n// `x509:crl:${url}` raw-bytes key, which this version no longer reads or writes but which older\n// Credo versions sharing the cache may still use.\nfunction crlSummaryCacheKey(url: string): string {\n return `x509:crl-summary:v1:${url}`\n}\n\n/**\n * Fetches a CRL from a given URL with size limit and timeout enforcement.\n *\n * This does NOT cache: caching is the responsibility of the caller, which should only cache a CRL\n * after it has been verified (see {@link setCachedCrlSummary}), so unverified or expired CRLs are\n * never persisted.\n *\n * @throws {X509Error} If the fetch fails, returns a non-OK status, or exceeds the size limit\n */\nexport async function fetchCrl(options: CrlFetchOptions): Promise<Uint8Array> {\n const { url, timeoutMs, agentContext, maxSizeBytes } = options\n\n try {\n const response = await fetchWithTimeout(agentContext.config.agentDependencies.fetch, url, {\n timeoutMs,\n maxResponseBytes: maxSizeBytes,\n })\n\n if (!response.ok) {\n throw new X509Error(`Failed to fetch CRL from ${url}: HTTP ${response.status} ${response.statusText}`)\n }\n\n return new Uint8Array(await response.arrayBuffer())\n } catch (error) {\n if (error instanceof X509Error) {\n throw error\n }\n throw new X509Error(`Failed to fetch CRL from ${url}: ${error instanceof Error ? error.message : String(error)}`, {\n cause: error instanceof Error ? error : undefined,\n })\n }\n}\n\n/**\n * Return the cached verified CRL summary for a URL, or `null` if not cached. Values that do not\n * match the expected summary shape are treated as a cache miss, never as an error.\n *\n * Summaries are cached in the global scope: CRLs are public data, shared across all agent contexts.\n */\nexport async function getCachedCrlSummary(agentContext: AgentContext, url: string): Promise<X509CrlSummary | null> {\n const cache = agentContext.resolve(CacheModuleConfig).cache\n const cached = await cache.get<unknown>(agentContext, crlSummaryCacheKey(url), { scope: 'global' }).catch(() => null)\n return isX509CrlSummary(cached) ? cached : null\n}\n\n/**\n * Cache the summary of a verified CRL for a URL.\n *\n * Callers must only cache summaries derived from CRLs that have passed verification, and should\n * derive `expiresInSeconds` from the CRL's `nextUpdate` so an expired CRL is never served from the\n * cache.\n *\n * Summaries are cached in the global scope: CRLs are public data, shared across all agent contexts.\n */\nexport async function setCachedCrlSummary(\n agentContext: AgentContext,\n url: string,\n summary: X509CrlSummary,\n expiresInSeconds: number\n): Promise<void> {\n if (expiresInSeconds <= 0) return\n const cache = agentContext.resolve(CacheModuleConfig).cache\n await cache\n .set(agentContext, crlSummaryCacheKey(url), summary, expiresInSeconds, { scope: 'global' })\n .catch(() => null)\n}\n"],"mappings":";;;;;;AAwBA,SAAS,mBAAmB,KAAqB;CAC/C,OAAO,uBAAuB;AAChC;;;;;;;;;;AAWA,eAAsB,SAAS,SAA+C;CAC5E,MAAM,EAAE,KAAK,WAAW,cAAc,iBAAiB;CAEvD,IAAI;EACF,MAAM,WAAW,MAAM,iBAAiB,aAAa,OAAO,kBAAkB,OAAO,KAAK;GACxF;GACA,kBAAkB;EACpB,CAAC;EAED,IAAI,CAAC,SAAS,IACZ,MAAM,IAAI,UAAU,4BAA4B,IAAI,SAAS,SAAS,OAAO,GAAG,SAAS,YAAY;EAGvG,OAAO,IAAI,WAAW,MAAM,SAAS,YAAY,CAAC;CACpD,SAAS,OAAO;EACd,IAAI,iBAAiB,WACnB,MAAM;EAER,MAAM,IAAI,UAAU,4BAA4B,IAAI,IAAI,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,KAAK,EAChH,OAAO,iBAAiB,QAAQ,QAAQ,KAAA,EAC1C,CAAC;CACH;AACF;;;;;;;AAQA,eAAsB,oBAAoB,cAA4B,KAA6C;CAEjH,MAAM,SAAS,MADD,aAAa,QAAQ,iBAAiB,CAAC,CAAC,MAC3B,IAAa,cAAc,mBAAmB,GAAG,GAAG,EAAE,OAAO,SAAS,CAAC,CAAC,CAAC,YAAY,IAAI;CACpH,OAAO,iBAAiB,MAAM,IAAI,SAAS;AAC7C;;;;;;;;;;AAWA,eAAsB,oBACpB,cACA,KACA,SACA,kBACe;CACf,IAAI,oBAAoB,GAAG;CAE3B,MADc,aAAa,QAAQ,iBAAiB,CAAC,CAAC,MAEnD,IAAI,cAAc,mBAAmB,GAAG,GAAG,SAAS,kBAAkB,EAAE,OAAO,SAAS,CAAC,CAAC,CAC1F,YAAY,IAAI;AACrB"}
|