{"record":{"id":"1a762c4ca112fde3","repo":"aaif-goose/goose","slug":"failed-to-read-client-certificate","errorCode":null,"errorMessage":"Failed to read client certificate: {}","messagePattern":"Failed to read client certificate: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/goose-providers/src/api_client.rs","lineNumber":92,"sourceCode":"            key_path,\n        });\n        self\n    }\n\n    pub fn with_ca_cert(mut self, path: PathBuf) -> Self {\n        self.ca_cert_path = Some(path);\n        self\n    }\n\n    pub fn is_configured(&self) -> bool {\n        self.client_identity.is_some() || self.ca_cert_path.is_some()\n    }\n\n    #[cfg(any(feature = \"rustls-tls\", feature = \"native-tls\"))]\n    fn load_identity(&self) -> Result<Option<Identity>> {\n        if let Some(cert_key_pair) = &self.client_identity {\n            let cert_pem = read_to_string(&cert_key_pair.cert_path)\n                .map_err(|e| anyhow::anyhow!(\"Failed to read client certificate: {}\", e))?;\n            let key_pem = read_to_string(&cert_key_pair.key_path)\n                .map_err(|e| anyhow::anyhow!(\"Failed to read client private key: {}\", e))?;\n\n            #[cfg(not(feature = \"native-tls\"))]\n            let identity = {\n                let combined_pem = format!(\"{}\\n{}\", cert_pem, key_pem);\n                Identity::from_pem(combined_pem.as_bytes()).map_err(|e| {\n                    anyhow::anyhow!(\"Failed to create identity from cert and key: {}\", e)\n                })?\n            };\n\n            #[cfg(feature = \"native-tls\")]\n            let identity = {\n                let pkcs8_key_pem = convert_key_to_pkcs8_pem(&key_pem)?;\n                Identity::from_pkcs8_pem(cert_pem.as_bytes(), pkcs8_key_pem.as_bytes()).map_err(\n                    |e| anyhow::anyhow!(\"Failed to create identity from cert and key: {}\", e),\n                )?\n            };","sourceCodeStart":74,"sourceCodeEnd":110,"githubUrl":"https://github.com/aaif-goose/goose/blob/3810898a7447ec3299be72e223d3570a7aabf0ab/crates/goose-providers/src/api_client.rs#L74-L110","documentation":"Part of goose's mTLS support: when a provider config supplies a client certificate pair, TlsConfig::load_identity reads the certificate PEM from cert_path with std::fs::read_to_string. Any I/O failure — missing file, wrong path, permission denied, non-UTF8 bytes — surfaces as 'Failed to read client certificate: {io_error}'.","triggerScenarios":"Configuring a provider with a client cert (client_identity) where cert_path does not exist in the filesystem, is unreadable by the goose process (ownership/mode), is a directory, or contains binary DER instead of PEM text. Fires when the TLS config is first loaded, i.e. when building the HTTP client for that provider.","commonSituations":"Relative paths resolved from a different working directory (cron, systemd, desktop app); cert mounted at a different path in a container; cert generated as DER (.crt/.der) instead of PEM; file owned by root with mode 600 while goose runs as a user.","solutions":["Verify the path exists and is readable by the goose process: ls -l /path/to/cert.pem","Use an absolute path in the provider config to avoid working-directory issues","If the file is DER, convert to PEM: openssl x509 -inform der -in cert.der -out cert.pem","Fix permissions so the running user can read it (chmod 640 + right group)"],"exampleFix":"# before\nclient_identity:\n  cert_path: ./certs/client.crt   # relative, or DER binary\n  key_path: ./certs/client.key\n\n# after\nclient_identity:\n  cert_path: /etc/goose/tls/client-cert.pem   # absolute, PEM format\n  key_path: /etc/goose/tls/client-key.pem","handlingStrategy":"validation","validationCode":"fn readable_utf8_file(path: &std::path::Path) -> bool {\n    std::fs::metadata(path).map(|m| m.is_file()).unwrap_or(false)\n        && std::fs::read_to_string(path).is_ok()\n}\n\nif !readable_utf8_file(std::path::Path::new(&cert_path)) {\n    anyhow::bail!(\"client cert missing/unreadable: {cert_path}\");\n}","typeGuard":"fn is_pem_text(path: &std::path::Path) -> bool {\n    std::fs::read_to_string(path)\n        .map(|s| s.starts_with(\"-----BEGIN\"))\n        .unwrap_or(false)\n}","tryCatchPattern":null,"preventionTips":["Use absolute paths for all TLS material in provider configs","Verify files from the goose process context: sudo -u goose cat /path/cert.pem","Keep PEM files as UTF-8 text with proper armor; convert DER with openssl x509","Mount secrets read-only at stable paths in containers"],"tags":["rust","tls","mtls","client-certificate","filesystem","config"],"backgroundTag":null,"analyzedSha":"3810898a7447ec3299be72e223d3570a7aabf0ab","analyzedAt":"2026-08-16T10:14:26.282Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}