Add options for specifying the TLS version (#1315)
This commit is contained in:
@@ -108,7 +108,7 @@ ipnet = "2.3"
|
||||
|
||||
## default-tls
|
||||
hyper-tls = { version = "0.5", optional = true }
|
||||
native-tls-crate = { version = "0.2.7", optional = true, package = "native-tls" }
|
||||
native-tls-crate = { version = "0.2.8", optional = true, package = "native-tls" }
|
||||
tokio-native-tls = { version = "0.3.0", optional = true }
|
||||
|
||||
# rustls-tls
|
||||
|
||||
@@ -37,7 +37,7 @@ use crate::error;
|
||||
use crate::into_url::{expect_uri, try_uri};
|
||||
use crate::redirect::{self, remove_sensitive_headers};
|
||||
#[cfg(feature = "__tls")]
|
||||
use crate::tls::TlsBackend;
|
||||
use crate::tls::{self, TlsBackend};
|
||||
#[cfg(feature = "__tls")]
|
||||
use crate::Certificate;
|
||||
#[cfg(any(feature = "native-tls", feature = "__rustls"))]
|
||||
@@ -99,6 +99,10 @@ struct Config {
|
||||
#[cfg(feature = "__tls")]
|
||||
tls_built_in_root_certs: bool,
|
||||
#[cfg(feature = "__tls")]
|
||||
min_tls_version: Option<tls::Version>,
|
||||
#[cfg(feature = "__tls")]
|
||||
max_tls_version: Option<tls::Version>,
|
||||
#[cfg(feature = "__tls")]
|
||||
tls: TlsBackend,
|
||||
http_version_pref: HttpVersionPref,
|
||||
http1_title_case_headers: bool,
|
||||
@@ -158,6 +162,10 @@ impl ClientBuilder {
|
||||
#[cfg(any(feature = "native-tls", feature = "__rustls"))]
|
||||
identity: None,
|
||||
#[cfg(feature = "__tls")]
|
||||
min_tls_version: None,
|
||||
#[cfg(feature = "__tls")]
|
||||
max_tls_version: None,
|
||||
#[cfg(feature = "__tls")]
|
||||
tls: TlsBackend::default(),
|
||||
http_version_pref: HttpVersionPref::All,
|
||||
http1_title_case_headers: false,
|
||||
@@ -262,6 +270,27 @@ impl ClientBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(min_tls_version) = config.min_tls_version {
|
||||
let protocol = min_tls_version.to_native_tls().ok_or_else(|| {
|
||||
// TLS v1.3. This would be entirely reasonable,
|
||||
// native-tls just doesn't support it.
|
||||
// https://github.com/sfackler/rust-native-tls/issues/140
|
||||
crate::error::builder("invalid minimum TLS version for backend")
|
||||
})?;
|
||||
tls.min_protocol_version(Some(protocol));
|
||||
}
|
||||
|
||||
if let Some(max_tls_version) = config.max_tls_version {
|
||||
let protocol = max_tls_version.to_native_tls().ok_or_else(|| {
|
||||
// TLS v1.3.
|
||||
// We could arguably do max_protocol_version(None), given
|
||||
// that 1.4 does not exist yet, but that'd get messy in the
|
||||
// future.
|
||||
crate::error::builder("invalid maximum TLS version for backend")
|
||||
})?;
|
||||
tls.max_protocol_version(Some(protocol));
|
||||
}
|
||||
|
||||
Connector::new_default_tls(
|
||||
http,
|
||||
tls,
|
||||
@@ -329,6 +358,34 @@ impl ClientBuilder {
|
||||
id.add_to_rustls(&mut tls)?;
|
||||
}
|
||||
|
||||
// rustls does not support TLS versions <1.2 and this is unlikely to change.
|
||||
// https://github.com/rustls/rustls/issues/33
|
||||
|
||||
// As of writing, TLS 1.2 and 1.3 are the only implemented versions and are both
|
||||
// enabled by default.
|
||||
// rustls 0.20 will add ALL_VERSIONS and DEFAULT_VERSIONS. That will enable a more
|
||||
// sophisticated approach.
|
||||
// For now we assume the default tls.versions matches the future ALL_VERSIONS and
|
||||
// act based on that.
|
||||
|
||||
if let Some(min_tls_version) = config.min_tls_version {
|
||||
tls.versions
|
||||
.retain(|&version| match tls::Version::from_rustls(version) {
|
||||
Some(version) => version >= min_tls_version,
|
||||
// Assume it's so new we don't know about it, allow it
|
||||
// (as of writing this is unreachable)
|
||||
None => true,
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(max_tls_version) = config.max_tls_version {
|
||||
tls.versions
|
||||
.retain(|&version| match tls::Version::from_rustls(version) {
|
||||
Some(version) => version <= max_tls_version,
|
||||
None => false,
|
||||
});
|
||||
}
|
||||
|
||||
Connector::new_rustls_tls(
|
||||
http,
|
||||
tls,
|
||||
@@ -957,6 +1014,64 @@ impl ClientBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the minimum required TLS version for connections.
|
||||
///
|
||||
/// By default the TLS backend's own default is used.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// A value of `tls::Version::TLS_1_3` will cause an error with the
|
||||
/// `native-tls`/`default-tls` backend. This does not mean the version
|
||||
/// isn't supported, just that it can't be set as a minimum due to
|
||||
/// technical limitations.
|
||||
///
|
||||
/// # Optional
|
||||
///
|
||||
/// This requires the optional `default-tls`, `native-tls`, or `rustls-tls(-...)`
|
||||
/// feature to be enabled.
|
||||
#[cfg(feature = "__tls")]
|
||||
#[cfg_attr(
|
||||
docsrs,
|
||||
doc(cfg(any(
|
||||
feature = "default-tls",
|
||||
feature = "native-tls",
|
||||
feature = "rustls-tls"
|
||||
)))
|
||||
)]
|
||||
pub fn min_tls_version(mut self, version: tls::Version) -> ClientBuilder {
|
||||
self.config.min_tls_version = Some(version);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the maximum allowed TLS version for connections.
|
||||
///
|
||||
/// By default there's no maximum.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// A value of `tls::Version::TLS_1_3` will cause an error with the
|
||||
/// `native-tls`/`default-tls` backend. This does not mean the version
|
||||
/// isn't supported, just that it can't be set as a maximum due to
|
||||
/// technical limitations.
|
||||
///
|
||||
/// # Optional
|
||||
///
|
||||
/// This requires the optional `default-tls`, `native-tls`, or `rustls-tls(-...)`
|
||||
/// feature to be enabled.
|
||||
#[cfg(feature = "__tls")]
|
||||
#[cfg_attr(
|
||||
docsrs,
|
||||
doc(cfg(any(
|
||||
feature = "default-tls",
|
||||
feature = "native-tls",
|
||||
feature = "rustls-tls"
|
||||
)))
|
||||
)]
|
||||
pub fn max_tls_version(mut self, version: tls::Version) -> ClientBuilder {
|
||||
self.config.max_tls_version = Some(version);
|
||||
self
|
||||
}
|
||||
|
||||
/// Force using the native TLS backend.
|
||||
///
|
||||
/// Since multiple TLS backends can be optionally enabled, this option will
|
||||
@@ -1399,6 +1514,14 @@ impl Config {
|
||||
if !self.certs_verification {
|
||||
f.field("danger_accept_invalid_certs", &true);
|
||||
}
|
||||
|
||||
if let Some(ref min_tls_version) = self.min_tls_version {
|
||||
f.field("min_tls_version", min_tls_version);
|
||||
}
|
||||
|
||||
if let Some(ref max_tls_version) = self.max_tls_version {
|
||||
f.field("max_tls_version", max_tls_version);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "native-tls-crate", feature = "__rustls"))]
|
||||
|
||||
@@ -16,6 +16,8 @@ use super::request::{Request, RequestBuilder};
|
||||
use super::response::Response;
|
||||
use super::wait;
|
||||
#[cfg(feature = "__tls")]
|
||||
use crate::tls;
|
||||
#[cfg(feature = "__tls")]
|
||||
use crate::Certificate;
|
||||
#[cfg(any(feature = "native-tls", feature = "__rustls"))]
|
||||
use crate::Identity;
|
||||
@@ -603,6 +605,62 @@ impl ClientBuilder {
|
||||
self.with_inner(|inner| inner.danger_accept_invalid_certs(accept_invalid_certs))
|
||||
}
|
||||
|
||||
/// Set the minimum required TLS version for connections.
|
||||
///
|
||||
/// By default the TLS backend's own default is used.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// A value of `tls::Version::TLS_1_3` will cause an error with the
|
||||
/// `native-tls`/`default-tls` backend. This does not mean the version
|
||||
/// isn't supported, just that it can't be set as a minimum due to
|
||||
/// technical limitations.
|
||||
///
|
||||
/// # Optional
|
||||
///
|
||||
/// This requires the optional `default-tls`, `native-tls`, or `rustls-tls(-...)`
|
||||
/// feature to be enabled.
|
||||
#[cfg(feature = "__tls")]
|
||||
#[cfg_attr(
|
||||
docsrs,
|
||||
doc(cfg(any(
|
||||
feature = "default-tls",
|
||||
feature = "native-tls",
|
||||
feature = "rustls-tls"
|
||||
)))
|
||||
)]
|
||||
pub fn min_tls_version(self, version: tls::Version) -> ClientBuilder {
|
||||
self.with_inner(|inner| inner.min_tls_version(version))
|
||||
}
|
||||
|
||||
/// Set the maximum allowed TLS version for connections.
|
||||
///
|
||||
/// By default there's no maximum.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// A value of `tls::Version::TLS_1_3` will cause an error with the
|
||||
/// `native-tls`/`default-tls` backend. This does not mean the version
|
||||
/// isn't supported, just that it can't be set as a maximum due to
|
||||
/// technical limitations.
|
||||
///
|
||||
/// # Optional
|
||||
///
|
||||
/// This requires the optional `default-tls`, `native-tls`, or `rustls-tls(-...)`
|
||||
/// feature to be enabled.
|
||||
#[cfg(feature = "__tls")]
|
||||
#[cfg_attr(
|
||||
docsrs,
|
||||
doc(cfg(any(
|
||||
feature = "default-tls",
|
||||
feature = "native-tls",
|
||||
feature = "rustls-tls"
|
||||
)))
|
||||
)]
|
||||
pub fn max_tls_version(self, version: tls::Version) -> ClientBuilder {
|
||||
self.with_inner(|inner| inner.max_tls_version(version))
|
||||
}
|
||||
|
||||
/// Force using the native TLS backend.
|
||||
///
|
||||
/// Since multiple TLS backends can be optionally enabled, this option will
|
||||
|
||||
@@ -298,7 +298,8 @@ if_hyper! {
|
||||
};
|
||||
pub use self::proxy::Proxy;
|
||||
#[cfg(feature = "__tls")]
|
||||
pub use self::tls::{Certificate, Identity};
|
||||
// Re-exports, to be removed in a future release
|
||||
pub use tls::{Certificate, Identity};
|
||||
#[cfg(feature = "multipart")]
|
||||
pub use self::async_impl::multipart;
|
||||
|
||||
@@ -314,7 +315,7 @@ if_hyper! {
|
||||
mod proxy;
|
||||
pub mod redirect;
|
||||
#[cfg(feature = "__tls")]
|
||||
mod tls;
|
||||
pub mod tls;
|
||||
mod util;
|
||||
}
|
||||
|
||||
|
||||
62
src/tls.rs
62
src/tls.rs
@@ -1,3 +1,16 @@
|
||||
//! TLS configuration
|
||||
//!
|
||||
//! By default, a `Client` will make use of system-native transport layer
|
||||
//! security to connect to HTTPS destinations. This means schannel on Windows,
|
||||
//! Security-Framework on macOS, and OpenSSL on Linux.
|
||||
//!
|
||||
//! - Additional X509 certificates can be configured on a `ClientBuilder` with the
|
||||
//! [`Certificate`](Certificate) type.
|
||||
//! - Client certificates can be add to a `ClientBuilder` with the
|
||||
//! [`Identity`][Identity] type.
|
||||
//! - Various parts of TLS can also be configured or even disabled on the
|
||||
//! `ClientBuilder`.
|
||||
|
||||
#[cfg(feature = "__rustls")]
|
||||
use rustls::{
|
||||
internal::msgs::handshake::DigitallySignedStruct, HandshakeSignatureValid, RootCertStore,
|
||||
@@ -268,6 +281,55 @@ impl fmt::Debug for Identity {
|
||||
}
|
||||
}
|
||||
|
||||
/// A TLS protocol version.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct Version(InnerVersion);
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[non_exhaustive]
|
||||
enum InnerVersion {
|
||||
Tls1_0,
|
||||
Tls1_1,
|
||||
Tls1_2,
|
||||
Tls1_3,
|
||||
}
|
||||
|
||||
// These could perhaps be From/TryFrom implementations, but those would be
|
||||
// part of the public API so let's be careful
|
||||
impl Version {
|
||||
/// Version 1.0 of the TLS protocol.
|
||||
pub const TLS_1_0: Version = Version(InnerVersion::Tls1_0);
|
||||
/// Version 1.1 of the TLS protocol.
|
||||
pub const TLS_1_1: Version = Version(InnerVersion::Tls1_1);
|
||||
/// Version 1.2 of the TLS protocol.
|
||||
pub const TLS_1_2: Version = Version(InnerVersion::Tls1_2);
|
||||
/// Version 1.3 of the TLS protocol.
|
||||
pub const TLS_1_3: Version = Version(InnerVersion::Tls1_3);
|
||||
|
||||
#[cfg(feature = "default-tls")]
|
||||
pub(crate) fn to_native_tls(self) -> Option<native_tls_crate::Protocol> {
|
||||
match self.0 {
|
||||
InnerVersion::Tls1_0 => Some(native_tls_crate::Protocol::Tlsv10),
|
||||
InnerVersion::Tls1_1 => Some(native_tls_crate::Protocol::Tlsv11),
|
||||
InnerVersion::Tls1_2 => Some(native_tls_crate::Protocol::Tlsv12),
|
||||
InnerVersion::Tls1_3 => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "__rustls")]
|
||||
pub(crate) fn from_rustls(version: rustls::ProtocolVersion) -> Option<Self> {
|
||||
match version {
|
||||
rustls::ProtocolVersion::SSLv2 => None,
|
||||
rustls::ProtocolVersion::SSLv3 => None,
|
||||
rustls::ProtocolVersion::TLSv1_0 => Some(Self(InnerVersion::Tls1_0)),
|
||||
rustls::ProtocolVersion::TLSv1_1 => Some(Self(InnerVersion::Tls1_1)),
|
||||
rustls::ProtocolVersion::TLSv1_2 => Some(Self(InnerVersion::Tls1_2)),
|
||||
rustls::ProtocolVersion::TLSv1_3 => Some(Self(InnerVersion::Tls1_3)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) enum TlsBackend {
|
||||
#[cfg(feature = "default-tls")]
|
||||
Default,
|
||||
|
||||
Reference in New Issue
Block a user