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

randombit / botan / 19012754211

02 Nov 2025 01:10PM UTC coverage: 90.677% (+0.006%) from 90.671%
19012754211

push

github

web-flow
Merge pull request #5137 from randombit/jack/clang-tidy-includes

Remove various unused includes flagged by clang-tidy misc-include-cleaner

100457 of 110786 relevant lines covered (90.68%)

12189873.8 hits per line

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

62.84
/src/lib/x509/name_constraint.cpp
1
/*
2
* X.509 Name Constraint
3
* (C) 2015 Kai Michaelis
4
*     2024 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/fmt.h>
14
#include <botan/internal/int_utils.h>
15
#include <botan/internal/loadstor.h>
16
#include <botan/internal/parsing.h>
17

18
namespace Botan {
19

20
class DER_Encoder;
21

22
std::string GeneralName::type() const {
×
23
   switch(m_type) {
×
24
      case NameType::Unknown:
×
25
         throw Encoding_Error("Could not convert unknown NameType to string");
×
26
      case NameType::RFC822:
×
27
         return "RFC822";
×
28
      case NameType::DNS:
×
29
         return "DNS";
×
30
      case NameType::URI:
×
31
         return "URI";
×
32
      case NameType::DN:
×
33
         return "DN";
×
34
      case NameType::IPv4:
×
35
         return "IP";
×
36
      case NameType::Other:
×
37
         return "Other";
×
38
   }
39

40
   BOTAN_ASSERT_UNREACHABLE();
×
41
}
42

43
std::string GeneralName::name() const {
×
44
   const size_t index = m_name.index();
×
45

46
   if(index == RFC822_IDX) {
×
47
      return std::get<RFC822_IDX>(m_name);
×
48
   } else if(index == DNS_IDX) {
49
      return std::get<DNS_IDX>(m_name);
×
50
   } else if(index == URI_IDX) {
51
      return std::get<URI_IDX>(m_name);
×
52
   } else if(index == DN_IDX) {
53
      return std::get<DN_IDX>(m_name).to_string();
×
54
   } else if(index == IPV4_IDX) {
55
      auto [net, mask] = std::get<IPV4_IDX>(m_name);
×
56
      return fmt("{}/{}", ipv4_to_string(net), ipv4_to_string(mask));
×
57
   } else {
58
      BOTAN_ASSERT_UNREACHABLE();
×
59
   }
60
}
61

62
void GeneralName::encode_into(DER_Encoder& /*to*/) const {
×
63
   throw Not_Implemented("GeneralName encoding");
×
64
}
65

66
void GeneralName::decode_from(BER_Decoder& ber) {
1,056✔
67
   BER_Object obj = ber.get_next_object();
1,056✔
68

69
   if(obj.is_a(0, ASN1_Class::ExplicitContextSpecific)) {
1,053✔
70
      m_type = NameType::Other;
11✔
71
   } else if(obj.is_a(1, ASN1_Class::ContextSpecific)) {
1,042✔
72
      m_type = NameType::RFC822;
228✔
73
      m_name.emplace<RFC822_IDX>(ASN1::to_string(obj));
456✔
74
   } else if(obj.is_a(2, ASN1_Class::ContextSpecific)) {
814✔
75
      m_type = NameType::DNS;
255✔
76
      // Store it in case insensitive form so we don't have to do it
77
      // again while matching
78
      m_name.emplace<DNS_IDX>(tolower_string(ASN1::to_string(obj)));
510✔
79
   } else if(obj.is_a(6, ASN1_Class::ContextSpecific)) {
559✔
80
      m_type = NameType::URI;
104✔
81
      m_name.emplace<URI_IDX>(ASN1::to_string(obj));
221✔
82
   } else if(obj.is_a(4, ASN1_Class::ContextSpecific | ASN1_Class::Constructed)) {
455✔
83
      X509_DN dn;
251✔
84
      BER_Decoder dec(obj);
251✔
85
      dn.decode_from(dec);
251✔
86
      m_type = NameType::DN;
243✔
87
      m_name.emplace<DN_IDX>(dn);
243✔
88
   } else if(obj.is_a(7, ASN1_Class::ContextSpecific)) {
706✔
89
      if(obj.length() == 8) {
37✔
90
         const uint32_t net = load_be<uint32_t>(obj.bits(), 0);
28✔
91
         const uint32_t mask = load_be<uint32_t>(obj.bits(), 1);
28✔
92

93
         m_type = NameType::IPv4;
28✔
94
         m_name.emplace<IPV4_IDX>(std::make_pair(net, mask));
56✔
95
      } else if(obj.length() == 32) {
9✔
96
         // IPv6 name constraints are not implemented
97
         m_type = NameType::Unknown;
4✔
98
      } else {
99
         throw Decoding_Error("Invalid IP name constraint size " + std::to_string(obj.length()));
15✔
100
      }
101
   } else {
102
      m_type = NameType::Unknown;
167✔
103
   }
104
}
1,040✔
105

