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