diff --git a/Cargo.lock b/Cargo.lock index 3ff44ae8..9b3b74b5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -145,6 +145,7 @@ dependencies = [ "guess_host_triple", "log", "miette", + "once_cell", "reqwest", "scopeguard", "semver", diff --git a/Cargo.toml b/Cargo.toml index 379e649b..42968b54 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,7 @@ flate2 = { version = "1.0.24", features = ["zlib-ng"], default-features = false futures-util = { version = "0.3.21", default-features = false } log = "0.4.14" miette = { version = "4.7.1", features = ["fancy-no-backtrace"] } +once_cell = "1.12.0" reqwest = { version = "0.11.11", features = [ "rustls-tls", "stream" ], default-features = false } scopeguard = "1.1.0" semver = "1.0.10" diff --git a/ci-scripts/run_tests_unix.sh b/ci-scripts/run_tests_unix.sh index dbc25abd..135d4765 100755 --- a/ci-scripts/run_tests_unix.sh +++ b/ci-scripts/run_tests_unix.sh @@ -20,3 +20,13 @@ cargo binstall --help >/dev/null "./$1" binstall --log-level debug --manifest-path . --no-confirm cargo-binstall # Test that the installed binaries can be run cargo binstall --help >/dev/null + +# Install binaries using https-only-mode and specify min tls ver +"./$1" binstall \ + --log-level debug \ + --https-only-mode \ + --min-tls-version tls1-3 \ + --no-confirm \ + cargo-binstall +# Test that the installed binaries can be run +cargo binstall --help >/dev/null diff --git a/src/fetchers/quickinstall.rs b/src/fetchers/quickinstall.rs index 7d6ed14a..120dd6cb 100644 --- a/src/fetchers/quickinstall.rs +++ b/src/fetchers/quickinstall.rs @@ -7,7 +7,9 @@ use tokio::task::JoinHandle; use url::Url; use super::Data; -use crate::{download_and_extract, remote_exists, BinstallError, PkgFmt}; +use crate::{ + download_and_extract, new_reqwest_client_builder, remote_exists, BinstallError, PkgFmt, +}; const BASE_URL: &str = "https://github.com/alsuren/cargo-quickinstall/releases/download"; const STATS_URL: &str = "https://warehouse-clerk-tmp.vercel.app/api/crate"; @@ -89,7 +91,7 @@ impl QuickInstall { let url = Url::parse(&stats_url)?; debug!("Sending installation report to quickinstall ({url})"); - reqwest::Client::builder() + new_reqwest_client_builder() .user_agent(USER_AGENT) .build()? .request(Method::HEAD, url.clone()) diff --git a/src/helpers.rs b/src/helpers.rs index 55aee08c..03bf97c9 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -5,7 +5,8 @@ use bytes::Bytes; use cargo_toml::Manifest; use futures_util::stream::Stream; use log::debug; -use reqwest::{Method, Response}; +use once_cell::sync::OnceCell; +use reqwest::{Client, ClientBuilder, Method, Response}; use serde::Serialize; use tinytemplate::TinyTemplate; use url::Url; @@ -27,6 +28,12 @@ mod stream_readable; mod path_ext; pub use path_ext::*; +mod tls_version; +pub use tls_version::TLSVersion; + +/// (enable https only mode, min TLS version_option) +pub static REQWESTGLOBALCONFIG: OnceCell<(bool, Option)> = OnceCell::new(); + /// Load binstall metadata from the crate `Cargo.toml` at the provided path pub fn load_manifest_path>( manifest_path: P, @@ -40,8 +47,30 @@ pub fn load_manifest_path>( Ok(manifest) } +pub fn new_reqwest_client_builder() -> ClientBuilder { + let mut builder = ClientBuilder::new(); + + if let Some((https_only, min_tls_ver_opt)) = REQWESTGLOBALCONFIG.get() { + builder = builder.https_only(*https_only); + + if *https_only { + builder = builder.min_tls_version(reqwest::tls::Version::TLS_1_2); + } + + if let Some(min_tls_ver) = *min_tls_ver_opt { + builder = builder.min_tls_version(min_tls_ver.into()); + } + } + + builder +} + +pub fn new_reqwest_client() -> reqwest::Result { + new_reqwest_client_builder().build() +} + pub async fn remote_exists(url: Url, method: Method) -> Result { - let req = reqwest::Client::new() + let req = new_reqwest_client()? .request(method.clone(), url.clone()) .send() .await @@ -54,7 +83,9 @@ async fn create_request( ) -> Result>, BinstallError> { debug!("Downloading from: '{url}'"); - reqwest::get(url.clone()) + new_reqwest_client()? + .get(url.clone()) + .send() .await .and_then(|r| r.error_for_status()) .map_err(|err| BinstallError::Http { diff --git a/src/helpers/tls_version.rs b/src/helpers/tls_version.rs new file mode 100644 index 00000000..1f0ad5dc --- /dev/null +++ b/src/helpers/tls_version.rs @@ -0,0 +1,17 @@ +use clap::ArgEnum; +use reqwest::tls::Version; + +#[derive(Debug, Copy, Clone, ArgEnum)] +pub enum TLSVersion { + Tls1_2, + Tls1_3, +} + +impl From for Version { + fn from(ver: TLSVersion) -> Self { + match ver { + TLSVersion::Tls1_2 => Version::TLS_1_2, + TLSVersion::Tls1_3 => Version::TLS_1_3, + } + } +} diff --git a/src/main.rs b/src/main.rs index 0d8748cc..5b2294ac 100644 --- a/src/main.rs +++ b/src/main.rs @@ -84,6 +84,17 @@ struct Options { #[clap(long)] no_cleanup: bool, + /// Enable https only mode. + /// + /// When https only mode is enabled, it will also set + /// minimum TLS version to tls1_2. + #[clap(long)] + https_only_mode: bool, + + /// Decide which TLS version to use. + #[clap(long, arg_enum)] + min_tls_version: Option, + /// Override manifest source. /// /// This skips searching crates.io for a manifest and uses the specified path directly, useful @@ -177,6 +188,11 @@ async fn entry() -> Result<()> { bin_dir: opts.bin_dir.take(), }; + // Initialize REQWESTGLOBALCONFIG + REQWESTGLOBALCONFIG + .set((opts.https_only_mode, opts.min_tls_version)) + .unwrap(); + // Setup logging let mut log_config = ConfigBuilder::new(); log_config.add_filter_ignore("hyper".to_string());