Based on Nekogram. Key additions: - Rebrand to FoxiGram (app name, APK name, applicationId com.foxigram.app) - Embedded Xray (VLESS+Reality) proxy client via JNI libxray.so - Bundled hidden one-tap proxies (LTE + WiFi), read-only in UI - Auto-restore proxy on restart, rebind to active network (LTE/WiFi) - Server credentials externalized to git-ignored XrayServers.java (+ template) - libxray Go source included; compiled .so, keystore, google-services.json ignored
720 lines
27 KiB
C++
720 lines
27 KiB
C++
// Copyright 2015 The Chromium Authors
|
|
//
|
|
// 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
|
|
//
|
|
// https://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.
|
|
|
|
#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
|