vector_core/tls/
settings.rs

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/// Configures the TLS options for incoming/outgoing connections.
42#[configurable_component]
43#[configurable(metadata(docs::advanced))]
44#[derive(Clone, Debug, Default)]
45#[serde(deny_unknown_fields)]
46pub struct TlsEnableableConfig {
47    /// Whether to require TLS for incoming or outgoing connections.
48    ///
49    /// When enabled and used for incoming connections, an identity certificate is also required. See `tls.crt_file` for
50    /// more information.
51    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/// `TlsEnableableConfig` for `sources`, adding metadata from the client certificate.
74#[configurable_component]
75#[derive(Clone, Debug, Default)]
76pub struct TlsSourceConfig {
77    /// Event field for client certificate metadata.
78    pub client_metadata_key: Option<OptionalValuePath>,
79
80    #[serde(flatten)]
81    pub tls_config: TlsEnableableConfig,
82}
83
84/// TLS configuration.
85#[configurable_component]
86#[configurable(metadata(docs::advanced))]
87#[derive(Clone, Debug, Default)]
88#[serde(deny_unknown_fields)]
89pub struct TlsConfig {
90    /// Enables certificate verification. For components that create a server, this requires that the
91    /// client connections have a valid client certificate. For components that initiate requests,
92    /// this validates that the upstream has a valid certificate.
93    ///
94    /// If enabled, certificates must not be expired and must be issued by a trusted
95    /// issuer. This verification operates in a hierarchical manner, checking that the leaf certificate (the
96    /// certificate presented by the client/server) is not only valid, but that the issuer of that certificate is also valid, and
97    /// so on, until the verification process reaches a root certificate.
98    ///
99    /// Do NOT set this to `false` unless you understand the risks of not verifying the validity of certificates.
100    pub verify_certificate: Option<bool>,
101
102    /// Enables hostname verification.
103    ///
104    /// If enabled, the hostname used to connect to the remote host must be present in the TLS certificate presented by
105    /// the remote host, either as the Common Name or as an entry in the Subject Alternative Name extension.
106    ///
107    /// Only relevant for outgoing connections.
108    ///
109    /// Do NOT set this to `false` unless you understand the risks of not verifying the remote hostname.
110    pub verify_hostname: Option<bool>,
111
112    /// Sets the list of supported ALPN protocols.
113    ///
114    /// Declare the supported ALPN protocols, which are used during negotiation with a peer. They are prioritized in the order
115    /// that they are defined.
116    #[configurable(metadata(docs::examples = "h2"))]
117    pub alpn_protocols: Option<Vec<String>>,
118
119    /// Absolute path to an additional CA certificate file.
120    ///
121    /// 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.
122    #[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    /// Absolute path to a certificate file used to identify this server.
128    ///
129    /// The certificate must be in DER, PEM (X.509), or PKCS#12 format. Additionally, the certificate can be provided as
130    /// an inline string in PEM format.
131    ///
132    /// If this is set _and_ is not a PKCS#12 archive, `key_file` must also be set.
133    #[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    /// Absolute path to a private key file used to identify this server.
139    ///
140    /// The key must be in DER or PEM (PKCS#8) format. Additionally, the key can be provided as an inline string in PEM format.
141    #[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    /// Passphrase used to unlock the encrypted key file.
147    ///
148    /// This has no effect unless `key_file` is set.
149    #[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    /// Server name to use when using Server Name Indication (SNI).
155    ///
156    /// Only relevant for outgoing connections.
157    #[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/// Directly usable settings for TLS connectors
175#[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>, // openssl::pkcs12::ParsedPkcs12 doesn't impl Clone yet
181    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    /// Generate a filled out settings struct from the given optional
190    /// option set, interpreted as client options. If `options` is
191    /// `None`, the result is set to defaults (ie empty).
192    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    /// Returns the identity as PKCS12
224    ///
225    /// # Panics
226    ///
227    /// Panics if the identity is invalid.
228    fn identity(&self) -> Option<ParsedPkcs12_2> {
229        // This data was test-built previously, so we can just use it
230        // here and expect the results will not fail. This can all be
231        // reworked when `openssl::pkcs12::ParsedPkcs12` gains the Clone
232        // impl.
233        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    /// Returns the identity as PEM data
242    ///
243    /// # Panics
244    ///
245    /// Panics if the identity is missing, invalid, or the authorities to chain are invalid.
246    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    /// Returns the authorities as PEM data
272    ///
273    /// # Panics
274    ///
275    /// Panics if the authority is invalid.
276    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! { // Panic in release builds, warn in debug builds.
322                        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                // See https://github.com/sfackler/rust-openssl/pull/2360.
348                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            // Prevent native TLS lib from inferring default SNI using domain name from url.
369            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    /// The input must be in ALPN "wire format".
412    ///
413    /// It consists of a sequence of supported protocol names prefixed by their byte length.
414    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    /// Parse identity from a PEM encoded certificate + key pair of files
429    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                // Build the resulting parsed PKCS#12 archive,
456                // but don't store it, as it cannot be cloned.
457                // This is just for error checking.
458                pkcs12.parse2("").context(TlsIdentitySnafu)?;
459
460                Ok(Some(IdentityStore(identity, String::new())))
461            }
462        }
463    }
464
465    /// Parse identity from a DER encoded PKCS#12 archive
466    fn parse_pkcs12_identity(&self, der: Vec<u8>) -> Result<Option<IdentityStore>> {
467        let pkcs12 = Pkcs12::from_der(&der).context(ParsePkcs12Snafu)?;
468        // Verify password
469        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/// === System Specific Root Cert ===
476///
477/// Most of this code is borrowed from https://github.com/ctz/rustls-native-certs
478
479/// Load the system default certs from `schannel` this should be in place
480/// of openssl-probe on linux.
481#[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    // The various domains are designed to interact like this:
512    //
513    // "Per-user Trust Settings override locally administered
514    //  Trust Settings, which in turn override the System Trust
515    //  Settings."
516    //
517    // So we collect the certificates in this order; as a map of
518    // their DER encoding to what we'll do with them.  We don't
519    // overwrite existing elements, which mean User settings
520    // trump Admin trump System, as desired.
521
522    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            // If there are no specific trust settings, the default
530            // is to trust the certificate as a root cert.  Weird API but OK.
531            // The docs say:
532            //
533            // "Note that an empty Trust Settings array means "always trust this cert,
534            //  with a resulting kSecTrustSettingsResult of kSecTrustSettingsResultTrustRoot".
535            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    /// Generate an optional settings struct from the given optional
583    /// configuration reference. If `config` is `None`, TLS is
584    /// disabled. The `for_server` parameter indicates the options
585    /// should be interpreted as being for a TLS server, which requires
586    /// an identity certificate and changes the certificate verification
587    /// default to false.
588    pub fn from_config(config: Option<&TlsEnableableConfig>, for_server: bool) -> Result<Self> {
589        match config {
590            None => Ok(Self::Raw(())), // No config, no TLS settings
591            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                        // Servers require an identity certificate
596                        (true, None) => Err(TlsError::MissingRequiredIdentity),
597                        _ => Ok(Self::Tls(tls)),
598                    }
599                } else {
600                    Ok(Self::Raw(())) // Explicitly disabled, still no TLS settings
601                }
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
620/// Load a private key from a named file
621fn 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
639/// Parse the data one way if it looks like a DER file, and the other if
640/// it looks like a PEM file. For the content to be treated as PEM, it
641/// must parse as valid UTF-8 and contain a PEM start marker.
642fn der_or_pem<T>(data: Vec<u8>, der_fn: impl Fn(Vec<u8>) -> T, pem_fn: impl Fn(String) -> T) -> T {
643    // None of these steps cause (re)allocations,
644    // just parsing and type manipulation
645    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
654/// Open the named file and read its entire contents into memory. If the
655/// file "name" contains a PEM start marker, it is assumed to contain
656/// inline data and is used directly instead of opening a file.
657fn 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        // Actual error is an ASN parse, doesn't really matter
813    }
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}