LCOV - code coverage report
Current view: top level - security/pkix/lib - pkixnames.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 0 666 0.0 %
Date: 2017-07-14 16:53:18 Functions: 0 28 0.0 %
Legend: Lines: hit not hit

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

Generated by: LCOV version 1.13