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

randombit / botan / 28217640675

26 Jun 2026 03:19AM UTC coverage: 89.354% (-0.001%) from 89.355%
28217640675

push

github

web-flow
Merge pull request #5699 from randombit/jack/faster-iso9796-test

Reduce runtime for the ISO 9796 padding test

112053 of 125404 relevant lines covered (89.35%)

10896923.85 hits per line

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

94.88
/src/tests/unit_x509.cpp
1
/*
2
* (C) 2009,2019 Jack Lloyd
3
* (C) 2016 René Korthaus, Rohde & Schwarz Cybersecurity
4
*
5
* Botan is released under the Simplified BSD License (see license.txt)
6
*/
7

8
#include "tests.h"
9

10
#if defined(BOTAN_HAS_X509_CERTIFICATES)
11
   #include <botan/ber_dec.h>
12
   #include <botan/der_enc.h>
13
   #include <botan/hex.h>
14
   #include <botan/pk_algs.h>
15
   #include <botan/pkcs10.h>
16
   #include <botan/pkcs8.h>
17
   #include <botan/pubkey.h>
18
   #include <botan/rng.h>
19
   #include <botan/x509_ca.h>
20
   #include <botan/x509_ext.h>
21
   #include <botan/x509path.h>
22
   #include <botan/x509self.h>
23
   #include <botan/internal/calendar.h>
24
   #include <algorithm>
25

26
   #if defined(BOTAN_HAS_ECC_GROUP)
27
      #include <botan/ec_group.h>
28
   #endif
29
#endif
30

