/src/PcapPlusPlus/Packet++/src/Asn1Codec.cpp
Line | Count | Source |
1 | | #define LOG_MODULE PacketLogModuleAsn1Codec |
2 | | |
3 | | #include "Asn1Codec.h" |
4 | | #include "GeneralUtils.h" |
5 | | #include "EndianPortable.h" |
6 | | #include "SystemUtils.h" |
7 | | #include <unordered_map> |
8 | | #include <numeric> |
9 | | #include <algorithm> |
10 | | #include <iostream> |
11 | | #include <iomanip> |
12 | | #include <sstream> |
13 | | #include <cmath> |
14 | | #include <limits> |
15 | | #include <cstring> |
16 | | |
17 | | #if defined(_WIN32) |
18 | | # undef max |
19 | | #endif |
20 | | |
21 | | namespace pcpp |
22 | | { |
23 | | const std::unordered_map<Asn1TagClass, std::string, EnumClassHash<Asn1TagClass>> Asn1TagClassToString{ |
24 | | { Asn1TagClass::Universal, "Universal" }, |
25 | | { Asn1TagClass::ContextSpecific, "ContextSpecific" }, |
26 | | { Asn1TagClass::Application, "Application" }, |
27 | | { Asn1TagClass::Private, "Private" } |
28 | | }; |
29 | | |
30 | | std::string toString(Asn1TagClass tagClass) |
31 | 0 | { |
32 | 0 | if (Asn1TagClassToString.find(tagClass) != Asn1TagClassToString.end()) |
33 | 0 | { |
34 | 0 | return Asn1TagClassToString.at(tagClass); |
35 | 0 | } |
36 | | |
37 | 0 | return "Unknown"; |
38 | 0 | } |
39 | | |
40 | | const std::unordered_map<Asn1UniversalTagType, std::string, EnumClassHash<Asn1UniversalTagType>> |
41 | | Asn1UniversalTagTypeToString{ |
42 | | { Asn1UniversalTagType::EndOfContent, "EndOfContent" }, |
43 | | { Asn1UniversalTagType::Boolean, "Boolean" }, |
44 | | { Asn1UniversalTagType::Integer, "Integer" }, |
45 | | { Asn1UniversalTagType::BitString, "BitString" }, |
46 | | { Asn1UniversalTagType::OctetString, "OctetString" }, |
47 | | { Asn1UniversalTagType::Null, "Null" }, |
48 | | { Asn1UniversalTagType::ObjectIdentifier, "ObjectIdentifier" }, |
49 | | { Asn1UniversalTagType::ObjectDescriptor, "ObjectDescriptor" }, |
50 | | { Asn1UniversalTagType::External, "External" }, |
51 | | { Asn1UniversalTagType::Real, "Real" }, |
52 | | { Asn1UniversalTagType::Enumerated, "Enumerated" }, |
53 | | { Asn1UniversalTagType::EmbeddedPDV, "EmbeddedPDV" }, |
54 | | { Asn1UniversalTagType::UTF8String, "UTF8String" }, |
55 | | { Asn1UniversalTagType::RelativeObjectIdentifier, "RelativeObjectIdentifier" }, |
56 | | { Asn1UniversalTagType::Time, "Time" }, |
57 | | { Asn1UniversalTagType::Reserved, "Reserved" }, |
58 | | { Asn1UniversalTagType::Sequence, "Sequence" }, |
59 | | { Asn1UniversalTagType::Set, "Set" }, |
60 | | { Asn1UniversalTagType::NumericString, "NumericString" }, |
61 | | { Asn1UniversalTagType::PrintableString, "PrintableString" }, |
62 | | { Asn1UniversalTagType::T61String, "T61String" }, |
63 | | { Asn1UniversalTagType::VideotexString, "VideotexString" }, |
64 | | { Asn1UniversalTagType::IA5String, "IA5String" }, |
65 | | { Asn1UniversalTagType::UTCTime, "UTCTime" }, |
66 | | { Asn1UniversalTagType::GeneralizedTime, "GeneralizedTime" }, |
67 | | { Asn1UniversalTagType::GraphicString, "GraphicString" }, |
68 | | { Asn1UniversalTagType::VisibleString, "VisibleString" }, |
69 | | { Asn1UniversalTagType::GeneralString, "GeneralString" }, |
70 | | { Asn1UniversalTagType::UniversalString, "UniversalString" }, |
71 | | { Asn1UniversalTagType::CharacterString, "CharacterString" }, |
72 | | { Asn1UniversalTagType::BMPString, "BMPString" }, |
73 | | { Asn1UniversalTagType::Date, "Date" }, |
74 | | { Asn1UniversalTagType::TimeOfDay, "TimeOfDay" }, |
75 | | { Asn1UniversalTagType::DateTime, "DateTime" }, |
76 | | { Asn1UniversalTagType::Duration, "Duration" }, |
77 | | { Asn1UniversalTagType::ObjectIdentifierIRI, "ObjectIdentifierIRI" }, |
78 | | { Asn1UniversalTagType::RelativeObjectIdentifierIRI, "RelativeObjectIdentifierIRI" }, |
79 | | { Asn1UniversalTagType::NotApplicable, "Unknown" } |
80 | | }; |
81 | | |
82 | | std::string toString(Asn1UniversalTagType tagType) |
83 | 0 | { |
84 | 0 | if (Asn1UniversalTagTypeToString.find(tagType) != Asn1UniversalTagTypeToString.end()) |
85 | 0 | { |
86 | 0 | return Asn1UniversalTagTypeToString.at(tagType); |
87 | 0 | } |
88 | | |
89 | 0 | return "Unknown"; |
90 | 0 | } |
91 | | |
92 | | std::unique_ptr<Asn1Record> Asn1Record::decode(const uint8_t* data, size_t dataLen, bool lazy) |
93 | 1.40M | { |
94 | 1.40M | uint8_t tagLen; |
95 | 1.40M | auto decodedRecord = decodeTagAndCreateRecord(data, dataLen, tagLen); |
96 | | |
97 | 1.40M | uint8_t lengthLen; |
98 | 1.40M | lengthLen = decodedRecord->decodeLength(data + tagLen, dataLen - tagLen); |
99 | | |
100 | 1.40M | decodedRecord->m_TotalLength = tagLen + lengthLen + decodedRecord->m_ValueLength; |
101 | 1.40M | if (decodedRecord->m_TotalLength < decodedRecord->m_ValueLength || // check for overflow |
102 | 1.39M | decodedRecord->m_TotalLength > dataLen) |
103 | 494 | { |
104 | 494 | throw std::invalid_argument("Cannot decode ASN.1 record, data doesn't contain the entire record"); |
105 | 494 | } |
106 | | |
107 | 1.39M | uint8_t const* startOfData = data + tagLen + lengthLen; |
108 | 1.39M | internal::Asn1LoadPolicy policy = lazy ? internal::Asn1LoadPolicy::Lazy : internal::Asn1LoadPolicy::Eager; |
109 | 1.39M | decodedRecord->setEncodedValue(startOfData, policy); |
110 | | |
111 | 1.39M | return decodedRecord; |
112 | 1.40M | } |
113 | | |
114 | | uint8_t Asn1Record::encodeTag() const |
115 | 0 | { |
116 | 0 | uint8_t tagByte; |
117 | |
|
118 | 0 | switch (m_TagClass) |
119 | 0 | { |
120 | 0 | case Asn1TagClass::Private: |
121 | 0 | { |
122 | 0 | tagByte = 0xc0; |
123 | 0 | break; |
124 | 0 | } |
125 | 0 | case Asn1TagClass::ContextSpecific: |
126 | 0 | { |
127 | 0 | tagByte = 0x80; |
128 | 0 | break; |
129 | 0 | } |
130 | 0 | case Asn1TagClass::Application: |
131 | 0 | { |
132 | 0 | tagByte = 0x40; |
133 | 0 | break; |
134 | 0 | } |
135 | 0 | default: |
136 | 0 | { |
137 | 0 | tagByte = 0; |
138 | 0 | break; |
139 | 0 | } |
140 | 0 | } |
141 | | |
142 | 0 | if (m_IsConstructed) |
143 | 0 | { |
144 | 0 | tagByte |= 0x20; |
145 | 0 | } |
146 | |
|
147 | 0 | auto tagType = m_TagType & 0x1f; |
148 | 0 | tagByte |= tagType; |
149 | |
|
150 | 0 | return tagByte; |
151 | 0 | } |
152 | | |
153 | | std::vector<uint8_t> Asn1Record::encodeLength() const |
154 | 0 | { |
155 | 0 | std::vector<uint8_t> result; |
156 | |
|
157 | 0 | if (m_ValueLength < 128) |
158 | 0 | { |
159 | 0 | result.push_back(static_cast<uint8_t>(m_ValueLength)); |
160 | 0 | return result; |
161 | 0 | } |
162 | | |
163 | 0 | auto tempValueLength = m_ValueLength; |
164 | 0 | do |
165 | 0 | { |
166 | 0 | uint8_t byte = tempValueLength & 0xff; |
167 | 0 | result.push_back(byte); // Inserts the bytes in reverse order |
168 | 0 | tempValueLength >>= 8; |
169 | 0 | } while (tempValueLength != 0); |
170 | |
|
171 | 0 | uint8_t firstByte = 0x80 | static_cast<uint8_t>(result.size()); |
172 | 0 | result.push_back(firstByte); |
173 | | |
174 | | // Reverses the bytes to get forward ordering |
175 | 0 | std::reverse(result.begin(), result.end()); |
176 | |
|
177 | 0 | return result; |
178 | 0 | } |
179 | | |
180 | | std::vector<uint8_t> Asn1Record::encode() const |
181 | 0 | { |
182 | 0 | std::vector<uint8_t> result; |
183 | |
|
184 | 0 | result.push_back(encodeTag()); |
185 | |
|
186 | 0 | auto lengthBytes = encodeLength(); |
187 | 0 | result.insert(result.end(), lengthBytes.begin(), lengthBytes.end()); |
188 | |
|
189 | 0 | auto encodedValue = encodeValueSafe(); |
190 | 0 | result.insert(result.end(), encodedValue.begin(), encodedValue.end()); |
191 | |
|
192 | 0 | return result; |
193 | 0 | } |
194 | | |
195 | | Asn1UniversalTagType Asn1Record::getUniversalTagType() const |
196 | 7.26k | { |
197 | 7.26k | if (m_TagClass == Asn1TagClass::Universal) |
198 | 7.25k | { |
199 | 7.25k | return static_cast<Asn1UniversalTagType>(m_TagType); |
200 | 7.25k | } |
201 | | |
202 | 6 | return Asn1UniversalTagType::NotApplicable; |
203 | 7.26k | } |
204 | | |
205 | | std::unique_ptr<Asn1Record> Asn1Record::decodeTagAndCreateRecord(const uint8_t* data, size_t dataLen, |
206 | | uint8_t& tagLen) |
207 | 1.40M | { |
208 | 1.40M | if (dataLen < 1) |
209 | 59 | { |
210 | 59 | throw std::invalid_argument("Cannot decode ASN.1 record tag"); |
211 | 59 | } |
212 | | |
213 | 1.40M | tagLen = 1; |
214 | | |
215 | 1.40M | Asn1TagClass tagClass = Asn1TagClass::Universal; |
216 | | |
217 | | // Check first 2 bits |
218 | 1.40M | auto tagClassBits = data[0] & 0xc0; |
219 | 1.40M | if (tagClassBits == 0) |
220 | 1.38M | { |
221 | 1.38M | tagClass = Asn1TagClass::Universal; |
222 | 1.38M | } |
223 | 11.1k | else if ((tagClassBits & 0xc0) == 0xc0) |
224 | 2.09k | { |
225 | 2.09k | tagClass = Asn1TagClass::Private; |
226 | 2.09k | } |
227 | 9.00k | else if ((tagClassBits & 0x80) == 0x80) |
228 | 5.08k | { |
229 | 5.08k | tagClass = Asn1TagClass::ContextSpecific; |
230 | 5.08k | } |
231 | 3.92k | else if ((tagClassBits & 0x40) == 0x40) |
232 | 3.92k | { |
233 | 3.92k | tagClass = Asn1TagClass::Application; |
234 | 3.92k | } |
235 | | |
236 | | // Check bit 6 |
237 | 1.40M | auto tagTypeBits = data[0] & 0x20; |
238 | 1.40M | bool isConstructed = (tagTypeBits != 0); |
239 | | |
240 | | // Check last 5 bits |
241 | 1.40M | auto tagType = data[0] & 0x1f; |
242 | 1.40M | if (tagType == 0x1f) |
243 | 1.14k | { |
244 | 1.14k | if (dataLen < 2) |
245 | 8 | { |
246 | 8 | throw std::invalid_argument("Cannot decode ASN.1 record tag"); |
247 | 8 | } |
248 | | |
249 | 1.14k | if ((data[1] & 0x80) != 0) |
250 | 40 | { |
251 | 40 | throw std::invalid_argument("ASN.1 tags with value larger than 127 are not supported"); |
252 | 40 | } |
253 | | |
254 | 1.10k | tagType = data[1] & 0x7f; |
255 | 1.10k | tagLen = 2; |
256 | 1.10k | } |
257 | | |
258 | 1.40M | std::unique_ptr<Asn1Record> newRecord; |
259 | | |
260 | 1.40M | if (isConstructed) |
261 | 52.1k | { |
262 | 52.1k | if (tagClass == Asn1TagClass::Universal) |
263 | 45.7k | { |
264 | 45.7k | switch (static_cast<Asn1UniversalTagType>(tagType)) |
265 | 45.7k | { |
266 | 32.9k | case Asn1UniversalTagType::Sequence: |
267 | 32.9k | { |
268 | 32.9k | newRecord.reset(new Asn1SequenceRecord()); |
269 | 32.9k | break; |
270 | 0 | } |
271 | 8.06k | case Asn1UniversalTagType::Set: |
272 | 8.06k | { |
273 | 8.06k | newRecord.reset(new Asn1SetRecord()); |
274 | 8.06k | break; |
275 | 0 | } |
276 | 4.76k | default: |
277 | 4.76k | { |
278 | 4.76k | newRecord.reset(new Asn1ConstructedRecord()); |
279 | 4.76k | } |
280 | 45.7k | } |
281 | 45.7k | } |
282 | 6.37k | else |
283 | 6.37k | { |
284 | 6.37k | newRecord.reset(new Asn1ConstructedRecord()); |
285 | 6.37k | } |
286 | 52.1k | } |
287 | 1.34M | else |
288 | 1.34M | { |
289 | 1.34M | if (tagClass == Asn1TagClass::Universal) |
290 | 1.34M | { |
291 | 1.34M | auto asn1UniversalTagType = static_cast<Asn1UniversalTagType>(tagType); |
292 | 1.34M | switch (asn1UniversalTagType) |
293 | 1.34M | { |
294 | 6.29k | case Asn1UniversalTagType::Integer: |
295 | 6.29k | { |
296 | 6.29k | newRecord.reset(new Asn1IntegerRecord()); |
297 | 6.29k | break; |
298 | 0 | } |
299 | 2.12k | case Asn1UniversalTagType::Enumerated: |
300 | 2.12k | { |
301 | 2.12k | newRecord.reset(new Asn1EnumeratedRecord()); |
302 | 2.12k | break; |
303 | 0 | } |
304 | 13.9k | case Asn1UniversalTagType::OctetString: |
305 | 13.9k | { |
306 | 13.9k | newRecord.reset(new Asn1OctetStringRecord()); |
307 | 13.9k | break; |
308 | 0 | } |
309 | 7.09k | case Asn1UniversalTagType::UTF8String: |
310 | 7.09k | { |
311 | 7.09k | newRecord.reset(new Asn1UTF8StringRecord()); |
312 | 7.09k | break; |
313 | 0 | } |
314 | 5.21k | case Asn1UniversalTagType::PrintableString: |
315 | 5.21k | { |
316 | 5.21k | newRecord.reset(new Asn1PrintableStringRecord()); |
317 | 5.21k | break; |
318 | 0 | } |
319 | 5.28k | case Asn1UniversalTagType::IA5String: |
320 | 5.28k | { |
321 | 5.28k | newRecord.reset(new Asn1IA5StringRecord()); |
322 | 5.28k | break; |
323 | 0 | } |
324 | 3.96k | case Asn1UniversalTagType::Boolean: |
325 | 3.96k | { |
326 | 3.96k | newRecord.reset(new Asn1BooleanRecord()); |
327 | 3.96k | break; |
328 | 0 | } |
329 | 4.54k | case Asn1UniversalTagType::BitString: |
330 | 4.54k | { |
331 | 4.54k | newRecord.reset(new Asn1BitStringRecord()); |
332 | 4.54k | break; |
333 | 0 | } |
334 | 1.03k | case Asn1UniversalTagType::Null: |
335 | 1.03k | { |
336 | 1.03k | newRecord.reset(new Asn1NullRecord()); |
337 | 1.03k | break; |
338 | 0 | } |
339 | 22.4k | case Asn1UniversalTagType::ObjectIdentifier: |
340 | 22.4k | { |
341 | 22.4k | newRecord.reset(new Asn1ObjectIdentifierRecord()); |
342 | 22.4k | break; |
343 | 0 | } |
344 | 6.45k | case Asn1UniversalTagType::UTCTime: |
345 | 6.45k | { |
346 | 6.45k | newRecord.reset(new Asn1UtcTimeRecord()); |
347 | 6.45k | break; |
348 | 0 | } |
349 | 6.17k | case Asn1UniversalTagType::GeneralizedTime: |
350 | 6.17k | { |
351 | 6.17k | newRecord.reset(new Asn1GeneralizedTimeRecord()); |
352 | 6.17k | break; |
353 | 0 | } |
354 | 1.25M | default: |
355 | 1.25M | { |
356 | 1.25M | newRecord.reset(new Asn1GenericRecord()); |
357 | 1.25M | } |
358 | 1.34M | } |
359 | 1.34M | } |
360 | 4.68k | else |
361 | 4.68k | { |
362 | 4.68k | newRecord.reset(new Asn1GenericRecord()); |
363 | 4.68k | } |
364 | 1.34M | } |
365 | | |
366 | 1.40M | newRecord->m_TagClass = tagClass; |
367 | 1.40M | newRecord->m_IsConstructed = isConstructed; |
368 | 1.40M | newRecord->m_TagType = tagType; |
369 | | |
370 | 1.40M | return newRecord; |
371 | 1.40M | } |
372 | | |
373 | | uint8_t Asn1Record::decodeLength(const uint8_t* data, size_t dataLen) |
374 | 1.40M | { |
375 | 1.40M | if (dataLen < 1) |
376 | 188 | { |
377 | 188 | throw std::invalid_argument("Cannot decode ASN.1 record length"); |
378 | 188 | } |
379 | | |
380 | | // Check 8th bit |
381 | 1.39M | auto lengthForm = data[0] & 0x80; |
382 | | |
383 | | // Check if the tag is using more than one byte |
384 | | // 8th bit at 0 means the length only uses one byte |
385 | | // 8th bit at 1 means the length uses more than one byte. The number of bytes is encoded in the other 7 bits |
386 | 1.39M | if (lengthForm == 0) |
387 | 1.39M | { |
388 | 1.39M | m_ValueLength = data[0]; |
389 | 1.39M | return 1; |
390 | 1.39M | } |
391 | | |
392 | 7.29k | uint8_t actualLengthBytes = data[0] & 0x7F; |
393 | 7.29k | const uint8_t* actualLengthData = data + 1; |
394 | | |
395 | 7.29k | if (dataLen < static_cast<size_t>(actualLengthBytes) + 1) |
396 | 75 | { |
397 | 75 | throw std::invalid_argument("Cannot decode ASN.1 record length"); |
398 | 75 | } |
399 | | |
400 | 27.3k | for (int i = 0; i < actualLengthBytes; i++) |
401 | 20.2k | { |
402 | 20.2k | size_t partialValueLength = m_ValueLength << 8; |
403 | 20.2k | if (partialValueLength < m_ValueLength) // check for overflow |
404 | 63 | { |
405 | 63 | throw std::invalid_argument("Cannot decode ASN.1 record length"); |
406 | 63 | } |
407 | | |
408 | 20.1k | m_ValueLength = partialValueLength | actualLengthData[i]; |
409 | 20.1k | } |
410 | | |
411 | 7.15k | return 1 + actualLengthBytes; |
412 | 7.21k | } |
413 | | |
414 | | void Asn1Record::decodeValueIfNeeded() const |
415 | 1.52M | { |
416 | | // TODO: This is not thread-safe and can cause issues in a multiple reader scenario. |
417 | 1.52M | if (m_EncodedValue != nullptr) |
418 | 1.39M | { |
419 | 1.39M | decodeValue(m_EncodedValue); |
420 | 1.39M | m_EncodedValue = nullptr; // Clear the encoded value after decoding |
421 | 1.39M | } |
422 | 1.52M | } |
423 | | |
424 | | std::string Asn1Record::toString() const |
425 | 0 | { |
426 | | // Decode the value if it hasn't been decoded yet to ensure we have the correct value |
427 | 0 | decodeValueIfNeeded(); |
428 | 0 | auto lines = toStringList(); |
429 | |
|
430 | 0 | auto commaSeparated = [](std::string str1, std::string str2) { |
431 | 0 | return std::move(str1) + '\n' + std::move(str2); |
432 | 0 | }; |
433 | |
|
434 | 0 | return std::accumulate(std::next(lines.begin()), lines.end(), lines[0], commaSeparated); |
435 | 0 | } |
436 | | |
437 | | std::vector<std::string> Asn1Record::toStringList() const |
438 | 0 | { |
439 | 0 | std::ostringstream stream; |
440 | |
|
441 | 0 | auto universalType = getUniversalTagType(); |
442 | 0 | if (universalType == Asn1UniversalTagType::NotApplicable) |
443 | 0 | { |
444 | 0 | stream << pcpp::toString(m_TagClass) << " (" << static_cast<int>(m_TagType) << ")"; |
445 | 0 | } |
446 | 0 | else |
447 | 0 | { |
448 | 0 | stream << pcpp::toString(universalType); |
449 | 0 | } |
450 | |
|
451 | 0 | if (m_IsConstructed) |
452 | 0 | { |
453 | 0 | stream << " (constructed)"; |
454 | 0 | } |
455 | |
|
456 | 0 | stream << ", Length: " << m_TotalLength - m_ValueLength << "+" << m_ValueLength; |
457 | |
|
458 | 0 | return { stream.str() }; |
459 | 0 | } |
460 | | |
461 | | void Asn1Record::setEncodedValue(uint8_t const* dataSource, internal::Asn1LoadPolicy loadPolicy) |
462 | 1.39M | { |
463 | 1.39M | m_EncodedValue = dataSource; |
464 | | |
465 | 1.39M | if (loadPolicy == internal::Asn1LoadPolicy::Eager) |
466 | 1.39M | { |
467 | 1.39M | decodeValueIfNeeded(); |
468 | 1.39M | } |
469 | 1.39M | } |
470 | | |
471 | | Asn1GenericRecord::Asn1GenericRecord(Asn1TagClass tagClass, bool isConstructed, uint8_t tagType, |
472 | | const uint8_t* value, size_t valueLen) |
473 | 0 | { |
474 | 0 | init(tagClass, isConstructed, tagType, value, valueLen); |
475 | 0 | } |
476 | | |
477 | | Asn1GenericRecord::Asn1GenericRecord(Asn1TagClass tagClass, bool isConstructed, uint8_t tagType, |
478 | | const std::string& value) |
479 | 0 | { |
480 | 0 | init(tagClass, isConstructed, tagType, reinterpret_cast<const uint8_t*>(value.c_str()), value.size()); |
481 | 0 | } |
482 | | |
483 | | void Asn1GenericRecord::decodeValue(uint8_t const* data) const |
484 | 1.26M | { |
485 | 1.26M | m_Value = std::make_unique<uint8_t[]>(m_ValueLength); |
486 | 1.26M | std::memcpy(m_Value.get(), data, m_ValueLength); |
487 | 1.26M | } |
488 | | |
489 | | std::vector<uint8_t> Asn1GenericRecord::encodeValue() const |
490 | 0 | { |
491 | 0 | return { m_Value.get(), m_Value.get() + m_ValueLength }; |
492 | 0 | } |
493 | | |
494 | | void Asn1GenericRecord::init(Asn1TagClass tagClass, bool isConstructed, uint8_t tagType, const uint8_t* value, |
495 | | size_t valueLen) |
496 | 0 | { |
497 | 0 | m_TagType = tagType; |
498 | 0 | m_TagClass = tagClass; |
499 | 0 | m_IsConstructed = isConstructed; |
500 | 0 | m_Value = std::make_unique<uint8_t[]>(valueLen); |
501 | 0 | std::memcpy(m_Value.get(), value, valueLen); |
502 | 0 | m_ValueLength = valueLen; |
503 | 0 | m_TotalLength = m_ValueLength + 2; |
504 | 0 | } |
505 | | |
506 | | Asn1ConstructedRecord::Asn1ConstructedRecord(Asn1TagClass tagClass, uint8_t tagType, |
507 | | const std::vector<Asn1Record*>& subRecords) |
508 | 0 | { |
509 | 0 | init(tagClass, tagType, subRecords.begin(), subRecords.end()); |
510 | 0 | } |
511 | | |
512 | | Asn1ConstructedRecord::Asn1ConstructedRecord(Asn1TagClass tagClass, uint8_t tagType, |
513 | | const PointerVector<Asn1Record>& subRecords) |
514 | 0 | { |
515 | 0 | init(tagClass, tagType, subRecords.begin(), subRecords.end()); |
516 | 0 | } |
517 | | |
518 | | void Asn1ConstructedRecord::decodeValue(uint8_t const* data) const |
519 | 51.7k | { |
520 | 51.7k | if (!(data || m_ValueLength)) |
521 | 0 | { |
522 | 0 | return; |
523 | 0 | } |
524 | | |
525 | 51.7k | auto value = data; |
526 | 51.7k | auto valueLen = m_ValueLength; |
527 | | |
528 | 1.44M | while (valueLen > 0) |
529 | 1.39M | { |
530 | 1.39M | auto subRecord = Asn1Record::decode(value, valueLen, LazySubRecordDecoding); |
531 | 1.39M | value += subRecord->getTotalLength(); |
532 | 1.39M | valueLen -= subRecord->getTotalLength(); |
533 | | |
534 | 1.39M | m_SubRecords.pushBack(std::move(subRecord)); |
535 | 1.39M | } |
536 | 51.7k | } |
537 | | |
538 | | std::vector<uint8_t> Asn1ConstructedRecord::encodeValue() const |
539 | 0 | { |
540 | 0 | std::vector<uint8_t> result; |
541 | 0 | result.reserve(m_ValueLength); |
542 | |
|
543 | 0 | for (auto record : m_SubRecords) |
544 | 0 | { |
545 | 0 | auto encodedRecord = record->encode(); |
546 | 0 | result.insert(result.end(), std::make_move_iterator(encodedRecord.begin()), |
547 | 0 | std::make_move_iterator(encodedRecord.end())); |
548 | 0 | } |
549 | 0 | return result; |
550 | 0 | } |
551 | | |
552 | | std::vector<std::string> Asn1ConstructedRecord::toStringList() const |
553 | 0 | { |
554 | 0 | std::vector<std::string> result = { Asn1Record::toStringList().front() }; |
555 | 0 | for (auto subRecord : m_SubRecords) |
556 | 0 | { |
557 | 0 | for (const auto& line : subRecord->toStringList()) |
558 | 0 | { |
559 | 0 | result.push_back(" " + line); |
560 | 0 | } |
561 | 0 | } |
562 | 0 | return result; |
563 | 0 | } |
564 | | |
565 | | Asn1SequenceRecord::Asn1SequenceRecord(const std::vector<Asn1Record*>& subRecords) |
566 | 0 | : Asn1ConstructedRecord(Asn1TagClass::Universal, static_cast<uint8_t>(Asn1UniversalTagType::Sequence), |
567 | 0 | subRecords) |
568 | 0 | {} |
569 | | |
570 | | Asn1SequenceRecord::Asn1SequenceRecord(const PointerVector<Asn1Record>& subRecords) |
571 | 0 | : Asn1ConstructedRecord(Asn1TagClass::Universal, static_cast<uint8_t>(Asn1UniversalTagType::Sequence), |
572 | 0 | subRecords) |
573 | 0 | {} |
574 | | |
575 | | Asn1SetRecord::Asn1SetRecord(const std::vector<Asn1Record*>& subRecords) |
576 | 0 | : Asn1ConstructedRecord(Asn1TagClass::Universal, static_cast<uint8_t>(Asn1UniversalTagType::Set), subRecords) |
577 | 0 | {} |
578 | | |
579 | | Asn1SetRecord::Asn1SetRecord(const PointerVector<Asn1Record>& subRecords) |
580 | 0 | : Asn1ConstructedRecord(Asn1TagClass::Universal, static_cast<uint8_t>(Asn1UniversalTagType::Set), subRecords) |
581 | 0 | {} |
582 | | |
583 | 32.5k | Asn1PrimitiveRecord::Asn1PrimitiveRecord(Asn1UniversalTagType tagType) : Asn1Record() |
584 | 32.5k | { |
585 | 32.5k | m_TagType = static_cast<uint8_t>(tagType); |
586 | 32.5k | m_TagClass = Asn1TagClass::Universal; |
587 | 32.5k | m_IsConstructed = false; |
588 | 32.5k | } |
589 | | |
590 | | Asn1IntegerRecord::BigInt::BigInt(const std::string& value) |
591 | 0 | { |
592 | 0 | m_Value = initFromString(value); |
593 | 0 | } |
594 | | |
595 | | Asn1IntegerRecord::BigInt::BigInt(const BigInt& other) |
596 | 0 | { |
597 | 0 | m_Value = other.m_Value; |
598 | 0 | } |
599 | | |
600 | | std::string Asn1IntegerRecord::BigInt::initFromString(const std::string& value) |
601 | 8.38k | { |
602 | 8.38k | std::string valueStr = value; |
603 | | |
604 | | // Optional 0x or 0X prefix |
605 | 8.38k | if (value.size() >= 2 && value[0] == '0' && (value[1] == 'x' || value[1] == 'X')) |
606 | 0 | { |
607 | 0 | valueStr = value.substr(2); |
608 | 0 | } |
609 | | |
610 | 8.38k | if (valueStr.empty()) |
611 | 27 | { |
612 | 27 | throw std::invalid_argument("Value is not a valid hex stream"); |
613 | 27 | } |
614 | | |
615 | 1.00M | if (std::any_of(valueStr.begin(), valueStr.end(), [](char c) { return !std::isxdigit(c); })) |
616 | 0 | { |
617 | 0 | throw std::invalid_argument("Value is not a valid hex stream"); |
618 | 0 | } |
619 | | |
620 | 8.35k | return valueStr; |
621 | 8.35k | } |
622 | | |
623 | | Asn1IntegerRecord::BigInt& Asn1IntegerRecord::BigInt::operator=(const std::string& value) |
624 | 8.38k | { |
625 | 8.38k | m_Value = initFromString(value); |
626 | 8.38k | return *this; |
627 | 8.38k | } |
628 | | |
629 | | size_t Asn1IntegerRecord::BigInt::size() const |
630 | 0 | { |
631 | 0 | return m_Value.size() / 2; |
632 | 0 | } |
633 | | |
634 | | std::string Asn1IntegerRecord::BigInt::toString(bool removeLeadingZeros) const |
635 | 1.99k | { |
636 | 1.99k | if (!removeLeadingZeros) |
637 | 0 | { |
638 | 0 | return m_Value; |
639 | 0 | } |
640 | | |
641 | 1.99k | auto firstNonZero = m_Value.find_first_not_of('0'); |
642 | 1.99k | if (firstNonZero == std::string::npos) |
643 | 70 | { |
644 | 70 | return "0"; |
645 | 70 | } |
646 | | |
647 | 1.92k | return m_Value.substr(firstNonZero); |
648 | 1.99k | } |
649 | | |
650 | | std::vector<uint8_t> Asn1IntegerRecord::BigInt::toBytes() const |
651 | 0 | { |
652 | 0 | std::string value = m_Value; |
653 | 0 | if (m_Value.size() % 2 != 0) |
654 | 0 | { |
655 | 0 | value.insert(0, 1, '0'); |
656 | 0 | } |
657 | |
|
658 | 0 | std::vector<uint8_t> result; |
659 | 0 | for (std::size_t i = 0; i < value.size(); i += 2) |
660 | 0 | { |
661 | 0 | std::string byteStr = value.substr(i, 2); |
662 | 0 | auto byte = static_cast<uint8_t>(std::stoul(byteStr, nullptr, 16)); |
663 | 0 | result.push_back(byte); |
664 | 0 | } |
665 | |
|
666 | 0 | return result; |
667 | 0 | } |
668 | | |
669 | 0 | Asn1IntegerRecord::Asn1IntegerRecord(uint64_t value) : Asn1PrimitiveRecord(Asn1UniversalTagType::Integer) |
670 | 0 | { |
671 | 0 | m_Value = value; |
672 | |
|
673 | 0 | std::size_t length = 0; |
674 | 0 | while (value != 0) |
675 | 0 | { |
676 | 0 | ++length; |
677 | 0 | value >>= 8; |
678 | 0 | } |
679 | 0 | m_ValueLength = length == 0 ? 1 : length; |
680 | |
|
681 | 0 | m_TotalLength = m_ValueLength + 2; |
682 | 0 | } |
683 | | |
684 | 0 | Asn1IntegerRecord::Asn1IntegerRecord(const std::string& value) : Asn1PrimitiveRecord(Asn1UniversalTagType::Integer) |
685 | 0 | { |
686 | 0 | m_Value = value; |
687 | 0 | m_ValueLength = m_Value.size(); |
688 | 0 | m_TotalLength = m_ValueLength + 2; |
689 | 0 | } |
690 | | |
691 | | void Asn1IntegerRecord::decodeValue(uint8_t const* data) const |
692 | 8.38k | { |
693 | 8.38k | m_Value = pcpp::byteArrayToHexString(data, m_ValueLength); |
694 | 8.38k | } |
695 | | |
696 | | std::vector<uint8_t> Asn1IntegerRecord::encodeValue() const |
697 | 0 | { |
698 | 0 | return m_Value.toBytes(); |
699 | 0 | } |
700 | | |
701 | | std::vector<std::string> Asn1IntegerRecord::toStringList() const |
702 | 0 | { |
703 | 0 | auto valueAsString = |
704 | 0 | m_Value.canFit<uint64_t>() ? std::to_string(getIntValue<uint64_t>()) : "0x" + getValueAsString(); |
705 | 0 | return std::vector<std::string>({ Asn1Record::toStringList().front() + ", Value: " + valueAsString }); |
706 | 0 | } |
707 | | |
708 | 0 | Asn1EnumeratedRecord::Asn1EnumeratedRecord(uint32_t value) : Asn1IntegerRecord(value) |
709 | 0 | { |
710 | 0 | m_TagType = static_cast<uint8_t>(Asn1UniversalTagType::Enumerated); |
711 | 0 | } |
712 | | |
713 | 0 | Asn1OctetStringRecord::Asn1OctetStringRecord(const uint8_t* value, size_t valueLength) : m_IsPrintable(false) |
714 | 0 | { |
715 | 0 | m_Value = byteArrayToHexString(value, valueLength); |
716 | 0 | m_ValueLength = valueLength; |
717 | 0 | m_TotalLength = m_ValueLength + 2; |
718 | 0 | } |
719 | | |
720 | | void Asn1OctetStringRecord::decodeValue(uint8_t const* data) const |
721 | 13.8k | { |
722 | 13.8k | auto value = reinterpret_cast<char const*>(data); |
723 | | |
724 | 55.3k | m_IsPrintable = std::all_of(value, value + m_ValueLength, [](char c) { return isprint(0xff & c); }); |
725 | | |
726 | 13.8k | if (m_IsPrintable) |
727 | 2.97k | { |
728 | 2.97k | Asn1StringRecord::decodeValue(data); |
729 | 2.97k | } |
730 | 10.9k | else |
731 | 10.9k | { |
732 | 10.9k | m_Value = byteArrayToHexString(data, m_ValueLength); |
733 | 10.9k | } |
734 | 13.8k | } |
735 | | |
736 | | std::vector<uint8_t> Asn1OctetStringRecord::encodeValue() const |
737 | 0 | { |
738 | 0 | if (m_IsPrintable) |
739 | 0 | { |
740 | 0 | return Asn1StringRecord::encodeValue(); |
741 | 0 | } |
742 | | |
743 | | // converting the hex stream to a byte array. |
744 | | // The byte array size is half the size of the string |
745 | | // i.e "1a2b" (length == 4) becomes {0x1a, 0x2b} (length == 2) |
746 | 0 | auto rawValueSize = static_cast<size_t>(m_Value.size() / 2); |
747 | 0 | std::vector<uint8_t> rawValue; |
748 | 0 | rawValue.resize(rawValueSize); |
749 | 0 | hexStringToByteArray(m_Value, rawValue.data(), rawValueSize); |
750 | 0 | return rawValue; |
751 | 0 | } |
752 | | |
753 | 0 | Asn1BooleanRecord::Asn1BooleanRecord(bool value) : Asn1PrimitiveRecord(Asn1UniversalTagType::Boolean) |
754 | 0 | { |
755 | 0 | m_Value = value; |
756 | 0 | m_ValueLength = 1; |
757 | 0 | m_TotalLength = 3; |
758 | 0 | } |
759 | | |
760 | | void Asn1BooleanRecord::decodeValue(uint8_t const* data) const |
761 | 3.93k | { |
762 | 3.93k | if (m_ValueLength != 1) |
763 | 27 | { |
764 | 27 | throw std::invalid_argument("Cannot decode ASN.1 Boolean record, value length must be 1"); |
765 | 27 | } |
766 | | |
767 | 3.90k | m_Value = data[0] != 0; |
768 | 3.90k | } |
769 | | |
770 | | std::vector<uint8_t> Asn1BooleanRecord::encodeValue() const |
771 | 0 | { |
772 | 0 | uint8_t byte = (m_Value ? 0xff : 0x00); |
773 | 0 | return { byte }; |
774 | 0 | } |
775 | | |
776 | | std::vector<std::string> Asn1BooleanRecord::toStringList() const |
777 | 0 | { |
778 | 0 | return { Asn1Record::toStringList().front() + ", Value: " + (getValue() ? "true" : "false") }; |
779 | 0 | } |
780 | | |
781 | 1.03k | Asn1NullRecord::Asn1NullRecord() : Asn1PrimitiveRecord(Asn1UniversalTagType::Null) |
782 | 1.03k | { |
783 | 1.03k | m_ValueLength = 0; |
784 | 1.03k | m_TotalLength = 2; |
785 | 1.03k | } |
786 | | |
787 | | Asn1ObjectIdentifier::Asn1ObjectIdentifier(const uint8_t* data, size_t dataLen) |
788 | 22.3k | { |
789 | | // A description of OID encoding can be found here: |
790 | | // https://learn.microsoft.com/en-us/windows/win32/seccertenroll/about-object-identifier?redirectedfrom=MSDN |
791 | | |
792 | 22.3k | if (!data || dataLen == 0) |
793 | 5 | { |
794 | 5 | throw std::invalid_argument("Malformed OID: Not enough bytes for the first component"); |
795 | 5 | } |
796 | | |
797 | 22.3k | size_t currentByteIndex = 0; |
798 | 22.3k | std::vector<uint32_t> components; |
799 | | |
800 | 22.3k | uint8_t firstByte = data[currentByteIndex++]; |
801 | | // Decode the first byte: first_component * 40 + second_component |
802 | 22.3k | components.push_back(static_cast<uint32_t>(firstByte / 40)); |
803 | 22.3k | components.push_back(static_cast<uint32_t>(firstByte % 40)); |
804 | | |
805 | 22.3k | uint32_t currentComponentValue = 0; |
806 | 22.3k | bool componentStarted = false; |
807 | | |
808 | | // Process remaining bytes using base-128 encoding |
809 | 530k | while (currentByteIndex < dataLen) |
810 | 508k | { |
811 | 508k | uint8_t byte = data[currentByteIndex++]; |
812 | | |
813 | | // Shift previous bits left by 7 and append lower 7 bits |
814 | 508k | currentComponentValue = (currentComponentValue << 7) | (byte & 0x7f); |
815 | 508k | componentStarted = true; |
816 | | |
817 | | // If the MSB is 0, this is the final byte of the current value |
818 | 508k | if ((byte & 0x80) == 0) |
819 | 460k | { |
820 | 460k | components.push_back(currentComponentValue); |
821 | 460k | currentComponentValue = 0; |
822 | 460k | componentStarted = false; |
823 | 460k | } |
824 | 508k | } |
825 | | |
826 | 22.3k | if (componentStarted) |
827 | 15 | { |
828 | 15 | throw std::invalid_argument("Malformed OID: Incomplete component at end of data"); |
829 | 15 | } |
830 | | |
831 | 22.3k | m_Components = components; |
832 | 22.3k | } |
833 | | |
834 | | Asn1ObjectIdentifier::Asn1ObjectIdentifier(const std::string& oidString) |
835 | 0 | { |
836 | 0 | std::vector<uint32_t> components; |
837 | 0 | std::istringstream stream(oidString); |
838 | 0 | std::string token; |
839 | |
|
840 | 0 | while (std::getline(stream, token, '.')) |
841 | 0 | { |
842 | 0 | if (token.empty()) |
843 | 0 | { |
844 | 0 | throw std::invalid_argument("Malformed OID: empty component"); |
845 | 0 | } |
846 | | |
847 | 0 | unsigned long long value; |
848 | 0 | try |
849 | 0 | { |
850 | 0 | value = std::stoull(token); |
851 | 0 | } |
852 | 0 | catch (const std::exception&) |
853 | 0 | { |
854 | 0 | throw std::invalid_argument("Malformed OID: invalid component"); |
855 | 0 | } |
856 | | |
857 | 0 | if (value > std::numeric_limits<uint32_t>::max()) |
858 | 0 | { |
859 | 0 | throw std::invalid_argument("Malformed OID: component out of uint32_t range"); |
860 | 0 | } |
861 | | |
862 | 0 | components.push_back(static_cast<uint32_t>(value)); |
863 | 0 | } |
864 | | |
865 | 0 | if (components.size() < 2) |
866 | 0 | { |
867 | 0 | throw std::invalid_argument("Malformed OID: an OID must have at least two components"); |
868 | 0 | } |
869 | | |
870 | 0 | if (components[0] > 2) |
871 | 0 | { |
872 | 0 | throw std::invalid_argument("Malformed OID: first component must be 0, 1, or 2"); |
873 | 0 | } |
874 | | |
875 | 0 | if ((components[0] == 0 || components[0] == 1) && components[1] >= 40) |
876 | 0 | { |
877 | 0 | throw std::invalid_argument( |
878 | 0 | "Malformed OID: second component must be less than 40 when first component is 0 or 1"); |
879 | 0 | } |
880 | | |
881 | 0 | m_Components = components; |
882 | 0 | } |
883 | | |
884 | | std::string Asn1ObjectIdentifier::toString() const |
885 | 16.1k | { |
886 | 16.1k | if (m_Components.empty()) |
887 | 0 | { |
888 | 0 | return ""; |
889 | 0 | } |
890 | | |
891 | 16.1k | std::ostringstream stream; |
892 | 16.1k | stream << m_Components[0]; |
893 | | |
894 | 88.7k | for (size_t i = 1; i < m_Components.size(); ++i) |
895 | 72.6k | { |
896 | 72.6k | stream << "." << m_Components[i]; |
897 | 72.6k | } |
898 | 16.1k | return stream.str(); |
899 | 16.1k | } |
900 | | |
901 | | std::vector<uint8_t> Asn1ObjectIdentifier::toBytes() const |
902 | 0 | { |
903 | | // A description of OID encoding can be found here: |
904 | | // https://learn.microsoft.com/en-us/windows/win32/seccertenroll/about-object-identifier?redirectedfrom=MSDN |
905 | |
|
906 | 0 | if (m_Components.size() < 2) |
907 | 0 | { |
908 | 0 | throw std::runtime_error("OID must have at least two components to encode."); |
909 | 0 | } |
910 | | |
911 | 0 | std::vector<uint8_t> encoded; |
912 | | |
913 | | // Encode the first two components into one byte |
914 | 0 | uint32_t firstComponent = m_Components[0]; |
915 | 0 | uint32_t secondComponent = m_Components[1]; |
916 | 0 | encoded.push_back(static_cast<uint8_t>(firstComponent * 40 + secondComponent)); |
917 | | |
918 | | // Encode remaining components using base-128 encoding |
919 | 0 | for (size_t i = 2; i < m_Components.size(); ++i) |
920 | 0 | { |
921 | 0 | uint32_t currentComponent = m_Components[i]; |
922 | 0 | std::vector<uint8_t> temp; |
923 | | |
924 | | // At least one byte must be generated even if value is 0 |
925 | 0 | do |
926 | 0 | { |
927 | 0 | temp.push_back(static_cast<uint8_t>(currentComponent & 0x7F)); |
928 | 0 | currentComponent >>= 7; |
929 | 0 | } while (currentComponent > 0); |
930 | | |
931 | | // Set continuation bits (MSB) for all but the last byte |
932 | 0 | for (size_t j = temp.size(); j-- > 0;) |
933 | 0 | { |
934 | 0 | uint8_t byte = temp[j]; |
935 | 0 | if (j != 0) |
936 | 0 | { |
937 | 0 | byte |= 0x80; |
938 | 0 | } |
939 | 0 | encoded.push_back(byte); |
940 | 0 | } |
941 | 0 | } |
942 | |
|
943 | 0 | return encoded; |
944 | 0 | } |
945 | | |
946 | | Asn1ObjectIdentifierRecord::Asn1ObjectIdentifierRecord(const Asn1ObjectIdentifier& value) |
947 | 0 | : Asn1PrimitiveRecord(Asn1UniversalTagType::ObjectIdentifier) |
948 | 0 | { |
949 | 0 | m_Value = value; |
950 | 0 | m_ValueLength = value.toBytes().size(); |
951 | 0 | m_TotalLength = m_ValueLength + 2; |
952 | 0 | } |
953 | | |
954 | | void Asn1ObjectIdentifierRecord::decodeValue(uint8_t const* data) const |
955 | 22.3k | { |
956 | 22.3k | m_Value = Asn1ObjectIdentifier(data, m_ValueLength); |
957 | 22.3k | } |
958 | | |
959 | | std::vector<uint8_t> Asn1ObjectIdentifierRecord::encodeValue() const |
960 | 0 | { |
961 | 0 | return m_Value.toBytes(); |
962 | 0 | } |
963 | | |
964 | | std::vector<std::string> Asn1ObjectIdentifierRecord::toStringList() const |
965 | 0 | { |
966 | 0 | return { Asn1Record::toStringList().front() + ", Value: " + getValue().toString() }; |
967 | 0 | } |
968 | | |
969 | | Asn1TimeRecord::Asn1TimeRecord(Asn1UniversalTagType tagType, const std::chrono::system_clock::time_point& value, |
970 | | const std::string& timezone) |
971 | 0 | : Asn1PrimitiveRecord(tagType) |
972 | 0 | { |
973 | 0 | validateTimezone(timezone); |
974 | 0 | m_Value = adjustTimezones(value, timezone, "Z"); |
975 | 0 | } |
976 | | |
977 | | std::string Asn1TimeRecord::getValueAsString(const std::string& format, const std::string& timezone, |
978 | | bool includeMilliseconds) const |
979 | 1.48k | { |
980 | 1.48k | auto value = getValue(timezone); |
981 | 1.48k | auto timeValue = std::chrono::system_clock::to_time_t(value); |
982 | 1.48k | auto tmValue = *std::gmtime(&timeValue); |
983 | | |
984 | 1.48k | std::ostringstream osstream; |
985 | 1.48k | osstream << std::put_time(&tmValue, format.c_str()); |
986 | | |
987 | 1.48k | if (includeMilliseconds) |
988 | 0 | { |
989 | 0 | auto milliseconds = |
990 | 0 | std::chrono::duration_cast<std::chrono::milliseconds>(value.time_since_epoch()).count() % 1000; |
991 | 0 | if (milliseconds != 0) |
992 | 0 | { |
993 | 0 | osstream << "." << std::setw(3) << std::setfill('0') << milliseconds; |
994 | 0 | } |
995 | 0 | } |
996 | | |
997 | 1.48k | if (timezone != "Z") |
998 | 0 | { |
999 | 0 | osstream << " UTC" << timezone; |
1000 | 0 | } |
1001 | | |
1002 | 1.48k | return osstream.str(); |
1003 | 1.48k | } |
1004 | | |
1005 | | std::vector<std::string> Asn1TimeRecord::toStringList() const |
1006 | 0 | { |
1007 | 0 | return { Asn1Record::toStringList().front() + ", Value: " + getValueAsString("%Y-%m-%d %H:%M:%S", "Z", true) }; |
1008 | 0 | } |
1009 | | |
1010 | | void Asn1TimeRecord::validateTimezone(const std::string& timezone) |
1011 | 14.7k | { |
1012 | 14.7k | if (timezone == "Z") |
1013 | 12.5k | { |
1014 | 12.5k | return; |
1015 | 12.5k | } |
1016 | | |
1017 | 2.17k | if (timezone.length() != 5 || (timezone[0] != '+' && timezone[0] != '-') || !std::isdigit(timezone[1]) || |
1018 | 2.08k | !std::isdigit(timezone[2]) || !std::isdigit(timezone[3]) || !std::isdigit(timezone[4])) |
1019 | 131 | { |
1020 | 131 | throw std::invalid_argument("Invalid timezone format. Use 'Z' or '+/-HHMM'."); |
1021 | 131 | } |
1022 | 2.17k | } |
1023 | | |
1024 | | std::chrono::system_clock::time_point Asn1TimeRecord::adjustTimezones( |
1025 | | const std::chrono::system_clock::time_point& value, const std::string& fromTimezone, |
1026 | | const std::string& toTimezone) |
1027 | 7.41k | { |
1028 | 7.41k | validateTimezone(fromTimezone); |
1029 | 7.41k | validateTimezone(toTimezone); |
1030 | | |
1031 | 7.41k | int fromOffsetSeconds = 0; |
1032 | 7.41k | if (fromTimezone != "Z") |
1033 | 2.04k | { |
1034 | 2.04k | int fromSign = (fromTimezone[0] == '+') ? 1 : -1; |
1035 | 2.04k | auto fromHours = std::stoi(fromTimezone.substr(1, 2)); |
1036 | 2.04k | auto fromMinutes = std::stoi(fromTimezone.substr(3, 2)); |
1037 | 2.04k | fromOffsetSeconds = fromSign * (fromHours * 3600 + fromMinutes * 60); |
1038 | 2.04k | } |
1039 | | |
1040 | 7.41k | int toOffsetSeconds = 0; |
1041 | 7.41k | if (toTimezone != "Z") |
1042 | 0 | { |
1043 | 0 | int toSign = (toTimezone[0] == '+') ? 1 : -1; |
1044 | 0 | auto toHours = std::stoi(toTimezone.substr(1, 2)); |
1045 | 0 | auto toMinutes = std::stoi(toTimezone.substr(3, 2)); |
1046 | 0 | toOffsetSeconds = toSign * (toHours * 3600 + toMinutes * 60); |
1047 | 0 | } |
1048 | | |
1049 | 7.41k | return value + std::chrono::seconds(toOffsetSeconds - fromOffsetSeconds); |
1050 | 7.41k | } |
1051 | | |
1052 | | Asn1UtcTimeRecord::Asn1UtcTimeRecord(const std::chrono::system_clock::time_point& value, bool withSeconds) |
1053 | 0 | : Asn1TimeRecord(Asn1UniversalTagType::UTCTime, value, "Z"), m_WithSeconds(withSeconds) |
1054 | 0 | { |
1055 | 0 | m_ValueLength = 11; |
1056 | 0 | if (withSeconds) |
1057 | 0 | { |
1058 | 0 | m_ValueLength += 2; |
1059 | 0 | } |
1060 | |
|
1061 | 0 | m_TotalLength = m_ValueLength + 2; |
1062 | 0 | } |
1063 | | |
1064 | | void Asn1UtcTimeRecord::decodeValue(uint8_t const* data) const |
1065 | 6.43k | { |
1066 | 6.43k | std::string timeString(reinterpret_cast<const char*>(data), m_ValueLength); |
1067 | | |
1068 | 6.43k | if (timeString.empty()) |
1069 | 4 | { |
1070 | 4 | throw std::runtime_error("Failed to parse ASN.1 UTC time"); |
1071 | 4 | } |
1072 | | |
1073 | 6.43k | if (timeString.back() == 'Z') |
1074 | 2.89k | { |
1075 | 2.89k | timeString.pop_back(); |
1076 | 2.89k | } |
1077 | | |
1078 | 6.43k | m_WithSeconds = true; |
1079 | 6.43k | if (timeString.size() == 10) |
1080 | 229 | { |
1081 | 229 | m_WithSeconds = false; |
1082 | 229 | timeString.append("00"); |
1083 | 229 | } |
1084 | | |
1085 | 6.43k | auto year = std::stoi(timeString.substr(0, 2)); |
1086 | 6.43k | if (year <= 50) |
1087 | 5.95k | { |
1088 | 5.95k | timeString.insert(0, "20"); |
1089 | 5.95k | } |
1090 | 483 | else |
1091 | 483 | { |
1092 | 483 | timeString.insert(0, "19"); |
1093 | 483 | } |
1094 | | |
1095 | 6.43k | std::tm tm = {}; |
1096 | 6.43k | std::istringstream sstream(timeString); |
1097 | 6.43k | sstream >> std::get_time(&tm, "%Y%m%d%H%M%S"); |
1098 | | |
1099 | 6.43k | if (sstream.fail()) |
1100 | 41 | { |
1101 | 41 | throw std::runtime_error("Failed to parse ASN.1 UTC time"); |
1102 | 41 | } |
1103 | | |
1104 | 6.39k | std::time_t timeValue = mkUtcTime(tm); |
1105 | 6.39k | m_Value = std::chrono::system_clock::from_time_t(timeValue); |
1106 | 6.39k | } |
1107 | | |
1108 | | std::vector<uint8_t> Asn1UtcTimeRecord::encodeValue() const |
1109 | 0 | { |
1110 | 0 | auto timeValue = std::chrono::system_clock::to_time_t(m_Value); |
1111 | |
|
1112 | 0 | auto tm = *std::gmtime(&timeValue); |
1113 | |
|
1114 | 0 | auto pattern = std::string("%y%m%d%H%M") + (m_WithSeconds ? "%S" : ""); |
1115 | 0 | std::ostringstream osstream; |
1116 | 0 | osstream << std::put_time(&tm, pattern.c_str()) << 'Z'; |
1117 | |
|
1118 | 0 | auto timeString = osstream.str(); |
1119 | 0 | return { timeString.begin(), timeString.end() }; |
1120 | 0 | } |
1121 | | |
1122 | | Asn1GeneralizedTimeRecord::Asn1GeneralizedTimeRecord(const std::chrono::system_clock::time_point& value, |
1123 | | const std::string& timezone) |
1124 | 0 | : Asn1TimeRecord(Asn1UniversalTagType::GeneralizedTime, value, timezone), m_Timezone(timezone) |
1125 | 0 | { |
1126 | 0 | m_ValueLength = 14 + (timezone == "Z" ? 1 : 5); |
1127 | |
|
1128 | 0 | auto milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(value.time_since_epoch()).count(); |
1129 | 0 | if (milliseconds % 1000 != 0) |
1130 | 0 | { |
1131 | 0 | m_ValueLength += 4; |
1132 | 0 | } |
1133 | |
|
1134 | 0 | m_TotalLength = m_ValueLength + 2; |
1135 | 0 | } |
1136 | | |
1137 | | void Asn1GeneralizedTimeRecord::decodeValue(uint8_t const* data) const |
1138 | 6.15k | { |
1139 | 6.15k | std::string timeString(reinterpret_cast<const char*>(data), m_ValueLength); |
1140 | | |
1141 | 6.15k | if (timeString.empty()) |
1142 | 6 | { |
1143 | 6 | throw std::runtime_error("Failed to parse ASN.1 generalized time"); |
1144 | 6 | } |
1145 | | |
1146 | 6.14k | std::string timezone = "Z"; |
1147 | 6.14k | auto timezonePos = timeString.find_first_of("+-"); |
1148 | 6.14k | if (timeString.back() == 'Z') |
1149 | 595 | { |
1150 | 595 | timeString.pop_back(); |
1151 | 595 | } |
1152 | 5.54k | else if (timezonePos != std::string::npos) |
1153 | 2.29k | { |
1154 | 2.29k | timezone = timeString.substr(timezonePos); |
1155 | 2.29k | timeString.erase(timezonePos); |
1156 | 2.29k | } |
1157 | | |
1158 | 6.14k | std::tm tm = {}; |
1159 | 6.14k | std::istringstream sstream(timeString); |
1160 | 6.14k | sstream >> std::get_time(&tm, "%Y%m%d%H%M%S"); |
1161 | | |
1162 | 6.14k | if (sstream.fail()) |
1163 | 152 | { |
1164 | 152 | throw std::runtime_error("Failed to parse ASN.1 generalized time"); |
1165 | 152 | } |
1166 | | |
1167 | 5.99k | size_t dotPos = timeString.find('.'); |
1168 | 5.99k | int milliseconds = 0; |
1169 | 5.99k | if (dotPos != std::string::npos) |
1170 | 2.37k | { |
1171 | | // The digits after the dot are a decimal fraction of a second, so ".5" is 500 milliseconds. |
1172 | | // Pad or truncate them to 3 digits to read them as milliseconds |
1173 | 2.37k | std::string millisecondsStr = timeString.substr(dotPos + 1); |
1174 | 2.37k | millisecondsStr.resize(3, '0'); |
1175 | 2.37k | milliseconds = std::stoi(millisecondsStr); |
1176 | 2.37k | } |
1177 | | |
1178 | 5.99k | auto timeValue = mkUtcTime(tm); |
1179 | | |
1180 | 5.99k | m_Timezone = timezone; |
1181 | 5.99k | m_Value = adjustTimezones( |
1182 | 5.99k | std::chrono::system_clock::from_time_t(timeValue) + std::chrono::milliseconds(milliseconds), timezone, "Z"); |
1183 | 5.99k | } |
1184 | | |
1185 | | std::vector<uint8_t> Asn1GeneralizedTimeRecord::encodeValue() const |
1186 | 0 | { |
1187 | 0 | auto value = adjustTimezones(m_Value, "Z", m_Timezone); |
1188 | 0 | auto timeValue = std::chrono::system_clock::to_time_t(value); |
1189 | |
|
1190 | 0 | auto tm = *std::gmtime(&timeValue); |
1191 | |
|
1192 | 0 | auto pattern = std::string("%Y%m%d%H%M%S"); |
1193 | 0 | std::ostringstream osstream; |
1194 | 0 | osstream << std::put_time(&tm, pattern.c_str()); |
1195 | |
|
1196 | 0 | auto milliseconds = |
1197 | 0 | std::chrono::duration_cast<std::chrono::milliseconds>(value.time_since_epoch()).count() % 1000; |
1198 | 0 | if (milliseconds != 0) |
1199 | 0 | { |
1200 | 0 | osstream << "." << std::setw(3) << std::setfill('0') << milliseconds; |
1201 | 0 | } |
1202 | |
|
1203 | 0 | osstream << m_Timezone; |
1204 | |
|
1205 | 0 | auto timeString = osstream.str(); |
1206 | 0 | return { timeString.begin(), timeString.end() }; |
1207 | 0 | } |
1208 | | |
1209 | | void Asn1BitStringRecord::BitSet::initFromString(const std::string& value) |
1210 | 0 | { |
1211 | 0 | m_NumBits = value.length(); |
1212 | |
|
1213 | 0 | size_t numBytes = (m_NumBits + 7) / 8; |
1214 | 0 | m_Data.clear(); |
1215 | 0 | m_Data.reserve(numBytes); |
1216 | |
|
1217 | 0 | size_t i = 0; |
1218 | 0 | while (i < value.length()) |
1219 | 0 | { |
1220 | 0 | std::string curByteString = value.substr(i, 8); |
1221 | 0 | curByteString.append(8 - curByteString.length(), '0'); |
1222 | 0 | try |
1223 | 0 | { |
1224 | 0 | std::bitset<8> bs(curByteString); |
1225 | 0 | m_Data.push_back(bs); |
1226 | 0 | i += 8; |
1227 | 0 | } |
1228 | 0 | catch (const std::invalid_argument&) |
1229 | 0 | { |
1230 | 0 | throw std::invalid_argument("Invalid bit string"); |
1231 | 0 | } |
1232 | 0 | } |
1233 | 0 | } |
1234 | | |
1235 | | Asn1BitStringRecord::BitSet::BitSet(const std::string& value) |
1236 | 0 | { |
1237 | 0 | initFromString(value); |
1238 | 0 | } |
1239 | | |
1240 | 3.93k | Asn1BitStringRecord::BitSet::BitSet(const uint8_t* data, size_t numBits) : m_NumBits(numBits) |
1241 | 3.93k | { |
1242 | 3.93k | if (!data || !numBits) |
1243 | 0 | { |
1244 | 0 | throw std::invalid_argument("Provided data is null or num of bits is 0"); |
1245 | 0 | } |
1246 | | |
1247 | 3.93k | size_t requiredBytes = (m_NumBits + 7) / 8; |
1248 | 3.93k | m_Data.resize(requiredBytes); |
1249 | 3.93k | std::copy_n(data, requiredBytes, m_Data.begin()); |
1250 | 3.93k | } |
1251 | | |
1252 | | Asn1BitStringRecord::BitSet& Asn1BitStringRecord::BitSet::operator=(const std::string& value) |
1253 | 0 | { |
1254 | 0 | initFromString(value); |
1255 | 0 | return *this; |
1256 | 0 | } |
1257 | | |
1258 | | std::string Asn1BitStringRecord::BitSet::toString() const |
1259 | 359 | { |
1260 | 359 | std::string result; |
1261 | 359 | result.reserve(m_Data.size() * 8); |
1262 | 359 | for (const auto bs : m_Data) |
1263 | 4.05k | { |
1264 | 4.05k | result += bs.to_string(); |
1265 | 4.05k | } |
1266 | 359 | result.resize(m_NumBits); |
1267 | 359 | return result; |
1268 | 359 | } |
1269 | | |
1270 | | std::vector<uint8_t> Asn1BitStringRecord::BitSet::toBytes() const |
1271 | 3.16k | { |
1272 | 3.16k | std::vector<uint8_t> result; |
1273 | 3.16k | result.reserve(m_Data.size()); |
1274 | 3.16k | for (const auto& bs : m_Data) |
1275 | 329k | { |
1276 | 329k | result.push_back(static_cast<uint8_t>(bs.to_ulong())); |
1277 | 329k | } |
1278 | | |
1279 | 3.16k | return result; |
1280 | 3.16k | } |
1281 | | |
1282 | | size_t Asn1BitStringRecord::BitSet::sizeInBytes() const |
1283 | 0 | { |
1284 | 0 | return m_Data.size(); |
1285 | 0 | } |
1286 | | |
1287 | | Asn1BitStringRecord::Asn1BitStringRecord(const std::string& value) |
1288 | 0 | : Asn1PrimitiveRecord(Asn1UniversalTagType::BitString) |
1289 | 0 | { |
1290 | 0 | m_Value = value; |
1291 | 0 | m_ValueLength = m_Value.sizeInBytes() + 1; |
1292 | 0 | m_TotalLength = m_ValueLength + 2; |
1293 | 0 | } |
1294 | | |
1295 | | void Asn1BitStringRecord::decodeValue(uint8_t const* data) const |
1296 | 4.51k | { |
1297 | | // A BitString value must contain at least the initial octet holding the number of unused bits |
1298 | 4.51k | if (m_ValueLength == 0) |
1299 | 6 | { |
1300 | 6 | throw std::invalid_argument("Cannot decode ASN.1 BitString record, value is empty"); |
1301 | 6 | } |
1302 | | |
1303 | 4.50k | auto unusedBits = static_cast<size_t>(data[0]); |
1304 | | |
1305 | | // The number of unused bits must be in the range 0-7, and must be 0 if the bit string is empty |
1306 | 4.50k | if (unusedBits > 7 || (m_ValueLength == 1 && unusedBits != 0)) |
1307 | 24 | { |
1308 | 24 | throw std::invalid_argument("Cannot decode ASN.1 BitString record, invalid number of unused bits"); |
1309 | 24 | } |
1310 | | |
1311 | 4.48k | auto numBits = (m_ValueLength - 1) * 8 - unusedBits; |
1312 | 4.48k | if (numBits == 0) |
1313 | 550 | { |
1314 | 550 | m_Value = BitSet(); |
1315 | 550 | return; |
1316 | 550 | } |
1317 | | |
1318 | 3.93k | m_Value = BitSet(data + 1, numBits); |
1319 | 3.93k | } |
1320 | | |
1321 | | std::vector<uint8_t> Asn1BitStringRecord::encodeValue() const |
1322 | 0 | { |
1323 | 0 | auto result = m_Value.toBytes(); |
1324 | 0 | size_t unusedBits = m_Value.sizeInBytes() * 8 - m_Value.getNumBits(); |
1325 | 0 | result.insert(result.begin(), static_cast<uint8_t>(unusedBits)); |
1326 | 0 | return result; |
1327 | 0 | } |
1328 | | |
1329 | | std::vector<std::string> Asn1BitStringRecord::toStringList() const |
1330 | 0 | { |
1331 | 0 | return { Asn1Record::toStringList().front() + ", Value: " + m_Value.toString() }; |
1332 | 0 | } |
1333 | | |
1334 | | } // namespace pcpp |