Botan 2.19.5
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*
5* Botan is released under the Simplified BSD License (see license.txt)
6*/
7
8#include <botan/pkix_types.h>
9#include <botan/ber_dec.h>
10#include <botan/loadstor.h>
11#include <botan/x509cert.h>
12#include <botan/parsing.h>
13#include <sstream>
14
15namespace Botan {
16
17class DER_Encoder;
18
19GeneralName::GeneralName(const std::string& str) : GeneralName()
20 {
21 size_t p = str.find(':');
22
23 if(p != std::string::npos)
24 {
25 m_type = str.substr(0, p);
26 m_name = str.substr(p + 1, std::string::npos);
27 }
28 else
29 {
30 throw Invalid_Argument("Failed to decode Name Constraint");
31 }
32 }
33
35 {
36 throw Not_Implemented("GeneralName encoding");
37 }
38
40 {
41 BER_Object obj = ber.get_next_object();
42
43 if(obj.is_a(1, CONTEXT_SPECIFIC))
44 {
45 m_type = "RFC822";
46 m_name = ASN1::to_string(obj);
47 }
48 else if(obj.is_a(2, CONTEXT_SPECIFIC))
49 {
50 m_type = "DNS";
51 m_name = ASN1::to_string(obj);
52 }
53 else if(obj.is_a(6, CONTEXT_SPECIFIC))
54 {
55 m_type = "URI";
56 m_name = ASN1::to_string(obj);
57 }
58 else if(obj.is_a(4, ASN1_Tag(CONTEXT_SPECIFIC | CONSTRUCTED)))
59 {
60 m_type = "DN";
61 X509_DN dn;
62 BER_Decoder dec(obj);
63 std::stringstream ss;
64
65 dn.decode_from(dec);
66 ss << dn;
67
68 m_name = ss.str();
69 }
70 else if(obj.is_a(7, CONTEXT_SPECIFIC))
71 {
72 if(obj.length() == 8)
73 {
74 m_type = "IP";
75 m_name = ipv4_to_string(load_be<uint32_t>(obj.bits(), 0)) + "/" +
77 }
78 else if(obj.length() == 32)
79 {
80 throw Decoding_Error("Unsupported IPv6 name constraint");
81 }
82 else
83 {
84 throw Decoding_Error("Invalid IP name constraint size " + std::to_string(obj.length()));
85 }
86 }
87 else
88 {
89 throw Decoding_Error("Found unknown GeneralName type");
90 }
91 }
92
94 {
95 std::vector<std::string> nam;
96 std::function<bool(const GeneralName*, const std::string&)> match_fn;
97
98 const X509_DN& dn = cert.subject_dn();
99 const AlternativeName& alt_name = cert.subject_alt_name();
100
101 if(type() == "DNS")
102 {
103 match_fn = std::mem_fn(&GeneralName::matches_dns);
104
105 nam = alt_name.get_attribute("DNS");
106
107 if(nam.empty())
108 {
109 nam = dn.get_attribute("CN");
110 }
111 }
112 else if(type() == "DN")
113 {
114 match_fn = std::mem_fn(&GeneralName::matches_dn);
115
116 nam.push_back(dn.to_string());
117
118 const auto alt_dn = alt_name.dn();
119 if(alt_dn.empty() == false)
120 {
121 nam.push_back(alt_dn.to_string());
122 }
123 }
124 else if(type() == "IP")
125 {
126 match_fn = std::mem_fn(&GeneralName::matches_ip);
127 nam = alt_name.get_attribute("IP");
128 }
129 else
130 {
132 }
133
134 if(nam.empty())
135 {
137 }
138
139 bool some = false;
140 bool all = true;
141
142 for(const std::string& n: nam)
143 {
144 bool m = match_fn(this, n);
145
146 some |= m;
147 all &= m;
148 }
149
150 if(all)
151 {
152 return MatchResult::All;
153 }
154 else if(some)
155 {
156 return MatchResult::Some;
157 }
158 else
159 {
160 return MatchResult::None;
161 }
162 }
163
164bool GeneralName::matches_dns(const std::string& nam) const
165 {
166 const std::string constraint = tolower_string(name());
167 const std::string issued = tolower_string(nam);
168
169 if(nam.size() == constraint.size())
170 {
171 return issued == constraint;
172 }
173 else if(constraint.size() > nam.size())
174 {
175 // The constraint is longer than the issued name: not possibly a match
176 return false;
177 }
178 else
179 {
180 if(constraint.empty()) {
181 return true;
182 }
183
184 std::string substr = issued.substr(nam.size() - constraint.size(), constraint.size());
185
186 if(constraint.front() == '.') {
187 return substr == constraint;
188 } else if(substr[0] == '.') {
189 return substr.substr(1) == constraint;
190 } else {
191 return substr == constraint && issued[issued.size() - constraint.size() - 1] == '.';
192 }
193 }
194}
195
196bool GeneralName::matches_dn(const std::string& nam) const
197 {
198 std::stringstream ss(nam);
199 X509_DN nam_dn;
200 ss >> nam_dn;
201 return matches_dn_obj(nam_dn);
202 }
203
204bool GeneralName::matches_dn_obj(const X509_DN& nam_dn) const
205 {
206 std::stringstream tt(name());
207 X509_DN my_dn;
208 tt >> my_dn;
209
210 auto attr = nam_dn.get_attributes();
211 bool ret = true;
212 size_t trys = 0;
213
214 for(const auto& c: my_dn.dn_info())
215 {
216 auto i = attr.equal_range(c.first);
217
218 if(i.first != i.second)
219 {
220 trys += 1;
221 ret = ret && (i.first->second == c.second.value());
222 }
223 }
224
225 return trys > 0 && ret;
226 }
227
228bool GeneralName::matches_ip(const std::string& nam) const
229 {
230 uint32_t ip = string_to_ipv4(nam);
231 std::vector<std::string> p = split_on(name(), '/');
232
233 if(p.size() != 2)
234 throw Decoding_Error("failed to parse IPv4 address");
235
236 uint32_t net = string_to_ipv4(p.at(0));
237 uint32_t mask = string_to_ipv4(p.at(1));
238
239 return (ip & mask) == net;
240 }
241
242std::ostream& operator<<(std::ostream& os, const GeneralName& gn)
243 {
244 os << gn.type() << ":" << gn.name();
245 return os;
246 }
247
249 {
250 size_t p0, p1;
251 const auto min = std::stoull(str, &p0, 10);
252 const auto max = std::stoull(str.substr(p0 + 1), &p1, 10);
253 GeneralName gn(str.substr(p0 + p1 + 2));
254
255 if(p0 > 0 && p1 > 0)
256 {
257 m_minimum = static_cast<size_t>(min);
258 m_maximum = static_cast<size_t>(max);
259 m_base = gn;
260 }
261 else
262 {
263 throw Invalid_Argument("Failed to decode Name Constraint");
264 }
265 }
266
268 {
269 throw Not_Implemented("General Subtree encoding");
270 }
271
273 {
275 .decode(m_base)
276 .decode_optional(m_minimum,ASN1_Tag(0), CONTEXT_SPECIFIC,size_t(0))
277 .end_cons();
278
279 if(m_minimum != 0)
280 throw Decoding_Error("GeneralSubtree minimum must be 0");
281
282 m_maximum = std::numeric_limits<std::size_t>::max();
283 }
284
285std::ostream& operator<<(std::ostream& os, const GeneralSubtree& gs)
286 {
287 os << gs.minimum() << "," << gs.maximum() << "," << gs.base();
288 return os;
289 }
290
291NameConstraints::NameConstraints(std::vector<GeneralSubtree>&& permitted_subtrees,
292 std::vector<GeneralSubtree>&& excluded_subtrees) :
293 m_permitted_subtrees(permitted_subtrees), m_excluded_subtrees(excluded_subtrees)
294 {
295 for(const auto& c : m_permitted_subtrees)
296 {
297 m_permitted_name_types.insert(c.base().type());
298 }
299 for(const auto& c : m_excluded_subtrees)
300 {
301 m_excluded_name_types.insert(c.base().type());
302 }
303 }
304
305namespace {
306
307bool looks_like_ipv4(const std::string& s)
308 {
309 try
310 {
311 // ignores return value
313 return true;
314 }
315 catch(...)
316 {
317 return false;
318 }
319 }
320
321}
322
323bool NameConstraints::is_permitted(const X509_Certificate& cert, bool reject_unknown) const {
324 if(permitted().empty()) {
325 return true;
326 }
327
328 const auto& alt_name = cert.subject_alt_name();
329
330 if(reject_unknown) {
331 if(m_permitted_name_types.find("URI") != m_permitted_name_types.end() && !alt_name.get_attribute("URI").empty()) {
332 return false;
333 }
334 if(m_permitted_name_types.find("RFC822") != m_permitted_name_types.end() && !alt_name.get_attribute("RFC822").empty()) {
335 return false;
336 }
337 }
338
339 auto is_permitted_dn = [&](const X509_DN& dn) {
340 // If no restrictions, then immediate accept
341 if(m_permitted_name_types.find("DN") == m_permitted_name_types.end()) {
342 return true;
343 }
344
345 if(dn.empty()) {
346 return true;
347 }
348
349 for(const auto& c : m_permitted_subtrees) {
350 if(c.base().type() == "DN" && c.base().matches_dn_obj(dn)) {
351 return true;
352 }
353 }
354
355 // There is at least one permitted name and we didn't match
356 return false;
357 };
358
359 auto is_permitted_dns_name = [&](const std::string& name) {
360 if(name.empty() || name[0] == '.') {
361 return false;
362 }
363
364 // If no restrictions, then immediate accept
365 if(m_permitted_name_types.find("DNS") == m_permitted_name_types.end()) {
366 return true;
367 }
368
369 for(const auto& c : m_permitted_subtrees) {
370 if(c.base().type() == "DNS" && c.base().matches_dns(name)) {
371 return true;
372 }
373 }
374
375 // There is at least one permitted name and we didn't match
376 return false;
377 };
378
379 auto is_permitted_ipv4 = [&](const std::string& ipv4) {
380 // If no restrictions, then immediate accept
381 if(m_permitted_name_types.find("IP") == m_permitted_name_types.end()) {
382 return true;
383 }
384
385 for(const auto& c : m_permitted_subtrees) {
386 if(c.base().type() == "IP" && c.base().matches_ip(ipv4)) {
387 return true;
388 }
389 }
390
391 // There is at least one permitted name and we didn't match
392 return false;
393 };
394
395 if(!is_permitted_dn(cert.subject_dn())) {
396 return false;
397 }
398
399 if(!is_permitted_dn(alt_name.dn()))
400 {
401 return false;
402 }
403
404 for(const auto& alt_dns : alt_name.get_attribute("DNS")) {
405 if(!is_permitted_dns_name(alt_dns)) {
406 return false;
407 }
408 }
409
410 for(const auto& alt_ipv4 : alt_name.get_attribute("IP")) {
411 if(!is_permitted_ipv4(alt_ipv4)) {
412 return false;
413 }
414 }
415
416 if(!alt_name.has_items())
417 {
418 for(const auto& cn : cert.subject_info("Name"))
419 {
420 if(cn.find(".") != std::string::npos)
421 {
422 if(looks_like_ipv4(cn))
423 {
424 if(!is_permitted_ipv4(cn))
425 {
426 return false;
427 }
428 }
429 else
430 {
431 if(!is_permitted_dns_name(cn))
432 {
433 return false;
434 }
435 }
436 }
437 }
438 }
439
440 // We didn't encounter a name that doesn't have a matching constraint
441 return true;
442}
443
444bool NameConstraints::is_excluded(const X509_Certificate& cert, bool reject_unknown) const {
445 if(excluded().empty()) {
446 return false;
447 }
448
449 const auto& alt_name = cert.subject_alt_name();
450
451 if(reject_unknown) {
452 if(m_excluded_name_types.find("URI") != m_excluded_name_types.end() && !alt_name.get_attribute("URI").empty()) {
453 return false;
454 }
455 if(m_excluded_name_types.find("RFC822") != m_excluded_name_types.end() && !alt_name.get_attribute("RFC822").empty()) {
456 return false;
457 }
458 }
459
460 auto is_excluded_dn = [&](const X509_DN& dn) {
461 // If no restrictions, then immediate accept
462 if(m_excluded_name_types.find("DN") == m_excluded_name_types.end()) {
463 return false;
464 }
465
466 if(dn.empty()) {
467 return false;
468 }
469
470 for(const auto& c : m_excluded_subtrees) {
471 if(c.base().type() == "DN" && c.base().matches_dn_obj(dn)) {
472 return true;
473 }
474 }
475
476 // There is at least one excluded name and we didn't match
477 return false;
478 };
479
480 auto is_excluded_dns_name = [&](const std::string& name) {
481 if(name.empty() || name[0] == '.') {
482 return true;
483 }
484
485 // If no restrictions, then immediate accept
486 if(m_excluded_name_types.find("DNS") == m_excluded_name_types.end()) {
487 return false;
488 }
489
490 for(const auto& c : m_excluded_subtrees) {
491 if(c.base().type() == "DNS" && c.base().matches_dns(name)) {
492 return true;
493 }
494 }
495
496 // There is at least one excluded name and we didn't match
497 return false;
498 };
499
500 auto is_excluded_ipv4 = [&](const std::string& ipv4) {
501 // If no restrictions, then immediate accept
502 if(m_excluded_name_types.find("IP") == m_excluded_name_types.end()) {
503 return false;
504 }
505
506 for(const auto& c : m_excluded_subtrees) {
507 if(c.base().type() == "IP" && c.base().matches_ip(ipv4)) {
508 return true;
509 }
510 }
511
512 // There is at least one excluded name and we didn't match
513 return false;
514 };
515
516 if(is_excluded_dn(cert.subject_dn())) {
517 return true;
518 }
519
520 if(is_excluded_dn(alt_name.dn())) {
521 return true;
522 }
523
524 for(const auto& alt_dns : alt_name.get_attribute("DNS")) {
525 if(is_excluded_dns_name(alt_dns)) {
526 return true;
527 }
528 }
529
530 for(const auto& alt_ipv4 : alt_name.get_attribute("IP")) {
531 if(is_excluded_ipv4(alt_ipv4)) {
532 return true;
533 }
534 }
535
536 if(!alt_name.has_items())
537 {
538 for(const auto& cn : cert.subject_info("Name"))
539 {
540 if(cn.find(".") != std::string::npos)
541 {
542 if(looks_like_ipv4(cn))
543 {
544 if(is_excluded_ipv4(cn))
545 {
546 return true;
547 }
548 }
549 else
550 {
551 if(is_excluded_dns_name(cn))
552 {
553 return true;
554 }
555 }
556 }
557 }
558 }
559
560 // We didn't encounter a name that matched any prohibited name
561 return false;
562}
563
564} // namespace Botan
std::vector< std::string > get_attribute(const std::string &attr) const
BER_Decoder start_cons(ASN1_Tag type_tag, ASN1_Tag class_tag=UNIVERSAL)
Definition ber_dec.cpp:290
BER_Object get_next_object()
Definition ber_dec.cpp:237
BER_Decoder & decode(bool &out)
Definition ber_dec.h:170
BER_Decoder & decode_optional(T &out, ASN1_Tag type_tag, ASN1_Tag class_tag, const T &default_value=T())
Definition ber_dec.h:337
BER_Decoder & end_cons()
Definition ber_dec.cpp:300
size_t length() const
Definition asn1_obj.h:121
const uint8_t * bits() const
Definition asn1_obj.h:119
bool is_a(ASN1_Tag type_tag, ASN1_Tag class_tag) const
Definition asn1_obj.cpp:71
X.509 GeneralName Type.
Definition pkix_types.h:199
GeneralName()=default
const std::string & type() const
Definition pkix_types.h:229
void encode_into(DER_Encoder &) const override
MatchResult matches(const X509_Certificate &cert) const
bool matches_dn(const std::string &) const
bool matches_dns(const std::string &) const
void decode_from(BER_Decoder &) override
const std::string & name() const
Definition pkix_types.h:234
bool matches_ip(const std::string &) const
bool matches_dn_obj(const X509_DN &dn) const
A single Name Constraint.
Definition pkix_types.h:267
void decode_from(BER_Decoder &) override
size_t maximum() const
Definition pkix_types.h:311
size_t minimum() const
Definition pkix_types.h:306
const GeneralName & base() const
Definition pkix_types.h:301
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:345
const std::vector< GeneralSubtree > & excluded() const
Definition pkix_types.h:350
const X509_DN & subject_dn() const
Definition x509cert.cpp:477
std::vector< std::string > subject_info(const std::string &name) const
Definition x509cert.cpp:646
const AlternativeName & subject_alt_name() const
Definition x509cert.cpp:632
void decode_from(BER_Decoder &) override
Definition x509_dn.cpp:272
std::vector< std::string > get_attribute(const std::string &attr) const
Definition x509_dn.cpp:109
std::multimap< OID, std::string > get_attributes() const
Definition x509_dn.cpp:44
const std::vector< std::pair< OID, ASN1_String > > & dn_info() const
Definition pkix_types.h:74
std::string to_string() const
Definition x509_dn.cpp:326
std::string name
std::string to_string(const BER_Object &obj)
Definition asn1_obj.cpp:213
std::vector< std::string > split_on(const std::string &str, char delim)
Definition parsing.cpp:148
uint32_t string_to_ipv4(const std::string &str)
Definition parsing.cpp:253
uint32_t load_be< uint32_t >(const uint8_t in[], size_t off)
Definition loadstor.h:179
std::string tolower_string(const std::string &in)
Definition parsing.cpp:327
int operator<<(int fd, Pipe &pipe)
Definition fd_unix.cpp:17
ASN1_Tag
Definition asn1_obj.h:25
@ CONSTRUCTED
Definition asn1_obj.h:30
@ SEQUENCE
Definition asn1_obj.h:42
@ CONTEXT_SPECIFIC
Definition asn1_obj.h:28
std::string ipv4_to_string(uint32_t ip)
Definition parsing.cpp:278