aaif-goose/goose · error

Failed to parse EC key: {}

Error message

Failed to parse EC key: {}

What it means

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.

Source

Thrown at crates/goose-providers/src/api_client.rs:178

#[cfg(feature = "native-tls")]
fn convert_key_to_pkcs8_pem(key_pem_str: &str) -> Result<String> {
    use pkcs8::der::{Decode, Encode};

    let parsed =
        pem::parse(key_pem_str).map_err(|e| anyhow::anyhow!("Failed to parse PEM key: {}", e))?;

    match parsed.tag() {
        "PRIVATE KEY" => Ok(key_pem_str.to_string()),
        "RSA PRIVATE KEY" => {
            let info = pkcs8::PrivateKeyInfo::new(pkcs1::ALGORITHM_ID, parsed.contents());
            let der_bytes = info
                .to_der()
                .map_err(|e| anyhow::anyhow!("Failed to encode PKCS#8: {}", e))?;
            Ok(pem::encode(&pem::Pem::new("PRIVATE KEY", der_bytes)))
        }
        "EC PRIVATE KEY" => {
            let ec_key = sec1::EcPrivateKey::from_der(parsed.contents())
                .map_err(|e| anyhow::anyhow!("Failed to parse EC key: {}", e))?;
            let curve_oid = ec_key
                .parameters
                .and_then(|p| p.named_curve())
                .ok_or_else(|| {
                    anyhow::anyhow!(
                        "EC key missing curve parameters. Convert to PKCS#8: \
                         openssl pkey -in key.pem -out key-pkcs8.pem"
                    )
                })?;
            let algorithm = pkcs8::AlgorithmIdentifierRef {
                oid: sec1::ALGORITHM_OID,
                parameters: Some((&curve_oid).into()),
            };
            let info = pkcs8::PrivateKeyInfo::new(algorithm, parsed.contents());
            let der_bytes = info
                .to_der()
                .map_err(|e| anyhow::anyhow!("Failed to encode PKCS#8: {}", e))?;
            Ok(pem::encode(&pem::Pem::new("PRIVATE KEY", der_bytes)))

View on GitHub (pinned to 3810898a74)

Solutions

  1. Regenerate or re-export the key cleanly: openssl ec -in key.pem -check -out checked.pem; if -check fails the file itself is corrupt
  2. 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
  3. Confirm the file really is an unencrypted EC private key: openssl pkey -in key.pem -noout -text
  4. If the key came through a secret store, re-download the original bytes instead of copying text from a terminal/browser

Example fix

# before (broken key bytes under SEC1 header)
-----BEGIN EC PRIVATE KEY-----
MHQCAQEE...(truncated/corrupted base64)
-----END EC PRIVATE KEY-----

# after: convert to PKCS#8 and use that path
openssl pkey -in key.pem -out key-pkcs8.pem

# TlsConfig::new().with_client_cert_and_key(cert.pem.into(), "key-pkcs8.pem".into())
Defensive patterns

Strategy: validation

Validate before calling

fn check_ec_key_parses(path: &str) -> anyhow::Result<()> {
    let pem_text = std::fs::read_to_string(path)?;
    let parsed = pem::parse(&pem_text)
        .map_err(|e| anyhow::anyhow!("bad PEM: {e}"))?;
    if parsed.tag() != "EC PRIVATE KEY" { return Ok(()); }
    sec1::EcPrivateKey::from_der(parsed.contents())
        .map_err(|e| anyhow::anyhow!("SEC1 body will fail: {e}"))?;
    Ok(())
}

Try / catch

// At client-construction time, convert once and report the file, not the DER internals:
let tls = match TlsConfig::new().with_client_cert_and_key(cert, key) { /* build later */ };
match client_result {
    Err(e) if e.to_string().contains("Failed to parse EC key") =>
        eprintln!("{key_path} is not a valid SEC1 EC key; run: openssl pkey -in {key_path} -out {key_path}.pkcs8"),
    other => other?,
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Related errors


AI-assisted analysis of aaif-goose/goose@3810898a74 (2026-08-16). Data as JSON: /api/errors/4a5df1eefece83dd. Report an issue: GitHub.