{"record":{"id":"f44e313c6c73dfc8","repo":"aaif-goose/goose","slug":"failed-to-create-identity-from-cert-and-key","errorCode":null,"errorMessage":"Failed to create identity from cert and key: {}","messagePattern":"Failed to create identity from cert and key: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/goose-providers/src/api_client.rs","lineNumber":100,"sourceCode":"    }\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            };\n\n            Ok(Some(identity))\n        } else {\n            Ok(None)\n        }\n    }\n\n    #[cfg(any(feature = \"rustls-tls\", feature = \"native-tls\"))]","sourceCodeStart":82,"sourceCodeEnd":118,"githubUrl":"https://github.com/aaif-goose/goose/blob/3810898a7447ec3299be72e223d3570a7aabf0ab/crates/goose-providers/src/api_client.rs#L82-L118","documentation":"On the rustls path (default when the native-tls feature is off), load_identity concatenates the cert PEM and key PEM and calls Identity::from_pem. rustls only accepts PKCS#8 keys (a 'PRIVATE KEY' PEM block); if the key is PKCS#1 ('RSA PRIVATE KEY'), SEC1 ('EC PRIVATE KEY'), encrypted, or the cert/key pair is malformed or mismatched, parsing fails with 'Failed to create identity from cert and key: {err}'.","triggerScenarios":"Client mTLS configured with a legacy key format (openssl genrsa default PKCS#1, EC keys in SEC1 form), an encrypted/private password-protected key, or cert and key that do not match (cert's public key differs from key). Fires at client construction, after both files were read successfully.","commonSituations":"Corporate PKI issuing PKCS#1 keys from Kubernetes secrets; keys from older openssl versions; copy-pasted PEMs missing trailing newlines or with mangled armor; pairing a renewed certificate with an old key.","solutions":["Convert the key to PKCS#8 PEM: openssl pkey -in key.pem -out key-pkcs8.pem (works for RSA and EC inputs)","Verify cert and key match: compare openssl x509 -noout -modulus -in cert.pem vs openssl rsa -noout -modulus -in key.pem (or use openssl x509 -checkend and pkey checks)","If the key is passphrase-protected, decrypt it first: openssl pkey -in key.enc -passin file:pass.txt -out key-pkcs8.pem","Re-issue a matching cert/key pair if they come from different generations"],"exampleFix":"# before\nclient_identity:\n  cert_path: /etc/goose/tls/client-cert.pem\n  key_path: /etc/goose/tls/client-key.pem   # '-----BEGIN RSA PRIVATE KEY-----'\n\n# after (shell)\n$ openssl pkey -in /etc/goose/tls/client-key.pem -out /etc/goose/tls/client-key-pkcs8.pem\n# client_identity.key_path now points at client-key-pkcs8.pem ('-----BEGIN PRIVATE KEY-----')","handlingStrategy":"validation","validationCode":"// fail fast: rustls needs PKCS#8 keys\nlet key = std::fs::read_to_string(&key_path)?;\nif !key.contains(\"-----BEGIN PRIVATE KEY-----\") {\n    anyhow::bail!(\n        \"key is not PKCS#8; run: openssl pkey -in {key_path} -out {key_path}.pkcs8.pem\"\n    );\n}","typeGuard":"fn is_pkcs8_pem(key_pem: &str) -> bool {\n    key_pem.contains(\"-----BEGIN PRIVATE KEY-----\")\n}","tryCatchPattern":null,"preventionTips":["Standardize on PKCS#8 PEM keys for all goose client identities","Automate conversion: openssl pkey -in key.pem -out key-pkcs8.pem during secret provisioning","Verify pairing after every cert rotation (compare public key hashes of cert and key)","Never use passphrase-protected keys with rustls identities"],"tags":["rust","tls","mtls","rustls","pem","pkcs8"],"backgroundTag":null,"analyzedSha":"3810898a7447ec3299be72e223d3570a7aabf0ab","analyzedAt":"2026-08-16T10:14:26.282Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}