106
bool GeneralName::matches_dns(const std::string& dns_name) const {
301✔
107
   if(m_type == NameType::DNS) {
301✔
108
      const auto& constraint = std::get<DNS_IDX>(m_name);
103✔
109
      return matches_dns(dns_name, constraint);
103✔
110
   }
111
   return false;
112
}
113

114
bool GeneralName::matches_ipv4(uint32_t ip) const {
4✔
115
   if(m_type == NameType::IPv4) {
4✔
116
      auto [net, mask] = std::get<IPV4_IDX>(m_name);
4✔
117
      return (ip & mask) == net;
4✔
118
   }
119
   return false;
120
}
121

122
bool GeneralName::matches_dn(const X509_DN& dn) const {
58✔
123
   if(m_type == NameType::DN) {
58✔
124
      const X509_DN& constraint = std::get<DN_IDX>(m_name);
11✔
125
      return matches_dn(dn, constraint);
11✔
126
   }
127
   return false;
128
}
129

130
GeneralName::MatchResult GeneralName::matches(const X509_Certificate& cert) const {
×
131
   class MatchScore final {
×
132
      public:
133
         MatchScore() : m_any(false), m_some(false), m_all(true) {}
×
134

135
         void add(bool m) {
×
136
            m_any = true;
×
137
            m_some |= m;
×
138
            m_all &= m;
×
139
         }
×
140

141
         MatchResult result() const {
×
142
            if(!m_any) {
×
143
               return MatchResult::NotFound;
144
            } else if(m_all) {
×
145
               return MatchResult::All;
146
            } else if(m_some) {
×
147
               return MatchResult::Some;
148
            } else {
149
               return MatchResult::None;
×
150
            }
151
         }
152

153
      private:
154
         bool m_any;
155
         bool m_some;
156
         bool m_all;
157
   };
158

159
   const X509_DN& dn = cert.subject_dn();
×
160
   const AlternativeName& alt_name = cert.subject_alt_name();
×
161

162
   MatchScore score;
×
163

164
   if(m_type == NameType::DNS) {
×
165
      const auto& constraint = std::get<DNS_IDX>(m_name);
×
166

167
      const auto& alt_names = alt_name.dns();
×
168

169
      for(const std::string& dns : alt_names) {
×
170
         score.add(matches_dns(dns, constraint));
×
171
      }
172

173
      if(alt_name.count() == 0) {
×
174
         // Check CN instead...
175
         for(const std::string& cn : dn.get_attribute("CN")) {
×
176
            if(!string_to_ipv4(cn).has_value()) {
×
177
               score.add(matches_dns(cn, constraint));
×
178
            }
179
         }
×
180
      }
181
   } else if(m_type == NameType::DN) {
×
182
      const X509_DN& constraint = std::get<DN_IDX>(m_name);
×
183
      score.add(matches_dn(dn, constraint));
×
184

185
      for(const auto& alt_dn : alt_name.directory_names()) {
×
186
         score.add(matches_dn(alt_dn, constraint));
×
187
      }
188
   } else if(m_type == NameType::IPv4) {
×
189
      auto [net, mask] = std::get<IPV4_IDX>(m_name);
×
190

191
      if(alt_name.count() == 0) {
×
192
         // Check CN instead...
193
         for(const std::string& cn : dn.get_attribute("CN")) {
×
194
            if(auto ipv4 = string_to_ipv4(cn)) {
×
195
               bool match = (ipv4.value() & mask) == net;
×
196
               score.add(match);
×
197
            }
198
         }
×
199
      } else {
200
         for(uint32_t ipv4 : alt_name.ipv4_address()) {
×
201
            bool match = (ipv4 & mask) == net;
×
202
            score.add(match);
×
203
         }
204
      }
205
   } else {
206
      // URI and email name constraint matching not implemented
207
      return MatchResult::UnknownType;
208
   }
209

210
   return score.result();
×
211
}
212

