/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This code is made available to you under your choice of the following sets
 * of licensing terms:
 */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */
/* Copyright 2014 Mozilla Contributors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// This code implements RFC6125-ish name matching, RFC5280-ish name constraint
// checking, and related things.
//
// In this code, identifiers are classified as either "presented" or
// "reference" identifiers are defined in
// http://tools.ietf.org/html/rfc6125#section-1.8. A "presented identifier" is
// one in the subjectAltName of the certificate, or sometimes within a CN of
// the certificate's subject. The "reference identifier" is the one we are
// being asked to match the certificate against. When checking name
// constraints, the reference identifier is the entire encoded name constraint
// extension value.

#include <algorithm>

#include "mozpkix/pkixcheck.h"
#include "mozpkix/pkixutil.h"

namespace mozilla { namespace pkix {

namespace {

// GeneralName ::= CHOICE {
//      otherName                       [0]     OtherName,
//      rfc822Name                      [1]     IA5String,
//      dNSName                         [2]     IA5String,
//      x400Address                     [3]     ORAddress,
//      directoryName                   [4]     Name,
//      ediPartyName                    [5]     EDIPartyName,
//      uniformResourceIdentifier       [6]     IA5String,
//      iPAddress                       [7]     OCTET STRING,
//      registeredID                    [8]     OBJECT IDENTIFIER }
enum class GeneralNameType : uint8_t
{
  // Note that these values are NOT contiguous. Some values have the
  // der::CONSTRUCTED bit set while others do not.
  // (The der::CONSTRUCTED bit is for types where the value is a SEQUENCE.)
  otherName = der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 0,
  rfc822Name = der::CONTEXT_SPECIFIC | 1,
  dNSName = der::CONTEXT_SPECIFIC | 2,
  x400Address = der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 3,
  directoryName = der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 4,
  ediPartyName = der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 5,
  uniformResourceIdentifier = der::CONTEXT_SPECIFIC | 6,
  iPAddress = der::CONTEXT_SPECIFIC | 7,
  registeredID = der::CONTEXT_SPECIFIC | 8,
  // nameConstraints is a pseudo-GeneralName used to signify that a
  // reference ID is actually the entire name constraint extension.
  nameConstraints = 0xff
};

inline Result
ReadGeneralName(Reader& reader,
                /*out*/ GeneralNameType& generalNameType,
                /*out*/ Input& value)
{
  uint8_t tag;
  Result rv = der::ReadTagAndGetValue(reader, tag, value);
  if (rv != Success) {
    return rv;
  }
  switch (tag) {
    case static_cast<uint8_t>(GeneralNameType::otherName):
      generalNameType = GeneralNameType::otherName;
      break;
    case static_cast<uint8_t>(GeneralNameType::rfc822Name):
      generalNameType = GeneralNameType::rfc822Name;
      break;
    case static_cast<uint8_t>(GeneralNameType::dNSName):
      generalNameType = GeneralNameType::dNSName;
      break;
    case static_cast<uint8_t>(GeneralNameType::x400Address):
      generalNameType = GeneralNameType::x400Address;
      break;
    case static_cast<uint8_t>(GeneralNameType::directoryName):
      generalNameType = GeneralNameType::directoryName;
      break;
    case static_cast<uint8_t>(GeneralNameType::ediPartyName):
      generalNameType = GeneralNameType::ediPartyName;
      break;
    case static_cast<uint8_t>(GeneralNameType::uniformResourceIdentifier):
      generalNameType = GeneralNameType::uniformResourceIdentifier;
      break;
    case static_cast<uint8_t>(GeneralNameType::iPAddress):
      generalNameType = GeneralNameType::iPAddress;
      break;
    case static_cast<uint8_t>(GeneralNameType::registeredID):
      generalNameType = GeneralNameType::registeredID;
      break;
    default:
      return Result::ERROR_BAD_DER;
  }
  return Success;
}

enum class MatchResult
{
  NoNamesOfGivenType = 0,
  Mismatch = 1,
  Match = 2
};

Result SearchNames(const Input* subjectAltName, Input subject,
                   GeneralNameType referenceIDType,
                   Input referenceID,
                   FallBackToSearchWithinSubject fallBackToCommonName,
                   HandleInvalidSubjectAlternativeNamesBy handleInvalidSANsBy,
                   /*out*/ MatchResult& match);
Result SearchWithinRDN(Reader& rdn,
                       GeneralNameType referenceIDType,
                       Input referenceID,
                       FallBackToSearchWithinSubject fallBackToEmailAddress,
                       FallBackToSearchWithinSubject fallBackToCommonName,
                       /*in/out*/ MatchResult& match);
Result MatchAVA(Input type,
                uint8_t valueEncodingTag,
                Input presentedID,
                GeneralNameType referenceIDType,
                Input referenceID,
                FallBackToSearchWithinSubject fallBackToEmailAddress,
                FallBackToSearchWithinSubject fallBackToCommonName,
                /*in/out*/ MatchResult& match);
Result ReadAVA(Reader& rdn,
               /*out*/ Input& type,
               /*out*/ uint8_t& valueTag,
               /*out*/ Input& value);
void MatchSubjectPresentedIDWithReferenceID(GeneralNameType presentedIDType,
                                            Input presentedID,
                                            GeneralNameType referenceIDType,
                                            Input referenceID,
                                            /*in/out*/ MatchResult& match);

Result MatchPresentedIDWithReferenceID(GeneralNameType presentedIDType,
                                       Input presentedID,
                                       GeneralNameType referenceIDType,
                                       Input referenceID,
                                       /*in/out*/ MatchResult& matchResult);
Result CheckPresentedIDConformsToConstraints(GeneralNameType referenceIDType,
                                             Input presentedID,
                                             Input nameConstraints);

uint8_t LocaleInsensitveToLower(uint8_t a);
bool StartsWithIDNALabel(Input id);

enum class IDRole
{
  ReferenceID = 0,
  PresentedID = 1,
  NameConstraint = 2,
};

enum class NameConstraintsSubtrees : uint8_t
{
  permittedSubtrees = der::CONSTRUCTED | der::CONTEXT_SPECIFIC | 0,
  excludedSubtrees  = der::CONSTRUCTED | der::CONTEXT_SPECIFIC | 1
};

enum class AllowWildcards { No = 0, Yes = 1 };

// DNSName constraints implicitly allow subdomain matching when there is no
// leading dot ("foo.example.com" matches a constraint of "example.com"), but
// RFC822Name constraints only allow subdomain matching when there is a leading
// dot ("foo.example.com" does not match "example.com" but does match
// ".example.com").
enum class AllowDotlessSubdomainMatches { No = 0, Yes = 1 };

bool IsValidDNSID(Input hostname, IDRole idRole,
                  AllowWildcards allowWildcards);

// `subtreesType` is relevant only when `referenceDNSIDRole` is
// `IDRole::NameConstraint`.
Result MatchPresentedDNSIDWithReferenceDNSID(
         Input presentedDNSID,
         AllowWildcards allowWildcards,
         AllowDotlessSubdomainMatches allowDotlessSubdomainMatches,
         IDRole referenceDNSIDRole,
         /*optional*/ const NameConstraintsSubtrees* subtreesType,
         Input referenceDNSID,
         /*out*/ bool& matches);

// `subtreesType` is relevant only when `referenceDNSIDRole` is
// `IDRole::NameConstraint`.
Result MatchPresentedRFC822NameWithReferenceRFC822Name(
         Input presentedRFC822Name, IDRole referenceRFC822NameRole,
         /*optional*/ const NameConstraintsSubtrees* subtreesType,
         Input referenceRFC822Name, /*out*/ bool& matches);

} // namespace

bool IsValidReferenceDNSID(Input hostname);
bool IsValidPresentedDNSID(Input hostname);
bool ParseIPv4Address(Input hostname, /*out*/ uint8_t (&out)[4]);
bool ParseIPv6Address(Input hostname, /*out*/ uint8_t (&out)[16]);

// This is used by the pkixnames_tests.cpp tests.
Result
MatchPresentedDNSIDWithReferenceDNSID(Input presentedDNSID,
                                      Input referenceDNSID,
                                      /*out*/ bool& matches)
{
  return MatchPresentedDNSIDWithReferenceDNSID(
           presentedDNSID, AllowWildcards::Yes,
           AllowDotlessSubdomainMatches::Yes, IDRole::ReferenceID,
           nullptr, referenceDNSID, matches);
}