31
namespace Botan_Tests {
32

33
namespace {
34

35
#if defined(BOTAN_HAS_X509_CERTIFICATES)
36

37
Botan::X509_Time from_date(const int y, const int m, const int d) {
336✔
38
   const size_t this_year = Botan::calendar_point(std::chrono::system_clock::now()).year();
336✔
39

40
   const Botan::calendar_point t(static_cast<uint32_t>(this_year + y), m, d, 0, 0, 0);
336✔
41
   return Botan::X509_Time(t.to_std_timepoint());
336✔
42
}
43

44
/* Return some option sets */
45
Botan::X509_Cert_Options ca_opts(const std::string& sig_padding = "") {
112✔
46
   Botan::X509_Cert_Options opts("Test CA/US/Botan Project/Testing");
112✔
47

48
   opts.uri = "https://botan.randombit.net";
112✔
49
   opts.dns = "botan.randombit.net";
112✔
50
   opts.email = "testing@randombit.net";
112✔
51
   opts.set_padding_scheme(sig_padding);
112✔
52

53
   opts.CA_key(1);
112✔
54

55
   return opts;
112✔
56
}
×
57

58
Botan::X509_Cert_Options req_opts1(const std::string& algo, const std::string& sig_padding = "") {
37✔
59
   Botan::X509_Cert_Options opts("Test User 1/US/Botan Project/Testing");
37✔
60

61
   opts.uri = "https://botan.randombit.net";
37✔
62
   opts.dns = "botan.randombit.net";
37✔
63
   opts.email = "testing@randombit.net";
37✔
64
   opts.set_padding_scheme(sig_padding);
37✔
65

66
   opts.not_before("160101200000Z");
37✔
67
   opts.not_after("300101200000Z");
37✔
68

69
   opts.challenge = "zoom";
37✔
70

71
   if(algo == "RSA") {
37✔
72
      opts.constraints = Botan::Key_Constraints::KeyEncipherment;
7✔
73
   } else if(algo == "DSA" || algo == "ECDSA" || algo == "ECGDSA" || algo == "ECKCDSA") {
30✔
74
      opts.constraints = Botan::Key_Constraints::DigitalSignature;
12✔
75
   }
76

77
   return opts;
37✔
78
}
×
79

80
Botan::X509_Cert_Options req_opts2(const std::string& sig_padding = "") {
11✔
81
   Botan::X509_Cert_Options opts("Test User 2/US/Botan Project/Testing");
11✔
82

83
   opts.uri = "https://botan.randombit.net";
11✔
84
   opts.dns = "botan.randombit.net";
11✔
85
   opts.email = "testing@randombit.net";
11✔
86
   opts.set_padding_scheme(sig_padding);
11✔
87

88
   opts.add_ex_constraint("PKIX.EmailProtection");
11✔
89

90
   return opts;
11✔
91
}
×
92

93
Botan::X509_Cert_Options req_opts3(const std::string& sig_padding = "") {
55✔
94
   Botan::X509_Cert_Options opts("Test User 2/US/Botan Project/Testing");
55✔
95

96
   opts.uri = "https://botan.randombit.net";
55✔
97
   opts.dns = "botan.randombit.net";
55✔
98
   opts.email = "testing@randombit.net";
55✔
99
   opts.set_padding_scheme(sig_padding);
55✔
100

101
   opts.more_org_units.push_back("IT");
110✔
102
   opts.more_org_units.push_back("Security");
110✔
103
   opts.more_dns.push_back("www.botan.randombit.net");
110✔
104

105
   return opts;
55✔
106
}
×
107

108
std::unique_ptr<Botan::Private_Key> make_a_private_key(const std::string& algo, Botan::RandomNumberGenerator& rng) {
99✔
109
   const std::string params = [&] {
198✔
110
      // Here we override defaults as needed
111
      if(algo == "RSA") {
99✔
112
         return "1024";
113
      }
114
      if(algo == "GOST-34.10") {
89✔
115
   #if defined(BOTAN_HAS_ECC_GROUP)
116
         if(Botan::EC_Group::supports_named_group("gost_256A")) {
8✔
117
            return "gost_256A";
118
         }
119
   #endif
120
         return "secp256r1";
×
121
      }
122
      if(algo == "ECKCDSA" || algo == "ECGDSA") {
81✔
123
         return "brainpool256r1";
124
      }
125
      if(algo == "HSS-LMS") {
65✔
126
         return "SHA-256,HW(5,4),HW(5,4)";
127
      }
128
      if(algo == "SLH-DSA") {
57✔
129
         return "SLH-DSA-SHA2-128f";
2✔
130
      }
131
      return "";  // default "" means choose acceptable algo-specific params
132
   }();
99✔
133

134
   try {
99✔
135
      return Botan::create_private_key(algo, rng, params);
99✔
136
   } catch(Botan::Not_Implemented&) {
×
137
      return {};
×
138
   }
×
139
}
99✔
140

141
Test::Result test_cert_status_strings() {
1✔
142
   Test::Result result("Certificate_Status_Code to_string");
1✔
143

144
   std::set<std::string> seen;
1✔
145

146
   result.test_str_eq("Same string",
1✔
147
                      Botan::to_string(Botan::Certificate_Status_Code::OK),
148
                      Botan::to_string(Botan::Certificate_Status_Code::VERIFIED));
149

150
   const Botan::Certificate_Status_Code codes[]{
1✔
151
      Botan::Certificate_Status_Code::OCSP_RESPONSE_GOOD,
152
      Botan::Certificate_Status_Code::OCSP_SIGNATURE_OK,
153
      Botan::Certificate_Status_Code::VALID_CRL_CHECKED,
154
      Botan::Certificate_Status_Code::OCSP_NO_HTTP,
155

156
      Botan::Certificate_Status_Code::CERT_SERIAL_NEGATIVE,
157
      Botan::Certificate_Status_Code::DN_TOO_LONG,
158

159
      Botan::Certificate_Status_Code::SIGNATURE_METHOD_TOO_WEAK,
160
      Botan::Certificate_Status_Code::NO_MATCHING_CRLDP,
161
      Botan::Certificate_Status_Code::UNTRUSTED_HASH,
162
      Botan::Certificate_Status_Code::NO_REVOCATION_DATA,
163
      Botan::Certificate_Status_Code::CERT_NOT_YET_VALID,
164
      Botan::Certificate_Status_Code::CERT_HAS_EXPIRED,
165
      Botan::Certificate_Status_Code::OCSP_NOT_YET_VALID,
166
      Botan::Certificate_Status_Code::OCSP_HAS_EXPIRED,
167
      Botan::Certificate_Status_Code::CRL_NOT_YET_VALID,
168
      Botan::Certificate_Status_Code::CRL_HAS_EXPIRED,
169
      Botan::Certificate_Status_Code::CERT_ISSUER_NOT_FOUND,
170
      Botan::Certificate_Status_Code::CANNOT_ESTABLISH_TRUST,
171
      Botan::Certificate_Status_Code::CERT_CHAIN_LOOP,
172
      Botan::Certificate_Status_Code::CHAIN_LACKS_TRUST_ROOT,
173
      Botan::Certificate_Status_Code::CHAIN_NAME_MISMATCH,
174
      Botan::Certificate_Status_Code::POLICY_ERROR,
175
      Botan::Certificate_Status_Code::DUPLICATE_CERT_POLICY,
176
      Botan::Certificate_Status_Code::INVALID_USAGE,
177
      Botan::Certificate_Status_Code::CERT_CHAIN_TOO_LONG,
178
      Botan::Certificate_Status_Code::CA_CERT_NOT_FOR_CERT_ISSUER,
179
      Botan::Certificate_Status_Code::NAME_CONSTRAINT_ERROR,
180
      Botan::Certificate_Status_Code::IPADDR_BLOCKS_ERROR,
181
      Botan::Certificate_Status_Code::AS_BLOCKS_ERROR,
182
      Botan::Certificate_Status_Code::CA_CERT_NOT_FOR_CRL_ISSUER,
183
      Botan::Certificate_Status_Code::OCSP_CERT_NOT_LISTED,
184
      Botan::Certificate_Status_Code::OCSP_BAD_STATUS,
185
      Botan::Certificate_Status_Code::CERT_NAME_NOMATCH,
186
      Botan::Certificate_Status_Code::UNKNOWN_CRITICAL_EXTENSION,
187
      Botan::Certificate_Status_Code::DUPLICATE_CERT_EXTENSION,
188
      Botan::Certificate_Status_Code::EXT_IN_V1_V2_CERT,
189
      Botan::Certificate_Status_Code::OCSP_SIGNATURE_ERROR,
190
      Botan::Certificate_Status_Code::OCSP_ISSUER_NOT_FOUND,
191
      Botan::Certificate_Status_Code::OCSP_RESPONSE_MISSING_KEYUSAGE,
192
      Botan::Certificate_Status_Code::OCSP_RESPONSE_INVALID,
193
      Botan::Certificate_Status_Code::CERT_IS_REVOKED,
194
      Botan::Certificate_Status_Code::CRL_BAD_SIGNATURE,
195
      Botan::Certificate_Status_Code::SIGNATURE_ERROR,
196
      Botan::Certificate_Status_Code::CERT_PUBKEY_INVALID,
197
      Botan::Certificate_Status_Code::SIGNATURE_ALGO_UNKNOWN,
198
      Botan::Certificate_Status_Code::SIGNATURE_ALGO_BAD_PARAMS,
199
   };
200

201
   for(const auto code : codes) {
47✔
202
      const std::string s = Botan::to_string(code);
46✔
203
      result.test_sz_gt("String is long enough to be informative", s.size(), 12);
46✔
204
      result.test_sz_eq("No duplicates", seen.count(s), 0);
46✔
205
      seen.insert(s);
46✔
206
   }
46✔
207

208
   return result;
1✔
209
}
1✔
210

211
Test::Result test_x509_extension() {
1✔
212
   Test::Result result("X509 Extensions API");
1✔
213

214
   Botan::Extensions extn;
1✔
215

216
   const auto oid_bc = Botan::OID::from_string("X509v3.BasicConstraints");
1✔
217
   const auto oid_skid = Botan::OID::from_string("X509v3.SubjectKeyIdentifier");
1✔
218

219
   extn.add(std::make_unique<Botan::Cert_Extension::Basic_Constraints>(true), true);
2✔
220

221
   result.test_is_true("Basic constraints is set", extn.extension_set(oid_bc));
1✔
222
   result.test_is_true("Basic constraints is critical", extn.critical_extension_set(oid_bc));
1✔
223
   result.test_is_true("SKID is not set", !extn.extension_set(oid_skid));
1✔
224
   result.test_is_true("SKID is not critical", !extn.critical_extension_set(oid_skid));
1✔
225

226
   result.test_bin_eq("Extension::get_extension_bits", extn.get_extension_bits(oid_bc), "30060101FF020100");
1✔
227

228
   result.test_throws("Extension::get_extension_bits throws if not set", [&]() { extn.get_extension_bits(oid_skid); });
2✔
229

230
   result.test_throws("Extension::add throws on second add",
1✔
231
                      [&]() { extn.add(std::make_unique<Botan::Cert_Extension::Basic_Constraints>(false), false); });
2✔
232

233
   result.test_bin_eq("Extension::get_extension_bits", extn.get_extension_bits(oid_bc), "30060101FF020100");
1✔
234

235
   result.test_is_true("Returns false since extension already existed",
1✔
236
                       !extn.add_new(std::make_unique<Botan::Cert_Extension::Basic_Constraints>(false), false));
2✔
237

238
   result.test_is_true("Basic constraints is still critical", extn.critical_extension_set(oid_bc));
1✔
239

240
   extn.replace(std::make_unique<Botan::Cert_Extension::Basic_Constraints>(false), false);
2✔
241
   result.test_is_true("Replaced basic constraints is not critical", !extn.critical_extension_set(oid_bc));
1✔
242
   result.test_bin_eq("Extension::get_extension_bits", extn.get_extension_bits(oid_bc), "3000");
1✔
243

244
   result.test_is_true("Delete returns false if extn not set", !extn.remove(oid_skid));
1✔
245
   result.test_is_true("Delete returns true if extn was set", extn.remove(oid_bc));
1✔
246
   result.test_is_true("Basic constraints is not set", !extn.extension_set(oid_bc));
1✔
247
   result.test_is_true("Basic constraints is not critical", !extn.critical_extension_set(oid_bc));
1✔
248

249
   std::vector<uint8_t> crl_number_bits;
1✔
250
   Botan::DER_Encoder(crl_number_bits).encode(size_t(42));
1✔
251

252
   std::vector<uint8_t> crl_number_extn_der;
1✔
253
   Botan::DER_Encoder(crl_number_extn_der)
1✔
254
      .start_sequence()
1✔
255
      .start_sequence()
1✔
256
      .encode(Botan::Cert_Extension::CRL_Number::static_oid())
2✔
257
      .encode(crl_number_bits, Botan::ASN1_Type::OctetString)
2✔
258
      .end_cons()
1✔
259
      .end_cons();
1✔
260

261
   Botan::Extensions decoded_extns;
1✔
262
   Botan::BER_Decoder dec(crl_number_extn_der);
1✔
263
   decoded_extns.decode_from(dec);
1✔
264

265
   const auto* crl_number = decoded_extns.get_extension_object_as<Botan::Cert_Extension::CRL_Number>();
1✔
266
   if(result.test_is_true("CRL number recognized without explicit context", crl_number != nullptr)) {
1✔
267
      result.test_bn_eq("Decoded CRL number", crl_number->crl_number(), Botan::BigInt(42));
1✔
268
      result.test_sz_eq("Decoded CRL number legacy accessor", crl_number->get_crl_number(), 42);
1✔
269
   }
270

271
   const Botan::BigInt large_crl_number("0xE3F59B2C66C2789E9AC53C545C80CF1F8B9E4BEA");
1✔
272
   const Botan::X509_CRL large_number_crl(Test::read_binary_data_file("x509/misc/crl_number_160bit.pem"));
1✔
273
   if(result.test_opt_not_null("X509_CRL large CRL number is present", large_number_crl.crl_number_bigint())) {
1✔
274
      result.test_bn_eq("X509_CRL large CRL number", large_number_crl.crl_number_bigint().value(), large_crl_number);
1✔
275
   }
276

277
   const auto* large_crl_number_extn =
1✔
278
      large_number_crl.extensions().get_extension_object_as<Botan::Cert_Extension::CRL_Number>();
2✔
279
   if(result.test_is_true("Large CRL number extension is present", large_crl_number_extn != nullptr)) {
1✔
280
      result.test_bn_eq("Large CRL number extension", large_crl_number_extn->crl_number(), large_crl_number);
1✔
281

282
      result.test_throws<Botan::Encoding_Error>("Large CRL number extension legacy accessor throws",
1✔
283
                                                [&]() { large_crl_number_extn->get_crl_number(); });
2✔
284
   }
285

286
   result.test_throws<Botan::Encoding_Error>("X509_CRL legacy CRL number accessor throws",
1✔
287
                                             [&]() { large_number_crl.crl_number(); });
2✔
288

289
   return result;
2✔
290
}
3✔
291

292
Test::Result test_x509_extension_decode_duplicate() {
1✔
293
   Test::Result result("X509 Extensions reject duplicate OID");
1✔
294

295
   const auto oid_bc = Botan::OID::from_string("X509v3.BasicConstraints");
1✔
296
   const std::vector<uint8_t> bc_bits = {0x30, 0x06, 0x01, 0x01, 0xFF, 0x02, 0x01, 0x00};
1✔
297

298
   std::vector<uint8_t> der;
1✔
299
   Botan::DER_Encoder enc(der);
1✔
300
   enc.start_sequence();
1✔
301
   for(size_t i = 0; i != 2; ++i) {
3✔
302
      enc.start_sequence().encode(oid_bc).encode(bc_bits, Botan::ASN1_Type::OctetString).end_cons();
2✔
303
   }
304
   enc.end_cons();
1✔
305

306
   Botan::Extensions extns;
1✔
307
   Botan::BER_Decoder dec(der);
1✔
308
   result.test_throws<Botan::Decoding_Error>("Duplicate extension OID is rejected at decode time",
1✔
309
                                             [&]() { extns.decode_from(dec); });
2✔
310

311
   return result;
1✔
312
}
2✔
313

314
Test::Result test_x509_dates() {
1✔
315
   Test::Result result("X509 Time");
1✔
316

317
   Botan::X509_Time time;
1✔
318
   result.test_is_true("unset time not set", !time.time_is_set());
1✔
319
   time = Botan::X509_Time("080201182200Z", Botan::ASN1_Type::UtcTime);
1✔
320
   result.test_is_true("time set after construction", time.time_is_set());
1✔
321
   result.test_str_eq("time readable_string", time.readable_string(), "2008/02/01 18:22:00 UTC");
1✔
322

323
   time = Botan::X509_Time("200305100350Z", Botan::ASN1_Type::UtcTime);
1✔
324
   result.test_str_eq("UTC_TIME readable_string", time.readable_string(), "2020/03/05 10:03:50 UTC");
1✔
325

326
   time = Botan::X509_Time("200305100350Z");
1✔
327
   result.test_str_eq(
1✔
328
      "UTC_OR_GENERALIZED_TIME from UTC_TIME readable_string", time.readable_string(), "2020/03/05 10:03:50 UTC");
1✔
329

330
   time = Botan::X509_Time("20200305100350Z");
1✔
331
   result.test_str_eq("UTC_OR_GENERALIZED_TIME from GENERALIZED_TIME readable_string",
1✔
332
                      time.readable_string(),
1✔
333
                      "2020/03/05 10:03:50 UTC");
334

335
   time = Botan::X509_Time("20200305100350Z", Botan::ASN1_Type::GeneralizedTime);
1✔
336
   result.test_str_eq("GENERALIZED_TIME readable_string", time.readable_string(), "2020/03/05 10:03:50 UTC");
1✔
337

338
   // Dates that are valid per X.500 but rejected as unsupported
339
   const std::string valid_but_unsup[]{
1✔
340
      "0802010000-0000",
341
      "0802011724+0000",
342
      "0406142334-0500",
343
      "9906142334+0500",
344
      "0006142334-0530",
345
      "0006142334+0530",
346

347
      "080201000000-0000",
348
      "080201172412+0000",
349
      "040614233433-0500",
350
      "990614233444+0500",
351
      "000614233455-0530",
352
      "000614233455+0530",
353
   };
13✔
354

355
   // valid length 13
356
   const std::string valid_utc[]{
1✔
357
      "080201000000Z",
358
      "080201172412Z",
359
      "040614233433Z",
360
      "990614233444Z",
361
      "000614233455Z",
362
   };
6✔
363

364
   const std::string invalid_utc[]{
1✔
365
      "",
366
      " ",
367
      "2008`02-01",
368
      "9999-02-01",
369
      "2000-02-01 17",
370
      "999921",
371

372
      // No seconds
373
      "0802010000Z",
374
      "0802011724Z",
375
      "0406142334Z",
376
      "9906142334Z",
377
      "0006142334Z",
378

379
      // valid length 13 -> range check
380
      "080201000061Z",  // seconds too big (61)
381
      "080201000060Z",  // seconds too big (60, leap seconds not covered by the standard)
382
      "0802010000-1Z",  // seconds too small (-1)
383
      "080201006000Z",  // minutes too big (60)
384
      "080201240000Z",  // hours too big (24:00)
385

386
      // valid length 13 -> invalid numbers
387
      "08020123112 Z",
388
      "08020123112!Z",
389
      "08020123112,Z",
390
      "08020123112\nZ",
391
      "080201232 33Z",
392
      "080201232!33Z",
393
      "080201232,33Z",
394
      "080201232\n33Z",
395
      "0802012 3344Z",
396
      "0802012!3344Z",
397
      "0802012,3344Z",
398
      "08022\n334455Z",
399
      "08022 334455Z",
400
      "08022!334455Z",
401
      "08022,334455Z",
402
      "08022\n334455Z",
403
      "082 33445511Z",
404
      "082!33445511Z",
405
      "082,33445511Z",
406
      "082\n33445511Z",
407
      "2 2211221122Z",
408
      "2!2211221122Z",
409
      "2,2211221122Z",
410
      "2\n2211221122Z",
411

412
      // wrong time zone
413
      "080201000000",
414
      "080201000000z",
415

416
      // Fractional seconds
417
      "170217180154.001Z",
418

419
      // Timezone offset
420
      "170217180154+0100",
421

422
      // Extra digits
423
      "17021718015400Z",
424

425
      // Non-digits
426
      "17021718015aZ",
427

428
      // Trailing garbage
429
      "170217180154Zlongtrailinggarbage",
430

431
      // Swapped type
432
      "20170217180154Z",
433
   };
49✔
434

435
   // valid length 15
436
   const std::string valid_generalized_time[]{
1✔
437
      "20000305100350Z",
438
   };
2✔
439

440
   const std::string invalid_generalized[]{
1✔
441
      // No trailing Z
442
      "20000305100350",
443

444
      // No seconds
445
      "200003051003Z",
446

447
      // Fractional seconds
448
      "20000305100350.001Z",
449

450
      // Timezone offset
451
      "20170217180154+0100",
452

453
      // Extra digits
454
      "2017021718015400Z",
455

456
      // Non-digits
457
      "2017021718015aZ",
458

459
      // Trailing garbage
460
      "20170217180154Zlongtrailinggarbage",
461

462
      // Swapped type
463
      "170217180154Z",
464
   };
9✔
465

466
   for(const auto& v : valid_but_unsup) {
13✔
467
      result.test_throws("valid but unsupported", [v]() { const Botan::X509_Time t(v, Botan::ASN1_Type::UtcTime); });
60✔
468
   }
469

470
   for(const auto& v : valid_utc) {
6✔
471
      const Botan::X509_Time t(v, Botan::ASN1_Type::UtcTime);
5✔
472
   }
5✔
473

474
   for(const auto& v : valid_generalized_time) {
2✔
475
      const Botan::X509_Time t(v, Botan::ASN1_Type::GeneralizedTime);
1✔
476
   }
1✔
477

478
   for(const auto& v : invalid_utc) {
49✔
479
      result.test_throws("invalid", [v]() { const Botan::X509_Time t(v, Botan::ASN1_Type::UtcTime); });
240✔
480
   }
481

482
   for(const auto& v : invalid_generalized) {
9✔
483
      result.test_throws("invalid", [v]() { const Botan::X509_Time t(v, Botan::ASN1_Type::GeneralizedTime); });
40✔
484
   }
485

486
   return result;
1✔
487
}
80✔
488

489
Test::Result test_x509_encode_authority_info_access_extension() {
1✔
490
   Test::Result result("X509 with encoded PKIX.AuthorityInformationAccess extension");
1✔
491

492
   #if defined(BOTAN_HAS_RSA) && defined(BOTAN_HAS_EMSA_PKCS1)
493
   auto rng = Test::new_rng(__func__);
1✔
494

495
   const std::string sig_algo{"RSA"};
1✔
496
   const std::string hash_fn{"SHA-256"};
1✔
497
   const std::string padding_method{"PKCS1v15(SHA-256)"};
1✔
498

499
   // CA Issuer information
500
   const std::vector<Botan::URI> ca_issuers = {
1✔
501
      Botan::URI::parse("http://www.d-trust.net/cgi-bin/Bdrive_Test_CA_1-2_2017.crt").value(),
1✔
502
      Botan::URI::parse(
2✔
503
         "ldap://directory.d-trust.net/CN=Bdrive%20Test%20CA%201-2%202017,O=Bundesdruckerei%20GmbH,C=DE?cACertificate?base?")
504
         .value()};
3✔
505

506
   // OCSP
507
   const std::string_view ocsp_uri{"http://staging.ocsp.d-trust.net"};
1✔
508
   const auto ocsp_uri_parsed = Botan::URI::parse(ocsp_uri).value();
2✔
509

510
   // create a CA
511
   auto ca_key = make_a_private_key(sig_algo, *rng);
1✔
512
   result.require("CA key", ca_key != nullptr);
1✔
513
   const auto ca_cert = Botan::X509::create_self_signed_cert(ca_opts(), *ca_key, hash_fn, *rng);
1✔
514
   const Botan::X509_CA ca(ca_cert, *ca_key, hash_fn, padding_method, *rng);
1✔
515

516
   // create a certificate with only caIssuer information
517
   auto key = make_a_private_key(sig_algo, *rng);
1✔
518

519
   Botan::X509_Cert_Options opts1 = req_opts1(sig_algo);
1✔
520
   opts1.extensions.add(
2✔
521
      std::make_unique<Botan::Cert_Extension::Authority_Information_Access>(std::vector<Botan::URI>{}, ca_issuers));
2✔
522

523
   Botan::PKCS10_Request req = Botan::X509::create_cert_req(opts1, *key, hash_fn, *rng);
1✔
524

525
   Botan::X509_Certificate cert = ca.sign_request(req, *rng, from_date(-1, 01, 01), from_date(2, 01, 01));
1✔
526

527
   if(!result.test_sz_eq("number of ca_issuers URIs", cert.ca_issuer_uris().size(), 2)) {
1✔
528
      return result;
529
   }
530

531
   for(const auto& ca_issuer : cert.ca_issuer_uris()) {
3✔
532
      result.test_is_true("CA issuer URI present in certificate",
4✔
533
                          std::ranges::find(ca_issuers, ca_issuer) != ca_issuers.end());
4✔
534
   }
535

536
   result.test_is_true("no OCSP url available", cert.ocsp_responder_uris().empty());
1✔
537

538
   // create a certificate with only OCSP URI information
539
   Botan::X509_Cert_Options opts2 = req_opts1(sig_algo);
1✔
540
   opts2.extensions.add(
2✔
541
      std::make_unique<Botan::Cert_Extension::Authority_Information_Access>(std::vector<Botan::URI>{ocsp_uri_parsed}));
3✔
542

543
   req = Botan::X509::create_cert_req(opts2, *key, hash_fn, *rng);
1✔
544

545
   cert = ca.sign_request(req, *rng, from_date(-1, 01, 01), from_date(2, 01, 01));
1✔
546

547
   result.test_is_true("OCSP URI available", !cert.ocsp_responder_uris().empty());
1✔
548
   result.test_is_true("no CA Issuer URI available", cert.ca_issuer_uris().empty());
1✔
549
   result.test_str_eq("OCSP responder URI matches", cert.ocsp_responder_uris().at(0).original_input(), ocsp_uri);
1✔
550

551
   // create a certificate with OCSP URI and CA Issuer information
552
   Botan::X509_Cert_Options opts3 = req_opts1(sig_algo);
1✔
553
   opts3.extensions.add(std::make_unique<Botan::Cert_Extension::Authority_Information_Access>(
2✔
554
      std::vector<Botan::URI>{ocsp_uri_parsed}, ca_issuers));
3✔
555

556
   req = Botan::X509::create_cert_req(opts3, *key, hash_fn, *rng);
1✔
557

558
   cert = ca.sign_request(req, *rng, from_date(-1, 01, 01), from_date(2, 01, 01));
1✔
559

560
   result.test_is_true("OCSP URI available", !cert.ocsp_responder_uris().empty());
1✔
561
   result.test_is_true("CA Issuer URI available", !cert.ca_issuer_uris().empty());
1✔
562

563
   // create a certificate with multiple OCSP URIs
564
   Botan::X509_Cert_Options opts_multi_ocsp = req_opts1(sig_algo);
1✔
565
   const std::vector<std::string> ocsp_uris = {"http://ocsp.example.com", "http://backup-ocsp.example.com"};
1✔
566
   opts_multi_ocsp.extensions.add(std::make_unique<Botan::Cert_Extension::Authority_Information_Access>(ocsp_uris));
2✔
567

568
   req = Botan::X509::create_cert_req(opts_multi_ocsp, *key, hash_fn, *rng);
1✔
569

570
   cert = ca.sign_request(req, *rng, from_date(-1, 01, 01), from_date(2, 01, 01));
1✔
571

572
   const auto* aia_ext =
1✔
573
      cert.v3_extensions().get_extension_object_as<Botan::Cert_Extension::Authority_Information_Access>();
1✔
574
   result.test_is_true("AIA extension present", aia_ext != nullptr);
1✔
575

576
   const auto ocsp_responders = aia_ext->ocsp_responders();
1✔
577
   result.test_sz_eq("number of OCSP responder URIs", ocsp_responders.size(), 2);
1✔
578
   result.test_str_eq("First OCSP responder URI matches", ocsp_responders[0], "http://ocsp.example.com");
1✔
579
   result.test_str_eq("Second OCSP responder URI matches", ocsp_responders[1], "http://backup-ocsp.example.com");
1✔
580

581
   const auto& cert_ocsp_responders = cert.ocsp_responder_uris();
1✔
582
   result.test_sz_eq("Certificate: number of OCSP responder URIs", cert_ocsp_responders.size(), 2);
1✔
583
   result.test_str_eq("Certificate: First OCSP responder URI matches",
1✔
584
                      cert_ocsp_responders[0].original_input(),
1✔
585
                      "http://ocsp.example.com");
586
   result.test_str_eq("Certificate: Second OCSP responder URI matches",
1✔
587
                      cert_ocsp_responders[1].original_input(),
1✔
588
                      "http://backup-ocsp.example.com");
589
   #endif
590

591
   return result;
1✔
592
}
9✔
593

594
   #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
595

596
Test::Result test_crl_dn_name() {
1✔
597
   Test::Result result("CRL DN name");
1✔
598

599
      // See GH #1252
600

601
      #if defined(BOTAN_HAS_RSA) && defined(BOTAN_HAS_EMSA_PKCS1)
602
   auto rng = Test::new_rng(__func__);
1✔
603

604
   const Botan::OID dc_oid("0.9.2342.19200300.100.1.25");
1✔
605

606
   const Botan::X509_Certificate cert(Test::data_file("x509/misc/opcuactt_ca.der"));
2✔
607

608
   Botan::DataSource_Stream key_input(Test::data_file("x509/misc/opcuactt_ca.pem"));
2✔
609
   auto key = Botan::PKCS8::load_key(key_input);
1✔
610
   const Botan::X509_CA ca(cert, *key, "SHA-256", *rng);
1✔
611

612
   const Botan::X509_CRL crl = ca.new_crl(*rng);
1✔
613

614
   result.test_is_true("matches issuer cert", crl.issuer_dn() == cert.subject_dn());
1✔
615

616
   result.test_is_true("contains DC component", crl.issuer_dn().get_attributes().count(dc_oid) == 1);
2✔
617
      #endif
618

619
   return result;
2✔
620
}
3✔
621

622
Test::Result test_rdn_multielement_set_name() {
1✔
623
   Test::Result result("DN with multiple elements in RDN");
1✔
624

625
   // GH #2611
626

627
   const Botan::X509_Certificate cert(Test::data_file("x509/misc/rdn_set.crt"));
2✔
628

629
   result.test_is_true("issuer DN contains expected name components", cert.issuer_dn().get_attributes().size() == 4);
1✔
630
   result.test_is_true("subject DN contains expected name components", cert.subject_dn().get_attributes().size() == 4);
1✔
631

632
   return result;
1✔
633
}
1✔
634

635
Test::Result test_rsa_oaep() {
1✔
636
   Test::Result result("RSA OAEP decoding");
1✔
637

638
      #if defined(BOTAN_HAS_RSA)
639
   const Botan::X509_Certificate cert(Test::data_file("x509/misc/rsa_oaep.pem"));
2✔
640

641
   auto public_key = cert.subject_public_key();
1✔
642
   result.test_not_null("Decoding RSA-OAEP worked", public_key.get());
1✔
643
   const auto& pk_info = cert.subject_public_key_algo();
1✔
644

645
   result.test_str_eq("RSA-OAEP OID", pk_info.oid().to_string(), Botan::OID::from_string("RSA/OAEP").to_string());
1✔
646
      #endif
647

648
   return result;
2✔
649
}
1✔
650

651
Test::Result test_x509_decode_list() {
1✔
652
   Test::Result result("X509_Certificate list decode");
1✔
653

654
   Botan::DataSource_Stream input(Test::data_file("x509/misc/cert_seq.der"), true);
2✔
655

656
   Botan::BER_Decoder dec(input);
1✔
657
   std::vector<Botan::X509_Certificate> certs;
1✔
658
   dec.decode_list(certs);
1✔
659

660
   result.test_sz_eq("Expected number of certs in list", certs.size(), 2);
1✔
661

662
   result.test_str_eq("Expected cert 1 CN", certs[0].subject_dn().get_first_attribute("CN"), "CA1-PP.01.02");
1✔
663
   result.test_str_eq("Expected cert 2 CN", certs[1].subject_dn().get_first_attribute("CN"), "User1-PP.01.02");
1✔
664

665
   return result;
1✔
666
}
1✔
667

668
Test::Result test_x509_utf8() {
1✔
669
   Test::Result result("X509 with UTF-8 encoded fields");
1✔
670

671
   try {
1✔
672
      const Botan::X509_Certificate utf8_cert(Test::data_file("x509/misc/contains_utf8string.pem"));
2✔
673

674
      // UTF-8 encoded fields of test certificate (contains cyrillic letters)
675
      const std::string organization =
1✔
676
         "\xD0\x9C\xD0\xBE\xD1\x8F\x20\xD0\xBA\xD0\xBE\xD0"
677
         "\xBC\xD0\xBF\xD0\xB0\xD0\xBD\xD0\xB8\xD1\x8F";
1✔
678
      const std::string organization_unit =
1✔
679
         "\xD0\x9C\xD0\xBE\xD1\x91\x20\xD0\xBF\xD0\xBE\xD0\xB4\xD1\x80\xD0\xB0"
680
         "\xD0\xB7\xD0\xB4\xD0\xB5\xD0\xBB\xD0\xB5\xD0\xBD\xD0\xB8\xD0\xB5";
1✔
681
      const std::string common_name =
1✔
682
         "\xD0\x9E\xD0\xBF\xD0\xB8\xD1\x81\xD0\xB0\xD0\xBD\xD0\xB8"
683
         "\xD0\xB5\x20\xD1\x81\xD0\xB0\xD0\xB9\xD1\x82\xD0\xB0";
1✔
684
      const std::string location = "\xD0\x9C\xD0\xBE\xD1\x81\xD0\xBA\xD0\xB2\xD0\xB0";
1✔
685

686
      const Botan::X509_DN& issuer_dn = utf8_cert.issuer_dn();
1✔
687

688
      result.test_str_eq("O", issuer_dn.get_first_attribute("O"), organization);
1✔
689
      result.test_str_eq("OU", issuer_dn.get_first_attribute("OU"), organization_unit);
1✔
690
      result.test_str_eq("CN", issuer_dn.get_first_attribute("CN"), common_name);
1✔
691
      result.test_str_eq("L", issuer_dn.get_first_attribute("L"), location);
1✔
692
   } catch(const Botan::Decoding_Error& ex) {
1✔
693
      result.test_failure(ex.what());
×
694
   }
×
695

696
   return result;
1✔
697
}
×
698

699
Test::Result test_x509_any_key_extended_usage() {
1✔
700
   using Botan::Key_Constraints;
1✔
701
   using Botan::Usage_Type;
1✔
702

703
   Test::Result result("X509 with X509v3.AnyExtendedKeyUsage");
1✔
704
   try {
1✔
705
      const Botan::X509_Certificate any_eku_cert(Test::data_file("x509/misc/contains_any_extended_key_usage.pem"));
2✔
706

707
      result.test_is_true("is CA cert", any_eku_cert.is_CA_cert());
1✔
708
      result.test_is_true("DigitalSignature is allowed", any_eku_cert.allowed_usage(Key_Constraints::DigitalSignature));
1✔
709
      result.test_is_true("CrlSign is allowed", any_eku_cert.allowed_usage(Key_Constraints::CrlSign));
1✔
710
      result.test_is_false("OCSP responder is not allowed", any_eku_cert.allowed_usage(Usage_Type::OCSP_RESPONDER));
1✔
711
   } catch(const Botan::Decoding_Error& ex) {
1✔
712
      result.test_failure(ex.what());
×
713
   }
×
714
   return result;
1✔
715
}
×
716

717
Test::Result test_x509_bmpstring() {
1✔
718
   Test::Result result("X509 with UCS-2 (BMPString) encoded fields");
1✔
719

720
   try {
1✔
721
      const Botan::X509_Certificate ucs2_cert(Test::data_file("x509/misc/contains_bmpstring.pem"));
2✔
722

723
      // UTF-8 encoded fields of test certificate (contains cyrillic and greek letters)
724
      const std::string organization = "\x6E\x65\xCF\x87\xCF\xB5\x6E\x69\xCF\x89";
1✔
725
      const std::string common_name =
1✔
726
         "\xC3\xA8\x6E\xC7\x9D\xD0\xAF\x20\xD0\x9C\xC7\x9D\xD0\xB9\xD0\xB7\xD1\x8D\xD0\xBB";
1✔
727

728
      // UTF-8 encoded fields of test certificate (contains only ASCII characters)
729
      const std::string location = "Berlin";
1✔
730

731
      const Botan::X509_DN& issuer_dn = ucs2_cert.issuer_dn();
1✔
732

733
      result.test_str_eq("O", issuer_dn.get_first_attribute("O"), organization);
1✔
734
      result.test_str_eq("CN", issuer_dn.get_first_attribute("CN"), common_name);
1✔
735
      result.test_str_eq("L", issuer_dn.get_first_attribute("L"), location);
1✔
736
   } catch(const Botan::Decoding_Error& ex) {
1✔
737
      result.test_failure(ex.what());
×
738
   }
×
739

740
   return result;
1✔
741
}
×
742

743
Test::Result test_x509_teletex() {
1✔
744
   Test::Result result("X509 with TeletexString encoded fields");
1✔
745

746
   try {
1✔
747
      const Botan::X509_Certificate teletex_cert(Test::data_file("x509/misc/teletex_dn.der"));
2✔
748

749
      const Botan::X509_DN& issuer_dn = teletex_cert.issuer_dn();
1✔
750

751
      const std::string common_name = "neam Gesellschaft f\xc3\xbcr Kommunikationsl\xc3\xb6sungen mbH";
1✔
752

753
      result.test_str_eq("O", issuer_dn.get_first_attribute("O"), "neam CA");
1✔
754
      result.test_str_eq("CN", issuer_dn.get_first_attribute("CN"), common_name);
1✔
755
   } catch(const Botan::Decoding_Error& ex) {
1✔
756
      result.test_failure(ex.what());
×
757
   }
×
758

759
   return result;
1✔
760
}
×
761

762
Test::Result test_x509_authority_info_access_extension() {
1✔
763
   Test::Result result("X509 with PKIX.AuthorityInformationAccess extension");
1✔
764

765
   // contains no AIA extension
766
   const Botan::X509_Certificate no_aia_cert(Test::data_file("x509/misc/contains_utf8string.pem"));
2✔
767

768
   result.test_sz_eq("number of ca_issuers URLs", no_aia_cert.ca_issuer_uris().size(), 0);
1✔
769
   result.test_is_true("no OCSP responder", no_aia_cert.ocsp_responder_uris().empty());
1✔
770

771
   // contains AIA extension with 1 CA issuer URL and 1 OCSP responder
772
   const Botan::X509_Certificate aia_cert(Test::data_file("x509/misc/contains_authority_info_access.pem"));
2✔
773

774
   const auto& ca_issuers = aia_cert.ca_issuer_uris();
1✔
775

776
   result.test_sz_eq("number of ca_issuers URLs", ca_issuers.size(), 1);
1✔
777
   if(result.tests_failed() > 0) {
1✔
778
      return result;
779
   }
780

781
   result.test_str_eq("CA issuer URL matches", ca_issuers[0].original_input(), "http://gp.symcb.com/gp.crt");
1✔
782
   result.test_sz_eq("one OCSP responder URI", aia_cert.ocsp_responder_uris().size(), 1);
1✔
783
   result.test_str_eq(
2✔
784
      "OCSP responder URL matches", aia_cert.ocsp_responder_uris().at(0).original_input(), "http://gp.symcd.com");
1✔
785

786
   // contains AIA extension with 2 CA issuer URL and 1 OCSP responder
787
   const Botan::X509_Certificate aia_cert_2ca(
1✔
788
      Test::data_file("x509/misc/contains_authority_info_access_with_two_ca_issuers.pem"));
2✔
789

790
   const auto& ca_issuers2 = aia_cert_2ca.ca_issuer_uris();
1✔
791

792
   result.test_sz_eq("number of ca_issuers URLs", ca_issuers2.size(), 2);
1✔
793
   if(result.tests_failed() > 0) {
1✔
794
      return result;
795
   }
796

797
   result.test_str_eq("CA issuer URL matches",
1✔
798
                      ca_issuers2[0].original_input(),
1✔
799
                      "http://www.d-trust.net/cgi-bin/Bdrive_Test_CA_1-2_2017.crt");
800
   result.test_str_eq(
1✔
801
      "CA issuer URL matches",
802
      ca_issuers2[1].original_input(),
1✔
803
      "ldap://directory.d-trust.net/CN=Bdrive%20Test%20CA%201-2%202017,O=Bundesdruckerei%20GmbH,C=DE?cACertificate?base?");
804
   result.test_sz_eq("one OCSP responder URI", aia_cert_2ca.ocsp_responder_uris().size(), 1);
1✔
805
   result.test_str_eq("OCSP responder URL matches",
2✔
806
                      aia_cert_2ca.ocsp_responder_uris().at(0).original_input(),
1✔
807
                      "http://staging.ocsp.d-trust.net");
808

809
   // contains AIA extension with multiple OCSP responders
810
   const Botan::X509_Certificate aia_cert_multi_ocsp(
1✔
811
      Test::data_file("x509/misc/contains_multiple_ocsp_responders.pem"));
2✔
812

813
   const auto& ocsp_responders_multi = aia_cert_multi_ocsp.ocsp_responder_uris();
1✔
814
   result.test_sz_eq("number of OCSP responders", ocsp_responders_multi.size(), 3);
1✔
815
   result.test_str_eq(
1✔
816
      "First OCSP responder URL matches", ocsp_responders_multi[0].original_input(), "http://ocsp1.example.com");
1✔
817
   result.test_str_eq(
1✔
818
      "Second OCSP responder URL matches", ocsp_responders_multi[1].original_input(), "http://ocsp2.example.com");
1✔
819
   result.test_str_eq(
1✔
820
      "Third OCSP responder URL matches", ocsp_responders_multi[2].original_input(), "http://ocsp3.example.com");
1✔
821
   result.test_is_true("no CA Issuer URI available", aia_cert_multi_ocsp.ca_issuer_uris().empty());
1✔
822

823
   return result;
1✔
824
}
1✔
825

826
Test::Result test_x509_ldap_empty_authority_uris() {
1✔
827
   Test::Result result("X509 LDAP URIs with empty authority");
1✔
828

829
   const auto check_uri = [&](std::string_view label, const Botan::URI& uri, std::string_view expected) {
4✔
830
      result.test_str_eq(std::string(label) + " original URI", uri.original_input(), expected);
9✔
831
      result.test_str_eq(std::string(label) + " scheme", uri.scheme(), "ldap");
6✔
832
      const auto raw_authority = uri.raw_authority();
3✔
833
      result.test_is_true(std::string(label) + " raw authority present", raw_authority.has_value());
9✔
834
      if(raw_authority.has_value()) {
3✔
835
         result.test_str_eq(std::string(label) + " raw authority is empty", std::string(*raw_authority), "");
9✔
836
      }
837
      result.test_is_false(std::string(label) + " has no parsed authority", uri.authority().has_value());
9✔
838
      result.test_is_false(std::string(label) + " has no host", uri.host().has_value());
9✔
839
   };
33✔
840

841
   const auto check_contains_uri =
1✔
842
      [&](std::string_view label, const std::vector<Botan::URI>& uris, std::string_view expected) {
3✔
843
         for(const auto& uri : uris) {
5✔
844
            if(uri.original_input() == expected) {
5✔
845
               check_uri(label, uri, expected);
3✔
846
               return;
3✔
847
            }
848
         }
849
         result.test_failure(std::string(label) + " URI not found");
×
850
      };
1✔
851

852
   const Botan::X509_Certificate aruba_cert(Test::data_file("x509/misc/aruba.pem"));
2✔
853
   const std::string aruba_aia =
1✔
854
      "ldap:///CN=Security1-WIN-05PRGNGEKAO-CA,CN=AIA,CN=Public%20Key%20Services,CN=Services,CN=Configuration,"
855
      "DC=Security1,DC=aruba,DC=com?cACertificate?base?objectClass=certificationAuthority";
1✔
856
   const std::string aruba_cdp =
1✔
857
      "ldap:///CN=Security1-WIN-05PRGNGEKAO-CA,CN=WIN-05PRGNGEKAO,CN=CDP,CN=Public%20Key%20Services,CN=Services,"
858
      "CN=Configuration,DC=Security1,DC=aruba,DC=com?certificateRevocationList?base?objectClass=cRLDistributionPoint";
1✔
859

860
   check_contains_uri("Aruba AIA", aruba_cert.ca_issuer_uris(), aruba_aia);
1✔
861
   check_contains_uri("Aruba CDP", aruba_cert.crl_distribution_point_uris(), aruba_cdp);
1✔
862

863
   const Botan::X509_Certificate bde_cert(Test::data_file("x509/misc/bde_v2.pem"));
2✔
864
   const std::string bde_cdp =
1✔
865
      "ldap:///CN=BANCO%20DE%20ESPA%D1A-AC%20RAIZ%20V2,CN=PKIBDE,CN=CDP,CN=Public%20Key%20Services,CN=Services,"
866
      "CN=Configuration,DC=BDE,DC=ES?authorityRevocationList?base?objectclass=cRLDistributionPoint";
1✔
867

868
   check_contains_uri("BDE CDP", bde_cert.crl_distribution_point_uris(), bde_cdp);
1✔
869

870
   return result;
2✔
871
}
1✔
872

873
Test::Result test_crl_issuing_distribution_point_extension() {
1✔
874
   Test::Result result("X509 CRL IssuingDistributionPoint extension");
1✔
875

876
   // BSI CRL_12 has an IDP with a URI general name
877
   const Botan::X509_CRL crl(Test::data_file("x509/bsi/CRL_12/crls/CRL_12_crl.pem.crl"));
2✔
878

879
   result.test_str_eq(
1✔
880
      "CRL IDP URI decoded correctly", crl.crl_issuing_distribution_point(), "http://localhost/subca/crldp/crl.crl");
1✔
881

882
   return result;
1✔
883
}
1✔
884

885
Test::Result test_parse_rsa_pss_cert() {
1✔
886
   Test::Result result("X509 RSA-PSS certificate");
1✔
887

888
   // See https://github.com/randombit/botan/issues/3019 for background
889

890
   try {
1✔
891
      const Botan::X509_Certificate rsa_pss(Test::data_file("x509/misc/rsa_pss.pem"));
2✔
892
      result.test_success("Was able to parse RSA-PSS certificate signed with ECDSA");
1✔
893
   } catch(Botan::Exception& e) {
1✔
894
      result.test_failure("Parsing failed", e.what());
×
895
   }
×
896

897
   return result;
1✔
898
}
×
899

900
Test::Result test_verify_gost2012_cert() {
1✔
901
   Test::Result result("X509 GOST-2012 certificates");
1✔
902

903
      #if defined(BOTAN_HAS_GOST_34_10_2012) && defined(BOTAN_HAS_STREEBOG)
904
   try {
1✔
905
      if(Botan::EC_Group::supports_named_group("gost_256A")) {
1✔
906
         const Botan::X509_Certificate root_cert(Test::data_file("x509/gost/gost_root.pem"));
2✔
907
         const Botan::X509_Certificate root_int(Test::data_file("x509/gost/gost_int.pem"));
2✔
908

909
         Botan::Certificate_Store_In_Memory trusted;
1✔
910
         trusted.add_certificate(root_cert);
1✔
911

912
         const Botan::Path_Validation_Restrictions restrictions(false, 128, false, {"Streebog-256"});
2✔
913
         const auto validation_time = Botan::calendar_point(2024, 1, 1, 0, 0, 0).to_std_timepoint();
1✔
914
         const Botan::Path_Validation_Result validation_result = Botan::x509_path_validate(
1✔
915
            root_int, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
1✔
916

917
         result.test_is_true("GOST certificate validates", validation_result.successful_validation());
1✔
918
      }
1✔
919
   } catch(const Botan::Decoding_Error& e) {
×
920
      result.test_failure(e.what());
×
921
   }
×
922
      #endif
923

924
   return result;
1✔
925
}
×
926

927
   /*
928
 * @brief checks the configurability of the RSA-PSS signature scheme
929
 *
930
 * For the other algorithms than RSA, only one padding is supported right now.
931
 */
932
      #if defined(BOTAN_HAS_EMSA_PKCS1) && defined(BOTAN_HAS_EMSA_PSSR) && defined(BOTAN_HAS_RSA)
933
Test::Result test_padding_config() {
1✔
934
   Test::Result test_result("X509 Padding Config");
1✔
935

936
   auto rng = Test::new_rng(__func__);
1✔
937

938
   Botan::DataSource_Stream key_stream(Test::data_file("x509/misc/rsa_key.pem"));
2✔
939
   auto sk = Botan::PKCS8::load_key(key_stream);
1✔
940

941
   // Create X509 CA certificate; PKCS1v15 is used for signing by default
942
   Botan::X509_Cert_Options opt("TEST CA");
1✔
943
   opt.CA_key();
1✔
944

945
   const Botan::X509_Certificate ca_cert_def = Botan::X509::create_self_signed_cert(opt, (*sk), "SHA-512", *rng);
1✔
946
   test_result.test_opt_str_eq("CA certificate signature algorithm (default)",
2✔
947
                               ca_cert_def.signature_algorithm().oid().registered_name(),
2✔
948
                               "RSA/PKCS1v15(SHA-512)");
949

950
   // Create X509 CA certificate; RSA-PSS is explicitly set
951
   opt.set_padding_scheme("PSSR");
1✔
952
   const Botan::X509_Certificate ca_cert_exp = Botan::X509::create_self_signed_cert(opt, (*sk), "SHA-512", *rng);
1✔
953
   test_result.test_opt_str_eq("CA certificate signature algorithm (explicit)",
2✔
954
                               ca_cert_exp.signature_algorithm().oid().registered_name(),
2✔
955
                               "RSA/PSS");
956

957
         #if defined(BOTAN_HAS_EMSA_X931)
958
   // Try to set a padding scheme that is not supported for signing with the given key type
959
   opt.set_padding_scheme("X9.31");
1✔
960
   try {
1✔
961
      const Botan::X509_Certificate ca_cert_wrong = Botan::X509::create_self_signed_cert(opt, (*sk), "SHA-512", *rng);
1✔
962
      test_result.test_failure("Could build CA cert with invalid encoding scheme X9.31 for key type " +
×
963
                               sk->algo_name());
×
964
   } catch(const Botan::Invalid_Argument& e) {
1✔
965
      test_result.test_str_eq("Build CA certificate with invalid encoding scheme X9.31 for key type " + sk->algo_name(),
3✔
966
                              e.what(),
1✔
967
                              "Signatures using RSA/X9.31(SHA-512) are not supported");
968
   }
1✔
969
         #endif
970

971
   test_result.test_opt_str_eq("CA certificate signature algorithm (explicit)",
2✔
972
                               ca_cert_exp.signature_algorithm().oid().registered_name(),
2✔
973
                               "RSA/PSS");
974

975
   const Botan::X509_Time not_before = from_date(-1, 1, 1);
1✔
976
   const Botan::X509_Time not_after = from_date(2, 1, 2);
1✔
977

978
   // Prepare a signing request for the end certificate
979
   Botan::X509_Cert_Options req_opt("endpoint");
1✔
980
   req_opt.set_padding_scheme("PSS(SHA-512,MGF1,64)");
1✔
981
   const Botan::PKCS10_Request end_req = Botan::X509::create_cert_req(req_opt, (*sk), "SHA-512", *rng);
1✔
982
   test_result.test_opt_str_eq(
2✔
983
      "Certificate request signature algorithm", end_req.signature_algorithm().oid().registered_name(), "RSA/PSS");
2✔
984

985
   // Create X509 CA object: will fail as the chosen hash functions differ
986
   try {
1✔
987
      const Botan::X509_CA ca_fail(ca_cert_exp, (*sk), "SHA-512", "PSS(SHA-256)", *rng);
1✔
988
      test_result.test_failure("Configured conflicting hash functions for CA");
×
989
   } catch(const Botan::Invalid_Argument& e) {
1✔
990
      test_result.test_str_eq(
1✔
991
         "Configured conflicting hash functions for CA",
992
         e.what(),
1✔
993
         "Specified hash function SHA-512 is incompatible with RSA chose hash function SHA-256 with user specified padding PSS(SHA-256)");
994
   }
1✔
995

996
   // Create X509 CA object: its signer will use the padding scheme from the CA certificate, i.e. PKCS1v15
997
   const Botan::X509_CA ca_def(ca_cert_def, (*sk), "SHA-512", *rng);
1✔
998
   const Botan::X509_Certificate end_cert_pkcs1 = ca_def.sign_request(end_req, *rng, not_before, not_after);
1✔
999
   test_result.test_opt_str_eq("End certificate signature algorithm",
2✔
1000
                               end_cert_pkcs1.signature_algorithm().oid().registered_name(),
2✔
1001
                               "RSA/PKCS1v15(SHA-512)");
1002

1003
   // Create X509 CA object: its signer will use the explicitly configured padding scheme, which is different from the CA certificate's scheme
1004
   const Botan::X509_CA ca_diff(ca_cert_def, (*sk), "SHA-512", "PSS", *rng);
1✔
1005
   const Botan::X509_Certificate end_cert_diff_pss = ca_diff.sign_request(end_req, *rng, not_before, not_after);
1✔
1006
   test_result.test_opt_str_eq("End certificate signature algorithm",
2✔
1007
                               end_cert_diff_pss.signature_algorithm().oid().registered_name(),
2✔
1008
                               "RSA/PSS");
1009

1010
   // Create X509 CA object: its signer will use the explicitly configured padding scheme, which is identical to the CA certificate's scheme
1011
   const Botan::X509_CA ca_exp(ca_cert_exp, (*sk), "SHA-512", "PSS(SHA-512,MGF1,64)", *rng);
1✔
1012
   const Botan::X509_Certificate end_cert_pss = ca_exp.sign_request(end_req, *rng, not_before, not_after);
1✔
1013
   test_result.test_opt_str_eq(
2✔
1014
      "End certificate signature algorithm", end_cert_pss.signature_algorithm().oid().registered_name(), "RSA/PSS");
2✔
1015

1016
   // Check CRL signature algorithm
1017
   const Botan::X509_CRL crl = ca_exp.new_crl(*rng);
1✔
1018
   test_result.test_opt_str_eq("CRL signature algorithm", crl.signature_algorithm().oid().registered_name(), "RSA/PSS");
2✔
1019

1020
   // sanity check for verification, the heavy lifting is done in the other unit tests
1021
   const Botan::Certificate_Store_In_Memory trusted(ca_exp.ca_certificate());
1✔
1022
   const Botan::Path_Validation_Restrictions restrictions(false, 80);
1✔
1023
   const Botan::Path_Validation_Result validation_result =
1✔
1024
      Botan::x509_path_validate(end_cert_pss, restrictions, trusted);
1✔
1025
   test_result.test_is_true("PSS signed certificate validates", validation_result.successful_validation());
1✔
1026

1027
   return test_result;
2✔
1028
}
3✔
1029
      #endif
1030

1031
   #endif
1032

1033
Test::Result test_pkcs10_ext(const Botan::Private_Key& key,
11✔
1034
                             const std::string& sig_padding,
1035
                             const std::string& hash_fn,
1036
                             Botan::RandomNumberGenerator& rng) {
1037
   Test::Result result("PKCS10 extensions");
11✔
1038

1039
   Botan::X509_Cert_Options opts;
11✔
1040

1041
   opts.dns = "main.example.org";
11✔
1042
   opts.more_dns.push_back("more1.example.org");
22✔
1043
   opts.more_dns.push_back("more2.example.org");
22✔
1044

1045
   opts.padding_scheme = sig_padding;
11✔
1046

1047
   Botan::AlternativeName alt_name;
11✔
1048
   alt_name.add_attribute("DNS", "bonus.example.org");
11✔
1049

1050
   Botan::X509_DN alt_dn;
11✔
1051
   alt_dn.add_attribute("X520.CommonName", "alt_cn");
11✔
1052
   alt_dn.add_attribute("X520.Organization", "testing");
11✔
1053
   alt_name.add_dn(alt_dn);
11✔
1054

1055
   opts.extensions.add(std::make_unique<Botan::Cert_Extension::Subject_Alternative_Name>(alt_name));
22✔
1056

1057
   const auto req = Botan::X509::create_cert_req(opts, key, hash_fn, rng);
11✔
1058

1059
   const auto alt_dns_names = req.subject_alt_name().get_attribute("DNS");
11✔
1060

1061
   result.test_sz_eq("Expected number of DNS names", alt_dns_names.size(), 4);
11✔
1062

1063
   if(alt_dns_names.size() == 4) {
11✔
1064
      result.test_str_eq("Expected DNS name 1", alt_dns_names.at(0), "bonus.example.org");
11✔
1065
      result.test_str_eq("Expected DNS name 2", alt_dns_names.at(1), "main.example.org");
11✔
1066
      result.test_str_eq("Expected DNS name 3", alt_dns_names.at(2), "more1.example.org");
11✔
1067
      result.test_str_eq("Expected DNS name 3", alt_dns_names.at(3), "more2.example.org");
11✔
1068
   }
1069

1070
   result.test_sz_eq("Expected number of alt DNs", req.subject_alt_name().directory_names().size(), 1);
11✔
1071
   result.test_is_true("Alt DN is correct", *req.subject_alt_name().directory_names().begin() == alt_dn);
11✔
1072

1073
   return result;
11✔
1074
}
11✔
1075

1076
Test::Result test_x509_cert(const Botan::Private_Key& ca_key,
11✔
1077
                            const std::string& sig_algo,
1078
                            const std::string& sig_padding,
1079
                            const std::string& hash_fn,
1080
                            Botan::RandomNumberGenerator& rng) {
1081
   Test::Result result("X509 Unit");
11✔
1082

1083
   /* Create the self-signed cert */
1084
   const auto ca_cert = Botan::X509::create_self_signed_cert(ca_opts(sig_padding), ca_key, hash_fn, rng);
11✔
1085

1086
   {
11✔
1087
      result.test_is_true("ca key usage cert", ca_cert.constraints().includes(Botan::Key_Constraints::KeyCertSign));
11✔
1088
      result.test_is_true("ca key usage crl", ca_cert.constraints().includes(Botan::Key_Constraints::CrlSign));
11✔
1089
   }
1090

1091
   /* Create user #1's key and cert request */
1092
   auto user1_key = make_a_private_key(sig_algo, rng);
11✔
1093

1094
   const Botan::PKCS10_Request user1_req =
11✔
1095
      Botan::X509::create_cert_req(req_opts1(sig_algo, sig_padding), *user1_key, hash_fn, rng);
11✔
1096

1097
   result.test_str_eq("PKCS10 challenge password parsed", user1_req.challenge_password(), "zoom");
11✔
1098

1099
   /* Create user #2's key and cert request */
1100
   auto user2_key = make_a_private_key(sig_algo, rng);
11✔
1101

1102
   const Botan::PKCS10_Request user2_req =
11✔
1103
      Botan::X509::create_cert_req(req_opts2(sig_padding), *user2_key, hash_fn, rng);
11✔
1104

1105
   // /* Create user #3's key and cert request */
1106
   auto user3_key = make_a_private_key(sig_algo, rng);
11✔
1107

1108
   const Botan::PKCS10_Request user3_req =
11✔
1109
      Botan::X509::create_cert_req(req_opts3(sig_padding), *user3_key, hash_fn, rng);
11✔
1110

1111
   /* Create the CA object */
1112
   const Botan::X509_CA ca(ca_cert, ca_key, hash_fn, sig_padding, rng);
11✔
1113

1114
   const BigInt user1_serial(99);
11✔
1115

1116
   /* Sign the requests to create the certs */
1117
   const Botan::X509_Certificate user1_cert =
11✔
1118
      ca.sign_request(user1_req, rng, user1_serial, from_date(-1, 01, 01), from_date(2, 01, 01));
11✔
1119

1120
   result.test_sz_eq("User1 serial size matches expected", user1_cert.serial_number().size(), 1);
11✔
1121
   result.test_sz_eq("User1 serial matches expected", user1_cert.serial_number().at(0), size_t(99));
11✔
1122

1123
   const Botan::X509_Certificate user2_cert =
11✔
1124
      ca.sign_request(user2_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
11✔
1125
   result.test_is_true("extended key usage is set", user2_cert.has_ex_constraint("PKIX.EmailProtection"));
11✔
1126

1127
   const Botan::X509_Certificate user3_cert =
11✔
1128
      ca.sign_request(user3_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
11✔
1129

1130
   // user#1 creates a self-signed cert on the side
1131
   const auto user1_ss_cert =
11✔
1132
      Botan::X509::create_self_signed_cert(req_opts1(sig_algo, sig_padding), *user1_key, hash_fn, rng);
11✔
1133

1134
   {
11✔
1135
      auto constraints = req_opts1(sig_algo).constraints;
11✔
1136
      result.test_is_true("user1 key usage", user1_cert.constraints().includes(constraints));
11✔
1137
   }
1138

1139
   /* Copy, assign and compare */
1140
   Botan::X509_Certificate user1_cert_copy(user1_cert);
11✔
1141
   result.test_is_true("certificate copy", user1_cert == user1_cert_copy);
11✔
1142

1143
   user1_cert_copy = user2_cert;
11✔
1144
   result.test_is_true("certificate assignment", user2_cert == user1_cert_copy);
11✔
1145

1146
   const Botan::X509_Certificate user1_cert_differ =
11✔
1147
      ca.sign_request(user1_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
11✔
1148

1149
   result.test_is_false("certificate differs", user1_cert == user1_cert_differ);
11✔
1150

1151
   /* Get cert data */
1152
   result.test_sz_eq("x509 version", user1_cert.x509_version(), size_t(3));
11✔
1153

1154
   const Botan::X509_DN& user1_issuer_dn = user1_cert.issuer_dn();
11✔
1155
   result.test_str_eq("issuer info CN", user1_issuer_dn.get_first_attribute("CN"), ca_opts().common_name);
11✔
1156
   result.test_str_eq("issuer info Country", user1_issuer_dn.get_first_attribute("C"), ca_opts().country);
11✔
1157
   result.test_str_eq("issuer info Orga", user1_issuer_dn.get_first_attribute("O"), ca_opts().organization);
11✔
1158
   result.test_str_eq("issuer info OrgaUnit", user1_issuer_dn.get_first_attribute("OU"), ca_opts().org_unit);
11✔
1159

1160
   const Botan::X509_DN& user3_subject_dn = user3_cert.subject_dn();
11✔
1161
   result.test_sz_eq("subject OrgaUnit count",
11✔
1162
                     user3_subject_dn.get_attribute("OU").size(),
22✔
1163
                     req_opts3(sig_algo).more_org_units.size() + 1);
22✔
1164
   result.test_str_eq(
22✔
1165
      "subject OrgaUnit #2", user3_subject_dn.get_attribute("OU").at(1), req_opts3(sig_algo).more_org_units.at(0));
33✔
1166

1167
   const Botan::AlternativeName& user1_altname = user1_cert.subject_alt_name();
11✔
1168
   result.test_str_eq("subject alt email", user1_altname.get_first_attribute("RFC822"), "testing@randombit.net");
11✔
1169
   result.test_str_eq("subject alt dns", user1_altname.get_first_attribute("DNS"), "botan.randombit.net");
11✔
1170
   result.test_str_eq("subject alt uri", user1_altname.get_first_attribute("URI"), "https://botan.randombit.net");
11✔
1171

1172
   const Botan::AlternativeName& user3_altname = user3_cert.subject_alt_name();
11✔
1173
   result.test_sz_eq(
11✔
1174
      "subject alt dns count", user3_altname.get_attribute("DNS").size(), req_opts3(sig_algo).more_dns.size() + 1);
22✔
1175
   result.test_str_eq(
22✔
1176
      "subject alt dns #2", user3_altname.get_attribute("DNS").at(1), req_opts3(sig_algo).more_dns.at(0));
33✔
1177

1178
   const Botan::X509_CRL crl1 = ca.new_crl(rng);
11✔
1179

1180
   /* Verify the certs */
1181
   const Botan::Path_Validation_Restrictions restrictions(false, 80);
11✔
1182
   Botan::Certificate_Store_In_Memory store;
11✔
1183

1184
   // First try with an empty store
1185
   const Botan::Path_Validation_Result result_no_issuer = Botan::x509_path_validate(user1_cert, restrictions, store);
11✔
1186
   result.test_str_eq(
11✔
1187
      "user 1 issuer not found",
1188
      result_no_issuer.result_string(),
11✔
1189
      Botan::Path_Validation_Result::status_string(Botan::Certificate_Status_Code::CERT_ISSUER_NOT_FOUND));
1190

1191
   store.add_certificate(ca.ca_certificate());
11✔
1192

1193
   Botan::Path_Validation_Result result_u1 = Botan::x509_path_validate(user1_cert, restrictions, store);
11✔
1194
   if(!result.test_is_true("user 1 validates", result_u1.successful_validation())) {
11✔
1195
      result.test_note("user 1 validation result", result_u1.result_string());
×
1196
   }
1197

1198
   Botan::Path_Validation_Result result_u2 = Botan::x509_path_validate(user2_cert, restrictions, store);
11✔
1199
   if(!result.test_is_true("user 2 validates", result_u2.successful_validation())) {
11✔
1200
      result.test_note("user 2 validation result", result_u2.result_string());
×
1201
   }
1202

1203
   const Botan::Path_Validation_Result result_self_signed =
11✔
1204
      Botan::x509_path_validate(user1_ss_cert, restrictions, store);
11✔
1205
   result.test_str_eq(
11✔
1206
      "user 1 issuer not found",
1207
      result_no_issuer.result_string(),
11✔
1208
      Botan::Path_Validation_Result::status_string(Botan::Certificate_Status_Code::CERT_ISSUER_NOT_FOUND));
1209
   store.add_crl(crl1);
11✔
1210

1211
   std::vector<Botan::CRL_Entry> revoked;
11✔
1212
   revoked.push_back(Botan::CRL_Entry(user1_cert, Botan::CRL_Code::CessationOfOperation));
22✔
1213
   revoked.push_back(Botan::CRL_Entry(user2_cert));
22✔
1214

1215
   const Botan::X509_CRL crl2 = ca.update_crl(crl1, revoked, rng);
11✔
1216

1217
   store.add_crl(crl2);
11✔
1218

1219
   const std::string revoked_str =
11✔
1220
      Botan::Path_Validation_Result::status_string(Botan::Certificate_Status_Code::CERT_IS_REVOKED);
11✔
1221

1222
   result_u1 = Botan::x509_path_validate(user1_cert, restrictions, store);
11✔
1223
   result.test_str_eq("user 1 revoked", result_u1.result_string(), revoked_str);
11✔
1224

1225
   result_u2 = Botan::x509_path_validate(user2_cert, restrictions, store);
11✔
1226
   result.test_str_eq("user 1 revoked", result_u2.result_string(), revoked_str);
11✔
1227

1228
   // RFC 5280 5.3.1: the removeFromCRL reason code MAY only appear in delta
1229
   // CRLs. Botan does not process delta CRLs, so a base CRL with this reason
1230
   // code is malformed; we keep the cert marked as revoked rather than honor
1231
   // the spec-violating "un-revoke" semantics.
1232
   revoked.clear();
11✔
1233
   revoked.push_back(Botan::CRL_Entry(user1_cert, Botan::CRL_Code::RemoveFromCrl));
22✔
1234
   const Botan::X509_CRL crl3 = ca.update_crl(crl2, revoked, rng);
11✔
1235

1236
   store.add_crl(crl3);
11✔
1237

1238
   result_u1 = Botan::x509_path_validate(user1_cert, restrictions, store);
11✔
1239
   result.test_str_eq("user 1 still revoked after RemoveFromCrl in base CRL", result_u1.result_string(), revoked_str);
11✔
1240

1241
   result_u2 = Botan::x509_path_validate(user2_cert, restrictions, store);
11✔
1242
   result.test_str_eq("user 2 still revoked", result_u2.result_string(), revoked_str);
11✔
1243

1244
   return result;
11✔
1245
}
44✔
1246

1247
Test::Result test_crl_entry_negative_serial() {
1✔
1248
   Test::Result result("CRL entry with negative serial matches certificate serial");
1✔
1249

1250
   const auto build_crl_entry = [](const std::vector<uint8_t>& serial_bytes) {
4✔
1251
      std::vector<uint8_t> der;
3✔
1252
      Botan::DER_Encoder enc(der);
3✔
1253
      enc.start_sequence()
3✔
1254
         .add_object(Botan::ASN1_Type::Integer, Botan::ASN1_Class::Universal, serial_bytes.data(), serial_bytes.size())
3✔
1255
         .encode(Botan::X509_Time(std::chrono::system_clock::now()))
3✔
1256
         .end_cons();
3✔
1257

1258
      Botan::CRL_Entry entry;
3✔
1259
      Botan::BER_Decoder dec(der);
3✔
1260
      entry.decode_from(dec);
3✔
1261
      return entry;
3✔
1262
   };
3✔
1263

1264
   // -129 in two's complement
1265
   auto entry = build_crl_entry({0xFF, 0x7F});
1✔
1266
   result.test_bin_eq("negative serial -129 magnitude", entry.serial_number(), Botan::BigInt(129).serialize());
2✔
1267

1268
   // -1 in two's complement
1269
   entry = build_crl_entry({0xFF});
2✔
1270
   result.test_bin_eq("negative serial -1 magnitude", entry.serial_number(), Botan::BigInt(1).serialize());
2✔
1271

1272
   // 128 (positive, high bit set so DER requires the leading zero)
1273
   entry = build_crl_entry({0x00, 0x80});
2✔
1274
   result.test_bin_eq("positive serial 128", entry.serial_number(), Botan::BigInt(128).serialize());
2✔
1275

1276
   return result;
1✔
1277
}
1✔
1278

1279
Test::Result test_usage(const Botan::Private_Key& ca_key,
12✔
1280
                        const std::string& sig_algo,
1281
                        const std::string& hash_fn,
1282
                        Botan::RandomNumberGenerator& rng) {
1283
   using Botan::Key_Constraints;
12✔
1284
   using Botan::Usage_Type;
12✔
1285

1286
   Test::Result result("X509 Usage");
12✔
1287

1288
   /* Create the self-signed cert */
1289
   const Botan::X509_Certificate ca_cert = Botan::X509::create_self_signed_cert(ca_opts(), ca_key, hash_fn, rng);
12✔
1290

1291
   /* Create the CA object */
1292
   const Botan::X509_CA ca(ca_cert, ca_key, hash_fn, rng);
12✔
1293

1294
   auto user1_key = make_a_private_key(sig_algo, rng);
12✔
1295

1296
   Botan::X509_Cert_Options opts("Test User 1/US/Botan Project/Testing");
12✔
1297
   opts.constraints = Key_Constraints::DigitalSignature;
12✔
1298

1299
   const Botan::PKCS10_Request user1_req = Botan::X509::create_cert_req(opts, *user1_key, hash_fn, rng);
12✔
1300

1301
   const Botan::X509_Certificate user1_cert =
12✔
1302
      ca.sign_request(user1_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
12✔
1303

1304
   // cert only allows digitalSignature, but we check for both digitalSignature and cRLSign
1305
   result.test_is_false(
12✔
1306
      "key usage cRLSign not allowed",
1307
      user1_cert.allowed_usage(Key_Constraints(Key_Constraints::DigitalSignature | Key_Constraints::CrlSign)));
12✔
1308
   result.test_is_false("encryption is not allowed", user1_cert.allowed_usage(Usage_Type::ENCRYPTION));
12✔
1309

1310
   // cert only allows digitalSignature, so checking for only that should be ok
1311
   result.test_is_true("key usage digitalSignature allowed",
12✔
1312
                       user1_cert.allowed_usage(Key_Constraints::DigitalSignature));
12✔
1313

1314
   opts.constraints = Key_Constraints(Key_Constraints::DigitalSignature | Key_Constraints::CrlSign);
12✔
1315

1316
   const Botan::PKCS10_Request mult_usage_req = Botan::X509::create_cert_req(opts, *user1_key, hash_fn, rng);
12✔
1317

1318
   const Botan::X509_Certificate mult_usage_cert =
12✔
1319
      ca.sign_request(mult_usage_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
12✔
1320

1321
   // cert allows multiple usages, so each one of them as well as both together should be allowed
1322
   result.test_is_true("key usage multiple digitalSignature allowed",
12✔
1323
                       mult_usage_cert.allowed_usage(Key_Constraints::DigitalSignature));
12✔
1324
   result.test_is_true("key usage multiple cRLSign allowed", mult_usage_cert.allowed_usage(Key_Constraints::CrlSign));
12✔
1325
   result.test_is_true(
12✔
1326
      "key usage multiple digitalSignature and cRLSign allowed",
1327
      mult_usage_cert.allowed_usage(Key_Constraints(Key_Constraints::DigitalSignature | Key_Constraints::CrlSign)));
12✔
1328
   result.test_is_false("encryption is not allowed", mult_usage_cert.allowed_usage(Usage_Type::ENCRYPTION));
12✔
1329

1330
   opts.constraints = Key_Constraints();
12✔
1331

1332
   const Botan::PKCS10_Request no_usage_req = Botan::X509::create_cert_req(opts, *user1_key, hash_fn, rng);
12✔
1333

1334
   const Botan::X509_Certificate no_usage_cert =
12✔
1335
      ca.sign_request(no_usage_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
12✔
1336

1337
   // cert allows every usage
1338
   result.test_is_true("key usage digitalSignature allowed",
12✔
1339
                       no_usage_cert.allowed_usage(Key_Constraints::DigitalSignature));
12✔
1340
   result.test_is_true("key usage cRLSign allowed", no_usage_cert.allowed_usage(Key_Constraints::CrlSign));
12✔
1341
   result.test_is_true("key usage encryption allowed", no_usage_cert.allowed_usage(Usage_Type::ENCRYPTION));
12✔
1342

1343
   if(sig_algo == "RSA") {
12✔
1344
      // cert allows data encryption
1345
      opts.constraints = Key_Constraints(Key_Constraints::KeyEncipherment | Key_Constraints::DataEncipherment);
1✔
1346

1347
      const Botan::PKCS10_Request enc_req = Botan::X509::create_cert_req(opts, *user1_key, hash_fn, rng);
1✔
1348

1349
      const Botan::X509_Certificate enc_cert =
1✔
1350
         ca.sign_request(enc_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
1✔
1351

1352
      result.test_is_true("cert allows encryption", enc_cert.allowed_usage(Usage_Type::ENCRYPTION));
1✔
1353
      result.test_is_true("cert does not allow TLS client auth", !enc_cert.allowed_usage(Usage_Type::TLS_CLIENT_AUTH));
1✔
1354
   }
1✔
1355

1356
   return result;
12✔
1357
}
24✔
1358

1359
Test::Result test_self_issued(const Botan::Private_Key& ca_key,
11✔
1360
                              const std::string& sig_algo,
1361
                              const std::string& sig_padding,
1362
                              const std::string& hash_fn,
1363
                              Botan::RandomNumberGenerator& rng) {
1364
   using Botan::Key_Constraints;
11✔
1365

1366
   Test::Result result("X509 Self Issued");
11✔
1367

1368
   // create the self-signed cert
1369
   const Botan::X509_Certificate ca_cert =
11✔
1370
      Botan::X509::create_self_signed_cert(ca_opts(sig_padding), ca_key, hash_fn, rng);
11✔
1371

1372
   /* Create the CA object */
1373
   const Botan::X509_CA ca(ca_cert, ca_key, hash_fn, sig_padding, rng);
11✔
1374

1375
   auto user_key = make_a_private_key(sig_algo, rng);
11✔
1376

1377
   // create a self-issued certificate, that is, a certificate with subject dn == issuer dn,
1378
   // but signed by a CA, not signed by it's own private key
1379
   Botan::X509_Cert_Options opts = ca_opts();
11✔
1380
   opts.constraints = Key_Constraints::DigitalSignature;
11✔
1381
   opts.set_padding_scheme(sig_padding);
11✔
1382

1383
   const Botan::PKCS10_Request self_issued_req = Botan::X509::create_cert_req(opts, *user_key, hash_fn, rng);
11✔
1384

1385
   const Botan::X509_Certificate self_issued_cert =
11✔
1386
      ca.sign_request(self_issued_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
11✔
1387

1388
   // check that this chain can can be verified successfully
1389
   const Botan::Certificate_Store_In_Memory trusted(ca.ca_certificate());
11✔
1390

1391
   const Botan::Path_Validation_Restrictions restrictions(false, 80);
11✔
1392

1393
   const Botan::Path_Validation_Result validation_result =
11✔
1394
      Botan::x509_path_validate(self_issued_cert, restrictions, trusted);
11✔
1395

1396
   result.test_is_true("chain with self-issued cert validates", validation_result.successful_validation());
11✔
1397

1398
   return result;
11✔
1399
}
22✔
1400

1401
Test::Result test_x509_uninit() {
1✔
1402
   Test::Result result("X509 object uninitialized access");
1✔
1403

1404
   Botan::X509_Certificate cert;
1✔
1405
   result.test_throws("uninitialized cert access causes exception", "X509_Certificate uninitialized", [&cert]() {
1✔
1406
      cert.x509_version();
1✔
1407
   });
1408

1409
   Botan::X509_CRL crl;
1✔
1410
   result.test_throws(
1✔
1411
      "uninitialized crl access causes exception", "X509_CRL uninitialized", [&crl]() { crl.crl_number_bigint(); });
2✔
1412

1413
   // X509_CRL constructed via the issuer-DN constructor leaves the inherited
1414
   // X509_Object signed-data null. The accessors must reject rather than UB.
1415
   const Botan::X509_DN issuer({{"X520.CommonName", "Test"}});
1✔
1416
   const Botan::X509_Time t(std::chrono::system_clock::now());
1✔
1417
   const Botan::X509_CRL synth_crl(issuer, t, t, std::vector<Botan::CRL_Entry>{});
1✔
1418

1419
   result.test_throws("synth crl signature() throws", "X509_Object uninitialized", [&]() { synth_crl.signature(); });
2✔
1420
   result.test_throws(
1✔
1421
      "synth crl signed_body() throws", "X509_Object uninitialized", [&]() { synth_crl.signed_body(); });
2✔
1422
   result.test_throws("synth crl signature_algorithm() throws", "X509_Object uninitialized", [&]() {
1✔
1423
      synth_crl.signature_algorithm();
1✔
1424
   });
1425
   result.test_throws("synth crl tbs_data() throws", "X509_Object uninitialized", [&]() { synth_crl.tbs_data(); });
2✔
1426
   result.test_throws("synth crl BER_encode() throws", "X509_Object uninitialized", [&]() { synth_crl.BER_encode(); });
2✔
1427

1428
   return result;
1✔
1429
}
1✔
1430

1431
Test::Result test_valid_constraints(const Botan::Private_Key& key, const std::string& pk_algo) {
19✔
1432
   using Botan::Key_Constraints;
19✔
1433

1434
   Test::Result result("X509 Valid Constraints " + pk_algo);
19✔
1435

1436
   result.test_is_true("empty constraints always acceptable", Key_Constraints().compatible_with(key));
19✔
1437

1438
   // Now check some typical usage scenarios for the given key type
1439
   // Taken from RFC 5280, sec. 4.2.1.3
1440
   // ALL constraints are not typical at all, but we use them for a negative test
1441
   const auto all = Key_Constraints(
19✔
1442
      Key_Constraints::DigitalSignature | Key_Constraints::NonRepudiation | Key_Constraints::KeyEncipherment |
1443
      Key_Constraints::DataEncipherment | Key_Constraints::KeyAgreement | Key_Constraints::KeyCertSign |
1444
      Key_Constraints::CrlSign | Key_Constraints::EncipherOnly | Key_Constraints::DecipherOnly);
19✔
1445

1446
   const auto ca = Key_Constraints(Key_Constraints::KeyCertSign);
19✔
1447
   const auto sign_data = Key_Constraints(Key_Constraints::DigitalSignature);
19✔
1448
   const auto non_repudiation = Key_Constraints(Key_Constraints::NonRepudiation | Key_Constraints::DigitalSignature);
19✔
1449
   const auto key_encipherment = Key_Constraints(Key_Constraints::KeyEncipherment);
19✔
1450
   const auto data_encipherment = Key_Constraints(Key_Constraints::DataEncipherment);
19✔
1451
   const auto key_agreement = Key_Constraints(Key_Constraints::KeyAgreement);
19✔
1452
   const auto key_agreement_encipher_only =
19✔
1453
      Key_Constraints(Key_Constraints::KeyAgreement | Key_Constraints::EncipherOnly);
19✔
1454
   const auto key_agreement_decipher_only =
19✔
1455
      Key_Constraints(Key_Constraints::KeyAgreement | Key_Constraints::DecipherOnly);
19✔
1456
   const auto crl_sign = Key_Constraints(Key_Constraints::CrlSign);
19✔
1457
   const auto sign_everything =
19✔
1458
      Key_Constraints(Key_Constraints::DigitalSignature | Key_Constraints::KeyCertSign | Key_Constraints::CrlSign);
19✔
1459

1460
   if(pk_algo == "DH" || pk_algo == "ECDH") {
19✔
1461
      // DH and ECDH only for key agreement
1462
      result.test_is_false("all constraints not permitted", all.compatible_with(key));
2✔
1463
      result.test_is_false("cert sign not permitted", ca.compatible_with(key));
2✔
1464
      result.test_is_false("signature not permitted", sign_data.compatible_with(key));
2✔
1465
      result.test_is_false("non repudiation not permitted", non_repudiation.compatible_with(key));
2✔
1466
      result.test_is_false("key encipherment not permitted", key_encipherment.compatible_with(key));
2✔
1467
      result.test_is_false("data encipherment not permitted", data_encipherment.compatible_with(key));
2✔
1468
      result.test_is_true("usage acceptable", key_agreement.compatible_with(key));
2✔
1469
      result.test_is_true("usage acceptable", key_agreement_encipher_only.compatible_with(key));
2✔
1470
      result.test_is_true("usage acceptable", key_agreement_decipher_only.compatible_with(key));
2✔
1471
      result.test_is_false("crl sign not permitted", crl_sign.compatible_with(key));
2✔
1472
      result.test_is_false("sign", sign_everything.compatible_with(key));
2✔
1473
   } else if(pk_algo == "Kyber" || pk_algo == "FrodoKEM" || pk_algo == "ML-KEM" || pk_algo == "ClassicMcEliece") {
17✔
1474
      // KEMs can encrypt and agree
1475
      result.test_is_false("all constraints not permitted", all.compatible_with(key));
4✔
1476
      result.test_is_false("cert sign not permitted", ca.compatible_with(key));
4✔
1477
      result.test_is_false("signature not permitted", sign_data.compatible_with(key));
4✔
1478
      result.test_is_false("non repudiation not permitted", non_repudiation.compatible_with(key));
4✔
1479
      result.test_is_false("crl sign not permitted", crl_sign.compatible_with(key));
4✔
1480
      result.test_is_false("sign", sign_everything.compatible_with(key));
4✔
1481
      result.test_is_false("key agreement not permitted", key_agreement.compatible_with(key));
4✔
1482
      result.test_is_false("usage acceptable", data_encipherment.compatible_with(key));
4✔
1483
      result.test_is_true("usage acceptable", key_encipherment.compatible_with(key));
4✔
1484
   } else if(pk_algo == "RSA") {
13✔
1485
      // RSA can do everything except key agreement
1486
      result.test_is_false("all constraints not permitted", all.compatible_with(key));
1✔
1487

1488
      result.test_is_true("usage acceptable", ca.compatible_with(key));
1✔
1489
      result.test_is_true("usage acceptable", sign_data.compatible_with(key));
1✔
1490
      result.test_is_true("usage acceptable", non_repudiation.compatible_with(key));
1✔
1491
      result.test_is_true("usage acceptable", key_encipherment.compatible_with(key));
1✔
1492
      result.test_is_true("usage acceptable", data_encipherment.compatible_with(key));
1✔
1493
      result.test_is_false("key agreement not permitted", key_agreement.compatible_with(key));
1✔
1494
      result.test_is_false("key agreement", key_agreement_encipher_only.compatible_with(key));
1✔
1495
      result.test_is_false("key agreement", key_agreement_decipher_only.compatible_with(key));
1✔
1496
      result.test_is_true("usage acceptable", crl_sign.compatible_with(key));
1✔
1497
      result.test_is_true("usage acceptable", sign_everything.compatible_with(key));
1✔
1498
   } else if(pk_algo == "ElGamal") {
12✔
1499
      // only ElGamal encryption is currently implemented
1500
      result.test_is_false("all constraints not permitted", all.compatible_with(key));
1✔
1501
      result.test_is_false("cert sign not permitted", ca.compatible_with(key));
1✔
1502
      result.test_is_true("data encipherment permitted", data_encipherment.compatible_with(key));
1✔
1503
      result.test_is_true("key encipherment permitted", key_encipherment.compatible_with(key));
1✔
1504
      result.test_is_false("key agreement not permitted", key_agreement.compatible_with(key));
1✔
1505
      result.test_is_false("key agreement", key_agreement_encipher_only.compatible_with(key));
1✔
1506
      result.test_is_false("key agreement", key_agreement_decipher_only.compatible_with(key));
1✔
1507
      result.test_is_false("crl sign not permitted", crl_sign.compatible_with(key));
1✔
1508
      result.test_is_false("sign", sign_everything.compatible_with(key));
1✔
1509
   } else if(pk_algo == "DSA" || pk_algo == "ECDSA" || pk_algo == "ECGDSA" || pk_algo == "ECKCDSA" ||
10✔
1510
             pk_algo == "GOST-34.10" || pk_algo == "Dilithium" || pk_algo == "ML-DSA" || pk_algo == "SLH-DSA" ||
18✔
1511
             pk_algo == "HSS-LMS") {
3✔
1512
      // these are signature algorithms only
1513
      result.test_is_false("all constraints not permitted", all.compatible_with(key));
9✔
1514

1515
      result.test_is_true("ca allowed", ca.compatible_with(key));
9✔
1516
      result.test_is_true("sign allowed", sign_data.compatible_with(key));
9✔
1517
      result.test_is_true("non-repudiation allowed", non_repudiation.compatible_with(key));
9✔
1518
      result.test_is_false("key encipherment not permitted", key_encipherment.compatible_with(key));
9✔
1519
      result.test_is_false("data encipherment not permitted", data_encipherment.compatible_with(key));
9✔
1520
      result.test_is_false("key agreement not permitted", key_agreement.compatible_with(key));
9✔
1521
      result.test_is_false("key agreement", key_agreement_encipher_only.compatible_with(key));
9✔
1522
      result.test_is_false("key agreement", key_agreement_decipher_only.compatible_with(key));
9✔
1523
      result.test_is_true("crl sign allowed", crl_sign.compatible_with(key));
9✔
1524
      result.test_is_true("sign allowed", sign_everything.compatible_with(key));
9✔
1525
   }
1526

1527
   return result;
19✔
1528
}
×
1529

1530
/**
1531
 * @brief X.509v3 extension that encodes a given string
1532
 */
1533
class String_Extension final : public Botan::Certificate_Extension {
22✔
1534
   public:
1535
      String_Extension() = default;
22✔
1536

1537
      explicit String_Extension(const std::string& val) : m_contents(val) {}
11✔
1538

1539
      std::string value() const { return m_contents; }
44✔
1540

1541
      std::unique_ptr<Certificate_Extension> copy() const override {
×
1542
         return std::make_unique<String_Extension>(m_contents);
×
1543
      }
1544

1545
      Botan::OID oid_of() const override { return Botan::OID("1.2.3.4.5.6.7.8.9.1"); }
22✔
1546

1547
      bool should_encode() const override { return true; }
22✔
1548

1549
      std::string oid_name() const override { return "String Extension"; }
×
1550

1551
      bool is_appropriate_context(Botan::Extension_Context /*context*/) const override { return true; }
×
1552

1553
      std::vector<uint8_t> encode_inner() const override {
11✔
1554
         std::vector<uint8_t> bits;
11✔
1555
         Botan::DER_Encoder(bits).encode(Botan::ASN1_String(m_contents, Botan::ASN1_Type::Utf8String));
22✔
1556
         return bits;
11✔
1557
      }
×
1558

1559
      void decode_inner(const std::vector<uint8_t>& in) override {
22✔
1560
         Botan::ASN1_String str;
22✔
1561
         Botan::BER_Decoder(in).decode(str, Botan::ASN1_Type::Utf8String).verify_end();
22✔
1562
         m_contents = str.value();
44✔
1563
      }
22✔
1564

1565
   private:
1566
      std::string m_contents;
1567
};
1568

1569
Test::Result test_x509_wrong_context_certificate_extensions() {
1✔
1570
   Test::Result result("X509 wrong-context certificate extensions");
1✔
1571

1572
   #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
1573
   const std::string base = "x509/wrong_context_ext/";
1✔
1574

1575
   const auto test_rejected = [&](const std::string& filename, std::string_view what) {
4✔
1576
      result.test_throws<Botan::Decoding_Error>(
3✔
1577
         std::string(what), [&]() { const Botan::X509_Certificate cert(Test::data_file(base + filename)); });
12✔
1578
   };
3✔
1579

1580
   test_rejected("cert_with_crl_number.pem", "CRL number rejected in certificate");
1✔
1581
   test_rejected("cert_with_reason_code.pem", "CRL reason rejected in certificate");
1✔
1582
   test_rejected("cert_with_idp.pem", "CRL issuing distribution point rejected in certificate");
1✔
1583
   #endif
1584

1585
   return result;
1✔
1586
}
1✔
1587

1588
Test::Result test_x509_wrong_context_crl_extensions() {
1✔
1589
   Test::Result result("X509 wrong-context CRL extensions");
1✔
1590

1591
   #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
1592
   const std::string base_dir = "x509/wrong_context_ext";
1✔
1593

1594
   auto load_crl_from_pem = [&](std::string_view filename) -> Botan::X509_CRL {
5✔
1595
      return Botan::X509_CRL(Test::data_file(base_dir, filename));
6✔
1596
   };
1✔
1597

1598
   result.test_throws<Botan::Decoding_Error>("SubjectAltName rejected in CRL",
1✔
1599
                                             [&]() { load_crl_from_pem("crl_with_san.pem"); });
2✔
1600
   result.test_throws<Botan::Decoding_Error>("SubjectAltName rejected in CRL entry",
1✔
1601
                                             [&]() { load_crl_from_pem("crl_entry_with_san.pem"); });
2✔
1602

1603
   const Botan::X509_Certificate ca_cert(Test::data_file(base_dir + "/ca.pem"));
2✔
1604
   const Botan::X509_Certificate user_cert(Test::data_file(base_dir + "/user.pem"));
2✔
1605
   const std::vector<Botan::X509_Certificate> cert_path = {user_cert, ca_cert};
3✔
1606
   const auto validation_time = Botan::calendar_point(2027, 1, 1, 0, 0, 0).to_std_timepoint();
1✔
1607

1608
   const auto check_crl_unusable = [&](const std::string& filename, std::string_view what) {
3✔
1609
      const auto crl = load_crl_from_pem(filename);
2✔
1610
      result.test_is_true("CRL records the unknown critical extension", crl.has_unknown_critical_extension());
2✔
1611

1612
      const std::vector<std::optional<Botan::X509_CRL>> crls = {crl};
4✔
1613
      const auto crl_status = Botan::PKIX::check_crl(cert_path, crls, validation_time);
2✔
1614

1615
      const bool contains_expected_code =
2✔
1616
         !crl_status.empty() &&
2✔
1617
         crl_status[0].contains(Botan::Certificate_Status_Code::CRL_HAS_UNKNOWN_CRITICAL_EXTENSION);
4✔
1618
      result.test_is_true(what, contains_expected_code);
2✔
1619
   };
4✔
1620

1621
   check_crl_unusable("crl_with_unknown_critical.pem", "critical unknown CRL extension rejects CRL");
1✔
1622
   check_crl_unusable("crl_entry_with_unknown_critical.pem", "critical unknown CRL entry extension rejects CRL");
1✔
1623
   #endif
1624

1625
   return result;
2✔
1626
}
2✔
1627

1628
Test::Result test_custom_dn_attr(const Botan::Private_Key& ca_key,
11✔
1629
                                 const std::string& sig_algo,
1630
                                 const std::string& sig_padding,
1631
                                 const std::string& hash_fn,
1632
                                 Botan::RandomNumberGenerator& rng) {
1633
   Test::Result result("X509 Custom DN");
11✔
1634

1635
   /* Create the self-signed cert */
1636
   const Botan::X509_Certificate ca_cert =
11✔
1637
      Botan::X509::create_self_signed_cert(ca_opts(sig_padding), ca_key, hash_fn, rng);
11✔
1638

1639
   /* Create the CA object */
1640
   const Botan::X509_CA ca(ca_cert, ca_key, hash_fn, sig_padding, rng);
11✔
1641

1642
   auto user_key = make_a_private_key(sig_algo, rng);
11✔
1643

1644
   Botan::X509_DN subject_dn;
11✔
1645

1646
   const Botan::OID attr1(Botan::OID("1.3.6.1.4.1.25258.9.1.1"));
11✔
1647
   const Botan::OID attr2(Botan::OID("1.3.6.1.4.1.25258.9.1.2"));
11✔
1648
   const Botan::ASN1_String val1("Custom Attr 1", Botan::ASN1_Type::PrintableString);
11✔
1649
   const Botan::ASN1_String val2("12345", Botan::ASN1_Type::Utf8String);
11✔
1650

1651
   subject_dn.add_attribute(attr1, val1);
11✔
1652
   subject_dn.add_attribute(attr2, val2);
11✔
1653

1654
   const Botan::Extensions extensions;
11✔
1655

1656
   const Botan::PKCS10_Request req =
11✔
1657
      Botan::PKCS10_Request::create(*user_key, subject_dn, extensions, hash_fn, rng, sig_padding);
11✔
1658

1659
   const Botan::X509_DN& req_dn = req.subject_dn();
11✔
1660

1661
   result.test_sz_eq("Expected number of DN entries", req_dn.count(), 2);
11✔
1662

1663
   const Botan::ASN1_String req_val1 = req_dn.get_first_attribute(attr1);
11✔
1664
   const Botan::ASN1_String req_val2 = req_dn.get_first_attribute(attr2);
11✔
1665
   result.test_is_true("Attr1 matches encoded", req_val1 == val1);
11✔
1666
   result.test_is_true("Attr2 matches encoded", req_val2 == val2);
11✔
1667
   result.test_is_true("Attr1 tag matches encoded", req_val1.tagging() == val1.tagging());
11✔
1668
   result.test_is_true("Attr2 tag matches encoded", req_val2.tagging() == val2.tagging());
11✔
1669

1670
   const Botan::X509_Time not_before("100301123001Z", Botan::ASN1_Type::UtcTime);
11✔
1671
   const Botan::X509_Time not_after("300301123001Z", Botan::ASN1_Type::UtcTime);
11✔
1672

1673
   auto cert = ca.sign_request(req, rng, not_before, not_after);
11✔
1674

1675
   const Botan::X509_DN& cert_dn = cert.subject_dn();
11✔
1676

1677
   result.test_sz_eq("Expected number of DN entries", cert_dn.count(), 2);
11✔
1678

1679
   const Botan::ASN1_String cert_val1 = cert_dn.get_first_attribute(attr1);
11✔
1680
   const Botan::ASN1_String cert_val2 = cert_dn.get_first_attribute(attr2);
11✔
1681
   result.test_is_true("Attr1 matches encoded", cert_val1 == val1);
11✔
1682
   result.test_is_true("Attr2 matches encoded", cert_val2 == val2);
11✔
1683
   result.test_is_true("Attr1 tag matches encoded", cert_val1.tagging() == val1.tagging());
11✔
1684
   result.test_is_true("Attr2 tag matches encoded", cert_val2.tagging() == val2.tagging());
11✔
1685

1686
   return result;
22✔
1687
}
88✔
1688

1689
Test::Result test_x509_extensions(const Botan::Private_Key& ca_key,
11✔
1690
                                  const std::string& sig_algo,
1691
                                  const std::string& sig_padding,
1692
                                  const std::string& hash_fn,
1693
                                  Botan::RandomNumberGenerator& rng) {
1694
   using Botan::Key_Constraints;
11✔
1695

1696
   Test::Result result("X509 Extensions");
11✔
1697

1698
   /* Create the self-signed cert */
1699
   const Botan::X509_Certificate ca_cert =
11✔
1700
      Botan::X509::create_self_signed_cert(ca_opts(sig_padding), ca_key, hash_fn, rng);
11✔
1701

1702
   /* Create the CA object */
1703
   const Botan::X509_CA ca(ca_cert, ca_key, hash_fn, sig_padding, rng);
11✔
1704

1705
   /* Prepare CDP extension */
1706
   std::vector<std::string> cdp_urls = {
11✔
1707
      "http://example.com/crl1.pem",
1708
      "ldap://ldap.example.com/cn=crl1,dc=example,dc=com?certificateRevocationList;binary"};
11✔
1709

1710
   std::vector<Botan::Cert_Extension::CRL_Distribution_Points::Distribution_Point> dps;
11✔
1711

1712
   for(const auto& uri : cdp_urls) {
33✔
1713
      Botan::AlternativeName cdp_alt_name;
22✔
1714
      cdp_alt_name.add_uri(uri);
22✔
1715
      const Botan::Cert_Extension::CRL_Distribution_Points::Distribution_Point dp(cdp_alt_name);
22✔
1716

1717
      dps.emplace_back(dp);
22✔
1718
   }
22✔
1719

1720
   auto user_key = make_a_private_key(sig_algo, rng);
11✔
1721

1722
   Botan::X509_Cert_Options opts("Test User 1/US/Botan Project/Testing");
11✔
1723
   opts.constraints = Key_Constraints::DigitalSignature;
11✔
1724

1725
   // include a custom extension in the request
1726
   Botan::Extensions req_extensions;
11✔
1727
   const Botan::OID oid("1.2.3.4.5.6.7.8.9.1");
11✔
1728
   const Botan::OID ku_oid = Botan::OID::from_string("X509v3.KeyUsage");
11✔
1729
   req_extensions.add(std::make_unique<String_Extension>("AAAAAAAAAAAAAABCDEF"), false);
22✔
1730
   req_extensions.add(std::make_unique<Botan::Cert_Extension::CRL_Distribution_Points>(dps));
22✔
1731
   opts.extensions = req_extensions;
11✔
1732
   opts.set_padding_scheme(sig_padding);
11✔
1733

1734
   /* Create a self-signed certificate */
1735
   const Botan::X509_Certificate self_signed_cert = Botan::X509::create_self_signed_cert(opts, *user_key, hash_fn, rng);
11✔
1736

1737
   result.test_is_true("Extensions::extension_set true for Key_Usage",
11✔
1738
                       self_signed_cert.v3_extensions().extension_set(ku_oid));
11✔
1739

1740
   // check if known Key_Usage extension is present in self-signed cert
1741
   auto key_usage_ext = self_signed_cert.v3_extensions().get(ku_oid);
11✔
1742
   if(result.test_is_true("Key_Usage extension present in self-signed certificate", key_usage_ext != nullptr)) {
11✔
1743
      result.test_is_true(
22✔
1744
         "Key_Usage extension value matches in self-signed certificate",
1745
         dynamic_cast<Botan::Cert_Extension::Key_Usage&>(*key_usage_ext).get_constraints() == opts.constraints);
11✔
1746
   }
1747

1748
   // check if custom extension is present in self-signed cert
1749
   auto string_ext = self_signed_cert.v3_extensions().get_raw<String_Extension>(oid);
11✔
1750
   if(result.test_is_true("Custom extension present in self-signed certificate", string_ext != nullptr)) {
11✔
1751
      result.test_str_eq(
22✔
1752
         "Custom extension value matches in self-signed certificate", string_ext->value(), "AAAAAAAAAAAAAABCDEF");
33✔
1753
   }
1754

1755
   // check if CDPs are present in the self-signed cert
1756
   const auto* cert_cdps =
11✔
1757
      self_signed_cert.v3_extensions().get_extension_object_as<Botan::Cert_Extension::CRL_Distribution_Points>();
22✔
1758

1759
   if(result.test_is_true("CRL Distribution Points extension present in self-signed certificate",
44✔
1760
                          !cert_cdps->crl_distribution_urls().empty())) {
11✔
1761
      for(const auto& cdp : cert_cdps->distribution_points()) {
33✔
1762
         result.test_is_true("CDP URI present in self-signed certificate",
44✔
1763
                             std::ranges::find(cdp_urls, cdp.point().get_first_attribute("URI")) != cdp_urls.end());
66✔
1764
      }
1765

1766
      // The accessor returns one entry per URI
1767
      const auto& urls = cert_cdps->crl_distribution_urls();
11✔
1768
      result.test_sz_eq("crl_distribution_urls has one entry per URI (self-signed)", urls.size(), cdp_urls.size());
11✔
1769
      for(const auto& url : urls) {
33✔
1770
         result.test_is_true("crl_distribution_urls entry is a bare URI (self-signed)",
44✔
1771
                             std::ranges::find(cdp_urls, url) != cdp_urls.end());
44✔
1772
      }
1773

1774
      // Ensure X509_Certificate's cached accessor returns the same bare URIs
1775
      const auto& cert_dp = self_signed_cert.crl_distribution_point_uris();
11✔
1776
      result.test_sz_eq(
11✔
1777
         "X509_Certificate::crl_distribution_points size (self-signed)", cert_dp.size(), cdp_urls.size());
1778
      for(const auto& url : cert_dp) {
33✔
1779
         result.test_is_true("X509_Certificate::crl_distribution_points entry is a bare URI (self-signed)",
44✔
1780
                             std::ranges::find(cdp_urls, url.original_input()) != cdp_urls.end());
44✔
1781
      }
1782
   }
11✔
1783

1784
   const Botan::PKCS10_Request user_req = Botan::X509::create_cert_req(opts, *user_key, hash_fn, rng);
11✔
1785

1786
   /* Create a CA-signed certificate */
1787
   const Botan::X509_Certificate ca_signed_cert =
11✔
1788
      ca.sign_request(user_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
11✔
1789

1790
   // check if known Key_Usage extension is present in CA-signed cert
1791
   result.test_is_true("Extensions::extension_set true for Key_Usage",
11✔
1792
                       ca_signed_cert.v3_extensions().extension_set(ku_oid));
11✔
1793

1794
   key_usage_ext = ca_signed_cert.v3_extensions().get(ku_oid);
22✔
1795
   if(result.test_is_true("Key_Usage extension present in CA-signed certificate", key_usage_ext != nullptr)) {
11✔
1796
      auto constraints = dynamic_cast<Botan::Cert_Extension::Key_Usage&>(*key_usage_ext).get_constraints();
11✔
1797
      result.test_is_true("Key_Usage extension value matches in user certificate",
22✔
1798
                          constraints == Botan::Key_Constraints::DigitalSignature);
11✔
1799
   }
1800

1801
   // check if custom extension is present in CA-signed cert
1802
   result.test_is_true("Extensions::extension_set true for String_Extension",
11✔
1803
                       ca_signed_cert.v3_extensions().extension_set(oid));
11✔
1804
   string_ext = ca_signed_cert.v3_extensions().get_raw<String_Extension>(oid);
22✔
1805
   if(result.test_is_true("Custom extension present in CA-signed certificate", string_ext != nullptr)) {
11✔
1806
      result.test_str_eq(
22✔
1807
         "Custom extension value matches in CA-signed certificate", string_ext->value(), "AAAAAAAAAAAAAABCDEF");
33✔
1808
   }
1809

1810
   // check if CDPs are present in the CA-signed cert
1811
   cert_cdps = ca_signed_cert.v3_extensions().get_extension_object_as<Botan::Cert_Extension::CRL_Distribution_Points>();
22✔
1812

1813
   if(result.test_is_true("CRL Distribution Points extension present in CA-signed certificate",
44✔
1814
                          !cert_cdps->crl_distribution_urls().empty())) {
11✔
1815
      for(const auto& cdp : cert_cdps->distribution_points()) {
33✔
1816
         result.test_is_true("CDP URI present in CA-signed certificate",
44✔
1817
                             std::ranges::find(cdp_urls, cdp.point().get_first_attribute("URI")) != cdp_urls.end());
66✔
1818
      }
1819

1820
      const auto& urls = cert_cdps->crl_distribution_urls();
11✔
1821
      result.test_sz_eq("crl_distribution_urls has one entry per URI (CA-signed)", urls.size(), cdp_urls.size());
11✔
1822
      for(const auto& url : urls) {
33✔
1823
         result.test_is_true("crl_distribution_urls entry is a bare URI (CA-signed)",
44✔
1824
                             std::ranges::find(cdp_urls, url) != cdp_urls.end());
44✔
1825
      }
1826

1827
      const auto& cert_dp = ca_signed_cert.crl_distribution_point_uris();
11✔
1828
      result.test_sz_eq("X509_Certificate::crl_distribution_points size (CA-signed)", cert_dp.size(), cdp_urls.size());
11✔
1829
      for(const auto& url : cert_dp) {
33✔
1830
         result.test_is_true("X509_Certificate::crl_distribution_points entry is a bare URI (CA-signed)",
44✔
1831
                             std::ranges::find(cdp_urls, url.original_input()) != cdp_urls.end());
44✔
1832
      }
1833
   }
11✔
1834

1835
   return result;
11✔
1836
}
55✔
1837

1838
Test::Result test_hashes(const Botan::Private_Key& key, const std::string& hash_fn, Botan::RandomNumberGenerator& rng) {
12✔
1839
   Test::Result result("X509 Hashes");
12✔
1840

1841
   struct TestData {
12✔
1842
         const std::string issuer, subject, issuer_hash, subject_hash;
1843
   } const cases[]{{"",
12✔
1844
                    "",
1845
                    "E4F60D0AA6D7F3D3B6A6494B1C861B99F649C6F9EC51ABAF201B20F297327C95",
1846
                    "E4F60D0AA6D7F3D3B6A6494B1C861B99F649C6F9EC51ABAF201B20F297327C95"},
1847
                   {"a",
1848
                    "b",
1849
                    "BC2E013472F39AC579964880E422737C82BA812CB8BC2FD17E013060D71E6E19",
1850
                    "5E31CFAA3FAFB1A5BA296A0D2BAB9CA44D7936E9BF0BBC54637D0C53DBC4A432"},
1851
                   {"A",
1852
                    "B",
1853
                    "4B3206201C4BC9B6CD6C36532A97687DF9238155D99ADB60C66BF2B2220643D8",
1854
                    "FFF635A52A16618B4A0E9CD26B5E5A2FA573D343C051E6DE8B0811B1ACC89B86"},
1855
                   {
1856
                      "Test Issuer/US/Botan Project/Testing",
1857
                      "Test Subject/US/Botan Project/Testing",
1858
                      "ACB4F373004A56A983A23EB8F60FA4706312B5DB90FD978574FE7ACC84E093A5",
1859
                      "87039231C2205B74B6F1F3830A66272C0B41F71894B03AC3150221766D95267B",
1860
                   },
1861
                   {
1862
                      "Test Subject/US/Botan Project/Testing",
1863
                      "Test Issuer/US/Botan Project/Testing",
1864
                      "87039231C2205B74B6F1F3830A66272C0B41F71894B03AC3150221766D95267B",
1865
                      "ACB4F373004A56A983A23EB8F60FA4706312B5DB90FD978574FE7ACC84E093A5",
1866
                   }};
72✔
1867

1868
   for(const auto& a : cases) {
72✔
1869
      Botan::X509_Cert_Options opts{a.issuer};
60✔
1870
      opts.CA_key();
60✔
1871

1872
      const Botan::X509_Certificate issuer_cert = Botan::X509::create_self_signed_cert(opts, key, hash_fn, rng);
60✔
1873

1874
      result.test_str_eq(a.issuer, Botan::hex_encode(issuer_cert.raw_issuer_dn_sha256()), a.issuer_hash);
60✔
1875
      result.test_str_eq(a.issuer, Botan::hex_encode(issuer_cert.raw_subject_dn_sha256()), a.issuer_hash);
60✔
1876

1877
      const Botan::X509_CA ca(issuer_cert, key, hash_fn, rng);
60✔
1878
      const Botan::PKCS10_Request req =
60✔
1879
         Botan::X509::create_cert_req(Botan::X509_Cert_Options(a.subject), key, hash_fn, rng);
60✔
1880
      const Botan::X509_Certificate subject_cert =
60✔
1881
         ca.sign_request(req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
60✔
1882

1883
      result.test_str_eq(a.subject, Botan::hex_encode(subject_cert.raw_issuer_dn_sha256()), a.issuer_hash);
60✔
1884
      result.test_str_eq(a.subject, Botan::hex_encode(subject_cert.raw_subject_dn_sha256()), a.subject_hash);
60✔
1885
   }
60✔
1886
   return result;
12✔
1887
}
72✔
1888

1889
   #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
1890

1891
Test::Result test_x509_tn_auth_list_extension_decode() {
1✔
1892
   /* cert with TNAuthList extension data was generated by asn1parse cfg:
1893

1894
      asn1=SEQUENCE:tn_auth_list
1895

1896
      [tn_auth_list]
1897
      spc=EXP:0,IA5:1001
1898
      range=EXP:1,SEQUENCE:TelephoneNumberRange
1899
      one=EXP:2,IA5:333
1900

1901
      [TelephoneNumberRange]
1902
      start1=IA5:111
1903
      count1=INT:128
1904
      start2=IA5:222
1905
      count2=INT:256
1906
    */
1907
   const std::string filename("TNAuthList.pem");
1✔
1908
   Test::Result result("X509 TNAuthList decode");
1✔
1909
   result.start_timer();
1✔
1910

1911
   const Botan::X509_Certificate cert(Test::data_file("x509/x509test/" + filename));
2✔
1912

1913
   using Botan::Cert_Extension::TNAuthList;
1✔
1914

1915
   const auto* tn_auth_list = cert.v3_extensions().get_extension_object_as<TNAuthList>();
1✔
1916

1917
   const auto& tn_entries = tn_auth_list->entries();
1✔
1918

1919
   result.test_not_null("cert has TNAuthList extension", tn_auth_list);
1✔
1920

1921
   result.test_throws("wrong telephone_number_range() accessor for spc",
1✔
1922
                      [&tn_entries] { tn_entries[0].telephone_number_range(); });
2✔
1923
   result.test_throws("wrong telephone_number() accessor for range",
1✔
1924
                      [&tn_entries] { tn_entries[1].telephone_number(); });
2✔
1925
   result.test_throws("wrong service_provider_code() accessor for one",
1✔
1926
                      [&tn_entries] { tn_entries[2].service_provider_code(); });
2✔
1927

1928
   result.test_is_true("spc entry type", tn_entries[0].type() == TNAuthList::Entry::ServiceProviderCode);
1✔
1929
   result.test_str_eq("spc entry data", tn_entries[0].service_provider_code(), "1001");
1✔
1930

1931
   result.test_is_true("range entry type", tn_entries[1].type() == TNAuthList::Entry::TelephoneNumberRange);
1✔
1932
   const auto& range = tn_entries[1].telephone_number_range();
1✔
1933
   result.test_sz_eq("range entries count", range.size(), 2);
1✔
1934
   result.test_str_eq("range entry 0 start data", range[0].start.value(), "111");
1✔
1935
   result.test_sz_eq("range entry 0 count data", range[0].count, 128);
1✔
1936
   result.test_str_eq("range entry 1 start data", range[1].start.value(), "222");
1✔
1937
   result.test_sz_eq("range entry 1 count data", range[1].count, 256);
1✔
1938

1939
   result.test_is_true("one entry type", tn_entries[2].type() == TNAuthList::Entry::TelephoneNumber);
1✔
1940
   result.test_str_eq("one entry data", tn_entries[2].telephone_number(), "333");
1✔
1941

1942
   result.end_timer();
1✔
1943
   return result;
2✔
1944
}
1✔
1945

1946
   #endif
1947

1948
std::vector<std::string> get_sig_paddings(const std::string& sig_algo, const std::string& hash) {
12✔
1949
   if(sig_algo == "RSA") {
12✔
1950
      return {
1✔
1951
   #if defined(BOTAN_HAS_EMSA_PKCS1)
1952
         "PKCS1v15(" + hash + ")",
1✔
1953
   #endif
1954
   #if defined(BOTAN_HAS_EMSA_PSS)
1955
            "PSS(" + hash + ")",
1956
   #endif
1957
      };
3✔
1958
   } else if(sig_algo == "DSA" || sig_algo == "ECDSA" || sig_algo == "ECGDSA" || sig_algo == "ECKCDSA" ||
11✔
1959
             sig_algo == "GOST-34.10") {
7✔
1960
      return {hash};
10✔
1961
   } else if(sig_algo == "Ed25519" || sig_algo == "Ed448") {
6✔
1962
      return {"Pure"};
2✔
1963
   } else if(sig_algo == "Dilithium" || sig_algo == "ML-DSA") {
4✔
1964
      return {"Randomized"};
2✔
1965
   } else if(sig_algo == "HSS-LMS") {
2✔
1966
      return {""};
1✔
1967
   } else {
1968
      return {};
1✔
1969
   }
1970
}
6✔
1971

1972
class X509_Cert_Unit_Tests final : public Test {
1✔
1973
   public:
1974
      std::vector<Test::Result> run() override {
1✔
1975
         std::vector<Test::Result> results;
1✔
1976

1977
         auto& rng = this->rng();
1✔
1978

1979
         const std::string sig_algos[]{"RSA",
1✔
1980
                                       "DSA",
1981
                                       "ECDSA",
1982
                                       "ECGDSA",
1983
                                       "ECKCDSA",
1984
                                       "GOST-34.10",
1985
                                       "Ed25519",
1986
                                       "Ed448",
1987
                                       "Dilithium",
1988
                                       "ML-DSA",
1989
                                       "SLH-DSA",
1990
                                       "HSS-LMS"};
13✔
1991

1992
         for(const std::string& algo : sig_algos) {
13✔
1993
   #if !defined(BOTAN_HAS_EMSA_PKCS1)
1994
            if(algo == "RSA")
1995
               continue;
1996
   #endif
1997

1998
            std::string hash = "SHA-256";
12✔
1999

2000
            if(algo == "Ed25519") {
12✔
2001
               hash = "SHA-512";
1✔
2002
            }
2003
            if(algo == "Ed448") {
12✔
2004
               hash = "SHAKE-256(912)";
1✔
2005
            }
2006
            if(algo == "Dilithium" || algo == "ML-DSA") {
12✔
2007
               hash = "SHAKE-256(512)";
2✔
2008
            }
2009

2010
            auto key = make_a_private_key(algo, rng);
12✔
2011

2012
            if(key == nullptr) {
12✔
2013
               continue;
×
2014
            }
2015

2016
            results.push_back(test_hashes(*key, hash, rng));
24✔
2017
            results.push_back(test_valid_constraints(*key, algo));
24✔
2018

2019
            Test::Result usage_result("X509 Usage");
12✔
2020
            try {
12✔
2021
               usage_result.merge(test_usage(*key, algo, hash, rng));
12✔
2022
            } catch(std::exception& e) {
×
2023
               usage_result.test_failure("test_usage " + algo, e.what());
×
2024
            }
×
2025
            results.push_back(usage_result);
12✔
2026

2027
            for(const auto& padding_scheme : get_sig_paddings(algo, hash)) {
23✔
2028
               Test::Result cert_result("X509 Unit");
11✔
2029

2030
               try {
11✔
2031
                  cert_result.merge(test_x509_cert(*key, algo, padding_scheme, hash, rng));
11✔
2032
               } catch(std::exception& e) {
×
2033
                  cert_result.test_failure("test_x509_cert " + algo, e.what());
×
2034
               }
×
2035
               results.push_back(cert_result);
11✔
2036

2037
               Test::Result pkcs10_result("PKCS10 extensions");
11✔
2038
               try {
11✔
2039
                  pkcs10_result.merge(test_pkcs10_ext(*key, padding_scheme, hash, rng));
11✔
2040
               } catch(std::exception& e) {
×
2041
                  pkcs10_result.test_failure("test_pkcs10_ext " + algo, e.what());
×
2042
               }
×
2043
               results.push_back(pkcs10_result);
11✔
2044

2045
               Test::Result self_issued_result("X509 Self Issued");
11✔
2046
               try {
11✔
2047
                  self_issued_result.merge(test_self_issued(*key, algo, padding_scheme, hash, rng));
11✔
2048
               } catch(std::exception& e) {
×
2049
                  self_issued_result.test_failure("test_self_issued " + algo, e.what());
×
2050
               }
×
2051
               results.push_back(self_issued_result);
11✔
2052

2053
               Test::Result extensions_result("X509 Extensions");
11✔
2054
               try {
11✔
2055
                  extensions_result.merge(test_x509_extensions(*key, algo, padding_scheme, hash, rng));
11✔
2056
               } catch(std::exception& e) {
×
2057
                  extensions_result.test_failure("test_extensions " + algo, e.what());
×
2058
               }
×
2059
               results.push_back(extensions_result);
11✔
2060

2061
               Test::Result custom_dn_result("X509 Custom DN");
11✔
2062
               try {
11✔
2063
                  custom_dn_result.merge(test_custom_dn_attr(*key, algo, padding_scheme, hash, rng));
11✔
2064
               } catch(std::exception& e) {
×
2065
                  custom_dn_result.test_failure("test_custom_dn_attr " + algo, e.what());
×
2066
               }
×
2067
               results.push_back(custom_dn_result);
11✔
2068
            }
23✔
2069
         }
24✔
2070

2071
         /*
2072
         These are algos which cannot sign but can be included in certs
2073
         */
2074
         const std::vector<std::string> enc_algos = {
1✔
2075
            "DH", "ECDH", "ElGamal", "Kyber", "ML-KEM", "FrodoKEM", "ClassicMcEliece"};
1✔
2076

2077
         for(const std::string& algo : enc_algos) {
8✔
2078
            auto key = make_a_private_key(algo, rng);
7✔
2079

2080
            if(key) {
7✔
2081
               results.push_back(test_valid_constraints(*key, algo));
14✔
2082
            }
2083
         }
7✔
2084

2085
   #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM) && defined(BOTAN_HAS_EMSA_PKCS1) && defined(BOTAN_HAS_EMSA_PSSR) && \
2086
      defined(BOTAN_HAS_RSA)
2087
         Test::Result pad_config_result("X509 Padding Config");
1✔
2088
         try {
1✔
2089
            pad_config_result.merge(test_padding_config());
1✔
2090
         } catch(const std::exception& e) {
×
2091
            pad_config_result.test_failure("test_padding_config", e.what());
×
2092
         }
×
2093
         results.push_back(pad_config_result);
1✔
2094
   #endif
2095

2096
   #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
2097
         results.push_back(test_x509_utf8());
2✔
2098
         results.push_back(test_x509_any_key_extended_usage());
2✔
2099
         results.push_back(test_x509_bmpstring());
2✔
2100
         results.push_back(test_x509_teletex());
2✔
2101
         results.push_back(test_crl_dn_name());
2✔
2102
         results.push_back(test_rdn_multielement_set_name());
2✔
2103
         results.push_back(test_x509_decode_list());
2✔
2104
         results.push_back(test_rsa_oaep());
2✔
2105
         results.push_back(test_x509_authority_info_access_extension());
2✔
2106
         results.push_back(test_x509_ldap_empty_authority_uris());
2✔
2107
         results.push_back(test_crl_issuing_distribution_point_extension());
2✔
2108
         results.push_back(test_verify_gost2012_cert());
2✔
2109
         results.push_back(test_parse_rsa_pss_cert());
2✔
2110
         results.push_back(test_x509_tn_auth_list_extension_decode());
2✔
2111
   #endif
2112

2113
         results.push_back(test_x509_encode_authority_info_access_extension());
2✔
2114
         results.push_back(test_x509_extension());
2✔
2115
         results.push_back(test_x509_extension_decode_duplicate());
2✔
2116
         results.push_back(test_x509_wrong_context_certificate_extensions());
2✔
2117
         results.push_back(test_x509_wrong_context_crl_extensions());
2✔
2118
         results.push_back(test_x509_dates());
2✔
2119
         results.push_back(test_cert_status_strings());
2✔
2120
         results.push_back(test_x509_uninit());
2✔
2121
         results.push_back(test_crl_entry_negative_serial());
2✔
2122

2123
         return results;
1✔
2124
      }
13✔
2125
};
2126

2127
BOTAN_REGISTER_TEST("x509", "x509_unit", X509_Cert_Unit_Tests);
2128

2129
#endif
2130

2131
}  // namespace
2132

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