• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

randombit / botan / 24872040145

24 Apr 2026 01:10AM UTC coverage: 89.401% (-0.07%) from 89.474%
24872040145

push

github

web-flow
Merge pull request #5539 from randombit/jack/cert-cache

Refactor and optimize Windows certificate store

106671 of 119318 relevant lines covered (89.4%)

11479699.69 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

70.83
/src/lib/x509/name_constraint.cpp
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/x509cert.h>
13
#include <botan/internal/concat_util.h>
14
#include <botan/internal/fmt.h>
15
#include <botan/internal/int_utils.h>
16
#include <botan/internal/loadstor.h>
17
#include <botan/internal/parsing.h>
18
#include <botan/internal/stl_util.h>
19
#include <botan/internal/x509_utils.h>
20
#include <span>
21

22
namespace Botan {
23

24
class DER_Encoder;
25

26
namespace {
27

28
std::string canonicalize_dns_name(std::string_view name) {
263✔
29
   return tolower_string(name);
1✔
30
}
31

32
}  // namespace
33

34
std::string GeneralName::type() const {
×
35
   switch(m_type) {
×
36
      case NameType::Unknown:
×
37
         throw Encoding_Error("Could not convert unknown NameType to string");
×
38
      case NameType::RFC822:
×
39
         return "RFC822";
×
40
      case NameType::DNS:
×
41
         return "DNS";
×
42
      case NameType::URI:
×
43
         return "URI";
×
44
      case NameType::DN:
×
45
         return "DN";
×
46
      case NameType::IPv4:
×
47
         return "IP";
×
48
      case NameType::IPv6:
×
49
         return "IPv6";
×
50
      case NameType::Other:
×
51
         return "Other";
×
52
   }
53

54
   BOTAN_ASSERT_UNREACHABLE();
×
55
}
56

57
GeneralName GeneralName::email(std::string_view email) {
12✔
58
   return GeneralName::make<RFC822_IDX>(email);
12✔
59
}
60

61
GeneralName GeneralName::dns(std::string_view dns) {
36✔
62
   return GeneralName::make<DNS_IDX>(dns);
36✔
63
}
64

65
GeneralName GeneralName::uri(std::string_view uri) {
24✔
66
   return GeneralName::make<URI_IDX>(uri);
24✔
67
}
68

69
GeneralName GeneralName::directory_name(Botan::X509_DN dn) {
36✔
70
   return GeneralName::make<DN_IDX>(std::move(dn));
36✔
71
}
72

73
GeneralName GeneralName::ipv4_address(uint32_t ipv4) {
12✔
74
   return GeneralName::ipv4_address(IPv4Address(ipv4));
12✔
75
}
76

77
GeneralName GeneralName::ipv4_address(uint32_t ipv4, uint32_t mask) {
×
78
   auto subnet = IPv4Subnet::from_address_and_mask(ipv4, mask);
×
79
   if(!subnet.has_value()) {
×
80
      throw Invalid_Argument("IPv4 subnet mask is not a contiguous CIDR prefix");
×
81
   }
82
   return GeneralName::make<IPV4_IDX>(*subnet);
×
83
}
84

85
GeneralName GeneralName::ipv4_address(IPv4Address ipv4) {
12✔
86
   return GeneralName::make<IPV4_IDX>(IPv4Subnet::host(ipv4));
12✔
87
}
88

89
GeneralName GeneralName::ipv4_address(const IPv4Subnet& subnet) {
×
90
   return GeneralName::make<IPV4_IDX>(subnet);
×
91
}
92

93
GeneralName GeneralName::ipv6_address(const IPv6Address& ipv6) {
×
94
   return GeneralName::make<IPV6_IDX>(IPv6Subnet::host(ipv6));
×
95
}
96

97
GeneralName GeneralName::ipv6_address(const IPv6Subnet& subnet) {
×
98
   return GeneralName::make<IPV6_IDX>(subnet);
×
99
}
100

101
std::string GeneralName::name() const {
66✔
102
   const size_t index = m_name.index();
66✔
103

104
   if(index == RFC822_IDX) {
66✔
105
      return std::get<RFC822_IDX>(m_name);
36✔
106
   } else if(index == DNS_IDX) {
107
      return std::get<DNS_IDX>(m_name);
21✔
108
   } else if(index == URI_IDX) {
109
      return std::get<URI_IDX>(m_name);
4✔
110
   } else if(index == DN_IDX) {
111
      return std::get<DN_IDX>(m_name).to_string();
×
112
   } else if(index == IPV4_IDX) {
113
      const auto& subnet = std::get<IPV4_IDX>(m_name);
4✔
114
      return subnet.is_host() ? subnet.address().to_string() : subnet.to_string();
4✔
115
   } else if(index == IPV6_IDX) {
116
      const auto& subnet = std::get<IPV6_IDX>(m_name);
1✔
117
      return subnet.is_host() ? subnet.address().to_string() : subnet.to_string();
1✔
118
   } else {
119
      BOTAN_ASSERT_UNREACHABLE();
×
120
   }
121
}
122

123
std::vector<uint8_t> GeneralName::binary_name() const {
35✔
124
   return std::visit(Botan::overloaded{
70✔
125
                        [](const Botan::X509_DN& dn) { return Botan::ASN1::put_in_sequence(dn.get_bits()); },
32✔
126
                        [](const IPv4Subnet& subnet) { return subnet.serialize(); },
3✔
127
                        [](const IPv6Subnet& subnet) { return subnet.serialize(); },
×
128
                        [](const auto&) -> std::vector<uint8_t> {
×
129
                           throw Invalid_State("Cannot convert GeneralName to binary string");
×
130
                        },
131
                     },
132
                     m_name);
35✔
133
}
134

135
void GeneralName::encode_into(DER_Encoder& /*to*/) const {
×
136
   throw Not_Implemented("GeneralName encoding");
×
137
}
138

139
void GeneralName::decode_from(BER_Decoder& ber) {
958✔
140
   const BER_Object obj = ber.get_next_object();
958✔
141

142
   if(obj.is_a(0, ASN1_Class::ExplicitContextSpecific)) {
954✔
143
      m_type = NameType::Other;
11✔
144
   } else if(obj.is_a(1, ASN1_Class::ContextSpecific)) {
943✔
145
      m_type = NameType::RFC822;
230✔
146
      m_name.emplace<RFC822_IDX>(ASN1::to_string(obj));
460✔
147
   } else if(obj.is_a(2, ASN1_Class::ContextSpecific)) {
713✔
148
      // Store it in case insensitive form so we don't have to do it
149
      // again while matching
150
      auto dns = canonicalize_dns_name(ASN1::to_string(obj));
262✔
151
      // An empty DNS subtree has no clear meaning, reject immediately
152
      if(dns.empty()) {
262✔
153
         throw Decoding_Error("Empty DNS name in GeneralName");
1✔
154
      }
155
      m_type = NameType::DNS;
261✔
156
      m_name.emplace<DNS_IDX>(std::move(dns));
261✔
157
   } else if(obj.is_a(6, ASN1_Class::ContextSpecific)) {
713✔
158
      m_type = NameType::URI;
20✔
159
      m_name.emplace<URI_IDX>(ASN1::to_string(obj));
40✔
160
   } else if(obj.is_a(4, ASN1_Class::ContextSpecific | ASN1_Class::Constructed)) {
431✔
161
      X509_DN dn;
239✔
162
      BER_Decoder dec(obj, ber.limits());
239✔
163
      dn.decode_from(dec);
239✔
164
      m_type = NameType::DN;
229✔
165
      m_name.emplace<DN_IDX>(dn);
229✔
166
   } else if(obj.is_a(7, ASN1_Class::ContextSpecific)) {
670✔
167
      if(obj.length() == 8) {
93✔
168
         const auto addr_and_mask = std::span<const uint8_t, 8>{obj.bits(), 8};
48✔
169
         auto subnet = IPv4Subnet::from_address_and_mask(addr_and_mask);
48✔
170
         if(!subnet.has_value()) {
48✔
171
            throw Decoding_Error("IPv4 name constraint mask is not a contiguous CIDR prefix");
8✔
172
         }
173

174
         m_type = NameType::IPv4;
40✔
175
         m_name.emplace<IPV4_IDX>(*subnet);
80✔
176
      } else if(obj.length() == 32) {
45✔
177
         const auto addr_and_mask = std::span<const uint8_t, 32>{obj.bits(), 32};
40✔
178
         auto subnet = IPv6Subnet::from_address_and_mask(addr_and_mask);
40✔
179
         if(!subnet.has_value()) {
40✔
180
            throw Decoding_Error("IPv6 name constraint mask is not a contiguous CIDR prefix");
2✔
181
         }
182

183
         m_type = NameType::IPv6;
38✔
184
         m_name.emplace<IPV6_IDX>(*subnet);
76✔
185
      } else {
186
         throw Decoding_Error("Invalid IP name constraint size " + std::to_string(obj.length()));
15✔
187
      }
188
   } else {
189
      m_type = NameType::Unknown;
99✔
190
   }
191
}
954✔
192

193
bool GeneralName::matches_dns(const std::string& dns_name) const {
343✔
194
   if(m_type == NameType::DNS) {
343✔
195
      const auto& constraint = std::get<DNS_IDX>(m_name);
145✔
196
      return matches_dns(dns_name, constraint);
145✔
197
   }
198
   return false;
199
}
200

201
bool GeneralName::matches_ipv4(uint32_t ip) const {
30✔
202
   if(m_type == NameType::IPv4) {
30✔
203
      return std::get<IPV4_IDX>(m_name).contains(IPv4Address(ip));
24✔
204
   }
205
   return false;
206
}
207

208
bool GeneralName::matches_ipv6(const IPv6Address& ip) const {
34✔
209
   if(m_type == NameType::IPv6) {
34✔
210
      return std::get<IPV6_IDX>(m_name).contains(ip);
18✔
211
   }
212
   return false;
213
}
214

215
bool GeneralName::matches_dn(const X509_DN& dn) const {
64✔
216
   if(m_type == NameType::DN) {
64✔
217
      const X509_DN& constraint = std::get<DN_IDX>(m_name);
17✔
218
      return matches_dn(dn, constraint);
17✔
219
   }
220
   return false;
221
}
222

223
GeneralName::MatchResult GeneralName::matches(const X509_Certificate& cert) const {
×
224
   class MatchScore final {
×
225
      public:
226
         MatchScore() : m_any(false), m_some(false), m_all(true) {}
×
227

228
         void add(bool m) {
×
229
            m_any = true;
×
230
            m_some |= m;
×
231
            m_all &= m;
×
232
         }
233

234
         MatchResult result() const {
×
235
            if(!m_any) {
×
236
               return MatchResult::NotFound;
237
            } else if(m_all) {
×
238
               return MatchResult::All;
239
            } else if(m_some) {
×
240
               return MatchResult::Some;
241
            } else {
242
               return MatchResult::None;
×
243
            }
244
         }
245

246
      private:
247
         bool m_any;
248
         bool m_some;
249
         bool m_all;
250
   };
251

252
   const X509_DN& dn = cert.subject_dn();
×
253
   const AlternativeName& alt_name = cert.subject_alt_name();
×
254

255
   MatchScore score;
×
256

257
   if(m_type == NameType::DNS) {
×
258
      const auto& constraint = std::get<DNS_IDX>(m_name);
×
259

260
      const auto& alt_names = alt_name.dns();
×
261

262
      for(const std::string& dns : alt_names) {
×
263
         score.add(matches_dns(dns, constraint));
×
264
      }
265

266
      if(alt_name.count() == 0) {
×
267
         // Check CN instead...
268
         for(const std::string& cn : dn.get_attribute("CN")) {
×
269
            if(!string_to_ipv4(cn).has_value()) {
×
270
               score.add(matches_dns(canonicalize_dns_name(cn), constraint));
×
271
            }
272
         }
×
273
      }
274
   } else if(m_type == NameType::DN) {
×
275
      const X509_DN& constraint = std::get<DN_IDX>(m_name);
×
276
      score.add(matches_dn(dn, constraint));
×
277

278
      for(const auto& alt_dn : alt_name.directory_names()) {
×
279
         score.add(matches_dn(alt_dn, constraint));
×
280
      }
281
   } else if(m_type == NameType::IPv4) {
×
282
      const auto& subnet = std::get<IPV4_IDX>(m_name);
×
283

284
      if(alt_name.count() == 0) {
×
285
         // Check CN instead...
286
         for(const std::string& cn : dn.get_attribute("CN")) {
×
287
            if(auto ipv4 = string_to_ipv4(cn)) {
×
288
               score.add(subnet.contains(IPv4Address(*ipv4)));
×
289
            }
290
         }
×
291
      } else {
292
         for(const uint32_t ipv4 : alt_name.ipv4_address()) {
×
293
            score.add(subnet.contains(IPv4Address(ipv4)));
×
294
         }
295
      }
296
   } else if(m_type == NameType::IPv6) {
×
297
      for(const auto& ipv6 : alt_name.ipv6_address()) {
×
298
         score.add(matches_ipv6(ipv6));
×
299
      }
300
   } else {
301
      // URI and email name constraint matching not implemented
302
      return MatchResult::UnknownType;
303
   }
304

305
   return score.result();
×
306
}
307

308
//static
309
bool GeneralName::matches_dns(std::string_view name, std::string_view constraint) {
145✔
310
   // both constraint and name are assumed already tolower
311
   if(name.size() == constraint.size()) {
145✔
312
      return name == constraint;
64✔
313
   } else if(constraint.size() > name.size()) {
113✔
314
      // The constraint is longer than the issued name: not possibly a match
315
      return false;
316
   } else {
317
      BOTAN_ASSERT_NOMSG(name.size() > constraint.size());
97✔
318

319
      if(constraint.empty()) {
97✔
320
         return true;
321
      }
322

323
      const std::string_view substr = name.substr(name.size() - constraint.size(), constraint.size());
97✔
324

325
      if(constraint.front() == '.') {
97✔
326
         return substr == constraint;
72✔
327
      } else if(substr[0] == '.') {
61✔
328
         return substr.substr(1) == constraint;
24✔
329
      } else {
330
         return substr == constraint && name[name.size() - constraint.size() - 1] == '.';
98✔
331
      }
332
   }
333
}
334

335
//static
336
bool GeneralName::matches_dn(const X509_DN& name, const X509_DN& constraint) {
17✔
337
   // Perform DN matching by comparing RDNs in sequence, i.e.,
338
   // whether the constraint is a prefix of the name.
339
   const auto& name_info = name.dn_info();
17✔
340
   const auto& constraint_info = constraint.dn_info();
17✔
341

342
   if(constraint_info.size() > name_info.size()) {
17✔
343
      return false;
344
   }
345

346
   for(size_t i = 0; i < constraint_info.size(); ++i) {
41✔
347
      if(name_info[i].first != constraint_info[i].first ||
66✔
348
         !x500_name_cmp(name_info[i].second.value(), constraint_info[i].second.value())) {
32✔
349
         return false;
10✔
350
      }
351
   }
352

353
   return !constraint_info.empty();
7✔
354
}
355

356
std::ostream& operator<<(std::ostream& os, const GeneralName& gn) {
×
357
   os << gn.type() << ":" << gn.name();
×
358
   return os;
×
359
}
360

361
GeneralSubtree::GeneralSubtree() = default;
954✔
362

363
void GeneralSubtree::encode_into(DER_Encoder& /*to*/) const {
×
364
   throw Not_Implemented("GeneralSubtree encoding");
×
365
}
366

367
void GeneralSubtree::decode_from(BER_Decoder& ber) {
954✔
368
   /*
369
   * RFC 5280 Section 4.2.1.10:
370
   *    Within this profile, the minimum and maximum fields are not used with any
371
   *    name forms, thus, the minimum MUST be zero, and maximum MUST be absent.
372
   */
373
   size_t minimum = 0;
954✔
374

375
   ber.start_sequence()
1,893✔
376
      .decode(m_base)
939✔
377
      .decode_optional(minimum, ASN1_Type(0), ASN1_Class::ContextSpecific, size_t(0))
916✔
378
      .end_cons();
912✔
379

380
   if(minimum != 0) {
912✔
381
      throw Decoding_Error("GeneralSubtree minimum must be 0");
1✔
382
   }
383
}
911✔
384

385
std::ostream& operator<<(std::ostream& os, const GeneralSubtree& gs) {
×
386
   os << gs.base();
×
387
   return os;
×
388
}
389

390
NameConstraints::NameConstraints(std::vector<GeneralSubtree>&& permitted_subtrees,
282✔
391
                                 std::vector<GeneralSubtree>&& excluded_subtrees) :
282✔
392
      m_permitted_subtrees(std::move(permitted_subtrees)), m_excluded_subtrees(std::move(excluded_subtrees)) {
282✔
393
   for(const auto& c : m_permitted_subtrees) {
729✔
394
      m_permitted_name_types.insert(c.base().type_code());
447✔
395
   }
396
   for(const auto& c : m_excluded_subtrees) {
524✔
397
      m_excluded_name_types.insert(c.base().type_code());
242✔
398
   }
399
}
282✔
400

401
namespace {
402

403
bool exceeds_limit(size_t dn_count, size_t alt_count, size_t constraint_count) {
133✔
404
   /**
405
   * OpenSSL uses a similar limit, but applies it to the total number of
406
   * constraints, while we apply it to permitted and excluded independently.
407
   */
408
   constexpr size_t MAX_NC_CHECKS = (1 << 16);
133✔
409

410
   if(auto names = checked_add(dn_count, alt_count)) {
266✔
411
      if(auto product = checked_mul(*names, constraint_count)) {
133✔
412
         if(*product < MAX_NC_CHECKS) {
133✔
413
            return false;
166✔
414
         }
415
      }
416
   }
417
   return true;
418
}
419

420
}  // namespace
421

422
bool NameConstraints::is_permitted(const X509_Certificate& cert, bool reject_unknown) const {
125✔
423
   if(permitted().empty()) {
125✔
424
      return true;
425
   }
426

427
   const auto& alt_name = cert.subject_alt_name();
68✔
428

429
   if(exceeds_limit(cert.subject_dn().count(), alt_name.count(), permitted().size())) {
68✔
430
      return false;
431
   }
432

433
   if(reject_unknown) {
68✔
434
      if(m_permitted_name_types.contains(GeneralName::NameType::Other) && !alt_name.other_names().empty()) {
134✔
435
         return false;
436
      }
437
      if(m_permitted_name_types.contains(GeneralName::NameType::URI) && !alt_name.uris().empty()) {
201✔
438
         return false;
439
      }
440
      if(m_permitted_name_types.contains(GeneralName::NameType::RFC822) && !alt_name.email().empty()) {
201✔
441
         return false;
442
      }
443
   }
444

445
   auto is_permitted_dn = [&](const X509_DN& dn) {
133✔
446
      // If no restrictions, then immediate accept
447
      if(!m_permitted_name_types.contains(GeneralName::NameType::DN)) {
134✔
448
         return true;
449
      }
450

451
      for(const auto& c : m_permitted_subtrees) {
55✔
452
         if(c.base().matches_dn(dn)) {
54✔
453
            return true;
66✔
454
         }
455
      }
456

457
      // There is at least one permitted name and we didn't match
458
      return false;
459
   };
66✔
460

461
   auto is_permitted_dns_name = [&](const std::string& name) {
102✔
462
      if(name.empty() || name.starts_with(".")) {
72✔
463
         return false;
×
464
      }
465

466
      // If no restrictions, then immediate accept
467
      if(!m_permitted_name_types.contains(GeneralName::NameType::DNS)) {
72✔
468
         return true;
469
      }
470

471
      for(const auto& c : m_permitted_subtrees) {
302✔
472
         if(c.base().matches_dns(name)) {
296✔
473
            return true;
30✔
474
         }
475
      }
476

477
      // There is at least one permitted name and we didn't match
478
      return false;
479
   };
66✔
480

481
   /*
482
   RFC 5280 4.2.1.10: iPAddress is a single GeneralName element where
483
   IPv4 and IPv6 are distinguished only by the length.
484

485
   An iPAddress subtree of either version therefore restricts the iPAddress name
486
   form for both versions.
487
   */
488
   const bool ip_form_restricted = m_permitted_name_types.contains(GeneralName::NameType::IPv4) ||
132✔
489
                                   m_permitted_name_types.contains(GeneralName::NameType::IPv6);
37✔
490

491
   auto is_permitted_ipv4 = [&](uint32_t ipv4) {
86✔
492
      if(!ip_form_restricted) {
20✔
493
         return true;
494
      }
495

496
      for(const auto& c : m_permitted_subtrees) {
29✔
497
         if(c.base().matches_ipv4(ipv4)) {
23✔
498
            return true;
14✔
499
         }
500
      }
501

502
      // We might here check if there are any IPv6 permitted names which are
503
      // mapped IPv4 addresses, and if so check if any of those apply. It's not
504
      // clear if this is desirable, and RFC 5280 is completely silent on the issue.
505

506
      // There is at least one permitted iPAddress name and we didn't match
507
      return false;
508
   };
66✔
509

510
   auto is_permitted_ipv6 = [&](const IPv6Address& ipv6) {
83✔
511
      if(!ip_form_restricted) {
17✔
512
         return true;
513
      }
514

515
      for(const auto& c : m_permitted_subtrees) {
35✔
516
         if(c.base().matches_ipv6(ipv6)) {
28✔
517
            return true;
10✔
518
         }
519
      }
520

521
      // There is at least one permitted iPAddress name and we didn't match
522
      return false;
523
   };
66✔
524

525
   if(!is_permitted_dn(cert.subject_dn())) {
66✔
526
      return false;
527
   }
528

529
   for(const auto& alt_dn : alt_name.directory_names()) {
66✔
530
      if(!is_permitted_dn(alt_dn)) {
1✔
531
         return false;
22✔
532
      }
533
   }
534

535
   for(const auto& alt_dns : alt_name.dns()) {
95✔
536
      if(!is_permitted_dns_name(alt_dns)) {
36✔
537
         return false;
22✔
538
      }
539
   }
540

541
   for(const auto& alt_ipv4 : alt_name.ipv4_address()) {
73✔
542
      if(!is_permitted_ipv4(alt_ipv4)) {
20✔
543
         return false;
22✔
544
      }
545
   }
546

547
   for(const auto& alt_ipv6 : alt_name.ipv6_address()) {
63✔
548
      if(!is_permitted_ipv6(alt_ipv6)) {
17✔
549
         return false;
22✔
550
      }
551
   }
552

553
   if(alt_name.count() == 0) {
46✔
554
      for(const auto& cn : cert.subject_info("Name")) {
15✔
555
         if(cn.find(".") != std::string::npos) {
7✔
556
            if(auto ipv4 = string_to_ipv4(cn)) {
×
557
               if(!is_permitted_ipv4(ipv4.value())) {
×
558
                  return false;
×
559
               }
560
            } else {
561
               if(!is_permitted_dns_name(canonicalize_dns_name(cn))) {
×
562
                  return false;
563
               }
564
            }
565
         }
566
      }
8✔
567
   }
568

569
   // We didn't encounter a name that doesn't have a matching constraint
570
   return true;
571
}
572

573
bool NameConstraints::is_excluded(const X509_Certificate& cert, bool reject_unknown) const {
103✔
574
   if(excluded().empty()) {
103✔
575
      return false;
576
   }
577

578
   const auto& alt_name = cert.subject_alt_name();
65✔
579

580
   if(exceeds_limit(cert.subject_dn().count(), alt_name.count(), excluded().size())) {
65✔
581
      return true;
582
   }
583

584
   if(reject_unknown) {
65✔
585
      // This is one is overly broad: we should just reject if there is a name constraint
586
      // with the same OID as one of the other names
587
      if(m_excluded_name_types.contains(GeneralName::NameType::Other) && !alt_name.other_names().empty()) {
52✔
588
         return true;
589
      }
590
      if(m_excluded_name_types.contains(GeneralName::NameType::URI) && !alt_name.uris().empty()) {
78✔
591
         return true;
592
      }
593
      if(m_excluded_name_types.contains(GeneralName::NameType::RFC822) && !alt_name.email().empty()) {
78✔
594
         return true;
595
      }
596
   }
597

598
   auto is_excluded_dn = [&](const X509_DN& dn) {
130✔
599
      // If no restrictions, then immediate accept
600
      if(!m_excluded_name_types.contains(GeneralName::NameType::DN)) {
130✔
601
         return false;
602
      }
603

604
      for(const auto& c : m_excluded_subtrees) {
16✔
605
         if(c.base().matches_dn(dn)) {
10✔
606
            return true;
65✔
607
         }
608
      }
609

610
      // There is at least one excluded name and we didn't match
611
      return false;
612
   };
65✔
613

614
   auto is_excluded_dns_name = [&](const std::string& name) {
118✔
615
      if(name.empty() || name.starts_with(".")) {
106✔
616
         return true;
×
617
      }
618

619
      // If no restrictions, then immediate accept
620
      if(!m_excluded_name_types.contains(GeneralName::NameType::DNS)) {
106✔
621
         return false;
622
      }
623

624
      const bool name_has_wildcard = (name.find('*') != std::string::npos);
47✔
625

626
      for(const auto& c : m_excluded_subtrees) {
91✔
627
         if(c.base().matches_dns(name)) {
47✔
628
            return true;
629
         }
630

631
         /*
632
         RFC 5280 4.2.1.10 - "any name matching a restriction in the
633
         excludedSubtrees field is invalid".
634

635
         If the cert has a wildcard SAN (*.example.com), and that wildcard
636
         could be matched against an excluded name, it must be rejected.
637
         */
638
         if(name_has_wildcard && c.base().m_type == GeneralName::NameType::DNS) {
44✔
639
            const auto& constraint = std::get<GeneralName::DNS_IDX>(c.base().m_name);
×
640
            if(host_wildcard_match(name, constraint)) {
×
641
               return true;
642
            }
643
         }
644
      }
645

646
      // There is at least one excluded name and we didn't match
647
      return false;
648
   };
65✔
649

650
   auto is_excluded_ipv4 = [&](uint32_t ipv4) {
68✔
651
      if(m_excluded_name_types.contains(GeneralName::NameType::IPv4)) {
6✔
652
         for(const auto& c : m_excluded_subtrees) {
7✔
653
            if(c.base().matches_ipv4(ipv4)) {
5✔
654
               return true;
3✔
655
            }
656
         }
657
      }
658

659
      // This name did not match any of the excluded names
660
      return false;
661
   };
65✔
662

663
   auto is_excluded_ipv6 = [&](const IPv6Address& ipv6) {
71✔
664
      if(m_excluded_name_types.contains(GeneralName::NameType::IPv6)) {
12✔
665
         for(const auto& c : m_excluded_subtrees) {
8✔
666
            if(c.base().matches_ipv6(ipv6)) {
6✔
667
               return true;
3✔
668
            }
669
         }
670
      }
671

672
      // An IPv4-mapped IPv6 address names an IPv4 address so verify that
673
      // address is not restricted by an IPv4 excludes rule
674
      if(m_excluded_name_types.contains(GeneralName::NameType::IPv4)) {
8✔
675
         if(auto embedded_v4 = ipv6.as_ipv4()) {
3✔
676
            for(const auto& c : m_excluded_subtrees) {
3✔
677
               if(c.base().matches_ipv4(*embedded_v4)) {
2✔
678
                  return true;
1✔
679
               }
680
            }
681
         }
682
      }
683

684
      // This name did not match any of the excluded names
685
      return false;
686
   };
65✔
687

688
   if(is_excluded_dn(cert.subject_dn())) {
65✔
689
      return true;
690
   }
691

692
   for(const auto& alt_dn : alt_name.directory_names()) {
61✔
693
      if(is_excluded_dn(alt_dn)) {
×
694
         return true;
11✔
695
      }
696
   }
697

698
   for(const auto& alt_dns : alt_name.dns()) {
111✔
699
      if(is_excluded_dns_name(alt_dns)) {
52✔
700
         return true;
11✔
701
      }
702
   }
703

704
   for(const auto& alt_ipv4 : alt_name.ipv4_address()) {
61✔
705
      if(is_excluded_ipv4(alt_ipv4)) {
3✔
706
         return true;
11✔
707
      }
708
   }
709

710
   for(const auto& alt_ipv6 : alt_name.ipv6_address()) {
61✔
711
      if(is_excluded_ipv6(alt_ipv6)) {
6✔
712
         return true;
11✔
713
      }
714
   }
715

716
   if(alt_name.count() == 0) {
55✔
717
      for(const auto& cn : cert.subject_info("Name")) {
13✔
718
         if(cn.find(".") != std::string::npos) {
7✔
719
            if(auto ipv4 = string_to_ipv4(cn)) {
1✔
720
               if(is_excluded_ipv4(ipv4.value())) {
×
721
                  return true;
1✔
722
               }
723
            } else {
724
               if(is_excluded_dns_name(canonicalize_dns_name(cn))) {
1✔
725
                  return true;
726
               }
727
            }
728
         }
729
      }
7✔
730
   }
731

732
   // We didn't encounter a name that matched any prohibited name
733
   return false;
734
}
735

736
}  // namespace Botan
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc