1#[cfg(any(feature = "tls-rustls", feature = "tls-native"))]
51use crate::error::{RepError, Result};
52
53#[derive(Clone)]
71#[non_exhaustive]
72pub enum TlsIdentity {
73 SelfSigned {
79 subject_alt_names: Vec<String>,
82 },
83
84 PemFiles {
88 cert: std::path::PathBuf,
90 key: std::path::PathBuf,
92 },
93
94 PemBytes {
98 cert: Vec<u8>,
100 key: Vec<u8>,
102 },
103
104 Pkcs12 {
113 der: Vec<u8>,
115 password: String,
117 },
118}
119
120#[derive(Clone)]
124#[non_exhaustive]
125pub enum TrustedCerts {
126 SkipVerification,
131
132 CaFiles(Vec<std::path::PathBuf>),
134
135 CaBytes(Vec<Vec<u8>>),
137}
138
139#[derive(Clone)]
151pub struct TlsConfig {
152 pub identity: TlsIdentity,
154 pub trusted_certs: TrustedCerts,
156 pub server_name: String,
162}
163
164impl TlsConfig {
165 pub fn insecure(server_name: impl Into<String>) -> Self {
173 TlsConfig {
174 identity: TlsIdentity::SelfSigned {
175 subject_alt_names: vec!["localhost".into()],
176 },
177 trusted_certs: TrustedCerts::SkipVerification,
178 server_name: server_name.into(),
179 }
180 }
181
182 pub fn from_pem_files(
187 cert: impl Into<std::path::PathBuf>,
188 key: impl Into<std::path::PathBuf>,
189 ca: impl Into<std::path::PathBuf>,
190 server_name: impl Into<String>,
191 ) -> Self {
192 TlsConfig {
193 identity: TlsIdentity::PemFiles {
194 cert: cert.into(),
195 key: key.into(),
196 },
197 trusted_certs: TrustedCerts::CaFiles(vec![ca.into()]),
198 server_name: server_name.into(),
199 }
200 }
201
202 pub fn from_pkcs12(
207 der: Vec<u8>,
208 password: impl Into<String>,
209 ca_pem: Vec<u8>,
210 server_name: impl Into<String>,
211 ) -> Self {
212 TlsConfig {
213 identity: TlsIdentity::Pkcs12 { der, password: password.into() },
214 trusted_certs: TrustedCerts::CaBytes(vec![ca_pem]),
215 server_name: server_name.into(),
216 }
217 }
218}
219
220#[cfg(feature = "tls-rustls")]
223impl TlsConfig {
224 pub(crate) fn to_rustls_server_config(
228 &self,
229 ) -> Result<std::sync::Arc<rustls::ServerConfig>> {
230 let (certs, key) = self.rustls_cert_and_key()?;
231
232 let cfg = rustls::ServerConfig::builder()
233 .with_no_client_auth()
234 .with_single_cert(certs, key)
235 .map_err(|e| {
236 RepError::NetworkError(format!("TLS server config: {e}"))
237 })?;
238 Ok(std::sync::Arc::new(cfg))
239 }
240
241 pub(crate) fn to_rustls_client_config(
245 &self,
246 ) -> Result<std::sync::Arc<rustls::ClientConfig>> {
247 if matches!(&self.trusted_certs, TrustedCerts::SkipVerification) {
248 let cfg = rustls::ClientConfig::builder()
249 .dangerous()
250 .with_custom_certificate_verifier(std::sync::Arc::new(
251 SkipCertVerification::new(),
252 ))
253 .with_no_client_auth();
254 return Ok(std::sync::Arc::new(cfg));
255 }
256
257 let root_store = self.rustls_root_store()?;
258 let cfg = rustls::ClientConfig::builder()
259 .with_root_certificates(root_store)
260 .with_no_client_auth();
261 Ok(std::sync::Arc::new(cfg))
262 }
263
264 #[cfg(feature = "quic")]
276 pub fn to_quinn_server_config(&self) -> Result<quinn::ServerConfig> {
277 let rustls_cfg = self.to_rustls_server_config()?;
278 let quic_cfg = quinn::crypto::rustls::QuicServerConfig::try_from(
279 rustls::ServerConfig::clone(&rustls_cfg),
280 )
281 .map_err(|e| {
282 RepError::NetworkError(format!("QUIC server config: {e}"))
283 })?;
284 let mut cfg =
285 quinn::ServerConfig::with_crypto(std::sync::Arc::new(quic_cfg));
286 let mut transport = quinn::TransportConfig::default();
287 transport.mtu_discovery_config(None);
288 transport.datagram_receive_buffer_size(Some(64 * 1024));
289 cfg.transport_config(std::sync::Arc::new(transport));
290 Ok(cfg)
291 }
292
293 #[cfg(feature = "quic")]
298 pub fn to_quinn_client_config(&self) -> Result<quinn::ClientConfig> {
299 let rustls_cfg = self.to_rustls_client_config()?;
300 let quic_cfg = quinn::crypto::rustls::QuicClientConfig::try_from(
301 rustls::ClientConfig::clone(&rustls_cfg),
302 )
303 .map_err(|e| {
304 RepError::NetworkError(format!("QUIC client config: {e}"))
305 })?;
306 let mut cfg = quinn::ClientConfig::new(std::sync::Arc::new(quic_cfg));
307 let mut transport = quinn::TransportConfig::default();
308 transport.mtu_discovery_config(None);
309 transport.datagram_receive_buffer_size(Some(64 * 1024));
310 cfg.transport_config(std::sync::Arc::new(transport));
311 Ok(cfg)
312 }
313
314 fn rustls_cert_and_key(
317 &self,
318 ) -> Result<(
319 Vec<rustls::pki_types::CertificateDer<'static>>,
320 rustls::pki_types::PrivateKeyDer<'static>,
321 )> {
322 use rustls::pki_types::{
323 CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer,
324 };
325
326 match &self.identity {
327 TlsIdentity::SelfSigned { subject_alt_names } => {
328 let ck = rcgen::generate_simple_self_signed(
329 subject_alt_names.clone(),
330 )
331 .map_err(|e| RepError::NetworkError(format!("rcgen: {e}")))?;
332 let cert = CertificateDer::from(ck.cert.der().to_vec());
333 let key = PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from(
334 ck.key_pair.serialize_der(),
335 ));
336 Ok((vec![cert], key))
337 }
338 TlsIdentity::PemFiles { cert, key } => {
339 let cert_bytes = std::fs::read(cert).map_err(|e| {
340 RepError::NetworkError(format!("cert file: {e}"))
341 })?;
342 let key_bytes = std::fs::read(key).map_err(|e| {
343 RepError::NetworkError(format!("key file: {e}"))
344 })?;
345 Self::parse_pem_cert_and_key(&cert_bytes, &key_bytes)
346 }
347 TlsIdentity::PemBytes { cert, key } => {
348 Self::parse_pem_cert_and_key(cert, key)
349 }
350 TlsIdentity::Pkcs12 { .. } => Err(RepError::NetworkError(
351 "Pkcs12 identity is not supported by the tls-rustls backend; \
352 use PemFiles or PemBytes instead"
353 .into(),
354 )),
355 }
356 }
357
358 fn parse_pem_cert_and_key(
359 cert_pem: &[u8],
360 key_pem: &[u8],
361 ) -> Result<(
362 Vec<rustls::pki_types::CertificateDer<'static>>,
363 rustls::pki_types::PrivateKeyDer<'static>,
364 )> {
365 use rustls_pemfile::{certs, private_key};
366 use std::io::BufReader;
367
368 let cert_chain: Vec<_> = certs(&mut BufReader::new(cert_pem))
369 .collect::<std::result::Result<_, _>>()
370 .map_err(|e| RepError::NetworkError(format!("cert parse: {e}")))?;
371 if cert_chain.is_empty() {
372 return Err(RepError::NetworkError(
373 "no certificates found in PEM".into(),
374 ));
375 }
376
377 let key = private_key(&mut BufReader::new(key_pem))
378 .map_err(|e| RepError::NetworkError(format!("key parse: {e}")))?
379 .ok_or_else(|| {
380 RepError::NetworkError("no private key found in PEM".into())
381 })?;
382
383 Ok((cert_chain, key))
384 }
385
386 fn rustls_root_store(&self) -> Result<rustls::RootCertStore> {
387 use rustls_pemfile::certs;
388 use std::io::BufReader;
389
390 let mut store = rustls::RootCertStore::empty();
391
392 match &self.trusted_certs {
393 TrustedCerts::SkipVerification => {
394 }
399 TrustedCerts::CaFiles(paths) => {
400 if paths.is_empty() {
404 return Err(RepError::ConfigError(
405 "TrustedCerts::CaFiles configured with no paths; \
406 this is a misconfiguration. Use \
407 TrustedCerts::SkipVerification to explicitly opt \
408 out of CA verification."
409 .into(),
410 ));
411 }
412 for path in paths {
413 let pem = std::fs::read(path).map_err(|e| {
414 RepError::NetworkError(format!("CA file: {e}"))
415 })?;
416 let parsed: Vec<_> =
417 certs(&mut BufReader::new(pem.as_slice()))
418 .collect::<std::result::Result<Vec<_>, _>>()
419 .map_err(|e| {
420 RepError::NetworkError(format!("CA parse: {e}"))
421 })?;
422 if !pem.is_empty() && parsed.is_empty() {
427 return Err(RepError::ConfigError(format!(
428 "CA file {} parsed but contained 0 certificates",
429 path.display()
430 )));
431 }
432 for cert in parsed {
433 store.add(cert).map_err(|e| {
434 RepError::NetworkError(format!("CA add: {e}"))
435 })?;
436 }
437 }
438 }
439 TrustedCerts::CaBytes(pems) => {
440 if pems.is_empty() {
443 return Err(RepError::ConfigError(
444 "TrustedCerts::CaBytes configured with no PEM blobs; \
445 this is a misconfiguration. Use \
446 TrustedCerts::SkipVerification to explicitly opt \
447 out of CA verification."
448 .into(),
449 ));
450 }
451 for (idx, pem) in pems.iter().enumerate() {
452 let parsed: Vec<_> =
453 certs(&mut BufReader::new(pem.as_slice()))
454 .collect::<std::result::Result<Vec<_>, _>>()
455 .map_err(|e| {
456 RepError::NetworkError(format!("CA parse: {e}"))
457 })?;
458 if !pem.is_empty() && parsed.is_empty() {
460 return Err(RepError::ConfigError(format!(
461 "CA bytes (index {idx}) parsed but contained 0 \
462 certificates"
463 )));
464 }
465 for cert in parsed {
466 store.add(cert).map_err(|e| {
467 RepError::NetworkError(format!("CA add: {e}"))
468 })?;
469 }
470 }
471 }
472 }
473
474 Ok(store)
475 }
476}
477
478#[cfg(feature = "tls-rustls")]
486#[derive(Debug)]
487pub(crate) struct SkipCertVerification(
488 std::sync::Arc<rustls::crypto::CryptoProvider>,
489);
490
491#[cfg(feature = "tls-rustls")]
492impl SkipCertVerification {
493 pub(crate) fn new() -> Self {
494 Self(std::sync::Arc::new(rustls::crypto::ring::default_provider()))
495 }
496}
497
498#[cfg(feature = "tls-rustls")]
499impl rustls::client::danger::ServerCertVerifier for SkipCertVerification {
500 fn verify_server_cert(
501 &self,
502 _end_entity: &rustls::pki_types::CertificateDer<'_>,
503 _intermediates: &[rustls::pki_types::CertificateDer<'_>],
504 _server_name: &rustls::pki_types::ServerName<'_>,
505 _ocsp_response: &[u8],
506 _now: rustls::pki_types::UnixTime,
507 ) -> std::result::Result<
508 rustls::client::danger::ServerCertVerified,
509 rustls::Error,
510 > {
511 Ok(rustls::client::danger::ServerCertVerified::assertion())
512 }
513
514 fn verify_tls12_signature(
515 &self,
516 message: &[u8],
517 cert: &rustls::pki_types::CertificateDer<'_>,
518 dss: &rustls::DigitallySignedStruct,
519 ) -> std::result::Result<
520 rustls::client::danger::HandshakeSignatureValid,
521 rustls::Error,
522 > {
523 rustls::crypto::verify_tls12_signature(
524 message,
525 cert,
526 dss,
527 &self.0.signature_verification_algorithms,
528 )
529 }
530
531 fn verify_tls13_signature(
532 &self,
533 message: &[u8],
534 cert: &rustls::pki_types::CertificateDer<'_>,
535 dss: &rustls::DigitallySignedStruct,
536 ) -> std::result::Result<
537 rustls::client::danger::HandshakeSignatureValid,
538 rustls::Error,
539 > {
540 rustls::crypto::verify_tls13_signature(
541 message,
542 cert,
543 dss,
544 &self.0.signature_verification_algorithms,
545 )
546 }
547
548 fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
549 self.0.signature_verification_algorithms.supported_schemes()
550 }
551}
552
553#[cfg(feature = "tls-native")]
556impl TlsConfig {
557 pub(crate) fn to_native_acceptor(&self) -> Result<native_tls::TlsAcceptor> {
564 let mtls_intent = match &self.trusted_certs {
573 TrustedCerts::CaFiles(v) => !v.is_empty(),
574 TrustedCerts::CaBytes(v) => !v.is_empty(),
575 TrustedCerts::SkipVerification => false,
576 };
577 if mtls_intent {
578 return Err(RepError::ConfigError(
579 "mTLS is configured (TrustedCerts has CA roots) but the \
580 tls-native server transport does not support it: \
581 native_tls::TlsAcceptorBuilder exposes no client-cert \
582 verification knobs. Use the tls-rustls feature for mTLS, \
583 or set TrustedCerts::SkipVerification on this transport."
584 .into(),
585 ));
586 }
587 let identity = self.native_identity()?;
588 let builder = native_tls::TlsAcceptor::builder(identity);
589 builder
590 .build()
591 .map_err(|e| RepError::NetworkError(format!("TLS acceptor: {e}")))
592 }
593
594 pub(crate) fn to_native_connector(
596 &self,
597 ) -> Result<native_tls::TlsConnector> {
598 let mut builder = native_tls::TlsConnector::builder();
599
600 if !matches!(&self.identity, TlsIdentity::SelfSigned { .. }) {
602 let id = self.native_identity()?;
603 builder.identity(id);
604 }
605
606 self.apply_native_trust(&mut builder)?;
607 builder
608 .build()
609 .map_err(|e| RepError::NetworkError(format!("TLS connector: {e}")))
610 }
611
612 fn native_identity(&self) -> Result<native_tls::Identity> {
613 match &self.identity {
614 TlsIdentity::Pkcs12 { der, password } => native_tls::Identity::from_pkcs12(der, password)
615 .map_err(|e| RepError::NetworkError(format!("PKCS12 identity: {e}"))),
616 TlsIdentity::SelfSigned { .. } => Err(RepError::NetworkError(
617 "SelfSigned identity is not supported by the tls-native backend; \
618 use the tls-rustls feature instead, or supply a Pkcs12 identity"
619 .into(),
620 )),
621 TlsIdentity::PemFiles { .. } | TlsIdentity::PemBytes { .. } => {
622 Err(RepError::NetworkError(
623 "PEM identities are not supported by the tls-native backend; \
624 convert to PKCS12 with: openssl pkcs12 -export -out id.p12 \
625 -inkey key.pem -in cert.pem"
626 .into(),
627 ))
628 }
629 }
630 }
631
632 fn apply_native_trust(
633 &self,
634 builder: &mut native_tls::TlsConnectorBuilder,
635 ) -> Result<()> {
636 match &self.trusted_certs {
637 TrustedCerts::SkipVerification => {
638 builder.danger_accept_invalid_certs(true);
639 }
640 TrustedCerts::CaFiles(paths) => {
641 for path in paths {
642 let pem = std::fs::read(path).map_err(|e| {
643 RepError::NetworkError(format!("CA file: {e}"))
644 })?;
645 let cert = native_tls::Certificate::from_pem(&pem)
646 .map_err(|e| {
647 RepError::NetworkError(format!("CA parse: {e}"))
648 })?;
649 builder.add_root_certificate(cert);
650 }
651 }
652 TrustedCerts::CaBytes(pems) => {
653 for pem in pems {
654 let cert = native_tls::Certificate::from_pem(pem).map_err(
655 |e| RepError::NetworkError(format!("CA parse: {e}")),
656 )?;
657 builder.add_root_certificate(cert);
658 }
659 }
660 }
661 Ok(())
662 }
663}
664
665#[cfg(test)]
675mod tests {
676 use super::*;
677
678 #[test]
681 fn insecure_constructor_uses_self_signed_localhost() {
682 let cfg = TlsConfig::insecure("node-a");
683 assert_eq!(cfg.server_name, "node-a");
684 match cfg.identity {
685 TlsIdentity::SelfSigned { subject_alt_names } => {
686 assert_eq!(subject_alt_names, vec!["localhost".to_string()]);
687 }
688 _ => panic!("insecure should produce SelfSigned identity"),
689 }
690 assert!(matches!(cfg.trusted_certs, TrustedCerts::SkipVerification));
691 }
692
693 #[test]
694 fn from_pem_files_constructor_records_paths() {
695 let cfg = TlsConfig::from_pem_files(
696 "/tmp/cert.pem",
697 "/tmp/key.pem",
698 "/tmp/ca.pem",
699 "node-b",
700 );
701 assert_eq!(cfg.server_name, "node-b");
702 match cfg.identity {
703 TlsIdentity::PemFiles { cert, key } => {
704 assert_eq!(cert, std::path::PathBuf::from("/tmp/cert.pem"));
705 assert_eq!(key, std::path::PathBuf::from("/tmp/key.pem"));
706 }
707 _ => panic!("from_pem_files should produce PemFiles identity"),
708 }
709 match cfg.trusted_certs {
710 TrustedCerts::CaFiles(paths) => {
711 assert_eq!(
712 paths,
713 vec![std::path::PathBuf::from("/tmp/ca.pem")]
714 );
715 }
716 _ => panic!("from_pem_files should produce CaFiles trust"),
717 }
718 }
719
720 #[test]
721 fn from_pkcs12_constructor_holds_bytes_and_password() {
722 let der = vec![0x30, 0x82, 0x00, 0x10]; let ca_pem = b"-----BEGIN CERTIFICATE-----\n".to_vec();
724 let cfg = TlsConfig::from_pkcs12(
725 der.clone(),
726 "secret".to_string(),
727 ca_pem.clone(),
728 "node-c",
729 );
730 assert_eq!(cfg.server_name, "node-c");
731 match cfg.identity {
732 TlsIdentity::Pkcs12 { der: d, password } => {
733 assert_eq!(d, der);
734 assert_eq!(password, "secret");
735 }
736 _ => panic!("from_pkcs12 should produce Pkcs12 identity"),
737 }
738 match cfg.trusted_certs {
739 TrustedCerts::CaBytes(pems) => {
740 assert_eq!(pems, vec![ca_pem]);
741 }
742 _ => panic!("from_pkcs12 should produce CaBytes trust"),
743 }
744 }
745
746 #[cfg(feature = "tls-rustls")]
749 #[test]
750 fn rustls_server_config_from_self_signed_succeeds() {
751 let cfg = TlsConfig::insecure("node-self");
755 let sc = cfg.to_rustls_server_config();
756 assert!(
757 sc.is_ok(),
758 "to_rustls_server_config from insecure() should succeed: {:?}",
759 sc.err()
760 );
761 }
762
763 #[cfg(feature = "tls-rustls")]
764 #[test]
765 fn rustls_client_config_skip_verification_succeeds() {
766 let cfg = TlsConfig::insecure("any-name");
770 let cc = cfg.to_rustls_client_config();
771 assert!(
772 cc.is_ok(),
773 "to_rustls_client_config with SkipVerification should succeed: \
774 {:?}",
775 cc.err()
776 );
777 }
778
779 #[cfg(feature = "tls-rustls")]
780 #[test]
781 fn rustls_client_config_with_empty_ca_bytes_errors() {
782 let cfg = TlsConfig {
786 identity: TlsIdentity::SelfSigned {
787 subject_alt_names: vec!["localhost".into()],
788 },
789 trusted_certs: TrustedCerts::CaBytes(vec![]),
790 server_name: "x".into(),
791 };
792 let cc = cfg.to_rustls_client_config();
793 assert!(
794 cc.is_err(),
795 "empty CaBytes must be a misconfiguration error, got Ok"
796 );
797 let msg = format!("{}", cc.err().unwrap());
798 assert!(
799 msg.contains("CaBytes") && msg.contains("misconfiguration"),
800 "error should mention CaBytes/misconfiguration, got: {msg}"
801 );
802 }
803
804 #[cfg(feature = "tls-rustls")]
805 #[test]
806 fn rustls_client_config_with_malformed_ca_bytes_errors() {
807 let cfg = TlsConfig {
810 identity: TlsIdentity::SelfSigned {
811 subject_alt_names: vec!["localhost".into()],
812 },
813 trusted_certs: TrustedCerts::CaBytes(vec![b"not-a-pem".to_vec()]),
814 server_name: "x".into(),
815 };
816 let cc = cfg.to_rustls_client_config();
817 assert!(
818 cc.is_err(),
819 "malformed CaBytes must error rather than build an empty store, \
820 got Ok"
821 );
822 let msg = format!("{}", cc.err().unwrap());
823 assert!(
824 msg.contains("0 certificates"),
825 "error should mention 0 certificates, got: {msg}"
826 );
827 }
828
829 #[cfg(feature = "tls-rustls")]
830 #[test]
831 fn skip_cert_verification_returns_ok_for_any_cert() {
832 use rustls::client::danger::ServerCertVerifier;
833 let v = SkipCertVerification::new();
834 let cert = rustls::pki_types::CertificateDer::from(vec![0u8; 8]);
835 let server_name =
836 rustls::pki_types::ServerName::try_from("localhost").unwrap();
837 let now = rustls::pki_types::UnixTime::now();
838 let r = v.verify_server_cert(&cert, &[], &server_name, &[], now);
839 assert!(r.is_ok(), "SkipCertVerification must return Ok for any cert");
840 }
841
842 #[cfg(feature = "tls-rustls")]
843 #[test]
844 fn skip_cert_verification_supports_some_schemes() {
845 use rustls::client::danger::ServerCertVerifier;
846 let v = SkipCertVerification::new();
847 let schemes = v.supported_verify_schemes();
848 assert!(
849 !schemes.is_empty(),
850 "SkipCertVerification must report at least one signature scheme"
851 );
852 }
853
854 #[cfg(feature = "tls-native")]
857 #[test]
858 fn native_acceptor_requires_pkcs12_identity() {
859 let cfg = TlsConfig {
862 identity: TlsIdentity::SelfSigned {
863 subject_alt_names: vec!["localhost".into()],
864 },
865 trusted_certs: TrustedCerts::SkipVerification,
866 server_name: "x".into(),
867 };
868 let r = cfg.to_native_acceptor();
869 assert!(
870 r.is_err(),
871 "SelfSigned identity with native-tls must error, got Ok"
872 );
873 }
874
875 #[cfg(feature = "tls-native")]
876 #[test]
877 fn native_connector_skip_verification_succeeds() {
878 let cfg = TlsConfig {
879 identity: TlsIdentity::SelfSigned {
880 subject_alt_names: vec!["localhost".into()],
881 },
882 trusted_certs: TrustedCerts::SkipVerification,
883 server_name: "any".into(),
884 };
885 let r = cfg.to_native_connector();
888 assert!(
889 r.is_ok(),
890 "native_tls client with SkipVerification should succeed: {:?}",
891 r.err()
892 );
893 }
894
895 #[cfg(feature = "tls-rustls")]
899 fn make_self_signed_pem(san: &[&str]) -> (Vec<u8>, Vec<u8>) {
900 let sans: Vec<String> = san.iter().map(|s| s.to_string()).collect();
902 let ck = rcgen::generate_simple_self_signed(sans).unwrap();
903 let cert_pem = ck.cert.pem().into_bytes();
904 let key_pem = ck.key_pair.serialize_pem().into_bytes();
905 (cert_pem, key_pem)
906 }
907
908 #[cfg(feature = "tls-rustls")]
909 #[test]
910 fn rustls_server_config_from_pem_bytes() {
911 let (cert_pem, key_pem) = make_self_signed_pem(&["localhost"]);
914 let cfg = TlsConfig {
915 identity: TlsIdentity::PemBytes { cert: cert_pem, key: key_pem },
916 trusted_certs: TrustedCerts::SkipVerification,
917 server_name: "localhost".into(),
918 };
919 let sc = cfg.to_rustls_server_config();
920 assert!(sc.is_ok(), "PemBytes server config: {:?}", sc.err());
921 }
922
923 #[cfg(feature = "tls-rustls")]
924 #[test]
925 fn rustls_server_config_from_pem_files_on_disk() {
926 let (cert_pem, key_pem) = make_self_signed_pem(&["localhost"]);
930 let dir = tempfile::tempdir().unwrap();
931 let cert_path = dir.path().join("cert.pem");
932 let key_path = dir.path().join("key.pem");
933 std::fs::write(&cert_path, &cert_pem).unwrap();
934 std::fs::write(&key_path, &key_pem).unwrap();
935
936 let cfg = TlsConfig {
937 identity: TlsIdentity::PemFiles { cert: cert_path, key: key_path },
938 trusted_certs: TrustedCerts::SkipVerification,
939 server_name: "localhost".into(),
940 };
941 let sc = cfg.to_rustls_server_config();
942 assert!(sc.is_ok(), "PemFiles server config: {:?}", sc.err());
943 }
944
945 #[cfg(feature = "tls-rustls")]
946 #[test]
947 fn rustls_client_config_with_real_ca_bytes() {
948 let (ca_pem, _ca_key) = make_self_signed_pem(&["test-ca"]);
952 let cfg = TlsConfig {
953 identity: TlsIdentity::SelfSigned {
954 subject_alt_names: vec!["localhost".into()],
955 },
956 trusted_certs: TrustedCerts::CaBytes(vec![ca_pem]),
957 server_name: "localhost".into(),
958 };
959 let cc = cfg.to_rustls_client_config();
960 assert!(cc.is_ok(), "real CA bytes: {:?}", cc.err());
961 }
962
963 #[cfg(feature = "tls-rustls")]
964 #[test]
965 fn rustls_client_config_with_real_ca_file() {
966 let (ca_pem, _ca_key) = make_self_signed_pem(&["test-ca"]);
967 let dir = tempfile::tempdir().unwrap();
968 let ca_path = dir.path().join("ca.pem");
969 std::fs::write(&ca_path, &ca_pem).unwrap();
970
971 let cfg = TlsConfig {
972 identity: TlsIdentity::SelfSigned {
973 subject_alt_names: vec!["localhost".into()],
974 },
975 trusted_certs: TrustedCerts::CaFiles(vec![ca_path]),
976 server_name: "localhost".into(),
977 };
978 let cc = cfg.to_rustls_client_config();
979 assert!(cc.is_ok(), "real CA file: {:?}", cc.err());
980 }
981
982 #[cfg(feature = "tls-rustls")]
983 #[test]
984 fn rustls_server_config_with_pem_files_missing_cert_errors() {
985 let dir = tempfile::tempdir().unwrap();
986 let nonexistent = dir.path().join("does-not-exist.pem");
987 let key_path = dir.path().join("key.pem");
988 let (_, key_pem) = make_self_signed_pem(&["localhost"]);
989 std::fs::write(&key_path, &key_pem).unwrap();
990
991 let cfg = TlsConfig {
992 identity: TlsIdentity::PemFiles {
993 cert: nonexistent,
994 key: key_path,
995 },
996 trusted_certs: TrustedCerts::SkipVerification,
997 server_name: "localhost".into(),
998 };
999 let sc = cfg.to_rustls_server_config();
1000 assert!(sc.is_err(), "missing cert file should error, got Ok");
1001 }
1002
1003 #[cfg(feature = "tls-rustls")]
1004 #[test]
1005 fn rustls_server_config_with_pem_files_missing_key_errors() {
1006 let dir = tempfile::tempdir().unwrap();
1007 let cert_path = dir.path().join("cert.pem");
1008 let nonexistent = dir.path().join("nonexistent-key.pem");
1009 let (cert_pem, _) = make_self_signed_pem(&["localhost"]);
1010 std::fs::write(&cert_path, &cert_pem).unwrap();
1011
1012 let cfg = TlsConfig {
1013 identity: TlsIdentity::PemFiles {
1014 cert: cert_path,
1015 key: nonexistent,
1016 },
1017 trusted_certs: TrustedCerts::SkipVerification,
1018 server_name: "localhost".into(),
1019 };
1020 let sc = cfg.to_rustls_server_config();
1021 assert!(sc.is_err(), "missing key file should error, got Ok");
1022 }
1023
1024 #[cfg(feature = "tls-rustls")]
1025 #[test]
1026 fn rustls_root_store_with_malformed_ca_file_errors() {
1027 let dir = tempfile::tempdir().unwrap();
1031 let bad_ca = dir.path().join("bad.pem");
1032 std::fs::write(&bad_ca, b"this is not a PEM file\n").unwrap();
1033
1034 let cfg = TlsConfig {
1035 identity: TlsIdentity::SelfSigned {
1036 subject_alt_names: vec!["localhost".into()],
1037 },
1038 trusted_certs: TrustedCerts::CaFiles(vec![bad_ca]),
1039 server_name: "x".into(),
1040 };
1041 let cc = cfg.to_rustls_client_config();
1042 assert!(
1043 cc.is_err(),
1044 "garbage CA file must error rather than yield empty trust"
1045 );
1046 let msg = format!("{}", cc.err().unwrap());
1047 assert!(
1048 msg.contains("0 certificates"),
1049 "error should mention 0 certificates, got: {msg}"
1050 );
1051 }
1052
1053 #[cfg(feature = "tls-rustls")]
1054 #[test]
1055 fn rustls_client_config_with_missing_ca_file_errors() {
1056 let cfg = TlsConfig {
1057 identity: TlsIdentity::SelfSigned {
1058 subject_alt_names: vec!["localhost".into()],
1059 },
1060 trusted_certs: TrustedCerts::CaFiles(vec![
1061 std::path::PathBuf::from("/nonexistent/ca.pem"),
1062 ]),
1063 server_name: "x".into(),
1064 };
1065 let cc = cfg.to_rustls_client_config();
1066 assert!(cc.is_err(), "missing CA file should error");
1067 }
1068
1069 #[cfg(feature = "tls-rustls")]
1070 #[test]
1071 fn rustls_server_config_self_signed_runtime() {
1072 let cfg = TlsConfig {
1076 identity: TlsIdentity::SelfSigned {
1077 subject_alt_names: vec!["host-a".into(), "host-b".into()],
1078 },
1079 trusted_certs: TrustedCerts::SkipVerification,
1080 server_name: "host-a".into(),
1081 };
1082 let sc = cfg.to_rustls_server_config();
1083 assert!(sc.is_ok(), "SelfSigned runtime cert: {:?}", sc.err());
1084 }
1085
1086 #[cfg(feature = "tls-rustls")]
1095 #[test]
1096 fn rustls_pkcs12_identity_is_rejected() {
1097 let cfg = TlsConfig {
1100 identity: TlsIdentity::Pkcs12 {
1101 der: vec![0x30, 0x82, 0x00, 0x10],
1102 password: "x".into(),
1103 },
1104 trusted_certs: TrustedCerts::SkipVerification,
1105 server_name: "x".into(),
1106 };
1107 let r = cfg.to_rustls_server_config();
1108 assert!(r.is_err(), "Pkcs12 with rustls must error");
1109 let msg = format!("{}", r.err().unwrap());
1110 assert!(
1111 msg.contains("Pkcs12") || msg.contains("not supported"),
1112 "error should mention Pkcs12 or not-supported, got: {msg}"
1113 );
1114 }
1115
1116 #[cfg(feature = "tls-rustls")]
1117 #[test]
1118 fn rustls_pem_bytes_no_certificates_errors() {
1119 let cfg = TlsConfig {
1120 identity: TlsIdentity::PemBytes {
1121 cert: b"-----BEGIN GARBAGE-----\nXX\n-----END GARBAGE-----\n"
1122 .to_vec(),
1123 key: b"-----BEGIN PRIVATE KEY-----\nMC4CAQA=\n-----END PRIVATE KEY-----\n"
1124 .to_vec(),
1125 },
1126 trusted_certs: TrustedCerts::SkipVerification,
1127 server_name: "x".into(),
1128 };
1129 let r = cfg.to_rustls_server_config();
1130 assert!(r.is_err(), "PEM with no certificates must error, got Ok");
1131 }
1132
1133 #[cfg(feature = "tls-rustls")]
1134 #[test]
1135 fn rustls_pem_bytes_no_private_key_errors() {
1136 let (cert_pem, _key_pem) = make_self_signed_pem(&["localhost"]);
1137 let cfg = TlsConfig {
1138 identity: TlsIdentity::PemBytes {
1139 cert: cert_pem,
1140 key: b"-----BEGIN GARBAGE-----\nXX\n-----END GARBAGE-----\n"
1141 .to_vec(),
1142 },
1143 trusted_certs: TrustedCerts::SkipVerification,
1144 server_name: "x".into(),
1145 };
1146 let r = cfg.to_rustls_server_config();
1147 assert!(r.is_err(), "PEM with no private key must error, got Ok");
1148 }
1149
1150 #[cfg(feature = "tls-rustls")]
1151 #[test]
1152 fn rustls_skip_verification_client_config_succeeds() {
1153 let skip_cfg = TlsConfig::insecure("localhost");
1157 let cc = skip_cfg.to_rustls_client_config();
1158 assert!(cc.is_ok());
1159 }
1160
1161 #[cfg(feature = "tls-rustls")]
1170 #[test]
1171 fn tls2_empty_ca_files_errors() {
1172 let cfg = TlsConfig {
1173 identity: TlsIdentity::SelfSigned {
1174 subject_alt_names: vec!["localhost".into()],
1175 },
1176 trusted_certs: TrustedCerts::CaFiles(vec![]),
1177 server_name: "x".into(),
1178 };
1179 let cc = cfg.to_rustls_client_config();
1180 assert!(
1181 cc.is_err(),
1182 "empty CaFiles must be a misconfiguration error, got Ok"
1183 );
1184 let msg = format!("{}", cc.err().unwrap());
1185 assert!(
1186 msg.contains("CaFiles") && msg.contains("misconfiguration"),
1187 "error should mention CaFiles/misconfiguration, got: {msg}"
1188 );
1189 }
1190
1191 #[cfg(feature = "tls-rustls")]
1193 #[test]
1194 fn tls2_empty_ca_bytes_errors() {
1195 let cfg = TlsConfig {
1196 identity: TlsIdentity::SelfSigned {
1197 subject_alt_names: vec!["localhost".into()],
1198 },
1199 trusted_certs: TrustedCerts::CaBytes(vec![]),
1200 server_name: "x".into(),
1201 };
1202 let cc = cfg.to_rustls_client_config();
1203 assert!(
1204 cc.is_err(),
1205 "empty CaBytes must be a misconfiguration error, got Ok"
1206 );
1207 let msg = format!("{}", cc.err().unwrap());
1208 assert!(
1209 msg.contains("CaBytes") && msg.contains("misconfiguration"),
1210 "error should mention CaBytes/misconfiguration, got: {msg}"
1211 );
1212 }
1213
1214 #[cfg(feature = "tls-rustls")]
1216 #[test]
1217 fn tls2_skip_verification_still_works() {
1218 let cfg = TlsConfig::insecure("localhost");
1219 let cc = cfg.to_rustls_client_config();
1220 assert!(
1221 cc.is_ok(),
1222 "SkipVerification must remain the supported opt-out, got Err: \
1223 {:?}",
1224 cc.err()
1225 );
1226 }
1227
1228 #[cfg(feature = "tls-rustls")]
1231 #[test]
1232 fn tls3_ca_bytes_with_zero_decoded_certs_errors() {
1233 let cfg = TlsConfig {
1234 identity: TlsIdentity::SelfSigned {
1235 subject_alt_names: vec!["localhost".into()],
1236 },
1237 trusted_certs: TrustedCerts::CaBytes(vec![
1238 b"this looks like text but is not a PEM certificate\n".to_vec(),
1239 ]),
1240 server_name: "x".into(),
1241 };
1242 let cc = cfg.to_rustls_client_config();
1243 assert!(
1244 cc.is_err(),
1245 "non-empty PEM with zero certs must error, got Ok"
1246 );
1247 let msg = format!("{}", cc.err().unwrap());
1248 assert!(
1249 msg.contains("0 certificates"),
1250 "error should mention 0 certificates, got: {msg}"
1251 );
1252 }
1253
1254 #[cfg(feature = "tls-rustls")]
1256 #[test]
1257 fn tls3_ca_file_with_zero_decoded_certs_errors() {
1258 let dir = tempfile::tempdir().unwrap();
1259 let bad_ca = dir.path().join("bad.pem");
1260 std::fs::write(
1263 &bad_ca,
1264 b"-----BEGIN GARBAGE-----\nAAAA\n-----END GARBAGE-----\n",
1265 )
1266 .unwrap();
1267
1268 let cfg = TlsConfig {
1269 identity: TlsIdentity::SelfSigned {
1270 subject_alt_names: vec!["localhost".into()],
1271 },
1272 trusted_certs: TrustedCerts::CaFiles(vec![bad_ca.clone()]),
1273 server_name: "x".into(),
1274 };
1275 let cc = cfg.to_rustls_client_config();
1276 assert!(cc.is_err(), "CA file with 0 certificates must error");
1277 let msg = format!("{}", cc.err().unwrap());
1278 assert!(
1279 msg.contains("0 certificates")
1280 && msg.contains(&bad_ca.display().to_string()),
1281 "error should mention 0 certificates and the file path, got: \
1282 {msg}"
1283 );
1284 }
1285
1286 #[cfg(feature = "tls-native")]
1293 #[test]
1294 fn tls4_native_acceptor_with_ca_files_intent_errors() {
1295 let cfg = TlsConfig {
1296 identity: TlsIdentity::Pkcs12 {
1297 der: vec![0x30, 0x82, 0x00, 0x10],
1298 password: "x".into(),
1299 },
1300 trusted_certs: TrustedCerts::CaFiles(vec![
1301 std::path::PathBuf::from("/etc/ssl/certs/ca.pem"),
1302 ]),
1303 server_name: "x".into(),
1304 };
1305 let r = cfg.to_native_acceptor();
1306 assert!(
1307 r.is_err(),
1308 "mTLS intent on tls-native server must error rather than warn"
1309 );
1310 let msg = format!("{}", r.err().unwrap());
1311 assert!(
1312 msg.contains("mTLS")
1313 && msg.contains("tls-native")
1314 && msg.contains("tls-rustls"),
1315 "error must point at mTLS / tls-native / tls-rustls remediation, \
1316 got: {msg}"
1317 );
1318 }
1319
1320 #[cfg(feature = "tls-native")]
1321 #[test]
1322 fn tls4_native_acceptor_with_ca_bytes_intent_errors() {
1323 let cfg = TlsConfig {
1324 identity: TlsIdentity::Pkcs12 {
1325 der: vec![0x30, 0x82, 0x00, 0x10],
1326 password: "x".into(),
1327 },
1328 trusted_certs: TrustedCerts::CaBytes(vec![
1329 b"-----BEGIN CERTIFICATE-----\nAAAA\n-----END CERTIFICATE-----\n"
1330 .to_vec(),
1331 ]),
1332 server_name: "x".into(),
1333 };
1334 let r = cfg.to_native_acceptor();
1335 assert!(
1336 r.is_err(),
1337 "non-empty CaBytes on tls-native server must error"
1338 );
1339 let msg = format!("{}", r.err().unwrap());
1340 assert!(msg.contains("mTLS"), "error must mention mTLS, got: {msg}");
1341 }
1342
1343 #[cfg(feature = "tls-native")]
1346 #[test]
1347 fn tls4_native_acceptor_skip_verification_unaffected() {
1348 let cfg = TlsConfig {
1353 identity: TlsIdentity::Pkcs12 {
1354 der: vec![0x30, 0x82, 0x00, 0x10],
1355 password: "x".into(),
1356 },
1357 trusted_certs: TrustedCerts::SkipVerification,
1358 server_name: "x".into(),
1359 };
1360 let r = cfg.to_native_acceptor();
1361 if let Err(e) = r {
1362 let msg = format!("{e}");
1363 assert!(
1364 !msg.contains("mTLS"),
1365 "SkipVerification must not trigger mTLS check, got: {msg}"
1366 );
1367 }
1368 }
1369
1370 #[cfg(all(feature = "tls-rustls", feature = "quic"))]
1373 #[test]
1374 fn quinn_server_config_builds_from_self_signed() {
1375 let cfg = TlsConfig::insecure("localhost");
1376 let qc = cfg.to_quinn_server_config();
1377 assert!(qc.is_ok(), "quinn server config: {:?}", qc.err());
1378 }
1379
1380 #[cfg(all(feature = "tls-rustls", feature = "quic"))]
1381 #[test]
1382 fn quinn_client_config_builds_from_skip_verification() {
1383 let cfg = TlsConfig::insecure("localhost");
1384 let qc = cfg.to_quinn_client_config();
1385 assert!(qc.is_ok(), "quinn client config: {:?}", qc.err());
1386 }
1387
1388 #[cfg(all(feature = "tls-rustls", feature = "quic"))]
1389 #[test]
1390 fn quinn_client_config_with_real_ca_bytes() {
1391 let (ca_pem, _) = make_self_signed_pem(&["test-ca"]);
1392 let cfg = TlsConfig {
1393 identity: TlsIdentity::SelfSigned {
1394 subject_alt_names: vec!["localhost".into()],
1395 },
1396 trusted_certs: TrustedCerts::CaBytes(vec![ca_pem]),
1397 server_name: "localhost".into(),
1398 };
1399 let qc = cfg.to_quinn_client_config();
1400 assert!(qc.is_ok(), "quinn client config with CA: {:?}", qc.err());
1401 }
1402}