diff --git a/Cargo.toml b/Cargo.toml index a1d75026aef..211a09c5e57 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,8 +25,8 @@ cargo-platform = { path = "crates/cargo-platform", version = "0.1" } crates-io = { path = "crates/crates-io", version = "0.28" } crossbeam-utils = "0.6" crypto-hash = "0.3.1" -curl = { version = "0.4.21", features = ['http2'] } -curl-sys = "0.4.18" +curl = { version = "0.4.23", features = ['http2'] } +curl-sys = "0.4.21" env_logger = "0.7.0" pretty_env_logger = { version = "0.3", optional = true } failure = "0.1.5" diff --git a/src/cargo/ops/registry.rs b/src/cargo/ops/registry.rs index 2b33abd70a8..d326664de90 100644 --- a/src/cargo/ops/registry.rs +++ b/src/cargo/ops/registry.rs @@ -7,7 +7,7 @@ use std::time::Duration; use std::{cmp, env}; use crates_io::{NewCrate, NewCrateDependency, Registry}; -use curl::easy::{Easy, InfoType, SslOpt}; +use curl::easy::{Easy, InfoType, SslOpt, SslVersion}; use failure::{bail, format_err}; use log::{log, Level}; use percent_encoding::{percent_encode, NON_ALPHANUMERIC}; @@ -18,7 +18,7 @@ use crate::core::source::Source; use crate::core::{Package, SourceId, Workspace}; use crate::ops; use crate::sources::{RegistrySource, SourceConfigMap, CRATES_IO_REGISTRY}; -use crate::util::config::{self, Config}; +use crate::util::config::{self, Config, SslVersionConfig, SslVersionConfigRange}; use crate::util::errors::{CargoResult, CargoResultExt}; use crate::util::important_paths::find_root_manifest_for_wd; use crate::util::IntoUrl; @@ -413,12 +413,14 @@ pub fn needs_custom_http_transport(config: &Config) -> CargoResult { let cainfo = config.get_path("http.cainfo")?; let check_revoke = config.get_bool("http.check-revoke")?; let user_agent = config.get_string("http.user-agent")?; + let ssl_version = config.get::>("http.ssl-version")?; Ok(proxy_exists || timeout || cainfo.is_some() || check_revoke.is_some() - || user_agent.is_some()) + || user_agent.is_some() + || ssl_version.is_some()) } /// Configure a libcurl http handle with the defaults options for Cargo @@ -438,6 +440,38 @@ pub fn configure_http_handle(config: &Config, handle: &mut Easy) -> CargoResult< handle.useragent(&version().to_string())?; } + fn to_ssl_version(s: &str) -> CargoResult { + let version = match s { + "default" => SslVersion::Default, + "tlsv1" => SslVersion::Tlsv1, + "tlsv1.0" => SslVersion::Tlsv10, + "tlsv1.1" => SslVersion::Tlsv11, + "tlsv1.2" => SslVersion::Tlsv12, + "tlsv1.3" => SslVersion::Tlsv13, + _ => bail!( + "Invalid ssl version `{}`,\ + choose from 'default', 'tlsv1', 'tlsv1.0', 'tlsv1.1', 'tlsv1.2', 'tlsv1.3'.", + s + ), + }; + Ok(version) + } + if let Some(ssl_version) = config.get::>("http.ssl-version")? { + match ssl_version { + SslVersionConfig::Single(s) => { + let version = to_ssl_version(s.as_str())?; + handle.ssl_version(version)?; + } + SslVersionConfig::Range(SslVersionConfigRange { min, max }) => { + let min_version = + min.map_or(Ok(SslVersion::Default), |s| to_ssl_version(s.as_str()))?; + let max_version = + max.map_or(Ok(SslVersion::Default), |s| to_ssl_version(s.as_str()))?; + handle.ssl_min_max_version(min_version, max_version)?; + } + } + } + if let Some(true) = config.get::>("http.debug")? { handle.verbose(true)?; handle.debug_function(|kind, data| { diff --git a/src/cargo/util/config.rs b/src/cargo/util/config.rs index 5b69c1d123a..af49d052a3c 100644 --- a/src/cargo/util/config.rs +++ b/src/cargo/util/config.rs @@ -1854,3 +1854,29 @@ pub fn clippy_driver() -> PathBuf { .unwrap_or_else(|_| "clippy-driver".into()) .into() } + +/// Configuration for `ssl-version` in `http` section +/// There are two ways to configure: +/// +/// ```text +/// [http] +/// ssl-version = "tlsv1.3" +/// ``` +/// +/// ```text +/// [http] +/// ssl-version.min = "tlsv1.2" +/// ssl-version.max = "tlsv1.3" +/// ``` +#[derive(Clone, Debug, Deserialize)] +#[serde(untagged)] +pub enum SslVersionConfig { + Single(String), + Range(SslVersionConfigRange), +} + +#[derive(Clone, Debug, Deserialize)] +pub struct SslVersionConfigRange { + pub min: Option, + pub max: Option, +} diff --git a/src/doc/src/reference/config.md b/src/doc/src/reference/config.md index c4d8799695f..cfd50c793a9 100644 --- a/src/doc/src/reference/config.md +++ b/src/doc/src/reference/config.md @@ -107,6 +107,13 @@ proxy = "host:port" # HTTP proxy to use for HTTP requests (defaults to none) timeout = 30 # Timeout for each HTTP request, in seconds cainfo = "cert.pem" # Path to Certificate Authority (CA) bundle (optional) check-revoke = true # Indicates whether SSL certs are checked for revocation +ssl-version = "tlsv1.3" # Indicates which SSL version or above to use (options are + # "default", "tlsv1", "tlsv1.0", "tlsv1.1", "tlsv1.2", "tlsv1.3") + # To better control SSL version, we can even use + # `ssl-version.min = "..."` and `ssl-version.max = "..."` + # where "..." is one of the above options. But note these two forms + # ("setting `ssl-version`" and "setting both `min`/`max`) + # can't co-exist. low-speed-limit = 5 # Lower threshold for bytes/sec (10 = default, 0 = disabled) multiplexing = true # whether or not to use HTTP/2 multiplexing where possible diff --git a/tests/testsuite/config.rs b/tests/testsuite/config.rs index d65fe7a9af0..85a685fd8a0 100644 --- a/tests/testsuite/config.rs +++ b/tests/testsuite/config.rs @@ -6,7 +6,7 @@ use std::os; use std::path::Path; use cargo::core::{enable_nightly_features, Shell}; -use cargo::util::config::{self, Config}; +use cargo::util::config::{self, Config, SslVersionConfig}; use cargo::util::toml::{self, VecStringOrBool as VSOB}; use cargo_test_support::{paths, project, t}; use serde::Deserialize; @@ -833,3 +833,87 @@ i64max = 9223372036854775807 invalid value: integer `123456789`, expected i8", ); } + +#[cargo_test] +fn config_get_ssl_version_missing() { + write_config( + "\ +[http] +hello = 'world' +", + ); + + let config = new_config(&[]); + + assert!(config + .get::>("http.ssl-version") + .unwrap() + .is_none()); +} + +#[cargo_test] +fn config_get_ssl_version_single() { + write_config( + "\ +[http] +ssl-version = 'tlsv1.2' +", + ); + + let config = new_config(&[]); + + let a = config + .get::>("http.ssl-version") + .unwrap() + .unwrap(); + match a { + SslVersionConfig::Single(v) => assert_eq!(&v, "tlsv1.2"), + SslVersionConfig::Range(_) => panic!("Did not expect ssl version min/max."), + }; +} + +#[cargo_test] +fn config_get_ssl_version_min_max() { + write_config( + "\ +[http] +ssl-version.min = 'tlsv1.2' +ssl-version.max = 'tlsv1.3' +", + ); + + let config = new_config(&[]); + + let a = config + .get::>("http.ssl-version") + .unwrap() + .unwrap(); + match a { + SslVersionConfig::Single(_) => panic!("Did not expect exact ssl version."), + SslVersionConfig::Range(range) => { + assert_eq!(range.min, Some(String::from("tlsv1.2"))); + assert_eq!(range.max, Some(String::from("tlsv1.3"))); + } + }; +} + +#[cargo_test] +fn config_get_ssl_version_both_forms_configured() { + // this is not allowed + write_config( + "\ +[http] +ssl-version = 'tlsv1.1' +ssl-version.min = 'tlsv1.2' +ssl-version.max = 'tlsv1.3' +", + ); + + let config = new_config(&[]); + + assert!(config.get::("http.ssl-version").is_err()); + assert!(config + .get::>("http.ssl-version") + .unwrap() + .is_none()); +}