Skip to content

Commit

Permalink
Add options for specifying the TLS version (seanmonstar#1315)
Browse files Browse the repository at this point in the history
  • Loading branch information
blyxxyz authored Aug 12, 2021
1 parent bdc57be commit 66c1b48
Show file tree
Hide file tree
Showing 5 changed files with 248 additions and 4 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
125 changes: 124 additions & 1 deletion src/async_impl/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"))]
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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"))]
Expand Down
58 changes: 58 additions & 0 deletions src/blocking/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -314,7 +315,7 @@ if_hyper! {
mod proxy;
pub mod redirect;
#[cfg(feature = "__tls")]
mod tls;
pub mod tls;
mod util;
}

Expand Down
62 changes: 62 additions & 0 deletions src/tls.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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,
Expand Down

0 comments on commit 66c1b48

Please sign in to comment.