zeroclaw-labs/zeroclaw · error

no certificates found in {path}

Error message

no certificates found in {path}

What it means

`load_certs` opens the file and parses PEM certificates with rustls-pemfile; separate contexts cover 'cannot open' and 'failed to parse'. This error is the third, distinct case: the file opened and parsed without error but contained zero `BEGIN CERTIFICATE` blocks. Typical causes are a private-key-only PEM, an empty file, or a non-PEM file passed where a certificate bundle was expected.

Source

Thrown at crates/zeroclaw-gateway/src/tls.rs:172

    ) -> std::result::Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
        self.inner.verify_tls13_signature(message, cert, dss)
    }

    fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
        self.inner.supported_verify_schemes()
    }
}

/// Load PEM-encoded certificates from a file.
fn load_certs(path: &str) -> Result<Vec<CertificateDer<'static>>> {
    let file = std::fs::File::open(path)
        .with_context(|| format!("cannot open certificate file: {path}"))?;
    let mut reader = std::io::BufReader::new(file);
    let certs: Vec<CertificateDer<'static>> = rustls_pemfile::certs(&mut reader)
        .collect::<std::result::Result<Vec<_>, _>>()
        .with_context(|| format!("failed to parse PEM certificates from {path}"))?;
    if certs.is_empty() {
        anyhow::bail!("no certificates found in {path}");
    }
    Ok(certs)
}

/// Load a PEM-encoded private key from a file.
fn load_private_key(path: &str) -> Result<PrivateKeyDer<'static>> {
    let file = std::fs::File::open(path)
        .with_context(|| format!("cannot open private key file: {path}"))?;
    let mut reader = std::io::BufReader::new(file);
    let key = rustls_pemfile::private_key(&mut reader)
        .with_context(|| format!("failed to parse private key from {path}"))?
        .ok_or_else(|| {
            ::zeroclaw_log::record!(
                ERROR,
                ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Fail)
                    .with_outcome(::zeroclaw_log::EventOutcome::Failure)
                    .with_attrs(::serde_json::json!({"path": path})),
                "TLS private key file contains no key"

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Inspect the file for `-----BEGIN CERTIFICATE-----` blocks; if absent, it is not a cert PEM
  2. Check for swapped cert/key paths in the TLS configuration
  3. Convert DER to PEM if needed: `openssl x509 -in cert.der -inform DER -out cert.pem`

Example fix

# before
cert_path = "config/server.key"  # key file passed as cert
cert_path = "config/server.key"
# after
cert_path = "config/server.crt"  # PEM containing BEGIN CERTIFICATE blocks
key_path = "config/server.key"
Defensive patterns

Strategy: validation

Validate before calling

fn pem_has_certificates(path: &std::path::Path) -> bool {
    std::fs::read_to_string(path)
        .map(|s| s.contains("-----BEGIN CERTIFICATE-----"))
        .unwrap_or(false)
}

assert!(pem_has_certificates(cert_path), "cert file contains no certificates");

Prevention

When it happens

Trigger: Passing the key file where the certificate is expected (swapped cert/key config values); an empty or truncated certificate file; a DER-encoded file that is not PEM.

Common situations: Swapping TLS cert and key paths in config; a certificate file overwritten with key material; downloading the chain in DER instead of PEM from the CA.

Understand the failure class

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/af029ba93f176d2b. Report an issue: GitHub.