nu_command/network/tls/
impl_rustls.rs

1use std::sync::{Arc, OnceLock};
2
3use nu_protocol::ShellError;
4use rustls::crypto::CryptoProvider;
5use ureq::tls::{RootCerts, TlsConfig};
6
7// TODO: replace all these generic errors with proper errors
8
9/// Stores the crypto provider used by `rustls`.
10///
11/// This struct lives in the [`CRYPTO_PROVIDER`] static.  
12/// It can't be created manually.
13///
14/// ## Purpose
15///
16/// Nushell does **not** use the global `rustls` crypto provider.  
17/// You **must** set a provider here—otherwise, any networking command
18/// that uses `rustls` won't be able to build a TLS connector.
19///
20/// This only matters if the **`rustls-tls`** feature is enabled.  
21/// Builds with **`native-tls`** ignore this completely.
22///
23/// ## How to set the provider
24///
25/// * [`NuCryptoProvider::default`]  
26///   Uses a built-in provider that works with official `nu` builds.  
27///   This might change in future versions.
28///
29/// * [`NuCryptoProvider::set`]  
30///   Lets you provide your own `CryptoProvider` using a closure:
31///
32///   ```rust
33///   use nu_command::tls::CRYPTO_PROVIDER;
34///
35///   // Call once at startup
36///   CRYPTO_PROVIDER.set(|| Ok(rustls::crypto::ring::default_provider()));
37///   ```
38///
39/// Only the first successful call takes effect. Later calls do nothing and return `false`.
40#[derive(Debug)]
41pub struct NuCryptoProvider(OnceLock<Result<Arc<CryptoProvider>, ShellError>>);
42
43/// Global [`NuCryptoProvider`] instance.
44///
45/// When the **`rustls-tls`** feature is active, call
46/// [`CRYPTO_PROVIDER.default()`](NuCryptoProvider::default) or  
47/// [`CRYPTO_PROVIDER.set(...)`](NuCryptoProvider::set) once at startup
48/// to pick the [`CryptoProvider`] that [`rustls`] will use.
49///
50/// Later TLS code gets the provider using [`get`](NuCryptoProvider::get).  
51/// If no provider was set or the closure returned an error, `get` returns a [`ShellError`].
52pub static CRYPTO_PROVIDER: NuCryptoProvider = NuCryptoProvider(OnceLock::new());
53
54impl NuCryptoProvider {
55    /// Returns the current [`CryptoProvider`].
56    ///
57    /// Comes from the first call to [`default`](Self::default) or [`set`](Self::set).
58    ///
59    /// # Errors
60    /// - If no provider was set.
61    /// - If the `set` closure returned an error.
62    pub fn get(&self) -> Result<Arc<CryptoProvider>, ShellError> {
63        // we clone here as the Arc for Ok is super cheap and basically all APIs expect an owned
64        // ShellError, so we might as well clone here already
65        match self.0.get() {
66            Some(val) => val.clone(),
67            None => Err(ShellError::GenericError {
68                error: "tls crypto provider not found".to_string(),
69                msg: "no crypto provider for rustls was defined".to_string(),
70                span: None,
71                help: Some("ensure that nu_command::tls::CRYPTO_PROVIDER is set".to_string()),
72                inner: vec![],
73            }),
74        }
75    }
76
77    /// Sets a custom [`CryptoProvider`].
78    ///
79    /// Call once at startup, before any TLS code runs.  
80    /// The closure runs immediately and the result (either `Ok` or `Err`) is stored.  
81    /// Returns whether the provider was stored successfully.
82    pub fn set(&self, f: impl FnOnce() -> Result<CryptoProvider, ShellError>) -> bool {
83        let value = f().map(Arc::new);
84        self.0.set(value).is_ok()
85    }
86
87    /// Sets a default [`CryptoProvider`] used in official `nu` builds.
88    ///
89    /// Should work on most systems, but may not work in every setup.  
90    /// If it fails, use [`set`](Self::set) to install a custom one.  
91    /// Returns whether the provider was stored successfully.
92    pub fn default(&self) -> bool {
93        self.set(|| Ok(rustls::crypto::ring::default_provider()))
94    }
95}
96
97#[doc = include_str!("./tls_config.rustdoc.md")]
98pub fn tls_config(allow_insecure: bool) -> Result<TlsConfig, ShellError> {
99    let crypto_provider = CRYPTO_PROVIDER.get()?;
100    let config = match allow_insecure {
101        false => {
102            #[cfg(feature = "os")]
103            let certs = RootCerts::PlatformVerifier;
104
105            #[cfg(not(feature = "os"))]
106            let certs = RootCerts::WebPki;
107
108            TlsConfig::builder()
109                .unversioned_rustls_crypto_provider(crypto_provider)
110                .root_certs(certs)
111                .build()
112        }
113        true => TlsConfig::builder().disable_verification(true).build(),
114    };
115
116    Ok(config)
117}