aaif-goose/goose · error

Failed to create identity from cert and key: {}

Error message

Failed to create identity from cert and key: {}

What it means

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}'.

Source

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

    }

    pub fn is_configured(&self) -> bool {
        self.client_identity.is_some() || self.ca_cert_path.is_some()
    }

    #[cfg(any(feature = "rustls-tls", feature = "native-tls"))]
    fn load_identity(&self) -> Result<Option<Identity>> {
        if let Some(cert_key_pair) = &self.client_identity {
            let cert_pem = read_to_string(&cert_key_pair.cert_path)
                .map_err(|e| anyhow::anyhow!("Failed to read client certificate: {}", e))?;
            let key_pem = read_to_string(&cert_key_pair.key_path)
                .map_err(|e| anyhow::anyhow!("Failed to read client private key: {}", e))?;

            #[cfg(not(feature = "native-tls"))]
            let identity = {
                let combined_pem = format!("{}\n{}", cert_pem, key_pem);
                Identity::from_pem(combined_pem.as_bytes()).map_err(|e| {
                    anyhow::anyhow!("Failed to create identity from cert and key: {}", e)
                })?
            };

            #[cfg(feature = "native-tls")]
            let identity = {
                let pkcs8_key_pem = convert_key_to_pkcs8_pem(&key_pem)?;
                Identity::from_pkcs8_pem(cert_pem.as_bytes(), pkcs8_key_pem.as_bytes()).map_err(
                    |e| anyhow::anyhow!("Failed to create identity from cert and key: {}", e),
                )?
            };

            Ok(Some(identity))
        } else {
            Ok(None)
        }
    }

    #[cfg(any(feature = "rustls-tls", feature = "native-tls"))]

View on GitHub (pinned to 3810898a74)

Solutions

  1. Convert the key to PKCS#8 PEM: openssl pkey -in key.pem -out key-pkcs8.pem (works for RSA and EC inputs)
  2. 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)
  3. If the key is passphrase-protected, decrypt it first: openssl pkey -in key.enc -passin file:pass.txt -out key-pkcs8.pem
  4. Re-issue a matching cert/key pair if they come from different generations

Example fix

# before
client_identity:
  cert_path: /etc/goose/tls/client-cert.pem
  key_path: /etc/goose/tls/client-key.pem   # '-----BEGIN RSA PRIVATE KEY-----'

# after (shell)
$ openssl pkey -in /etc/goose/tls/client-key.pem -out /etc/goose/tls/client-key-pkcs8.pem
# client_identity.key_path now points at client-key-pkcs8.pem ('-----BEGIN PRIVATE KEY-----')
Defensive patterns

Strategy: validation

Validate before calling

// fail fast: rustls needs PKCS#8 keys
let key = std::fs::read_to_string(&key_path)?;
if !key.contains("-----BEGIN PRIVATE KEY-----") {
    anyhow::bail!(
        "key is not PKCS#8; run: openssl pkey -in {key_path} -out {key_path}.pkcs8.pem"
    );
}

Type guard

fn is_pkcs8_pem(key_pem: &str) -> bool {
    key_pem.contains("-----BEGIN PRIVATE KEY-----")
}

Prevention

When it happens

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

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

Related errors


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