213
//static
214
bool GeneralName::matches_dns(std::string_view name, std::string_view constraint) {
103✔
215
   // both constraint and name are assumed already tolower
216
   if(name.size() == constraint.size()) {
103✔
217
      return name == constraint;
32✔
218
   } else if(constraint.size() > name.size()) {
87✔
219
      // The constraint is longer than the issued name: not possibly a match
220
      return false;
221
   } else {
222
      BOTAN_ASSERT_NOMSG(name.size() > constraint.size());
78✔
223

224
      if(constraint.empty()) {
78✔
225
         return true;
226
      }
227

228
      std::string_view substr = name.substr(name.size() - constraint.size(), constraint.size());
78✔
229

230
      if(constraint.front() == '.') {
78✔
231
         return substr == constraint;
36✔
232
      } else if(substr[0] == '.') {
60✔
233
         return substr.substr(1) == constraint;
24✔
234
      } else {
235
         return substr == constraint && name[name.size() - constraint.size() - 1] == '.';
96✔
236
      }
237
   }
238
}
239

240
//static
241
bool GeneralName::matches_dn(const X509_DN& name, const X509_DN& constraint) {
11✔
242
   const auto attr = name.get_attributes();
11✔
243
   bool ret = true;
11✔
244
   size_t tries = 0;
11✔
245

246
   for(const auto& c : constraint.dn_info()) {
37✔
247
      auto i = attr.equal_range(c.first);
26✔
248

249
      if(i.first != i.second) {
26✔
250
         tries += 1;
26✔
251
         ret = ret && (i.first->second == c.second.value());
26✔
252
      }
253
   }
254

255
   return tries > 0 && ret;
11✔
256
}
11✔
257

258
std::ostream& operator<<(std::ostream& os, const GeneralName& gn) {
×
259
   os << gn.type() << ":" << gn.name();
×
260
   return os;
×
261
}
262

263
GeneralSubtree::GeneralSubtree() = default;
1,073✔
264

265
void GeneralSubtree::encode_into(DER_Encoder& /*to*/) const {
×
266
   throw Not_Implemented("GeneralSubtree encoding");
×
267
}
268

269
void GeneralSubtree::decode_from(BER_Decoder& ber) {
1,073✔
270
   size_t minimum = 0;
1,073✔
271

272
   ber.start_sequence()
2,129✔
273
      .decode(m_base)
1,056✔
274
      .decode_optional(minimum, ASN1_Type(0), ASN1_Class::ContextSpecific, size_t(0))
1,040✔
275
      .end_cons();
1,030✔
276

277
   if(minimum != 0) {
1,027✔
278
      throw Decoding_Error("GeneralSubtree minimum must be 0");
2✔
279
   }
280
}
1,025✔
281

282
std::ostream& operator<<(std::ostream& os, const GeneralSubtree& gs) {
×
283
   os << gs.base();
×
284
   return os;
×
285
}
286

287
NameConstraints::NameConstraints(std::vector<GeneralSubtree>&& permitted_subtrees,
343✔
288
                                 std::vector<GeneralSubtree>&& excluded_subtrees) :
343✔
289
      m_permitted_subtrees(std::move(permitted_subtrees)), m_excluded_subtrees(std::move(excluded_subtrees)) {
343✔
290
   for(const auto& c : m_permitted_subtrees) {
820✔
291
      m_permitted_name_types.insert(c.base().type_code());
477✔
292
   }
293
   for(const auto& c : m_excluded_subtrees) {
634✔
294
      m_excluded_name_types.insert(c.base().type_code());
291✔
295
   }
296
}
343✔
297

298
namespace {
299

300
bool exceeds_limit(size_t dn_count, size_t alt_count, size_t constraint_count) {
53✔
301
   /**
302
   * OpenSSL uses a similar limit, but applies it to the total number of
303
   * constraints, while we apply it to permitted and excluded independently.
304
   */
305
   constexpr size_t MAX_NC_CHECKS = (1 << 20);
53✔
306

307
   if(auto names = checked_add(dn_count, alt_count)) {
106✔
308
      if(auto product = checked_mul(*names, constraint_count)) {
53✔
309
         if(*product < MAX_NC_CHECKS) {
53✔
310
            return false;
64✔
311
         }
312
      }
313
   }
314
   return true;
315
}
316

317
}  // namespace
318

