Botan 3.11.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 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
21namespace Botan {
22
23class DER_Encoder;
24
25namespace {
26
27std::string canonicalize_dns_name(std::string_view name) {
28 return tolower_string(name);
29}
30
31} // namespace
32
33std::string GeneralName::type() const {
34 switch(m_type) {
36 throw Encoding_Error("Could not convert unknown NameType to string");
38 return "RFC822";
39 case NameType::DNS:
40 return "DNS";
41 case NameType::URI:
42 return "URI";
43 case NameType::DN:
44 return "DN";
45 case NameType::IPv4:
46 return "IP";
47 case NameType::Other:
48 return "Other";
49 }
50
52}
53
54/**
55 * Placeholder for "no subnet mask", i.e. "a specific IP address".
56 * The GeneralName must be able to store both individual addresses, when used in
57 * Subject/Issuer alternative names as well as IP subnet identifier as name
58 * constraints.
59 */
60constexpr static uint32_t blind_mask = 0xFFFFFFFF;
61
63 return GeneralName::make<RFC822_IDX>(email);
64}
65
66GeneralName GeneralName::dns(std::string_view dns) {
67 return GeneralName::make<DNS_IDX>(dns);
68}
69
70GeneralName GeneralName::uri(std::string_view uri) {
71 return GeneralName::make<URI_IDX>(uri);
72}
73
75 return GeneralName::make<DN_IDX>(std::move(dn));
76}
77
79 return GeneralName::ipv4_address(ipv4, blind_mask);
80}
81
82GeneralName GeneralName::ipv4_address(uint32_t ipv4, uint32_t mask) {
83 return GeneralName::make<IPV4_IDX>(std::pair{ipv4, mask});
84}
85
86std::string GeneralName::name() const {
87 const size_t index = m_name.index();
88
89 if(index == RFC822_IDX) {
90 return std::get<RFC822_IDX>(m_name);
91 } else if(index == DNS_IDX) {
92 return std::get<DNS_IDX>(m_name);
93 } else if(index == URI_IDX) {
94 return std::get<URI_IDX>(m_name);
95 } else if(index == DN_IDX) {
96 return std::get<DN_IDX>(m_name).to_string();
97 } else if(index == IPV4_IDX) {
98 auto [net, mask] = std::get<IPV4_IDX>(m_name);
99 if(mask == blind_mask) {
100 return ipv4_to_string(net);
101 } else {
102 return fmt("{}/{}", ipv4_to_string(net), ipv4_to_string(mask));
103 }
104 } else {
106 }
107}
108
109std::vector<uint8_t> GeneralName::binary_name() const {
110 return std::visit(Botan::overloaded{
111 [](const Botan::X509_DN& dn) { return Botan::ASN1::put_in_sequence(dn.get_bits()); },
112 [](const std::pair<uint32_t, uint32_t>& ip) {
113 if(ip.second == blind_mask) {
114 return store_be<std::vector<uint8_t>>(ip.first);
115 } else {
116 return concat<std::vector<uint8_t>>(store_be(ip.first), store_be(ip.second));
117 }
118 },
119 [](const auto&) -> std::vector<uint8_t> {
120 throw Invalid_State("Cannot convert GeneralName to binary string");
121 },
122 },
123 m_name);
124}
125
127 throw Not_Implemented("GeneralName encoding");
128}
129
131 const BER_Object obj = ber.get_next_object();
132
134 m_type = NameType::Other;
135 } else if(obj.is_a(1, ASN1_Class::ContextSpecific)) {
136 m_type = NameType::RFC822;
137 m_name.emplace<RFC822_IDX>(ASN1::to_string(obj));
138 } else if(obj.is_a(2, ASN1_Class::ContextSpecific)) {
139 m_type = NameType::DNS;
140 // Store it in case insensitive form so we don't have to do it
141 // again while matching
142 m_name.emplace<DNS_IDX>(canonicalize_dns_name(ASN1::to_string(obj)));
143 } else if(obj.is_a(6, ASN1_Class::ContextSpecific)) {
144 m_type = NameType::URI;
145 m_name.emplace<URI_IDX>(ASN1::to_string(obj));
147 X509_DN dn;
148 BER_Decoder dec(obj);
149 dn.decode_from(dec);
150 m_type = NameType::DN;
151 m_name.emplace<DN_IDX>(dn);
152 } else if(obj.is_a(7, ASN1_Class::ContextSpecific)) {
153 if(obj.length() == 8) {
154 const uint32_t net = load_be<uint32_t>(obj.bits(), 0);
155 const uint32_t mask = load_be<uint32_t>(obj.bits(), 1);
156
157 m_type = NameType::IPv4;
158 m_name.emplace<IPV4_IDX>(std::make_pair(net, mask));
159 } else if(obj.length() == 32) {
160 // IPv6 name constraints are not implemented
161 m_type = NameType::Unknown;
162 } else {
163 throw Decoding_Error("Invalid IP name constraint size " + std::to_string(obj.length()));
164 }
165 } else {
166 m_type = NameType::Unknown;
167 }
168}
169
170bool GeneralName::matches_dns(const std::string& dns_name) const {
171 if(m_type == NameType::DNS) {
172 const auto& constraint = std::get<DNS_IDX>(m_name);
173 return matches_dns(dns_name, constraint);
174 }
175 return false;
176}
177
178bool GeneralName::matches_ipv4(uint32_t ip) const {
179 if(m_type == NameType::IPv4) {
180 auto [net, mask] = std::get<IPV4_IDX>(m_name);
181 return (ip & mask) == net;
182 }
183 return false;
184}
185
186bool GeneralName::matches_dn(const X509_DN& dn) const {
187 if(m_type == NameType::DN) {
188 const X509_DN& constraint = std::get<DN_IDX>(m_name);
189 return matches_dn(dn, constraint);
190 }
191 return false;
192}
193
195 class MatchScore final {
196 public:
197 MatchScore() : m_any(false), m_some(false), m_all(true) {}
198
199 void add(bool m) {
200 m_any = true;
201 m_some |= m;
202 m_all &= m;
203 }
204
205 MatchResult result() const {
206 if(!m_any) {
208 } else if(m_all) {
209 return MatchResult::All;
210 } else if(m_some) {
211 return MatchResult::Some;
212 } else {
213 return MatchResult::None;
214 }
215 }
216
217 private:
218 bool m_any;
219 bool m_some;
220 bool m_all;
221 };
222
223 const X509_DN& dn = cert.subject_dn();
224 const AlternativeName& alt_name = cert.subject_alt_name();
225
226 MatchScore score;
227
228 if(m_type == NameType::DNS) {
229 const auto& constraint = std::get<DNS_IDX>(m_name);
230
231 const auto& alt_names = alt_name.dns();
232
233 for(const std::string& dns : alt_names) {
234 score.add(matches_dns(dns, constraint));
235 }
236
237 if(alt_name.count() == 0) {
238 // Check CN instead...
239 for(const std::string& cn : dn.get_attribute("CN")) {
240 if(!string_to_ipv4(cn).has_value()) {
241 score.add(matches_dns(canonicalize_dns_name(cn), constraint));
242 }
243 }
244 }
245 } else if(m_type == NameType::DN) {
246 const X509_DN& constraint = std::get<DN_IDX>(m_name);
247 score.add(matches_dn(dn, constraint));
248
249 for(const auto& alt_dn : alt_name.directory_names()) {
250 score.add(matches_dn(alt_dn, constraint));
251 }
252 } else if(m_type == NameType::IPv4) {
253 auto [net, mask] = std::get<IPV4_IDX>(m_name);
254
255 if(alt_name.count() == 0) {
256 // Check CN instead...
257 for(const std::string& cn : dn.get_attribute("CN")) {
258 if(auto ipv4 = string_to_ipv4(cn)) {
259 const bool match = (ipv4.value() & mask) == net;
260 score.add(match);
261 }
262 }
263 } else {
264 for(const uint32_t ipv4 : alt_name.ipv4_address()) {
265 const bool match = (ipv4 & mask) == net;
266 score.add(match);
267 }
268 }
269 } else {
270 // URI and email name constraint matching not implemented
272 }
273
274 return score.result();
275}
276
277//static
278bool GeneralName::matches_dns(std::string_view name, std::string_view constraint) {
279 // both constraint and name are assumed already tolower
280 if(name.size() == constraint.size()) {
281 return name == constraint;
282 } else if(constraint.size() > name.size()) {
283 // The constraint is longer than the issued name: not possibly a match
284 return false;
285 } else {
286 BOTAN_ASSERT_NOMSG(name.size() > constraint.size());
287
288 if(constraint.empty()) {
289 return true;
290 }
291
292 const std::string_view substr = name.substr(name.size() - constraint.size(), constraint.size());
293
294 if(constraint.front() == '.') {
295 return substr == constraint;
296 } else if(substr[0] == '.') {
297 return substr.substr(1) == constraint;
298 } else {
299 return substr == constraint && name[name.size() - constraint.size() - 1] == '.';
300 }
301 }
302}
303
304//static
305bool GeneralName::matches_dn(const X509_DN& name, const X509_DN& constraint) {
306 // Perform DN matching by comparing RDNs in sequence, i.e.,
307 // whether the constraint is a prefix of the name.
308 const auto& name_info = name.dn_info();
309 const auto& constraint_info = constraint.dn_info();
310
311 if(constraint_info.size() > name_info.size()) {
312 return false;
313 }
314
315 for(size_t i = 0; i < constraint_info.size(); ++i) {
316 if(name_info[i].first != constraint_info[i].first ||
317 !x500_name_cmp(name_info[i].second.value(), constraint_info[i].second.value())) {
318 return false;
319 }
320 }
321
322 return !constraint_info.empty();
323}
324
325std::ostream& operator<<(std::ostream& os, const GeneralName& gn) {
326 os << gn.type() << ":" << gn.name();
327 return os;
328}
329
331
333 throw Not_Implemented("GeneralSubtree encoding");
334}
335
337 size_t minimum = 0;
338
339 ber.start_sequence()
340 .decode(m_base)
342 .end_cons();
343
344 if(minimum != 0) {
345 throw Decoding_Error("GeneralSubtree minimum must be 0");
346 }
347}
348
349std::ostream& operator<<(std::ostream& os, const GeneralSubtree& gs) {
350 os << gs.base();
351 return os;
352}
353
354NameConstraints::NameConstraints(std::vector<GeneralSubtree>&& permitted_subtrees,
355 std::vector<GeneralSubtree>&& excluded_subtrees) :
356 m_permitted_subtrees(std::move(permitted_subtrees)), m_excluded_subtrees(std::move(excluded_subtrees)) {
357 for(const auto& c : m_permitted_subtrees) {
358 m_permitted_name_types.insert(c.base().type_code());
359 }
360 for(const auto& c : m_excluded_subtrees) {
361 m_excluded_name_types.insert(c.base().type_code());
362 }
363}
364
365namespace {
366
367bool exceeds_limit(size_t dn_count, size_t alt_count, size_t constraint_count) {
368 /**
369 * OpenSSL uses a similar limit, but applies it to the total number of
370 * constraints, while we apply it to permitted and excluded independently.
371 */
372 constexpr size_t MAX_NC_CHECKS = (1 << 20);
373
374 if(auto names = checked_add(dn_count, alt_count)) {
375 if(auto product = checked_mul(*names, constraint_count)) {
376 if(*product < MAX_NC_CHECKS) {
377 return false;
378 }
379 }
380 }
381 return true;
382}
383
384} // namespace
385
386bool NameConstraints::is_permitted(const X509_Certificate& cert, bool reject_unknown) const {
387 if(permitted().empty()) {
388 return true;
389 }
390
391 const auto& alt_name = cert.subject_alt_name();
392
393 if(exceeds_limit(cert.subject_dn().count(), alt_name.count(), permitted().size())) {
394 return false;
395 }
396
397 if(reject_unknown) {
398 if(m_permitted_name_types.contains(GeneralName::NameType::Other) && !alt_name.other_names().empty()) {
399 return false;
400 }
401 if(m_permitted_name_types.contains(GeneralName::NameType::URI) && !alt_name.uris().empty()) {
402 return false;
403 }
404 if(m_permitted_name_types.contains(GeneralName::NameType::RFC822) && !alt_name.email().empty()) {
405 return false;
406 }
407 }
408
409 auto is_permitted_dn = [&](const X509_DN& dn) {
410 // If no restrictions, then immediate accept
411 if(!m_permitted_name_types.contains(GeneralName::NameType::DN)) {
412 return true;
413 }
414
415 for(const auto& c : m_permitted_subtrees) {
416 if(c.base().matches_dn(dn)) {
417 return true;
418 }
419 }
420
421 // There is at least one permitted name and we didn't match
422 return false;
423 };
424
425 auto is_permitted_dns_name = [&](const std::string& name) {
426 if(name.empty() || name.starts_with(".")) {
427 return false;
428 }
429
430 // If no restrictions, then immediate accept
431 if(!m_permitted_name_types.contains(GeneralName::NameType::DNS)) {
432 return true;
433 }
434
435 for(const auto& c : m_permitted_subtrees) {
436 if(c.base().matches_dns(name)) {
437 return true;
438 }
439 }
440
441 // There is at least one permitted name and we didn't match
442 return false;
443 };
444
445 auto is_permitted_ipv4 = [&](uint32_t ipv4) {
446 // If no restrictions, then immediate accept
447 if(!m_permitted_name_types.contains(GeneralName::NameType::IPv4)) {
448 return true;
449 }
450
451 for(const auto& c : m_permitted_subtrees) {
452 if(c.base().matches_ipv4(ipv4)) {
453 return true;
454 }
455 }
456
457 // There is at least one permitted name and we didn't match
458 return false;
459 };
460
461 if(!is_permitted_dn(cert.subject_dn())) {
462 return false;
463 }
464
465 for(const auto& alt_dn : alt_name.directory_names()) {
466 if(!is_permitted_dn(alt_dn)) {
467 return false;
468 }
469 }
470
471 for(const auto& alt_dns : alt_name.dns()) {
472 if(!is_permitted_dns_name(alt_dns)) {
473 return false;
474 }
475 }
476
477 for(const auto& alt_ipv4 : alt_name.ipv4_address()) {
478 if(!is_permitted_ipv4(alt_ipv4)) {
479 return false;
480 }
481 }
482
483 if(alt_name.count() == 0) {
484 for(const auto& cn : cert.subject_info("Name")) {
485 if(cn.find(".") != std::string::npos) {
486 if(auto ipv4 = string_to_ipv4(cn)) {
487 if(!is_permitted_ipv4(ipv4.value())) {
488 return false;
489 }
490 } else {
491 if(!is_permitted_dns_name(canonicalize_dns_name(cn))) {
492 return false;
493 }
494 }
495 }
496 }
497 }
498
499 // We didn't encounter a name that doesn't have a matching constraint
500 return true;
501}
502
503bool NameConstraints::is_excluded(const X509_Certificate& cert, bool reject_unknown) const {
504 if(excluded().empty()) {
505 return false;
506 }
507
508 const auto& alt_name = cert.subject_alt_name();
509
510 if(exceeds_limit(cert.subject_dn().count(), alt_name.count(), excluded().size())) {
511 return true;
512 }
513
514 if(reject_unknown) {
515 // This is one is overly broad: we should just reject if there is a name constraint
516 // with the same OID as one of the other names
517 if(m_excluded_name_types.contains(GeneralName::NameType::Other) && !alt_name.other_names().empty()) {
518 return true;
519 }
520 if(m_excluded_name_types.contains(GeneralName::NameType::URI) && !alt_name.uris().empty()) {
521 return true;
522 }
523 if(m_excluded_name_types.contains(GeneralName::NameType::RFC822) && !alt_name.email().empty()) {
524 return true;
525 }
526 }
527
528 auto is_excluded_dn = [&](const X509_DN& dn) {
529 // If no restrictions, then immediate accept
530 if(!m_excluded_name_types.contains(GeneralName::NameType::DN)) {
531 return false;
532 }
533
534 for(const auto& c : m_excluded_subtrees) {
535 if(c.base().matches_dn(dn)) {
536 return true;
537 }
538 }
539
540 // There is at least one excluded name and we didn't match
541 return false;
542 };
543
544 auto is_excluded_dns_name = [&](const std::string& name) {
545 if(name.empty() || name.starts_with(".")) {
546 return true;
547 }
548
549 // If no restrictions, then immediate accept
550 if(!m_excluded_name_types.contains(GeneralName::NameType::DNS)) {
551 return false;
552 }
553
554 for(const auto& c : m_excluded_subtrees) {
555 if(c.base().matches_dns(name)) {
556 return true;
557 }
558 }
559
560 // There is at least one excluded name and we didn't match
561 return false;
562 };
563
564 auto is_excluded_ipv4 = [&](uint32_t ipv4) {
565 // If no restrictions, then immediate accept
566 if(!m_excluded_name_types.contains(GeneralName::NameType::IPv4)) {
567 return false;
568 }
569
570 for(const auto& c : m_excluded_subtrees) {
571 if(c.base().matches_ipv4(ipv4)) {
572 return true;
573 }
574 }
575
576 // There is at least one excluded name and we didn't match
577 return false;
578 };
579
580 if(is_excluded_dn(cert.subject_dn())) {
581 return true;
582 }
583
584 for(const auto& alt_dn : alt_name.directory_names()) {
585 if(is_excluded_dn(alt_dn)) {
586 return true;
587 }
588 }
589
590 for(const auto& alt_dns : alt_name.dns()) {
591 if(is_excluded_dns_name(alt_dns)) {
592 return true;
593 }
594 }
595
596 for(const auto& alt_ipv4 : alt_name.ipv4_address()) {
597 if(is_excluded_ipv4(alt_ipv4)) {
598 return true;
599 }
600 }
601
602 if(alt_name.count() == 0) {
603 for(const auto& cn : cert.subject_info("Name")) {
604 if(cn.find(".") != std::string::npos) {
605 if(auto ipv4 = string_to_ipv4(cn)) {
606 if(is_excluded_ipv4(ipv4.value())) {
607 return true;
608 }
609 } else {
610 if(is_excluded_dns_name(canonicalize_dns_name(cn))) {
611 return true;
612 }
613 }
614 }
615 }
616 }
617
618 // We didn't encounter a name that matched any prohibited name
619 return false;
620}
621
622} // namespace Botan
#define BOTAN_ASSERT_NOMSG(expr)
Definition assert.h:75
#define BOTAN_ASSERT_UNREACHABLE()
Definition assert.h:163
const std::set< X509_DN > & directory_names() const
Return the set of directory names included in this alternative name.
Definition pkix_types.h:187
size_t count() const
Definition alt_name.cpp:47
const std::set< uint32_t > & ipv4_address() const
Return the set of IPv4 addresses included in this alternative name.
Definition pkix_types.h:178
const std::set< std::string > & dns() const
Return the set of DNS names included in this alternative name.
Definition pkix_types.h:175
BER_Object get_next_object()
Definition ber_dec.cpp:261
BER_Decoder & decode(bool &out)
Definition ber_dec.h:188
BER_Decoder & end_cons()
Definition ber_dec.cpp:337
BER_Decoder start_sequence()
Definition ber_dec.h:128
BER_Decoder & decode_optional(T &out, ASN1_Type type_tag, ASN1_Class class_tag, const T &default_value=T())
Definition ber_dec.h:253
size_t length() const
Definition asn1_obj.h:152
const uint8_t * bits() const
Definition asn1_obj.h:150
bool is_a(ASN1_Type type_tag, ASN1_Class class_tag) const
Definition asn1_obj.cpp:66
X.509 GeneralName Type.
Definition pkix_types.h:274
static GeneralName email(std::string_view email)
void decode_from(BER_Decoder &from) override
GeneralName()=default
static GeneralName ipv4_address(uint32_t ipv4)
void encode_into(DER_Encoder &to) const override
std::string type() const
static GeneralName uri(std::string_view uri)
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_dns(const std::string &dns_name) const
bool matches_ipv4(uint32_t ip) const
static GeneralName dns(std::string_view dns)
static GeneralName directory_name(Botan::X509_DN dn)
A single Name Constraint.
Definition pkix_types.h:381
void encode_into(DER_Encoder &to) const override
const GeneralName & base() const
Definition pkix_types.h:395
void decode_from(BER_Decoder &from) override
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:426
const std::vector< GeneralSubtree > & excluded() const
Definition pkix_types.h:433
const X509_DN & subject_dn() const
Definition x509cert.cpp:414
std::vector< std::string > subject_info(std::string_view name) const
Definition x509cert.cpp:608
const AlternativeName & subject_alt_name() const
Definition x509cert.cpp:571
std::vector< std::string > get_attribute(std::string_view attr) const
Definition x509_dn.cpp:173
void decode_from(BER_Decoder &from) override
Definition x509_dn.cpp:348
size_t count() const
Definition pkix_types.h:88
std::vector< uint8_t > put_in_sequence(const std::vector< uint8_t > &contents)
Definition asn1_obj.cpp:177
std::string to_string(const BER_Object &obj)
Definition asn1_obj.cpp:190
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
std::string tolower_string(std::string_view str)
Definition parsing.cpp:241
ASN1_Type
Definition asn1_obj.h:43
bool x500_name_cmp(std::string_view name1, std::string_view name2)
Definition x509_dn.cpp:32
std::ostream & operator<<(std::ostream &out, const OID &oid)
Definition asn1_oid.cpp:300
constexpr std::optional< T > checked_mul(T a, T b)
Definition int_utils.h:46
constexpr auto concat(Rs &&... ranges)
Definition concat_util.h:90
std::optional< uint32_t > string_to_ipv4(std::string_view str)
Definition parsing.cpp:156
std::string ipv4_to_string(uint32_t ip)
Definition parsing.cpp:225
constexpr auto store_be(ParamTs &&... params)
Definition loadstor.h:745
constexpr auto load_be(ParamTs &&... params)
Definition loadstor.h:504