mirror of
https://github.com/nushell/nushell.git
synced 2025-06-18 16:07:02 +02:00
Add rustls
for TLS (#15810)
<!-- if this PR closes one or more issues, you can automatically link the PR with them by using one of the [*linking keywords*](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword), e.g. - this PR should close #xxxx - fixes #xxxx you can also mention related issues, PRs or discussions! --> closes #14041 # Description <!-- Thank you for improving Nushell. Please, check our [contributing guide](../CONTRIBUTING.md) and talk to the core team before making major changes. Description of your pull request goes here. **Provide examples and/or screenshots** if your changes affect the user experience. --> This PR switches our default TLS backend from `native-tls` to `rustls`. Cross-compiles, `musl`, and other targets build smoother because we drop the OpenSSL requirement. `native-tls` is still available as an opt-in on `nu-command` via the `native-tls` feature. WASM + `network` still fails for unrelated crates, but the OpenSSL roadblock is gone. # User-Facing Changes <!-- List of all changes that impact the user experience here. This helps us keep track of breaking changes. --> No changes to the Nushell API. If you embed Nushell you now need to pick a [`rustls::crypto::CryptoProvider`](https://docs.rs/rustls/0.23.27/rustls/crypto/struct.CryptoProvider.html) at startup: ```rust use nu_command::tls::CRYPTO_PROVIDER; // common case CRYPTO_PROVIDER.default(); // or supply your own CRYPTO_PROVIDER.set(|| Ok(my_provider())); ``` # Tests + Formatting <!-- Don't forget to add tests that cover your changes. Make sure you've run and fixed any issues with these commands: - `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes) - `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to check that you're using the standard code style - `cargo test --workspace` to check that all tests pass (on Windows make sure to [enable developer mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging)) - `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the tests for the standard library > **Note** > from `nushell` you can also use the `toolkit` as follows > ```bash > use toolkit.nu # or use an `env_change` hook to activate it automatically > toolkit check pr > ``` --> * 🟢 `toolkit fmt` * 🟢 `toolkit clippy` * 🟢 `toolkit test` * 🟢 `toolkit test stdlib` # After Submitting <!-- If your PR had any user-facing changes, update [the documentation](https://github.com/nushell/nushell.github.io) after the PR is merged, if necessary. This will help us keep the docs up to date. -->
This commit is contained in:
parent
60cb13c493
commit
cc8b623ff8
120
Cargo.lock
generated
120
Cargo.lock
generated
@ -370,6 +370,29 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aws-lc-rs"
|
||||
version = "1.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93fcc8f365936c834db5514fc45aee5b1202d677e6b40e48468aaaa8183ca8c7"
|
||||
dependencies = [
|
||||
"aws-lc-sys",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aws-lc-sys"
|
||||
version = "0.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61b1d86e7705efe1be1b569bab41d4fa1e14e220b60a160f78de2db687add079"
|
||||
dependencies = [
|
||||
"bindgen 0.69.5",
|
||||
"cc",
|
||||
"cmake",
|
||||
"dunce",
|
||||
"fs_extra",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aws-runtime"
|
||||
version = "1.4.3"
|
||||
@ -680,6 +703,29 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bindgen"
|
||||
version = "0.69.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"cexpr",
|
||||
"clang-sys",
|
||||
"itertools 0.11.0",
|
||||
"lazy_static",
|
||||
"lazycell",
|
||||
"log",
|
||||
"prettyplease",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"rustc-hash 1.1.0",
|
||||
"shlex",
|
||||
"syn 2.0.90",
|
||||
"which 4.4.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bindgen"
|
||||
version = "0.70.1"
|
||||
@ -1069,6 +1115,15 @@ dependencies = [
|
||||
"error-code",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cmake"
|
||||
version = "0.1.54"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codepage"
|
||||
version = "0.1.2"
|
||||
@ -2482,6 +2537,7 @@ dependencies = [
|
||||
"tokio",
|
||||
"tokio-rustls 0.26.1",
|
||||
"tower-service",
|
||||
"webpki-roots 0.26.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2910,6 +2966,12 @@ version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "lazycell"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.168"
|
||||
@ -2972,7 +3034,7 @@ version = "0.14.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e78a09b56be5adbcad5aa1197371688dc6bb249a26da3bca2011ee2fb987ebfb"
|
||||
dependencies = [
|
||||
"bindgen",
|
||||
"bindgen 0.70.1",
|
||||
"errno",
|
||||
"libc",
|
||||
]
|
||||
@ -3563,7 +3625,7 @@ dependencies = [
|
||||
"tempfile",
|
||||
"unicode-segmentation",
|
||||
"uuid",
|
||||
"which",
|
||||
"which 7.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3719,6 +3781,8 @@ dependencies = [
|
||||
"rstest",
|
||||
"rstest_reuse",
|
||||
"rusqlite",
|
||||
"rustls 0.23.20",
|
||||
"rustls-native-certs 0.8.1",
|
||||
"scopeguard",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@ -3749,7 +3813,8 @@ dependencies = [
|
||||
"v_htmlescape",
|
||||
"wax",
|
||||
"web-time",
|
||||
"which",
|
||||
"webpki-roots 1.0.0",
|
||||
"which 7.0.0",
|
||||
"windows 0.56.0",
|
||||
"winreg",
|
||||
]
|
||||
@ -4059,7 +4124,7 @@ dependencies = [
|
||||
"nu-utils",
|
||||
"num-format",
|
||||
"tempfile",
|
||||
"which",
|
||||
"which 7.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -5512,6 +5577,16 @@ dependencies = [
|
||||
"yansi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prettyplease"
|
||||
version = "0.2.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "print-positions"
|
||||
version = "0.6.1"
|
||||
@ -6028,6 +6103,7 @@ dependencies = [
|
||||
"wasm-bindgen-futures",
|
||||
"wasm-streams",
|
||||
"web-sys",
|
||||
"webpki-roots 0.26.8",
|
||||
"windows-registry",
|
||||
]
|
||||
|
||||
@ -6272,6 +6348,8 @@ version = "0.23.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5065c3f250cbd332cd894be57c40fa52387247659b14a2d6041d121547903b1b"
|
||||
dependencies = [
|
||||
"aws-lc-rs",
|
||||
"log",
|
||||
"once_cell",
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
@ -6347,6 +6425,7 @@ version = "0.102.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9"
|
||||
dependencies = [
|
||||
"aws-lc-rs",
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
"untrusted",
|
||||
@ -7648,10 +7727,13 @@ dependencies = [
|
||||
"log",
|
||||
"native-tls",
|
||||
"once_cell",
|
||||
"rustls 0.23.20",
|
||||
"rustls-pki-types",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"socks",
|
||||
"url",
|
||||
"webpki-roots 0.26.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -8153,6 +8235,36 @@ dependencies = [
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "0.26.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2210b291f7ea53617fbafcc4939f10914214ec15aace5ba62293a668f322c5c9"
|
||||
dependencies = [
|
||||
"rustls-pki-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2853738d1cc4f2da3a225c18ec6c3721abb31961096e9dbf5ab35fa88b19cfdb"
|
||||
dependencies = [
|
||||
"rustls-pki-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "which"
|
||||
version = "4.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7"
|
||||
dependencies = [
|
||||
"either",
|
||||
"home",
|
||||
"once_cell",
|
||||
"rustix 0.38.42",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "which"
|
||||
version = "7.0.0"
|
||||
|
11
Cargo.toml
11
Cargo.toml
@ -148,6 +148,8 @@ rstest = { version = "0.23", default-features = false }
|
||||
rstest_reuse = "0.7"
|
||||
rusqlite = "0.31"
|
||||
rust-embed = "8.7.0"
|
||||
rustls = "0.23"
|
||||
rustls-native-certs = "0.8"
|
||||
scopeguard = { version = "1.2.0" }
|
||||
serde = { version = "1.0" }
|
||||
serde_json = "1.0.97"
|
||||
@ -164,7 +166,7 @@ tempfile = "3.20"
|
||||
titlecase = "3.5"
|
||||
toml = "0.8"
|
||||
trash = "5.2"
|
||||
update-informer = { version = "1.2.0", default-features = false, features = ["github", "native-tls", "ureq"] }
|
||||
update-informer = { version = "1.2.0", default-features = false, features = ["github", "ureq"] }
|
||||
umask = "2.1"
|
||||
unicode-segmentation = "1.12"
|
||||
unicode-width = "0.2"
|
||||
@ -187,6 +189,7 @@ windows = "0.56"
|
||||
windows-sys = "0.48"
|
||||
winreg = "0.52"
|
||||
memchr = "2.7.4"
|
||||
webpki-roots = "1.0"
|
||||
|
||||
[workspace.lints.clippy]
|
||||
# Warning: workspace lints affect library code as well as tests, so don't enable lints that would be too noisy in tests like that.
|
||||
@ -202,7 +205,7 @@ nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.104.2" }
|
||||
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.104.2" }
|
||||
nu-cmd-plugin = { path = "./crates/nu-cmd-plugin", version = "0.104.2", optional = true }
|
||||
nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.104.2" }
|
||||
nu-command = { path = "./crates/nu-command", version = "0.104.2" }
|
||||
nu-command = { path = "./crates/nu-command", version = "0.104.2", default-features = false, features = ["os"] }
|
||||
nu-engine = { path = "./crates/nu-engine", version = "0.104.2" }
|
||||
nu-explore = { path = "./crates/nu-explore", version = "0.104.2" }
|
||||
nu-lsp = { path = "./crates/nu-lsp/", version = "0.104.2" }
|
||||
@ -269,10 +272,14 @@ plugin = [
|
||||
"nu-protocol/plugin",
|
||||
]
|
||||
|
||||
native-tls = ["nu-command/native-tls"]
|
||||
rustls-tls = ["nu-command/rustls-tls"]
|
||||
|
||||
default = [
|
||||
"plugin",
|
||||
"trash-support",
|
||||
"sqlite",
|
||||
"rustls-tls"
|
||||
]
|
||||
stable = ["default"]
|
||||
# NOTE: individual features are also passed to `nu-cmd-lang` that uses them to generate the feature matrix in the `version` command
|
||||
|
@ -91,6 +91,8 @@ rusqlite = { workspace = true, features = [
|
||||
"backup",
|
||||
"chrono",
|
||||
], optional = true }
|
||||
rustls = { workspace = true, optional = true }
|
||||
rustls-native-certs = { workspace = true, optional = true }
|
||||
rmp = { workspace = true }
|
||||
scopeguard = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
@ -108,7 +110,6 @@ ureq = { workspace = true, default-features = false, features = [
|
||||
"charset",
|
||||
"gzip",
|
||||
"json",
|
||||
"native-tls",
|
||||
], optional = true }
|
||||
url = { workspace = true }
|
||||
uu_cp = { workspace = true, optional = true }
|
||||
@ -131,6 +132,7 @@ which = { workspace = true, optional = true }
|
||||
unicode-width = { workspace = true }
|
||||
data-encoding = { version = "2.9.0", features = ["alloc"] }
|
||||
web-time = { workspace = true }
|
||||
webpki-roots = { workspace = true, optional = true }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
winreg = { workspace = true }
|
||||
@ -165,7 +167,7 @@ features = [
|
||||
workspace = true
|
||||
|
||||
[features]
|
||||
default = ["os"]
|
||||
default = ["os", "rustls-tls"]
|
||||
os = [
|
||||
# include other features
|
||||
"js",
|
||||
@ -197,11 +199,24 @@ js = ["getrandom", "getrandom/js", "rand", "uuid"]
|
||||
# interface requires openssl which is not easy to embed into wasm,
|
||||
# using rustls could solve this issue.
|
||||
network = [
|
||||
# these two don't require openssl
|
||||
"multipart-rs",
|
||||
"native-tls",
|
||||
"update-informer/native-tls",
|
||||
"ureq",
|
||||
"uuid",
|
||||
"ureq",
|
||||
"update-informer"
|
||||
]
|
||||
|
||||
native-tls = [
|
||||
"dep:native-tls",
|
||||
"update-informer/native-tls",
|
||||
"ureq/native-tls",
|
||||
]
|
||||
rustls-tls = [
|
||||
"dep:rustls",
|
||||
"dep:rustls-native-certs",
|
||||
"dep:webpki-roots",
|
||||
"update-informer/rustls-tls",
|
||||
"ureq/tls", # ureq 3 will has the feature rustls instead
|
||||
]
|
||||
|
||||
plugin = ["nu-parser/plugin", "os"]
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::formats::value_to_json_value;
|
||||
use crate::{formats::value_to_json_value, network::tls::tls};
|
||||
use base64::{
|
||||
Engine, alphabet,
|
||||
engine::{GeneralPurpose, general_purpose::PAD},
|
||||
@ -56,20 +56,9 @@ pub fn http_client(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
) -> Result<ureq::Agent, ShellError> {
|
||||
let tls = native_tls::TlsConnector::builder()
|
||||
.danger_accept_invalid_certs(allow_insecure)
|
||||
.build()
|
||||
.map_err(|e| ShellError::GenericError {
|
||||
error: format!("Failed to build network tls: {}", e),
|
||||
msg: String::new(),
|
||||
span: None,
|
||||
help: None,
|
||||
inner: vec![],
|
||||
})?;
|
||||
|
||||
let mut agent_builder = ureq::builder()
|
||||
.user_agent("nushell")
|
||||
.tls_connector(std::sync::Arc::new(tls));
|
||||
.tls_connector(std::sync::Arc::new(tls(allow_insecure)?));
|
||||
|
||||
if let RedirectMode::Manual | RedirectMode::Error = redirect_mode {
|
||||
agent_builder = agent_builder.redirects(0);
|
||||
|
@ -2,6 +2,8 @@
|
||||
mod http;
|
||||
#[cfg(feature = "network")]
|
||||
mod port;
|
||||
#[cfg(feature = "network")]
|
||||
pub mod tls;
|
||||
mod url;
|
||||
#[cfg(feature = "network")]
|
||||
mod version_check;
|
||||
|
16
crates/nu-command/src/network/tls/impl_native_tls.rs
Normal file
16
crates/nu-command/src/network/tls/impl_native_tls.rs
Normal file
@ -0,0 +1,16 @@
|
||||
use nu_protocol::ShellError;
|
||||
use ureq::TlsConnector;
|
||||
|
||||
#[doc = include_str!("./tls.rustdoc.md")]
|
||||
pub fn tls(allow_insecure: bool) -> Result<impl TlsConnector, ShellError> {
|
||||
native_tls::TlsConnector::builder()
|
||||
.danger_accept_invalid_certs(allow_insecure)
|
||||
.build()
|
||||
.map_err(|e| ShellError::GenericError {
|
||||
error: format!("Failed to build network tls: {}", e),
|
||||
msg: String::new(),
|
||||
span: None,
|
||||
help: None,
|
||||
inner: vec![],
|
||||
})
|
||||
}
|
251
crates/nu-command/src/network/tls/impl_rustls.rs
Normal file
251
crates/nu-command/src/network/tls/impl_rustls.rs
Normal file
@ -0,0 +1,251 @@
|
||||
use std::{
|
||||
ops::Deref,
|
||||
sync::{Arc, LazyLock, OnceLock},
|
||||
};
|
||||
|
||||
use nu_engine::command_prelude::IoError;
|
||||
use nu_protocol::ShellError;
|
||||
use rustls::{
|
||||
DigitallySignedStruct, RootCertStore, SignatureScheme,
|
||||
client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier},
|
||||
crypto::CryptoProvider,
|
||||
pki_types::{CertificateDer, ServerName, UnixTime},
|
||||
};
|
||||
use ureq::TlsConnector;
|
||||
|
||||
// TODO: replace all these generic errors with proper errors
|
||||
|
||||
/// Stores the crypto provider used by `rustls`.
|
||||
///
|
||||
/// This struct lives in the [`CRYPTO_PROVIDER`] static.
|
||||
/// It can't be created manually.
|
||||
///
|
||||
/// ## Purpose
|
||||
///
|
||||
/// Nushell does **not** use the global `rustls` crypto provider.
|
||||
/// You **must** set a provider here—otherwise, any networking command
|
||||
/// that uses `rustls` won't be able to build a TLS connector.
|
||||
///
|
||||
/// This only matters if the **`rustls-tls`** feature is enabled.
|
||||
/// Builds with **`native-tls`** ignore this completely.
|
||||
///
|
||||
/// ## How to set the provider
|
||||
///
|
||||
/// * [`NuCryptoProvider::default`]
|
||||
/// Uses a built-in provider that works with official `nu` builds.
|
||||
/// This might change in future versions.
|
||||
///
|
||||
/// * [`NuCryptoProvider::set`]
|
||||
/// Lets you provide your own `CryptoProvider` using a closure:
|
||||
///
|
||||
/// ```rust
|
||||
/// use nu_command::tls::CRYPTO_PROVIDER;
|
||||
///
|
||||
/// // Call once at startup
|
||||
/// CRYPTO_PROVIDER.set(|| Ok(rustls::crypto::aws_lc_rs::default_provider()));
|
||||
/// ```
|
||||
///
|
||||
/// Only the first successful call takes effect. Later calls do nothing and return `false`.
|
||||
#[derive(Debug)]
|
||||
pub struct NuCryptoProvider(OnceLock<Result<Arc<CryptoProvider>, ShellError>>);
|
||||
|
||||
/// Global [`NuCryptoProvider`] instance.
|
||||
///
|
||||
/// When the **`rustls-tls`** feature is active, call
|
||||
/// [`CRYPTO_PROVIDER.default()`](NuCryptoProvider::default) or
|
||||
/// [`CRYPTO_PROVIDER.set(...)`](NuCryptoProvider::set) once at startup
|
||||
/// to pick the [`CryptoProvider`] that [`rustls`] will use.
|
||||
///
|
||||
/// Later TLS code gets the provider using [`get`](NuCryptoProvider::get).
|
||||
/// If no provider was set or the closure returned an error, `get` returns a [`ShellError`].
|
||||
pub static CRYPTO_PROVIDER: NuCryptoProvider = NuCryptoProvider(OnceLock::new());
|
||||
|
||||
impl NuCryptoProvider {
|
||||
/// Returns the current [`CryptoProvider`].
|
||||
///
|
||||
/// Comes from the first call to [`default`](Self::default) or [`set`](Self::set).
|
||||
///
|
||||
/// # Errors
|
||||
/// - If no provider was set.
|
||||
/// - If the `set` closure returned an error.
|
||||
pub fn get(&self) -> Result<Arc<CryptoProvider>, ShellError> {
|
||||
// we clone here as the Arc for Ok is super cheap and basically all APIs expect an owned
|
||||
// ShellError, so we might as well clone here already
|
||||
match self.0.get() {
|
||||
Some(val) => val.clone(),
|
||||
None => Err(ShellError::GenericError {
|
||||
error: "tls crypto provider not found".to_string(),
|
||||
msg: "no crypto provider for rustls was defined".to_string(),
|
||||
span: None,
|
||||
help: Some("ensure that nu_command::tls::CRYPTO_PROVIDER is set".to_string()),
|
||||
inner: vec![],
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets a custom [`CryptoProvider`].
|
||||
///
|
||||
/// Call once at startup, before any TLS code runs.
|
||||
/// The closure runs immediately and the result (either `Ok` or `Err`) is stored.
|
||||
/// Returns whether the provider was stored successfully.
|
||||
pub fn set(&self, f: impl FnOnce() -> Result<CryptoProvider, ShellError>) -> bool {
|
||||
let value = f().map(Arc::new);
|
||||
self.0.set(value).is_ok()
|
||||
}
|
||||
|
||||
/// Sets a default [`CryptoProvider`] used in official `nu` builds.
|
||||
///
|
||||
/// Should work on most systems, but may not work in every setup.
|
||||
/// If it fails, use [`set`](Self::set) to install a custom one.
|
||||
/// Returns whether the provider was stored successfully.
|
||||
pub fn default(&self) -> bool {
|
||||
self.set(|| Ok(rustls::crypto::aws_lc_rs::default_provider()))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "os")]
|
||||
static ROOT_CERT_STORE: LazyLock<Result<Arc<RootCertStore>, ShellError>> = LazyLock::new(|| {
|
||||
let mut roots = RootCertStore::empty();
|
||||
|
||||
let native_certs = rustls_native_certs::load_native_certs();
|
||||
|
||||
let errors: Vec<_> = native_certs
|
||||
.errors
|
||||
.into_iter()
|
||||
.map(|err| match err.kind {
|
||||
rustls_native_certs::ErrorKind::Io { inner, path } => ShellError::Io(
|
||||
IoError::new_internal_with_path(inner, err.context, nu_protocol::location!(), path),
|
||||
),
|
||||
rustls_native_certs::ErrorKind::Os(error) => ShellError::GenericError {
|
||||
error: error.to_string(),
|
||||
msg: err.context.to_string(),
|
||||
span: None,
|
||||
help: None,
|
||||
inner: vec![],
|
||||
},
|
||||
rustls_native_certs::ErrorKind::Pem(error) => ShellError::GenericError {
|
||||
error: error.to_string(),
|
||||
msg: err.context.to_string(),
|
||||
span: None,
|
||||
help: None,
|
||||
inner: vec![],
|
||||
},
|
||||
_ => ShellError::GenericError {
|
||||
error: String::from("unknown error loading native certs"),
|
||||
msg: err.context.to_string(),
|
||||
span: None,
|
||||
help: None,
|
||||
inner: vec![],
|
||||
},
|
||||
})
|
||||
.collect();
|
||||
if !errors.is_empty() {
|
||||
return Err(ShellError::GenericError {
|
||||
error: String::from("error loading native certs"),
|
||||
msg: String::from("could not load native certs"),
|
||||
span: None,
|
||||
help: None,
|
||||
inner: errors,
|
||||
});
|
||||
}
|
||||
|
||||
for cert in native_certs.certs {
|
||||
roots.add(cert).map_err(|err| ShellError::GenericError {
|
||||
error: err.to_string(),
|
||||
msg: String::from("could not add root cert"),
|
||||
span: None,
|
||||
help: None,
|
||||
inner: vec![],
|
||||
})?;
|
||||
}
|
||||
|
||||
Ok(Arc::new(roots))
|
||||
});
|
||||
|
||||
#[cfg(not(feature = "os"))]
|
||||
static ROOT_CERT_STORE: LazyLock<Result<Arc<RootCertStore>, ShellError>> = LazyLock::new(|| {
|
||||
Ok(Arc::new(rustls::RootCertStore {
|
||||
roots: webpki_roots::TLS_SERVER_ROOTS.to_vec(),
|
||||
}))
|
||||
});
|
||||
|
||||
#[doc = include_str!("./tls.rustdoc.md")]
|
||||
pub fn tls(allow_insecure: bool) -> Result<impl TlsConnector, ShellError> {
|
||||
let crypto_provider = CRYPTO_PROVIDER.get()?;
|
||||
|
||||
let make_protocol_versions_error = |err: rustls::Error| ShellError::GenericError {
|
||||
error: err.to_string(),
|
||||
msg: "crypto provider is incompatible with protocol versions".to_string(),
|
||||
span: None,
|
||||
help: None,
|
||||
inner: vec![],
|
||||
};
|
||||
|
||||
let client_config = match allow_insecure {
|
||||
false => rustls::ClientConfig::builder_with_provider(crypto_provider)
|
||||
.with_safe_default_protocol_versions()
|
||||
.map_err(make_protocol_versions_error)?
|
||||
.with_root_certificates(ROOT_CERT_STORE.deref().clone()?)
|
||||
.with_no_client_auth(),
|
||||
true => rustls::ClientConfig::builder_with_provider(crypto_provider)
|
||||
.with_safe_default_protocol_versions()
|
||||
.map_err(make_protocol_versions_error)?
|
||||
.dangerous()
|
||||
.with_custom_certificate_verifier(Arc::new(UnsecureServerCertVerifier))
|
||||
.with_no_client_auth(),
|
||||
};
|
||||
|
||||
Ok(Arc::new(client_config))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct UnsecureServerCertVerifier;
|
||||
|
||||
impl ServerCertVerifier for UnsecureServerCertVerifier {
|
||||
fn verify_server_cert(
|
||||
&self,
|
||||
_end_entity: &CertificateDer<'_>,
|
||||
_intermediates: &[CertificateDer<'_>],
|
||||
_server_name: &ServerName<'_>,
|
||||
_ocsp_response: &[u8],
|
||||
_now: UnixTime,
|
||||
) -> Result<ServerCertVerified, rustls::Error> {
|
||||
Ok(ServerCertVerified::assertion())
|
||||
}
|
||||
|
||||
fn verify_tls12_signature(
|
||||
&self,
|
||||
_message: &[u8],
|
||||
_cert: &CertificateDer<'_>,
|
||||
_dss: &DigitallySignedStruct,
|
||||
) -> Result<HandshakeSignatureValid, rustls::Error> {
|
||||
Ok(HandshakeSignatureValid::assertion())
|
||||
}
|
||||
|
||||
fn verify_tls13_signature(
|
||||
&self,
|
||||
_message: &[u8],
|
||||
_cert: &CertificateDer<'_>,
|
||||
_dss: &DigitallySignedStruct,
|
||||
) -> Result<HandshakeSignatureValid, rustls::Error> {
|
||||
Ok(HandshakeSignatureValid::assertion())
|
||||
}
|
||||
|
||||
fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
|
||||
vec![
|
||||
SignatureScheme::RSA_PKCS1_SHA1,
|
||||
SignatureScheme::ECDSA_SHA1_Legacy,
|
||||
SignatureScheme::RSA_PKCS1_SHA256,
|
||||
SignatureScheme::ECDSA_NISTP256_SHA256,
|
||||
SignatureScheme::RSA_PKCS1_SHA384,
|
||||
SignatureScheme::ECDSA_NISTP384_SHA384,
|
||||
SignatureScheme::RSA_PKCS1_SHA512,
|
||||
SignatureScheme::ECDSA_NISTP521_SHA512,
|
||||
SignatureScheme::RSA_PSS_SHA256,
|
||||
SignatureScheme::RSA_PSS_SHA384,
|
||||
SignatureScheme::RSA_PSS_SHA512,
|
||||
SignatureScheme::ED25519,
|
||||
SignatureScheme::ED448,
|
||||
]
|
||||
}
|
||||
}
|
26
crates/nu-command/src/network/tls/mod.rs
Normal file
26
crates/nu-command/src/network/tls/mod.rs
Normal file
@ -0,0 +1,26 @@
|
||||
//! TLS support for networking commands.
|
||||
//!
|
||||
//! This module is available when the `network` feature is enabled. It requires
|
||||
//! either the `native-tls` or `rustls-tls` feature to be selected.
|
||||
//!
|
||||
//! See [`tls`] for how to get a TLS connector.
|
||||
|
||||
#[cfg(feature = "native-tls")]
|
||||
#[path = "impl_native_tls.rs"]
|
||||
mod impl_tls;
|
||||
|
||||
#[cfg(feature = "rustls-tls")]
|
||||
#[path = "impl_rustls.rs"]
|
||||
mod impl_tls;
|
||||
|
||||
#[cfg(all(not(feature = "native-tls"), not(feature = "rustls-tls")))]
|
||||
compile_error!(
|
||||
"No TLS backend enabled. Please enable either the `native-tls` or `rustls-tls` feature."
|
||||
);
|
||||
|
||||
#[cfg(all(feature = "native-tls", feature = "rustls-tls"))]
|
||||
compile_error!(
|
||||
"Multiple TLS backends enabled. Please enable only one of `native-tls` or `rustls-tls`, not both."
|
||||
);
|
||||
|
||||
pub use impl_tls::*;
|
31
crates/nu-command/src/network/tls/tls.rustdoc.md
Normal file
31
crates/nu-command/src/network/tls/tls.rustdoc.md
Normal file
@ -0,0 +1,31 @@
|
||||
Provide a [`TlsConnector`] for [`ureq`].
|
||||
|
||||
This is used by Nushell's networking commands (`http`) to handle secure
|
||||
(or optionally insecure) HTTP connections.
|
||||
The returned connector enables `ureq` to perform HTTPS requests.
|
||||
If `allow_insecure` is set to `true`, certificate verification is disabled.
|
||||
|
||||
This function is only available when the `network` feature is enabled,
|
||||
and requires exactly one of the `native-tls` or `rustls-tls` features to
|
||||
be active.
|
||||
|
||||
# With `native-tls`
|
||||
|
||||
When built with `native-tls`, this uses the platform TLS backend:
|
||||
- OpenSSL on most Unix systems
|
||||
- SChannel on Windows
|
||||
|
||||
These are mature and widely-deployed TLS implementations.
|
||||
Expect strong platform integration.
|
||||
|
||||
# With `rustls-tls`
|
||||
|
||||
When built with `rustls-tls`, this uses the pure-Rust [`rustls`] library for TLS.
|
||||
This has several benefits:
|
||||
- Easier cross-compilation (no need for OpenSSL headers or linker setup)
|
||||
- Works with `musl` targets out of the box
|
||||
- Can be compiled to WASM
|
||||
|
||||
A [`NuCryptoProvider`] must be configured before calling this function.
|
||||
Use [`CRYPTO_PROVIDER.default()`](NuCryptoProvider::default) or
|
||||
[`CRYPTO_PROVIDER.set(...)`](NuCryptoProvider::set) to initialize it.
|
@ -6,6 +6,8 @@ use update_informer::{
|
||||
registry,
|
||||
};
|
||||
|
||||
use super::tls::tls;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct VersionCheck;
|
||||
|
||||
@ -92,7 +94,7 @@ impl HttpClient for NativeTlsHttpClient {
|
||||
headers: update_informer::http_client::HeaderMap,
|
||||
) -> update_informer::Result<T> {
|
||||
let agent = ureq::AgentBuilder::new()
|
||||
.tls_connector(std::sync::Arc::new(native_tls::TlsConnector::new()?))
|
||||
.tls_connector(std::sync::Arc::new(tls(false)?))
|
||||
.build();
|
||||
|
||||
let mut req = agent.get(url).timeout(timeout);
|
||||
|
@ -82,6 +82,9 @@ fn main() -> Result<()> {
|
||||
// TODO: make this conditional in the future
|
||||
ctrlc_protection(&mut engine_state);
|
||||
|
||||
#[cfg(feature = "rustls-tls")]
|
||||
nu_command::tls::CRYPTO_PROVIDER.default();
|
||||
|
||||
// Begin: Default NU_LIB_DIRS, NU_PLUGIN_DIRS
|
||||
// Set default NU_LIB_DIRS and NU_PLUGIN_DIRS here before the env.nu is processed. If
|
||||
// the env.nu file exists, these values will be overwritten, if it does not exist, or
|
||||
|
Loading…
x
Reference in New Issue
Block a user