1use crate::config::SslMode;
24use std::path::Path;
25
26#[cfg(feature = "postgres")]
27use crate::error::{Result, WaypointError};
28#[cfg(feature = "postgres")]
29use std::sync::Arc;
30
31#[derive(Debug, Default, PartialEq, Eq)]
35pub struct EmbeddedSslParams {
36 pub mode: Option<SslMode>,
38 pub root_cert: Option<std::path::PathBuf>,
40}
41
42pub fn parse_url_sslmode(conn_string: &str) -> (String, EmbeddedSslParams) {
61 if let Some(q) = conn_string.find('?') {
62 parse_query_form(conn_string, q)
63 } else {
64 parse_keyvalue_form(conn_string)
65 }
66}
67
68fn is_ssl_key(k: &str) -> bool {
70 k.eq_ignore_ascii_case("sslmode") || k.eq_ignore_ascii_case("sslrootcert")
71}
72
73fn store_param(out: &mut EmbeddedSslParams, key: &str, raw: &str) {
74 let value = raw.trim().trim_matches('\'');
75 if key.eq_ignore_ascii_case("sslmode") {
76 match value.parse::<SslMode>() {
77 Ok(mode) => out.mode = Some(mode),
78 Err(e) => log::warn!("{} (from the connection string); ignoring it.", e),
79 }
80 } else if !value.is_empty() {
81 out.root_cert = Some(std::path::PathBuf::from(value));
82 }
83}
84
85fn parse_query_form(conn_string: &str, q: usize) -> (String, EmbeddedSslParams) {
86 let (base, query) = conn_string.split_at(q);
87 let query = &query[1..];
88
89 let mut kept: Vec<&str> = Vec::new();
90 let mut out = EmbeddedSslParams::default();
91 let mut found = false;
92
93 for pair in query.split('&') {
94 match pair.split_once('=') {
95 Some((k, v)) if is_ssl_key(k) => {
96 found = true;
97 store_param(&mut out, k, v);
98 }
99 _ => kept.push(pair),
103 }
104 }
105
106 if !found {
107 return (conn_string.to_string(), out);
108 }
109
110 let rebuilt = if kept.is_empty() {
111 base.to_string()
112 } else {
113 format!("{}?{}", base, kept.join("&"))
114 };
115 (rebuilt, out)
116}
117
118fn parse_keyvalue_form(conn_string: &str) -> (String, EmbeddedSslParams) {
119 let lowered = conn_string.to_lowercase();
120 if !lowered.contains("sslmode=") && !lowered.contains("sslrootcert=") {
123 return (conn_string.to_string(), EmbeddedSslParams::default());
124 }
125
126 let mut kept: Vec<&str> = Vec::new();
127 let mut out = EmbeddedSslParams::default();
128 let mut found = false;
129 let mut quoted = false;
130 let mut consumed = 0usize;
131
132 for token in conn_string.split_whitespace() {
133 let inside = quoted;
137 let offset = conn_string[consumed..].find(token).unwrap_or(0);
138 consumed += offset + token.len();
139 quoted ^= token.matches('\'').count() % 2 == 1;
140
141 if !inside
142 && let Some((k, v)) = token.split_once('=')
143 && is_ssl_key(k)
144 {
145 found = true;
146 store_param(&mut out, k, v);
147 continue;
148 }
149 kept.push(token);
150 }
151
152 if found {
153 (kept.join(" "), out)
154 } else {
155 (conn_string.to_string(), out)
156 }
157}
158
159pub fn reconcile_root_cert(
162 configured: Option<&Path>,
163 from_url: Option<std::path::PathBuf>,
164) -> Option<std::path::PathBuf> {
165 match configured {
166 Some(p) => Some(p.to_path_buf()),
167 None => from_url,
168 }
169}
170
171pub fn reconcile_ssl_mode(configured: SslMode, from_url: Option<SslMode>) -> SslMode {
179 match from_url {
180 Some(url_mode) if configured == SslMode::Prefer => {
181 if url_mode != configured {
182 log::debug!(
183 "Using sslmode '{}' from the connection string (ssl_mode is at its default).",
184 url_mode
185 );
186 }
187 url_mode
188 }
189 Some(url_mode) if url_mode != configured => {
190 log::debug!(
191 "Connection string requests sslmode '{}', but ssl_mode is set to '{}'; \
192 the configured value wins.",
193 url_mode,
194 configured
195 );
196 configured
197 }
198 _ => configured,
199 }
200}
201
202#[cfg(feature = "postgres")]
216pub fn load_root_store(ssl_root_cert: Option<&Path>) -> Result<rustls::RootCertStore> {
217 let Some(path) = ssl_root_cert else {
218 return Ok(rustls::RootCertStore::from_iter(
219 webpki_roots::TLS_SERVER_ROOTS.iter().cloned(),
220 ));
221 };
222
223 let pem = std::fs::read(path).map_err(|e| {
224 WaypointError::ConfigError(format!(
225 "Failed to read ssl_root_cert '{}': {}",
226 path.display(),
227 e
228 ))
229 })?;
230
231 use rustls::pki_types::pem::PemObject;
236
237 let mut store = rustls::RootCertStore::empty();
238 let mut count = 0usize;
239
240 for cert in rustls::pki_types::CertificateDer::pem_slice_iter(&pem) {
241 let cert = cert.map_err(|e| {
242 WaypointError::ConfigError(format!(
243 "Failed to parse ssl_root_cert '{}': {}",
244 path.display(),
245 e
246 ))
247 })?;
248 store.add(cert).map_err(|e| {
249 WaypointError::ConfigError(format!(
250 "Certificate in ssl_root_cert '{}' was rejected: {}",
251 path.display(),
252 e
253 ))
254 })?;
255 count += 1;
256 }
257
258 if count == 0 {
259 return Err(WaypointError::ConfigError(format!(
260 "ssl_root_cert '{}' contains no certificates. It must be a PEM file \
261 with at least one CERTIFICATE block.",
262 path.display()
263 )));
264 }
265
266 log::debug!(
267 "Loaded {} CA certificate(s) from {}; the built-in trust store is not used.",
268 count,
269 path.display()
270 );
271 Ok(store)
272}
273
274#[cfg(feature = "postgres")]
281#[derive(Debug)]
282struct NoVerifier {
283 provider: Arc<rustls::crypto::CryptoProvider>,
284}
285
286#[cfg(feature = "postgres")]
287impl rustls::client::danger::ServerCertVerifier for NoVerifier {
288 fn verify_server_cert(
289 &self,
290 _end_entity: &rustls::pki_types::CertificateDer<'_>,
291 _intermediates: &[rustls::pki_types::CertificateDer<'_>],
292 _server_name: &rustls::pki_types::ServerName<'_>,
293 _ocsp_response: &[u8],
294 _now: rustls::pki_types::UnixTime,
295 ) -> std::result::Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
296 Ok(rustls::client::danger::ServerCertVerified::assertion())
297 }
298
299 fn verify_tls12_signature(
300 &self,
301 message: &[u8],
302 cert: &rustls::pki_types::CertificateDer<'_>,
303 dss: &rustls::DigitallySignedStruct,
304 ) -> std::result::Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
305 rustls::crypto::verify_tls12_signature(
306 message,
307 cert,
308 dss,
309 &self.provider.signature_verification_algorithms,
310 )
311 }
312
313 fn verify_tls13_signature(
314 &self,
315 message: &[u8],
316 cert: &rustls::pki_types::CertificateDer<'_>,
317 dss: &rustls::DigitallySignedStruct,
318 ) -> std::result::Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
319 rustls::crypto::verify_tls13_signature(
320 message,
321 cert,
322 dss,
323 &self.provider.signature_verification_algorithms,
324 )
325 }
326
327 fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
328 self.provider
329 .signature_verification_algorithms
330 .supported_schemes()
331 }
332}
333
334#[cfg(feature = "postgres")]
339#[derive(Debug)]
340struct NoHostnameVerifier {
341 inner: Arc<rustls::client::WebPkiServerVerifier>,
342}
343
344#[cfg(feature = "postgres")]
345impl rustls::client::danger::ServerCertVerifier for NoHostnameVerifier {
346 fn verify_server_cert(
347 &self,
348 end_entity: &rustls::pki_types::CertificateDer<'_>,
349 intermediates: &[rustls::pki_types::CertificateDer<'_>],
350 server_name: &rustls::pki_types::ServerName<'_>,
351 ocsp_response: &[u8],
352 now: rustls::pki_types::UnixTime,
353 ) -> std::result::Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
354 match self.inner.verify_server_cert(
355 end_entity,
356 intermediates,
357 server_name,
358 ocsp_response,
359 now,
360 ) {
361 Ok(v) => Ok(v),
362 Err(rustls::Error::InvalidCertificate(
367 rustls::CertificateError::NotValidForName
368 | rustls::CertificateError::NotValidForNameContext { .. },
369 )) => {
370 log::debug!(
371 "Server certificate is not valid for the requested name; \
372 accepted anyway because ssl_mode is 'verify-ca'."
373 );
374 Ok(rustls::client::danger::ServerCertVerified::assertion())
375 }
376 Err(e) => Err(e),
377 }
378 }
379
380 fn verify_tls12_signature(
381 &self,
382 message: &[u8],
383 cert: &rustls::pki_types::CertificateDer<'_>,
384 dss: &rustls::DigitallySignedStruct,
385 ) -> std::result::Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
386 self.inner.verify_tls12_signature(message, cert, dss)
387 }
388
389 fn verify_tls13_signature(
390 &self,
391 message: &[u8],
392 cert: &rustls::pki_types::CertificateDer<'_>,
393 dss: &rustls::DigitallySignedStruct,
394 ) -> std::result::Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
395 self.inner.verify_tls13_signature(message, cert, dss)
396 }
397
398 fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
399 self.inner.supported_verify_schemes()
400 }
401}
402
403#[cfg(feature = "postgres")]
408pub fn make_rustls_config(
409 ssl_mode: SslMode,
410 ssl_root_cert: Option<&Path>,
411) -> Result<rustls::ClientConfig> {
412 let provider = Arc::new(rustls::crypto::ring::default_provider());
413
414 let builder = rustls::ClientConfig::builder_with_provider(provider.clone())
415 .with_safe_default_protocol_versions()
416 .map_err(|e| {
417 WaypointError::ConfigError(format!("Failed to configure TLS protocol versions: {}", e))
418 })?;
419
420 let config = match ssl_mode {
421 SslMode::Disable | SslMode::Prefer | SslMode::Require => builder
423 .dangerous()
424 .with_custom_certificate_verifier(Arc::new(NoVerifier { provider }))
425 .with_no_client_auth(),
426 SslMode::VerifyCa => {
427 let roots = Arc::new(load_root_store(ssl_root_cert)?);
428 let inner =
429 rustls::client::WebPkiServerVerifier::builder_with_provider(roots, provider)
430 .build()
431 .map_err(|e| {
432 WaypointError::ConfigError(format!(
433 "Failed to build certificate verifier: {}",
434 e
435 ))
436 })?;
437 builder
438 .dangerous()
439 .with_custom_certificate_verifier(Arc::new(NoHostnameVerifier { inner }))
440 .with_no_client_auth()
441 }
442 SslMode::VerifyFull => builder
443 .with_root_certificates(load_root_store(ssl_root_cert)?)
444 .with_no_client_auth(),
445 };
446
447 Ok(config)
448}
449
450#[cfg(feature = "mysql")]
459pub fn make_mysql_ssl_opts(
460 ssl_mode: SslMode,
461 ssl_root_cert: Option<&std::path::Path>,
462) -> Option<mysql_async::SslOpts> {
463 if ssl_mode == SslMode::Disable {
464 return None;
465 }
466
467 let mut opts = mysql_async::SslOpts::default();
468
469 if ssl_mode.verifies_certificate() {
470 if let Some(path) = ssl_root_cert {
471 opts = opts
472 .with_root_certs(vec![path.to_path_buf().into()])
473 .with_disable_built_in_roots(true);
476 }
477 opts = opts.with_danger_accept_invalid_certs(false);
478 opts = opts.with_danger_skip_domain_validation(ssl_mode == SslMode::VerifyCa);
491 if ssl_mode == SslMode::VerifyCa {
492 log::warn!(
493 "ssl_mode = 'verify-ca' on MySQL: the driver cannot currently skip \
494 hostname validation, so the certificate name will be checked too \
495 (as if 'verify-full'). This is stricter than requested and may \
496 reject a certificate issued to a different name."
497 );
498 }
499 } else {
500 opts = opts
502 .with_danger_accept_invalid_certs(true)
503 .with_danger_skip_domain_validation(true);
504 }
505
506 Some(opts)
507}
508
509#[cfg(test)]
510mod tests {
511 use super::*;
512
513 #[test]
516 fn test_parse_url_sslmode_absent_is_untouched() {
517 let s = "postgres://u:p@host:5432/db";
518 let (out, p) = parse_url_sslmode(s);
519 assert_eq!(out, s);
520 assert_eq!(p, EmbeddedSslParams::default());
521 }
522
523 #[test]
524 fn test_parse_url_sslmode_query_form() {
525 let (out, p) = parse_url_sslmode("postgres://u@host/db?sslmode=require");
526 assert_eq!(out, "postgres://u@host/db");
527 assert_eq!(p.mode, Some(SslMode::Require));
528 }
529
530 #[test]
531 fn test_parse_url_sslmode_extracts_verify_full() {
532 let (out, p) = parse_url_sslmode("postgres://u@host/db?sslmode=verify-full");
534 assert_eq!(out, "postgres://u@host/db");
535 assert_eq!(p.mode, Some(SslMode::VerifyFull));
536 }
537
538 #[test]
539 fn test_parse_url_sslmode_extracts_root_cert() {
540 let (out, p) = parse_url_sslmode("postgres://u@host/db?sslrootcert=/etc/ssl/ca.pem");
543 assert_eq!(out, "postgres://u@host/db");
544 assert_eq!(
545 p.root_cert,
546 Some(std::path::PathBuf::from("/etc/ssl/ca.pem"))
547 );
548 }
549
550 #[test]
551 fn test_parse_url_sslmode_extracts_both_params() {
552 let (out, p) = parse_url_sslmode(
553 "postgres://u@host/db?sslmode=verify-full&sslrootcert=/ca.pem&application_name=wp",
554 );
555 assert_eq!(out, "postgres://u@host/db?application_name=wp");
556 assert_eq!(p.mode, Some(SslMode::VerifyFull));
557 assert_eq!(p.root_cert, Some(std::path::PathBuf::from("/ca.pem")));
558 }
559
560 #[test]
561 fn test_parse_url_sslmode_keeps_other_query_params() {
562 let (out, p) =
563 parse_url_sslmode("postgres://u@host/db?sslmode=verify-ca&keepalives=1&foo=bar");
564 assert_eq!(out, "postgres://u@host/db?keepalives=1&foo=bar");
565 assert_eq!(p.mode, Some(SslMode::VerifyCa));
566 }
567
568 #[test]
569 fn test_parse_url_sslmode_preserves_encoded_values_verbatim() {
570 let (out, _) =
573 parse_url_sslmode("postgres://h/db?options=-c%20search_path%3Dfoo&sslmode=require");
574 assert_eq!(out, "postgres://h/db?options=-c%20search_path%3Dfoo");
575 }
576
577 #[test]
578 fn test_parse_url_sslmode_is_case_insensitive() {
579 let (out, p) = parse_url_sslmode("postgres://u@host/db?SSLMode=Verify-Full");
580 assert_eq!(out, "postgres://u@host/db");
581 assert_eq!(p.mode, Some(SslMode::VerifyFull));
582 }
583
584 #[test]
585 fn test_parse_url_sslmode_keyvalue_form() {
586 let (out, p) =
587 parse_url_sslmode("host=db port=5432 sslmode=verify-full user=admin dbname=app");
588 assert_eq!(out, "host=db port=5432 user=admin dbname=app");
589 assert_eq!(p.mode, Some(SslMode::VerifyFull));
590 }
591
592 #[test]
593 fn test_parse_url_sslmode_keyvalue_extracts_root_cert() {
594 let (out, p) = parse_url_sslmode("host=db sslrootcert=/ca.pem dbname=app");
595 assert_eq!(out, "host=db dbname=app");
596 assert_eq!(p.root_cert, Some(std::path::PathBuf::from("/ca.pem")));
597 }
598
599 #[test]
600 fn test_parse_url_sslmode_keyvalue_ignores_quoted_value() {
601 let s = "host=db user=admin password='a sslmode=require b' dbname=app";
604 let (out, p) = parse_url_sslmode(s);
605 assert_eq!(out, s);
606 assert_eq!(p, EmbeddedSslParams::default());
607 }
608
609 #[test]
610 fn test_parse_url_sslmode_unparseable_is_dropped() {
611 let (out, p) = parse_url_sslmode("postgres://u@host/db?sslmode=banana");
612 assert_eq!(out, "postgres://u@host/db");
613 assert_eq!(p.mode, None);
614 }
615
616 #[test]
617 fn test_parse_url_sslmode_allow_is_dropped() {
618 let (_, p) = parse_url_sslmode("postgres://u@host/db?sslmode=allow");
621 assert_eq!(p.mode, None);
622 }
623
624 #[cfg(feature = "postgres")]
627 #[test]
628 fn test_stripped_string_is_parseable_by_tokio_postgres() {
629 for raw in [
630 "postgres://u@host/db?sslmode=verify-full",
631 "postgres://u@host/db?sslrootcert=/ca.pem",
632 "host=db sslmode=verify-ca dbname=app",
633 ] {
634 assert!(
635 raw.parse::<tokio_postgres::Config>().is_err(),
636 "expected tokio-postgres to reject {raw}"
637 );
638 let (cleaned, _) = parse_url_sslmode(raw);
639 assert!(
640 cleaned.parse::<tokio_postgres::Config>().is_ok(),
641 "tokio-postgres rejected the cleaned string {cleaned}"
642 );
643 }
644 }
645
646 #[test]
647 fn test_reconcile_root_cert_config_wins() {
648 let configured = std::path::PathBuf::from("/config/ca.pem");
649 let from_url = std::path::PathBuf::from("/url/ca.pem");
650 assert_eq!(
651 reconcile_root_cert(Some(&configured), Some(from_url.clone())),
652 Some(configured.clone())
653 );
654 assert_eq!(
655 reconcile_root_cert(None, Some(from_url.clone())),
656 Some(from_url)
657 );
658 assert_eq!(reconcile_root_cert(None, None), None);
659 }
660
661 #[test]
664 fn test_reconcile_url_wins_when_config_is_default() {
665 assert_eq!(
666 reconcile_ssl_mode(SslMode::Prefer, Some(SslMode::VerifyFull)),
667 SslMode::VerifyFull
668 );
669 }
670
671 #[test]
672 fn test_reconcile_config_wins_when_set() {
673 assert_eq!(
674 reconcile_ssl_mode(SslMode::VerifyFull, Some(SslMode::Require)),
675 SslMode::VerifyFull
676 );
677 assert_eq!(
678 reconcile_ssl_mode(SslMode::Disable, Some(SslMode::Require)),
679 SslMode::Disable
680 );
681 }
682
683 #[test]
684 fn test_reconcile_without_url_mode_keeps_config() {
685 assert_eq!(reconcile_ssl_mode(SslMode::Require, None), SslMode::Require);
686 assert_eq!(reconcile_ssl_mode(SslMode::Prefer, None), SslMode::Prefer);
687 }
688
689 #[cfg(feature = "postgres")]
692 mod root_store {
693 use super::*;
694 use std::io::Write;
695
696 const TEST_CA_PEM: &str = include_str!("../tests/fixtures/test-ca.pem");
699
700 fn write_temp(contents: &str) -> tempfile::NamedTempFile {
701 let mut f = tempfile::NamedTempFile::new().unwrap();
702 f.write_all(contents.as_bytes()).unwrap();
703 f.flush().unwrap();
704 f
705 }
706
707 #[test]
708 fn test_load_root_store_defaults_to_builtin_roots() {
709 let store = load_root_store(None).unwrap();
710 assert!(
711 !store.is_empty(),
712 "the built-in Mozilla bundle should be non-empty"
713 );
714 }
715
716 #[test]
717 fn test_load_root_store_reads_custom_ca() {
718 let f = write_temp(TEST_CA_PEM);
719 let store = load_root_store(Some(f.path())).unwrap();
720 assert_eq!(store.len(), 1);
723 }
724
725 #[test]
726 fn test_load_root_store_missing_file_errors() {
727 let err = load_root_store(Some(Path::new("/nonexistent/ca.pem"))).unwrap_err();
728 let msg = err.to_string();
729 assert!(msg.contains("ssl_root_cert"), "got: {}", msg);
730 assert!(msg.contains("/nonexistent/ca.pem"), "got: {}", msg);
731 }
732
733 #[test]
734 fn test_load_root_store_empty_file_errors() {
735 let f = write_temp("");
736 let err = load_root_store(Some(f.path())).unwrap_err();
737 assert!(err.to_string().contains("no certificates"), "got: {}", err);
738 }
739
740 #[test]
741 fn test_load_root_store_pem_without_certificates_errors() {
742 let f = write_temp(
744 "-----BEGIN PRIVATE KEY-----\nMIIBVQIBADANBg==\n-----END PRIVATE KEY-----\n",
745 );
746 let err = load_root_store(Some(f.path())).unwrap_err();
747 assert!(err.to_string().contains("no certificates"), "got: {}", err);
748 }
749
750 #[test]
751 fn test_load_root_store_malformed_pem_errors() {
752 let f = write_temp("-----BEGIN CERTIFICATE-----\nnot base64 at all!!\n");
753 let err = load_root_store(Some(f.path())).unwrap_err();
754 let msg = err.to_string();
755 assert!(
756 msg.contains("parse") || msg.contains("no certificates"),
757 "got: {}",
758 msg
759 );
760 }
761
762 #[test]
763 fn test_make_rustls_config_all_modes_build() {
764 let f = write_temp(TEST_CA_PEM);
765 for mode in [
766 SslMode::Prefer,
767 SslMode::Require,
768 SslMode::VerifyCa,
769 SslMode::VerifyFull,
770 ] {
771 assert!(
772 make_rustls_config(mode, Some(f.path())).is_ok(),
773 "mode {} failed to build",
774 mode
775 );
776 }
777 }
778
779 #[test]
780 fn test_make_rustls_config_propagates_ca_errors() {
781 let bad = Path::new("/nonexistent/ca.pem");
784 assert!(make_rustls_config(SslMode::VerifyFull, Some(bad)).is_err());
785 assert!(make_rustls_config(SslMode::VerifyCa, Some(bad)).is_err());
786 assert!(make_rustls_config(SslMode::Require, Some(bad)).is_ok());
788 }
789 }
790
791 #[cfg(feature = "mysql")]
794 mod mysql_opts {
795 use super::*;
796
797 #[test]
798 fn test_mysql_ssl_opts_disable_is_none() {
799 assert!(make_mysql_ssl_opts(SslMode::Disable, None).is_none());
800 }
801
802 #[test]
803 fn test_mysql_ssl_opts_non_verifying_modes_skip_checks() {
804 for mode in [SslMode::Prefer, SslMode::Require] {
805 let opts = make_mysql_ssl_opts(mode, None).unwrap();
806 assert!(opts.accept_invalid_certs(), "mode {}", mode);
807 assert!(opts.skip_domain_validation(), "mode {}", mode);
808 }
809 }
810
811 #[test]
812 fn test_mysql_ssl_opts_verify_ca_checks_chain_not_name() {
813 let opts = make_mysql_ssl_opts(SslMode::VerifyCa, None).unwrap();
814 assert!(!opts.accept_invalid_certs());
815 assert!(opts.skip_domain_validation());
816 }
817
818 #[test]
819 fn test_mysql_ssl_opts_verify_full_checks_everything() {
820 let opts = make_mysql_ssl_opts(SslMode::VerifyFull, None).unwrap();
821 assert!(!opts.accept_invalid_certs());
822 assert!(!opts.skip_domain_validation());
823 }
824
825 #[test]
826 fn test_mysql_ssl_opts_custom_ca_replaces_builtin_roots() {
827 let path = std::path::Path::new("/etc/ssl/my-ca.pem");
828 let opts = make_mysql_ssl_opts(SslMode::VerifyFull, Some(path)).unwrap();
829 assert_eq!(opts.root_certs().len(), 1);
830 assert!(opts.disable_built_in_roots());
831
832 let opts = make_mysql_ssl_opts(SslMode::VerifyFull, None).unwrap();
834 assert!(opts.root_certs().is_empty());
835 assert!(!opts.disable_built_in_roots());
836 }
837
838 #[test]
839 fn test_mysql_ssl_opts_ignores_ca_for_non_verifying_modes() {
840 let path = std::path::Path::new("/etc/ssl/my-ca.pem");
841 let opts = make_mysql_ssl_opts(SslMode::Require, Some(path)).unwrap();
842 assert!(opts.root_certs().is_empty());
843 }
844 }
845}