9#include <botan/pkix_types.h>
11#include <botan/ber_dec.h>
12#include <botan/der_enc.h>
14#include <botan/x509cert.h>
15#include <botan/internal/concat_util.h>
16#include <botan/internal/fmt.h>
17#include <botan/internal/int_utils.h>
18#include <botan/internal/loadstor.h>
19#include <botan/internal/stl_util.h>
20#include <botan/internal/x509_utils.h>
27enum class RequireFQDN :
bool { Yes =
true, No =
false };
36std::optional<std::string> validate_subtree_constraint_host(std::string_view input, RequireFQDN require_fqdn) {
40 const bool subtree = input.starts_with(
'.');
41 const std::string_view body = subtree ? input.substr(1) : input;
43 if(!dns.has_value()) {
46 if(require_fqdn == RequireFQDN::Yes && dns->to_string().find(
'.') == std::string::npos) {
51 return std::string(
".") + dns->to_string();
53 return dns->to_string();
59std::optional<GeneralName::DNSConstraint> GeneralName::DNSConstraint::from_string(std::string_view input) {
62 if(
auto canonical = validate_subtree_constraint_host(input, RequireFQDN::No)) {
63 return DNSConstraint(std::move(*canonical));
69std::optional<GeneralName::DNSConstraint> GeneralName::DNSConstraint::from_san_value(std::string_view input) {
71 return DNSConstraint(parsed->to_string());
77std::optional<GeneralName::URIConstraint> GeneralName::URIConstraint::from_string(std::string_view input) {
84 if(
auto canonical = validate_subtree_constraint_host(input, RequireFQDN::Yes)) {
85 return URIConstraint(std::move(*canonical));
91std::optional<GeneralName::URIConstraint> GeneralName::URIConstraint::from_san_value(std::string_view full_uri) {
93 return URIConstraint(std::string(full_uri));
99std::optional<GeneralName::EmailConstraint> GeneralName::EmailConstraint::from_string(std::string_view input) {
103 if(input.find(
'@') != std::string_view::npos) {
106 if(!
email.has_value()) {
109 return EmailConstraint(
email->to_string());
111 if(
auto canonical = validate_subtree_constraint_host(input, RequireFQDN::No)) {
113 return EmailConstraint(std::move(*canonical));
125bool wildcard_label_matches(std::string_view pattern_label, std::string_view candidate) {
126 if(candidate.find(
'.') != std::string_view::npos) {
129 const auto star = pattern_label.find(
'*');
130 if(star == std::string_view::npos) {
131 return pattern_label == candidate;
133 const auto prefix = pattern_label.substr(0, star);
134 const auto suffix = pattern_label.substr(star + 1);
135 if(candidate.size() < prefix.size() + suffix.size()) {
138 return candidate.starts_with(prefix) && candidate.ends_with(suffix);
155 if(pattern.empty() || constraint.empty()) {
158 const bool subtree_form = (constraint.front() ==
'.');
159 const std::string_view c_base = subtree_form ? constraint.substr(1) : constraint;
164 const auto first_dot = pattern.find(
'.');
165 const std::string_view p_left = (first_dot == std::string_view::npos) ? pattern : pattern.substr(0, first_dot);
166 const std::string_view p_tail =
167 (first_dot == std::string_view::npos) ? std::string_view{} : pattern.substr(first_dot);
173 if(subtree_form || c_base.find(
'.') != std::string_view::npos) {
176 return wildcard_label_matches(p_left, c_base);
183 if(
auto suffix_len =
checked_add(c_base.size(),
size_t{1})) {
184 if(p_tail.size() >= *suffix_len) {
185 const auto tail_suffix = p_tail.substr(p_tail.size() - *suffix_len);
186 if(tail_suffix.front() ==
'.' && tail_suffix.substr(1) == c_base) {
195 if(!subtree_form && c_base.size() > p_tail.size() && c_base.substr(c_base.size() - p_tail.size()) == p_tail) {
196 const auto x_view = c_base.substr(0, c_base.size() - p_tail.size());
197 return wildcard_label_matches(p_left, x_view);
219bool dns_subtree_match(std::string_view
name, std::string_view constraint) {
223 if(
name.size() == constraint.size()) {
224 return name == constraint;
225 }
else if(constraint.size() >
name.size()) {
230 if(constraint.empty()) {
236 const std::string_view substr =
name.substr(
name.size() - constraint.size());
238 if(constraint.front() ==
'.') {
239 return substr == constraint;
241 return substr == constraint &&
name[
name.size() - constraint.size() - 1] ==
'.';
256bool email_subtree_match(
const EmailAddress& candidate, std::string_view c) {
268 const std::string& candidate_domain = candidate.domain().to_string();
269 const auto at = c.find(
'@');
270 if(at != std::string_view::npos) {
272 return (candidate.local_part() == c.substr(0, at)) && (candidate_domain == c.substr(at + 1));
274 if(!c.empty() && c.front() ==
'.') {
276 return dns_subtree_match(candidate_domain, c);
285 return candidate_domain == c;
293 throw Encoding_Error(
"Could not convert unknown NameType to string");
314 if(
auto constraint = EmailConstraint::from_string(
email)) {
322 if(
auto constraint = DNSConstraint::from_string(
dns)) {
330 if(
auto constraint = URIConstraint::from_string(
uri)) {
338 if(
auto uri = URIConstraint::from_san_value(full_uri)) {
346 if(
auto dns = DNSConstraint::from_san_value(dns_name)) {
388 [](
const EmailConstraint& c) -> std::string {
return c.value(); },
389 [](
const DNSConstraint& c) -> std::string {
return c.value(); },
390 [](
const URIConstraint& c) -> std::string {
return c.value(); },
391 [](
const X509_DN& dn) -> std::string {
return dn.to_string(); },
392 [](
const IPv4Subnet& s) -> std::string {
return s.is_host() ? s.address().to_string() : s.to_string(); },
393 [](
const IPv6Subnet& s) -> std::string {
return s.is_host() ? s.address().to_string() : s.to_string(); },
401 [](
const IPv4Subnet& subnet) {
return subnet.serialize(); },
402 [](
const IPv6Subnet& subnet) {
return subnet.serialize(); },
403 [](
const auto&) -> std::vector<uint8_t> {
404 throw Invalid_State(
"Cannot convert GeneralName to binary string");
423 auto emit_ia5_implicit = [&](uint32_t tag, std::string_view value) {
430 emit_ia5_implicit(1, std::get<EmailConstraint>(m_name).value());
433 emit_ia5_implicit(2, std::get<DNSConstraint>(m_name).value());
436 emit_ia5_implicit(6, std::get<URIConstraint>(m_name).value());
444 const auto& subnet = std::get<IPv4Subnet>(m_name);
445 const auto addr_and_mask =
452 const auto& subnet = std::get<IPv6Subnet>(m_name);
453 const auto addr_and_mask =
465 throw Encoding_Error(
"Cannot encode GeneralName of Other or Unknown type");
483 if(!constraint.has_value()) {
487 m_name = std::move(*constraint);
490 if(!constraint.has_value()) {
494 m_name = std::move(*constraint);
504 if(!constraint.has_value()) {
508 m_name = std::move(*constraint);
518 const auto addr_and_mask = std::span<const uint8_t, 8>{obj.
bits(), 8};
520 if(!subnet.has_value()) {
521 throw Decoding_Error(
"IPv4 name constraint mask is not a contiguous CIDR prefix");
526 }
else if(obj.
length() == 32) {
527 const auto addr_and_mask = std::span<const uint8_t, 32>{obj.
bits(), 32};
529 if(!subnet.has_value()) {
530 throw Decoding_Error(
"IPv6 name constraint mask is not a contiguous CIDR prefix");
545 return dns_subtree_match(dns_name, std::get<DNSConstraint>(m_name).value());
552 return dns_subtree_match(dns_name.
to_string(), std::get<DNSConstraint>(m_name).value());
559 return std::get<IPv4Subnet>(m_name).contains(
IPv4Address(ip));
566 return std::get<IPv6Subnet>(m_name).contains(ip);
573 return matches_dn(dn, std::get<X509_DN>(m_name));
584 const auto host =
uri.host();
585 if(!host.has_value() || !std::holds_alternative<DNSName>(host->get())) {
588 const std::string& dns_host = std::get<DNSName>(host->get()).to_string();
589 const std::string& constraint = std::get<URIConstraint>(m_name).value();
602 if(!constraint.empty() && constraint.front() ==
'.') {
603 return dns_subtree_match(dns_host, constraint);
605 return dns_host == constraint;
612 return email_subtree_match(addr, std::get<EmailConstraint>(m_name).value());
643 const std::string& constraint = std::get<EmailConstraint>(m_name).value();
644 if(constraint.find(
'@') != std::string::npos) {
664 if(!constraint.empty() && constraint.front() ==
'.') {
666 return candidate_domain.ends_with(constraint);
669 return candidate_domain == constraint;
673 class MatchScore final {
675 MatchScore() : m_any(
false), m_some(
false), m_all(
true) {}
707 const auto& constraint = std::get<DNSConstraint>(m_name).value();
710 score.add(dns_subtree_match(
dns.to_string(), constraint));
717 if(cn.find(
'.') == std::string::npos) {
724 score.add(dns_subtree_match(dns_form->to_string(), constraint));
729 const X509_DN& constraint = std::get<X509_DN>(m_name);
736 const auto& subnet = std::get<IPv4Subnet>(m_name);
743 score.add(subnet.contains(*ipv4));
748 score.add(subnet.contains(ipv4));
769 return score.result();
787 os << gn.
type() <<
":" << gn.
name();
811 std::optional<size_t> maximum;
822 if(maximum.has_value()) {
833 std::vector<GeneralSubtree>&& excluded_subtrees) :
834 m_permitted_subtrees(std::move(permitted_subtrees)), m_excluded_subtrees(std::move(excluded_subtrees)) {
835 for(
const auto& c : m_permitted_subtrees) {
836 m_permitted_name_types.insert(c.base().type_code());
838 for(
const auto& c : m_excluded_subtrees) {
839 m_excluded_name_types.insert(c.base().type_code());
845bool exceeds_limit(
size_t dn_count,
size_t alt_count,
size_t constraint_count) {
850 constexpr size_t MAX_NC_CHECKS = (1 << 16);
852 if(
auto names =
checked_add(dn_count, alt_count)) {
853 if(
auto product =
checked_mul(*names, constraint_count)) {
854 if(*product < MAX_NC_CHECKS) {
900 auto is_permitted_dn = [&](
const X509_DN& dn) {
906 for(
const auto& c : m_permitted_subtrees) {
907 if(c.base().matches_dn(dn)) {
916 auto is_permitted_dns_name = [&](
const DNSName& name) {
922 for(
const auto& c : m_permitted_subtrees) {
923 if(c.base().matches_dns(name)) {
942 auto is_permitted_ipv4 = [&](
const IPv4Address& ipv4) {
943 if(!ip_form_restricted) {
947 for(
const auto& c : m_permitted_subtrees) {
948 if(c.base().matches_ipv4(ipv4)) {
961 auto is_permitted_ipv6 = [&](
const IPv6Address& ipv6) {
962 if(!ip_form_restricted) {
966 for(
const auto& c : m_permitted_subtrees) {
967 if(c.base().matches_ipv6(ipv6)) {
976 auto is_permitted_uri = [&](
const URI& uri) {
992 const auto host = uri.host();
993 if(!host.has_value() || !std::holds_alternative<DNSName>(host->get())) {
996 if(std::get<DNSName>(host->get()).to_string().find(
'.') == std::string::npos) {
999 for(
const auto& c : m_permitted_subtrees) {
1000 if(c.base().matches_uri(uri)) {
1007 auto is_permitted_email = [&](
const EmailAddress& addr) {
1012 for(
const auto& c : m_permitted_subtrees) {
1013 if(c.base().matches_email(addr)) {
1028 for(
const auto& c : m_permitted_subtrees) {
1029 if(c.base().matches_email(mailbox)) {
1053 for(
const auto& alt_dn : alt_name.directory_names()) {
1054 if(!is_permitted_dn(alt_dn)) {
1059 for(
const auto& alt_dns : alt_name.dns_names()) {
1060 if(!is_permitted_dns_name(alt_dns)) {
1065 for(
const auto& alt_ipv4 : alt_name.ipv4_addresses()) {
1066 if(!is_permitted_ipv4(alt_ipv4)) {
1071 for(
const auto& alt_ipv6 : alt_name.ipv6_addresses()) {
1072 if(!is_permitted_ipv6(alt_ipv6)) {
1077 for(
const auto& uri : alt_name.uri_names()) {
1078 if(!is_permitted_uri(uri)) {
1083 for(
const auto& addr : alt_name.email_addresses()) {
1084 if(!is_permitted_email(addr)) {
1089 for(
const auto& mailbox : alt_name.smtp_utf8_mailboxes()) {
1090 if(!is_permitted_smtp_utf8(mailbox)) {
1096 if(alt_name.is_empty()) {
1099 if(!is_permitted_ipv4(*ipv4)) {
1102 }
else if(cn.find(
'.') != std::string::npos) {
1104 if(!is_permitted_dns_name(*dns_form)) {
1120 if(!is_permitted_email(*addr)) {
1146 if(reject_unknown) {
1159 auto is_excluded_dn = [&](
const X509_DN& dn) {
1165 for(
const auto& c : m_excluded_subtrees) {
1166 if(c.base().matches_dn(dn)) {
1175 auto is_excluded_dns_name = [&](
const DNSName& name) {
1181 for(
const auto& c : m_excluded_subtrees) {
1182 if(c.base().matches_dns(name)) {
1196 const auto& constraint = std::get<GeneralName::DNSConstraint>(c.base().m_name).value();
1207 auto is_excluded_ipv4 = [&](
const IPv4Address& ipv4) {
1209 for(
const auto& c : m_excluded_subtrees) {
1210 if(c.base().matches_ipv4(ipv4)) {
1220 auto is_excluded_ipv6 = [&](
const IPv6Address& ipv6) {
1222 for(
const auto& c : m_excluded_subtrees) {
1223 if(c.base().matches_ipv6(ipv6)) {
1232 if(
auto embedded_v4 = ipv6.as_ipv4()) {
1233 for(
const auto& c : m_excluded_subtrees) {
1234 if(c.base().matches_ipv4(*embedded_v4)) {
1245 auto is_excluded_uri = [&](
const URI& uri) {
1260 const auto host = uri.host();
1261 if(!host.has_value() || !std::holds_alternative<DNSName>(host->get())) {
1264 if(std::get<DNSName>(host->get()).to_string().find(
'.') == std::string::npos) {
1267 for(
const auto& c : m_excluded_subtrees) {
1268 if(c.base().matches_uri(uri)) {
1295 auto mailbox_form_constraint_covers_domain = [](
const GeneralName& gn,
const DNSName& san_domain) {
1299 const auto& constraint = std::get<GeneralName::EmailConstraint>(gn.m_name).value();
1300 const auto at = constraint.find(
'@');
1301 return at != std::string::npos && san_domain.to_string() == constraint.substr(at + 1);
1304 auto is_excluded_email = [&](
const EmailAddress& addr) {
1306 for(
const auto& c : m_excluded_subtrees) {
1307 if(c.base().matches_email(addr)) {
1324 for(
const auto& c : m_excluded_subtrees) {
1325 if(c.base().matches_email(mailbox)) {
1328 if(mailbox_form_constraint_covers_domain(c.base(), mailbox.domain())) {
1340 for(
const auto& alt_dn : alt_name.directory_names()) {
1341 if(is_excluded_dn(alt_dn)) {
1346 for(
const auto& alt_dns : alt_name.dns_names()) {
1347 if(is_excluded_dns_name(alt_dns)) {
1352 for(
const auto& alt_ipv4 : alt_name.ipv4_addresses()) {
1353 if(is_excluded_ipv4(alt_ipv4)) {
1358 for(
const auto& alt_ipv6 : alt_name.ipv6_addresses()) {
1359 if(is_excluded_ipv6(alt_ipv6)) {
1364 for(
const auto& uri : alt_name.uri_names()) {
1365 if(is_excluded_uri(uri)) {
1370 for(
const auto& addr : alt_name.email_addresses()) {
1371 if(is_excluded_email(addr)) {
1376 for(
const auto& mailbox : alt_name.smtp_utf8_mailboxes()) {
1377 if(is_excluded_smtp_utf8(mailbox)) {
1383 if(alt_name.is_empty()) {
1386 if(is_excluded_ipv4(*ipv4)) {
1389 }
else if(cn.find(
'.') != std::string::npos) {
1391 if(is_excluded_dns_name(*dns_form)) {
1401 if(is_excluded_email(*addr)) {
#define BOTAN_ASSERT_NOMSG(expr)
#define BOTAN_DEBUG_ASSERT(expr)
#define BOTAN_ASSERT_UNREACHABLE()
const std::string & value() const
const std::set< IPv6Address > & ipv6_addresses() const
Return the set of IPv6 addresses included in this alternative name.
const std::set< DNSName > & dns_names() const
Return the set of DNS names included in this alternative name.
const std::set< X509_DN > & directory_names() const
Return the set of directory names included in this alternative name.
const std::set< EmailAddress > & email_addresses() const
Return the set of email addresses included in this alternative name.
const std::set< IPv4Address > & ipv4_addresses() const
Return the set of IPv4 addresses included in this alternative name.
const std::set< URI > & uri_names() const
Return the set of URIs included in this alternative name.
bool is_empty() const
Return true if this alternative name is empty (zero names).
BER_Object get_next_object()
BER_Decoder & decode(bool &out)
BER_Decoder & verify_end()
BER_Decoder start_sequence()
BER_Decoder & decode_optional(T &out, ASN1_Type type_tag, ASN1_Class class_tag, const T &default_value=T())
const uint8_t * bits() const
bool is_a(ASN1_Type type_tag, ASN1_Class class_tag) const
DER_Encoder & add_object(ASN1_Type type_tag, ASN1_Class class_tag, const uint8_t rep[], size_t length)
DER_Encoder & start_sequence()
DER_Encoder & encode(bool b)
const std::string & to_string() const
static std::optional< DNSName > from_san_string(std::string_view name)
static std::optional< DNSName > from_string(std::string_view name)
static std::optional< EmailAddress > from_string(std::string_view addr)
static GeneralName email(std::string_view email)
void decode_from(BER_Decoder &from) override
bool matches_ipv6(const IPv6Address &ip) const
static GeneralName ipv4_address(uint32_t ipv4)
bool matches_uri(const URI &uri) const
void encode_into(DER_Encoder &to) const override
static GeneralName uri(std::string_view uri)
static GeneralName _dns_san_value(std::string_view dns)
MatchResult matches(const X509_Certificate &cert) const
bool matches_dn(const X509_DN &dn) const
std::vector< uint8_t > binary_name() const
bool matches_email(const EmailAddress &addr) const
static GeneralName ipv6_address(const IPv6Address &ipv6)
bool matches_dns(const std::string &dns_name) const
NameType type_code() const
bool matches_ipv4(uint32_t ip) const
static GeneralName dns(std::string_view dns)
static GeneralName _uri_san_value(std::string_view full_uri)
static GeneralName directory_name(Botan::X509_DN dn)
A single Name Constraint.
void encode_into(DER_Encoder &to) const override
const GeneralName & base() const
void decode_from(BER_Decoder &from) override
static std::optional< IPv4Address > from_string(std::string_view str)
static IPv4Address netmask(size_t bits)
static std::optional< IPv4Subnet > from_address_and_mask(std::span< const uint8_t, 8 > addr_and_mask)
static IPv4Subnet host(IPv4Address address)
static IPv6Address netmask(size_t bits)
static std::optional< IPv6Subnet > from_address_and_mask(std::span< const uint8_t, 32 > addr_and_mask)
static IPv6Subnet host(IPv6Address address)
NameConstraints()=default
bool is_permitted(const X509_Certificate &cert, bool reject_unknown) const
bool is_excluded(const X509_Certificate &cert, bool reject_unknown) const
const std::vector< GeneralSubtree > & permitted() const
const std::vector< GeneralSubtree > & excluded() const
const DNSName & domain() const
The domain, as an LDH host name in A-label form (RFC 9598 Section 3).
static std::optional< URI > from_string(std::string_view raw)
const X509_DN & subject_dn() const
std::vector< std::string > subject_info(std::string_view name) const
const AlternativeName & subject_alt_name() const
std::vector< std::string > get_attribute(std::string_view attr) const
void decode_from(BER_Decoder &from) override
std::vector< uint8_t > put_in_sequence(const std::vector< uint8_t > &contents)
std::string to_string(const BER_Object &obj)
bool wildcard_intersects_excluded_dns_subtree(std::string_view pattern, std::string_view constraint)
constexpr std::optional< T > checked_add(T a, T b)
@ ExplicitContextSpecific
std::string fmt(std::string_view format, const T &... args)
std::ostream & operator<<(std::ostream &out, const OID &oid)
constexpr std::optional< T > checked_mul(T a, T b)
bool x509_dn_subtree_match(const X509_DN &name, const X509_DN &constraint)
constexpr auto concat(Rs &&... ranges)