fix(mqtt): prevent TLS certificate loading crashes

Closes #5790
This commit is contained in:
t8y2 committed 2026-08-10 21:14:26 +08:00
1 parent 28f3705596
commit 3f74fc37fd
1 file changed
+122 -19
+122 -19
View File
@@ -4,7 +4,7 @@ use std::collections::{HashMap, HashSet, VecDeque};
use std::fs::File; use std::fs::File;
use std::io::BufReader; use std::io::BufReader;
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Weak}; use std::sync::{Arc, LazyLock, Weak};
use std::time::{Duration, SystemTime, UNIX_EPOCH}; use std::time::{Duration, SystemTime, UNIX_EPOCH};
use rumqttc::tokio_rustls::rustls::{ use rumqttc::tokio_rustls::rustls::{
@@ -1279,13 +1279,15 @@ fn build_transport(
Ok(match (transport, tls_verification_mode) { Ok(match (transport, tls_verification_mode) {
(MqttTransport::Tcp, None) => Transport::Tcp, (MqttTransport::Tcp, None) => Transport::Tcp,
(MqttTransport::Tcp, Some(MqttTlsVerificationMode::VerifyServerCert)) => Transport::tls_with_default_config(), (MqttTransport::Tcp, Some(MqttTlsVerificationMode::VerifyServerCert)) => {
Transport::tls_with_config(verified_tls_configuration())
}
(MqttTransport::Tcp, Some(MqttTlsVerificationMode::SkipServerCertVerification)) => { (MqttTransport::Tcp, Some(MqttTlsVerificationMode::SkipServerCertVerification)) => {
Transport::tls_with_config(insecure_tls_configuration()) Transport::tls_with_config(insecure_tls_configuration())
} }
(MqttTransport::WebSocket, None) => Transport::ws(), (MqttTransport::WebSocket, None) => Transport::ws(),
(MqttTransport::WebSocket, Some(MqttTlsVerificationMode::VerifyServerCert)) => { (MqttTransport::WebSocket, Some(MqttTlsVerificationMode::VerifyServerCert)) => {
Transport::wss_with_default_config() Transport::wss_with_config(verified_tls_configuration())
} }
(MqttTransport::WebSocket, Some(MqttTlsVerificationMode::SkipServerCertVerification)) => { (MqttTransport::WebSocket, Some(MqttTlsVerificationMode::SkipServerCertVerification)) => {
Transport::wss_with_config(insecure_tls_configuration()) Transport::wss_with_config(insecure_tls_configuration())
@@ -1297,8 +1299,8 @@ fn certificate_tls_configuration(auth: &MqttAuth, mode: MqttTlsVerificationMode)
let MqttAuth::Certificate { ca_cert_path, client_cert_path, client_key_path } = auth else { let MqttAuth::Certificate { ca_cert_path, client_cert_path, client_key_path } = auth else {
return Err("MQTT 证书认证配置无效".to_string()); return Err("MQTT 证书认证配置无效".to_string());
}; };
let client_cert_path = client_cert_path.as_deref().ok_or("MQTT 证书认证缺少客户端证书路径")?; let client_cert_path = client_cert_path.as_deref().filter(|path| !path.trim().is_empty());
let client_key_path = client_key_path.as_deref().ok_or("MQTT 证书认证缺少客户端私钥路径")?; let client_key_path = client_key_path.as_deref().filter(|path| !path.trim().is_empty());
let mut root_cert_store = rustls::RootCertStore::empty(); let mut root_cert_store = rustls::RootCertStore::empty();
if let Some(ca_cert_path) = ca_cert_path.as_deref().filter(|path| !path.trim().is_empty()) { if let Some(ca_cert_path) = ca_cert_path.as_deref().filter(|path| !path.trim().is_empty()) {
@@ -1325,22 +1327,39 @@ fn certificate_tls_configuration(auth: &MqttAuth, mode: MqttTlsVerificationMode)
.with_custom_certificate_verifier(Arc::new(NoCertificateVerification { provider })) .with_custom_certificate_verifier(Arc::new(NoCertificateVerification { provider }))
} }
}; };
let cert_file = File::open(client_cert_path).map_err(|e| format!("读取 MQTT 客户端证书失败: {e}"))?; let config = match (client_cert_path, client_key_path) {
let certs = rustls_pemfile::certs(&mut BufReader::new(cert_file)) (None, None) => builder.with_no_client_auth(),
.collect::<Result<Vec<_>, _>>() (None, Some(_)) => return Err("MQTT 证书认证缺少客户端证书路径".to_string()),
.map_err(|e| format!("解析 MQTT 客户端证书失败: {e}"))?; (Some(_), None) => return Err("MQTT 证书认证缺少客户端私钥路径".to_string()),
if certs.is_empty() { (Some(client_cert_path), Some(client_key_path)) => {
return Err("MQTT 客户端证书文件不包含有效证书".to_string()); let cert_file = File::open(client_cert_path).map_err(|e| format!("读取 MQTT 客户端证书失败: {e}"))?;
} let certs = rustls_pemfile::certs(&mut BufReader::new(cert_file))
let key_file = File::open(client_key_path).map_err(|e| format!("读取 MQTT 客户端私钥失败: {e}"))?; .collect::<Result<Vec<_>, _>>()
let mut key_reader = BufReader::new(key_file); .map_err(|e| format!("解析 MQTT 客户端证书失败: {e}"))?;
let key = rustls_pemfile::private_key(&mut key_reader) if certs.is_empty() {
.map_err(|e| format!("解析 MQTT 客户端私钥失败: {e}"))? return Err("MQTT 客户端证书文件不包含有效证书".to_string());
.ok_or("MQTT 客户端私钥文件不包含有效私钥")?; }
let config = let key_file = File::open(client_key_path).map_err(|e| format!("读取 MQTT 客户端私钥失败: {e}"))?;
builder.with_client_auth_cert(certs, key).map_err(|e| format!("构建 MQTT 客户端 TLS 配置失败: {e}"))?; let mut key_reader = BufReader::new(key_file);
let key = rustls_pemfile::private_key(&mut key_reader)
.map_err(|e| format!("解析 MQTT 客户端私钥失败: {e}"))?
.ok_or("MQTT 客户端私钥文件不包含有效私钥")?;
builder.with_client_auth_cert(certs, key).map_err(|e| format!("构建 MQTT 客户端 TLS 配置失败: {e}"))?
}
};
Ok(TlsConfiguration::from(config)) Ok(TlsConfiguration::from(config))
} }
fn verified_tls_configuration() -> TlsConfiguration {
static CONFIG: LazyLock<TlsConfiguration> = LazyLock::new(|| {
let mut root_cert_store = rustls::RootCertStore::empty();
root_cert_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
let config = rustls::ClientConfig::builder().with_root_certificates(root_cert_store).with_no_client_auth();
TlsConfiguration::from(config)
});
CONFIG.clone()
}
fn insecure_tls_configuration() -> TlsConfiguration { fn insecure_tls_configuration() -> TlsConfiguration {
let provider = Arc::new(rustls::crypto::ring::default_provider()); let provider = Arc::new(rustls::crypto::ring::default_provider());
let config = rustls::ClientConfig::builder() let config = rustls::ClientConfig::builder()
@@ -1674,6 +1693,90 @@ mod tests {
Ok(()) Ok(())
} }
#[test]
fn verified_tls_does_not_depend_on_platform_certificate_store() {
let missing_cert_file = std::env::temp_dir().join(format!("dbx-mqtt-missing-ca-{}.pem", uuid::Uuid::new_v4()));
let output = std::process::Command::new(std::env::current_exe().unwrap())
.arg("verified_tls_platform_store_child")
.arg("--ignored")
.arg("--nocapture")
.env("SSL_CERT_FILE", missing_cert_file)
.env_remove("SSL_CERT_DIR")
.output()
.unwrap();
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(output.status.success(), "verified TLS child failed:\nstdout:\n{stdout}\nstderr:\n{stderr}");
assert!(
stdout.contains("verified_tls_platform_store_child") && stdout.contains("1 passed"),
"verified TLS child did not run the regression test:\n{stdout}"
);
}
#[test]
#[ignore]
fn verified_tls_platform_store_child() {
let config = MqttConnectionConfig {
host: "broker.example.com".to_string(),
port: 8883,
tls: true,
tls_skip_verify: false,
..Default::default()
};
let plan = build_connect_plan(&config).unwrap();
assert!(matches!(plan.transport, Transport::Tls(TlsConfiguration::Rustls(_))));
}
#[test]
fn verified_tls_reuses_the_client_config() {
let first = verified_tls_configuration();
let second = verified_tls_configuration();
match (first, second) {
(TlsConfiguration::Rustls(first), TlsConfiguration::Rustls(second)) => {
assert!(Arc::ptr_eq(&first, &second));
}
_ => panic!("verified TLS 应复用显式 Rustls 配置"),
}
}
#[test]
fn certificate_auth_allows_server_only_tls() {
let auth = MqttAuth::Certificate { ca_cert_path: None, client_cert_path: None, client_key_path: None };
let transport =
build_transport(MqttTransport::Tcp, Some(MqttTlsVerificationMode::VerifyServerCert), &auth).unwrap();
assert!(matches!(transport, Transport::Tls(TlsConfiguration::Rustls(_))));
}
#[test]
fn certificate_auth_requires_client_certificate_and_key_as_a_pair() {
let missing_cert = MqttAuth::Certificate {
ca_cert_path: None,
client_cert_path: None,
client_key_path: Some("client.key".to_string()),
};
let missing_key = MqttAuth::Certificate {
ca_cert_path: None,
client_cert_path: Some("client.crt".to_string()),
client_key_path: None,
};
let missing_cert_error =
build_transport(MqttTransport::Tcp, Some(MqttTlsVerificationMode::VerifyServerCert), &missing_cert)
.err()
.unwrap();
let missing_key_error =
build_transport(MqttTransport::Tcp, Some(MqttTlsVerificationMode::VerifyServerCert), &missing_key)
.err()
.unwrap();
assert_eq!(missing_cert_error, "MQTT 证书认证缺少客户端证书路径");
assert_eq!(missing_key_error, "MQTT 证书认证缺少客户端私钥路径");
}
#[tokio::test] #[tokio::test]
async fn connect_applies_selected_protocol_versions() { async fn connect_applies_selected_protocol_versions() {
assert_v4_connect_protocol(crate::mqtt::types::MqttProtocolVersion::V3, Protocol::V3).await; assert_v4_connect_protocol(crate::mqtt::types::MqttProtocolVersion::V3, Protocol::V3).await;