Coverage Report

Created: 2021-02-21 07:20

/src/botan/src/lib/x509/x509_dn.cpp
Line
Count
Source (jump to first uncovered line)
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
#include <botan/der_enc.h>
10
#include <botan/ber_dec.h>
11
#include <botan/internal/stl_util.h>
12
#include <botan/oids.h>
13
#include <ostream>
14
#include <sstream>
15
#include <cctype>
16
17
namespace Botan {
18
19
namespace {
20
21
namespace {
22
23
bool caseless_cmp(char a, char b)
24
562k
   {
25
562k
   return (std::tolower(static_cast<unsigned char>(a)) ==
26
562k
           std::tolower(static_cast<unsigned char>(b)));
27
562k
   }
28
29
bool is_space(char c)
30
900k
   {
31
900k
   return std::isspace(static_cast<unsigned char>(c));
32
900k
   }
33
34
}
35
36
/*
37
* X.500 String Comparison
38
*/
39
bool x500_name_cmp(const std::string& name1, const std::string& name2)
40
58.5k
   {
41
58.5k
   auto p1 = name1.begin();
42
58.5k
   auto p2 = name2.begin();
43
44
60.9k
   while((p1 != name1.end()) && is_space(*p1)) ++p1;
45
63.5k
   while((p2 != name2.end()) && is_space(*p2)) ++p2;
46
47
618k
   while(p1 != name1.end() && p2 != name2.end())
48
563k
      {
49
563k
      if(is_space(*p1))
50
41.7k
         {
51
41.7k
         if(!is_space(*p2))
52
179
            return false;
53
54
84.7k
         while((p1 != name1.end()) && is_space(*p1)) ++p1;
55
84.9k
         while((p2 != name2.end()) && is_space(*p2)) ++p2;
56
57
41.6k
         if(p1 == name1.end() && p2 == name2.end())
58
154
            return true;
59
41.4k
         if(p1 == name1.end() || p2 == name2.end())
60
206
            return false;
61
562k
         }
62
63
562k
      if(!caseless_cmp(*p1, *p2))
64
2.24k
         return false;
65
560k
      ++p1;
66
560k
      ++p2;
67
560k
      }
68
69
56.4k
   while((p1 != name1.end()) && is_space(*p1)) ++p1;
70
57.2k
   while((p2 != name2.end()) && is_space(*p2)) ++p2;
71
72
55.7k
   if((p1 != name1.end()) || (p2 != name2.end()))
73
397
      return false;
74
55.3k
   return true;
75
55.3k
   }
76
77
}
78
79
/*
80
* Add an attribute to a X509_DN
81
*/
82
void X509_DN::add_attribute(const std::string& type,
83
                            const std::string& str)
84
0
   {
85
0
   add_attribute(OID::from_string(type), str);
86
0
   }
87
88
/*
89
* Add an attribute to a X509_DN
90
*/
91
void X509_DN::add_attribute(const OID& oid, const ASN1_String& str)
92
110k
   {
93
110k
   if(str.empty())
94
4.80k
      return;
95
96
105k
   m_rdn.push_back(std::make_pair(oid, str));
97
105k
   m_dn_bits.clear();
98
105k
   }
99
100
/*
101
* Get the attributes of this X509_DN
102
*/
103
std::multimap<OID, std::string> X509_DN::get_attributes() const
104
46.2k
   {
105
46.2k
   std::multimap<OID, std::string> retval;
106
107
46.2k
   for(auto& i : m_rdn)
108
172k
      multimap_insert(retval, i.first, i.second.value());
109
46.2k
   return retval;
110
46.2k
   }
111
112
/*
113
* Get the contents of this X.500 Name
114
*/
115
std::multimap<std::string, std::string> X509_DN::contents() const
116
0
   {
117
0
   std::multimap<std::string, std::string> retval;
118
119
0
   for(auto& i : m_rdn)
120
0
      {
121
0
      multimap_insert(retval, i.first.to_formatted_string(), i.second.value());
122
0
      }
123
0
   return retval;
124
0
   }
125
126
bool X509_DN::has_field(const std::string& attr) const
127
0
   {
128
0
   const OID o = OIDS::str2oid_or_empty(deref_info_field(attr));
129
0
   if(o.has_value())
130
0
      return has_field(o);
131
0
   else
132
0
      return false;
133
0
   }
134
135
bool X509_DN::has_field(const OID& oid) const
136
0
   {
137
0
   for(auto& i : m_rdn)
138
0
      {
139
0
      if(i.first == oid)
140
0
         return true;
141
0
      }
142
143
0
   return false;
144
0
   }
145
146
std::string X509_DN::get_first_attribute(const std::string& attr) const
147
0
   {
148
0
   const OID oid = OID::from_string(deref_info_field(attr));
149
0
   return get_first_attribute(oid).value();
150
0
   }
151
152
ASN1_String X509_DN::get_first_attribute(const OID& oid) const
153
0
   {
154
0
   for(auto& i : m_rdn)
155
0
      {
156
0
      if(i.first == oid)
157
0
         {
158
0
         return i.second;
159
0
         }
160
0
      }
161
162
0
   return ASN1_String();
163
0
   }
164
165
/*
166
* Get a single attribute type
167
*/
168
std::vector<std::string> X509_DN::get_attribute(const std::string& attr) const
169
0
   {
170
0
   const OID oid = OID::from_string(deref_info_field(attr));
171
172
0
   std::vector<std::string> values;
173
174
0
   for(auto& i : m_rdn)
175
0
      {
176
0
      if(i.first == oid)
177
0
         {
178
0
         values.push_back(i.second.value());
179
0
         }
180
0
      }
181
182
0
   return values;
183
0
   }
184
185
/*
186
* Deref aliases in a subject/issuer info request
187
*/
188
std::string X509_DN::deref_info_field(const std::string& info)
189
0
   {
190
0
   if(info == "Name" || info == "CommonName" || info == "CN") return "X520.CommonName";
191
0
   if(info == "SerialNumber" || info == "SN")                 return "X520.SerialNumber";
192
0
   if(info == "Country" || info == "C")                       return "X520.Country";
193
0
   if(info == "Organization" || info == "O")                  return "X520.Organization";
194
0
   if(info == "Organizational Unit" || info == "OrgUnit" || info == "OU")
195
0
      return "X520.OrganizationalUnit";
196
0
   if(info == "Locality" || info == "L")                      return "X520.Locality";
197
0
   if(info == "State" || info == "Province" || info == "ST")  return "X520.State";
198
0
   if(info == "Email")                                        return "RFC822";
199
0
   return info;
200
0
   }
201
202
/*
203
* Compare two X509_DNs for equality
204
*/
205
bool operator==(const X509_DN& dn1, const X509_DN& dn2)
206
21.7k
   {
207
21.7k
   auto attr1 = dn1.get_attributes();
208
21.7k
   auto attr2 = dn2.get_attributes();
209
210
21.7k
   if(attr1.size() != attr2.size()) return false;
211
212
20.5k
   auto p1 = attr1.begin();
213
20.5k
   auto p2 = attr2.begin();
214
215
75.6k
   while(true)
216
75.6k
      {
217
75.6k
      if(p1 == attr1.end() && p2 == attr2.end())
218
16.2k
         break;
219
59.4k
      if(p1 == attr1.end())      return false;
220
59.4k
      if(p2 == attr2.end())      return false;
221
59.4k
      if(p1->first != p2->first) return false;
222
57.6k
      if(!x500_name_cmp(p1->second, p2->second))
223
2.54k
         return false;
224
55.1k
      ++p1;
225
55.1k
      ++p2;
226
55.1k
      }
227
16.2k
   return true;
228
20.5k
   }
229
230
/*
231
* Compare two X509_DNs for inequality
232
*/
233
bool operator!=(const X509_DN& dn1, const X509_DN& dn2)
234
0
   {
235
0
   return !(dn1 == dn2);
236
0
   }
237
238
/*
239
* Induce an arbitrary ordering on DNs
240
*/
241
bool operator<(const X509_DN& dn1, const X509_DN& dn2)
242
1.35k
   {
243
1.35k
   auto attr1 = dn1.get_attributes();
244
1.35k
   auto attr2 = dn2.get_attributes();
245
246
   // If they are not the same size, choose the smaller as the "lessor"
247
1.35k
   if(attr1.size() < attr2.size())
248
262
      return true;
249
1.08k
   if(attr1.size() > attr2.size())
250
262
      return false;
251
252
   // We know they are the same # of elements, now compare the OIDs:
253
826
   auto p1 = attr1.begin();
254
826
   auto p2 = attr2.begin();
255
256
1.87k
   while(p1 != attr1.end() && p2 != attr2.end())
257
1.27k
      {
258
1.27k
      if(p1->first != p2->first)
259
222
         {
260
222
         return (p1->first < p2->first);
261
222
         }
262
263
1.04k
      ++p1;
264
1.04k
      ++p2;
265
1.04k
      }
266
267
   // We know this is true because maps have the same size
268
604
   BOTAN_ASSERT_NOMSG(p1 == attr1.end());
269
604
   BOTAN_ASSERT_NOMSG(p2 == attr2.end());
270
271
   // Now we know all elements have the same OIDs, compare
272
   // their string values:
273
274
604
   p1 = attr1.begin();
275
604
   p2 = attr2.begin();
276
992
   while(p1 != attr1.end() && p2 != attr2.end())
277
868
      {
278
868
      BOTAN_DEBUG_ASSERT(p1->first == p2->first);
279
280
      // They may be binary different but same by X.500 rules, check this
281
868
      if(!x500_name_cmp(p1->second, p2->second))
282
480
         {
283
         // If they are not (by X.500) the same string, pick the
284
         // lexicographic first as the lessor
285
480
         return (p1->second < p2->second);
286
480
         }
287
288
388
      ++p1;
289
388
      ++p2;
290
388
      }
291
292
   // if we reach here, then the DNs should be identical
293
124
   BOTAN_DEBUG_ASSERT(dn1 == dn2);
294
124
   return false;
295
604
   }
296
297
std::vector<uint8_t> X509_DN::DER_encode() const
298
0
   {
299
0
   std::vector<uint8_t> result;
300
0
   DER_Encoder der(result);
301
0
   this->encode_into(der);
302
0
   return result;
303
0
   }
304
305
/*
306
* DER encode a DistinguishedName
307
*/
308
void X509_DN::encode_into(DER_Encoder& der) const
309
0
   {
310
0
   der.start_sequence();
311
312
0
   if(!m_dn_bits.empty())
313
0
      {
314
      /*
315
      If we decoded this from somewhere, encode it back exactly as
316
      we received it
317
      */
318
0
      der.raw_bytes(m_dn_bits);
319
0
      }
320
0
   else
321
0
      {
322
0
      for(const auto& dn : m_rdn)
323
0
         {
324
0
         der.start_set()
325
0
            .start_sequence()
326
0
            .encode(dn.first)
327
0
            .encode(dn.second)
328
0
            .end_cons()
329
0
         .end_cons();
330
0
         }
331
0
      }
332
333
0
   der.end_cons();
334
0
   }
335
336
/*
337
* Decode a BER encoded DistinguishedName
338
*/
339
void X509_DN::decode_from(BER_Decoder& source)
340
45.8k
   {
341
45.8k
   std::vector<uint8_t> bits;
342
343
45.8k
   source.start_sequence()
344
45.8k
      .raw_bytes(bits)
345
45.8k
   .end_cons();
346
347
45.8k
   BER_Decoder sequence(bits);
348
349
45.8k
   m_rdn.clear();
350
351
161k
   while(sequence.more_items())
352
115k
      {
353
115k
      BER_Decoder rdn = sequence.start_set();
354
355
227k
      while(rdn.more_items())
356
112k
         {
357
112k
         OID oid;
358
112k
         ASN1_String str;
359
360
112k
         rdn.start_sequence()
361
112k
            .decode(oid)
362
112k
            .decode(str) // TODO support Any
363
112k
            .end_cons().verify_end("Invalid X509_DN, data follows RDN");
364
365
112k
         add_attribute(oid, str);
366
112k
         }
367
115k
      }
368
369
   // Have to assign last as add_attribute zaps m_dn_bits
370
45.8k
   m_dn_bits = bits;
371
45.8k
   }
372
373
namespace {
374
375
std::string to_short_form(const OID& oid)
376
5.57k
   {
377
5.57k
   const std::string long_id = oid.to_formatted_string();
378
379
5.57k
   if(long_id == "X520.CommonName")
380
130
      return "CN";
381
382
5.44k
   if(long_id == "X520.Country")
383
279
      return "C";
384
385
5.16k
   if(long_id == "X520.Organization")
386
121
      return "O";
387
388
5.04k
   if(long_id == "X520.OrganizationalUnit")
389
104
      return "OU";
390
391
4.93k
   return long_id;
392
4.93k
   }
393
394
}
395
396
std::string X509_DN::to_string() const
397
0
   {
398
0
   std::ostringstream out;
399
0
   out << *this;
400
0
   return out.str();
401
0
   }
402
403
std::ostream& operator<<(std::ostream& out, const X509_DN& dn)
404
3.75k
   {
405
3.75k
   auto info = dn.dn_info();
406
407
9.32k
   for(size_t i = 0; i != info.size(); ++i)
408
5.57k
      {
409
5.57k
      out << to_short_form(info[i].first) << "=\"";
410
5.57k
      for(char c : info[i].second.value())
411
22.8k
         {
412
22.8k
         if(c == '\\' || c == '\"')
413
868
            {
414
868
            out << "\\";
415
868
            }
416
22.8k
         out << c;
417
22.8k
         }
418
5.57k
      out << "\"";
419
420
5.57k
      if(i + 1 < info.size())
421
2.46k
         {
422
2.46k
         out << ",";
423
2.46k
         }
424
5.57k
      }
425
3.75k
   return out;
426
3.75k
   }
427
428
std::istream& operator>>(std::istream& in, X509_DN& dn)
429
0
   {
430
0
   in >> std::noskipws;
431
0
   do
432
0
      {
433
0
      std::string key;
434
0
      std::string val;
435
0
      char c;
436
437
0
      while(in.good())
438
0
         {
439
0
         in >> c;
440
441
0
         if(std::isspace(c) && key.empty())
442
0
            continue;
443
0
         else if(!std::isspace(c))
444
0
            {
445
0
            key.push_back(c);
446
0
            break;
447
0
            }
448
0
         else
449
0
            break;
450
0
         }
451
452
0
      while(in.good())
453
0
         {
454
0
         in >> c;
455
456
0
         if(!std::isspace(c) && c != '=')
457
0
            key.push_back(c);
458
0
         else if(c == '=')
459
0
            break;
460
0
         else
461
0
            throw Invalid_Argument("Ill-formed X.509 DN");
462
0
         }
463
464
0
      bool in_quotes = false;
465
0
      while(in.good())
466
0
         {
467
0
         in >> c;
468
469
0
         if(std::isspace(c))
470
0
            {
471
0
            if(!in_quotes && !val.empty())
472
0
               break;
473
0
            else if(in_quotes)
474
0
               val.push_back(' ');
475
0
            }
476
0
         else if(c == '"')
477
0
            in_quotes = !in_quotes;
478
0
         else if(c == '\\')
479
0
            {
480
0
            if(in.good())
481
0
               in >> c;
482
0
            val.push_back(c);
483
0
            }
484
0
         else if(c == ',' && !in_quotes)
485
0
            break;
486
0
         else
487
0
            val.push_back(c);
488
0
         }
489
490
0
      if(!key.empty() && !val.empty())
491
0
         dn.add_attribute(X509_DN::deref_info_field(key),val);
492
0
      else
493
0
         break;
494
0
      }
495
0
   while(in.good());
496
0
   return in;
497
0
   }
498
}