Botan 3.13.0
Crypto and TLS for C&
name_constraint.cpp
Go to the documentation of this file.
1/*
2* X.509 Name Constraint
3* (C) 2015 Kai Michaelis
4* 2024,2026 Jack Lloyd
5*
6* Botan is released under the Simplified BSD License (see license.txt)
7*/
8
9#include <botan/pkix_types.h>
10
11#include <botan/ber_dec.h>
12#include <botan/der_enc.h>
13#include <botan/uri.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>
21#include <span>
22
23namespace Botan {
24
25namespace {
26
27enum class RequireFQDN : bool { Yes = true, No = false };
28
29/*
30* Validate a host constraint - either a DNS name or a subtree of the
31* form of "." followed by a DNS name. RFC 5280 4.2.1.10 defines this
32* style for URI and email constraints. For DNS it is silent, but it
33* seems in practice implementations accept subtrees for DNS
34* constraints as well.
35*/
36std::optional<std::string> validate_subtree_constraint_host(std::string_view input, RequireFQDN require_fqdn) {
37 if(input.empty()) {
38 return std::nullopt;
39 }
40 const bool subtree = input.starts_with('.');
41 const std::string_view body = subtree ? input.substr(1) : input;
42 auto dns = DNSName::from_string(body);
43 if(!dns.has_value()) {
44 return std::nullopt;
45 }
46 if(require_fqdn == RequireFQDN::Yes && dns->to_string().find('.') == std::string::npos) {
47 return std::nullopt;
48 }
49
50 if(subtree) {
51 return std::string(".") + dns->to_string();
52 } else {
53 return dns->to_string();
54 }
55}
56
57} // namespace
58
59std::optional<GeneralName::DNSConstraint> GeneralName::DNSConstraint::from_string(std::string_view input) {
60 // TODO(C++23): validate_subtree_constraint_host(input, RequireFQDN::No)
61 // .transform([](std::string s) { return DNSConstraint(std::move(s)); });
62 if(auto canonical = validate_subtree_constraint_host(input, RequireFQDN::No)) {
63 return DNSConstraint(std::move(*canonical));
64 } else {
65 return std::nullopt;
66 }
67}
68
69std::optional<GeneralName::DNSConstraint> GeneralName::DNSConstraint::from_san_value(std::string_view input) {
70 if(auto parsed = DNSName::from_san_string(input)) {
71 return DNSConstraint(parsed->to_string());
72 } else {
73 return std::nullopt;
74 }
75}
76
77std::optional<GeneralName::URIConstraint> GeneralName::URIConstraint::from_string(std::string_view input) {
78 /*
79 RFC 5280 4.2.1.10:
80 The constraint MUST be specified as a fully qualified domain
81 name and MAY specify a host or a domain. Examples would be
82 "host.example.com" and ".example.com".
83 */
84 if(auto canonical = validate_subtree_constraint_host(input, RequireFQDN::Yes)) {
85 return URIConstraint(std::move(*canonical));
86 } else {
87 return std::nullopt;
88 }
89}
90
91std::optional<GeneralName::URIConstraint> GeneralName::URIConstraint::from_san_value(std::string_view full_uri) {
92 if(URI::from_string(full_uri).has_value()) {
93 return URIConstraint(std::string(full_uri));
94 } else {
95 return std::nullopt;
96 }
97}
98
99std::optional<GeneralName::EmailConstraint> GeneralName::EmailConstraint::from_string(std::string_view input) {
100 if(input.empty()) {
101 return std::nullopt;
102 }
103 if(input.find('@') != std::string_view::npos) {
104 // Mailbox form:
105 auto email = EmailAddress::from_string(input);
106 if(!email.has_value()) {
107 return std::nullopt;
108 }
109 return EmailConstraint(email->to_string());
110 }
111 if(auto canonical = validate_subtree_constraint_host(input, RequireFQDN::No)) {
112 // Host form
113 return EmailConstraint(std::move(*canonical));
114 }
115 return std::nullopt;
116}
117
118namespace {
119
120/*
121* Match a single DNS label against an RFC 6125 6.4.3 wildcard pattern
122* label (containing exactly one '*'). The candidate must have no dots
123* (it is a single label).
124*/
125bool wildcard_label_matches(std::string_view pattern_label, std::string_view candidate) {
126 if(candidate.find('.') != std::string_view::npos) {
127 return false;
128 }
129 const auto star = pattern_label.find('*');
130 if(star == std::string_view::npos) {
131 return pattern_label == candidate;
132 }
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()) {
136 return false;
137 }
138 return candidate.starts_with(prefix) && candidate.ends_with(suffix);
139}
140
141} // namespace
142
143/*
144* Does the wildcard SAN @p pattern have some expansion that falls inside the
145* excluded DNS subtree @p constraint?
146*
147* This function is similar to but subtly different from host_wildcard_match,
148* which is trying to answer a different question, namely "is `host` a name that
149* a client should trust this wildcard cert for", including various checks such
150* as the maximum length of labels. In contrast here we want to check for any
151* possible overlap - could this wildcard expand to any name inside the excluded
152* subtree.
153*/
154bool wildcard_intersects_excluded_dns_subtree(std::string_view pattern, std::string_view constraint) {
155 if(pattern.empty() || constraint.empty()) {
156 return false;
157 }
158 const bool subtree_form = (constraint.front() == '.');
159 const std::string_view c_base = subtree_form ? constraint.substr(1) : constraint;
160 if(c_base.empty()) {
161 return false;
162 }
163
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);
168
169 if(p_tail.empty()) {
170 // Single-label wildcard. Matches single-label names only, so it
171 // can only land inside a bare-host subtree whose base is also a
172 // single label.
173 if(subtree_form || c_base.find('.') != std::string_view::npos) {
174 return false;
175 }
176 return wildcard_label_matches(p_left, c_base);
177 }
178
179 // p_tail starts with ".". If it ends (label-aligned) with "." + c_base,
180 // then every wildcard expansion produces a name ending with that
181 // suffix, which is inside c_base's subtree (both bare-host and
182 // leading-dot forms accept proper-subdomain entries).
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) {
187 return true;
188 }
189 }
190 }
191
192 // Bare-host subtrees also contain c_base itself. The wildcard can
193 // produce c_base directly iff c_base = (single label) + p_tail and
194 // the prefix label fits p_left.
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);
198 }
199
200 return false;
201}
202
203namespace {
204
205/*
206* RFC 5280 subtree matching for DNS-form names: a bare-host constraint
207* matches the host itself or any name with extra leading labels (so
208* "host.example.com" matches "host.example.com" and "www.host.example.com"
209* but not "host1.example.com"). A constraint with a leading dot matches
210* proper subdomains only.
211*
212* Used as-is for DNS name constraints. URI / RFC822 host-form constraints
213* differ from this -- they're exact-match only on the bare-host form, and
214* only the leading-dot ".host" form here is shared with them. Callers
215* dispatch the leading-dot case to this helper.
216*
217* Both inputs are assumed to already be lowercased.
218*/
219bool dns_subtree_match(std::string_view name, std::string_view constraint) {
220 // Embedded nulls should have been rejected during decoding before this point
221 BOTAN_DEBUG_ASSERT(name.find('\0') == std::string_view::npos);
222
223 if(name.size() == constraint.size()) {
224 return name == constraint;
225 } else if(constraint.size() > name.size()) {
226 // The constraint is longer than the issued name: not possibly a match
227 return false;
228 }
229
230 if(constraint.empty()) {
231 return true;
232 }
233
234 BOTAN_ASSERT_NOMSG(name.size() > constraint.size());
235
236 const std::string_view substr = name.substr(name.size() - constraint.size());
237
238 if(constraint.front() == '.') {
239 return substr == constraint;
240 } else {
241 return substr == constraint && name[name.size() - constraint.size() - 1] == '.';
242 }
243}
244
245/*
246* RFC 5280 4.2.1.10 RFC822 name constraint matching.
247*
248* The constraint @p c is one of:
249* - "local@host" - matches exactly one mailbox (case-insensitive)
250* - "host" - matches addresses whose domain is exactly host
251* - ".host" - matches addresses in any subdomain of host
252* (but NOT the base host itself)
253*
254* @p c is assumed to be already lowercased and validated at decode time.
255*/
256bool email_subtree_match(const EmailAddress& candidate, std::string_view c) {
257 /*
258 RFC 5280 7.5:
259 Two email addresses are considered to match if:
260 1) the local-part of each name is an exact match, AND
261 2) the host-part of each name matches using a case-insensitive
262 ASCII comparison.
263
264 The candidate's domain comes through DNSName as canonical-lowercase, and the
265 constraint string was lowercased only on its host portion at decode, so a
266 plain string compare on each side produces the correct result.
267 */
268 const std::string& candidate_domain = candidate.domain().to_string();
269 const auto at = c.find('@');
270 if(at != std::string_view::npos) {
271 // Mailbox form: exact-match against candidate
272 return (candidate.local_part() == c.substr(0, at)) && (candidate_domain == c.substr(at + 1));
273 }
274 if(!c.empty() && c.front() == '.') {
275 // Subtree form: any subdomain, but not the base host.
276 return dns_subtree_match(candidate_domain, c);
277 }
278 /*
279 RFC 5280 4.2.1.10:
280 To indicate all Internet mail addresses on a particular host, the
281 constraint is specified as the host name. For example, the
282 constraint "example.com" is satisfied by any mail address at the
283 host "example.com".
284 */
285 return candidate_domain == c;
286}
287
288} // namespace
289
290std::string GeneralName::type() const {
291 switch(m_type) {
293 throw Encoding_Error("Could not convert unknown NameType to string");
294 case NameType::RFC822:
295 return "RFC822";
296 case NameType::DNS:
297 return "DNS";
298 case NameType::URI:
299 return "URI";
300 case NameType::DN:
301 return "DN";
302 case NameType::IPv4:
303 return "IP";
304 case NameType::IPv6:
305 return "IPv6";
306 case NameType::Other:
307 return "Other";
308 }
309
311}
312
314 if(auto constraint = EmailConstraint::from_string(email)) {
315 return {NameType::RFC822, std::move(*constraint)};
316 } else {
317 throw Invalid_Argument(fmt("Invalid RFC822 name constraint '{}'", email));
318 }
319}
320
322 if(auto constraint = DNSConstraint::from_string(dns)) {
323 return {NameType::DNS, std::move(*constraint)};
324 } else {
325 throw Invalid_Argument(fmt("Invalid DNS name constraint '{}'", dns));
326 }
327}
328
330 if(auto constraint = URIConstraint::from_string(uri)) {
331 return {NameType::URI, std::move(*constraint)};
332 } else {
333 throw Invalid_Argument(fmt("Invalid URI name constraint '{}'", uri));
334 }
335}
336
337GeneralName GeneralName::_uri_san_value(std::string_view full_uri) {
338 if(auto uri = URIConstraint::from_san_value(full_uri)) {
339 return {NameType::URI, std::move(*uri)};
340 } else {
341 throw Invalid_Argument(fmt("Invalid URI SAN value '{}'", full_uri));
342 }
343}
344
345GeneralName GeneralName::_dns_san_value(std::string_view dns_name) {
346 if(auto dns = DNSConstraint::from_san_value(dns_name)) {
347 return {NameType::DNS, std::move(*dns)};
348 } else {
349 throw Invalid_Argument(fmt("Invalid DNS SAN value '{}'", dns_name));
350 }
351}
352
356
360
361GeneralName GeneralName::ipv4_address(uint32_t ipv4, uint32_t mask) {
362 if(auto subnet = IPv4Subnet::from_address_and_mask(ipv4, mask)) {
363 return {NameType::IPv4, *subnet};
364 } else {
365 throw Invalid_Argument("IPv4 subnet mask is not a contiguous CIDR prefix");
366 }
367}
368
372
374 return {NameType::IPv4, subnet};
375}
376
380
382 return {NameType::IPv6, subnet};
383}
384
385std::string GeneralName::name() const {
386 return std::visit(
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(); },
394 },
395 m_name);
396}
397
398std::vector<uint8_t> GeneralName::binary_name() const {
399 return std::visit(Botan::overloaded{
400 [](const Botan::X509_DN& dn) { return Botan::ASN1::put_in_sequence(dn.get_bits()); },
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");
405 },
406 },
407 m_name);
408}
409
411 /*
412 GeneralName ::= CHOICE {
413 otherName [0] OtherName,
414 rfc822Name [1] IA5String,
415 dNSName [2] IA5String,
416 x400Address [3] ORAddress,
417 directoryName [4] Name,
418 ediPartyName [5] EDIPartyName,
419 uniformResourceIdentifier [6] IA5String,
420 iPAddress [7] OCTET STRING,
421 registeredID [8] OBJECT IDENTIFIER }
422 */
423 auto emit_ia5_implicit = [&](uint32_t tag, std::string_view value) {
424 const ASN1_String str(value, ASN1_Type::Ia5String);
426 };
427
428 switch(m_type) {
429 case NameType::RFC822:
430 emit_ia5_implicit(1, std::get<EmailConstraint>(m_name).value());
431 return;
432 case NameType::DNS:
433 emit_ia5_implicit(2, std::get<DNSConstraint>(m_name).value());
434 return;
435 case NameType::URI:
436 emit_ia5_implicit(6, std::get<URIConstraint>(m_name).value());
437 return;
438 case NameType::DN:
439 to.add_object(ASN1_Type(4), ASN1_Class::ExplicitContextSpecific, std::get<X509_DN>(m_name).DER_encode());
440 return;
441 case NameType::IPv4: {
442 // In a name constraint the iPAddress is always address followed by mask,
443 // even for a single host (unlike the SAN form)
444 const auto& subnet = std::get<IPv4Subnet>(m_name);
445 const auto addr_and_mask =
446 concat(subnet.address().to_bytes(), IPv4Address::netmask(subnet.prefix_length()).to_bytes());
447 // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange)
448 to.add_object(ASN1_Type(7), ASN1_Class::ContextSpecific, addr_and_mask);
449 return;
450 }
451 case NameType::IPv6: {
452 const auto& subnet = std::get<IPv6Subnet>(m_name);
453 const auto addr_and_mask =
454 concat(subnet.address().address(), IPv6Address::netmask(subnet.prefix_length()).address());
455 // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange)
456 to.add_object(ASN1_Type(7), ASN1_Class::ContextSpecific, addr_and_mask);
457 return;
458 }
459 case NameType::Other:
461 // Decoding retains only the type tag for these forms, not the value
462 break;
463 }
464
465 throw Encoding_Error("Cannot encode GeneralName of Other or Unknown type");
466}
467
469 const BER_Object obj = ber.get_next_object();
470
472 m_type = NameType::Other;
473 } else if(obj.is_a(1, ASN1_Class::ContextSpecific)) {
474 /*
475 RFC 5280 4.2.1.10:
476 A name constraint for Internet mail addresses MAY specify a
477 particular mailbox, all addresses at a particular host, or all
478 mailboxes in a domain.
479 EmailConstraint::from_string validates and canonicalizes per the
480 Section 7.5 matching rules.
481 */
482 auto constraint = EmailConstraint::from_string(ASN1::to_string(obj));
483 if(!constraint.has_value()) {
484 throw Decoding_Error("Malformed RFC822 name in GeneralName");
485 }
486 m_type = NameType::RFC822;
487 m_name = std::move(*constraint);
488 } else if(obj.is_a(2, ASN1_Class::ContextSpecific)) {
489 auto constraint = DNSConstraint::from_string(ASN1::to_string(obj));
490 if(!constraint.has_value()) {
491 throw Decoding_Error("Malformed DNS name in GeneralName");
492 }
493 m_type = NameType::DNS;
494 m_name = std::move(*constraint);
495 } else if(obj.is_a(6, ASN1_Class::ContextSpecific)) {
496 /*
497 RFC 5280 4.2.1.10:
498 For URIs, the constraint applies to the host part of the name.
499 The constraint MUST be specified as a fully qualified domain
500 name and MAY specify a host or a domain. Examples would be
501 "host.example.com" and ".example.com".
502 */
503 auto constraint = URIConstraint::from_string(ASN1::to_string(obj));
504 if(!constraint.has_value()) {
505 throw Decoding_Error("Malformed URI name in GeneralName");
506 }
507 m_type = NameType::URI;
508 m_name = std::move(*constraint);
510 X509_DN dn;
511 BER_Decoder dec(obj, ber.limits());
512 dn.decode_from(dec);
513 dec.verify_end();
514 m_type = NameType::DN;
515 m_name.emplace<X509_DN>(dn);
516 } else if(obj.is_a(7, ASN1_Class::ContextSpecific)) {
517 if(obj.length() == 8) {
518 const auto addr_and_mask = std::span<const uint8_t, 8>{obj.bits(), 8};
519 auto subnet = IPv4Subnet::from_address_and_mask(addr_and_mask);
520 if(!subnet.has_value()) {
521 throw Decoding_Error("IPv4 name constraint mask is not a contiguous CIDR prefix");
522 }
523
524 m_type = NameType::IPv4;
525 m_name.emplace<IPv4Subnet>(*subnet);
526 } else if(obj.length() == 32) {
527 const auto addr_and_mask = std::span<const uint8_t, 32>{obj.bits(), 32};
528 auto subnet = IPv6Subnet::from_address_and_mask(addr_and_mask);
529 if(!subnet.has_value()) {
530 throw Decoding_Error("IPv6 name constraint mask is not a contiguous CIDR prefix");
531 }
532
533 m_type = NameType::IPv6;
534 m_name.emplace<IPv6Subnet>(*subnet);
535 } else {
536 throw Decoding_Error("Invalid IP name constraint size " + std::to_string(obj.length()));
537 }
538 } else {
539 m_type = NameType::Unknown;
540 }
541}
542
543bool GeneralName::matches_dns(const std::string& dns_name) const {
544 if(m_type == NameType::DNS) {
545 return dns_subtree_match(dns_name, std::get<DNSConstraint>(m_name).value());
546 }
547 return false;
548}
549
550bool GeneralName::matches_dns(const DNSName& dns_name) const {
551 if(m_type == NameType::DNS) {
552 return dns_subtree_match(dns_name.to_string(), std::get<DNSConstraint>(m_name).value());
553 }
554 return false;
555}
556
557bool GeneralName::matches_ipv4(uint32_t ip) const {
558 if(m_type == NameType::IPv4) {
559 return std::get<IPv4Subnet>(m_name).contains(IPv4Address(ip));
560 }
561 return false;
562}
563
565 if(m_type == NameType::IPv6) {
566 return std::get<IPv6Subnet>(m_name).contains(ip);
567 }
568 return false;
569}
570
571bool GeneralName::matches_dn(const X509_DN& dn) const {
572 if(m_type == NameType::DN) {
573 return matches_dn(dn, std::get<X509_DN>(m_name));
574 }
575 return false;
576}
577
578bool GeneralName::matches_uri(const URI& uri) const {
579 if(m_type != NameType::URI) {
580 return false;
581 }
582 // RFC 5280 4.2.1.10 does not provide for applying a DNS-form URI
583 // constraint to an IP-literal host.
584 const auto host = uri.host();
585 if(!host.has_value() || !std::holds_alternative<DNSName>(host->get())) {
586 return false;
587 }
588 const std::string& dns_host = std::get<DNSName>(host->get()).to_string();
589 const std::string& constraint = std::get<URIConstraint>(m_name).value();
590 /*
591 RFC 5280 4.2.1.10:
592 When the constraint begins with a period, it MAY be expanded with
593 one or more labels. That is, the constraint ".example.com" is
594 satisfied by both host.example.com and my.host.example.com.
595 However, the constraint ".example.com" is not satisfied by
596 "example.com". When the constraint does not begin with a period,
597 it specifies a host.
598
599 So a bare-host URI constraint is exact-match only; subdomains don't
600 satisfy it. dns_subtree_match handles the leading-dot form correctly.
601 */
602 if(!constraint.empty() && constraint.front() == '.') {
603 return dns_subtree_match(dns_host, constraint);
604 }
605 return dns_host == constraint;
606}
607
609 if(m_type != NameType::RFC822) {
610 return false;
611 }
612 return email_subtree_match(addr, std::get<EmailConstraint>(m_name).value());
613}
614
615bool GeneralName::matches_email(const SmtpUtf8Mailbox& mailbox) const {
616 if(m_type != NameType::RFC822) {
617 return false;
618 }
619 /*
620 RFC 9598 Section 6:
621 Setup converts the inputs of the comparison ... to constraint
622 comparison form. For both the name constraint and the subject,
623 this will convert all A-labels and NR-LDH labels to lowercase.
624 Strip the Local-part and "@" separator from each rfc822Name and
625 SmtpUTF8Mailbox, which leaves just the domain part. After setup,
626 follow the comparison steps defined in Section 4.2.1.10 of
627 [RFC5280] as follows. If the resulting name constraint domain
628 starts with a "." character, then for the name constraint to
629 match, a suffix of the resulting subject alternative name domain
630 MUST match the name constraint (including the leading ".") octet
631 for octet. If the resulting name constraint domain does not
632 start with a "." character, then for the name constraint to
633 match, the entire resulting subject alternative name domain MUST
634 match the name constraint octet for octet.
635
636 Per RFC 9598 Section 3 the SmtpUTF8Mailbox domain is already A-label /
637 NR-LDH and lowercase by construction (DNSName::from_string enforces
638 LDH + lowercase). The rfc822Name constraint flows through the same
639 DNSName validation. So octet-for-octet comparison is the correct
640 algorithm with no IDNA conversion required.
641 */
642 const std::string& candidate_domain = mailbox.domain().to_string();
643 const std::string& constraint = std::get<EmailConstraint>(m_name).value();
644 if(constraint.find('@') != std::string::npos) {
645 /*
646 * The situation with SmtpUTF8Mailbox mailbox constraints (with '@') is a bit confused.
647 *
648 * RFC 9549 updates RFC 5280 to completely drop support for mailbox constraints.
649 * Then RFC 9598 Section 6 (relevant section quoted above) defines a mechanism to
650 * apply rfc822 mailbox name constraints to SmtpUTF8Mailbox, but it does so in a
651 * completely insecure way, namely by stripping off the local-part and comparing just
652 * the domains. Under these rules, if an intermediate certificate had a permittedSubtrees
653 * containing alice@example.com then a leaf certificate could have a SmtpUTF8Mailbox
654 * containing bob@example.com, and per RFC 9598 that's fine because we are supposed
655 * to just check the domains.
656 *
657 * This is obviously nonsense. Here we return false, which ensures that
658 * is_permitted_smtp_utf8 never accepts on a mailbox constraint. In is_excluded_smtp_utf8
659 * we first call matches_email then additionally (for mailbox constraints) reject any
660 * matching domain using the additional check in mailbox_form_constraint_covers_domain.
661 */
662 return false;
663 }
664 if(!constraint.empty() && constraint.front() == '.') {
665 // Leading-dot subtree form: suffix match including the dot.
666 return candidate_domain.ends_with(constraint);
667 }
668 // Host form: exact match on the domain.
669 return candidate_domain == constraint;
670}
671
673 class MatchScore final {
674 public:
675 MatchScore() : m_any(false), m_some(false), m_all(true) {}
676
677 void add(bool m) {
678 m_any = true;
679 m_some |= m;
680 m_all &= m;
681 }
682
683 MatchResult result() const {
684 if(!m_any) {
686 } else if(m_all) {
687 return MatchResult::All;
688 } else if(m_some) {
689 return MatchResult::Some;
690 } else {
691 return MatchResult::None;
692 }
693 }
694
695 private:
696 bool m_any;
697 bool m_some;
698 bool m_all;
699 };
700
701 const X509_DN& dn = cert.subject_dn();
702 const AlternativeName& alt_name = cert.subject_alt_name();
703
704 MatchScore score;
705
706 if(m_type == NameType::DNS) {
707 const auto& constraint = std::get<DNSConstraint>(m_name).value();
708
709 for(const auto& dns : alt_name.dns_names()) {
710 score.add(dns_subtree_match(dns.to_string(), constraint));
711 }
712
713 if(alt_name.is_empty()) {
714 // TODO(Botan4): CN fallback is deprecated for removal in Botan4.
715 // Check CN instead...
716 for(const std::string& cn : dn.get_attribute("CN")) {
717 if(cn.find('.') == std::string::npos) {
718 continue;
719 }
720 if(IPv4Address::from_string(cn).has_value()) {
721 continue;
722 }
723 if(auto dns_form = DNSName::from_san_string(cn)) {
724 score.add(dns_subtree_match(dns_form->to_string(), constraint));
725 }
726 }
727 }
728 } else if(m_type == NameType::DN) {
729 const X509_DN& constraint = std::get<X509_DN>(m_name);
730 score.add(matches_dn(dn, constraint));
731
732 for(const auto& alt_dn : alt_name.directory_names()) {
733 score.add(matches_dn(alt_dn, constraint));
734 }
735 } else if(m_type == NameType::IPv4) {
736 const auto& subnet = std::get<IPv4Subnet>(m_name);
737
738 if(alt_name.is_empty()) {
739 // TODO(Botan4): CN fallback is deprecated for removal in Botan4.
740 // Check CN instead...
741 for(const std::string& cn : dn.get_attribute("CN")) {
742 if(auto ipv4 = IPv4Address::from_string(cn)) {
743 score.add(subnet.contains(*ipv4));
744 }
745 }
746 } else {
747 for(const auto& ipv4 : alt_name.ipv4_addresses()) {
748 score.add(subnet.contains(ipv4));
749 }
750 }
751 } else if(m_type == NameType::IPv6) {
752 for(const auto& ipv6 : alt_name.ipv6_addresses()) {
753 score.add(matches_ipv6(ipv6));
754 }
755 } else if(m_type == NameType::URI) {
756 for(const auto& uri : alt_name.uri_names()) {
757 score.add(matches_uri(uri));
758 }
759 } else if(m_type == NameType::RFC822) {
760 for(const auto& addr : alt_name.email_addresses()) {
761 score.add(matches_email(addr));
762 }
763 } else {
764 // Only NameType::Other (and the sentinel Unknown) remain; those
765 // cannot be matched without per-OID semantics.
767 }
768
769 return score.result();
770}
771
772//static
773bool GeneralName::matches_dn(const X509_DN& name, const X509_DN& constraint) {
774 /*
775 RFC 5280 7.1:
776 Two RelativeDistinguishedNames RDN1 and RDN2 match if they have
777 the same number of naming attributes and for each naming attribute
778 in RDN1 there is a matching naming attribute in RDN2.
779
780 This is implementing directoryName subtree match, so the constraint's RDN
781 sequence must be a prefix of the name's RDN sequence.
782 */
783 return x509_dn_subtree_match(name, constraint);
784}
785
786std::ostream& operator<<(std::ostream& os, const GeneralName& gn) {
787 os << gn.type() << ":" << gn.name();
788 return os;
789}
790
792
794 /*
795 * RFC 5280 Section 4.2.1.10:
796 * Within this profile, the minimum and maximum fields are not used with any
797 * name forms, thus, the minimum MUST be zero, and maximum MUST be absent.
798 *
799 * minimum is DEFAULT 0 so it is not encoded.
800 */
801 to.start_sequence().encode(m_base).end_cons();
802}
803
805 /*
806 * RFC 5280 Section 4.2.1.10:
807 * Within this profile, the minimum and maximum fields are not used with any
808 * name forms, thus, the minimum MUST be zero, and maximum MUST be absent.
809 */
810 size_t minimum = 0;
811 std::optional<size_t> maximum;
812
813 ber.start_sequence()
814 .decode(m_base)
817 .end_cons();
818
819 if(minimum != 0) {
820 throw Decoding_Error("GeneralSubtree minimum must be 0");
821 }
822 if(maximum.has_value()) {
823 throw Decoding_Error("GeneralSubtree maximum must be absent");
824 }
825}
826
827std::ostream& operator<<(std::ostream& os, const GeneralSubtree& gs) {
828 os << gs.base();
829 return os;
830}
831
832NameConstraints::NameConstraints(std::vector<GeneralSubtree>&& permitted_subtrees,
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());
837 }
838 for(const auto& c : m_excluded_subtrees) {
839 m_excluded_name_types.insert(c.base().type_code());
840 }
841}
842
843namespace {
844
845bool exceeds_limit(size_t dn_count, size_t alt_count, size_t constraint_count) {
846 /**
847 * OpenSSL uses a similar limit, but applies it to the total number of
848 * constraints, while we apply it to permitted and excluded independently.
849 */
850 constexpr size_t MAX_NC_CHECKS = (1 << 16);
851
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) {
855 return false;
856 }
857 }
858 }
859 return true;
860}
861
862} // namespace
863
864bool NameConstraints::is_permitted(const X509_Certificate& cert, bool reject_unknown) const {
865 if(permitted().empty()) {
866 return true;
867 }
868
869 const auto& alt_name = cert.subject_alt_name();
870
871 if(exceeds_limit(cert.subject_dn().count(), alt_name.count(), permitted().size())) {
872 return false;
873 }
874
875 if(reject_unknown) {
876 /* A critical NC restricting an unrecognized GeneralName form (e.g. x400Address)
877 * causes immediate rejection.
878 *
879 * RFC 5280 4.2.1.10 leaves this both unspecified
880 * The syntax and semantics for name constraints for otherName, ediPartyName, and
881 * registeredID are not defined by this specification
882 * and discouraged
883 * Conforming CAs [...] SHOULD NOT impose name constraints on the x400Address,
884 * ediPartyName, or registeredID name forms.
885 *
886 * In principle we should only reject when the constrained form appears in the
887 * certificate. But this situation in general seems to be a minefield, with no help
888 * from specs, test suites, etc. Lacking any obvious use case, just fail closed.
889 *
890 * If you happen to hit this with a real chain, open an issue.
891 */
892 if(m_permitted_name_types.contains(GeneralName::NameType::Unknown)) {
893 return false;
894 }
895 if(m_permitted_name_types.contains(GeneralName::NameType::Other) && !alt_name.other_name_values().empty()) {
896 return false;
897 }
898 }
899
900 auto is_permitted_dn = [&](const X509_DN& dn) {
901 // If no restrictions, then immediate accept
902 if(!m_permitted_name_types.contains(GeneralName::NameType::DN)) {
903 return true;
904 }
905
906 for(const auto& c : m_permitted_subtrees) {
907 if(c.base().matches_dn(dn)) {
908 return true;
909 }
910 }
911
912 // There is at least one permitted name and we didn't match
913 return false;
914 };
915
916 auto is_permitted_dns_name = [&](const DNSName& name) {
917 // If no restrictions, then immediate accept
918 if(!m_permitted_name_types.contains(GeneralName::NameType::DNS)) {
919 return true;
920 }
921
922 for(const auto& c : m_permitted_subtrees) {
923 if(c.base().matches_dns(name)) {
924 return true;
925 }
926 }
927
928 // There is at least one permitted name and we didn't match
929 return false;
930 };
931
932 /*
933 RFC 5280 4.2.1.10: iPAddress is a single GeneralName element where
934 IPv4 and IPv6 are distinguished only by the length.
935
936 An iPAddress subtree of either version therefore restricts the iPAddress name
937 form for both versions.
938 */
939 const bool ip_form_restricted = m_permitted_name_types.contains(GeneralName::NameType::IPv4) ||
940 m_permitted_name_types.contains(GeneralName::NameType::IPv6);
941
942 auto is_permitted_ipv4 = [&](const IPv4Address& ipv4) {
943 if(!ip_form_restricted) {
944 return true;
945 }
946
947 for(const auto& c : m_permitted_subtrees) {
948 if(c.base().matches_ipv4(ipv4)) {
949 return true;
950 }
951 }
952
953 // We might here check if there are any IPv6 permitted names which are
954 // mapped IPv4 addresses, and if so check if any of those apply. It's not
955 // clear if this is desirable, and RFC 5280 is completely silent on the issue.
956
957 // There is at least one permitted iPAddress name and we didn't match
958 return false;
959 };
960
961 auto is_permitted_ipv6 = [&](const IPv6Address& ipv6) {
962 if(!ip_form_restricted) {
963 return true;
964 }
965
966 for(const auto& c : m_permitted_subtrees) {
967 if(c.base().matches_ipv6(ipv6)) {
968 return true;
969 }
970 }
971
972 // There is at least one permitted iPAddress name and we didn't match
973 return false;
974 };
975
976 auto is_permitted_uri = [&](const URI& uri) {
977 // If no URI restrictions, accept.
978 if(!m_permitted_name_types.contains(GeneralName::NameType::URI)) {
979 return true;
980 }
981 /*
982 RFC 5280 4.2.1.10:
983 If a constraint is applied to the uniformResourceIdentifier
984 name form and a subsequent certificate includes a
985 subjectAltName extension with a uniformResourceIdentifier that
986 does not include an authority component with a host name
987 specified as a fully qualified domain name (e.g., if the URI
988 either does not include an authority component or includes an
989 authority component in which the host name is specified as an
990 IP address), then the application MUST reject the certificate.
991 */
992 const auto host = uri.host();
993 if(!host.has_value() || !std::holds_alternative<DNSName>(host->get())) {
994 return false;
995 }
996 if(std::get<DNSName>(host->get()).to_string().find('.') == std::string::npos) {
997 return false;
998 }
999 for(const auto& c : m_permitted_subtrees) {
1000 if(c.base().matches_uri(uri)) {
1001 return true;
1002 }
1003 }
1004 return false;
1005 };
1006
1007 auto is_permitted_email = [&](const EmailAddress& addr) {
1008 // If no email restrictions, accept.
1009 if(!m_permitted_name_types.contains(GeneralName::NameType::RFC822)) {
1010 return true;
1011 }
1012 for(const auto& c : m_permitted_subtrees) {
1013 if(c.base().matches_email(addr)) {
1014 return true;
1015 }
1016 }
1017 return false;
1018 };
1019
1020 // RFC 9598 Section 6 extends rfc822Name name constraints to SmtpUTF8Mailbox
1021 // SAN entries (id-on-SmtpUTF8Mailbox otherNames). When rfc822Name
1022 // constraints are in effect, every SmtpUTF8Mailbox SAN must match
1023 // at least one permitted entry.
1024 auto is_permitted_smtp_utf8 = [&](const SmtpUtf8Mailbox& mailbox) {
1025 if(!m_permitted_name_types.contains(GeneralName::NameType::RFC822)) {
1026 return true;
1027 }
1028 for(const auto& c : m_permitted_subtrees) {
1029 if(c.base().matches_email(mailbox)) {
1030 return true;
1031 }
1032 }
1033 return false;
1034 };
1035
1036 /*
1037 RFC 5280 4.1.2.6:
1038 If subject naming information is present only in the
1039 subjectAltName extension (e.g., a key bound only to an email
1040 address or URI), then the subject name MUST be an empty
1041 sequence and the subjectAltName extension MUST be critical.
1042
1043 RFC 5280 4.2.1.10:
1044 Restrictions of the form directoryName MUST be applied to the subject
1045 field in the certificate (when the certificate includes a non-empty
1046 subject field) and to any names of type directoryName in the
1047 subjectAltName extension.
1048 */
1049 if(!cert.subject_dn().empty() && !is_permitted_dn(cert.subject_dn())) {
1050 return false;
1051 }
1052
1053 for(const auto& alt_dn : alt_name.directory_names()) {
1054 if(!is_permitted_dn(alt_dn)) {
1055 return false;
1056 }
1057 }
1058
1059 for(const auto& alt_dns : alt_name.dns_names()) {
1060 if(!is_permitted_dns_name(alt_dns)) {
1061 return false;
1062 }
1063 }
1064
1065 for(const auto& alt_ipv4 : alt_name.ipv4_addresses()) {
1066 if(!is_permitted_ipv4(alt_ipv4)) {
1067 return false;
1068 }
1069 }
1070
1071 for(const auto& alt_ipv6 : alt_name.ipv6_addresses()) {
1072 if(!is_permitted_ipv6(alt_ipv6)) {
1073 return false;
1074 }
1075 }
1076
1077 for(const auto& uri : alt_name.uri_names()) {
1078 if(!is_permitted_uri(uri)) {
1079 return false;
1080 }
1081 }
1082
1083 for(const auto& addr : alt_name.email_addresses()) {
1084 if(!is_permitted_email(addr)) {
1085 return false;
1086 }
1087 }
1088
1089 for(const auto& mailbox : alt_name.smtp_utf8_mailboxes()) {
1090 if(!is_permitted_smtp_utf8(mailbox)) {
1091 return false;
1092 }
1093 }
1094
1095 // TODO(Botan4): CN fallback is deprecated for removal in Botan4.
1096 if(alt_name.is_empty()) {
1097 for(const auto& cn : cert.subject_info("CN")) {
1098 if(auto ipv4 = IPv4Address::from_string(cn)) {
1099 if(!is_permitted_ipv4(*ipv4)) {
1100 return false;
1101 }
1102 } else if(cn.find('.') != std::string::npos) {
1103 if(auto dns_form = DNSName::from_san_string(cn)) {
1104 if(!is_permitted_dns_name(*dns_form)) {
1105 return false;
1106 }
1107 }
1108 }
1109 }
1110
1111 /*
1112 RFC 5280 4.2.1.10:
1113 When constraints are imposed on the rfc822Name name form, but the
1114 certificate does not include a subject alternative name, the
1115 rfc822Name constraint MUST be applied to the attribute of type
1116 emailAddress in the subject distinguished name.
1117 */
1118 for(const auto& email_str : cert.subject_dn().get_attribute("PKCS9.EmailAddress")) {
1119 if(auto addr = EmailAddress::from_string(email_str)) {
1120 if(!is_permitted_email(*addr)) {
1121 return false;
1122 }
1123 } else if(m_permitted_name_types.contains(GeneralName::NameType::RFC822)) {
1124 // emailAddress is present but unparsable and an rfc822Name
1125 // constraint is in effect; treat as not permitted.
1126 return false;
1127 }
1128 }
1129 }
1130
1131 // We didn't encounter a name that doesn't have a matching constraint
1132 return true;
1133}
1134
1135bool NameConstraints::is_excluded(const X509_Certificate& cert, bool reject_unknown) const {
1136 if(excluded().empty()) {
1137 return false;
1138 }
1139
1140 const auto& alt_name = cert.subject_alt_name();
1141
1142 if(exceeds_limit(cert.subject_dn().count(), alt_name.count(), excluded().size())) {
1143 return true;
1144 }
1145
1146 if(reject_unknown) {
1147 // This is one is overly broad: we should just reject if there is a name constraint
1148 // with the same OID as one of the other names
1149 if(m_excluded_name_types.contains(GeneralName::NameType::Other) && !alt_name.other_name_values().empty()) {
1150 return true;
1151 }
1152 // As in is_permitted: a critical NC restricting an unrecognized
1153 // GeneralName form cannot be evaluated; reject conservatively.
1154 if(m_excluded_name_types.contains(GeneralName::NameType::Unknown)) {
1155 return true;
1156 }
1157 }
1158
1159 auto is_excluded_dn = [&](const X509_DN& dn) {
1160 // If no restrictions, then immediate accept
1161 if(!m_excluded_name_types.contains(GeneralName::NameType::DN)) {
1162 return false;
1163 }
1164
1165 for(const auto& c : m_excluded_subtrees) {
1166 if(c.base().matches_dn(dn)) {
1167 return true;
1168 }
1169 }
1170
1171 // There is at least one excluded name and we didn't match
1172 return false;
1173 };
1174
1175 auto is_excluded_dns_name = [&](const DNSName& name) {
1176 // If no restrictions, then immediate accept
1177 if(!m_excluded_name_types.contains(GeneralName::NameType::DNS)) {
1178 return false;
1179 }
1180
1181 for(const auto& c : m_excluded_subtrees) {
1182 if(c.base().matches_dns(name)) {
1183 return true;
1184 }
1185
1186 /*
1187 RFC 5280 4.2.1.10:
1188 Any name matching a restriction in the excludedSubtrees
1189 field is invalid regardless of information appearing in
1190 the permittedSubtrees.
1191
1192 If the cert has a wildcard SAN (*.example.com), and that wildcard
1193 could be matched against an excluded name, it must be rejected.
1194 */
1195 if(c.base().m_type == GeneralName::NameType::DNS && name.is_wildcard()) {
1196 const auto& constraint = std::get<GeneralName::DNSConstraint>(c.base().m_name).value();
1197 if(wildcard_intersects_excluded_dns_subtree(name.to_string(), constraint)) {
1198 return true;
1199 }
1200 }
1201 }
1202
1203 // There is at least one excluded name and we didn't match
1204 return false;
1205 };
1206
1207 auto is_excluded_ipv4 = [&](const IPv4Address& ipv4) {
1208 if(m_excluded_name_types.contains(GeneralName::NameType::IPv4)) {
1209 for(const auto& c : m_excluded_subtrees) {
1210 if(c.base().matches_ipv4(ipv4)) {
1211 return true;
1212 }
1213 }
1214 }
1215
1216 // This name did not match any of the excluded names
1217 return false;
1218 };
1219
1220 auto is_excluded_ipv6 = [&](const IPv6Address& ipv6) {
1221 if(m_excluded_name_types.contains(GeneralName::NameType::IPv6)) {
1222 for(const auto& c : m_excluded_subtrees) {
1223 if(c.base().matches_ipv6(ipv6)) {
1224 return true;
1225 }
1226 }
1227 }
1228
1229 // An IPv4-mapped IPv6 address names an IPv4 address so verify that
1230 // address is not restricted by an IPv4 excludes rule
1231 if(m_excluded_name_types.contains(GeneralName::NameType::IPv4)) {
1232 if(auto embedded_v4 = ipv6.as_ipv4()) {
1233 for(const auto& c : m_excluded_subtrees) {
1234 if(c.base().matches_ipv4(*embedded_v4)) {
1235 return true;
1236 }
1237 }
1238 }
1239 }
1240
1241 // This name did not match any of the excluded names
1242 return false;
1243 };
1244
1245 auto is_excluded_uri = [&](const URI& uri) {
1246 if(!m_excluded_name_types.contains(GeneralName::NameType::URI)) {
1247 return false;
1248 }
1249 /*
1250 RFC 5280 4.2.1.10:
1251 If a constraint is applied to the uniformResourceIdentifier
1252 name form and a subsequent certificate includes a
1253 subjectAltName extension with a uniformResourceIdentifier that
1254 does not include an authority component with a host name
1255 specified as a fully qualified domain name (e.g., if the URI
1256 either does not include an authority component or includes an
1257 authority component in which the host name is specified as an
1258 IP address), then the application MUST reject the certificate.
1259 */
1260 const auto host = uri.host();
1261 if(!host.has_value() || !std::holds_alternative<DNSName>(host->get())) {
1262 return true;
1263 }
1264 if(std::get<DNSName>(host->get()).to_string().find('.') == std::string::npos) {
1265 return true;
1266 }
1267 for(const auto& c : m_excluded_subtrees) {
1268 if(c.base().matches_uri(uri)) {
1269 return true;
1270 }
1271 }
1272 return false;
1273 };
1274
1275 /*
1276 * The email matching logic on the exclude side is intentionally stricter
1277 * (more expansive) than the permit side logic.
1278 *
1279 * RFC 9549 updates RFC 5280 and among other things completely removes mailbox
1280 * form constraints (ones with a '@', rather than just a domain constraint)
1281 * claiming "This capability was not used".
1282 *
1283 * This prohibition is reiterated in RFC 9598 Section 6 with "rfc822Name
1284 * constraints with a Local-part SHOULD NOT be used."
1285 *
1286 * Here we lean very conservative in our interpretation: if there is a
1287 * mailbox-form exclude constraint, we reject any mailbox at that domain. That
1288 * is, if excludedSubtrees includes "user@example.com", we treat that
1289 * constraint identically to an exclusion of "example.com".
1290 *
1291 * This might be overly cautious, but generally a rejects-valid bug gets you a
1292 * prompt bug report with testcase, while an accepts-invalid eventually gets
1293 * you a surprise CVE.
1294 */
1295 auto mailbox_form_constraint_covers_domain = [](const GeneralName& gn, const DNSName& san_domain) {
1297 return false;
1298 }
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);
1302 };
1303
1304 auto is_excluded_email = [&](const EmailAddress& addr) {
1305 if(m_excluded_name_types.contains(GeneralName::NameType::RFC822)) {
1306 for(const auto& c : m_excluded_subtrees) {
1307 if(c.base().matches_email(addr)) {
1308 return true;
1309 }
1310 /*
1311 If we were strictly following RFC 9549 we would here want to call
1312 mailbox_form_constraint_covers_domain, but this breaks chains which
1313 are in conformance to the specifications prior to 9549.
1314 */
1315 }
1316 }
1317 return false;
1318 };
1319
1320 // RFC 9598 Section 6: rfc822Name name constraints also apply to
1321 // SmtpUTF8Mailbox SAN entries. See is_permitted_smtp_utf8.
1322 auto is_excluded_smtp_utf8 = [&](const SmtpUtf8Mailbox& mailbox) {
1323 if(m_excluded_name_types.contains(GeneralName::NameType::RFC822)) {
1324 for(const auto& c : m_excluded_subtrees) {
1325 if(c.base().matches_email(mailbox)) {
1326 return true;
1327 }
1328 if(mailbox_form_constraint_covers_domain(c.base(), mailbox.domain())) {
1329 return true;
1330 }
1331 }
1332 }
1333 return false;
1334 };
1335
1336 if(is_excluded_dn(cert.subject_dn())) {
1337 return true;
1338 }
1339
1340 for(const auto& alt_dn : alt_name.directory_names()) {
1341 if(is_excluded_dn(alt_dn)) {
1342 return true;
1343 }
1344 }
1345
1346 for(const auto& alt_dns : alt_name.dns_names()) {
1347 if(is_excluded_dns_name(alt_dns)) {
1348 return true;
1349 }
1350 }
1351
1352 for(const auto& alt_ipv4 : alt_name.ipv4_addresses()) {
1353 if(is_excluded_ipv4(alt_ipv4)) {
1354 return true;
1355 }
1356 }
1357
1358 for(const auto& alt_ipv6 : alt_name.ipv6_addresses()) {
1359 if(is_excluded_ipv6(alt_ipv6)) {
1360 return true;
1361 }
1362 }
1363
1364 for(const auto& uri : alt_name.uri_names()) {
1365 if(is_excluded_uri(uri)) {
1366 return true;
1367 }
1368 }
1369
1370 for(const auto& addr : alt_name.email_addresses()) {
1371 if(is_excluded_email(addr)) {
1372 return true;
1373 }
1374 }
1375
1376 for(const auto& mailbox : alt_name.smtp_utf8_mailboxes()) {
1377 if(is_excluded_smtp_utf8(mailbox)) {
1378 return true;
1379 }
1380 }
1381
1382 // TODO(Botan4): CN fallback is deprecated for removal in Botan4.
1383 if(alt_name.is_empty()) {
1384 for(const auto& cn : cert.subject_info("Name")) {
1385 if(auto ipv4 = IPv4Address::from_string(cn)) {
1386 if(is_excluded_ipv4(*ipv4)) {
1387 return true;
1388 }
1389 } else if(cn.find('.') != std::string::npos) {
1390 if(auto dns_form = DNSName::from_san_string(cn)) {
1391 if(is_excluded_dns_name(*dns_form)) {
1392 return true;
1393 }
1394 }
1395 }
1396 }
1397
1398 // RFC 5280 4.2.1.10 fallback to subject DN emailAddress when the cert has no SAN
1399 for(const auto& email_str : cert.subject_dn().get_attribute("PKCS9.EmailAddress")) {
1400 if(auto addr = EmailAddress::from_string(email_str)) {
1401 if(is_excluded_email(*addr)) {
1402 return true;
1403 }
1404 } else if(m_excluded_name_types.contains(GeneralName::NameType::RFC822)) {
1405 return true;
1406 }
1407 }
1408 }
1409
1410 // We didn't encounter a name that matched any prohibited name
1411 return false;
1412}
1413
1414} // namespace Botan
#define BOTAN_ASSERT_NOMSG(expr)
Definition assert.h:75
#define BOTAN_DEBUG_ASSERT(expr)
Definition assert.h:129
#define BOTAN_ASSERT_UNREACHABLE()
Definition assert.h:166
const std::string & value() const
Definition asn1_obj.h:590
const std::set< IPv6Address > & ipv6_addresses() const
Return the set of IPv6 addresses included in this alternative name.
Definition pkix_types.h:423
const std::set< DNSName > & dns_names() const
Return the set of DNS names included in this alternative name.
Definition pkix_types.h:409
const std::set< X509_DN > & directory_names() const
Return the set of directory names included in this alternative name.
Definition pkix_types.h:443
const std::set< EmailAddress > & email_addresses() const
Return the set of email addresses included in this alternative name.
Definition pkix_types.h:400
const std::set< IPv4Address > & ipv4_addresses() const
Return the set of IPv4 addresses included in this alternative name.
Definition pkix_types.h:420
const std::set< URI > & uri_names() const
Return the set of URIs included in this alternative name.
Definition pkix_types.h:391
bool is_empty() const
Return true if this alternative name is empty (zero names).
Definition alt_name.cpp:131
BER_Object get_next_object()
Definition ber_dec.cpp:516
BER_Decoder & decode(bool &out)
Definition ber_dec.h:358
Limits limits() const
Definition ber_dec.h:197
BER_Decoder & verify_end()
Definition ber_dec.cpp:471
BER_Decoder & end_cons()
Definition ber_dec.cpp:630
BER_Decoder start_sequence()
Definition ber_dec.h:275
BER_Decoder & decode_optional(T &out, ASN1_Type type_tag, ASN1_Class class_tag, const T &default_value=T())
Definition ber_dec.h:553
size_t length() const
Definition asn1_obj.h:303
const uint8_t * bits() const
Definition asn1_obj.h:298
bool is_a(ASN1_Type type_tag, ASN1_Class class_tag) const
Definition asn1_obj.cpp:97
DER_Encoder & add_object(ASN1_Type type_tag, ASN1_Class class_tag, const uint8_t rep[], size_t length)
Definition der_enc.cpp:285
DER_Encoder & start_sequence()
Definition der_enc.h:86
DER_Encoder & end_cons()
Definition der_enc.cpp:208
DER_Encoder & encode(bool b)
Definition der_enc.cpp:313
const std::string & to_string() const
Definition dns_name.h:51
static std::optional< DNSName > from_san_string(std::string_view name)
Definition dns_name.cpp:149
static std::optional< DNSName > from_string(std::string_view name)
Definition dns_name.cpp:136
static std::optional< EmailAddress > from_string(std::string_view addr)
Definition email.cpp:78
X.509 GeneralName Type.
Definition pkix_types.h:543
static GeneralName email(std::string_view email)
void decode_from(BER_Decoder &from) override
bool matches_ipv6(const IPv6Address &ip) const
GeneralName()=default
static GeneralName ipv4_address(uint32_t ipv4)
bool matches_uri(const URI &uri) const
void encode_into(DER_Encoder &to) const override
std::string type() const
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
std::string 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
Definition pkix_types.h:596
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.
Definition pkix_types.h:718
void encode_into(DER_Encoder &to) const override
const GeneralName & base() const
Definition pkix_types.h:737
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)
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
Definition pkix_types.h:768
const std::vector< GeneralSubtree > & excluded() const
Definition pkix_types.h:775
const DNSName & domain() const
The domain, as an LDH host name in A-label form (RFC 9598 Section 3).
Definition email.h:99
static std::optional< URI > from_string(std::string_view raw)
Definition uri.cpp:164
const X509_DN & subject_dn() const
Definition x509cert.cpp:460
std::vector< std::string > subject_info(std::string_view name) const
Definition x509cert.cpp:744
const AlternativeName & subject_alt_name() const
Definition x509cert.cpp:688
std::vector< std::string > get_attribute(std::string_view attr) const
Definition x509_dn.cpp:245
bool empty() const
Definition pkix_types.h:202
void decode_from(BER_Decoder &from) override
Definition x509_dn.cpp:408
size_t count() const
Definition pkix_types.h:209
std::vector< uint8_t > put_in_sequence(const std::vector< uint8_t > &contents)
Definition asn1_obj.cpp:208
std::string to_string(const BER_Object &obj)
Definition asn1_obj.cpp:224
bool wildcard_intersects_excluded_dns_subtree(std::string_view pattern, std::string_view constraint)
constexpr std::optional< T > checked_add(T a, T b)
Definition int_utils.h:19
std::string fmt(std::string_view format, const T &... args)
Definition fmt.h:53
ASN1_Type
Definition asn1_obj.h:47
std::ostream & operator<<(std::ostream &out, const OID &oid)
Definition asn1_oid.cpp:302
constexpr std::optional< T > checked_mul(T a, T b)
Definition int_utils.h:46
bool x509_dn_subtree_match(const X509_DN &name, const X509_DN &constraint)
Definition x509_dn.cpp:358
constexpr auto concat(Rs &&... ranges)
Definition concat_util.h:90