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

randombit / botan / 28311706783

27 Jun 2026 04:51PM UTC coverage: 89.346% (-0.006%) from 89.352%
28311706783

push

github

web-flow
Merge pull request #5703 from randombit/jack/alg-id-param-validation

Validate AlgorithmIdentifier parameters on decode

112110 of 125479 relevant lines covered (89.35%)

10963156.46 hits per line

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

90.16
/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()) {
2✔
54
         Botan::BER_Object obj;
1✔
55
         dec.get_next(obj);
1✔
56
      }
1✔
57
   } catch(Botan::Decoding_Error&) {}
2✔
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_asn1_utf8_ascii_parsing() {
1✔
100
   Test::Result result("ASN.1 ASCII parsing");
1✔
101

102
   try {
1✔
103
      // \x13 - ASN1 tag for 'printable string'
104
      // \x06 - 6 characters of payload
105
      // ...  - UTF-8 encoded (ASCII chars only) word 'Moscow'
106
      const std::string moscow = "\x13\x06\x4D\x6F\x73\x63\x6F\x77";
1✔
107
      const std::string moscow_plain = "Moscow";
1✔
108
      Botan::DataSource_Memory input(moscow);
1✔
109
      Botan::BER_Decoder dec(input);
1✔
110

111
      Botan::ASN1_String str;
1✔
112
      str.decode_from(dec);
1✔
113

114
      result.test_str_eq("value()", str.value(), moscow_plain);
1✔
115
   } catch(const Botan::Decoding_Error& ex) {
2✔
116
      result.test_failure(ex.what());
×
117
   }
×
118

119
   return result;
1✔
120
}
×
121

122
Test::Result test_asn1_utf8_parsing() {
1✔
123
   Test::Result result("ASN.1 UTF-8 parsing");
1✔
124

125
   try {
1✔
126
      // \x0C - ASN1 tag for 'UTF8 string'
127
      // \x0C - 12 characters of payload
128
      // ...  - UTF-8 encoded russian word for Moscow in cyrillic script
129
      const std::string moscow = "\x0C\x0C\xD0\x9C\xD0\xBE\xD1\x81\xD0\xBA\xD0\xB2\xD0\xB0";
1✔
130
      const std::string moscow_plain = "\xD0\x9C\xD0\xBE\xD1\x81\xD0\xBA\xD0\xB2\xD0\xB0";
1✔
131
      Botan::DataSource_Memory input(moscow);
1✔
132
      Botan::BER_Decoder dec(input);
1✔
133

134
      Botan::ASN1_String str;
1✔
135
      str.decode_from(dec);
1✔
136

137
      result.test_str_eq("value()", str.value(), moscow_plain);
1✔
138
   } catch(const Botan::Decoding_Error& ex) {
2✔
139
      result.test_failure(ex.what());
×
140
   }
×
141

142
   return result;
1✔
143
}
×
144

145
Test::Result test_asn1_ucs2_parsing() {
1✔
146
   Test::Result result("ASN.1 BMP string (UCS-2) parsing");
1✔
147

148
   try {
1✔
149
      // \x1E     - ASN1 tag for 'BMP (UCS-2) string'
150
      // \x0C     - 12 characters of payload
151
      // ...      - UCS-2 encoding for Moscow in cyrillic script
152
      const std::string moscow = "\x1E\x0C\x04\x1C\x04\x3E\x04\x41\x04\x3A\x04\x32\x04\x30";
1✔
153
      const std::string moscow_plain = "\xD0\x9C\xD0\xBE\xD1\x81\xD0\xBA\xD0\xB2\xD0\xB0";
1✔
154

155
      Botan::DataSource_Memory input(moscow);
1✔
156
      Botan::BER_Decoder dec(input);
1✔
157

158
      Botan::ASN1_String str;
1✔
159
      str.decode_from(dec);
1✔
160

161
      result.test_str_eq("value()", str.value(), moscow_plain);
1✔
162
   } catch(const Botan::Decoding_Error& ex) {
2✔
163
      result.test_failure(ex.what());
×
164
   }
×
165

166
   return result;
1✔
167
}
×
168

169
Test::Result test_asn1_ucs4_parsing() {
1✔
170
   Test::Result result("ASN.1 universal string (UCS-4) parsing");
1✔
171

172
   try {
1✔
173
      // \x1C - ASN1 tag for 'universal string'
174
      // \x18 - 24 characters of payload
175
      // ...  - UCS-4 encoding for Moscow in cyrillic script
176
      const uint8_t moscow[] =
1✔
177
         "\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";
178
      const std::string moscow_plain = "\xD0\x9C\xD0\xBE\xD1\x81\xD0\xBA\xD0\xB2\xD0\xB0";
1✔
179
      Botan::DataSource_Memory input(moscow, sizeof(moscow));
1✔
180
      Botan::BER_Decoder dec(input);
1✔
181

182
      Botan::ASN1_String str;
1✔
183
      str.decode_from(dec);
1✔
184

185
      result.test_str_eq("value()", str.value(), moscow_plain);
1✔
186
   } catch(const Botan::Decoding_Error& ex) {
2✔
187
      result.test_failure(ex.what());
×
188
   }
×
189

190
   return result;
1✔
191
}
×
192

