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

randombit / botan / 28311706783

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

push

github

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

Validate AlgorithmIdentifier parameters on decode

112110 of 125479 relevant lines covered (89.35%)

10963156.46 hits per line

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

94.85
/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
   return result;
22✔
1229
}
44✔
1230

1231
Test::Result test_crl_entry_negative_serial() {
1✔
1232
   Test::Result result("CRL entry with negative serial matches certificate serial");
1✔
1233

1234
   const auto build_crl_entry = [](const std::vector<uint8_t>& serial_bytes) {
4✔
1235
      std::vector<uint8_t> der;
3✔
1236
      Botan::DER_Encoder enc(der);
3✔
1237
      enc.start_sequence()
3✔
1238
         .add_object(Botan::ASN1_Type::Integer, Botan::ASN1_Class::Universal, serial_bytes.data(), serial_bytes.size())
3✔
1239
         .encode(Botan::X509_Time(std::chrono::system_clock::now()))
3✔
1240
         .end_cons();
3✔
1241

1242
      Botan::CRL_Entry entry;
3✔
1243
      Botan::BER_Decoder dec(der);
3✔
1244
      entry.decode_from(dec);
3✔
1245
      return entry;
3✔
1246
   };
3✔
1247

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

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

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

1260
   return result;
1✔
1261
}
1✔
1262

