{"record":{"id":"0fd17b686bab905f","repo":"aaif-goose/goose","slug":"failed-to-parse-pem-key","errorCode":null,"errorMessage":"Failed to parse PEM key: {}","messagePattern":"Failed to parse PEM key: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/goose-providers/src/api_client.rs","lineNumber":165,"sourceCode":"///   Generated by default with `openssl genrsa`. Very common in older setups, tutorials,\n///   and CA-issued key files.\n/// - **SEC1** (`-----BEGIN EC PRIVATE KEY-----`): Legacy EC-specific format.\n///   Generated by default with `openssl ecparam -genkey`. Common with EC certificates.\n/// - **PKCS#8** (`-----BEGIN PRIVATE KEY-----`): Generic, algorithm-agnostic wrapper.\n///   This is the only format native-tls accepts.\n///\n/// Without this conversion, users with legacy-format keys (Kubernetes secrets, corporate\n/// PKI, etc.) would get a cryptic \"Failed to create identity\" error and need to manually\n/// run `openssl pkey -in key.pem -out key-pkcs8.pem`.\n///\n/// Note: the rustls code path (`Identity::from_pem`) accepts all formats natively,\n/// so this conversion is only needed for native-tls.\n#[cfg(feature = \"native-tls\")]\nfn convert_key_to_pkcs8_pem(key_pem_str: &str) -> Result<String> {\n    use pkcs8::der::{Decode, Encode};\n\n    let parsed =\n        pem::parse(key_pem_str).map_err(|e| anyhow::anyhow!(\"Failed to parse PEM key: {}\", e))?;\n\n    match parsed.tag() {\n        \"PRIVATE KEY\" => Ok(key_pem_str.to_string()),\n        \"RSA PRIVATE KEY\" => {\n            let info = pkcs8::PrivateKeyInfo::new(pkcs1::ALGORITHM_ID, parsed.contents());\n            let der_bytes = info\n                .to_der()\n                .map_err(|e| anyhow::anyhow!(\"Failed to encode PKCS#8: {}\", e))?;\n            Ok(pem::encode(&pem::Pem::new(\"PRIVATE KEY\", der_bytes)))\n        }\n        \"EC PRIVATE KEY\" => {\n            let ec_key = sec1::EcPrivateKey::from_der(parsed.contents())\n                .map_err(|e| anyhow::anyhow!(\"Failed to parse EC key: {}\", e))?;\n            let curve_oid = ec_key\n                .parameters\n                .and_then(|p| p.named_curve())\n                .ok_or_else(|| {\n                    anyhow::anyhow!(","sourceCodeStart":147,"sourceCodeEnd":183,"githubUrl":"https://github.com/aaif-goose/goose/blob/3810898a7447ec3299be72e223d3570a7aabf0ab/crates/goose-providers/src/api_client.rs#L147-L183","documentation":"On the native-tls build, convert_key_to_pkcs8_pem re-encodes legacy key formats (PKCS#1 RSA, SEC1 EC) into PKCS#8. First it runs pem::parse on the key text; if the file is not a valid PEM document at all — binary DER, encrypted/garbled content, missing armor — parsing fails with 'Failed to parse PEM key: {err}'. This is a key-format problem, not a file-read problem.","triggerScenarios":"client_identity key_path contains DER-encoded key bytes (no -----BEGIN----- armor), a PKCS#12 (.p12/.pfx) file, an encrypted PEM, or a corrupted download. Only compiled with the native-tls feature; fires at client build time after the key file was read as text.","commonSituations":"Kubernetes secrets or corporate PKI handing out DER keys; users pointing key_path at a .pfx bundle; Windows-exported keys in PKCS#12; truncated file transfers.","solutions":["Check the first line of the key file: it must read -----BEGIN ... PRIVATE KEY-----","Convert DER to PEM: openssl pkey -inform der -in key.der -out key.pem","Extract a key from PKCS#12 instead: openssl pkcs12 -in bundle.pfx -nocerts -nodes -out key.pem","Remove passphrase protection: openssl pkey -in key.enc -passin pass:... -out key.pem"],"exampleFix":"# before\nclient_identity:\n  key_path: /etc/goose/tls/client-key.der   # binary DER\n\n# after (shell)\n$ openssl pkey -inform der -in /etc/goose/tls/client-key.der -out /etc/goose/tls/client-key.pem\n# key_path: /etc/goose/tls/client-key.pem  (text PEM with armor)","handlingStrategy":"validation","validationCode":"let key_text = std::fs::read_to_string(&key_path)?;\nanyhow::ensure!(\n    key_text.contains(\"-----BEGIN\") && key_text.contains(\"PRIVATE KEY-----\"),\n    \"key file is not PEM armor; convert DER: openssl pkey -inform der -in {key_path} -out key.pem\"\n);","typeGuard":"fn looks_like_pem_key(text: &str) -> bool {\n    text.contains(\"-----BEGIN\") && text.contains(\"PRIVATE KEY-----\")\n}","tryCatchPattern":null,"preventionTips":["Never point key_path at .pfx/.p12 or DER files — convert first with openssl","Extract keys from PKCS#12 with openssl pkcs12 -nocerts -nodes","After any transfer, verify: openssl pkey -in key.pem -noout must succeed","Keep armored PEM files out of editors that re-wrap long lines"],"tags":["rust","tls","native-tls","pem","private-key","pkcs8"],"backgroundTag":null,"analyzedSha":"3810898a7447ec3299be72e223d3570a7aabf0ab","analyzedAt":"2026-08-16T10:14:26.282Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}