193
Test::Result test_asn1_ucs_invalid_codepoint_rejection() {
1✔
194
   Test::Result result("ASN.1 UCS-2/UCS-4 invalid codepoint rejection");
1✔
195

196
   auto expect_decode_throws = [&](const char* what, const std::vector<uint8_t>& wire) {
7✔
197
      result.test_throws(what, [&]() {
6✔
198
         Botan::DataSource_Memory input(wire.data(), wire.size());
6✔
199
         Botan::BER_Decoder dec(input);
6✔
200
         Botan::ASN1_String str;
6✔
201
         str.decode_from(dec);
6✔
202
      });
6✔
203
   };
6✔
204

205
   auto expect_decode_ok = [&](const char* what, const std::vector<uint8_t>& wire) {
2✔
206
      try {
1✔
207
         Botan::DataSource_Memory input(wire.data(), wire.size());
1✔
208
         Botan::BER_Decoder dec(input);
1✔
209
         Botan::ASN1_String str;
1✔
210
         str.decode_from(dec);
1✔
211
         result.test_success(what);
1✔
212
      } catch(const std::exception& ex) {
2✔
213
         result.test_failure(Botan::fmt("{}: unexpected throw: {}", what, ex.what()));
×
214
      }
×
215
   };
1✔
216

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

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

223
   // UniversalString with high surrogate 0xD800
224
   expect_decode_throws("UniversalString rejects surrogate codepoint", {0x1C, 0x04, 0x00, 0x00, 0xD8, 0x00});
1✔
225

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

229
   // BmpString (tag 0x1E) with high surrogate
230
   expect_decode_throws("BmpString rejects surrogate codepoint", {0x1E, 0x02, 0xD8, 0x00});
1✔
231

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

235
   // UniversalString with non-multiple-of-4 length is malformed
236
   expect_decode_throws("UniversalString rejects non-multiple-of-4 payload",
1✔
237
                        {0x1C, 0x05, 0x00, 0x00, 0x00, 0x41, 0x00});
238

239
   return result;
1✔
240
}
×
241

242
Test::Result test_asn1_ascii_encoding() {
1✔
243
   Test::Result result("ASN.1 ASCII encoding");
1✔
244

245
   try {
1✔
246
      // UTF-8 encoded (ASCII chars only) word 'Moscow'
247
      const std::string moscow = "Moscow";
1✔
248
      const Botan::ASN1_String str(moscow);
1✔
249

250
      Botan::DER_Encoder enc;
1✔
251

252
      str.encode_into(enc);
1✔
253
      auto encodingResult = enc.get_contents();
1✔
254

255
      // \x13 - ASN1 tag for 'printable string'
256
      // \x06 - 6 characters of payload
257
      result.test_bin_eq("encoding result", encodingResult, "13064D6F73636F77");
1✔
258

259
      result.test_success("No crash");
1✔
260
   } catch(const std::exception& ex) {
2✔
261
      result.test_failure(ex.what());
×
262
   }
×
263

264
   return result;
1✔
265
}
×
266

267
Test::Result test_asn1_utf8_encoding() {
1✔
268
   Test::Result result("ASN.1 UTF-8 encoding");
1✔
269

270
   try {
1✔
271
      // UTF-8 encoded russian word for Moscow in cyrillic script
272
      const std::string moscow = "\xD0\x9C\xD0\xBE\xD1\x81\xD0\xBA\xD0\xB2\xD0\xB0";
1✔
273
      const Botan::ASN1_String str(moscow);
1✔
274

275
      Botan::DER_Encoder enc;
1✔
276

277
      str.encode_into(enc);
1✔
278
      auto encodingResult = enc.get_contents();
1✔
279

280
      // \x0C - ASN1 tag for 'UTF8 string'
281
      // \x0C - 12 characters of payload
282
      result.test_bin_eq("encoding result", encodingResult, "0C0CD09CD0BED181D0BAD0B2D0B0");
1✔
283

284
      result.test_success("No crash");
1✔
285
   } catch(const std::exception& ex) {
2✔
286
      result.test_failure(ex.what());
×
287
   }
×
288

289
   return result;
1✔
290
}
×
291

292
Test::Result test_asn1_tag_underlying_type() {
1✔
293
   Test::Result result("ASN.1 class and type underlying type");
1✔
294

295
   if constexpr(std::is_same_v<std::underlying_type_t<Botan::ASN1_Class>, std::underlying_type_t<Botan::ASN1_Type>>) {
1✔
296
      if constexpr(!std::is_same_v<std::underlying_type_t<Botan::ASN1_Class>,
1✔
297
                                   std::invoke_result_t<decltype(&Botan::BER_Object::tagging), Botan::BER_Object>>) {
298
         result.test_failure(
299
            "Return type of BER_Object::tagging() is different than the underlying type of ASN1_Class");
300
      } else {
301
         result.test_success("Same types");
1✔
302
      }
303
   } else {
304
      result.test_failure("ASN1_Class and ASN1_Type have different underlying types");
305
   }
306

307
   return result;
1✔
308
}
×
309

