Coverage Report

Created: 2026-03-31 07:01

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/botan/src/lib/asn1/asn1_oid.cpp
Line
Count
Source
1
/*
2
* ASN.1 OID
3
* (C) 1999-2007,2024 Jack Lloyd
4
*
5
* Botan is released under the Simplified BSD License (see license.txt)
6
*/
7
8
#include <botan/asn1_obj.h>
9
10
#include <botan/ber_dec.h>
11
#include <botan/der_enc.h>
12
#include <botan/internal/bit_ops.h>
13
#include <botan/internal/fmt.h>
14
#include <botan/internal/int_utils.h>
15
#include <botan/internal/oid_map.h>
16
#include <botan/internal/parsing.h>
17
#include <botan/internal/stl_util.h>
18
#include <algorithm>
19
#include <span>
20
#include <sstream>
21
22
namespace Botan {
23
24
namespace {
25
26
1.10k
void oid_valid_check(std::span<const uint32_t> oid) {
27
1.10k
   BOTAN_ARG_CHECK(oid.size() >= 2, "OID too short to be valid");
28
1.10k
   BOTAN_ARG_CHECK(oid[0] <= 2, "OID root out of range");
29
1.10k
   BOTAN_ARG_CHECK(oid[1] <= 39 || oid[0] == 2, "OID second arc too large");
30
   // This last is a limitation of using 32 bit integers when decoding
31
   // not a limitation of ASN.1 object identifiers in general
32
1.10k
   BOTAN_ARG_CHECK(oid[1] <= 0xFFFFFFAF, "OID second arc too large");
33
1.10k
}
34
35
// returns empty on invalid
36
8
std::vector<uint32_t> parse_oid_str(std::string_view oid) {
37
8
   try {
38
8
      std::string elem;
39
8
      std::vector<uint32_t> oid_elems;
40
41
92
      for(char c : oid) {
42
92
         if(c == '.') {
43
32
            if(elem.empty()) {
44
0
               return std::vector<uint32_t>();
45
0
            }
46
32
            oid_elems.push_back(to_u32bit(elem));
47
32
            elem.clear();
48
60
         } else {
49
60
            elem += c;
50
60
         }
51
92
      }
52
53
8
      if(!elem.empty()) {
54
8
         oid_elems.push_back(to_u32bit(elem));
55
8
      }
56
57
8
      return oid_elems;
58
8
   } catch(Invalid_Argument&) {
59
      // thrown by to_u32bit
60
0
      return std::vector<uint32_t>();
61
0
   }
62
8
}
63
64
}  // namespace
65
66
//static
67
8
void OID::register_oid(const OID& oid, std::string_view name) {
68
8
   OID_Map::global_registry().add_oid(oid, name);
69
8
}
70
71
//static
72
0
std::optional<OID> OID::from_name(std::string_view name) {
73
0
   if(name.empty()) {
74
0
      throw Invalid_Argument("OID::from_name argument must be non-empty");
75
0
   }
76
77
0
   OID o = OID_Map::global_registry().str2oid(name);
78
0
   if(o.has_value()) {
79
0
      return std::optional(o);
80
0
   }
81
82
0
   return std::nullopt;
83
0
}
84
85
//static
86
806
OID OID::from_string(std::string_view str) {
87
806
   if(str.empty()) {
88
0
      throw Invalid_Argument("OID::from_string argument must be non-empty");
89
0
   }
90
91
806
   OID o = OID_Map::global_registry().str2oid(str);
92
806
   if(o.has_value()) {
93
806
      return o;
94
806
   }
95
96
   // Try to parse as a dotted decimal
97
0
   try {
98
0
      return OID(str);
99
0
   } catch(...) {}
100
101
0
   throw Lookup_Error(fmt("No OID associated with name '{}'", str));
102
0
}
103
104
1.09k
OID::OID(std::initializer_list<uint32_t> init) : m_id(init) {
105
1.09k
   oid_valid_check(m_id);
106
1.09k
}
107
108
0
OID::OID(std::vector<uint32_t>&& init) : m_id(std::move(init)) {
109
0
   oid_valid_check(m_id);
110
0
}
111
112
/*
113
* ASN.1 OID Constructor
114
*/
115
8
OID::OID(std::string_view oid_str) {
116
8
   if(!oid_str.empty()) {
117
8
      m_id = parse_oid_str(oid_str);
118
8
      oid_valid_check(m_id);
119
8
   }
120
8
}
121
122
/*
123
* Return this OID as a string
124
*/
125
0
std::string OID::to_string() const {
126
0
   std::ostringstream out;
127
128
0
   for(size_t i = 0; i != m_id.size(); ++i) {
129
      // avoid locale issues with integer formatting
130
0
      out << std::to_string(m_id[i]);
131
0
      if(i != m_id.size() - 1) {
132
0
         out << ".";
133
0
      }
134
0
   }
135
136
0
   return out.str();
137
0
}
138
139
0
std::string OID::to_formatted_string() const {
140
0
   std::string s = this->human_name_or_empty();
141
0
   if(!s.empty()) {
142
0
      return s;
143
0
   }
144
0
   return this->to_string();
145
0
}
146
147
9
std::string OID::human_name_or_empty() const {
148
9
   return OID_Map::global_registry().oid2str(*this);
149
9
}
150
151
0
bool OID::registered_oid() const {
152
0
   return !human_name_or_empty().empty();
153
0
}
154
155
1
bool OID::matches(std::initializer_list<uint32_t> other) const {
156
   // TODO: once all target compilers support it, use std::ranges::equal
157
1
   return std::equal(m_id.begin(), m_id.end(), other.begin(), other.end());
158
1
}
159
160
49
uint64_t OID::hash_code() const {
161
   // If this is changed also update gen_oids.py to match
162
49
   uint64_t hash = 0x621F302327D9A49A;
163
269
   for(auto id : m_id) {
164
269
      hash *= 193;
165
269
      hash += id;
166
269
   }
167
49
   return hash;
168
49
}
169
170
/*
171
* Compare two OIDs
172
*/
173
0
bool operator<(const OID& a, const OID& b) {
174
0
   const std::vector<uint32_t>& oid1 = a.get_components();
175
0
   const std::vector<uint32_t>& oid2 = b.get_components();
176
177
0
   return std::lexicographical_compare(oid1.begin(), oid1.end(), oid2.begin(), oid2.end());
178
0
}
179
180
/*
181
* DER encode an OBJECT IDENTIFIER
182
*/
183
9
void OID::encode_into(DER_Encoder& der) const {
184
9
   if(m_id.size() < 2) {
185
0
      throw Invalid_Argument("OID::encode_into: OID is invalid");
186
0
   }
187
188
36
   auto append = [](std::vector<uint8_t>& encoding, uint32_t z) {
189
36
      if(z <= 0x7F) {
190
27
         encoding.push_back(static_cast<uint8_t>(z));
191
27
      } else {
192
9
         size_t z7 = (high_bit(z) + 7 - 1) / 7;
193
194
27
         for(size_t j = 0; j != z7; ++j) {
195
18
            uint8_t zp = static_cast<uint8_t>(z >> (7 * (z7 - j - 1)) & 0x7F);
196
197
18
            if(j != z7 - 1) {
198
9
               zp |= 0x80;
199
9
            }
200
201
18
            encoding.push_back(zp);
202
18
         }
203
9
      }
204
36
   };
205
206
9
   std::vector<uint8_t> encoding;
207
208
   // We know 40 * root can't overflow because root is between 0 and 2
209
9
   auto first = BOTAN_ASSERT_IS_SOME(checked_add(40 * m_id[0], m_id[1]));
210
211
9
   append(encoding, first);
212
213
36
   for(size_t i = 2; i != m_id.size(); ++i) {
214
27
      append(encoding, m_id[i]);
215
27
   }
216
9
   der.add_object(ASN1_Type::ObjectId, ASN1_Class::Universal, encoding);
217
9
}
218
219
/*
220
* Decode a BER encoded OBJECT IDENTIFIER
221
*/
222
0
void OID::decode_from(BER_Decoder& decoder) {
223
0
   BER_Object obj = decoder.get_next_object();
224
0
   if(obj.tagging() != (ASN1_Class::Universal | ASN1_Type::ObjectId)) {
225
0
      throw BER_Bad_Tag("Error decoding OID, unknown tag", obj.tagging());
226
0
   }
227
228
0
   if(obj.length() == 0) {
229
0
      throw BER_Decoding_Error("OID encoding is too short");
230
0
   }
231
232
0
   auto consume = [](BufferSlicer& data) -> uint32_t {
233
0
      BOTAN_ASSERT_NOMSG(!data.empty());
234
0
      uint32_t b = data.take_byte();
235
236
0
      if(b > 0x7F) {
237
0
         b &= 0x7F;
238
239
         // Even BER requires that the OID have minimal length, ie that
240
         // the first byte of a multibyte encoding cannot be zero
241
         // See X.690 section 8.19.2
242
0
         if(b == 0) {
243
0
            throw Decoding_Error("Leading zero byte in multibyte OID encoding");
244
0
         }
245
246
0
         while(true) {
247
0
            if(data.empty()) {
248
0
               throw Decoding_Error("Truncated OID value");
249
0
            }
250
251
0
            const uint8_t next = data.take_byte();
252
0
            const bool more = (next & 0x80) == 0x80;
253
0
            const uint8_t value = next & 0x7F;
254
255
0
            if((b >> (32 - 7)) != 0) {
256
0
               throw Decoding_Error("OID component overflow");
257
0
            }
258
259
0
            b = (b << 7) | value;
260
261
0
            if(!more) {
262
0
               break;
263
0
            }
264
0
         }
265
0
      }
266
267
0
      return b;
268
0
   };
269
270
0
   BufferSlicer data(obj.data());
271
0
   std::vector<uint32_t> parts;
272
0
   while(!data.empty()) {
273
0
      const uint32_t comp = consume(data);
274
275
0
      if(parts.empty()) {
276
         // divide into root and second arc
277
278
0
         const uint32_t root_arc = [](uint32_t b0) -> uint32_t {
279
0
            if(b0 < 40) {
280
0
               return 0;
281
0
            } else if(b0 < 80) {
282
0
               return 1;
283
0
            } else {
284
0
               return 2;
285
0
            }
286
0
         }(comp);
287
288
0
         parts.push_back(root_arc);
289
0
         BOTAN_ASSERT_NOMSG(comp >= 40 * root_arc);
290
0
         parts.push_back(comp - 40 * root_arc);
291
0
      } else {
292
0
         parts.push_back(comp);
293
0
      }
294
0
   }
295
296
0
   m_id = parts;
297
0
}
298
299
}  // namespace Botan