File stalwart-fix-tls-pem-combinded-files.patch of Package stalwart
From 91d0946348de281ccdf919bad5083ebbe3f6978e Mon Sep 17 00:00:00 2001
From: Andreas Schneider <asn@cryptomilk.org>
Date: Mon, 23 Feb 2026 12:00:34 +0100
Subject: [PATCH] TLS: Skip non-key PEM items when parsing private keys
It is common that you use combined PEM files including the certificate
chain, private keys and DH params.
However in the current implementation read_one() returns the first
recognized PEM item, which may be a certificate when cert and key are in
the same file. We should loop through PEM items to find the first
private key instead of failing on non-key entries like certificates or
DH parameters.
---
crates/common/src/config/mod.rs | 56 ++++++++++++++++----------
crates/common/src/config/server/tls.rs | 22 +++++-----
2 files changed, 47 insertions(+), 31 deletions(-)
diff --git a/crates/common/src/config/mod.rs b/crates/common/src/config/mod.rs
index 533e8132..6eeb3e86 100644
--- a/crates/common/src/config/mod.rs
+++ b/crates/common/src/config/mod.rs
@@ -226,18 +226,24 @@ impl Core {
}
pub fn build_rsa_keypair(pem: &str) -> Result<RsaKeyPair, String> {
- match rustls_pemfile::read_one(&mut pem.as_bytes()) {
- Ok(Some(rustls_pemfile::Item::Pkcs1Key(key))) => {
- RsaKeyPair::from_der(key.secret_pkcs1_der())
- .map_err(|err| format!("Failed to parse PKCS1 RSA key: {err}"))
- }
- Ok(Some(rustls_pemfile::Item::Pkcs8Key(key))) => {
- RsaKeyPair::from_pkcs8(key.secret_pkcs8_der())
- .map_err(|err| format!("Failed to parse PKCS8 RSA key: {err}"))
+ let mut cursor = std::io::Cursor::new(pem.as_bytes());
+
+ // Skip non-key PEM items (certificates, etc.) until we find an RSA key
+ loop {
+ match rustls_pemfile::read_one(&mut cursor)
+ .map_err(|err| format!("Failed to read RSA key: {err}"))?
+ {
+ Some(rustls_pemfile::Item::Pkcs1Key(key)) => {
+ return RsaKeyPair::from_der(key.secret_pkcs1_der())
+ .map_err(|err| format!("Failed to parse PKCS1 RSA key: {err}"));
+ }
+ Some(rustls_pemfile::Item::Pkcs8Key(key)) => {
+ return RsaKeyPair::from_pkcs8(key.secret_pkcs8_der())
+ .map_err(|err| format!("Failed to parse PKCS8 RSA key: {err}"));
+ }
+ Some(_) => continue,
+ None => return Err("No RSA key found in PEM".to_string()),
}
- Err(err) => Err(format!("Failed to read PEM: {err}")),
- Ok(Some(key)) => Err(format!("Unsupported key type: {key:?}")),
- Ok(None) => Err("No RSA key found in PEM".to_string()),
}
}
@@ -245,15 +251,23 @@ pub fn build_ecdsa_pem(
alg: &'static ring::signature::EcdsaSigningAlgorithm,
pem: &str,
) -> Result<EcdsaKeyPair, String> {
- match rustls_pemfile::read_one(&mut pem.as_bytes()) {
- Ok(Some(rustls_pemfile::Item::Pkcs8Key(key))) => EcdsaKeyPair::from_pkcs8(
- alg,
- key.secret_pkcs8_der(),
- &ring::rand::SystemRandom::new(),
- )
- .map_err(|err| format!("Failed to parse PKCS8 ECDSA key: {err}")),
- Err(err) => Err(format!("Failed to read PEM: {err}")),
- Ok(Some(key)) => Err(format!("Unsupported key type: {key:?}")),
- Ok(None) => Err("No ECDSA key found in PEM".to_string()),
+ let mut cursor = std::io::Cursor::new(pem.as_bytes());
+
+ // Skip non-key PEM items (certificates, etc.) until we find an ECDSA key
+ loop {
+ match rustls_pemfile::read_one(&mut cursor)
+ .map_err(|err| format!("Failed to read ECDSA key: {err}"))?
+ {
+ Some(rustls_pemfile::Item::Pkcs8Key(key)) => {
+ return EcdsaKeyPair::from_pkcs8(
+ alg,
+ key.secret_pkcs8_der(),
+ &ring::rand::SystemRandom::new(),
+ )
+ .map_err(|err| format!("Failed to parse PKCS8 ECDSA key: {err}"));
+ }
+ Some(_) => continue,
+ None => return Err("No ECDSA key found in PEM".to_string()),
+ }
}
}
diff --git a/crates/common/src/config/server/tls.rs b/crates/common/src/config/server/tls.rs
index e4e591f9..8bfbbc25 100644
--- a/crates/common/src/config/server/tls.rs
+++ b/crates/common/src/config/server/tls.rs
@@ -421,16 +421,18 @@ pub(crate) fn build_certified_key(cert: Vec<u8>, pk: Vec<u8>) -> Result<Certifie
if cert.is_empty() {
return Err("No certificates found.".to_string());
}
- let pk = match read_one(&mut Cursor::new(pk))
- .map_err(|err| format!("Failed to read private keys.: {err}",))?
- .into_iter()
- .next()
- {
- Some(Item::Pkcs8Key(key)) => PrivateKeyDer::Pkcs8(key),
- Some(Item::Pkcs1Key(key)) => PrivateKeyDer::Pkcs1(key),
- Some(Item::Sec1Key(key)) => PrivateKeyDer::Sec1(key),
- Some(_) => return Err("Unsupported private keys found.".to_string()),
- None => return Err("No private keys found.".to_string()),
+ let mut cursor = Cursor::new(pk);
+ // Skip non-key PEM items (certificates, etc.) until we find a private key
+ let pk = loop {
+ match read_one(&mut cursor)
+ .map_err(|err| format!("Failed to read private keys: {err}"))?
+ {
+ Some(Item::Pkcs8Key(key)) => break PrivateKeyDer::Pkcs8(key),
+ Some(Item::Pkcs1Key(key)) => break PrivateKeyDer::Pkcs1(key),
+ Some(Item::Sec1Key(key)) => break PrivateKeyDer::Sec1(key),
+ Some(_) => continue,
+ None => return Err("No private keys found.".to_string()),
+ }
};
Ok(CertifiedKey {
--
2.53.0