| // 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 <optional> |
| |
| #include <openssl/base.h> |
| #include <openssl/bytestring.h> |
| |
| #include "cert_errors.h" |
| #include "common_cert_errors.h" |
| #include "general_names.h" |
| #include "input.h" |
| #include "ip_util.h" |
| #include "parser.h" |
| #include "string_util.h" |
| #include "verify_name_match.h" |
| |
| BSSL_NAMESPACE_BEGIN |
| |
| 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(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( |
| 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; |
| } |
| |
| std::unique_ptr<NameConstraints> NameConstraints::CreateFromPermittedSubtrees( |
| GeneralNames permitted_subtrees) { |
| auto name_constraints = std::make_unique<NameConstraints>(); |
| |
| name_constraints->constrained_name_types_ = |
| permitted_subtrees.present_name_types; |
| name_constraints->permitted_subtrees_ = std::move(permitted_subtrees); |
| |
| return name_constraints; |
| } |
| |
| bool NameConstraints::Parse(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( |
| CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 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( |
| CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 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(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.size(); |
| } |
| // 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.empty()) { |
| 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( |
| 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(der::Input ip) const { |
| for (const auto &excluded_ip : excluded_subtrees_.ip_address_ranges) { |
| if (IPAddressMatchesWithNetmask(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 (IPAddressMatchesWithNetmask(ip, permitted_ip.first, |
| permitted_ip.second)) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| BSSL_NAMESPACE_END |