// Copyright 2015-2020 Brian Smith. // // Permission to use, copy, modify, and/or distribute this software for any // purpose with or without fee is hereby granted, provided that the above // copyright notice and this permission notice appear in all copies. // // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #[cfg(feature = "alloc")] use alloc::string::String; /// A DNS Name suitable for use in the TLS Server Name Indication (SNI) /// extension and/or for use as the reference hostname for which to verify a /// certificate. /// /// A `DnsName` is guaranteed to be syntactically valid. The validity rules are /// specified in [RFC 5280 Section 7.2], except that underscores are also /// allowed. /// /// `DnsName` stores a copy of the input it was constructed from in a `String` /// and so it is only available when the `std` default feature is enabled. /// /// `Eq`, `PartialEq`, etc. are not implemented because name comparison /// frequently should be done case-insensitively and/or with other caveats that /// depend on the specific circumstances in which the comparison is done. /// /// [RFC 5280 Section 7.2]: https://tools.ietf.org/html/rfc5280#section-7.2 /// /// Requires the `alloc` feature. #[cfg(feature = "alloc")] #[derive(Clone, Debug, Eq, PartialEq, Hash)] pub struct DnsName(String); /// Requires the `alloc` feature. #[cfg(feature = "alloc")] impl DnsName { /// Returns a `DnsNameRef` that refers to this `DnsName`. pub fn as_ref(&self) -> DnsNameRef { DnsNameRef(self.0.as_bytes()) } } /// Requires the `alloc` feature. #[cfg(feature = "alloc")] impl AsRef for DnsName { fn as_ref(&self) -> &str { self.0.as_ref() } } /// Requires the `alloc` feature. // Deprecated #[cfg(feature = "alloc")] impl From> for DnsName { fn from(dns_name: DnsNameRef) -> Self { dns_name.to_owned() } } /// A reference to a DNS Name suitable for use in the TLS Server Name Indication /// (SNI) extension and/or for use as the reference hostname for which to verify /// a certificate. /// /// A `DnsNameRef` is guaranteed to be syntactically valid. The validity rules /// are specified in [RFC 5280 Section 7.2], except that underscores are also /// allowed. /// /// `Eq`, `PartialEq`, etc. are not implemented because name comparison /// frequently should be done case-insensitively and/or with other caveats that /// depend on the specific circumstances in which the comparison is done. /// /// [RFC 5280 Section 7.2]: https://tools.ietf.org/html/rfc5280#section-7.2 #[derive(Clone, Copy)] pub struct DnsNameRef<'a>(&'a [u8]); impl AsRef<[u8]> for DnsNameRef<'_> { #[inline] fn as_ref(&self) -> &[u8] { self.0 } } /// An error indicating that a `DnsNameRef` could not built because the input /// is not a syntactically-valid DNS Name. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct InvalidDnsNameError; impl core::fmt::Display for InvalidDnsNameError { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { write!(f, "{:?}", self) } } /// Requires the `std` feature. #[cfg(feature = "std")] impl ::std::error::Error for InvalidDnsNameError {} impl<'a> DnsNameRef<'a> { /// Constructs a `DnsNameRef` from the given input if the input is a /// syntactically-valid DNS name. pub fn try_from_ascii(dns_name: &'a [u8]) -> Result { if !is_valid_reference_dns_id(untrusted::Input::from(dns_name)) { return Err(InvalidDnsNameError); } Ok(Self(dns_name)) } /// Constructs a `DnsNameRef` from the given input if the input is a /// syntactically-valid DNS name. pub fn try_from_ascii_str(dns_name: &'a str) -> Result { Self::try_from_ascii(dns_name.as_bytes()) } /// Constructs a `DnsName` from this `DnsNameRef` /// /// Requires the `alloc` feature. #[cfg(feature = "alloc")] pub fn to_owned(&self) -> DnsName { // DnsNameRef is already guaranteed to be valid ASCII, which is a // subset of UTF-8. let s: &str = (*self).into(); DnsName(s.to_ascii_lowercase()) } } /// Requires the `alloc` feature. #[cfg(feature = "alloc")] impl core::fmt::Debug for DnsNameRef<'_> { fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> { let lowercase = self.clone().to_owned(); f.debug_tuple("DnsNameRef").field(&lowercase.0).finish() } } impl<'a> From> for &'a str { fn from(DnsNameRef(d): DnsNameRef<'a>) -> Self { // The unwrap won't fail because DnsNameRefs are guaranteed to be ASCII // and ASCII is a subset of UTF-8. core::str::from_utf8(d).unwrap() } } pub(super) fn presented_id_matches_reference_id( presented_dns_id: untrusted::Input, reference_dns_id: untrusted::Input, ) -> Option { presented_id_matches_reference_id_internal( presented_dns_id, IdRole::Reference, reference_dns_id, ) } pub(super) fn presented_id_matches_constraint( presented_dns_id: untrusted::Input, reference_dns_id: untrusted::Input, ) -> Option { presented_id_matches_reference_id_internal( presented_dns_id, IdRole::NameConstraint, reference_dns_id, ) } // We do not distinguish between a syntactically-invalid presented_dns_id and // one that is syntactically valid but does not match reference_dns_id; in both // cases, the result is false. // // We assume that both presented_dns_id and reference_dns_id are encoded in // such a way that US-ASCII (7-bit) characters are encoded in one byte and no // encoding of a non-US-ASCII character contains a code point in the range // 0-127. For example, UTF-8 is OK but UTF-16 is not. // // RFC6125 says that a wildcard label may be of the form *., where // and/or may be empty. However, NSS requires to be empty, and we // follow NSS's stricter policy by accepting wildcards only of the form // *., where may be empty. // // An relative presented DNS ID matches both an absolute reference ID and a // relative reference ID. Absolute presented DNS IDs are not supported: // // Presented ID Reference ID Result // ------------------------------------- // example.com example.com Match // example.com. example.com Mismatch // example.com example.com. Match // example.com. example.com. Mismatch // // There are more subtleties documented inline in the code. // // Name constraints /////////////////////////////////////////////////////////// // // This is all RFC 5280 has to say about dNSName constraints: // // DNS name restrictions are expressed as host.example.com. Any DNS // name that can be constructed by simply adding zero or more labels to // the left-hand side of the name satisfies the name constraint. For // example, www.host.example.com would satisfy the constraint but // host1.example.com would not. // // This lack of specificity has lead to a lot of uncertainty regarding // subdomain matching. In particular, the following questions have been // raised and answered: // // Q: Does a presented identifier equal (case insensitive) to the name // constraint match the constraint? For example, does the presented // ID "host.example.com" match a "host.example.com" constraint? // A: Yes. RFC5280 says "by simply adding zero or more labels" and this // is the case of adding zero labels. // // Q: When the name constraint does not start with ".", do subdomain // presented identifiers match it? For example, does the presented // ID "www.host.example.com" match a "host.example.com" constraint? // A: Yes. RFC5280 says "by simply adding zero or more labels" and this // is the case of adding more than zero labels. The example is the // one from RFC 5280. // // Q: When the name constraint does not start with ".", does a // non-subdomain prefix match it? For example, does "bigfoo.bar.com" // match "foo.bar.com"? [4] // A: No. We interpret RFC 5280's language of "adding zero or more labels" // to mean that whole labels must be prefixed. // // (Note that the above three scenarios are the same as the RFC 6265 // domain matching rules [0].) // // Q: Is a name constraint that starts with "." valid, and if so, what // semantics does it have? For example, does a presented ID of // "www.example.com" match a constraint of ".example.com"? Does a // presented ID of "example.com" match a constraint of ".example.com"? // A: This implementation, NSS[1], and SChannel[2] all support a // leading ".", but OpenSSL[3] does not yet. Amongst the // implementations that support it, a leading "." is legal and means // the same thing as when the "." is omitted, EXCEPT that a // presented identifier equal (case insensitive) to the name // constraint is not matched; i.e. presented dNSName identifiers // must be subdomains. Some CAs in Mozilla's CA program (e.g. HARICA) // have name constraints with the leading "." in their root // certificates. The name constraints imposed on DCISS by Mozilla also // have the it, so supporting this is a requirement for backward // compatibility, even if it is not yet standardized. So, for example, a // presented ID of "www.example.com" matches a constraint of // ".example.com" but a presented ID of "example.com" does not. // // Q: Is there a way to prevent subdomain matches? // A: Yes. // // Some people have proposed that dNSName constraints that do not // start with a "." should be restricted to exact (case insensitive) // matches. However, such a change of semantics from what RFC5280 // specifies would be a non-backward-compatible change in the case of // permittedSubtrees constraints, and it would be a security issue for // excludedSubtrees constraints. // // However, it can be done with a combination of permittedSubtrees and // excludedSubtrees, e.g. "example.com" in permittedSubtrees and // ".example.com" in excludedSubtrees. // // Q: Are name constraints allowed to be specified as absolute names? // For example, does a presented ID of "example.com" match a name // constraint of "example.com." and vice versa. // A: Absolute names are not supported as presented IDs or name // constraints. Only reference IDs may be absolute. // // Q: Is "" a valid dNSName constraint? If so, what does it mean? // A: Yes. Any valid presented dNSName can be formed "by simply adding zero // or more labels to the left-hand side" of "". In particular, an // excludedSubtrees dNSName constraint of "" forbids all dNSNames. // // Q: Is "." a valid dNSName constraint? If so, what does it mean? // A: No, because absolute names are not allowed (see above). // // [0] RFC 6265 (Cookies) Domain Matching rules: // http://tools.ietf.org/html/rfc6265#section-5.1.3 // [1] NSS source code: // https://mxr.mozilla.org/nss/source/lib/certdb/genname.c?rev=2a7348f013cb#1209 // [2] Description of SChannel's behavior from Microsoft: // http://www.imc.org/ietf-pkix/mail-archive/msg04668.html // [3] Proposal to add such support to OpenSSL: // http://www.mail-archive.com/openssl-dev%40openssl.org/msg36204.html // https://rt.openssl.org/Ticket/Display.html?id=3562 // [4] Feedback on the lack of clarify in the definition that never got // incorporated into the spec: // https://www.ietf.org/mail-archive/web/pkix/current/msg21192.html fn presented_id_matches_reference_id_internal( presented_dns_id: untrusted::Input, reference_dns_id_role: IdRole, reference_dns_id: untrusted::Input, ) -> Option { if !is_valid_dns_id(presented_dns_id, IdRole::Presented, AllowWildcards::Yes) { return None; } if !is_valid_dns_id(reference_dns_id, reference_dns_id_role, AllowWildcards::No) { return None; } let mut presented = untrusted::Reader::new(presented_dns_id); let mut reference = untrusted::Reader::new(reference_dns_id); match reference_dns_id_role { IdRole::Reference => (), IdRole::NameConstraint if presented_dns_id.len() > reference_dns_id.len() => { if reference_dns_id.is_empty() { // An empty constraint matches everything. return Some(true); } // If the reference ID starts with a dot then skip the prefix of // the presented ID and start the comparison at the position of // that dot. Examples: // // Matches Doesn't Match // ----------------------------------------------------------- // original presented ID: www.example.com badexample.com // skipped: www ba // presented ID w/o prefix: .example.com dexample.com // reference ID: .example.com .example.com // // If the reference ID does not start with a dot then we skip // the prefix of the presented ID but also verify that the // prefix ends with a dot. Examples: // // Matches Doesn't Match // ----------------------------------------------------------- // original presented ID: www.example.com badexample.com // skipped: www ba // must be '.': . d // presented ID w/o prefix: example.com example.com // reference ID: example.com example.com // if reference.peek(b'.') { if presented .skip(presented_dns_id.len() - reference_dns_id.len()) .is_err() { unreachable!(); } } else { if presented .skip(presented_dns_id.len() - reference_dns_id.len() - 1) .is_err() { unreachable!(); } if presented.read_byte() != Ok(b'.') { return Some(false); } } } IdRole::NameConstraint => (), IdRole::Presented => unreachable!(), } // Only allow wildcard labels that consist only of '*'. if presented.peek(b'*') { if presented.skip(1).is_err() { unreachable!(); } loop { if reference.read_byte().is_err() { return Some(false); } if reference.peek(b'.') { break; } } } loop { let presented_byte = match (presented.read_byte(), reference.read_byte()) { (Ok(p), Ok(r)) if ascii_lower(p) == ascii_lower(r) => p, _ => { return Some(false); } }; if presented.at_end() { // Don't allow presented IDs to be absolute. if presented_byte == b'.' { return None; } break; } } // Allow a relative presented DNS ID to match an absolute reference DNS ID, // unless we're matching a name constraint. if !reference.at_end() { if reference_dns_id_role != IdRole::NameConstraint { match reference.read_byte() { Ok(b'.') => (), _ => { return Some(false); } }; } if !reference.at_end() { return Some(false); } } assert!(presented.at_end()); assert!(reference.at_end()); Some(true) } #[inline] fn ascii_lower(b: u8) -> u8 { match b { b'A'..=b'Z' => b + b'a' - b'A', _ => b, } } #[derive(Clone, Copy, PartialEq)] enum AllowWildcards { No, Yes, } #[derive(Clone, Copy, PartialEq)] enum IdRole { Reference, Presented, NameConstraint, } fn is_valid_reference_dns_id(hostname: untrusted::Input) -> bool { is_valid_dns_id(hostname, IdRole::Reference, AllowWildcards::No) } // https://tools.ietf.org/html/rfc5280#section-4.2.1.6: // // When the subjectAltName extension contains a domain name system // label, the domain name MUST be stored in the dNSName (an IA5String). // The name MUST be in the "preferred name syntax", as specified by // Section 3.5 of [RFC1034] and as modified by Section 2.1 of // [RFC1123]. // // https://bugzilla.mozilla.org/show_bug.cgi?id=1136616: As an exception to the // requirement above, underscores are also allowed in names for compatibility. fn is_valid_dns_id( hostname: untrusted::Input, id_role: IdRole, allow_wildcards: AllowWildcards, ) -> bool { // https://blogs.msdn.microsoft.com/oldnewthing/20120412-00/?p=7873/ if hostname.len() > 253 { return false; } let mut input = untrusted::Reader::new(hostname); if id_role == IdRole::NameConstraint && input.at_end() { return true; } let mut dot_count = 0; let mut label_length = 0; let mut label_is_all_numeric = false; let mut label_ends_with_hyphen = false; // Only presented IDs are allowed to have wildcard labels. And, like // Chromium, be stricter than RFC 6125 requires by insisting that a // wildcard label consist only of '*'. let is_wildcard = allow_wildcards == AllowWildcards::Yes && input.peek(b'*'); let mut is_first_byte = !is_wildcard; if is_wildcard { if input.read_byte() != Ok(b'*') || input.read_byte() != Ok(b'.') { return false; } dot_count += 1; } loop { const MAX_LABEL_LENGTH: usize = 63; match input.read_byte() { Ok(b'-') => { if label_length == 0 { return false; // Labels must not start with a hyphen. } label_is_all_numeric = false; label_ends_with_hyphen = true; label_length += 1; if label_length > MAX_LABEL_LENGTH { return false; } } Ok(b'0'..=b'9') => { if label_length == 0 { label_is_all_numeric = true; } label_ends_with_hyphen = false; label_length += 1; if label_length > MAX_LABEL_LENGTH { return false; } } Ok(b'a'..=b'z') | Ok(b'A'..=b'Z') | Ok(b'_') => { label_is_all_numeric = false; label_ends_with_hyphen = false; label_length += 1; if label_length > MAX_LABEL_LENGTH { return false; } } Ok(b'.') => { dot_count += 1; if label_length == 0 && (id_role != IdRole::NameConstraint || !is_first_byte) { return false; } if label_ends_with_hyphen { return false; // Labels must not end with a hyphen. } label_length = 0; } _ => { return false; } } is_first_byte = false; if input.at_end() { break; } } // Only reference IDs, not presented IDs or name constraints, may be // absolute. if label_length == 0 && id_role != IdRole::Reference { return false; } if label_ends_with_hyphen { return false; // Labels must not end with a hyphen. } if label_is_all_numeric { return false; // Last label must not be all numeric. } if is_wildcard { // If the DNS ID ends with a dot, the last dot signifies an absolute ID. let label_count = if label_length == 0 { dot_count } else { dot_count + 1 }; // Like NSS, require at least two labels to follow the wildcard label. // TODO: Allow the TrustDomain to control this on a per-eTLD+1 basis, // similar to Chromium. Even then, it might be better to still enforce // that there are at least two labels after the wildcard. if label_count < 3 { return false; } } true } #[cfg(test)] mod tests { use super::*; const PRESENTED_MATCHES_REFERENCE: &[(&[u8], &[u8], Option)] = &[ (b"", b"a", None), (b"a", b"a", Some(true)), (b"b", b"a", Some(false)), (b"*.b.a", b"c.b.a", Some(true)), (b"*.b.a", b"b.a", Some(false)), (b"*.b.a", b"b.a.", Some(false)), // Wildcard not in leftmost label (b"d.c.b.a", b"d.c.b.a", Some(true)), (b"d.*.b.a", b"d.c.b.a", None), (b"d.c*.b.a", b"d.c.b.a", None), (b"d.c*.b.a", b"d.cc.b.a", None), // case sensitivity ( b"abcdefghijklmnopqrstuvwxyz", b"ABCDEFGHIJKLMNOPQRSTUVWXYZ", Some(true), ), ( b"ABCDEFGHIJKLMNOPQRSTUVWXYZ", b"abcdefghijklmnopqrstuvwxyz", Some(true), ), (b"aBc", b"Abc", Some(true)), // digits (b"a1", b"a1", Some(true)), // A trailing dot indicates an absolute name, and absolute names can match // relative names, and vice-versa. (b"example", b"example", Some(true)), (b"example.", b"example.", None), (b"example", b"example.", Some(true)), (b"example.", b"example", None), (b"example.com", b"example.com", Some(true)), (b"example.com.", b"example.com.", None), (b"example.com", b"example.com.", Some(true)), (b"example.com.", b"example.com", None), (b"example.com..", b"example.com.", None), (b"example.com..", b"example.com", None), (b"example.com...", b"example.com.", None), // xn-- IDN prefix (b"x*.b.a", b"xa.b.a", None), (b"x*.b.a", b"xna.b.a", None), (b"x*.b.a", b"xn-a.b.a", None), (b"x*.b.a", b"xn--a.b.a", None), (b"xn*.b.a", b"xn--a.b.a", None), (b"xn-*.b.a", b"xn--a.b.a", None), (b"xn--*.b.a", b"xn--a.b.a", None), (b"xn*.b.a", b"xn--a.b.a", None), (b"xn-*.b.a", b"xn--a.b.a", None), (b"xn--*.b.a", b"xn--a.b.a", None), (b"xn---*.b.a", b"xn--a.b.a", None), // "*" cannot expand to nothing. (b"c*.b.a", b"c.b.a", None), // -------------------------------------------------------------------------- // The rest of these are test cases adapted from Chromium's // x509_certificate_unittest.cc. The parameter order is the opposite in // Chromium's tests. Also, they some tests were modified to fit into this // framework or due to intentional differences between mozilla::pkix and // Chromium. (b"foo.com", b"foo.com", Some(true)), (b"f", b"f", Some(true)), (b"i", b"h", Some(false)), (b"*.foo.com", b"bar.foo.com", Some(true)), (b"*.test.fr", b"www.test.fr", Some(true)), (b"*.test.FR", b"wwW.tESt.fr", Some(true)), (b".uk", b"f.uk", None), (b"?.bar.foo.com", b"w.bar.foo.com", None), (b"(www|ftp).foo.com", b"www.foo.com", None), // regex! (b"www.foo.com\0", b"www.foo.com", None), (b"www.foo.com\0*.foo.com", b"www.foo.com", None), (b"ww.house.example", b"www.house.example", Some(false)), (b"www.test.org", b"test.org", Some(false)), (b"*.test.org", b"test.org", Some(false)), (b"*.org", b"test.org", None), // '*' must be the only character in the wildcard label (b"w*.bar.foo.com", b"w.bar.foo.com", None), (b"ww*ww.bar.foo.com", b"www.bar.foo.com", None), (b"ww*ww.bar.foo.com", b"wwww.bar.foo.com", None), (b"w*w.bar.foo.com", b"wwww.bar.foo.com", None), (b"w*w.bar.foo.c0m", b"wwww.bar.foo.com", None), (b"wa*.bar.foo.com", b"WALLY.bar.foo.com", None), (b"*Ly.bar.foo.com", b"wally.bar.foo.com", None), // Chromium does URL decoding of the reference ID, but we don't, and we also // require that the reference ID is valid, so we can't test these two. // (b"www.foo.com", b"ww%57.foo.com", Some(true)), // (b"www&.foo.com", b"www%26.foo.com", Some(true)), (b"*.test.de", b"www.test.co.jp", Some(false)), (b"*.jp", b"www.test.co.jp", None), (b"www.test.co.uk", b"www.test.co.jp", Some(false)), (b"www.*.co.jp", b"www.test.co.jp", None), (b"www.bar.foo.com", b"www.bar.foo.com", Some(true)), (b"*.foo.com", b"www.bar.foo.com", Some(false)), (b"*.*.foo.com", b"www.bar.foo.com", None), // Our matcher requires the reference ID to be a valid DNS name, so we cannot // test this case. // (b"*.*.bar.foo.com", b"*..bar.foo.com", Some(false)), (b"www.bath.org", b"www.bath.org", Some(true)), // Our matcher requires the reference ID to be a valid DNS name, so we cannot // test these cases. // DNS_ID_MISMATCH("www.bath.org", ""), // (b"www.bath.org", b"20.30.40.50", Some(false)), // (b"www.bath.org", b"66.77.88.99", Some(false)), // IDN tests ( b"xn--poema-9qae5a.com.br", b"xn--poema-9qae5a.com.br", Some(true), ), ( b"*.xn--poema-9qae5a.com.br", b"www.xn--poema-9qae5a.com.br", Some(true), ), ( b"*.xn--poema-9qae5a.com.br", b"xn--poema-9qae5a.com.br", Some(false), ), (b"xn--poema-*.com.br", b"xn--poema-9qae5a.com.br", None), (b"xn--*-9qae5a.com.br", b"xn--poema-9qae5a.com.br", None), (b"*--poema-9qae5a.com.br", b"xn--poema-9qae5a.com.br", None), // The following are adapted from the examples quoted from // http://tools.ietf.org/html/rfc6125#section-6.4.3 // (e.g., *.example.com would match foo.example.com but // not bar.foo.example.com or example.com). (b"*.example.com", b"foo.example.com", Some(true)), (b"*.example.com", b"bar.foo.example.com", Some(false)), (b"*.example.com", b"example.com", Some(false)), (b"baz*.example.net", b"baz1.example.net", None), (b"*baz.example.net", b"foobaz.example.net", None), (b"b*z.example.net", b"buzz.example.net", None), // Wildcards should not be valid for public registry controlled domains, // and unknown/unrecognized domains, at least three domain components must // be present. For mozilla::pkix and NSS, there must always be at least two // labels after the wildcard label. (b"*.test.example", b"www.test.example", Some(true)), (b"*.example.co.uk", b"test.example.co.uk", Some(true)), (b"*.example", b"test.example", None), // The result is different than Chromium, because Chromium takes into account // the additional knowledge it has that "co.uk" is a TLD. mozilla::pkix does // not know that. (b"*.co.uk", b"example.co.uk", Some(true)), (b"*.com", b"foo.com", None), (b"*.us", b"foo.us", None), (b"*", b"foo", None), // IDN variants of wildcards and registry controlled domains. ( b"*.xn--poema-9qae5a.com.br", b"www.xn--poema-9qae5a.com.br", Some(true), ), ( b"*.example.xn--mgbaam7a8h", b"test.example.xn--mgbaam7a8h", Some(true), ), // RFC6126 allows this, and NSS accepts it, but Chromium disallows it. // TODO: File bug against Chromium. (b"*.com.br", b"xn--poema-9qae5a.com.br", Some(true)), (b"*.xn--mgbaam7a8h", b"example.xn--mgbaam7a8h", None), // Wildcards should be permissible for 'private' registry-controlled // domains. (In mozilla::pkix, we do not know if it is a private registry- // controlled domain or not.) (b"*.appspot.com", b"www.appspot.com", Some(true)), (b"*.s3.amazonaws.com", b"foo.s3.amazonaws.com", Some(true)), // Multiple wildcards are not valid. (b"*.*.com", b"foo.example.com", None), (b"*.bar.*.com", b"foo.bar.example.com", None), // Absolute vs relative DNS name tests. Although not explicitly specified // in RFC 6125, absolute reference names (those ending in a .) should // match either absolute or relative presented names. // TODO: File errata against RFC 6125 about this. (b"foo.com.", b"foo.com", None), (b"foo.com", b"foo.com.", Some(true)), (b"foo.com.", b"foo.com.", None), (b"f.", b"f", None), (b"f", b"f.", Some(true)), (b"f.", b"f.", None), (b"*.bar.foo.com.", b"www-3.bar.foo.com", None), (b"*.bar.foo.com", b"www-3.bar.foo.com.", Some(true)), (b"*.bar.foo.com.", b"www-3.bar.foo.com.", None), // We require the reference ID to be a valid DNS name, so we cannot test this // case. // (b".", b".", Some(false)), (b"*.com.", b"example.com", None), (b"*.com", b"example.com.", None), (b"*.com.", b"example.com.", None), (b"*.", b"foo.", None), (b"*.", b"foo", None), // The result is different than Chromium because we don't know that co.uk is // a TLD. (b"*.co.uk.", b"foo.co.uk", None), (b"*.co.uk.", b"foo.co.uk.", None), ]; #[test] fn presented_matches_reference_test() { for &(presented, reference, expected_result) in PRESENTED_MATCHES_REFERENCE { let actual_result = presented_id_matches_reference_id( untrusted::Input::from(presented), untrusted::Input::from(reference), ); assert_eq!( actual_result, expected_result, "presented_dns_id_matches_reference_dns_id(\"{:?}\", IDRole::ReferenceID, \"{:?}\")", presented, reference ); } } }