// Verify that the given end-entity cert, which is assumed to have been already
// validated with BuildCertChain, is valid for the given hostname. hostname is
// assumed to be a string representation of an IPv4 address, an IPv6 addresss,
// or a normalized ASCII (possibly punycode) DNS name.
Result
CheckCertHostname(Input endEntityCertDER, Input hostname,
                  NameMatchingPolicy& nameMatchingPolicy)
{
  BackCert cert(endEntityCertDER, EndEntityOrCA::MustBeEndEntity, nullptr);
  Result rv = cert.Init();
  if (rv != Success) {
    return rv;
  }

  Time notBefore(Time::uninitialized);
  rv = ParseValidity(cert.GetValidity(), &notBefore);
  if (rv != Success) {
    return rv;
  }
  FallBackToSearchWithinSubject fallBackToSearchWithinSubject;
  rv = nameMatchingPolicy.FallBackToCommonName(notBefore,
                                               fallBackToSearchWithinSubject);
  if (rv != Success) {
    return rv;
  }
  HandleInvalidSubjectAlternativeNamesBy handleInvalidSANsBy =
    nameMatchingPolicy.HandleInvalidSubjectAlternativeNames();

  const Input* subjectAltName(cert.GetSubjectAltName());
  Input subject(cert.GetSubject());

  // For backward compatibility with legacy certificates, we may fall back to
  // searching for a name match in the subject common name for DNS names and
  // IPv4 addresses. We don't do so for IPv6 addresses because we do not think
  // there are many certificates that would need such fallback, and because
  // comparisons of string representations of IPv6 addresses are particularly
  // error prone due to the syntactic flexibility that IPv6 addresses have.
  //
  // IPv4 and IPv6 addresses are represented using the same type of GeneralName
  // (iPAddress); they are differentiated by the lengths of the values.
  MatchResult match;
  uint8_t ipv6[16];
  uint8_t ipv4[4];
  if (IsValidReferenceDNSID(hostname)) {
    rv = SearchNames(subjectAltName, subject, GeneralNameType::dNSName,
                     hostname, fallBackToSearchWithinSubject,
                     handleInvalidSANsBy, match);
  } else if (ParseIPv6Address(hostname, ipv6)) {
    rv = SearchNames(subjectAltName, subject, GeneralNameType::iPAddress,
                     Input(ipv6), FallBackToSearchWithinSubject::No,
                     HandleInvalidSubjectAlternativeNamesBy::Halting, match);
  } else if (ParseIPv4Address(hostname, ipv4)) {
    rv = SearchNames(subjectAltName, subject, GeneralNameType::iPAddress,
                     Input(ipv4), fallBackToSearchWithinSubject,
                     HandleInvalidSubjectAlternativeNamesBy::Halting, match);
  } else {
    return Result::ERROR_BAD_CERT_DOMAIN;
  }
  if (rv != Success) {
    return rv;
  }
  switch (match) {
    case MatchResult::NoNamesOfGivenType: // fall through
    case MatchResult::Mismatch:
      return Result::ERROR_BAD_CERT_DOMAIN;
    case MatchResult::Match:
      return Success;
    MOZILLA_PKIX_UNREACHABLE_DEFAULT_ENUM
  }
}

// A strict name matching policy for CheckCertHostname which never
// falls back to searching within the subject name.
Result StrictNameMatchingPolicy::FallBackToCommonName(
    Time notBefore,
    /*out*/ FallBackToSearchWithinSubject& fallBackToCommonName) {
  fallBackToCommonName = FallBackToSearchWithinSubject::No;
  return Success;
}

// A strict name matching policy for CheckCertHostname which halts with an
// error upon encountering an invalid subject alternative name entry.
HandleInvalidSubjectAlternativeNamesBy
StrictNameMatchingPolicy::HandleInvalidSubjectAlternativeNames() {
  return HandleInvalidSubjectAlternativeNamesBy::Halting;
}

Result
CheckCertHostname(Input endEntityCertDER, Input hostname)
{
  StrictNameMatchingPolicy policy{};
  return CheckCertHostname(endEntityCertDER, hostname, policy);
}


// 4.2.1.10. Name Constraints
Result
CheckNameConstraints(Input encodedNameConstraints,
                     const BackCert& firstChild,
                     KeyPurposeId requiredEKUIfPresent)
{
  for (const BackCert* child = &firstChild; child; child = child->childCert) {
    FallBackToSearchWithinSubject fallBackToCommonName
      = (child->endEntityOrCA == EndEntityOrCA::MustBeEndEntity &&
         requiredEKUIfPresent == KeyPurposeId::id_kp_serverAuth)
      ? FallBackToSearchWithinSubject::Yes
      : FallBackToSearchWithinSubject::No;

    MatchResult match;
    Result rv = SearchNames(child->GetSubjectAltName(), child->GetSubject(),
                            GeneralNameType::nameConstraints,
                            encodedNameConstraints, fallBackToCommonName,
                            HandleInvalidSubjectAlternativeNamesBy::Halting,
                            match);
    if (rv != Success) {
      return rv;
    }
    switch (match) {
      case MatchResult::Match: // fall through
      case MatchResult::NoNamesOfGivenType:
        break;
      case MatchResult::Mismatch:
        return Result::ERROR_CERT_NOT_IN_NAME_SPACE;
    }
  }

  return Success;
}

