nautechsystems/nautilus_trader · error
PEM does not contain a private key
Error message
PEM does not contain a private key
What it means
After successfully parsing the PEM structure, `rsa_signature` checks that the PEM tag ends with 'PRIVATE KEY' (i.e. 'PRIVATE KEY' for PKCS#8 or 'RSA PRIVATE KEY'). This error means the PEM block is well-formed but is not a private key — commonly a public key, certificate, or another object type.
Source
Thrown at crates/cryptography/src/signing.rs:55
///
/// # Errors
///
/// Returns an error if:
/// - `data` is empty.
/// - `private_key_pem` is not a valid PEM-encoded PKCS#8 RSA private key or cannot be parsed.
/// - Signature generation fails due to key or cryptographic errors.
pub fn rsa_signature(private_key_pem: &str, data: &str) -> anyhow::Result<String> {
if data.is_empty() {
anyhow::bail!("Query string cannot be empty");
}
// Remove PEM headings and decode to DER bytes using the `pem` crate
let pem = pem::parse(private_key_pem.trim())
.map_err(|e| anyhow::anyhow!("Failed to parse PEM: {e}"))?;
// Ensure this is a private key
if !pem.tag().ends_with("PRIVATE KEY") {
anyhow::bail!("PEM does not contain a private key");
}
// Construct RSA key pair from PKCS#8 DER bytes
let key_pair = KeyPair::from_pkcs8(pem.contents())
.map_err(|_| anyhow::anyhow!("Failed to decode RSA private key"))?;
// Prepare RNG and output buffer (signature length = modulus length)
let rng = lc_rand::SystemRandom::new();
let mut signature = vec![0u8; key_pair.public_modulus_len()];
key_pair
.sign(
&lc_signature::RSA_PKCS1_SHA256,
&rng,
data.as_bytes(),
&mut signature,
)
.map_err(|_| anyhow::anyhow!("Failed to generate RSA signature"))?;View on GitHub (pinned to 18893faf8b)
Solutions
- Point the key path/env var at the actual private key PEM file (BEGIN PRIVATE KEY or BEGIN RSA PRIVATE KEY)
- If you only have a certificate, extract the private key from where it was generated — you cannot sign with a certificate
- Generate a proper key: openssl genpkey -algorithm RSA -out key.pem
- Check for 'ENCRYPTED PRIVATE KEY' — decrypt it (openssl pkcs8 -topk8 -nocrypt) since this API expects an unencrypted key
Example fix
// before
let pem = std::fs::read_to_string("cert.pem")?; // certificate, not a key
// after
let pem = std::fs::read_to_string("private_key.pem")?; // -----BEGIN PRIVATE KEY----- Defensive patterns
Strategy: validation
Validate before calling
let pem = pem::parse(key_text.trim())?;
if !pem.tag().ends_with("PRIVATE KEY") {
return Err(anyhow::anyhow!("expected a private key PEM, got tag: {}", pem.tag()));
} Type guard
fn pem_is_private_key(s: &str) -> bool {
pem::parse(s.trim())
.map(|p| p.tag().ends_with("PRIVATE KEY"))
.unwrap_or(false)
} Try / catch
match rsa_signature(&key, query) {
Ok(sig) => use(sig),
Err(e) if e.to_string().contains("does not contain a private key") => {
tracing::error!("configured key is a public key/certificate");
}
Err(e) => return Err(e),
} Prevention
- Double-check which file/env var holds the private key vs the certificate
- Name key files unambiguously (private_key.pem vs cert.pem)
- Verify with `openssl pkey -in key.pem -noout` before deploying
When it happens
Trigger: Calling `rsa_signature` with a PEM whose tag is e.g. 'PUBLIC KEY', 'CERTIFICATE', or 'ENCRYPTED PRIVATE KEY' (which also ends with 'PRIVATE KEY'... note encrypted keys do end with that tag but will then fail later at decode). The immediate trigger is any tag not ending in 'PRIVATE KEY'.
Common situations: Pointing the config at the public key or TLS certificate file instead of the private key; a keychain/cloud secret returning the certificate chain; mixing up the files generated alongside the keypair.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- Failed to parse PEM: {e}
- Failed to decode RSA private key
- Query string cannot be empty
- No valid private key found in {}
- Invalid `key`, missing a '{REDIS_DELIMITER}' delimiter, was
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/dee939bc51009280.
Report an issue: GitHub.