319
bool NameConstraints::is_permitted(const X509_Certificate& cert, bool reject_unknown) const {
50✔
320
   if(permitted().empty()) {
50✔
321
      return true;
322
   }
323

324
   const auto& alt_name = cert.subject_alt_name();
24✔
325

326
   if(exceeds_limit(cert.subject_dn().count(), alt_name.count(), permitted().size())) {
24✔
327
      return false;
328
   }
329

330
   if(reject_unknown) {
24✔
331
      if(m_permitted_name_types.contains(GeneralName::NameType::Other) && !alt_name.other_names().empty()) {
46✔
332
         return false;
333
      }
334
      if(m_permitted_name_types.contains(GeneralName::NameType::URI) && !alt_name.uris().empty()) {
69✔
335
         return false;
336
      }
337
      if(m_permitted_name_types.contains(GeneralName::NameType::RFC822) && !alt_name.email().empty()) {
69✔
338
         return false;
339
      }
340
   }
341

342
   auto is_permitted_dn = [&](const X509_DN& dn) {
45✔
343
      // If no restrictions, then immediate accept
344
      if(!m_permitted_name_types.contains(GeneralName::NameType::DN)) {
46✔
345
         return true;
346
      }
347

348
      for(const auto& c : m_permitted_subtrees) {
55✔
349
         if(c.base().matches_dn(dn)) {
54✔
350
            return true;
22✔
351
         }
352
      }
353

354
      // There is at least one permitted name and we didn't match
355
      return false;
356
   };
22✔
357

358
   auto is_permitted_dns_name = [&](const std::string& name) {
43✔
359
      if(name.empty() || name.starts_with(".")) {
42✔
360
         return false;
×
361
      }
362

363
      // If no restrictions, then immediate accept
364
      if(!m_permitted_name_types.contains(GeneralName::NameType::DNS)) {
42✔
365
         return true;
366
      }
367

368
      for(const auto& c : m_permitted_subtrees) {
280✔
369
         if(c.base().matches_dns(name)) {
277✔
370
            return true;
18✔
371
         }
372
      }
373

374
      // There is at least one permitted name and we didn't match
375
      return false;
376
   };
22✔
377

378
   auto is_permitted_ipv4 = [&](uint32_t ipv4) {
26✔
379
      // If no restrictions, then immediate accept
380
      if(!m_permitted_name_types.contains(GeneralName::NameType::IPv4)) {
8✔
381
         return true;
382
      }
383

384
      for(const auto& c : m_permitted_subtrees) {
6✔
385
         if(c.base().matches_ipv4(ipv4)) {
4✔
386
            return true;
2✔
387
         }
388
      }
389

390
      // There is at least one permitted name and we didn't match
391
      return false;
392
   };
22✔
393

394
   if(!is_permitted_dn(cert.subject_dn())) {
22✔
395
      return false;
396
   }
397

398
   for(const auto& alt_dn : alt_name.directory_names()) {
22✔
399
      if(!is_permitted_dn(alt_dn)) {
1✔
400
         return false;
8✔
401
      }
402
   }
403

404
   for(const auto& alt_dns : alt_name.dns()) {
39✔
405
      if(!is_permitted_dns_name(alt_dns)) {
21✔
406
         return false;
8✔
407
      }
408
   }
409

410
   for(const auto& alt_ipv4 : alt_name.ipv4_address()) {
20✔
411
      if(!is_permitted_ipv4(alt_ipv4)) {
4✔
412
         return false;
8✔
413
      }
414
   }
415

416
   if(alt_name.count() == 0) {
16✔
417
      for(const auto& cn : cert.subject_info("Name")) {
1✔
418
         if(cn.find(".") != std::string::npos) {
×
419
            if(auto ipv4 = string_to_ipv4(cn)) {
×
420
               if(!is_permitted_ipv4(ipv4.value())) {
×
421
                  return false;
×
422
               }
423
            } else {
424
               if(!is_permitted_dns_name(cn)) {
×
425
                  return false;
426
               }
427
            }
428
         }
429
      }
1✔
430
   }
431

432
   // We didn't encounter a name that doesn't have a matching constraint
433
   return true;
434
}
435

