webpki/name.rs
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
15use crate::{
16 cert::{Cert, EndEntityOrCA},
17 der, Error,
18};
19use 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)]
39pub struct DNSName(String);
40
41#[cfg(feature = "std")]
42impl DNSName {
43 /// Returns a `DNSNameRef` that refers to this `DNSName`.
44 pub fn as_ref(&self) -> DNSNameRef { DNSNameRef(self.0.as_bytes()) }
45}
46
47#[cfg(feature = "std")]
48impl AsRef<str> for DNSName {
49 fn as_ref(&self) -> &str { self.0.as_ref() }
50}
51
52// Deprecated
53#[cfg(feature = "std")]
54impl From<DNSNameRef<'_>> for DNSName {
55 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)]
72pub 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)]
77pub struct InvalidDNSNameError;
78
79impl core::fmt::Display for InvalidDNSNameError {
80 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { write!(f, "{:?}", self) }
81}
82
83#[cfg(feature = "std")]
84impl ::std::error::Error for InvalidDNSNameError {}
85
86impl<'a> DNSNameRef<'a> {
87 /// Constructs a `DNSNameRef` from the given input if the input is a
88 /// syntactically-valid DNS name.
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.
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")]
105 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")]
114impl core::fmt::Debug for DNSNameRef<'_> {
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
121impl<'a> From<DNSNameRef<'a>> for &'a str {
122 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
129pub 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
158pub 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
200fn 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)]
223enum Subtrees {
224 PermittedSubtrees,
225 ExcludedSubtrees,
226}
227
228fn 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.
324fn 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).
342fn 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)]
382enum NameIteration {
383 KeepGoing,
384 Stop(Result<(), Error>),
385}
386
387fn 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)]
425enum 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
436fn 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
466fn 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
476fn 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
606fn 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]
734fn 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)]
742enum AllowWildcards {
743 No,
744 Yes,
745}
746
747#[derive(Clone, Copy, PartialEq)]
748enum IDRole {
749 ReferenceID,
750 PresentedID,
751 NameConstraint,
752}
753
754fn 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.
768fn 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)]
892mod 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]
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}