webpki/subject_name/
dns_name.rs

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
15use core::fmt::Write;
16
17use pki_types::{DnsName, InvalidDnsNameError};
18
19use super::verify::{GeneralName, NameIterator};
20use crate::Error;
21
22pub(crate) fn verify_dns_names(
23    reference: &DnsName<'_>,
24    mut names: NameIterator<'_>,
25) -> Result<(), Error> {
26    let dns_name = untrusted::Input::from(reference.as_ref().as_bytes());
27    names
28        .find_map(|result| {
29            let name = match result {
30                Ok(name) => name,
31                Err(err) => return Some(Err(err)),
32            };
33
34            let presented_id = match name {
35                GeneralName::DnsName(presented) => presented,
36                _ => return None,
37            };
38
39            match presented_id_matches_reference_id(presented_id, IdRole::Reference, dns_name) {
40                Ok(true) => Some(Ok(())),
41                Ok(false) | Err(Error::MalformedDnsIdentifier) => None,
42                Err(e) => Some(Err(e)),
43            }
44        })
45        .unwrap_or(Err(Error::CertNotValidForName))
46}
47
48/// A reference to a DNS Name presented by a server that may include a wildcard.
49///
50/// A `WildcardDnsNameRef` is guaranteed to be syntactically valid. The validity rules
51/// are specified in [RFC 5280 Section 7.2], except that underscores are also
52/// allowed.
53///
54/// Additionally, while [RFC6125 Section 4.1] says that a wildcard label may be of the form
55/// `<x>*<y>.<DNSID>`, where `<x>` and/or `<y>` may be empty, we follow a stricter policy common
56/// to most validation libraries (e.g. NSS) and only accept wildcard labels that are exactly `*`.
57///
58/// [RFC 5280 Section 7.2]: https://tools.ietf.org/html/rfc5280#section-7.2
59/// [RFC 6125 Section 4.1]: https://www.rfc-editor.org/rfc/rfc6125#section-4.1
60#[derive(Clone, Copy, Eq, PartialEq, Hash)]
61pub(crate) struct WildcardDnsNameRef<'a>(&'a [u8]);
62
63impl<'a> WildcardDnsNameRef<'a> {
64    /// Constructs a `WildcardDnsNameRef` from the given input if the input is a
65    /// syntactically-valid DNS name.
66    pub(crate) fn try_from_ascii(dns_name: &'a [u8]) -> Result<Self, InvalidDnsNameError> {
67        if !is_valid_dns_id(
68            untrusted::Input::from(dns_name),
69            IdRole::Reference,
70            Wildcards::Allow,
71        ) {
72            return Err(InvalidDnsNameError);
73        }
74
75        Ok(Self(dns_name))
76    }
77
78    /// Yields a reference to the DNS name as a `&str`.
79    pub(crate) fn as_str(&self) -> &'a str {
80        // The unwrap won't fail because a `WildcardDnsNameRef` is guaranteed to be ASCII and
81        // ASCII is a subset of UTF-8.
82        core::str::from_utf8(self.0).unwrap()
83    }
84}
85
86impl core::fmt::Debug for WildcardDnsNameRef<'_> {
87    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
88        f.write_str("WildcardDnsNameRef(\"")?;
89
90        // Convert each byte of the underlying ASCII string to a `char` and
91        // downcase it prior to formatting it. We avoid self.to_owned() since
92        // it requires allocation.
93        for &ch in self.0 {
94            f.write_char(char::from(ch).to_ascii_lowercase())?;
95        }
96
97        f.write_str("\")")
98    }
99}
100
101// We assume that both presented_dns_id and reference_dns_id are encoded in
102// such a way that US-ASCII (7-bit) characters are encoded in one byte and no
103// encoding of a non-US-ASCII character contains a code point in the range
104// 0-127. For example, UTF-8 is OK but UTF-16 is not.
105//
106// RFC6125 says that a wildcard label may be of the form <x>*<y>.<DNSID>, where
107// <x> and/or <y> may be empty. However, NSS requires <y> to be empty, and we
108// follow NSS's stricter policy by accepting wildcards only of the form
109// <x>*.<DNSID>, where <x> may be empty.
110//
111// An relative presented DNS ID matches both an absolute reference ID and a
112// relative reference ID. Absolute presented DNS IDs are not supported:
113//
114//      Presented ID   Reference ID  Result
115//      -------------------------------------
116//      example.com    example.com   Match
117//      example.com.   example.com   Mismatch
118//      example.com    example.com.  Match
119//      example.com.   example.com.  Mismatch
120//
121// There are more subtleties documented inline in the code.
122//
123// Name constraints ///////////////////////////////////////////////////////////
124//
125// This is all RFC 5280 has to say about dNSName constraints:
126//
127//     DNS name restrictions are expressed as host.example.com.  Any DNS
128//     name that can be constructed by simply adding zero or more labels to
129//     the left-hand side of the name satisfies the name constraint.  For
130//     example, www.host.example.com would satisfy the constraint but
131//     host1.example.com would not.
132//
133// This lack of specificity has lead to a lot of uncertainty regarding
134// subdomain matching. In particular, the following questions have been
135// raised and answered:
136//
137//     Q: Does a presented identifier equal (case insensitive) to the name
138//        constraint match the constraint? For example, does the presented
139//        ID "host.example.com" match a "host.example.com" constraint?
140//     A: Yes. RFC5280 says "by simply adding zero or more labels" and this
141//        is the case of adding zero labels.
142//
143//     Q: When the name constraint does not start with ".", do subdomain
144//        presented identifiers match it? For example, does the presented
145//        ID "www.host.example.com" match a "host.example.com" constraint?
146//     A: Yes. RFC5280 says "by simply adding zero or more labels" and this
147//        is the case of adding more than zero labels. The example is the
148//        one from RFC 5280.
149//
150//     Q: When the name constraint does not start with ".", does a
151//        non-subdomain prefix match it? For example, does "bigfoo.bar.com"
152//        match "foo.bar.com"? [4]
153//     A: No. We interpret RFC 5280's language of "adding zero or more labels"
154//        to mean that whole labels must be prefixed.
155//
156//     (Note that the above three scenarios are the same as the RFC 6265
157//     domain matching rules [0].)
158//
159//     Q: Is a name constraint that starts with "." valid, and if so, what
160//        semantics does it have? For example, does a presented ID of
161//        "www.example.com" match a constraint of ".example.com"? Does a
162//        presented ID of "example.com" match a constraint of ".example.com"?
163//     A: This implementation, NSS[1], and SChannel[2] all support a
164//        leading ".", but OpenSSL[3] does not yet. Amongst the
165//        implementations that support it, a leading "." is legal and means
166//        the same thing as when the "." is omitted, EXCEPT that a
167//        presented identifier equal (case insensitive) to the name
168//        constraint is not matched; i.e. presented dNSName identifiers
169//        must be subdomains. Some CAs in Mozilla's CA program (e.g. HARICA)
170//        have name constraints with the leading "." in their root
171//        certificates. The name constraints imposed on DCISS by Mozilla also
172//        have the it, so supporting this is a requirement for backward
173//        compatibility, even if it is not yet standardized. So, for example, a
174//        presented ID of "www.example.com" matches a constraint of
175//        ".example.com" but a presented ID of "example.com" does not.
176//
177//     Q: Is there a way to prevent subdomain matches?
178//     A: Yes.
179//
180//        Some people have proposed that dNSName constraints that do not
181//        start with a "." should be restricted to exact (case insensitive)
182//        matches. However, such a change of semantics from what RFC5280
183//        specifies would be a non-backward-compatible change in the case of
184//        permittedSubtrees constraints, and it would be a security issue for
185//        excludedSubtrees constraints.
186//
187//        However, it can be done with a combination of permittedSubtrees and
188//        excludedSubtrees, e.g. "example.com" in permittedSubtrees and
189//        ".example.com" in excludedSubtrees.
190//
191//     Q: Are name constraints allowed to be specified as absolute names?
192//        For example, does a presented ID of "example.com" match a name
193//        constraint of "example.com." and vice versa.
194//     A: Absolute names are not supported as presented IDs or name
195//        constraints. Only reference IDs may be absolute.
196//
197//     Q: Is "" a valid dNSName constraint? If so, what does it mean?
198//     A: Yes. Any valid presented dNSName can be formed "by simply adding zero
199//        or more labels to the left-hand side" of "". In particular, an
200//        excludedSubtrees dNSName constraint of "" forbids all dNSNames.
201//
202//     Q: Is "." a valid dNSName constraint? If so, what does it mean?
203//     A: No, because absolute names are not allowed (see above).
204//
205// [0] RFC 6265 (Cookies) Domain Matching rules:
206//     http://tools.ietf.org/html/rfc6265#section-5.1.3
207// [1] NSS source code:
208//     https://mxr.mozilla.org/nss/source/lib/certdb/genname.c?rev=2a7348f013cb#1209
209// [2] Description of SChannel's behavior from Microsoft:
210//     http://www.imc.org/ietf-pkix/mail-archive/msg04668.html
211// [3] Proposal to add such support to OpenSSL:
212//     http://www.mail-archive.com/openssl-dev%40openssl.org/msg36204.html
213//     https://rt.openssl.org/Ticket/Display.html?id=3562
214// [4] Feedback on the lack of clarify in the definition that never got
215//     incorporated into the spec:
216//     https://www.ietf.org/mail-archive/web/pkix/current/msg21192.html
217pub(super) fn presented_id_matches_reference_id(
218    presented_dns_id: untrusted::Input<'_>,
219    reference_dns_id_role: IdRole,
220    reference_dns_id: untrusted::Input<'_>,
221) -> Result<bool, Error> {
222    if !is_valid_dns_id(presented_dns_id, IdRole::Presented, Wildcards::Allow) {
223        return Err(Error::MalformedDnsIdentifier);
224    }
225
226    if !is_valid_dns_id(reference_dns_id, reference_dns_id_role, Wildcards::Deny) {
227        return Err(match reference_dns_id_role {
228            IdRole::NameConstraint => Error::MalformedNameConstraint,
229            _ => Error::MalformedDnsIdentifier,
230        });
231    }
232
233    let mut presented = untrusted::Reader::new(presented_dns_id);
234    let mut reference = untrusted::Reader::new(reference_dns_id);
235
236    match reference_dns_id_role {
237        IdRole::Reference => (),
238
239        IdRole::NameConstraint if presented_dns_id.len() > reference_dns_id.len() => {
240            if reference_dns_id.is_empty() {
241                // An empty constraint matches everything.
242                return Ok(true);
243            }
244
245            // If the reference ID starts with a dot then skip the prefix of
246            // the presented ID and start the comparison at the position of
247            // that dot. Examples:
248            //
249            //                                       Matches     Doesn't Match
250            //     -----------------------------------------------------------
251            //       original presented ID:  www.example.com    badexample.com
252            //                     skipped:  www                ba
253            //     presented ID w/o prefix:     .example.com      dexample.com
254            //                reference ID:     .example.com      .example.com
255            //
256            // If the reference ID does not start with a dot then we skip
257            // the prefix of the presented ID but also verify that the
258            // prefix ends with a dot. Examples:
259            //
260            //                                       Matches     Doesn't Match
261            //     -----------------------------------------------------------
262            //       original presented ID:  www.example.com    badexample.com
263            //                     skipped:  www                ba
264            //                 must be '.':     .                 d
265            //     presented ID w/o prefix:      example.com       example.com
266            //                reference ID:      example.com       example.com
267            //
268            if reference.peek(b'.') {
269                if presented
270                    .skip(presented_dns_id.len() - reference_dns_id.len())
271                    .is_err()
272                {
273                    unreachable!();
274                }
275            } else {
276                if presented
277                    .skip(presented_dns_id.len() - reference_dns_id.len() - 1)
278                    .is_err()
279                {
280                    unreachable!();
281                }
282                if presented.read_byte() != Ok(b'.') {
283                    return Ok(false);
284                }
285            }
286        }
287
288        IdRole::NameConstraint => (),
289
290        IdRole::Presented => unreachable!(),
291    }
292
293    // Only allow wildcard labels that consist only of '*'.
294    if presented.peek(b'*') {
295        if presented.skip(1).is_err() {
296            unreachable!();
297        }
298
299        loop {
300            if reference.read_byte().is_err() {
301                return Ok(false);
302            }
303            if reference.peek(b'.') {
304                break;
305            }
306        }
307    }
308
309    loop {
310        let presented_byte = match (presented.read_byte(), reference.read_byte()) {
311            (Ok(p), Ok(r)) if ascii_lower(p) == ascii_lower(r) => p,
312            _ => {
313                return Ok(false);
314            }
315        };
316
317        if presented.at_end() {
318            // Don't allow presented IDs to be absolute.
319            if presented_byte == b'.' {
320                return Err(Error::MalformedDnsIdentifier);
321            }
322            break;
323        }
324    }
325
326    // Allow a relative presented DNS ID to match an absolute reference DNS ID,
327    // unless we're matching a name constraint.
328    if !reference.at_end() {
329        if reference_dns_id_role != IdRole::NameConstraint {
330            match reference.read_byte() {
331                Ok(b'.') => (),
332                _ => {
333                    return Ok(false);
334                }
335            };
336        }
337        if !reference.at_end() {
338            return Ok(false);
339        }
340    }
341
342    assert!(presented.at_end());
343    assert!(reference.at_end());
344
345    Ok(true)
346}
347
348#[inline]
349fn ascii_lower(b: u8) -> u8 {
350    match b {
351        b'A'..=b'Z' => b + b'a' - b'A',
352        _ => b,
353    }
354}
355
356#[derive(Clone, Copy, PartialEq)]
357enum Wildcards {
358    Deny,
359    Allow,
360}
361
362#[derive(Clone, Copy, PartialEq)]
363pub(super) enum IdRole {
364    Reference,
365    Presented,
366    NameConstraint,
367}
368
369// https://tools.ietf.org/html/rfc5280#section-4.2.1.6:
370//
371//   When the subjectAltName extension contains a domain name system
372//   label, the domain name MUST be stored in the dNSName (an IA5String).
373//   The name MUST be in the "preferred name syntax", as specified by
374//   Section 3.5 of [RFC1034] and as modified by Section 2.1 of
375//   [RFC1123].
376//
377// https://bugzilla.mozilla.org/show_bug.cgi?id=1136616: As an exception to the
378// requirement above, underscores are also allowed in names for compatibility.
379fn is_valid_dns_id(
380    hostname: untrusted::Input<'_>,
381    id_role: IdRole,
382    allow_wildcards: Wildcards,
383) -> bool {
384    // https://blogs.msdn.microsoft.com/oldnewthing/20120412-00/?p=7873/
385    if hostname.len() > 253 {
386        return false;
387    }
388
389    let mut input = untrusted::Reader::new(hostname);
390
391    if id_role == IdRole::NameConstraint && input.at_end() {
392        return true;
393    }
394
395    let mut dot_count = 0;
396    let mut label_length = 0;
397    let mut label_is_all_numeric = false;
398    let mut label_ends_with_hyphen = false;
399
400    // Only presented IDs are allowed to have wildcard labels. And, like
401    // Chromium, be stricter than RFC 6125 requires by insisting that a
402    // wildcard label consist only of '*'.
403    let is_wildcard = allow_wildcards == Wildcards::Allow && input.peek(b'*');
404    let mut is_first_byte = !is_wildcard;
405    if is_wildcard {
406        if input.read_byte() != Ok(b'*') || input.read_byte() != Ok(b'.') {
407            return false;
408        }
409        dot_count += 1;
410    }
411
412    loop {
413        const MAX_LABEL_LENGTH: usize = 63;
414
415        match input.read_byte() {
416            Ok(b'-') => {
417                if label_length == 0 {
418                    return false; // Labels must not start with a hyphen.
419                }
420                label_is_all_numeric = false;
421                label_ends_with_hyphen = true;
422                label_length += 1;
423                if label_length > MAX_LABEL_LENGTH {
424                    return false;
425                }
426            }
427
428            Ok(b'0'..=b'9') => {
429                if label_length == 0 {
430                    label_is_all_numeric = true;
431                }
432                label_ends_with_hyphen = false;
433                label_length += 1;
434                if label_length > MAX_LABEL_LENGTH {
435                    return false;
436                }
437            }
438
439            Ok(b'a'..=b'z') | Ok(b'A'..=b'Z') | Ok(b'_') => {
440                label_is_all_numeric = false;
441                label_ends_with_hyphen = false;
442                label_length += 1;
443                if label_length > MAX_LABEL_LENGTH {
444                    return false;
445                }
446            }
447
448            Ok(b'.') => {
449                dot_count += 1;
450                if label_length == 0 && (id_role != IdRole::NameConstraint || !is_first_byte) {
451                    return false;
452                }
453                if label_ends_with_hyphen {
454                    return false; // Labels must not end with a hyphen.
455                }
456                label_length = 0;
457            }
458
459            _ => {
460                return false;
461            }
462        }
463        is_first_byte = false;
464
465        if input.at_end() {
466            break;
467        }
468    }
469
470    // Only reference IDs, not presented IDs or name constraints, may be
471    // absolute.
472    if label_length == 0 && id_role != IdRole::Reference {
473        return false;
474    }
475
476    if label_ends_with_hyphen {
477        return false; // Labels must not end with a hyphen.
478    }
479
480    if label_is_all_numeric {
481        return false; // Last label must not be all numeric.
482    }
483
484    if is_wildcard {
485        // If the DNS ID ends with a dot, the last dot signifies an absolute ID.
486        let label_count = if label_length == 0 {
487            dot_count
488        } else {
489            dot_count + 1
490        };
491
492        // Like NSS, require at least two labels to follow the wildcard label.
493        // TODO: Allow the TrustDomain to control this on a per-eTLD+1 basis,
494        // similar to Chromium. Even then, it might be better to still enforce
495        // that there are at least two labels after the wildcard.
496        if label_count < 3 {
497            return false;
498        }
499    }
500
501    true
502}
503
504#[cfg(test)]
505mod tests {
506    use super::*;
507
508    #[allow(clippy::type_complexity)]
509    const PRESENTED_MATCHES_REFERENCE: &[(&[u8], &[u8], Result<bool, Error>)] = &[
510        (b"", b"a", Err(Error::MalformedDnsIdentifier)),
511        (b"a", b"a", Ok(true)),
512        (b"b", b"a", Ok(false)),
513        (b"*.b.a", b"c.b.a", Ok(true)),
514        (b"*.b.a", b"b.a", Ok(false)),
515        (b"*.b.a", b"b.a.", Ok(false)),
516        // Wildcard not in leftmost label
517        (b"d.c.b.a", b"d.c.b.a", Ok(true)),
518        (b"d.*.b.a", b"d.c.b.a", Err(Error::MalformedDnsIdentifier)),
519        (b"d.c*.b.a", b"d.c.b.a", Err(Error::MalformedDnsIdentifier)),
520        (b"d.c*.b.a", b"d.cc.b.a", Err(Error::MalformedDnsIdentifier)),
521        // case sensitivity
522        (
523            b"abcdefghijklmnopqrstuvwxyz",
524            b"ABCDEFGHIJKLMNOPQRSTUVWXYZ",
525            Ok(true),
526        ),
527        (
528            b"ABCDEFGHIJKLMNOPQRSTUVWXYZ",
529            b"abcdefghijklmnopqrstuvwxyz",
530            Ok(true),
531        ),
532        (b"aBc", b"Abc", Ok(true)),
533        // digits
534        (b"a1", b"a1", Ok(true)),
535        // A trailing dot indicates an absolute name, and absolute names can match
536        // relative names, and vice-versa.
537        (b"example", b"example", Ok(true)),
538        (b"example.", b"example.", Err(Error::MalformedDnsIdentifier)),
539        (b"example", b"example.", Ok(true)),
540        (b"example.", b"example", Err(Error::MalformedDnsIdentifier)),
541        (b"example.com", b"example.com", Ok(true)),
542        (
543            b"example.com.",
544            b"example.com.",
545            Err(Error::MalformedDnsIdentifier),
546        ),
547        (b"example.com", b"example.com.", Ok(true)),
548        (
549            b"example.com.",
550            b"example.com",
551            Err(Error::MalformedDnsIdentifier),
552        ),
553        (
554            b"example.com..",
555            b"example.com.",
556            Err(Error::MalformedDnsIdentifier),
557        ),
558        (
559            b"example.com..",
560            b"example.com",
561            Err(Error::MalformedDnsIdentifier),
562        ),
563        (
564            b"example.com...",
565            b"example.com.",
566            Err(Error::MalformedDnsIdentifier),
567        ),
568        // xn-- IDN prefix
569        (b"x*.b.a", b"xa.b.a", Err(Error::MalformedDnsIdentifier)),
570        (b"x*.b.a", b"xna.b.a", Err(Error::MalformedDnsIdentifier)),
571        (b"x*.b.a", b"xn-a.b.a", Err(Error::MalformedDnsIdentifier)),
572        (b"x*.b.a", b"xn--a.b.a", Err(Error::MalformedDnsIdentifier)),
573        (b"xn*.b.a", b"xn--a.b.a", Err(Error::MalformedDnsIdentifier)),
574        (
575            b"xn-*.b.a",
576            b"xn--a.b.a",
577            Err(Error::MalformedDnsIdentifier),
578        ),
579        (
580            b"xn--*.b.a",
581            b"xn--a.b.a",
582            Err(Error::MalformedDnsIdentifier),
583        ),
584        (b"xn*.b.a", b"xn--a.b.a", Err(Error::MalformedDnsIdentifier)),
585        (
586            b"xn-*.b.a",
587            b"xn--a.b.a",
588            Err(Error::MalformedDnsIdentifier),
589        ),
590        (
591            b"xn--*.b.a",
592            b"xn--a.b.a",
593            Err(Error::MalformedDnsIdentifier),
594        ),
595        (
596            b"xn---*.b.a",
597            b"xn--a.b.a",
598            Err(Error::MalformedDnsIdentifier),
599        ),
600        // "*" cannot expand to nothing.
601        (b"c*.b.a", b"c.b.a", Err(Error::MalformedDnsIdentifier)),
602        // --------------------------------------------------------------------------
603        // The rest of these are test cases adapted from Chromium's
604        // x509_certificate_unittest.cc. The parameter order is the opposite in
605        // Chromium's tests. Also, they Ok tests were modified to fit into this
606        // framework or due to intentional differences between mozilla::pkix and
607        // Chromium.
608        (b"foo.com", b"foo.com", Ok(true)),
609        (b"f", b"f", Ok(true)),
610        (b"i", b"h", Ok(false)),
611        (b"*.foo.com", b"bar.foo.com", Ok(true)),
612        (b"*.test.fr", b"www.test.fr", Ok(true)),
613        (b"*.test.FR", b"wwW.tESt.fr", Ok(true)),
614        (b".uk", b"f.uk", Err(Error::MalformedDnsIdentifier)),
615        (
616            b"?.bar.foo.com",
617            b"w.bar.foo.com",
618            Err(Error::MalformedDnsIdentifier),
619        ),
620        (
621            b"(www|ftp).foo.com",
622            b"www.foo.com",
623            Err(Error::MalformedDnsIdentifier),
624        ), // regex!
625        (
626            b"www.foo.com\0",
627            b"www.foo.com",
628            Err(Error::MalformedDnsIdentifier),
629        ),
630        (
631            b"www.foo.com\0*.foo.com",
632            b"www.foo.com",
633            Err(Error::MalformedDnsIdentifier),
634        ),
635        (b"ww.house.example", b"www.house.example", Ok(false)),
636        (b"www.test.org", b"test.org", Ok(false)),
637        (b"*.test.org", b"test.org", Ok(false)),
638        (b"*.org", b"test.org", Err(Error::MalformedDnsIdentifier)),
639        // '*' must be the only character in the wildcard label
640        (
641            b"w*.bar.foo.com",
642            b"w.bar.foo.com",
643            Err(Error::MalformedDnsIdentifier),
644        ),
645        (
646            b"ww*ww.bar.foo.com",
647            b"www.bar.foo.com",
648            Err(Error::MalformedDnsIdentifier),
649        ),
650        (
651            b"ww*ww.bar.foo.com",
652            b"wwww.bar.foo.com",
653            Err(Error::MalformedDnsIdentifier),
654        ),
655        (
656            b"w*w.bar.foo.com",
657            b"wwww.bar.foo.com",
658            Err(Error::MalformedDnsIdentifier),
659        ),
660        (
661            b"w*w.bar.foo.c0m",
662            b"wwww.bar.foo.com",
663            Err(Error::MalformedDnsIdentifier),
664        ),
665        (
666            b"wa*.bar.foo.com",
667            b"WALLY.bar.foo.com",
668            Err(Error::MalformedDnsIdentifier),
669        ),
670        (
671            b"*Ly.bar.foo.com",
672            b"wally.bar.foo.com",
673            Err(Error::MalformedDnsIdentifier),
674        ),
675        // Chromium does URL decoding of the reference ID, but we don't, and we also
676        // require that the reference ID is valid, so we can't test these two.
677        //     (b"www.foo.com", b"ww%57.foo.com", Ok(true)),
678        //     (b"www&.foo.com", b"www%26.foo.com", Ok(true)),
679        (b"*.test.de", b"www.test.co.jp", Ok(false)),
680        (
681            b"*.jp",
682            b"www.test.co.jp",
683            Err(Error::MalformedDnsIdentifier),
684        ),
685        (b"www.test.co.uk", b"www.test.co.jp", Ok(false)),
686        (
687            b"www.*.co.jp",
688            b"www.test.co.jp",
689            Err(Error::MalformedDnsIdentifier),
690        ),
691        (b"www.bar.foo.com", b"www.bar.foo.com", Ok(true)),
692        (b"*.foo.com", b"www.bar.foo.com", Ok(false)),
693        (
694            b"*.*.foo.com",
695            b"www.bar.foo.com",
696            Err(Error::MalformedDnsIdentifier),
697        ),
698        // Our matcher requires the reference ID to be a valid DNS name, so we cannot
699        // test this case.
700        //     (b"*.*.bar.foo.com", b"*..bar.foo.com", Ok(false)),
701        (b"www.bath.org", b"www.bath.org", Ok(true)),
702        // Our matcher requires the reference ID to be a valid DNS name, so we cannot
703        // test these cases.
704        // DNS_ID_MISMATCH("www.bath.org", ""),
705        //     (b"www.bath.org", b"20.30.40.50", Ok(false)),
706        //     (b"www.bath.org", b"66.77.88.99", Ok(false)),
707
708        // IDN tests
709        (
710            b"xn--poema-9qae5a.com.br",
711            b"xn--poema-9qae5a.com.br",
712            Ok(true),
713        ),
714        (
715            b"*.xn--poema-9qae5a.com.br",
716            b"www.xn--poema-9qae5a.com.br",
717            Ok(true),
718        ),
719        (
720            b"*.xn--poema-9qae5a.com.br",
721            b"xn--poema-9qae5a.com.br",
722            Ok(false),
723        ),
724        (
725            b"xn--poema-*.com.br",
726            b"xn--poema-9qae5a.com.br",
727            Err(Error::MalformedDnsIdentifier),
728        ),
729        (
730            b"xn--*-9qae5a.com.br",
731            b"xn--poema-9qae5a.com.br",
732            Err(Error::MalformedDnsIdentifier),
733        ),
734        (
735            b"*--poema-9qae5a.com.br",
736            b"xn--poema-9qae5a.com.br",
737            Err(Error::MalformedDnsIdentifier),
738        ),
739        // The following are adapted from the examples quoted from
740        //   http://tools.ietf.org/html/rfc6125#section-6.4.3
741        // (e.g., *.example.com would match foo.example.com but
742        // not bar.foo.example.com or example.com).
743        (b"*.example.com", b"foo.example.com", Ok(true)),
744        (b"*.example.com", b"bar.foo.example.com", Ok(false)),
745        (b"*.example.com", b"example.com", Ok(false)),
746        (
747            b"baz*.example.net",
748            b"baz1.example.net",
749            Err(Error::MalformedDnsIdentifier),
750        ),
751        (
752            b"*baz.example.net",
753            b"foobaz.example.net",
754            Err(Error::MalformedDnsIdentifier),
755        ),
756        (
757            b"b*z.example.net",
758            b"buzz.example.net",
759            Err(Error::MalformedDnsIdentifier),
760        ),
761        // Wildcards should not be valid for public registry controlled domains,
762        // and unknown/unrecognized domains, at least three domain components must
763        // be present. For mozilla::pkix and NSS, there must always be at least two
764        // labels after the wildcard label.
765        (b"*.test.example", b"www.test.example", Ok(true)),
766        (b"*.example.co.uk", b"test.example.co.uk", Ok(true)),
767        (
768            b"*.example",
769            b"test.example",
770            Err(Error::MalformedDnsIdentifier),
771        ),
772        // The result is different than Chromium, because Chromium takes into account
773        // the additional knowledge it has that "co.uk" is a TLD. mozilla::pkix does
774        // not know that.
775        (b"*.co.uk", b"example.co.uk", Ok(true)),
776        (b"*.com", b"foo.com", Err(Error::MalformedDnsIdentifier)),
777        (b"*.us", b"foo.us", Err(Error::MalformedDnsIdentifier)),
778        (b"*", b"foo", Err(Error::MalformedDnsIdentifier)),
779        // IDN variants of wildcards and registry controlled domains.
780        (
781            b"*.xn--poema-9qae5a.com.br",
782            b"www.xn--poema-9qae5a.com.br",
783            Ok(true),
784        ),
785        (
786            b"*.example.xn--mgbaam7a8h",
787            b"test.example.xn--mgbaam7a8h",
788            Ok(true),
789        ),
790        // RFC6126 allows this, and NSS accepts it, but Chromium disallows it.
791        // TODO: File bug against Chromium.
792        (b"*.com.br", b"xn--poema-9qae5a.com.br", Ok(true)),
793        (
794            b"*.xn--mgbaam7a8h",
795            b"example.xn--mgbaam7a8h",
796            Err(Error::MalformedDnsIdentifier),
797        ),
798        // Wildcards should be permissible for 'private' registry-controlled
799        // domains. (In mozilla::pkix, we do not know if it is a private registry-
800        // controlled domain or not.)
801        (b"*.appspot.com", b"www.appspot.com", Ok(true)),
802        (b"*.s3.amazonaws.com", b"foo.s3.amazonaws.com", Ok(true)),
803        // Multiple wildcards are not valid.
804        (
805            b"*.*.com",
806            b"foo.example.com",
807            Err(Error::MalformedDnsIdentifier),
808        ),
809        (
810            b"*.bar.*.com",
811            b"foo.bar.example.com",
812            Err(Error::MalformedDnsIdentifier),
813        ),
814        // Absolute vs relative DNS name tests. Although not explicitly specified
815        // in RFC 6125, absolute reference names (those ending in a .) should
816        // match either absolute or relative presented names.
817        // TODO: File errata against RFC 6125 about this.
818        (b"foo.com.", b"foo.com", Err(Error::MalformedDnsIdentifier)),
819        (b"foo.com", b"foo.com.", Ok(true)),
820        (b"foo.com.", b"foo.com.", Err(Error::MalformedDnsIdentifier)),
821        (b"f.", b"f", Err(Error::MalformedDnsIdentifier)),
822        (b"f", b"f.", Ok(true)),
823        (b"f.", b"f.", Err(Error::MalformedDnsIdentifier)),
824        (
825            b"*.bar.foo.com.",
826            b"www-3.bar.foo.com",
827            Err(Error::MalformedDnsIdentifier),
828        ),
829        (b"*.bar.foo.com", b"www-3.bar.foo.com.", Ok(true)),
830        (
831            b"*.bar.foo.com.",
832            b"www-3.bar.foo.com.",
833            Err(Error::MalformedDnsIdentifier),
834        ),
835        // We require the reference ID to be a valid DNS name, so we cannot test this
836        // case.
837        //     (b".", b".", Ok(false)),
838        (
839            b"*.com.",
840            b"example.com",
841            Err(Error::MalformedDnsIdentifier),
842        ),
843        (
844            b"*.com",
845            b"example.com.",
846            Err(Error::MalformedDnsIdentifier),
847        ),
848        (
849            b"*.com.",
850            b"example.com.",
851            Err(Error::MalformedDnsIdentifier),
852        ),
853        (b"*.", b"foo.", Err(Error::MalformedDnsIdentifier)),
854        (b"*.", b"foo", Err(Error::MalformedDnsIdentifier)),
855        // The result is different than Chromium because we don't know that co.uk is
856        // a TLD.
857        (
858            b"*.co.uk.",
859            b"foo.co.uk",
860            Err(Error::MalformedDnsIdentifier),
861        ),
862        (
863            b"*.co.uk.",
864            b"foo.co.uk.",
865            Err(Error::MalformedDnsIdentifier),
866        ),
867    ];
868
869    #[test]
870    fn presented_matches_reference_test() {
871        for &(presented, reference, expected_result) in PRESENTED_MATCHES_REFERENCE {
872            let actual_result = presented_id_matches_reference_id(
873                untrusted::Input::from(presented),
874                IdRole::Reference,
875                untrusted::Input::from(reference),
876            );
877            assert_eq!(
878                actual_result, expected_result,
879                "presented_id_matches_reference_id(\"{:?}\", \"{:?}\")",
880                presented, reference
881            );
882        }
883    }
884
885    // (presented_name, constraint, expected_matches)
886    #[allow(clippy::type_complexity)]
887    const PRESENTED_MATCHES_CONSTRAINT: &[(&[u8], &[u8], Result<bool, Error>)] = &[
888        // No absolute presented IDs allowed
889        (b".", b"", Err(Error::MalformedDnsIdentifier)),
890        (b"www.example.com.", b"", Err(Error::MalformedDnsIdentifier)),
891        (
892            b"www.example.com.",
893            b"www.example.com.",
894            Err(Error::MalformedDnsIdentifier),
895        ),
896        // No absolute constraints allowed
897        (
898            b"www.example.com",
899            b".",
900            Err(Error::MalformedNameConstraint),
901        ),
902        (
903            b"www.example.com",
904            b"www.example.com.",
905            Err(Error::MalformedNameConstraint),
906        ),
907        // No wildcard in constraints allowed
908        (
909            b"www.example.com",
910            b"*.example.com",
911            Err(Error::MalformedNameConstraint),
912        ),
913        // No empty presented IDs allowed
914        (b"", b"", Err(Error::MalformedDnsIdentifier)),
915        // Empty constraints match everything allowed
916        (b"example.com", b"", Ok(true)),
917        (b"*.example.com", b"", Ok(true)),
918        // Constraints that start with a dot
919        (b"www.example.com", b".example.com", Ok(true)),
920        (b"www.example.com", b".EXAMPLE.COM", Ok(true)),
921        (b"www.example.com", b".axample.com", Ok(false)),
922        (b"www.example.com", b".xample.com", Ok(false)),
923        (b"www.example.com", b".exampl.com", Ok(false)),
924        (b"badexample.com", b".example.com", Ok(false)),
925        // Constraints that do not start with a dot
926        (b"www.example.com", b"example.com", Ok(true)),
927        (b"www.example.com", b"EXAMPLE.COM", Ok(true)),
928        (b"www.example.com", b"axample.com", Ok(false)),
929        (b"www.example.com", b"xample.com", Ok(false)),
930        (b"www.example.com", b"exampl.com", Ok(false)),
931        (b"badexample.com", b"example.com", Ok(false)),
932        // Presented IDs with wildcard
933        (b"*.example.com", b".example.com", Ok(true)),
934        (b"*.example.com", b"example.com", Ok(true)),
935        (b"*.example.com", b"www.example.com", Ok(true)),
936        (b"*.example.com", b"www.EXAMPLE.COM", Ok(true)),
937        (b"*.example.com", b"www.axample.com", Ok(false)),
938        (b"*.example.com", b".xample.com", Ok(false)),
939        (b"*.example.com", b"xample.com", Ok(false)),
940        (b"*.example.com", b".exampl.com", Ok(false)),
941        (b"*.example.com", b"exampl.com", Ok(false)),
942        // Matching IDs
943        (b"www.example.com", b"www.example.com", Ok(true)),
944    ];
945
946    #[test]
947    fn presented_matches_constraint_test() {
948        for &(presented, constraint, expected_result) in PRESENTED_MATCHES_CONSTRAINT {
949            let actual_result = presented_id_matches_reference_id(
950                untrusted::Input::from(presented),
951                IdRole::NameConstraint,
952                untrusted::Input::from(constraint),
953            );
954            assert_eq!(
955                actual_result, expected_result,
956                "presented_id_matches_constraint(\"{:?}\", \"{:?}\")",
957                presented, constraint,
958            );
959        }
960    }
961}