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
- Regenerate/re-download the correct full chain from the CA and serve leaf + correct intermediates in order
- Verify the file integrity: openssl x509 -in cert.pem -noout -text parses cleanly, and openssl verify -CAfile chain.pem cert.pem succeeds
- If a stale cross-signed intermediate is cached client-side, clear it / pass the current intermediate via tls.ca
- 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
- Automate chain assembly from the CA (download intermediates at issuance, serve in order)
- After every cert rotation, verify with openssl verify -CAfile chain leaf before deploying
- Never hand-stitch PEM bundles from different issuance eras
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
- UNABLE_TO_GET_ISSUER_CERT
- UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY
- CERT_SIGNATURE_FAILURE
- CERT_HAS_EXPIRED
- ERROR_IN_CERT_NOT_BEFORE_FIELD
AI-assisted analysis of oven-sh/bun@8c5296ac45 (2026-08-16).
Data as JSON: /api/errors/d909ea81e124a0c7.
Report an issue: GitHub.