1 use super::{
2     AddCertToStore, AddExtraChainCert, DerExportError, FileOpenFailed, FileReadFailed, MaybeTls,
3     NewStoreBuilder, ParsePkcs12, Pkcs12Error, PrivateKeyParseError, Result, SetCertificate,
4     SetPrivateKey, SetVerifyCert, TlsError, TlsIdentityError, X509ParseError,
5 };
6 use openssl::{
7     pkcs12::{ParsedPkcs12, Pkcs12},
8     pkey::{PKey, Private},
9     ssl::{ConnectConfiguration, SslContextBuilder, SslVerifyMode},
10     x509::{store::X509StoreBuilder, X509},
11 };
12 use serde::{Deserialize, Serialize};
13 use snafu::ResultExt;
14 use std::fmt::{self, Debug, Formatter};
15 use std::fs::File;
16 use std::io::Read;
17 use std::path::{Path, PathBuf};
18 
19 const PEM_START_MARKER: &str = "-----BEGIN ";
20 
21 #[derive(Clone, Debug, Default, Deserialize, Serialize)]
22 pub struct TlsConfig {
23     pub enabled: Option<bool>,
24     #[serde(flatten)]
25     pub options: TlsOptions,
26 }
27 
28 /// Standard TLS options
29 #[derive(Clone, Debug, Default, Deserialize, Serialize)]
30 pub struct TlsOptions {
31     pub verify_certificate: Option<bool>,
32     pub verify_hostname: Option<bool>,
33     #[serde(alias = "ca_path")]
34     pub ca_file: Option<PathBuf>,
35     #[serde(alias = "crt_path")]
36     pub crt_file: Option<PathBuf>,
37     #[serde(alias = "key_path")]
38     pub key_file: Option<PathBuf>,
39     pub key_pass: Option<String>,
40 }
41 
42 /// Directly usable settings for TLS connectors
43 #[derive(Clone, Default)]
44 pub struct TlsSettings {
45     verify_certificate: bool,
46     pub(super) verify_hostname: bool,
47     authorities: Vec<X509>,
48     pub(super) identity: Option<IdentityStore>, // openssl::pkcs12::ParsedPkcs12 doesn't impl Clone yet
49 }
50 
51 #[derive(Clone)]
52 pub struct IdentityStore(Vec<u8>, String);
53 
54 impl TlsSettings {
55     /// Generate a filled out settings struct from the given optional
56     /// option set, interpreted as client options. If `options` is
57     /// `None`, the result is set to defaults (ie empty).
from_options(options: &Option<TlsOptions>) -> Result<Self>58     pub fn from_options(options: &Option<TlsOptions>) -> Result<Self> {
59         Self::from_options_base(options, false)
60     }
61 
from_options_base( options: &Option<TlsOptions>, for_server: bool, ) -> Result<Self>62     pub(super) fn from_options_base(
63         options: &Option<TlsOptions>,
64         for_server: bool,
65     ) -> Result<Self> {
66         let default = TlsOptions::default();
67         let options = options.as_ref().unwrap_or(&default);
68 
69         if !for_server {
70             if options.verify_certificate == Some(false) {
71                 warn!(
72                     "`verify_certificate` is DISABLED, this may lead to security vulnerabilities"
73                 );
74             }
75             if options.verify_hostname == Some(false) {
76                 warn!("`verify_hostname` is DISABLED, this may lead to security vulnerabilities");
77             }
78         }
79 
80         Ok(Self {
81             verify_certificate: options.verify_certificate.unwrap_or(!for_server),
82             verify_hostname: options.verify_hostname.unwrap_or(!for_server),
83             authorities: options.load_authorities()?,
84             identity: options.load_identity()?,
85         })
86     }
87 
identity(&self) -> Option<ParsedPkcs12>88     fn identity(&self) -> Option<ParsedPkcs12> {
89         // This data was test-built previously, so we can just use it
90         // here and expect the results will not fail. This can all be
91         // reworked when `openssl::pkcs12::ParsedPkcs12` gains the Clone
92         // impl.
93         self.identity.as_ref().map(|identity| {
94             Pkcs12::from_der(&identity.0)
95                 .expect("Could not build PKCS#12 archive from parsed data")
96                 .parse(&identity.1)
97                 .expect("Could not parse stored PKCS#12 archive")
98         })
99     }
100 
apply_context(&self, context: &mut SslContextBuilder) -> Result<()>101     pub(super) fn apply_context(&self, context: &mut SslContextBuilder) -> Result<()> {
102         context.set_verify(if self.verify_certificate {
103             SslVerifyMode::PEER | SslVerifyMode::FAIL_IF_NO_PEER_CERT
104         } else {
105             SslVerifyMode::NONE
106         });
107         if let Some(identity) = self.identity() {
108             context
109                 .set_certificate(&identity.cert)
110                 .context(SetCertificate)?;
111             context
112                 .set_private_key(&identity.pkey)
113                 .context(SetPrivateKey)?;
114             if let Some(chain) = identity.chain {
115                 for cert in chain {
116                     context
117                         .add_extra_chain_cert(cert)
118                         .context(AddExtraChainCert)?;
119                 }
120             }
121         }
122         if !self.authorities.is_empty() {
123             let mut store = X509StoreBuilder::new().context(NewStoreBuilder)?;
124             for authority in &self.authorities {
125                 store.add_cert(authority.clone()).context(AddCertToStore)?;
126             }
127             context
128                 .set_verify_cert_store(store.build())
129                 .context(SetVerifyCert)?;
130         } else {
131             debug!("Fetching system root certs.");
132 
133             #[cfg(windows)]
134             load_windows_certs(context).unwrap();
135 
136             #[cfg(target_os = "macos")]
137             load_mac_certs(context).unwrap();
138         }
139 
140         Ok(())
141     }
142 
apply_connect_configuration(&self, connection: &mut ConnectConfiguration)143     pub fn apply_connect_configuration(&self, connection: &mut ConnectConfiguration) {
144         connection.set_verify_hostname(self.verify_hostname);
145     }
146 }
147 
148 impl TlsOptions {
load_authorities(&self) -> Result<Vec<X509>>149     fn load_authorities(&self) -> Result<Vec<X509>> {
150         match &self.ca_file {
151             None => Ok(vec![]),
152             Some(filename) => {
153                 let (data, filename) = open_read(filename, "certificate")?;
154                 der_or_pem(
155                     data,
156                     |der| X509::from_der(&der).map(|x509| vec![x509]),
157                     |pem| {
158                         pem.match_indices(PEM_START_MARKER)
159                             .map(|(start, _)| X509::from_pem(pem[start..].as_bytes()))
160                             .collect()
161                     },
162                 )
163                 .with_context(|| X509ParseError { filename })
164             }
165         }
166     }
167 
load_identity(&self) -> Result<Option<IdentityStore>>168     fn load_identity(&self) -> Result<Option<IdentityStore>> {
169         match (&self.crt_file, &self.key_file) {
170             (None, Some(_)) => Err(TlsError::MissingCrtKeyFile),
171             (None, None) => Ok(None),
172             (Some(filename), _) => {
173                 let (data, filename) = open_read(filename, "certificate")?;
174                 der_or_pem(
175                     data,
176                     |der| self.parse_pkcs12_identity(der),
177                     |pem| self.parse_pem_identity(pem, &filename),
178                 )
179             }
180         }
181     }
182 
183     /// Parse identity from a PEM encoded certificate + key pair of files
parse_pem_identity(&self, pem: String, crt_file: &PathBuf) -> Result<Option<IdentityStore>>184     fn parse_pem_identity(&self, pem: String, crt_file: &PathBuf) -> Result<Option<IdentityStore>> {
185         match &self.key_file {
186             None => Err(TlsError::MissingKey),
187             Some(key_file) => {
188                 let name = crt_file.to_string_lossy().to_string();
189                 let crt = X509::from_pem(pem.as_bytes())
190                     .with_context(|| X509ParseError { filename: crt_file })?;
191                 let key = load_key(&key_file, &self.key_pass)?;
192                 let pkcs12 = Pkcs12::builder()
193                     .build("", &name, &key, &crt)
194                     .context(Pkcs12Error)?;
195                 let identity = pkcs12.to_der().context(DerExportError)?;
196 
197                 // Build the resulting parsed PKCS#12 archive,
198                 // but don't store it, as it cannot be cloned.
199                 // This is just for error checking.
200                 pkcs12.parse("").context(TlsIdentityError)?;
201 
202                 Ok(Some(IdentityStore(identity, "".into())))
203             }
204         }
205     }
206 
207     /// Parse identity from a DER encoded PKCS#12 archive
parse_pkcs12_identity(&self, der: Vec<u8>) -> Result<Option<IdentityStore>>208     fn parse_pkcs12_identity(&self, der: Vec<u8>) -> Result<Option<IdentityStore>> {
209         let pkcs12 = Pkcs12::from_der(&der).context(ParsePkcs12)?;
210         // Verify password
211         let key_pass = self.key_pass.as_deref().unwrap_or("");
212         pkcs12.parse(&key_pass).context(ParsePkcs12)?;
213         Ok(Some(IdentityStore(der, key_pass.to_string())))
214     }
215 }
216 
217 /// === System Specific Root Cert ===
218 ///
219 /// Most of this code is borrowed from https://github.com/ctz/rustls-native-certs
220 
221 /// Load the system default certs from `schannel` this should be in place
222 /// of openssl-probe on linux.
223 #[cfg(windows)]
load_windows_certs(builder: &mut SslContextBuilder) -> Result<()>224 fn load_windows_certs(builder: &mut SslContextBuilder) -> Result<()> {
225     use super::Schannel;
226 
227     let mut store = X509StoreBuilder::new().context(NewStoreBuilder)?;
228 
229     let current_user_store =
230         schannel::cert_store::CertStore::open_current_user("ROOT").context(Schannel)?;
231 
232     for cert in current_user_store.certs() {
233         let cert = cert.to_der().to_vec();
234         let cert = X509::from_der(&cert[..]).context(super::X509SystemParseError)?;
235         store.add_cert(cert).context(AddCertToStore)?;
236     }
237 
238     builder
239         .set_verify_cert_store(store.build())
240         .context(SetVerifyCert)?;
241 
242     Ok(())
243 }
244 
245 #[cfg(target_os = "macos")]
load_mac_certs(builder: &mut SslContextBuilder) -> Result<()>246 fn load_mac_certs(builder: &mut SslContextBuilder) -> Result<()> {
247     use super::SecurityFramework;
248     use security_framework::trust_settings::{Domain, TrustSettings, TrustSettingsForCertificate};
249     use std::collections::HashMap;
250 
251     // The various domains are designed to interact like this:
252     //
253     // "Per-user Trust Settings override locally administered
254     //  Trust Settings, which in turn override the System Trust
255     //  Settings."
256     //
257     // So we collect the certificates in this order; as a map of
258     // their DER encoding to what we'll do with them.  We don't
259     // overwrite existing elements, which mean User settings
260     // trump Admin trump System, as desired.
261 
262     let mut store = X509StoreBuilder::new().context(NewStoreBuilder)?;
263     let mut all_certs = HashMap::new();
264 
265     for domain in &[Domain::User, Domain::Admin, Domain::System] {
266         let ts = TrustSettings::new(*domain);
267 
268         for cert in ts.iter().context(SecurityFramework)? {
269             // If there are no specific trust settings, the default
270             // is to trust the certificate as a root cert.  Weird API but OK.
271             // The docs say:
272             //
273             // "Note that an empty Trust Settings array means "always trust this cert,
274             //  with a resulting kSecTrustSettingsResult of kSecTrustSettingsResultTrustRoot".
275             let trusted = ts
276                 .tls_trust_settings_for_certificate(&cert)
277                 .context(SecurityFramework)?
278                 .unwrap_or(TrustSettingsForCertificate::TrustRoot);
279 
280             all_certs.entry(cert.to_der()).or_insert(trusted);
281         }
282     }
283 
284     for (cert, trusted) in all_certs {
285         if matches!(
286             trusted,
287             TrustSettingsForCertificate::TrustRoot | TrustSettingsForCertificate::TrustAsRoot
288         ) {
289             let cert = X509::from_der(&cert[..]).context(super::X509SystemParseError)?;
290             store.add_cert(cert).context(AddCertToStore)?;
291         }
292     }
293 
294     builder
295         .set_verify_cert_store(store.build())
296         .context(SetVerifyCert)?;
297 
298     Ok(())
299 }
300 
301 impl Debug for TlsSettings {
fmt(&self, f: &mut Formatter) -> fmt::Result302     fn fmt(&self, f: &mut Formatter) -> fmt::Result {
303         f.debug_struct("TlsSettings")
304             .field("verify_certificate", &self.verify_certificate)
305             .field("verify_hostname", &self.verify_hostname)
306             .finish()
307     }
308 }
309 
310 pub type MaybeTlsSettings = MaybeTls<(), TlsSettings>;
311 
312 impl MaybeTlsSettings {
enable_client() -> Result<Self>313     pub fn enable_client() -> Result<Self> {
314         let tls = TlsSettings::from_options_base(&None, false)?;
315         Ok(Self::Tls(tls))
316     }
317 
318     /// Generate an optional settings struct from the given optional
319     /// configuration reference. If `config` is `None`, TLS is
320     /// disabled. The `for_server` parameter indicates the options
321     /// should be interpreted as being for a TLS server, which requires
322     /// an identity certificate and changes the certificate verification
323     /// default to false.
from_config(config: &Option<TlsConfig>, for_server: bool) -> Result<Self>324     pub fn from_config(config: &Option<TlsConfig>, for_server: bool) -> Result<Self> {
325         match config {
326             None => Ok(Self::Raw(())), // No config, no TLS settings
327             Some(config) => {
328                 if config.enabled.unwrap_or(false) {
329                     let tls =
330                         TlsSettings::from_options_base(&Some(config.options.clone()), for_server)?;
331                     match (for_server, &tls.identity) {
332                         // Servers require an identity certificate
333                         (true, None) => Err(TlsError::MissingRequiredIdentity),
334                         _ => Ok(Self::Tls(tls)),
335                     }
336                 } else {
337                     Ok(Self::Raw(())) // Explicitly disabled, still no TLS settings
338                 }
339             }
340         }
341     }
342 }
343 
344 impl From<TlsSettings> for MaybeTlsSettings {
from(tls: TlsSettings) -> Self345     fn from(tls: TlsSettings) -> Self {
346         Self::Tls(tls)
347     }
348 }
349 
350 /// Load a private key from a named file
load_key(filename: &Path, pass_phrase: &Option<String>) -> Result<PKey<Private>>351 fn load_key(filename: &Path, pass_phrase: &Option<String>) -> Result<PKey<Private>> {
352     let (data, filename) = open_read(filename, "key")?;
353     match pass_phrase {
354         None => der_or_pem(
355             data,
356             |der| PKey::private_key_from_der(&der),
357             |pem| PKey::private_key_from_pem(pem.as_bytes()),
358         )
359         .with_context(|| PrivateKeyParseError { filename }),
360         Some(phrase) => der_or_pem(
361             data,
362             |der| PKey::private_key_from_pkcs8_passphrase(&der, phrase.as_bytes()),
363             |pem| PKey::private_key_from_pem_passphrase(pem.as_bytes(), phrase.as_bytes()),
364         )
365         .with_context(|| PrivateKeyParseError { filename }),
366     }
367 }
368 
369 /// Parse the data one way if it looks like a DER file, and the other if
370 /// it looks like a PEM file. For the content to be treated as PEM, it
371 /// must parse as valid UTF-8 and contain a PEM start marker.
der_or_pem<T>(data: Vec<u8>, der_fn: impl Fn(Vec<u8>) -> T, pem_fn: impl Fn(String) -> T) -> T372 fn der_or_pem<T>(data: Vec<u8>, der_fn: impl Fn(Vec<u8>) -> T, pem_fn: impl Fn(String) -> T) -> T {
373     // None of these steps cause (re)allocations,
374     // just parsing and type manipulation
375     match String::from_utf8(data) {
376         Ok(text) => match text.find(PEM_START_MARKER) {
377             Some(_) => pem_fn(text),
378             None => der_fn(text.into_bytes()),
379         },
380         Err(err) => der_fn(err.into_bytes()),
381     }
382 }
383 
384 /// Open the named file and read its entire contents into memory. If the
385 /// file "name" contains a PEM start marker, it is assumed to contain
386 /// inline data and is used directly instead of opening a file.
open_read(filename: &Path, note: &'static str) -> Result<(Vec<u8>, PathBuf)>387 fn open_read(filename: &Path, note: &'static str) -> Result<(Vec<u8>, PathBuf)> {
388     if let Some(filename) = filename.to_str() {
389         if filename.find(PEM_START_MARKER).is_some() {
390             return Ok((Vec::from(filename), "inline text".into()));
391         }
392     }
393 
394     let mut text = Vec::<u8>::new();
395 
396     File::open(filename)
397         .with_context(|| FileOpenFailed { note, filename })?
398         .read_to_end(&mut text)
399         .with_context(|| FileReadFailed { note, filename })?;
400 
401     Ok((text, filename.into()))
402 }
403 
404 #[cfg(test)]
405 mod test {
406     use super::*;
407 
408     const TEST_PKCS12_PATH: &str = "tests/data/localhost.p12";
409     const TEST_PEM_CRT_PATH: &str = "tests/data/localhost.crt";
410     const TEST_PEM_CRT_BYTES: &[u8] = include_bytes!("../../tests/data/localhost.crt");
411     const TEST_PEM_KEY_PATH: &str = "tests/data/localhost.key";
412     const TEST_PEM_KEY_BYTES: &[u8] = include_bytes!("../../tests/data/localhost.key");
413 
414     #[test]
from_options_pkcs12()415     fn from_options_pkcs12() {
416         let options = TlsOptions {
417             crt_file: Some(TEST_PKCS12_PATH.into()),
418             key_pass: Some("NOPASS".into()),
419             ..Default::default()
420         };
421         let settings =
422             TlsSettings::from_options(&Some(options)).expect("Failed to load PKCS#12 certificate");
423         assert!(settings.identity.is_some());
424         assert_eq!(settings.authorities.len(), 0);
425     }
426 
427     #[test]
from_options_pem()428     fn from_options_pem() {
429         let options = TlsOptions {
430             crt_file: Some(TEST_PEM_CRT_PATH.into()),
431             key_file: Some(TEST_PEM_KEY_PATH.into()),
432             ..Default::default()
433         };
434         let settings =
435             TlsSettings::from_options(&Some(options)).expect("Failed to load PEM certificate");
436         assert!(settings.identity.is_some());
437         assert_eq!(settings.authorities.len(), 0);
438     }
439 
440     #[test]
from_options_inline_pem()441     fn from_options_inline_pem() {
442         let crt = String::from_utf8(TEST_PEM_CRT_BYTES.to_vec()).unwrap();
443         let key = String::from_utf8(TEST_PEM_KEY_BYTES.to_vec()).unwrap();
444         let options = TlsOptions {
445             crt_file: Some(crt.into()),
446             key_file: Some(key.into()),
447             ..Default::default()
448         };
449         let settings =
450             TlsSettings::from_options(&Some(options)).expect("Failed to load PEM certificate");
451         assert!(settings.identity.is_some());
452         assert_eq!(settings.authorities.len(), 0);
453     }
454 
455     #[test]
from_options_ca()456     fn from_options_ca() {
457         let options = TlsOptions {
458             ca_file: Some("tests/data/Vector_CA.crt".into()),
459             ..Default::default()
460         };
461         let settings = TlsSettings::from_options(&Some(options))
462             .expect("Failed to load authority certificate");
463         assert!(settings.identity.is_none());
464         assert_eq!(settings.authorities.len(), 1);
465     }
466 
467     #[test]
from_options_inline_ca()468     fn from_options_inline_ca() {
469         let ca =
470             String::from_utf8(include_bytes!("../../tests/data/Vector_CA.crt").to_vec()).unwrap();
471         let options = TlsOptions {
472             ca_file: Some(ca.into()),
473             ..Default::default()
474         };
475         let settings = TlsSettings::from_options(&Some(options))
476             .expect("Failed to load authority certificate");
477         assert!(settings.identity.is_none());
478         assert_eq!(settings.authorities.len(), 1);
479     }
480 
481     #[test]
from_options_multi_ca()482     fn from_options_multi_ca() {
483         let options = TlsOptions {
484             ca_file: Some("tests/data/Multi_CA.crt".into()),
485             ..Default::default()
486         };
487         let settings = TlsSettings::from_options(&Some(options))
488             .expect("Failed to load authority certificate");
489         assert!(settings.identity.is_none());
490         assert_eq!(settings.authorities.len(), 2);
491     }
492 
493     #[test]
from_options_none()494     fn from_options_none() {
495         let settings = TlsSettings::from_options(&None).expect("Failed to generate null settings");
496         assert!(settings.identity.is_none());
497         assert_eq!(settings.authorities.len(), 0);
498     }
499 
500     #[test]
from_options_bad_certificate()501     fn from_options_bad_certificate() {
502         let options = TlsOptions {
503             key_file: Some(TEST_PEM_KEY_PATH.into()),
504             ..Default::default()
505         };
506         let error = TlsSettings::from_options(&Some(options))
507             .expect_err("from_options failed to check certificate");
508         assert!(matches!(error, TlsError::MissingCrtKeyFile));
509 
510         let options = TlsOptions {
511             crt_file: Some(TEST_PEM_CRT_PATH.into()),
512             ..Default::default()
513         };
514         let _error = TlsSettings::from_options(&Some(options))
515             .expect_err("from_options failed to check certificate");
516         // Actual error is an ASN parse, doesn't really matter
517     }
518 
519     #[test]
from_config_none()520     fn from_config_none() {
521         assert!(MaybeTlsSettings::from_config(&None, true).unwrap().is_raw());
522         assert!(MaybeTlsSettings::from_config(&None, false)
523             .unwrap()
524             .is_raw());
525     }
526 
527     #[test]
from_config_not_enabled()528     fn from_config_not_enabled() {
529         assert!(settings_from_config(None, false, false, true).is_raw());
530         assert!(settings_from_config(None, false, false, false).is_raw());
531         assert!(settings_from_config(Some(false), false, false, true).is_raw());
532         assert!(settings_from_config(Some(false), false, false, false).is_raw());
533     }
534 
535     #[test]
from_config_fails_without_certificate()536     fn from_config_fails_without_certificate() {
537         let config = make_config(Some(true), false, false);
538         let error = MaybeTlsSettings::from_config(&Some(config), true)
539             .expect_err("from_config failed to check for a certificate");
540         assert!(matches!(error, TlsError::MissingRequiredIdentity));
541     }
542 
543     #[test]
from_config_with_certificate()544     fn from_config_with_certificate() {
545         let config = settings_from_config(Some(true), true, true, true);
546         assert!(config.is_tls());
547     }
548 
settings_from_config( enabled: Option<bool>, set_crt: bool, set_key: bool, for_server: bool, ) -> MaybeTlsSettings549     fn settings_from_config(
550         enabled: Option<bool>,
551         set_crt: bool,
552         set_key: bool,
553         for_server: bool,
554     ) -> MaybeTlsSettings {
555         let config = make_config(enabled, set_crt, set_key);
556         MaybeTlsSettings::from_config(&Some(config), for_server)
557             .expect("Failed to generate settings from config")
558     }
559 
make_config(enabled: Option<bool>, set_crt: bool, set_key: bool) -> TlsConfig560     fn make_config(enabled: Option<bool>, set_crt: bool, set_key: bool) -> TlsConfig {
561         TlsConfig {
562             enabled,
563             options: TlsOptions {
564                 crt_file: and_some(set_crt, TEST_PEM_CRT_PATH.into()),
565                 key_file: and_some(set_key, TEST_PEM_KEY_PATH.into()),
566                 ..Default::default()
567             },
568         }
569     }
570 
571     // This can be eliminated once the `bool_to_option` feature migrates
572     // out of nightly.
and_some<T>(src: bool, value: T) -> Option<T>573     fn and_some<T>(src: bool, value: T) -> Option<T> {
574         if src {
575             Some(value)
576         } else {
577             None
578         }
579     }
580 }
581