1 use cfg_if::cfg_if;
2 use std::io::{Read, Write};
3 use std::ops::{Deref, DerefMut};
4 
5 use crate::dh::Dh;
6 use crate::error::ErrorStack;
7 #[cfg(any(ossl111, libressl340))]
8 use crate::ssl::SslVersion;
9 use crate::ssl::{
10     HandshakeError, Ssl, SslContext, SslContextBuilder, SslContextRef, SslMethod, SslMode,
11     SslOptions, SslRef, SslStream, SslVerifyMode,
12 };
13 use crate::version;
14 use std::net::IpAddr;
15 
16 const FFDHE_2048: &str = "
17 -----BEGIN DH PARAMETERS-----
18 MIIBCAKCAQEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz
19 +8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a
20 87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7
21 YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi
22 7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD
23 ssbzSibBsu/6iGtCOGEoXJf//////////wIBAg==
24 -----END DH PARAMETERS-----
25 ";
26 
27 #[allow(clippy::inconsistent_digit_grouping, clippy::unusual_byte_groupings)]
ctx(method: SslMethod) -> Result<SslContextBuilder, ErrorStack>28 fn ctx(method: SslMethod) -> Result<SslContextBuilder, ErrorStack> {
29     let mut ctx = SslContextBuilder::new(method)?;
30 
31     cfg_if! {
32         if #[cfg(not(boringssl))] {
33             let mut opts = SslOptions::ALL
34                 | SslOptions::NO_COMPRESSION
35                 | SslOptions::NO_SSLV2
36                 | SslOptions::NO_SSLV3
37                 | SslOptions::SINGLE_DH_USE
38                 | SslOptions::SINGLE_ECDH_USE;
39             opts &= !SslOptions::DONT_INSERT_EMPTY_FRAGMENTS;
40 
41             ctx.set_options(opts);
42         }
43     }
44 
45     let mut mode =
46         SslMode::AUTO_RETRY | SslMode::ACCEPT_MOVING_WRITE_BUFFER | SslMode::ENABLE_PARTIAL_WRITE;
47 
48     // This is quite a useful optimization for saving memory, but historically
49     // caused CVEs in OpenSSL pre-1.0.1h, according to
50     // https://bugs.python.org/issue25672
51     if version::number() >= 0x1_00_01_08_0 {
52         mode |= SslMode::RELEASE_BUFFERS;
53     }
54 
55     ctx.set_mode(mode);
56 
57     Ok(ctx)
58 }
59 
60 /// A type which wraps client-side streams in a TLS session.
61 ///
62 /// OpenSSL's default configuration is highly insecure. This connector manages the OpenSSL
63 /// structures, configuring cipher suites, session options, hostname verification, and more.
64 ///
65 /// OpenSSL's built-in hostname verification is used when linking against OpenSSL 1.0.2 or 1.1.0,
66 /// and a custom implementation is used when linking against OpenSSL 1.0.1.
67 #[derive(Clone, Debug)]
68 pub struct SslConnector(SslContext);
69 
70 impl SslConnector {
71     /// Creates a new builder for TLS connections.
72     ///
73     /// The default configuration is subject to change, and is currently derived from Python.
builder(method: SslMethod) -> Result<SslConnectorBuilder, ErrorStack>74     pub fn builder(method: SslMethod) -> Result<SslConnectorBuilder, ErrorStack> {
75         let mut ctx = ctx(method)?;
76         ctx.set_default_verify_paths()?;
77         ctx.set_cipher_list(
78             "DEFAULT:!aNULL:!eNULL:!MD5:!3DES:!DES:!RC4:!IDEA:!SEED:!aDSS:!SRP:!PSK",
79         )?;
80         setup_verify(&mut ctx);
81 
82         Ok(SslConnectorBuilder(ctx))
83     }
84 
85     /// Initiates a client-side TLS session on a stream.
86     ///
87     /// The domain is used for SNI and hostname verification.
connect<S>(&self, domain: &str, stream: S) -> Result<SslStream<S>, HandshakeError<S>> where S: Read + Write,88     pub fn connect<S>(&self, domain: &str, stream: S) -> Result<SslStream<S>, HandshakeError<S>>
89     where
90         S: Read + Write,
91     {
92         self.configure()?.connect(domain, stream)
93     }
94 
95     /// Returns a structure allowing for configuration of a single TLS session before connection.
configure(&self) -> Result<ConnectConfiguration, ErrorStack>96     pub fn configure(&self) -> Result<ConnectConfiguration, ErrorStack> {
97         Ssl::new(&self.0).map(|ssl| ConnectConfiguration {
98             ssl,
99             sni: true,
100             verify_hostname: true,
101         })
102     }
103 
104     /// Consumes the `SslConnector`, returning the inner raw `SslContext`.
into_context(self) -> SslContext105     pub fn into_context(self) -> SslContext {
106         self.0
107     }
108 
109     /// Returns a shared reference to the inner raw `SslContext`.
context(&self) -> &SslContextRef110     pub fn context(&self) -> &SslContextRef {
111         &self.0
112     }
113 }
114 
115 /// A builder for `SslConnector`s.
116 pub struct SslConnectorBuilder(SslContextBuilder);
117 
118 impl SslConnectorBuilder {
119     /// Consumes the builder, returning an `SslConnector`.
build(self) -> SslConnector120     pub fn build(self) -> SslConnector {
121         SslConnector(self.0.build())
122     }
123 }
124 
125 impl Deref for SslConnectorBuilder {
126     type Target = SslContextBuilder;
127 
deref(&self) -> &SslContextBuilder128     fn deref(&self) -> &SslContextBuilder {
129         &self.0
130     }
131 }
132 
133 impl DerefMut for SslConnectorBuilder {
deref_mut(&mut self) -> &mut SslContextBuilder134     fn deref_mut(&mut self) -> &mut SslContextBuilder {
135         &mut self.0
136     }
137 }
138 
139 /// A type which allows for configuration of a client-side TLS session before connection.
140 pub struct ConnectConfiguration {
141     ssl: Ssl,
142     sni: bool,
143     verify_hostname: bool,
144 }
145 
146 impl ConnectConfiguration {
147     /// A builder-style version of `set_use_server_name_indication`.
use_server_name_indication(mut self, use_sni: bool) -> ConnectConfiguration148     pub fn use_server_name_indication(mut self, use_sni: bool) -> ConnectConfiguration {
149         self.set_use_server_name_indication(use_sni);
150         self
151     }
152 
153     /// Configures the use of Server Name Indication (SNI) when connecting.
154     ///
155     /// Defaults to `true`.
set_use_server_name_indication(&mut self, use_sni: bool)156     pub fn set_use_server_name_indication(&mut self, use_sni: bool) {
157         self.sni = use_sni;
158     }
159 
160     /// A builder-style version of `set_verify_hostname`.
verify_hostname(mut self, verify_hostname: bool) -> ConnectConfiguration161     pub fn verify_hostname(mut self, verify_hostname: bool) -> ConnectConfiguration {
162         self.set_verify_hostname(verify_hostname);
163         self
164     }
165 
166     /// Configures the use of hostname verification when connecting.
167     ///
168     /// Defaults to `true`.
169     ///
170     /// # Warning
171     ///
172     /// You should think very carefully before you use this method. If hostname verification is not
173     /// used, *any* valid certificate for *any* site will be trusted for use from any other. This
174     /// introduces a significant vulnerability to man-in-the-middle attacks.
set_verify_hostname(&mut self, verify_hostname: bool)175     pub fn set_verify_hostname(&mut self, verify_hostname: bool) {
176         self.verify_hostname = verify_hostname;
177     }
178 
179     /// Returns an `Ssl` configured to connect to the provided domain.
180     ///
181     /// The domain is used for SNI (if it is not an IP address) and hostname verification if enabled.
into_ssl(mut self, domain: &str) -> Result<Ssl, ErrorStack>182     pub fn into_ssl(mut self, domain: &str) -> Result<Ssl, ErrorStack> {
183         if self.sni && domain.parse::<IpAddr>().is_err() {
184             self.ssl.set_hostname(domain)?;
185         }
186 
187         if self.verify_hostname {
188             setup_verify_hostname(&mut self.ssl, domain)?;
189         }
190 
191         Ok(self.ssl)
192     }
193 
194     /// Initiates a client-side TLS session on a stream.
195     ///
196     /// The domain is used for SNI and hostname verification if enabled.
connect<S>(self, domain: &str, stream: S) -> Result<SslStream<S>, HandshakeError<S>> where S: Read + Write,197     pub fn connect<S>(self, domain: &str, stream: S) -> Result<SslStream<S>, HandshakeError<S>>
198     where
199         S: Read + Write,
200     {
201         self.into_ssl(domain)?.connect(stream)
202     }
203 }
204 
205 impl Deref for ConnectConfiguration {
206     type Target = SslRef;
207 
deref(&self) -> &SslRef208     fn deref(&self) -> &SslRef {
209         &self.ssl
210     }
211 }
212 
213 impl DerefMut for ConnectConfiguration {
deref_mut(&mut self) -> &mut SslRef214     fn deref_mut(&mut self) -> &mut SslRef {
215         &mut self.ssl
216     }
217 }
218 
219 /// A type which wraps server-side streams in a TLS session.
220 ///
221 /// OpenSSL's default configuration is highly insecure. This connector manages the OpenSSL
222 /// structures, configuring cipher suites, session options, and more.
223 #[derive(Clone)]
224 pub struct SslAcceptor(SslContext);
225 
226 impl SslAcceptor {
227     /// Creates a new builder configured to connect to non-legacy clients. This should generally be
228     /// considered a reasonable default choice.
229     ///
230     /// This corresponds to the intermediate configuration of version 5 of Mozilla's server side TLS
231     /// recommendations. See its [documentation][docs] for more details on specifics.
232     ///
233     /// [docs]: https://wiki.mozilla.org/Security/Server_Side_TLS
mozilla_intermediate_v5(method: SslMethod) -> Result<SslAcceptorBuilder, ErrorStack>234     pub fn mozilla_intermediate_v5(method: SslMethod) -> Result<SslAcceptorBuilder, ErrorStack> {
235         let mut ctx = ctx(method)?;
236         ctx.set_options(SslOptions::NO_TLSV1 | SslOptions::NO_TLSV1_1);
237         let dh = Dh::params_from_pem(FFDHE_2048.as_bytes())?;
238         ctx.set_tmp_dh(&dh)?;
239         setup_curves(&mut ctx)?;
240         ctx.set_cipher_list(
241             "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:\
242              ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:\
243              DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384"
244         )?;
245         #[cfg(any(ossl111, libressl340))]
246         ctx.set_ciphersuites(
247             "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256",
248         )?;
249         Ok(SslAcceptorBuilder(ctx))
250     }
251 
252     /// Creates a new builder configured to connect to modern clients.
253     ///
254     /// This corresponds to the modern configuration of version 5 of Mozilla's server side TLS recommendations.
255     /// See its [documentation][docs] for more details on specifics.
256     ///
257     /// Requires OpenSSL 1.1.1 or LibreSSL 3.4.0 or newer.
258     ///
259     /// [docs]: https://wiki.mozilla.org/Security/Server_Side_TLS
260     #[cfg(any(ossl111, libressl340))]
mozilla_modern_v5(method: SslMethod) -> Result<SslAcceptorBuilder, ErrorStack>261     pub fn mozilla_modern_v5(method: SslMethod) -> Result<SslAcceptorBuilder, ErrorStack> {
262         let mut ctx = ctx(method)?;
263         ctx.set_min_proto_version(Some(SslVersion::TLS1_3))?;
264         ctx.set_ciphersuites(
265             "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256",
266         )?;
267         Ok(SslAcceptorBuilder(ctx))
268     }
269 
270     /// Creates a new builder configured to connect to non-legacy clients. This should generally be
271     /// considered a reasonable default choice.
272     ///
273     /// This corresponds to the intermediate configuration of version 4 of Mozilla's server side TLS
274     /// recommendations. See its [documentation][docs] for more details on specifics.
275     ///
276     /// [docs]: https://wiki.mozilla.org/Security/Server_Side_TLS
277     // FIXME remove in next major version
mozilla_intermediate(method: SslMethod) -> Result<SslAcceptorBuilder, ErrorStack>278     pub fn mozilla_intermediate(method: SslMethod) -> Result<SslAcceptorBuilder, ErrorStack> {
279         let mut ctx = ctx(method)?;
280         ctx.set_options(SslOptions::CIPHER_SERVER_PREFERENCE);
281         #[cfg(any(ossl111, libressl340))]
282         ctx.set_options(SslOptions::NO_TLSV1_3);
283         let dh = Dh::params_from_pem(FFDHE_2048.as_bytes())?;
284         ctx.set_tmp_dh(&dh)?;
285         setup_curves(&mut ctx)?;
286         ctx.set_cipher_list(
287             "ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:\
288              ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:\
289              DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:\
290              ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:\
291              ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:\
292              DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:\
293              EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:\
294              AES256-SHA:DES-CBC3-SHA:!DSS",
295         )?;
296         Ok(SslAcceptorBuilder(ctx))
297     }
298 
299     /// Creates a new builder configured to connect to modern clients.
300     ///
301     /// This corresponds to the modern configuration of version 4 of Mozilla's server side TLS recommendations.
302     /// See its [documentation][docs] for more details on specifics.
303     ///
304     /// [docs]: https://wiki.mozilla.org/Security/Server_Side_TLS
305     // FIXME remove in next major version
mozilla_modern(method: SslMethod) -> Result<SslAcceptorBuilder, ErrorStack>306     pub fn mozilla_modern(method: SslMethod) -> Result<SslAcceptorBuilder, ErrorStack> {
307         let mut ctx = ctx(method)?;
308         ctx.set_options(
309             SslOptions::CIPHER_SERVER_PREFERENCE | SslOptions::NO_TLSV1 | SslOptions::NO_TLSV1_1,
310         );
311         #[cfg(any(ossl111, libressl340))]
312         ctx.set_options(SslOptions::NO_TLSV1_3);
313         setup_curves(&mut ctx)?;
314         ctx.set_cipher_list(
315             "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:\
316              ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:\
317              ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256",
318         )?;
319         Ok(SslAcceptorBuilder(ctx))
320     }
321 
322     /// Initiates a server-side TLS session on a stream.
accept<S>(&self, stream: S) -> Result<SslStream<S>, HandshakeError<S>> where S: Read + Write,323     pub fn accept<S>(&self, stream: S) -> Result<SslStream<S>, HandshakeError<S>>
324     where
325         S: Read + Write,
326     {
327         let ssl = Ssl::new(&self.0)?;
328         ssl.accept(stream)
329     }
330 
331     /// Consumes the `SslAcceptor`, returning the inner raw `SslContext`.
into_context(self) -> SslContext332     pub fn into_context(self) -> SslContext {
333         self.0
334     }
335 
336     /// Returns a shared reference to the inner raw `SslContext`.
context(&self) -> &SslContextRef337     pub fn context(&self) -> &SslContextRef {
338         &self.0
339     }
340 }
341 
342 /// A builder for `SslAcceptor`s.
343 pub struct SslAcceptorBuilder(SslContextBuilder);
344 
345 impl SslAcceptorBuilder {
346     /// Consumes the builder, returning a `SslAcceptor`.
build(self) -> SslAcceptor347     pub fn build(self) -> SslAcceptor {
348         SslAcceptor(self.0.build())
349     }
350 }
351 
352 impl Deref for SslAcceptorBuilder {
353     type Target = SslContextBuilder;
354 
deref(&self) -> &SslContextBuilder355     fn deref(&self) -> &SslContextBuilder {
356         &self.0
357     }
358 }
359 
360 impl DerefMut for SslAcceptorBuilder {
deref_mut(&mut self) -> &mut SslContextBuilder361     fn deref_mut(&mut self) -> &mut SslContextBuilder {
362         &mut self.0
363     }
364 }
365 
366 cfg_if! {
367     if #[cfg(ossl110)] {
368         #[allow(clippy::unnecessary_wraps)]
369         fn setup_curves(_: &mut SslContextBuilder) -> Result<(), ErrorStack> {
370             Ok(())
371         }
372     } else if #[cfg(any(ossl102, libressl))] {
373         fn setup_curves(ctx: &mut SslContextBuilder) -> Result<(), ErrorStack> {
374             ctx.set_ecdh_auto(true)
375         }
376     } else {
377         fn setup_curves(ctx: &mut SslContextBuilder) -> Result<(), ErrorStack> {
378             use crate::ec::EcKey;
379             use crate::nid::Nid;
380 
381             let curve = EcKey::from_curve_name(Nid::X9_62_PRIME256V1)?;
382             ctx.set_tmp_ecdh(&curve)
383         }
384     }
385 }
386 
387 cfg_if! {
388     if #[cfg(any(ossl102, libressl261))] {
389         fn setup_verify(ctx: &mut SslContextBuilder) {
390             ctx.set_verify(SslVerifyMode::PEER);
391         }
392 
393         fn setup_verify_hostname(ssl: &mut SslRef, domain: &str) -> Result<(), ErrorStack> {
394             use crate::x509::verify::X509CheckFlags;
395 
396             let param = ssl.param_mut();
397             param.set_hostflags(X509CheckFlags::NO_PARTIAL_WILDCARDS);
398             match domain.parse() {
399                 Ok(ip) => param.set_ip(ip),
400                 Err(_) => param.set_host(domain),
401             }
402         }
403     } else {
404         fn setup_verify(ctx: &mut SslContextBuilder) {
405             ctx.set_verify_callback(SslVerifyMode::PEER, verify::verify_callback);
406         }
407 
408         fn setup_verify_hostname(ssl: &mut Ssl, domain: &str) -> Result<(), ErrorStack> {
409             let domain = domain.to_string();
410             let hostname_idx = verify::try_get_hostname_idx()?;
411             ssl.set_ex_data(*hostname_idx, domain);
412             Ok(())
413         }
414 
415         mod verify {
416             use std::net::IpAddr;
417             use std::str;
418             use once_cell::sync::OnceCell;
419 
420             use crate::error::ErrorStack;
421             use crate::ex_data::Index;
422             use crate::nid::Nid;
423             use crate::ssl::Ssl;
424             use crate::stack::Stack;
425             use crate::x509::{
426                 GeneralName, X509NameRef, X509Ref, X509StoreContext, X509StoreContextRef,
427                 X509VerifyResult,
428             };
429 
430             static HOSTNAME_IDX: OnceCell<Index<Ssl, String>> = OnceCell::new();
431 
432             pub fn try_get_hostname_idx() -> Result<&'static Index<Ssl, String>, ErrorStack> {
433                 HOSTNAME_IDX.get_or_try_init(Ssl::new_ex_index)
434             }
435 
436             pub fn verify_callback(preverify_ok: bool, x509_ctx: &mut X509StoreContextRef) -> bool {
437                 if !preverify_ok || x509_ctx.error_depth() != 0 {
438                     return preverify_ok;
439                 }
440 
441                 let hostname_idx =
442                     try_get_hostname_idx().expect("failed to initialize hostname index");
443                 let ok = match (
444                     x509_ctx.current_cert(),
445                     X509StoreContext::ssl_idx()
446                         .ok()
447                         .and_then(|idx| x509_ctx.ex_data(idx))
448                         .and_then(|ssl| ssl.ex_data(*hostname_idx)),
449                 ) {
450                     (Some(x509), Some(domain)) => verify_hostname(domain, &x509),
451                     _ => true,
452                 };
453 
454                 if !ok {
455                     x509_ctx.set_error(X509VerifyResult::APPLICATION_VERIFICATION);
456                 }
457 
458                 ok
459             }
460 
461             fn verify_hostname(domain: &str, cert: &X509Ref) -> bool {
462                 match cert.subject_alt_names() {
463                     Some(names) => verify_subject_alt_names(domain, names),
464                     None => verify_subject_name(domain, &cert.subject_name()),
465                 }
466             }
467 
468             fn verify_subject_alt_names(domain: &str, names: Stack<GeneralName>) -> bool {
469                 let ip = domain.parse();
470 
471                 for name in &names {
472                     match ip {
473                         Ok(ip) => {
474                             if let Some(actual) = name.ipaddress() {
475                                 if matches_ip(&ip, actual) {
476                                     return true;
477                                 }
478                             }
479                         }
480                         Err(_) => {
481                             if let Some(pattern) = name.dnsname() {
482                                 if matches_dns(pattern, domain) {
483                                     return true;
484                                 }
485                             }
486                         }
487                     }
488                 }
489 
490                 false
491             }
492 
493             fn verify_subject_name(domain: &str, subject_name: &X509NameRef) -> bool {
494                 match subject_name.entries_by_nid(Nid::COMMONNAME).next() {
495                     Some(pattern) => {
496                         let pattern = match str::from_utf8(pattern.data().as_slice()) {
497                             Ok(pattern) => pattern,
498                             Err(_) => return false,
499                         };
500 
501                         // Unlike SANs, IP addresses in the subject name don't have a
502                         // different encoding.
503                         match domain.parse::<IpAddr>() {
504                             Ok(ip) => pattern
505                                 .parse::<IpAddr>()
506                                 .ok()
507                                 .map_or(false, |pattern| pattern == ip),
508                             Err(_) => matches_dns(pattern, domain),
509                         }
510                     }
511                     None => false,
512                 }
513             }
514 
515             fn matches_dns(mut pattern: &str, mut hostname: &str) -> bool {
516                 // first strip trailing . off of pattern and hostname to normalize
517                 if pattern.ends_with('.') {
518                     pattern = &pattern[..pattern.len() - 1];
519                 }
520                 if hostname.ends_with('.') {
521                     hostname = &hostname[..hostname.len() - 1];
522                 }
523 
524                 matches_wildcard(pattern, hostname).unwrap_or_else(|| pattern.eq_ignore_ascii_case(hostname))
525             }
526 
527             fn matches_wildcard(pattern: &str, hostname: &str) -> Option<bool> {
528                 let wildcard_location = match pattern.find('*') {
529                     Some(l) => l,
530                     None => return None,
531                 };
532 
533                 let mut dot_idxs = pattern.match_indices('.').map(|(l, _)| l);
534                 let wildcard_end = match dot_idxs.next() {
535                     Some(l) => l,
536                     None => return None,
537                 };
538 
539                 // Never match wildcards if the pattern has less than 2 '.'s (no *.com)
540                 //
541                 // This is a bit dubious, as it doesn't disallow other TLDs like *.co.uk.
542                 // Chrome has a black- and white-list for this, but Firefox (via NSS) does
543                 // the same thing we do here.
544                 //
545                 // The Public Suffix (https://www.publicsuffix.org/) list could
546                 // potentially be used here, but it's both huge and updated frequently
547                 // enough that management would be a PITA.
548                 if dot_idxs.next().is_none() {
549                     return None;
550                 }
551 
552                 // Wildcards can only be in the first component, and must be the entire first label
553                 if wildcard_location != 0 || wildcard_end != wildcard_location + 1 {
554                     return None;
555                 }
556 
557                 let hostname_label_end = match hostname.find('.') {
558                     Some(l) => l,
559                     None => return None,
560                 };
561 
562                 let pattern_after_wildcard = &pattern[wildcard_end..];
563                 let hostname_after_wildcard = &hostname[hostname_label_end..];
564 
565                 Some(pattern_after_wildcard.eq_ignore_ascii_case(hostname_after_wildcard))
566             }
567 
568             fn matches_ip(expected: &IpAddr, actual: &[u8]) -> bool {
569                 match *expected {
570                     IpAddr::V4(ref addr) => actual == addr.octets(),
571                     IpAddr::V6(ref addr) => actual == addr.octets(),
572                 }
573             }
574 
575             #[test]
576             fn test_dns_match() {
577                 use crate::ssl::connector::verify::matches_dns;
578                 assert!(matches_dns("website.tld", "website.tld")); // A name should match itself.
579                 assert!(matches_dns("website.tld", "wEbSiTe.tLd")); // DNS name matching ignores case of hostname.
580                 assert!(matches_dns("wEbSiTe.TlD", "website.tld")); // DNS name matching ignores case of subject.
581 
582                 assert!(matches_dns("xn--bcher-kva.tld", "xn--bcher-kva.tld")); // Likewise, nothing special to punycode names.
583                 assert!(matches_dns("xn--bcher-kva.tld", "xn--BcHer-Kva.tLd")); // And punycode must be compared similarly case-insensitively.
584 
585                 assert!(matches_dns("*.example.com", "subdomain.example.com")); // Wildcard matching works.
586                 assert!(matches_dns("*.eXaMpLe.cOm", "subdomain.example.com")); // Wildcard matching ignores case of subject.
587                 assert!(matches_dns("*.example.com", "sUbDoMaIn.eXaMpLe.cOm")); // Wildcard matching ignores case of hostname.
588 
589                 assert!(!matches_dns("prefix*.example.com", "p.example.com")); // Prefix longer than the label works and does not match.
590                 assert!(!matches_dns("*suffix.example.com", "s.example.com")); // Suffix longer than the label works and does not match.
591 
592                 assert!(!matches_dns("prefix*.example.com", "prefix.example.com")); // Partial wildcards do not work.
593                 assert!(!matches_dns("*suffix.example.com", "suffix.example.com")); // Partial wildcards do not work.
594 
595                 assert!(!matches_dns("prefix*.example.com", "prefixdomain.example.com")); // Partial wildcards do not work.
596                 assert!(!matches_dns("*suffix.example.com", "domainsuffix.example.com")); // Partial wildcards do not work.
597 
598                 assert!(!matches_dns("xn--*.example.com", "subdomain.example.com")); // Punycode domains with wildcard parts do not match.
599                 assert!(!matches_dns("xN--*.example.com", "subdomain.example.com")); // And we can't bypass a punycode test with weird casing.
600                 assert!(!matches_dns("Xn--*.example.com", "subdomain.example.com")); // And we can't bypass a punycode test with weird casing.
601                 assert!(!matches_dns("XN--*.example.com", "subdomain.example.com")); // And we can't bypass a punycode test with weird casing.
602             }
603         }
604     }
605 }
606