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
- Re-encode the key as named-curve PKCS#8: openssl pkey -in key.pem -out key_pkcs8.pem
- Check the PEM header is '-----BEGIN PRIVATE KEY-----'; convert SEC.1 files with: openssl pkcs8 -topk8 -in ec_key.pem -out key_pkcs8.pem
- Verify the key is actually P-256 (prime256v1), not RSA or another curve: openssl pkey -in key.pem -text -noout
- 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
- Generate VAPID keys with named-curve parameters: openssl ecparam -name prime256v1 -genkey -noout -param_enc named_curve
- Always store keys as PKCS#8 ('BEGIN PRIVATE KEY'); convert legacy 'EC PRIVATE KEY' files with openssl pkcs8 -topk8
- Validate the key at startup/config-load time, not on first WebPush send
- Confirm the curve is P-256 with: openssl pkey -in key.pem -text -noout
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
- Failed to read {yaml_path:?}
- Node id {node_id} exceeds {MAX_NODE_ID}, panicking to avoid
- Invalid jump position
- unwrap_tls called on non-TLS acceptor
- Sieve test failed: {}
AI-assisted analysis of stalwartlabs/stalwart@e962003857 (2026-09-06).
Data as JSON: /api/errors/e55712bf8fdd2ce7.
Report an issue: GitHub.