From 6409d49ad32686d1014e4d4cb61815d1b1668db4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabr=C3=ADcio=20Bracht?= Date: Wed, 20 May 2026 09:06:16 -0700 Subject: [PATCH 1/2] drop rustls-pemfile for rustls-pki-types pem api --- crates/mqtt5/Cargo.toml | 1 - crates/mqtt5/src/broker/tls_acceptor.rs | 31 ++------- crates/mqtt5/src/transport/tls.rs | 76 +++++----------------- crates/mqtt5/tests/wss_alpn_integration.rs | 5 +- 4 files changed, 22 insertions(+), 91 deletions(-) diff --git a/crates/mqtt5/Cargo.toml b/crates/mqtt5/Cargo.toml index 109013c2..c5cf2583 100644 --- a/crates/mqtt5/Cargo.toml +++ b/crates/mqtt5/Cargo.toml @@ -144,7 +144,6 @@ http = "1.4.0" humantime-serde = "1.1" ring = "0.17.14" rustls = { version = "0.23", default-features = false, features = ["ring", "std", "tls12"] } -rustls-pemfile = "2.2" rustls-pki-types = "1.13.0" tokio = { version = "1.47", features = ["full"] } tokio-rustls = { version = "0.26", default-features = false, features = ["ring", "tls12"] } diff --git a/crates/mqtt5/src/broker/tls_acceptor.rs b/crates/mqtt5/src/broker/tls_acceptor.rs index b10e0574..1914d73e 100644 --- a/crates/mqtt5/src/broker/tls_acceptor.rs +++ b/crates/mqtt5/src/broker/tls_acceptor.rs @@ -4,6 +4,7 @@ //! allowing secure connections on a dedicated TLS port (typically 8883). use crate::error::{MqttError, Result}; +use rustls::pki_types::pem::PemObject; use rustls::pki_types::{CertificateDer, PrivateKeyDer}; use rustls::server::WebPkiClientVerifier; use rustls::{RootCertStore, ServerConfig}; @@ -53,7 +54,7 @@ impl TlsAcceptorConfig { path: impl AsRef, ) -> Result>> { let cert_pem = tokio::fs::read(path.as_ref()).await?; - let certs: Vec> = rustls_pemfile::certs(&mut &cert_pem[..]) + let certs: Vec> = CertificateDer::pem_slice_iter(&cert_pem) .filter_map(std::result::Result::ok) .collect(); @@ -75,32 +76,8 @@ impl TlsAcceptorConfig { path: impl AsRef, ) -> Result> { let key_pem = tokio::fs::read(path.as_ref()).await?; - - // Try PKCS#8 format first - let mut keys: Vec> = - rustls_pemfile::pkcs8_private_keys(&mut &key_pem[..]) - .filter_map(std::result::Result::ok) - .map(PrivateKeyDer::from) - .collect(); - - // Try RSA format if PKCS#8 didn't work - if keys.is_empty() { - keys = rustls_pemfile::rsa_private_keys(&mut &key_pem[..]) - .filter_map(std::result::Result::ok) - .map(PrivateKeyDer::from) - .collect(); - } - - // Try EC format if RSA didn't work - if keys.is_empty() { - keys = rustls_pemfile::ec_private_keys(&mut &key_pem[..]) - .filter_map(std::result::Result::ok) - .map(PrivateKeyDer::from) - .collect(); - } - - keys.into_iter() - .next() + PrivateKeyDer::pem_slice_iter(&key_pem) + .find_map(std::result::Result::ok) .ok_or_else(|| MqttError::Configuration("No private keys found in file".to_string())) } diff --git a/crates/mqtt5/src/transport/tls.rs b/crates/mqtt5/src/transport/tls.rs index bd681886..4670499e 100644 --- a/crates/mqtt5/src/transport/tls.rs +++ b/crates/mqtt5/src/transport/tls.rs @@ -2,6 +2,7 @@ use crate::crypto::NoVerification; use crate::error::{MqttError, Result}; use crate::time::Duration; use crate::Transport; +use rustls::pki_types::pem::PemObject; use rustls::pki_types::{CertificateDer, PrivateKeyDer, ServerName}; use rustls::{ClientConfig, RootCertStore}; use std::net::SocketAddr; @@ -175,13 +176,9 @@ impl TlsConfig { /// # Errors /// /// Returns an error if the file cannot be read or parsed - /// - /// # Errors - /// - /// Returns an error if the operation fails pub fn load_client_cert_pem(&mut self, cert_path: &str) -> Result<()> { let cert_pem = std::fs::read(cert_path)?; - let certs: Vec> = rustls_pemfile::certs(&mut &cert_pem[..]) + let certs: Vec> = CertificateDer::pem_slice_iter(&cert_pem) .filter_map(std::result::Result::ok) .collect(); if certs.is_empty() { @@ -198,34 +195,12 @@ impl TlsConfig { /// # Errors /// /// Returns an error if the file cannot be read or parsed - /// - /// # Errors - /// - /// Returns an error if the operation fails pub fn load_client_key_pem(&mut self, key_path: &str) -> Result<()> { let key_pem = std::fs::read(key_path)?; - let mut keys: Vec> = - rustls_pemfile::pkcs8_private_keys(&mut &key_pem[..]) - .filter_map(std::result::Result::ok) - .map(PrivateKeyDer::from) - .collect(); - - if keys.is_empty() { - // Try RSA keys if PKCS8 didn't work - keys = rustls_pemfile::rsa_private_keys(&mut &key_pem[..]) - .filter_map(std::result::Result::ok) - .map(PrivateKeyDer::from) - .collect(); - } - - if keys.is_empty() { - return Err(MqttError::ProtocolError( - "No private keys found in file".to_string(), - )); - } - self.client_key = Some(keys.into_iter().next().ok_or_else(|| { - MqttError::ProtocolError("Keys vector unexpectedly empty".to_string()) - })?); + let key = PrivateKeyDer::pem_slice_iter(&key_pem) + .find_map(std::result::Result::ok) + .ok_or_else(|| MqttError::ProtocolError("No private keys found in file".to_string()))?; + self.client_key = Some(key); Ok(()) } @@ -234,13 +209,9 @@ impl TlsConfig { /// # Errors /// /// Returns an error if the file cannot be read or parsed - /// - /// # Errors - /// - /// Returns an error if the operation fails pub fn load_ca_cert_pem(&mut self, ca_path: &str) -> Result<()> { let ca_pem = std::fs::read(ca_path)?; - let ca_certs: Vec> = rustls_pemfile::certs(&mut &ca_pem[..]) + let ca_certs: Vec> = CertificateDer::pem_slice_iter(&ca_pem) .filter_map(std::result::Result::ok) .collect(); if ca_certs.is_empty() { @@ -258,7 +229,7 @@ impl TlsConfig { /// /// Returns an error if the bytes cannot be parsed as PEM certificate pub fn load_client_cert_pem_bytes(&mut self, cert_pem: &[u8]) -> Result<()> { - let certs: Vec> = rustls_pemfile::certs(&mut &cert_pem[..]) + let certs: Vec> = CertificateDer::pem_slice_iter(cert_pem) .filter_map(std::result::Result::ok) .collect(); if certs.is_empty() { @@ -276,28 +247,12 @@ impl TlsConfig { /// /// Returns an error if the bytes cannot be parsed as PEM private key pub fn load_client_key_pem_bytes(&mut self, key_pem: &[u8]) -> Result<()> { - let mut keys: Vec> = - rustls_pemfile::pkcs8_private_keys(&mut &key_pem[..]) - .filter_map(std::result::Result::ok) - .map(PrivateKeyDer::from) - .collect(); - - if keys.is_empty() { - // Try RSA keys if PKCS8 didn't work - keys = rustls_pemfile::rsa_private_keys(&mut &key_pem[..]) - .filter_map(std::result::Result::ok) - .map(PrivateKeyDer::from) - .collect(); - } - - if keys.is_empty() { - return Err(MqttError::ProtocolError( - "No private keys found in PEM bytes".to_string(), - )); - } - self.client_key = Some(keys.into_iter().next().ok_or_else(|| { - MqttError::ProtocolError("Keys vector unexpectedly empty".to_string()) - })?); + let key = PrivateKeyDer::pem_slice_iter(key_pem) + .find_map(std::result::Result::ok) + .ok_or_else(|| { + MqttError::ProtocolError("No private keys found in PEM bytes".to_string()) + })?; + self.client_key = Some(key); Ok(()) } @@ -307,7 +262,7 @@ impl TlsConfig { /// /// Returns an error if the bytes cannot be parsed as PEM certificate pub fn load_ca_cert_pem_bytes(&mut self, ca_pem: &[u8]) -> Result<()> { - let ca_certs: Vec> = rustls_pemfile::certs(&mut &ca_pem[..]) + let ca_certs: Vec> = CertificateDer::pem_slice_iter(ca_pem) .filter_map(std::result::Result::ok) .collect(); if ca_certs.is_empty() { @@ -336,7 +291,6 @@ impl TlsConfig { /// /// Returns an error if the bytes are not valid DER-encoded private key pub fn load_client_key_der_bytes(&mut self, key_der: &[u8]) -> Result<()> { - // Assume PKCS#8 format for DER keys - this is the most common format use rustls::pki_types::PrivatePkcs8KeyDer; let key = PrivateKeyDer::from(PrivatePkcs8KeyDer::from(key_der.to_vec())); self.client_key = Some(key); diff --git a/crates/mqtt5/tests/wss_alpn_integration.rs b/crates/mqtt5/tests/wss_alpn_integration.rs index 2844aaf0..85beff5a 100644 --- a/crates/mqtt5/tests/wss_alpn_integration.rs +++ b/crates/mqtt5/tests/wss_alpn_integration.rs @@ -3,7 +3,8 @@ use common::{find_workspace_root, TestBroker}; use mqtt5::broker::config::{ BrokerConfig, StorageBackend, StorageConfig, TlsConfig as BrokerTlsConfig, WebSocketConfig, }; -use rustls::pki_types::ServerName; +use rustls::pki_types::pem::PemObject; +use rustls::pki_types::{CertificateDer, ServerName}; use std::net::SocketAddr; use std::sync::atomic::{AtomicU16, Ordering}; use std::sync::Arc; @@ -48,7 +49,7 @@ fn build_tls_connector(alpn_protocols: Vec>) -> tokio_rustls::TlsConnect let mut root_store = rustls::RootCertStore::empty(); let workspace_root = find_workspace_root(); let ca_pem = std::fs::read(workspace_root.join("test_certs/ca.pem")).unwrap(); - let ca_certs: Vec<_> = rustls_pemfile::certs(&mut &ca_pem[..]) + let ca_certs: Vec> = CertificateDer::pem_slice_iter(&ca_pem) .filter_map(std::result::Result::ok) .collect(); for cert in ca_certs { From 425331073f5cf02e57e7245f0d3510ce84f51e5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabr=C3=ADcio=20Bracht?= Date: Wed, 20 May 2026 10:51:20 -0700 Subject: [PATCH 2/2] bump mqtt5 to 0.32.2, update changelog --- CHANGELOG.md | 6 ++++++ crates/mqtt5/Cargo.toml | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e758d91d..bfa6b03e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [mqtt5 0.32.2] - 2026-05-20 + +### Changed + +- **Drop unmaintained `rustls-pemfile` dependency** - migrated all PEM parsing to `rustls-pki-types::pem::PemObject` (already implemented by `CertificateDer` and `PrivateKeyDer`). Closes [RUSTSEC-2025-0134](https://rustsec.org/advisories/RUSTSEC-2025-0134.html). As a side effect, private-key loading now accepts PKCS#8, PKCS#1 (RSA), and SEC1 (EC) sections in one pass instead of three sequential format probes, and respects file order when multiple key types are present (issue #82, PR #83). + ## [mqtt5 0.32.1] - 2026-05-17 ### Fixed diff --git a/crates/mqtt5/Cargo.toml b/crates/mqtt5/Cargo.toml index c5cf2583..170a8e9d 100644 --- a/crates/mqtt5/Cargo.toml +++ b/crates/mqtt5/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mqtt5" -version = "0.32.1" +version = "0.32.2" edition.workspace = true rust-version.workspace = true authors.workspace = true