{"record":{"id":"4a5df1eefece83dd","repo":"aaif-goose/goose","slug":"failed-to-parse-ec-key","errorCode":null,"errorMessage":"Failed to parse EC key: {}","messagePattern":"Failed to parse EC key: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/goose-providers/src/api_client.rs","lineNumber":178,"sourceCode":"#[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!(\n                        \"EC key missing curve parameters. Convert to PKCS#8: \\\n                         openssl pkey -in key.pem -out key-pkcs8.pem\"\n                    )\n                })?;\n            let algorithm = pkcs8::AlgorithmIdentifierRef {\n                oid: sec1::ALGORITHM_OID,\n                parameters: Some((&curve_oid).into()),\n            };\n            let info = pkcs8::PrivateKeyInfo::new(algorithm, 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)))","sourceCodeStart":160,"sourceCodeEnd":196,"githubUrl":"https://github.com/aaif-goose/goose/blob/3810898a7447ec3299be72e223d3570a7aabf0ab/crates/goose-providers/src/api_client.rs#L160-L196","documentation":"Thrown by goose's native-tls client-identity loader. When TlsConfig carries a client cert/key pair and goose is built with the native-tls feature, convert_key_to_pkcs8_pem normalizes the private key to PKCS#8 because reqwest's native-tls Identity::from_pkcs8_pem only accepts that format. This error means the PEM tag was 'EC PRIVATE KEY' (SEC1) but sec1::EcPrivateKey::from_der could not decode the DER body, so the key bytes are malformed, truncated, or not actually an EC key.","triggerScenarios":"Calling TlsConfig::with_client_cert_and_key(cert, key) with native-tls enabled where the key file's PEM header is '-----BEGIN EC PRIVATE KEY-----' and the DER payload fails SEC1 parsing: base64 corrupted by copy/paste, file truncated, wrong file (e.g. a CSR) renamed to .pem, or hand-edited key text.","commonSituations":"Legacy SEC1 keys generated with 'openssl ecparam -genkey' and then mangled by copy/paste (lost lines, joined base64), secrets from Kubernetes/corporate PKI exported through JSON wrappers that strip trailing '=' padding, or a passphrase-encrypted body under a plain EC header.","solutions":["Regenerate or re-export the key cleanly: openssl ec -in key.pem -check -out checked.pem; if -check fails the file itself is corrupt","Convert the key to PKCS#8, which every code path accepts: openssl pkey -in key.pem -out key-pkcs8.pem, then point TlsConfig at key-pkcs8.pem","Confirm the file really is an unencrypted EC private key: openssl pkey -in key.pem -noout -text","If the key came through a secret store, re-download the original bytes instead of copying text from a terminal/browser"],"exampleFix":"# before (broken key bytes under SEC1 header)\n-----BEGIN EC PRIVATE KEY-----\nMHQCAQEE...(truncated/corrupted base64)\n-----END EC PRIVATE KEY-----\n\n# after: convert to PKCS#8 and use that path\nopenssl pkey -in key.pem -out key-pkcs8.pem\n\n# TlsConfig::new().with_client_cert_and_key(cert.pem.into(), \"key-pkcs8.pem\".into())","handlingStrategy":"validation","validationCode":"fn check_ec_key_parses(path: &str) -> anyhow::Result<()> {\n    let pem_text = std::fs::read_to_string(path)?;\n    let parsed = pem::parse(&pem_text)\n        .map_err(|e| anyhow::anyhow!(\"bad PEM: {e}\"))?;\n    if parsed.tag() != \"EC PRIVATE KEY\" { return Ok(()); }\n    sec1::EcPrivateKey::from_der(parsed.contents())\n        .map_err(|e| anyhow::anyhow!(\"SEC1 body will fail: {e}\"))?;\n    Ok(())\n}","typeGuard":null,"tryCatchPattern":"// At client-construction time, convert once and report the file, not the DER internals:\nlet tls = match TlsConfig::new().with_client_cert_and_key(cert, key) { /* build later */ };\nmatch client_result {\n    Err(e) if e.to_string().contains(\"Failed to parse EC key\") =>\n        eprintln!(\"{key_path} is not a valid SEC1 EC key; run: openssl pkey -in {key_path} -out {key_path}.pkcs8\"),\n    other => other?,\n}","preventionTips":["Generate mTLS keys directly as PKCS#8: openssl genpkey (not genrsa/ecparam) or openssl pkey -out key-pkcs8.pem","Never hand-copy PEM bodies between terminals or chat tools; transfer files or use secret managers byte-for-byte","Add a CI step that runs openssl pkey -in <key> -noout on every cert/key pair before deployment"],"tags":["tls","crypto","pem","native-tls","mtls"],"backgroundTag":null,"analyzedSha":"3810898a7447ec3299be72e223d3570a7aabf0ab","analyzedAt":"2026-08-16T10:14:26.282Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}