risingwavelabs/risingwave · error

No private key found

Error message

No private key found

What it means

Thrown by `load_private_key` when building TLS credentials. The function iterates PEM sections with `PrivatePkcs8KeyDer::pem_slice_iter` looking for a PKCS#8 private key block, and this error is raised when no such block is present in the supplied string or file. Only PKCS#8 (`-----BEGIN PRIVATE KEY-----`) format is accepted by this parser.

Source

Thrown at src/connector/src/connector_common/common.rs:1305

    CertificateDer::pem_slice_iter(&cert_bytes)
        .collect::<Result<Vec<_>, _>>()
        .context("failed to parse certificates")
        .map_err(Into::into)
}

pub(crate) fn load_private_key(
    certificate: &str,
) -> ConnectorResult<rustls_pki_types::PrivateKeyDer<'static>> {
    let cert_bytes = if let Some(path) = certificate.strip_prefix("fs://") {
        std::fs::read_to_string(path).map(|cert| cert.as_bytes().to_owned())?
    } else {
        certificate.as_bytes().to_owned()
    };

    let cert = PrivatePkcs8KeyDer::pem_slice_iter(&cert_bytes)
        .next()
        .ok_or_else(|| anyhow!("No private key found"))?
        .context("failed to parse the private key")?;
    Ok(cert.into())
}

#[serde_as]
#[derive(Deserialize, Debug, Clone, WithOptions)]
pub struct MongodbCommon {
    /// The URL of `MongoDB`
    #[serde(rename = "mongodb.url")]
    pub connect_uri: String,
    /// The collection name where data should be written to or read from. For sinks, the format is
    /// `db_name.collection_name`. Data can also be written to dynamic collections, see `collection.name.field`
    /// for more information.
    #[serde(rename = "collection.name")]
    pub collection_name: String,
}

impl EnforceSecret for MongodbCommon {

View on GitHub (pinned to 6469eb736d)

Solutions

  1. Convert the key to PKCS#8 format, e.g. `openssl pkcs8 -topk8 -nocrypt -in key.pem -out key_pkcs8.pem`, and use that.
  2. Check that the private-key option points to (or inline contains) the actual key file, not the certificate.
  3. Verify the file contents contain a `-----BEGIN PRIVATE KEY-----` block (`grep 'PRIVATE KEY' key.pem`).
  4. If using a file, ensure the `fs://` prefix is present; if inlining, ensure the PEM text is fully included.

Example fix

// before
key = "-----BEGIN RSA PRIVATE KEY-----\nMIIEpA...";
// after
key = "-----BEGIN PRIVATE KEY-----\nMIIEvQ..."; // converted with openssl pkcs8 -topk8 -nocrypt
Defensive patterns

Strategy: validation

Validate before calling

fn has_pkcs8_pem(s: &str) -> bool { s.contains("-----BEGIN PRIVATE KEY-----") }
if !has_pkcs8_pem(key_input) { return Err("private key must be PKCS#8 PEM (BEGIN PRIVATE KEY)"); }

Type guard

fn is_pkcs8_key_pem(s: &str) -> bool {
    s.contains("-----BEGIN PRIVATE KEY-----") && !s.contains("RSA PRIVATE KEY") && !s.contains("EC PRIVATE KEY")
}

Try / catch

let key = load_private_key(&cfg.tls_key).map_err(|e| match e.to_string().as_str() {
    "No private key found" => ConfigError::new("key file has no PKCS#8 PEM block; convert with `openssl pkcs8 -topk8 -nocrypt`"),
    _ => e.into(),
})?;

Prevention

When it happens

Trigger: Passing a value to the private-key option (TLS config) whose bytes contain no `-----BEGIN PRIVATE KEY-----` PEM section — e.g. an empty string, a file containing only the certificate, or a key in PKCS#1 (`BEGIN RSA PRIVATE KEY`) or SECG1 (`BEGIN EC PRIVATE KEY`) legacy formats.

Common situations: Providing the certificate path where the key path is expected; a key file with legacy `RSA PRIVATE KEY` headers instead of PKCS#8; missing `fs://` prefix so an inline value is parsed as a path (or vice versa); empty/unreadable key file contents after `fs://` read.

Related errors


AI-assisted analysis of risingwavelabs/risingwave@6469eb736d (2026-09-11). Data as JSON: /api/errors/4583371f897ae982. Report an issue: GitHub.