Skip to main content

vector_core/tls/
settings.rs

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/// 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>,
181    alpn_protocols: Option<Vec<u8>>,
182    server_name: Option<String>,
183}
184
185/// Identity store in PEM format
186#[derive(Clone)]
187pub(super) struct IdentityStore {
188    cert: X509,
189    key: PKey<Private>,
190    ca: Option<Vec<X509>>,
191}
192
193impl TlsSettings {
194    /// Generate a filled out settings struct from the given optional
195    /// option set, interpreted as client options. If `options` is
196    /// `None`, the result is set to defaults (ie empty).
197    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    /// Returns the identity as PEM encoded byte arrays
229    ///
230    /// # Panics
231    ///
232    /// Panics if the identity is missing, invalid, or the authorities to chain are invalid.
233    pub fn identity_pem(&self) -> Option<(Vec<u8>, Vec<u8>)> {
234        self.identity.as_ref().map(|identity| {
235            // we have verified correct formatting at ingest time
236            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    /// Returns the authorities as PEM data
255    ///
256    /// # Panics
257    ///
258    /// Panics if the authority is invalid.
259    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! { // Panic in release builds, warn in debug builds.
305                        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                // The server ALPN select callback requires a `'static` protocol list (see
330                // https://github.com/sfackler/rust-openssl/pull/2360). Intern it so the intentional
331                // leak happens at most once per distinct list, rather than leaking a fresh copy
332                // every time the context is (re)built.
333                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            // Prevent native TLS lib from inferring default SNI using domain name from url.
354            connection.set_use_server_name_indication(false);
355            connection.set_hostname(server_name)?;
356        }
357        Ok(())
358    }
359}
360
361/// Return a `'static` copy of a server ALPN protocol list, leaking each distinct list at most once.
362///
363/// `SslContextBuilder::set_alpn_select_callback` requires the protocol list to outlive the context
364/// with a `'static` lifetime, so the bytes must be leaked. Interning by content means rebuilding an
365/// acceptor with the same ALPN configuration (e.g. on every certificate reload) reuses the existing
366/// allocation instead of leaking a fresh copy each time, keeping the leak bounded and one-time.
367fn 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    /// The input must be in ALPN "wire format".
417    ///
418    /// It consists of a sequence of supported protocol names prefixed by their byte length.
419    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    /// Parse identity from a PEM encoded certificate + key pair of files
434    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    /// Parse identity from a DER encoded PKCS#12 archive
463    fn parse_pkcs12_identity(&self, der: &[u8]) -> Result<Option<IdentityStore>> {
464        let pkcs12 = Pkcs12::from_der(der).context(ParsePkcs12Snafu)?;
465        // Verify password
466        let key_pass = self.key_pass.as_deref().unwrap_or("");
467        let parsed = pkcs12.parse2(key_pass).context(ParsePkcs12Snafu)?;
468        // extract cert, key and ca and store as PEM sow e can return an IdentityStore
469        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/// === System Specific Root Cert ===
479///
480/// Most of this code is borrowed from https://github.com/ctz/rustls-native-certs
481
482/// Load the system default certs from `schannel` this should be in place
483/// of openssl-probe on linux.
484#[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    // The various domains are designed to interact like this:
515    //
516    // "Per-user Trust Settings override locally administered
517    //  Trust Settings, which in turn override the System Trust
518    //  Settings."
519    //
520    // So we collect the certificates in this order; as a map of
521    // their DER encoding to what we'll do with them.  We don't
522    // overwrite existing elements, which mean User settings
523    // trump Admin trump System, as desired.
524
525    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            // If there are no specific trust settings, the default
533            // is to trust the certificate as a root cert.  Weird API but OK.
534            // The docs say:
535            //
536            // "Note that an empty Trust Settings array means "always trust this cert,
537            //  with a resulting kSecTrustSettingsResult of kSecTrustSettingsResultTrustRoot".
538            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    /// Generate an optional settings struct from the given optional
586    /// configuration reference. If `config` is `None`, TLS is
587    /// disabled. The `for_server` parameter indicates the options
588    /// should be interpreted as being for a TLS server, which requires
589    /// an identity certificate and changes the certificate verification
590    /// default to false.
591    pub fn from_config(config: Option<&TlsEnableableConfig>, for_server: bool) -> Result<Self> {
592        match config {
593            None => Ok(Self::Raw(())), // No config, no TLS settings
594            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                        // Servers require an identity certificate
599                        (true, None) => Err(TlsError::MissingRequiredIdentity),
600                        _ => Ok(Self::Tls(tls)),
601                    }
602                } else {
603                    Ok(Self::Raw(())) // Explicitly disabled, still no TLS settings
604                }
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
623/// Load a private key from a named file
624fn 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
642/// Parse the data one way if it looks like a DER file, and the other if
643/// it looks like a PEM file. For the content to be treated as PEM, it
644/// must parse as valid UTF-8 and contain a PEM start marker.
645fn der_or_pem<T>(data: Vec<u8>, der_fn: impl Fn(Vec<u8>) -> T, pem_fn: impl Fn(String) -> T) -> T {
646    // None of these steps cause (re)allocations,
647    // just parsing and type manipulation
648    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
657/// Open the named file and read its entire contents into memory. If the
658/// file "name" contains a PEM start marker, it is assumed to contain
659/// inline data and is used directly instead of opening a file.
660fn 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        // Actual error is an ASN parse, doesn't really matter
816    }
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}