Botan 3.10.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
18namespace Botan {
19
20class DER_Encoder;
21
22std::string GeneralName::type() const {
23 switch(m_type) {
25 throw Encoding_Error("Could not convert unknown NameType to string");
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
41}
42
43std::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 {
59 }
60}
61
63 throw Not_Implemented("GeneralName encoding");
64}
65
67 BER_Object obj = ber.get_next_object();
68
70 m_type = NameType::Other;
71 } else if(obj.is_a(1, ASN1_Class::ContextSpecific)) {
72 m_type = NameType::RFC822;
73 m_name.emplace<RFC822_IDX>(ASN1::to_string(obj));
74 } else if(obj.is_a(2, ASN1_Class::ContextSpecific)) {
75 m_type = NameType::DNS;
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)));
79 } else if(obj.is_a(6, ASN1_Class::ContextSpecific)) {
80 m_type = NameType::URI;
81 m_name.emplace<URI_IDX>(ASN1::to_string(obj));
83 X509_DN dn;
84 BER_Decoder dec(obj);
85 dn.decode_from(dec);
86 m_type = NameType::DN;
87 m_name.emplace<DN_IDX>(dn);
88 } else if(obj.is_a(7, ASN1_Class::ContextSpecific)) {
89 if(obj.length() == 8) {
90 const uint32_t net = load_be<uint32_t>(obj.bits(), 0);
91 const uint32_t mask = load_be<uint32_t>(obj.bits(), 1);
92
93 m_type = NameType::IPv4;
94 m_name.emplace<IPV4_IDX>(std::make_pair(net, mask));
95 } else if(obj.length() == 32) {
96 // IPv6 name constraints are not implemented
97 m_type = NameType::Unknown;
98 } else {
99 throw Decoding_Error("Invalid IP name constraint size " + std::to_string(obj.length()));
100 }
101 } else {
102 m_type = NameType::Unknown;
103 }
104}
105
106bool GeneralName::matches_dns(const std::string& dns_name) const {
107 if(m_type == NameType::DNS) {
108 const auto& constraint = std::get<DNS_IDX>(m_name);
109 return matches_dns(dns_name, constraint);
110 }
111 return false;
112}
113
114bool GeneralName::matches_ipv4(uint32_t ip) const {
115 if(m_type == NameType::IPv4) {
116 auto [net, mask] = std::get<IPV4_IDX>(m_name);
117 return (ip & mask) == net;
118 }
119 return false;
120}
121
122bool GeneralName::matches_dn(const X509_DN& dn) const {
123 if(m_type == NameType::DN) {
124 const X509_DN& constraint = std::get<DN_IDX>(m_name);
125 return matches_dn(dn, constraint);
126 }
127 return false;
128}
129
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) {
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
208 }
209
210 return score.result();
211}
212
213//static
214bool GeneralName::matches_dns(std::string_view name, std::string_view constraint) {
215 // both constraint and name are assumed already tolower
216 if(name.size() == constraint.size()) {
217 return name == constraint;
218 } else if(constraint.size() > name.size()) {
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());
223
224 if(constraint.empty()) {
225 return true;
226 }
227
228 std::string_view substr = name.substr(name.size() - constraint.size(), constraint.size());
229
230 if(constraint.front() == '.') {
231 return substr == constraint;
232 } else if(substr[0] == '.') {
233 return substr.substr(1) == constraint;
234 } else {
235 return substr == constraint && name[name.size() - constraint.size() - 1] == '.';
236 }
237 }
238}
239
240//static
241bool GeneralName::matches_dn(const X509_DN& name, const X509_DN& constraint) {
242 const auto attr = name.get_attributes();
243 bool ret = true;
244 size_t tries = 0;
245
246 for(const auto& c : constraint.dn_info()) {
247 auto i = attr.equal_range(c.first);
248
249 if(i.first != i.second) {
250 tries += 1;
251 ret = ret && (i.first->second == c.second.value());
252 }
253 }
254
255 return tries > 0 && ret;
256}
257
258std::ostream& operator<<(std::ostream& os, const GeneralName& gn) {
259 os << gn.type() << ":" << gn.name();
260 return os;
261}
262
264
266 throw Not_Implemented("GeneralSubtree encoding");
267}
268
270 size_t minimum = 0;
271
272 ber.start_sequence()
273 .decode(m_base)
275 .end_cons();
276
277 if(minimum != 0) {
278 throw Decoding_Error("GeneralSubtree minimum must be 0");
279 }
280}
281
282std::ostream& operator<<(std::ostream& os, const GeneralSubtree& gs) {
283 os << gs.base();
284 return os;
285}
286
287NameConstraints::NameConstraints(std::vector<GeneralSubtree>&& permitted_subtrees,
288 std::vector<GeneralSubtree>&& excluded_subtrees) :
289 m_permitted_subtrees(std::move(permitted_subtrees)), m_excluded_subtrees(std::move(excluded_subtrees)) {
290 for(const auto& c : m_permitted_subtrees) {
291 m_permitted_name_types.insert(c.base().type_code());
292 }
293 for(const auto& c : m_excluded_subtrees) {
294 m_excluded_name_types.insert(c.base().type_code());
295 }
296}
297
298namespace {
299
300bool exceeds_limit(size_t dn_count, size_t alt_count, size_t constraint_count) {
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);
306
307 if(auto names = checked_add(dn_count, alt_count)) {
308 if(auto product = checked_mul(*names, constraint_count)) {
309 if(*product < MAX_NC_CHECKS) {
310 return false;
311 }
312 }
313 }
314 return true;
315}
316
317} // namespace
318
319bool NameConstraints::is_permitted(const X509_Certificate& cert, bool reject_unknown) const {
320 if(permitted().empty()) {
321 return true;
322 }
323
324 const auto& alt_name = cert.subject_alt_name();
325
326 if(exceeds_limit(cert.subject_dn().count(), alt_name.count(), permitted().size())) {
327 return false;
328 }
329
330 if(reject_unknown) {
331 if(m_permitted_name_types.contains(GeneralName::NameType::Other) && !alt_name.other_names().empty()) {
332 return false;
333 }
334 if(m_permitted_name_types.contains(GeneralName::NameType::URI) && !alt_name.uris().empty()) {
335 return false;
336 }
337 if(m_permitted_name_types.contains(GeneralName::NameType::RFC822) && !alt_name.email().empty()) {
338 return false;
339 }
340 }
341
342 auto is_permitted_dn = [&](const X509_DN& dn) {
343 // If no restrictions, then immediate accept
344 if(!m_permitted_name_types.contains(GeneralName::NameType::DN)) {
345 return true;
346 }
347
348 for(const auto& c : m_permitted_subtrees) {
349 if(c.base().matches_dn(dn)) {
350 return true;
351 }
352 }
353
354 // There is at least one permitted name and we didn't match
355 return false;
356 };
357
358 auto is_permitted_dns_name = [&](const std::string& name) {
359 if(name.empty() || name.starts_with(".")) {
360 return false;
361 }
362
363 // If no restrictions, then immediate accept
364 if(!m_permitted_name_types.contains(GeneralName::NameType::DNS)) {
365 return true;
366 }
367
368 for(const auto& c : m_permitted_subtrees) {
369 if(c.base().matches_dns(name)) {
370 return true;
371 }
372 }
373
374 // There is at least one permitted name and we didn't match
375 return false;
376 };
377
378 auto is_permitted_ipv4 = [&](uint32_t ipv4) {
379 // If no restrictions, then immediate accept
380 if(!m_permitted_name_types.contains(GeneralName::NameType::IPv4)) {
381 return true;
382 }
383
384 for(const auto& c : m_permitted_subtrees) {
385 if(c.base().matches_ipv4(ipv4)) {
386 return true;
387 }
388 }
389
390 // There is at least one permitted name and we didn't match
391 return false;
392 };
393
394 if(!is_permitted_dn(cert.subject_dn())) {
395 return false;
396 }
397
398 for(const auto& alt_dn : alt_name.directory_names()) {
399 if(!is_permitted_dn(alt_dn)) {
400 return false;
401 }
402 }
403
404 for(const auto& alt_dns : alt_name.dns()) {
405 if(!is_permitted_dns_name(alt_dns)) {
406 return false;
407 }
408 }
409
410 for(const auto& alt_ipv4 : alt_name.ipv4_address()) {
411 if(!is_permitted_ipv4(alt_ipv4)) {
412 return false;
413 }
414 }
415
416 if(alt_name.count() == 0) {
417 for(const auto& cn : cert.subject_info("Name")) {
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 }
430 }
431
432 // We didn't encounter a name that doesn't have a matching constraint
433 return true;
434}
435
436bool NameConstraints::is_excluded(const X509_Certificate& cert, bool reject_unknown) const {
437 if(excluded().empty()) {
438 return false;
439 }
440
441 const auto& alt_name = cert.subject_alt_name();
442
443 if(exceeds_limit(cert.subject_dn().count(), alt_name.count(), excluded().size())) {
444 return true;
445 }
446
447 if(reject_unknown) {
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()) {
451 return true;
452 }
453 if(m_excluded_name_types.contains(GeneralName::NameType::URI) && !alt_name.uris().empty()) {
454 return true;
455 }
456 if(m_excluded_name_types.contains(GeneralName::NameType::RFC822) && !alt_name.email().empty()) {
457 return true;
458 }
459 }
460
461 auto is_excluded_dn = [&](const X509_DN& dn) {
462 // If no restrictions, then immediate accept
463 if(!m_excluded_name_types.contains(GeneralName::NameType::DN)) {
464 return false;
465 }
466
467 for(const auto& c : m_excluded_subtrees) {
468 if(c.base().matches_dn(dn)) {
469 return true;
470 }
471 }
472
473 // There is at least one excluded name and we didn't match
474 return false;
475 };
476
477 auto is_excluded_dns_name = [&](const std::string& name) {
478 if(name.empty() || name.starts_with(".")) {
479 return true;
480 }
481
482 // If no restrictions, then immediate accept
483 if(!m_excluded_name_types.contains(GeneralName::NameType::DNS)) {
484 return false;
485 }
486
487 for(const auto& c : m_excluded_subtrees) {
488 if(c.base().matches_dns(name)) {
489 return true;
490 }
491 }
492
493 // There is at least one excluded name and we didn't match
494 return false;
495 };
496
497 auto is_excluded_ipv4 = [&](uint32_t ipv4) {
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 };
512
513 if(is_excluded_dn(cert.subject_dn())) {
514 return true;
515 }
516
517 for(const auto& alt_dn : alt_name.directory_names()) {
518 if(is_excluded_dn(alt_dn)) {
519 return true;
520 }
521 }
522
523 for(const auto& alt_dns : alt_name.dns()) {
524 if(is_excluded_dns_name(alt_dns)) {
525 return true;
526 }
527 }
528
529 for(const auto& alt_ipv4 : alt_name.ipv4_address()) {
530 if(is_excluded_ipv4(alt_ipv4)) {
531 return true;
532 }
533 }
534
535 if(alt_name.count() == 0) {
536 for(const auto& cn : cert.subject_info("Name")) {
537 if(cn.find(".") != std::string::npos) {
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 }
549 }
550
551 // We didn't encounter a name that matched any prohibited name
552 return false;
553}
554
555} // 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