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 path::Path,
11};
12use std::{
13 path::PathBuf,
14 sync::{Arc, Mutex, OnceLock},
15};
16
17#[cfg(not(feature = "fips"))]
18use rustls::KeyLog;
19use rustls::{
20 client::{
21 danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier},
22 Resumption,
23 },
24 crypto::CryptoProvider,
25 pki_types::{CertificateDer, ServerName, UnixTime},
26 version::{TLS12, TLS13},
27 ClientConfig, DigitallySignedStruct, RootCertStore, ServerConfig, SignatureScheme, SupportedProtocolVersion,
28};
29#[cfg(not(feature = "fips"))]
30use saluki_common::collections::FastHashMap;
31use saluki_error::{generic_error, GenericError};
32use tracing::debug;
33#[cfg(not(feature = "fips"))]
34use tracing::warn;
35
36#[cfg(any(test, feature = "test-util"))]
37pub mod test_util;
38
39static DEFAULT_CRYPTO_PROVIDER_SET: OnceLock<()> = OnceLock::new();
41
42static DEFAULT_ROOT_CERT_STORE_MUTEX: Mutex<()> = Mutex::new(());
44static DEFAULT_ROOT_CERT_STORE: OnceLock<Arc<RootCertStore>> = OnceLock::new();
45#[cfg(not(feature = "fips"))]
46static KEY_LOG_FILES: OnceLock<Mutex<FastHashMap<PathBuf, Option<Arc<NssKeyLogFile>>>>> = OnceLock::new();
47
48const DEFAULT_MAX_TLS12_RESUMPTION_SESSIONS: usize = 8;
50const TLS12_PLUS_PROTOCOL_VERSIONS: &[&SupportedProtocolVersion] = &[&TLS13, &TLS12];
51const TLS13_PROTOCOL_VERSIONS: &[&SupportedProtocolVersion] = &[&TLS13];
52
53#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
55pub enum TlsMinimumVersion {
56 #[default]
58 Tls12,
59
60 Tls13,
62}
63
64impl TlsMinimumVersion {
65 const fn protocol_versions(self) -> &'static [&'static SupportedProtocolVersion] {
66 match self {
67 Self::Tls12 => TLS12_PLUS_PROTOCOL_VERSIONS,
68 Self::Tls13 => TLS13_PROTOCOL_VERSIONS,
69 }
70 }
71}
72
73#[derive(Debug)]
79struct AcceptAllServerCertVerifier {
80 provider: Arc<CryptoProvider>,
81}
82
83impl ServerCertVerifier for AcceptAllServerCertVerifier {
84 fn verify_server_cert(
85 &self, _end_entity: &CertificateDer<'_>, _intermediates: &[CertificateDer<'_>], _server_name: &ServerName<'_>,
86 _ocsp_response: &[u8], _now: UnixTime,
87 ) -> Result<ServerCertVerified, rustls::Error> {
88 Ok(ServerCertVerified::assertion())
89 }
90
91 fn verify_tls12_signature(
92 &self, message: &[u8], cert: &CertificateDer<'_>, dss: &DigitallySignedStruct,
93 ) -> Result<HandshakeSignatureValid, rustls::Error> {
94 rustls::crypto::verify_tls12_signature(message, cert, dss, &self.provider.signature_verification_algorithms)
95 }
96
97 fn verify_tls13_signature(
98 &self, message: &[u8], cert: &CertificateDer<'_>, dss: &DigitallySignedStruct,
99 ) -> Result<HandshakeSignatureValid, rustls::Error> {
100 rustls::crypto::verify_tls13_signature(message, cert, dss, &self.provider.signature_verification_algorithms)
101 }
102
103 fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
104 self.provider.signature_verification_algorithms.supported_schemes()
105 }
106}
107
108#[cfg(not(feature = "fips"))]
109struct NssKeyLogFile {
110 path: PathBuf,
111 file: Mutex<File>,
112}
113
114#[cfg(not(feature = "fips"))]
115impl NssKeyLogFile {
116 fn open_shared<P: Into<PathBuf>>(path: P) -> Option<Arc<Self>> {
117 let path = path.into();
118 let mut key_log_files = match KEY_LOG_FILES.get_or_init(|| Mutex::new(FastHashMap::default())).lock() {
119 Ok(key_log_files) => key_log_files,
120 Err(_) => {
121 warn!("Failed to acquire TLS key log file registry lock; TLS key logging disabled.");
122 return None;
123 }
124 };
125
126 if let Some(cached) = key_log_files.get(&path) {
129 return cached.clone();
130 }
131
132 let key_log_file = match open_key_log_file(&path) {
133 Ok(file) => {
134 warn!(
135 path = %path.display(),
136 "TLS key logging enabled; TLS session secrets will be written to disk."
137 );
138 Some(Arc::new(Self {
139 path: path.clone(),
140 file: Mutex::new(file),
141 }))
142 }
143 Err(e) => {
144 warn!(
145 path = %path.display(),
146 error = %e,
147 "Failed to open TLS key log file for appending; TLS key logging disabled."
148 );
149 None
150 }
151 };
152
153 key_log_files.insert(path, key_log_file.clone());
154 key_log_file
155 }
156}
157
158#[cfg(not(feature = "fips"))]
159impl Debug for NssKeyLogFile {
160 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
161 f.debug_struct("NssKeyLogFile").field("path", &self.path).finish()
162 }
163}
164
165#[cfg(feature = "fips")]
166static FIPS_KEY_LOG_WARNED_PATHS: OnceLock<Mutex<saluki_common::collections::FastHashSet<PathBuf>>> = OnceLock::new();
167
168#[cfg(feature = "fips")]
169fn fips_key_log_warn_once(path: PathBuf) {
170 let warned =
171 FIPS_KEY_LOG_WARNED_PATHS.get_or_init(|| Mutex::new(saluki_common::collections::FastHashSet::default()));
172 let Ok(mut warned) = warned.lock() else {
173 return;
174 };
175 if warned.insert(path.clone()) {
176 tracing::warn!(
177 path = %path.display(),
178 "FIPS build: TLS key logging is disabled because exporting TLS secrets is not FIPS-compliant."
179 );
180 }
181}
182
183#[cfg(not(feature = "fips"))]
184impl KeyLog for NssKeyLogFile {
185 fn log(&self, label: &str, client_random: &[u8], secret: &[u8]) {
186 let line = match build_nss_key_log_line(label, client_random, secret) {
187 Ok(line) => line,
188 Err(e) => {
189 debug!(path = %self.path.display(), error = %e, "Failed to format TLS key log line.");
190 return;
191 }
192 };
193
194 match self.file.lock() {
195 Ok(mut file) => {
196 if let Err(e) = file.write_all(&line) {
197 debug!(path = %self.path.display(), error = %e, "Failed to write TLS key log line.");
198 }
199 }
200 Err(_) => {
201 debug!(path = %self.path.display(), "TLS key log file lock poisoned; dropping TLS key log line.");
202 }
203 }
204 }
205}
206
207#[cfg(not(feature = "fips"))]
208fn open_key_log_file(path: &Path) -> io::Result<File> {
209 let mut options = OpenOptions::new();
210 options.write(true).create(true).append(true);
211
212 #[cfg(unix)]
213 options.mode(0o600);
214
215 options.open(path)
216}
217
218#[cfg(not(feature = "fips"))]
219fn build_nss_key_log_line(label: &str, client_random: &[u8], secret: &[u8]) -> io::Result<Vec<u8>> {
220 let mut line = Vec::new();
221 write!(line, "{label} ")?;
222 write_hex(&mut line, client_random)?;
223 write!(line, " ")?;
224 write_hex(&mut line, secret)?;
225 writeln!(line)?;
226
227 Ok(line)
228}
229
230#[cfg(not(feature = "fips"))]
231fn write_hex(writer: &mut impl Write, bytes: &[u8]) -> io::Result<()> {
232 for byte in bytes {
233 write!(writer, "{byte:02x}")?;
234 }
235
236 Ok(())
237}
238
239pub struct ClientTLSConfigBuilder {
248 key_log_file_path: Option<PathBuf>,
249 max_tls12_resumption_sessions: Option<usize>,
250 min_tls_version: TlsMinimumVersion,
251 root_cert_store: Option<RootCertStore>,
252 danger_accept_invalid_certs: bool,
253}
254
255impl ClientTLSConfigBuilder {
256 pub fn new() -> Self {
257 Self {
258 key_log_file_path: None,
259 max_tls12_resumption_sessions: None,
260 min_tls_version: TlsMinimumVersion::default(),
261 root_cert_store: None,
262 danger_accept_invalid_certs: false,
263 }
264 }
265
266 pub fn with_key_log_file<P: Into<PathBuf>>(mut self, path: P) -> Self {
277 self.key_log_file_path = Some(path.into());
278 self
279 }
280
281 pub fn with_max_tls12_resumption_sessions(mut self, max: usize) -> Self {
285 self.max_tls12_resumption_sessions = Some(max);
286 self
287 }
288
289 pub fn with_root_cert_store(mut self, store: RootCertStore) -> Self {
293 self.root_cert_store = Some(store);
294 self
295 }
296
297 pub fn with_min_tls_version(mut self, version: TlsMinimumVersion) -> Self {
301 self.min_tls_version = version;
302 self
303 }
304
305 pub fn danger_accept_invalid_certs(mut self) -> Self {
311 self.danger_accept_invalid_certs = true;
312 self
313 }
314
315 pub fn build(self) -> Result<ClientConfig, GenericError> {
323 let max_tls12_resumption_sessions = self
324 .max_tls12_resumption_sessions
325 .unwrap_or(DEFAULT_MAX_TLS12_RESUMPTION_SESSIONS);
326 let protocol_versions = self.min_tls_version.protocol_versions();
327
328 let mut config = if self.danger_accept_invalid_certs {
329 let crypto_provider = CryptoProvider::get_default()
330 .map(Arc::clone)
331 .ok_or_else(|| generic_error!("Default cryptography provider not yet installed."))?;
332 let verifier = Arc::new(AcceptAllServerCertVerifier {
333 provider: crypto_provider,
334 });
335
336 ClientConfig::builder_with_protocol_versions(protocol_versions)
337 .dangerous()
338 .with_custom_certificate_verifier(verifier)
339 .with_no_client_auth()
340 } else {
341 let root_cert_store = self.root_cert_store.map(Arc::new).map(Ok).unwrap_or_else(|| {
342 DEFAULT_ROOT_CERT_STORE
343 .get()
344 .map(Arc::clone)
345 .ok_or(generic_error!("Default TLS root certificate store not initialized."))
346 })?;
347
348 ClientConfig::builder_with_protocol_versions(protocol_versions)
349 .with_root_certificates(root_cert_store)
350 .with_no_client_auth()
351 };
352
353 if let Some(path) = self.key_log_file_path {
354 #[cfg(feature = "fips")]
355 fips_key_log_warn_once(path);
356
357 #[cfg(not(feature = "fips"))]
358 if let Some(key_log) = NssKeyLogFile::open_shared(path) {
359 config.key_log = key_log;
360 }
361 }
362
363 config.resumption = Resumption::in_memory_sessions(max_tls12_resumption_sessions);
367
368 ensure_client_config_fips_compliant(&config)?;
369
370 Ok(config)
371 }
372}
373
374pub fn ensure_client_config_fips_compliant(config: &ClientConfig) -> Result<(), GenericError> {
382 #[cfg(feature = "fips")]
383 if !config.fips() {
384 return Err(generic_error!("Client TLS configuration is not FIPS compliant."));
385 }
386
387 #[cfg(not(feature = "fips"))]
388 let _ = config;
389
390 Ok(())
391}
392
393pub fn ensure_server_config_fips_compliant(config: &mut ServerConfig) -> Result<(), GenericError> {
402 #[cfg(feature = "fips")]
403 {
404 config.key_log = Arc::new(rustls::NoKeyLog);
405 config.enable_secret_extraction = false;
406
407 if !config.fips() {
408 return Err(generic_error!("Server TLS configuration is not FIPS compliant."));
409 }
410 }
411
412 #[cfg(not(feature = "fips"))]
413 let _ = config;
414
415 Ok(())
416}
417
418pub fn initialize_default_crypto_provider() -> Result<(), GenericError> {
427 if DEFAULT_CRYPTO_PROVIDER_SET.get().is_some() {
428 return Err(generic_error!("Default TLS cryptography provider already initialized."));
429 }
430
431 default_crypto_provider().install_default().map_err(|_| {
432 generic_error!(
433 "Failed to install the default TLS cryptography provider. This is likely due to a conflicting provider already being installed."
434 )
435 })?;
436
437 DEFAULT_CRYPTO_PROVIDER_SET
439 .set(())
440 .expect("should be impossible for DEFAULT_CRYPTO_PROVIDER_SET to be initialized twice");
441
442 Ok(())
443}
444
445#[cfg(not(windows))]
446fn default_crypto_provider() -> CryptoProvider {
447 let provider = rustls::crypto::aws_lc_rs::default_provider();
448
449 #[cfg(feature = "fips")]
450 {
451 let mut provider = provider;
452 provider.cipher_suites.retain(|suite| suite.fips());
453 provider.kx_groups.retain(|group| group.fips());
454 provider
455 }
456
457 #[cfg(not(feature = "fips"))]
458 provider
459}
460
461#[cfg(windows)]
462fn default_crypto_provider() -> CryptoProvider {
463 rustls_cng_crypto::default_provider()
464}
465
466pub fn load_platform_root_certificates() -> Result<(), GenericError> {
533 let _guard = DEFAULT_ROOT_CERT_STORE_MUTEX
534 .lock()
535 .map_err(|_| generic_error!("Default TLS root certificate store update lock poisoned."))?;
536 if DEFAULT_ROOT_CERT_STORE.get().is_some() {
537 return Err(generic_error!(
538 "Default TLS root certificate store already initialized."
539 ));
540 }
541
542 let root_cert_store = load_platform_root_certificates_inner()?;
543
544 DEFAULT_ROOT_CERT_STORE
547 .set(Arc::new(root_cert_store))
548 .expect("should be impossible for DEFAULT_ROOT_CERT_STORE to be initialized twice");
549
550 Ok(())
551}
552
553pub fn load_platform_root_certificates_inner() -> Result<RootCertStore, GenericError> {
565 let mut root_cert_store = RootCertStore::empty();
566
567 let mut result = rustls_native_certs::load_native_certs();
568
569 result.errors.retain(|err| match &err.kind {
576 rustls_native_certs::ErrorKind::Io { inner, path }
577 if matches!(
578 inner.kind(),
579 std::io::ErrorKind::NotFound | std::io::ErrorKind::PermissionDenied
580 ) =>
581 {
582 debug!(
583 error = %inner,
584 path = %path.display(),
585 "Skipping missing or unreadable certificate source while loading platform root certificates."
586 );
587 false
588 }
589 _ => true,
590 });
591
592 let (added, failed) = root_cert_store.add_parsable_certificates(result.certs);
594 if failed == 0 && added > 0 {
595 debug!(
596 "Added {} certificates from environment to the default root certificate store.",
597 added
598 );
599 } else if failed > 0 && added > 0 {
600 debug!("Added {} certificates from environment to the default root certificate store, but failed to add {} certificates.", added, failed);
601 } else {
602 if !result.errors.is_empty() {
610 let joined_errors = result
611 .errors
612 .iter()
613 .map(|e| e.to_string())
614 .collect::<Vec<_>>()
615 .join(", ");
616
617 return Err(generic_error!(
618 "Failed to load certificates from platform's native certificate store: {}",
619 joined_errors
620 ));
621 }
622 }
623
624 Ok(root_cert_store)
625}
626
627#[cfg(test)]
628mod tests {
629 #[cfg(not(feature = "fips"))]
630 use std::fs;
631 #[cfg(all(unix, not(feature = "fips")))]
632 use std::os::unix::fs::PermissionsExt;
633 use std::path::Path;
634 #[cfg(feature = "fips")]
635 use std::sync::{
636 atomic::{AtomicBool, Ordering},
637 Arc,
638 };
639
640 #[cfg(feature = "fips")]
641 use rustls::KeyLog;
642 use rustls::{
643 client::danger::ServerCertVerifier,
644 crypto::CryptoProvider,
645 pki_types::{ServerName, UnixTime},
646 ClientConfig, ProtocolVersion, RootCertStore, ServerConfig,
647 };
648
649 use super::test_util::SelfSignedCert;
650 #[cfg(not(feature = "fips"))]
651 use super::{build_nss_key_log_line, open_key_log_file};
652 use super::{
653 ensure_client_config_fips_compliant, ensure_server_config_fips_compliant, AcceptAllServerCertVerifier,
654 ClientTLSConfigBuilder, TlsMinimumVersion,
655 };
656
657 fn client_config_with_key_log(path: &Path) -> ClientConfig {
660 let _ = super::initialize_default_crypto_provider();
661
662 ClientTLSConfigBuilder::new()
663 .with_root_cert_store(RootCertStore::empty())
664 .with_key_log_file(path)
665 .build()
666 .expect("client TLS config should build")
667 }
668
669 fn temp_file_path(file_name: &str) -> (tempfile::TempDir, std::path::PathBuf) {
671 let tempdir = tempfile::tempdir().expect("temporary directory should be created");
672 let path = tempdir.path().join(file_name);
673 (tempdir, path)
674 }
675
676 #[test]
677 fn tls12_minimum_enables_tls12_and_tls13() {
678 let versions = TlsMinimumVersion::Tls12.protocol_versions();
679
680 assert_eq!(versions.len(), 2);
681 assert_eq!(versions[0].version, ProtocolVersion::TLSv1_3);
682 assert_eq!(versions[1].version, ProtocolVersion::TLSv1_2);
683 }
684
685 #[test]
686 fn tls13_minimum_enables_tls13_only() {
687 let versions = TlsMinimumVersion::Tls13.protocol_versions();
688
689 assert_eq!(versions.len(), 1);
690 assert_eq!(versions[0].version, ProtocolVersion::TLSv1_3);
691 }
692
693 #[test]
694 fn client_config_fips_validation_accepts_builder_config() {
695 let _ = super::initialize_default_crypto_provider();
696
697 let config = ClientTLSConfigBuilder::new()
698 .with_root_cert_store(RootCertStore::empty())
699 .build()
700 .expect("client TLS config should build");
701
702 ensure_client_config_fips_compliant(&config).expect("client TLS config should pass FIPS validation");
703 }
704
705 #[test]
706 fn accept_all_verifier_accepts_mismatched_server_certificate() {
707 let _ = super::initialize_default_crypto_provider();
711 let provider = CryptoProvider::get_default()
712 .cloned()
713 .expect("default crypto provider should be installed");
714 let verifier = AcceptAllServerCertVerifier { provider };
715
716 let cert = SelfSignedCert::new(["localhost"]);
717 let cert_chain = cert.cert_chain();
718 let server_name = ServerName::try_from("totally.different.example").expect("server name should parse");
719
720 let result = verifier.verify_server_cert(&cert_chain[0], &[], &server_name, &[], UnixTime::now());
721
722 assert!(
723 result.is_ok(),
724 "accept-all verifier must accept a certificate presented for a mismatched server name"
725 );
726 }
727
728 #[test]
729 fn danger_accept_invalid_certs_builds_without_root_cert_store() {
730 let _ = super::initialize_default_crypto_provider();
731
732 let missing_store_error = ClientTLSConfigBuilder::new()
735 .build()
736 .expect_err("client TLS config should fail to build without any root cert store");
737 assert!(
738 missing_store_error
739 .to_string()
740 .contains("root certificate store not initialized"),
741 "unexpected error: {missing_store_error}"
742 );
743
744 ClientTLSConfigBuilder::new()
747 .danger_accept_invalid_certs()
748 .build()
749 .expect("client TLS config should build with an accept-all verifier and no root cert store");
750 }
751
752 #[test]
753 fn server_config_fips_validation_accepts_basic_server_config() {
754 let _ = super::initialize_default_crypto_provider();
755 let cert = SelfSignedCert::localhost();
756 let mut config = ServerConfig::builder()
757 .with_no_client_auth()
758 .with_single_cert(cert.cert_chain(), cert.private_key())
759 .expect("server TLS config should build");
760
761 ensure_server_config_fips_compliant(&mut config).expect("server TLS config should pass FIPS validation");
762 }
763
764 #[cfg(feature = "fips")]
765 #[test]
766 fn server_config_fips_validation_disables_secret_extraction() {
767 #[derive(Debug)]
768 struct TestKeyLog(Arc<AtomicBool>);
769
770 impl KeyLog for TestKeyLog {
771 fn log(&self, _label: &str, _client_random: &[u8], _secret: &[u8]) {
772 self.0.store(true, Ordering::Relaxed);
773 }
774 }
775
776 let _ = super::initialize_default_crypto_provider();
777 let cert = SelfSignedCert::localhost();
778 let key_log_used = Arc::new(AtomicBool::new(false));
779 let mut config = ServerConfig::builder()
780 .with_no_client_auth()
781 .with_single_cert(cert.cert_chain(), cert.private_key())
782 .expect("server TLS config should build");
783 config.key_log = Arc::new(TestKeyLog(Arc::clone(&key_log_used)));
784 config.enable_secret_extraction = true;
785
786 ensure_server_config_fips_compliant(&mut config).expect("server TLS config should pass FIPS validation");
787
788 config.key_log.log("CLIENT_RANDOM", &[0xab, 0xcd], &[0x01, 0x23]);
789
790 assert!(!key_log_used.load(Ordering::Relaxed));
791 assert!(!config.enable_secret_extraction);
792 }
793
794 #[cfg(all(feature = "fips", not(windows)))]
799 #[test]
800 fn client_config_fips_validation_rejects_non_fips_config() {
801 let provider = Arc::new(rustls::crypto::aws_lc_rs::default_provider());
802 let config = ClientConfig::builder_with_provider(provider)
803 .with_protocol_versions(&[&rustls::version::TLS13, &rustls::version::TLS12])
804 .expect("client config builder should accept protocol versions")
805 .with_root_certificates(RootCertStore::empty())
806 .with_no_client_auth();
807
808 let error =
809 ensure_client_config_fips_compliant(&config).expect_err("a non-FIPS client configuration must be rejected");
810 assert!(
811 error.to_string().contains("not FIPS compliant"),
812 "unexpected error: {error}"
813 );
814 }
815
816 #[cfg(all(feature = "fips", not(windows)))]
817 #[test]
818 fn server_config_fips_validation_rejects_non_fips_config() {
819 let cert = SelfSignedCert::localhost();
820 let provider = Arc::new(rustls::crypto::aws_lc_rs::default_provider());
821 let mut config = ServerConfig::builder_with_provider(provider)
822 .with_protocol_versions(&[&rustls::version::TLS13, &rustls::version::TLS12])
823 .expect("server config builder should accept protocol versions")
824 .with_no_client_auth()
825 .with_single_cert(cert.cert_chain(), cert.private_key())
826 .expect("server TLS config should build");
827
828 let error = ensure_server_config_fips_compliant(&mut config)
829 .expect_err("a non-FIPS server configuration must be rejected");
830 assert!(
831 error.to_string().contains("not FIPS compliant"),
832 "unexpected error: {error}"
833 );
834 }
835
836 #[test]
837 #[cfg(not(feature = "fips"))]
838 fn nss_key_log_lines_are_written_in_hex_format() {
839 let output =
840 build_nss_key_log_line("CLIENT_RANDOM", &[0xab, 0xcd], &[0x01, 0x23]).expect("key log line should build");
841
842 assert_eq!(output, b"CLIENT_RANDOM abcd 0123\n");
843 }
844
845 #[test]
846 #[cfg(not(feature = "fips"))]
847 fn client_config_uses_configured_key_log_file() {
848 let (_tempdir, key_log_path) = temp_file_path("sslkeylogfile");
849
850 let config = client_config_with_key_log(&key_log_path);
851 config.key_log.log("CLIENT_RANDOM", &[0xab, 0xcd], &[0x01, 0x23]);
852
853 let contents = fs::read_to_string(&key_log_path).expect("key log file should be readable");
854 assert_eq!(contents, "CLIENT_RANDOM abcd 0123\n");
855 }
856
857 #[test]
858 #[cfg(not(feature = "fips"))]
859 fn client_config_ignores_unwritable_key_log_file() {
860 let (_tempdir, key_log_path) = temp_file_path("missing/sslkeylogfile");
863
864 let config = client_config_with_key_log(&key_log_path);
865 config.key_log.log("CLIENT_RANDOM", &[0xab, 0xcd], &[0x01, 0x23]);
866
867 assert!(!key_log_path.exists());
868 }
869
870 #[test]
871 #[cfg(not(feature = "fips"))]
872 fn client_configs_append_to_shared_key_log_file() {
873 let (_tempdir, key_log_path) = temp_file_path("shared-sslkeylogfile");
874
875 let first_config = client_config_with_key_log(&key_log_path);
876 let second_config = client_config_with_key_log(&key_log_path);
877
878 first_config.key_log.log("CLIENT_RANDOM", &[0xab, 0xcd], &[0x01, 0x23]);
879 second_config.key_log.log("CLIENT_RANDOM", &[0xef, 0x01], &[0x45, 0x67]);
880
881 let contents = fs::read_to_string(&key_log_path).expect("key log file should be readable");
882 assert_eq!(contents, "CLIENT_RANDOM abcd 0123\nCLIENT_RANDOM ef01 4567\n");
883 }
884
885 #[cfg(all(unix, not(feature = "fips")))]
886 #[test]
887 fn key_log_file_is_created_with_owner_only_permissions() {
888 let tempdir = tempfile::tempdir().expect("temporary directory should be created");
889 let key_log_path = tempdir.path().join("sslkeylogfile");
890
891 let file = open_key_log_file(&key_log_path).expect("key log file should open");
892 drop(file);
893
894 let mode = fs::metadata(&key_log_path)
895 .expect("key log file metadata should be readable")
896 .permissions()
897 .mode()
898 & 0o777;
899 assert_eq!(mode, 0o600);
900 }
901
902 #[cfg(windows)]
903 #[test]
904 fn windows_default_crypto_provider_builds_client_config() {
905 let _ = super::initialize_default_crypto_provider();
906
907 ClientTLSConfigBuilder::new()
908 .with_root_cert_store(RootCertStore::empty())
909 .build()
910 .expect("Windows CNG-backed TLS config should build");
911 }
912
913 #[test]
914 #[cfg(feature = "fips")]
915 fn key_log_file_ignored_in_fips_mode() {
916 let (_tempdir, key_log_path) = temp_file_path("fips-sslkeylogfile");
919
920 let config = client_config_with_key_log(&key_log_path);
921
922 config.key_log.log("CLIENT_RANDOM", &[0xab, 0xcd], &[0x01, 0x23]);
924
925 assert!(!key_log_path.exists(), "FIPS builds must not create a TLS key log file");
926 }
927}