vector_core/tls/
settings.rs

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/// Configures the TLS options for incoming/outgoing connections.
40#[configurable_component]
41#[configurable(metadata(docs::advanced))]
42#[derive(Clone, Debug, Default)]
43#[serde(deny_unknown_fields)]
44pub struct TlsEnableableConfig {
45    /// Whether to require TLS for incoming or outgoing connections.
46    ///
47    /// When enabled and used for incoming connections, an identity certificate is also required. See `tls.crt_file` for
48    /// more information.
49    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/// `TlsEnableableConfig` for `sources`, adding metadata from the client certificate.
72#[configurable_component]
73#[derive(Clone, Debug, Default)]
74pub struct TlsSourceConfig {
75    /// Event field for client certificate metadata.
76    pub client_metadata_key: Option<OptionalValuePath>,
77
78    #[serde(flatten)]
79    pub tls_config: TlsEnableableConfig,
80}
81
82/// TLS configuration.
83#[configurable_component]
84#[configurable(metadata(docs::advanced))]
85#[derive(Clone, Debug, Default)]
86#[serde(deny_unknown_fields)]
87pub struct TlsConfig {
88    /// Enables certificate verification. For components that create a server, this requires that the
89    /// client connections have a valid client certificate. For components that initiate requests,
90    /// this validates that the upstream has a valid certificate.
91    ///
92    /// If enabled, certificates must not be expired and must be issued by a trusted
93    /// issuer. This verification operates in a hierarchical manner, checking that the leaf certificate (the
94    /// certificate presented by the client/server) is not only valid, but that the issuer of that certificate is also valid, and
95    /// so on, until the verification process reaches a root certificate.
96    ///
97    /// Do NOT set this to `false` unless you understand the risks of not verifying the validity of certificates.
98    pub verify_certificate: Option<bool>,
99
100    /// Enables hostname verification.
101    ///
102    /// If enabled, the hostname used to connect to the remote host must be present in the TLS certificate presented by
103    /// the remote host, either as the Common Name or as an entry in the Subject Alternative Name extension.
104    ///
105    /// Only relevant for outgoing connections.
106    ///
107    /// Do NOT set this to `false` unless you understand the risks of not verifying the remote hostname.
108    pub verify_hostname: Option<bool>,
109
110    /// Sets the list of supported ALPN protocols.
111    ///
112    /// Declare the supported ALPN protocols, which are used during negotiation with a peer. They are prioritized in the order
113    /// that they are defined.
114    #[configurable(metadata(docs::examples = "h2"))]
115    pub alpn_protocols: Option<Vec<String>>,
116
117    /// Absolute path to an additional CA certificate file.
118    ///
119    /// The certificate must be in the DER or PEM (X.509) format. Additionally, the certificate can be provided as an inline string in PEM format.
120    #[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    /// Absolute path to a certificate file used to identify this server.
126    ///
127    /// The certificate must be in DER, PEM (X.509), or PKCS#12 format. Additionally, the certificate can be provided as
128    /// an inline string in PEM format.
129    ///
130    /// If this is set _and_ is not a PKCS#12 archive, `key_file` must also be set.
131    #[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    /// Absolute path to a private key file used to identify this server.
137    ///
138    /// The key must be in DER or PEM (PKCS#8) format. Additionally, the key can be provided as an inline string in PEM format.
139    #[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    /// Passphrase used to unlock the encrypted key file.
145    ///
146    /// This has no effect unless `key_file` is set.
147    #[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    /// Server name to use when using Server Name Indication (SNI).
153    ///
154    /// Only relevant for outgoing connections.
155    #[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/// Directly usable settings for TLS connectors
173#[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/// Identity store in PEM format
184#[derive(Clone)]
185pub(super) struct IdentityStore {
186    cert: X509,
187    key: PKey<Private>,
188    ca: Option<Vec<X509>>,
189}
190
191impl TlsSettings {
192    /// Generate a filled out settings struct from the given optional
193    /// option set, interpreted as client options. If `options` is
194    /// `None`, the result is set to defaults (ie empty).
195    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    /// Returns the identity as PEM encoded byte arrays
227    ///
228    /// # Panics
229    ///
230    /// Panics if the identity is missing, invalid, or the authorities to chain are invalid.
231    pub fn identity_pem(&self) -> Option<(Vec<u8>, Vec<u8>)> {
232        self.identity.as_ref().map(|identity| {
233            // we have verified correct formatting at ingest time
234            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    /// Returns the authorities as PEM data
253    ///
254    /// # Panics
255    ///
256    /// Panics if the authority is invalid.
257    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! { // Panic in release builds, warn in debug builds.
303                        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                // See https://github.com/sfackler/rust-openssl/pull/2360.
329                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            // Prevent native TLS lib from inferring default SNI using domain name from url.
350            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    /// The input must be in ALPN "wire format".
393    ///
394    /// It consists of a sequence of supported protocol names prefixed by their byte length.
395    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    /// Parse identity from a PEM encoded certificate + key pair of files
410    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    /// Parse identity from a DER encoded PKCS#12 archive
439    fn parse_pkcs12_identity(&self, der: &[u8]) -> Result<Option<IdentityStore>> {
440        let pkcs12 = Pkcs12::from_der(der).context(ParsePkcs12Snafu)?;
441        // Verify password
442        let key_pass = self.key_pass.as_deref().unwrap_or("");
443        let parsed = pkcs12.parse2(key_pass).context(ParsePkcs12Snafu)?;
444        // extract cert, key and ca and store as PEM sow e can return an IdentityStore
445        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/// === System Specific Root Cert ===
455///
456/// Most of this code is borrowed from https://github.com/ctz/rustls-native-certs
457
458/// Load the system default certs from `schannel` this should be in place
459/// of openssl-probe on linux.
460#[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    // The various domains are designed to interact like this:
491    //
492    // "Per-user Trust Settings override locally administered
493    //  Trust Settings, which in turn override the System Trust
494    //  Settings."
495    //
496    // So we collect the certificates in this order; as a map of
497    // their DER encoding to what we'll do with them.  We don't
498    // overwrite existing elements, which mean User settings
499    // trump Admin trump System, as desired.
500
501    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            // If there are no specific trust settings, the default
509            // is to trust the certificate as a root cert.  Weird API but OK.
510            // The docs say:
511            //
512            // "Note that an empty Trust Settings array means "always trust this cert,
513            //  with a resulting kSecTrustSettingsResult of kSecTrustSettingsResultTrustRoot".
514            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    /// Generate an optional settings struct from the given optional
562    /// configuration reference. If `config` is `None`, TLS is
563    /// disabled. The `for_server` parameter indicates the options
564    /// should be interpreted as being for a TLS server, which requires
565    /// an identity certificate and changes the certificate verification
566    /// default to false.
567    pub fn from_config(config: Option<&TlsEnableableConfig>, for_server: bool) -> Result<Self> {
568        match config {
569            None => Ok(Self::Raw(())), // No config, no TLS settings
570            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                        // Servers require an identity certificate
575                        (true, None) => Err(TlsError::MissingRequiredIdentity),
576                        _ => Ok(Self::Tls(tls)),
577                    }
578                } else {
579                    Ok(Self::Raw(())) // Explicitly disabled, still no TLS settings
580                }
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
599/// Load a private key from a named file
600fn 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
618/// Parse the data one way if it looks like a DER file, and the other if
619/// it looks like a PEM file. For the content to be treated as PEM, it
620/// must parse as valid UTF-8 and contain a PEM start marker.
621fn der_or_pem<T>(data: Vec<u8>, der_fn: impl Fn(Vec<u8>) -> T, pem_fn: impl Fn(String) -> T) -> T {
622    // None of these steps cause (re)allocations,
623    // just parsing and type manipulation
624    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
633/// Open the named file and read its entire contents into memory. If the
634/// file "name" contains a PEM start marker, it is assumed to contain
635/// inline data and is used directly instead of opening a file.
636fn 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        // Actual error is an ASN parse, doesn't really matter
792    }
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}