namespace {

// SearchNames is used by CheckCertHostname and CheckNameConstraints.
//
// When called during name constraint checking, referenceIDType is
// GeneralNameType::nameConstraints and referenceID is the entire encoded name
// constraints extension value.
//
// The main benefit of using the exact same code paths for both is that we
// ensure consistency between name validation and name constraint enforcement
// regarding thing like "Which CN attributes should be considered as potential
// CN-IDs" and "Which character sets are acceptable for CN-IDs?" If the name
// matching and the name constraint enforcement logic were out of sync on these
// issues (e.g. if name matching were to consider all subject CN attributes,
// but name constraints were only enforced on the most specific subject CN),
// trivial name constraint bypasses could result.

Result
SearchNames(/*optional*/ const Input* subjectAltName,
            Input subject,
            GeneralNameType referenceIDType,
            Input referenceID,
            FallBackToSearchWithinSubject fallBackToCommonName,
            HandleInvalidSubjectAlternativeNamesBy handleInvalidSANsBy,
            /*out*/ MatchResult& match)
{
  Result rv;

  match = MatchResult::NoNamesOfGivenType;

  // RFC 6125 says "A client MUST NOT seek a match for a reference identifier
  // of CN-ID if the presented identifiers include a DNS-ID, SRV-ID, URI-ID, or
  // any application-specific identifier types supported by the client."
  // Accordingly, we only consider CN-IDs if there are no DNS-IDs in the
  // subjectAltName.
  //
  // RFC 6125 says that IP addresses are out of scope, but for backward
  // compatibility we accept them, by considering IP addresses to be an
  // "application-specific identifier type supported by the client."
  //
  // TODO(bug XXXXXXX): Consider strengthening this check to "A client MUST NOT
  // seek a match for a reference identifier of CN-ID if the certificate
  // contains a subjectAltName extension."
  //
  // TODO(bug XXXXXXX): Consider dropping support for IP addresses as
  // identifiers completely.

  if (subjectAltName) {
    Reader altNames;
    rv = der::ExpectTagAndGetValueAtEnd(*subjectAltName, der::SEQUENCE,
                                        altNames);
    if (rv != Success) {
      return rv;
    }

    // According to RFC 5280, "If the subjectAltName extension is present, the
    // sequence MUST contain at least one entry." For compatibility reasons, we
    // do not enforce this. See bug 1143085.
    while (!altNames.AtEnd()) {
      GeneralNameType presentedIDType;
      Input presentedID;
      rv = ReadGeneralName(altNames, presentedIDType, presentedID);
      if (rv != Success) {
        return rv;
      }

      rv = MatchPresentedIDWithReferenceID(presentedIDType, presentedID,
                                           referenceIDType, referenceID,
                                           match);
      if (rv != Success) {
        if (!IsFatalError(rv) &&
            handleInvalidSANsBy == HandleInvalidSubjectAlternativeNamesBy::Skipping) {
          continue;
        }
        return rv;
      }
      if (referenceIDType != GeneralNameType::nameConstraints &&
          match == MatchResult::Match) {
        return Success;
      }
      if (presentedIDType == GeneralNameType::dNSName ||
          presentedIDType == GeneralNameType::iPAddress) {
        fallBackToCommonName = FallBackToSearchWithinSubject::No;
      }
    }
  }

  if (referenceIDType == GeneralNameType::nameConstraints) {
    rv = CheckPresentedIDConformsToConstraints(GeneralNameType::directoryName,
                                               subject, referenceID);
    if (rv != Success) {
      return rv;
    }
  }

  FallBackToSearchWithinSubject fallBackToEmailAddress;
  if (!subjectAltName &&
      (referenceIDType == GeneralNameType::rfc822Name ||
       referenceIDType == GeneralNameType::nameConstraints)) {
    fallBackToEmailAddress = FallBackToSearchWithinSubject::Yes;
  } else {
    fallBackToEmailAddress = FallBackToSearchWithinSubject::No;
  }

  // Short-circuit the parsing of the subject name if we're not going to match
  // any names in it
  if (fallBackToEmailAddress == FallBackToSearchWithinSubject::No &&
      fallBackToCommonName == FallBackToSearchWithinSubject::No) {
    return Success;
  }

  // Attempt to match the reference ID against the CN-ID, which we consider to
  // be the most-specific CN AVA in the subject field.
  //
  // https://tools.ietf.org/html/rfc6125#section-2.3.1 says:
  //
  //   To reduce confusion, in this specification we avoid such terms and
  //   instead use the terms provided under Section 1.8; in particular, we
  //   do not use the term "(most specific) Common Name field in the subject
  //   field" from [HTTP-TLS] and instead state that a CN-ID is a Relative
  //   Distinguished Name (RDN) in the certificate subject containing one
  //   and only one attribute-type-and-value pair of type Common Name (thus
  //   removing the possibility that an RDN might contain multiple AVAs
  //   (Attribute Value Assertions) of type CN, one of which could be
  //   considered "most specific").
  //
  // https://tools.ietf.org/html/rfc6125#section-7.4 says:
  //
  //   [...] Although it would be preferable to
  //   forbid multiple CN-IDs entirely, there are several reasons at this
  //   time why this specification states that they SHOULD NOT (instead of
  //   MUST NOT) be included [...]
  //
  // Consequently, it is unclear what to do when there are multiple CNs in the
  // subject, regardless of whether there "SHOULD NOT" be.
  //
  // NSS's CERT_VerifyCertName mostly follows RFC2818 in this instance, which
  // says:
  //
  //   If a subjectAltName extension of type dNSName is present, that MUST
  //   be used as the identity. Otherwise, the (most specific) Common Name
  //   field in the Subject field of the certificate MUST be used.
  //
  //   [...]
  //
  //   In some cases, the URI is specified as an IP address rather than a
  //   hostname. In this case, the iPAddress subjectAltName must be present
  //   in the certificate and must exactly match the IP in the URI.
  //
  // (The main difference from RFC2818 is that NSS's CERT_VerifyCertName also
  // matches IP addresses in the most-specific CN.)
  //
  // NSS's CERT_VerifyCertName finds the most specific CN via
  // CERT_GetCommoName, which uses CERT_GetLastNameElement. Note that many
  // NSS-based applications, including Gecko, also use CERT_GetCommonName. It
  // is likely that other, non-NSS-based, applications also expect only the
  // most specific CN to be matched against the reference ID.
  //
  // "A Layman's Guide to a Subset of ASN.1, BER, and DER" and other sources
  // agree that an RDNSequence is ordered from most significant (least
  // specific) to least significant (most specific), as do other references.
  //
  // However, Chromium appears to use the least-specific (first) CN instead of
  // the most-specific; see https://crbug.com/366957. Also, MSIE and some other
  // popular implementations apparently attempt to match the reference ID
  // against any/all CNs in the subject. Since we're trying to phase out the
  // use of CN-IDs, we intentionally avoid trying to match MSIE's more liberal
  // behavior.

  // Name ::= CHOICE { -- only one possibility for now --
  //   rdnSequence  RDNSequence }
  //
  // RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
  //
  // RelativeDistinguishedName ::=
  //   SET SIZE (1..MAX) OF AttributeTypeAndValue
  Reader subjectReader(subject);
  return der::NestedOf(subjectReader, der::SEQUENCE, der::SET,
                       der::EmptyAllowed::Yes, [&](Reader& r) {
    return SearchWithinRDN(r, referenceIDType, referenceID,
                          fallBackToEmailAddress, fallBackToCommonName, match);
  });
}

// RelativeDistinguishedName ::=
//   SET SIZE (1..MAX) OF AttributeTypeAndValue
//
// AttributeTypeAndValue ::= SEQUENCE {
//   type     AttributeType,
//   value    AttributeValue }
Result
SearchWithinRDN(Reader& rdn,
                GeneralNameType referenceIDType,
                Input referenceID,
                FallBackToSearchWithinSubject fallBackToEmailAddress,
                FallBackToSearchWithinSubject fallBackToCommonName,
                /*in/out*/ MatchResult& match)
{
  do {
    Input type;
    uint8_t valueTag;
    Input value;
    Result rv = ReadAVA(rdn, type, valueTag, value);
    if (rv != Success) {
      return rv;
    }
    rv = MatchAVA(type, valueTag, value, referenceIDType, referenceID,
                  fallBackToEmailAddress, fallBackToCommonName, match);
    if (rv != Success) {
      return rv;
    }
  } while (!rdn.AtEnd());

  return Success;
}

// AttributeTypeAndValue ::= SEQUENCE {
//   type     AttributeType,
//   value    AttributeValue }
//
// AttributeType ::= OBJECT IDENTIFIER
//
// AttributeValue ::= ANY -- DEFINED BY AttributeType
//
// DirectoryString ::= CHOICE {
//       teletexString           TeletexString (SIZE (1..MAX)),
//       printableString         PrintableString (SIZE (1..MAX)),
//       universalString         UniversalString (SIZE (1..MAX)),
//       utf8String              UTF8String (SIZE (1..MAX)),
//       bmpString               BMPString (SIZE (1..MAX)) }
Result
MatchAVA(Input type, uint8_t valueEncodingTag, Input presentedID,
         GeneralNameType referenceIDType,
         Input referenceID,
         FallBackToSearchWithinSubject fallBackToEmailAddress,
         FallBackToSearchWithinSubject fallBackToCommonName,
         /*in/out*/ MatchResult& match)
{
  // Try to match the  CN as a DNSName or an IPAddress.
  //
  // id-at-commonName        AttributeType ::= { id-at 3 }
  //
  // -- Naming attributes of type X520CommonName:
  // --   X520CommonName ::= DirectoryName (SIZE (1..ub-common-name))
  // --
  // -- Expanded to avoid parameterized type:
  // X520CommonName ::= CHOICE {
  //       teletexString     TeletexString   (SIZE (1..ub-common-name)),
  //       printableString   PrintableString (SIZE (1..ub-common-name)),
  //       universalString   UniversalString (SIZE (1..ub-common-name)),
  //       utf8String        UTF8String      (SIZE (1..ub-common-name)),
  //       bmpString         BMPString       (SIZE (1..ub-common-name)) }
  //
  // python DottedOIDToCode.py id-at-commonName 2.5.4.3
  static const uint8_t id_at_commonName[] = {
    0x55, 0x04, 0x03
  };
  if (fallBackToCommonName == FallBackToSearchWithinSubject::Yes &&
      InputsAreEqual(type, Input(id_at_commonName))) {
    // We might have previously found a match. Now that we've found another CN,
    // we no longer consider that previous match to be a match, so "forget" about
    // it.
    match = MatchResult::NoNamesOfGivenType;

    // PrintableString is a subset of ASCII that contains all the characters
    // allowed in CN-IDs except '*'. Although '*' is illegal, there are many
    // real-world certificates that are encoded this way, so we accept it.
    //
    // In the case of UTF8String, we rely on the fact that in UTF-8 the octets in
    // a multi-byte encoding of a code point are always distinct from ASCII. Any
    // non-ASCII byte in a UTF-8 string causes us to fail to match. We make no
    // attempt to detect or report malformed UTF-8 (e.g. incomplete or overlong
    // encodings of code points, or encodings of invalid code points).
    //
    // TeletexString is supported as long as it does not contain any escape
    // sequences, which are not supported. We'll reject escape sequences as
    // invalid characters in names, which means we only accept strings that are
    // in the default character set, which is a superset of ASCII. Note that NSS
    // actually treats TeletexString as ISO-8859-1. Many certificates that have
    // wildcard CN-IDs (e.g. "*.example.com") use TeletexString because
    // PrintableString is defined to not allow '*' and because, at one point in
    // history, UTF8String was too new to use for compatibility reasons.
    //
    // UniversalString and BMPString are also deprecated, and they are a little
    // harder to support because they are not single-byte ASCII superset
    // encodings, so we don't bother.
    if (valueEncodingTag != der::PrintableString &&
        valueEncodingTag != der::UTF8String &&
        valueEncodingTag != der::TeletexString) {
      return Success;
    }

    if (IsValidPresentedDNSID(presentedID)) {
      MatchSubjectPresentedIDWithReferenceID(GeneralNameType::dNSName,
                                             presentedID, referenceIDType,
                                             referenceID, match);
    } else {
      // We don't match CN-IDs for IPv6 addresses.
      // MatchSubjectPresentedIDWithReferenceID ensures that it won't match an
      // IPv4 address with an IPv6 address, so we don't need to check that
      // referenceID is an IPv4 address here.
      uint8_t ipv4[4];
      if (ParseIPv4Address(presentedID, ipv4)) {
        MatchSubjectPresentedIDWithReferenceID(GeneralNameType::iPAddress,
                                               Input(ipv4), referenceIDType,
                                               referenceID, match);
      }
    }

    // Regardless of whether there was a match, we keep going in case we find
    // another CN later. If we do find another one, then this match/mismatch
    // will be ignored, because we only care about the most specific CN.

    return Success;
  }

  // Match an email address against an emailAddress attribute in the
  // subject.
  //
  // id-emailAddress      AttributeType ::= { pkcs-9 1 }
  //
  // EmailAddress ::=     IA5String (SIZE (1..ub-emailaddress-length))
  //
  // python DottedOIDToCode.py id-emailAddress 1.2.840.113549.1.9.1
  static const uint8_t id_emailAddress[] = {
    0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01
  };
  if (fallBackToEmailAddress == FallBackToSearchWithinSubject::Yes &&
      InputsAreEqual(type, Input(id_emailAddress))) {
    if (referenceIDType == GeneralNameType::rfc822Name &&
        match == MatchResult::Match) {
      // We already found a match; we don't need to match another one
      return Success;
    }
    if (valueEncodingTag != der::IA5String) {
      return Result::ERROR_BAD_DER;
    }
    return MatchPresentedIDWithReferenceID(GeneralNameType::rfc822Name,
                                           presentedID, referenceIDType,
                                           referenceID, match);
  }

  return Success;
}

void
MatchSubjectPresentedIDWithReferenceID(GeneralNameType presentedIDType,
                                       Input presentedID,
                                       GeneralNameType referenceIDType,
                                       Input referenceID,
                                       /*in/out*/ MatchResult& match)
{
  Result rv = MatchPresentedIDWithReferenceID(presentedIDType, presentedID,
                                              referenceIDType, referenceID,
                                              match);
  if (rv != Success) {
    match = MatchResult::Mismatch;
  }
}

Result
MatchPresentedIDWithReferenceID(GeneralNameType presentedIDType,
                                Input presentedID,
                                GeneralNameType referenceIDType,
                                Input referenceID,
                                /*out*/ MatchResult& matchResult)
{
  if (referenceIDType == GeneralNameType::nameConstraints) {
    // matchResult is irrelevant when checking name constraints; only the
    // pass/fail result of CheckPresentedIDConformsToConstraints matters.
    return CheckPresentedIDConformsToConstraints(presentedIDType, presentedID,
                                                 referenceID);
  }

  if (presentedIDType != referenceIDType) {
    matchResult = MatchResult::Mismatch;
    return Success;
  }

  Result rv;
  bool foundMatch;

  switch (referenceIDType) {
    case GeneralNameType::dNSName:
      rv = MatchPresentedDNSIDWithReferenceDNSID(
             presentedID, AllowWildcards::Yes,
             AllowDotlessSubdomainMatches::Yes, IDRole::ReferenceID,
             nullptr, referenceID, foundMatch);
      break;

    case GeneralNameType::iPAddress:
      foundMatch = InputsAreEqual(presentedID, referenceID);
      rv = Success;
      break;

    case GeneralNameType::rfc822Name:
      rv = MatchPresentedRFC822NameWithReferenceRFC822Name(
             presentedID, IDRole::ReferenceID, nullptr, referenceID, foundMatch);
      break;

    case GeneralNameType::directoryName:
      // TODO: At some point, we may add APIs for matching DirectoryNames.
      // fall through

    case GeneralNameType::otherName: // fall through
    case GeneralNameType::x400Address: // fall through
    case GeneralNameType::ediPartyName: // fall through
    case GeneralNameType::uniformResourceIdentifier: // fall through
    case GeneralNameType::registeredID: // fall through
    case GeneralNameType::nameConstraints:
      return NotReached("unexpected nameType for SearchType::Match",
                        Result::FATAL_ERROR_INVALID_ARGS);

    MOZILLA_PKIX_UNREACHABLE_DEFAULT_ENUM
 }

  if (rv != Success) {
    return rv;
  }
  matchResult = foundMatch ? MatchResult::Match : MatchResult::Mismatch;
  return Success;
}

Result CheckPresentedIDConformsToNameConstraintsSubtrees(
         GeneralNameType presentedIDType,
         Input presentedID,
         Reader& nameConstraints,
         NameConstraintsSubtrees subtreesType);

Result MatchPresentedIPAddressWithConstraint(Input presentedID,
                                             Input iPAddressConstraint,
                                             /*out*/ bool& foundMatch);

Result MatchPresentedDirectoryNameWithConstraint(
         NameConstraintsSubtrees subtreesType, Input presentedID,
         Input directoryNameConstraint, /*out*/ bool& matches);

Result
CheckPresentedIDConformsToConstraints(
  GeneralNameType presentedIDType,
  Input presentedID,
  Input encodedNameConstraints)
{
  // NameConstraints ::= SEQUENCE {
  //      permittedSubtrees       [0]     GeneralSubtrees OPTIONAL,
  //      excludedSubtrees        [1]     GeneralSubtrees OPTIONAL }
  Reader nameConstraints;
  Result rv = der::ExpectTagAndGetValueAtEnd(encodedNameConstraints,
                                             der::SEQUENCE, nameConstraints);
  if (rv != Success) {
    return rv;
  }

  // RFC 5280 says "Conforming CAs MUST NOT issue certificates where name
  // constraints is an empty sequence. That is, either the permittedSubtrees
  // field or the excludedSubtrees MUST be present."
  if (nameConstraints.AtEnd()) {
    return Result::ERROR_BAD_DER;
  }

  rv = CheckPresentedIDConformsToNameConstraintsSubtrees(
         presentedIDType, presentedID, nameConstraints,
         NameConstraintsSubtrees::permittedSubtrees);
  if (rv != Success) {
    return rv;
  }

  rv = CheckPresentedIDConformsToNameConstraintsSubtrees(
         presentedIDType, presentedID, nameConstraints,
         NameConstraintsSubtrees::excludedSubtrees);
  if (rv != Success) {
    return rv;
  }

  return der::End(nameConstraints);
}

Result
CheckPresentedIDConformsToNameConstraintsSubtrees(
  GeneralNameType presentedIDType,
  Input presentedID,
  Reader& nameConstraints,
  NameConstraintsSubtrees subtreesType)
{
  if (!nameConstraints.Peek(static_cast<uint8_t>(subtreesType))) {
    return Success;
  }

  Reader subtrees;
  Result rv = der::ExpectTagAndGetValue(nameConstraints,
                                        static_cast<uint8_t>(subtreesType),
                                        subtrees);
  if (rv != Success) {
    return rv;
  }

  bool hasPermittedSubtreesMatch = false;
  bool hasPermittedSubtreesMismatch = false;

  // GeneralSubtrees ::= SEQUENCE SIZE (1..MAX) OF GeneralSubtree
  //
  // do { ... } while(...) because subtrees isn't allowed to be empty.
  do {
    // GeneralSubtree ::= SEQUENCE {
    //      base                    GeneralName,
    //      minimum         [0]     BaseDistance DEFAULT 0,
    //      maximum         [1]     BaseDistance OPTIONAL }
    Reader subtree;
    rv = ExpectTagAndGetValue(subtrees, der::SEQUENCE, subtree);
    if (rv != Success) {
      return rv;
    }
    GeneralNameType nameConstraintType;
    Input base;
    rv = ReadGeneralName(subtree, nameConstraintType, base);
    if (rv != Success) {
      return rv;
    }
    // http://tools.ietf.org/html/rfc5280#section-4.2.1.10: "Within this
    // profile, the minimum and maximum fields are not used with any name
    // forms, thus, the minimum MUST be zero, and maximum MUST be absent."
    //
    // Since the default value isn't allowed to be encoded according to the DER
    // encoding rules for DEFAULT, this is equivalent to saying that neither
    // minimum or maximum must be encoded.
    rv = der::End(subtree);
    if (rv != Success) {
      return rv;
    }

    if (presentedIDType == nameConstraintType) {
      bool matches;

      switch (presentedIDType) {
        case GeneralNameType::dNSName:
          rv = MatchPresentedDNSIDWithReferenceDNSID(
                 presentedID, AllowWildcards::Yes,
                 AllowDotlessSubdomainMatches::Yes, IDRole::NameConstraint,
                 &subtreesType, base, matches);
          if (rv != Success) {
            return rv;
          }
          break;

        case GeneralNameType::iPAddress:
          rv = MatchPresentedIPAddressWithConstraint(presentedID, base,
                                                     matches);
          if (rv != Success) {
            return rv;
          }
          break;

        case GeneralNameType::directoryName:
          rv = MatchPresentedDirectoryNameWithConstraint(subtreesType,
                                                         presentedID, base,
                                                         matches);
          if (rv != Success) {
            return rv;
          }
          break;

        case GeneralNameType::rfc822Name:
          rv = MatchPresentedRFC822NameWithReferenceRFC822Name(
                 presentedID, IDRole::NameConstraint, &subtreesType, base, matches);
          if (rv != Success) {
            return rv;
          }
          break;

        // RFC 5280 says "Conforming CAs [...] SHOULD NOT impose name
        // constraints on the x400Address, ediPartyName, or registeredID
        // name forms. It also says "Applications conforming to this profile
        // [...] SHOULD be able to process name constraints that are imposed
        // on [...] uniformResourceIdentifier [...]", but we don't bother.
        //
        // TODO: Ask to have spec updated to say ""Conforming CAs [...] SHOULD
        // NOT impose name constraints on the otherName, x400Address,
        // ediPartyName, uniformResourceIdentifier, or registeredID name
        // forms."
        case GeneralNameType::otherName: // fall through
        case GeneralNameType::x400Address: // fall through
        case GeneralNameType::ediPartyName: // fall through
        case GeneralNameType::uniformResourceIdentifier: // fall through
        case GeneralNameType::registeredID: // fall through
          return Result::ERROR_CERT_NOT_IN_NAME_SPACE;

        case GeneralNameType::nameConstraints:
          return NotReached("invalid presentedIDType",
                            Result::FATAL_ERROR_LIBRARY_FAILURE);

        MOZILLA_PKIX_UNREACHABLE_DEFAULT_ENUM
      }

      switch (subtreesType) {
        case NameConstraintsSubtrees::permittedSubtrees:
          if (matches) {
            hasPermittedSubtreesMatch = true;
          } else {
            hasPermittedSubtreesMismatch = true;
          }
          break;
        case NameConstraintsSubtrees::excludedSubtrees:
          if (matches) {
            return Result::ERROR_CERT_NOT_IN_NAME_SPACE;
          }
          break;
      }
    }
  } while (!subtrees.AtEnd());

  if (hasPermittedSubtreesMismatch && !hasPermittedSubtreesMatch) {
    // If there was any entry of the given type in permittedSubtrees, then it
    // required that at least one of them must match. Since none of them did,
    // we have a failure.
    return Result::ERROR_CERT_NOT_IN_NAME_SPACE;
  }

  return Success;
}

// We do not distinguish between a syntactically-invalid presentedDNSID and one
// that is syntactically valid but does not match referenceDNSID; in both
// cases, the result is false.
//
// We assume that both presentedDNSID and referenceDNSID are encoded in such a
// way that US-ASCII (7-bit) characters are encoded in one byte and no encoding
// of a non-US-ASCII character contains a code point in the range 0-127. For
// example, UTF-8 is OK but UTF-16 is not.
//
// RFC6125 says that a wildcard label may be of the form <x>*<y>.<DNSID>, where
// <x> and/or <y> may be empty. However, NSS requires <y> to be empty, and we
// follow NSS's stricter policy by accepting wildcards only of the form
// <x>*.<DNSID>, where <x> may be empty.
//
// An relative presented DNS ID matches both an absolute reference ID and a
// relative reference ID. Absolute presented DNS IDs are not supported:
//
//      Presented ID   Reference ID  Result
//      -------------------------------------
//      example.com    example.com   Match
//      example.com.   example.com   Mismatch
//      example.com    example.com.  Match
//      example.com.   example.com.  Mismatch
//
// There are more subtleties documented inline in the code.
//
// Name constraints ///////////////////////////////////////////////////////////
//
// This is all RFC 5280 has to say about DNSName constraints:
//
//     DNS name restrictions are expressed as host.example.com.  Any DNS
//     name that can be constructed by simply adding zero or more labels to
//     the left-hand side of the name satisfies the name constraint.  For
//     example, www.host.example.com would satisfy the constraint but
//     host1.example.com would not.
//
// This lack of specificity has lead to a lot of uncertainty regarding
// subdomain matching. In particular, the following questions have been
// raised and answered:
//
//     Q: Does a presented identifier equal (case insensitive) to the name
//        constraint match the constraint? For example, does the presented
//        ID "host.example.com" match a "host.example.com" constraint?
//     A: Yes. RFC5280 says "by simply adding zero or more labels" and this
//        is the case of adding zero labels.
//
//     Q: When the name constraint does not start with ".", do subdomain
//        presented identifiers match it? For example, does the presented
//        ID "www.host.example.com" match a "host.example.com" constraint?
//     A: Yes. RFC5280 says "by simply adding zero or more labels" and this
//        is the case of adding more than zero labels. The example is the
//        one from RFC 5280.
//
//     Q: When the name constraint does not start with ".", does a
//        non-subdomain prefix match it? For example, does "bigfoo.bar.com"
//        match "foo.bar.com"? [4]
//     A: No. We interpret RFC 5280's language of "adding zero or more labels"
//        to mean that whole labels must be prefixed.
//
//     (Note that the above three scenarios are the same as the RFC 6265
//     domain matching rules [0].)
//
//     Q: Is a name constraint that starts with "." valid, and if so, what
//        semantics does it have? For example, does a presented ID of
//        "www.example.com" match a constraint of ".example.com"? Does a
//        presented ID of "example.com" match a constraint of ".example.com"?
//     A: This implementation, NSS[1], and SChannel[2] all support a
//        leading ".", but OpenSSL[3] does not yet. Amongst the
//        implementations that support it, a leading "." is legal and means
//        the same thing as when the "." is omitted, EXCEPT that a
//        presented identifier equal (case insensitive) to the name
//        constraint is not matched; i.e. presented DNSName identifiers
//        must be subdomains. Some CAs in Mozilla's CA program (e.g. HARICA)
//        have name constraints with the leading "." in their root
//        certificates. The name constraints imposed on DCISS by Mozilla also
//        have the it, so supporting this is a requirement for backward
//        compatibility, even if it is not yet standardized. So, for example, a
//        presented ID of "www.example.com" matches a constraint of
//        ".example.com" but a presented ID of "example.com" does not.
//
//     Q: Is there a way to prevent subdomain matches?
//     A: Yes.
//
//        Some people have proposed that dNSName constraints that do not
//        start with a "." should be restricted to exact (case insensitive)
//        matches. However, such a change of semantics from what RFC5280
//        specifies would be a non-backward-compatible change in the case of
//        permittedSubtrees constraints, and it would be a security issue for
//        excludedSubtrees constraints.
//
//        However, it can be done with a combination of permittedSubtrees and
//        excludedSubtrees, e.g. "example.com" in permittedSubtrees and
//        ".example.com" in excudedSubtrees.
//
//     Q: Are name constraints allowed to be specified as absolute names?
//        For example, does a presented ID of "example.com" match a name
//        constraint of "example.com." and vice versa.
//     A: Absolute names are not supported as presented IDs or name
//        constraints. Only reference IDs may be absolute.
//
//     Q: Is "" a valid DNSName constraints? If so, what does it mean?
//     A: Yes. Any valid presented DNSName can be formed "by simply adding zero
//        or more labels to the left-hand side" of "". In particular, an
//        excludedSubtrees DNSName constraint of "" forbids all DNSNames.
//
//     Q: Is "." a valid DNSName constraints? If so, what does it mean?
//     A: No, because absolute names are not allowed (see above).
//
// [0] RFC 6265 (Cookies) Domain Matching rules:
//     http://tools.ietf.org/html/rfc6265#section-5.1.3
// [1] NSS source code:
//     https://mxr.mozilla.org/nss/source/lib/certdb/genname.c?rev=2a7348f013cb#1209
// [2] Description of SChannel's behavior from Microsoft:
//     http://www.imc.org/ietf-pkix/mail-archive/msg04668.html
// [3] Proposal to add such support to OpenSSL:
//     http://www.mail-archive.com/openssl-dev%40openssl.org/msg36204.html
//     https://rt.openssl.org/Ticket/Display.html?id=3562
// [4] Feedback on the lack of clarify in the definition that never got
//     incorporated into the spec:
//     https://www.ietf.org/mail-archive/web/pkix/current/msg21192.html
Result
MatchPresentedDNSIDWithReferenceDNSID(
  Input presentedDNSID,
  AllowWildcards allowWildcards,
  AllowDotlessSubdomainMatches allowDotlessSubdomainMatches,
  IDRole referenceDNSIDRole,
  /*optional*/ const NameConstraintsSubtrees* subtreesType,
  Input referenceDNSID,
  /*out*/ bool& matches)
{
  if (!IsValidDNSID(presentedDNSID, IDRole::PresentedID, allowWildcards)) {
    return Result::ERROR_BAD_DER;
  }

  if (!IsValidDNSID(referenceDNSID, referenceDNSIDRole, AllowWildcards::No)) {
    return Result::ERROR_BAD_DER;
  }

  Reader presented(presentedDNSID);
  Reader reference(referenceDNSID);

  switch (referenceDNSIDRole)
  {
    case IDRole::ReferenceID:
      break;

    case IDRole::NameConstraint:
    {
      if (presentedDNSID.GetLength() > referenceDNSID.GetLength()) {
        if (referenceDNSID.GetLength() == 0) {
          // An empty constraint matches everything.
          matches = true;
          return Success;
        }
        // If the reference ID starts with a dot then skip the prefix of
        // of the presented ID and start the comparison at the position of that
        // dot. Examples:
        //
        //                                       Matches     Doesn't Match
        //     -----------------------------------------------------------
        //       original presented ID:  www.example.com    badexample.com
        //                     skipped:  www                ba
        //     presented ID w/o prefix:     .example.com      dexample.com
        //                reference ID:     .example.com      .example.com
        //
        // If the reference ID does not start with a dot then we skip the
        // prefix of the presented ID but also verify that the prefix ends with
        // a dot. Examples:
        //
        //                                       Matches     Doesn't Match
        //     -----------------------------------------------------------
        //       original presented ID:  www.example.com    badexample.com
        //                     skipped:  www                ba
        //                 must be '.':     .                 d
        //     presented ID w/o prefix:      example.com       example.com
        //                reference ID:      example.com       example.com
        //
        if (reference.Peek('.')) {
          if (presented.Skip(static_cast<Input::size_type>(
                               presentedDNSID.GetLength() -
                                 referenceDNSID.GetLength())) != Success) {
            return NotReached("skipping subdomain failed",
                              Result::FATAL_ERROR_LIBRARY_FAILURE);
          }
        } else if (allowDotlessSubdomainMatches ==
                   AllowDotlessSubdomainMatches::Yes) {
          if (presented.Skip(static_cast<Input::size_type>(
                               presentedDNSID.GetLength() -
                                 referenceDNSID.GetLength() - 1)) != Success) {
            return NotReached("skipping subdomains failed",
                              Result::FATAL_ERROR_LIBRARY_FAILURE);
          }
          uint8_t b;
          if (presented.Read(b) != Success) {
            return NotReached("reading from presentedDNSID failed",
                              Result::FATAL_ERROR_LIBRARY_FAILURE);
          }
          if (b != '.') {
            matches = false;
            return Success;
          }
        }
      }
      break;
    }

    case IDRole::PresentedID: // fall through
      return NotReached("IDRole::PresentedID is not a valid referenceDNSIDRole",
                        Result::FATAL_ERROR_INVALID_ARGS);
  }

  // We only allow wildcard labels that consist only of '*'.
  if (presented.Peek('*')) {
    if (presented.Skip(1) != Success) {
      return NotReached("Skipping '*' failed",
                        Result::FATAL_ERROR_LIBRARY_FAILURE);
    }
    // For the permittedSubtrees of a name constraint, wildcard presented
    // DNSIDs of the form `*.example.com` only match if the name constraint is
    // of the form `.example.com` or `example.com`. To put it another way, a
    // permittedSubtrees of `foo.example.com` does not match a wildcard
    // presented DNSID of `*.example.com`, because in that case, the
    // certificate could be valid for `bar.example.com`, which does not match
    // the name constraint.
    if (referenceDNSIDRole != IDRole::NameConstraint ||
        (subtreesType && *subtreesType != NameConstraintsSubtrees::permittedSubtrees)) {
      do {
        // This will happen if reference is a single, relative label
        if (reference.AtEnd()) {
          matches = false;
          return Success;
        }
        uint8_t referenceByte;
        if (reference.Read(referenceByte) != Success) {
          return NotReached("invalid reference ID",
                            Result::FATAL_ERROR_INVALID_ARGS);
        }
      } while (!reference.Peek('.'));
    }
  }

  for (;;) {
    uint8_t presentedByte;
    if (presented.Read(presentedByte) != Success) {
      matches = false;
      return Success;
    }
    uint8_t referenceByte;
    if (reference.Read(referenceByte) != Success) {
      matches = false;
      return Success;
    }
    if (LocaleInsensitveToLower(presentedByte) !=
        LocaleInsensitveToLower(referenceByte)) {
      matches = false;
      return Success;
    }
    if (presented.AtEnd()) {
      // Don't allow presented IDs to be absolute.
      if (presentedByte == '.') {
        return Result::ERROR_BAD_DER;
      }
      break;
    }
  }

  // Allow a relative presented DNS ID to match an absolute reference DNS ID,
  // unless we're matching a name constraint.
  if (!reference.AtEnd()) {
    if (referenceDNSIDRole != IDRole::NameConstraint) {
      uint8_t referenceByte;
      if (reference.Read(referenceByte) != Success) {
        return NotReached("read failed but not at end",
                          Result::FATAL_ERROR_LIBRARY_FAILURE);
      }
      if (referenceByte != '.') {
        matches = false;
        return Success;
      }
    }
    if (!reference.AtEnd()) {
      matches = false;
      return Success;
    }
  }

  matches = true;
  return Success;
}

// https://tools.ietf.org/html/rfc5280#section-4.2.1.10 says:
//
//     For IPv4 addresses, the iPAddress field of GeneralName MUST contain
//     eight (8) octets, encoded in the style of RFC 4632 (CIDR) to represent
//     an address range [RFC4632].  For IPv6 addresses, the iPAddress field
//     MUST contain 32 octets similarly encoded.  For example, a name
//     constraint for "class C" subnet 192.0.2.0 is represented as the
//     octets C0 00 02 00 FF FF FF 00, representing the CIDR notation
//     192.0.2.0/24 (mask 255.255.255.0).
Result
MatchPresentedIPAddressWithConstraint(Input presentedID,
                                      Input iPAddressConstraint,
                                      /*out*/ bool& foundMatch)
{
  if (presentedID.GetLength() != 4 && presentedID.GetLength() != 16) {
    return Result::ERROR_BAD_DER;
  }
  if (iPAddressConstraint.GetLength() != 8 &&
      iPAddressConstraint.GetLength() != 32) {
    return Result::ERROR_BAD_DER;
  }

  // an IPv4 address never matches an IPv6 constraint, and vice versa.
  if (presentedID.GetLength() * 2 != iPAddressConstraint.GetLength()) {
    foundMatch = false;
    return Success;
  }

  Reader constraint(iPAddressConstraint);
  Reader constraintAddress;
  Result rv = constraint.Skip(iPAddressConstraint.GetLength() / 2u,
                              constraintAddress);
  if (rv != Success) {
    return rv;
  }
  Reader constraintMask;
  rv = constraint.Skip(iPAddressConstraint.GetLength() / 2u, constraintMask);
  if (rv != Success) {
    return rv;
  }
  rv = der::End(constraint);
  if (rv != Success) {
    return rv;
  }

  Reader presented(presentedID);
  do {
    uint8_t presentedByte;
    rv = presented.Read(presentedByte);
    if (rv != Success) {
      return rv;
    }
    uint8_t constraintAddressByte;
    rv = constraintAddress.Read(constraintAddressByte);
    if (rv != Success) {
      return rv;
    }
    uint8_t constraintMaskByte;
    rv = constraintMask.Read(constraintMaskByte);
    if (rv != Success) {
      return rv;
    }
    foundMatch =
      ((presentedByte ^ constraintAddressByte) & constraintMaskByte) == 0;
  } while (foundMatch && !presented.AtEnd());

  return Success;
}

// AttributeTypeAndValue ::= SEQUENCE {
//   type     AttributeType,
//   value    AttributeValue }
//
// AttributeType ::= OBJECT IDENTIFIER
//
// AttributeValue ::= ANY -- DEFINED BY AttributeType
Result
ReadAVA(Reader& rdn,
        /*out*/ Input& type,
        /*out*/ uint8_t& valueTag,
        /*out*/ Input& value)
{
  return der::Nested(rdn, der::SEQUENCE, [&](Reader& ava) -> Result {
    Result rv = der::ExpectTagAndGetValue(ava, der::OIDTag, type);
    if (rv != Success) {
      return rv;
    }
    rv = der::ReadTagAndGetValue(ava, valueTag, value);
    if (rv != Success) {
      return rv;
    }
    return Success;
  });
}

// Names are sequences of RDNs. RDNS are sets of AVAs. That means that RDNs are
// unordered, so in theory we should match RDNs with equivalent AVAs that are
// in different orders. Within the AVAs are DirectoryNames that are supposed to
// be compared according to LDAP stringprep normalization rules (e.g.
// normalizing whitespace), consideration of different character encodings,
// etc. Indeed, RFC 5280 says we MUST deal with all of that.
//
// In practice, many implementations, including NSS, only match Names in a way
// that only meets a subset of the requirements of RFC 5280. Those
// normalization and character encoding conversion steps appear to be
// unnecessary for processing real-world certificates, based on experience from
// having used NSS in Firefox for many years.
//
// RFC 5280 also says "CAs issuing certificates with a restriction of the form
// directoryName SHOULD NOT rely on implementation of the full
// ISO DN name comparison algorithm. This implies name restrictions MUST
// be stated identically to the encoding used in the subject field or
// subjectAltName extension." It goes on to say, in the security
// considerations:
//
//     In addition, name constraints for distinguished names MUST be stated
//     identically to the encoding used in the subject field or
//     subjectAltName extension.  If not, then name constraints stated as
//     excludedSubtrees will not match and invalid paths will be accepted
//     and name constraints expressed as permittedSubtrees will not match
//     and valid paths will be rejected.  To avoid acceptance of invalid
//     paths, CAs SHOULD state name constraints for distinguished names as
//     permittedSubtrees wherever possible.
//
// For permittedSubtrees, the MUST-level requirement is relaxed for
// compatibility in the case of PrintableString and UTF8String. That is, if a
// name constraint has been encoded using UTF8String and the presented ID has
// been encoded with a PrintableString (or vice-versa), they are considered to
// match if they are equal everywhere except for the tag identifying the
// encoding. See bug 1150114.
//
// For excludedSubtrees, we simply prohibit any non-empty directoryName
// constraint to ensure we are not being too lenient. We support empty
// DirectoryName constraints in excludedSubtrees so that a CA can say "Do not
// allow any DirectoryNames in issued certificates."
Result
MatchPresentedDirectoryNameWithConstraint(NameConstraintsSubtrees subtreesType,
                                          Input presentedID,
                                          Input directoryNameConstraint,
                                          /*out*/ bool& matches)
{
  Reader constraintRDNs;
  Result rv = der::ExpectTagAndGetValueAtEnd(directoryNameConstraint,
                                             der::SEQUENCE, constraintRDNs);
  if (rv != Success) {
    return rv;
  }
  Reader presentedRDNs;
  rv = der::ExpectTagAndGetValueAtEnd(presentedID, der::SEQUENCE,
                                      presentedRDNs);
  if (rv != Success) {
    return rv;
  }

  switch (subtreesType) {
    case NameConstraintsSubtrees::permittedSubtrees:
      break; // dealt with below
    case NameConstraintsSubtrees::excludedSubtrees:
      if (!constraintRDNs.AtEnd() || !presentedRDNs.AtEnd()) {
        return Result::ERROR_CERT_NOT_IN_NAME_SPACE;
      }
      matches = true;
      return Success;
  }

  for (;;) {
    // The AVAs have to be fully equal, but the constraint RDNs just need to be
    // a prefix of the presented RDNs.
    if (constraintRDNs.AtEnd()) {
      matches = true;
      return Success;
    }
    if (presentedRDNs.AtEnd()) {
      matches = false;
      return Success;
    }
    Reader constraintRDN;
    rv = der::ExpectTagAndGetValue(constraintRDNs, der::SET, constraintRDN);
    if (rv != Success) {
      return rv;
    }
    Reader presentedRDN;
    rv = der::ExpectTagAndGetValue(presentedRDNs, der::SET, presentedRDN);
    if (rv != Success) {
      return rv;
    }
    while (!constraintRDN.AtEnd() && !presentedRDN.AtEnd()) {
      Input constraintType;
      uint8_t constraintValueTag;
      Input constraintValue;
      rv = ReadAVA(constraintRDN, constraintType, constraintValueTag,
                   constraintValue);
      if (rv != Success) {
        return rv;
      }
      Input presentedType;
      uint8_t presentedValueTag;
      Input presentedValue;
      rv = ReadAVA(presentedRDN, presentedType, presentedValueTag,
                   presentedValue);
      if (rv != Success) {
        return rv;
      }
      // TODO (bug 1155767): verify that if an AVA is a PrintableString it
      // consists only of characters valid for PrintableStrings.
      bool avasMatch =
        InputsAreEqual(constraintType, presentedType) &&
        InputsAreEqual(constraintValue, presentedValue) &&
        (constraintValueTag == presentedValueTag ||
         (constraintValueTag == der::Tag::UTF8String &&
          presentedValueTag == der::Tag::PrintableString) ||
         (constraintValueTag == der::Tag::PrintableString &&
          presentedValueTag == der::Tag::UTF8String));
      if (!avasMatch) {
        matches = false;
        return Success;
      }
    }
    if (!constraintRDN.AtEnd() || !presentedRDN.AtEnd()) {
      matches = false;
      return Success;
    }
  }
}

// RFC 5280 says:
//
//     The format of an rfc822Name is a "Mailbox" as defined in Section 4.1.2
//     of [RFC2821]. A Mailbox has the form "Local-part@Domain".  Note that a
//     Mailbox has no phrase (such as a common name) before it, has no comment
//     (text surrounded in parentheses) after it, and is not surrounded by "<"
//     and ">".  Rules for encoding Internet mail addresses that include
//     internationalized domain names are specified in Section 7.5.
//
// and:
//
//     A name constraint for Internet mail addresses MAY specify a
//     particular mailbox, all addresses at a particular host, or all
//     mailboxes in a domain.  To indicate a particular mailbox, the
//     constraint is the complete mail address.  For example,
//     "root@example.com" indicates the root mailbox on the host
//     "example.com".  To indicate all Internet mail addresses on a
//     particular host, the constraint is specified as the host name.  For
//     example, the constraint "example.com" is satisfied by any mail
//     address at the host "example.com".  To specify any address within a
//     domain, the constraint is specified with a leading period (as with
//     URIs).  For example, ".example.com" indicates all the Internet mail
//     addresses in the domain "example.com", but not Internet mail
//     addresses on the host "example.com".

bool
IsValidRFC822Name(Input input)
{
  Reader reader(input);

  // Local-part@.
  bool startOfAtom = true;
  for (;;) {
    uint8_t presentedByte;
    if (reader.Read(presentedByte) != Success) {
      return false;
    }
    switch (presentedByte) {
      // atext is defined in https://tools.ietf.org/html/rfc2822#section-3.2.4
      case 'A': case 'a': case 'N': case 'n': case '0': case '!': case '#':
      case 'B': case 'b': case 'O': case 'o': case '1': case '$': case '%':
      case 'C': case 'c': case 'P': case 'p': case '2': case '&': case '\'':
      case 'D': case 'd': case 'Q': case 'q': case '3': case '*': case '+':
      case 'E': case 'e': case 'R': case 'r': case '4': case '-': case '/':
      case 'F': case 'f': case 'S': case 's': case '5': case '=': case '?':
      case 'G': case 'g': case 'T': case 't': case '6': case '^': case '_':
      case 'H': case 'h': case 'U': case 'u': case '7': case '`': case '{':
      case 'I': case 'i': case 'V': case 'v': case '8': case '|': case '}':
      case 'J': case 'j': case 'W': case 'w': case '9': case '~':
      case 'K': case 'k': case 'X': case 'x':
      case 'L': case 'l': case 'Y': case 'y':
      case 'M': case 'm': case 'Z': case 'z':
        startOfAtom = false;
        break;

      case '.':
        if (startOfAtom) {
          return false;
        }
        startOfAtom = true;
        break;

      case '@':
      {
        if (startOfAtom) {
          return false;
        }
        Input domain;
        if (reader.SkipToEnd(domain) != Success) {
          return false;
        }
        return IsValidDNSID(domain, IDRole::PresentedID, AllowWildcards::No);
      }

      default:
        return false;
    }
  }
}

// `subtreesType` is relevant only when `referenceRFC822NameRole` is
// `IDRole::NameConstraint`.
Result
MatchPresentedRFC822NameWithReferenceRFC822Name(
  Input presentedRFC822Name, IDRole referenceRFC822NameRole,
  /*optional*/ const NameConstraintsSubtrees* subtreesType,
  Input referenceRFC822Name, /*out*/ bool& matches)
{
  if (!IsValidRFC822Name(presentedRFC822Name)) {
    return Result::ERROR_BAD_DER;
  }
  Reader presented(presentedRFC822Name);

  switch (referenceRFC822NameRole)
  {
    case IDRole::PresentedID:
      return Result::FATAL_ERROR_INVALID_ARGS;

    case IDRole::ReferenceID:
      break;

    case IDRole::NameConstraint:
    {
      if (InputContains(referenceRFC822Name, '@')) {
        // The constraint is of the form "Local-part@Domain".
        break;
      }

      // The constraint is of the form "example.com" or ".example.com".

      // Skip past the '@' in the presented ID.
      for (;;) {
        uint8_t presentedByte;
        if (presented.Read(presentedByte) != Success) {
          return Result::FATAL_ERROR_LIBRARY_FAILURE;
        }
        if (presentedByte == '@') {
          break;
        }
      }

      Input presentedDNSID;
      if (presented.SkipToEnd(presentedDNSID) != Success) {
        return Result::FATAL_ERROR_LIBRARY_FAILURE;
      }

      return MatchPresentedDNSIDWithReferenceDNSID(
               presentedDNSID, AllowWildcards::No,
               AllowDotlessSubdomainMatches::No, IDRole::NameConstraint,
               subtreesType,
               referenceRFC822Name, matches);
    }
  }

  if (!IsValidRFC822Name(referenceRFC822Name)) {
    return Result::ERROR_BAD_DER;
  }

  Reader reference(referenceRFC822Name);

  for (;;) {
    uint8_t presentedByte;
    if (presented.Read(presentedByte) != Success) {
      matches = reference.AtEnd();
      return Success;
    }
    uint8_t referenceByte;
    if (reference.Read(referenceByte) != Success) {
      matches = false;
      return Success;
    }
    if (LocaleInsensitveToLower(presentedByte) !=
        LocaleInsensitveToLower(referenceByte)) {
      matches = false;
      return Success;
    }
  }
}

// We avoid isdigit because it is locale-sensitive. See
// http://pubs.opengroup.org/onlinepubs/009695399/functions/tolower.html.
inline uint8_t
LocaleInsensitveToLower(uint8_t a)
{
  if (a >= 'A' && a <= 'Z') { // unlikely
    return static_cast<uint8_t>(
             static_cast<uint8_t>(a - static_cast<uint8_t>('A')) +
             static_cast<uint8_t>('a'));
  }
  return a;
}

bool
StartsWithIDNALabel(Input id)
{
  static const uint8_t IDN_ALABEL_PREFIX[4] = { 'x', 'n', '-', '-' };
  Reader input(id);
  for (const uint8_t prefixByte : IDN_ALABEL_PREFIX) {
    uint8_t b;
    if (input.Read(b) != Success) {
      return false;
    }
    if (b != prefixByte) {
      return false;
    }
  }
  return true;
}

bool
ReadIPv4AddressComponent(Reader& input, bool lastComponent,
                         /*out*/ uint8_t& valueOut)
{
  size_t length = 0;
  unsigned int value = 0; // Must be larger than uint8_t.

  for (;;) {
    if (input.AtEnd() && lastComponent) {
      break;
    }

    uint8_t b;
    if (input.Read(b) != Success) {
      return false;
    }

    if (b >= '0' && b <= '9') {
      if (value == 0 && length > 0) {
        return false; // Leading zeros are not allowed.
      }
      value = (value * 10) + (b - '0');
      if (value > 255) {
        return false; // Component's value is too large.
      }
      ++length;
    } else if (!lastComponent && b == '.') {
      break;
    } else {
      return false; // Invalid character.
    }
  }

  if (length == 0) {
    return false; // empty components not allowed
  }

  valueOut = static_cast<uint8_t>(value);
  return true;
}

} // namespace

