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