{"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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"}