// On Windows and maybe other platforms, OS-provided IP address parsing
// functions might fail if the protocol (IPv4 or IPv6) has been disabled, so we
// can't rely on them.
bool
ParseIPv4Address(Input hostname, /*out*/ uint8_t (&out)[4])
{
  Reader input(hostname);
  return ReadIPv4AddressComponent(input, false, out[0]) &&
         ReadIPv4AddressComponent(input, false, out[1]) &&
         ReadIPv4AddressComponent(input, false, out[2]) &&
         ReadIPv4AddressComponent(input, true, out[3]);
}

namespace {

bool
FinishIPv6Address(/*in/out*/ uint8_t (&address)[16], int numComponents,
                  int contractionIndex)
{
  assert(numComponents >= 0);
  assert(numComponents <= 8);
  assert(contractionIndex >= -1);
  assert(contractionIndex <= 8);
  assert(contractionIndex <= numComponents);
  if (!(numComponents >= 0 &&
        numComponents <= 8 &&
        contractionIndex >= -1 &&
        contractionIndex <= 8 &&
        contractionIndex <= numComponents)) {
    return false;
  }

  if (contractionIndex == -1) {
    // no contraction
    return numComponents == 8;
  }

  if (numComponents >= 8) {
    return false; // no room left to expand the contraction.
  }

  // Shift components that occur after the contraction over.
  std::copy_backward(address + (2u * static_cast<size_t>(contractionIndex)),
                     address + (2u * static_cast<size_t>(numComponents)),
                     address + (2u * 8u));
  // Fill in the contracted area with zeros.
  std::fill_n(address + 2u * static_cast<size_t>(contractionIndex),
              (8u - static_cast<size_t>(numComponents)) * 2u, static_cast<uint8_t>(0u));

  return true;
}

} // namespace

