zeroclaw-labs/zeroclaw · error · anyhow::Error

amqp channel '{alias}': client_cert contains no certificates

Error message

amqp channel '{alias}': client_cert contains no certificates

What it means

The configured client_cert file was read successfully but contained zero PEM CERTIFICATE sections — rustls_pemfile::certs found nothing to import while building the PKCS#12 client identity in pem_to_pkcs12_der. This is a file-content problem detected before any network connection is made.

Source

Thrown at crates/zeroclaw-channels/src/amqp.rs:382

}

/// Ephemeral password protecting the in-memory PKCSidentity. The bundle is
/// built and consumed within a single connect call and never persisted, so the
/// password only has to round-trip through tcp-stream's PKCSreader.
const PKCS12_PASSWORD: &str = "zeroclaw-amqp";

/// Convert a PEM client certificate chain and private key into a PKCSDER
/// bundle suitable for tcp-stream's rustls client-auth path.
fn pem_to_pkcs12_der(cert_pem: &[u8], key_pem: &[u8], alias: &str) -> anyhow::Result<Vec<u8>> {
    use p12_keystore::{Certificate, KeyStore, KeyStoreEntry, PrivateKeyChain};

    let certs: Vec<Vec<u8>> = rustls_pemfile::certs(&mut &cert_pem[..])
        .collect::<Result<Vec<_>, _>>()?
        .into_iter()
        .map(|c| c.as_ref().to_vec())
        .collect();
    if certs.is_empty() {
        anyhow::bail!("amqp channel '{alias}': client_cert contains no certificates");
    }

    let key = rustls_pemfile::private_key(&mut &key_pem[..])?.ok_or_else(|| {
        anyhow::Error::msg(format!(
            "amqp channel '{alias}': client_key contains no private key"
        ))
    })?;

    let chain: Vec<Certificate> = certs
        .iter()
        .map(|der| Certificate::from_der(der))
        .collect::<Result<_, _>>()
        .map_err(|e| {
            anyhow::Error::msg(format!(
                "amqp channel '{alias}': invalid client certificate: {e}"
            ))
        })?;

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Inspect the file for a BEGIN CERTIFICATE block: openssl x509 -in client.pem -noout
  2. If cert and key were swapped, point client_cert at the certificate and client_key at the private key.
  3. Convert DER to PEM if needed: openssl x509 -inform der -in cert.der -out client.pem

Example fix

# before: client_cert points at the private key file (no CERTIFICATE blocks)
client_cert = "/etc/zeroclaw/tls/client.key"

# after
client_cert = "/etc/zeroclaw/tls/client.pem"  # leaf cert (+ chain) in PEM
client_key = "/etc/zeroclaw/tls/client.key"
Defensive patterns

Strategy: validation

Validate before calling

fn pem_contains_certs(path: &std::path::Path) -> anyhow::Result<bool> {
    let pem = std::fs::read(path)?;
    let n = rustls_pemfile::certs(&mut &pem[..]).count();
    Ok(n > 0)
}

Type guard

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

Prevention

When it happens

Trigger: The client_cert path points at a file with no CERTIFICATE blocks: the private key file (cert and key paths swapped), a CSR, an empty file, or a DER-encoded binary certificate instead of PEM.

Common situations: Cert/key path swaps in config; provisioning scripts writing the wrong artifact; CAs or tooling that deliver DER by default; truncated files from a partial secret sync.

Understand the failure class

Related errors


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