nu_command/network/tls/
impl_rustls.rs

1use std::{
2    ops::Deref,
3    sync::{Arc, LazyLock, OnceLock},
4};
5
6use nu_engine::command_prelude::IoError;
7use nu_protocol::ShellError;
8use rustls::{
9    DigitallySignedStruct, RootCertStore, SignatureScheme,
10    client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier},
11    crypto::CryptoProvider,
12    pki_types::{CertificateDer, ServerName, UnixTime},
13};
14use ureq::TlsConnector;
15
16// TODO: replace all these generic errors with proper errors
17
18/// Stores the crypto provider used by `rustls`.
19///
20/// This struct lives in the [`CRYPTO_PROVIDER`] static.  
21/// It can't be created manually.
22///
23/// ## Purpose
24///
25/// Nushell does **not** use the global `rustls` crypto provider.  
26/// You **must** set a provider here—otherwise, any networking command
27/// that uses `rustls` won't be able to build a TLS connector.
28///
29/// This only matters if the **`rustls-tls`** feature is enabled.  
30/// Builds with **`native-tls`** ignore this completely.
31///
32/// ## How to set the provider
33///
34/// * [`NuCryptoProvider::default`]  
35///   Uses a built-in provider that works with official `nu` builds.  
36///   This might change in future versions.
37///
38/// * [`NuCryptoProvider::set`]  
39///   Lets you provide your own `CryptoProvider` using a closure:
40///
41///   ```rust
42///   use nu_command::tls::CRYPTO_PROVIDER;
43///
44///   // Call once at startup
45///   CRYPTO_PROVIDER.set(|| Ok(rustls::crypto::ring::default_provider()));
46///   ```
47///
48/// Only the first successful call takes effect. Later calls do nothing and return `false`.
49#[derive(Debug)]
50pub struct NuCryptoProvider(OnceLock<Result<Arc<CryptoProvider>, ShellError>>);
51
52/// Global [`NuCryptoProvider`] instance.
53///
54/// When the **`rustls-tls`** feature is active, call
55/// [`CRYPTO_PROVIDER.default()`](NuCryptoProvider::default) or  
56/// [`CRYPTO_PROVIDER.set(...)`](NuCryptoProvider::set) once at startup
57/// to pick the [`CryptoProvider`] that [`rustls`] will use.
58///
59/// Later TLS code gets the provider using [`get`](NuCryptoProvider::get).  
60/// If no provider was set or the closure returned an error, `get` returns a [`ShellError`].
61pub static CRYPTO_PROVIDER: NuCryptoProvider = NuCryptoProvider(OnceLock::new());
62
63impl NuCryptoProvider {
64    /// Returns the current [`CryptoProvider`].
65    ///
66    /// Comes from the first call to [`default`](Self::default) or [`set`](Self::set).
67    ///
68    /// # Errors
69    /// - If no provider was set.
70    /// - If the `set` closure returned an error.
71    pub fn get(&self) -> Result<Arc<CryptoProvider>, ShellError> {
72        // we clone here as the Arc for Ok is super cheap and basically all APIs expect an owned
73        // ShellError, so we might as well clone here already
74        match self.0.get() {
75            Some(val) => val.clone(),
76            None => Err(ShellError::GenericError {
77                error: "tls crypto provider not found".to_string(),
78                msg: "no crypto provider for rustls was defined".to_string(),
79                span: None,
80                help: Some("ensure that nu_command::tls::CRYPTO_PROVIDER is set".to_string()),
81                inner: vec![],
82            }),
83        }
84    }
85
86    /// Sets a custom [`CryptoProvider`].
87    ///
88    /// Call once at startup, before any TLS code runs.  
89    /// The closure runs immediately and the result (either `Ok` or `Err`) is stored.  
90    /// Returns whether the provider was stored successfully.
91    pub fn set(&self, f: impl FnOnce() -> Result<CryptoProvider, ShellError>) -> bool {
92        let value = f().map(Arc::new);
93        self.0.set(value).is_ok()
94    }
95
96    /// Sets a default [`CryptoProvider`] used in official `nu` builds.
97    ///
98    /// Should work on most systems, but may not work in every setup.  
99    /// If it fails, use [`set`](Self::set) to install a custom one.  
100    /// Returns whether the provider was stored successfully.
101    pub fn default(&self) -> bool {
102        self.set(|| Ok(rustls::crypto::ring::default_provider()))
103    }
104}
105
106#[cfg(feature = "os")]
107static ROOT_CERT_STORE: LazyLock<Result<Arc<RootCertStore>, ShellError>> = LazyLock::new(|| {
108    let mut roots = RootCertStore::empty();
109
110    let native_certs = rustls_native_certs::load_native_certs();
111
112    let errors: Vec<_> = native_certs
113        .errors
114        .into_iter()
115        .map(|err| match err.kind {
116            rustls_native_certs::ErrorKind::Io { inner, path } => ShellError::Io(
117                IoError::new_internal_with_path(inner, err.context, nu_protocol::location!(), path),
118            ),
119            rustls_native_certs::ErrorKind::Os(error) => ShellError::GenericError {
120                error: error.to_string(),
121                msg: err.context.to_string(),
122                span: None,
123                help: None,
124                inner: vec![],
125            },
126            rustls_native_certs::ErrorKind::Pem(error) => ShellError::GenericError {
127                error: error.to_string(),
128                msg: err.context.to_string(),
129                span: None,
130                help: None,
131                inner: vec![],
132            },
133            _ => ShellError::GenericError {
134                error: String::from("unknown error loading native certs"),
135                msg: err.context.to_string(),
136                span: None,
137                help: None,
138                inner: vec![],
139            },
140        })
141        .collect();
142    if !errors.is_empty() {
143        return Err(ShellError::GenericError {
144            error: String::from("error loading native certs"),
145            msg: String::from("could not load native certs"),
146            span: None,
147            help: None,
148            inner: errors,
149        });
150    }
151
152    for cert in native_certs.certs {
153        roots.add(cert).map_err(|err| ShellError::GenericError {
154            error: err.to_string(),
155            msg: String::from("could not add root cert"),
156            span: None,
157            help: None,
158            inner: vec![],
159        })?;
160    }
161
162    Ok(Arc::new(roots))
163});
164
165#[cfg(not(feature = "os"))]
166static ROOT_CERT_STORE: LazyLock<Result<Arc<RootCertStore>, ShellError>> = LazyLock::new(|| {
167    Ok(Arc::new(rustls::RootCertStore {
168        roots: webpki_roots::TLS_SERVER_ROOTS.to_vec(),
169    }))
170});
171
172#[doc = include_str!("./tls.rustdoc.md")]
173pub fn tls(allow_insecure: bool) -> Result<impl TlsConnector, ShellError> {
174    let crypto_provider = CRYPTO_PROVIDER.get()?;
175
176    let make_protocol_versions_error = |err: rustls::Error| ShellError::GenericError {
177        error: err.to_string(),
178        msg: "crypto provider is incompatible with protocol versions".to_string(),
179        span: None,
180        help: None,
181        inner: vec![],
182    };
183
184    let client_config = match allow_insecure {
185        false => rustls::ClientConfig::builder_with_provider(crypto_provider)
186            .with_safe_default_protocol_versions()
187            .map_err(make_protocol_versions_error)?
188            .with_root_certificates(ROOT_CERT_STORE.deref().clone()?)
189            .with_no_client_auth(),
190        true => rustls::ClientConfig::builder_with_provider(crypto_provider)
191            .with_safe_default_protocol_versions()
192            .map_err(make_protocol_versions_error)?
193            .dangerous()
194            .with_custom_certificate_verifier(Arc::new(UnsecureServerCertVerifier))
195            .with_no_client_auth(),
196    };
197
198    Ok(Arc::new(client_config))
199}
200
201#[derive(Debug)]
202struct UnsecureServerCertVerifier;
203
204impl ServerCertVerifier for UnsecureServerCertVerifier {
205    fn verify_server_cert(
206        &self,
207        _end_entity: &CertificateDer<'_>,
208        _intermediates: &[CertificateDer<'_>],
209        _server_name: &ServerName<'_>,
210        _ocsp_response: &[u8],
211        _now: UnixTime,
212    ) -> Result<ServerCertVerified, rustls::Error> {
213        Ok(ServerCertVerified::assertion())
214    }
215
216    fn verify_tls12_signature(
217        &self,
218        _message: &[u8],
219        _cert: &CertificateDer<'_>,
220        _dss: &DigitallySignedStruct,
221    ) -> Result<HandshakeSignatureValid, rustls::Error> {
222        Ok(HandshakeSignatureValid::assertion())
223    }
224
225    fn verify_tls13_signature(
226        &self,
227        _message: &[u8],
228        _cert: &CertificateDer<'_>,
229        _dss: &DigitallySignedStruct,
230    ) -> Result<HandshakeSignatureValid, rustls::Error> {
231        Ok(HandshakeSignatureValid::assertion())
232    }
233
234    fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
235        vec![
236            SignatureScheme::RSA_PKCS1_SHA1,
237            SignatureScheme::ECDSA_SHA1_Legacy,
238            SignatureScheme::RSA_PKCS1_SHA256,
239            SignatureScheme::ECDSA_NISTP256_SHA256,
240            SignatureScheme::RSA_PKCS1_SHA384,
241            SignatureScheme::ECDSA_NISTP384_SHA384,
242            SignatureScheme::RSA_PKCS1_SHA512,
243            SignatureScheme::ECDSA_NISTP521_SHA512,
244            SignatureScheme::RSA_PSS_SHA256,
245            SignatureScheme::RSA_PSS_SHA384,
246            SignatureScheme::RSA_PSS_SHA512,
247            SignatureScheme::ED25519,
248            SignatureScheme::ED448,
249        ]
250    }
251}