oven-sh/bun · error · CertError
INVALID_CA
INVALID_CA
Error message
INVALID_CA
What it means
CertError::INVALID_CA maps BoringSSL X509 verify code 24 (X509_V_ERR_INVALID_CA) via get_cert_error_from_no() in src/http/lib.rs:1543. A certificate used as a CA in the path is not actually a CA: its Basic Constraints lack CA:TRUE, or its key type/usage disallows acting as one. JS error.code is INVALID_CA with message 'invalid CA certificate' (FetchTasklet.rs:1440).
Source
Thrown at src/http/error.rs:169
#[error("ERROR_IN_CRL_LAST_UPDATE_FIELD")]
ERROR_IN_CRL_LAST_UPDATE_FIELD,
#[error("ERROR_IN_CRL_NEXT_UPDATE_FIELD")]
ERROR_IN_CRL_NEXT_UPDATE_FIELD,
#[error("OUT_OF_MEM")]
OUT_OF_MEM,
#[error("DEPTH_ZERO_SELF_SIGNED_CERT")]
DEPTH_ZERO_SELF_SIGNED_CERT,
#[error("SELF_SIGNED_CERT_IN_CHAIN")]
SELF_SIGNED_CERT_IN_CHAIN,
#[error("UNABLE_TO_GET_ISSUER_CERT_LOCALLY")]
UNABLE_TO_GET_ISSUER_CERT_LOCALLY,
#[error("UNABLE_TO_VERIFY_LEAF_SIGNATURE")]
UNABLE_TO_VERIFY_LEAF_SIGNATURE,
#[error("CERT_CHAIN_TOO_LONG")]
CERT_CHAIN_TOO_LONG,
#[error("CERT_REVOKED")]
CERT_REVOKED,
#[error("INVALID_CA")]
INVALID_CA,
#[error("PATH_LENGTH_EXCEEDED")]
PATH_LENGTH_EXCEEDED,
#[error("INVALID_PURPOSE")]
INVALID_PURPOSE,
#[error("CERT_UNTRUSTED")]
CERT_UNTRUSTED,
#[error("CERT_REJECTED")]
CERT_REJECTED,
#[error("SUBJECT_ISSUER_MISMATCH")]
SUBJECT_ISSUER_MISMATCH,
#[error("AKID_SKID_MISMATCH")]
AKID_SKID_MISMATCH,
#[error("AKID_ISSUER_SERIAL_MISMATCH")]
AKID_ISSUER_SERIAL_MISMATCH,
#[error("KEYUSAGE_NO_CERTSIGN")]
KEYUSAGE_NO_CERTSIGN,
#[error("UNABLE_TO_GET_CRL_ISSUER")]View on GitHub (pinned to 8c5296ac45)
Solutions
- Regenerate the CA with explicit CA:TRUE (see exampleFix) and reissue the leaf from it
- Check the intermediate: openssl x509 -in int.pem -text -noout | grep -A2 'Basic Constraints' must show CA:TRUE
- Serve/anchor the correct intermediate that actually has CA:TRUE
- Pass the real CA (the one that signed the leaf) in tls: { ca }, not the leaf itself
Example fix
# before: leaf-style self-signed cert used as CA openssl req -x509 -newkey rsa:2048 -keyout k.pem -out c.pem -days 30 # after: proper CA with Basic Constraints openssl req -x509 -newkey rsa:2048 -keyout ca-key.pem -out ca.pem -days 3650 \ -addext 'basicConstraints=critical,CA:TRUE' \ -addext 'keyUsage=critical,keyCertSign,cRLSign'
Defensive patterns
Strategy: validation
Validate before calling
import { X509Certificate } from "node:crypto";
const ca = new X509Certificate(await Bun.file("ca.pem").text());
if (!ca.ca) throw new Error("ca.pem lacks BasicConstraints CA:TRUE — regenerate it before using as tls.ca");
await fetch(url, { tls: { ca: ca.raw.toString() } }); Type guard
function isInvalidCa(e: unknown): e is Error & { code: "INVALID_CA" } {
return e instanceof Error && (e as any).code === "INVALID_CA";
} Try / catch
try { await fetch(url, { tls: { ca } }); } catch (e) { if (isInvalidCa(e)) { failFast("trust anchor is not a real CA — fix ca.pem generation"); } throw e; } Prevention
- Generate test CAs with basicConstraints=critical,CA:TRUE and keyUsage=keyCertSign,cRLSign
- Programmatically assert X509Certificate#ca === true before installing anchors
- Keep a canonical CA-creation script instead of ad-hoc openssl req -x509 calls
When it happens
Trigger: A self-signed leaf minted with openssl req -x509 WITHOUT basicConstraints=CA:TRUE is passed via tls: { ca } or installed as a root; an intermediate in the served chain lacks CA:TRUE; a server cert being used to sign another cert.
Common situations: Dev/test self-signed certs generated with defaults used as trust anchors (very common in docker-compose local stacks), mkcert-less local CA attempts, private PKI where the intermediate template omitted Basic Constraints.
Related errors
- UNABLE_TO_VERIFY_LEAF_SIGNATURE
- CERT_CHAIN_TOO_LONG
- PATH_LENGTH_EXCEEDED
- INVALID_PURPOSE
- SUBJECT_ISSUER_MISMATCH
AI-assisted analysis of oven-sh/bun@8c5296ac45 (2026-08-16).
Data as JSON: /api/errors/d0153c649d9b745e.
Report an issue: GitHub.