Botan 3.13.0
Crypto and TLS for C&
x509_dn.cpp
Go to the documentation of this file.
1/*
2* X509_DN
3* (C) 1999-2007,2018 Jack Lloyd
4*
5* Botan is released under the Simplified BSD License (see license.txt)
6*/
7
8#include <botan/pkix_types.h>
9
10#include <botan/assert.h>
11#include <botan/ber_dec.h>
12#include <botan/der_enc.h>
13#include <botan/internal/charset.h>
14#include <botan/internal/loadstor.h>
15#include <botan/internal/x509_utils.h>
16#include <algorithm>
17#include <istream>
18#include <iterator>
19#include <optional>
20#include <ostream>
21#include <sstream>
22
23namespace Botan {
24
25namespace {
26
27bool is_space(char c) {
28 return c == ' ' || c == '\t';
29}
30
31std::optional<uint8_t> hex_digit_value(char c) {
32 if(c >= '0' && c <= '9') {
33 return static_cast<uint8_t>(c - '0');
34 } else if(c >= 'a' && c <= 'f') {
35 return static_cast<uint8_t>(c - 'a' + 10);
36 } else if(c >= 'A' && c <= 'F') {
37 return static_cast<uint8_t>(c - 'A' + 10);
38 } else {
39 return {};
40 }
41}
42
43/*
44* RFC 4514 Section 3 specifies which characters can be escaped
45*/
46bool is_escapable_char(char c) {
47 switch(c) {
48 case '\\':
49 case '"':
50 case '+':
51 case ',':
52 case ';':
53 case '<':
54 case '>':
55 case ' ':
56 case '#':
57 case '=':
58 return true;
59 default:
60 return false;
61 }
62}
63
64bool is_unescaped_special_value_char(char c) {
65 switch(c) {
66 case ';':
67 case '<':
68 case '>':
69 case '#':
70 case '=':
71 return true;
72 default:
73 return false;
74 }
75}
76
77/*
78* Yields the X.500 canonical form of a name component one character at a time
79*/
80class X500_Char_Iterator final {
81 public:
82 explicit X500_Char_Iterator(std::string_view s) : m_str(s), m_pos(0) {
83 // Skip leading whitespace
84 while(m_pos < m_str.size() && is_space(m_str[m_pos])) {
85 ++m_pos;
86 }
87 }
88
89 // Returns next canonical character, or nullopt when exhausted.
90 std::optional<char> next() {
91 if(m_pos >= m_str.size()) {
92 return std::nullopt;
93 }
94
95 if(is_space(m_str[m_pos])) {
96 // Skip the entire whitespace run
97 while(m_pos < m_str.size() && is_space(m_str[m_pos])) {
98 ++m_pos;
99 }
100 // Emit a single space only if more content follows (strip trailing ws)
101 if(m_pos < m_str.size()) {
102 return ' ';
103 }
104 return std::nullopt;
105 }
106
107 const char c = m_str[m_pos++];
108 // Locale-independent ASCII fold; RFC 5280 DN matching does not depend on libc locale
109 if(c >= 'A' && c <= 'Z') {
110 return static_cast<char>(c + ('a' - 'A'));
111 }
112 return c;
113 }
114
115 static std::string canonicalize(std::string_view name) {
116 std::string result;
117 result.reserve(name.size());
118
119 X500_Char_Iterator it(name);
120 while(auto c = it.next()) {
121 result += *c;
122 }
123
124 return result;
125 }
126
127 private:
128 std::string_view m_str;
129 size_t m_pos;
130};
131
132} // namespace
133
134/*
135* Add an attribute to a X509_DN
136*/
137void X509_DN::add_attribute(std::string_view type, std::string_view str) {
139}
140
141/*
142* Add an attribute to a X509_DN
143*/
144void X509_DN::add_attribute(const OID& oid, const ASN1_String& str) {
145 if(str.empty()) {
146 return;
147 }
148
149 // Each programmatic add appends a new single-AVA RDN.
150 m_rdn.push_back({std::make_pair(oid, str)});
151 m_dn_bits.clear();
152 update_canonical_bits();
153}
154
155void X509_DN::add_rdn(std::vector<std::pair<OID, ASN1_String>> rdn) {
156 if(rdn.empty()) {
157 return;
158 }
159 m_rdn.push_back(std::move(rdn));
160 m_dn_bits.clear();
161 update_canonical_bits();
162}
163
164std::vector<std::pair<OID, ASN1_String>> X509_DN::dn_info() const {
165 std::vector<std::pair<OID, ASN1_String>> flat;
166 for(const auto& rdn : m_rdn) {
167 for(const auto& ava : rdn) {
168 flat.push_back(ava);
169 }
170 }
171 return flat;
172}
173
174/*
175* Get the attributes of this X509_DN
176*/
177std::multimap<OID, std::string> X509_DN::get_attributes() const {
178 std::multimap<OID, std::string> retval;
179
180 for(const auto& rdn : m_rdn) {
181 for(const auto& ava : rdn) {
182 retval.emplace(ava.first, ava.second.value());
183 }
184 }
185 return retval;
186}
187
188/*
189* Get the contents of this X.500 Name
190*/
191std::multimap<std::string, std::string> X509_DN::contents() const {
192 std::multimap<std::string, std::string> retval;
193
194 for(const auto& rdn : m_rdn) {
195 for(const auto& ava : rdn) {
196 retval.emplace(ava.first.to_formatted_string(), ava.second.value());
197 }
198 }
199 return retval;
200}
201
202bool X509_DN::has_field(std::string_view attr) const {
203 try {
204 const OID o = OID::from_string(deref_info_field(attr));
205 if(o.has_value()) {
206 return has_field(o);
207 }
208 } catch(Lookup_Error&) {}
209
210 return false;
211}
212
213bool X509_DN::has_field(const OID& oid) const {
214 for(const auto& rdn : m_rdn) {
215 for(const auto& ava : rdn) {
216 if(ava.first == oid) {
217 return true;
218 }
219 }
220 }
221
222 return false;
223}
224
225std::string X509_DN::get_first_attribute(std::string_view attr) const {
226 const OID oid = OID::from_string(deref_info_field(attr));
227 return get_first_attribute(oid).value();
228}
229
231 for(const auto& rdn : m_rdn) {
232 for(const auto& ava : rdn) {
233 if(ava.first == oid) {
234 return ava.second;
235 }
236 }
237 }
238
239 return ASN1_String();
240}
241
242/*
243* Get a single attribute type
244*/
245std::vector<std::string> X509_DN::get_attribute(std::string_view attr) const {
246 const OID oid = OID::from_string(deref_info_field(attr));
247
248 std::vector<std::string> values;
249
250 for(const auto& rdn : m_rdn) {
251 for(const auto& ava : rdn) {
252 if(ava.first == oid) {
253 values.push_back(ava.second.value());
254 }
255 }
256 }
257
258 return values;
259}
260
261/*
262* Deref aliases in a subject/issuer info request
263*/
264std::string X509_DN::deref_info_field(std::string_view info) {
265 if(info == "Name" || info == "CommonName" || info == "CN") {
266 return "X520.CommonName";
267 }
268 if(info == "SerialNumber" || info == "SN") {
269 return "X520.SerialNumber";
270 }
271 if(info == "Country" || info == "C") {
272 return "X520.Country";
273 }
274 if(info == "Organization" || info == "O") {
275 return "X520.Organization";
276 }
277 if(info == "Organizational Unit" || info == "OrgUnit" || info == "OU") {
278 return "X520.OrganizationalUnit";
279 }
280 if(info == "Locality" || info == "L") {
281 return "X520.Locality";
282 }
283 if(info == "State" || info == "Province" || info == "ST") {
284 return "X520.State";
285 }
286 if(info == "Email") {
287 return "RFC822";
288 }
289 return std::string(info);
290}
291
292namespace {
293
294/*
295* Canonical form of an RDN's AVAs: each value is X.500-canonicalized
296* (case-fold and whitespace collapse) and the resulting (OID, value)
297* pairs are sorted, so an RDN's SET semantics reduce to vector equality.
298*/
299std::vector<std::pair<OID, std::string>> canonicalize_rdn(const std::vector<std::pair<OID, ASN1_String>>& rdn) {
300 std::vector<std::pair<OID, std::string>> result;
301 result.reserve(rdn.size());
302 for(const auto& ava : rdn) {
303 result.emplace_back(ava.first, X500_Char_Iterator::canonicalize(ava.second.value()));
304 }
305 if(result.size() != 1) {
306 std::sort(result.begin(), result.end());
307 }
308 return result;
309}
310
311std::vector<uint8_t> canonicalize_dn(const std::vector<std::vector<std::pair<OID, ASN1_String>>>& rdns) {
312 auto append_canonical_data = []<typename T>(std::vector<uint8_t>& out, const T& data) {
313 const std::array<uint8_t, 8> data_len = store_le(static_cast<uint64_t>(data.size()));
314 out.insert(out.end(), data_len.begin(), data_len.end());
315 out.insert(out.end(), data.begin(), data.end());
316 };
317
318 std::vector<uint8_t> canonical_bits;
319
320 for(const auto& rdn : rdns) {
321 std::vector<uint8_t> rdn_bits;
322
323 for(const auto& [oid, value] : canonicalize_rdn(rdn)) {
324 append_canonical_data(rdn_bits, oid.BER_encode());
325 append_canonical_data(rdn_bits, value);
326 }
327
328 append_canonical_data(canonical_bits, rdn_bits);
329 }
330
331 return canonical_bits;
332}
333
334} // namespace
335
336/*
337* Compare two X509_DNs for equality
338*/
339bool operator==(const X509_DN& dn1, const X509_DN& dn2) {
340 return dn1._canonical_bytes() == dn2._canonical_bytes();
341}
342
343/*
344* Compare two X509_DNs for inequality
345*/
346bool operator!=(const X509_DN& dn1, const X509_DN& dn2) {
347 return !(dn1 == dn2);
348}
349
350/*
351* Induce an arbitrary ordering on DNs that respects RDN sequence order
352* and RDN set-equality.
353*/
354bool operator<(const X509_DN& dn1, const X509_DN& dn2) {
355 return dn1._canonical_bytes() < dn2._canonical_bytes();
356}
357
358bool x509_dn_subtree_match(const X509_DN& name, const X509_DN& constraint) {
359 const auto& name_bits = name._canonical_bytes();
360 const auto& constraint_bits = constraint._canonical_bytes();
361
362 if(constraint_bits.size() > name_bits.size()) {
363 return false;
364 }
365
366 return std::equal(constraint_bits.begin(), constraint_bits.end(), name_bits.begin());
367}
368
369void X509_DN::update_canonical_bits() {
370 m_canonical_dn_bits = canonicalize_dn(m_rdn);
371}
372
373std::vector<uint8_t> X509_DN::DER_encode() const {
374 std::vector<uint8_t> result;
375 DER_Encoder der(result);
376 this->encode_into(der);
377 return result;
378}
379
380/*
381* DER encode a DistinguishedName
382*/
384 der.start_sequence();
385
386 if(!m_dn_bits.empty()) {
387 /*
388 If we decoded this from somewhere, encode it back exactly as
389 we received it
390 */
391 der.raw_bytes(m_dn_bits);
392 } else {
393 for(const auto& rdn : m_rdn) {
394 der.start_set();
395 for(const auto& ava : rdn) {
396 der.start_sequence().encode(ava.first).encode(ava.second).end_cons();
397 }
398 der.end_cons();
399 }
400 }
401
402 der.end_cons();
403}
404
405/*
406* Decode a BER encoded DistinguishedName
407*/
409 std::vector<uint8_t> bits;
410
411 source.start_sequence().raw_bytes(bits).end_cons();
412
413 BER_Decoder sequence(bits, source.limits());
414
415 std::vector<std::vector<std::pair<OID, ASN1_String>>> rdns;
416
417 // Cap AVAs per RDN to bound work for downstream set-based matching.
418 // No legitimate cert has anywhere near this many AVAs in a single RDN.
419 constexpr size_t MAX_AVAS_PER_RDN = 32;
420
421 while(sequence.more_items()) {
422 BER_Decoder rdn_decoder = sequence.start_set();
423
424 std::vector<std::pair<OID, ASN1_String>> rdn;
425 while(rdn_decoder.more_items()) {
426 OID oid;
427 ASN1_String str;
428
429 rdn_decoder.start_sequence()
430 .decode(oid)
431 .decode(str) // TODO support Any
432 .end_cons();
433
434 rdn.emplace_back(std::move(oid), std::move(str));
435
436 if(rdn.size() > MAX_AVAS_PER_RDN) {
437 throw Decoding_Error("X.500 RDN has too many attribute-value assertions");
438 }
439 }
440
441 /*
442 RFC 5280 4.1.2.4:
443 RelativeDistinguishedName ::=
444 SET SIZE (1..MAX) OF AttributeTypeAndValue
445 */
446 if(rdn.empty()) {
447 throw Decoding_Error("X.500 RDN must contain at least one attribute-value assertion");
448 }
449 rdns.push_back(std::move(rdn));
450 }
451
452 auto canonical_bits = canonicalize_dn(rdns);
453
454 m_rdn = std::move(rdns);
455 m_dn_bits = std::move(bits);
456 m_canonical_dn_bits = std::move(canonical_bits);
457}
458
459namespace {
460
461std::string to_short_form(const OID& oid) {
462 std::string long_id = oid.to_formatted_string();
463
464 if(long_id == "X520.CommonName") {
465 return "CN";
466 }
467
468 if(long_id == "X520.Country") {
469 return "C";
470 }
471
472 if(long_id == "X520.Organization") {
473 return "O";
474 }
475
476 if(long_id == "X520.OrganizationalUnit") {
477 return "OU";
478 }
479
480 return long_id;
481}
482
483} // namespace
484
485std::string X509_DN::to_string() const {
486 std::ostringstream out;
487 out << *this;
488 return out.str();
489}
490
491std::ostream& operator<<(std::ostream& out, const X509_DN& dn) {
492 const auto& rdns = dn.rdns();
493
494 // Escape characters as a backslash plus two hex digits per byte
495 // See RFC 4514 Sections 2.4 and 4
496 auto hex_escape = [](std::ostream& s, char c) {
497 const auto b = static_cast<uint8_t>(c);
498 s << '\\' << nibble_to_hex(b >> 4) << nibble_to_hex(b);
499 };
500
501 // AVAs within the same RDN are joined with '+' (per RFC 4514), so a
502 // multi-valued RDN remains distinguishable from multiple single-valued
503 // RDNs separated by ','.
504 bool first_rdn = true;
505 for(const auto& rdn : rdns) {
506 if(!first_rdn) {
507 out << ",";
508 }
509 first_rdn = false;
510
511 bool first_ava = true;
512 for(const auto& ava : rdn) {
513 if(!first_ava) {
514 out << "+";
515 }
516 first_ava = false;
517 out << to_short_form(ava.first) << "=\"";
518 const std::string_view value = ava.second.value();
519 size_t pos = 0;
520 while(pos < value.size()) {
521 const size_t start = pos;
522
523 uint32_t cp = 0;
524 try {
525 cp = next_utf8_codepoint(value, pos);
526 } catch(const Decoding_Error&) {
527 // value() should always be valid UTF-8, but escape defensively otherwise
528 hex_escape(out, value[start]);
529 pos = start + 1;
530 continue;
531 }
532
533 if(cp == '\\' || cp == '"') {
534 out << '\\' << static_cast<char>(cp);
535 } else if(is_unicode_control_char(cp)) {
536 for(size_t i = start; i < pos; ++i) {
537 hex_escape(out, value[i]);
538 }
539 } else {
540 out << value.substr(start, pos - start);
541 }
542 }
543 out << "\"";
544 }
545 }
546 return out;
547}
548
549/*
550* Parse the string representation of a distinguished name, accepting
551* the formats specified in RFC 4514 Section 3 as well as RFC 2253's
552* quoted format.
553*/
554std::optional<X509_DN> X509_DN::parse(std::string_view str) {
555 X509_DN dn;
556
557 // AVAs accumulate here; a trailing '+' keeps the next AVA in the same
558 // RDN, while a ',' (or end of input) flushes them as a single RDN.
559 std::vector<std::pair<OID, ASN1_String>> pending_rdn;
560
561 // Separator that ended the previous AVA. A ',' or '+' still pending after the
562 // loop means the input ended with a separator and no AVA to follow it.
563 char terminator = '\0';
564
565 size_t pos = 0;
566 while(pos < str.size()) {
567 // Whitespace separating an attributeType from the preceding ',' or '+'
568 // is tolerated even though RFC 4514 does not produce it.
569 while(pos < str.size() && is_space(str[pos])) {
570 ++pos;
571 }
572 if(pos == str.size()) {
573 break;
574 }
575
576 // attributeType, terminated by '='
577 const size_t type_start = pos;
578 while(pos < str.size() && str[pos] != '=' && !is_space(str[pos])) {
579 ++pos;
580 }
581 const std::string_view type = str.substr(type_start, pos - type_start);
582 if(type.empty() || pos == str.size() || str[pos] != '=') {
583 return std::nullopt;
584 }
585 ++pos; // consume '='
586
587 /*
588 attributeValue, in RFC 4514 <string> form plus the legacy quoted form.
589 value_len tracks the length up to the last significant octet: leading and
590 trailing unescaped whitespace is not significant unless it was escaped or
591 quoted, so trailing whitespace is dropped by the final resize.
592 */
593 std::string value;
594 size_t value_len = 0;
595
596 // The legacy quoted form wraps the whole value: a quote is only an opening
597 // quote at the start of the value, and nothing but trailing whitespace or a
598 // separator may follow the closing quote.
599 enum class Quote : uint8_t { None, Open, Closed };
600 Quote quote = Quote::None;
601
602 terminator = '\0';
603
604 while(pos < str.size()) {
605 const char c = str[pos];
606
607 if(c == '"') {
608 if(quote == Quote::Open) {
609 quote = Quote::Closed;
610 } else if(quote == Quote::None && value.empty()) {
611 quote = Quote::Open;
612 } else {
613 return std::nullopt; // quote in mid-value or after the closing quote
614 }
615 ++pos;
616 } else if(c == '\\') {
617 if(quote == Quote::Closed) {
618 return std::nullopt; // escape after the closing quote
619 }
620 // pair = ESC ( ESC / special / hexpair )
621 ++pos;
622 if(pos == str.size()) {
623 return std::nullopt;
624 }
625 if(const auto hi = hex_digit_value(str[pos])) {
626 const auto lo = (pos + 1 < str.size()) ? hex_digit_value(str[pos + 1]) : std::nullopt;
627 if(!lo) {
628 return std::nullopt;
629 }
630 value.push_back(static_cast<char>((*hi << 4) | *lo));
631 pos += 2;
632 } else if(is_escapable_char(str[pos])) {
633 value.push_back(str[pos]);
634 ++pos;
635 } else {
636 return std::nullopt; // not ESC / special / hexpair
637 }
638 value_len = value.size(); // an escaped octet is always significant
639 } else if((c == ',' || c == '+') && quote != Quote::Open) {
640 terminator = c;
641 ++pos;
642 break;
643 } else if(quote == Quote::Closed) {
644 if(!is_space(c)) {
645 return std::nullopt; // content after the closing quote
646 }
647 ++pos; // trailing whitespace after the closing quote is insignificant
648 } else if(quote != Quote::Open && is_unescaped_special_value_char(c)) {
649 return std::nullopt;
650 } else {
651 ++pos;
652 if(is_space(c) && quote != Quote::Open) {
653 // Keep interior whitespace only if more content follows; skip it
654 // entirely while leading (value is still empty)
655 if(!value.empty()) {
656 value.push_back(c);
657 }
658 } else {
659 value.push_back(c);
660 value_len = value.size();
661 }
662 }
663 }
664
665 if(quote == Quote::Open) {
666 return std::nullopt; // unterminated quoted value
667 }
668 value.resize(value_len); // strip trailing unescaped whitespace
669
670 try {
672 // ASN1_String rejects values (e.g. a \FF hexpair) that are not valid
673 // for any supported string encoding.
674 pending_rdn.emplace_back(std::move(oid), ASN1_String(value));
675 } catch(const Exception&) {
676 return std::nullopt; // unknown attributeType or invalid attributeValue
677 }
678
679 if(terminator != '+') {
680 dn.add_rdn(std::move(pending_rdn));
681 pending_rdn.clear();
682 }
683 }
684
685 // A trailing ',' or '+' leaves an RDN/AVA with nothing to follow it
686 if(terminator == ',' || terminator == '+') {
687 return std::nullopt;
688 }
689 return dn;
690}
691
692std::istream& operator>>(std::istream& in, X509_DN& dn) {
693 const std::istreambuf_iterator<char> begin(in);
694 const std::istreambuf_iterator<char> end;
695 const std::string contents(begin, end);
696
697 if(auto parsed = X509_DN::parse(contents)) {
698 dn = std::move(*parsed);
699 } else {
700 in.setstate(std::ios::failbit);
701 }
702 return in;
703}
704} // namespace Botan
const std::string & value() const
Definition asn1_obj.h:590
bool empty() const
Definition asn1_obj.h:600
BER_Decoder start_set()
Definition ber_dec.h:281
BER_Decoder & decode(bool &out)
Definition ber_dec.h:358
bool more_items() const
Definition ber_dec.cpp:461
Limits limits() const
Definition ber_dec.h:197
BER_Decoder & raw_bytes(std::vector< uint8_t, Alloc > &out)
Definition ber_dec.h:338
BER_Decoder & end_cons()
Definition ber_dec.cpp:630
BER_Decoder start_sequence()
Definition ber_dec.h:275
DER_Encoder & start_set()
Definition der_enc.h:91
DER_Encoder & start_sequence()
Definition der_enc.h:86
DER_Encoder & raw_bytes(const uint8_t val[], size_t len)
Definition der_enc.cpp:237
DER_Encoder & end_cons()
Definition der_enc.cpp:208
DER_Encoder & encode(bool b)
Definition der_enc.cpp:313
std::string to_formatted_string() const
Definition asn1_oid.cpp:137
bool has_value() const
Definition asn1_obj.h:474
static OID from_string(std::string_view str)
Definition asn1_oid.cpp:80
const std::vector< std::vector< std::pair< OID, ASN1_String > > > & rdns() const
Definition pkix_types.h:229
void add_rdn(std::vector< std::pair< OID, ASN1_String > > rdn)
Definition x509_dn.cpp:155
static std::optional< X509_DN > parse(std::string_view str)
Definition x509_dn.cpp:554
bool has_field(const OID &oid) const
Definition x509_dn.cpp:213
std::multimap< std::string, std::string > contents() const
Definition x509_dn.cpp:191
std::vector< std::pair< OID, ASN1_String > > dn_info() const
Definition x509_dn.cpp:164
std::vector< std::string > get_attribute(std::string_view attr) const
Definition x509_dn.cpp:245
void add_attribute(std::string_view key, std::string_view val)
Definition x509_dn.cpp:137
X509_DN()=default
std::multimap< OID, std::string > get_attributes() const
Definition x509_dn.cpp:177
ASN1_String get_first_attribute(const OID &oid) const
Definition x509_dn.cpp:230
void encode_into(DER_Encoder &to) const override
Definition x509_dn.cpp:383
std::vector< uint8_t > DER_encode() const
Definition x509_dn.cpp:373
std::string to_string() const
Definition x509_dn.cpp:485
const std::vector< uint8_t > & _canonical_bytes() const
Definition pkix_types.h:274
void decode_from(BER_Decoder &from) override
Definition x509_dn.cpp:408
static std::string deref_info_field(std::string_view key)
Definition x509_dn.cpp:264
constexpr char nibble_to_hex(uint8_t b)
Definition charset.h:76
bool is_unicode_control_char(uint32_t cp)
Definition charset.cpp:203
bool operator<(const OID &a, const OID &b)
Definition asn1_oid.cpp:175
std::ostream & operator<<(std::ostream &out, const OID &oid)
Definition asn1_oid.cpp:302
constexpr auto store_le(ParamTs &&... params)
Definition loadstor.h:736
bool x509_dn_subtree_match(const X509_DN &name, const X509_DN &constraint)
Definition x509_dn.cpp:358
int operator>>(int fd, Pipe &pipe)
Definition fd_unix.cpp:43
bool operator!=(const AlgorithmIdentifier &x, const AlgorithmIdentifier &y)
Definition alg_id.cpp:58
uint32_t next_utf8_codepoint(std::string_view utf8, size_t &pos)
Definition charset.cpp:53
bool operator==(const AlgorithmIdentifier &x, const AlgorithmIdentifier &y)
Definition alg_id.cpp:54