oven-sh/bun · error · CertError

UNABLE_TO_DECRYPT_CERT_SIGNATURE

Error message

UNABLE_TO_DECRYPT_CERT_SIGNATURE

What it means

X509 verify result 4 (X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE): the signature on a certificate could not be decrypted with the public key of its supposed issuer — the issuer certificate found does not actually sign this cert (or the cert bytes are corrupt). Mapped by get_cert_error_from_no (src/http/lib.rs:1523) to CertError::UNABLE_TO_DECRYPT_CERT_SIGNATURE, message "unable to decrypt certificate's signature" (FetchTasklet.rs:1382).

Source

Thrown at src/http/error.rs:129

    Zlib(bun_zlib::ZlibError),
    #[error(transparent)]
    Brotli(bun_brotli::Error),
    #[error(transparent)]
    Zstd(bun_zstd::ZstdError),
    #[error(transparent)]
    Picohttp(bun_picohttp::ParseResponseError),
}

#[allow(non_camel_case_types)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error, strum::IntoStaticStr)]
pub enum CertError {
    #[error("OK")]
    OK,
    #[error("UNABLE_TO_GET_ISSUER_CERT")]
    UNABLE_TO_GET_ISSUER_CERT,
    #[error("UNABLE_TO_GET_CRL")]
    UNABLE_TO_GET_CRL,
    #[error("UNABLE_TO_DECRYPT_CERT_SIGNATURE")]
    UNABLE_TO_DECRYPT_CERT_SIGNATURE,
    #[error("UNABLE_TO_DECRYPT_CRL_SIGNATURE")]
    UNABLE_TO_DECRYPT_CRL_SIGNATURE,
    #[error("UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY")]
    UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY,
    #[error("CERT_SIGNATURE_FAILURE")]
    CERT_SIGNATURE_FAILURE,
    #[error("CRL_SIGNATURE_FAILURE")]
    CRL_SIGNATURE_FAILURE,
    #[error("CERT_NOT_YET_VALID")]
    CERT_NOT_YET_VALID,
    #[error("CERT_HAS_EXPIRED")]
    CERT_HAS_EXPIRED,
    #[error("CRL_NOT_YET_VALID")]
    CRL_NOT_YET_VALID,
    #[error("CRL_HAS_EXPIRED")]
    CRL_HAS_EXPIRED,
    #[error("ERROR_IN_CERT_NOT_BEFORE_FIELD")]

View on GitHub (pinned to 8c5296ac45)

Solutions

  1. Regenerate/re-download the correct full chain from the CA and serve leaf + correct intermediates in order
  2. Verify the file integrity: openssl x509 -in cert.pem -noout -text parses cleanly, and openssl verify -CAfile chain.pem cert.pem succeeds
  3. If a stale cross-signed intermediate is cached client-side, clear it / pass the current intermediate via tls.ca
  4. For internal PKIs, confirm the leaf was signed by the intermediate actually being served

Example fix

# diagnose
openssl s_client -connect example.com:443 -showcerts
openssl verify -CAfile <(cat intermediate.pem root.pem) leaf.pem
Defensive patterns

Strategy: try-catch

Type guard

function isCertErrorCode(e: unknown, code = "UNABLE_TO_DECRYPT_CERT_SIGNATURE"): e is Error & { code: string } {
  return e instanceof Error && (e as any).code === code;
}

Try / catch

try {
  await fetch(url);
} catch (e) {
  if (isCertErrorCode(e, "UNABLE_TO_DECRYPT_CERT_SIGNATURE")) {
    // wrong intermediate paired with leaf — fix on the server; not transient, don't blind-retry
    throw new Error("Server presents a mismatched certificate chain");
  }
  throw e;
}

Prevention

When it happens

Trigger: fetch/https/Bun.connect TLS handshake where the chain the client assembles pairs the leaf with the wrong intermediate (cross-signed vs. modern issuer mixups), or the served certificate is truncated/corrupted.

Common situations: Servers serving an outdated cross-signed intermediate after a CA rotation (e.g. Let's Encrypt 2021 R3/E1 transitions), wrong-chain ordering in a PEM bundle, or a cert file corrupted during copy/paste (missing BEGIN/END lines).

Related errors


AI-assisted analysis of oven-sh/bun@8c5296ac45 (2026-08-16). Data as JSON: /api/errors/d909ea81e124a0c7. Report an issue: GitHub.