wasmcloud_core/
tls.rs

1//! Reusable types related to enabling consistent TLS ([webpki-roots]/[rustls-native-certs]) usage in downstream libraries.
2//!
3//! Downstream libraries can utilize this module to ensure a consistent set of CA roots and/or connectors.
4//!
5//! For example, when building a [`rustls::ClientConfig`]:
6//!
7//! ```rust
8//! use rustls;
9//! use wasmcloud_core::tls;
10//!
11//! let config = rustls::ClientConfig::builder()
12//!     .with_root_certificates(rustls::RootCertStore {
13//!         roots: tls::DEFAULT_ROOTS.roots.clone(),
14//!     })
15//!     .with_no_client_auth();
16//! ```
17//!
18//! [webpki-roots]: https://crates.io/crates/webpki-roots
19//! [rustls-native-certs]: https://crates.io/crates/rustls-native-certs
20
21use std::{path::Path, sync::Arc};
22
23use anyhow::{Context as _, Result};
24use once_cell::sync::Lazy;
25
26#[cfg(feature = "rustls-native-certs")]
27pub static NATIVE_ROOTS: Lazy<Arc<[rustls::pki_types::CertificateDer<'static>]>> =
28    Lazy::new(|| {
29        let res = rustls_native_certs::load_native_certs();
30        if !res.errors.is_empty() {
31            tracing::warn!(errors = ?res.errors, "failed to load native root certificate store");
32        }
33        Arc::from(res.certs)
34    });
35
36#[cfg(all(feature = "rustls-native-certs", feature = "oci"))]
37pub static NATIVE_ROOTS_OCI: Lazy<Arc<[oci_client::client::Certificate]>> = Lazy::new(|| {
38    NATIVE_ROOTS
39        .iter()
40        .map(|cert| oci_client::client::Certificate {
41            encoding: oci_client::client::CertificateEncoding::Der,
42            data: cert.to_vec(),
43        })
44        .collect()
45});
46
47#[cfg(all(feature = "rustls-native-certs", feature = "reqwest"))]
48pub static NATIVE_ROOTS_REQWEST: Lazy<Arc<[reqwest::tls::Certificate]>> = Lazy::new(|| {
49    NATIVE_ROOTS
50        .iter()
51        .filter_map(|cert| reqwest::tls::Certificate::from_der(cert.as_ref()).ok())
52        .collect()
53});
54
55pub static DEFAULT_ROOTS: Lazy<Arc<rustls::RootCertStore>> = Lazy::new(|| {
56    #[allow(unused_mut)]
57    let mut ca = rustls::RootCertStore::empty();
58    #[cfg(feature = "rustls-native-certs")]
59    {
60        let (added, ignored) = ca.add_parsable_certificates(NATIVE_ROOTS.iter().cloned());
61        tracing::debug!(added, ignored, "loaded native root certificate store");
62    }
63    #[cfg(feature = "webpki-roots")]
64    ca.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
65    Arc::new(ca)
66});
67
68pub static DEFAULT_CLIENT_CONFIG: Lazy<rustls::ClientConfig> = Lazy::new(|| {
69    rustls::ClientConfig::builder()
70        .with_root_certificates(Arc::clone(&DEFAULT_ROOTS))
71        .with_no_client_auth()
72});
73
74pub static DEFAULT_CLIENT_CONFIG_ARC: Lazy<Arc<rustls::ClientConfig>> =
75    Lazy::new(|| Arc::new(DEFAULT_CLIENT_CONFIG.clone()));
76
77#[cfg(feature = "tokio-rustls")]
78pub static DEFAULT_RUSTLS_CONNECTOR: Lazy<tokio_rustls::TlsConnector> =
79    Lazy::new(|| tokio_rustls::TlsConnector::from(Arc::clone(&DEFAULT_CLIENT_CONFIG_ARC)));
80
81#[cfg(feature = "hyper-rustls")]
82pub static DEFAULT_HYPER_CONNECTOR: Lazy<
83    hyper_rustls::HttpsConnector<hyper_util::client::legacy::connect::HttpConnector>,
84> = Lazy::new(|| {
85    hyper_rustls::HttpsConnectorBuilder::new()
86        .with_tls_config(DEFAULT_CLIENT_CONFIG.clone())
87        .https_or_http()
88        .enable_all_versions()
89        .build()
90});
91
92#[cfg(all(feature = "reqwest", feature = "rustls-native-certs"))]
93pub static DEFAULT_REQWEST_CLIENT: Lazy<reqwest::Client> = Lazy::new(|| {
94    reqwest::ClientBuilder::default()
95        .user_agent(REQWEST_USER_AGENT)
96        .with_native_certificates()
97        .build()
98        .expect("failed to build HTTP client")
99});
100
101pub static REQWEST_USER_AGENT: &str =
102    concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"));
103
104#[cfg(feature = "rustls-native-certs")]
105pub trait NativeRootsExt {
106    fn with_native_certificates(self) -> Self;
107}
108
109#[cfg(all(feature = "reqwest", feature = "rustls-native-certs"))]
110impl NativeRootsExt for reqwest::ClientBuilder {
111    fn with_native_certificates(self) -> Self {
112        NATIVE_ROOTS_REQWEST
113            .iter()
114            .cloned()
115            .fold(self, reqwest::ClientBuilder::add_root_certificate)
116    }
117}
118
119/// Attempt to load certificates from a given array of paths
120pub fn load_certs_from_paths(
121    paths: &[impl AsRef<Path>],
122) -> Result<Vec<rustls::pki_types::CertificateDer<'static>>> {
123    paths
124        .iter()
125        .map(read_certs_from_path)
126        .flat_map(|result| match result {
127            Ok(vec) => vec.into_iter().map(Ok).collect(),
128            Err(er) => vec![Err(er)],
129        })
130        .collect::<Result<Vec<_>, _>>()
131}
132
133/// Read certificates from a given path
134///
135/// At present this function only supports files -- directories will return an empty list
136pub fn read_certs_from_path(
137    path: impl AsRef<Path>,
138) -> Result<Vec<rustls::pki_types::CertificateDer<'static>>> {
139    let path = path.as_ref();
140    // TODO(joonas): Support directories
141    if !path.is_file() {
142        return Ok(Vec::with_capacity(0));
143    }
144    let mut reader =
145        std::io::BufReader::new(std::fs::File::open(path).with_context(|| {
146            format!("failed to open file at provided path: {}", path.display())
147        })?);
148    Ok(rustls_pemfile::certs(&mut reader).collect::<Result<Vec<_>, _>>()?)
149}