• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

randombit / botan / 29388558087

14 Jul 2026 06:10PM UTC coverage: 91.656% (+2.3%) from 89.387%
29388558087

push

github

web-flow
Merge pull request #5722 from randombit/jack/python-fixes

Fix some errors in the Python binding

116292 of 126879 relevant lines covered (91.66%)

10459987.7 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.3
/src/tests/test_asn1.cpp
1
/*
2
* (C) 2017 Jack Lloyd
3
*
4
* Botan is released under the Simplified BSD License (see license.txt)
5
*/
6

7
#include "tests.h"
8

9
#if defined(BOTAN_HAS_ASN1)
10
   #include <botan/asn1_obj.h>
11
   #include <botan/asn1_print.h>
12
   #include <botan/asn1_time.h>
13
   #include <botan/ber_dec.h>
14
   #include <botan/bigint.h>
15
   #include <botan/data_src.h>
16
   #include <botan/der_enc.h>
17
   #include <botan/hex.h>
18
   #include <botan/pss_params.h>
19
   #include <botan/internal/fmt.h>
20
   #include <botan/internal/parsing.h>
21
#endif
22

23
namespace Botan_Tests {
24

25
namespace {
26

27
#if defined(BOTAN_HAS_ASN1)
28

29
class ASN1_Test_Sequence final : public Botan::ASN1_Object {
2✔
30
   public:
31
      explicit ASN1_Test_Sequence(size_t value = 0) : m_value(value) {}
2✔
32

33
      void encode_into(Botan::DER_Encoder& der) const override { der.start_sequence().encode(m_value).end_cons(); }
1✔
34

35
      void decode_from(Botan::BER_Decoder& ber) override { ber.start_sequence().decode(m_value).end_cons(); }
1✔
36

37
      size_t value() const { return m_value; }
1✔
38

39
   private:
40
      size_t m_value;
41
};
42

43
Test::Result test_ber_stack_recursion() {
1✔
44
   Test::Result result("BER stack recursion");
1✔
45

46
   // OSS-Fuzz #813 GitHub #989
47

48
   try {
1✔
49
      const std::vector<uint8_t> in(10000000, 0);
1✔
50
      Botan::DataSource_Memory input(in.data(), in.size());
1✔
51
      Botan::BER_Decoder dec(input);
1✔
52

53
      while(dec.more_items()) {
1✔
54
         Botan::BER_Object obj;
1✔
55
         dec.get_next(obj);
1✔
56
      }
1✔
57
   } catch(Botan::Decoding_Error&) {}
3✔
58

59
   result.test_success("No crash");
1✔
60

61
   return result;
1✔
62
}
×
63

64
Test::Result test_ber_eoc_decoding_limits() {
1✔
65
   Test::Result result("BER nested indefinite length");
1✔
66

67
   // OSS-Fuzz #4353
68

69
   const Botan::ASN1_Pretty_Printer printer;
1✔
70

71
   size_t max_eoc_allowed = 0;
1✔
72

73
   for(size_t len = 1; len < 1024; ++len) {
17✔
74
      std::vector<uint8_t> buf(4 * len);
17✔
75

76
      /*
77
      This constructs a len deep sequence of SEQUENCES each with
78
      an indefinite length
79
      */
80
      for(size_t i = 0; i != 2 * len; i += 2) {
170✔
81
         buf[i] = 0x30;
153✔
82
         buf[i + 1] = 0x80;
153✔
83
      }
84
      // remainder of values left as zeros (EOC markers)
85

86
      try {
17✔
87
         printer.print(buf);
17✔
88
      } catch(Botan::BER_Decoding_Error&) {
1✔
89
         max_eoc_allowed = len - 1;
1✔
90
         break;
1✔
91
      }
1✔
92
   }
17✔
93

94
   result.test_sz_eq("EOC limited to prevent stack exhaustion", max_eoc_allowed, 16);
1✔
95

96
   return result;
1✔
97
}
1✔
98

99
Test::Result test_ber_standalone_eoc_limits() {
1✔
100
   Test::Result result("BER standalone EOC handling");
1✔
101

102
   // Empty SEQUENCE (30 00) followed by a standalone EOC marker (00 00) that
103
   // does not terminate any indefinite-length encoding.
104
   const std::vector<uint8_t> wire = {0x30, 0x00, 0x00, 0x00};
1✔
105

106
   auto count_objects = [](const std::vector<uint8_t>& in, Botan::BER_Decoder::Limits limits) {
6✔
107
      Botan::BER_Decoder dec(in, limits);
5✔
108
      size_t objects = 0;
5✔
109
      while(dec.more_items()) {
8✔
110
         if(dec.get_next_object().is_set()) {
7✔
111
            objects += 1;
2✔
112
         }
113
      }
114
      return objects;
1✔
115
   };
5✔
116

117
   // A standalone EOC marker is rejected by default
118
   result.test_throws<Botan::Decoding_Error>("standalone EOC rejected by default",
1✔
119
                                             [&]() { count_objects(wire, Botan::BER_Decoder::Limits::BER()); });
2✔
120

121
   // ... but tolerated when the decoder is configured to allow it
122
   try {
1✔
123
      const size_t objects = count_objects(wire, Botan::BER_Decoder::Limits::BER().with_standalone_eoc_allowed());
1✔
124
      result.test_sz_eq("standalone EOC skipped when allowed", objects, 1);
1✔
125
   } catch(const std::exception& e) {
×
126
      result.test_failure(Botan::fmt("standalone EOC unexpectedly rejected: {}", e.what()));
×
127
   }
×
128

129
   // A constructed encoding of the EOC tag (20 00) is not an EOC marker at
130
   // all; it is rejected in every mode, including with the leniency flag
131
   const std::vector<uint8_t> cons_eoc = {0x20, 0x00};
1✔
132

133
   for(auto limits : {Botan::BER_Decoder::Limits::DER(),
1✔
134
                      Botan::BER_Decoder::Limits::BER(),
1✔
135
                      Botan::BER_Decoder::Limits::BER().with_standalone_eoc_allowed()}) {
4✔
136
      result.test_throws<Botan::Decoding_Error>("constructed EOC tag rejected",
3✔
137
                                                [&]() { count_objects(cons_eoc, limits); });
9✔
138
   }
139

140
   return result;
1✔
141
}
1✔
142

143
Test::Result test_ber_max_object_size() {
1✔
144
   Test::Result result("BER maximum object size");
1✔
145

146
   // OCTET STRING with 5 content bytes
147
   const std::vector<uint8_t> obj = {0x04, 0x05, 0x01, 0x02, 0x03, 0x04, 0x05};
1✔
148

149
   auto decode = [&](const char* what, Botan::BER_Decoder::Limits limits, bool expect_ok) {
5✔
150
      try {
4✔
151
         Botan::BER_Decoder(obj, limits).get_next_object();
4✔
152
         result.test_bool_eq(what, true, expect_ok);
3✔
153
      } catch(const Botan::Decoding_Error&) {
1✔
154
         result.test_bool_eq(what, false, expect_ok);
1✔
155
      }
1✔
156
   };
4✔
157

158
   using Limits = Botan::BER_Decoder::Limits;
1✔
159

160
   decode("accepted under the default limit", Limits::BER(), true);
1✔
161
   decode("accepted at exactly the limit", Limits::BER().with_max_object_size(5), true);
1✔
162
   decode("rejected one byte over the limit", Limits::BER().with_max_object_size(4), false);
1✔
163
   decode("accepted when the limit is disabled", Limits::BER().with_max_object_size(std::nullopt), true);
1✔
164

165
   return result;
1✔
166
}
1✔
167

168
Test::Result test_ber_constructed_string_decoding() {
1✔
169
   Test::Result result("BER constructed OCTET/BIT STRING decoding");
1✔
170

171
   using Botan::ASN1_Class;
1✔
172
   using Botan::ASN1_Type;
1✔
173

174
   const auto ber = Botan::BER_Decoder::Limits::BER();
1✔
175

176
   // Constructed OCTET STRING wrapping two fragments plus an empty one
177
   const std::vector<uint8_t> cons_octet = {0x24, 0x09, 0x04, 0x02, 0xAA, 0xBB, 0x04, 0x00, 0x04, 0x01, 0xCC};
1✔
178
   // The same value using indefinite length
179
   const std::vector<uint8_t> cons_octet_indef = {
1✔
180
      0x24, 0x80, 0x04, 0x02, 0xAA, 0xBB, 0x04, 0x00, 0x04, 0x01, 0xCC, 0x00, 0x00};
1✔
181
   // The same value with a nested constructed segment holding the last fragment
182
   const std::vector<uint8_t> cons_octet_nested = {
1✔
183
      0x24, 0x0B, 0x04, 0x02, 0xAA, 0xBB, 0x24, 0x80, 0x04, 0x01, 0xCC, 0x00, 0x00};
1✔
184
   const std::vector<uint8_t> expected_octets = {0xAA, 0xBB, 0xCC};
1✔
185

186
   for(const auto& input : {cons_octet, cons_octet_indef, cons_octet_nested}) {
8✔
187
      std::vector<uint8_t> out;
3✔
188
      Botan::BER_Decoder(input, ber).decode(out, ASN1_Type::OctetString).verify_end();
6✔
189
      result.test_bin_eq("constructed OCTET STRING concatenated", out, expected_octets);
3✔
190
   }
7✔
191

192
   // An implicitly tagged [0] constructed OCTET STRING
193
   const std::vector<uint8_t> cons_octet_implicit = {0xA0, 0x07, 0x04, 0x02, 0xAA, 0xBB, 0x04, 0x01, 0xCC};
1✔
194
   std::vector<uint8_t> implicit_out;
1✔
195
   Botan::BER_Decoder(cons_octet_implicit, ber)
2✔
196
      .decode(implicit_out, ASN1_Type::OctetString, ASN1_Type(0), ASN1_Class::ContextSpecific)
1✔
197
      .verify_end();
1✔
198
   result.test_bin_eq("implicitly tagged constructed OCTET STRING", implicit_out, expected_octets);
1✔
199

200
   // Constructed BIT STRING: 8 bits then 4 bits, so the final segment
201
   // carries 4 unused bits
202
   const std::vector<uint8_t> cons_bits = {0x23, 0x08, 0x03, 0x02, 0x00, 0xAA, 0x03, 0x02, 0x04, 0xB0};
1✔
203
   Botan::ASN1_BitString bs;
1✔
204
   Botan::BER_Decoder(cons_bits, ber).decode_bitstring(bs).verify_end();
1✔
205
   result.test_sz_eq("constructed BIT STRING length", bs.bit_length(), 12);
1✔
206
   result.test_bin_eq("constructed BIT STRING value", bs.bytes(), std::vector<uint8_t>{0xAA, 0xB0});
1✔
207

208
   std::vector<uint8_t> bits_out;
1✔
209
   Botan::BER_Decoder(cons_bits, ber).decode(bits_out, ASN1_Type::BitString).verify_end();
2✔
210
   result.test_bin_eq("constructed BIT STRING via vector decode", bits_out, std::vector<uint8_t>{0xAA, 0xB0});
1✔
211

212
   // A constructed string with no segments is an empty string
213
   const std::vector<uint8_t> cons_empty = {0x24, 0x00};
1✔
214
   std::vector<uint8_t> empty_out = {0xFF};
1✔
215
   Botan::BER_Decoder(cons_empty, ber).decode(empty_out, ASN1_Type::OctetString).verify_end();
3✔
216
   result.test_sz_eq("empty constructed OCTET STRING", empty_out.size(), 0);
1✔
217

218
   // Unused bits are only permitted in the final segment (X.690 8.6.4)
219
   const std::vector<uint8_t> bad_bits = {0x23, 0x08, 0x03, 0x02, 0x04, 0xA0, 0x03, 0x02, 0x00, 0xBB};
1✔
220
   result.test_throws<Botan::Decoding_Error>("unused bits before final segment rejected", [&]() {
1✔
221
      Botan::ASN1_BitString out;
1✔
222
      Botan::BER_Decoder(bad_bits, ber).decode_bitstring(out);
2✔
223
   });
×
224

225
   // Segments must have the same string type as the outer object
226
   const std::vector<uint8_t> bad_segment = {0x24, 0x04, 0x03, 0x02, 0x00, 0xAA};
1✔
227
   result.test_throws<Botan::Decoding_Error>("wrong segment type rejected", [&]() {
1✔
228
      std::vector<uint8_t> out;
1✔
229
      Botan::BER_Decoder(bad_segment, ber).decode(out, ASN1_Type::OctetString);
2✔
230
   });
1✔
231

232
   // Nesting beyond the limit is rejected. This value must match ALLOWED_CONSTRUCTED_STRING_NESTING
233
   // in ber_dec.cpp
234
   constexpr size_t expected_constr_nesting_allowed = 2;
1✔
235

236
   for(size_t depth = 0; depth != 16; ++depth) {
17✔
237
      std::vector<uint8_t> deep = {0x04, 0x01, 0xAA};
16✔
238
      for(size_t i = 0; i != depth; ++i) {
136✔
239
         std::vector<uint8_t> wrapped = {0x24, static_cast<uint8_t>(deep.size())};
120✔
240
         wrapped.insert(wrapped.end(), deep.begin(), deep.end());
120✔
241
         deep = std::move(wrapped);
120✔
242
      }
120✔
243

244
      if(depth <= expected_constr_nesting_allowed) {
16✔
245
         std::vector<uint8_t> out;
3✔
246
         Botan::BER_Decoder(deep, ber).decode(out, ASN1_Type::OctetString);
6✔
247
         result.test_success("BER_Decoder accepted nested encoding");
3✔
248
         result.test_bin_eq("Constructed matched expected value", out, "AA");
3✔
249
      } else {
3✔
250
         result.test_throws<Botan::Decoding_Error>("deeply nested constructed string rejected", [&]() {
13✔
251
            std::vector<uint8_t> out;
13✔
252
            Botan::BER_Decoder(deep, ber).decode(out, ASN1_Type::OctetString);
26✔
253
         });
13✔
254
      }
255
   }
16✔
256

257
   // DER requires the primitive form, in every code path
258
   const auto der = Botan::BER_Decoder::Limits::DER();
1✔
259
   result.test_throws<Botan::Decoding_Error>("constructed OCTET STRING rejected in DER", [&]() {
1✔
260
      std::vector<uint8_t> out;
1✔
261
      Botan::BER_Decoder(cons_octet, der)
2✔
262
         .decode(out, ASN1_Type::OctetString, ASN1_Type::OctetString, ASN1_Class::Constructed);
1✔
263
   });
1✔
264
   result.test_throws<Botan::Decoding_Error>("constructed BIT STRING rejected in DER", [&]() {
1✔
265
      std::vector<uint8_t> out;
1✔
266
      Botan::BER_Decoder(cons_bits, der)
2✔
267
         .decode(out, ASN1_Type::BitString, ASN1_Type::BitString, ASN1_Class::Constructed);
1✔
268
   });
1✔
269
   result.test_throws<Botan::Decoding_Error>("constructed BIT STRING rejected in DER via decode_bitstring", [&]() {
1✔
270
      Botan::ASN1_BitString out;
1✔
271
      Botan::BER_Decoder(cons_bits, der).decode_bitstring(out, ASN1_Type::BitString, ASN1_Class::Constructed);
2✔
272
   });
×
273

274
   return result;
1✔
275
}
1✔
276

277
Test::Result test_asn1_utf8_ascii_parsing() {
1✔
278
   Test::Result result("ASN.1 ASCII parsing");
1✔
279

280
   try {
1✔
281
      // \x13 - ASN1 tag for 'printable string'
282
      // \x06 - 6 characters of payload
283
      // ...  - UTF-8 encoded (ASCII chars only) word 'Moscow'
284
      const std::string moscow = "\x13\x06\x4D\x6F\x73\x63\x6F\x77";
1✔
285
      const std::string moscow_plain = "Moscow";
1✔
286
      Botan::DataSource_Memory input(moscow);
1✔
287
      Botan::BER_Decoder dec(input);
1✔
288

289
      Botan::ASN1_String str;
1✔
290
      str.decode_from(dec);
1✔
291

292
      result.test_str_eq("value()", str.value(), moscow_plain);
1✔
293
   } catch(const Botan::Decoding_Error& ex) {
2✔
294
      result.test_failure(ex.what());
×
295
   }
×
296

297
   return result;
1✔
298
}
×
299

300
Test::Result test_asn1_utf8_parsing() {
1✔
301
   Test::Result result("ASN.1 UTF-8 parsing");
1✔
302

303
   try {
1✔
304
      // \x0C - ASN1 tag for 'UTF8 string'
305
      // \x0C - 12 characters of payload
306
      // ...  - UTF-8 encoded russian word for Moscow in cyrillic script
307
      const std::string moscow = "\x0C\x0C\xD0\x9C\xD0\xBE\xD1\x81\xD0\xBA\xD0\xB2\xD0\xB0";
1✔
308
      const std::string moscow_plain = "\xD0\x9C\xD0\xBE\xD1\x81\xD0\xBA\xD0\xB2\xD0\xB0";
1✔
309
      Botan::DataSource_Memory input(moscow);
1✔
310
      Botan::BER_Decoder dec(input);
1✔
311

312
      Botan::ASN1_String str;
1✔
313
      str.decode_from(dec);
1✔
314

315
      result.test_str_eq("value()", str.value(), moscow_plain);
1✔
316
   } catch(const Botan::Decoding_Error& ex) {
2✔
317
      result.test_failure(ex.what());
×
318
   }
×
319

320
   return result;
1✔
321
}
×
322

323
Test::Result test_asn1_ucs2_parsing() {
1✔
324
   Test::Result result("ASN.1 BMP string (UCS-2) parsing");
1✔
325

326
   try {
1✔
327
      // \x1E     - ASN1 tag for 'BMP (UCS-2) string'
328
      // \x0C     - 12 characters of payload
329
      // ...      - UCS-2 encoding for Moscow in cyrillic script
330
      const std::string moscow = "\x1E\x0C\x04\x1C\x04\x3E\x04\x41\x04\x3A\x04\x32\x04\x30";
1✔
331
      const std::string moscow_plain = "\xD0\x9C\xD0\xBE\xD1\x81\xD0\xBA\xD0\xB2\xD0\xB0";
1✔
332

333
      Botan::DataSource_Memory input(moscow);
1✔
334
      Botan::BER_Decoder dec(input);
1✔
335

336
      Botan::ASN1_String str;
1✔
337
      str.decode_from(dec);
1✔
338

339
      result.test_str_eq("value()", str.value(), moscow_plain);
1✔
340
   } catch(const Botan::Decoding_Error& ex) {
2✔
341
      result.test_failure(ex.what());
×
342
   }
×
343

344
   return result;
1✔
345
}
×
346

347
Test::Result test_asn1_ucs4_parsing() {
1✔
348
   Test::Result result("ASN.1 universal string (UCS-4) parsing");
1✔
349

350
   try {
1✔
351
      // \x1C - ASN1 tag for 'universal string'
352
      // \x18 - 24 characters of payload
353
      // ...  - UCS-4 encoding for Moscow in cyrillic script
354
      const uint8_t moscow[] =
1✔
355
         "\x1C\x18\x00\x00\x04\x1C\x00\x00\x04\x3E\x00\x00\x04\x41\x00\x00\x04\x3A\x00\x00\x04\x32\x00\x00\x04\x30";
356
      const std::string moscow_plain = "\xD0\x9C\xD0\xBE\xD1\x81\xD0\xBA\xD0\xB2\xD0\xB0";
1✔
357
      Botan::DataSource_Memory input(moscow, sizeof(moscow));
1✔
358
      Botan::BER_Decoder dec(input);
1✔
359

360
      Botan::ASN1_String str;
1✔
361
      str.decode_from(dec);
1✔
362

363
      result.test_str_eq("value()", str.value(), moscow_plain);
1✔
364
   } catch(const Botan::Decoding_Error& ex) {
2✔
365
      result.test_failure(ex.what());
×
366
   }
×
367

368
   return result;
1✔
369
}
×
370

371
Test::Result test_asn1_ucs_invalid_codepoint_rejection() {
1✔
372
   Test::Result result("ASN.1 UCS-2/UCS-4 invalid codepoint rejection");
1✔
373

374
   auto expect_decode_throws = [&](const char* what, const std::vector<uint8_t>& wire) {
7✔
375
      result.test_throws(what, [&]() {
6✔
376
         Botan::DataSource_Memory input(wire.data(), wire.size());
6✔
377
         Botan::BER_Decoder dec(input);
6✔
378
         Botan::ASN1_String str;
6✔
379
         str.decode_from(dec);
6✔
380
      });
6✔
381
   };
6✔
382

383
   auto expect_decode_ok = [&](const char* what, const std::vector<uint8_t>& wire) {
2✔
384
      try {
1✔
385
         Botan::DataSource_Memory input(wire.data(), wire.size());
1✔
386
         Botan::BER_Decoder dec(input);
1✔
387
         Botan::ASN1_String str;
1✔
388
         str.decode_from(dec);
1✔
389
         result.test_success(what);
1✔
390
      } catch(const std::exception& ex) {
2✔
391
         result.test_failure(Botan::fmt("{}: unexpected throw: {}", what, ex.what()));
×
392
      }
×
393
   };
1✔
394

395
   // UniversalString (tag 0x1C) with codepoint 0x00110000 - one past Unicode max
396
   expect_decode_throws("UniversalString rejects codepoint > 0x10FFFF", {0x1C, 0x04, 0x00, 0x11, 0x00, 0x00});
1✔
397

398
   // UniversalString with codepoint 0xFFFFFFFF (clearly out of range)
399
   expect_decode_throws("UniversalString rejects codepoint 0xFFFFFFFF", {0x1C, 0x04, 0xFF, 0xFF, 0xFF, 0xFF});
1✔
400

401
   // UniversalString with high surrogate 0xD800
402
   expect_decode_throws("UniversalString rejects surrogate codepoint", {0x1C, 0x04, 0x00, 0x00, 0xD8, 0x00});
1✔
403

404
   // UniversalString boundary case: 0x10FFFF is the highest valid codepoint
405
   expect_decode_ok("UniversalString accepts codepoint 0x10FFFF", {0x1C, 0x04, 0x00, 0x10, 0xFF, 0xFF});
1✔
406

407
   // BmpString (tag 0x1E) with high surrogate
408
   expect_decode_throws("BmpString rejects surrogate codepoint", {0x1E, 0x02, 0xD8, 0x00});
1✔
409

410
   // BmpString with odd length is malformed
411
   expect_decode_throws("BmpString rejects odd-length payload", {0x1E, 0x03, 0x00, 0x41, 0x00});
1✔
412

413
   // UniversalString with non-multiple-of-4 length is malformed
414
   expect_decode_throws("UniversalString rejects non-multiple-of-4 payload",
1✔
415
                        {0x1C, 0x05, 0x00, 0x00, 0x00, 0x41, 0x00});
416

417
   return result;
1✔
418
}
×
419

420
Test::Result test_asn1_ascii_encoding() {
1✔
421
   Test::Result result("ASN.1 ASCII encoding");
1✔
422

423
   try {
1✔
424
      // UTF-8 encoded (ASCII chars only) word 'Moscow'
425
      const std::string moscow = "Moscow";
1✔
426
      const Botan::ASN1_String str(moscow);
1✔
427

428
      Botan::DER_Encoder enc;
1✔
429

430
      str.encode_into(enc);
1✔
431
      auto encodingResult = enc.get_contents();
1✔
432

433
      // \x13 - ASN1 tag for 'printable string'
434
      // \x06 - 6 characters of payload
435
      result.test_bin_eq("encoding result", encodingResult, "13064D6F73636F77");
1✔
436

437
      result.test_success("No crash");
1✔
438
   } catch(const std::exception& ex) {
2✔
439
      result.test_failure(ex.what());
×
440
   }
×
441

442
   return result;
1✔
443
}
×
444

445
Test::Result test_asn1_utf8_encoding() {
1✔
446
   Test::Result result("ASN.1 UTF-8 encoding");
1✔
447

448
   try {
1✔
449
      // UTF-8 encoded russian word for Moscow in cyrillic script
450
      const std::string moscow = "\xD0\x9C\xD0\xBE\xD1\x81\xD0\xBA\xD0\xB2\xD0\xB0";
1✔
451
      const Botan::ASN1_String str(moscow);
1✔
452

453
      Botan::DER_Encoder enc;
1✔
454

455
      str.encode_into(enc);
1✔
456
      auto encodingResult = enc.get_contents();
1✔
457

458
      // \x0C - ASN1 tag for 'UTF8 string'
459
      // \x0C - 12 characters of payload
460
      result.test_bin_eq("encoding result", encodingResult, "0C0CD09CD0BED181D0BAD0B2D0B0");
1✔
461

462
      result.test_success("No crash");
1✔
463
   } catch(const std::exception& ex) {
2✔
464
      result.test_failure(ex.what());
×
465
   }
×
466

467
   return result;
1✔
468
}
×
469

470
Test::Result test_asn1_tag_underlying_type() {
1✔
471
   Test::Result result("ASN.1 class and type underlying type");
1✔
472

473
   if constexpr(std::is_same_v<std::underlying_type_t<Botan::ASN1_Class>, std::underlying_type_t<Botan::ASN1_Type>>) {
1✔
474
      if constexpr(!std::is_same_v<std::underlying_type_t<Botan::ASN1_Class>,
1✔
475
                                   std::invoke_result_t<decltype(&Botan::BER_Object::tagging), Botan::BER_Object>>) {
476
         result.test_failure(
477
            "Return type of BER_Object::tagging() is different than the underlying type of ASN1_Class");
478
      } else {
479
         result.test_success("Same types");
1✔
480
      }
481
   } else {
482
      result.test_failure("ASN1_Class and ASN1_Type have different underlying types");
483
   }
484

485
   return result;
1✔
486
}
×
487

488
Test::Result test_asn1_high_tag_number() {
1✔
489
   Test::Result result("ASN.1 high tag number encode/decode");
1✔
490

491
   // The encoder emits high-tag-number encodings for the full uint32_t range,
492
   // so the decoder must round trip the same range.
493
   const std::vector<uint8_t> content = {0x01, 0x02, 0x03};
1✔
494

495
   const uint32_t tags[] = {31, 0x7FFFFFFF, 0x80000000, 0xFFFFFFFE, 0xFFFFFFFF};
1✔
496

497
   for(const uint32_t tag : tags) {
6✔
498
      Botan::DER_Encoder enc;
5✔
499
      // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange)
500
      enc.add_object(static_cast<Botan::ASN1_Type>(tag), Botan::ASN1_Class::ContextSpecific, content);
5✔
501
      const auto der = enc.get_contents_unlocked();
5✔
502

503
      try {
5✔
504
         const Botan::BER_Object obj = Botan::BER_Decoder(der).get_next_object();
5✔
505
         result.test_sz_eq("decoded tag matches encoded tag", static_cast<uint32_t>(obj.type()), tag);
5✔
506
      } catch(const std::exception& e) {
5✔
507
         result.test_failure(Botan::fmt("tag {} unexpectedly rejected: {}", tag, e.what()));
×
508
      }
×
509
   }
5✔
510

511
   // A tag value that does not fit in uint32_t (here 2^32, encoded as
512
   // 1F 90 80 80 80 00) must be rejected rather than silently truncated.
513
   const std::vector<uint8_t> overflow_tag = {0x1F, 0x90, 0x80, 0x80, 0x80, 0x00};
1✔
514
   result.test_throws<Botan::Decoding_Error>("over-uint32 tag rejected",
1✔
515
                                             [&]() { Botan::BER_Decoder(overflow_tag).get_next_object(); });
2✔
516

517
   return result;
1✔
518
}
1✔
519

520
Test::Result test_asn1_negative_int_encoding() {
1✔
521
   Test::Result result("DER encode/decode of negative integers");
1✔
522

523
   BigInt n(32);
1✔
524

525
   for(size_t i = 0; i != 2048; ++i) {
2,049✔
526
      n--;
2,048✔
527

528
      const auto enc = Botan::DER_Encoder().encode(n).get_contents_unlocked();
2,048✔
529

530
      BigInt n_dec;
2,048✔
531
      Botan::BER_Decoder(enc, Botan::BER_Decoder::Limits::DER()).decode(n_dec);
4,096✔
532

533
      result.test_bn_eq("DER encoding round trips negative integers", n_dec, n);
2,048✔
534
   }
2,048✔
535

536
   return result;
1✔
537
}
1✔
538

539
Test::Result test_der_set_ordering() {
1✔
540
   Test::Result result("DER SET ordering validation");
1✔
541

542
   using Limits = Botan::BER_Decoder::Limits;
1✔
543

544
   // SET { INTEGER 1, INTEGER 2 } - canonically sorted
545
   const std::vector<uint8_t> sorted_set = {0x31, 0x06, 0x02, 0x01, 0x01, 0x02, 0x01, 0x02};
1✔
546
   // SET { INTEGER 2, INTEGER 1 } - elements out of order
547
   const std::vector<uint8_t> unsorted_set = {0x31, 0x06, 0x02, 0x01, 0x02, 0x02, 0x01, 0x01};
1✔
548

549
   auto decode_set = [](const std::vector<uint8_t>& wire, Limits limits) {
5✔
550
      Botan::BER_Decoder dec(wire, limits);
4✔
551
      Botan::BER_Decoder set = dec.start_set();
4✔
552
      while(set.more_items()) {
9✔
553
         Botan::BigInt v;
6✔
554
         set.decode(v);
6✔
555
      }
6✔
556
      set.end_cons();
3✔
557
   };
4✔
558

559
   // A sorted SET is accepted in both modes
560
   try {
1✔
561
      decode_set(sorted_set, Limits::DER());
1✔
562
      decode_set(sorted_set, Limits::BER());
1✔
563
      result.test_success("sorted SET accepted");
1✔
564
   } catch(const std::exception& e) {
×
565
      result.test_failure(Botan::fmt("sorted SET unexpectedly rejected: {}", e.what()));
×
566
   }
×
567

568
   // An unsorted SET is rejected in DER mode ...
569
   result.test_throws<Botan::Decoding_Error>("unsorted SET rejected in DER mode",
1✔
570
                                             [&]() { decode_set(unsorted_set, Limits::DER()); });
2✔
571

572
   // ... but accepted in BER mode (canonical ordering is a DER requirement)
573
   try {
1✔
574
      decode_set(unsorted_set, Limits::BER());
1✔
575
      result.test_success("unsorted SET accepted in BER mode");
1✔
576
   } catch(const std::exception& e) {
×
577
      result.test_failure(Botan::fmt("unsorted SET unexpectedly rejected in BER mode: {}", e.what()));
×
578
   }
×
579

580
   return result;
1✔
581
}
1✔
582

583
Test::Result test_der_constructed_tag_17_not_sorted() {
1✔
584
   Test::Result result("DER constructed [17] is not SET-sorted");
1✔
585

586
   // Two INTEGERs in descending order. A universal SET would lex-sort and put
587
   // 0x01 before 0x02; a non-universal constructed [17] must preserve order.
588
   const std::vector<uint8_t> first = {0x02, 0x01, 0x02};   // INTEGER 2
1✔
589
   const std::vector<uint8_t> second = {0x02, 0x01, 0x01};  // INTEGER 1
1✔
590

591
   auto encode_with = [&](auto starter) {
5✔
592
      Botan::DER_Encoder enc;
4✔
593
      starter(enc).raw_bytes(first).raw_bytes(second).end_cons();
4✔
594
      return enc.get_contents_unlocked();
8✔
595
   };
5✔
596

597
   // Reference: a universal SET of the same children gets sorted
598
   const auto set_enc = encode_with([](Botan::DER_Encoder& e) -> Botan::DER_Encoder& { return e.start_set(); });
2✔
599
   const std::vector<uint8_t> set_expected = {0x31, 0x06, 0x02, 0x01, 0x01, 0x02, 0x01, 0x02};
1✔
600
   result.test_bin_eq("universal SET is lex-sorted", set_enc, set_expected);
1✔
601

602
   // start_context_specific(17): tag byte = ContextSpecific | Constructed | 17 = 0xB1
603
   const auto ctx_enc =
1✔
604
      encode_with([](Botan::DER_Encoder& e) -> Botan::DER_Encoder& { return e.start_context_specific(17); });
2✔
605
   const std::vector<uint8_t> ctx_expected = {0xB1, 0x06, 0x02, 0x01, 0x02, 0x02, 0x01, 0x01};
1✔
606
   result.test_bin_eq("context-specific [17] preserves order", ctx_enc, ctx_expected);
1✔
607

608
   // start_explicit_context_specific(17): same tag byte 0xB1
609
   const auto explicit_ctx_enc =
1✔
610
      encode_with([](Botan::DER_Encoder& e) -> Botan::DER_Encoder& { return e.start_explicit_context_specific(17); });
2✔
611
   result.test_bin_eq("explicit context-specific [17] preserves order", explicit_ctx_enc, ctx_expected);
1✔
612

613
   // start_explicit(17): used to throw Internal_Error; must now produce [17] in order
614
   const auto explicit_enc =
1✔
615
      encode_with([](Botan::DER_Encoder& e) -> Botan::DER_Encoder& { return e.start_explicit(17); });
2✔
616
   result.test_bin_eq("start_explicit(17) preserves order", explicit_enc, ctx_expected);
1✔
617

618
   return result;
1✔
619
}
1✔
620

621
Test::Result test_der_implicit_tagging_helpers() {
1✔
622
   Test::Result result("DER implicit tagging helpers");
1✔
623

624
   const std::vector<uint8_t> first = {0x02, 0x01, 0x02};   // INTEGER 2
1✔
625
   const std::vector<uint8_t> second = {0x02, 0x01, 0x01};  // INTEGER 1
1✔
626

627
   Botan::DER_Encoder set_enc;
1✔
628
   set_enc.start_set(23).raw_bytes(first).raw_bytes(second).end_cons();
1✔
629
   const auto implicit_set = set_enc.get_contents_unlocked();
1✔
630
   result.test_bin_eq("implicit SET is still sorted", implicit_set, "B706020101020102");
1✔
631

632
   const ASN1_Test_Sequence seq(42);
1✔
633
   const auto implicit_seq = Botan::DER_Encoder().encode_implicit(seq, Botan::ASN1_Type(3)).get_contents_unlocked();
1✔
634
   result.test_bin_eq("implicit constructed object keeps constructed bit", implicit_seq, "A30302012A");
1✔
635

636
   ASN1_Test_Sequence decoded;
1✔
637
   Botan::BER_Decoder(implicit_seq, Botan::BER_Decoder::Limits::DER())
2✔
638
      .decode_implicit(decoded,
1✔
639
                       Botan::ASN1_Type(3),
640
                       Botan::ASN1_Class::ContextSpecific | Botan::ASN1_Class::Constructed,
641
                       Botan::ASN1_Type::Sequence,
642
                       Botan::ASN1_Class::Constructed)
643
      .verify_end();
1✔
644
   result.test_sz_eq("implicit constructed object decodes", decoded.value(), 42);
1✔
645

646
   const std::vector<uint8_t> one_bit = {0x80};
1✔
647
   const auto implicit_bitstring =
1✔
648
      Botan::DER_Encoder()
1✔
649
         .encode_bitstring(one_bit, 7, Botan::ASN1_Type(1), Botan::ASN1_Class::ContextSpecific)
2✔
650
         .get_contents_unlocked();
1✔
651
   result.test_bin_eq("implicit BIT STRING keeps unused bit count", implicit_bitstring, "81020780");
1✔
652

653
   const std::vector<uint8_t> bad_padding = {0x81};
1✔
654
   result.test_throws<Botan::Invalid_Argument>("BIT STRING unused bits must be zero",
1✔
655
                                               [&] { Botan::DER_Encoder().encode_bitstring(bad_padding, 7); });
2✔
656

657
   return result;
1✔
658
}
1✔
659

660
Test::Result test_asn1_bitstring_helpers() {
1✔
661
   Test::Result result("ASN.1 BIT STRING helpers");
1✔
662

663
   const std::vector<uint8_t> raw_der = {0x03, 0x03, 0x03, 0xA8, 0x00};
1✔
664
   Botan::ASN1_BitString raw_bits;
1✔
665
   Botan::BER_Decoder(raw_der, Botan::BER_Decoder::Limits::DER()).decode_bitstring(raw_bits).verify_end();
1✔
666

667
   result.test_sz_eq("raw bytes", raw_bits.bytes().size(), 2);
1✔
668
   result.test_sz_eq("raw unused bits", raw_bits.unused_bits(), 3);
1✔
669
   result.test_sz_eq("raw bit length", raw_bits.bit_length(), 13);
1✔
670
   result.test_is_true("raw bit 0", raw_bits.bit_at(0));
1✔
671
   result.test_is_false("raw bit 1", raw_bits.bit_at(1));
1✔
672
   result.test_is_true("raw bit 2", raw_bits.bit_at(2));
1✔
673

674
   const auto raw_reencoded = Botan::DER_Encoder().encode_bitstring(raw_bits).get_contents_unlocked();
1✔
675
   result.test_bin_eq("raw BIT STRING re-encodes", raw_reencoded, raw_der);
1✔
676

677
   const std::vector<uint8_t> octet_aligned_der = {0x03, 0x02, 0x00, 0xAA};
1✔
678
   std::vector<uint8_t> octets;
1✔
679
   Botan::BER_Decoder(octet_aligned_der, Botan::BER_Decoder::Limits::DER())
2✔
680
      .decode_octet_aligned_bitstring(octets)
1✔
681
      .verify_end();
1✔
682
   const std::vector<uint8_t> expected_octets = {0xAA};
1✔
683
   result.test_bin_eq("octet-aligned BIT STRING decodes as bytes", octets, expected_octets);
1✔
684

685
   const std::vector<uint8_t> non_octet_aligned_der = {0x03, 0x02, 0x01, 0x80};
1✔
686
   result.test_throws<Botan::Decoding_Error>("octet-aligned BIT STRING rejects unused bits", [&] {
1✔
687
      std::vector<uint8_t> rejected;
1✔
688
      Botan::BER_Decoder(non_octet_aligned_der, Botan::BER_Decoder::Limits::DER())
3✔
689
         .decode_octet_aligned_bitstring(rejected)
1✔
690
         .verify_end();
×
691
   });
1✔
692

693
   const uint64_t named = (uint64_t(1) << 15) | (uint64_t(1) << 7);
1✔
694
   const auto named_der = Botan::DER_Encoder().encode_named_bitstring(named, 16).get_contents_unlocked();
1✔
695
   const std::vector<uint8_t> expected_named_der = {0x03, 0x03, 0x07, 0x80, 0x80};
1✔
696
   result.test_bin_eq("named BIT STRING uses DER minimum length", named_der, expected_named_der);
1✔
697

698
   uint64_t decoded_named = 0;
1✔
699
   Botan::BER_Decoder(named_der, Botan::BER_Decoder::Limits::DER())
2✔
700
      .decode_named_bitstring(decoded_named, 16)
1✔
701
      .verify_end();
1✔
702
   result.test_u64_eq("named BIT STRING round-trips", decoded_named, named);
1✔
703

704
   const auto width9_der = Botan::DER_Encoder().encode_named_bitstring(1, 9).get_contents_unlocked();
1✔
705
   const std::vector<uint8_t> expected_width9_der = {0x03, 0x03, 0x07, 0x00, 0x80};
1✔
706
   result.test_bin_eq("named BIT STRING handles non-byte width", width9_der, expected_width9_der);
1✔
707

708
   const std::vector<uint8_t> non_minimal_named_der = {0x03, 0x02, 0x00, 0x80};
1✔
709
   result.test_throws<Botan::BER_Decoding_Error>("DER named BIT STRING rejects trailing zero bits", [&] {
1✔
710
      uint64_t rejected = 0;
1✔
711
      Botan::BER_Decoder(non_minimal_named_der, Botan::BER_Decoder::Limits::DER())
2✔
712
         .decode_named_bitstring(rejected, 16)
1✔
713
         .verify_end();
×
714
   });
×
715

716
   uint64_t non_minimal_named = 0;
1✔
717
   Botan::BER_Decoder(non_minimal_named_der, Botan::BER_Decoder::Limits::BER())
2✔
718
      .decode_named_bitstring(non_minimal_named, 16)
1✔
719
      .verify_end();
1✔
720
   result.test_u64_eq("BER named BIT STRING accepts trailing zero bits", non_minimal_named, uint64_t(1) << 15);
1✔
721

722
   return result;
1✔
723
}
1✔
724

725
Test::Result test_ber_indefinite_length_trailing_data() {
1✔
726
   Test::Result result("BER indefinite length trailing data");
1✔
727

728
   // Case 1: verify_end after consuming indef SEQUENCE
729
   try {
1✔
730
      const std::vector<uint8_t> enc = {0x30, 0x80, 0x02, 0x01, 0x42, 0x00, 0x00};
1✔
731
      Botan::BER_Decoder dec(enc);
1✔
732
      Botan::BigInt x;
1✔
733
      dec.start_sequence().decode(x).end_cons();
1✔
734
      dec.verify_end();
1✔
735
      result.test_bn_eq("verify_end decoded x", x, Botan::BigInt(0x42));
1✔
736
   } catch(Botan::Exception& e) {
1✔
737
      result.test_failure("verify_end after indef SEQUENCE", e.what());
×
738
   }
×
739

740
   // Case 2: two back-to-back indef SEQUENCES at top level
741
   try {
1✔
742
      const std::vector<uint8_t> enc = {
1✔
743
         0x30, 0x80, 0x02, 0x01, 0x42, 0x00, 0x00, 0x30, 0x80, 0x02, 0x01, 0x43, 0x00, 0x00};
1✔
744
      Botan::BER_Decoder dec(enc);
1✔
745
      Botan::BigInt x;
1✔
746
      Botan::BigInt y;
1✔
747
      dec.start_sequence().decode(x).end_cons();
1✔
748
      dec.start_sequence().decode(y).end_cons();
1✔
749
      dec.verify_end();
1✔
750
      result.test_bn_eq("back-to-back x", x, Botan::BigInt(0x42));
1✔
751
      result.test_bn_eq("back-to-back y", y, Botan::BigInt(0x43));
1✔
752
   } catch(Botan::Exception& e) {
1✔
753
      result.test_failure("two back-to-back indef SEQUENCES", e.what());
×
754
   }
×
755

756
   // Case 3: nested indef SEQUENCES
757
   try {
1✔
758
      const std::vector<uint8_t> enc = {0x30, 0x80, 0x30, 0x80, 0x02, 0x01, 0x42, 0x00, 0x00, 0x00, 0x00};
1✔
759
      Botan::BER_Decoder dec(enc);
1✔
760
      Botan::BigInt x;
1✔
761
      auto outer = dec.start_sequence();
1✔
762
      outer.start_sequence().decode(x).end_cons();
1✔
763
      outer.end_cons();
1✔
764
      dec.verify_end();
1✔
765
      result.test_bn_eq("nested x", x, Botan::BigInt(0x42));
1✔
766
   } catch(Botan::Exception& e) {
1✔
767
      result.test_failure("nested indef SEQUENCE", e.what());
×
768
   }
×
769

770
   // Case 4: while(more_items()) loop over an indef SEQUENCE
771
   try {
1✔
772
      const std::vector<uint8_t> enc = {0x30, 0x80, 0x02, 0x01, 0x42, 0x02, 0x01, 0x43, 0x00, 0x00};
1✔
773
      Botan::BER_Decoder dec(enc);
1✔
774
      auto seq = dec.start_sequence();
1✔
775
      std::vector<Botan::BigInt> xs;
1✔
776
      while(seq.more_items()) {
3✔
777
         Botan::BigInt x;
2✔
778
         seq.decode(x);
2✔
779
         xs.push_back(x);
2✔
780
      }
2✔
781
      seq.end_cons();
1✔
782
      dec.verify_end();
1✔
783
      result.test_sz_eq("more_items count", xs.size(), 2);
1✔
784
      if(xs.size() == 2) {
1✔
785
         result.test_bn_eq("more_items xs[0]", xs[0], Botan::BigInt(0x42));
1✔
786
         result.test_bn_eq("more_items xs[1]", xs[1], Botan::BigInt(0x43));
1✔
787
      }
788
   } catch(Botan::Exception& e) {
1✔
789
      result.test_failure("more_items loop over indef SEQUENCE", e.what());
×
790
   }
×
791

792
   return result;
1✔
793
}
×
794

795
Test::Result test_ber_find_eoc() {
1✔
796
   Test::Result result("BER indefinite length EOC matching");
1✔
797

798
   const size_t num_siblings = 4096;
1✔
799

800
   std::vector<uint8_t> ber;
1✔
801
   ber.push_back(0x30);  // outer SEQUENCE | CONSTRUCTED
1✔
802
   ber.push_back(0x80);  // indefinite length
1✔
803
   for(size_t i = 0; i != num_siblings; ++i) {
4,097✔
804
      ber.push_back(0x30);  // inner SEQUENCE | CONSTRUCTED
4,096✔
805
      ber.push_back(0x80);  // indefinite length
4,096✔
806
      ber.push_back(0x00);  // EOC tag
4,096✔
807
      ber.push_back(0x00);  // EOC length
4,096✔
808
   }
809
   ber.push_back(0x00);  // outer EOC tag
1✔
810
   ber.push_back(0x00);  // outer EOC length
1✔
811

812
   try {
1✔
813
      Botan::BER_Decoder dec(ber);
1✔
814
      const Botan::BER_Object obj = dec.get_next_object();
1✔
815

816
      result.test_sz_eq("object body includes children", obj.length(), num_siblings * 4);
1✔
817
   } catch(Botan::Exception& e) {
1✔
818
      result.test_failure("decode failed", e.what());
×
819
   }
×
820

821
   return result;
1✔
822
}
1✔
823

824
Test::Result test_asn1_string_zero_length_roundtrip() {
1✔
825
   Test::Result result("ASN.1 String zero-length round-trip");
1✔
826

827
   auto roundtrip = [&](const char* what, const std::vector<uint8_t>& wire) {
4✔
828
      try {
3✔
829
         Botan::DataSource_Memory input(wire.data(), wire.size());
3✔
830
         Botan::BER_Decoder dec(input);
3✔
831
         Botan::ASN1_String str;
3✔
832
         str.decode_from(dec);
3✔
833

834
         Botan::DER_Encoder enc;
3✔
835
         str.encode_into(enc);
3✔
836
         const auto out = enc.get_contents();
3✔
837
         result.test_bin_eq(what, std::span{out}, std::span{wire});
3✔
838
      } catch(const std::exception& ex) {
9✔
839
         result.test_failure(Botan::fmt("{}: unexpected throw: {}", what, ex.what()));
×
840
      }
×
841
   };
3✔
842

843
   roundtrip("BmpString 1E 00", {0x1E, 0x00});
1✔
844
   roundtrip("UniversalString 1C 00", {0x1C, 0x00});
1✔
845
   roundtrip("TeletexString 14 00", {0x14, 0x00});
1✔
846

847
   return result;
1✔
848
}
×
849

850
Test::Result test_pss_params_rejects_trailing_data_in_mgf1_params() {
1✔
851
   Test::Result result("PSS-Params rejects trailing data in MGF1 parameters");
1✔
852

853
   const Botan::AlgorithmIdentifier sha256_alg_id("SHA-256", Botan::AlgorithmIdentifier::USE_NULL_PARAM);
1✔
854
   const auto sha256_der = sha256_alg_id.BER_encode();
1✔
855

856
   auto encode_pss_params = [&](const std::vector<uint8_t>& mgf_params) {
3✔
857
      const Botan::AlgorithmIdentifier mgf("MGF1", mgf_params);
2✔
858
      Botan::DER_Encoder enc;
2✔
859
      enc.start_sequence()
2✔
860
         .start_context_specific(0)
2✔
861
         .encode(sha256_alg_id)
2✔
862
         .end_cons()
2✔
863
         .start_context_specific(1)
2✔
864
         .encode(mgf)
2✔
865
         .end_cons()
2✔
866
         .start_context_specific(2)
2✔
867
         .encode(static_cast<size_t>(32))
2✔
868
         .end_cons()
2✔
869
         .end_cons();
2✔
870
      return enc.get_contents();
2✔
871
   };
4✔
872

873
   try {
1✔
874
      const auto clean_der = encode_pss_params(sha256_der);
1✔
875
      const Botan::PSS_Params clean(clean_der);
1✔
876
      result.test_success("control: clean PSS-Params decodes");
1✔
877
   } catch(const std::exception& e) {
2✔
878
      result.test_failure(Botan::fmt("clean PSS-Params unexpected throw: {}", e.what()));
×
879
   }
×
880

881
   std::vector<uint8_t> mgf_params_with_junk = sha256_der;
1✔
882
   const std::vector<uint8_t> trailing_junk{0x02, 0x01, 0x00};
1✔
883
   mgf_params_with_junk.insert(mgf_params_with_junk.end(), trailing_junk.begin(), trailing_junk.end());
1✔
884
   const auto bad_der = encode_pss_params(mgf_params_with_junk);
1✔
885

886
   result.test_throws<Botan::Decoding_Error>("PSS-Params rejects trailing data in MGF1 parameters",
1✔
887
                                             [&]() { const Botan::PSS_Params bad(bad_der); });
2✔
888

889
   return result;
1✔
890
}
2✔
891

892
Test::Result test_alg_id_parameter_validation() {
1✔
893
   Test::Result result("AlgorithmIdentifier parameter validation");
1✔
894

895
   auto decode_alg_id = [](std::string_view hex) {
10✔
896
      const auto wire = Botan::hex_decode(hex);
9✔
897
      Botan::AlgorithmIdentifier alg_id;
9✔
898
      Botan::BER_Decoder(wire).decode(alg_id).verify_end();
13✔
899
   };
9✔
900

901
   auto verify_params_accepted = [&](const std::string& label, std::string_view hex) {
6✔
902
      try {
5✔
903
         decode_alg_id(hex);
5✔
904
         result.test_success(Botan::fmt("{} parameters accepted", label));
10✔
905
      } catch(const std::exception& e) {
×
906
         result.test_failure(Botan::fmt("{} parameters unexpectedly rejected: {}", label, e.what()));
×
907
      }
×
908
   };
5✔
909

910
   auto verify_params_rejected = [&](const std::string& label, std::string_view hex) {
5✔
911
      result.test_throws<Botan::Decoding_Error>(Botan::fmt("{} parameters rejected", label),
8✔
912
                                                [&]() { decode_alg_id(hex); });
8✔
913
   };
4✔
914

915
   // The wire is SEQUENCE { OID 2.5.4.3, <parameters> } in each case
916

917
   verify_params_accepted("absent", "30050603550403");
1✔
918
   verify_params_accepted("NULL", "300706035504030500");
1✔
919
   verify_params_accepted("SEQUENCE", "300706035504033000");
1✔
920
   verify_params_accepted("OID", "300A06035504030603550403");
1✔
921
   verify_params_accepted("OCTET STRING", "300906035504030402AABB");
1✔
922

923
   verify_params_rejected("two values (NULL, NULL)", "3009060355040305000500");
1✔
924
   verify_params_rejected("trailing data after NULL", "300A06035504030500020100");
1✔
925
   verify_params_rejected("truncated SEQUENCE", "300706035504033005");
1✔
926
   verify_params_rejected("single INTEGER", "30080603550403020100");
1✔
927

928
   return result;
1✔
929
}
×
930

931
Test::Result test_der_default_value_encoding() {
1✔
932
   Test::Result result("DER DEFAULT value rejection");
1✔
933

934
   using Limits = Botan::BER_Decoder::Limits;
1✔
935

936
   // SEQUENCE { version INTEGER DEFAULT 0 } in three forms
937
   const std::vector<uint8_t> present_default = {0x30, 0x03, 0x02, 0x01, 0x00};     // present, == default
1✔
938
   const std::vector<uint8_t> omitted = {0x30, 0x00};                               // absent
1✔
939
   const std::vector<uint8_t> present_nondefault = {0x30, 0x03, 0x02, 0x01, 0x05};  // present, != default
1✔
940

941
   auto decode_version = [](const std::vector<uint8_t>& wire, Limits limits) {
7✔
942
      Botan::BER_Decoder dec(wire, limits);
6✔
943
      size_t version = 99;
6✔
944
      dec.start_sequence()
6✔
945
         .decode_default(version, Botan::ASN1_Type::Integer, Botan::ASN1_Class::Universal, size_t(0))
11✔
946
         .end_cons();
5✔
947
      return version;
5✔
948
   };
6✔
949

950
   // By default an explicitly-encoded default value is accepted
951
   try {
1✔
952
      result.test_sz_eq("present default accepted (lax)", decode_version(present_default, Limits::DER()), 0);
1✔
953
      result.test_sz_eq("omitted uses default (lax)", decode_version(omitted, Limits::DER()), 0);
1✔
954
      result.test_sz_eq("present non-default decoded", decode_version(present_nondefault, Limits::DER()), 5);
1✔
955
   } catch(const std::exception& e) {
×
956
      result.test_failure(Botan::fmt("unexpected rejection: {}", e.what()));
×
957
   }
×
958

959
   // With the flag set, a present default-valued component is rejected ...
960
   result.test_throws<Botan::Decoding_Error>("present default rejected (strict)", [&]() {
1✔
961
      decode_version(present_default, Limits::DER().with_default_value_encoding_rejected());
1✔
962
   });
×
963

964
   // ... but omitted and non-default forms are still accepted
965
   try {
1✔
966
      const auto strict = Limits::DER().with_default_value_encoding_rejected();
1✔
967
      result.test_sz_eq("omitted uses default (strict)", decode_version(omitted, strict), 0);
1✔
968
      result.test_sz_eq("present non-default accepted (strict)", decode_version(present_nondefault, strict), 5);
1✔
969
   } catch(const std::exception& e) {
×
970
      result.test_failure(Botan::fmt("unexpected strict rejection: {}", e.what()));
×
971
   }
×
972

973
   return result;
1✔
974
}
1✔
975

976
class ASN1_Tests final : public Test {
1✔
977
   public:
978
      std::vector<Test::Result> run() override {
1✔
979
         std::vector<Test::Result> results;
1✔
980

981
         results.push_back(test_ber_stack_recursion());
2✔
982
         results.push_back(test_ber_eoc_decoding_limits());
2✔
983
         results.push_back(test_ber_standalone_eoc_limits());
2✔
984
         results.push_back(test_ber_max_object_size());
2✔
985
         results.push_back(test_ber_constructed_string_decoding());
2✔
986
         results.push_back(test_ber_indefinite_length_trailing_data());
2✔
987
         results.push_back(test_ber_find_eoc());
2✔
988
         results.push_back(test_asn1_utf8_ascii_parsing());
2✔
989
         results.push_back(test_asn1_utf8_parsing());
2✔
990
         results.push_back(test_asn1_ucs2_parsing());
2✔
991
         results.push_back(test_asn1_ucs4_parsing());
2✔
992
         results.push_back(test_asn1_ucs_invalid_codepoint_rejection());
2✔
993
         results.push_back(test_asn1_ascii_encoding());
2✔
994
         results.push_back(test_asn1_utf8_encoding());
2✔
995
         results.push_back(test_asn1_tag_underlying_type());
2✔
996
         results.push_back(test_asn1_high_tag_number());
2✔
997
         results.push_back(test_asn1_negative_int_encoding());
2✔
998
         results.push_back(test_der_set_ordering());
2✔
999
         results.push_back(test_der_constructed_tag_17_not_sorted());
2✔
1000
         results.push_back(test_der_implicit_tagging_helpers());
2✔
1001
         results.push_back(test_asn1_bitstring_helpers());
2✔
1002
         results.push_back(test_asn1_string_zero_length_roundtrip());
2✔
1003
         results.push_back(test_pss_params_rejects_trailing_data_in_mgf1_params());
2✔
1004
         results.push_back(test_alg_id_parameter_validation());
2✔
1005
         results.push_back(test_der_default_value_encoding());
2✔
1006

1007
         return results;
1✔
1008
      }
×
1009
};
1010

1011
BOTAN_REGISTER_TEST("asn1", "asn1_encoding", ASN1_Tests);
1012

1013
class ASN1_Time_Parsing_Tests final : public Text_Based_Test {
×
1014
   public:
1015
      ASN1_Time_Parsing_Tests() : Text_Based_Test("asn1_time.vec", "Tspec") {}
2✔
1016

1017
      Test::Result run_one_test(const std::string& tag_str, const VarMap& vars) override {
26✔
1018
         Test::Result result("ASN.1 date parsing");
26✔
1019

1020
         const std::string tspec = vars.get_req_str("Tspec");
26✔
1021

1022
         if(tag_str != "UTC" && tag_str != "UTC.invalid" && tag_str != "Generalized" &&
26✔
1023
            tag_str != "Generalized.invalid") {
13✔
1024
            throw Test_Error("Invalid tag value in ASN1 date parsing test");
×
1025
         }
1026

1027
         const bool out_of_range = [&]() -> bool {
78✔
1028
            if(tspec.size() == 15) {
26✔
1029
               const size_t year = Botan::to_u32bit(std::string_view(tspec).substr(0, 4));
24✔
1030
               if(year >= 2262) {
24✔
1031
                  return true;
1032
               }
1033
               if(year >= 2038 && sizeof(time_t) == 4) {
1034
                  return true;
1035
               }
1036
            }
1037

1038
            return false;
1039
         }();
26✔
1040

1041
         const Botan::ASN1_Type tag = (tag_str == "UTC" || tag_str == "UTC.invalid")
25✔
1042
                                         ? Botan::ASN1_Type::UtcTime
26✔
1043
                                         : Botan::ASN1_Type::GeneralizedTime;
26✔
1044

1045
         const bool valid = tag_str.find(".invalid") == std::string::npos;
26✔
1046

1047
         if(valid) {
26✔
1048
            const Botan::ASN1_Time time(tspec, tag);
12✔
1049
            result.test_success("Accepted valid time");
12✔
1050

1051
            try {
12✔
1052
               const auto std_timepoint = time.to_std_timepoint();
12✔
1053
               result.test_success("Was able to convert time to std timepoint");
10✔
1054

1055
               const auto from_std_timepoint = Botan::ASN1_Time::from_time_point(std_timepoint);
10✔
1056
               result.test_is_true("ASN1_Time from std timepoint matches input", from_std_timepoint == time);
10✔
1057
            } catch(std::exception& e) {
12✔
1058
               if(out_of_range) {
2✔
1059
                  result.test_str_contains("Exception message", e.what(), "time is outside the representable range");
2✔
1060
               } else {
1061
                  result.test_failure("Was not able to convert time to std timepoint", e.what());
×
1062
               }
1063
            }
2✔
1064
         } else {
12✔
1065
            result.test_throws("Invalid time rejected", [=]() { const Botan::ASN1_Time time(tspec, tag); });
70✔
1066
         }
1067

1068
         return result;
26✔
1069
      }
26✔
1070
};
1071

1072
BOTAN_REGISTER_TEST("asn1", "asn1_time", ASN1_Time_Parsing_Tests);
1073

1074
class ASN1_String_Validation_Tests final : public Text_Based_Test {
×
1075
   public:
1076
      ASN1_String_Validation_Tests() :
1✔
1077
            Text_Based_Test("asn1_string_validation.vec",
1078
                            "Input,ValidNumeric,ValidPrintable,ValidIa5,ValidVisible,ValidUtf8") {}
2✔
1079

1080
      Test::Result run_one_test(const std::string& /*header*/, const VarMap& vars) override {
7✔
1081
         Test::Result result("ASN.1 string validation");
7✔
1082

1083
         const auto input = vars.get_req_str("Input");
7✔
1084
         const bool valid_numeric = vars.get_req_bool("ValidNumeric");
7✔
1085
         const bool valid_printable = vars.get_req_bool("ValidPrintable");
7✔
1086
         const bool valid_ia5 = vars.get_req_bool("ValidIa5");
7✔
1087
         const bool valid_visible = vars.get_req_bool("ValidVisible");
7✔
1088
         const bool valid_utf8 = vars.get_req_bool("ValidUtf8");
7✔
1089

1090
         test_string_type(result, input, "NumericString", Botan::ASN1_Type::NumericString, valid_numeric);
7✔
1091
         test_string_type(result, input, "PrintableString", Botan::ASN1_Type::PrintableString, valid_printable);
7✔
1092
         test_string_type(result, input, "Ia5String", Botan::ASN1_Type::Ia5String, valid_ia5);
7✔
1093
         test_string_type(result, input, "VisibleString", Botan::ASN1_Type::VisibleString, valid_visible);
7✔
1094
         test_string_type(result, input, "Utf8String", Botan::ASN1_Type::Utf8String, valid_utf8);
7✔
1095

1096
         if(valid_utf8) {
7✔
1097
            try {
7✔
1098
               const Botan::ASN1_String str(input);
7✔
1099
               const auto expected_tag =
14✔
1100
                  valid_printable ? Botan::ASN1_Type::PrintableString : Botan::ASN1_Type::Utf8String;
7✔
1101
               result.test_u32_eq("String tagging categorization",
7✔
1102
                                  static_cast<uint32_t>(str.tagging()),
7✔
1103
                                  static_cast<uint32_t>(expected_tag));
1104
            } catch(const std::exception& ex) {
7✔
1105
               result.test_failure(Botan::fmt("default constructor unexpectedly rejected '{}': {}", input, ex.what()));
×
1106
            }
×
1107
         }
1108

1109
         return result;
7✔
1110
      }
7✔
1111

1112
   private:
1113
      void test_string_type(Test::Result& result,
35✔
1114
                            std::string_view input,
1115
                            std::string_view type,
1116
                            Botan::ASN1_Type tag,
1117
                            bool expected_valid) {
1118
         if(expected_valid) {
35✔
1119
            try {
24✔
1120
               const Botan::ASN1_String str(input, tag);
24✔
1121
               result.test_str_eq(Botan::fmt("{} constructor value", type), str.value(), input);
24✔
1122

1123
               const auto enc = raw_encode_string(input, tag);
24✔
1124
               Botan::BER_Decoder dec(enc);
24✔
1125
               Botan::ASN1_String decoded;
24✔
1126
               decoded.decode_from(dec);
24✔
1127
               result.test_str_eq(Botan::fmt("{} decode value", type), decoded.value(), input);
48✔
1128
            } catch(const std::exception& e) {
48✔
1129
               result.test_failure(Botan::fmt("{} unexpectedly rejected '{}': {}", type, input, e.what()));
×
1130
            }
×
1131
         } else {
1132
            result.test_throws(Botan::fmt("{} constructor rejects", type),
22✔
1133
                               [&]() { const Botan::ASN1_String str(input, tag); });
22✔
1134

1135
            result.test_throws(Botan::fmt("{} decode rejects", type), [&]() {
22✔
1136
               const auto enc = raw_encode_string(input, tag);
11✔
1137
               Botan::BER_Decoder dec(enc);
11✔
1138
               Botan::ASN1_String decoded;
11✔
1139
               decoded.decode_from(dec);
11✔
1140
            });
22✔
1141
         }
1142
      }
35✔
1143

1144
      static std::vector<uint8_t> raw_encode_string(std::string_view input, Botan::ASN1_Type tag) {
35✔
1145
         std::vector<uint8_t> encoding;
35✔
1146
         Botan::DER_Encoder der(encoding);
35✔
1147
         der.add_object(tag, Botan::ASN1_Class::Universal, input);
35✔
1148
         return encoding;
35✔
1149
      }
35✔
1150
};
1151

1152
BOTAN_REGISTER_TEST("asn1", "asn1_string_validation", ASN1_String_Validation_Tests);
1153

1154
class ASN1_Printer_Tests final : public Test {
1✔
1155
   public:
1156
      std::vector<Test::Result> run() override {
1✔
1157
         Test::Result result("ASN1_Pretty_Printer");
1✔
1158

1159
         const Botan::ASN1_Pretty_Printer printer;
1✔
1160

1161
         const size_t num_tests = 8;
1✔
1162

1163
         for(size_t i = 1; i <= num_tests; ++i) {
9✔
1164
            const std::string i_str = std::to_string(i);
8✔
1165
            const std::vector<uint8_t> input_data = Test::read_binary_data_file("asn1_print/input" + i_str + ".der");
24✔
1166
            const std::string expected_output = Test::read_data_file("asn1_print/output" + i_str + ".txt");
24✔
1167

1168
            try {
8✔
1169
               const std::string output = printer.print(input_data);
8✔
1170
               result.test_str_eq("Test " + i_str, output, expected_output);
16✔
1171
            } catch(Botan::Exception& e) {
8✔
1172
               result.test_failure(Botan::fmt("Printing test {} failed with an exception: '{}'", i, e.what()));
×
1173
            }
×
1174
         }
8✔
1175

1176
         return {result};
2✔
1177
      }
10✔
1178
};
1179

1180
BOTAN_REGISTER_TEST("asn1", "asn1_printer", ASN1_Printer_Tests);
1181

1182
class ASN1_Decoding_Tests final : public Text_Based_Test {
×
1183
   public:
1184
      ASN1_Decoding_Tests() : Text_Based_Test("asn1_decoding.vec", "Input,ResultBER", "ResultDER") {}
2✔
1185

1186
      Test::Result run_one_test(const std::string& /*header*/, const VarMap& vars) override {
58✔
1187
         const auto input = vars.get_req_bin("Input");
58✔
1188
         const std::string expected_ber = vars.get_req_str("ResultBER");
58✔
1189
         const std::string expected_der = vars.get_opt_str("ResultDER", expected_ber);
58✔
1190

1191
         Test::Result result("ASN1 decoding");
58✔
1192

1193
         decoding_test(result, input, expected_ber, false);
58✔
1194
         decoding_test(result, input, expected_der, true);
58✔
1195

1196
         return result;
116✔
1197
      }
58✔
1198

1199
   private:
1200
      static void decoding_test(Test::Result& result,
116✔
1201
                                std::span<const uint8_t> input,
1202
                                std::string_view expected,
1203
                                bool require_der) {
1204
         const Botan::ASN1_Pretty_Printer printer(4096, 2048, true, 0, 60, 64, require_der);
116✔
1205
         const std::string mode = require_der ? "DER" : "BER";
174✔
1206
         std::ostringstream sink;
116✔
1207

1208
         try {
116✔
1209
            printer.print_to_stream(sink, input.data(), input.size());
116✔
1210

1211
            if(expected == "OK") {
51✔
1212
               result.test_success();
51✔
1213
            } else {
1214
               result.test_failure(Botan::fmt("Accepted invalid {} input, expected error {}", mode, expected));
×
1215
            }
1216
         } catch(const std::exception& e) {
65✔
1217
            if(expected == "OK") {
65✔
1218
               result.test_failure(Botan::fmt("Rejected valid {} input with {}", mode, e.what()));
×
1219
            } else {
1220
               // BER_Decoding_Error prepends "BER: " to the message
1221
               std::string msg = e.what();
65✔
1222
               if(msg.starts_with("BER: ")) {
65✔
1223
                  msg = msg.substr(5);
45✔
1224
               }
1225
               result.test_str_eq("error message", msg, expected);
65✔
1226
            }
65✔
1227
         }
65✔
1228
      }
161✔
1229
};
1230

1231
BOTAN_REGISTER_TEST("asn1", "asn1_decoding", ASN1_Decoding_Tests);
1232

1233
#endif
1234

1235
}  // namespace
1236

1237
}  // namespace Botan_Tests
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc