From d91effd3ebbba6df76fd18f9cafbb241f92e8518 Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Tue, 10 Feb 2026 13:55:32 -0600 Subject: [PATCH] Add integration tests for TLS CryptoProvider configuration Verifies that ring is properly installed as the default crypto provider and that all TLS-dependent operations work: rustls ClientConfig, reqwest client, tokio-rustls connector, and WebSocket TLS with webpki roots. These tests catch the CryptoProvider panic before it reaches production. --- Cargo.lock | 1 + Cargo.toml | 1 + tests/tls_provider.rs | 142 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 144 insertions(+) create mode 100644 tests/tls_provider.rs diff --git a/Cargo.lock b/Cargo.lock index 7c7ce22..405d280 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2972,6 +2972,7 @@ dependencies = [ "tokio-tungstenite", "tracing", "tracing-subscriber", + "webpki-roots 1.0.6", "zeroize", ] diff --git a/Cargo.toml b/Cargo.toml index c0e8a18..b83e6dd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ sha2 = "0.10" [dev-dependencies] regex = "1" +webpki-roots = "1" [build-dependencies] prost-build = "0.14" diff --git a/tests/tls_provider.rs b/tests/tls_provider.rs new file mode 100644 index 0000000..a0711e3 --- /dev/null +++ b/tests/tls_provider.rs @@ -0,0 +1,142 @@ +//! Integration tests verifying the rustls CryptoProvider is configured correctly. +//! +//! These tests catch the panic that occurs when both ring and aws-lc-rs features +//! are enabled transitively but no default provider is explicitly installed. +//! Without the install_default() call in main(), any TLS operation panics with: +//! "no process-level CryptoProvider was set" + +use std::sync::Once; + +static INIT: Once = Once::new(); + +/// Install the ring crypto provider once for all tests in this module, +/// matching what main() does at startup. +fn ensure_crypto_provider() { + INIT.call_once(|| { + rustls::crypto::ring::default_provider() + .install_default() + .expect("Failed to install rustls CryptoProvider"); + }); +} + +#[test] +fn test_crypto_provider_is_installed() { + ensure_crypto_provider(); + + // After install_default(), the process-level provider must be available. + // This is the call that panicked before the fix. + let provider = rustls::crypto::CryptoProvider::get_default(); + assert!(provider.is_some(), "CryptoProvider should be installed"); +} + +#[test] +fn test_rustls_client_config_builder_does_not_panic() { + ensure_crypto_provider(); + + // ClientConfig::builder() uses the default CryptoProvider internally. + // Before the fix, this panicked with "no process-level CryptoProvider was set". + let config = rustls::ClientConfig::builder() + .with_root_certificates(rustls::RootCertStore::empty()) + .with_no_client_auth(); + + // Verify config was created with TLS 1.2 and 1.3 support + assert!( + config.alpn_protocols.is_empty(), + "Default config should have no ALPN protocols" + ); +} + +#[test] +fn test_reqwest_tls_client_creation() { + ensure_crypto_provider(); + + // reqwest::Client with rustls-tls needs a working CryptoProvider. + // This would panic without the provider installed. + let client = reqwest::Client::builder() + .use_rustls_tls() + .build() + .expect("Should be able to build reqwest client with rustls TLS"); + + // Verify the client is usable (doesn't panic on creation) + drop(client); +} + +#[test] +fn test_tokio_rustls_connector_creation() { + ensure_crypto_provider(); + + // This mirrors how the MikroTik client creates its TLS connector. + // ClientConfig::builder() was the exact call site of the original panic. + let config = rustls::ClientConfig::builder() + .dangerous() + .with_custom_certificate_verifier(std::sync::Arc::new(TestVerifier)) + .with_no_client_auth(); + + let _connector = tokio_rustls::TlsConnector::from(std::sync::Arc::new(config)); +} + +#[test] +fn test_websocket_tls_connector_available() { + ensure_crypto_provider(); + + // Verify the full TLS config chain used by WebSocket connections works. + // tokio-tungstenite uses rustls internally for wss:// connections. + let mut root_store = rustls::RootCertStore::empty(); + root_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()); + + let config = rustls::ClientConfig::builder() + .with_root_certificates(root_store) + .with_no_client_auth(); + + assert!( + !config + .crypto_provider() + .signature_verification_algorithms + .all + .is_empty(), + "Crypto provider should have signature verification algorithms" + ); +} + +/// Dummy certificate verifier for testing (accepts all certs). +#[derive(Debug)] +struct TestVerifier; + +impl rustls::client::danger::ServerCertVerifier for TestVerifier { + fn verify_server_cert( + &self, + _end_entity: &rustls::pki_types::CertificateDer<'_>, + _intermediates: &[rustls::pki_types::CertificateDer<'_>], + _server_name: &rustls::pki_types::ServerName<'_>, + _ocsp_response: &[u8], + _now: rustls::pki_types::UnixTime, + ) -> Result { + Ok(rustls::client::danger::ServerCertVerified::assertion()) + } + + fn verify_tls12_signature( + &self, + _message: &[u8], + _cert: &rustls::pki_types::CertificateDer<'_>, + _dss: &rustls::DigitallySignedStruct, + ) -> Result { + Ok(rustls::client::danger::HandshakeSignatureValid::assertion()) + } + + fn verify_tls13_signature( + &self, + _message: &[u8], + _cert: &rustls::pki_types::CertificateDer<'_>, + _dss: &rustls::DigitallySignedStruct, + ) -> Result { + Ok(rustls::client::danger::HandshakeSignatureValid::assertion()) + } + + fn supported_verify_schemes(&self) -> Vec { + vec![ + rustls::SignatureScheme::RSA_PKCS1_SHA256, + rustls::SignatureScheme::ECDSA_NISTP256_SHA256, + rustls::SignatureScheme::ED25519, + ] + } +}