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