310
Test::Result test_asn1_negative_int_encoding() {
1✔
311
   Test::Result result("DER encode/decode of negative integers");
1✔
312

313
   BigInt n(32);
1✔
314

315
   for(size_t i = 0; i != 2048; ++i) {
2,049✔
316
      n--;
2,048✔
317

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

320
      BigInt n_dec;
2,048✔
321
      Botan::BER_Decoder(enc, Botan::BER_Decoder::Limits::DER()).decode(n_dec);
4,096✔
322

323
      result.test_bn_eq("DER encoding round trips negative integers", n_dec, n);
2,048✔
324
   }
2,048✔
325

326
   return result;
1✔
327
}
1✔
328

329
Test::Result test_der_constructed_tag_17_not_sorted() {
1✔
330
   Test::Result result("DER constructed [17] is not SET-sorted");
1✔
331

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

337
   auto encode_with = [&](auto starter) {
5✔
338
      Botan::DER_Encoder enc;
4✔
339
      starter(enc).raw_bytes(first).raw_bytes(second).end_cons();
4✔
340
      return enc.get_contents_unlocked();
8✔
341
   };
5✔
342

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

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

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

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

364
   return result;
1✔
365
}
1✔
366

367
Test::Result test_der_implicit_tagging_helpers() {
1✔
368
   Test::Result result("DER implicit tagging helpers");
1✔
369

370
   const std::vector<uint8_t> first = {0x02, 0x01, 0x02};   // INTEGER 2
1✔
371
   const std::vector<uint8_t> second = {0x02, 0x01, 0x01};  // INTEGER 1
1✔
372

373
   Botan::DER_Encoder set_enc;
1✔
374
   set_enc.start_set(23).raw_bytes(first).raw_bytes(second).end_cons();
1✔
375
   const auto implicit_set = set_enc.get_contents_unlocked();
1✔
376
   result.test_bin_eq("implicit SET is still sorted", implicit_set, "B706020101020102");
1✔
377

378
   const ASN1_Test_Sequence seq(42);
1✔
379
   const auto implicit_seq = Botan::DER_Encoder().encode_implicit(seq, Botan::ASN1_Type(3)).get_contents_unlocked();
1✔
380
   result.test_bin_eq("implicit constructed object keeps constructed bit", implicit_seq, "A30302012A");
1✔
381

382
   ASN1_Test_Sequence decoded;
1✔
383
   Botan::BER_Decoder(implicit_seq, Botan::BER_Decoder::Limits::DER())
2✔
384
      .decode_implicit(decoded,
1✔
385
                       Botan::ASN1_Type(3),
386
                       Botan::ASN1_Class::ContextSpecific | Botan::ASN1_Class::Constructed,
387
                       Botan::ASN1_Type::Sequence,
388
                       Botan::ASN1_Class::Constructed)
389
      .verify_end();
1✔
390
   result.test_sz_eq("implicit constructed object decodes", decoded.value(), 42);
1✔
391

392
   const std::vector<uint8_t> one_bit = {0x80};
1✔
393
   const auto implicit_bitstring =
1✔
394
      Botan::DER_Encoder()
1✔
395
         .encode_bitstring(one_bit, 7, Botan::ASN1_Type(1), Botan::ASN1_Class::ContextSpecific)
2✔
396
         .get_contents_unlocked();
1✔
397
   result.test_bin_eq("implicit BIT STRING keeps unused bit count", implicit_bitstring, "81020780");
1✔
398

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

403
   return result;
1✔
404
}
1✔
405

406
Test::Result test_asn1_bitstring_helpers() {
1✔
407
   Test::Result result("ASN.1 BIT STRING helpers");
1✔
408

409
   const std::vector<uint8_t> raw_der = {0x03, 0x03, 0x03, 0xA8, 0x00};
1✔
410
   Botan::ASN1_BitString raw_bits;
1✔
411
   Botan::BER_Decoder(raw_der, Botan::BER_Decoder::Limits::DER()).decode_bitstring(raw_bits).verify_end();
1✔
412

413
   result.test_sz_eq("raw bytes", raw_bits.bytes().size(), 2);
1✔
414
   result.test_sz_eq("raw unused bits", raw_bits.unused_bits(), 3);
1✔
415
   result.test_sz_eq("raw bit length", raw_bits.bit_length(), 13);
1✔
416
   result.test_is_true("raw bit 0", raw_bits.bit_at(0));
1✔
417
   result.test_is_false("raw bit 1", raw_bits.bit_at(1));
1✔
418
   result.test_is_true("raw bit 2", raw_bits.bit_at(2));
1✔
419

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

423
   const std::vector<uint8_t> octet_aligned_der = {0x03, 0x02, 0x00, 0xAA};
1✔
424
   std::vector<uint8_t> octets;
1✔
425
   Botan::BER_Decoder(octet_aligned_der, Botan::BER_Decoder::Limits::DER())
2✔
426
      .decode_octet_aligned_bitstring(octets)
1✔
427
      .verify_end();
1✔
428
   const std::vector<uint8_t> expected_octets = {0xAA};
1✔
429
   result.test_bin_eq("octet-aligned BIT STRING decodes as bytes", octets, expected_octets);
1✔
430

431
   const std::vector<uint8_t> non_octet_aligned_der = {0x03, 0x02, 0x01, 0x80};
1✔
432
   result.test_throws<Botan::Decoding_Error>("octet-aligned BIT STRING rejects unused bits", [&] {
1✔
433
      std::vector<uint8_t> rejected;
1✔
434
      Botan::BER_Decoder(non_octet_aligned_der, Botan::BER_Decoder::Limits::DER())
3✔
435
         .decode_octet_aligned_bitstring(rejected)
1✔
436
         .verify_end();
×
437
   });
1✔
438

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

444
   uint64_t decoded_named = 0;
1✔
445
   Botan::BER_Decoder(named_der, Botan::BER_Decoder::Limits::DER())
2✔
446
      .decode_named_bitstring(decoded_named, 16)
1✔
447
      .verify_end();
1✔
448
   result.test_u64_eq("named BIT STRING round-trips", decoded_named, named);
1✔
449

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

454
   const std::vector<uint8_t> non_minimal_named_der = {0x03, 0x02, 0x00, 0x80};
1✔
455
   result.test_throws<Botan::BER_Decoding_Error>("DER named BIT STRING rejects trailing zero bits", [&] {
1✔
456
      uint64_t rejected = 0;
1✔
457
      Botan::BER_Decoder(non_minimal_named_der, Botan::BER_Decoder::Limits::DER())
2✔
458
         .decode_named_bitstring(rejected, 16)
1✔
459
         .verify_end();
×
460
   });
×
461

462
   uint64_t non_minimal_named = 0;
1✔
463
   Botan::BER_Decoder(non_minimal_named_der, Botan::BER_Decoder::Limits::BER())
2✔
464
      .decode_named_bitstring(non_minimal_named, 16)
1✔
465
      .verify_end();
1✔
466
   result.test_u64_eq("BER named BIT STRING accepts trailing zero bits", non_minimal_named, uint64_t(1) << 15);
1✔
467

468
   return result;
1✔
469
}
1✔
470

471
Test::Result test_ber_indefinite_length_trailing_data() {
1✔
472
   Test::Result result("BER indefinite length trailing data");
1✔
473

474
   // Case 1: verify_end after consuming indef SEQUENCE
475
   try {
1✔
476
      const std::vector<uint8_t> enc = {0x30, 0x80, 0x02, 0x01, 0x42, 0x00, 0x00};
1✔
477
      Botan::BER_Decoder dec(enc);
1✔
478
      Botan::BigInt x;
1✔
479
      dec.start_sequence().decode(x).end_cons();
1✔
480
      dec.verify_end();
1✔
481
      result.test_bn_eq("verify_end decoded x", x, Botan::BigInt(0x42));
1✔
482
   } catch(Botan::Exception& e) {
1✔
483
      result.test_failure("verify_end after indef SEQUENCE", e.what());
×
484
   }
×
485

486
   // Case 2: two back-to-back indef SEQUENCES at top level
487
   try {
1✔
488
      const std::vector<uint8_t> enc = {
1✔
489
         0x30, 0x80, 0x02, 0x01, 0x42, 0x00, 0x00, 0x30, 0x80, 0x02, 0x01, 0x43, 0x00, 0x00};
1✔
490
      Botan::BER_Decoder dec(enc);
1✔
491
      Botan::BigInt x;
1✔
492
      Botan::BigInt y;
1✔
493
      dec.start_sequence().decode(x).end_cons();
1✔
494
      dec.start_sequence().decode(y).end_cons();
1✔
495
      dec.verify_end();
1✔
496
      result.test_bn_eq("back-to-back x", x, Botan::BigInt(0x42));
1✔
497
      result.test_bn_eq("back-to-back y", y, Botan::BigInt(0x43));
1✔
498
   } catch(Botan::Exception& e) {
1✔
499
      result.test_failure("two back-to-back indef SEQUENCES", e.what());
×
500
   }
×
501

502
   // Case 3: nested indef SEQUENCES
503
   try {
1✔
504
      const std::vector<uint8_t> enc = {0x30, 0x80, 0x30, 0x80, 0x02, 0x01, 0x42, 0x00, 0x00, 0x00, 0x00};
1✔
505
      Botan::BER_Decoder dec(enc);
1✔
506
      Botan::BigInt x;
1✔
507
      auto outer = dec.start_sequence();
1✔
508
      outer.start_sequence().decode(x).end_cons();
1✔
509
      outer.end_cons();
1✔
510
      dec.verify_end();
1✔
511
      result.test_bn_eq("nested x", x, Botan::BigInt(0x42));
1✔
512
   } catch(Botan::Exception& e) {
1✔
513
      result.test_failure("nested indef SEQUENCE", e.what());
×
514
   }
×
515

516
   // Case 4: while(more_items()) loop over an indef SEQUENCE
517
   try {
1✔
518
      const std::vector<uint8_t> enc = {0x30, 0x80, 0x02, 0x01, 0x42, 0x02, 0x01, 0x43, 0x00, 0x00};
1✔
519
      Botan::BER_Decoder dec(enc);
1✔
520
      auto seq = dec.start_sequence();
1✔
521
      std::vector<Botan::BigInt> xs;
1✔
522
      while(seq.more_items()) {
3✔
523
         Botan::BigInt x;
2✔
524
         seq.decode(x);
2✔
525
         xs.push_back(x);
2✔
526
      }
2✔
527
      seq.end_cons();
1✔
528
      dec.verify_end();
1✔
529
      result.test_sz_eq("more_items count", xs.size(), 2);
1✔
530
      if(xs.size() == 2) {
1✔
531
         result.test_bn_eq("more_items xs[0]", xs[0], Botan::BigInt(0x42));
1✔
532
         result.test_bn_eq("more_items xs[1]", xs[1], Botan::BigInt(0x43));
1✔
533
      }
534
   } catch(Botan::Exception& e) {
1✔
535
      result.test_failure("more_items loop over indef SEQUENCE", e.what());
×
536
   }
×
537

538
   return result;
1✔
539
}
×
540

541
Test::Result test_ber_find_eoc() {
1✔
542
   Test::Result result("BER indefinite length EOC matching");
1✔
543

544
   const size_t num_siblings = 4096;
1✔
545

546
   std::vector<uint8_t> ber;
1✔
547
   ber.push_back(0x30);  // outer SEQUENCE | CONSTRUCTED
1✔
548
   ber.push_back(0x80);  // indefinite length
1✔
549
   for(size_t i = 0; i != num_siblings; ++i) {
4,097✔
550
      ber.push_back(0x30);  // inner SEQUENCE | CONSTRUCTED
4,096✔
551
      ber.push_back(0x80);  // indefinite length
4,096✔
552
      ber.push_back(0x00);  // EOC tag
4,096✔
553
      ber.push_back(0x00);  // EOC length
4,096✔
554
   }
555
   ber.push_back(0x00);  // outer EOC tag
1✔
556
   ber.push_back(0x00);  // outer EOC length
1✔
557

558
   try {
1✔
559
      Botan::BER_Decoder dec(ber);
1✔
560
      const Botan::BER_Object obj = dec.get_next_object();
1✔
561

562
      result.test_sz_eq("object body includes children", obj.length(), num_siblings * 4);
1✔
563
   } catch(Botan::Exception& e) {
1✔
564
      result.test_failure("decode failed", e.what());
×
565
   }
×
566

567
   return result;
1✔
568
}
1✔
569

570
Test::Result test_asn1_string_zero_length_roundtrip() {
1✔
571
   Test::Result result("ASN.1 String zero-length round-trip");
1✔
572

573
   auto roundtrip = [&](const char* what, const std::vector<uint8_t>& wire) {
4✔
574
      try {
3✔
575
         Botan::DataSource_Memory input(wire.data(), wire.size());
3✔
576
         Botan::BER_Decoder dec(input);
3✔
577
         Botan::ASN1_String str;
3✔
578
         str.decode_from(dec);
3✔
579

580
         Botan::DER_Encoder enc;
3✔
581
         str.encode_into(enc);
3✔
582
         const auto out = enc.get_contents();
3✔
583
         result.test_bin_eq(what, std::span{out}, std::span{wire});
3✔
584
      } catch(const std::exception& ex) {
9✔
585
         result.test_failure(Botan::fmt("{}: unexpected throw: {}", what, ex.what()));
×
586
      }
×
587
   };
3✔
588

589
   roundtrip("BmpString 1E 00", {0x1E, 0x00});
1✔
590
   roundtrip("UniversalString 1C 00", {0x1C, 0x00});
1✔
591
   roundtrip("TeletexString 14 00", {0x14, 0x00});
1✔
592

593
   return result;
1✔
594
}
×
595

596
Test::Result test_pss_params_rejects_trailing_data_in_mgf1_params() {
1✔
597
   Test::Result result("PSS-Params rejects trailing data in MGF1 parameters");
1✔
598

599
   const Botan::AlgorithmIdentifier sha256_alg_id("SHA-256", Botan::AlgorithmIdentifier::USE_NULL_PARAM);
1✔
600
   const auto sha256_der = sha256_alg_id.BER_encode();
1✔
601

602
   auto encode_pss_params = [&](const std::vector<uint8_t>& mgf_params) {
3✔
603
      const Botan::AlgorithmIdentifier mgf("MGF1", mgf_params);
2✔
604
      Botan::DER_Encoder enc;
2✔
605
      enc.start_sequence()
2✔
606
         .start_context_specific(0)
2✔
607
         .encode(sha256_alg_id)
2✔
608
         .end_cons()
2✔
609
         .start_context_specific(1)
2✔
610
         .encode(mgf)
2✔
611
         .end_cons()
2✔
612
         .start_context_specific(2)
2✔
613
         .encode(static_cast<size_t>(32))
2✔
614
         .end_cons()
2✔
615
         .end_cons();
2✔
616
      return enc.get_contents();
2✔
617
   };
4✔
618

619
   try {
1✔
620
      const auto clean_der = encode_pss_params(sha256_der);
1✔
621
      const Botan::PSS_Params clean(clean_der);
1✔
622
      result.test_success("control: clean PSS-Params decodes");
1✔
623
   } catch(const std::exception& e) {
2✔
624
      result.test_failure(Botan::fmt("clean PSS-Params unexpected throw: {}", e.what()));
×
625
   }
×
626

627
   std::vector<uint8_t> mgf_params_with_junk = sha256_der;
1✔
628
   const std::vector<uint8_t> trailing_junk{0x02, 0x01, 0x00};
1✔
629
   mgf_params_with_junk.insert(mgf_params_with_junk.end(), trailing_junk.begin(), trailing_junk.end());
1✔
630
   const auto bad_der = encode_pss_params(mgf_params_with_junk);
1✔
631

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

635
   return result;
1✔
636
}
2✔
637

638
Test::Result test_alg_id_parameter_validation() {
1✔
639
   Test::Result result("AlgorithmIdentifier parameter validation");
1✔
640

641
   auto decode_alg_id = [](std::string_view hex) {
10✔
642
      const auto wire = Botan::hex_decode(hex);
9✔
643
      Botan::AlgorithmIdentifier alg_id;
9✔
644
      Botan::BER_Decoder(wire).decode(alg_id).verify_end();
13✔
645
   };
9✔
646

647
   auto verify_params_accepted = [&](const std::string& label, std::string_view hex) {
6✔
648
      try {
5✔
649
         decode_alg_id(hex);
5✔
650
         result.test_success(Botan::fmt("{} parameters accepted", label));
10✔
651
      } catch(const std::exception& e) {
×
652
         result.test_failure(Botan::fmt("{} parameters unexpectedly rejected: {}", label, e.what()));
×
653
      }
×
654
   };
5✔
655

656
   auto verify_params_rejected = [&](const std::string& label, std::string_view hex) {
5✔
657
      result.test_throws<Botan::Decoding_Error>(Botan::fmt("{} parameters rejected", label),
8✔
658
                                                [&]() { decode_alg_id(hex); });
8✔
659
   };
4✔
660

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

663
   verify_params_accepted("absent", "30050603550403");
1✔
664
   verify_params_accepted("NULL", "300706035504030500");
1✔
665
   verify_params_accepted("SEQUENCE", "300706035504033000");
1✔
666
   verify_params_accepted("OID", "300A06035504030603550403");
1✔
667
   verify_params_accepted("OCTET STRING", "300906035504030402AABB");
1✔
668

669
   verify_params_rejected("two values (NULL, NULL)", "3009060355040305000500");
1✔
670
   verify_params_rejected("trailing data after NULL", "300A06035504030500020100");
1✔
671
   verify_params_rejected("truncated SEQUENCE", "300706035504033005");
1✔
672
   verify_params_rejected("single INTEGER", "30080603550403020100");
1✔
673

674
   return result;
1✔
675
}
×
676

677
class ASN1_Tests final : public Test {
1✔
678
   public:
679
      std::vector<Test::Result> run() override {
1✔
680
         std::vector<Test::Result> results;
1✔
681

682
         results.push_back(test_ber_stack_recursion());
2✔
683
         results.push_back(test_ber_eoc_decoding_limits());
2✔
684
         results.push_back(test_ber_indefinite_length_trailing_data());
2✔
685
         results.push_back(test_ber_find_eoc());
2✔
686
         results.push_back(test_asn1_utf8_ascii_parsing());
2✔
687
         results.push_back(test_asn1_utf8_parsing());
2✔
688
         results.push_back(test_asn1_ucs2_parsing());
2✔
689
         results.push_back(test_asn1_ucs4_parsing());
2✔
690
         results.push_back(test_asn1_ucs_invalid_codepoint_rejection());
2✔
691
         results.push_back(test_asn1_ascii_encoding());
2✔
692
         results.push_back(test_asn1_utf8_encoding());
2✔
693
         results.push_back(test_asn1_tag_underlying_type());
2✔
694
         results.push_back(test_asn1_negative_int_encoding());
2✔
695
         results.push_back(test_der_constructed_tag_17_not_sorted());
2✔
696
         results.push_back(test_der_implicit_tagging_helpers());
2✔
697
         results.push_back(test_asn1_bitstring_helpers());
2✔
698
         results.push_back(test_asn1_string_zero_length_roundtrip());
2✔
699
         results.push_back(test_pss_params_rejects_trailing_data_in_mgf1_params());
2✔
700
         results.push_back(test_alg_id_parameter_validation());
2✔
701

702
         return results;
1✔
703
      }
×
704
};
705

706
BOTAN_REGISTER_TEST("asn1", "asn1_encoding", ASN1_Tests);
707

708
class ASN1_Time_Parsing_Tests final : public Text_Based_Test {
×
709
   public:
710
      ASN1_Time_Parsing_Tests() : Text_Based_Test("asn1_time.vec", "Tspec") {}
2✔
711

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

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

717
         if(tag_str != "UTC" && tag_str != "UTC.invalid" && tag_str != "Generalized" &&
26✔
718
            tag_str != "Generalized.invalid") {
13✔
719
            throw Test_Error("Invalid tag value in ASN1 date parsing test");
×
720
         }
721

722
         const bool out_of_range = [&]() -> bool {
78✔
723
            if(tspec.size() == 15) {
26✔
724
               const size_t year = Botan::to_u32bit(std::string_view(tspec).substr(0, 4));
24✔
725
               if(year >= 2262) {
24✔
726
                  return true;
727
               }
728
               if(year >= 2038 && sizeof(time_t) == 4) {
729
                  return true;
730
               }
731
            }
732

733
            return false;
734
         }();
26✔
735

736
         const Botan::ASN1_Type tag = (tag_str == "UTC" || tag_str == "UTC.invalid")
25✔
737
                                         ? Botan::ASN1_Type::UtcTime
26✔
738
                                         : Botan::ASN1_Type::GeneralizedTime;
26✔
739

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

742
         if(valid) {
26✔
743
            const Botan::ASN1_Time time(tspec, tag);
12✔
744
            result.test_success("Accepted valid time");
12✔
745

746
            try {
12✔
747
               const auto std_timepoint = time.to_std_timepoint();
12✔
748
               result.test_success("Was able to convert time to std timepoint");
10✔
749

750
               const auto from_std_timepoint = Botan::ASN1_Time::from_time_point(std_timepoint);
10✔
751
               result.test_is_true("ASN1_Time from std timepoint matches input", from_std_timepoint == time);
10✔
752
            } catch(std::exception& e) {
12✔
753
               if(out_of_range) {
2✔
754
                  result.test_str_contains("Exception message", e.what(), "time is outside the representable range");
2✔
755
               } else {
756
                  result.test_failure("Was not able to convert time to std timepoint", e.what());
×
757
               }
758
            }
2✔
759
         } else {
12✔
760
            result.test_throws("Invalid time rejected", [=]() { const Botan::ASN1_Time time(tspec, tag); });
70✔
761
         }
762

763
         return result;
26✔
764
      }
26✔
765
};
766

767
BOTAN_REGISTER_TEST("asn1", "asn1_time", ASN1_Time_Parsing_Tests);
768

769
class ASN1_String_Validation_Tests final : public Text_Based_Test {
×
770
   public:
771
      ASN1_String_Validation_Tests() :
1✔
772
            Text_Based_Test("asn1_string_validation.vec",
773
                            "Input,ValidNumeric,ValidPrintable,ValidIa5,ValidVisible,ValidUtf8") {}
2✔
774

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

778
         const auto input = vars.get_req_str("Input");
7✔
779
         const bool valid_numeric = vars.get_req_bool("ValidNumeric");
7✔
780
         const bool valid_printable = vars.get_req_bool("ValidPrintable");
7✔
781
         const bool valid_ia5 = vars.get_req_bool("ValidIa5");
7✔
782
         const bool valid_visible = vars.get_req_bool("ValidVisible");
7✔
783
         const bool valid_utf8 = vars.get_req_bool("ValidUtf8");
7✔
784

785
         test_string_type(result, input, "NumericString", Botan::ASN1_Type::NumericString, valid_numeric);
7✔
786
         test_string_type(result, input, "PrintableString", Botan::ASN1_Type::PrintableString, valid_printable);
7✔
787
         test_string_type(result, input, "Ia5String", Botan::ASN1_Type::Ia5String, valid_ia5);
7✔
788
         test_string_type(result, input, "VisibleString", Botan::ASN1_Type::VisibleString, valid_visible);
7✔
789
         test_string_type(result, input, "Utf8String", Botan::ASN1_Type::Utf8String, valid_utf8);
7✔
790

791
         if(valid_utf8) {
7✔
792
            try {
7✔
793
               const Botan::ASN1_String str(input);
7✔
794
               const auto expected_tag =
14✔
795
                  valid_printable ? Botan::ASN1_Type::PrintableString : Botan::ASN1_Type::Utf8String;
7✔
796
               result.test_u32_eq("String tagging categorization",
7✔
797
                                  static_cast<uint32_t>(str.tagging()),
7✔
798
                                  static_cast<uint32_t>(expected_tag));
799
            } catch(const std::exception& ex) {
7✔
800
               result.test_failure(Botan::fmt("default constructor unexpectedly rejected '{}': {}", input, ex.what()));
×
801
            }
×
802
         }
803

804
         return result;
7✔
805
      }
7✔
806

807
   private:
