aaif-goose/goose · error

Failed to parse CA certificate bundle: {}

Error message

Failed to parse CA certificate bundle: {}

What it means

After reading the CA bundle, load_ca_certificates calls Certificate::from_pem_bundle on its bytes. If the file is valid text but not a PEM bundle (no BEGIN/END CERTIFICATE blocks, Base64 corrupted, or a lone private key), parsing fails with 'Failed to parse CA certificate bundle: {err}'. The read succeeded; the content is not a certificate bundle.

Source

Thrown at crates/goose-providers/src/api_client.rs:126

                    |e| anyhow::anyhow!("Failed to create identity from cert and key: {}", e),
                )?
            };

            Ok(Some(identity))
        } else {
            Ok(None)
        }
    }

    #[cfg(any(feature = "rustls-tls", feature = "native-tls"))]
    fn load_ca_certificates(&self) -> Result<Vec<Certificate>> {
        match &self.ca_cert_path {
            Some(ca_path) => {
                let ca_pem = read_to_string(ca_path)
                    .map_err(|e| anyhow::anyhow!("Failed to read CA certificate: {}", e))?;

                let certs = Certificate::from_pem_bundle(ca_pem.as_bytes())
                    .map_err(|e| anyhow::anyhow!("Failed to parse CA certificate bundle: {}", e))?;

                Ok(certs)
            }
            None => Ok(Vec::new()),
        }
    }
}

impl Default for TlsConfig {
    fn default() -> Self {
        Self::new()
    }
}

/// Convert a PEM private key from any format (PKCS#1, SEC1, PKCS#8) to PKCS#8 PEM.
///
/// `reqwest::Identity::from_pkcs8_pem` (native-tls) only accepts PKCS#8
/// (`-----BEGIN PRIVATE KEY-----`), but private keys in the wild come in three formats:

View on GitHub (pinned to 3810898a74)

Solutions

  1. Inspect the file: it must contain one or more -----BEGIN CERTIFICATE----- ... -----END CERTIFICATE----- blocks
  2. Re-download the CA properly: curl -fsSL -o ca.pem https://proxy.example/ca.pem
  3. Convert DER to PEM: openssl x509 -inform der -in ca.crt -out ca.pem
  4. Ensure exactly one newline between concatenated PEM blocks

Example fix

# before
ca_cert_path: /etc/goose/tls/ca.pem   # actually DER bytes or an HTML error page

# after (shell)
$ file /etc/goose/tls/ca.pem                     # expect: PEM certificate
$ openssl x509 -inform der -in ca.der -out /etc/goose/tls/ca.pem
$ openssl verify -CAfile /etc/goose/tls/ca.pem server-cert.pem
Defensive patterns

Strategy: validation

Validate before calling

let pem = std::fs::read_to_string(&ca_cert_path)?;
let blocks = pem.matches("-----BEGIN CERTIFICATE-----").count();
anyhow::ensure!(blocks > 0, "file contains no PEM certificate blocks");

Type guard

fn is_pem_certificate_bundle(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: ca_cert_path points at a file that is not PEM: a DER binary renamed .pem, a concatenated chain with a broken Base64 body, a chain with Windows line endings or stray headers that break armor detection, or a JSON/HTML error page saved by mistake (e.g. a failed download).

Common situations: Downloading a CA via curl without following redirects and saving an error page; cert managers writing DER despite the extension; hand-concatenated bundles with missing newlines between blocks; expired tooling emitting malformed armor.

Understand the failure class

Related errors


AI-assisted analysis of aaif-goose/goose@3810898a74 (2026-08-16). Data as JSON: /api/errors/c72dd30c46f2ca20. Report an issue: GitHub.