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