/src/botan/src/lib/utils/parsing.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Various string utils and parsing functions |
3 | | * (C) 1999-2007,2013,2014,2015,2018 Jack Lloyd |
4 | | * (C) 2015 Simon Warta (Kullo GmbH) |
5 | | * (C) 2017 René Korthaus, Rohde & Schwarz Cybersecurity |
6 | | * |
7 | | * Botan is released under the Simplified BSD License (see license.txt) |
8 | | */ |
9 | | |
10 | | #include <botan/parsing.h> |
11 | | #include <botan/exceptn.h> |
12 | | #include <botan/charset.h> |
13 | | #include <botan/loadstor.h> |
14 | | #include <algorithm> |
15 | | #include <cctype> |
16 | | #include <limits> |
17 | | #include <set> |
18 | | |
19 | | #if defined(BOTAN_HAS_ASN1) |
20 | | #include <botan/asn1_oid.h> |
21 | | #endif |
22 | | |
23 | | namespace Botan { |
24 | | |
25 | | uint16_t to_uint16(const std::string& str) |
26 | 0 | { |
27 | 0 | const uint32_t x = to_u32bit(str); |
28 | 0 |
|
29 | 0 | if(x >> 16) |
30 | 0 | throw Invalid_Argument("Integer value exceeds 16 bit range"); |
31 | 0 | |
32 | 0 | return static_cast<uint16_t>(x); |
33 | 0 | } |
34 | | |
35 | | uint32_t to_u32bit(const std::string& str) |
36 | 2.60M | { |
37 | 2.60M | // std::stoul is not strict enough. Ensure that str is digit only [0-9]* |
38 | 2.60M | for(const char chr : str) |
39 | 3.93M | { |
40 | 3.93M | if(chr < '0' || chr > '9') |
41 | 603 | { |
42 | 603 | std::string chrAsString(1, chr); |
43 | 603 | throw Invalid_Argument("String contains non-digit char: " + chrAsString); |
44 | 603 | } |
45 | 3.93M | } |
46 | 2.60M | |
47 | 2.60M | const unsigned long int x = std::stoul(str); |
48 | 2.60M | |
49 | 2.60M | if(sizeof(unsigned long int) > 4) |
50 | 2.60M | { |
51 | 2.60M | // x might be uint64 |
52 | 2.60M | if (x > std::numeric_limits<uint32_t>::max()) |
53 | 0 | { |
54 | 0 | throw Invalid_Argument("Integer value of " + str + " exceeds 32 bit range"); |
55 | 0 | } |
56 | 2.60M | } |
57 | 2.60M | |
58 | 2.60M | return static_cast<uint32_t>(x); |
59 | 2.60M | } |
60 | | |
61 | | /* |
62 | | * Convert a string into a time duration |
63 | | */ |
64 | | uint32_t timespec_to_u32bit(const std::string& timespec) |
65 | 0 | { |
66 | 0 | if(timespec.empty()) |
67 | 0 | return 0; |
68 | 0 | |
69 | 0 | const char suffix = timespec[timespec.size()-1]; |
70 | 0 | std::string value = timespec.substr(0, timespec.size()-1); |
71 | 0 |
|
72 | 0 | uint32_t scale = 1; |
73 | 0 |
|
74 | 0 | if(Charset::is_digit(suffix)) |
75 | 0 | value += suffix; |
76 | 0 | else if(suffix == 's') |
77 | 0 | scale = 1; |
78 | 0 | else if(suffix == 'm') |
79 | 0 | scale = 60; |
80 | 0 | else if(suffix == 'h') |
81 | 0 | scale = 60 * 60; |
82 | 0 | else if(suffix == 'd') |
83 | 0 | scale = 24 * 60 * 60; |
84 | 0 | else if(suffix == 'y') |
85 | 0 | scale = 365 * 24 * 60 * 60; |
86 | 0 | else |
87 | 0 | throw Decoding_Error("timespec_to_u32bit: Bad input " + timespec); |
88 | 0 | |
89 | 0 | return scale * to_u32bit(value); |
90 | 0 | } |
91 | | |
92 | | /* |
93 | | * Parse a SCAN-style algorithm name |
94 | | */ |
95 | | std::vector<std::string> parse_algorithm_name(const std::string& namex) |
96 | 1.78k | { |
97 | 1.78k | if(namex.find('(') == std::string::npos && |
98 | 1.78k | namex.find(')') == std::string::npos) |
99 | 1.03k | return std::vector<std::string>(1, namex); |
100 | 751 | |
101 | 751 | std::string name = namex, substring; |
102 | 751 | std::vector<std::string> elems; |
103 | 751 | size_t level = 0; |
104 | 751 | |
105 | 751 | elems.push_back(name.substr(0, name.find('('))); |
106 | 751 | name = name.substr(name.find('(')); |
107 | 751 | |
108 | 2.78k | for(auto i = name.begin(); i != name.end(); ++i) |
109 | 2.78k | { |
110 | 2.78k | char c = *i; |
111 | 2.78k | |
112 | 2.78k | if(c == '(') |
113 | 751 | ++level; |
114 | 2.78k | if(c == ')') |
115 | 751 | { |
116 | 751 | if(level == 1 && i == name.end() - 1) |
117 | 751 | { |
118 | 751 | if(elems.size() == 1) |
119 | 751 | elems.push_back(substring.substr(1)); |
120 | 0 | else |
121 | 0 | elems.push_back(substring); |
122 | 751 | return elems; |
123 | 751 | } |
124 | 0 | |
125 | 0 | if(level == 0 || (level == 1 && i != name.end() - 1)) |
126 | 0 | throw Invalid_Algorithm_Name(namex); |
127 | 0 | --level; |
128 | 0 | } |
129 | 2.78k | |
130 | 2.78k | if(c == ',' && level == 1) |
131 | 0 | { |
132 | 0 | if(elems.size() == 1) |
133 | 0 | elems.push_back(substring.substr(1)); |
134 | 0 | else |
135 | 0 | elems.push_back(substring); |
136 | 0 | substring.clear(); |
137 | 0 | } |
138 | 2.03k | else |
139 | 2.03k | substring += c; |
140 | 2.03k | } |
141 | 751 | |
142 | 751 | if(!substring.empty()) |
143 | 0 | throw Invalid_Algorithm_Name(namex); |
144 | 0 | |
145 | 0 | return elems; |
146 | 0 | } |
147 | | |
148 | | std::vector<std::string> split_on(const std::string& str, char delim) |
149 | 33.3k | { |
150 | 287k | return split_on_pred(str, [delim](char c) { return c == delim; }); |
151 | 33.3k | } |
152 | | |
153 | | std::vector<std::string> split_on_pred(const std::string& str, |
154 | | std::function<bool (char)> pred) |
155 | 33.3k | { |
156 | 33.3k | std::vector<std::string> elems; |
157 | 33.3k | if(str.empty()) return elems; |
158 | 30.7k | |
159 | 30.7k | std::string substr; |
160 | 318k | for(auto i = str.begin(); i != str.end(); ++i) |
161 | 287k | { |
162 | 287k | if(pred(*i)) |
163 | 10.8k | { |
164 | 10.8k | if(!substr.empty()) |
165 | 10.8k | elems.push_back(substr); |
166 | 10.8k | substr.clear(); |
167 | 10.8k | } |
168 | 276k | else |
169 | 276k | substr += *i; |
170 | 287k | } |
171 | 30.7k | |
172 | 30.7k | if(substr.empty()) |
173 | 0 | throw Invalid_Argument("Unable to split string: " + str); |
174 | 30.7k | elems.push_back(substr); |
175 | 30.7k | |
176 | 30.7k | return elems; |
177 | 30.7k | } |
178 | | |
179 | | /* |
180 | | * Join a string |
181 | | */ |
182 | | std::string string_join(const std::vector<std::string>& strs, char delim) |
183 | 0 | { |
184 | 0 | std::string out = ""; |
185 | 0 |
|
186 | 0 | for(size_t i = 0; i != strs.size(); ++i) |
187 | 0 | { |
188 | 0 | if(i != 0) |
189 | 0 | out += delim; |
190 | 0 | out += strs[i]; |
191 | 0 | } |
192 | 0 |
|
193 | 0 | return out; |
194 | 0 | } |
195 | | |
196 | | /* |
197 | | * Parse an ASN.1 OID string |
198 | | */ |
199 | | std::vector<uint32_t> parse_asn1_oid(const std::string& oid) |
200 | 0 | { |
201 | 0 | #if defined(BOTAN_HAS_ASN1) |
202 | 0 | return OID(oid).get_components(); |
203 | | #else |
204 | | BOTAN_UNUSED(oid); |
205 | | throw Not_Implemented("ASN1 support not available"); |
206 | | #endif |
207 | | } |
208 | | |
209 | | /* |
210 | | * X.500 String Comparison |
211 | | */ |
212 | | bool x500_name_cmp(const std::string& name1, const std::string& name2) |
213 | 59.0k | { |
214 | 59.0k | auto p1 = name1.begin(); |
215 | 59.0k | auto p2 = name2.begin(); |
216 | 59.0k | |
217 | 61.0k | while((p1 != name1.end()) && Charset::is_space(*p1)) ++p1; |
218 | 63.3k | while((p2 != name2.end()) && Charset::is_space(*p2)) ++p2; |
219 | 59.0k | |
220 | 633k | while(p1 != name1.end() && p2 != name2.end()) |
221 | 576k | { |
222 | 576k | if(Charset::is_space(*p1)) |
223 | 42.6k | { |
224 | 42.6k | if(!Charset::is_space(*p2)) |
225 | 233 | return false; |
226 | 42.4k | |
227 | 86.3k | while((p1 != name1.end()) && Charset::is_space(*p1)) ++p1; |
228 | 86.3k | while((p2 != name2.end()) && Charset::is_space(*p2)) ++p2; |
229 | 42.4k | |
230 | 42.4k | if(p1 == name1.end() && p2 == name2.end()) |
231 | 46 | return true; |
232 | 42.3k | if(p1 == name1.end() || p2 == name2.end()) |
233 | 202 | return false; |
234 | 576k | } |
235 | 576k | |
236 | 576k | if(!Charset::caseless_cmp(*p1, *p2)) |
237 | 2.23k | return false; |
238 | 574k | ++p1; |
239 | 574k | ++p2; |
240 | 574k | } |
241 | 59.0k | |
242 | 59.0k | while((p1 != name1.end()) && Charset::is_space(*p1)) ++p1; |
243 | 57.2k | while((p2 != name2.end()) && Charset::is_space(*p2)) ++p2; |
244 | 56.3k | |
245 | 56.3k | if((p1 != name1.end()) || (p2 != name2.end())) |
246 | 399 | return false; |
247 | 55.9k | return true; |
248 | 55.9k | } |
249 | | |
250 | | /* |
251 | | * Convert a decimal-dotted string to binary IP |
252 | | */ |
253 | | uint32_t string_to_ipv4(const std::string& str) |
254 | 0 | { |
255 | 0 | std::vector<std::string> parts = split_on(str, '.'); |
256 | 0 |
|
257 | 0 | if(parts.size() != 4) |
258 | 0 | throw Decoding_Error("Invalid IP string " + str); |
259 | 0 | |
260 | 0 | uint32_t ip = 0; |
261 | 0 |
|
262 | 0 | for(auto part = parts.begin(); part != parts.end(); ++part) |
263 | 0 | { |
264 | 0 | uint32_t octet = to_u32bit(*part); |
265 | 0 |
|
266 | 0 | if(octet > 255) |
267 | 0 | throw Decoding_Error("Invalid IP string " + str); |
268 | 0 | |
269 | 0 | ip = (ip << 8) | (octet & 0xFF); |
270 | 0 | } |
271 | 0 |
|
272 | 0 | return ip; |
273 | 0 | } |
274 | | |
275 | | /* |
276 | | * Convert an IP address to decimal-dotted string |
277 | | */ |
278 | | std::string ipv4_to_string(uint32_t ip) |
279 | 3.41k | { |
280 | 3.41k | std::string str; |
281 | 3.41k | |
282 | 17.0k | for(size_t i = 0; i != sizeof(ip); ++i) |
283 | 13.6k | { |
284 | 13.6k | if(i) |
285 | 10.2k | str += "."; |
286 | 13.6k | str += std::to_string(get_byte(i, ip)); |
287 | 13.6k | } |
288 | 3.41k | |
289 | 3.41k | return str; |
290 | 3.41k | } |
291 | | |
292 | | std::string erase_chars(const std::string& str, const std::set<char>& chars) |
293 | 0 | { |
294 | 0 | std::string out; |
295 | 0 |
|
296 | 0 | for(auto c: str) |
297 | 0 | if(chars.count(c) == 0) |
298 | 0 | out += c; |
299 | 0 |
|
300 | 0 | return out; |
301 | 0 | } |
302 | | |
303 | | std::string replace_chars(const std::string& str, |
304 | | const std::set<char>& chars, |
305 | | char to_char) |
306 | 0 | { |
307 | 0 | std::string out = str; |
308 | 0 |
|
309 | 0 | for(size_t i = 0; i != out.size(); ++i) |
310 | 0 | if(chars.count(out[i])) |
311 | 0 | out[i] = to_char; |
312 | 0 |
|
313 | 0 | return out; |
314 | 0 | } |
315 | | |
316 | | std::string replace_char(const std::string& str, char from_char, char to_char) |
317 | 0 | { |
318 | 0 | std::string out = str; |
319 | 0 |
|
320 | 0 | for(size_t i = 0; i != out.size(); ++i) |
321 | 0 | if(out[i] == from_char) |
322 | 0 | out[i] = to_char; |
323 | 0 |
|
324 | 0 | return out; |
325 | 0 | } |
326 | | |
327 | | namespace { |
328 | | |
329 | | std::string tolower_string(const std::string& in) |
330 | 0 | { |
331 | 0 | std::string s = in; |
332 | 0 | for(size_t i = 0; i != s.size(); ++i) |
333 | 0 | { |
334 | 0 | const int cu = static_cast<unsigned char>(s[i]); |
335 | 0 | if(std::isalpha(cu)) |
336 | 0 | s[i] = static_cast<char>(std::tolower(cu)); |
337 | 0 | } |
338 | 0 | return s; |
339 | 0 | } |
340 | | |
341 | | } |
342 | | |
343 | | bool host_wildcard_match(const std::string& issued_, const std::string& host_) |
344 | 0 | { |
345 | 0 | const std::string issued = tolower_string(issued_); |
346 | 0 | const std::string host = tolower_string(host_); |
347 | 0 |
|
348 | 0 | if(host.empty() || issued.empty()) |
349 | 0 | return false; |
350 | 0 | |
351 | 0 | /* |
352 | 0 | If there are embedded nulls in your issued name |
353 | 0 | Well I feel bad for you son |
354 | 0 | */ |
355 | 0 | if(std::count(issued.begin(), issued.end(), char(0)) > 0) |
356 | 0 | return false; |
357 | 0 | |
358 | 0 | // If more than one wildcard, then issued name is invalid |
359 | 0 | const size_t stars = std::count(issued.begin(), issued.end(), '*'); |
360 | 0 | if(stars > 1) |
361 | 0 | return false; |
362 | 0 | |
363 | 0 | // '*' is not a valid character in DNS names so should not appear on the host side |
364 | 0 | if(std::count(host.begin(), host.end(), '*') != 0) |
365 | 0 | return false; |
366 | 0 | |
367 | 0 | // Similarly a DNS name can't end in . |
368 | 0 | if(host[host.size() - 1] == '.') |
369 | 0 | return false; |
370 | 0 | |
371 | 0 | // And a host can't have an empty name component, so reject that |
372 | 0 | if(host.find("..") != std::string::npos) |
373 | 0 | return false; |
374 | 0 | |
375 | 0 | // Exact match: accept |
376 | 0 | if(issued == host) |
377 | 0 | { |
378 | 0 | return true; |
379 | 0 | } |
380 | 0 | |
381 | 0 | /* |
382 | 0 | Otherwise it might be a wildcard |
383 | 0 | |
384 | 0 | If the issued size is strictly longer than the hostname size it |
385 | 0 | couldn't possibly be a match, even if the issued value is a |
386 | 0 | wildcard. The only exception is when the wildcard ends up empty |
387 | 0 | (eg www.example.com matches www*.example.com) |
388 | 0 | */ |
389 | 0 | if(issued.size() > host.size() + 1) |
390 | 0 | { |
391 | 0 | return false; |
392 | 0 | } |
393 | 0 | |
394 | 0 | // If no * at all then not a wildcard, and so not a match |
395 | 0 | if(stars != 1) |
396 | 0 | { |
397 | 0 | return false; |
398 | 0 | } |
399 | 0 | |
400 | 0 | /* |
401 | 0 | Now walk through the issued string, making sure every character |
402 | 0 | matches. When we come to the (singular) '*', jump forward in the |
403 | 0 | hostname by the corresponding amount. We know exactly how much |
404 | 0 | space the wildcard takes because it must be exactly `len(host) - |
405 | 0 | len(issued) + 1 chars`. |
406 | 0 | |
407 | 0 | We also verify that the '*' comes in the leftmost component, and |
408 | 0 | doesn't skip over any '.' in the hostname. |
409 | 0 | */ |
410 | 0 | size_t dots_seen = 0; |
411 | 0 | size_t host_idx = 0; |
412 | 0 |
|
413 | 0 | for(size_t i = 0; i != issued.size(); ++i) |
414 | 0 | { |
415 | 0 | dots_seen += (issued[i] == '.'); |
416 | 0 |
|
417 | 0 | if(issued[i] == '*') |
418 | 0 | { |
419 | 0 | // Fail: wildcard can only come in leftmost component |
420 | 0 | if(dots_seen > 0) |
421 | 0 | { |
422 | 0 | return false; |
423 | 0 | } |
424 | 0 | |
425 | 0 | /* |
426 | 0 | Since there is only one * we know the tail of the issued and |
427 | 0 | hostname must be an exact match. In this case advance host_idx |
428 | 0 | to match. |
429 | 0 | */ |
430 | 0 | const size_t advance = (host.size() - issued.size() + 1); |
431 | 0 |
|
432 | 0 | if(host_idx + advance > host.size()) // shouldn't happen |
433 | 0 | return false; |
434 | 0 | |
435 | 0 | // Can't be any intervening .s that we would have skipped |
436 | 0 | if(std::count(host.begin() + host_idx, |
437 | 0 | host.begin() + host_idx + advance, '.') != 0) |
438 | 0 | return false; |
439 | 0 | |
440 | 0 | host_idx += advance; |
441 | 0 | } |
442 | 0 | else |
443 | 0 | { |
444 | 0 | if(issued[i] != host[host_idx]) |
445 | 0 | { |
446 | 0 | return false; |
447 | 0 | } |
448 | 0 | |
449 | 0 | host_idx += 1; |
450 | 0 | } |
451 | 0 | } |
452 | 0 |
|
453 | 0 | // Wildcard issued name must have at least 3 components |
454 | 0 | if(dots_seen < 2) |
455 | 0 | { |
456 | 0 | return false; |
457 | 0 | } |
458 | 0 | |
459 | 0 | return true; |
460 | 0 | } |
461 | | |
462 | | } |