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