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