| //! 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::{ |
| client::HandshakeSignatureValid, client::ServerCertVerified, client::ServerCertVerifier, |
| internal::msgs::handshake::DigitallySignedStruct, Error as TLSError, ServerName, |
| }; |
| use std::fmt; |
| |
| /// Represents a server X509 certificate. |
| #[derive(Clone)] |
| pub struct Certificate { |
| #[cfg(feature = "native-tls-crate")] |
| native: native_tls_crate::Certificate, |
| #[cfg(feature = "__rustls")] |
| original: Cert, |
| } |
| |
| #[cfg(feature = "__rustls")] |
| #[derive(Clone)] |
| enum Cert { |
| Der(Vec<u8>), |
| Pem(Vec<u8>), |
| } |
| |
| /// Represents a private key and X509 cert as a client certificate. |
| #[derive(Clone)] |
| pub struct Identity { |
| #[cfg_attr(not(any(feature = "native-tls", feature = "__rustls")), allow(unused))] |
| inner: ClientCert, |
| } |
| |
| #[derive(Clone)] |
| enum ClientCert { |
| #[cfg(feature = "native-tls")] |
| Pkcs12(native_tls_crate::Identity), |
| #[cfg(feature = "native-tls")] |
| Pkcs8(native_tls_crate::Identity), |
| #[cfg(feature = "__rustls")] |
| Pem { |
| key: rustls::PrivateKey, |
| certs: Vec<rustls::Certificate>, |
| }, |
| } |
| |
| impl Certificate { |
| /// Create a `Certificate` from a binary DER encoded certificate |
| /// |
| /// # Examples |
| /// |
| /// ``` |
| /// # use std::fs::File; |
| /// # use std::io::Read; |
| /// # fn cert() -> Result<(), Box<std::error::Error>> { |
| /// let mut buf = Vec::new(); |
| /// File::open("my_cert.der")? |
| /// .read_to_end(&mut buf)?; |
| /// let cert = reqwest::Certificate::from_der(&buf)?; |
| /// # drop(cert); |
| /// # Ok(()) |
| /// # } |
| /// ``` |
| pub fn from_der(der: &[u8]) -> crate::Result<Certificate> { |
| Ok(Certificate { |
| #[cfg(feature = "native-tls-crate")] |
| native: native_tls_crate::Certificate::from_der(der).map_err(crate::error::builder)?, |
| #[cfg(feature = "__rustls")] |
| original: Cert::Der(der.to_owned()), |
| }) |
| } |
| |
| /// Create a `Certificate` from a PEM encoded certificate |
| /// |
| /// # Examples |
| /// |
| /// ``` |
| /// # use std::fs::File; |
| /// # use std::io::Read; |
| /// # fn cert() -> Result<(), Box<std::error::Error>> { |
| /// let mut buf = Vec::new(); |
| /// File::open("my_cert.pem")? |
| /// .read_to_end(&mut buf)?; |
| /// let cert = reqwest::Certificate::from_pem(&buf)?; |
| /// # drop(cert); |
| /// # Ok(()) |
| /// # } |
| /// ``` |
| pub fn from_pem(pem: &[u8]) -> crate::Result<Certificate> { |
| Ok(Certificate { |
| #[cfg(feature = "native-tls-crate")] |
| native: native_tls_crate::Certificate::from_pem(pem).map_err(crate::error::builder)?, |
| #[cfg(feature = "__rustls")] |
| original: Cert::Pem(pem.to_owned()), |
| }) |
| } |
| |
| #[cfg(feature = "native-tls-crate")] |
| pub(crate) fn add_to_native_tls(self, tls: &mut native_tls_crate::TlsConnectorBuilder) { |
| tls.add_root_certificate(self.native); |
| } |
| |
| #[cfg(feature = "__rustls")] |
| pub(crate) fn add_to_rustls( |
| self, |
| root_cert_store: &mut rustls::RootCertStore, |
| ) -> crate::Result<()> { |
| use std::io::Cursor; |
| |
| match self.original { |
| Cert::Der(buf) => root_cert_store |
| .add(&rustls::Certificate(buf)) |
| .map_err(crate::error::builder)?, |
| Cert::Pem(buf) => { |
| let mut pem = Cursor::new(buf); |
| let certs = rustls_pemfile::certs(&mut pem).map_err(|_| { |
| crate::error::builder(TLSError::General(String::from( |
| "No valid certificate was found", |
| ))) |
| })?; |
| for c in certs { |
| root_cert_store |
| .add(&rustls::Certificate(c)) |
| .map_err(crate::error::builder)?; |
| } |
| } |
| } |
| Ok(()) |
| } |
| } |
| |
| impl Identity { |
| /// Parses a DER-formatted PKCS #12 archive, using the specified password to decrypt the key. |
| /// |
| /// The archive should contain a leaf certificate and its private key, as well any intermediate |
| /// certificates that allow clients to build a chain to a trusted root. |
| /// The chain certificates should be in order from the leaf certificate towards the root. |
| /// |
| /// PKCS #12 archives typically have the file extension `.p12` or `.pfx`, and can be created |
| /// with the OpenSSL `pkcs12` tool: |
| /// |
| /// ```bash |
| /// openssl pkcs12 -export -out identity.pfx -inkey key.pem -in cert.pem -certfile chain_certs.pem |
| /// ``` |
| /// |
| /// # Examples |
| /// |
| /// ``` |
| /// # use std::fs::File; |
| /// # use std::io::Read; |
| /// # fn pkcs12() -> Result<(), Box<std::error::Error>> { |
| /// let mut buf = Vec::new(); |
| /// File::open("my-ident.pfx")? |
| /// .read_to_end(&mut buf)?; |
| /// let pkcs12 = reqwest::Identity::from_pkcs12_der(&buf, "my-privkey-password")?; |
| /// # drop(pkcs12); |
| /// # Ok(()) |
| /// # } |
| /// ``` |
| /// |
| /// # Optional |
| /// |
| /// This requires the `native-tls` Cargo feature enabled. |
| #[cfg(feature = "native-tls")] |
| pub fn from_pkcs12_der(der: &[u8], password: &str) -> crate::Result<Identity> { |
| Ok(Identity { |
| inner: ClientCert::Pkcs12( |
| native_tls_crate::Identity::from_pkcs12(der, password) |
| .map_err(crate::error::builder)?, |
| ), |
| }) |
| } |
| |
| /// Parses a chain of PEM encoded X509 certificates, with the leaf certificate first. |
| /// `key` is a PEM encoded PKCS #8 formatted private key for the leaf certificate. |
| /// |
| /// The certificate chain should contain any intermediate cerficates that should be sent to |
| /// clients to allow them to build a chain to a trusted root. |
| /// |
| /// A certificate chain here means a series of PEM encoded certificates concatenated together. |
| /// |
| /// # Examples |
| /// |
| /// ``` |
| /// # use std::fs; |
| /// # fn pkcs8() -> Result<(), Box<std::error::Error>> { |
| /// let cert = fs::read("client.pem")?; |
| /// let key = fs::read("key.pem")?; |
| /// let pkcs8 = reqwest::Identity::from_pkcs8_pem(&cert, &key)?; |
| /// # drop(pkcs8); |
| /// # Ok(()) |
| /// # } |
| /// ``` |
| /// |
| /// # Optional |
| /// |
| /// This requires the `native-tls` Cargo feature enabled. |
| #[cfg(feature = "native-tls")] |
| pub fn from_pkcs8_pem(pem: &[u8], key: &[u8]) -> crate::Result<Identity> { |
| Ok(Identity { |
| inner: ClientCert::Pkcs8( |
| native_tls_crate::Identity::from_pkcs8(pem, key).map_err(crate::error::builder)?, |
| ), |
| }) |
| } |
| |
| /// Parses PEM encoded private key and certificate. |
| /// |
| /// The input should contain a PEM encoded private key |
| /// and at least one PEM encoded certificate. |
| /// |
| /// Note: The private key must be in RSA, SEC1 Elliptic Curve or PKCS#8 format. |
| /// |
| /// # Examples |
| /// |
| /// ``` |
| /// # use std::fs::File; |
| /// # use std::io::Read; |
| /// # fn pem() -> Result<(), Box<std::error::Error>> { |
| /// let mut buf = Vec::new(); |
| /// File::open("my-ident.pem")? |
| /// .read_to_end(&mut buf)?; |
| /// let id = reqwest::Identity::from_pem(&buf)?; |
| /// # drop(id); |
| /// # Ok(()) |
| /// # } |
| /// ``` |
| /// |
| /// # Optional |
| /// |
| /// This requires the `rustls-tls(-...)` Cargo feature enabled. |
| #[cfg(feature = "__rustls")] |
| pub fn from_pem(buf: &[u8]) -> crate::Result<Identity> { |
| use std::io::Cursor; |
| |
| let (key, certs) = { |
| let mut pem = Cursor::new(buf); |
| let mut sk = Vec::<rustls::PrivateKey>::new(); |
| let mut certs = Vec::<rustls::Certificate>::new(); |
| |
| for item in std::iter::from_fn(|| rustls_pemfile::read_one(&mut pem).transpose()) { |
| match item.map_err(|_| { |
| crate::error::builder(TLSError::General(String::from( |
| "Invalid identity PEM file", |
| ))) |
| })? { |
| rustls_pemfile::Item::X509Certificate(cert) => { |
| certs.push(rustls::Certificate(cert)) |
| } |
| rustls_pemfile::Item::PKCS8Key(key) => sk.push(rustls::PrivateKey(key)), |
| rustls_pemfile::Item::RSAKey(key) => sk.push(rustls::PrivateKey(key)), |
| rustls_pemfile::Item::ECKey(key) => sk.push(rustls::PrivateKey(key)), |
| _ => { |
| return Err(crate::error::builder(TLSError::General(String::from( |
| "No valid certificate was found", |
| )))) |
| } |
| } |
| } |
| |
| if let (Some(sk), false) = (sk.pop(), certs.is_empty()) { |
| (sk, certs) |
| } else { |
| return Err(crate::error::builder(TLSError::General(String::from( |
| "private key or certificate not found", |
| )))); |
| } |
| }; |
| |
| Ok(Identity { |
| inner: ClientCert::Pem { key, certs }, |
| }) |
| } |
| |
| #[cfg(feature = "native-tls")] |
| pub(crate) fn add_to_native_tls( |
| self, |
| tls: &mut native_tls_crate::TlsConnectorBuilder, |
| ) -> crate::Result<()> { |
| match self.inner { |
| ClientCert::Pkcs12(id) | ClientCert::Pkcs8(id) => { |
| tls.identity(id); |
| Ok(()) |
| } |
| #[cfg(feature = "__rustls")] |
| ClientCert::Pem { .. } => Err(crate::error::builder("incompatible TLS identity type")), |
| } |
| } |
| |
| #[cfg(feature = "__rustls")] |
| pub(crate) fn add_to_rustls( |
| self, |
| config_builder: rustls::ConfigBuilder< |
| rustls::ClientConfig, |
| rustls::client::WantsTransparencyPolicyOrClientCert, |
| >, |
| ) -> crate::Result<rustls::ClientConfig> { |
| match self.inner { |
| ClientCert::Pem { key, certs } => config_builder |
| .with_single_cert(certs, key) |
| .map_err(crate::error::builder), |
| #[cfg(feature = "native-tls")] |
| ClientCert::Pkcs12(..) | ClientCert::Pkcs8(..) => { |
| Err(crate::error::builder("incompatible TLS identity type")) |
| } |
| } |
| } |
| } |
| |
| impl fmt::Debug for Certificate { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| f.debug_struct("Certificate").finish() |
| } |
| } |
| |
| impl fmt::Debug for Identity { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| f.debug_struct("Identity").finish() |
| } |
| } |
| |
| /// 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, |
| #[cfg(feature = "native-tls")] |
| BuiltNativeTls(native_tls_crate::TlsConnector), |
| #[cfg(feature = "__rustls")] |
| Rustls, |
| #[cfg(feature = "__rustls")] |
| BuiltRustls(rustls::ClientConfig), |
| #[cfg(any(feature = "native-tls", feature = "__rustls",))] |
| UnknownPreconfigured, |
| } |
| |
| impl fmt::Debug for TlsBackend { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| match self { |
| #[cfg(feature = "default-tls")] |
| TlsBackend::Default => write!(f, "Default"), |
| #[cfg(feature = "native-tls")] |
| TlsBackend::BuiltNativeTls(_) => write!(f, "BuiltNativeTls"), |
| #[cfg(feature = "__rustls")] |
| TlsBackend::Rustls => write!(f, "Rustls"), |
| #[cfg(feature = "__rustls")] |
| TlsBackend::BuiltRustls(_) => write!(f, "BuiltRustls"), |
| #[cfg(any(feature = "native-tls", feature = "__rustls",))] |
| TlsBackend::UnknownPreconfigured => write!(f, "UnknownPreconfigured"), |
| } |
| } |
| } |
| |
| impl Default for TlsBackend { |
| fn default() -> TlsBackend { |
| #[cfg(feature = "default-tls")] |
| { |
| TlsBackend::Default |
| } |
| |
| #[cfg(all(feature = "__rustls", not(feature = "default-tls")))] |
| { |
| TlsBackend::Rustls |
| } |
| } |
| } |
| |
| #[cfg(feature = "__rustls")] |
| pub(crate) struct NoVerifier; |
| |
| #[cfg(feature = "__rustls")] |
| impl ServerCertVerifier for NoVerifier { |
| fn verify_server_cert( |
| &self, |
| _end_entity: &rustls::Certificate, |
| _intermediates: &[rustls::Certificate], |
| _server_name: &ServerName, |
| _scts: &mut dyn Iterator<Item = &[u8]>, |
| _ocsp_response: &[u8], |
| _now: std::time::SystemTime, |
| ) -> Result<ServerCertVerified, TLSError> { |
| Ok(ServerCertVerified::assertion()) |
| } |
| |
| fn verify_tls12_signature( |
| &self, |
| _message: &[u8], |
| _cert: &rustls::Certificate, |
| _dss: &DigitallySignedStruct, |
| ) -> Result<HandshakeSignatureValid, TLSError> { |
| Ok(HandshakeSignatureValid::assertion()) |
| } |
| |
| fn verify_tls13_signature( |
| &self, |
| _message: &[u8], |
| _cert: &rustls::Certificate, |
| _dss: &DigitallySignedStruct, |
| ) -> Result<HandshakeSignatureValid, TLSError> { |
| Ok(HandshakeSignatureValid::assertion()) |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| |
| #[cfg(feature = "default-tls")] |
| #[test] |
| fn certificate_from_der_invalid() { |
| Certificate::from_der(b"not der").unwrap_err(); |
| } |
| |
| #[cfg(feature = "default-tls")] |
| #[test] |
| fn certificate_from_pem_invalid() { |
| Certificate::from_pem(b"not pem").unwrap_err(); |
| } |
| |
| #[cfg(feature = "native-tls")] |
| #[test] |
| fn identity_from_pkcs12_der_invalid() { |
| Identity::from_pkcs12_der(b"not der", "nope").unwrap_err(); |
| } |
| |
| #[cfg(feature = "native-tls")] |
| #[test] |
| fn identity_from_pkcs8_pem_invalid() { |
| Identity::from_pkcs8_pem(b"not pem", b"not key").unwrap_err(); |
| } |
| |
| #[cfg(feature = "__rustls")] |
| #[test] |
| fn identity_from_pem_invalid() { |
| Identity::from_pem(b"not pem").unwrap_err(); |
| } |
| |
| #[cfg(feature = "__rustls")] |
| #[test] |
| fn identity_from_pem_pkcs1_key() { |
| let pem = b"-----BEGIN CERTIFICATE-----\n\ |
| -----END CERTIFICATE-----\n\ |
| -----BEGIN RSA PRIVATE KEY-----\n\ |
| -----END RSA PRIVATE KEY-----\n"; |
| |
| Identity::from_pem(pem).unwrap(); |
| } |
| } |