436
bool NameConstraints::is_excluded(const X509_Certificate& cert, bool reject_unknown) const {
42✔
437
   if(excluded().empty()) {
42✔
438
      return false;
439
   }
440

441
   const auto& alt_name = cert.subject_alt_name();
29✔
442

443
   if(exceeds_limit(cert.subject_dn().count(), alt_name.count(), excluded().size())) {
29✔
444
      return true;
445
   }
446

447
   if(reject_unknown) {
29✔
448
      // This is one is overly broad: we should just reject if there is a name constraint
449
      // with the same OID as one of the other names
450
      if(m_excluded_name_types.contains(GeneralName::NameType::Other) && !alt_name.other_names().empty()) {
24✔
451
         return true;
452
      }
453
      if(m_excluded_name_types.contains(GeneralName::NameType::URI) && !alt_name.uris().empty()) {
36✔
454
         return true;
455
      }
456
      if(m_excluded_name_types.contains(GeneralName::NameType::RFC822) && !alt_name.email().empty()) {
36✔
457
         return true;
458
      }
459
   }
460

461
   auto is_excluded_dn = [&](const X509_DN& dn) {
58✔
462
      // If no restrictions, then immediate accept
463
      if(!m_excluded_name_types.contains(GeneralName::NameType::DN)) {
58✔
464
         return false;
465
      }
466

467
      for(const auto& c : m_excluded_subtrees) {
6✔
468
         if(c.base().matches_dn(dn)) {
4✔
469
            return true;
29✔
470
         }
471
      }
472

473
      // There is at least one excluded name and we didn't match
474
      return false;
475
   };
29✔
476

477
   auto is_excluded_dns_name = [&](const std::string& name) {
59✔
478
      if(name.empty() || name.starts_with(".")) {
60✔
479
         return true;
×
480
      }
481

482
      // If no restrictions, then immediate accept
483
      if(!m_excluded_name_types.contains(GeneralName::NameType::DNS)) {
60✔
484
         return false;
485
      }
486

487
      for(const auto& c : m_excluded_subtrees) {
47✔
488
         if(c.base().matches_dns(name)) {
24✔
489
            return true;
1✔
490
         }
491
      }
492

493
      // There is at least one excluded name and we didn't match
494
      return false;
495
   };
29✔
496

497
   auto is_excluded_ipv4 = [&](uint32_t ipv4) {
29✔
498
      // If no restrictions, then immediate accept
499
      if(!m_excluded_name_types.contains(GeneralName::NameType::IPv4)) {
×
500
         return false;
501
      }
502

503
      for(const auto& c : m_excluded_subtrees) {
×
504
         if(c.base().matches_ipv4(ipv4)) {
×
505
            return true;
×
506
         }
507
      }
508

509
      // There is at least one excluded name and we didn't match
510
      return false;
511
   };
29✔
512

513
   if(is_excluded_dn(cert.subject_dn())) {
29✔
514
      return true;
515
   }
516

517
   for(const auto& alt_dn : alt_name.directory_names()) {
27✔
518
      if(is_excluded_dn(alt_dn)) {
×
519
         return true;
3✔
520
      }
521
   }
522

523
   for(const auto& alt_dns : alt_name.dns()) {
56✔
524
      if(is_excluded_dns_name(alt_dns)) {
30✔
525
         return true;
3✔
526
      }
527
   }
528

529
   for(const auto& alt_ipv4 : alt_name.ipv4_address()) {
26✔
530
      if(is_excluded_ipv4(alt_ipv4)) {
×
531
         return true;
3✔
532
      }
533
   }
534

535
   if(alt_name.count() == 0) {
26✔
536
      for(const auto& cn : cert.subject_info("Name")) {
4✔
537
         if(cn.find(".") != std::string::npos) {
2✔
538
            if(auto ipv4 = string_to_ipv4(cn)) {
×
539
               if(is_excluded_ipv4(ipv4.value())) {
×
540
                  return true;
×
541
               }
542
            } else {
543
               if(is_excluded_dns_name(cn)) {
×
544
                  return true;
545
               }
546
            }
547
         }
548
      }
2✔
549
   }
550

551
   // We didn't encounter a name that matched any prohibited name
552
   return false;
553
}
554

555
}  // 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