// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "name_constraints.h"

#include <limits.h>

#include <memory>

#include "cert_errors.h"
#include "common_cert_errors.h"
#include "general_names.h"
#include "string_util.h"
#include "verify_name_match.h"
#include "input.h"
#include "parser.h"
#include "tag.h"
#include <optional>
#include <openssl/base.h>

namespace bssl {

namespace {

// The name types of GeneralName that are fully supported in name constraints.
//
// (The other types will have the minimal checking described by RFC 5280
// section 4.2.1.10: If a name constraints extension that is marked as critical
// imposes constraints on a particular name form, and an instance of
// that name form appears in the subject field or subjectAltName
// extension of a subsequent certificate, then the application MUST
// either process the constraint or reject the certificate.)
const int kSupportedNameTypes =
    GENERAL_NAME_RFC822_NAME | GENERAL_NAME_DNS_NAME |
    GENERAL_NAME_DIRECTORY_NAME | GENERAL_NAME_IP_ADDRESS;

// Controls wildcard handling of DNSNameMatches.
// If WildcardMatchType is WILDCARD_PARTIAL_MATCH "*.bar.com" is considered to
// match the constraint "foo.bar.com". If it is WILDCARD_FULL_MATCH, "*.bar.com"
// will match "bar.com" but not "foo.bar.com".
enum WildcardMatchType { WILDCARD_PARTIAL_MATCH, WILDCARD_FULL_MATCH };

// Returns true if |name| falls in the subtree defined by |dns_constraint|.
// RFC 5280 section 4.2.1.10:
// 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.
//
// |wildcard_matching| controls handling of wildcard names (|name| starts with
// "*."). Wildcard handling is not specified by RFC 5280, but certificate
// verification allows it, name constraints must check it similarly.
bool DNSNameMatches(std::string_view name,
                    std::string_view dns_constraint,
                    WildcardMatchType wildcard_matching) {
  // Everything matches the empty DNS name constraint.
  if (dns_constraint.empty())
    return true;

  // Normalize absolute DNS names by removing the trailing dot, if any.
  if (!name.empty() && *name.rbegin() == '.')
    name.remove_suffix(1);
  if (!dns_constraint.empty() && *dns_constraint.rbegin() == '.')
    dns_constraint.remove_suffix(1);

  // Wildcard partial-match handling ("*.bar.com" matching name constraint
  // "foo.bar.com"). This only handles the case where the the dnsname and the
  // constraint match after removing the leftmost label, otherwise it is handled
  // by falling through to the check of whether the dnsname is fully within or
  // fully outside of the constraint.
  if (wildcard_matching == WILDCARD_PARTIAL_MATCH && name.size() > 2 &&
      name[0] == '*' && name[1] == '.') {
    size_t dns_constraint_dot_pos = dns_constraint.find('.');
    if (dns_constraint_dot_pos != std::string::npos) {
      std::string_view dns_constraint_domain =
          dns_constraint.substr(dns_constraint_dot_pos + 1);
      std::string_view wildcard_domain = name.substr(2);
      if (bssl::string_util::IsEqualNoCase(wildcard_domain,
                                          dns_constraint_domain)) {
        return true;
      }
    }
  }

  if (!bssl::string_util::EndsWithNoCase(name, dns_constraint)) {
    return false;
  }

  // Exact match.
  if (name.size() == dns_constraint.size())
    return true;
  // If dNSName constraint starts with a dot, only subdomains should match.
  // (e.g., "foo.bar.com" matches constraint ".bar.com", but "bar.com" doesn't.)
  // RFC 5280 is ambiguous, but this matches the behavior of other platforms.
  if (!dns_constraint.empty() && dns_constraint[0] == '.')
    dns_constraint.remove_prefix(1);
  // Subtree match.
  if (name.size() > dns_constraint.size() &&
      name[name.size() - dns_constraint.size() - 1] == '.') {
    return true;
  }
  // Trailing text matches, but not in a subtree (e.g., "foobar.com" is not a
  // match for "bar.com").
  return false;
}

// Parses a GeneralSubtrees |value| and store the contents in |subtrees|.
// The individual values stored into |subtrees| are not validated by this
// function.
// NOTE: |subtrees| is not pre-initialized by the function(it is expected to be
// a default initialized object), and it will be modified regardless of the
// return value.
[[nodiscard]] bool ParseGeneralSubtrees(const der::Input& value,
                                        GeneralNames* subtrees,
                                        CertErrors* errors) {
  BSSL_CHECK(errors);

  // GeneralSubtrees ::= SEQUENCE SIZE (1..MAX) OF GeneralSubtree
  //
  // GeneralSubtree ::= SEQUENCE {
  //      base                    GeneralName,
  //      minimum         [0]     BaseDistance DEFAULT 0,
  //      maximum         [1]     BaseDistance OPTIONAL }
  //
  // BaseDistance ::= INTEGER (0..MAX)
  der::Parser sequence_parser(value);
  // The GeneralSubtrees sequence should have at least 1 element.
  if (!sequence_parser.HasMore())
    return false;
  while (sequence_parser.HasMore()) {
    der::Parser subtree_sequence;
    if (!sequence_parser.ReadSequence(&subtree_sequence))
      return false;

    der::Input raw_general_name;
    if (!subtree_sequence.ReadRawTLV(&raw_general_name))
      return false;

    if (!ParseGeneralName(raw_general_name,
                          GeneralNames::IP_ADDRESS_AND_NETMASK, subtrees,
                          errors)) {
      errors->AddError(kFailedParsingGeneralName);
      return false;
    }

    // RFC 5280 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.
    // However, if an application encounters a critical name constraints
    // extension that specifies other values for minimum or maximum for a name
    // form that appears in a subsequent certificate, the application MUST
    // either process these fields or reject the certificate.

    // Note that technically failing here isn't required: rather only need to
    // fail if a name of this type actually appears in a subsequent cert and
    // this extension was marked critical. However the minimum and maximum
    // fields appear uncommon enough that implementing that isn't useful.
    if (subtree_sequence.HasMore())
      return false;
  }
  return true;
}

bool IsAlphaDigit(char c) {
  return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') ||
         (c >= 'A' && c <= 'Z');
}

// Returns true if 'local_part' contains only characters that are valid in a
// non-quoted mailbox local-part. Does not check any other part of the syntax
// requirements. Does not allow whitespace.
bool IsAllowedRfc822LocalPart(std::string_view local_part) {
  if (local_part.empty()) {
    return false;
  }
  for (char c : local_part) {
    if (!(IsAlphaDigit(c) || c == '!' || c == '#' || c == '$' || c == '%' ||
          c == '&' || c == '\'' || c == '*' || c == '+' || c == '-' ||
          c == '/' || c == '=' || c == '?' || c == '^' || c == '_' ||
          c == '`' || c == '{' || c == '|' || c == '}' || c == '~' ||
          c == '.')) {
      return false;
    }
  }
  return true;
}

// Returns true if 'domain' contains only characters that are valid in a
// mailbox domain. Does not check any other part of the syntax
// requirements. Does not allow IPv6-address-literal as text IPv6 addresses are
// non-unique. Does not allow other address literals either as how to handle
// them with domain/subdomain matching isn't specified/possible.
bool IsAllowedRfc822Domain(std::string_view domain) {
  if (domain.empty()) {
    return false;
  }
  for (char c : domain) {
    if (!(IsAlphaDigit(c) || c == '-' || c == '.')) {
      return false;
    }
  }
  return true;
}

enum class Rfc822NameMatchType { kPermitted, kExcluded };
bool Rfc822NameMatches(std::string_view local_part,
                       std::string_view domain,
                       std::string_view rfc822_constraint,
                       Rfc822NameMatchType match_type,
                       bool case_insensitive_local_part) {
  // In case of parsing errors, return a value that will cause the name to not
  // be permitted.
  const bool error_value =
      match_type == Rfc822NameMatchType::kPermitted ? false : true;

  std::vector<std::string_view> constraint_components =
      bssl::string_util::SplitString(rfc822_constraint, '@');
  std::string_view constraint_local_part;
  std::string_view constraint_domain;
  if (constraint_components.size() == 1) {
    constraint_domain = constraint_components[0];
  } else if (constraint_components.size() == 2) {
    constraint_local_part = constraint_components[0];
    if (!IsAllowedRfc822LocalPart(constraint_local_part)) {
      return error_value;
    }
    constraint_domain = constraint_components[1];
  } else {
    // If we did the full parsing then it is possible for a @ to be in a quoted
    // local-part of the name, but we don't do that, so just error if @ appears
    // more than once.
    return error_value;
  }
  if (!IsAllowedRfc822Domain(constraint_domain)) {
    return error_value;
  }

  // RFC 5280 section 4.2.1.10:
  // 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".
  if (!constraint_local_part.empty()) {
    return (case_insensitive_local_part
                ? string_util::IsEqualNoCase(local_part, constraint_local_part)
                : local_part == constraint_local_part) &&
           string_util::IsEqualNoCase(domain, constraint_domain);
  }

  // RFC 5280 section 4.2.1.10:
  // 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".
  if (!constraint_domain.empty() && constraint_domain[0] == '.') {
    return string_util::EndsWithNoCase(domain, constraint_domain);
  }

  // RFC 5280 section 4.2.1.10:
  // 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".
  return string_util::IsEqualNoCase(domain, constraint_domain);
}

}  // namespace

NameConstraints::~NameConstraints() = default;

// static
std::unique_ptr<NameConstraints> NameConstraints::Create(
    const der::Input& extension_value,
    bool is_critical,
    CertErrors* errors) {
  BSSL_CHECK(errors);

  auto name_constraints = std::make_unique<NameConstraints>();
  if (!name_constraints->Parse(extension_value, is_critical, errors))
    return nullptr;
  return name_constraints;
}

bool NameConstraints::Parse(const der::Input& extension_value,
                            bool is_critical,
                            CertErrors* errors) {
  BSSL_CHECK(errors);

  der::Parser extension_parser(extension_value);
  der::Parser sequence_parser;

  // NameConstraints ::= SEQUENCE {
  //      permittedSubtrees       [0]     GeneralSubtrees OPTIONAL,
  //      excludedSubtrees        [1]     GeneralSubtrees OPTIONAL }
  if (!extension_parser.ReadSequence(&sequence_parser))
    return false;
  if (extension_parser.HasMore())
    return false;

  std::optional<der::Input> permitted_subtrees_value;
  if (!sequence_parser.ReadOptionalTag(der::ContextSpecificConstructed(0),
                                       &permitted_subtrees_value)) {
    return false;
  }
  if (permitted_subtrees_value &&
      !ParseGeneralSubtrees(permitted_subtrees_value.value(),
                            &permitted_subtrees_, errors)) {
    return false;
  }
  constrained_name_types_ |=
      permitted_subtrees_.present_name_types &
      (is_critical ? GENERAL_NAME_ALL_TYPES : kSupportedNameTypes);

  std::optional<der::Input> excluded_subtrees_value;
  if (!sequence_parser.ReadOptionalTag(der::ContextSpecificConstructed(1),
                                       &excluded_subtrees_value)) {
    return false;
  }
  if (excluded_subtrees_value &&
      !ParseGeneralSubtrees(excluded_subtrees_value.value(),
                            &excluded_subtrees_, errors)) {
    return false;
  }
  constrained_name_types_ |=
      excluded_subtrees_.present_name_types &
      (is_critical ? GENERAL_NAME_ALL_TYPES : kSupportedNameTypes);

  // RFC 5280 section 4.2.1.10:
  // 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 (!permitted_subtrees_value && !excluded_subtrees_value)
    return false;

  if (sequence_parser.HasMore())
    return false;

  return true;
}

void NameConstraints::IsPermittedCert(const der::Input& subject_rdn_sequence,
                                      const GeneralNames* subject_alt_names,
                                      CertErrors* errors) const {
  // Checking NameConstraints is O(number_of_names * number_of_constraints).
  // Impose a hard limit to mitigate the use of name constraints as a DoS
  // mechanism. This mimics the similar check in BoringSSL x509/v_ncons.c
  // TODO(bbe): make both name constraint mechanisms subquadratic and remove
  // this check.

  const size_t kMaxChecks = 1048576;  // 1 << 20

  // Names all come from a certificate, which is bound by size_t, so adding them
  // up can not overflow a size_t.
  size_t name_count = 0;
  // Constraints all come from a certificate, which is bound by a size_t, so
  // adding them up can not overflow a size_t.
  size_t constraint_count = 0;
  if (subject_alt_names) {
    name_count = subject_alt_names->rfc822_names.size() +
                 subject_alt_names->dns_names.size() +
                 subject_alt_names->directory_names.size() +
                 subject_alt_names->ip_addresses.size();
    constraint_count = excluded_subtrees_.rfc822_names.size() +
                       permitted_subtrees_.rfc822_names.size() +
                       excluded_subtrees_.dns_names.size() +
                       permitted_subtrees_.dns_names.size() +
                       excluded_subtrees_.directory_names.size() +
                       permitted_subtrees_.directory_names.size() +
                       excluded_subtrees_.ip_address_ranges.size() +
                       permitted_subtrees_.ip_address_ranges.size();
  } else {
    constraint_count += excluded_subtrees_.directory_names.size() +
                        permitted_subtrees_.directory_names.size();
    name_count = subject_rdn_sequence.Length();
  }
  // Upper bound the number of possible checks, checking for overflow.
  size_t check_count = constraint_count * name_count;
  if ((constraint_count > 0 && check_count / constraint_count != name_count) ||
      check_count > kMaxChecks) {
    errors->AddError(cert_errors::kTooManyNameConstraintChecks);
    return;
  }

  std::vector<std::string> subject_email_addresses_to_check;
  if (!subject_alt_names &&
      (constrained_name_types() & GENERAL_NAME_RFC822_NAME)) {
    if (!FindEmailAddressesInName(subject_rdn_sequence,
                                  &subject_email_addresses_to_check)) {
      // Error parsing |subject_rdn_sequence|.
      errors->AddError(cert_errors::kNotPermittedByNameConstraints);
      return;
    }
  }

  // Subject Alternative Name handling:
  //
  // RFC 5280 section 4.2.1.6:
  // id-ce-subjectAltName OBJECT IDENTIFIER ::=  { id-ce 17 }
  //
  // SubjectAltName ::= GeneralNames
  //
  // GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName

  if (subject_alt_names) {
    // Check unsupported name types:
    // constrained_name_types() for the unsupported types will only be true if
    // that type of name was present in a name constraint that was marked
    // critical.
    //
    // RFC 5280 section 4.2.1.10:
    // If a name constraints extension that is marked as critical
    // imposes constraints on a particular name form, and an instance of
    // that name form appears in the subject field or subjectAltName
    // extension of a subsequent certificate, then the application MUST
    // either process the constraint or reject the certificate.
    if (constrained_name_types() & subject_alt_names->present_name_types &
        ~kSupportedNameTypes) {
      errors->AddError(cert_errors::kNotPermittedByNameConstraints);
      return;
    }

    // Check supported name types:

    // Only check rfc822 SANs if any rfc822 constraints are present, since we
    // might fail if there are email addresses we don't know how to parse but
    // are technically correct.
    if (constrained_name_types() & GENERAL_NAME_RFC822_NAME) {
      for (const auto& rfc822_name : subject_alt_names->rfc822_names) {
        if (!IsPermittedRfc822Name(
                rfc822_name, /*case_insensitive_exclude_localpart=*/false)) {
          errors->AddError(cert_errors::kNotPermittedByNameConstraints);
          return;
        }
      }
    }

    for (const auto& dns_name : subject_alt_names->dns_names) {
      if (!IsPermittedDNSName(dns_name)) {
        errors->AddError(cert_errors::kNotPermittedByNameConstraints);
        return;
      }
    }

    for (const auto& directory_name : subject_alt_names->directory_names) {
      if (!IsPermittedDirectoryName(directory_name)) {
        errors->AddError(cert_errors::kNotPermittedByNameConstraints);
        return;
      }
    }

    for (const auto& ip_address : subject_alt_names->ip_addresses) {
      if (!IsPermittedIP(ip_address)) {
        errors->AddError(cert_errors::kNotPermittedByNameConstraints);
        return;
      }
    }
  }

  // Subject handling:

  // RFC 5280 section 4.2.1.10:
  // Legacy implementations exist where an electronic mail address is embedded
  // in the subject distinguished name in an attribute of type emailAddress
  // (Section 4.1.2.6). When constraints are imposed on the rfc822Name name
  // form, but the certificate does not include a subject alternative name, the
  // rfc822Name constraint MUST be applied to the attribute of type emailAddress
  // in the subject distinguished name.
  for (const auto& rfc822_name : subject_email_addresses_to_check) {
    // Whether local_part should be matched case-sensitive or not is somewhat
    // unclear. RFC 2821 says that it should be case-sensitive. RFC 2985 says
    // that emailAddress attributes in a Name are fully case-insensitive.
    // Some other verifier implementations always do local-part comparison
    // case-sensitive, while some always do it case-insensitive. Many but not
    // all SMTP servers interpret addresses as case-insensitive.
    //
    // Give how poorly specified this is, and the conflicting implementations
    // in the wild, this implementation will do case-insensitive match for
    // excluded names from the subject to avoid potentially allowing
    // something that wasn't expected.
    if (!IsPermittedRfc822Name(rfc822_name,
                               /*case_insensitive_exclude_localpart=*/true)) {
      errors->AddError(cert_errors::kNotPermittedByNameConstraints);
      return;
    }
  }

  // RFC 5280 4.1.2.6:
  // If subject naming information is present only in the subjectAltName
  // extension (e.g., a key bound only to an email address or URI), then the
  // subject name MUST be an empty sequence and the subjectAltName extension
  // MUST be critical.
  // This code assumes that criticality condition is checked by the caller, and
  // therefore only needs to avoid the IsPermittedDirectoryName check against an
  // empty subject in such a case.
  if (subject_alt_names && subject_rdn_sequence.Length() == 0)
    return;

  if (!IsPermittedDirectoryName(subject_rdn_sequence)) {
    errors->AddError(cert_errors::kNotPermittedByNameConstraints);
    return;
  }
}

bool NameConstraints::IsPermittedRfc822Name(
    std::string_view name,
    bool case_insensitive_exclude_localpart) const {
  // RFC 5280 4.2.1.6.  Subject Alternative Name
  //
  // When the subjectAltName extension contains an Internet mail address,
  // the address MUST be stored in the rfc822Name.  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.

  // Relevant parts from RFC 2821 & RFC 2822
  //
  // Mailbox = Local-part "@" Domain
  // Local-part = Dot-string / Quoted-string
  //       ; MAY be case-sensitive
  //
  // Dot-string = Atom *("." Atom)
  // Atom = 1*atext
  // Quoted-string = DQUOTE *qcontent DQUOTE
  //
  //
  // atext           =       ALPHA / DIGIT / ; Any character except controls,
  //                         "!" / "#" /     ;  SP, and specials.
  //                         "$" / "%" /     ;  Used for atoms
  //                         "&" / "'" /
  //                         "*" / "+" /
  //                         "-" / "/" /
  //                         "=" / "?" /
  //                         "^" / "_" /
  //                         "`" / "{" /
  //                         "|" / "}" /
  //                         "~"
  //
  // atom            =       [CFWS] 1*atext [CFWS]
  //
  //
  // qtext           =       NO-WS-CTL /     ; Non white space controls
  //                         %d33 /          ; The rest of the US-ASCII
  //                         %d35-91 /       ;  characters not including "\"
  //                         %d93-126        ;  or the quote character
  //
  // quoted-pair     =       ("\" text) / obs-qp
  // qcontent        =       qtext / quoted-pair
  //
  //
  // Domain = (sub-domain 1*("." sub-domain)) / address-literal
  // sub-domain = Let-dig [Ldh-str]
  //
  // Let-dig = ALPHA / DIGIT
  // Ldh-str = *( ALPHA / DIGIT / "-" ) Let-dig
  //
  // address-literal = "[" IPv4-address-literal /
  //                       IPv6-address-literal /
  //                       General-address-literal "]"
  //       ; See section 4.1.3

  // However, no one actually implements all that. Known implementations just
  // do string comparisons, but that is technically incorrect. (Ex: a
  // constraint excluding |foo@example.com| should exclude a SAN of
  // |"foo"@example.com|, while a naive direct comparison will allow it.)
  //
  // We don't implement all that either, but do something a bit more fail-safe
  // by rejecting any addresses that contain characters that are not allowed in
  // the non-quoted formats.

  std::vector<std::string_view> name_components =
      bssl::string_util::SplitString(name, '@');
  if (name_components.size() != 2) {
    // If we did the full parsing then it is possible for a @ to be in a quoted
    // local-part of the name, but we don't do that, so just fail if @ appears
    // more than once.
    return false;
  }
  if (!IsAllowedRfc822LocalPart(name_components[0]) ||
      !IsAllowedRfc822Domain(name_components[1])) {
    return false;
  }

  for (const auto& excluded_name : excluded_subtrees_.rfc822_names) {
    if (Rfc822NameMatches(name_components[0], name_components[1], excluded_name,
                          Rfc822NameMatchType::kExcluded,
                          case_insensitive_exclude_localpart)) {
      return false;
    }
  }

  // If permitted subtrees are not constrained, any name that is not excluded is
  // allowed.
  if (!(permitted_subtrees_.present_name_types & GENERAL_NAME_RFC822_NAME)) {
    return true;
  }

  for (const auto& permitted_name : permitted_subtrees_.rfc822_names) {
    if (Rfc822NameMatches(name_components[0], name_components[1],
                          permitted_name, Rfc822NameMatchType::kPermitted,
                          /*case_insenitive_local_part=*/false)) {
      return true;
    }
  }

  return false;
}

bool NameConstraints::IsPermittedDNSName(std::string_view name) const {
  for (const auto& excluded_name : excluded_subtrees_.dns_names) {
    // When matching wildcard hosts against excluded subtrees, consider it a
    // match if the constraint would match any expansion of the wildcard. Eg,
    // *.bar.com should match a constraint of foo.bar.com.
    if (DNSNameMatches(name, excluded_name, WILDCARD_PARTIAL_MATCH))
      return false;
  }

  // If permitted subtrees are not constrained, any name that is not excluded is
  // allowed.
  if (!(permitted_subtrees_.present_name_types & GENERAL_NAME_DNS_NAME))
    return true;

  for (const auto& permitted_name : permitted_subtrees_.dns_names) {
    // When matching wildcard hosts against permitted subtrees, consider it a
    // match only if the constraint would match all expansions of the wildcard.
    // Eg, *.bar.com should match a constraint of bar.com, but not foo.bar.com.
    if (DNSNameMatches(name, permitted_name, WILDCARD_FULL_MATCH))
      return true;
  }

  return false;
}

bool NameConstraints::IsPermittedDirectoryName(
    const der::Input& name_rdn_sequence) const {
  for (const auto& excluded_name : excluded_subtrees_.directory_names) {
    if (VerifyNameInSubtree(name_rdn_sequence, excluded_name))
      return false;
  }

  // If permitted subtrees are not constrained, any name that is not excluded is
  // allowed.
  if (!(permitted_subtrees_.present_name_types & GENERAL_NAME_DIRECTORY_NAME))
    return true;

  for (const auto& permitted_name : permitted_subtrees_.directory_names) {
    if (VerifyNameInSubtree(name_rdn_sequence, permitted_name))
      return true;
  }

  return false;
}

bool NameConstraints::IsPermittedIP(const fillins::IPAddress& ip) const {
  // fillins::IPAddressMatchesPrefix internally maps v4 addresses to/from v6 on type
  // mismatch. We don't wish to do this, so check the sizes match first.
  for (const auto& excluded_ip : excluded_subtrees_.ip_address_ranges) {
    if (ip.size() == excluded_ip.first.size() &&
        fillins::IPAddressMatchesPrefix(ip, excluded_ip.first, excluded_ip.second)) {
      return false;
    }
  }

  // If permitted subtrees are not constrained, any name that is not excluded is
  // allowed.
  if (!(permitted_subtrees_.present_name_types & GENERAL_NAME_IP_ADDRESS)) {
    return true;
  }

  for (const auto& permitted_ip : permitted_subtrees_.ip_address_ranges) {
    if (ip.size() == permitted_ip.first.size() &&
        fillins::IPAddressMatchesPrefix(ip, permitted_ip.first, permitted_ip.second)) {
      return true;
    }
  }

  return false;
}

}  // namespace net
