1use std::{
2 fmt,
3 fs::File,
4 io::Read,
5 path::{Path, PathBuf},
6};
7
8use cfg_if::cfg_if;
9use lookup::lookup_v2::OptionalValuePath;
10use openssl::{
11 pkcs12::{ParsedPkcs12_2, Pkcs12},
12 pkey::{PKey, Private},
13 ssl::{AlpnError, ConnectConfiguration, SslContextBuilder, SslVerifyMode, select_next_proto},
14 stack::Stack,
15 x509::{X509, store::X509StoreBuilder},
16};
17use snafu::ResultExt;
18use vector_config::configurable_component;
19
20use super::{
21 AddCertToStoreSnafu, AddExtraChainCertSnafu, CaStackPushSnafu, DerExportSnafu,
22 EncodeAlpnProtocolsSnafu, FileOpenFailedSnafu, FileReadFailedSnafu, MaybeTls, NewCaStackSnafu,
23 NewStoreBuilderSnafu, ParsePkcs12Snafu, Pkcs12Snafu, PrivateKeyParseSnafu, Result,
24 SetAlpnProtocolsSnafu, SetCertificateSnafu, SetPrivateKeySnafu, SetVerifyCertSnafu, TlsError,
25 TlsIdentitySnafu, X509ParseSnafu,
26};
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>, alpn_protocols: Option<Vec<u8>>,
182 server_name: Option<String>,
183}
184
185#[derive(Clone)]
186pub(super) struct IdentityStore(Vec<u8>, String);
187
188impl TlsSettings {
189 pub fn from_options(options: Option<&TlsConfig>) -> Result<Self> {
193 Self::from_options_base(options, false)
194 }
195
196 pub(super) fn from_options_base(options: Option<&TlsConfig>, for_server: bool) -> Result<Self> {
197 let default = TlsConfig::default();
198 let options = options.unwrap_or(&default);
199
200 if !for_server {
201 if options.verify_certificate == Some(false) {
202 warn!(
203 "The `verify_certificate` option is DISABLED, this may lead to security vulnerabilities."
204 );
205 }
206 if options.verify_hostname == Some(false) {
207 warn!(
208 "The `verify_hostname` option is DISABLED, this may lead to security vulnerabilities."
209 );
210 }
211 }
212
213 Ok(Self {
214 verify_certificate: options.verify_certificate.unwrap_or(!for_server),
215 verify_hostname: options.verify_hostname.unwrap_or(!for_server),
216 authorities: options.load_authorities()?,
217 identity: options.load_identity()?,
218 alpn_protocols: options.parse_alpn_protocols()?,
219 server_name: options.server_name.clone(),
220 })
221 }
222
223 fn identity(&self) -> Option<ParsedPkcs12_2> {
229 self.identity.as_ref().map(|identity| {
234 Pkcs12::from_der(&identity.0)
235 .expect("Could not build PKCS#12 archive from parsed data")
236 .parse2(&identity.1)
237 .expect("Could not parse stored PKCS#12 archive")
238 })
239 }
240
241 pub fn identity_pem(&self) -> Option<(Vec<u8>, Vec<u8>)> {
247 self.identity().map(|identity| {
248 let mut cert = identity
249 .cert
250 .expect("Identity required")
251 .to_pem()
252 .expect("Invalid stored identity");
253 if let Some(chain) = identity.ca {
254 for authority in chain {
255 cert.extend(
256 authority
257 .to_pem()
258 .expect("Invalid stored identity chain certificate"),
259 );
260 }
261 }
262 let key = identity
263 .pkey
264 .expect("Private key required")
265 .private_key_to_pem_pkcs8()
266 .expect("Invalid stored private key");
267 (cert, key)
268 })
269 }
270
271 pub fn authorities_pem(&self) -> impl Iterator<Item = Vec<u8>> + '_ {
277 self.authorities.iter().map(|authority| {
278 authority
279 .to_pem()
280 .expect("Invalid stored authority certificate")
281 })
282 }
283
284 pub(super) fn apply_context(&self, context: &mut SslContextBuilder) -> Result<()> {
285 self.apply_context_base(context, false)
286 }
287
288 pub(super) fn apply_context_base(
289 &self,
290 context: &mut SslContextBuilder,
291 for_server: bool,
292 ) -> Result<()> {
293 context.set_verify(if self.verify_certificate {
294 SslVerifyMode::PEER | SslVerifyMode::FAIL_IF_NO_PEER_CERT
295 } else {
296 SslVerifyMode::NONE
297 });
298 if let Some(identity) = self.identity() {
299 if let Some(cert) = &identity.cert {
300 context.set_certificate(cert).context(SetCertificateSnafu)?;
301 }
302 if let Some(pkey) = &identity.pkey {
303 context.set_private_key(pkey).context(SetPrivateKeySnafu)?;
304 }
305
306 if let Some(chain) = identity.ca {
307 for cert in chain {
308 context
309 .add_extra_chain_cert(cert)
310 .context(AddExtraChainCertSnafu)?;
311 }
312 }
313 }
314 if self.authorities.is_empty() {
315 debug!("Fetching system root certs.");
316
317 cfg_if! {
318 if #[cfg(windows)] {
319 load_windows_certs(context).unwrap();
320 } else if #[cfg(target_os = "macos")] {
321 cfg_if! { if #[cfg(debug_assertions)] {
323 if let Err(error) = load_mac_certs(context) {
324 warn!("Failed to load macOS certs: {error}");
325 }
326 } else {
327 load_mac_certs(context).unwrap();
328 }
329 }
330 }
331 }
332 } else {
333 let mut store = X509StoreBuilder::new().context(NewStoreBuilderSnafu)?;
334 for authority in &self.authorities {
335 store
336 .add_cert(authority.clone())
337 .context(AddCertToStoreSnafu)?;
338 }
339 context
340 .set_verify_cert_store(store.build())
341 .context(SetVerifyCertSnafu)?;
342 }
343
344 if let Some(alpn) = &self.alpn_protocols {
345 if for_server {
346 let server_proto = alpn.clone();
347 let server_proto_ref: &'static [u8] = Box::leak(server_proto.into_boxed_slice());
349 context.set_alpn_select_callback(move |_, client_proto| {
350 select_next_proto(server_proto_ref, client_proto).ok_or(AlpnError::NOACK)
351 });
352 } else {
353 context
354 .set_alpn_protos(alpn.as_slice())
355 .context(SetAlpnProtocolsSnafu)?;
356 }
357 }
358
359 Ok(())
360 }
361
362 pub fn apply_connect_configuration(
363 &self,
364 connection: &mut ConnectConfiguration,
365 ) -> std::result::Result<(), openssl::error::ErrorStack> {
366 connection.set_verify_hostname(self.verify_hostname);
367 if let Some(server_name) = &self.server_name {
368 connection.set_use_server_name_indication(false);
370 connection.set_hostname(server_name)?;
371 }
372 Ok(())
373 }
374}
375
376impl TlsConfig {
377 fn load_authorities(&self) -> Result<Vec<X509>> {
378 match &self.ca_file {
379 None => Ok(vec![]),
380 Some(filename) => {
381 let (data, filename) = open_read(filename, "certificate")?;
382 der_or_pem(
383 data,
384 |der| X509::from_der(&der).map(|x509| vec![x509]),
385 |pem| {
386 pem.match_indices(PEM_START_MARKER)
387 .map(|(start, _)| X509::from_pem(&pem.as_bytes()[start..]))
388 .collect()
389 },
390 )
391 .with_context(|_| X509ParseSnafu { filename })
392 }
393 }
394 }
395
396 fn load_identity(&self) -> Result<Option<IdentityStore>> {
397 match (&self.crt_file, &self.key_file) {
398 (None, Some(_)) => Err(TlsError::MissingCrtKeyFile),
399 (None, None) => Ok(None),
400 (Some(filename), _) => {
401 let (data, filename) = open_read(filename, "certificate")?;
402 der_or_pem(
403 data,
404 |der| self.parse_pkcs12_identity(der),
405 |pem| self.parse_pem_identity(&pem, &filename),
406 )
407 }
408 }
409 }
410
411 fn parse_alpn_protocols(&self) -> Result<Option<Vec<u8>>> {
415 match &self.alpn_protocols {
416 None => Ok(None),
417 Some(protocols) => {
418 let mut data: Vec<u8> = Vec::new();
419 for str in protocols {
420 data.push(str.len().try_into().context(EncodeAlpnProtocolsSnafu)?);
421 data.append(&mut str.clone().into_bytes());
422 }
423 Ok(Some(data))
424 }
425 }
426 }
427
428 fn parse_pem_identity(&self, pem: &str, crt_file: &Path) -> Result<Option<IdentityStore>> {
430 match &self.key_file {
431 None => Err(TlsError::MissingKey),
432 Some(key_file) => {
433 let name = crt_file.to_string_lossy().to_string();
434 let mut crt_stack = X509::stack_from_pem(pem.as_bytes())
435 .with_context(|_| X509ParseSnafu { filename: crt_file })?
436 .into_iter();
437
438 let crt = crt_stack.next().ok_or(TlsError::MissingCertificate)?;
439 let key = load_key(key_file.as_path(), self.key_pass.as_ref())?;
440
441 let mut ca_stack = Stack::new().context(NewCaStackSnafu)?;
442 for intermediate in crt_stack {
443 ca_stack.push(intermediate).context(CaStackPushSnafu)?;
444 }
445
446 let pkcs12 = Pkcs12::builder()
447 .ca(ca_stack)
448 .name(&name)
449 .pkey(&key)
450 .cert(&crt)
451 .build2("")
452 .context(Pkcs12Snafu)?;
453 let identity = pkcs12.to_der().context(DerExportSnafu)?;
454
455 pkcs12.parse2("").context(TlsIdentitySnafu)?;
459
460 Ok(Some(IdentityStore(identity, String::new())))
461 }
462 }
463 }
464
465 fn parse_pkcs12_identity(&self, der: Vec<u8>) -> Result<Option<IdentityStore>> {
467 let pkcs12 = Pkcs12::from_der(&der).context(ParsePkcs12Snafu)?;
468 let key_pass = self.key_pass.as_deref().unwrap_or("");
470 pkcs12.parse2(key_pass).context(ParsePkcs12Snafu)?;
471 Ok(Some(IdentityStore(der, key_pass.to_string())))
472 }
473}
474
475#[cfg(windows)]
482fn load_windows_certs(builder: &mut SslContextBuilder) -> Result<()> {
483 use super::SchannelSnafu;
484
485 let mut store = X509StoreBuilder::new().context(NewStoreBuilderSnafu)?;
486
487 let current_user_store =
488 schannel::cert_store::CertStore::open_current_user("ROOT").context(SchannelSnafu)?;
489
490 for cert in current_user_store.certs() {
491 let cert = cert.to_der().to_vec();
492 let cert = X509::from_der(&cert[..]).context(super::X509SystemParseSnafu)?;
493 store.add_cert(cert).context(AddCertToStoreSnafu)?;
494 }
495
496 builder
497 .set_verify_cert_store(store.build())
498 .context(SetVerifyCertSnafu)?;
499
500 Ok(())
501}
502
503#[cfg(target_os = "macos")]
504fn load_mac_certs(builder: &mut SslContextBuilder) -> Result<()> {
505 use std::collections::HashMap;
506
507 use security_framework::trust_settings::{Domain, TrustSettings, TrustSettingsForCertificate};
508
509 use super::SecurityFrameworkSnafu;
510
511 let mut store = X509StoreBuilder::new().context(NewStoreBuilderSnafu)?;
523 let mut all_certs = HashMap::new();
524
525 for domain in &[Domain::User, Domain::Admin, Domain::System] {
526 let ts = TrustSettings::new(*domain);
527
528 for cert in ts.iter().context(SecurityFrameworkSnafu)? {
529 let trusted = ts
536 .tls_trust_settings_for_certificate(&cert)
537 .context(SecurityFrameworkSnafu)?
538 .unwrap_or(TrustSettingsForCertificate::TrustRoot);
539
540 all_certs.entry(cert.to_der()).or_insert(trusted);
541 }
542 }
543
544 for (cert, trusted) in all_certs {
545 if matches!(
546 trusted,
547 TrustSettingsForCertificate::TrustRoot | TrustSettingsForCertificate::TrustAsRoot
548 ) {
549 let cert = X509::from_der(&cert[..]).context(super::X509SystemParseSnafu)?;
550 store.add_cert(cert).context(AddCertToStoreSnafu)?;
551 }
552 }
553
554 builder
555 .set_verify_cert_store(store.build())
556 .context(SetVerifyCertSnafu)?;
557
558 Ok(())
559}
560
561impl fmt::Debug for TlsSettings {
562 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
563 f.debug_struct("TlsSettings")
564 .field("verify_certificate", &self.verify_certificate)
565 .field("verify_hostname", &self.verify_hostname)
566 .finish_non_exhaustive()
567 }
568}
569
570pub type MaybeTlsSettings = MaybeTls<(), TlsSettings>;
571
572impl MaybeTlsSettings {
573 pub fn enable_client() -> Result<Self> {
574 let tls = TlsSettings::from_options_base(None, false)?;
575 Ok(Self::Tls(tls))
576 }
577
578 pub fn tls_client(config: Option<&TlsConfig>) -> Result<Self> {
579 Ok(Self::Tls(TlsSettings::from_options_base(config, false)?))
580 }
581
582 pub fn from_config(config: Option<&TlsEnableableConfig>, for_server: bool) -> Result<Self> {
589 match config {
590 None => Ok(Self::Raw(())), Some(config) => {
592 if config.enabled.unwrap_or(false) {
593 let tls = TlsSettings::from_options_base(Some(&config.options), for_server)?;
594 match (for_server, &tls.identity) {
595 (true, None) => Err(TlsError::MissingRequiredIdentity),
597 _ => Ok(Self::Tls(tls)),
598 }
599 } else {
600 Ok(Self::Raw(())) }
602 }
603 }
604 }
605
606 pub const fn http_protocol_name(&self) -> &'static str {
607 match self {
608 MaybeTls::Raw(()) => "http",
609 MaybeTls::Tls(_) => "https",
610 }
611 }
612}
613
614impl From<TlsSettings> for MaybeTlsSettings {
615 fn from(tls: TlsSettings) -> Self {
616 Self::Tls(tls)
617 }
618}
619
620fn load_key(filename: &Path, pass_phrase: Option<&String>) -> Result<PKey<Private>> {
622 let (data, filename) = open_read(filename, "key")?;
623 match pass_phrase {
624 None => der_or_pem(
625 data,
626 |der| PKey::private_key_from_der(&der),
627 |pem| PKey::private_key_from_pem(pem.as_bytes()),
628 )
629 .with_context(|_| PrivateKeyParseSnafu { filename }),
630 Some(phrase) => der_or_pem(
631 data,
632 |der| PKey::private_key_from_pkcs8_passphrase(&der, phrase.as_bytes()),
633 |pem| PKey::private_key_from_pem_passphrase(pem.as_bytes(), phrase.as_bytes()),
634 )
635 .with_context(|_| PrivateKeyParseSnafu { filename }),
636 }
637}
638
639fn der_or_pem<T>(data: Vec<u8>, der_fn: impl Fn(Vec<u8>) -> T, pem_fn: impl Fn(String) -> T) -> T {
643 match String::from_utf8(data) {
646 Ok(text) => match text.find(PEM_START_MARKER) {
647 Some(_) => pem_fn(text),
648 None => der_fn(text.into_bytes()),
649 },
650 Err(err) => der_fn(err.into_bytes()),
651 }
652}
653
654fn open_read(filename: &Path, note: &'static str) -> Result<(Vec<u8>, PathBuf)> {
658 if let Some(filename) = filename.to_str()
659 && filename.contains(PEM_START_MARKER)
660 {
661 return Ok((Vec::from(filename), "inline text".into()));
662 }
663
664 let mut text = Vec::<u8>::new();
665
666 File::open(filename)
667 .with_context(|_| FileOpenFailedSnafu { note, filename })?
668 .read_to_end(&mut text)
669 .with_context(|_| FileReadFailedSnafu { note, filename })?;
670
671 Ok((text, filename.into()))
672}
673
674#[cfg(test)]
675mod test {
676 use super::*;
677
678 const TEST_PKCS12_PATH: &str = "tests/data/ca/intermediate_client/private/localhost.p12";
679 const TEST_PEM_CRT_BYTES: &[u8] =
680 include_bytes!("../../../../tests/data/ca/intermediate_server/certs/localhost.cert.pem");
681 const TEST_PEM_KEY_BYTES: &[u8] =
682 include_bytes!("../../../../tests/data/ca/intermediate_server/private/localhost.key.pem");
683
684 #[test]
685 fn parse_alpn_protocols() {
686 let options = TlsConfig {
687 alpn_protocols: Some(vec![String::from("h2")]),
688 ..Default::default()
689 };
690 let settings =
691 TlsSettings::from_options(Some(&options)).expect("Failed to parse alpn_protocols");
692 assert_eq!(settings.alpn_protocols, Some(vec![2, 104, 50]));
693 }
694
695 #[test]
696 fn from_options_pkcs12() {
697 let _provider = openssl::provider::Provider::try_load(None, "legacy", true).unwrap();
698 let options = TlsConfig {
699 crt_file: Some(TEST_PKCS12_PATH.into()),
700 key_pass: Some("NOPASS".into()),
701 ..Default::default()
702 };
703 let settings =
704 TlsSettings::from_options(Some(&options)).expect("Failed to load PKCS#12 certificate");
705 assert!(settings.identity.is_some());
706 assert_eq!(settings.authorities.len(), 0);
707 }
708
709 #[test]
710 fn from_options_pem() {
711 let options = TlsConfig {
712 crt_file: Some(TEST_PEM_CRT_PATH.into()),
713 key_file: Some(TEST_PEM_KEY_PATH.into()),
714 ..Default::default()
715 };
716 let settings =
717 TlsSettings::from_options(Some(&options)).expect("Failed to load PEM certificate");
718 assert!(settings.identity.is_some());
719 assert_eq!(settings.authorities.len(), 0);
720 }
721
722 #[test]
723 fn from_options_inline_pem() {
724 let crt = String::from_utf8(TEST_PEM_CRT_BYTES.to_vec()).unwrap();
725 let key = String::from_utf8(TEST_PEM_KEY_BYTES.to_vec()).unwrap();
726 let options = TlsConfig {
727 crt_file: Some(crt.into()),
728 key_file: Some(key.into()),
729 ..Default::default()
730 };
731 let settings =
732 TlsSettings::from_options(Some(&options)).expect("Failed to load PEM certificate");
733 assert!(settings.identity.is_some());
734 assert_eq!(settings.authorities.len(), 0);
735 }
736
737 #[test]
738 fn from_options_ca() {
739 let options = TlsConfig {
740 ca_file: Some(TEST_PEM_CA_PATH.into()),
741 ..Default::default()
742 };
743 let settings = TlsSettings::from_options(Some(&options))
744 .expect("Failed to load authority certificate");
745 assert!(settings.identity.is_none());
746 assert_eq!(settings.authorities.len(), 1);
747 }
748
749 #[test]
750 fn from_options_inline_ca() {
751 let ca = String::from_utf8(
752 include_bytes!("../../../../tests/data/ca/certs/ca.cert.pem").to_vec(),
753 )
754 .unwrap();
755 let options = TlsConfig {
756 ca_file: Some(ca.into()),
757 ..Default::default()
758 };
759 let settings = TlsSettings::from_options(Some(&options))
760 .expect("Failed to load authority certificate");
761 assert!(settings.identity.is_none());
762 assert_eq!(settings.authorities.len(), 1);
763 }
764
765 #[test]
766 fn from_options_intermediate_ca() {
767 let options = TlsConfig {
768 ca_file: Some("tests/data/ca/intermediate_server/certs/ca-chain.cert.pem".into()),
769 ..Default::default()
770 };
771 let settings = TlsSettings::from_options(Some(&options))
772 .expect("Failed to load authority certificate");
773 assert!(settings.identity.is_none());
774 assert_eq!(settings.authorities.len(), 2);
775 }
776
777 #[test]
778 fn from_options_multi_ca() {
779 let options = TlsConfig {
780 ca_file: Some("tests/data/Multi_CA.crt".into()),
781 ..Default::default()
782 };
783 let settings = TlsSettings::from_options(Some(&options))
784 .expect("Failed to load authority certificate");
785 assert!(settings.identity.is_none());
786 assert_eq!(settings.authorities.len(), 2);
787 }
788
789 #[test]
790 fn from_options_none() {
791 let settings = TlsSettings::from_options(None).expect("Failed to generate null settings");
792 assert!(settings.identity.is_none());
793 assert_eq!(settings.authorities.len(), 0);
794 }
795
796 #[test]
797 fn from_options_bad_certificate() {
798 let options = TlsConfig {
799 key_file: Some(TEST_PEM_KEY_PATH.into()),
800 ..Default::default()
801 };
802 let error = TlsSettings::from_options(Some(&options))
803 .expect_err("from_options failed to check certificate");
804 assert!(matches!(error, TlsError::MissingCrtKeyFile));
805
806 let options = TlsConfig {
807 crt_file: Some(TEST_PEM_CRT_PATH.into()),
808 ..Default::default()
809 };
810 let _error = TlsSettings::from_options(Some(&options))
811 .expect_err("from_options failed to check certificate");
812 }
814
815 #[test]
816 fn from_config_none() {
817 assert!(MaybeTlsSettings::from_config(None, true).unwrap().is_raw());
818 assert!(MaybeTlsSettings::from_config(None, false).unwrap().is_raw());
819 }
820
821 #[test]
822 fn from_config_not_enabled() {
823 assert!(settings_from_config(None, false, false, true).is_raw());
824 assert!(settings_from_config(None, false, false, false).is_raw());
825 assert!(settings_from_config(Some(false), false, false, true).is_raw());
826 assert!(settings_from_config(Some(false), false, false, false).is_raw());
827 }
828
829 #[test]
830 fn from_config_fails_without_certificate() {
831 let config = make_config(Some(true), false, false);
832 let error = MaybeTlsSettings::from_config(Some(&config), true)
833 .expect_err("from_config failed to check for a certificate");
834 assert!(matches!(error, TlsError::MissingRequiredIdentity));
835 }
836
837 #[test]
838 fn from_config_with_certificate() {
839 let config = settings_from_config(Some(true), true, true, true);
840 assert!(config.is_tls());
841 }
842
843 fn settings_from_config(
844 enabled: Option<bool>,
845 set_crt: bool,
846 set_key: bool,
847 for_server: bool,
848 ) -> MaybeTlsSettings {
849 let config = make_config(enabled, set_crt, set_key);
850 MaybeTlsSettings::from_config(Some(&config), for_server)
851 .expect("Failed to generate settings from config")
852 }
853
854 fn make_config(enabled: Option<bool>, set_crt: bool, set_key: bool) -> TlsEnableableConfig {
855 TlsEnableableConfig {
856 enabled,
857 options: TlsConfig {
858 crt_file: set_crt.then(|| TEST_PEM_CRT_PATH.into()),
859 key_file: set_key.then(|| TEST_PEM_KEY_PATH.into()),
860 ..Default::default()
861 },
862 }
863 }
864}