// On Windows and maybe other platforms, OS-provided IP address parsing
// functions might fail if the protocol (IPv4 or IPv6) has been disabled, so we
// can't rely on them.
bool
ParseIPv6Address(Input hostname, /*out*/ uint8_t (&out)[16])
{
  Reader input(hostname);

  int currentComponentIndex = 0;
  int contractionIndex = -1;

  if (input.Peek(':')) {
    // A valid input can only start with ':' if there is a contraction at the
    // beginning.
    uint8_t b;
    if (input.Read(b) != Success || b != ':') {
      assert(false);
      return false;
    }
    if (input.Read(b) != Success) {
      return false;
    }
    if (b != ':') {
      return false;
    }
    contractionIndex = 0;
  }

  for (;;) {
    // If we encounter a '.' then we'll have to backtrack to parse the input
    // from startOfComponent to the end of the input as an IPv4 address.
    Reader::Mark startOfComponent(input.GetMark());
    uint16_t componentValue = 0;
    size_t componentLength = 0;
    while (!input.AtEnd() && !input.Peek(':')) {
      uint8_t value;
      uint8_t b;
      if (input.Read(b) != Success) {
        assert(false);
        return false;
      }
      switch (b) {
        case '0': case '1': case '2': case '3': case '4':
        case '5': case '6': case '7': case '8': case '9':
          value = static_cast<uint8_t>(b - static_cast<uint8_t>('0'));
          break;
        case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
          value = static_cast<uint8_t>(b - static_cast<uint8_t>('a') +
                                       UINT8_C(10));
          break;
        case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
          value = static_cast<uint8_t>(b - static_cast<uint8_t>('A') +
                                       UINT8_C(10));
          break;
        case '.':
        {
          // A dot indicates we hit a IPv4-syntax component. Backtrack, parsing
          // the input from startOfComponent to the end of the input as an IPv4
          // address, and then combine it with the other components.

          if (currentComponentIndex > 6) {
            return false; // Too many components before the IPv4 component
          }

          input.SkipToEnd();
          Input ipv4Component;
          if (input.GetInput(startOfComponent, ipv4Component) != Success) {
            return false;
          }
          uint8_t (*ipv4)[4] =
            reinterpret_cast<uint8_t(*)[4]>(&out[2 * currentComponentIndex]);
          if (!ParseIPv4Address(ipv4Component, *ipv4)) {
            return false;
          }
          assert(input.AtEnd());
          currentComponentIndex += 2;

          return FinishIPv6Address(out, currentComponentIndex,
                                   contractionIndex);
        }
        default:
          return false;
      }
      if (componentLength >= 4) {
        // component too long
        return false;
      }
      ++componentLength;
      componentValue = (componentValue * 0x10u) + value;
    }

    if (currentComponentIndex >= 8) {
      return false; // too many components
    }

    if (componentLength == 0) {
      if (input.AtEnd() && currentComponentIndex == contractionIndex) {
        if (contractionIndex == 0) {
          // don't accept "::"
          return false;
        }
        return FinishIPv6Address(out, currentComponentIndex,
                                 contractionIndex);
      }
      return false;
    }

    out[2 * currentComponentIndex] =
      static_cast<uint8_t>(componentValue / 0x100);
    out[(2 * currentComponentIndex) + 1] =
      static_cast<uint8_t>(componentValue % 0x100);

    ++currentComponentIndex;

    if (input.AtEnd()) {
      return FinishIPv6Address(out, currentComponentIndex,
                               contractionIndex);
    }

    uint8_t b;
    if (input.Read(b) != Success || b != ':') {
      assert(false);
      return false;
    }

    if (input.Peek(':')) {
      // Contraction
      if (contractionIndex != -1) {
        return false; // multiple contractions are not allowed.
      }
      if (input.Read(b) != Success || b != ':') {
        assert(false);
        return false;
      }
      contractionIndex = currentComponentIndex;
      if (input.AtEnd()) {
        // "::" at the end of the input.
        return FinishIPv6Address(out, currentComponentIndex,
                                 contractionIndex);
      }
    }
  }
}

