{"record":{"id":"239fec21a0c81293","repo":"aaif-goose/goose","slug":"unsupported-key-format-expected-pkcs-8-pkcs","errorCode":null,"errorMessage":"Unsupported key format '{}'. Expected PKCS#8, PKCS#1, or SEC1. Convert with: openssl pkey -in key.pem -out key-pkcs8.pem","messagePattern":"Unsupported key format '(.+?)'\\. Expected PKCS#8, PKCS#1, or SEC1\\. Convert with: openssl pkey -in key\\.pem -out key-pkcs8\\.pem","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/goose-providers/src/api_client.rs","lineNumber":198,"sourceCode":"                .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)))\n        }\n        tag => Err(anyhow::anyhow!(\n            \"Unsupported key format '{}'. Expected PKCS#8, PKCS#1, or SEC1. \\\n             Convert with: openssl pkey -in key.pem -out key-pkcs8.pem\",\n            tag\n        )),\n    }\n}\n\n#[async_trait]\npub trait AuthProvider: Send + Sync {\n    async fn get_auth_header(&self) -> Result<(String, String)>;\n\n    async fn refresh_credentials(&self) -> Result<()> {\n        anyhow::bail!(\"credential refresh not supported\")\n    }\n}\n\npub struct ApiResponse {\n    pub status: StatusCode,","sourceCodeStart":180,"sourceCodeEnd":216,"githubUrl":"https://github.com/aaif-goose/goose/blob/3810898a7447ec3299be72e223d3570a7aabf0ab/crates/goose-providers/src/api_client.rs#L180-L216","documentation":"convert_key_to_pkcs8_pem recognizes exactly three PEM tags: 'PRIVATE KEY' (PKCS#8), 'RSA PRIVATE KEY' (PKCS#1), and 'EC PRIVATE KEY' (SEC1). Any other leading tag hits this catch-all arm. Typical offenders: 'ENCRYPTED PRIVATE KEY' (passphrase-protected PKCS#8), 'OPENSSH PRIVATE KEY' (ed25519 keys from ssh-keygen), 'DSA PRIVATE KEY', or a certificate body passed as the key.","triggerScenarios":"Configuring TlsConfig::with_client_cert_and_key (native-tls build) with a key file that is passphrase-encrypted, in OpenSSH format, DSA, or actually the certificate/public half instead of the private key.","commonSituations":"Teams generate ed25519 keys with ssh-keygen by habit and hand them to mTLS config; security teams deliver passphrase-protected PKCS#8; someone pastes cert.pem into the key_path field. Note the rustls path (Identity::from_pem) is equally strict about encryption, so the fix is the same regardless of backend.","solutions":["Convert whatever you have to unencrypted PKCS#8: openssl pkey -in key.pem -out key-pkcs8.pem (use -passin for encrypted input)","For encrypted PKCS#8 specifically: openssl pkcs8 -topk8 -nocrypt -in key.pem -out key-pkcs8.pem","For OpenSSH ed25519 keys: openssl pkey -in id_ed25519 -out key-pkcs8.pem (recent OpenSSL reads OpenSSH format)","Double-check key_path really points at the private key ('openssl pkey -in <file> -noout' must succeed)"],"exampleFix":"# before\n-----BEGIN OPENSSH PRIVATE KEY-----\nb3BlbnNzaC1rZXktdjEAAAAA...\n# key_path = id_ed25519 -> Unsupported key format 'OPENSSH PRIVATE KEY'\n\n# after\nopenssl pkey -in id_ed25519 -out key-pkcs8.pem\n-----BEGIN PRIVATE KEY-----\nMIGHAgEAMBMGByqGSM49AgEG...\n-----END PRIVATE KEY-----","handlingStrategy":"validation","validationCode":"const SUPPORTED: [&str; 3] = [\"PRIVATE KEY\", \"RSA PRIVATE KEY\", \"EC PRIVATE KEY\"];\nfn pem_tag_ok(path: &str) -> anyhow::Result<()> {\n    let first = std::fs::read_to_string(path)?\n        .lines().find(|l| l.starts_with(\"-----BEGIN\"))\n        .unwrap_or_default().to_string();\n    let tag = first.trim_start_matches(\"-----BEGIN\").trim_end_matches(\"-----\").trim();\n    anyhow::ensure!(SUPPORTED.contains(&tag),\n        \"unsupported PEM tag '{tag}'; convert: openssl pkey -in {path} -out {path}.pkcs8\");\n    Ok(())\n}","typeGuard":null,"tryCatchPattern":"match build_identity(cert, key) {\n    Err(e) if e.to_string().contains(\"Unsupported key format\") =>\n        eprintln!(\"convert the key first: openssl pkey -in {key} -out {key}.pkcs8 (or -passin for encrypted keys)\"),\n    r => r?,\n}","preventionTips":["Ban ssh-keygen output and encrypted PKCS#8 from mTLS config; enforce unencrypted PKCS#8 via policy","Script key distribution to always end with openssl pkey -in k -out k.pkcs8 so the canonical form is what ships","Name files explicitly (server.key vs server.crt) and lint config for the key path pointing at a cert"],"tags":["tls","crypto","pem","ssh","encryption","native-tls"],"backgroundTag":null,"analyzedSha":"3810898a7447ec3299be72e223d3570a7aabf0ab","analyzedAt":"2026-08-16T10:14:26.282Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}