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