bool
IsValidReferenceDNSID(Input hostname)
{
  return IsValidDNSID(hostname, IDRole::ReferenceID, AllowWildcards::No);
}

bool
IsValidPresentedDNSID(Input hostname)
{
  return IsValidDNSID(hostname, IDRole::PresentedID, AllowWildcards::Yes);
}

namespace {

// RFC 5280 Section 4.2.1.6 says that a dNSName "MUST be in the 'preferred name
// syntax', as specified by Section 3.5 of [RFC1034] and as modified by Section
// 2.1 of [RFC1123]" except "a dNSName of ' ' MUST NOT be used." Additionally,
// we allow underscores for compatibility with existing practice.
bool
IsValidDNSID(Input hostname, IDRole idRole, AllowWildcards allowWildcards)
{
  if (hostname.GetLength() > 253) {
    return false;
  }

  Reader input(hostname);

  if (idRole == IDRole::NameConstraint && input.AtEnd()) {
    return true;
  }

  size_t dotCount = 0;
  size_t labelLength = 0;
  bool labelIsAllNumeric = false;
  bool labelEndsWithHyphen = false;

  // Only presented IDs are allowed to have wildcard labels. And, like
  // Chromium, be stricter than RFC 6125 requires by insisting that a
  // wildcard label consist only of '*'.
  bool isWildcard = allowWildcards == AllowWildcards::Yes && input.Peek('*');
  bool isFirstByte = !isWildcard;
  if (isWildcard) {
    Result rv = input.Skip(1);
    if (rv != Success) {
      assert(false);
      return false;
    }

    uint8_t b;
    rv = input.Read(b);
    if (rv != Success) {
      return false;
    }
    if (b != '.') {
      return false;
    }
    ++dotCount;
  }

  do {
    static const size_t MAX_LABEL_LENGTH = 63;

    uint8_t b;
    if (input.Read(b) != Success) {
      return false;
    }
    switch (b) {
      case '-':
        // Only reference ID labels can start with a hyphen.
        if (labelLength == 0 && idRole != IDRole::ReferenceID) {
          return false;
        }
        labelIsAllNumeric = false;
        labelEndsWithHyphen = true;
        ++labelLength;
        if (labelLength > MAX_LABEL_LENGTH) {
          return false;
        }
        break;

      // We avoid isdigit because it is locale-sensitive. See
      // http://pubs.opengroup.org/onlinepubs/009695399/functions/isdigit.html
      case '0': case '5':
      case '1': case '6':
      case '2': case '7':
      case '3': case '8':
      case '4': case '9':
        if (labelLength == 0) {
          labelIsAllNumeric = true;
        }
        labelEndsWithHyphen = false;
        ++labelLength;
        if (labelLength > MAX_LABEL_LENGTH) {
          return false;
        }
        break;

      // We avoid using islower/isupper/tolower/toupper or similar things, to
      // avoid any possibility of this code being locale-sensitive. See
      // http://pubs.opengroup.org/onlinepubs/009695399/functions/isupper.html
      case 'a': case 'A': case 'n': case 'N':
      case 'b': case 'B': case 'o': case 'O':
      case 'c': case 'C': case 'p': case 'P':
      case 'd': case 'D': case 'q': case 'Q':
      case 'e': case 'E': case 'r': case 'R':
      case 'f': case 'F': case 's': case 'S':
      case 'g': case 'G': case 't': case 'T':
      case 'h': case 'H': case 'u': case 'U':
      case 'i': case 'I': case 'v': case 'V':
      case 'j': case 'J': case 'w': case 'W':
      case 'k': case 'K': case 'x': case 'X':
      case 'l': case 'L': case 'y': case 'Y':
      case 'm': case 'M': case 'z': case 'Z':
      // We allow underscores for compatibility with existing practices.
      // See bug 1136616.
      case '_':
        labelIsAllNumeric = false;
        labelEndsWithHyphen = false;
        ++labelLength;
        if (labelLength > MAX_LABEL_LENGTH) {
          return false;
        }
        break;

      case '.':
        ++dotCount;
        if (labelLength == 0 &&
            (idRole != IDRole::NameConstraint || !isFirstByte)) {
          return false;
        }
        // Only reference ID labels can end with a hyphen.
        if (labelEndsWithHyphen && idRole != IDRole::ReferenceID) {
          return false;
        }
        labelLength = 0;
        break;

      default:
        return false; // Invalid character.
    }
    isFirstByte = false;
  } while (!input.AtEnd());

  // Only reference IDs, not presented IDs or name constraints, may be
  // absolute.
  if (labelLength == 0 && idRole != IDRole::ReferenceID) {
    return false;
  }

  // Only reference ID labels can end with a hyphen.
  if (labelEndsWithHyphen && idRole != IDRole::ReferenceID) {
    return false;
  }

  if (labelIsAllNumeric) {
    return false; // Last label must not be all numeric.
  }

  if (isWildcard) {
    // If the DNS ID ends with a dot, the last dot signifies an absolute ID.
    size_t labelCount = (labelLength == 0) ? dotCount : (dotCount + 1);

    // Like NSS, require at least two labels to follow the wildcard label.
    //
    // TODO(bug XXXXXXX): Allow the TrustDomain to control this on a
    // per-eTLD+1 basis, similar to Chromium. Even then, it might be better to
    // still enforce that there are at least two labels after the wildcard.
    if (labelCount < 3) {
      return false;
    }
    // XXX: RFC6125 says that we shouldn't accept wildcards within an IDN
    // A-Label. The consequence of this is that we effectively discriminate
    // against users of languages that cannot be encoded with ASCII.
    if (StartsWithIDNALabel(hostname)) {
      return false;
    }

    // TODO(bug XXXXXXX): Wildcards are not allowed for EV certificates.
    // Provide an option to indicate whether wildcards should be matched, for
    // the purpose of helping the application enforce this.
  }

  return true;
}

} // namespace

} } // namespace mozilla::pkix