808
      void test_string_type(Test::Result& result,
35✔
809
                            std::string_view input,
810
                            std::string_view type,
811
                            Botan::ASN1_Type tag,
812
                            bool expected_valid) {
813
         if(expected_valid) {
35✔
814
            try {
24✔
815
               const Botan::ASN1_String str(input, tag);
24✔
816
               result.test_str_eq(Botan::fmt("{} constructor value", type), str.value(), input);
24✔
817

818
               const auto enc = raw_encode_string(input, tag);
24✔
819
               Botan::BER_Decoder dec(enc);
24✔
820
               Botan::ASN1_String decoded;
24✔
821
               decoded.decode_from(dec);
24✔
822
               result.test_str_eq(Botan::fmt("{} decode value", type), decoded.value(), input);
48✔
823
            } catch(const std::exception& e) {
48✔
824
               result.test_failure(Botan::fmt("{} unexpectedly rejected '{}': {}", type, input, e.what()));
×
825
            }
×
826
         } else {
827
            result.test_throws(Botan::fmt("{} constructor rejects", type),
22✔
828
                               [&]() { const Botan::ASN1_String str(input, tag); });
22✔
829

830
            result.test_throws(Botan::fmt("{} decode rejects", type), [&]() {
22✔
831
               const auto enc = raw_encode_string(input, tag);
11✔
832
               Botan::BER_Decoder dec(enc);
11✔
833
               Botan::ASN1_String decoded;
11✔
834
               decoded.decode_from(dec);
11✔
835
            });
22✔
836
         }
837
      }
35✔
838

839
      static std::vector<uint8_t> raw_encode_string(std::string_view input, Botan::ASN1_Type tag) {
35✔
840
         std::vector<uint8_t> encoding;
35✔
841
         Botan::DER_Encoder der(encoding);
35✔
842
         der.add_object(tag, Botan::ASN1_Class::Universal, input);
35✔
843
         return encoding;
35✔
844
      }
35✔
845
};
846

847
BOTAN_REGISTER_TEST("asn1", "asn1_string_validation", ASN1_String_Validation_Tests);
848

849
class ASN1_Printer_Tests final : public Test {
1✔
850
   public:
851
      std::vector<Test::Result> run() override {
1✔
852
         Test::Result result("ASN1_Pretty_Printer");
1✔
853

854
         const Botan::ASN1_Pretty_Printer printer;
1✔
855

856
         const size_t num_tests = 8;
1✔
857

858
         for(size_t i = 1; i <= num_tests; ++i) {
9✔
859
            const std::string i_str = std::to_string(i);
8✔
860
            const std::vector<uint8_t> input_data = Test::read_binary_data_file("asn1_print/input" + i_str + ".der");
24✔
861
            const std::string expected_output = Test::read_data_file("asn1_print/output" + i_str + ".txt");
24✔
862

863
            try {
8✔
864
               const std::string output = printer.print(input_data);
8✔
865
               result.test_str_eq("Test " + i_str, output, expected_output);
16✔
866
            } catch(Botan::Exception& e) {
8✔
867
               result.test_failure(Botan::fmt("Printing test {} failed with an exception: '{}'", i, e.what()));
×
868
            }
×
869
         }
8✔
870

871
         return {result};
2✔
872
      }
10✔
873
};
874

875
BOTAN_REGISTER_TEST("asn1", "asn1_printer", ASN1_Printer_Tests);
876

877
class ASN1_Decoding_Tests final : public Text_Based_Test {
×
878
   public:
879
      ASN1_Decoding_Tests() : Text_Based_Test("asn1_decoding.vec", "Input,ResultBER", "ResultDER") {}
2✔
880

881
      Test::Result run_one_test(const std::string& /*header*/, const VarMap& vars) override {
55✔
882
         const auto input = vars.get_req_bin("Input");
55✔
883
         const std::string expected_ber = vars.get_req_str("ResultBER");
55✔
884
         const std::string expected_der = vars.get_opt_str("ResultDER", expected_ber);
55✔
885

886
         Test::Result result("ASN1 decoding");
55✔
887

888
         decoding_test(result, input, expected_ber, false);
55✔
889
         decoding_test(result, input, expected_der, true);
55✔
890

891
         return result;
110✔
892
      }
55✔
893

894
   private:
895
      static void decoding_test(Test::Result& result,
110✔
896
                                std::span<const uint8_t> input,
897
                                std::string_view expected,
898
                                bool require_der) {
899
         const Botan::ASN1_Pretty_Printer printer(4096, 2048, true, 0, 60, 64, require_der);
110✔
900
         const std::string mode = require_der ? "DER" : "BER";
165✔
901
         std::ostringstream sink;
110✔
902

903
         try {
110✔
904
            printer.print_to_stream(sink, input.data(), input.size());
110✔
905

906
            if(expected == "OK") {
52✔
907
               result.test_success();
52✔
908
            } else {
909
               result.test_failure(Botan::fmt("Accepted invalid {} input, expected error {}", mode, expected));
×
910
            }
911
         } catch(const std::exception& e) {
58✔
912
            if(expected == "OK") {
58✔
913
               result.test_failure(Botan::fmt("Rejected valid {} input with {}", mode, e.what()));
×
914
            } else {
915
               // BER_Decoding_Error prepends "BER: " to the message
916
               std::string msg = e.what();
58✔
917
               if(msg.starts_with("BER: ")) {
58✔
918
                  msg = msg.substr(5);
38✔
919
               }
920
               result.test_str_eq("error message", msg, expected);
58✔
921
            }
58✔
922
         }
58✔
923
      }
148✔
924
};
925

926
BOTAN_REGISTER_TEST("asn1", "asn1_decoding", ASN1_Decoding_Tests);
927

928
#endif
929

930
}  // namespace
931

932
}  // 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