1263
Test::Result test_usage(const Botan::Private_Key& ca_key,
12✔
1264
                        const std::string& sig_algo,
1265
                        const std::string& hash_fn,
1266
                        Botan::RandomNumberGenerator& rng) {
1267
   using Botan::Key_Constraints;
12✔
1268
   using Botan::Usage_Type;
12✔
1269

1270
   Test::Result result("X509 Usage");
12✔
1271

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

1275
   /* Create the CA object */
1276
   const Botan::X509_CA ca(ca_cert, ca_key, hash_fn, rng);
12✔
1277

1278
   auto user1_key = make_a_private_key(sig_algo, rng);
12✔
1279

1280
   Botan::X509_Cert_Options opts("Test User 1/US/Botan Project/Testing");
12✔
1281
   opts.constraints = Key_Constraints::DigitalSignature;
12✔
1282

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

1285
   const Botan::X509_Certificate user1_cert =
12✔
1286
      ca.sign_request(user1_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
12✔
1287

1288
   // cert only allows digitalSignature, but we check for both digitalSignature and cRLSign
1289
   result.test_is_false(
12✔
1290
      "key usage cRLSign not allowed",
1291
      user1_cert.allowed_usage(Key_Constraints(Key_Constraints::DigitalSignature | Key_Constraints::CrlSign)));
12✔
1292
   result.test_is_false("encryption is not allowed", user1_cert.allowed_usage(Usage_Type::ENCRYPTION));
12✔
1293

1294
   // cert only allows digitalSignature, so checking for only that should be ok
1295
   result.test_is_true("key usage digitalSignature allowed",
12✔
1296
                       user1_cert.allowed_usage(Key_Constraints::DigitalSignature));
12✔
1297

1298
   opts.constraints = Key_Constraints(Key_Constraints::DigitalSignature | Key_Constraints::CrlSign);
12✔
1299

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

1302
   const Botan::X509_Certificate mult_usage_cert =
12✔
1303
      ca.sign_request(mult_usage_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
12✔
1304

1305
   // cert allows multiple usages, so each one of them as well as both together should be allowed
1306
   result.test_is_true("key usage multiple digitalSignature allowed",
12✔
1307
                       mult_usage_cert.allowed_usage(Key_Constraints::DigitalSignature));
12✔
1308
   result.test_is_true("key usage multiple cRLSign allowed", mult_usage_cert.allowed_usage(Key_Constraints::CrlSign));
12✔
1309
   result.test_is_true(
12✔
1310
      "key usage multiple digitalSignature and cRLSign allowed",
1311
      mult_usage_cert.allowed_usage(Key_Constraints(Key_Constraints::DigitalSignature | Key_Constraints::CrlSign)));
12✔
1312
   result.test_is_false("encryption is not allowed", mult_usage_cert.allowed_usage(Usage_Type::ENCRYPTION));
12✔
1313

1314
   opts.constraints = Key_Constraints();
12✔
1315

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

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

1321
   // cert allows every usage
1322
   result.test_is_true("key usage digitalSignature allowed",
12✔
1323
                       no_usage_cert.allowed_usage(Key_Constraints::DigitalSignature));
12✔
1324
   result.test_is_true("key usage cRLSign allowed", no_usage_cert.allowed_usage(Key_Constraints::CrlSign));
12✔
1325
   result.test_is_true("key usage encryption allowed", no_usage_cert.allowed_usage(Usage_Type::ENCRYPTION));
12✔
1326

1327
   if(sig_algo == "RSA") {
12✔
1328
      // cert allows data encryption
1329
      opts.constraints = Key_Constraints(Key_Constraints::KeyEncipherment | Key_Constraints::DataEncipherment);
1✔
1330

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

1333
      const Botan::X509_Certificate enc_cert =
1✔
1334
         ca.sign_request(enc_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
1✔
1335

1336
      result.test_is_true("cert allows encryption", enc_cert.allowed_usage(Usage_Type::ENCRYPTION));
1✔
1337
      result.test_is_true("cert does not allow TLS client auth", !enc_cert.allowed_usage(Usage_Type::TLS_CLIENT_AUTH));
1✔
1338
   }
1✔
1339

1340
   return result;
12✔
1341
}
24✔
1342

1343
Test::Result test_self_issued(const Botan::Private_Key& ca_key,
11✔
1344
                              const std::string& sig_algo,
1345
                              const std::string& sig_padding,
1346
                              const std::string& hash_fn,
1347
                              Botan::RandomNumberGenerator& rng) {
1348
   using Botan::Key_Constraints;
11✔
1349

1350
   Test::Result result("X509 Self Issued");
11✔
1351

1352
   // create the self-signed cert
1353
   const Botan::X509_Certificate ca_cert =
11✔
1354
      Botan::X509::create_self_signed_cert(ca_opts(sig_padding), ca_key, hash_fn, rng);
11✔
1355

1356
   /* Create the CA object */
1357
   const Botan::X509_CA ca(ca_cert, ca_key, hash_fn, sig_padding, rng);
11✔
1358

1359
   auto user_key = make_a_private_key(sig_algo, rng);
11✔
1360

1361
   // create a self-issued certificate, that is, a certificate with subject dn == issuer dn,
1362
   // but signed by a CA, not signed by it's own private key
1363
   Botan::X509_Cert_Options opts = ca_opts();
11✔
1364
   opts.constraints = Key_Constraints::DigitalSignature;
11✔
1365
   opts.set_padding_scheme(sig_padding);
11✔
1366

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

1369
   const Botan::X509_Certificate self_issued_cert =
11✔
1370
      ca.sign_request(self_issued_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
11✔
1371

1372
   // check that this chain can can be verified successfully
1373
   const Botan::Certificate_Store_In_Memory trusted(ca.ca_certificate());
11✔
1374

1375
   const Botan::Path_Validation_Restrictions restrictions(false, 80);
11✔
1376

1377
   const Botan::Path_Validation_Result validation_result =
11✔
1378
      Botan::x509_path_validate(self_issued_cert, restrictions, trusted);
11✔
1379

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

1382
   return result;
11✔
1383
}
22✔
1384

1385
Test::Result test_x509_uninit() {
1✔
1386
   Test::Result result("X509 object uninitialized access");
1✔
1387

1388
   Botan::X509_Certificate cert;
1✔
1389
   result.test_throws("uninitialized cert access causes exception", "X509_Certificate uninitialized", [&cert]() {
1✔
1390
      cert.x509_version();
1✔
1391
   });
1392

1393
   Botan::X509_CRL crl;
1✔
1394
   result.test_throws(
1✔
1395
      "uninitialized crl access causes exception", "X509_CRL uninitialized", [&crl]() { crl.crl_number_bigint(); });
2✔
1396

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

1403
   result.test_throws("synth crl signature() throws", "X509_Object uninitialized", [&]() { synth_crl.signature(); });
2✔
1404
   result.test_throws(
1✔
1405
      "synth crl signed_body() throws", "X509_Object uninitialized", [&]() { synth_crl.signed_body(); });
2✔
1406
   result.test_throws("synth crl signature_algorithm() throws", "X509_Object uninitialized", [&]() {
1✔
1407
      synth_crl.signature_algorithm();
1✔
1408
   });
1409
   result.test_throws("synth crl tbs_data() throws", "X509_Object uninitialized", [&]() { synth_crl.tbs_data(); });
2✔
1410
   result.test_throws("synth crl BER_encode() throws", "X509_Object uninitialized", [&]() { synth_crl.BER_encode(); });
2✔
1411

1412
   return result;
1✔
1413
}
1✔
1414

1415
Test::Result test_valid_constraints(const Botan::Private_Key& key, const std::string& pk_algo) {
19✔
1416
   using Botan::Key_Constraints;
19✔
1417

1418
   Test::Result result("X509 Valid Constraints " + pk_algo);
19✔
1419

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

1422
   // Now check some typical usage scenarios for the given key type
1423
   // Taken from RFC 5280, sec. 4.2.1.3
1424
   // ALL constraints are not typical at all, but we use them for a negative test
1425
   const auto all = Key_Constraints(
19✔
1426
      Key_Constraints::DigitalSignature | Key_Constraints::NonRepudiation | Key_Constraints::KeyEncipherment |
1427
      Key_Constraints::DataEncipherment | Key_Constraints::KeyAgreement | Key_Constraints::KeyCertSign |
1428
      Key_Constraints::CrlSign | Key_Constraints::EncipherOnly | Key_Constraints::DecipherOnly);
19✔
1429

1430
   const auto ca = Key_Constraints(Key_Constraints::KeyCertSign);
19✔
1431
   const auto sign_data = Key_Constraints(Key_Constraints::DigitalSignature);
19✔
1432
   const auto non_repudiation = Key_Constraints(Key_Constraints::NonRepudiation | Key_Constraints::DigitalSignature);
19✔
1433
   const auto key_encipherment = Key_Constraints(Key_Constraints::KeyEncipherment);
19✔
1434
   const auto data_encipherment = Key_Constraints(Key_Constraints::DataEncipherment);
19✔
1435
   const auto key_agreement = Key_Constraints(Key_Constraints::KeyAgreement);
19✔
1436
   const auto key_agreement_encipher_only =
19✔
1437
      Key_Constraints(Key_Constraints::KeyAgreement | Key_Constraints::EncipherOnly);
19✔
1438
   const auto key_agreement_decipher_only =
19✔
1439
      Key_Constraints(Key_Constraints::KeyAgreement | Key_Constraints::DecipherOnly);
19✔
1440
   const auto crl_sign = Key_Constraints(Key_Constraints::CrlSign);
19✔
1441
   const auto sign_everything =
19✔
1442
      Key_Constraints(Key_Constraints::DigitalSignature | Key_Constraints::KeyCertSign | Key_Constraints::CrlSign);
19✔
1443

1444
   if(pk_algo == "DH" || pk_algo == "ECDH") {
19✔
1445
      // DH and ECDH only for key agreement
1446
      result.test_is_false("all constraints not permitted", all.compatible_with(key));
2✔
1447
      result.test_is_false("cert sign not permitted", ca.compatible_with(key));
2✔
1448
      result.test_is_false("signature not permitted", sign_data.compatible_with(key));
2✔
1449
      result.test_is_false("non repudiation not permitted", non_repudiation.compatible_with(key));
2✔
1450
      result.test_is_false("key encipherment not permitted", key_encipherment.compatible_with(key));
2✔
1451
      result.test_is_false("data encipherment not permitted", data_encipherment.compatible_with(key));
2✔
1452
      result.test_is_true("usage acceptable", key_agreement.compatible_with(key));
2✔
1453
      result.test_is_true("usage acceptable", key_agreement_encipher_only.compatible_with(key));
2✔
1454
      result.test_is_true("usage acceptable", key_agreement_decipher_only.compatible_with(key));
2✔
1455
      result.test_is_false("crl sign not permitted", crl_sign.compatible_with(key));
2✔
1456
      result.test_is_false("sign", sign_everything.compatible_with(key));
2✔
1457
   } else if(pk_algo == "Kyber" || pk_algo == "FrodoKEM" || pk_algo == "ML-KEM" || pk_algo == "ClassicMcEliece") {
17✔
1458
      // KEMs can encrypt and agree
1459
      result.test_is_false("all constraints not permitted", all.compatible_with(key));
4✔
1460
      result.test_is_false("cert sign not permitted", ca.compatible_with(key));
4✔
1461
      result.test_is_false("signature not permitted", sign_data.compatible_with(key));
4✔
1462
      result.test_is_false("non repudiation not permitted", non_repudiation.compatible_with(key));
4✔
1463
      result.test_is_false("crl sign not permitted", crl_sign.compatible_with(key));
4✔
1464
      result.test_is_false("sign", sign_everything.compatible_with(key));
4✔
1465
      result.test_is_false("key agreement not permitted", key_agreement.compatible_with(key));
4✔
1466
      result.test_is_false("usage acceptable", data_encipherment.compatible_with(key));
4✔
1467
      result.test_is_true("usage acceptable", key_encipherment.compatible_with(key));
4✔
1468
   } else if(pk_algo == "RSA") {
13✔
1469
      // RSA can do everything except key agreement
1470
      result.test_is_false("all constraints not permitted", all.compatible_with(key));
1✔
1471

1472
      result.test_is_true("usage acceptable", ca.compatible_with(key));
1✔
1473
      result.test_is_true("usage acceptable", sign_data.compatible_with(key));
1✔
1474
      result.test_is_true("usage acceptable", non_repudiation.compatible_with(key));
1✔
1475
      result.test_is_true("usage acceptable", key_encipherment.compatible_with(key));
1✔
1476
      result.test_is_true("usage acceptable", data_encipherment.compatible_with(key));
1✔
1477
      result.test_is_false("key agreement not permitted", key_agreement.compatible_with(key));
1✔
1478
      result.test_is_false("key agreement", key_agreement_encipher_only.compatible_with(key));
1✔
1479
      result.test_is_false("key agreement", key_agreement_decipher_only.compatible_with(key));
1✔
1480
      result.test_is_true("usage acceptable", crl_sign.compatible_with(key));
1✔
1481
      result.test_is_true("usage acceptable", sign_everything.compatible_with(key));
1✔
1482
   } else if(pk_algo == "ElGamal") {
12✔
1483
      // only ElGamal encryption is currently implemented
1484
      result.test_is_false("all constraints not permitted", all.compatible_with(key));
1✔
1485
      result.test_is_false("cert sign not permitted", ca.compatible_with(key));
1✔
1486
      result.test_is_true("data encipherment permitted", data_encipherment.compatible_with(key));
1✔
1487
      result.test_is_true("key encipherment permitted", key_encipherment.compatible_with(key));
1✔
1488
      result.test_is_false("key agreement not permitted", key_agreement.compatible_with(key));
1✔
1489
      result.test_is_false("key agreement", key_agreement_encipher_only.compatible_with(key));
1✔
1490
      result.test_is_false("key agreement", key_agreement_decipher_only.compatible_with(key));
1✔
1491
      result.test_is_false("crl sign not permitted", crl_sign.compatible_with(key));
1✔
1492
      result.test_is_false("sign", sign_everything.compatible_with(key));
1✔
1493
   } else if(pk_algo == "DSA" || pk_algo == "ECDSA" || pk_algo == "ECGDSA" || pk_algo == "ECKCDSA" ||
10✔
1494
             pk_algo == "GOST-34.10" || pk_algo == "Dilithium" || pk_algo == "ML-DSA" || pk_algo == "SLH-DSA" ||
18✔
1495
             pk_algo == "HSS-LMS") {
3✔
1496
      // these are signature algorithms only
1497
      result.test_is_false("all constraints not permitted", all.compatible_with(key));
9✔
1498

1499
      result.test_is_true("ca allowed", ca.compatible_with(key));
9✔
1500
      result.test_is_true("sign allowed", sign_data.compatible_with(key));
9✔
1501
      result.test_is_true("non-repudiation allowed", non_repudiation.compatible_with(key));
9✔
1502
      result.test_is_false("key encipherment not permitted", key_encipherment.compatible_with(key));
9✔
1503
      result.test_is_false("data encipherment not permitted", data_encipherment.compatible_with(key));
9✔
1504
      result.test_is_false("key agreement not permitted", key_agreement.compatible_with(key));
9✔
1505
      result.test_is_false("key agreement", key_agreement_encipher_only.compatible_with(key));
9✔
1506
      result.test_is_false("key agreement", key_agreement_decipher_only.compatible_with(key));
9✔
1507
      result.test_is_true("crl sign allowed", crl_sign.compatible_with(key));
9✔
1508
      result.test_is_true("sign allowed", sign_everything.compatible_with(key));
9✔
1509
   }
1510

1511
   return result;
19✔
1512
}
×
1513

1514
/**
1515
 * @brief X.509v3 extension that encodes a given string
1516
 */
1517
class String_Extension final : public Botan::Certificate_Extension {
22✔
1518
   public:
1519
      String_Extension() = default;
22✔
1520

1521
      explicit String_Extension(const std::string& val) : m_contents(val) {}
11✔
1522

1523
      std::string value() const { return m_contents; }
44✔
1524

1525
      std::unique_ptr<Certificate_Extension> copy() const override {
×
1526
         return std::make_unique<String_Extension>(m_contents);
×
1527
      }
1528

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

1531
      bool should_encode() const override { return true; }
22✔
1532

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

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

1537
      std::vector<uint8_t> encode_inner() const override {
11✔
1538
         std::vector<uint8_t> bits;
11✔
1539
         Botan::DER_Encoder(bits).encode(Botan::ASN1_String(m_contents, Botan::ASN1_Type::Utf8String));
22✔
1540
         return bits;
11✔
1541
      }
×
1542

1543
      void decode_inner(const std::vector<uint8_t>& in) override {
22✔
1544
         Botan::ASN1_String str;
22✔
1545
         Botan::BER_Decoder(in).decode(str, Botan::ASN1_Type::Utf8String).verify_end();
22✔
1546
         m_contents = str.value();
44✔
1547
      }
22✔
1548

1549
   private:
1550
      std::string m_contents;
1551
};
1552

1553
Test::Result test_x509_wrong_context_certificate_extensions() {
1✔
1554
   Test::Result result("X509 wrong-context certificate extensions");
1✔
1555

1556
   #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
1557
   const std::string base = "x509/wrong_context_ext/";
1✔
1558

1559
   const auto test_rejected = [&](const std::string& filename, std::string_view what) {
4✔
1560
      result.test_throws<Botan::Decoding_Error>(
3✔
1561
         std::string(what), [&]() { const Botan::X509_Certificate cert(Test::data_file(base + filename)); });
12✔
1562
   };
3✔
1563

1564
   test_rejected("cert_with_crl_number.pem", "CRL number rejected in certificate");
1✔
1565
   test_rejected("cert_with_reason_code.pem", "CRL reason rejected in certificate");
1✔
1566
   test_rejected("cert_with_idp.pem", "CRL issuing distribution point rejected in certificate");
1✔
1567
   #endif
1568

1569
   return result;
1✔
1570
}
1✔
1571

1572
Test::Result test_x509_wrong_context_crl_extensions() {
1✔
1573
   Test::Result result("X509 wrong-context CRL extensions");
1✔
1574

1575
   #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
1576
   const std::string base_dir = "x509/wrong_context_ext";
1✔
1577

1578
   auto load_crl_from_pem = [&](std::string_view filename) -> Botan::X509_CRL {
5✔
1579
      return Botan::X509_CRL(Test::data_file(base_dir, filename));
6✔
1580
   };
1✔
1581

1582
   result.test_throws<Botan::Decoding_Error>("SubjectAltName rejected in CRL",
1✔
1583
                                             [&]() { load_crl_from_pem("crl_with_san.pem"); });
2✔
1584
   result.test_throws<Botan::Decoding_Error>("SubjectAltName rejected in CRL entry",
1✔
1585
                                             [&]() { load_crl_from_pem("crl_entry_with_san.pem"); });
2✔
1586

1587
   const Botan::X509_Certificate ca_cert(Test::data_file(base_dir + "/ca.pem"));
2✔
1588
   const Botan::X509_Certificate user_cert(Test::data_file(base_dir + "/user.pem"));
2✔
1589
   const std::vector<Botan::X509_Certificate> cert_path = {user_cert, ca_cert};
3✔
1590
   const auto validation_time = Botan::calendar_point(2027, 1, 1, 0, 0, 0).to_std_timepoint();
1✔
1591

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

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

1599
      const bool contains_expected_code =
2✔
1600
         !crl_status.empty() &&
2✔
1601
         crl_status[0].contains(Botan::Certificate_Status_Code::CRL_HAS_UNKNOWN_CRITICAL_EXTENSION);
4✔
1602
      result.test_is_true(what, contains_expected_code);
2✔
1603
   };
4✔
1604

1605
   check_crl_unusable("crl_with_unknown_critical.pem", "critical unknown CRL extension rejects CRL");
1✔
1606
   check_crl_unusable("crl_entry_with_unknown_critical.pem", "critical unknown CRL entry extension rejects CRL");
1✔
1607
   #endif
1608

1609
   return result;
2✔
1610
}
2✔
1611

1612
Test::Result test_custom_dn_attr(const Botan::Private_Key& ca_key,
11✔
1613
                                 const std::string& sig_algo,
1614
                                 const std::string& sig_padding,
1615
                                 const std::string& hash_fn,
1616
                                 Botan::RandomNumberGenerator& rng) {
1617
   Test::Result result("X509 Custom DN");
11✔
1618

1619
   /* Create the self-signed cert */
1620
   const Botan::X509_Certificate ca_cert =
11✔
1621
      Botan::X509::create_self_signed_cert(ca_opts(sig_padding), ca_key, hash_fn, rng);
11✔
1622

1623
   /* Create the CA object */
1624
   const Botan::X509_CA ca(ca_cert, ca_key, hash_fn, sig_padding, rng);
11✔
1625

1626
   auto user_key = make_a_private_key(sig_algo, rng);
11✔
1627

1628
   Botan::X509_DN subject_dn;
11✔
1629

1630
   const Botan::OID attr1(Botan::OID("1.3.6.1.4.1.25258.9.1.1"));
11✔
1631
   const Botan::OID attr2(Botan::OID("1.3.6.1.4.1.25258.9.1.2"));
11✔
1632
   const Botan::ASN1_String val1("Custom Attr 1", Botan::ASN1_Type::PrintableString);
11✔
1633
   const Botan::ASN1_String val2("12345", Botan::ASN1_Type::Utf8String);
11✔
1634

1635
   subject_dn.add_attribute(attr1, val1);
11✔
1636
   subject_dn.add_attribute(attr2, val2);
11✔
1637

1638
   const Botan::Extensions extensions;
11✔
1639

1640
   const Botan::PKCS10_Request req =
11✔
1641
      Botan::PKCS10_Request::create(*user_key, subject_dn, extensions, hash_fn, rng, sig_padding);
11✔
1642

1643
   const Botan::X509_DN& req_dn = req.subject_dn();
11✔
1644

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

1647
   const Botan::ASN1_String req_val1 = req_dn.get_first_attribute(attr1);
11✔
1648
   const Botan::ASN1_String req_val2 = req_dn.get_first_attribute(attr2);
11✔
1649
   result.test_is_true("Attr1 matches encoded", req_val1 == val1);
11✔
1650
   result.test_is_true("Attr2 matches encoded", req_val2 == val2);
11✔
1651
   result.test_is_true("Attr1 tag matches encoded", req_val1.tagging() == val1.tagging());
11✔
1652
   result.test_is_true("Attr2 tag matches encoded", req_val2.tagging() == val2.tagging());
11✔
1653

1654
   const Botan::X509_Time not_before("100301123001Z", Botan::ASN1_Type::UtcTime);
11✔
1655
   const Botan::X509_Time not_after("300301123001Z", Botan::ASN1_Type::UtcTime);
11✔
1656

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

1659
   const Botan::X509_DN& cert_dn = cert.subject_dn();
11✔
1660

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

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

1670
   return result;
22✔
1671
}
88✔
1672

1673
Test::Result test_x509_extensions(const Botan::Private_Key& ca_key,
11✔
1674
                                  const std::string& sig_algo,
1675
                                  const std::string& sig_padding,
1676
                                  const std::string& hash_fn,
1677
                                  Botan::RandomNumberGenerator& rng) {
1678
   using Botan::Key_Constraints;
11✔
1679

1680
   Test::Result result("X509 Extensions");
11✔
1681

1682
   /* Create the self-signed cert */
1683
   const Botan::X509_Certificate ca_cert =
11✔
1684
      Botan::X509::create_self_signed_cert(ca_opts(sig_padding), ca_key, hash_fn, rng);
11✔
1685

1686
   /* Create the CA object */
1687
   const Botan::X509_CA ca(ca_cert, ca_key, hash_fn, sig_padding, rng);
11✔
1688

1689
   /* Prepare CDP extension */
1690
   std::vector<std::string> cdp_urls = {
11✔
1691
      "http://example.com/crl1.pem",
1692
      "ldap://ldap.example.com/cn=crl1,dc=example,dc=com?certificateRevocationList;binary"};
11✔
1693

1694
   std::vector<Botan::Cert_Extension::CRL_Distribution_Points::Distribution_Point> dps;
11✔
1695

1696
   for(const auto& uri : cdp_urls) {
33✔
1697
      Botan::AlternativeName cdp_alt_name;
22✔
1698
      cdp_alt_name.add_uri(uri);
22✔
1699
      const Botan::Cert_Extension::CRL_Distribution_Points::Distribution_Point dp(cdp_alt_name);
22✔
1700

1701
      dps.emplace_back(dp);
22✔
1702
   }
22✔
1703

1704
   auto user_key = make_a_private_key(sig_algo, rng);
11✔
1705

1706
   Botan::X509_Cert_Options opts("Test User 1/US/Botan Project/Testing");
11✔
1707
   opts.constraints = Key_Constraints::DigitalSignature;
11✔
1708

1709
   // include a custom extension in the request
1710
   Botan::Extensions req_extensions;
11✔
1711
   const Botan::OID oid("1.2.3.4.5.6.7.8.9.1");
11✔
1712
   const Botan::OID ku_oid = Botan::OID::from_string("X509v3.KeyUsage");
11✔
1713
   req_extensions.add(std::make_unique<String_Extension>("AAAAAAAAAAAAAABCDEF"), false);
22✔
1714
   req_extensions.add(std::make_unique<Botan::Cert_Extension::CRL_Distribution_Points>(dps));
22✔
1715
   opts.extensions = req_extensions;
11✔
1716
   opts.set_padding_scheme(sig_padding);
11✔
1717

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

1721
   result.test_is_true("Extensions::extension_set true for Key_Usage",
11✔
1722
                       self_signed_cert.v3_extensions().extension_set(ku_oid));
11✔
1723

1724
   // check if known Key_Usage extension is present in self-signed cert
1725
   auto key_usage_ext = self_signed_cert.v3_extensions().get(ku_oid);
11✔
1726
   if(result.test_is_true("Key_Usage extension present in self-signed certificate", key_usage_ext != nullptr)) {
11✔
1727
      result.test_is_true(
22✔
1728
         "Key_Usage extension value matches in self-signed certificate",
1729
         dynamic_cast<Botan::Cert_Extension::Key_Usage&>(*key_usage_ext).get_constraints() == opts.constraints);
11✔
1730
   }
1731

1732
   // check if custom extension is present in self-signed cert
1733
   auto string_ext = self_signed_cert.v3_extensions().get_raw<String_Extension>(oid);
11✔
1734
   if(result.test_is_true("Custom extension present in self-signed certificate", string_ext != nullptr)) {
11✔
1735
      result.test_str_eq(
22✔
1736
         "Custom extension value matches in self-signed certificate", string_ext->value(), "AAAAAAAAAAAAAABCDEF");
33✔
1737
   }
1738

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

1743
   if(result.test_is_true("CRL Distribution Points extension present in self-signed certificate",
44✔
1744
                          !cert_cdps->crl_distribution_urls().empty())) {
11✔
1745
      for(const auto& cdp : cert_cdps->distribution_points()) {
33✔
1746
         result.test_is_true("CDP URI present in self-signed certificate",
44✔
1747
                             std::ranges::find(cdp_urls, cdp.point().get_first_attribute("URI")) != cdp_urls.end());
66✔
1748
      }
1749

1750
      // The accessor returns one entry per URI
1751
      const auto& urls = cert_cdps->crl_distribution_urls();
11✔
1752
      result.test_sz_eq("crl_distribution_urls has one entry per URI (self-signed)", urls.size(), cdp_urls.size());
11✔
1753
      for(const auto& url : urls) {
33✔
1754
         result.test_is_true("crl_distribution_urls entry is a bare URI (self-signed)",
44✔
1755
                             std::ranges::find(cdp_urls, url) != cdp_urls.end());
44✔
1756
      }
1757

1758
      // Ensure X509_Certificate's cached accessor returns the same bare URIs
1759
      const auto& cert_dp = self_signed_cert.crl_distribution_point_uris();
11✔
1760
      result.test_sz_eq(
11✔
1761
         "X509_Certificate::crl_distribution_points size (self-signed)", cert_dp.size(), cdp_urls.size());
1762
      for(const auto& url : cert_dp) {
33✔
1763
         result.test_is_true("X509_Certificate::crl_distribution_points entry is a bare URI (self-signed)",
44✔
1764
                             std::ranges::find(cdp_urls, url.original_input()) != cdp_urls.end());
44✔
1765
      }
1766
   }
11✔
1767

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

1770
   /* Create a CA-signed certificate */
1771
   const Botan::X509_Certificate ca_signed_cert =
11✔
1772
      ca.sign_request(user_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
11✔
1773

1774
   // check if known Key_Usage extension is present in CA-signed cert
1775
   result.test_is_true("Extensions::extension_set true for Key_Usage",
11✔
1776
                       ca_signed_cert.v3_extensions().extension_set(ku_oid));
11✔
1777

1778
   key_usage_ext = ca_signed_cert.v3_extensions().get(ku_oid);
22✔
1779
   if(result.test_is_true("Key_Usage extension present in CA-signed certificate", key_usage_ext != nullptr)) {
11✔
1780
      auto constraints = dynamic_cast<Botan::Cert_Extension::Key_Usage&>(*key_usage_ext).get_constraints();
11✔
1781
      result.test_is_true("Key_Usage extension value matches in user certificate",
22✔
1782
                          constraints == Botan::Key_Constraints::DigitalSignature);
11✔
1783
   }
1784

1785
   // check if custom extension is present in CA-signed cert
1786
   result.test_is_true("Extensions::extension_set true for String_Extension",
11✔
1787
                       ca_signed_cert.v3_extensions().extension_set(oid));
11✔
1788
   string_ext = ca_signed_cert.v3_extensions().get_raw<String_Extension>(oid);
22✔
1789
   if(result.test_is_true("Custom extension present in CA-signed certificate", string_ext != nullptr)) {
11✔
1790
      result.test_str_eq(
22✔
1791
         "Custom extension value matches in CA-signed certificate", string_ext->value(), "AAAAAAAAAAAAAABCDEF");
33✔
1792
   }
1793

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

1797
   if(result.test_is_true("CRL Distribution Points extension present in CA-signed certificate",
44✔
1798
                          !cert_cdps->crl_distribution_urls().empty())) {
11✔
1799
      for(const auto& cdp : cert_cdps->distribution_points()) {
33✔
1800
         result.test_is_true("CDP URI present in CA-signed certificate",
44✔
1801
                             std::ranges::find(cdp_urls, cdp.point().get_first_attribute("URI")) != cdp_urls.end());
66✔
1802
      }
1803

1804
      const auto& urls = cert_cdps->crl_distribution_urls();
11✔
1805
      result.test_sz_eq("crl_distribution_urls has one entry per URI (CA-signed)", urls.size(), cdp_urls.size());
11✔
1806
      for(const auto& url : urls) {
33✔
1807
         result.test_is_true("crl_distribution_urls entry is a bare URI (CA-signed)",
44✔
1808
                             std::ranges::find(cdp_urls, url) != cdp_urls.end());
44✔
1809
      }
1810

1811
      const auto& cert_dp = ca_signed_cert.crl_distribution_point_uris();
11✔
1812
      result.test_sz_eq("X509_Certificate::crl_distribution_points size (CA-signed)", cert_dp.size(), cdp_urls.size());
11✔
1813
      for(const auto& url : cert_dp) {
33✔
1814
         result.test_is_true("X509_Certificate::crl_distribution_points entry is a bare URI (CA-signed)",
44✔
1815
                             std::ranges::find(cdp_urls, url.original_input()) != cdp_urls.end());
44✔
1816
      }
1817
   }
11✔
1818

1819
   return result;
11✔
1820
}
55✔
1821

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

1825
   struct TestData {
12✔
1826
         const std::string issuer, subject, issuer_hash, subject_hash;
1827
   } const cases[]{{"",
12✔
1828
                    "",
1829
                    "E4F60D0AA6D7F3D3B6A6494B1C861B99F649C6F9EC51ABAF201B20F297327C95",
1830
                    "E4F60D0AA6D7F3D3B6A6494B1C861B99F649C6F9EC51ABAF201B20F297327C95"},
1831
                   {"a",
1832
                    "b",
1833
                    "BC2E013472F39AC579964880E422737C82BA812CB8BC2FD17E013060D71E6E19",
1834
                    "5E31CFAA3FAFB1A5BA296A0D2BAB9CA44D7936E9BF0BBC54637D0C53DBC4A432"},
1835
                   {"A",
1836
                    "B",
1837
                    "4B3206201C4BC9B6CD6C36532A97687DF9238155D99ADB60C66BF2B2220643D8",
1838
                    "FFF635A52A16618B4A0E9CD26B5E5A2FA573D343C051E6DE8B0811B1ACC89B86"},
1839
                   {
1840
                      "Test Issuer/US/Botan Project/Testing",
1841
                      "Test Subject/US/Botan Project/Testing",
1842
                      "ACB4F373004A56A983A23EB8F60FA4706312B5DB90FD978574FE7ACC84E093A5",
1843
                      "87039231C2205B74B6F1F3830A66272C0B41F71894B03AC3150221766D95267B",
1844
                   },
1845
                   {
1846
                      "Test Subject/US/Botan Project/Testing",
1847
                      "Test Issuer/US/Botan Project/Testing",
1848
                      "87039231C2205B74B6F1F3830A66272C0B41F71894B03AC3150221766D95267B",
1849
                      "ACB4F373004A56A983A23EB8F60FA4706312B5DB90FD978574FE7ACC84E093A5",
1850
                   }};
72✔
1851

1852
   for(const auto& a : cases) {
72✔
1853
      Botan::X509_Cert_Options opts{a.issuer};
60✔
1854
      opts.CA_key();
60✔
1855

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

1858
      result.test_str_eq(a.issuer, Botan::hex_encode(issuer_cert.raw_issuer_dn_sha256()), a.issuer_hash);
60✔
1859
      result.test_str_eq(a.issuer, Botan::hex_encode(issuer_cert.raw_subject_dn_sha256()), a.issuer_hash);
60✔
1860

1861
      const Botan::X509_CA ca(issuer_cert, key, hash_fn, rng);
60✔
1862
      const Botan::PKCS10_Request req =
60✔
1863
         Botan::X509::create_cert_req(Botan::X509_Cert_Options(a.subject), key, hash_fn, rng);
60✔
1864
      const Botan::X509_Certificate subject_cert =
60✔
1865
         ca.sign_request(req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
60✔
1866

1867
      result.test_str_eq(a.subject, Botan::hex_encode(subject_cert.raw_issuer_dn_sha256()), a.issuer_hash);
60✔
1868
      result.test_str_eq(a.subject, Botan::hex_encode(subject_cert.raw_subject_dn_sha256()), a.subject_hash);
60✔
1869
   }
60✔
1870
   return result;
12✔
1871
}
72✔
1872

1873
   #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
1874

1875
Test::Result test_x509_tn_auth_list_extension_decode() {
1✔
1876
   /* cert with TNAuthList extension data was generated by asn1parse cfg:
1877

1878
      asn1=SEQUENCE:tn_auth_list
1879

1880
      [tn_auth_list]
1881
      spc=EXP:0,IA5:1001
1882
      range=EXP:1,SEQUENCE:TelephoneNumberRange
1883
      one=EXP:2,IA5:333
1884

1885
      [TelephoneNumberRange]
1886
      start1=IA5:111
1887
      count1=INT:128
1888
      start2=IA5:222
1889
      count2=INT:256
1890
    */
1891
   const std::string filename("TNAuthList.pem");
1✔
1892
   Test::Result result("X509 TNAuthList decode");
1✔
1893
   result.start_timer();
1✔
1894

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

1897
   using Botan::Cert_Extension::TNAuthList;
1✔
1898

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

1901
   const auto& tn_entries = tn_auth_list->entries();
1✔
1902

1903
   result.test_not_null("cert has TNAuthList extension", tn_auth_list);
1✔
1904

1905
   result.test_throws("wrong telephone_number_range() accessor for spc",
1✔
1906
                      [&tn_entries] { tn_entries[0].telephone_number_range(); });
2✔
1907
   result.test_throws("wrong telephone_number() accessor for range",
1✔
1908
                      [&tn_entries] { tn_entries[1].telephone_number(); });
2✔
1909
   result.test_throws("wrong service_provider_code() accessor for one",
1✔
1910
                      [&tn_entries] { tn_entries[2].service_provider_code(); });
2✔
1911

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

1915
   result.test_is_true("range entry type", tn_entries[1].type() == TNAuthList::Entry::TelephoneNumberRange);
1✔
1916
   const auto& range = tn_entries[1].telephone_number_range();
1✔
1917
   result.test_sz_eq("range entries count", range.size(), 2);
1✔
1918
   result.test_str_eq("range entry 0 start data", range[0].start.value(), "111");
1✔
1919
   result.test_sz_eq("range entry 0 count data", range[0].count, 128);
1✔
1920
   result.test_str_eq("range entry 1 start data", range[1].start.value(), "222");
1✔
1921
   result.test_sz_eq("range entry 1 count data", range[1].count, 256);
1✔
1922

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

1926
   result.end_timer();
1✔
1927
   return result;
2✔
1928
}
1✔
1929

1930
   #endif
1931

1932
std::vector<std::string> get_sig_paddings(const std::string& sig_algo, const std::string& hash) {
12✔
1933
   if(sig_algo == "RSA") {
12✔
1934
      return {
1✔
1935
   #if defined(BOTAN_HAS_EMSA_PKCS1)
1936
         "PKCS1v15(" + hash + ")",
1✔
1937
   #endif
1938
   #if defined(BOTAN_HAS_EMSA_PSS)
1939
            "PSS(" + hash + ")",
1940
   #endif
1941
      };
3✔
1942
   } else if(sig_algo == "DSA" || sig_algo == "ECDSA" || sig_algo == "ECGDSA" || sig_algo == "ECKCDSA" ||
11✔
1943
             sig_algo == "GOST-34.10") {
7✔
1944
      return {hash};
10✔
1945
   } else if(sig_algo == "Ed25519" || sig_algo == "Ed448") {
6✔
1946
      return {"Pure"};
2✔
1947
   } else if(sig_algo == "Dilithium" || sig_algo == "ML-DSA") {
4✔
1948
      return {"Randomized"};
2✔
1949
   } else if(sig_algo == "HSS-LMS") {
2✔
1950
      return {""};
1✔
1951
   } else {
1952
      return {};
1✔
1953
   }
1954
}
6✔
1955

1956
class X509_Cert_Unit_Tests final : public Test {
1✔
1957
   public:
1958
      std::vector<Test::Result> run() override {
1✔
1959
         std::vector<Test::Result> results;
1✔
1960

1961
         auto& rng = this->rng();
1✔
1962

1963
         const std::string sig_algos[]{"RSA",
1✔
1964
                                       "DSA",
1965
                                       "ECDSA",
1966
                                       "ECGDSA",
1967
                                       "ECKCDSA",
1968
                                       "GOST-34.10",
1969
                                       "Ed25519",
1970
                                       "Ed448",
1971
                                       "Dilithium",
1972
                                       "ML-DSA",
1973
                                       "SLH-DSA",
1974
                                       "HSS-LMS"};
13✔
1975

1976
         for(const std::string& algo : sig_algos) {
13✔
1977
   #if !defined(BOTAN_HAS_EMSA_PKCS1)
1978
            if(algo == "RSA")
1979
               continue;
1980
   #endif
1981

1982
            std::string hash = "SHA-256";
12✔
1983

1984
            if(algo == "Ed25519") {
12✔
1985
               hash = "SHA-512";
1✔
1986
            }
1987
            if(algo == "Ed448") {
12✔
1988
               hash = "SHAKE-256(912)";
1✔
1989
            }
1990
            if(algo == "Dilithium" || algo == "ML-DSA") {
12✔
1991
               hash = "SHAKE-256(512)";
2✔
1992
            }
1993

1994
            auto key = make_a_private_key(algo, rng);
12✔
1995

1996
            if(key == nullptr) {
12✔
1997
               continue;
×
1998
            }
1999

2000
            results.push_back(test_hashes(*key, hash, rng));
24✔
2001
            results.push_back(test_valid_constraints(*key, algo));
24✔
2002

2003
            Test::Result usage_result("X509 Usage");
12✔
2004
            try {
12✔
2005
               usage_result.merge(test_usage(*key, algo, hash, rng));
12✔
2006
            } catch(std::exception& e) {
×
2007
               usage_result.test_failure("test_usage " + algo, e.what());
×
2008
            }
×
2009
            results.push_back(usage_result);
12✔
2010

2011
            for(const auto& padding_scheme : get_sig_paddings(algo, hash)) {
23✔
2012
               Test::Result cert_result("X509 Unit");
11✔
2013

2014
               try {
11✔
2015
                  cert_result.merge(test_x509_cert(*key, algo, padding_scheme, hash, rng));
11✔
2016
               } catch(std::exception& e) {
×
2017
                  cert_result.test_failure("test_x509_cert " + algo, e.what());
×
2018
               }
×
2019
               results.push_back(cert_result);
11✔
2020

2021
               Test::Result pkcs10_result("PKCS10 extensions");
11✔
2022
               try {
11✔
2023
                  pkcs10_result.merge(test_pkcs10_ext(*key, padding_scheme, hash, rng));
11✔
2024
               } catch(std::exception& e) {
×
2025
                  pkcs10_result.test_failure("test_pkcs10_ext " + algo, e.what());
×
2026
               }
×
2027
               results.push_back(pkcs10_result);
11✔
2028

2029
               Test::Result self_issued_result("X509 Self Issued");
11✔
2030
               try {
11✔
2031
                  self_issued_result.merge(test_self_issued(*key, algo, padding_scheme, hash, rng));
11✔
2032
               } catch(std::exception& e) {
×
2033
                  self_issued_result.test_failure("test_self_issued " + algo, e.what());
×
2034
               }
×
2035
               results.push_back(self_issued_result);
11✔
2036

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

2045
               Test::Result custom_dn_result("X509 Custom DN");
11✔
2046
               try {
11✔
2047
                  custom_dn_result.merge(test_custom_dn_attr(*key, algo, padding_scheme, hash, rng));
11✔
2048
               } catch(std::exception& e) {
×
2049
                  custom_dn_result.test_failure("test_custom_dn_attr " + algo, e.what());
×
2050
               }
×
2051
               results.push_back(custom_dn_result);
11✔
2052
            }
23✔
2053
         }
24✔
2054

2055
         /*
2056
         These are algos which cannot sign but can be included in certs
2057
         */
2058
         const std::vector<std::string> enc_algos = {
1✔
2059
            "DH", "ECDH", "ElGamal", "Kyber", "ML-KEM", "FrodoKEM", "ClassicMcEliece"};
1✔
2060

2061
         for(const std::string& algo : enc_algos) {
8✔
2062
            auto key = make_a_private_key(algo, rng);
7✔
2063

2064
            if(key) {
7✔
2065
               results.push_back(test_valid_constraints(*key, algo));
14✔
2066
            }
2067
         }
7✔
2068

2069
   #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM) && defined(BOTAN_HAS_EMSA_PKCS1) && defined(BOTAN_HAS_EMSA_PSSR) && \
2070
      defined(BOTAN_HAS_RSA)
2071
         Test::Result pad_config_result("X509 Padding Config");
1✔
2072
         try {
1✔
2073
            pad_config_result.merge(test_padding_config());
1✔
2074
         } catch(const std::exception& e) {
×
2075
            pad_config_result.test_failure("test_padding_config", e.what());
×
2076
         }
×
2077
         results.push_back(pad_config_result);
1✔
2078
   #endif
2079

2080
   #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
2081
         results.push_back(test_x509_utf8());
2✔
2082
         results.push_back(test_x509_any_key_extended_usage());
2✔
2083
         results.push_back(test_x509_bmpstring());
2✔
2084
         results.push_back(test_x509_teletex());
2✔
2085
         results.push_back(test_crl_dn_name());
2✔
2086
         results.push_back(test_rdn_multielement_set_name());
2✔
2087
         results.push_back(test_x509_decode_list());
2✔
2088
         results.push_back(test_rsa_oaep());
2✔
2089
         results.push_back(test_x509_authority_info_access_extension());
2✔
2090
         results.push_back(test_x509_ldap_empty_authority_uris());
2✔
2091
         results.push_back(test_crl_issuing_distribution_point_extension());
2✔
2092
         results.push_back(test_verify_gost2012_cert());
2✔
2093
         results.push_back(test_parse_rsa_pss_cert());
2✔
2094
         results.push_back(test_x509_tn_auth_list_extension_decode());
2✔
2095
   #endif
2096

2097
         results.push_back(test_x509_encode_authority_info_access_extension());
2✔
2098
         results.push_back(test_x509_extension());
2✔
2099
         results.push_back(test_x509_extension_decode_duplicate());
2✔
2100
         results.push_back(test_x509_wrong_context_certificate_extensions());
2✔
2101
         results.push_back(test_x509_wrong_context_crl_extensions());
2✔
2102
         results.push_back(test_x509_dates());
2✔
2103
         results.push_back(test_cert_status_strings());
2✔
2104
         results.push_back(test_x509_uninit());
2✔
2105
         results.push_back(test_crl_entry_negative_serial());
2✔
2106

2107
         return results;
1✔
2108
      }
13✔
2109
};
2110

2111
BOTAN_REGISTER_TEST("x509", "x509_unit", X509_Cert_Unit_Tests);
2112

2113
#endif
2114

2115
}  // namespace
2116

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