Add options for specifying the TLS version (#1315)
This commit is contained in:
		| @@ -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