]> git.proxmox.com Git - cargo.git/blobdiff - vendor/openssl/src/ssl/connector.rs
New upstream version 0.52.0
[cargo.git] / vendor / openssl / src / ssl / connector.rs
index d2a7ea679e385916dcf43bc36e6394b8f5a1ac68..1d2d2c643cf0a29859678d130d2ed0f40a4d7cad 100644 (file)
@@ -1,13 +1,14 @@
+use cfg_if::cfg_if;
 use std::io::{Read, Write};
 use std::ops::{Deref, DerefMut};
 
-use dh::Dh;
-use error::ErrorStack;
-use ssl::{
+use crate::dh::Dh;
+use crate::error::ErrorStack;
+use crate::ssl::{
     HandshakeError, Ssl, SslContext, SslContextBuilder, SslContextRef, SslMethod, SslMode,
     SslOptions, SslRef, SslStream, SslVerifyMode,
 };
-use version;
+use crate::version;
 
 const FFDHE_2048: &str = "
 -----BEGIN DH PARAMETERS-----
@@ -20,7 +21,7 @@ ssbzSibBsu/6iGtCOGEoXJf//////////wIBAg==
 -----END DH PARAMETERS-----
 ";
 
-#[allow(clippy::inconsistent_digit_grouping)]
+#[allow(clippy::inconsistent_digit_grouping, clippy::unusual_byte_groupings)]
 fn ctx(method: SslMethod) -> Result<SslContextBuilder, ErrorStack> {
     let mut ctx = SslContextBuilder::new(method)?;
 
@@ -168,13 +169,10 @@ impl ConnectConfiguration {
         self.verify_hostname = verify_hostname;
     }
 
-    /// Initiates a client-side TLS session on a stream.
+    /// Returns an `Ssl` configured to connect to the provided domain.
     ///
     /// The domain is used for SNI and hostname verification if enabled.
-    pub fn connect<S>(mut self, domain: &str, stream: S) -> Result<SslStream<S>, HandshakeError<S>>
-    where
-        S: Read + Write,
-    {
+    pub fn into_ssl(mut self, domain: &str) -> Result<Ssl, ErrorStack> {
         if self.sni {
             self.ssl.set_hostname(domain)?;
         }
@@ -183,7 +181,17 @@ impl ConnectConfiguration {
             setup_verify_hostname(&mut self.ssl, domain)?;
         }
 
-        self.ssl.connect(stream)
+        Ok(self.ssl)
+    }
+
+    /// Initiates a client-side TLS session on a stream.
+    ///
+    /// The domain is used for SNI and hostname verification if enabled.
+    pub fn connect<S>(self, domain: &str, stream: S) -> Result<SslStream<S>, HandshakeError<S>>
+    where
+        S: Read + Write,
+    {
+        self.into_ssl(domain)?.connect(stream)
     }
 }
 
@@ -350,6 +358,7 @@ impl DerefMut for SslAcceptorBuilder {
 
 cfg_if! {
     if #[cfg(ossl110)] {
+        #[allow(clippy::unnecessary_wraps)]
         fn setup_curves(_: &mut SslContextBuilder) -> Result<(), ErrorStack> {
             Ok(())
         }
@@ -359,8 +368,8 @@ cfg_if! {
         }
     } else {
         fn setup_curves(ctx: &mut SslContextBuilder) -> Result<(), ErrorStack> {
-            use ec::EcKey;
-            use nid::Nid;
+            use crate::ec::EcKey;
+            use crate::nid::Nid;
 
             let curve = EcKey::from_curve_name(Nid::X9_62_PRIME256V1)?;
             ctx.set_tmp_ecdh(&curve)
@@ -375,7 +384,7 @@ cfg_if! {
         }
 
         fn setup_verify_hostname(ssl: &mut SslRef, domain: &str) -> Result<(), ErrorStack> {
-            use x509::verify::X509CheckFlags;
+            use crate::x509::verify::X509CheckFlags;
 
             let param = ssl.param_mut();
             param.set_hostflags(X509CheckFlags::NO_PARTIAL_WILDCARDS);
@@ -391,25 +400,30 @@ cfg_if! {
 
         fn setup_verify_hostname(ssl: &mut Ssl, domain: &str) -> Result<(), ErrorStack> {
             let domain = domain.to_string();
-            ssl.set_ex_data(*verify::HOSTNAME_IDX, domain);
+            let hostname_idx = verify::try_get_hostname_idx()?;
+            ssl.set_ex_data(*hostname_idx, domain);
             Ok(())
         }
 
         mod verify {
             use std::net::IpAddr;
             use std::str;
-
-            use ex_data::Index;
-            use nid::Nid;
-            use ssl::Ssl;
-            use stack::Stack;
-            use x509::{
+            use once_cell::sync::OnceCell;
+
+            use crate::error::ErrorStack;
+            use crate::ex_data::Index;
+            use crate::nid::Nid;
+            use crate::ssl::Ssl;
+            use crate::stack::Stack;
+            use crate::x509::{
                 GeneralName, X509NameRef, X509Ref, X509StoreContext, X509StoreContextRef,
                 X509VerifyResult,
             };
 
-            lazy_static! {
-                pub static ref HOSTNAME_IDX: Index<Ssl, String> = Ssl::new_ex_index().unwrap();
+            static HOSTNAME_IDX: OnceCell<Index<Ssl, String>> = OnceCell::new();
+
+            pub fn try_get_hostname_idx() -> Result<&'static Index<Ssl, String>, ErrorStack> {
+                HOSTNAME_IDX.get_or_try_init(Ssl::new_ex_index)
             }
 
             pub fn verify_callback(preverify_ok: bool, x509_ctx: &mut X509StoreContextRef) -> bool {
@@ -417,12 +431,14 @@ cfg_if! {
                     return preverify_ok;
                 }
 
+                let hostname_idx =
+                    try_get_hostname_idx().expect("failed to initialize hostname index");
                 let ok = match (
                     x509_ctx.current_cert(),
                     X509StoreContext::ssl_idx()
                         .ok()
                         .and_then(|idx| x509_ctx.ex_data(idx))
-                        .and_then(|ssl| ssl.ex_data(*HOSTNAME_IDX)),
+                        .and_then(|ssl| ssl.ex_data(*hostname_idx)),
                 ) {
                     (Some(x509), Some(domain)) => verify_hostname(domain, &x509),
                     _ => true,
@@ -498,15 +514,10 @@ cfg_if! {
                     hostname = &hostname[..hostname.len() - 1];
                 }
 
-                matches_wildcard(pattern, hostname).unwrap_or_else(|| pattern == hostname)
+                matches_wildcard(pattern, hostname).unwrap_or_else(|| pattern.eq_ignore_ascii_case(hostname))
             }
 
             fn matches_wildcard(pattern: &str, hostname: &str) -> Option<bool> {
-                // internationalized domains can't involved in wildcards
-                if pattern.starts_with("xn--") {
-                    return None;
-                }
-
                 let wildcard_location = match pattern.find('*') {
                     Some(l) => l,
                     None => return None,
@@ -531,8 +542,8 @@ cfg_if! {
                     return None;
                 }
 
-                // Wildcards can only be in the first component
-                if wildcard_location > wildcard_end {
+                // Wildcards can only be in the first component, and must be the entire first label
+                if wildcard_location != 0 || wildcard_end != wildcard_location + 1 {
                     return None;
                 }
 
@@ -541,27 +552,10 @@ cfg_if! {
                     None => return None,
                 };
 
-                // check that the non-wildcard parts are identical
-                if pattern[wildcard_end..] != hostname[hostname_label_end..] {
-                    return Some(false);
-                }
-
-                let wildcard_prefix = &pattern[..wildcard_location];
-                let wildcard_suffix = &pattern[wildcard_location + 1..wildcard_end];
-
-                let hostname_label = &hostname[..hostname_label_end];
-
-                // check the prefix of the first label
-                if !hostname_label.starts_with(wildcard_prefix) {
-                    return Some(false);
-                }
-
-                // and the suffix
-                if !hostname_label[wildcard_prefix.len()..].ends_with(wildcard_suffix) {
-                    return Some(false);
-                }
+                let pattern_after_wildcard = &pattern[wildcard_end..];
+                let hostname_after_wildcard = &hostname[hostname_label_end..];
 
-                Some(true)
+                Some(pattern_after_wildcard.eq_ignore_ascii_case(hostname_after_wildcard))
             }
 
             fn matches_ip(expected: &IpAddr, actual: &[u8]) -> bool {
@@ -570,6 +564,35 @@ cfg_if! {
                     IpAddr::V6(ref addr) => actual == addr.octets(),
                 }
             }
+
+            #[test]
+            fn test_dns_match() {
+                use crate::ssl::connector::verify::matches_dns;
+                assert!(matches_dns("website.tld", "website.tld")); // A name should match itself.
+                assert!(matches_dns("website.tld", "wEbSiTe.tLd")); // DNS name matching ignores case of hostname.
+                assert!(matches_dns("wEbSiTe.TlD", "website.tld")); // DNS name matching ignores case of subject.
+
+                assert!(matches_dns("xn--bcher-kva.tld", "xn--bcher-kva.tld")); // Likewise, nothing special to punycode names.
+                assert!(matches_dns("xn--bcher-kva.tld", "xn--BcHer-Kva.tLd")); // And punycode must be compared similarly case-insensitively.
+
+                assert!(matches_dns("*.example.com", "subdomain.example.com")); // Wildcard matching works.
+                assert!(matches_dns("*.eXaMpLe.cOm", "subdomain.example.com")); // Wildcard matching ignores case of subject.
+                assert!(matches_dns("*.example.com", "sUbDoMaIn.eXaMpLe.cOm")); // Wildcard matching ignores case of hostname.
+
+                assert!(!matches_dns("prefix*.example.com", "p.example.com")); // Prefix longer than the label works and does not match.
+                assert!(!matches_dns("*suffix.example.com", "s.example.com")); // Suffix longer than the label works and does not match.
+
+                assert!(!matches_dns("prefix*.example.com", "prefix.example.com")); // Partial wildcards do not work.
+                assert!(!matches_dns("*suffix.example.com", "suffix.example.com")); // Partial wildcards do not work.
+
+                assert!(!matches_dns("prefix*.example.com", "prefixdomain.example.com")); // Partial wildcards do not work.
+                assert!(!matches_dns("*suffix.example.com", "domainsuffix.example.com")); // Partial wildcards do not work.
+
+                assert!(!matches_dns("xn--*.example.com", "subdomain.example.com")); // Punycode domains with wildcard parts do not match.
+                assert!(!matches_dns("xN--*.example.com", "subdomain.example.com")); // And we can't bypass a punycode test with weird casing.
+                assert!(!matches_dns("Xn--*.example.com", "subdomain.example.com")); // And we can't bypass a punycode test with weird casing.
+                assert!(!matches_dns("XN--*.example.com", "subdomain.example.com")); // And we can't bypass a punycode test with weird casing.
+            }
         }
     }
 }