1#[cfg(all(unix, not(feature = "fips")))]
4use std::os::unix::fs::OpenOptionsExt;
5#[cfg(not(feature = "fips"))]
6use std::{
7 fmt::{Debug, Formatter},
8 fs::{File, OpenOptions},
9 io::{self, Write},
10};
11use std::{
12 path::{Path, PathBuf},
13 sync::{Arc, Mutex, OnceLock},
14};
15
16#[cfg(not(feature = "fips"))]
17use rustls::KeyLog;
18use rustls::{
19 client::{
20 danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier},
21 Resumption,
22 },
23 crypto::CryptoProvider,
24 pki_types::{pem::PemObject as _, CertificateDer, PrivateKeyDer, ServerName, UnixTime},
25 server::WebPkiClientVerifier,
26 version::{TLS12, TLS13},
27 ClientConfig, DigitallySignedStruct, RootCertStore, ServerConfig, SignatureScheme, SupportedCipherSuite,
28 SupportedProtocolVersion, Tls13CipherSuite,
29};
30#[cfg(not(feature = "fips"))]
31use saluki_common::collections::FastHashMap;
32use saluki_error::{generic_error, GenericError};
33use tracing::debug;
34#[cfg(not(feature = "fips"))]
35use tracing::warn;
36
37#[cfg(any(test, feature = "test-util"))]
38pub mod test_util;
39
40static DEFAULT_CRYPTO_PROVIDER_SET: OnceLock<()> = OnceLock::new();
42
43static DEFAULT_ROOT_CERT_STORE_MUTEX: Mutex<()> = Mutex::new(());
45static DEFAULT_ROOT_CERT_STORE: OnceLock<Arc<RootCertStore>> = OnceLock::new();
46#[cfg(not(feature = "fips"))]
47static KEY_LOG_FILES: OnceLock<Mutex<FastHashMap<PathBuf, Option<Arc<NssKeyLogFile>>>>> = OnceLock::new();
48
49const DEFAULT_MAX_TLS12_RESUMPTION_SESSIONS: usize = 8;
51const TLS12_PLUS_PROTOCOL_VERSIONS: &[&SupportedProtocolVersion] = &[&TLS13, &TLS12];
52const TLS13_PROTOCOL_VERSIONS: &[&SupportedProtocolVersion] = &[&TLS13];
53
54#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
56pub enum TlsMinimumVersion {
57 #[default]
59 Tls12,
60
61 Tls13,
63}
64
65impl TlsMinimumVersion {
66 const fn protocol_versions(self) -> &'static [&'static SupportedProtocolVersion] {
67 match self {
68 Self::Tls12 => TLS12_PLUS_PROTOCOL_VERSIONS,
69 Self::Tls13 => TLS13_PROTOCOL_VERSIONS,
70 }
71 }
72}
73
74#[derive(Debug)]
80struct AcceptAllServerCertVerifier {
81 provider: Arc<CryptoProvider>,
82}
83
84impl ServerCertVerifier for AcceptAllServerCertVerifier {
85 fn verify_server_cert(
86 &self, _end_entity: &CertificateDer<'_>, _intermediates: &[CertificateDer<'_>], _server_name: &ServerName<'_>,
87 _ocsp_response: &[u8], _now: UnixTime,
88 ) -> Result<ServerCertVerified, rustls::Error> {
89 Ok(ServerCertVerified::assertion())
90 }
91
92 fn verify_tls12_signature(
93 &self, message: &[u8], cert: &CertificateDer<'_>, dss: &DigitallySignedStruct,
94 ) -> Result<HandshakeSignatureValid, rustls::Error> {
95 rustls::crypto::verify_tls12_signature(message, cert, dss, &self.provider.signature_verification_algorithms)
96 }
97
98 fn verify_tls13_signature(
99 &self, message: &[u8], cert: &CertificateDer<'_>, dss: &DigitallySignedStruct,
100 ) -> Result<HandshakeSignatureValid, rustls::Error> {
101 rustls::crypto::verify_tls13_signature(message, cert, dss, &self.provider.signature_verification_algorithms)
102 }
103
104 fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
105 self.provider.signature_verification_algorithms.supported_schemes()
106 }
107}
108
109#[cfg(not(feature = "fips"))]
110struct NssKeyLogFile {
111 path: PathBuf,
112 file: Mutex<File>,
113}
114
115#[cfg(not(feature = "fips"))]
116impl NssKeyLogFile {
117 fn open_shared<P: Into<PathBuf>>(path: P) -> Option<Arc<Self>> {
118 let path = path.into();
119 let mut key_log_files = match KEY_LOG_FILES.get_or_init(|| Mutex::new(FastHashMap::default())).lock() {
120 Ok(key_log_files) => key_log_files,
121 Err(_) => {
122 warn!("Failed to acquire TLS key log file registry lock; TLS key logging disabled.");
123 return None;
124 }
125 };
126
127 if let Some(cached) = key_log_files.get(&path) {
130 return cached.clone();
131 }
132
133 let key_log_file = match open_key_log_file(&path) {
134 Ok(file) => {
135 warn!(
136 path = %path.display(),
137 "TLS key logging enabled; TLS session secrets will be written to disk."
138 );
139 Some(Arc::new(Self {
140 path: path.clone(),
141 file: Mutex::new(file),
142 }))
143 }
144 Err(e) => {
145 warn!(
146 path = %path.display(),
147 error = %e,
148 "Failed to open TLS key log file for appending; TLS key logging disabled."
149 );
150 None
151 }
152 };
153
154 key_log_files.insert(path, key_log_file.clone());
155 key_log_file
156 }
157}
158
159#[cfg(not(feature = "fips"))]
160impl Debug for NssKeyLogFile {
161 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
162 f.debug_struct("NssKeyLogFile").field("path", &self.path).finish()
163 }
164}
165
166#[cfg(feature = "fips")]
167static FIPS_KEY_LOG_WARNED_PATHS: OnceLock<Mutex<saluki_common::collections::FastHashSet<PathBuf>>> = OnceLock::new();
168
169#[cfg(feature = "fips")]
170fn fips_key_log_warn_once(path: PathBuf) {
171 let warned =
172 FIPS_KEY_LOG_WARNED_PATHS.get_or_init(|| Mutex::new(saluki_common::collections::FastHashSet::default()));
173 let Ok(mut warned) = warned.lock() else {
174 return;
175 };
176 if warned.insert(path.clone()) {
177 tracing::warn!(
178 path = %path.display(),
179 "FIPS build: TLS key logging is disabled because exporting TLS secrets is not FIPS-compliant."
180 );
181 }
182}
183
184#[cfg(not(feature = "fips"))]
185impl KeyLog for NssKeyLogFile {
186 fn log(&self, label: &str, client_random: &[u8], secret: &[u8]) {
187 let line = match build_nss_key_log_line(label, client_random, secret) {
188 Ok(line) => line,
189 Err(e) => {
190 debug!(path = %self.path.display(), error = %e, "Failed to format TLS key log line.");
191 return;
192 }
193 };
194
195 match self.file.lock() {
196 Ok(mut file) => {
197 if let Err(e) = file.write_all(&line) {
198 debug!(path = %self.path.display(), error = %e, "Failed to write TLS key log line.");
199 }
200 }
201 Err(_) => {
202 debug!(path = %self.path.display(), "TLS key log file lock poisoned; dropping TLS key log line.");
203 }
204 }
205 }
206}
207
208#[cfg(not(feature = "fips"))]
209fn open_key_log_file(path: &Path) -> io::Result<File> {
210 let mut options = OpenOptions::new();
211 options.write(true).create(true).append(true);
212
213 #[cfg(unix)]
214 options.mode(0o600);
215
216 options.open(path)
217}
218
219#[cfg(not(feature = "fips"))]
220fn build_nss_key_log_line(label: &str, client_random: &[u8], secret: &[u8]) -> io::Result<Vec<u8>> {
221 let mut line = Vec::new();
222 write!(line, "{label} ")?;
223 write_hex(&mut line, client_random)?;
224 write!(line, " ")?;
225 write_hex(&mut line, secret)?;
226 writeln!(line)?;
227
228 Ok(line)
229}
230
231#[cfg(not(feature = "fips"))]
232fn write_hex(writer: &mut impl Write, bytes: &[u8]) -> io::Result<()> {
233 for byte in bytes {
234 write!(writer, "{byte:02x}")?;
235 }
236
237 Ok(())
238}
239
240pub struct ClientTLSConfigBuilder {
249 key_log_file_path: Option<PathBuf>,
250 max_tls12_resumption_sessions: Option<usize>,
251 min_tls_version: TlsMinimumVersion,
252 root_cert_store: Option<RootCertStore>,
253 danger_accept_invalid_certs: bool,
254}
255
256impl ClientTLSConfigBuilder {
257 pub fn new() -> Self {
258 Self {
259 key_log_file_path: None,
260 max_tls12_resumption_sessions: None,
261 min_tls_version: TlsMinimumVersion::default(),
262 root_cert_store: None,
263 danger_accept_invalid_certs: false,
264 }
265 }
266
267 pub fn with_key_log_file<P: Into<PathBuf>>(mut self, path: P) -> Self {
278 self.key_log_file_path = Some(path.into());
279 self
280 }
281
282 pub fn with_max_tls12_resumption_sessions(mut self, max: usize) -> Self {
286 self.max_tls12_resumption_sessions = Some(max);
287 self
288 }
289
290 pub fn with_root_cert_store(mut self, store: RootCertStore) -> Self {
294 self.root_cert_store = Some(store);
295 self
296 }
297
298 pub fn with_min_tls_version(mut self, version: TlsMinimumVersion) -> Self {
302 self.min_tls_version = version;
303 self
304 }
305
306 pub fn danger_accept_invalid_certs(mut self) -> Self {
312 self.danger_accept_invalid_certs = true;
313 self
314 }
315
316 pub fn build(self) -> Result<ClientConfig, GenericError> {
324 let max_tls12_resumption_sessions = self
325 .max_tls12_resumption_sessions
326 .unwrap_or(DEFAULT_MAX_TLS12_RESUMPTION_SESSIONS);
327 let protocol_versions = self.min_tls_version.protocol_versions();
328
329 let mut config = if self.danger_accept_invalid_certs {
330 let crypto_provider = CryptoProvider::get_default()
331 .map(Arc::clone)
332 .ok_or_else(|| generic_error!("Default cryptography provider not yet installed."))?;
333 let verifier = Arc::new(AcceptAllServerCertVerifier {
334 provider: crypto_provider,
335 });
336
337 ClientConfig::builder_with_protocol_versions(protocol_versions)
338 .dangerous()
339 .with_custom_certificate_verifier(verifier)
340 .with_no_client_auth()
341 } else {
342 let root_cert_store = self.root_cert_store.map(Arc::new).map(Ok).unwrap_or_else(|| {
343 DEFAULT_ROOT_CERT_STORE
344 .get()
345 .map(Arc::clone)
346 .ok_or(generic_error!("Default TLS root certificate store not initialized."))
347 })?;
348
349 ClientConfig::builder_with_protocol_versions(protocol_versions)
350 .with_root_certificates(root_cert_store)
351 .with_no_client_auth()
352 };
353
354 if let Some(path) = self.key_log_file_path {
355 #[cfg(feature = "fips")]
356 fips_key_log_warn_once(path);
357
358 #[cfg(not(feature = "fips"))]
359 if let Some(key_log) = NssKeyLogFile::open_shared(path) {
360 config.key_log = key_log;
361 }
362 }
363
364 config.resumption = Resumption::in_memory_sessions(max_tls12_resumption_sessions);
368
369 ensure_client_config_fips_compliant(&config)?;
370
371 Ok(config)
372 }
373}
374
375pub struct ServerTLSConfigBuilder {
386 cert_file: Option<PathBuf>,
387 key_file: Option<PathBuf>,
388 ca_file: Option<PathBuf>,
389}
390
391impl ServerTLSConfigBuilder {
392 pub fn new() -> Self {
394 Self {
395 cert_file: None,
396 key_file: None,
397 ca_file: None,
398 }
399 }
400
401 pub fn with_cert_file<P: Into<PathBuf>>(mut self, path: P) -> Self {
406 self.cert_file = Some(path.into());
407 self
408 }
409
410 pub fn with_key_file<P: Into<PathBuf>>(mut self, path: P) -> Self {
414 self.key_file = Some(path.into());
415 self
416 }
417
418 pub fn with_ca_file<P: Into<PathBuf>>(mut self, path: P) -> Self {
426 self.ca_file = Some(path.into());
427 self
428 }
429
430 pub fn build(self) -> Result<ServerConfig, GenericError> {
437 let cert_file = self
438 .cert_file
439 .ok_or_else(|| generic_error!("No certificate file configured for server TLS."))?;
440 let key_file = self
441 .key_file
442 .ok_or_else(|| generic_error!("No private key file configured for server TLS."))?;
443
444 let cert_bytes = std::fs::read(&cert_file)
446 .map_err(|e| generic_error!("Failed to read certificate file '{}': {}", cert_file.display(), e))?;
447 let cert_chain: Vec<CertificateDer<'static>> = CertificateDer::pem_slice_iter(&cert_bytes)
448 .collect::<Result<Vec<_>, _>>()
449 .map_err(|e| generic_error!("Failed to parse certificate file '{}': {}", cert_file.display(), e))?;
450
451 if cert_chain.is_empty() {
452 return Err(generic_error!(
453 "No PEM-encoded certificates found in certificate file '{}'.",
454 cert_file.display()
455 ));
456 }
457
458 let key_bytes = std::fs::read(&key_file)
460 .map_err(|e| generic_error!("Failed to read private key file '{}': {}", key_file.display(), e))?;
461 let private_key = PrivateKeyDer::from_pem_slice(&key_bytes)
462 .map_err(|e| generic_error!("Failed to parse private key file '{}': {}", key_file.display(), e))?;
463
464 let mut config = if let Some(ca_file) = self.ca_file {
465 build_server_config_with_client_verifier(&ca_file, cert_chain, private_key)?
466 } else {
467 ServerConfig::builder()
468 .with_no_client_auth()
469 .with_single_cert(cert_chain, private_key)
470 .map_err(|e| generic_error!("Failed to build server TLS configuration: {}", e))?
471 };
472
473 ensure_server_config_fips_compliant(&mut config)?;
474
475 Ok(config)
476 }
477}
478
479fn build_server_config_with_client_verifier(
484 ca_file: &Path, cert_chain: Vec<CertificateDer<'static>>, private_key: PrivateKeyDer<'static>,
485) -> Result<ServerConfig, GenericError> {
486 let ca_bytes = std::fs::read(ca_file)
487 .map_err(|e| generic_error!("Failed to read CA certificate file '{}': {}", ca_file.display(), e))?;
488 let mut root_cert_store = RootCertStore::empty();
489 let ca_certs: Vec<CertificateDer<'static>> = CertificateDer::pem_slice_iter(&ca_bytes)
490 .collect::<Result<Vec<_>, _>>()
491 .map_err(|e| generic_error!("Failed to parse CA certificate file '{}': {}", ca_file.display(), e))?;
492
493 if ca_certs.is_empty() {
494 return Err(generic_error!(
495 "No PEM-encoded certificates found in CA file '{}'.",
496 ca_file.display()
497 ));
498 }
499
500 for ca_cert in ca_certs {
501 root_cert_store
502 .add(ca_cert)
503 .map_err(|e| generic_error!("Failed to add CA certificate to root store: {}", e))?;
504 }
505
506 let client_verifier = WebPkiClientVerifier::builder(Arc::new(root_cert_store))
507 .allow_unauthenticated()
508 .build()
509 .map_err(|e| generic_error!("Failed to build client certificate verifier: {}", e))?;
510
511 ServerConfig::builder()
512 .with_client_cert_verifier(client_verifier)
513 .with_single_cert(cert_chain, private_key)
514 .map_err(|e| generic_error!("Failed to build server TLS configuration: {}", e))
515}
516
517pub fn ensure_client_config_fips_compliant(config: &ClientConfig) -> Result<(), GenericError> {
525 #[cfg(feature = "fips")]
526 if !config.fips() {
527 return Err(generic_error!("Client TLS configuration is not FIPS compliant."));
528 }
529
530 #[cfg(not(feature = "fips"))]
531 let _ = config;
532
533 Ok(())
534}
535
536pub fn ensure_server_config_fips_compliant(config: &mut ServerConfig) -> Result<(), GenericError> {
545 #[cfg(feature = "fips")]
546 {
547 config.key_log = Arc::new(rustls::NoKeyLog);
548 config.enable_secret_extraction = false;
549
550 if !config.fips() {
551 return Err(generic_error!("Server TLS configuration is not FIPS compliant."));
552 }
553 }
554
555 #[cfg(not(feature = "fips"))]
556 let _ = config;
557
558 Ok(())
559}
560
561pub fn initialize_default_crypto_provider() -> Result<(), GenericError> {
570 if DEFAULT_CRYPTO_PROVIDER_SET.get().is_some() {
571 return Err(generic_error!("Default TLS cryptography provider already initialized."));
572 }
573
574 default_crypto_provider().install_default().map_err(|_| {
575 generic_error!(
576 "Failed to install the default TLS cryptography provider. This is likely due to a conflicting provider already being installed."
577 )
578 })?;
579
580 DEFAULT_CRYPTO_PROVIDER_SET
582 .set(())
583 .expect("should be impossible for DEFAULT_CRYPTO_PROVIDER_SET to be initialized twice");
584
585 Ok(())
586}
587
588#[cfg(not(windows))]
589fn default_crypto_provider() -> CryptoProvider {
590 let provider = rustls::crypto::aws_lc_rs::default_provider();
591
592 #[cfg(feature = "fips")]
593 {
594 let mut provider = provider;
595 provider.cipher_suites.retain(|suite| suite.fips());
596 provider.kx_groups.retain(|group| group.fips());
597 provider
598 }
599
600 #[cfg(not(feature = "fips"))]
601 provider
602}
603
604#[cfg(windows)]
605fn default_crypto_provider() -> CryptoProvider {
606 rustls_cng_crypto::default_provider()
607}
608
609pub fn sha256(data: &[u8]) -> [u8; 32] {
614 SHA256_SUITE
615 .common
616 .hash_provider
617 .hash(data)
618 .as_ref()
619 .try_into()
620 .expect("SHA-256 digest should be 32 bytes")
621}
622
623#[cfg(not(windows))]
625static SHA256_SUITE: &Tls13CipherSuite = tls13_suite(rustls::crypto::aws_lc_rs::cipher_suite::TLS13_AES_128_GCM_SHA256);
626#[cfg(windows)]
627static SHA256_SUITE: &Tls13CipherSuite = tls13_suite(rustls_cng_crypto::cipher_suite::TLS13_AES_128_GCM_SHA256);
628
629const fn tls13_suite(suite: SupportedCipherSuite) -> &'static Tls13CipherSuite {
630 match suite.tls13() {
631 Some(suite) => suite,
632 None => panic!("SHA-256 source should be a TLS 1.3 cipher suite"),
633 }
634}
635
636pub fn load_platform_root_certificates() -> Result<(), GenericError> {
703 let _guard = DEFAULT_ROOT_CERT_STORE_MUTEX
704 .lock()
705 .map_err(|_| generic_error!("Default TLS root certificate store update lock poisoned."))?;
706 if DEFAULT_ROOT_CERT_STORE.get().is_some() {
707 return Err(generic_error!(
708 "Default TLS root certificate store already initialized."
709 ));
710 }
711
712 let root_cert_store = load_platform_root_certificates_inner()?;
713
714 DEFAULT_ROOT_CERT_STORE
717 .set(Arc::new(root_cert_store))
718 .expect("should be impossible for DEFAULT_ROOT_CERT_STORE to be initialized twice");
719
720 Ok(())
721}
722
723pub fn load_platform_root_certificates_inner() -> Result<RootCertStore, GenericError> {
735 let mut root_cert_store = RootCertStore::empty();
736
737 let mut result = rustls_native_certs::load_native_certs();
738
739 result.errors.retain(|err| match &err.kind {
746 rustls_native_certs::ErrorKind::Io { inner, path }
747 if matches!(
748 inner.kind(),
749 std::io::ErrorKind::NotFound | std::io::ErrorKind::PermissionDenied
750 ) =>
751 {
752 debug!(
753 error = %inner,
754 path = %path.display(),
755 "Skipping missing or unreadable certificate source while loading platform root certificates."
756 );
757 false
758 }
759 _ => true,
760 });
761
762 let (added, failed) = root_cert_store.add_parsable_certificates(result.certs);
764 if failed == 0 && added > 0 {
765 debug!(
766 "Added {} certificates from environment to the default root certificate store.",
767 added
768 );
769 } else if failed > 0 && added > 0 {
770 debug!("Added {} certificates from environment to the default root certificate store, but failed to add {} certificates.", added, failed);
771 } else {
772 if !result.errors.is_empty() {
780 let joined_errors = result
781 .errors
782 .iter()
783 .map(|e| e.to_string())
784 .collect::<Vec<_>>()
785 .join(", ");
786
787 return Err(generic_error!(
788 "Failed to load certificates from platform's native certificate store: {}",
789 joined_errors
790 ));
791 }
792 }
793
794 Ok(root_cert_store)
795}
796
797#[cfg(test)]
798mod tests {
799 use std::fs;
800 #[cfg(all(unix, not(feature = "fips")))]
801 use std::os::unix::fs::PermissionsExt;
802 use std::path::Path;
803 #[cfg(feature = "fips")]
804 use std::sync::{
805 atomic::{AtomicBool, Ordering},
806 Arc,
807 };
808
809 #[cfg(feature = "fips")]
810 use rustls::KeyLog;
811 use rustls::{
812 client::danger::ServerCertVerifier,
813 crypto::CryptoProvider,
814 pki_types::{ServerName, UnixTime},
815 ClientConfig, ProtocolVersion, RootCertStore, ServerConfig,
816 };
817
818 #[cfg(not(feature = "fips"))]
819 use super::build_nss_key_log_line;
820 #[cfg(all(unix, not(feature = "fips")))]
821 use super::open_key_log_file;
822 use super::test_util::SelfSignedCert;
823 use super::{
824 ensure_client_config_fips_compliant, ensure_server_config_fips_compliant, AcceptAllServerCertVerifier,
825 ClientTLSConfigBuilder, ServerTLSConfigBuilder, TlsMinimumVersion,
826 };
827
828 fn client_config_with_key_log(path: &Path) -> ClientConfig {
831 let _ = super::initialize_default_crypto_provider();
832
833 ClientTLSConfigBuilder::new()
834 .with_root_cert_store(RootCertStore::empty())
835 .with_key_log_file(path)
836 .build()
837 .expect("client TLS config should build")
838 }
839
840 fn temp_file_path(file_name: &str) -> (tempfile::TempDir, std::path::PathBuf) {
842 let tempdir = tempfile::tempdir().expect("temporary directory should be created");
843 let path = tempdir.path().join(file_name);
844 (tempdir, path)
845 }
846
847 #[test]
848 fn sha256_matches_known_answer() {
849 let expected = [
851 0xba, 0x78, 0x16, 0xbf, 0x8f, 0x01, 0xcf, 0xea, 0x41, 0x41, 0x40, 0xde, 0x5d, 0xae, 0x22, 0x23, 0xb0, 0x03,
852 0x61, 0xa3, 0x96, 0x17, 0x7a, 0x9c, 0xb4, 0x10, 0xff, 0x61, 0xf2, 0x00, 0x15, 0xad,
853 ];
854 assert_eq!(super::sha256(b"abc"), expected);
855 }
856
857 #[test]
858 fn tls12_minimum_enables_tls12_and_tls13() {
859 let versions = TlsMinimumVersion::Tls12.protocol_versions();
860
861 assert_eq!(versions.len(), 2);
862 assert_eq!(versions[0].version, ProtocolVersion::TLSv1_3);
863 assert_eq!(versions[1].version, ProtocolVersion::TLSv1_2);
864 }
865
866 #[test]
867 fn tls13_minimum_enables_tls13_only() {
868 let versions = TlsMinimumVersion::Tls13.protocol_versions();
869
870 assert_eq!(versions.len(), 1);
871 assert_eq!(versions[0].version, ProtocolVersion::TLSv1_3);
872 }
873
874 #[test]
875 fn client_config_fips_validation_accepts_builder_config() {
876 let _ = super::initialize_default_crypto_provider();
877
878 let config = ClientTLSConfigBuilder::new()
879 .with_root_cert_store(RootCertStore::empty())
880 .build()
881 .expect("client TLS config should build");
882
883 ensure_client_config_fips_compliant(&config).expect("client TLS config should pass FIPS validation");
884 }
885
886 #[test]
887 fn accept_all_verifier_accepts_mismatched_server_certificate() {
888 let _ = super::initialize_default_crypto_provider();
892 let provider = CryptoProvider::get_default()
893 .cloned()
894 .expect("default crypto provider should be installed");
895 let verifier = AcceptAllServerCertVerifier { provider };
896
897 let cert = SelfSignedCert::new(["localhost"]);
898 let cert_chain = cert.cert_chain();
899 let server_name = ServerName::try_from("totally.different.example").expect("server name should parse");
900
901 let result = verifier.verify_server_cert(&cert_chain[0], &[], &server_name, &[], UnixTime::now());
902
903 assert!(
904 result.is_ok(),
905 "accept-all verifier must accept a certificate presented for a mismatched server name"
906 );
907 }
908
909 #[test]
910 fn danger_accept_invalid_certs_builds_without_root_cert_store() {
911 let _ = super::initialize_default_crypto_provider();
912
913 let missing_store_error = ClientTLSConfigBuilder::new()
916 .build()
917 .expect_err("client TLS config should fail to build without any root cert store");
918 assert!(
919 missing_store_error
920 .to_string()
921 .contains("root certificate store not initialized"),
922 "unexpected error: {missing_store_error}"
923 );
924
925 ClientTLSConfigBuilder::new()
928 .danger_accept_invalid_certs()
929 .build()
930 .expect("client TLS config should build with an accept-all verifier and no root cert store");
931 }
932
933 #[test]
934 fn server_config_fips_validation_accepts_basic_server_config() {
935 let _ = super::initialize_default_crypto_provider();
936 let cert = SelfSignedCert::localhost();
937 let mut config = ServerConfig::builder()
938 .with_no_client_auth()
939 .with_single_cert(cert.cert_chain(), cert.private_key())
940 .expect("server TLS config should build");
941
942 ensure_server_config_fips_compliant(&mut config).expect("server TLS config should pass FIPS validation");
943 }
944
945 #[cfg(feature = "fips")]
946 #[test]
947 fn server_config_fips_validation_disables_secret_extraction() {
948 #[derive(Debug)]
949 struct TestKeyLog(Arc<AtomicBool>);
950
951 impl KeyLog for TestKeyLog {
952 fn log(&self, _label: &str, _client_random: &[u8], _secret: &[u8]) {
953 self.0.store(true, Ordering::Relaxed);
954 }
955 }
956
957 let _ = super::initialize_default_crypto_provider();
958 let cert = SelfSignedCert::localhost();
959 let key_log_used = Arc::new(AtomicBool::new(false));
960 let mut config = ServerConfig::builder()
961 .with_no_client_auth()
962 .with_single_cert(cert.cert_chain(), cert.private_key())
963 .expect("server TLS config should build");
964 config.key_log = Arc::new(TestKeyLog(Arc::clone(&key_log_used)));
965 config.enable_secret_extraction = true;
966
967 ensure_server_config_fips_compliant(&mut config).expect("server TLS config should pass FIPS validation");
968
969 config.key_log.log("CLIENT_RANDOM", &[0xab, 0xcd], &[0x01, 0x23]);
970
971 assert!(!key_log_used.load(Ordering::Relaxed));
972 assert!(!config.enable_secret_extraction);
973 }
974
975 #[cfg(all(feature = "fips", not(windows)))]
980 #[test]
981 fn client_config_fips_validation_rejects_non_fips_config() {
982 let provider = Arc::new(rustls::crypto::aws_lc_rs::default_provider());
983 let config = ClientConfig::builder_with_provider(provider)
984 .with_protocol_versions(&[&rustls::version::TLS13, &rustls::version::TLS12])
985 .expect("client config builder should accept protocol versions")
986 .with_root_certificates(RootCertStore::empty())
987 .with_no_client_auth();
988
989 let error =
990 ensure_client_config_fips_compliant(&config).expect_err("a non-FIPS client configuration must be rejected");
991 assert!(
992 error.to_string().contains("not FIPS compliant"),
993 "unexpected error: {error}"
994 );
995 }
996
997 #[cfg(all(feature = "fips", not(windows)))]
998 #[test]
999 fn server_config_fips_validation_rejects_non_fips_config() {
1000 let cert = SelfSignedCert::localhost();
1001 let provider = Arc::new(rustls::crypto::aws_lc_rs::default_provider());
1002 let mut config = ServerConfig::builder_with_provider(provider)
1003 .with_protocol_versions(&[&rustls::version::TLS13, &rustls::version::TLS12])
1004 .expect("server config builder should accept protocol versions")
1005 .with_no_client_auth()
1006 .with_single_cert(cert.cert_chain(), cert.private_key())
1007 .expect("server TLS config should build");
1008
1009 let error = ensure_server_config_fips_compliant(&mut config)
1010 .expect_err("a non-FIPS server configuration must be rejected");
1011 assert!(
1012 error.to_string().contains("not FIPS compliant"),
1013 "unexpected error: {error}"
1014 );
1015 }
1016
1017 #[test]
1018 #[cfg(not(feature = "fips"))]
1019 fn nss_key_log_lines_are_written_in_hex_format() {
1020 let output =
1021 build_nss_key_log_line("CLIENT_RANDOM", &[0xab, 0xcd], &[0x01, 0x23]).expect("key log line should build");
1022
1023 assert_eq!(output, b"CLIENT_RANDOM abcd 0123\n");
1024 }
1025
1026 #[test]
1027 #[cfg(not(feature = "fips"))]
1028 fn client_config_uses_configured_key_log_file() {
1029 let (_tempdir, key_log_path) = temp_file_path("sslkeylogfile");
1030
1031 let config = client_config_with_key_log(&key_log_path);
1032 config.key_log.log("CLIENT_RANDOM", &[0xab, 0xcd], &[0x01, 0x23]);
1033
1034 let contents = fs::read_to_string(&key_log_path).expect("key log file should be readable");
1035 assert_eq!(contents, "CLIENT_RANDOM abcd 0123\n");
1036 }
1037
1038 #[test]
1039 #[cfg(not(feature = "fips"))]
1040 fn client_config_ignores_unwritable_key_log_file() {
1041 let (_tempdir, key_log_path) = temp_file_path("missing/sslkeylogfile");
1044
1045 let config = client_config_with_key_log(&key_log_path);
1046 config.key_log.log("CLIENT_RANDOM", &[0xab, 0xcd], &[0x01, 0x23]);
1047
1048 assert!(!key_log_path.exists());
1049 }
1050
1051 #[test]
1052 #[cfg(not(feature = "fips"))]
1053 fn client_configs_append_to_shared_key_log_file() {
1054 let (_tempdir, key_log_path) = temp_file_path("shared-sslkeylogfile");
1055
1056 let first_config = client_config_with_key_log(&key_log_path);
1057 let second_config = client_config_with_key_log(&key_log_path);
1058
1059 first_config.key_log.log("CLIENT_RANDOM", &[0xab, 0xcd], &[0x01, 0x23]);
1060 second_config.key_log.log("CLIENT_RANDOM", &[0xef, 0x01], &[0x45, 0x67]);
1061
1062 let contents = fs::read_to_string(&key_log_path).expect("key log file should be readable");
1063 assert_eq!(contents, "CLIENT_RANDOM abcd 0123\nCLIENT_RANDOM ef01 4567\n");
1064 }
1065
1066 #[cfg(all(unix, not(feature = "fips")))]
1067 #[test]
1068 fn key_log_file_is_created_with_owner_only_permissions() {
1069 let tempdir = tempfile::tempdir().expect("temporary directory should be created");
1070 let key_log_path = tempdir.path().join("sslkeylogfile");
1071
1072 let file = open_key_log_file(&key_log_path).expect("key log file should open");
1073 drop(file);
1074
1075 let mode = fs::metadata(&key_log_path)
1076 .expect("key log file metadata should be readable")
1077 .permissions()
1078 .mode()
1079 & 0o777;
1080 assert_eq!(mode, 0o600);
1081 }
1082
1083 #[cfg(windows)]
1084 #[test]
1085 fn windows_default_crypto_provider_builds_client_config() {
1086 let _ = super::initialize_default_crypto_provider();
1087
1088 ClientTLSConfigBuilder::new()
1089 .with_root_cert_store(RootCertStore::empty())
1090 .build()
1091 .expect("Windows CNG-backed TLS config should build");
1092 }
1093
1094 #[test]
1095 #[cfg(feature = "fips")]
1096 fn key_log_file_ignored_in_fips_mode() {
1097 let (_tempdir, key_log_path) = temp_file_path("fips-sslkeylogfile");
1100
1101 let config = client_config_with_key_log(&key_log_path);
1102
1103 config.key_log.log("CLIENT_RANDOM", &[0xab, 0xcd], &[0x01, 0x23]);
1105
1106 assert!(!key_log_path.exists(), "FIPS builds must not create a TLS key log file");
1107 }
1108
1109 #[test]
1110 fn server_tls_config_builder_loads_cert_and_key_files() {
1111 let _ = super::initialize_default_crypto_provider();
1112 let cert = SelfSignedCert::localhost();
1113 let tempdir = tempfile::tempdir().expect("temporary directory should be created");
1114 let cert_path = tempdir.path().join("cert.pem");
1115 let key_path = tempdir.path().join("key.pem");
1116 cert.write_cert_pem(&cert_path);
1117 cert.write_key_pem(&key_path);
1118
1119 ServerTLSConfigBuilder::new()
1120 .with_cert_file(&cert_path)
1121 .with_key_file(&key_path)
1122 .build()
1123 .expect("server TLS config should build from cert and key files");
1124 }
1125
1126 #[test]
1127 fn server_tls_config_builder_loads_ca_file_for_client_auth() {
1128 let _ = super::initialize_default_crypto_provider();
1129 let server_cert = SelfSignedCert::localhost();
1130 let ca_cert = SelfSignedCert::new(["test-ca"]);
1131 let tempdir = tempfile::tempdir().expect("temporary directory should be created");
1132 let cert_path = tempdir.path().join("server-cert.pem");
1133 let key_path = tempdir.path().join("server-key.pem");
1134 let ca_path = tempdir.path().join("ca.pem");
1135 server_cert.write_cert_pem(&cert_path);
1136 server_cert.write_key_pem(&key_path);
1137 ca_cert.write_cert_pem(&ca_path);
1138
1139 ServerTLSConfigBuilder::new()
1140 .with_cert_file(&cert_path)
1141 .with_key_file(&key_path)
1142 .with_ca_file(&ca_path)
1143 .build()
1144 .expect("server TLS config with CA should build");
1145 }
1146
1147 #[test]
1148 fn server_tls_config_builder_errors_without_cert_file() {
1149 let _ = super::initialize_default_crypto_provider();
1150
1151 let error = ServerTLSConfigBuilder::new()
1152 .with_key_file("/tmp/key.pem")
1153 .build()
1154 .expect_err("server TLS config should fail without a cert file");
1155
1156 assert!(
1157 error.to_string().contains("No certificate file"),
1158 "unexpected error: {error}"
1159 );
1160 }
1161
1162 #[test]
1163 fn server_tls_config_builder_errors_without_key_file() {
1164 let _ = super::initialize_default_crypto_provider();
1165
1166 let error = ServerTLSConfigBuilder::new()
1167 .with_cert_file("/tmp/cert.pem")
1168 .build()
1169 .expect_err("server TLS config should fail without a key file");
1170
1171 assert!(
1172 error.to_string().contains("No private key file"),
1173 "unexpected error: {error}"
1174 );
1175 }
1176
1177 #[test]
1178 fn server_tls_config_builder_errors_on_unreadable_cert_file() {
1179 let _ = super::initialize_default_crypto_provider();
1180 let tempdir = tempfile::tempdir().expect("temporary directory should be created");
1181 let key_path = tempdir.path().join("key.pem");
1182 let cert_path = tempdir.path().join("nonexistent.pem");
1183 let cert = SelfSignedCert::localhost();
1184 cert.write_key_pem(&key_path);
1185
1186 let error = ServerTLSConfigBuilder::new()
1187 .with_cert_file(&cert_path)
1188 .with_key_file(&key_path)
1189 .build()
1190 .expect_err("server TLS config should fail with unreadable cert file");
1191
1192 assert!(
1193 error.to_string().contains("Failed to read certificate file"),
1194 "unexpected error: {error}"
1195 );
1196 }
1197
1198 #[test]
1199 fn server_tls_config_builder_errors_on_non_pem_cert_file() {
1200 let _ = super::initialize_default_crypto_provider();
1201 let tempdir = tempfile::tempdir().expect("temporary directory should be created");
1202 let cert_path = tempdir.path().join("cert.pem");
1203 let key_path = tempdir.path().join("key.pem");
1204 fs::write(&cert_path, "this is not a certificate").expect("should write garbage file");
1206 let cert = SelfSignedCert::localhost();
1207 cert.write_key_pem(&key_path);
1208
1209 let error = ServerTLSConfigBuilder::new()
1210 .with_cert_file(&cert_path)
1211 .with_key_file(&key_path)
1212 .build()
1213 .expect_err("server TLS config should fail with non-PEM cert file");
1214
1215 assert!(
1216 error.to_string().contains("No PEM-encoded certificates found"),
1217 "unexpected error: {error}"
1218 );
1219 }
1220}