1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This code is made available to you under your choice of the following sets
4  * of licensing terms:
5  */
6 /* This Source Code Form is subject to the terms of the Mozilla Public
7  * License, v. 2.0. If a copy of the MPL was not distributed with this
8  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9  */
10 /* Copyright 2014 Mozilla Contributors
11  *
12  * Licensed under the Apache License, Version 2.0 (the "License");
13  * you may not use this file except in compliance with the License.
14  * You may obtain a copy of the License at
15  *
16  *     http://www.apache.org/licenses/LICENSE-2.0
17  *
18  * Unless required by applicable law or agreed to in writing, software
19  * distributed under the License is distributed on an "AS IS" BASIS,
20  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21  * See the License for the specific language governing permissions and
22  * limitations under the License.
23  */
24 
25 // This code implements RFC6125-ish name matching, RFC5280-ish name constraint
26 // checking, and related things.
27 //
28 // In this code, identifiers are classified as either "presented" or
29 // "reference" identifiers are defined in
30 // http://tools.ietf.org/html/rfc6125#section-1.8. A "presented identifier" is
31 // one in the subjectAltName of the certificate, or sometimes within a CN of
32 // the certificate's subject. The "reference identifier" is the one we are
33 // being asked to match the certificate against. When checking name
34 // constraints, the reference identifier is the entire encoded name constraint
35 // extension value.
36 
37 #include <algorithm>
38 
39 #include "mozpkix/pkixcheck.h"
40 #include "mozpkix/pkixutil.h"
41 
42 namespace mozilla { namespace pkix {
43 
44 namespace {
45 
46 // GeneralName ::= CHOICE {
47 //      otherName                       [0]     OtherName,
48 //      rfc822Name                      [1]     IA5String,
49 //      dNSName                         [2]     IA5String,
50 //      x400Address                     [3]     ORAddress,
51 //      directoryName                   [4]     Name,
52 //      ediPartyName                    [5]     EDIPartyName,
53 //      uniformResourceIdentifier       [6]     IA5String,
54 //      iPAddress                       [7]     OCTET STRING,
55 //      registeredID                    [8]     OBJECT IDENTIFIER }
56 enum class GeneralNameType : uint8_t
57 {
58   // Note that these values are NOT contiguous. Some values have the
59   // der::CONSTRUCTED bit set while others do not.
60   // (The der::CONSTRUCTED bit is for types where the value is a SEQUENCE.)
61   otherName = der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 0,
62   rfc822Name = der::CONTEXT_SPECIFIC | 1,
63   dNSName = der::CONTEXT_SPECIFIC | 2,
64   x400Address = der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 3,
65   directoryName = der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 4,
66   ediPartyName = der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 5,
67   uniformResourceIdentifier = der::CONTEXT_SPECIFIC | 6,
68   iPAddress = der::CONTEXT_SPECIFIC | 7,
69   registeredID = der::CONTEXT_SPECIFIC | 8,
70   // nameConstraints is a pseudo-GeneralName used to signify that a
71   // reference ID is actually the entire name constraint extension.
72   nameConstraints = 0xff
73 };
74 
75 inline Result
ReadGeneralName(Reader & reader,GeneralNameType & generalNameType,Input & value)76 ReadGeneralName(Reader& reader,
77                 /*out*/ GeneralNameType& generalNameType,
78                 /*out*/ Input& value)
79 {
80   uint8_t tag;
81   Result rv = der::ReadTagAndGetValue(reader, tag, value);
82   if (rv != Success) {
83     return rv;
84   }
85   switch (tag) {
86     case static_cast<uint8_t>(GeneralNameType::otherName):
87       generalNameType = GeneralNameType::otherName;
88       break;
89     case static_cast<uint8_t>(GeneralNameType::rfc822Name):
90       generalNameType = GeneralNameType::rfc822Name;
91       break;
92     case static_cast<uint8_t>(GeneralNameType::dNSName):
93       generalNameType = GeneralNameType::dNSName;
94       break;
95     case static_cast<uint8_t>(GeneralNameType::x400Address):
96       generalNameType = GeneralNameType::x400Address;
97       break;
98     case static_cast<uint8_t>(GeneralNameType::directoryName):
99       generalNameType = GeneralNameType::directoryName;
100       break;
101     case static_cast<uint8_t>(GeneralNameType::ediPartyName):
102       generalNameType = GeneralNameType::ediPartyName;
103       break;
104     case static_cast<uint8_t>(GeneralNameType::uniformResourceIdentifier):
105       generalNameType = GeneralNameType::uniformResourceIdentifier;
106       break;
107     case static_cast<uint8_t>(GeneralNameType::iPAddress):
108       generalNameType = GeneralNameType::iPAddress;
109       break;
110     case static_cast<uint8_t>(GeneralNameType::registeredID):
111       generalNameType = GeneralNameType::registeredID;
112       break;
113     default:
114       return Result::ERROR_BAD_DER;
115   }
116   return Success;
117 }
118 
119 enum class MatchResult
120 {
121   NoNamesOfGivenType = 0,
122   Mismatch = 1,
123   Match = 2
124 };
125 
126 Result SearchNames(const Input* subjectAltName, Input subject,
127                    GeneralNameType referenceIDType,
128                    Input referenceID,
129                    FallBackToSearchWithinSubject fallBackToCommonName,
130                    /*out*/ MatchResult& match);
131 Result SearchWithinRDN(Reader& rdn,
132                        GeneralNameType referenceIDType,
133                        Input referenceID,
134                        FallBackToSearchWithinSubject fallBackToEmailAddress,
135                        FallBackToSearchWithinSubject fallBackToCommonName,
136                        /*in/out*/ MatchResult& match);
137 Result MatchAVA(Input type,
138                 uint8_t valueEncodingTag,
139                 Input presentedID,
140                 GeneralNameType referenceIDType,
141                 Input referenceID,
142                 FallBackToSearchWithinSubject fallBackToEmailAddress,
143                 FallBackToSearchWithinSubject fallBackToCommonName,
144                 /*in/out*/ MatchResult& match);
145 Result ReadAVA(Reader& rdn,
146                /*out*/ Input& type,
147                /*out*/ uint8_t& valueTag,
148                /*out*/ Input& value);
149 void MatchSubjectPresentedIDWithReferenceID(GeneralNameType presentedIDType,
150                                             Input presentedID,
151                                             GeneralNameType referenceIDType,
152                                             Input referenceID,
153                                             /*in/out*/ MatchResult& match);
154 
155 Result MatchPresentedIDWithReferenceID(GeneralNameType presentedIDType,
156                                        Input presentedID,
157                                        GeneralNameType referenceIDType,
158                                        Input referenceID,
159                                        /*in/out*/ MatchResult& matchResult);
160 Result CheckPresentedIDConformsToConstraints(GeneralNameType referenceIDType,
161                                              Input presentedID,
162                                              Input nameConstraints);
163 
164 uint8_t LocaleInsensitveToLower(uint8_t a);
165 bool StartsWithIDNALabel(Input id);
166 
167 enum class IDRole
168 {
169   ReferenceID = 0,
170   PresentedID = 1,
171   NameConstraint = 2,
172 };
173 
174 enum class AllowWildcards { No = 0, Yes = 1 };
175 
176 // DNSName constraints implicitly allow subdomain matching when there is no
177 // leading dot ("foo.example.com" matches a constraint of "example.com"), but
178 // RFC822Name constraints only allow subdomain matching when there is a leading
179 // dot ("foo.example.com" does not match "example.com" but does match
180 // ".example.com").
181 enum class AllowDotlessSubdomainMatches { No = 0, Yes = 1 };
182 
183 bool IsValidDNSID(Input hostname, IDRole idRole,
184                   AllowWildcards allowWildcards);
185 
186 Result MatchPresentedDNSIDWithReferenceDNSID(
187          Input presentedDNSID,
188          AllowWildcards allowWildcards,
189          AllowDotlessSubdomainMatches allowDotlessSubdomainMatches,
190          IDRole referenceDNSIDRole,
191          Input referenceDNSID,
192          /*out*/ bool& matches);
193 
194 Result MatchPresentedRFC822NameWithReferenceRFC822Name(
195          Input presentedRFC822Name, IDRole referenceRFC822NameRole,
196          Input referenceRFC822Name, /*out*/ bool& matches);
197 
198 } // namespace
199 
200 bool IsValidReferenceDNSID(Input hostname);
201 bool IsValidPresentedDNSID(Input hostname);
202 bool ParseIPv4Address(Input hostname, /*out*/ uint8_t (&out)[4]);
203 bool ParseIPv6Address(Input hostname, /*out*/ uint8_t (&out)[16]);
204 
205 // This is used by the pkixnames_tests.cpp tests.
206 Result
MatchPresentedDNSIDWithReferenceDNSID(Input presentedDNSID,Input referenceDNSID,bool & matches)207 MatchPresentedDNSIDWithReferenceDNSID(Input presentedDNSID,
208                                       Input referenceDNSID,
209                                       /*out*/ bool& matches)
210 {
211   return MatchPresentedDNSIDWithReferenceDNSID(
212            presentedDNSID, AllowWildcards::Yes,
213            AllowDotlessSubdomainMatches::Yes, IDRole::ReferenceID,
214            referenceDNSID, matches);
215 }
216 
217 // Verify that the given end-entity cert, which is assumed to have been already
218 // validated with BuildCertChain, is valid for the given hostname. hostname is
219 // assumed to be a string representation of an IPv4 address, an IPv6 addresss,
220 // or a normalized ASCII (possibly punycode) DNS name.
221 Result
CheckCertHostname(Input endEntityCertDER,Input hostname,NameMatchingPolicy & nameMatchingPolicy)222 CheckCertHostname(Input endEntityCertDER, Input hostname,
223                   NameMatchingPolicy& nameMatchingPolicy)
224 {
225   BackCert cert(endEntityCertDER, EndEntityOrCA::MustBeEndEntity, nullptr);
226   Result rv = cert.Init();
227   if (rv != Success) {
228     return rv;
229   }
230 
231   Time notBefore(Time::uninitialized);
232   rv = ParseValidity(cert.GetValidity(), &notBefore);
233   if (rv != Success) {
234     return rv;
235   }
236   FallBackToSearchWithinSubject fallBackToSearchWithinSubject;
237   rv = nameMatchingPolicy.FallBackToCommonName(notBefore,
238                                                fallBackToSearchWithinSubject);
239   if (rv != Success) {
240     return rv;
241   }
242 
243   const Input* subjectAltName(cert.GetSubjectAltName());
244   Input subject(cert.GetSubject());
245 
246   // For backward compatibility with legacy certificates, we may fall back to
247   // searching for a name match in the subject common name for DNS names and
248   // IPv4 addresses. We don't do so for IPv6 addresses because we do not think
249   // there are many certificates that would need such fallback, and because
250   // comparisons of string representations of IPv6 addresses are particularly
251   // error prone due to the syntactic flexibility that IPv6 addresses have.
252   //
253   // IPv4 and IPv6 addresses are represented using the same type of GeneralName
254   // (iPAddress); they are differentiated by the lengths of the values.
255   MatchResult match;
256   uint8_t ipv6[16];
257   uint8_t ipv4[4];
258   if (IsValidReferenceDNSID(hostname)) {
259     rv = SearchNames(subjectAltName, subject, GeneralNameType::dNSName,
260                      hostname, fallBackToSearchWithinSubject, match);
261   } else if (ParseIPv6Address(hostname, ipv6)) {
262     rv = SearchNames(subjectAltName, subject, GeneralNameType::iPAddress,
263                      Input(ipv6), FallBackToSearchWithinSubject::No, match);
264   } else if (ParseIPv4Address(hostname, ipv4)) {
265     rv = SearchNames(subjectAltName, subject, GeneralNameType::iPAddress,
266                      Input(ipv4), fallBackToSearchWithinSubject, match);
267   } else {
268     return Result::ERROR_BAD_CERT_DOMAIN;
269   }
270   if (rv != Success) {
271     return rv;
272   }
273   switch (match) {
274     case MatchResult::NoNamesOfGivenType: // fall through
275     case MatchResult::Mismatch:
276       return Result::ERROR_BAD_CERT_DOMAIN;
277     case MatchResult::Match:
278       return Success;
279     MOZILLA_PKIX_UNREACHABLE_DEFAULT_ENUM
280   }
281 }
282 
283 // 4.2.1.10. Name Constraints
284 Result
CheckNameConstraints(Input encodedNameConstraints,const BackCert & firstChild,KeyPurposeId requiredEKUIfPresent)285 CheckNameConstraints(Input encodedNameConstraints,
286                      const BackCert& firstChild,
287                      KeyPurposeId requiredEKUIfPresent)
288 {
289   for (const BackCert* child = &firstChild; child; child = child->childCert) {
290     FallBackToSearchWithinSubject fallBackToCommonName
291       = (child->endEntityOrCA == EndEntityOrCA::MustBeEndEntity &&
292          requiredEKUIfPresent == KeyPurposeId::id_kp_serverAuth)
293       ? FallBackToSearchWithinSubject::Yes
294       : FallBackToSearchWithinSubject::No;
295 
296     MatchResult match;
297     Result rv = SearchNames(child->GetSubjectAltName(), child->GetSubject(),
298                             GeneralNameType::nameConstraints,
299                             encodedNameConstraints, fallBackToCommonName,
300                             match);
301     if (rv != Success) {
302       return rv;
303     }
304     switch (match) {
305       case MatchResult::Match: // fall through
306       case MatchResult::NoNamesOfGivenType:
307         break;
308       case MatchResult::Mismatch:
309         return Result::ERROR_CERT_NOT_IN_NAME_SPACE;
310     }
311   }
312 
313   return Success;
314 }
315 
316 namespace {
317 
318 // SearchNames is used by CheckCertHostname and CheckNameConstraints.
319 //
320 // When called during name constraint checking, referenceIDType is
321 // GeneralNameType::nameConstraints and referenceID is the entire encoded name
322 // constraints extension value.
323 //
324 // The main benefit of using the exact same code paths for both is that we
325 // ensure consistency between name validation and name constraint enforcement
326 // regarding thing like "Which CN attributes should be considered as potential
327 // CN-IDs" and "Which character sets are acceptable for CN-IDs?" If the name
328 // matching and the name constraint enforcement logic were out of sync on these
329 // issues (e.g. if name matching were to consider all subject CN attributes,
330 // but name constraints were only enforced on the most specific subject CN),
331 // trivial name constraint bypasses could result.
332 
333 Result
SearchNames(const Input * subjectAltName,Input subject,GeneralNameType referenceIDType,Input referenceID,FallBackToSearchWithinSubject fallBackToCommonName,MatchResult & match)334 SearchNames(/*optional*/ const Input* subjectAltName,
335             Input subject,
336             GeneralNameType referenceIDType,
337             Input referenceID,
338             FallBackToSearchWithinSubject fallBackToCommonName,
339             /*out*/ MatchResult& match)
340 {
341   Result rv;
342 
343   match = MatchResult::NoNamesOfGivenType;
344 
345   // RFC 6125 says "A client MUST NOT seek a match for a reference identifier
346   // of CN-ID if the presented identifiers include a DNS-ID, SRV-ID, URI-ID, or
347   // any application-specific identifier types supported by the client."
348   // Accordingly, we only consider CN-IDs if there are no DNS-IDs in the
349   // subjectAltName.
350   //
351   // RFC 6125 says that IP addresses are out of scope, but for backward
352   // compatibility we accept them, by considering IP addresses to be an
353   // "application-specific identifier type supported by the client."
354   //
355   // TODO(bug XXXXXXX): Consider strengthening this check to "A client MUST NOT
356   // seek a match for a reference identifier of CN-ID if the certificate
357   // contains a subjectAltName extension."
358   //
359   // TODO(bug XXXXXXX): Consider dropping support for IP addresses as
360   // identifiers completely.
361 
362   if (subjectAltName) {
363     Reader altNames;
364     rv = der::ExpectTagAndGetValueAtEnd(*subjectAltName, der::SEQUENCE,
365                                         altNames);
366     if (rv != Success) {
367       return rv;
368     }
369 
370     // According to RFC 5280, "If the subjectAltName extension is present, the
371     // sequence MUST contain at least one entry." For compatibility reasons, we
372     // do not enforce this. See bug 1143085.
373     while (!altNames.AtEnd()) {
374       GeneralNameType presentedIDType;
375       Input presentedID;
376       rv = ReadGeneralName(altNames, presentedIDType, presentedID);
377       if (rv != Success) {
378         return rv;
379       }
380 
381       rv = MatchPresentedIDWithReferenceID(presentedIDType, presentedID,
382                                            referenceIDType, referenceID,
383                                            match);
384       if (rv != Success) {
385         return rv;
386       }
387       if (referenceIDType != GeneralNameType::nameConstraints &&
388           match == MatchResult::Match) {
389         return Success;
390       }
391       if (presentedIDType == GeneralNameType::dNSName ||
392           presentedIDType == GeneralNameType::iPAddress) {
393         fallBackToCommonName = FallBackToSearchWithinSubject::No;
394       }
395     }
396   }
397 
398   if (referenceIDType == GeneralNameType::nameConstraints) {
399     rv = CheckPresentedIDConformsToConstraints(GeneralNameType::directoryName,
400                                                subject, referenceID);
401     if (rv != Success) {
402       return rv;
403     }
404   }
405 
406   FallBackToSearchWithinSubject fallBackToEmailAddress;
407   if (!subjectAltName &&
408       (referenceIDType == GeneralNameType::rfc822Name ||
409        referenceIDType == GeneralNameType::nameConstraints)) {
410     fallBackToEmailAddress = FallBackToSearchWithinSubject::Yes;
411   } else {
412     fallBackToEmailAddress = FallBackToSearchWithinSubject::No;
413   }
414 
415   // Short-circuit the parsing of the subject name if we're not going to match
416   // any names in it
417   if (fallBackToEmailAddress == FallBackToSearchWithinSubject::No &&
418       fallBackToCommonName == FallBackToSearchWithinSubject::No) {
419     return Success;
420   }
421 
422   // Attempt to match the reference ID against the CN-ID, which we consider to
423   // be the most-specific CN AVA in the subject field.
424   //
425   // https://tools.ietf.org/html/rfc6125#section-2.3.1 says:
426   //
427   //   To reduce confusion, in this specification we avoid such terms and
428   //   instead use the terms provided under Section 1.8; in particular, we
429   //   do not use the term "(most specific) Common Name field in the subject
430   //   field" from [HTTP-TLS] and instead state that a CN-ID is a Relative
431   //   Distinguished Name (RDN) in the certificate subject containing one
432   //   and only one attribute-type-and-value pair of type Common Name (thus
433   //   removing the possibility that an RDN might contain multiple AVAs
434   //   (Attribute Value Assertions) of type CN, one of which could be
435   //   considered "most specific").
436   //
437   // https://tools.ietf.org/html/rfc6125#section-7.4 says:
438   //
439   //   [...] Although it would be preferable to
440   //   forbid multiple CN-IDs entirely, there are several reasons at this
441   //   time why this specification states that they SHOULD NOT (instead of
442   //   MUST NOT) be included [...]
443   //
444   // Consequently, it is unclear what to do when there are multiple CNs in the
445   // subject, regardless of whether there "SHOULD NOT" be.
446   //
447   // NSS's CERT_VerifyCertName mostly follows RFC2818 in this instance, which
448   // says:
449   //
450   //   If a subjectAltName extension of type dNSName is present, that MUST
451   //   be used as the identity. Otherwise, the (most specific) Common Name
452   //   field in the Subject field of the certificate MUST be used.
453   //
454   //   [...]
455   //
456   //   In some cases, the URI is specified as an IP address rather than a
457   //   hostname. In this case, the iPAddress subjectAltName must be present
458   //   in the certificate and must exactly match the IP in the URI.
459   //
460   // (The main difference from RFC2818 is that NSS's CERT_VerifyCertName also
461   // matches IP addresses in the most-specific CN.)
462   //
463   // NSS's CERT_VerifyCertName finds the most specific CN via
464   // CERT_GetCommoName, which uses CERT_GetLastNameElement. Note that many
465   // NSS-based applications, including Gecko, also use CERT_GetCommonName. It
466   // is likely that other, non-NSS-based, applications also expect only the
467   // most specific CN to be matched against the reference ID.
468   //
469   // "A Layman's Guide to a Subset of ASN.1, BER, and DER" and other sources
470   // agree that an RDNSequence is ordered from most significant (least
471   // specific) to least significant (most specific), as do other references.
472   //
473   // However, Chromium appears to use the least-specific (first) CN instead of
474   // the most-specific; see https://crbug.com/366957. Also, MSIE and some other
475   // popular implementations apparently attempt to match the reference ID
476   // against any/all CNs in the subject. Since we're trying to phase out the
477   // use of CN-IDs, we intentionally avoid trying to match MSIE's more liberal
478   // behavior.
479 
480   // Name ::= CHOICE { -- only one possibility for now --
481   //   rdnSequence  RDNSequence }
482   //
483   // RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
484   //
485   // RelativeDistinguishedName ::=
486   //   SET SIZE (1..MAX) OF AttributeTypeAndValue
487   Reader subjectReader(subject);
488   return der::NestedOf(subjectReader, der::SEQUENCE, der::SET,
489                        der::EmptyAllowed::Yes, [&](Reader& r) {
490     return SearchWithinRDN(r, referenceIDType, referenceID,
491                           fallBackToEmailAddress, fallBackToCommonName, match);
492   });
493 }
494 
495 // RelativeDistinguishedName ::=
496 //   SET SIZE (1..MAX) OF AttributeTypeAndValue
497 //
498 // AttributeTypeAndValue ::= SEQUENCE {
499 //   type     AttributeType,
500 //   value    AttributeValue }
501 Result
SearchWithinRDN(Reader & rdn,GeneralNameType referenceIDType,Input referenceID,FallBackToSearchWithinSubject fallBackToEmailAddress,FallBackToSearchWithinSubject fallBackToCommonName,MatchResult & match)502 SearchWithinRDN(Reader& rdn,
503                 GeneralNameType referenceIDType,
504                 Input referenceID,
505                 FallBackToSearchWithinSubject fallBackToEmailAddress,
506                 FallBackToSearchWithinSubject fallBackToCommonName,
507                 /*in/out*/ MatchResult& match)
508 {
509   do {
510     Input type;
511     uint8_t valueTag;
512     Input value;
513     Result rv = ReadAVA(rdn, type, valueTag, value);
514     if (rv != Success) {
515       return rv;
516     }
517     rv = MatchAVA(type, valueTag, value, referenceIDType, referenceID,
518                   fallBackToEmailAddress, fallBackToCommonName, match);
519     if (rv != Success) {
520       return rv;
521     }
522   } while (!rdn.AtEnd());
523 
524   return Success;
525 }
526 
527 // AttributeTypeAndValue ::= SEQUENCE {
528 //   type     AttributeType,
529 //   value    AttributeValue }
530 //
531 // AttributeType ::= OBJECT IDENTIFIER
532 //
533 // AttributeValue ::= ANY -- DEFINED BY AttributeType
534 //
535 // DirectoryString ::= CHOICE {
536 //       teletexString           TeletexString (SIZE (1..MAX)),
537 //       printableString         PrintableString (SIZE (1..MAX)),
538 //       universalString         UniversalString (SIZE (1..MAX)),
539 //       utf8String              UTF8String (SIZE (1..MAX)),
540 //       bmpString               BMPString (SIZE (1..MAX)) }
541 Result
MatchAVA(Input type,uint8_t valueEncodingTag,Input presentedID,GeneralNameType referenceIDType,Input referenceID,FallBackToSearchWithinSubject fallBackToEmailAddress,FallBackToSearchWithinSubject fallBackToCommonName,MatchResult & match)542 MatchAVA(Input type, uint8_t valueEncodingTag, Input presentedID,
543          GeneralNameType referenceIDType,
544          Input referenceID,
545          FallBackToSearchWithinSubject fallBackToEmailAddress,
546          FallBackToSearchWithinSubject fallBackToCommonName,
547          /*in/out*/ MatchResult& match)
548 {
549   // Try to match the  CN as a DNSName or an IPAddress.
550   //
551   // id-at-commonName        AttributeType ::= { id-at 3 }
552   //
553   // -- Naming attributes of type X520CommonName:
554   // --   X520CommonName ::= DirectoryName (SIZE (1..ub-common-name))
555   // --
556   // -- Expanded to avoid parameterized type:
557   // X520CommonName ::= CHOICE {
558   //       teletexString     TeletexString   (SIZE (1..ub-common-name)),
559   //       printableString   PrintableString (SIZE (1..ub-common-name)),
560   //       universalString   UniversalString (SIZE (1..ub-common-name)),
561   //       utf8String        UTF8String      (SIZE (1..ub-common-name)),
562   //       bmpString         BMPString       (SIZE (1..ub-common-name)) }
563   //
564   // python DottedOIDToCode.py id-at-commonName 2.5.4.3
565   static const uint8_t id_at_commonName[] = {
566     0x55, 0x04, 0x03
567   };
568   if (fallBackToCommonName == FallBackToSearchWithinSubject::Yes &&
569       InputsAreEqual(type, Input(id_at_commonName))) {
570     // We might have previously found a match. Now that we've found another CN,
571     // we no longer consider that previous match to be a match, so "forget" about
572     // it.
573     match = MatchResult::NoNamesOfGivenType;
574 
575     // PrintableString is a subset of ASCII that contains all the characters
576     // allowed in CN-IDs except '*'. Although '*' is illegal, there are many
577     // real-world certificates that are encoded this way, so we accept it.
578     //
579     // In the case of UTF8String, we rely on the fact that in UTF-8 the octets in
580     // a multi-byte encoding of a code point are always distinct from ASCII. Any
581     // non-ASCII byte in a UTF-8 string causes us to fail to match. We make no
582     // attempt to detect or report malformed UTF-8 (e.g. incomplete or overlong
583     // encodings of code points, or encodings of invalid code points).
584     //
585     // TeletexString is supported as long as it does not contain any escape
586     // sequences, which are not supported. We'll reject escape sequences as
587     // invalid characters in names, which means we only accept strings that are
588     // in the default character set, which is a superset of ASCII. Note that NSS
589     // actually treats TeletexString as ISO-8859-1. Many certificates that have
590     // wildcard CN-IDs (e.g. "*.example.com") use TeletexString because
591     // PrintableString is defined to not allow '*' and because, at one point in
592     // history, UTF8String was too new to use for compatibility reasons.
593     //
594     // UniversalString and BMPString are also deprecated, and they are a little
595     // harder to support because they are not single-byte ASCII superset
596     // encodings, so we don't bother.
597     if (valueEncodingTag != der::PrintableString &&
598         valueEncodingTag != der::UTF8String &&
599         valueEncodingTag != der::TeletexString) {
600       return Success;
601     }
602 
603     if (IsValidPresentedDNSID(presentedID)) {
604       MatchSubjectPresentedIDWithReferenceID(GeneralNameType::dNSName,
605                                              presentedID, referenceIDType,
606                                              referenceID, match);
607     } else {
608       // We don't match CN-IDs for IPv6 addresses.
609       // MatchSubjectPresentedIDWithReferenceID ensures that it won't match an
610       // IPv4 address with an IPv6 address, so we don't need to check that
611       // referenceID is an IPv4 address here.
612       uint8_t ipv4[4];
613       if (ParseIPv4Address(presentedID, ipv4)) {
614         MatchSubjectPresentedIDWithReferenceID(GeneralNameType::iPAddress,
615                                                Input(ipv4), referenceIDType,
616                                                referenceID, match);
617       }
618     }
619 
620     // Regardless of whether there was a match, we keep going in case we find
621     // another CN later. If we do find another one, then this match/mismatch
622     // will be ignored, because we only care about the most specific CN.
623 
624     return Success;
625   }
626 
627   // Match an email address against an emailAddress attribute in the
628   // subject.
629   //
630   // id-emailAddress      AttributeType ::= { pkcs-9 1 }
631   //
632   // EmailAddress ::=     IA5String (SIZE (1..ub-emailaddress-length))
633   //
634   // python DottedOIDToCode.py id-emailAddress 1.2.840.113549.1.9.1
635   static const uint8_t id_emailAddress[] = {
636     0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01
637   };
638   if (fallBackToEmailAddress == FallBackToSearchWithinSubject::Yes &&
639       InputsAreEqual(type, Input(id_emailAddress))) {
640     if (referenceIDType == GeneralNameType::rfc822Name &&
641         match == MatchResult::Match) {
642       // We already found a match; we don't need to match another one
643       return Success;
644     }
645     if (valueEncodingTag != der::IA5String) {
646       return Result::ERROR_BAD_DER;
647     }
648     return MatchPresentedIDWithReferenceID(GeneralNameType::rfc822Name,
649                                            presentedID, referenceIDType,
650                                            referenceID, match);
651   }
652 
653   return Success;
654 }
655 
656 void
MatchSubjectPresentedIDWithReferenceID(GeneralNameType presentedIDType,Input presentedID,GeneralNameType referenceIDType,Input referenceID,MatchResult & match)657 MatchSubjectPresentedIDWithReferenceID(GeneralNameType presentedIDType,
658                                        Input presentedID,
659                                        GeneralNameType referenceIDType,
660                                        Input referenceID,
661                                        /*in/out*/ MatchResult& match)
662 {
663   Result rv = MatchPresentedIDWithReferenceID(presentedIDType, presentedID,
664                                               referenceIDType, referenceID,
665                                               match);
666   if (rv != Success) {
667     match = MatchResult::Mismatch;
668   }
669 }
670 
671 Result
MatchPresentedIDWithReferenceID(GeneralNameType presentedIDType,Input presentedID,GeneralNameType referenceIDType,Input referenceID,MatchResult & matchResult)672 MatchPresentedIDWithReferenceID(GeneralNameType presentedIDType,
673                                 Input presentedID,
674                                 GeneralNameType referenceIDType,
675                                 Input referenceID,
676                                 /*out*/ MatchResult& matchResult)
677 {
678   if (referenceIDType == GeneralNameType::nameConstraints) {
679     // matchResult is irrelevant when checking name constraints; only the
680     // pass/fail result of CheckPresentedIDConformsToConstraints matters.
681     return CheckPresentedIDConformsToConstraints(presentedIDType, presentedID,
682                                                  referenceID);
683   }
684 
685   if (presentedIDType != referenceIDType) {
686     matchResult = MatchResult::Mismatch;
687     return Success;
688   }
689 
690   Result rv;
691   bool foundMatch;
692 
693   switch (referenceIDType) {
694     case GeneralNameType::dNSName:
695       rv = MatchPresentedDNSIDWithReferenceDNSID(
696              presentedID, AllowWildcards::Yes,
697              AllowDotlessSubdomainMatches::Yes, IDRole::ReferenceID,
698              referenceID, foundMatch);
699       break;
700 
701     case GeneralNameType::iPAddress:
702       foundMatch = InputsAreEqual(presentedID, referenceID);
703       rv = Success;
704       break;
705 
706     case GeneralNameType::rfc822Name:
707       rv = MatchPresentedRFC822NameWithReferenceRFC822Name(
708              presentedID, IDRole::ReferenceID, referenceID, foundMatch);
709       break;
710 
711     case GeneralNameType::directoryName:
712       // TODO: At some point, we may add APIs for matching DirectoryNames.
713       // fall through
714 
715     case GeneralNameType::otherName: // fall through
716     case GeneralNameType::x400Address: // fall through
717     case GeneralNameType::ediPartyName: // fall through
718     case GeneralNameType::uniformResourceIdentifier: // fall through
719     case GeneralNameType::registeredID: // fall through
720     case GeneralNameType::nameConstraints:
721       return NotReached("unexpected nameType for SearchType::Match",
722                         Result::FATAL_ERROR_INVALID_ARGS);
723 
724     MOZILLA_PKIX_UNREACHABLE_DEFAULT_ENUM
725  }
726 
727   if (rv != Success) {
728     return rv;
729   }
730   matchResult = foundMatch ? MatchResult::Match : MatchResult::Mismatch;
731   return Success;
732 }
733 
734 enum class NameConstraintsSubtrees : uint8_t
735 {
736   permittedSubtrees = der::CONSTRUCTED | der::CONTEXT_SPECIFIC | 0,
737   excludedSubtrees  = der::CONSTRUCTED | der::CONTEXT_SPECIFIC | 1
738 };
739 
740 Result CheckPresentedIDConformsToNameConstraintsSubtrees(
741          GeneralNameType presentedIDType,
742          Input presentedID,
743          Reader& nameConstraints,
744          NameConstraintsSubtrees subtreesType);
745 Result MatchPresentedIPAddressWithConstraint(Input presentedID,
746                                              Input iPAddressConstraint,
747                                              /*out*/ bool& foundMatch);
748 Result MatchPresentedDirectoryNameWithConstraint(
749          NameConstraintsSubtrees subtreesType, Input presentedID,
750          Input directoryNameConstraint, /*out*/ bool& matches);
751 
752 Result
CheckPresentedIDConformsToConstraints(GeneralNameType presentedIDType,Input presentedID,Input encodedNameConstraints)753 CheckPresentedIDConformsToConstraints(
754   GeneralNameType presentedIDType,
755   Input presentedID,
756   Input encodedNameConstraints)
757 {
758   // NameConstraints ::= SEQUENCE {
759   //      permittedSubtrees       [0]     GeneralSubtrees OPTIONAL,
760   //      excludedSubtrees        [1]     GeneralSubtrees OPTIONAL }
761   Reader nameConstraints;
762   Result rv = der::ExpectTagAndGetValueAtEnd(encodedNameConstraints,
763                                              der::SEQUENCE, nameConstraints);
764   if (rv != Success) {
765     return rv;
766   }
767 
768   // RFC 5280 says "Conforming CAs MUST NOT issue certificates where name
769   // constraints is an empty sequence. That is, either the permittedSubtrees
770   // field or the excludedSubtrees MUST be present."
771   if (nameConstraints.AtEnd()) {
772     return Result::ERROR_BAD_DER;
773   }
774 
775   rv = CheckPresentedIDConformsToNameConstraintsSubtrees(
776          presentedIDType, presentedID, nameConstraints,
777          NameConstraintsSubtrees::permittedSubtrees);
778   if (rv != Success) {
779     return rv;
780   }
781 
782   rv = CheckPresentedIDConformsToNameConstraintsSubtrees(
783          presentedIDType, presentedID, nameConstraints,
784          NameConstraintsSubtrees::excludedSubtrees);
785   if (rv != Success) {
786     return rv;
787   }
788 
789   return der::End(nameConstraints);
790 }
791 
792 Result
CheckPresentedIDConformsToNameConstraintsSubtrees(GeneralNameType presentedIDType,Input presentedID,Reader & nameConstraints,NameConstraintsSubtrees subtreesType)793 CheckPresentedIDConformsToNameConstraintsSubtrees(
794   GeneralNameType presentedIDType,
795   Input presentedID,
796   Reader& nameConstraints,
797   NameConstraintsSubtrees subtreesType)
798 {
799   if (!nameConstraints.Peek(static_cast<uint8_t>(subtreesType))) {
800     return Success;
801   }
802 
803   Reader subtrees;
804   Result rv = der::ExpectTagAndGetValue(nameConstraints,
805                                         static_cast<uint8_t>(subtreesType),
806                                         subtrees);
807   if (rv != Success) {
808     return rv;
809   }
810 
811   bool hasPermittedSubtreesMatch = false;
812   bool hasPermittedSubtreesMismatch = false;
813 
814   // GeneralSubtrees ::= SEQUENCE SIZE (1..MAX) OF GeneralSubtree
815   //
816   // do { ... } while(...) because subtrees isn't allowed to be empty.
817   do {
818     // GeneralSubtree ::= SEQUENCE {
819     //      base                    GeneralName,
820     //      minimum         [0]     BaseDistance DEFAULT 0,
821     //      maximum         [1]     BaseDistance OPTIONAL }
822     Reader subtree;
823     rv = ExpectTagAndGetValue(subtrees, der::SEQUENCE, subtree);
824     if (rv != Success) {
825       return rv;
826     }
827     GeneralNameType nameConstraintType;
828     Input base;
829     rv = ReadGeneralName(subtree, nameConstraintType, base);
830     if (rv != Success) {
831       return rv;
832     }
833     // http://tools.ietf.org/html/rfc5280#section-4.2.1.10: "Within this
834     // profile, the minimum and maximum fields are not used with any name
835     // forms, thus, the minimum MUST be zero, and maximum MUST be absent."
836     //
837     // Since the default value isn't allowed to be encoded according to the DER
838     // encoding rules for DEFAULT, this is equivalent to saying that neither
839     // minimum or maximum must be encoded.
840     rv = der::End(subtree);
841     if (rv != Success) {
842       return rv;
843     }
844 
845     if (presentedIDType == nameConstraintType) {
846       bool matches;
847 
848       switch (presentedIDType) {
849         case GeneralNameType::dNSName:
850           rv = MatchPresentedDNSIDWithReferenceDNSID(
851                  presentedID, AllowWildcards::Yes,
852                  AllowDotlessSubdomainMatches::Yes, IDRole::NameConstraint,
853                  base, matches);
854           if (rv != Success) {
855             return rv;
856           }
857           break;
858 
859         case GeneralNameType::iPAddress:
860           rv = MatchPresentedIPAddressWithConstraint(presentedID, base,
861                                                      matches);
862           if (rv != Success) {
863             return rv;
864           }
865           break;
866 
867         case GeneralNameType::directoryName:
868           rv = MatchPresentedDirectoryNameWithConstraint(subtreesType,
869                                                          presentedID, base,
870                                                          matches);
871           if (rv != Success) {
872             return rv;
873           }
874           break;
875 
876         case GeneralNameType::rfc822Name:
877           rv = MatchPresentedRFC822NameWithReferenceRFC822Name(
878                  presentedID, IDRole::NameConstraint, base, matches);
879           if (rv != Success) {
880             return rv;
881           }
882           break;
883 
884         // RFC 5280 says "Conforming CAs [...] SHOULD NOT impose name
885         // constraints on the x400Address, ediPartyName, or registeredID
886         // name forms. It also says "Applications conforming to this profile
887         // [...] SHOULD be able to process name constraints that are imposed
888         // on [...] uniformResourceIdentifier [...]", but we don't bother.
889         //
890         // TODO: Ask to have spec updated to say ""Conforming CAs [...] SHOULD
891         // NOT impose name constraints on the otherName, x400Address,
892         // ediPartyName, uniformResourceIdentifier, or registeredID name
893         // forms."
894         case GeneralNameType::otherName: // fall through
895         case GeneralNameType::x400Address: // fall through
896         case GeneralNameType::ediPartyName: // fall through
897         case GeneralNameType::uniformResourceIdentifier: // fall through
898         case GeneralNameType::registeredID: // fall through
899           return Result::ERROR_CERT_NOT_IN_NAME_SPACE;
900 
901         case GeneralNameType::nameConstraints:
902           return NotReached("invalid presentedIDType",
903                             Result::FATAL_ERROR_LIBRARY_FAILURE);
904 
905         MOZILLA_PKIX_UNREACHABLE_DEFAULT_ENUM
906       }
907 
908       switch (subtreesType) {
909         case NameConstraintsSubtrees::permittedSubtrees:
910           if (matches) {
911             hasPermittedSubtreesMatch = true;
912           } else {
913             hasPermittedSubtreesMismatch = true;
914           }
915           break;
916         case NameConstraintsSubtrees::excludedSubtrees:
917           if (matches) {
918             return Result::ERROR_CERT_NOT_IN_NAME_SPACE;
919           }
920           break;
921       }
922     }
923   } while (!subtrees.AtEnd());
924 
925   if (hasPermittedSubtreesMismatch && !hasPermittedSubtreesMatch) {
926     // If there was any entry of the given type in permittedSubtrees, then it
927     // required that at least one of them must match. Since none of them did,
928     // we have a failure.
929     return Result::ERROR_CERT_NOT_IN_NAME_SPACE;
930   }
931 
932   return Success;
933 }
934 
935 // We do not distinguish between a syntactically-invalid presentedDNSID and one
936 // that is syntactically valid but does not match referenceDNSID; in both
937 // cases, the result is false.
938 //
939 // We assume that both presentedDNSID and referenceDNSID are encoded in such a
940 // way that US-ASCII (7-bit) characters are encoded in one byte and no encoding
941 // of a non-US-ASCII character contains a code point in the range 0-127. For
942 // example, UTF-8 is OK but UTF-16 is not.
943 //
944 // RFC6125 says that a wildcard label may be of the form <x>*<y>.<DNSID>, where
945 // <x> and/or <y> may be empty. However, NSS requires <y> to be empty, and we
946 // follow NSS's stricter policy by accepting wildcards only of the form
947 // <x>*.<DNSID>, where <x> may be empty.
948 //
949 // An relative presented DNS ID matches both an absolute reference ID and a
950 // relative reference ID. Absolute presented DNS IDs are not supported:
951 //
952 //      Presented ID   Reference ID  Result
953 //      -------------------------------------
954 //      example.com    example.com   Match
955 //      example.com.   example.com   Mismatch
956 //      example.com    example.com.  Match
957 //      example.com.   example.com.  Mismatch
958 //
959 // There are more subtleties documented inline in the code.
960 //
961 // Name constraints ///////////////////////////////////////////////////////////
962 //
963 // This is all RFC 5280 has to say about DNSName constraints:
964 //
965 //     DNS name restrictions are expressed as host.example.com.  Any DNS
966 //     name that can be constructed by simply adding zero or more labels to
967 //     the left-hand side of the name satisfies the name constraint.  For
968 //     example, www.host.example.com would satisfy the constraint but
969 //     host1.example.com would not.
970 //
971 // This lack of specificity has lead to a lot of uncertainty regarding
972 // subdomain matching. In particular, the following questions have been
973 // raised and answered:
974 //
975 //     Q: Does a presented identifier equal (case insensitive) to the name
976 //        constraint match the constraint? For example, does the presented
977 //        ID "host.example.com" match a "host.example.com" constraint?
978 //     A: Yes. RFC5280 says "by simply adding zero or more labels" and this
979 //        is the case of adding zero labels.
980 //
981 //     Q: When the name constraint does not start with ".", do subdomain
982 //        presented identifiers match it? For example, does the presented
983 //        ID "www.host.example.com" match a "host.example.com" constraint?
984 //     A: Yes. RFC5280 says "by simply adding zero or more labels" and this
985 //        is the case of adding more than zero labels. The example is the
986 //        one from RFC 5280.
987 //
988 //     Q: When the name constraint does not start with ".", does a
989 //        non-subdomain prefix match it? For example, does "bigfoo.bar.com"
990 //        match "foo.bar.com"? [4]
991 //     A: No. We interpret RFC 5280's language of "adding zero or more labels"
992 //        to mean that whole labels must be prefixed.
993 //
994 //     (Note that the above three scenarios are the same as the RFC 6265
995 //     domain matching rules [0].)
996 //
997 //     Q: Is a name constraint that starts with "." valid, and if so, what
998 //        semantics does it have? For example, does a presented ID of
999 //        "www.example.com" match a constraint of ".example.com"? Does a
1000 //        presented ID of "example.com" match a constraint of ".example.com"?
1001 //     A: This implementation, NSS[1], and SChannel[2] all support a
1002 //        leading ".", but OpenSSL[3] does not yet. Amongst the
1003 //        implementations that support it, a leading "." is legal and means
1004 //        the same thing as when the "." is omitted, EXCEPT that a
1005 //        presented identifier equal (case insensitive) to the name
1006 //        constraint is not matched; i.e. presented DNSName identifiers
1007 //        must be subdomains. Some CAs in Mozilla's CA program (e.g. HARICA)
1008 //        have name constraints with the leading "." in their root
1009 //        certificates. The name constraints imposed on DCISS by Mozilla also
1010 //        have the it, so supporting this is a requirement for backward
1011 //        compatibility, even if it is not yet standardized. So, for example, a
1012 //        presented ID of "www.example.com" matches a constraint of
1013 //        ".example.com" but a presented ID of "example.com" does not.
1014 //
1015 //     Q: Is there a way to prevent subdomain matches?
1016 //     A: Yes.
1017 //
1018 //        Some people have proposed that dNSName constraints that do not
1019 //        start with a "." should be restricted to exact (case insensitive)
1020 //        matches. However, such a change of semantics from what RFC5280
1021 //        specifies would be a non-backward-compatible change in the case of
1022 //        permittedSubtrees constraints, and it would be a security issue for
1023 //        excludedSubtrees constraints.
1024 //
1025 //        However, it can be done with a combination of permittedSubtrees and
1026 //        excludedSubtrees, e.g. "example.com" in permittedSubtrees and
1027 //        ".example.com" in excudedSubtrees.
1028 //
1029 //     Q: Are name constraints allowed to be specified as absolute names?
1030 //        For example, does a presented ID of "example.com" match a name
1031 //        constraint of "example.com." and vice versa.
1032 //     A: Absolute names are not supported as presented IDs or name
1033 //        constraints. Only reference IDs may be absolute.
1034 //
1035 //     Q: Is "" a valid DNSName constraints? If so, what does it mean?
1036 //     A: Yes. Any valid presented DNSName can be formed "by simply adding zero
1037 //        or more labels to the left-hand side" of "". In particular, an
1038 //        excludedSubtrees DNSName constraint of "" forbids all DNSNames.
1039 //
1040 //     Q: Is "." a valid DNSName constraints? If so, what does it mean?
1041 //     A: No, because absolute names are not allowed (see above).
1042 //
1043 // [0] RFC 6265 (Cookies) Domain Matching rules:
1044 //     http://tools.ietf.org/html/rfc6265#section-5.1.3
1045 // [1] NSS source code:
1046 //     https://mxr.mozilla.org/nss/source/lib/certdb/genname.c?rev=2a7348f013cb#1209
1047 // [2] Description of SChannel's behavior from Microsoft:
1048 //     http://www.imc.org/ietf-pkix/mail-archive/msg04668.html
1049 // [3] Proposal to add such support to OpenSSL:
1050 //     http://www.mail-archive.com/openssl-dev%40openssl.org/msg36204.html
1051 //     https://rt.openssl.org/Ticket/Display.html?id=3562
1052 // [4] Feedback on the lack of clarify in the definition that never got
1053 //     incorporated into the spec:
1054 //     https://www.ietf.org/mail-archive/web/pkix/current/msg21192.html
1055 Result
MatchPresentedDNSIDWithReferenceDNSID(Input presentedDNSID,AllowWildcards allowWildcards,AllowDotlessSubdomainMatches allowDotlessSubdomainMatches,IDRole referenceDNSIDRole,Input referenceDNSID,bool & matches)1056 MatchPresentedDNSIDWithReferenceDNSID(
1057   Input presentedDNSID,
1058   AllowWildcards allowWildcards,
1059   AllowDotlessSubdomainMatches allowDotlessSubdomainMatches,
1060   IDRole referenceDNSIDRole,
1061   Input referenceDNSID,
1062   /*out*/ bool& matches)
1063 {
1064   if (!IsValidDNSID(presentedDNSID, IDRole::PresentedID, allowWildcards)) {
1065     return Result::ERROR_BAD_DER;
1066   }
1067 
1068   if (!IsValidDNSID(referenceDNSID, referenceDNSIDRole, AllowWildcards::No)) {
1069     return Result::ERROR_BAD_DER;
1070   }
1071 
1072   Reader presented(presentedDNSID);
1073   Reader reference(referenceDNSID);
1074 
1075   switch (referenceDNSIDRole)
1076   {
1077     case IDRole::ReferenceID:
1078       break;
1079 
1080     case IDRole::NameConstraint:
1081     {
1082       if (presentedDNSID.GetLength() > referenceDNSID.GetLength()) {
1083         if (referenceDNSID.GetLength() == 0) {
1084           // An empty constraint matches everything.
1085           matches = true;
1086           return Success;
1087         }
1088         // If the reference ID starts with a dot then skip the prefix of
1089         // of the presented ID and start the comparison at the position of that
1090         // dot. Examples:
1091         //
1092         //                                       Matches     Doesn't Match
1093         //     -----------------------------------------------------------
1094         //       original presented ID:  www.example.com    badexample.com
1095         //                     skipped:  www                ba
1096         //     presented ID w/o prefix:     .example.com      dexample.com
1097         //                reference ID:     .example.com      .example.com
1098         //
1099         // If the reference ID does not start with a dot then we skip the
1100         // prefix of the presented ID but also verify that the prefix ends with
1101         // a dot. Examples:
1102         //
1103         //                                       Matches     Doesn't Match
1104         //     -----------------------------------------------------------
1105         //       original presented ID:  www.example.com    badexample.com
1106         //                     skipped:  www                ba
1107         //                 must be '.':     .                 d
1108         //     presented ID w/o prefix:      example.com       example.com
1109         //                reference ID:      example.com       example.com
1110         //
1111         if (reference.Peek('.')) {
1112           if (presented.Skip(static_cast<Input::size_type>(
1113                                presentedDNSID.GetLength() -
1114                                  referenceDNSID.GetLength())) != Success) {
1115             return NotReached("skipping subdomain failed",
1116                               Result::FATAL_ERROR_LIBRARY_FAILURE);
1117           }
1118         } else if (allowDotlessSubdomainMatches ==
1119                    AllowDotlessSubdomainMatches::Yes) {
1120           if (presented.Skip(static_cast<Input::size_type>(
1121                                presentedDNSID.GetLength() -
1122                                  referenceDNSID.GetLength() - 1)) != Success) {
1123             return NotReached("skipping subdomains failed",
1124                               Result::FATAL_ERROR_LIBRARY_FAILURE);
1125           }
1126           uint8_t b;
1127           if (presented.Read(b) != Success) {
1128             return NotReached("reading from presentedDNSID failed",
1129                               Result::FATAL_ERROR_LIBRARY_FAILURE);
1130           }
1131           if (b != '.') {
1132             matches = false;
1133             return Success;
1134           }
1135         }
1136       }
1137       break;
1138     }
1139 
1140     case IDRole::PresentedID: // fall through
1141       return NotReached("IDRole::PresentedID is not a valid referenceDNSIDRole",
1142                         Result::FATAL_ERROR_INVALID_ARGS);
1143   }
1144 
1145   // We only allow wildcard labels that consist only of '*'.
1146   if (presented.Peek('*')) {
1147     if (presented.Skip(1) != Success) {
1148       return NotReached("Skipping '*' failed",
1149                         Result::FATAL_ERROR_LIBRARY_FAILURE);
1150     }
1151     do {
1152       // This will happen if reference is a single, relative label
1153       if (reference.AtEnd()) {
1154         matches = false;
1155         return Success;
1156       }
1157       uint8_t referenceByte;
1158       if (reference.Read(referenceByte) != Success) {
1159         return NotReached("invalid reference ID",
1160                           Result::FATAL_ERROR_INVALID_ARGS);
1161       }
1162     } while (!reference.Peek('.'));
1163   }
1164 
1165   for (;;) {
1166     uint8_t presentedByte;
1167     if (presented.Read(presentedByte) != Success) {
1168       matches = false;
1169       return Success;
1170     }
1171     uint8_t referenceByte;
1172     if (reference.Read(referenceByte) != Success) {
1173       matches = false;
1174       return Success;
1175     }
1176     if (LocaleInsensitveToLower(presentedByte) !=
1177         LocaleInsensitveToLower(referenceByte)) {
1178       matches = false;
1179       return Success;
1180     }
1181     if (presented.AtEnd()) {
1182       // Don't allow presented IDs to be absolute.
1183       if (presentedByte == '.') {
1184         return Result::ERROR_BAD_DER;
1185       }
1186       break;
1187     }
1188   }
1189 
1190   // Allow a relative presented DNS ID to match an absolute reference DNS ID,
1191   // unless we're matching a name constraint.
1192   if (!reference.AtEnd()) {
1193     if (referenceDNSIDRole != IDRole::NameConstraint) {
1194       uint8_t referenceByte;
1195       if (reference.Read(referenceByte) != Success) {
1196         return NotReached("read failed but not at end",
1197                           Result::FATAL_ERROR_LIBRARY_FAILURE);
1198       }
1199       if (referenceByte != '.') {
1200         matches = false;
1201         return Success;
1202       }
1203     }
1204     if (!reference.AtEnd()) {
1205       matches = false;
1206       return Success;
1207     }
1208   }
1209 
1210   matches = true;
1211   return Success;
1212 }
1213 
1214 // https://tools.ietf.org/html/rfc5280#section-4.2.1.10 says:
1215 //
1216 //     For IPv4 addresses, the iPAddress field of GeneralName MUST contain
1217 //     eight (8) octets, encoded in the style of RFC 4632 (CIDR) to represent
1218 //     an address range [RFC4632].  For IPv6 addresses, the iPAddress field
1219 //     MUST contain 32 octets similarly encoded.  For example, a name
1220 //     constraint for "class C" subnet 192.0.2.0 is represented as the
1221 //     octets C0 00 02 00 FF FF FF 00, representing the CIDR notation
1222 //     192.0.2.0/24 (mask 255.255.255.0).
1223 Result
MatchPresentedIPAddressWithConstraint(Input presentedID,Input iPAddressConstraint,bool & foundMatch)1224 MatchPresentedIPAddressWithConstraint(Input presentedID,
1225                                       Input iPAddressConstraint,
1226                                       /*out*/ bool& foundMatch)
1227 {
1228   if (presentedID.GetLength() != 4 && presentedID.GetLength() != 16) {
1229     return Result::ERROR_BAD_DER;
1230   }
1231   if (iPAddressConstraint.GetLength() != 8 &&
1232       iPAddressConstraint.GetLength() != 32) {
1233     return Result::ERROR_BAD_DER;
1234   }
1235 
1236   // an IPv4 address never matches an IPv6 constraint, and vice versa.
1237   if (presentedID.GetLength() * 2 != iPAddressConstraint.GetLength()) {
1238     foundMatch = false;
1239     return Success;
1240   }
1241 
1242   Reader constraint(iPAddressConstraint);
1243   Reader constraintAddress;
1244   Result rv = constraint.Skip(iPAddressConstraint.GetLength() / 2u,
1245                               constraintAddress);
1246   if (rv != Success) {
1247     return rv;
1248   }
1249   Reader constraintMask;
1250   rv = constraint.Skip(iPAddressConstraint.GetLength() / 2u, constraintMask);
1251   if (rv != Success) {
1252     return rv;
1253   }
1254   rv = der::End(constraint);
1255   if (rv != Success) {
1256     return rv;
1257   }
1258 
1259   Reader presented(presentedID);
1260   do {
1261     uint8_t presentedByte;
1262     rv = presented.Read(presentedByte);
1263     if (rv != Success) {
1264       return rv;
1265     }
1266     uint8_t constraintAddressByte;
1267     rv = constraintAddress.Read(constraintAddressByte);
1268     if (rv != Success) {
1269       return rv;
1270     }
1271     uint8_t constraintMaskByte;
1272     rv = constraintMask.Read(constraintMaskByte);
1273     if (rv != Success) {
1274       return rv;
1275     }
1276     foundMatch =
1277       ((presentedByte ^ constraintAddressByte) & constraintMaskByte) == 0;
1278   } while (foundMatch && !presented.AtEnd());
1279 
1280   return Success;
1281 }
1282 
1283 // AttributeTypeAndValue ::= SEQUENCE {
1284 //   type     AttributeType,
1285 //   value    AttributeValue }
1286 //
1287 // AttributeType ::= OBJECT IDENTIFIER
1288 //
1289 // AttributeValue ::= ANY -- DEFINED BY AttributeType
1290 Result
ReadAVA(Reader & rdn,Input & type,uint8_t & valueTag,Input & value)1291 ReadAVA(Reader& rdn,
1292         /*out*/ Input& type,
1293         /*out*/ uint8_t& valueTag,
1294         /*out*/ Input& value)
1295 {
1296   return der::Nested(rdn, der::SEQUENCE, [&](Reader& ava) -> Result {
1297     Result rv = der::ExpectTagAndGetValue(ava, der::OIDTag, type);
1298     if (rv != Success) {
1299       return rv;
1300     }
1301     rv = der::ReadTagAndGetValue(ava, valueTag, value);
1302     if (rv != Success) {
1303       return rv;
1304     }
1305     return Success;
1306   });
1307 }
1308 
1309 // Names are sequences of RDNs. RDNS are sets of AVAs. That means that RDNs are
1310 // unordered, so in theory we should match RDNs with equivalent AVAs that are
1311 // in different orders. Within the AVAs are DirectoryNames that are supposed to
1312 // be compared according to LDAP stringprep normalization rules (e.g.
1313 // normalizing whitespace), consideration of different character encodings,
1314 // etc. Indeed, RFC 5280 says we MUST deal with all of that.
1315 //
1316 // In practice, many implementations, including NSS, only match Names in a way
1317 // that only meets a subset of the requirements of RFC 5280. Those
1318 // normalization and character encoding conversion steps appear to be
1319 // unnecessary for processing real-world certificates, based on experience from
1320 // having used NSS in Firefox for many years.
1321 //
1322 // RFC 5280 also says "CAs issuing certificates with a restriction of the form
1323 // directoryName SHOULD NOT rely on implementation of the full
1324 // ISO DN name comparison algorithm. This implies name restrictions MUST
1325 // be stated identically to the encoding used in the subject field or
1326 // subjectAltName extension." It goes on to say, in the security
1327 // considerations:
1328 //
1329 //     In addition, name constraints for distinguished names MUST be stated
1330 //     identically to the encoding used in the subject field or
1331 //     subjectAltName extension.  If not, then name constraints stated as
1332 //     excludedSubtrees will not match and invalid paths will be accepted
1333 //     and name constraints expressed as permittedSubtrees will not match
1334 //     and valid paths will be rejected.  To avoid acceptance of invalid
1335 //     paths, CAs SHOULD state name constraints for distinguished names as
1336 //     permittedSubtrees wherever possible.
1337 //
1338 // For permittedSubtrees, the MUST-level requirement is relaxed for
1339 // compatibility in the case of PrintableString and UTF8String. That is, if a
1340 // name constraint has been encoded using UTF8String and the presented ID has
1341 // been encoded with a PrintableString (or vice-versa), they are considered to
1342 // match if they are equal everywhere except for the tag identifying the
1343 // encoding. See bug 1150114.
1344 //
1345 // For excludedSubtrees, we simply prohibit any non-empty directoryName
1346 // constraint to ensure we are not being too lenient. We support empty
1347 // DirectoryName constraints in excludedSubtrees so that a CA can say "Do not
1348 // allow any DirectoryNames in issued certificates."
1349 Result
MatchPresentedDirectoryNameWithConstraint(NameConstraintsSubtrees subtreesType,Input presentedID,Input directoryNameConstraint,bool & matches)1350 MatchPresentedDirectoryNameWithConstraint(NameConstraintsSubtrees subtreesType,
1351                                           Input presentedID,
1352                                           Input directoryNameConstraint,
1353                                           /*out*/ bool& matches)
1354 {
1355   Reader constraintRDNs;
1356   Result rv = der::ExpectTagAndGetValueAtEnd(directoryNameConstraint,
1357                                              der::SEQUENCE, constraintRDNs);
1358   if (rv != Success) {
1359     return rv;
1360   }
1361   Reader presentedRDNs;
1362   rv = der::ExpectTagAndGetValueAtEnd(presentedID, der::SEQUENCE,
1363                                       presentedRDNs);
1364   if (rv != Success) {
1365     return rv;
1366   }
1367 
1368   switch (subtreesType) {
1369     case NameConstraintsSubtrees::permittedSubtrees:
1370       break; // dealt with below
1371     case NameConstraintsSubtrees::excludedSubtrees:
1372       if (!constraintRDNs.AtEnd() || !presentedRDNs.AtEnd()) {
1373         return Result::ERROR_CERT_NOT_IN_NAME_SPACE;
1374       }
1375       matches = true;
1376       return Success;
1377   }
1378 
1379   for (;;) {
1380     // The AVAs have to be fully equal, but the constraint RDNs just need to be
1381     // a prefix of the presented RDNs.
1382     if (constraintRDNs.AtEnd()) {
1383       matches = true;
1384       return Success;
1385     }
1386     if (presentedRDNs.AtEnd()) {
1387       matches = false;
1388       return Success;
1389     }
1390     Reader constraintRDN;
1391     rv = der::ExpectTagAndGetValue(constraintRDNs, der::SET, constraintRDN);
1392     if (rv != Success) {
1393       return rv;
1394     }
1395     Reader presentedRDN;
1396     rv = der::ExpectTagAndGetValue(presentedRDNs, der::SET, presentedRDN);
1397     if (rv != Success) {
1398       return rv;
1399     }
1400     while (!constraintRDN.AtEnd() && !presentedRDN.AtEnd()) {
1401       Input constraintType;
1402       uint8_t constraintValueTag;
1403       Input constraintValue;
1404       rv = ReadAVA(constraintRDN, constraintType, constraintValueTag,
1405                    constraintValue);
1406       if (rv != Success) {
1407         return rv;
1408       }
1409       Input presentedType;
1410       uint8_t presentedValueTag;
1411       Input presentedValue;
1412       rv = ReadAVA(presentedRDN, presentedType, presentedValueTag,
1413                    presentedValue);
1414       if (rv != Success) {
1415         return rv;
1416       }
1417       // TODO (bug 1155767): verify that if an AVA is a PrintableString it
1418       // consists only of characters valid for PrintableStrings.
1419       bool avasMatch =
1420         InputsAreEqual(constraintType, presentedType) &&
1421         InputsAreEqual(constraintValue, presentedValue) &&
1422         (constraintValueTag == presentedValueTag ||
1423          (constraintValueTag == der::Tag::UTF8String &&
1424           presentedValueTag == der::Tag::PrintableString) ||
1425          (constraintValueTag == der::Tag::PrintableString &&
1426           presentedValueTag == der::Tag::UTF8String));
1427       if (!avasMatch) {
1428         matches = false;
1429         return Success;
1430       }
1431     }
1432     if (!constraintRDN.AtEnd() || !presentedRDN.AtEnd()) {
1433       matches = false;
1434       return Success;
1435     }
1436   }
1437 }
1438 
1439 // RFC 5280 says:
1440 //
1441 //     The format of an rfc822Name is a "Mailbox" as defined in Section 4.1.2
1442 //     of [RFC2821]. A Mailbox has the form "Local-part@Domain".  Note that a
1443 //     Mailbox has no phrase (such as a common name) before it, has no comment
1444 //     (text surrounded in parentheses) after it, and is not surrounded by "<"
1445 //     and ">".  Rules for encoding Internet mail addresses that include
1446 //     internationalized domain names are specified in Section 7.5.
1447 //
1448 // and:
1449 //
1450 //     A name constraint for Internet mail addresses MAY specify a
1451 //     particular mailbox, all addresses at a particular host, or all
1452 //     mailboxes in a domain.  To indicate a particular mailbox, the
1453 //     constraint is the complete mail address.  For example,
1454 //     "root@example.com" indicates the root mailbox on the host
1455 //     "example.com".  To indicate all Internet mail addresses on a
1456 //     particular host, the constraint is specified as the host name.  For
1457 //     example, the constraint "example.com" is satisfied by any mail
1458 //     address at the host "example.com".  To specify any address within a
1459 //     domain, the constraint is specified with a leading period (as with
1460 //     URIs).  For example, ".example.com" indicates all the Internet mail
1461 //     addresses in the domain "example.com", but not Internet mail
1462 //     addresses on the host "example.com".
1463 
1464 bool
IsValidRFC822Name(Input input)1465 IsValidRFC822Name(Input input)
1466 {
1467   Reader reader(input);
1468 
1469   // Local-part@.
1470   bool startOfAtom = true;
1471   for (;;) {
1472     uint8_t presentedByte;
1473     if (reader.Read(presentedByte) != Success) {
1474       return false;
1475     }
1476     switch (presentedByte) {
1477       // atext is defined in https://tools.ietf.org/html/rfc2822#section-3.2.4
1478       case 'A': case 'a': case 'N': case 'n': case '0': case '!': case '#':
1479       case 'B': case 'b': case 'O': case 'o': case '1': case '$': case '%':
1480       case 'C': case 'c': case 'P': case 'p': case '2': case '&': case '\'':
1481       case 'D': case 'd': case 'Q': case 'q': case '3': case '*': case '+':
1482       case 'E': case 'e': case 'R': case 'r': case '4': case '-': case '/':
1483       case 'F': case 'f': case 'S': case 's': case '5': case '=': case '?':
1484       case 'G': case 'g': case 'T': case 't': case '6': case '^': case '_':
1485       case 'H': case 'h': case 'U': case 'u': case '7': case '`': case '{':
1486       case 'I': case 'i': case 'V': case 'v': case '8': case '|': case '}':
1487       case 'J': case 'j': case 'W': case 'w': case '9': case '~':
1488       case 'K': case 'k': case 'X': case 'x':
1489       case 'L': case 'l': case 'Y': case 'y':
1490       case 'M': case 'm': case 'Z': case 'z':
1491         startOfAtom = false;
1492         break;
1493 
1494       case '.':
1495         if (startOfAtom) {
1496           return false;
1497         }
1498         startOfAtom = true;
1499         break;
1500 
1501       case '@':
1502       {
1503         if (startOfAtom) {
1504           return false;
1505         }
1506         Input domain;
1507         if (reader.SkipToEnd(domain) != Success) {
1508           return false;
1509         }
1510         return IsValidDNSID(domain, IDRole::PresentedID, AllowWildcards::No);
1511       }
1512 
1513       default:
1514         return false;
1515     }
1516   }
1517 }
1518 
1519 Result
MatchPresentedRFC822NameWithReferenceRFC822Name(Input presentedRFC822Name,IDRole referenceRFC822NameRole,Input referenceRFC822Name,bool & matches)1520 MatchPresentedRFC822NameWithReferenceRFC822Name(Input presentedRFC822Name,
1521                                                 IDRole referenceRFC822NameRole,
1522                                                 Input referenceRFC822Name,
1523                                                 /*out*/ bool& matches)
1524 {
1525   if (!IsValidRFC822Name(presentedRFC822Name)) {
1526     return Result::ERROR_BAD_DER;
1527   }
1528   Reader presented(presentedRFC822Name);
1529 
1530   switch (referenceRFC822NameRole)
1531   {
1532     case IDRole::PresentedID:
1533       return Result::FATAL_ERROR_INVALID_ARGS;
1534 
1535     case IDRole::ReferenceID:
1536       break;
1537 
1538     case IDRole::NameConstraint:
1539     {
1540       if (InputContains(referenceRFC822Name, '@')) {
1541         // The constraint is of the form "Local-part@Domain".
1542         break;
1543       }
1544 
1545       // The constraint is of the form "example.com" or ".example.com".
1546 
1547       // Skip past the '@' in the presented ID.
1548       for (;;) {
1549         uint8_t presentedByte;
1550         if (presented.Read(presentedByte) != Success) {
1551           return Result::FATAL_ERROR_LIBRARY_FAILURE;
1552         }
1553         if (presentedByte == '@') {
1554           break;
1555         }
1556       }
1557 
1558       Input presentedDNSID;
1559       if (presented.SkipToEnd(presentedDNSID) != Success) {
1560         return Result::FATAL_ERROR_LIBRARY_FAILURE;
1561       }
1562 
1563       return MatchPresentedDNSIDWithReferenceDNSID(
1564                presentedDNSID, AllowWildcards::No,
1565                AllowDotlessSubdomainMatches::No, IDRole::NameConstraint,
1566                referenceRFC822Name, matches);
1567     }
1568   }
1569 
1570   if (!IsValidRFC822Name(referenceRFC822Name)) {
1571     return Result::ERROR_BAD_DER;
1572   }
1573 
1574   Reader reference(referenceRFC822Name);
1575 
1576   for (;;) {
1577     uint8_t presentedByte;
1578     if (presented.Read(presentedByte) != Success) {
1579       matches = reference.AtEnd();
1580       return Success;
1581     }
1582     uint8_t referenceByte;
1583     if (reference.Read(referenceByte) != Success) {
1584       matches = false;
1585       return Success;
1586     }
1587     if (LocaleInsensitveToLower(presentedByte) !=
1588         LocaleInsensitveToLower(referenceByte)) {
1589       matches = false;
1590       return Success;
1591     }
1592   }
1593 }
1594 
1595 // We avoid isdigit because it is locale-sensitive. See
1596 // http://pubs.opengroup.org/onlinepubs/009695399/functions/tolower.html.
1597 inline uint8_t
LocaleInsensitveToLower(uint8_t a)1598 LocaleInsensitveToLower(uint8_t a)
1599 {
1600   if (a >= 'A' && a <= 'Z') { // unlikely
1601     return static_cast<uint8_t>(
1602              static_cast<uint8_t>(a - static_cast<uint8_t>('A')) +
1603              static_cast<uint8_t>('a'));
1604   }
1605   return a;
1606 }
1607 
1608 bool
StartsWithIDNALabel(Input id)1609 StartsWithIDNALabel(Input id)
1610 {
1611   static const uint8_t IDN_ALABEL_PREFIX[4] = { 'x', 'n', '-', '-' };
1612   Reader input(id);
1613   for (const uint8_t prefixByte : IDN_ALABEL_PREFIX) {
1614     uint8_t b;
1615     if (input.Read(b) != Success) {
1616       return false;
1617     }
1618     if (b != prefixByte) {
1619       return false;
1620     }
1621   }
1622   return true;
1623 }
1624 
1625 bool
ReadIPv4AddressComponent(Reader & input,bool lastComponent,uint8_t & valueOut)1626 ReadIPv4AddressComponent(Reader& input, bool lastComponent,
1627                          /*out*/ uint8_t& valueOut)
1628 {
1629   size_t length = 0;
1630   unsigned int value = 0; // Must be larger than uint8_t.
1631 
1632   for (;;) {
1633     if (input.AtEnd() && lastComponent) {
1634       break;
1635     }
1636 
1637     uint8_t b;
1638     if (input.Read(b) != Success) {
1639       return false;
1640     }
1641 
1642     if (b >= '0' && b <= '9') {
1643       if (value == 0 && length > 0) {
1644         return false; // Leading zeros are not allowed.
1645       }
1646       value = (value * 10) + (b - '0');
1647       if (value > 255) {
1648         return false; // Component's value is too large.
1649       }
1650       ++length;
1651     } else if (!lastComponent && b == '.') {
1652       break;
1653     } else {
1654       return false; // Invalid character.
1655     }
1656   }
1657 
1658   if (length == 0) {
1659     return false; // empty components not allowed
1660   }
1661 
1662   valueOut = static_cast<uint8_t>(value);
1663   return true;
1664 }
1665 
1666 } // namespace
1667 
1668 // On Windows and maybe other platforms, OS-provided IP address parsing
1669 // functions might fail if the protocol (IPv4 or IPv6) has been disabled, so we
1670 // can't rely on them.
1671 bool
ParseIPv4Address(Input hostname,uint8_t (& out)[4])1672 ParseIPv4Address(Input hostname, /*out*/ uint8_t (&out)[4])
1673 {
1674   Reader input(hostname);
1675   return ReadIPv4AddressComponent(input, false, out[0]) &&
1676          ReadIPv4AddressComponent(input, false, out[1]) &&
1677          ReadIPv4AddressComponent(input, false, out[2]) &&
1678          ReadIPv4AddressComponent(input, true, out[3]);
1679 }
1680 
1681 namespace {
1682 
1683 bool
FinishIPv6Address(uint8_t (& address)[16],int numComponents,int contractionIndex)1684 FinishIPv6Address(/*in/out*/ uint8_t (&address)[16], int numComponents,
1685                   int contractionIndex)
1686 {
1687   assert(numComponents >= 0);
1688   assert(numComponents <= 8);
1689   assert(contractionIndex >= -1);
1690   assert(contractionIndex <= 8);
1691   assert(contractionIndex <= numComponents);
1692   if (!(numComponents >= 0 &&
1693         numComponents <= 8 &&
1694         contractionIndex >= -1 &&
1695         contractionIndex <= 8 &&
1696         contractionIndex <= numComponents)) {
1697     return false;
1698   }
1699 
1700   if (contractionIndex == -1) {
1701     // no contraction
1702     return numComponents == 8;
1703   }
1704 
1705   if (numComponents >= 8) {
1706     return false; // no room left to expand the contraction.
1707   }
1708 
1709   // Shift components that occur after the contraction over.
1710   std::copy_backward(address + (2u * static_cast<size_t>(contractionIndex)),
1711                      address + (2u * static_cast<size_t>(numComponents)),
1712                      address + (2u * 8u));
1713   // Fill in the contracted area with zeros.
1714   std::fill_n(address + 2u * static_cast<size_t>(contractionIndex),
1715               (8u - static_cast<size_t>(numComponents)) * 2u, static_cast<uint8_t>(0u));
1716 
1717   return true;
1718 }
1719 
1720 } // namespace
1721 
1722 // On Windows and maybe other platforms, OS-provided IP address parsing
1723 // functions might fail if the protocol (IPv4 or IPv6) has been disabled, so we
1724 // can't rely on them.
1725 bool
ParseIPv6Address(Input hostname,uint8_t (& out)[16])1726 ParseIPv6Address(Input hostname, /*out*/ uint8_t (&out)[16])
1727 {
1728   Reader input(hostname);
1729 
1730   int currentComponentIndex = 0;
1731   int contractionIndex = -1;
1732 
1733   if (input.Peek(':')) {
1734     // A valid input can only start with ':' if there is a contraction at the
1735     // beginning.
1736     uint8_t b;
1737     if (input.Read(b) != Success || b != ':') {
1738       assert(false);
1739       return false;
1740     }
1741     if (input.Read(b) != Success) {
1742       return false;
1743     }
1744     if (b != ':') {
1745       return false;
1746     }
1747     contractionIndex = 0;
1748   }
1749 
1750   for (;;) {
1751     // If we encounter a '.' then we'll have to backtrack to parse the input
1752     // from startOfComponent to the end of the input as an IPv4 address.
1753     Reader::Mark startOfComponent(input.GetMark());
1754     uint16_t componentValue = 0;
1755     size_t componentLength = 0;
1756     while (!input.AtEnd() && !input.Peek(':')) {
1757       uint8_t value;
1758       uint8_t b;
1759       if (input.Read(b) != Success) {
1760         assert(false);
1761         return false;
1762       }
1763       switch (b) {
1764         case '0': case '1': case '2': case '3': case '4':
1765         case '5': case '6': case '7': case '8': case '9':
1766           value = static_cast<uint8_t>(b - static_cast<uint8_t>('0'));
1767           break;
1768         case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
1769           value = static_cast<uint8_t>(b - static_cast<uint8_t>('a') +
1770                                        UINT8_C(10));
1771           break;
1772         case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
1773           value = static_cast<uint8_t>(b - static_cast<uint8_t>('A') +
1774                                        UINT8_C(10));
1775           break;
1776         case '.':
1777         {
1778           // A dot indicates we hit a IPv4-syntax component. Backtrack, parsing
1779           // the input from startOfComponent to the end of the input as an IPv4
1780           // address, and then combine it with the other components.
1781 
1782           if (currentComponentIndex > 6) {
1783             return false; // Too many components before the IPv4 component
1784           }
1785 
1786           input.SkipToEnd();
1787           Input ipv4Component;
1788           if (input.GetInput(startOfComponent, ipv4Component) != Success) {
1789             return false;
1790           }
1791           uint8_t (*ipv4)[4] =
1792             reinterpret_cast<uint8_t(*)[4]>(&out[2 * currentComponentIndex]);
1793           if (!ParseIPv4Address(ipv4Component, *ipv4)) {
1794             return false;
1795           }
1796           assert(input.AtEnd());
1797           currentComponentIndex += 2;
1798 
1799           return FinishIPv6Address(out, currentComponentIndex,
1800                                    contractionIndex);
1801         }
1802         default:
1803           return false;
1804       }
1805       if (componentLength >= 4) {
1806         // component too long
1807         return false;
1808       }
1809       ++componentLength;
1810       componentValue = (componentValue * 0x10u) + value;
1811     }
1812 
1813     if (currentComponentIndex >= 8) {
1814       return false; // too many components
1815     }
1816 
1817     if (componentLength == 0) {
1818       if (input.AtEnd() && currentComponentIndex == contractionIndex) {
1819         if (contractionIndex == 0) {
1820           // don't accept "::"
1821           return false;
1822         }
1823         return FinishIPv6Address(out, currentComponentIndex,
1824                                  contractionIndex);
1825       }
1826       return false;
1827     }
1828 
1829     out[2 * currentComponentIndex] =
1830       static_cast<uint8_t>(componentValue / 0x100);
1831     out[(2 * currentComponentIndex) + 1] =
1832       static_cast<uint8_t>(componentValue % 0x100);
1833 
1834     ++currentComponentIndex;
1835 
1836     if (input.AtEnd()) {
1837       return FinishIPv6Address(out, currentComponentIndex,
1838                                contractionIndex);
1839     }
1840 
1841     uint8_t b;
1842     if (input.Read(b) != Success || b != ':') {
1843       assert(false);
1844       return false;
1845     }
1846 
1847     if (input.Peek(':')) {
1848       // Contraction
1849       if (contractionIndex != -1) {
1850         return false; // multiple contractions are not allowed.
1851       }
1852       if (input.Read(b) != Success || b != ':') {
1853         assert(false);
1854         return false;
1855       }
1856       contractionIndex = currentComponentIndex;
1857       if (input.AtEnd()) {
1858         // "::" at the end of the input.
1859         return FinishIPv6Address(out, currentComponentIndex,
1860                                  contractionIndex);
1861       }
1862     }
1863   }
1864 }
1865 
1866 bool
IsValidReferenceDNSID(Input hostname)1867 IsValidReferenceDNSID(Input hostname)
1868 {
1869   return IsValidDNSID(hostname, IDRole::ReferenceID, AllowWildcards::No);
1870 }
1871 
1872 bool
IsValidPresentedDNSID(Input hostname)1873 IsValidPresentedDNSID(Input hostname)
1874 {
1875   return IsValidDNSID(hostname, IDRole::PresentedID, AllowWildcards::Yes);
1876 }
1877 
1878 namespace {
1879 
1880 // RFC 5280 Section 4.2.1.6 says that a dNSName "MUST be in the 'preferred name
1881 // syntax', as specified by Section 3.5 of [RFC1034] and as modified by Section
1882 // 2.1 of [RFC1123]" except "a dNSName of ' ' MUST NOT be used." Additionally,
1883 // we allow underscores for compatibility with existing practice.
1884 bool
IsValidDNSID(Input hostname,IDRole idRole,AllowWildcards allowWildcards)1885 IsValidDNSID(Input hostname, IDRole idRole, AllowWildcards allowWildcards)
1886 {
1887   if (hostname.GetLength() > 253) {
1888     return false;
1889   }
1890 
1891   Reader input(hostname);
1892 
1893   if (idRole == IDRole::NameConstraint && input.AtEnd()) {
1894     return true;
1895   }
1896 
1897   size_t dotCount = 0;
1898   size_t labelLength = 0;
1899   bool labelIsAllNumeric = false;
1900   bool labelEndsWithHyphen = false;
1901 
1902   // Only presented IDs are allowed to have wildcard labels. And, like
1903   // Chromium, be stricter than RFC 6125 requires by insisting that a
1904   // wildcard label consist only of '*'.
1905   bool isWildcard = allowWildcards == AllowWildcards::Yes && input.Peek('*');
1906   bool isFirstByte = !isWildcard;
1907   if (isWildcard) {
1908     Result rv = input.Skip(1);
1909     if (rv != Success) {
1910       assert(false);
1911       return false;
1912     }
1913 
1914     uint8_t b;
1915     rv = input.Read(b);
1916     if (rv != Success) {
1917       return false;
1918     }
1919     if (b != '.') {
1920       return false;
1921     }
1922     ++dotCount;
1923   }
1924 
1925   do {
1926     static const size_t MAX_LABEL_LENGTH = 63;
1927 
1928     uint8_t b;
1929     if (input.Read(b) != Success) {
1930       return false;
1931     }
1932     switch (b) {
1933       case '-':
1934         if (labelLength == 0) {
1935           return false; // Labels must not start with a hyphen.
1936         }
1937         labelIsAllNumeric = false;
1938         labelEndsWithHyphen = true;
1939         ++labelLength;
1940         if (labelLength > MAX_LABEL_LENGTH) {
1941           return false;
1942         }
1943         break;
1944 
1945       // We avoid isdigit because it is locale-sensitive. See
1946       // http://pubs.opengroup.org/onlinepubs/009695399/functions/isdigit.html
1947       case '0': case '5':
1948       case '1': case '6':
1949       case '2': case '7':
1950       case '3': case '8':
1951       case '4': case '9':
1952         if (labelLength == 0) {
1953           labelIsAllNumeric = true;
1954         }
1955         labelEndsWithHyphen = false;
1956         ++labelLength;
1957         if (labelLength > MAX_LABEL_LENGTH) {
1958           return false;
1959         }
1960         break;
1961 
1962       // We avoid using islower/isupper/tolower/toupper or similar things, to
1963       // avoid any possibility of this code being locale-sensitive. See
1964       // http://pubs.opengroup.org/onlinepubs/009695399/functions/isupper.html
1965       case 'a': case 'A': case 'n': case 'N':
1966       case 'b': case 'B': case 'o': case 'O':
1967       case 'c': case 'C': case 'p': case 'P':
1968       case 'd': case 'D': case 'q': case 'Q':
1969       case 'e': case 'E': case 'r': case 'R':
1970       case 'f': case 'F': case 's': case 'S':
1971       case 'g': case 'G': case 't': case 'T':
1972       case 'h': case 'H': case 'u': case 'U':
1973       case 'i': case 'I': case 'v': case 'V':
1974       case 'j': case 'J': case 'w': case 'W':
1975       case 'k': case 'K': case 'x': case 'X':
1976       case 'l': case 'L': case 'y': case 'Y':
1977       case 'm': case 'M': case 'z': case 'Z':
1978       // We allow underscores for compatibility with existing practices.
1979       // See bug 1136616.
1980       case '_':
1981         labelIsAllNumeric = false;
1982         labelEndsWithHyphen = false;
1983         ++labelLength;
1984         if (labelLength > MAX_LABEL_LENGTH) {
1985           return false;
1986         }
1987         break;
1988 
1989       case '.':
1990         ++dotCount;
1991         if (labelLength == 0 &&
1992             (idRole != IDRole::NameConstraint || !isFirstByte)) {
1993           return false;
1994         }
1995         if (labelEndsWithHyphen) {
1996           return false; // Labels must not end with a hyphen.
1997         }
1998         labelLength = 0;
1999         break;
2000 
2001       default:
2002         return false; // Invalid character.
2003     }
2004     isFirstByte = false;
2005   } while (!input.AtEnd());
2006 
2007   // Only reference IDs, not presented IDs or name constraints, may be
2008   // absolute.
2009   if (labelLength == 0 && idRole != IDRole::ReferenceID) {
2010     return false;
2011   }
2012 
2013   if (labelEndsWithHyphen) {
2014     return false; // Labels must not end with a hyphen.
2015   }
2016 
2017   if (labelIsAllNumeric) {
2018     return false; // Last label must not be all numeric.
2019   }
2020 
2021   if (isWildcard) {
2022     // If the DNS ID ends with a dot, the last dot signifies an absolute ID.
2023     size_t labelCount = (labelLength == 0) ? dotCount : (dotCount + 1);
2024 
2025     // Like NSS, require at least two labels to follow the wildcard label.
2026     //
2027     // TODO(bug XXXXXXX): Allow the TrustDomain to control this on a
2028     // per-eTLD+1 basis, similar to Chromium. Even then, it might be better to
2029     // still enforce that there are at least two labels after the wildcard.
2030     if (labelCount < 3) {
2031       return false;
2032     }
2033     // XXX: RFC6125 says that we shouldn't accept wildcards within an IDN
2034     // A-Label. The consequence of this is that we effectively discriminate
2035     // against users of languages that cannot be encoded with ASCII.
2036     if (StartsWithIDNALabel(hostname)) {
2037       return false;
2038     }
2039 
2040     // TODO(bug XXXXXXX): Wildcards are not allowed for EV certificates.
2041     // Provide an option to indicate whether wildcards should be matched, for
2042     // the purpose of helping the application enforce this.
2043   }
2044 
2045   return true;
2046 }
2047 
2048 } // namespace
2049 
2050 } } // namespace mozilla::pkix
2051