stalwartlabs/stalwart · error

{err}. Re-encode the key as named-curve PKCS#8, e.g. `openss

Error message

{err}. Re-encode the key as named-curve PKCS#8, e.g. `openssl pkey -in key.pem -out key_pkcs8.pem`.

What it means

from_pkcs8_pem in crates/common/src/network/webpush.rs parses a WebPush VAPID signing key from PKCS#8 PEM. If explicit-parameters extraction fails, it falls back to p256::SigningKey::from_pkcs8_pem; when that also fails it returns this error wrapping the underlying message plus a hint to re-encode the key as named-curve PKCS#8. The usual cause is a PEM whose algorithm parameters encode the curve explicitly (or a wrong key type/format) rather than the named-curve form p256 expects.

Source

Thrown at crates/common/src/network/webpush.rs:58

    public_key: String,
}

impl VapidKey {
    pub fn from_pkcs8_pem(pem: &str) -> Result<Self, String> {
        let pem = pem.trim_start_matches('\u{feff}').trim();

        if let Ok(key) = SigningKey::from_pkcs8_pem(pem) {
            return Ok(Self::from_signing_key(key));
        }
        if let Ok(secret) = SecretKey::from_sec1_pem(pem) {
            return Ok(Self::from_signing_key(secret.into()));
        }
        if let Some(secret) = secret_key_from_explicit_params(pem) {
            return Ok(Self::from_signing_key(secret.into()));
        }

        Err(SigningKey::from_pkcs8_pem(pem)
            .err()
            .map(|err| {
                format!(
                    "{err}. Re-encode the key as named-curve PKCS#8, \
                     e.g. `openssl pkey -in key.pem -out key_pkcs8.pem`."
                )
            })
            .unwrap_or_else(|| "unsupported VAPID key encoding".to_string()))
    }

    fn from_signing_key(signing_key: SigningKey) -> Self {
        let public_key = URL_SAFE_NO_PAD.encode(
            signing_key
                .verifying_key()
                .to_encoded_point(false)
                .as_bytes(),
        );
        Self {
            signing_key,

View on GitHub (pinned to e962003857)

Solutions

  1. Re-encode the key as named-curve PKCS#8: openssl pkey -in key.pem -out key_pkcs8.pem
  2. Check the PEM header is '-----BEGIN PRIVATE KEY-----'; convert SEC.1 files with: openssl pkcs8 -topk8 -in ec_key.pem -out key_pkcs8.pem
  3. Verify the key is actually P-256 (prime256v1), not RSA or another curve: openssl pkey -in key.pem -text -noout
  4. Generate a fresh key if needed: openssl ecparam -name prime256v1 -genkey -noout -param_enc named_curve | openssl pkcs8 -topk8 -nocrypt -out key.pem

Example fix

// before (SEC.1 / explicit params, rejected)
// -----BEGIN EC PRIVATE KEY-----
// ...
// after (named-curve PKCS#8)
// $ openssl pkey -in key.pem -out key_pkcs8.pem
// -----BEGIN PRIVATE KEY-----
// ...
Defensive patterns

Strategy: validation

Validate before calling

// validate the PEM before calling from_pkcs8_pem:
fn validate_p256_pkcs8_pem(pem: &str) -> Result<(), String> {
    if !pem.contains("-----BEGIN PRIVATE KEY-----") {
        return Err("expected PKCS#8 PEM ('PRIVATE KEY'), got SEC.1 or other format".into());
    }
    use p256::pkcs8::DecodePrivateKey;
    p256::SecretKey::from_pkcs8_pem(pem)
        .map(|_| ())
        .map_err(|e| format!("not a named-curve P-256 PKCS#8 key: {e}"))
}

Type guard

fn is_p256_pkcs8_pem(pem: &str) -> bool {
    pem.contains("-----BEGIN PRIVATE KEY-----")
        && p256::SecretKey::from_pkcs8_pem(pem).is_ok()
}

Try / catch

match VapidKey::from_pkcs8_pem(&pem) {
    Ok(key) => use_key(key),
    Err(e) => {
        eprintln!("VAPID key rejected: {e}");
        eprintln!("fix: openssl pkey -in key.pem -out key_pkcs8.pem");
        std::process::exit(1);
    }
}

Prevention

When it happens

Trigger: Calling from_pkcs8_pem with: a PKCS#8 PEM containing explicit curve parameters instead of the namedCurve OID; a key that is not P-256/prime256v1 (e.g. RSA or P-384); a DER or raw key passed where PEM is expected; a SEC.1 'EC PRIVATE KEY' PEM instead of a PKCS#8 'PRIVATE KEY' PEM.

Common situations: Keys generated/exported by openssl with explicit parameters (missing -param_enc named_curve); pasting a public key instead of the private key; legacy SEC.1 key files; VAPID keys generated on a different curve.

Related errors


AI-assisted analysis of stalwartlabs/stalwart@e962003857 (2026-09-06). Data as JSON: /api/errors/e55712bf8fdd2ce7. Report an issue: GitHub.