1use std::{
2 fmt,
3 fs::File,
4 io::Read,
5 path::{Path, PathBuf},
6};
7
8use super::{
9 AddCertToStoreSnafu, AddExtraChainCertSnafu, CaStackPushSnafu, EncodeAlpnProtocolsSnafu,
10 FileOpenFailedSnafu, FileReadFailedSnafu, MaybeTls, NewCaStackSnafu, NewStoreBuilderSnafu,
11 ParsePkcs12Snafu, PrivateKeyParseSnafu, Result, SetAlpnProtocolsSnafu, SetCertificateSnafu,
12 SetPrivateKeySnafu, SetVerifyCertSnafu, TlsError, X509ParseSnafu,
13};
14use cfg_if::cfg_if;
15use lookup::lookup_v2::OptionalValuePath;
16use openssl::{
17 pkcs12::Pkcs12,
18 pkey::{PKey, Private},
19 ssl::{AlpnError, ConnectConfiguration, SslContextBuilder, SslVerifyMode, select_next_proto},
20 stack::Stack,
21 x509::{X509, store::X509StoreBuilder},
22};
23use snafu::ResultExt;
24use vector_config::configurable_component;
25
26pub const PEM_START_MARKER: &str = "-----BEGIN ";
27
28pub const TEST_PEM_CA_PATH: &str = "tests/data/ca/certs/ca.cert.pem";
29pub const TEST_PEM_INTERMEDIATE_CA_PATH: &str =
30 "tests/data/ca/intermediate_server/certs/ca-chain.cert.pem";
31pub const TEST_PEM_CRT_PATH: &str =
32 "tests/data/ca/intermediate_server/certs/localhost-chain.cert.pem";
33pub const TEST_PEM_KEY_PATH: &str = "tests/data/ca/intermediate_server/private/localhost.key.pem";
34pub const TEST_PEM_CLIENT_CRT_PATH: &str =
35 "tests/data/ca/intermediate_client/certs/localhost-chain.cert.pem";
36pub const TEST_PEM_CLIENT_KEY_PATH: &str =
37 "tests/data/ca/intermediate_client/private/localhost.key.pem";
38
39#[configurable_component]
41#[configurable(metadata(docs::advanced))]
42#[derive(Clone, Debug, Default)]
43#[serde(deny_unknown_fields)]
44pub struct TlsEnableableConfig {
45 pub enabled: Option<bool>,
50
51 #[serde(flatten)]
52 pub options: TlsConfig,
53}
54
55impl TlsEnableableConfig {
56 pub fn enabled() -> Self {
57 Self {
58 enabled: Some(true),
59 ..Self::default()
60 }
61 }
62
63 pub fn test_config() -> Self {
64 Self {
65 enabled: Some(true),
66 options: TlsConfig::test_config(),
67 }
68 }
69}
70
71#[configurable_component]
73#[derive(Clone, Debug, Default)]
74pub struct TlsSourceConfig {
75 pub client_metadata_key: Option<OptionalValuePath>,
77
78 #[serde(flatten)]
79 pub tls_config: TlsEnableableConfig,
80}
81
82#[configurable_component]
84#[configurable(metadata(docs::advanced))]
85#[derive(Clone, Debug, Default)]
86#[serde(deny_unknown_fields)]
87pub struct TlsConfig {
88 pub verify_certificate: Option<bool>,
99
100 pub verify_hostname: Option<bool>,
109
110 #[configurable(metadata(docs::examples = "h2"))]
115 pub alpn_protocols: Option<Vec<String>>,
116
117 #[serde(alias = "ca_path")]
121 #[configurable(metadata(docs::examples = "/path/to/certificate_authority.crt"))]
122 #[configurable(metadata(docs::human_name = "CA File Path"))]
123 pub ca_file: Option<PathBuf>,
124
125 #[serde(alias = "crt_path")]
132 #[configurable(metadata(docs::examples = "/path/to/host_certificate.crt"))]
133 #[configurable(metadata(docs::human_name = "Certificate File Path"))]
134 pub crt_file: Option<PathBuf>,
135
136 #[serde(alias = "key_path")]
140 #[configurable(metadata(docs::examples = "/path/to/host_certificate.key"))]
141 #[configurable(metadata(docs::human_name = "Key File Path"))]
142 pub key_file: Option<PathBuf>,
143
144 #[configurable(metadata(docs::examples = "${KEY_PASS_ENV_VAR}"))]
148 #[configurable(metadata(docs::examples = "PassWord1"))]
149 #[configurable(metadata(docs::human_name = "Key File Password"))]
150 pub key_pass: Option<String>,
151
152 #[serde(alias = "server_name")]
156 #[configurable(metadata(docs::examples = "www.example.com"))]
157 #[configurable(metadata(docs::human_name = "Server Name"))]
158 pub server_name: Option<String>,
159}
160
161impl TlsConfig {
162 pub fn test_config() -> Self {
163 Self {
164 ca_file: Some(TEST_PEM_CA_PATH.into()),
165 crt_file: Some(TEST_PEM_CRT_PATH.into()),
166 key_file: Some(TEST_PEM_KEY_PATH.into()),
167 ..Self::default()
168 }
169 }
170}
171
172#[derive(Clone, Default)]
174pub struct TlsSettings {
175 verify_certificate: bool,
176 pub(super) verify_hostname: bool,
177 authorities: Vec<X509>,
178 pub(super) identity: Option<IdentityStore>,
179 alpn_protocols: Option<Vec<u8>>,
180 server_name: Option<String>,
181}
182
183#[derive(Clone)]
185pub(super) struct IdentityStore {
186 cert: X509,
187 key: PKey<Private>,
188 ca: Option<Vec<X509>>,
189}
190
191impl TlsSettings {
192 pub fn from_options(options: Option<&TlsConfig>) -> Result<Self> {
196 Self::from_options_base(options, false)
197 }
198
199 pub(super) fn from_options_base(options: Option<&TlsConfig>, for_server: bool) -> Result<Self> {
200 let default = TlsConfig::default();
201 let options = options.unwrap_or(&default);
202
203 if !for_server {
204 if options.verify_certificate == Some(false) {
205 warn!(
206 "The `verify_certificate` option is DISABLED, this may lead to security vulnerabilities."
207 );
208 }
209 if options.verify_hostname == Some(false) {
210 warn!(
211 "The `verify_hostname` option is DISABLED, this may lead to security vulnerabilities."
212 );
213 }
214 }
215
216 Ok(Self {
217 verify_certificate: options.verify_certificate.unwrap_or(!for_server),
218 verify_hostname: options.verify_hostname.unwrap_or(!for_server),
219 authorities: options.load_authorities()?,
220 identity: options.load_identity()?,
221 alpn_protocols: options.parse_alpn_protocols()?,
222 server_name: options.server_name.clone(),
223 })
224 }
225
226 pub fn identity_pem(&self) -> Option<(Vec<u8>, Vec<u8>)> {
232 self.identity.as_ref().map(|identity| {
233 let mut cert = identity.cert.to_pem().expect("Invalid stored identity");
235 let key = identity
236 .key
237 .private_key_to_pem_pkcs8()
238 .expect("Invalid stored identity");
239 if let Some(chain) = identity.ca.as_ref() {
240 for authority in chain {
241 cert.extend(
242 authority
243 .to_pem()
244 .expect("Invalid stored identity chain certificate"),
245 );
246 }
247 }
248 (cert, key)
249 })
250 }
251
252 pub fn authorities_pem(&self) -> impl Iterator<Item = Vec<u8>> + '_ {
258 self.authorities.iter().map(|authority| {
259 authority
260 .to_pem()
261 .expect("Invalid stored authority certificate")
262 })
263 }
264
265 pub(super) fn apply_context(&self, context: &mut SslContextBuilder) -> Result<()> {
266 self.apply_context_base(context, false)
267 }
268
269 pub(super) fn apply_context_base(
270 &self,
271 context: &mut SslContextBuilder,
272 for_server: bool,
273 ) -> Result<()> {
274 context.set_verify(if self.verify_certificate {
275 SslVerifyMode::PEER | SslVerifyMode::FAIL_IF_NO_PEER_CERT
276 } else {
277 SslVerifyMode::NONE
278 });
279 if let Some(identity) = &self.identity {
280 context
281 .set_certificate(&identity.cert)
282 .context(SetCertificateSnafu)?;
283 context
284 .set_private_key(&identity.key)
285 .context(SetPrivateKeySnafu)?;
286
287 if let Some(chain) = &identity.ca {
288 for cert in chain {
289 context
290 .add_extra_chain_cert(cert.clone())
291 .context(AddExtraChainCertSnafu)?;
292 }
293 }
294 }
295 if self.authorities.is_empty() {
296 debug!("Fetching system root certs.");
297
298 cfg_if! {
299 if #[cfg(windows)] {
300 load_windows_certs(context).unwrap();
301 } else if #[cfg(target_os = "macos")] {
302 cfg_if! { if #[cfg(debug_assertions)] {
304 if let Err(error) = load_mac_certs(context) {
305 warn!("Failed to load macOS certs: {error}");
306 }
307 } else {
308 load_mac_certs(context).unwrap();
309 }
310 }
311 }
312 }
313 } else {
314 let mut store = X509StoreBuilder::new().context(NewStoreBuilderSnafu)?;
315 for authority in &self.authorities {
316 store
317 .add_cert(authority.clone())
318 .context(AddCertToStoreSnafu)?;
319 }
320 context
321 .set_verify_cert_store(store.build())
322 .context(SetVerifyCertSnafu)?;
323 }
324
325 if let Some(alpn) = &self.alpn_protocols {
326 if for_server {
327 let server_proto = alpn.clone();
328 let server_proto_ref: &'static [u8] = Box::leak(server_proto.into_boxed_slice());
330 context.set_alpn_select_callback(move |_, client_proto| {
331 select_next_proto(server_proto_ref, client_proto).ok_or(AlpnError::NOACK)
332 });
333 } else {
334 context
335 .set_alpn_protos(alpn.as_slice())
336 .context(SetAlpnProtocolsSnafu)?;
337 }
338 }
339
340 Ok(())
341 }
342
343 pub fn apply_connect_configuration(
344 &self,
345 connection: &mut ConnectConfiguration,
346 ) -> std::result::Result<(), openssl::error::ErrorStack> {
347 connection.set_verify_hostname(self.verify_hostname);
348 if let Some(server_name) = &self.server_name {
349 connection.set_use_server_name_indication(false);
351 connection.set_hostname(server_name)?;
352 }
353 Ok(())
354 }
355}
356
357impl TlsConfig {
358 fn load_authorities(&self) -> Result<Vec<X509>> {
359 match &self.ca_file {
360 None => Ok(vec![]),
361 Some(filename) => {
362 let (data, filename) = open_read(filename, "certificate")?;
363 der_or_pem(
364 data,
365 |der| X509::from_der(&der).map(|x509| vec![x509]),
366 |pem| {
367 pem.match_indices(PEM_START_MARKER)
368 .map(|(start, _)| X509::from_pem(&pem.as_bytes()[start..]))
369 .collect()
370 },
371 )
372 .with_context(|_| X509ParseSnafu { filename })
373 }
374 }
375 }
376
377 fn load_identity(&self) -> Result<Option<IdentityStore>> {
378 match (&self.crt_file, &self.key_file) {
379 (None, Some(_)) => Err(TlsError::MissingCrtKeyFile),
380 (None, None) => Ok(None),
381 (Some(filename), _) => {
382 let (data, filename) = open_read(filename, "certificate")?;
383 der_or_pem(
384 data,
385 |der| self.parse_pkcs12_identity(&der),
386 |pem| self.parse_pem_identity(&pem, &filename),
387 )
388 }
389 }
390 }
391
392 fn parse_alpn_protocols(&self) -> Result<Option<Vec<u8>>> {
396 match &self.alpn_protocols {
397 None => Ok(None),
398 Some(protocols) => {
399 let mut data: Vec<u8> = Vec::new();
400 for str in protocols {
401 data.push(str.len().try_into().context(EncodeAlpnProtocolsSnafu)?);
402 data.append(&mut str.clone().into_bytes());
403 }
404 Ok(Some(data))
405 }
406 }
407 }
408
409 fn parse_pem_identity(&self, pem: &str, crt_file: &Path) -> Result<Option<IdentityStore>> {
411 match &self.key_file {
412 None => Err(TlsError::MissingKey),
413 Some(key_file) => {
414 let mut crt_stack = X509::stack_from_pem(pem.as_bytes())
415 .with_context(|_| X509ParseSnafu { filename: crt_file })?
416 .into_iter();
417
418 let cert = crt_stack.next().ok_or(TlsError::MissingCertificate)?;
419 let key = load_key(key_file.as_path(), self.key_pass.as_ref())?;
420
421 let mut ca_stack = Stack::new().context(NewCaStackSnafu)?;
422 for intermediate in crt_stack {
423 ca_stack.push(intermediate).context(CaStackPushSnafu)?;
424 }
425 let ca: Vec<X509> = ca_stack
426 .iter()
427 .map(std::borrow::ToOwned::to_owned)
428 .collect();
429 Ok(Some(IdentityStore {
430 cert,
431 key,
432 ca: Some(ca),
433 }))
434 }
435 }
436 }
437
438 fn parse_pkcs12_identity(&self, der: &[u8]) -> Result<Option<IdentityStore>> {
440 let pkcs12 = Pkcs12::from_der(der).context(ParsePkcs12Snafu)?;
441 let key_pass = self.key_pass.as_deref().unwrap_or("");
443 let parsed = pkcs12.parse2(key_pass).context(ParsePkcs12Snafu)?;
444 let cert = parsed.cert.ok_or(TlsError::MissingCertificate)?;
446 let key = parsed.pkey.ok_or(TlsError::MissingKey)?;
447 let ca: Option<Vec<X509>> = parsed
448 .ca
449 .map(|stack| stack.iter().map(std::borrow::ToOwned::to_owned).collect());
450 Ok(Some(IdentityStore { cert, key, ca }))
451 }
452}
453
454#[cfg(windows)]
461fn load_windows_certs(builder: &mut SslContextBuilder) -> Result<()> {
462 use super::SchannelSnafu;
463
464 let mut store = X509StoreBuilder::new().context(NewStoreBuilderSnafu)?;
465
466 let current_user_store =
467 schannel::cert_store::CertStore::open_current_user("ROOT").context(SchannelSnafu)?;
468
469 for cert in current_user_store.certs() {
470 let cert = cert.to_der().to_vec();
471 let cert = X509::from_der(&cert[..]).context(super::X509SystemParseSnafu)?;
472 store.add_cert(cert).context(AddCertToStoreSnafu)?;
473 }
474
475 builder
476 .set_verify_cert_store(store.build())
477 .context(SetVerifyCertSnafu)?;
478
479 Ok(())
480}
481
482#[cfg(target_os = "macos")]
483fn load_mac_certs(builder: &mut SslContextBuilder) -> Result<()> {
484 use std::collections::HashMap;
485
486 use security_framework::trust_settings::{Domain, TrustSettings, TrustSettingsForCertificate};
487
488 use super::SecurityFrameworkSnafu;
489
490 let mut store = X509StoreBuilder::new().context(NewStoreBuilderSnafu)?;
502 let mut all_certs = HashMap::new();
503
504 for domain in &[Domain::User, Domain::Admin, Domain::System] {
505 let ts = TrustSettings::new(*domain);
506
507 for cert in ts.iter().context(SecurityFrameworkSnafu)? {
508 let trusted = ts
515 .tls_trust_settings_for_certificate(&cert)
516 .context(SecurityFrameworkSnafu)?
517 .unwrap_or(TrustSettingsForCertificate::TrustRoot);
518
519 all_certs.entry(cert.to_der()).or_insert(trusted);
520 }
521 }
522
523 for (cert, trusted) in all_certs {
524 if matches!(
525 trusted,
526 TrustSettingsForCertificate::TrustRoot | TrustSettingsForCertificate::TrustAsRoot
527 ) {
528 let cert = X509::from_der(&cert[..]).context(super::X509SystemParseSnafu)?;
529 store.add_cert(cert).context(AddCertToStoreSnafu)?;
530 }
531 }
532
533 builder
534 .set_verify_cert_store(store.build())
535 .context(SetVerifyCertSnafu)?;
536
537 Ok(())
538}
539
540impl fmt::Debug for TlsSettings {
541 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
542 f.debug_struct("TlsSettings")
543 .field("verify_certificate", &self.verify_certificate)
544 .field("verify_hostname", &self.verify_hostname)
545 .finish_non_exhaustive()
546 }
547}
548
549pub type MaybeTlsSettings = MaybeTls<(), TlsSettings>;
550
551impl MaybeTlsSettings {
552 pub fn enable_client() -> Result<Self> {
553 let tls = TlsSettings::from_options_base(None, false)?;
554 Ok(Self::Tls(tls))
555 }
556
557 pub fn tls_client(config: Option<&TlsConfig>) -> Result<Self> {
558 Ok(Self::Tls(TlsSettings::from_options_base(config, false)?))
559 }
560
561 pub fn from_config(config: Option<&TlsEnableableConfig>, for_server: bool) -> Result<Self> {
568 match config {
569 None => Ok(Self::Raw(())), Some(config) => {
571 if config.enabled.unwrap_or(false) {
572 let tls = TlsSettings::from_options_base(Some(&config.options), for_server)?;
573 match (for_server, &tls.identity) {
574 (true, None) => Err(TlsError::MissingRequiredIdentity),
576 _ => Ok(Self::Tls(tls)),
577 }
578 } else {
579 Ok(Self::Raw(())) }
581 }
582 }
583 }
584
585 pub const fn http_protocol_name(&self) -> &'static str {
586 match self {
587 MaybeTls::Raw(()) => "http",
588 MaybeTls::Tls(_) => "https",
589 }
590 }
591}
592
593impl From<TlsSettings> for MaybeTlsSettings {
594 fn from(tls: TlsSettings) -> Self {
595 Self::Tls(tls)
596 }
597}
598
599fn load_key(filename: &Path, pass_phrase: Option<&String>) -> Result<PKey<Private>> {
601 let (data, filename) = open_read(filename, "key")?;
602 match pass_phrase {
603 None => der_or_pem(
604 data,
605 |der| PKey::private_key_from_der(&der),
606 |pem| PKey::private_key_from_pem(pem.as_bytes()),
607 )
608 .with_context(|_| PrivateKeyParseSnafu { filename }),
609 Some(phrase) => der_or_pem(
610 data,
611 |der| PKey::private_key_from_pkcs8_passphrase(&der, phrase.as_bytes()),
612 |pem| PKey::private_key_from_pem_passphrase(pem.as_bytes(), phrase.as_bytes()),
613 )
614 .with_context(|_| PrivateKeyParseSnafu { filename }),
615 }
616}
617
618fn der_or_pem<T>(data: Vec<u8>, der_fn: impl Fn(Vec<u8>) -> T, pem_fn: impl Fn(String) -> T) -> T {
622 match String::from_utf8(data) {
625 Ok(text) => match text.find(PEM_START_MARKER) {
626 Some(_) => pem_fn(text),
627 None => der_fn(text.into_bytes()),
628 },
629 Err(err) => der_fn(err.into_bytes()),
630 }
631}
632
633fn open_read(filename: &Path, note: &'static str) -> Result<(Vec<u8>, PathBuf)> {
637 if let Some(filename) = filename.to_str()
638 && filename.contains(PEM_START_MARKER)
639 {
640 return Ok((Vec::from(filename), "inline text".into()));
641 }
642
643 let mut text = Vec::<u8>::new();
644
645 File::open(filename)
646 .with_context(|_| FileOpenFailedSnafu { note, filename })?
647 .read_to_end(&mut text)
648 .with_context(|_| FileReadFailedSnafu { note, filename })?;
649
650 Ok((text, filename.into()))
651}
652
653#[cfg(test)]
654mod test {
655 use super::*;
656
657 const TEST_PKCS12_PATH: &str = "tests/data/ca/intermediate_client/private/localhost.p12";
658 const TEST_PEM_CRT_BYTES: &[u8] =
659 include_bytes!("../../../../tests/data/ca/intermediate_server/certs/localhost.cert.pem");
660 const TEST_PEM_KEY_BYTES: &[u8] =
661 include_bytes!("../../../../tests/data/ca/intermediate_server/private/localhost.key.pem");
662
663 #[test]
664 fn parse_alpn_protocols() {
665 let options = TlsConfig {
666 alpn_protocols: Some(vec![String::from("h2")]),
667 ..Default::default()
668 };
669 let settings =
670 TlsSettings::from_options(Some(&options)).expect("Failed to parse alpn_protocols");
671 assert_eq!(settings.alpn_protocols, Some(vec![2, 104, 50]));
672 }
673
674 #[test]
675 fn from_options_pkcs12() {
676 let _provider = openssl::provider::Provider::try_load(None, "legacy", true).unwrap();
677 let options = TlsConfig {
678 crt_file: Some(TEST_PKCS12_PATH.into()),
679 key_pass: Some("NOPASS".into()),
680 ..Default::default()
681 };
682 let settings =
683 TlsSettings::from_options(Some(&options)).expect("Failed to load PKCS#12 certificate");
684 assert!(settings.identity.is_some());
685 assert_eq!(settings.authorities.len(), 0);
686 }
687
688 #[test]
689 fn from_options_pem() {
690 let options = TlsConfig {
691 crt_file: Some(TEST_PEM_CRT_PATH.into()),
692 key_file: Some(TEST_PEM_KEY_PATH.into()),
693 ..Default::default()
694 };
695 let settings =
696 TlsSettings::from_options(Some(&options)).expect("Failed to load PEM certificate");
697 assert!(settings.identity.is_some());
698 assert_eq!(settings.authorities.len(), 0);
699 }
700
701 #[test]
702 fn from_options_inline_pem() {
703 let crt = String::from_utf8(TEST_PEM_CRT_BYTES.to_vec()).unwrap();
704 let key = String::from_utf8(TEST_PEM_KEY_BYTES.to_vec()).unwrap();
705 let options = TlsConfig {
706 crt_file: Some(crt.into()),
707 key_file: Some(key.into()),
708 ..Default::default()
709 };
710 let settings =
711 TlsSettings::from_options(Some(&options)).expect("Failed to load PEM certificate");
712 assert!(settings.identity.is_some());
713 assert_eq!(settings.authorities.len(), 0);
714 }
715
716 #[test]
717 fn from_options_ca() {
718 let options = TlsConfig {
719 ca_file: Some(TEST_PEM_CA_PATH.into()),
720 ..Default::default()
721 };
722 let settings = TlsSettings::from_options(Some(&options))
723 .expect("Failed to load authority certificate");
724 assert!(settings.identity.is_none());
725 assert_eq!(settings.authorities.len(), 1);
726 }
727
728 #[test]
729 fn from_options_inline_ca() {
730 let ca = String::from_utf8(
731 include_bytes!("../../../../tests/data/ca/certs/ca.cert.pem").to_vec(),
732 )
733 .unwrap();
734 let options = TlsConfig {
735 ca_file: Some(ca.into()),
736 ..Default::default()
737 };
738 let settings = TlsSettings::from_options(Some(&options))
739 .expect("Failed to load authority certificate");
740 assert!(settings.identity.is_none());
741 assert_eq!(settings.authorities.len(), 1);
742 }
743
744 #[test]
745 fn from_options_intermediate_ca() {
746 let options = TlsConfig {
747 ca_file: Some("tests/data/ca/intermediate_server/certs/ca-chain.cert.pem".into()),
748 ..Default::default()
749 };
750 let settings = TlsSettings::from_options(Some(&options))
751 .expect("Failed to load authority certificate");
752 assert!(settings.identity.is_none());
753 assert_eq!(settings.authorities.len(), 2);
754 }
755
756 #[test]
757 fn from_options_multi_ca() {
758 let options = TlsConfig {
759 ca_file: Some("tests/data/Multi_CA.crt".into()),
760 ..Default::default()
761 };
762 let settings = TlsSettings::from_options(Some(&options))
763 .expect("Failed to load authority certificate");
764 assert!(settings.identity.is_none());
765 assert_eq!(settings.authorities.len(), 2);
766 }
767
768 #[test]
769 fn from_options_none() {
770 let settings = TlsSettings::from_options(None).expect("Failed to generate null settings");
771 assert!(settings.identity.is_none());
772 assert_eq!(settings.authorities.len(), 0);
773 }
774
775 #[test]
776 fn from_options_bad_certificate() {
777 let options = TlsConfig {
778 key_file: Some(TEST_PEM_KEY_PATH.into()),
779 ..Default::default()
780 };
781 let error = TlsSettings::from_options(Some(&options))
782 .expect_err("from_options failed to check certificate");
783 assert!(matches!(error, TlsError::MissingCrtKeyFile));
784
785 let options = TlsConfig {
786 crt_file: Some(TEST_PEM_CRT_PATH.into()),
787 ..Default::default()
788 };
789 let _error = TlsSettings::from_options(Some(&options))
790 .expect_err("from_options failed to check certificate");
791 }
793
794 #[test]
795 fn from_config_none() {
796 assert!(MaybeTlsSettings::from_config(None, true).unwrap().is_raw());
797 assert!(MaybeTlsSettings::from_config(None, false).unwrap().is_raw());
798 }
799
800 #[test]
801 fn from_config_not_enabled() {
802 assert!(settings_from_config(None, false, false, true).is_raw());
803 assert!(settings_from_config(None, false, false, false).is_raw());
804 assert!(settings_from_config(Some(false), false, false, true).is_raw());
805 assert!(settings_from_config(Some(false), false, false, false).is_raw());
806 }
807
808 #[test]
809 fn from_config_fails_without_certificate() {
810 let config = make_config(Some(true), false, false);
811 let error = MaybeTlsSettings::from_config(Some(&config), true)
812 .expect_err("from_config failed to check for a certificate");
813 assert!(matches!(error, TlsError::MissingRequiredIdentity));
814 }
815
816 #[test]
817 fn from_config_with_certificate() {
818 let config = settings_from_config(Some(true), true, true, true);
819 assert!(config.is_tls());
820 }
821
822 fn settings_from_config(
823 enabled: Option<bool>,
824 set_crt: bool,
825 set_key: bool,
826 for_server: bool,
827 ) -> MaybeTlsSettings {
828 let config = make_config(enabled, set_crt, set_key);
829 MaybeTlsSettings::from_config(Some(&config), for_server)
830 .expect("Failed to generate settings from config")
831 }
832
833 fn make_config(enabled: Option<bool>, set_crt: bool, set_key: bool) -> TlsEnableableConfig {
834 TlsEnableableConfig {
835 enabled,
836 options: TlsConfig {
837 crt_file: set_crt.then(|| TEST_PEM_CRT_PATH.into()),
838 key_file: set_key.then(|| TEST_PEM_KEY_PATH.into()),
839 ..Default::default()
840 },
841 }
842 }
843}