1 // Copyright 2015 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 use crate::{
16     cert::{Cert, EndEntityOrCA},
17     der, Error,
18 };
19 use core;
20 
21 /// A DNS Name suitable for use in the TLS Server Name Indication (SNI)
22 /// extension and/or for use as the reference hostname for which to verify a
23 /// certificate.
24 ///
25 /// A `DNSName` is guaranteed to be syntactically valid. The validity rules are
26 /// specified in [RFC 5280 Section 7.2], except that underscores are also
27 /// allowed.
28 ///
29 /// `DNSName` stores a copy of the input it was constructed from in a `String`
30 /// and so it is only available when the `std` default feature is enabled.
31 ///
32 /// `Eq`, `PartialEq`, etc. are not implemented because name comparison
33 /// frequently should be done case-insensitively and/or with other caveats that
34 /// depend on the specific circumstances in which the comparison is done.
35 ///
36 /// [RFC 5280 Section 7.2]: https://tools.ietf.org/html/rfc5280#section-7.2
37 #[cfg(feature = "std")]
38 #[derive(Clone, Debug, Eq, PartialEq, Hash)]
39 pub struct DNSName(String);
40 
41 #[cfg(feature = "std")]
42 impl DNSName {
43     /// Returns a `DNSNameRef` that refers to this `DNSName`.
as_ref(&self) -> DNSNameRef44     pub fn as_ref(&self) -> DNSNameRef { DNSNameRef(self.0.as_bytes()) }
45 }
46 
47 #[cfg(feature = "std")]
48 impl AsRef<str> for DNSName {
as_ref(&self) -> &str49     fn as_ref(&self) -> &str { self.0.as_ref() }
50 }
51 
52 // Deprecated
53 #[cfg(feature = "std")]
54 impl From<DNSNameRef<'_>> for DNSName {
from(dns_name: DNSNameRef) -> Self55     fn from(dns_name: DNSNameRef) -> Self { dns_name.to_owned() }
56 }
57 
58 /// A reference to a DNS Name suitable for use in the TLS Server Name Indication
59 /// (SNI) extension and/or for use as the reference hostname for which to verify
60 /// a certificate.
61 ///
62 /// A `DNSNameRef` is guaranteed to be syntactically valid. The validity rules
63 /// are specified in [RFC 5280 Section 7.2], except that underscores are also
64 /// allowed.
65 ///
66 /// `Eq`, `PartialEq`, etc. are not implemented because name comparison
67 /// frequently should be done case-insensitively and/or with other caveats that
68 /// depend on the specific circumstances in which the comparison is done.
69 ///
70 /// [RFC 5280 Section 7.2]: https://tools.ietf.org/html/rfc5280#section-7.2
71 #[derive(Clone, Copy)]
72 pub struct DNSNameRef<'a>(&'a [u8]);
73 
74 /// An error indicating that a `DNSNameRef` could not built because the input
75 /// is not a syntactically-valid DNS Name.
76 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
77 pub struct InvalidDNSNameError;
78 
79 impl core::fmt::Display for InvalidDNSNameError {
fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result80     fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { write!(f, "{:?}", self) }
81 }
82 
83 #[cfg(feature = "std")]
84 impl ::std::error::Error for InvalidDNSNameError {}
85 
86 impl<'a> DNSNameRef<'a> {
87     /// Constructs a `DNSNameRef` from the given input if the input is a
88     /// syntactically-valid DNS name.
try_from_ascii(dns_name: &'a [u8]) -> Result<Self, InvalidDNSNameError>89     pub fn try_from_ascii(dns_name: &'a [u8]) -> Result<Self, InvalidDNSNameError> {
90         if !is_valid_reference_dns_id(untrusted::Input::from(dns_name)) {
91             return Err(InvalidDNSNameError);
92         }
93 
94         Ok(Self(dns_name))
95     }
96 
97     /// Constructs a `DNSNameRef` from the given input if the input is a
98     /// syntactically-valid DNS name.
try_from_ascii_str(dns_name: &'a str) -> Result<Self, InvalidDNSNameError>99     pub fn try_from_ascii_str(dns_name: &'a str) -> Result<Self, InvalidDNSNameError> {
100         Self::try_from_ascii(dns_name.as_bytes())
101     }
102 
103     /// Constructs a `DNSName` from this `DNSNameRef`
104     #[cfg(feature = "std")]
to_owned(&self) -> DNSName105     pub fn to_owned(&self) -> DNSName {
106         // DNSNameRef is already guaranteed to be valid ASCII, which is a
107         // subset of UTF-8.
108         let s: &str = self.clone().into();
109         DNSName(s.to_ascii_lowercase())
110     }
111 }
112 
113 #[cfg(feature = "std")]
114 impl core::fmt::Debug for DNSNameRef<'_> {
fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error>115     fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> {
116         let lowercase = self.clone().to_owned();
117         f.debug_tuple("DNSNameRef").field(&lowercase.0).finish()
118     }
119 }
120 
121 impl<'a> From<DNSNameRef<'a>> for &'a str {
from(DNSNameRef(d): DNSNameRef<'a>) -> Self122     fn from(DNSNameRef(d): DNSNameRef<'a>) -> Self {
123         // The unwrap won't fail because DNSNameRefs are guaranteed to be ASCII
124         // and ASCII is a subset of UTF-8.
125         core::str::from_utf8(d).unwrap()
126     }
127 }
128 
verify_cert_dns_name( cert: &super::EndEntityCert, DNSNameRef(dns_name): DNSNameRef, ) -> Result<(), Error>129 pub fn verify_cert_dns_name(
130     cert: &super::EndEntityCert, DNSNameRef(dns_name): DNSNameRef,
131 ) -> Result<(), Error> {
132     let cert = &cert.inner;
133     let dns_name = untrusted::Input::from(dns_name);
134     iterate_names(
135         cert.subject,
136         cert.subject_alt_name,
137         Err(Error::CertNotValidForName),
138         &|name| {
139             match name {
140                 GeneralName::DNSName(presented_id) =>
141                     match presented_dns_id_matches_reference_dns_id(presented_id, dns_name) {
142                         Some(true) => {
143                             return NameIteration::Stop(Ok(()));
144                         },
145                         Some(false) => (),
146                         None => {
147                             return NameIteration::Stop(Err(Error::BadDER));
148                         },
149                     },
150                 _ => (),
151             }
152             NameIteration::KeepGoing
153         },
154     )
155 }
156 
157 // https://tools.ietf.org/html/rfc5280#section-4.2.1.10
check_name_constraints( input: Option<&mut untrusted::Reader>, subordinate_certs: &Cert, ) -> Result<(), Error>158 pub fn check_name_constraints(
159     input: Option<&mut untrusted::Reader>, subordinate_certs: &Cert,
160 ) -> Result<(), Error> {
161     let input = match input {
162         Some(input) => input,
163         None => {
164             return Ok(());
165         },
166     };
167 
168     fn parse_subtrees<'b>(
169         inner: &mut untrusted::Reader<'b>, subtrees_tag: der::Tag,
170     ) -> Result<Option<untrusted::Input<'b>>, Error> {
171         if !inner.peek(subtrees_tag as u8) {
172             return Ok(None);
173         }
174         let subtrees = der::nested(inner, subtrees_tag, Error::BadDER, |tagged| {
175             der::expect_tag_and_get_value(tagged, der::Tag::Sequence)
176         })?;
177         Ok(Some(subtrees))
178     }
179 
180     let permitted_subtrees = parse_subtrees(input, der::Tag::ContextSpecificConstructed0)?;
181     let excluded_subtrees = parse_subtrees(input, der::Tag::ContextSpecificConstructed1)?;
182 
183     let mut child = subordinate_certs;
184     loop {
185         iterate_names(child.subject, child.subject_alt_name, Ok(()), &|name| {
186             check_presented_id_conforms_to_constraints(name, permitted_subtrees, excluded_subtrees)
187         })?;
188 
189         child = match child.ee_or_ca {
190             EndEntityOrCA::CA(child_cert) => child_cert,
191             EndEntityOrCA::EndEntity => {
192                 break;
193             },
194         };
195     }
196 
197     Ok(())
198 }
199 
check_presented_id_conforms_to_constraints( name: GeneralName, permitted_subtrees: Option<untrusted::Input>, excluded_subtrees: Option<untrusted::Input>, ) -> NameIteration200 fn check_presented_id_conforms_to_constraints(
201     name: GeneralName, permitted_subtrees: Option<untrusted::Input>,
202     excluded_subtrees: Option<untrusted::Input>,
203 ) -> NameIteration {
204     match check_presented_id_conforms_to_constraints_in_subtree(
205         name,
206         Subtrees::PermittedSubtrees,
207         permitted_subtrees,
208     ) {
209         stop @ NameIteration::Stop(..) => {
210             return stop;
211         },
212         NameIteration::KeepGoing => (),
213     };
214 
215     check_presented_id_conforms_to_constraints_in_subtree(
216         name,
217         Subtrees::ExcludedSubtrees,
218         excluded_subtrees,
219     )
220 }
221 
222 #[derive(Clone, Copy)]
223 enum Subtrees {
224     PermittedSubtrees,
225     ExcludedSubtrees,
226 }
227 
check_presented_id_conforms_to_constraints_in_subtree( name: GeneralName, subtrees: Subtrees, constraints: Option<untrusted::Input>, ) -> NameIteration228 fn check_presented_id_conforms_to_constraints_in_subtree(
229     name: GeneralName, subtrees: Subtrees, constraints: Option<untrusted::Input>,
230 ) -> NameIteration {
231     let mut constraints = match constraints {
232         Some(constraints) => untrusted::Reader::new(constraints),
233         None => {
234             return NameIteration::KeepGoing;
235         },
236     };
237 
238     let mut has_permitted_subtrees_match = false;
239     let mut has_permitted_subtrees_mismatch = false;
240 
241     loop {
242         // http://tools.ietf.org/html/rfc5280#section-4.2.1.10: "Within this
243         // profile, the minimum and maximum fields are not used with any name
244         // forms, thus, the minimum MUST be zero, and maximum MUST be absent."
245         //
246         // Since the default value isn't allowed to be encoded according to the
247         // DER encoding rules for DEFAULT, this is equivalent to saying that
248         // neither minimum or maximum must be encoded.
249         fn general_subtree<'b>(
250             input: &mut untrusted::Reader<'b>,
251         ) -> Result<GeneralName<'b>, Error> {
252             let general_subtree = der::expect_tag_and_get_value(input, der::Tag::Sequence)?;
253             general_subtree.read_all(Error::BadDER, |subtree| general_name(subtree))
254         }
255 
256         let base = match general_subtree(&mut constraints) {
257             Ok(base) => base,
258             Err(err) => {
259                 return NameIteration::Stop(Err(err));
260             },
261         };
262 
263         let matches = match (name, base) {
264             (GeneralName::DNSName(name), GeneralName::DNSName(base)) =>
265                 presented_dns_id_matches_dns_id_constraint(name, base).ok_or(Error::BadDER),
266 
267             (GeneralName::DirectoryName(name), GeneralName::DirectoryName(base)) =>
268                 presented_directory_name_matches_constraint(name, base, subtrees),
269 
270             (GeneralName::IPAddress(name), GeneralName::IPAddress(base)) =>
271                 presented_ip_address_matches_constraint(name, base),
272 
273             // RFC 4280 says "If a name constraints extension that is marked as
274             // critical imposes constraints on a particular name form, and an
275             // instance of that name form appears in the subject field or
276             // subjectAltName extension of a subsequent certificate, then the
277             // application MUST either process the constraint or reject the
278             // certificate." Later, the CABForum agreed to support non-critical
279             // constraints, so it is important to reject the cert without
280             // considering whether the name constraint it critical.
281             (GeneralName::Unsupported(name_tag), GeneralName::Unsupported(base_tag))
282                 if name_tag == base_tag =>
283                 Err(Error::NameConstraintViolation),
284 
285             _ => Ok(false),
286         };
287 
288         match (subtrees, matches) {
289             (Subtrees::PermittedSubtrees, Ok(true)) => {
290                 has_permitted_subtrees_match = true;
291             },
292 
293             (Subtrees::PermittedSubtrees, Ok(false)) => {
294                 has_permitted_subtrees_mismatch = true;
295             },
296 
297             (Subtrees::ExcludedSubtrees, Ok(true)) => {
298                 return NameIteration::Stop(Err(Error::NameConstraintViolation));
299             },
300 
301             (Subtrees::ExcludedSubtrees, Ok(false)) => (),
302 
303             (_, Err(err)) => {
304                 return NameIteration::Stop(Err(err));
305             },
306         }
307 
308         if constraints.at_end() {
309             break;
310         }
311     }
312 
313     if has_permitted_subtrees_mismatch && !has_permitted_subtrees_match {
314         // If there was any entry of the given type in permittedSubtrees, then
315         // it required that at least one of them must match. Since none of them
316         // did, we have a failure.
317         NameIteration::Stop(Err(Error::NameConstraintViolation))
318     } else {
319         NameIteration::KeepGoing
320     }
321 }
322 
323 // TODO: document this.
presented_directory_name_matches_constraint( name: untrusted::Input, constraint: untrusted::Input, subtrees: Subtrees, ) -> Result<bool, Error>324 fn presented_directory_name_matches_constraint(
325     name: untrusted::Input, constraint: untrusted::Input, subtrees: Subtrees,
326 ) -> Result<bool, Error> {
327     match subtrees {
328         Subtrees::PermittedSubtrees => Ok(name == constraint),
329         Subtrees::ExcludedSubtrees => Ok(true),
330     }
331 }
332 
333 // https://tools.ietf.org/html/rfc5280#section-4.2.1.10 says:
334 //
335 //     For IPv4 addresses, the iPAddress field of GeneralName MUST contain
336 //     eight (8) octets, encoded in the style of RFC 4632 (CIDR) to represent
337 //     an address range [RFC4632].  For IPv6 addresses, the iPAddress field
338 //     MUST contain 32 octets similarly encoded.  For example, a name
339 //     constraint for "class C" subnet 192.0.2.0 is represented as the
340 //     octets C0 00 02 00 FF FF FF 00, representing the CIDR notation
341 //     192.0.2.0/24 (mask 255.255.255.0).
presented_ip_address_matches_constraint( name: untrusted::Input, constraint: untrusted::Input, ) -> Result<bool, Error>342 fn presented_ip_address_matches_constraint(
343     name: untrusted::Input, constraint: untrusted::Input,
344 ) -> Result<bool, Error> {
345     if name.len() != 4 && name.len() != 16 {
346         return Err(Error::BadDER);
347     }
348     if constraint.len() != 8 && constraint.len() != 32 {
349         return Err(Error::BadDER);
350     }
351 
352     // an IPv4 address never matches an IPv6 constraint, and vice versa.
353     if name.len() * 2 != constraint.len() {
354         return Ok(false);
355     }
356 
357     let (constraint_address, constraint_mask) = constraint.read_all(Error::BadDER, |value| {
358         let address = value.read_bytes(constraint.len() / 2).unwrap();
359         let mask = value.read_bytes(constraint.len() / 2).unwrap();
360         Ok((address, mask))
361     })?;
362 
363     let mut name = untrusted::Reader::new(name);
364     let mut constraint_address = untrusted::Reader::new(constraint_address);
365     let mut constraint_mask = untrusted::Reader::new(constraint_mask);
366     loop {
367         let name_byte = name.read_byte().unwrap();
368         let constraint_address_byte = constraint_address.read_byte().unwrap();
369         let constraint_mask_byte = constraint_mask.read_byte().unwrap();
370         if ((name_byte ^ constraint_address_byte) & constraint_mask_byte) != 0 {
371             return Ok(false);
372         }
373         if name.at_end() {
374             break;
375         }
376     }
377 
378     return Ok(true);
379 }
380 
381 #[derive(Clone, Copy)]
382 enum NameIteration {
383     KeepGoing,
384     Stop(Result<(), Error>),
385 }
386 
iterate_names( subject: untrusted::Input, subject_alt_name: Option<untrusted::Input>, result_if_never_stopped_early: Result<(), Error>, f: &dyn Fn(GeneralName) -> NameIteration, ) -> Result<(), Error>387 fn iterate_names(
388     subject: untrusted::Input, subject_alt_name: Option<untrusted::Input>,
389     result_if_never_stopped_early: Result<(), Error>, f: &dyn Fn(GeneralName) -> NameIteration,
390 ) -> Result<(), Error> {
391     match subject_alt_name {
392         Some(subject_alt_name) => {
393             let mut subject_alt_name = untrusted::Reader::new(subject_alt_name);
394             // https://bugzilla.mozilla.org/show_bug.cgi?id=1143085: An empty
395             // subjectAltName is not legal, but some certificates have an empty
396             // subjectAltName. Since we don't support CN-IDs, the certificate
397             // will be rejected either way, but checking `at_end` before
398             // attempting to parse the first entry allows us to return a better
399             // error code.
400             while !subject_alt_name.at_end() {
401                 let name = general_name(&mut subject_alt_name)?;
402                 match f(name) {
403                     NameIteration::Stop(result) => {
404                         return result;
405                     },
406                     NameIteration::KeepGoing => (),
407                 }
408             }
409         },
410         None => (),
411     }
412 
413     match f(GeneralName::DirectoryName(subject)) {
414         NameIteration::Stop(result) => result,
415         NameIteration::KeepGoing => result_if_never_stopped_early,
416     }
417 }
418 
419 // It is *not* valid to derive `Eq`, `PartialEq, etc. for this type. In
420 // particular, for the types of `GeneralName`s that we don't understand, we
421 // don't even store the value. Also, the meaning of a `GeneralName` in a name
422 // constraint is different than the meaning of the identically-represented
423 // `GeneralName` in other contexts.
424 #[derive(Clone, Copy)]
425 enum GeneralName<'a> {
426     DNSName(untrusted::Input<'a>),
427     DirectoryName(untrusted::Input<'a>),
428     IPAddress(untrusted::Input<'a>),
429 
430     // The value is the `tag & ~(der::CONTEXT_SPECIFIC | der::CONSTRUCTED)` so
431     // that the name constraint checking matches tags regardless of whether
432     // those bits are set.
433     Unsupported(u8),
434 }
435 
general_name<'a>(input: &mut untrusted::Reader<'a>) -> Result<GeneralName<'a>, Error>436 fn general_name<'a>(input: &mut untrusted::Reader<'a>) -> Result<GeneralName<'a>, Error> {
437     use ring::io::der::{CONSTRUCTED, CONTEXT_SPECIFIC};
438     const OTHER_NAME_TAG: u8 = CONTEXT_SPECIFIC | CONSTRUCTED | 0;
439     const RFC822_NAME_TAG: u8 = CONTEXT_SPECIFIC | 1;
440     const DNS_NAME_TAG: u8 = CONTEXT_SPECIFIC | 2;
441     const X400_ADDRESS_TAG: u8 = CONTEXT_SPECIFIC | CONSTRUCTED | 3;
442     const DIRECTORY_NAME_TAG: u8 = CONTEXT_SPECIFIC | CONSTRUCTED | 4;
443     const EDI_PARTY_NAME_TAG: u8 = CONTEXT_SPECIFIC | CONSTRUCTED | 5;
444     const UNIFORM_RESOURCE_IDENTIFIER_TAG: u8 = CONTEXT_SPECIFIC | 6;
445     const IP_ADDRESS_TAG: u8 = CONTEXT_SPECIFIC | 7;
446     const REGISTERED_ID_TAG: u8 = CONTEXT_SPECIFIC | 8;
447 
448     let (tag, value) = der::read_tag_and_get_value(input)?;
449     let name = match tag {
450         DNS_NAME_TAG => GeneralName::DNSName(value),
451         DIRECTORY_NAME_TAG => GeneralName::DirectoryName(value),
452         IP_ADDRESS_TAG => GeneralName::IPAddress(value),
453 
454         OTHER_NAME_TAG
455         | RFC822_NAME_TAG
456         | X400_ADDRESS_TAG
457         | EDI_PARTY_NAME_TAG
458         | UNIFORM_RESOURCE_IDENTIFIER_TAG
459         | REGISTERED_ID_TAG => GeneralName::Unsupported(tag & !(CONTEXT_SPECIFIC | CONSTRUCTED)),
460 
461         _ => return Err(Error::BadDER),
462     };
463     Ok(name)
464 }
465 
presented_dns_id_matches_reference_dns_id( presented_dns_id: untrusted::Input, reference_dns_id: untrusted::Input, ) -> Option<bool>466 fn presented_dns_id_matches_reference_dns_id(
467     presented_dns_id: untrusted::Input, reference_dns_id: untrusted::Input,
468 ) -> Option<bool> {
469     presented_dns_id_matches_reference_dns_id_internal(
470         presented_dns_id,
471         IDRole::ReferenceID,
472         reference_dns_id,
473     )
474 }
475 
presented_dns_id_matches_dns_id_constraint( presented_dns_id: untrusted::Input, reference_dns_id: untrusted::Input, ) -> Option<bool>476 fn presented_dns_id_matches_dns_id_constraint(
477     presented_dns_id: untrusted::Input, reference_dns_id: untrusted::Input,
478 ) -> Option<bool> {
479     presented_dns_id_matches_reference_dns_id_internal(
480         presented_dns_id,
481         IDRole::NameConstraint,
482         reference_dns_id,
483     )
484 }
485 
486 // We do not distinguish between a syntactically-invalid presented_dns_id and
487 // one that is syntactically valid but does not match reference_dns_id; in both
488 // cases, the result is false.
489 //
490 // We assume that both presented_dns_id and reference_dns_id are encoded in
491 // such a way that US-ASCII (7-bit) characters are encoded in one byte and no
492 // encoding of a non-US-ASCII character contains a code point in the range
493 // 0-127. For example, UTF-8 is OK but UTF-16 is not.
494 //
495 // RFC6125 says that a wildcard label may be of the form <x>*<y>.<DNSID>, where
496 // <x> and/or <y> may be empty. However, NSS requires <y> to be empty, and we
497 // follow NSS's stricter policy by accepting wildcards only of the form
498 // <x>*.<DNSID>, where <x> may be empty.
499 //
500 // An relative presented DNS ID matches both an absolute reference ID and a
501 // relative reference ID. Absolute presented DNS IDs are not supported:
502 //
503 //      Presented ID   Reference ID  Result
504 //      -------------------------------------
505 //      example.com    example.com   Match
506 //      example.com.   example.com   Mismatch
507 //      example.com    example.com.  Match
508 //      example.com.   example.com.  Mismatch
509 //
510 // There are more subtleties documented inline in the code.
511 //
512 // Name constraints ///////////////////////////////////////////////////////////
513 //
514 // This is all RFC 5280 has to say about DNSName constraints:
515 //
516 //     DNS name restrictions are expressed as host.example.com.  Any DNS
517 //     name that can be constructed by simply adding zero or more labels to
518 //     the left-hand side of the name satisfies the name constraint.  For
519 //     example, www.host.example.com would satisfy the constraint but
520 //     host1.example.com would not.
521 //
522 // This lack of specificity has lead to a lot of uncertainty regarding
523 // subdomain matching. In particular, the following questions have been
524 // raised and answered:
525 //
526 //     Q: Does a presented identifier equal (case insensitive) to the name
527 //        constraint match the constraint? For example, does the presented
528 //        ID "host.example.com" match a "host.example.com" constraint?
529 //     A: Yes. RFC5280 says "by simply adding zero or more labels" and this
530 //        is the case of adding zero labels.
531 //
532 //     Q: When the name constraint does not start with ".", do subdomain
533 //        presented identifiers match it? For example, does the presented
534 //        ID "www.host.example.com" match a "host.example.com" constraint?
535 //     A: Yes. RFC5280 says "by simply adding zero or more labels" and this
536 //        is the case of adding more than zero labels. The example is the
537 //        one from RFC 5280.
538 //
539 //     Q: When the name constraint does not start with ".", does a
540 //        non-subdomain prefix match it? For example, does "bigfoo.bar.com"
541 //        match "foo.bar.com"? [4]
542 //     A: No. We interpret RFC 5280's language of "adding zero or more labels"
543 //        to mean that whole labels must be prefixed.
544 //
545 //     (Note that the above three scenarios are the same as the RFC 6265
546 //     domain matching rules [0].)
547 //
548 //     Q: Is a name constraint that starts with "." valid, and if so, what
549 //        semantics does it have? For example, does a presented ID of
550 //        "www.example.com" match a constraint of ".example.com"? Does a
551 //        presented ID of "example.com" match a constraint of ".example.com"?
552 //     A: This implementation, NSS[1], and SChannel[2] all support a
553 //        leading ".", but OpenSSL[3] does not yet. Amongst the
554 //        implementations that support it, a leading "." is legal and means
555 //        the same thing as when the "." is omitted, EXCEPT that a
556 //        presented identifier equal (case insensitive) to the name
557 //        constraint is not matched; i.e. presented DNSName identifiers
558 //        must be subdomains. Some CAs in Mozilla's CA program (e.g. HARICA)
559 //        have name constraints with the leading "." in their root
560 //        certificates. The name constraints imposed on DCISS by Mozilla also
561 //        have the it, so supporting this is a requirement for backward
562 //        compatibility, even if it is not yet standardized. So, for example, a
563 //        presented ID of "www.example.com" matches a constraint of
564 //        ".example.com" but a presented ID of "example.com" does not.
565 //
566 //     Q: Is there a way to prevent subdomain matches?
567 //     A: Yes.
568 //
569 //        Some people have proposed that dNSName constraints that do not
570 //        start with a "." should be restricted to exact (case insensitive)
571 //        matches. However, such a change of semantics from what RFC5280
572 //        specifies would be a non-backward-compatible change in the case of
573 //        permittedSubtrees constraints, and it would be a security issue for
574 //        excludedSubtrees constraints.
575 //
576 //        However, it can be done with a combination of permittedSubtrees and
577 //        excludedSubtrees, e.g. "example.com" in permittedSubtrees and
578 //        ".example.com" in excludedSubtrees.
579 //
580 //     Q: Are name constraints allowed to be specified as absolute names?
581 //        For example, does a presented ID of "example.com" match a name
582 //        constraint of "example.com." and vice versa.
583 //     A: Absolute names are not supported as presented IDs or name
584 //        constraints. Only reference IDs may be absolute.
585 //
586 //     Q: Is "" a valid DNSName constraint? If so, what does it mean?
587 //     A: Yes. Any valid presented DNSName can be formed "by simply adding zero
588 //        or more labels to the left-hand side" of "". In particular, an
589 //        excludedSubtrees DNSName constraint of "" forbids all DNSNames.
590 //
591 //     Q: Is "." a valid DNSName constraint? If so, what does it mean?
592 //     A: No, because absolute names are not allowed (see above).
593 //
594 // [0] RFC 6265 (Cookies) Domain Matching rules:
595 //     http://tools.ietf.org/html/rfc6265#section-5.1.3
596 // [1] NSS source code:
597 //     https://mxr.mozilla.org/nss/source/lib/certdb/genname.c?rev=2a7348f013cb#1209
598 // [2] Description of SChannel's behavior from Microsoft:
599 //     http://www.imc.org/ietf-pkix/mail-archive/msg04668.html
600 // [3] Proposal to add such support to OpenSSL:
601 //     http://www.mail-archive.com/openssl-dev%40openssl.org/msg36204.html
602 //     https://rt.openssl.org/Ticket/Display.html?id=3562
603 // [4] Feedback on the lack of clarify in the definition that never got
604 //     incorporated into the spec:
605 //     https://www.ietf.org/mail-archive/web/pkix/current/msg21192.html
presented_dns_id_matches_reference_dns_id_internal( presented_dns_id: untrusted::Input, reference_dns_id_role: IDRole, reference_dns_id: untrusted::Input, ) -> Option<bool>606 fn presented_dns_id_matches_reference_dns_id_internal(
607     presented_dns_id: untrusted::Input, reference_dns_id_role: IDRole,
608     reference_dns_id: untrusted::Input,
609 ) -> Option<bool> {
610     if !is_valid_dns_id(presented_dns_id, IDRole::PresentedID, AllowWildcards::Yes) {
611         return None;
612     }
613 
614     if !is_valid_dns_id(reference_dns_id, reference_dns_id_role, AllowWildcards::No) {
615         return None;
616     }
617 
618     let mut presented = untrusted::Reader::new(presented_dns_id);
619     let mut reference = untrusted::Reader::new(reference_dns_id);
620 
621     match reference_dns_id_role {
622         IDRole::ReferenceID => (),
623 
624         IDRole::NameConstraint if presented_dns_id.len() > reference_dns_id.len() => {
625             if reference_dns_id.len() == 0 {
626                 // An empty constraint matches everything.
627                 return Some(true);
628             }
629 
630             // If the reference ID starts with a dot then skip the prefix of
631             // the presented ID and start the comparison at the position of
632             // that dot. Examples:
633             //
634             //                                       Matches     Doesn't Match
635             //     -----------------------------------------------------------
636             //       original presented ID:  www.example.com    badexample.com
637             //                     skipped:  www                ba
638             //     presented ID w/o prefix:     .example.com      dexample.com
639             //                reference ID:     .example.com      .example.com
640             //
641             // If the reference ID does not start with a dot then we skip
642             // the prefix of the presented ID but also verify that the
643             // prefix ends with a dot. Examples:
644             //
645             //                                       Matches     Doesn't Match
646             //     -----------------------------------------------------------
647             //       original presented ID:  www.example.com    badexample.com
648             //                     skipped:  www                ba
649             //                 must be '.':     .                 d
650             //     presented ID w/o prefix:      example.com       example.com
651             //                reference ID:      example.com       example.com
652             //
653             if reference.peek(b'.') {
654                 if presented
655                     .skip(presented_dns_id.len() - reference_dns_id.len())
656                     .is_err()
657                 {
658                     unreachable!();
659                 }
660             } else {
661                 if presented
662                     .skip(presented_dns_id.len() - reference_dns_id.len() - 1)
663                     .is_err()
664                 {
665                     unreachable!();
666                 }
667                 if presented.read_byte() != Ok(b'.') {
668                     return Some(false);
669                 }
670             }
671         },
672 
673         IDRole::NameConstraint => (),
674 
675         IDRole::PresentedID => unreachable!(),
676     }
677 
678     // Only allow wildcard labels that consist only of '*'.
679     if presented.peek(b'*') {
680         if presented.skip(1).is_err() {
681             unreachable!();
682         }
683 
684         loop {
685             if reference.read_byte().is_err() {
686                 return Some(false);
687             }
688             if reference.peek(b'.') {
689                 break;
690             }
691         }
692     }
693 
694     loop {
695         let presented_byte = match (presented.read_byte(), reference.read_byte()) {
696             (Ok(p), Ok(r)) if ascii_lower(p) == ascii_lower(r) => p,
697             _ => {
698                 return Some(false);
699             },
700         };
701 
702         if presented.at_end() {
703             // Don't allow presented IDs to be absolute.
704             if presented_byte == b'.' {
705                 return None;
706             }
707             break;
708         }
709     }
710 
711     // Allow a relative presented DNS ID to match an absolute reference DNS ID,
712     // unless we're matching a name constraint.
713     if !reference.at_end() {
714         if reference_dns_id_role != IDRole::NameConstraint {
715             match reference.read_byte() {
716                 Ok(b'.') => (),
717                 _ => {
718                     return Some(false);
719                 },
720             };
721         }
722         if !reference.at_end() {
723             return Some(false);
724         }
725     }
726 
727     assert!(presented.at_end());
728     assert!(reference.at_end());
729 
730     return Some(true);
731 }
732 
733 #[inline]
ascii_lower(b: u8) -> u8734 fn ascii_lower(b: u8) -> u8 {
735     match b {
736         b'A'..=b'Z' => b + b'a' - b'A',
737         _ => b,
738     }
739 }
740 
741 #[derive(PartialEq)]
742 enum AllowWildcards {
743     No,
744     Yes,
745 }
746 
747 #[derive(Clone, Copy, PartialEq)]
748 enum IDRole {
749     ReferenceID,
750     PresentedID,
751     NameConstraint,
752 }
753 
is_valid_reference_dns_id(hostname: untrusted::Input) -> bool754 fn is_valid_reference_dns_id(hostname: untrusted::Input) -> bool {
755     is_valid_dns_id(hostname, IDRole::ReferenceID, AllowWildcards::No)
756 }
757 
758 // https://tools.ietf.org/html/rfc5280#section-4.2.1.6:
759 //
760 //   When the subjectAltName extension contains a domain name system
761 //   label, the domain name MUST be stored in the dNSName (an IA5String).
762 //   The name MUST be in the "preferred name syntax", as specified by
763 //   Section 3.5 of [RFC1034] and as modified by Section 2.1 of
764 //   [RFC1123].
765 //
766 // https://bugzilla.mozilla.org/show_bug.cgi?id=1136616: As an exception to the
767 // requirement above, underscores are also allowed in names for compatibility.
is_valid_dns_id( hostname: untrusted::Input, id_role: IDRole, allow_wildcards: AllowWildcards, ) -> bool768 fn is_valid_dns_id(
769     hostname: untrusted::Input, id_role: IDRole, allow_wildcards: AllowWildcards,
770 ) -> bool {
771     // https://blogs.msdn.microsoft.com/oldnewthing/20120412-00/?p=7873/
772     if hostname.len() > 253 {
773         return false;
774     }
775 
776     let mut input = untrusted::Reader::new(hostname);
777 
778     if id_role == IDRole::NameConstraint && input.at_end() {
779         return true;
780     }
781 
782     let mut dot_count = 0;
783     let mut label_length = 0;
784     let mut label_is_all_numeric = false;
785     let mut label_ends_with_hyphen = false;
786 
787     // Only presented IDs are allowed to have wildcard labels. And, like
788     // Chromium, be stricter than RFC 6125 requires by insisting that a
789     // wildcard label consist only of '*'.
790     let is_wildcard = allow_wildcards == AllowWildcards::Yes && input.peek(b'*');
791     let mut is_first_byte = !is_wildcard;
792     if is_wildcard {
793         if input.read_byte() != Ok(b'*') || input.read_byte() != Ok(b'.') {
794             return false;
795         }
796         dot_count += 1;
797     }
798 
799     loop {
800         const MAX_LABEL_LENGTH: usize = 63;
801 
802         match input.read_byte() {
803             Ok(b'-') => {
804                 if label_length == 0 {
805                     return false; // Labels must not start with a hyphen.
806                 }
807                 label_is_all_numeric = false;
808                 label_ends_with_hyphen = true;
809                 label_length += 1;
810                 if label_length > MAX_LABEL_LENGTH {
811                     return false;
812                 }
813             },
814 
815             Ok(b'0'..=b'9') => {
816                 if label_length == 0 {
817                     label_is_all_numeric = true;
818                 }
819                 label_ends_with_hyphen = false;
820                 label_length += 1;
821                 if label_length > MAX_LABEL_LENGTH {
822                     return false;
823                 }
824             },
825 
826             Ok(b'a'..=b'z') | Ok(b'A'..=b'Z') | Ok(b'_') => {
827                 label_is_all_numeric = false;
828                 label_ends_with_hyphen = false;
829                 label_length += 1;
830                 if label_length > MAX_LABEL_LENGTH {
831                     return false;
832                 }
833             },
834 
835             Ok(b'.') => {
836                 dot_count += 1;
837                 if label_length == 0 && (id_role != IDRole::NameConstraint || !is_first_byte) {
838                     return false;
839                 }
840                 if label_ends_with_hyphen {
841                     return false; // Labels must not end with a hyphen.
842                 }
843                 label_length = 0;
844             },
845 
846             _ => {
847                 return false;
848             },
849         }
850         is_first_byte = false;
851 
852         if input.at_end() {
853             break;
854         }
855     }
856 
857     // Only reference IDs, not presented IDs or name constraints, may be
858     // absolute.
859     if label_length == 0 && id_role != IDRole::ReferenceID {
860         return false;
861     }
862 
863     if label_ends_with_hyphen {
864         return false; // Labels must not end with a hyphen.
865     }
866 
867     if label_is_all_numeric {
868         return false; // Last label must not be all numeric.
869     }
870 
871     if is_wildcard {
872         // If the DNS ID ends with a dot, the last dot signifies an absolute ID.
873         let label_count = if label_length == 0 {
874             dot_count
875         } else {
876             dot_count + 1
877         };
878 
879         // Like NSS, require at least two labels to follow the wildcard label.
880         // TODO: Allow the TrustDomain to control this on a per-eTLD+1 basis,
881         // similar to Chromium. Even then, it might be better to still enforce
882         // that there are at least two labels after the wildcard.
883         if label_count < 3 {
884             return false;
885         }
886     }
887 
888     true
889 }
890 
891 #[cfg(test)]
892 mod tests {
893     use super::*;
894 
895     const PRESENTED_MATCHES_REFERENCE: &[(&[u8], &[u8], Option<bool>)] = &[
896         (b"", b"a", None),
897         (b"a", b"a", Some(true)),
898         (b"b", b"a", Some(false)),
899         (b"*.b.a", b"c.b.a", Some(true)),
900         (b"*.b.a", b"b.a", Some(false)),
901         (b"*.b.a", b"b.a.", Some(false)),
902         // Wildcard not in leftmost label
903         (b"d.c.b.a", b"d.c.b.a", Some(true)),
904         (b"d.*.b.a", b"d.c.b.a", None),
905         (b"d.c*.b.a", b"d.c.b.a", None),
906         (b"d.c*.b.a", b"d.cc.b.a", None),
907         // case sensitivity
908         (
909             b"abcdefghijklmnopqrstuvwxyz",
910             b"ABCDEFGHIJKLMNOPQRSTUVWXYZ",
911             Some(true),
912         ),
913         (
914             b"ABCDEFGHIJKLMNOPQRSTUVWXYZ",
915             b"abcdefghijklmnopqrstuvwxyz",
916             Some(true),
917         ),
918         (b"aBc", b"Abc", Some(true)),
919         // digits
920         (b"a1", b"a1", Some(true)),
921         // A trailing dot indicates an absolute name, and absolute names can match
922         // relative names, and vice-versa.
923         (b"example", b"example", Some(true)),
924         (b"example.", b"example.", None),
925         (b"example", b"example.", Some(true)),
926         (b"example.", b"example", None),
927         (b"example.com", b"example.com", Some(true)),
928         (b"example.com.", b"example.com.", None),
929         (b"example.com", b"example.com.", Some(true)),
930         (b"example.com.", b"example.com", None),
931         (b"example.com..", b"example.com.", None),
932         (b"example.com..", b"example.com", None),
933         (b"example.com...", b"example.com.", None),
934         // xn-- IDN prefix
935         (b"x*.b.a", b"xa.b.a", None),
936         (b"x*.b.a", b"xna.b.a", None),
937         (b"x*.b.a", b"xn-a.b.a", None),
938         (b"x*.b.a", b"xn--a.b.a", None),
939         (b"xn*.b.a", b"xn--a.b.a", None),
940         (b"xn-*.b.a", b"xn--a.b.a", None),
941         (b"xn--*.b.a", b"xn--a.b.a", None),
942         (b"xn*.b.a", b"xn--a.b.a", None),
943         (b"xn-*.b.a", b"xn--a.b.a", None),
944         (b"xn--*.b.a", b"xn--a.b.a", None),
945         (b"xn---*.b.a", b"xn--a.b.a", None),
946         // "*" cannot expand to nothing.
947         (b"c*.b.a", b"c.b.a", None),
948         // --------------------------------------------------------------------------
949         // The rest of these are test cases adapted from Chromium's
950         // x509_certificate_unittest.cc. The parameter order is the opposite in
951         // Chromium's tests. Also, they some tests were modified to fit into this
952         // framework or due to intentional differences between mozilla::pkix and
953         // Chromium.
954         (b"foo.com", b"foo.com", Some(true)),
955         (b"f", b"f", Some(true)),
956         (b"i", b"h", Some(false)),
957         (b"*.foo.com", b"bar.foo.com", Some(true)),
958         (b"*.test.fr", b"www.test.fr", Some(true)),
959         (b"*.test.FR", b"wwW.tESt.fr", Some(true)),
960         (b".uk", b"f.uk", None),
961         (b"?.bar.foo.com", b"w.bar.foo.com", None),
962         (b"(www|ftp).foo.com", b"www.foo.com", None), // regex!
963         (b"www.foo.com\0", b"www.foo.com", None),
964         (b"www.foo.com\0*.foo.com", b"www.foo.com", None),
965         (b"ww.house.example", b"www.house.example", Some(false)),
966         (b"www.test.org", b"test.org", Some(false)),
967         (b"*.test.org", b"test.org", Some(false)),
968         (b"*.org", b"test.org", None),
969         // '*' must be the only character in the wildcard label
970         (b"w*.bar.foo.com", b"w.bar.foo.com", None),
971         (b"ww*ww.bar.foo.com", b"www.bar.foo.com", None),
972         (b"ww*ww.bar.foo.com", b"wwww.bar.foo.com", None),
973         (b"w*w.bar.foo.com", b"wwww.bar.foo.com", None),
974         (b"w*w.bar.foo.c0m", b"wwww.bar.foo.com", None),
975         (b"wa*.bar.foo.com", b"WALLY.bar.foo.com", None),
976         (b"*Ly.bar.foo.com", b"wally.bar.foo.com", None),
977         // Chromium does URL decoding of the reference ID, but we don't, and we also
978         // require that the reference ID is valid, so we can't test these two.
979         //     (b"www.foo.com", b"ww%57.foo.com", Some(true)),
980         //     (b"www&.foo.com", b"www%26.foo.com", Some(true)),
981         (b"*.test.de", b"www.test.co.jp", Some(false)),
982         (b"*.jp", b"www.test.co.jp", None),
983         (b"www.test.co.uk", b"www.test.co.jp", Some(false)),
984         (b"www.*.co.jp", b"www.test.co.jp", None),
985         (b"www.bar.foo.com", b"www.bar.foo.com", Some(true)),
986         (b"*.foo.com", b"www.bar.foo.com", Some(false)),
987         (b"*.*.foo.com", b"www.bar.foo.com", None),
988         // Our matcher requires the reference ID to be a valid DNS name, so we cannot
989         // test this case.
990         //     (b"*.*.bar.foo.com", b"*..bar.foo.com", Some(false)),
991         (b"www.bath.org", b"www.bath.org", Some(true)),
992         // Our matcher requires the reference ID to be a valid DNS name, so we cannot
993         // test these cases.
994         // DNS_ID_MISMATCH("www.bath.org", ""),
995         //     (b"www.bath.org", b"20.30.40.50", Some(false)),
996         //     (b"www.bath.org", b"66.77.88.99", Some(false)),
997 
998         // IDN tests
999         (
1000             b"xn--poema-9qae5a.com.br",
1001             b"xn--poema-9qae5a.com.br",
1002             Some(true),
1003         ),
1004         (
1005             b"*.xn--poema-9qae5a.com.br",
1006             b"www.xn--poema-9qae5a.com.br",
1007             Some(true),
1008         ),
1009         (
1010             b"*.xn--poema-9qae5a.com.br",
1011             b"xn--poema-9qae5a.com.br",
1012             Some(false),
1013         ),
1014         (b"xn--poema-*.com.br", b"xn--poema-9qae5a.com.br", None),
1015         (b"xn--*-9qae5a.com.br", b"xn--poema-9qae5a.com.br", None),
1016         (b"*--poema-9qae5a.com.br", b"xn--poema-9qae5a.com.br", None),
1017         // The following are adapted from the examples quoted from
1018         //   http://tools.ietf.org/html/rfc6125#section-6.4.3
1019         // (e.g., *.example.com would match foo.example.com but
1020         // not bar.foo.example.com or example.com).
1021         (b"*.example.com", b"foo.example.com", Some(true)),
1022         (b"*.example.com", b"bar.foo.example.com", Some(false)),
1023         (b"*.example.com", b"example.com", Some(false)),
1024         (b"baz*.example.net", b"baz1.example.net", None),
1025         (b"*baz.example.net", b"foobaz.example.net", None),
1026         (b"b*z.example.net", b"buzz.example.net", None),
1027         // Wildcards should not be valid for public registry controlled domains,
1028         // and unknown/unrecognized domains, at least three domain components must
1029         // be present. For mozilla::pkix and NSS, there must always be at least two
1030         // labels after the wildcard label.
1031         (b"*.test.example", b"www.test.example", Some(true)),
1032         (b"*.example.co.uk", b"test.example.co.uk", Some(true)),
1033         (b"*.example", b"test.example", None),
1034         // The result is different than Chromium, because Chromium takes into account
1035         // the additional knowledge it has that "co.uk" is a TLD. mozilla::pkix does
1036         // not know that.
1037         (b"*.co.uk", b"example.co.uk", Some(true)),
1038         (b"*.com", b"foo.com", None),
1039         (b"*.us", b"foo.us", None),
1040         (b"*", b"foo", None),
1041         // IDN variants of wildcards and registry controlled domains.
1042         (
1043             b"*.xn--poema-9qae5a.com.br",
1044             b"www.xn--poema-9qae5a.com.br",
1045             Some(true),
1046         ),
1047         (
1048             b"*.example.xn--mgbaam7a8h",
1049             b"test.example.xn--mgbaam7a8h",
1050             Some(true),
1051         ),
1052         // RFC6126 allows this, and NSS accepts it, but Chromium disallows it.
1053         // TODO: File bug against Chromium.
1054         (b"*.com.br", b"xn--poema-9qae5a.com.br", Some(true)),
1055         (b"*.xn--mgbaam7a8h", b"example.xn--mgbaam7a8h", None),
1056         // Wildcards should be permissible for 'private' registry-controlled
1057         // domains. (In mozilla::pkix, we do not know if it is a private registry-
1058         // controlled domain or not.)
1059         (b"*.appspot.com", b"www.appspot.com", Some(true)),
1060         (b"*.s3.amazonaws.com", b"foo.s3.amazonaws.com", Some(true)),
1061         // Multiple wildcards are not valid.
1062         (b"*.*.com", b"foo.example.com", None),
1063         (b"*.bar.*.com", b"foo.bar.example.com", None),
1064         // Absolute vs relative DNS name tests. Although not explicitly specified
1065         // in RFC 6125, absolute reference names (those ending in a .) should
1066         // match either absolute or relative presented names.
1067         // TODO: File errata against RFC 6125 about this.
1068         (b"foo.com.", b"foo.com", None),
1069         (b"foo.com", b"foo.com.", Some(true)),
1070         (b"foo.com.", b"foo.com.", None),
1071         (b"f.", b"f", None),
1072         (b"f", b"f.", Some(true)),
1073         (b"f.", b"f.", None),
1074         (b"*.bar.foo.com.", b"www-3.bar.foo.com", None),
1075         (b"*.bar.foo.com", b"www-3.bar.foo.com.", Some(true)),
1076         (b"*.bar.foo.com.", b"www-3.bar.foo.com.", None),
1077         // We require the reference ID to be a valid DNS name, so we cannot test this
1078         // case.
1079         //     (b".", b".", Some(false)),
1080         (b"*.com.", b"example.com", None),
1081         (b"*.com", b"example.com.", None),
1082         (b"*.com.", b"example.com.", None),
1083         (b"*.", b"foo.", None),
1084         (b"*.", b"foo", None),
1085         // The result is different than Chromium because we don't know that co.uk is
1086         // a TLD.
1087         (b"*.co.uk.", b"foo.co.uk", None),
1088         (b"*.co.uk.", b"foo.co.uk.", None),
1089     ];
1090 
1091     #[test]
presented_matches_reference_test()1092     fn presented_matches_reference_test() {
1093         for &(presented, reference, expected_result) in PRESENTED_MATCHES_REFERENCE {
1094             use std::string::String;
1095 
1096             let actual_result = presented_dns_id_matches_reference_dns_id(
1097                 untrusted::Input::from(presented),
1098                 untrusted::Input::from(reference),
1099             );
1100             assert_eq!(
1101                 actual_result,
1102                 expected_result,
1103                 "presented_dns_id_matches_reference_dns_id(\"{}\", IDRole::ReferenceID, \"{}\")",
1104                 String::from_utf8_lossy(presented),
1105                 String::from_utf8_lossy(reference)
1106             );
1107         }
1108     }
1109 }
1110