nu_command/network/tls/impl_rustls.rs
1use std::sync::{Arc, OnceLock};
2
3use nu_protocol::ShellError;
4use nu_protocol::shell_error::generic::GenericError;
5use rustls::crypto::CryptoProvider;
6use ureq::tls::{RootCerts, TlsConfig};
7
8// TODO: replace all these generic errors with proper errors
9
10/// Stores the crypto provider used by `rustls`.
11///
12/// This struct lives in the [`CRYPTO_PROVIDER`] static.
13/// It can't be created manually.
14///
15/// ## Purpose
16///
17/// Nushell does **not** use the global `rustls` crypto provider.
18/// You **must** set a provider here—otherwise, any networking command
19/// that uses `rustls` won't be able to build a TLS connector.
20///
21/// This only matters if the **`rustls-tls`** feature is enabled.
22/// Builds with **`native-tls`** ignore this completely.
23///
24/// ## How to set the provider
25///
26/// * [`NuCryptoProvider::default`]
27/// Uses a built-in provider that works with official `nu` builds.
28/// This might change in future versions.
29///
30/// * [`NuCryptoProvider::set`]
31/// Lets you provide your own `CryptoProvider` using a closure:
32///
33/// ```rust
34/// use nu_command::tls::CRYPTO_PROVIDER;
35///
36/// // Call once at startup
37/// CRYPTO_PROVIDER.set(|| Ok(rustls::crypto::ring::default_provider()));
38/// ```
39///
40/// Only the first successful call takes effect. Later calls do nothing and return `false`.
41#[derive(Debug)]
42pub struct NuCryptoProvider(OnceLock<Result<Arc<CryptoProvider>, ShellError>>);
43
44/// Global [`NuCryptoProvider`] instance.
45///
46/// When the **`rustls-tls`** feature is active, call
47/// [`CRYPTO_PROVIDER.default()`](NuCryptoProvider::default) or
48/// [`CRYPTO_PROVIDER.set(...)`](NuCryptoProvider::set) once at startup
49/// to pick the [`CryptoProvider`] that [`rustls`] will use.
50///
51/// Later TLS code gets the provider using [`get`](NuCryptoProvider::get).
52/// If no provider was set or the closure returned an error, `get` returns a [`ShellError`].
53pub static CRYPTO_PROVIDER: NuCryptoProvider = NuCryptoProvider(OnceLock::new());
54
55impl NuCryptoProvider {
56 /// Returns the current [`CryptoProvider`].
57 ///
58 /// Comes from the first call to [`default`](Self::default) or [`set`](Self::set).
59 ///
60 /// # Errors
61 /// - If no provider was set.
62 /// - If the `set` closure returned an error.
63 pub fn get(&self) -> Result<Arc<CryptoProvider>, ShellError> {
64 // we clone here as the Arc for Ok is super cheap and basically all APIs expect an owned
65 // ShellError, so we might as well clone here already
66 match self.0.get() {
67 Some(val) => val.clone(),
68 None => Err(ShellError::Generic(
69 GenericError::new_internal(
70 "tls crypto provider not found",
71 "no crypto provider for rustls was defined",
72 )
73 .with_help("ensure that nu_command::tls::CRYPTO_PROVIDER is set"),
74 )),
75 }
76 }
77
78 /// Sets a custom [`CryptoProvider`].
79 ///
80 /// Call once at startup, before any TLS code runs.
81 /// The closure runs immediately and the result (either `Ok` or `Err`) is stored.
82 /// Returns whether the provider was stored successfully.
83 pub fn set(&self, f: impl FnOnce() -> Result<CryptoProvider, ShellError>) -> bool {
84 let value = f().map(Arc::new);
85 self.0.set(value).is_ok()
86 }
87
88 /// Sets a default [`CryptoProvider`] used in official `nu` builds.
89 ///
90 /// Should work on most systems, but may not work in every setup.
91 /// If it fails, use [`set`](Self::set) to install a custom one.
92 /// Returns whether the provider was stored successfully.
93 pub fn default(&self) -> bool {
94 self.set(|| Ok(rustls::crypto::ring::default_provider()))
95 }
96}
97
98#[doc = include_str!("./tls_config.rustdoc.md")]
99pub fn tls_config(allow_insecure: bool) -> Result<TlsConfig, ShellError> {
100 let crypto_provider = CRYPTO_PROVIDER.get()?;
101 let config = match allow_insecure {
102 false => {
103 #[cfg(all(feature = "os", not(target_os = "android")))]
104 let certs = RootCerts::PlatformVerifier;
105
106 // Use native cert store instead of platform verifier on Android, as we cannot use the
107 // `platform-android-verifier-android` crate properly as we don't build a proper
108 // android app but rather just a binary that is executed in termux.
109 // Otherwise this guide would be really relevant:
110 // https://github.com/rustls/rustls-platform-verifier/blob/1099f161bfc5e3ac7f90aad88b1bf788e72906cb/README.md#android
111 #[cfg(all(feature = "os", target_os = "android"))]
112 let certs = native_certs();
113
114 #[cfg(not(feature = "os"))]
115 let certs = RootCerts::WebPki;
116
117 TlsConfig::builder()
118 .unversioned_rustls_crypto_provider(crypto_provider)
119 .root_certs(certs)
120 .build()
121 }
122 true => TlsConfig::builder().disable_verification(true).build(),
123 };
124
125 Ok(config)
126}
127
128/// Load certificates from the platform certificate store.
129///
130/// This method of loading certs is discouraged by the rustls team, see
131/// [here](https://github.com/rustls/rustls-native-certs).
132/// However this impl still works and is expected to work, especially for platforms that not
133/// properly support `rustls-platform-verifier`.
134#[cfg(feature = "os")]
135pub fn native_certs() -> RootCerts {
136 use rustls_native_certs::CertificateResult;
137 use ureq::tls::Certificate;
138
139 let CertificateResult { certs, errors, .. } = rustls_native_certs::load_native_certs();
140
141 // We only assert that we had no errors in a debug build.
142 // We don't want to crash release builds when they encounter an error,
143 // users rather have a broken http client than a crashing shell.
144 debug_assert!(
145 errors.is_empty(),
146 "encountered errors while loading tls certificates"
147 );
148
149 // This sadly copies the certs around but we cannot get the `CertificateDer<'static>` as
150 // `&'static [u8]`.
151 // Also internally is `ureq` loading the certificates into the `CertificateDer` format, oh well.
152 let certs: Vec<_> = certs
153 .into_iter()
154 .map(|cert| Certificate::from_der(&cert).to_owned())
155 .collect();
156 let certs = Arc::new(certs);
157
158 RootCerts::Specific(certs)
159}