1 // Copyright 2015-2020 Brian Smith.
2 //
3 // Permission to use, copy, modify, and/or distribute this software for any
4 // purpose with or without fee is hereby granted, provided that the above
5 // copyright notice and this permission notice appear in all copies.
6 //
7 // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
8 // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9 // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
10 // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11 // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12 // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
13 // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14 
15 #[cfg(feature = "alloc")]
16 use alloc::string::String;
17 
18 /// A DNS Name suitable for use in the TLS Server Name Indication (SNI)
19 /// extension and/or for use as the reference hostname for which to verify a
20 /// certificate.
21 ///
22 /// A `DnsName` is guaranteed to be syntactically valid. The validity rules are
23 /// specified in [RFC 5280 Section 7.2], except that underscores are also
24 /// allowed.
25 ///
26 /// `DnsName` stores a copy of the input it was constructed from in a `String`
27 /// and so it is only available when the `std` default feature is enabled.
28 ///
29 /// `Eq`, `PartialEq`, etc. are not implemented because name comparison
30 /// frequently should be done case-insensitively and/or with other caveats that
31 /// depend on the specific circumstances in which the comparison is done.
32 ///
33 /// [RFC 5280 Section 7.2]: https://tools.ietf.org/html/rfc5280#section-7.2
34 ///
35 /// Requires the `alloc` feature.
36 #[cfg(feature = "alloc")]
37 #[derive(Clone, Debug, Eq, PartialEq, Hash)]
38 pub struct DnsName(String);
39 
40 /// Requires the `alloc` feature.
41 #[cfg(feature = "alloc")]
42 impl DnsName {
43     /// Returns a `DnsNameRef` that refers to this `DnsName`.
as_ref(&self) -> DnsNameRef44     pub fn as_ref(&self) -> DnsNameRef {
45         DnsNameRef(self.0.as_bytes())
46     }
47 }
48 
49 /// Requires the `alloc` feature.
50 #[cfg(feature = "alloc")]
51 impl AsRef<str> for DnsName {
as_ref(&self) -> &str52     fn as_ref(&self) -> &str {
53         self.0.as_ref()
54     }
55 }
56 
57 /// Requires the `alloc` feature.
58 // Deprecated
59 #[cfg(feature = "alloc")]
60 impl From<DnsNameRef<'_>> for DnsName {
from(dns_name: DnsNameRef) -> Self61     fn from(dns_name: DnsNameRef) -> Self {
62         dns_name.to_owned()
63     }
64 }
65 
66 /// A reference to a DNS Name suitable for use in the TLS Server Name Indication
67 /// (SNI) extension and/or for use as the reference hostname for which to verify
68 /// a certificate.
69 ///
70 /// A `DnsNameRef` is guaranteed to be syntactically valid. The validity rules
71 /// are specified in [RFC 5280 Section 7.2], except that underscores are also
72 /// allowed.
73 ///
74 /// `Eq`, `PartialEq`, etc. are not implemented because name comparison
75 /// frequently should be done case-insensitively and/or with other caveats that
76 /// depend on the specific circumstances in which the comparison is done.
77 ///
78 /// [RFC 5280 Section 7.2]: https://tools.ietf.org/html/rfc5280#section-7.2
79 #[derive(Clone, Copy)]
80 pub struct DnsNameRef<'a>(&'a [u8]);
81 
82 impl AsRef<[u8]> for DnsNameRef<'_> {
83     #[inline]
as_ref(&self) -> &[u8]84     fn as_ref(&self) -> &[u8] {
85         self.0
86     }
87 }
88 
89 /// An error indicating that a `DnsNameRef` could not built because the input
90 /// is not a syntactically-valid DNS Name.
91 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
92 pub struct InvalidDnsNameError;
93 
94 impl core::fmt::Display for InvalidDnsNameError {
fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result95     fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
96         write!(f, "{:?}", self)
97     }
98 }
99 
100 /// Requires the `std` feature.
101 #[cfg(feature = "std")]
102 impl ::std::error::Error for InvalidDnsNameError {}
103 
104 impl<'a> DnsNameRef<'a> {
105     /// Constructs a `DnsNameRef` from the given input if the input is a
106     /// syntactically-valid DNS name.
try_from_ascii(dns_name: &'a [u8]) -> Result<Self, InvalidDnsNameError>107     pub fn try_from_ascii(dns_name: &'a [u8]) -> Result<Self, InvalidDnsNameError> {
108         if !is_valid_reference_dns_id(untrusted::Input::from(dns_name)) {
109             return Err(InvalidDnsNameError);
110         }
111 
112         Ok(Self(dns_name))
113     }
114 
115     /// Constructs a `DnsNameRef` from the given input if the input is a
116     /// syntactically-valid DNS name.
try_from_ascii_str(dns_name: &'a str) -> Result<Self, InvalidDnsNameError>117     pub fn try_from_ascii_str(dns_name: &'a str) -> Result<Self, InvalidDnsNameError> {
118         Self::try_from_ascii(dns_name.as_bytes())
119     }
120 
121     /// Constructs a `DnsName` from this `DnsNameRef`
122     ///
123     /// Requires the `alloc` feature.
124     #[cfg(feature = "alloc")]
to_owned(&self) -> DnsName125     pub fn to_owned(&self) -> DnsName {
126         // DnsNameRef is already guaranteed to be valid ASCII, which is a
127         // subset of UTF-8.
128         let s: &str = self.clone().into();
129         DnsName(s.to_ascii_lowercase())
130     }
131 }
132 
133 /// Requires the `alloc` feature.
134 #[cfg(feature = "alloc")]
135 impl core::fmt::Debug for DnsNameRef<'_> {
fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error>136     fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> {
137         let lowercase = self.clone().to_owned();
138         f.debug_tuple("DnsNameRef").field(&lowercase.0).finish()
139     }
140 }
141 
142 impl<'a> From<DnsNameRef<'a>> for &'a str {
from(DnsNameRef(d): DnsNameRef<'a>) -> Self143     fn from(DnsNameRef(d): DnsNameRef<'a>) -> Self {
144         // The unwrap won't fail because DnsNameRefs are guaranteed to be ASCII
145         // and ASCII is a subset of UTF-8.
146         core::str::from_utf8(d).unwrap()
147     }
148 }
149 
presented_id_matches_reference_id( presented_dns_id: untrusted::Input, reference_dns_id: untrusted::Input, ) -> Option<bool>150 pub(super) fn presented_id_matches_reference_id(
151     presented_dns_id: untrusted::Input,
152     reference_dns_id: untrusted::Input,
153 ) -> Option<bool> {
154     presented_id_matches_reference_id_internal(
155         presented_dns_id,
156         IdRole::Reference,
157         reference_dns_id,
158     )
159 }
160 
presented_id_matches_constraint( presented_dns_id: untrusted::Input, reference_dns_id: untrusted::Input, ) -> Option<bool>161 pub(super) fn presented_id_matches_constraint(
162     presented_dns_id: untrusted::Input,
163     reference_dns_id: untrusted::Input,
164 ) -> Option<bool> {
165     presented_id_matches_reference_id_internal(
166         presented_dns_id,
167         IdRole::NameConstraint,
168         reference_dns_id,
169     )
170 }
171 
172 // We do not distinguish between a syntactically-invalid presented_dns_id and
173 // one that is syntactically valid but does not match reference_dns_id; in both
174 // cases, the result is false.
175 //
176 // We assume that both presented_dns_id and reference_dns_id are encoded in
177 // such a way that US-ASCII (7-bit) characters are encoded in one byte and no
178 // encoding of a non-US-ASCII character contains a code point in the range
179 // 0-127. For example, UTF-8 is OK but UTF-16 is not.
180 //
181 // RFC6125 says that a wildcard label may be of the form <x>*<y>.<DNSID>, where
182 // <x> and/or <y> may be empty. However, NSS requires <y> to be empty, and we
183 // follow NSS's stricter policy by accepting wildcards only of the form
184 // <x>*.<DNSID>, where <x> may be empty.
185 //
186 // An relative presented DNS ID matches both an absolute reference ID and a
187 // relative reference ID. Absolute presented DNS IDs are not supported:
188 //
189 //      Presented ID   Reference ID  Result
190 //      -------------------------------------
191 //      example.com    example.com   Match
192 //      example.com.   example.com   Mismatch
193 //      example.com    example.com.  Match
194 //      example.com.   example.com.  Mismatch
195 //
196 // There are more subtleties documented inline in the code.
197 //
198 // Name constraints ///////////////////////////////////////////////////////////
199 //
200 // This is all RFC 5280 has to say about dNSName constraints:
201 //
202 //     DNS name restrictions are expressed as host.example.com.  Any DNS
203 //     name that can be constructed by simply adding zero or more labels to
204 //     the left-hand side of the name satisfies the name constraint.  For
205 //     example, www.host.example.com would satisfy the constraint but
206 //     host1.example.com would not.
207 //
208 // This lack of specificity has lead to a lot of uncertainty regarding
209 // subdomain matching. In particular, the following questions have been
210 // raised and answered:
211 //
212 //     Q: Does a presented identifier equal (case insensitive) to the name
213 //        constraint match the constraint? For example, does the presented
214 //        ID "host.example.com" match a "host.example.com" constraint?
215 //     A: Yes. RFC5280 says "by simply adding zero or more labels" and this
216 //        is the case of adding zero labels.
217 //
218 //     Q: When the name constraint does not start with ".", do subdomain
219 //        presented identifiers match it? For example, does the presented
220 //        ID "www.host.example.com" match a "host.example.com" constraint?
221 //     A: Yes. RFC5280 says "by simply adding zero or more labels" and this
222 //        is the case of adding more than zero labels. The example is the
223 //        one from RFC 5280.
224 //
225 //     Q: When the name constraint does not start with ".", does a
226 //        non-subdomain prefix match it? For example, does "bigfoo.bar.com"
227 //        match "foo.bar.com"? [4]
228 //     A: No. We interpret RFC 5280's language of "adding zero or more labels"
229 //        to mean that whole labels must be prefixed.
230 //
231 //     (Note that the above three scenarios are the same as the RFC 6265
232 //     domain matching rules [0].)
233 //
234 //     Q: Is a name constraint that starts with "." valid, and if so, what
235 //        semantics does it have? For example, does a presented ID of
236 //        "www.example.com" match a constraint of ".example.com"? Does a
237 //        presented ID of "example.com" match a constraint of ".example.com"?
238 //     A: This implementation, NSS[1], and SChannel[2] all support a
239 //        leading ".", but OpenSSL[3] does not yet. Amongst the
240 //        implementations that support it, a leading "." is legal and means
241 //        the same thing as when the "." is omitted, EXCEPT that a
242 //        presented identifier equal (case insensitive) to the name
243 //        constraint is not matched; i.e. presented dNSName identifiers
244 //        must be subdomains. Some CAs in Mozilla's CA program (e.g. HARICA)
245 //        have name constraints with the leading "." in their root
246 //        certificates. The name constraints imposed on DCISS by Mozilla also
247 //        have the it, so supporting this is a requirement for backward
248 //        compatibility, even if it is not yet standardized. So, for example, a
249 //        presented ID of "www.example.com" matches a constraint of
250 //        ".example.com" but a presented ID of "example.com" does not.
251 //
252 //     Q: Is there a way to prevent subdomain matches?
253 //     A: Yes.
254 //
255 //        Some people have proposed that dNSName constraints that do not
256 //        start with a "." should be restricted to exact (case insensitive)
257 //        matches. However, such a change of semantics from what RFC5280
258 //        specifies would be a non-backward-compatible change in the case of
259 //        permittedSubtrees constraints, and it would be a security issue for
260 //        excludedSubtrees constraints.
261 //
262 //        However, it can be done with a combination of permittedSubtrees and
263 //        excludedSubtrees, e.g. "example.com" in permittedSubtrees and
264 //        ".example.com" in excludedSubtrees.
265 //
266 //     Q: Are name constraints allowed to be specified as absolute names?
267 //        For example, does a presented ID of "example.com" match a name
268 //        constraint of "example.com." and vice versa.
269 //     A: Absolute names are not supported as presented IDs or name
270 //        constraints. Only reference IDs may be absolute.
271 //
272 //     Q: Is "" a valid dNSName constraint? If so, what does it mean?
273 //     A: Yes. Any valid presented dNSName can be formed "by simply adding zero
274 //        or more labels to the left-hand side" of "". In particular, an
275 //        excludedSubtrees dNSName constraint of "" forbids all dNSNames.
276 //
277 //     Q: Is "." a valid dNSName constraint? If so, what does it mean?
278 //     A: No, because absolute names are not allowed (see above).
279 //
280 // [0] RFC 6265 (Cookies) Domain Matching rules:
281 //     http://tools.ietf.org/html/rfc6265#section-5.1.3
282 // [1] NSS source code:
283 //     https://mxr.mozilla.org/nss/source/lib/certdb/genname.c?rev=2a7348f013cb#1209
284 // [2] Description of SChannel's behavior from Microsoft:
285 //     http://www.imc.org/ietf-pkix/mail-archive/msg04668.html
286 // [3] Proposal to add such support to OpenSSL:
287 //     http://www.mail-archive.com/openssl-dev%40openssl.org/msg36204.html
288 //     https://rt.openssl.org/Ticket/Display.html?id=3562
289 // [4] Feedback on the lack of clarify in the definition that never got
290 //     incorporated into the spec:
291 //     https://www.ietf.org/mail-archive/web/pkix/current/msg21192.html
presented_id_matches_reference_id_internal( presented_dns_id: untrusted::Input, reference_dns_id_role: IdRole, reference_dns_id: untrusted::Input, ) -> Option<bool>292 fn presented_id_matches_reference_id_internal(
293     presented_dns_id: untrusted::Input,
294     reference_dns_id_role: IdRole,
295     reference_dns_id: untrusted::Input,
296 ) -> Option<bool> {
297     if !is_valid_dns_id(presented_dns_id, IdRole::Presented, AllowWildcards::Yes) {
298         return None;
299     }
300 
301     if !is_valid_dns_id(reference_dns_id, reference_dns_id_role, AllowWildcards::No) {
302         return None;
303     }
304 
305     let mut presented = untrusted::Reader::new(presented_dns_id);
306     let mut reference = untrusted::Reader::new(reference_dns_id);
307 
308     match reference_dns_id_role {
309         IdRole::Reference => (),
310 
311         IdRole::NameConstraint if presented_dns_id.len() > reference_dns_id.len() => {
312             if reference_dns_id.is_empty() {
313                 // An empty constraint matches everything.
314                 return Some(true);
315             }
316 
317             // If the reference ID starts with a dot then skip the prefix of
318             // the presented ID and start the comparison at the position of
319             // that dot. Examples:
320             //
321             //                                       Matches     Doesn't Match
322             //     -----------------------------------------------------------
323             //       original presented ID:  www.example.com    badexample.com
324             //                     skipped:  www                ba
325             //     presented ID w/o prefix:     .example.com      dexample.com
326             //                reference ID:     .example.com      .example.com
327             //
328             // If the reference ID does not start with a dot then we skip
329             // the prefix of the presented ID but also verify that the
330             // prefix ends with a dot. Examples:
331             //
332             //                                       Matches     Doesn't Match
333             //     -----------------------------------------------------------
334             //       original presented ID:  www.example.com    badexample.com
335             //                     skipped:  www                ba
336             //                 must be '.':     .                 d
337             //     presented ID w/o prefix:      example.com       example.com
338             //                reference ID:      example.com       example.com
339             //
340             if reference.peek(b'.') {
341                 if presented
342                     .skip(presented_dns_id.len() - reference_dns_id.len())
343                     .is_err()
344                 {
345                     unreachable!();
346                 }
347             } else {
348                 if presented
349                     .skip(presented_dns_id.len() - reference_dns_id.len() - 1)
350                     .is_err()
351                 {
352                     unreachable!();
353                 }
354                 if presented.read_byte() != Ok(b'.') {
355                     return Some(false);
356                 }
357             }
358         }
359 
360         IdRole::NameConstraint => (),
361 
362         IdRole::Presented => unreachable!(),
363     }
364 
365     // Only allow wildcard labels that consist only of '*'.
366     if presented.peek(b'*') {
367         if presented.skip(1).is_err() {
368             unreachable!();
369         }
370 
371         loop {
372             if reference.read_byte().is_err() {
373                 return Some(false);
374             }
375             if reference.peek(b'.') {
376                 break;
377             }
378         }
379     }
380 
381     loop {
382         let presented_byte = match (presented.read_byte(), reference.read_byte()) {
383             (Ok(p), Ok(r)) if ascii_lower(p) == ascii_lower(r) => p,
384             _ => {
385                 return Some(false);
386             }
387         };
388 
389         if presented.at_end() {
390             // Don't allow presented IDs to be absolute.
391             if presented_byte == b'.' {
392                 return None;
393             }
394             break;
395         }
396     }
397 
398     // Allow a relative presented DNS ID to match an absolute reference DNS ID,
399     // unless we're matching a name constraint.
400     if !reference.at_end() {
401         if reference_dns_id_role != IdRole::NameConstraint {
402             match reference.read_byte() {
403                 Ok(b'.') => (),
404                 _ => {
405                     return Some(false);
406                 }
407             };
408         }
409         if !reference.at_end() {
410             return Some(false);
411         }
412     }
413 
414     assert!(presented.at_end());
415     assert!(reference.at_end());
416 
417     Some(true)
418 }
419 
420 #[inline]
ascii_lower(b: u8) -> u8421 fn ascii_lower(b: u8) -> u8 {
422     match b {
423         b'A'..=b'Z' => b + b'a' - b'A',
424         _ => b,
425     }
426 }
427 
428 #[derive(Clone, Copy, PartialEq)]
429 enum AllowWildcards {
430     No,
431     Yes,
432 }
433 
434 #[derive(Clone, Copy, PartialEq)]
435 enum IdRole {
436     Reference,
437     Presented,
438     NameConstraint,
439 }
440 
is_valid_reference_dns_id(hostname: untrusted::Input) -> bool441 fn is_valid_reference_dns_id(hostname: untrusted::Input) -> bool {
442     is_valid_dns_id(hostname, IdRole::Reference, AllowWildcards::No)
443 }
444 
445 // https://tools.ietf.org/html/rfc5280#section-4.2.1.6:
446 //
447 //   When the subjectAltName extension contains a domain name system
448 //   label, the domain name MUST be stored in the dNSName (an IA5String).
449 //   The name MUST be in the "preferred name syntax", as specified by
450 //   Section 3.5 of [RFC1034] and as modified by Section 2.1 of
451 //   [RFC1123].
452 //
453 // https://bugzilla.mozilla.org/show_bug.cgi?id=1136616: As an exception to the
454 // requirement above, underscores are also allowed in names for compatibility.
is_valid_dns_id( hostname: untrusted::Input, id_role: IdRole, allow_wildcards: AllowWildcards, ) -> bool455 fn is_valid_dns_id(
456     hostname: untrusted::Input,
457     id_role: IdRole,
458     allow_wildcards: AllowWildcards,
459 ) -> bool {
460     // https://blogs.msdn.microsoft.com/oldnewthing/20120412-00/?p=7873/
461     if hostname.len() > 253 {
462         return false;
463     }
464 
465     let mut input = untrusted::Reader::new(hostname);
466 
467     if id_role == IdRole::NameConstraint && input.at_end() {
468         return true;
469     }
470 
471     let mut dot_count = 0;
472     let mut label_length = 0;
473     let mut label_is_all_numeric = false;
474     let mut label_ends_with_hyphen = false;
475 
476     // Only presented IDs are allowed to have wildcard labels. And, like
477     // Chromium, be stricter than RFC 6125 requires by insisting that a
478     // wildcard label consist only of '*'.
479     let is_wildcard = allow_wildcards == AllowWildcards::Yes && input.peek(b'*');
480     let mut is_first_byte = !is_wildcard;
481     if is_wildcard {
482         if input.read_byte() != Ok(b'*') || input.read_byte() != Ok(b'.') {
483             return false;
484         }
485         dot_count += 1;
486     }
487 
488     loop {
489         const MAX_LABEL_LENGTH: usize = 63;
490 
491         match input.read_byte() {
492             Ok(b'-') => {
493                 if label_length == 0 {
494                     return false; // Labels must not start with a hyphen.
495                 }
496                 label_is_all_numeric = false;
497                 label_ends_with_hyphen = true;
498                 label_length += 1;
499                 if label_length > MAX_LABEL_LENGTH {
500                     return false;
501                 }
502             }
503 
504             Ok(b'0'..=b'9') => {
505                 if label_length == 0 {
506                     label_is_all_numeric = true;
507                 }
508                 label_ends_with_hyphen = false;
509                 label_length += 1;
510                 if label_length > MAX_LABEL_LENGTH {
511                     return false;
512                 }
513             }
514 
515             Ok(b'a'..=b'z') | Ok(b'A'..=b'Z') | Ok(b'_') => {
516                 label_is_all_numeric = false;
517                 label_ends_with_hyphen = false;
518                 label_length += 1;
519                 if label_length > MAX_LABEL_LENGTH {
520                     return false;
521                 }
522             }
523 
524             Ok(b'.') => {
525                 dot_count += 1;
526                 if label_length == 0 && (id_role != IdRole::NameConstraint || !is_first_byte) {
527                     return false;
528                 }
529                 if label_ends_with_hyphen {
530                     return false; // Labels must not end with a hyphen.
531                 }
532                 label_length = 0;
533             }
534 
535             _ => {
536                 return false;
537             }
538         }
539         is_first_byte = false;
540 
541         if input.at_end() {
542             break;
543         }
544     }
545 
546     // Only reference IDs, not presented IDs or name constraints, may be
547     // absolute.
548     if label_length == 0 && id_role != IdRole::Reference {
549         return false;
550     }
551 
552     if label_ends_with_hyphen {
553         return false; // Labels must not end with a hyphen.
554     }
555 
556     if label_is_all_numeric {
557         return false; // Last label must not be all numeric.
558     }
559 
560     if is_wildcard {
561         // If the DNS ID ends with a dot, the last dot signifies an absolute ID.
562         let label_count = if label_length == 0 {
563             dot_count
564         } else {
565             dot_count + 1
566         };
567 
568         // Like NSS, require at least two labels to follow the wildcard label.
569         // TODO: Allow the TrustDomain to control this on a per-eTLD+1 basis,
570         // similar to Chromium. Even then, it might be better to still enforce
571         // that there are at least two labels after the wildcard.
572         if label_count < 3 {
573             return false;
574         }
575     }
576 
577     true
578 }
579 
580 #[cfg(test)]
581 mod tests {
582     use super::*;
583 
584     const PRESENTED_MATCHES_REFERENCE: &[(&[u8], &[u8], Option<bool>)] = &[
585         (b"", b"a", None),
586         (b"a", b"a", Some(true)),
587         (b"b", b"a", Some(false)),
588         (b"*.b.a", b"c.b.a", Some(true)),
589         (b"*.b.a", b"b.a", Some(false)),
590         (b"*.b.a", b"b.a.", Some(false)),
591         // Wildcard not in leftmost label
592         (b"d.c.b.a", b"d.c.b.a", Some(true)),
593         (b"d.*.b.a", b"d.c.b.a", None),
594         (b"d.c*.b.a", b"d.c.b.a", None),
595         (b"d.c*.b.a", b"d.cc.b.a", None),
596         // case sensitivity
597         (
598             b"abcdefghijklmnopqrstuvwxyz",
599             b"ABCDEFGHIJKLMNOPQRSTUVWXYZ",
600             Some(true),
601         ),
602         (
603             b"ABCDEFGHIJKLMNOPQRSTUVWXYZ",
604             b"abcdefghijklmnopqrstuvwxyz",
605             Some(true),
606         ),
607         (b"aBc", b"Abc", Some(true)),
608         // digits
609         (b"a1", b"a1", Some(true)),
610         // A trailing dot indicates an absolute name, and absolute names can match
611         // relative names, and vice-versa.
612         (b"example", b"example", Some(true)),
613         (b"example.", b"example.", None),
614         (b"example", b"example.", Some(true)),
615         (b"example.", b"example", None),
616         (b"example.com", b"example.com", Some(true)),
617         (b"example.com.", b"example.com.", None),
618         (b"example.com", b"example.com.", Some(true)),
619         (b"example.com.", b"example.com", None),
620         (b"example.com..", b"example.com.", None),
621         (b"example.com..", b"example.com", None),
622         (b"example.com...", b"example.com.", None),
623         // xn-- IDN prefix
624         (b"x*.b.a", b"xa.b.a", None),
625         (b"x*.b.a", b"xna.b.a", None),
626         (b"x*.b.a", b"xn-a.b.a", None),
627         (b"x*.b.a", b"xn--a.b.a", None),
628         (b"xn*.b.a", b"xn--a.b.a", None),
629         (b"xn-*.b.a", b"xn--a.b.a", None),
630         (b"xn--*.b.a", b"xn--a.b.a", None),
631         (b"xn*.b.a", b"xn--a.b.a", None),
632         (b"xn-*.b.a", b"xn--a.b.a", None),
633         (b"xn--*.b.a", b"xn--a.b.a", None),
634         (b"xn---*.b.a", b"xn--a.b.a", None),
635         // "*" cannot expand to nothing.
636         (b"c*.b.a", b"c.b.a", None),
637         // --------------------------------------------------------------------------
638         // The rest of these are test cases adapted from Chromium's
639         // x509_certificate_unittest.cc. The parameter order is the opposite in
640         // Chromium's tests. Also, they some tests were modified to fit into this
641         // framework or due to intentional differences between mozilla::pkix and
642         // Chromium.
643         (b"foo.com", b"foo.com", Some(true)),
644         (b"f", b"f", Some(true)),
645         (b"i", b"h", Some(false)),
646         (b"*.foo.com", b"bar.foo.com", Some(true)),
647         (b"*.test.fr", b"www.test.fr", Some(true)),
648         (b"*.test.FR", b"wwW.tESt.fr", Some(true)),
649         (b".uk", b"f.uk", None),
650         (b"?.bar.foo.com", b"w.bar.foo.com", None),
651         (b"(www|ftp).foo.com", b"www.foo.com", None), // regex!
652         (b"www.foo.com\0", b"www.foo.com", None),
653         (b"www.foo.com\0*.foo.com", b"www.foo.com", None),
654         (b"ww.house.example", b"www.house.example", Some(false)),
655         (b"www.test.org", b"test.org", Some(false)),
656         (b"*.test.org", b"test.org", Some(false)),
657         (b"*.org", b"test.org", None),
658         // '*' must be the only character in the wildcard label
659         (b"w*.bar.foo.com", b"w.bar.foo.com", None),
660         (b"ww*ww.bar.foo.com", b"www.bar.foo.com", None),
661         (b"ww*ww.bar.foo.com", b"wwww.bar.foo.com", None),
662         (b"w*w.bar.foo.com", b"wwww.bar.foo.com", None),
663         (b"w*w.bar.foo.c0m", b"wwww.bar.foo.com", None),
664         (b"wa*.bar.foo.com", b"WALLY.bar.foo.com", None),
665         (b"*Ly.bar.foo.com", b"wally.bar.foo.com", None),
666         // Chromium does URL decoding of the reference ID, but we don't, and we also
667         // require that the reference ID is valid, so we can't test these two.
668         //     (b"www.foo.com", b"ww%57.foo.com", Some(true)),
669         //     (b"www&.foo.com", b"www%26.foo.com", Some(true)),
670         (b"*.test.de", b"www.test.co.jp", Some(false)),
671         (b"*.jp", b"www.test.co.jp", None),
672         (b"www.test.co.uk", b"www.test.co.jp", Some(false)),
673         (b"www.*.co.jp", b"www.test.co.jp", None),
674         (b"www.bar.foo.com", b"www.bar.foo.com", Some(true)),
675         (b"*.foo.com", b"www.bar.foo.com", Some(false)),
676         (b"*.*.foo.com", b"www.bar.foo.com", None),
677         // Our matcher requires the reference ID to be a valid DNS name, so we cannot
678         // test this case.
679         //     (b"*.*.bar.foo.com", b"*..bar.foo.com", Some(false)),
680         (b"www.bath.org", b"www.bath.org", Some(true)),
681         // Our matcher requires the reference ID to be a valid DNS name, so we cannot
682         // test these cases.
683         // DNS_ID_MISMATCH("www.bath.org", ""),
684         //     (b"www.bath.org", b"20.30.40.50", Some(false)),
685         //     (b"www.bath.org", b"66.77.88.99", Some(false)),
686 
687         // IDN tests
688         (
689             b"xn--poema-9qae5a.com.br",
690             b"xn--poema-9qae5a.com.br",
691             Some(true),
692         ),
693         (
694             b"*.xn--poema-9qae5a.com.br",
695             b"www.xn--poema-9qae5a.com.br",
696             Some(true),
697         ),
698         (
699             b"*.xn--poema-9qae5a.com.br",
700             b"xn--poema-9qae5a.com.br",
701             Some(false),
702         ),
703         (b"xn--poema-*.com.br", b"xn--poema-9qae5a.com.br", None),
704         (b"xn--*-9qae5a.com.br", b"xn--poema-9qae5a.com.br", None),
705         (b"*--poema-9qae5a.com.br", b"xn--poema-9qae5a.com.br", None),
706         // The following are adapted from the examples quoted from
707         //   http://tools.ietf.org/html/rfc6125#section-6.4.3
708         // (e.g., *.example.com would match foo.example.com but
709         // not bar.foo.example.com or example.com).
710         (b"*.example.com", b"foo.example.com", Some(true)),
711         (b"*.example.com", b"bar.foo.example.com", Some(false)),
712         (b"*.example.com", b"example.com", Some(false)),
713         (b"baz*.example.net", b"baz1.example.net", None),
714         (b"*baz.example.net", b"foobaz.example.net", None),
715         (b"b*z.example.net", b"buzz.example.net", None),
716         // Wildcards should not be valid for public registry controlled domains,
717         // and unknown/unrecognized domains, at least three domain components must
718         // be present. For mozilla::pkix and NSS, there must always be at least two
719         // labels after the wildcard label.
720         (b"*.test.example", b"www.test.example", Some(true)),
721         (b"*.example.co.uk", b"test.example.co.uk", Some(true)),
722         (b"*.example", b"test.example", None),
723         // The result is different than Chromium, because Chromium takes into account
724         // the additional knowledge it has that "co.uk" is a TLD. mozilla::pkix does
725         // not know that.
726         (b"*.co.uk", b"example.co.uk", Some(true)),
727         (b"*.com", b"foo.com", None),
728         (b"*.us", b"foo.us", None),
729         (b"*", b"foo", None),
730         // IDN variants of wildcards and registry controlled domains.
731         (
732             b"*.xn--poema-9qae5a.com.br",
733             b"www.xn--poema-9qae5a.com.br",
734             Some(true),
735         ),
736         (
737             b"*.example.xn--mgbaam7a8h",
738             b"test.example.xn--mgbaam7a8h",
739             Some(true),
740         ),
741         // RFC6126 allows this, and NSS accepts it, but Chromium disallows it.
742         // TODO: File bug against Chromium.
743         (b"*.com.br", b"xn--poema-9qae5a.com.br", Some(true)),
744         (b"*.xn--mgbaam7a8h", b"example.xn--mgbaam7a8h", None),
745         // Wildcards should be permissible for 'private' registry-controlled
746         // domains. (In mozilla::pkix, we do not know if it is a private registry-
747         // controlled domain or not.)
748         (b"*.appspot.com", b"www.appspot.com", Some(true)),
749         (b"*.s3.amazonaws.com", b"foo.s3.amazonaws.com", Some(true)),
750         // Multiple wildcards are not valid.
751         (b"*.*.com", b"foo.example.com", None),
752         (b"*.bar.*.com", b"foo.bar.example.com", None),
753         // Absolute vs relative DNS name tests. Although not explicitly specified
754         // in RFC 6125, absolute reference names (those ending in a .) should
755         // match either absolute or relative presented names.
756         // TODO: File errata against RFC 6125 about this.
757         (b"foo.com.", b"foo.com", None),
758         (b"foo.com", b"foo.com.", Some(true)),
759         (b"foo.com.", b"foo.com.", None),
760         (b"f.", b"f", None),
761         (b"f", b"f.", Some(true)),
762         (b"f.", b"f.", None),
763         (b"*.bar.foo.com.", b"www-3.bar.foo.com", None),
764         (b"*.bar.foo.com", b"www-3.bar.foo.com.", Some(true)),
765         (b"*.bar.foo.com.", b"www-3.bar.foo.com.", None),
766         // We require the reference ID to be a valid DNS name, so we cannot test this
767         // case.
768         //     (b".", b".", Some(false)),
769         (b"*.com.", b"example.com", None),
770         (b"*.com", b"example.com.", None),
771         (b"*.com.", b"example.com.", None),
772         (b"*.", b"foo.", None),
773         (b"*.", b"foo", None),
774         // The result is different than Chromium because we don't know that co.uk is
775         // a TLD.
776         (b"*.co.uk.", b"foo.co.uk", None),
777         (b"*.co.uk.", b"foo.co.uk.", None),
778     ];
779 
780     #[test]
presented_matches_reference_test()781     fn presented_matches_reference_test() {
782         for &(presented, reference, expected_result) in PRESENTED_MATCHES_REFERENCE {
783             let actual_result = presented_id_matches_reference_id(
784                 untrusted::Input::from(presented),
785                 untrusted::Input::from(reference),
786             );
787             assert_eq!(
788                 actual_result,
789                 expected_result,
790                 "presented_dns_id_matches_reference_dns_id(\"{:?}\", IDRole::ReferenceID, \"{:?}\")",
791                 presented,
792                 reference
793             );
794         }
795     }
796 }
797