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

randombit / botan / 26323460081

23 May 2026 12:00AM UTC coverage: 89.383% (+0.03%) from 89.349%
26323460081

push

github

web-flow
Merge pull request #5621 from randombit/jack/cli-port-shift

Move the cli test TCP/UDP port range downward

109787 of 122827 relevant lines covered (89.38%)

11032030.9 hits per line

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

94.76
/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)
1✔
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_sz_eq("Decoded CRL number", crl_number->get_crl_number(), 42);
1✔
268
   }
269

270
   return result;
2✔
271
}
3✔
272

273
Test::Result test_x509_extension_decode_duplicate() {
1✔
274
   Test::Result result("X509 Extensions reject duplicate OID");
1✔
275

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

279
   std::vector<uint8_t> der;
1✔
280
   Botan::DER_Encoder enc(der);
1✔
281
   enc.start_sequence();
1✔
282
   for(size_t i = 0; i != 2; ++i) {
3✔
283
      enc.start_sequence().encode(oid_bc).encode(bc_bits, Botan::ASN1_Type::OctetString).end_cons();
2✔
284
   }
285
   enc.end_cons();
1✔
286

287
   Botan::Extensions extns;
1✔
288
   Botan::BER_Decoder dec(der);
1✔
289
   result.test_throws<Botan::Decoding_Error>("Duplicate extension OID is rejected at decode time",
1✔
290
                                             [&]() { extns.decode_from(dec); });
2✔
291

292
   return result;
1✔
293
}
2✔
294

295
Test::Result test_x509_dates() {
1✔
296
   Test::Result result("X509 Time");
1✔
297

298
   Botan::X509_Time time;
1✔
299
   result.test_is_true("unset time not set", !time.time_is_set());
1✔
300
   time = Botan::X509_Time("080201182200Z", Botan::ASN1_Type::UtcTime);
1✔
301
   result.test_is_true("time set after construction", time.time_is_set());
1✔
302
   result.test_str_eq("time readable_string", time.readable_string(), "2008/02/01 18:22:00 UTC");
1✔
303

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

307
   time = Botan::X509_Time("200305100350Z");
1✔
308
   result.test_str_eq(
1✔
309
      "UTC_OR_GENERALIZED_TIME from UTC_TIME readable_string", time.readable_string(), "2020/03/05 10:03:50 UTC");
1✔
310

311
   time = Botan::X509_Time("20200305100350Z");
1✔
312
   result.test_str_eq("UTC_OR_GENERALIZED_TIME from GENERALIZED_TIME readable_string",
1✔
313
                      time.readable_string(),
1✔
314
                      "2020/03/05 10:03:50 UTC");
315

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

319
   // Dates that are valid per X.500 but rejected as unsupported
320
   const std::string valid_but_unsup[]{
1✔
321
      "0802010000-0000",
322
      "0802011724+0000",
323
      "0406142334-0500",
324
      "9906142334+0500",
325
      "0006142334-0530",
326
      "0006142334+0530",
327

328
      "080201000000-0000",
329
      "080201172412+0000",
330
      "040614233433-0500",
331
      "990614233444+0500",
332
      "000614233455-0530",
333
      "000614233455+0530",
334
   };
13✔
335

336
   // valid length 13
337
   const std::string valid_utc[]{
1✔
338
      "080201000000Z",
339
      "080201172412Z",
340
      "040614233433Z",
341
      "990614233444Z",
342
      "000614233455Z",
343
   };
6✔
344

345
   const std::string invalid_utc[]{
1✔
346
      "",
347
      " ",
348
      "2008`02-01",
349
      "9999-02-01",
350
      "2000-02-01 17",
351
      "999921",
352

353
      // No seconds
354
      "0802010000Z",
355
      "0802011724Z",
356
      "0406142334Z",
357
      "9906142334Z",
358
      "0006142334Z",
359

360
      // valid length 13 -> range check
361
      "080201000061Z",  // seconds too big (61)
362
      "080201000060Z",  // seconds too big (60, leap seconds not covered by the standard)
363
      "0802010000-1Z",  // seconds too small (-1)
364
      "080201006000Z",  // minutes too big (60)
365
      "080201240000Z",  // hours too big (24:00)
366

367
      // valid length 13 -> invalid numbers
368
      "08020123112 Z",
369
      "08020123112!Z",
370
      "08020123112,Z",
371
      "08020123112\nZ",
372
      "080201232 33Z",
373
      "080201232!33Z",
374
      "080201232,33Z",
375
      "080201232\n33Z",
376
      "0802012 3344Z",
377
      "0802012!3344Z",
378
      "0802012,3344Z",
379
      "08022\n334455Z",
380
      "08022 334455Z",
381
      "08022!334455Z",
382
      "08022,334455Z",
383
      "08022\n334455Z",
384
      "082 33445511Z",
385
      "082!33445511Z",
386
      "082,33445511Z",
387
      "082\n33445511Z",
388
      "2 2211221122Z",
389
      "2!2211221122Z",
390
      "2,2211221122Z",
391
      "2\n2211221122Z",
392

393
      // wrong time zone
394
      "080201000000",
395
      "080201000000z",
396

397
      // Fractional seconds
398
      "170217180154.001Z",
399

400
      // Timezone offset
401
      "170217180154+0100",
402

403
      // Extra digits
404
      "17021718015400Z",
405

406
      // Non-digits
407
      "17021718015aZ",
408

409
      // Trailing garbage
410
      "170217180154Zlongtrailinggarbage",
411

412
      // Swapped type
413
      "20170217180154Z",
414
   };
49✔
415

416
   // valid length 15
417
   const std::string valid_generalized_time[]{
1✔
418
      "20000305100350Z",
419
   };
2✔
420

421
   const std::string invalid_generalized[]{
1✔
422
      // No trailing Z
423
      "20000305100350",
424

425
      // No seconds
426
      "200003051003Z",
427

428
      // Fractional seconds
429
      "20000305100350.001Z",
430

431
      // Timezone offset
432
      "20170217180154+0100",
433

434
      // Extra digits
435
      "2017021718015400Z",
436

437
      // Non-digits
438
      "2017021718015aZ",
439

440
      // Trailing garbage
441
      "20170217180154Zlongtrailinggarbage",
442

443
      // Swapped type
444
      "170217180154Z",
445
   };
9✔
446

447
   for(const auto& v : valid_but_unsup) {
13✔
448
      result.test_throws("valid but unsupported", [v]() { const Botan::X509_Time t(v, Botan::ASN1_Type::UtcTime); });
60✔
449
   }
450

451
   for(const auto& v : valid_utc) {
6✔
452
      const Botan::X509_Time t(v, Botan::ASN1_Type::UtcTime);
5✔
453
   }
5✔
454

455
   for(const auto& v : valid_generalized_time) {
2✔
456
      const Botan::X509_Time t(v, Botan::ASN1_Type::GeneralizedTime);
1✔
457
   }
1✔
458

459
   for(const auto& v : invalid_utc) {
49✔
460
      result.test_throws("invalid", [v]() { const Botan::X509_Time t(v, Botan::ASN1_Type::UtcTime); });
240✔
461
   }
462

463
   for(const auto& v : invalid_generalized) {
9✔
464
      result.test_throws("invalid", [v]() { const Botan::X509_Time t(v, Botan::ASN1_Type::GeneralizedTime); });
40✔
465
   }
466

467
   return result;
1✔
468
}
80✔
469

470
Test::Result test_x509_encode_authority_info_access_extension() {
1✔
471
   Test::Result result("X509 with encoded PKIX.AuthorityInformationAccess extension");
1✔
472

473
   #if defined(BOTAN_HAS_RSA) && defined(BOTAN_HAS_EMSA_PKCS1)
474
   auto rng = Test::new_rng(__func__);
1✔
475

476
   const std::string sig_algo{"RSA"};
1✔
477
   const std::string hash_fn{"SHA-256"};
1✔
478
   const std::string padding_method{"PKCS1v15(SHA-256)"};
1✔
479

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

487
   // OCSP
488
   const std::string_view ocsp_uri{"http://staging.ocsp.d-trust.net"};
1✔
489
   const auto ocsp_uri_parsed = Botan::URI::parse(ocsp_uri).value();
2✔
490

491
   // create a CA
492
   auto ca_key = make_a_private_key(sig_algo, *rng);
1✔
493
   result.require("CA key", ca_key != nullptr);
1✔
494
   const auto ca_cert = Botan::X509::create_self_signed_cert(ca_opts(), *ca_key, hash_fn, *rng);
1✔
495
   const Botan::X509_CA ca(ca_cert, *ca_key, hash_fn, padding_method, *rng);
1✔
496

497
   // create a certificate with only caIssuer information
498
   auto key = make_a_private_key(sig_algo, *rng);
1✔
499

500
   Botan::X509_Cert_Options opts1 = req_opts1(sig_algo);
1✔
501
   opts1.extensions.add(
2✔
502
      std::make_unique<Botan::Cert_Extension::Authority_Information_Access>(std::vector<Botan::URI>{}, ca_issuers));
2✔
503

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

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

508
   if(!result.test_sz_eq("number of ca_issuers URIs", cert.ca_issuer_uris().size(), 2)) {
1✔
509
      return result;
510
   }
511

512
   for(const auto& ca_issuer : cert.ca_issuer_uris()) {
3✔
513
      result.test_is_true("CA issuer URI present in certificate",
4✔
514
                          std::ranges::find(ca_issuers, ca_issuer) != ca_issuers.end());
4✔
515
   }
516

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

519
   // create a certificate with only OCSP URI information
520
   Botan::X509_Cert_Options opts2 = req_opts1(sig_algo);
1✔
521
   opts2.extensions.add(
2✔
522
      std::make_unique<Botan::Cert_Extension::Authority_Information_Access>(std::vector<Botan::URI>{ocsp_uri_parsed}));
3✔
523

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

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

528
   result.test_is_true("OCSP URI available", !cert.ocsp_responder_uris().empty());
1✔
529
   result.test_is_true("no CA Issuer URI available", cert.ca_issuer_uris().empty());
1✔
530
   result.test_str_eq("OCSP responder URI matches", cert.ocsp_responder_uris().at(0).original_input(), ocsp_uri);
1✔
531

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

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

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

541
   result.test_is_true("OCSP URI available", !cert.ocsp_responder_uris().empty());
1✔
542
   result.test_is_true("CA Issuer URI available", !cert.ca_issuer_uris().empty());
1✔
543

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

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

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

553
   const auto* aia_ext =
1✔
554
      cert.v3_extensions().get_extension_object_as<Botan::Cert_Extension::Authority_Information_Access>();
1✔
555
   result.test_is_true("AIA extension present", aia_ext != nullptr);
1✔
556

557
   const auto ocsp_responders = aia_ext->ocsp_responders();
1✔
558
   result.test_sz_eq("number of OCSP responder URIs", ocsp_responders.size(), 2);
1✔
559
   result.test_str_eq("First OCSP responder URI matches", ocsp_responders[0], "http://ocsp.example.com");
1✔
560
   result.test_str_eq("Second OCSP responder URI matches", ocsp_responders[1], "http://backup-ocsp.example.com");
1✔
561

562
   const auto& cert_ocsp_responders = cert.ocsp_responder_uris();
1✔
563
   result.test_sz_eq("Certificate: number of OCSP responder URIs", cert_ocsp_responders.size(), 2);
1✔
564
   result.test_str_eq("Certificate: First OCSP responder URI matches",
1✔
565
                      cert_ocsp_responders[0].original_input(),
1✔
566
                      "http://ocsp.example.com");
567
   result.test_str_eq("Certificate: Second OCSP responder URI matches",
1✔
568
                      cert_ocsp_responders[1].original_input(),
1✔
569
                      "http://backup-ocsp.example.com");
570
   #endif
571

572
   return result;
1✔
573
}
9✔
574

575
   #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
576

577
Test::Result test_crl_dn_name() {
1✔
578
   Test::Result result("CRL DN name");
1✔
579

580
      // See GH #1252
581

582
      #if defined(BOTAN_HAS_RSA) && defined(BOTAN_HAS_EMSA_PKCS1)
583
   auto rng = Test::new_rng(__func__);
1✔
584

585
   const Botan::OID dc_oid("0.9.2342.19200300.100.1.25");
1✔
586

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

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

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

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

597
   result.test_is_true("contains DC component", crl.issuer_dn().get_attributes().count(dc_oid) == 1);
2✔
598
      #endif
599

600
   return result;
2✔
601
}
3✔
602

603
Test::Result test_rdn_multielement_set_name() {
1✔
604
   Test::Result result("DN with multiple elements in RDN");
1✔
605

606
   // GH #2611
607

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

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

613
   return result;
1✔
614
}
1✔
615

616
Test::Result test_rsa_oaep() {
1✔
617
   Test::Result result("RSA OAEP decoding");
1✔
618

619
      #if defined(BOTAN_HAS_RSA)
620
   const Botan::X509_Certificate cert(Test::data_file("x509/misc/rsa_oaep.pem"));
2✔
621

622
   auto public_key = cert.subject_public_key();
1✔
623
   result.test_not_null("Decoding RSA-OAEP worked", public_key.get());
1✔
624
   const auto& pk_info = cert.subject_public_key_algo();
1✔
625

626
   result.test_str_eq("RSA-OAEP OID", pk_info.oid().to_string(), Botan::OID::from_string("RSA/OAEP").to_string());
1✔
627
      #endif
628

629
   return result;
2✔
630
}
1✔
631

632
Test::Result test_x509_decode_list() {
1✔
633
   Test::Result result("X509_Certificate list decode");
1✔
634

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

637
   Botan::BER_Decoder dec(input);
1✔
638
   std::vector<Botan::X509_Certificate> certs;
1✔
639
   dec.decode_list(certs);
1✔
640

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

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

646
   return result;
1✔
647
}
1✔
648

649
Test::Result test_x509_utf8() {
1✔
650
   Test::Result result("X509 with UTF-8 encoded fields");
1✔
651

652
   try {
1✔
653
      const Botan::X509_Certificate utf8_cert(Test::data_file("x509/misc/contains_utf8string.pem"));
2✔
654

655
      // UTF-8 encoded fields of test certificate (contains cyrillic letters)
656
      const std::string organization =
1✔
657
         "\xD0\x9C\xD0\xBE\xD1\x8F\x20\xD0\xBA\xD0\xBE\xD0"
658
         "\xBC\xD0\xBF\xD0\xB0\xD0\xBD\xD0\xB8\xD1\x8F";
1✔
659
      const std::string organization_unit =
1✔
660
         "\xD0\x9C\xD0\xBE\xD1\x91\x20\xD0\xBF\xD0\xBE\xD0\xB4\xD1\x80\xD0\xB0"
661
         "\xD0\xB7\xD0\xB4\xD0\xB5\xD0\xBB\xD0\xB5\xD0\xBD\xD0\xB8\xD0\xB5";
1✔
662
      const std::string common_name =
1✔
663
         "\xD0\x9E\xD0\xBF\xD0\xB8\xD1\x81\xD0\xB0\xD0\xBD\xD0\xB8"
664
         "\xD0\xB5\x20\xD1\x81\xD0\xB0\xD0\xB9\xD1\x82\xD0\xB0";
1✔
665
      const std::string location = "\xD0\x9C\xD0\xBE\xD1\x81\xD0\xBA\xD0\xB2\xD0\xB0";
1✔
666

667
      const Botan::X509_DN& issuer_dn = utf8_cert.issuer_dn();
1✔
668

669
      result.test_str_eq("O", issuer_dn.get_first_attribute("O"), organization);
1✔
670
      result.test_str_eq("OU", issuer_dn.get_first_attribute("OU"), organization_unit);
1✔
671
      result.test_str_eq("CN", issuer_dn.get_first_attribute("CN"), common_name);
1✔
672
      result.test_str_eq("L", issuer_dn.get_first_attribute("L"), location);
1✔
673
   } catch(const Botan::Decoding_Error& ex) {
1✔
674
      result.test_failure(ex.what());
×
675
   }
×
676

677
   return result;
1✔
678
}
×
679

680
Test::Result test_x509_any_key_extended_usage() {
1✔
681
   using Botan::Key_Constraints;
1✔
682
   using Botan::Usage_Type;
1✔
683

684
   Test::Result result("X509 with X509v3.AnyExtendedKeyUsage");
1✔
685
   try {
1✔
686
      const Botan::X509_Certificate any_eku_cert(Test::data_file("x509/misc/contains_any_extended_key_usage.pem"));
2✔
687

688
      result.test_is_true("is CA cert", any_eku_cert.is_CA_cert());
1✔
689
      result.test_is_true("DigitalSignature is allowed", any_eku_cert.allowed_usage(Key_Constraints::DigitalSignature));
1✔
690
      result.test_is_true("CrlSign is allowed", any_eku_cert.allowed_usage(Key_Constraints::CrlSign));
1✔
691
      result.test_is_false("OCSP responder is not allowed", any_eku_cert.allowed_usage(Usage_Type::OCSP_RESPONDER));
1✔
692
   } catch(const Botan::Decoding_Error& ex) {
1✔
693
      result.test_failure(ex.what());
×
694
   }
×
695
   return result;
1✔
696
}
×
697

698
Test::Result test_x509_bmpstring() {
1✔
699
   Test::Result result("X509 with UCS-2 (BMPString) encoded fields");
1✔
700

701
   try {
1✔
702
      const Botan::X509_Certificate ucs2_cert(Test::data_file("x509/misc/contains_bmpstring.pem"));
2✔
703

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

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

712
      const Botan::X509_DN& issuer_dn = ucs2_cert.issuer_dn();
1✔
713

714
      result.test_str_eq("O", issuer_dn.get_first_attribute("O"), organization);
1✔
715
      result.test_str_eq("CN", issuer_dn.get_first_attribute("CN"), common_name);
1✔
716
      result.test_str_eq("L", issuer_dn.get_first_attribute("L"), location);
1✔
717
   } catch(const Botan::Decoding_Error& ex) {
1✔
718
      result.test_failure(ex.what());
×
719
   }
×
720

721
   return result;
1✔
722
}
×
723

724
Test::Result test_x509_teletex() {
1✔
725
   Test::Result result("X509 with TeletexString encoded fields");
1✔
726

727
   try {
1✔
728
      const Botan::X509_Certificate teletex_cert(Test::data_file("x509/misc/teletex_dn.der"));
2✔
729

730
      const Botan::X509_DN& issuer_dn = teletex_cert.issuer_dn();
1✔
731

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

734
      result.test_str_eq("O", issuer_dn.get_first_attribute("O"), "neam CA");
1✔
735
      result.test_str_eq("CN", issuer_dn.get_first_attribute("CN"), common_name);
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_authority_info_access_extension() {
1✔
744
   Test::Result result("X509 with PKIX.AuthorityInformationAccess extension");
1✔
745

746
   // contains no AIA extension
747
   const Botan::X509_Certificate no_aia_cert(Test::data_file("x509/misc/contains_utf8string.pem"));
2✔
748

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

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

755
   const auto& ca_issuers = aia_cert.ca_issuer_uris();
1✔
756

757
   result.test_sz_eq("number of ca_issuers URLs", ca_issuers.size(), 1);
1✔
758
   if(result.tests_failed() > 0) {
1✔
759
      return result;
760
   }
761

762
   result.test_str_eq("CA issuer URL matches", ca_issuers[0].original_input(), "http://gp.symcb.com/gp.crt");
1✔
763
   result.test_sz_eq("one OCSP responder URI", aia_cert.ocsp_responder_uris().size(), 1);
1✔
764
   result.test_str_eq(
2✔
765
      "OCSP responder URL matches", aia_cert.ocsp_responder_uris().at(0).original_input(), "http://gp.symcd.com");
1✔
766

767
   // contains AIA extension with 2 CA issuer URL and 1 OCSP responder
768
   const Botan::X509_Certificate aia_cert_2ca(
1✔
769
      Test::data_file("x509/misc/contains_authority_info_access_with_two_ca_issuers.pem"));
2✔
770

771
   const auto& ca_issuers2 = aia_cert_2ca.ca_issuer_uris();
1✔
772

773
   result.test_sz_eq("number of ca_issuers URLs", ca_issuers2.size(), 2);
1✔
774
   if(result.tests_failed() > 0) {
1✔
775
      return result;
776
   }
777

778
   result.test_str_eq("CA issuer URL matches",
1✔
779
                      ca_issuers2[0].original_input(),
1✔
780
                      "http://www.d-trust.net/cgi-bin/Bdrive_Test_CA_1-2_2017.crt");
781
   result.test_str_eq(
1✔
782
      "CA issuer URL matches",
783
      ca_issuers2[1].original_input(),
1✔
784
      "ldap://directory.d-trust.net/CN=Bdrive%20Test%20CA%201-2%202017,O=Bundesdruckerei%20GmbH,C=DE?cACertificate?base?");
785
   result.test_sz_eq("one OCSP responder URI", aia_cert_2ca.ocsp_responder_uris().size(), 1);
1✔
786
   result.test_str_eq("OCSP responder URL matches",
2✔
787
                      aia_cert_2ca.ocsp_responder_uris().at(0).original_input(),
1✔
788
                      "http://staging.ocsp.d-trust.net");
789

790
   // contains AIA extension with multiple OCSP responders
791
   const Botan::X509_Certificate aia_cert_multi_ocsp(
1✔
792
      Test::data_file("x509/misc/contains_multiple_ocsp_responders.pem"));
2✔
793

794
   const auto& ocsp_responders_multi = aia_cert_multi_ocsp.ocsp_responder_uris();
1✔
795
   result.test_sz_eq("number of OCSP responders", ocsp_responders_multi.size(), 3);
1✔
796
   result.test_str_eq(
1✔
797
      "First OCSP responder URL matches", ocsp_responders_multi[0].original_input(), "http://ocsp1.example.com");
1✔
798
   result.test_str_eq(
1✔
799
      "Second OCSP responder URL matches", ocsp_responders_multi[1].original_input(), "http://ocsp2.example.com");
1✔
800
   result.test_str_eq(
1✔
801
      "Third OCSP responder URL matches", ocsp_responders_multi[2].original_input(), "http://ocsp3.example.com");
1✔
802
   result.test_is_true("no CA Issuer URI available", aia_cert_multi_ocsp.ca_issuer_uris().empty());
1✔
803

804
   return result;
1✔
805
}
1✔
806

807
Test::Result test_crl_issuing_distribution_point_extension() {
1✔
808
   Test::Result result("X509 CRL IssuingDistributionPoint extension");
1✔
809

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

813
   result.test_str_eq(
1✔
814
      "CRL IDP URI decoded correctly", crl.crl_issuing_distribution_point(), "http://localhost/subca/crldp/crl.crl");
1✔
815

816
   return result;
1✔
817
}
1✔
818

819
Test::Result test_parse_rsa_pss_cert() {
1✔
820
   Test::Result result("X509 RSA-PSS certificate");
1✔
821

822
   // See https://github.com/randombit/botan/issues/3019 for background
823

824
   try {
1✔
825
      const Botan::X509_Certificate rsa_pss(Test::data_file("x509/misc/rsa_pss.pem"));
2✔
826
      result.test_success("Was able to parse RSA-PSS certificate signed with ECDSA");
1✔
827
   } catch(Botan::Exception& e) {
1✔
828
      result.test_failure("Parsing failed", e.what());
×
829
   }
×
830

831
   return result;
1✔
832
}
×
833

834
Test::Result test_verify_gost2012_cert() {
1✔
835
   Test::Result result("X509 GOST-2012 certificates");
1✔
836

837
      #if defined(BOTAN_HAS_GOST_34_10_2012) && defined(BOTAN_HAS_STREEBOG)
838
   try {
1✔
839
      if(Botan::EC_Group::supports_named_group("gost_256A")) {
1✔
840
         const Botan::X509_Certificate root_cert(Test::data_file("x509/gost/gost_root.pem"));
2✔
841
         const Botan::X509_Certificate root_int(Test::data_file("x509/gost/gost_int.pem"));
2✔
842

843
         Botan::Certificate_Store_In_Memory trusted;
1✔
844
         trusted.add_certificate(root_cert);
1✔
845

846
         const Botan::Path_Validation_Restrictions restrictions(false, 128, false, {"Streebog-256"});
2✔
847
         const auto validation_time = Botan::calendar_point(2024, 1, 1, 0, 0, 0).to_std_timepoint();
1✔
848
         const Botan::Path_Validation_Result validation_result = Botan::x509_path_validate(
1✔
849
            root_int, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
1✔
850

851
         result.test_is_true("GOST certificate validates", validation_result.successful_validation());
1✔
852
      }
1✔
853
   } catch(const Botan::Decoding_Error& e) {
×
854
      result.test_failure(e.what());
×
855
   }
×
856
      #endif
857

858
   return result;
1✔
859
}
×
860

861
   /*
862
 * @brief checks the configurability of the RSA-PSS signature scheme
863
 *
864
 * For the other algorithms than RSA, only one padding is supported right now.
865
 */
866
      #if defined(BOTAN_HAS_EMSA_PKCS1) && defined(BOTAN_HAS_EMSA_PSSR) && defined(BOTAN_HAS_RSA)
867
Test::Result test_padding_config() {
1✔
868
   Test::Result test_result("X509 Padding Config");
1✔
869

870
   auto rng = Test::new_rng(__func__);
1✔
871

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

875
   // Create X509 CA certificate; PKCS1v15 is used for signing by default
876
   Botan::X509_Cert_Options opt("TEST CA");
1✔
877
   opt.CA_key();
1✔
878

879
   const Botan::X509_Certificate ca_cert_def = Botan::X509::create_self_signed_cert(opt, (*sk), "SHA-512", *rng);
1✔
880
   test_result.test_str_eq("CA certificate signature algorithm (default)",
2✔
881
                           ca_cert_def.signature_algorithm().oid().to_formatted_string(),
1✔
882
                           "RSA/PKCS1v15(SHA-512)");
883

884
   // Create X509 CA certificate; RSA-PSS is explicitly set
885
   opt.set_padding_scheme("PSSR");
1✔
886
   const Botan::X509_Certificate ca_cert_exp = Botan::X509::create_self_signed_cert(opt, (*sk), "SHA-512", *rng);
1✔
887
   test_result.test_str_eq("CA certificate signature algorithm (explicit)",
2✔
888
                           ca_cert_exp.signature_algorithm().oid().to_formatted_string(),
1✔
889
                           "RSA/PSS");
890

891
         #if defined(BOTAN_HAS_EMSA_X931)
892
   // Try to set a padding scheme that is not supported for signing with the given key type
893
   opt.set_padding_scheme("X9.31");
1✔
894
   try {
1✔
895
      const Botan::X509_Certificate ca_cert_wrong = Botan::X509::create_self_signed_cert(opt, (*sk), "SHA-512", *rng);
1✔
896
      test_result.test_failure("Could build CA cert with invalid encoding scheme X9.31 for key type " +
×
897
                               sk->algo_name());
×
898
   } catch(const Botan::Invalid_Argument& e) {
1✔
899
      test_result.test_str_eq("Build CA certificate with invalid encoding scheme X9.31 for key type " + sk->algo_name(),
3✔
900
                              e.what(),
1✔
901
                              "Signatures using RSA/X9.31(SHA-512) are not supported");
902
   }
1✔
903
         #endif
904

905
   test_result.test_str_eq("CA certificate signature algorithm (explicit)",
2✔
906
                           ca_cert_exp.signature_algorithm().oid().to_formatted_string(),
1✔
907
                           "RSA/PSS");
908

909
   const Botan::X509_Time not_before = from_date(-1, 1, 1);
1✔
910
   const Botan::X509_Time not_after = from_date(2, 1, 2);
1✔
911

912
   // Prepare a signing request for the end certificate
913
   Botan::X509_Cert_Options req_opt("endpoint");
1✔
914
   req_opt.set_padding_scheme("PSS(SHA-512,MGF1,64)");
1✔
915
   const Botan::PKCS10_Request end_req = Botan::X509::create_cert_req(req_opt, (*sk), "SHA-512", *rng);
1✔
916
   test_result.test_str_eq(
2✔
917
      "Certificate request signature algorithm", end_req.signature_algorithm().oid().to_formatted_string(), "RSA/PSS");
1✔
918

919
   // Create X509 CA object: will fail as the chosen hash functions differ
920
   try {
1✔
921
      const Botan::X509_CA ca_fail(ca_cert_exp, (*sk), "SHA-512", "PSS(SHA-256)", *rng);
1✔
922
      test_result.test_failure("Configured conflicting hash functions for CA");
×
923
   } catch(const Botan::Invalid_Argument& e) {
1✔
924
      test_result.test_str_eq(
1✔
925
         "Configured conflicting hash functions for CA",
926
         e.what(),
1✔
927
         "Specified hash function SHA-512 is incompatible with RSA chose hash function SHA-256 with user specified padding PSS(SHA-256)");
928
   }
1✔
929

930
   // Create X509 CA object: its signer will use the padding scheme from the CA certificate, i.e. PKCS1v15
931
   const Botan::X509_CA ca_def(ca_cert_def, (*sk), "SHA-512", *rng);
1✔
932
   const Botan::X509_Certificate end_cert_pkcs1 = ca_def.sign_request(end_req, *rng, not_before, not_after);
1✔
933
   test_result.test_str_eq("End certificate signature algorithm",
2✔
934
                           end_cert_pkcs1.signature_algorithm().oid().to_formatted_string(),
1✔
935
                           "RSA/PKCS1v15(SHA-512)");
936

937
   // Create X509 CA object: its signer will use the explicitly configured padding scheme, which is different from the CA certificate's scheme
938
   const Botan::X509_CA ca_diff(ca_cert_def, (*sk), "SHA-512", "PSS", *rng);
1✔
939
   const Botan::X509_Certificate end_cert_diff_pss = ca_diff.sign_request(end_req, *rng, not_before, not_after);
1✔
940
   test_result.test_str_eq("End certificate signature algorithm",
2✔
941
                           end_cert_diff_pss.signature_algorithm().oid().to_formatted_string(),
1✔
942
                           "RSA/PSS");
943

944
   // Create X509 CA object: its signer will use the explicitly configured padding scheme, which is identical to the CA certificate's scheme
945
   const Botan::X509_CA ca_exp(ca_cert_exp, (*sk), "SHA-512", "PSS(SHA-512,MGF1,64)", *rng);
1✔
946
   const Botan::X509_Certificate end_cert_pss = ca_exp.sign_request(end_req, *rng, not_before, not_after);
1✔
947
   test_result.test_str_eq(
2✔
948
      "End certificate signature algorithm", end_cert_pss.signature_algorithm().oid().to_formatted_string(), "RSA/PSS");
1✔
949

950
   // Check CRL signature algorithm
951
   const Botan::X509_CRL crl = ca_exp.new_crl(*rng);
1✔
952
   test_result.test_str_eq("CRL signature algorithm", crl.signature_algorithm().oid().to_formatted_string(), "RSA/PSS");
1✔
953

954
   // sanity check for verification, the heavy lifting is done in the other unit tests
955
   const Botan::Certificate_Store_In_Memory trusted(ca_exp.ca_certificate());
1✔
956
   const Botan::Path_Validation_Restrictions restrictions(false, 80);
2✔
957
   const Botan::Path_Validation_Result validation_result =
1✔
958
      Botan::x509_path_validate(end_cert_pss, restrictions, trusted);
1✔
959
   test_result.test_is_true("PSS signed certificate validates", validation_result.successful_validation());
1✔
960

961
   return test_result;
2✔
962
}
3✔
963
      #endif
964

965
   #endif
966

967
Test::Result test_pkcs10_ext(const Botan::Private_Key& key,
11✔
968
                             const std::string& sig_padding,
969
                             const std::string& hash_fn,
970
                             Botan::RandomNumberGenerator& rng) {
971
   Test::Result result("PKCS10 extensions");
11✔
972

973
   Botan::X509_Cert_Options opts;
11✔
974

975
   opts.dns = "main.example.org";
11✔
976
   opts.more_dns.push_back("more1.example.org");
22✔
977
   opts.more_dns.push_back("more2.example.org");
22✔
978

979
   opts.padding_scheme = sig_padding;
11✔
980

981
   Botan::AlternativeName alt_name;
11✔
982
   alt_name.add_attribute("DNS", "bonus.example.org");
11✔
983

984
   Botan::X509_DN alt_dn;
11✔
985
   alt_dn.add_attribute("X520.CommonName", "alt_cn");
11✔
986
   alt_dn.add_attribute("X520.Organization", "testing");
11✔
987
   alt_name.add_dn(alt_dn);
11✔
988

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

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

993
   const auto alt_dns_names = req.subject_alt_name().get_attribute("DNS");
11✔
994

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

997
   if(alt_dns_names.size() == 4) {
11✔
998
      result.test_str_eq("Expected DNS name 1", alt_dns_names.at(0), "bonus.example.org");
11✔
999
      result.test_str_eq("Expected DNS name 2", alt_dns_names.at(1), "main.example.org");
11✔
1000
      result.test_str_eq("Expected DNS name 3", alt_dns_names.at(2), "more1.example.org");
11✔
1001
      result.test_str_eq("Expected DNS name 3", alt_dns_names.at(3), "more2.example.org");
11✔
1002
   }
1003

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

1007
   return result;
11✔
1008
}
22✔
1009

1010
Test::Result test_x509_cert(const Botan::Private_Key& ca_key,
11✔
1011
                            const std::string& sig_algo,
1012
                            const std::string& sig_padding,
1013
                            const std::string& hash_fn,
1014
                            Botan::RandomNumberGenerator& rng) {
1015
   Test::Result result("X509 Unit");
11✔
1016

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

1020
   {
11✔
1021
      result.test_is_true("ca key usage cert", ca_cert.constraints().includes(Botan::Key_Constraints::KeyCertSign));
11✔
1022
      result.test_is_true("ca key usage crl", ca_cert.constraints().includes(Botan::Key_Constraints::CrlSign));
11✔
1023
   }
1024

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

1028
   const Botan::PKCS10_Request user1_req =
11✔
1029
      Botan::X509::create_cert_req(req_opts1(sig_algo, sig_padding), *user1_key, hash_fn, rng);
11✔
1030

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

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

1036
   const Botan::PKCS10_Request user2_req =
11✔
1037
      Botan::X509::create_cert_req(req_opts2(sig_padding), *user2_key, hash_fn, rng);
11✔
1038

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

1042
   const Botan::PKCS10_Request user3_req =
11✔
1043
      Botan::X509::create_cert_req(req_opts3(sig_padding), *user3_key, hash_fn, rng);
11✔
1044

1045
   /* Create the CA object */
1046
   const Botan::X509_CA ca(ca_cert, ca_key, hash_fn, sig_padding, rng);
11✔
1047

1048
   const BigInt user1_serial(99);
11✔
1049

1050
   /* Sign the requests to create the certs */
1051
   const Botan::X509_Certificate user1_cert =
11✔
1052
      ca.sign_request(user1_req, rng, user1_serial, from_date(-1, 01, 01), from_date(2, 01, 01));
11✔
1053

1054
   result.test_sz_eq("User1 serial size matches expected", user1_cert.serial_number().size(), 1);
11✔
1055
   result.test_sz_eq("User1 serial matches expected", user1_cert.serial_number().at(0), size_t(99));
11✔
1056

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

1061
   const Botan::X509_Certificate user3_cert =
11✔
1062
      ca.sign_request(user3_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
11✔
1063

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

1068
   {
11✔
1069
      auto constraints = req_opts1(sig_algo).constraints;
11✔
1070
      result.test_is_true("user1 key usage", user1_cert.constraints().includes(constraints));
11✔
1071
   }
1072

1073
   /* Copy, assign and compare */
1074
   Botan::X509_Certificate user1_cert_copy(user1_cert);
11✔
1075
   result.test_is_true("certificate copy", user1_cert == user1_cert_copy);
11✔
1076

1077
   user1_cert_copy = user2_cert;
11✔
1078
   result.test_is_true("certificate assignment", user2_cert == user1_cert_copy);
11✔
1079

1080
   const Botan::X509_Certificate user1_cert_differ =
11✔
1081
      ca.sign_request(user1_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
11✔
1082

1083
   result.test_is_false("certificate differs", user1_cert == user1_cert_differ);
11✔
1084

1085
   /* Get cert data */
1086
   result.test_sz_eq("x509 version", user1_cert.x509_version(), size_t(3));
11✔
1087

1088
   const Botan::X509_DN& user1_issuer_dn = user1_cert.issuer_dn();
11✔
1089
   result.test_str_eq("issuer info CN", user1_issuer_dn.get_first_attribute("CN"), ca_opts().common_name);
11✔
1090
   result.test_str_eq("issuer info Country", user1_issuer_dn.get_first_attribute("C"), ca_opts().country);
11✔
1091
   result.test_str_eq("issuer info Orga", user1_issuer_dn.get_first_attribute("O"), ca_opts().organization);
11✔
1092
   result.test_str_eq("issuer info OrgaUnit", user1_issuer_dn.get_first_attribute("OU"), ca_opts().org_unit);
11✔
1093

1094
   const Botan::X509_DN& user3_subject_dn = user3_cert.subject_dn();
11✔
1095
   result.test_sz_eq("subject OrgaUnit count",
11✔
1096
                     user3_subject_dn.get_attribute("OU").size(),
22✔
1097
                     req_opts3(sig_algo).more_org_units.size() + 1);
22✔
1098
   result.test_str_eq(
22✔
1099
      "subject OrgaUnit #2", user3_subject_dn.get_attribute("OU").at(1), req_opts3(sig_algo).more_org_units.at(0));
33✔
1100

1101
   const Botan::AlternativeName& user1_altname = user1_cert.subject_alt_name();
11✔
1102
   result.test_str_eq("subject alt email", user1_altname.get_first_attribute("RFC822"), "testing@randombit.net");
11✔
1103
   result.test_str_eq("subject alt dns", user1_altname.get_first_attribute("DNS"), "botan.randombit.net");
11✔
1104
   result.test_str_eq("subject alt uri", user1_altname.get_first_attribute("URI"), "https://botan.randombit.net");
11✔
1105

1106
   const Botan::AlternativeName& user3_altname = user3_cert.subject_alt_name();
11✔
1107
   result.test_sz_eq(
11✔
1108
      "subject alt dns count", user3_altname.get_attribute("DNS").size(), req_opts3(sig_algo).more_dns.size() + 1);
22✔
1109
   result.test_str_eq(
22✔
1110
      "subject alt dns #2", user3_altname.get_attribute("DNS").at(1), req_opts3(sig_algo).more_dns.at(0));
33✔
1111

1112
   const Botan::X509_CRL crl1 = ca.new_crl(rng);
11✔
1113

1114
   /* Verify the certs */
1115
   const Botan::Path_Validation_Restrictions restrictions(false, 80);
22✔
1116
   Botan::Certificate_Store_In_Memory store;
11✔
1117

1118
   // First try with an empty store
1119
   const Botan::Path_Validation_Result result_no_issuer = Botan::x509_path_validate(user1_cert, restrictions, store);
11✔
1120
   result.test_str_eq(
11✔
1121
      "user 1 issuer not found",
1122
      result_no_issuer.result_string(),
11✔
1123
      Botan::Path_Validation_Result::status_string(Botan::Certificate_Status_Code::CERT_ISSUER_NOT_FOUND));
1124

1125
   store.add_certificate(ca.ca_certificate());
11✔
1126

1127
   Botan::Path_Validation_Result result_u1 = Botan::x509_path_validate(user1_cert, restrictions, store);
11✔
1128
   if(!result.test_is_true("user 1 validates", result_u1.successful_validation())) {
11✔
1129
      result.test_note("user 1 validation result", result_u1.result_string());
×
1130
   }
1131

1132
   Botan::Path_Validation_Result result_u2 = Botan::x509_path_validate(user2_cert, restrictions, store);
11✔
1133
   if(!result.test_is_true("user 2 validates", result_u2.successful_validation())) {
11✔
1134
      result.test_note("user 2 validation result", result_u2.result_string());
×
1135
   }
1136

1137
   const Botan::Path_Validation_Result result_self_signed =
11✔
1138
      Botan::x509_path_validate(user1_ss_cert, restrictions, store);
11✔
1139
   result.test_str_eq(
11✔
1140
      "user 1 issuer not found",
1141
      result_no_issuer.result_string(),
11✔
1142
      Botan::Path_Validation_Result::status_string(Botan::Certificate_Status_Code::CERT_ISSUER_NOT_FOUND));
1143
   store.add_crl(crl1);
11✔
1144

1145
   std::vector<Botan::CRL_Entry> revoked;
11✔
1146
   revoked.push_back(Botan::CRL_Entry(user1_cert, Botan::CRL_Code::CessationOfOperation));
22✔
1147
   revoked.push_back(Botan::CRL_Entry(user2_cert));
22✔
1148

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

1151
   store.add_crl(crl2);
11✔
1152

1153
   const std::string revoked_str =
11✔
1154
      Botan::Path_Validation_Result::status_string(Botan::Certificate_Status_Code::CERT_IS_REVOKED);
11✔
1155

1156
   result_u1 = Botan::x509_path_validate(user1_cert, restrictions, store);
11✔
1157
   result.test_str_eq("user 1 revoked", result_u1.result_string(), revoked_str);
11✔
1158

1159
   result_u2 = Botan::x509_path_validate(user2_cert, restrictions, store);
11✔
1160
   result.test_str_eq("user 1 revoked", result_u2.result_string(), revoked_str);
11✔
1161

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

1170
   store.add_crl(crl3);
11✔
1171

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

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

1178
   return result;
11✔
1179
}
44✔
1180

1181
Test::Result test_crl_entry_negative_serial() {
1✔
1182
   Test::Result result("CRL entry with negative serial matches certificate serial");
1✔
1183

1184
   const auto build_crl_entry = [](const std::vector<uint8_t>& serial_bytes) {
4✔
1185
      std::vector<uint8_t> der;
3✔
1186
      Botan::DER_Encoder enc(der);
3✔
1187
      enc.start_sequence()
3✔
1188
         .add_object(Botan::ASN1_Type::Integer, Botan::ASN1_Class::Universal, serial_bytes.data(), serial_bytes.size())
3✔
1189
         .encode(Botan::X509_Time(std::chrono::system_clock::now()))
3✔
1190
         .end_cons();
3✔
1191

1192
      Botan::CRL_Entry entry;
3✔
1193
      Botan::BER_Decoder dec(der);
3✔
1194
      entry.decode_from(dec);
3✔
1195
      return entry;
3✔
1196
   };
3✔
1197

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

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

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

1210
   return result;
1✔
1211
}
1✔
1212

1213
Test::Result test_usage(const Botan::Private_Key& ca_key,
12✔
1214
                        const std::string& sig_algo,
1215
                        const std::string& hash_fn,
1216
                        Botan::RandomNumberGenerator& rng) {
1217
   using Botan::Key_Constraints;
12✔
1218
   using Botan::Usage_Type;
12✔
1219

1220
   Test::Result result("X509 Usage");
12✔
1221

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

1225
   /* Create the CA object */
1226
   const Botan::X509_CA ca(ca_cert, ca_key, hash_fn, rng);
12✔
1227

1228
   auto user1_key = make_a_private_key(sig_algo, rng);
12✔
1229

1230
   Botan::X509_Cert_Options opts("Test User 1/US/Botan Project/Testing");
12✔
1231
   opts.constraints = Key_Constraints::DigitalSignature;
12✔
1232

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

1235
   const Botan::X509_Certificate user1_cert =
12✔
1236
      ca.sign_request(user1_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
12✔
1237

1238
   // cert only allows digitalSignature, but we check for both digitalSignature and cRLSign
1239
   result.test_is_false(
12✔
1240
      "key usage cRLSign not allowed",
1241
      user1_cert.allowed_usage(Key_Constraints(Key_Constraints::DigitalSignature | Key_Constraints::CrlSign)));
12✔
1242
   result.test_is_false("encryption is not allowed", user1_cert.allowed_usage(Usage_Type::ENCRYPTION));
12✔
1243

1244
   // cert only allows digitalSignature, so checking for only that should be ok
1245
   result.test_is_true("key usage digitalSignature allowed",
12✔
1246
                       user1_cert.allowed_usage(Key_Constraints::DigitalSignature));
12✔
1247

1248
   opts.constraints = Key_Constraints(Key_Constraints::DigitalSignature | Key_Constraints::CrlSign);
12✔
1249

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

1252
   const Botan::X509_Certificate mult_usage_cert =
12✔
1253
      ca.sign_request(mult_usage_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
12✔
1254

1255
   // cert allows multiple usages, so each one of them as well as both together should be allowed
1256
   result.test_is_true("key usage multiple digitalSignature allowed",
12✔
1257
                       mult_usage_cert.allowed_usage(Key_Constraints::DigitalSignature));
12✔
1258
   result.test_is_true("key usage multiple cRLSign allowed", mult_usage_cert.allowed_usage(Key_Constraints::CrlSign));
12✔
1259
   result.test_is_true(
12✔
1260
      "key usage multiple digitalSignature and cRLSign allowed",
1261
      mult_usage_cert.allowed_usage(Key_Constraints(Key_Constraints::DigitalSignature | Key_Constraints::CrlSign)));
12✔
1262
   result.test_is_false("encryption is not allowed", mult_usage_cert.allowed_usage(Usage_Type::ENCRYPTION));
12✔
1263

1264
   opts.constraints = Key_Constraints();
12✔
1265

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

1268
   const Botan::X509_Certificate no_usage_cert =
12✔
1269
      ca.sign_request(no_usage_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
12✔
1270

1271
   // cert allows every usage
1272
   result.test_is_true("key usage digitalSignature allowed",
12✔
1273
                       no_usage_cert.allowed_usage(Key_Constraints::DigitalSignature));
12✔
1274
   result.test_is_true("key usage cRLSign allowed", no_usage_cert.allowed_usage(Key_Constraints::CrlSign));
12✔
1275
   result.test_is_true("key usage encryption allowed", no_usage_cert.allowed_usage(Usage_Type::ENCRYPTION));
12✔
1276

1277
   if(sig_algo == "RSA") {
12✔
1278
      // cert allows data encryption
1279
      opts.constraints = Key_Constraints(Key_Constraints::KeyEncipherment | Key_Constraints::DataEncipherment);
1✔
1280

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

1283
      const Botan::X509_Certificate enc_cert =
1✔
1284
         ca.sign_request(enc_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
1✔
1285

1286
      result.test_is_true("cert allows encryption", enc_cert.allowed_usage(Usage_Type::ENCRYPTION));
1✔
1287
      result.test_is_true("cert does not allow TLS client auth", !enc_cert.allowed_usage(Usage_Type::TLS_CLIENT_AUTH));
1✔
1288
   }
1✔
1289

1290
   return result;
12✔
1291
}
24✔
1292

1293
Test::Result test_self_issued(const Botan::Private_Key& ca_key,
11✔
1294
                              const std::string& sig_algo,
1295
                              const std::string& sig_padding,
1296
                              const std::string& hash_fn,
1297
                              Botan::RandomNumberGenerator& rng) {
1298
   using Botan::Key_Constraints;
11✔
1299

1300
   Test::Result result("X509 Self Issued");
11✔
1301

1302
   // create the self-signed cert
1303
   const Botan::X509_Certificate ca_cert =
11✔
1304
      Botan::X509::create_self_signed_cert(ca_opts(sig_padding), ca_key, hash_fn, rng);
11✔
1305

1306
   /* Create the CA object */
1307
   const Botan::X509_CA ca(ca_cert, ca_key, hash_fn, sig_padding, rng);
11✔
1308

1309
   auto user_key = make_a_private_key(sig_algo, rng);
11✔
1310

1311
   // create a self-issued certificate, that is, a certificate with subject dn == issuer dn,
1312
   // but signed by a CA, not signed by it's own private key
1313
   Botan::X509_Cert_Options opts = ca_opts();
11✔
1314
   opts.constraints = Key_Constraints::DigitalSignature;
11✔
1315
   opts.set_padding_scheme(sig_padding);
11✔
1316

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

1319
   const Botan::X509_Certificate self_issued_cert =
11✔
1320
      ca.sign_request(self_issued_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
11✔
1321

1322
   // check that this chain can can be verified successfully
1323
   const Botan::Certificate_Store_In_Memory trusted(ca.ca_certificate());
11✔
1324

1325
   const Botan::Path_Validation_Restrictions restrictions(false, 80);
22✔
1326

1327
   const Botan::Path_Validation_Result validation_result =
11✔
1328
      Botan::x509_path_validate(self_issued_cert, restrictions, trusted);
11✔
1329

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

1332
   return result;
11✔
1333
}
22✔
1334

1335
Test::Result test_x509_uninit() {
1✔
1336
   Test::Result result("X509 object uninitialized access");
1✔
1337

1338
   Botan::X509_Certificate cert;
1✔
1339
   result.test_throws("uninitialized cert access causes exception", "X509_Certificate uninitialized", [&cert]() {
1✔
1340
      cert.x509_version();
1✔
1341
   });
1342

1343
   Botan::X509_CRL crl;
1✔
1344
   result.test_throws(
1✔
1345
      "uninitialized crl access causes exception", "X509_CRL uninitialized", [&crl]() { crl.crl_number(); });
2✔
1346

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

1353
   result.test_throws("synth crl signature() throws", "X509_Object uninitialized", [&]() { synth_crl.signature(); });
2✔
1354
   result.test_throws(
1✔
1355
      "synth crl signed_body() throws", "X509_Object uninitialized", [&]() { synth_crl.signed_body(); });
2✔
1356
   result.test_throws("synth crl signature_algorithm() throws", "X509_Object uninitialized", [&]() {
1✔
1357
      synth_crl.signature_algorithm();
1✔
1358
   });
1359
   result.test_throws("synth crl tbs_data() throws", "X509_Object uninitialized", [&]() { synth_crl.tbs_data(); });
2✔
1360
   result.test_throws("synth crl BER_encode() throws", "X509_Object uninitialized", [&]() { synth_crl.BER_encode(); });
2✔
1361

1362
   return result;
1✔
1363
}
2✔
1364

1365
Test::Result test_valid_constraints(const Botan::Private_Key& key, const std::string& pk_algo) {
19✔
1366
   using Botan::Key_Constraints;
19✔
1367

1368
   Test::Result result("X509 Valid Constraints " + pk_algo);
19✔
1369

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

1372
   // Now check some typical usage scenarios for the given key type
1373
   // Taken from RFC 5280, sec. 4.2.1.3
1374
   // ALL constraints are not typical at all, but we use them for a negative test
1375
   const auto all = Key_Constraints(
19✔
1376
      Key_Constraints::DigitalSignature | Key_Constraints::NonRepudiation | Key_Constraints::KeyEncipherment |
1377
      Key_Constraints::DataEncipherment | Key_Constraints::KeyAgreement | Key_Constraints::KeyCertSign |
1378
      Key_Constraints::CrlSign | Key_Constraints::EncipherOnly | Key_Constraints::DecipherOnly);
19✔
1379

1380
   const auto ca = Key_Constraints(Key_Constraints::KeyCertSign);
19✔
1381
   const auto sign_data = Key_Constraints(Key_Constraints::DigitalSignature);
19✔
1382
   const auto non_repudiation = Key_Constraints(Key_Constraints::NonRepudiation | Key_Constraints::DigitalSignature);
19✔
1383
   const auto key_encipherment = Key_Constraints(Key_Constraints::KeyEncipherment);
19✔
1384
   const auto data_encipherment = Key_Constraints(Key_Constraints::DataEncipherment);
19✔
1385
   const auto key_agreement = Key_Constraints(Key_Constraints::KeyAgreement);
19✔
1386
   const auto key_agreement_encipher_only =
19✔
1387
      Key_Constraints(Key_Constraints::KeyAgreement | Key_Constraints::EncipherOnly);
19✔
1388
   const auto key_agreement_decipher_only =
19✔
1389
      Key_Constraints(Key_Constraints::KeyAgreement | Key_Constraints::DecipherOnly);
19✔
1390
   const auto crl_sign = Key_Constraints(Key_Constraints::CrlSign);
19✔
1391
   const auto sign_everything =
19✔
1392
      Key_Constraints(Key_Constraints::DigitalSignature | Key_Constraints::KeyCertSign | Key_Constraints::CrlSign);
19✔
1393

1394
   if(pk_algo == "DH" || pk_algo == "ECDH") {
19✔
1395
      // DH and ECDH only for key agreement
1396
      result.test_is_false("all constraints not permitted", all.compatible_with(key));
2✔
1397
      result.test_is_false("cert sign not permitted", ca.compatible_with(key));
2✔
1398
      result.test_is_false("signature not permitted", sign_data.compatible_with(key));
2✔
1399
      result.test_is_false("non repudiation not permitted", non_repudiation.compatible_with(key));
2✔
1400
      result.test_is_false("key encipherment not permitted", key_encipherment.compatible_with(key));
2✔
1401
      result.test_is_false("data encipherment not permitted", data_encipherment.compatible_with(key));
2✔
1402
      result.test_is_true("usage acceptable", key_agreement.compatible_with(key));
2✔
1403
      result.test_is_true("usage acceptable", key_agreement_encipher_only.compatible_with(key));
2✔
1404
      result.test_is_true("usage acceptable", key_agreement_decipher_only.compatible_with(key));
2✔
1405
      result.test_is_false("crl sign not permitted", crl_sign.compatible_with(key));
2✔
1406
      result.test_is_false("sign", sign_everything.compatible_with(key));
2✔
1407
   } else if(pk_algo == "Kyber" || pk_algo == "FrodoKEM" || pk_algo == "ML-KEM" || pk_algo == "ClassicMcEliece") {
17✔
1408
      // KEMs can encrypt and agree
1409
      result.test_is_false("all constraints not permitted", all.compatible_with(key));
4✔
1410
      result.test_is_false("cert sign not permitted", ca.compatible_with(key));
4✔
1411
      result.test_is_false("signature not permitted", sign_data.compatible_with(key));
4✔
1412
      result.test_is_false("non repudiation not permitted", non_repudiation.compatible_with(key));
4✔
1413
      result.test_is_false("crl sign not permitted", crl_sign.compatible_with(key));
4✔
1414
      result.test_is_false("sign", sign_everything.compatible_with(key));
4✔
1415
      result.test_is_false("key agreement not permitted", key_agreement.compatible_with(key));
4✔
1416
      result.test_is_false("usage acceptable", data_encipherment.compatible_with(key));
4✔
1417
      result.test_is_true("usage acceptable", key_encipherment.compatible_with(key));
4✔
1418
   } else if(pk_algo == "RSA") {
13✔
1419
      // RSA can do everything except key agreement
1420
      result.test_is_false("all constraints not permitted", all.compatible_with(key));
1✔
1421

1422
      result.test_is_true("usage acceptable", ca.compatible_with(key));
1✔
1423
      result.test_is_true("usage acceptable", sign_data.compatible_with(key));
1✔
1424
      result.test_is_true("usage acceptable", non_repudiation.compatible_with(key));
1✔
1425
      result.test_is_true("usage acceptable", key_encipherment.compatible_with(key));
1✔
1426
      result.test_is_true("usage acceptable", data_encipherment.compatible_with(key));
1✔
1427
      result.test_is_false("key agreement not permitted", key_agreement.compatible_with(key));
1✔
1428
      result.test_is_false("key agreement", key_agreement_encipher_only.compatible_with(key));
1✔
1429
      result.test_is_false("key agreement", key_agreement_decipher_only.compatible_with(key));
1✔
1430
      result.test_is_true("usage acceptable", crl_sign.compatible_with(key));
1✔
1431
      result.test_is_true("usage acceptable", sign_everything.compatible_with(key));
1✔
1432
   } else if(pk_algo == "ElGamal") {
12✔
1433
      // only ElGamal encryption is currently implemented
1434
      result.test_is_false("all constraints not permitted", all.compatible_with(key));
1✔
1435
      result.test_is_false("cert sign not permitted", ca.compatible_with(key));
1✔
1436
      result.test_is_true("data encipherment permitted", data_encipherment.compatible_with(key));
1✔
1437
      result.test_is_true("key encipherment permitted", key_encipherment.compatible_with(key));
1✔
1438
      result.test_is_false("key agreement not permitted", key_agreement.compatible_with(key));
1✔
1439
      result.test_is_false("key agreement", key_agreement_encipher_only.compatible_with(key));
1✔
1440
      result.test_is_false("key agreement", key_agreement_decipher_only.compatible_with(key));
1✔
1441
      result.test_is_false("crl sign not permitted", crl_sign.compatible_with(key));
1✔
1442
      result.test_is_false("sign", sign_everything.compatible_with(key));
1✔
1443
   } else if(pk_algo == "DSA" || pk_algo == "ECDSA" || pk_algo == "ECGDSA" || pk_algo == "ECKCDSA" ||
10✔
1444
             pk_algo == "GOST-34.10" || pk_algo == "Dilithium" || pk_algo == "ML-DSA" || pk_algo == "SLH-DSA" ||
18✔
1445
             pk_algo == "HSS-LMS") {
3✔
1446
      // these are signature algorithms only
1447
      result.test_is_false("all constraints not permitted", all.compatible_with(key));
9✔
1448

1449
      result.test_is_true("ca allowed", ca.compatible_with(key));
9✔
1450
      result.test_is_true("sign allowed", sign_data.compatible_with(key));
9✔
1451
      result.test_is_true("non-repudiation allowed", non_repudiation.compatible_with(key));
9✔
1452
      result.test_is_false("key encipherment not permitted", key_encipherment.compatible_with(key));
9✔
1453
      result.test_is_false("data encipherment not permitted", data_encipherment.compatible_with(key));
9✔
1454
      result.test_is_false("key agreement not permitted", key_agreement.compatible_with(key));
9✔
1455
      result.test_is_false("key agreement", key_agreement_encipher_only.compatible_with(key));
9✔
1456
      result.test_is_false("key agreement", key_agreement_decipher_only.compatible_with(key));
9✔
1457
      result.test_is_true("crl sign allowed", crl_sign.compatible_with(key));
9✔
1458
      result.test_is_true("sign allowed", sign_everything.compatible_with(key));
9✔
1459
   }
1460

1461
   return result;
19✔
1462
}
×
1463

1464
/**
1465
 * @brief X.509v3 extension that encodes a given string
1466
 */
1467
class String_Extension final : public Botan::Certificate_Extension {
22✔
1468
   public:
1469
      String_Extension() = default;
22✔
1470

1471
      explicit String_Extension(const std::string& val) : m_contents(val) {}
11✔
1472

1473
      std::string value() const { return m_contents; }
44✔
1474

1475
      std::unique_ptr<Certificate_Extension> copy() const override {
×
1476
         return std::make_unique<String_Extension>(m_contents);
×
1477
      }
1478

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

1481
      bool should_encode() const override { return true; }
22✔
1482

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

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

1487
      std::vector<uint8_t> encode_inner() const override {
11✔
1488
         std::vector<uint8_t> bits;
11✔
1489
         Botan::DER_Encoder(bits).encode(Botan::ASN1_String(m_contents, Botan::ASN1_Type::Utf8String));
22✔
1490
         return bits;
11✔
1491
      }
×
1492

1493
      void decode_inner(const std::vector<uint8_t>& in) override {
22✔
1494
         Botan::ASN1_String str;
22✔
1495
         Botan::BER_Decoder(in).decode(str, Botan::ASN1_Type::Utf8String).verify_end();
22✔
1496
         m_contents = str.value();
44✔
1497
      }
22✔
1498

1499
   private:
1500
      std::string m_contents;
1501
};
1502

1503
Test::Result test_x509_wrong_context_certificate_extensions() {
1✔
1504
   Test::Result result("X509 wrong-context certificate extensions");
1✔
1505

1506
   #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
1507
   const std::string base = "x509/wrong_context_ext/";
1✔
1508

1509
   const auto test_rejected = [&](const std::string& filename, std::string_view what) {
4✔
1510
      result.test_throws<Botan::Decoding_Error>(
3✔
1511
         std::string(what), [&]() { const Botan::X509_Certificate cert(Test::data_file(base + filename)); });
9✔
1512
   };
6✔
1513

1514
   test_rejected("cert_with_crl_number.pem", "CRL number rejected in certificate");
1✔
1515
   test_rejected("cert_with_reason_code.pem", "CRL reason rejected in certificate");
1✔
1516
   test_rejected("cert_with_idp.pem", "CRL issuing distribution point rejected in certificate");
1✔
1517
   #endif
1518

1519
   return result;
1✔
1520
}
1✔
1521

1522
Test::Result test_x509_wrong_context_crl_extensions() {
1✔
1523
   Test::Result result("X509 wrong-context CRL extensions");
1✔
1524

1525
   #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
1526
   const std::string base_dir = "x509/wrong_context_ext";
1✔
1527

1528
   auto load_crl_from_pem = [&](std::string_view filename) -> Botan::X509_CRL {
5✔
1529
      return Botan::X509_CRL(Test::data_file(base_dir, filename));
6✔
1530
   };
1✔
1531

1532
   result.test_throws<Botan::Decoding_Error>("SubjectAltName rejected in CRL",
1✔
1533
                                             [&]() { load_crl_from_pem("crl_with_san.pem"); });
2✔
1534
   result.test_throws<Botan::Decoding_Error>("SubjectAltName rejected in CRL entry",
1✔
1535
                                             [&]() { load_crl_from_pem("crl_entry_with_san.pem"); });
2✔
1536

1537
   const Botan::X509_Certificate ca_cert(Test::data_file(base_dir + "/ca.pem"));
2✔
1538
   const Botan::X509_Certificate user_cert(Test::data_file(base_dir + "/user.pem"));
2✔
1539
   const std::vector<Botan::X509_Certificate> cert_path = {user_cert, ca_cert};
3✔
1540
   const auto validation_time = Botan::calendar_point(2027, 1, 1, 0, 0, 0).to_std_timepoint();
1✔
1541

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

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

1549
      const bool contains_expected_code =
2✔
1550
         !crl_status.empty() &&
2✔
1551
         crl_status[0].contains(Botan::Certificate_Status_Code::CRL_HAS_UNKNOWN_CRITICAL_EXTENSION);
4✔
1552
      result.test_is_true(what, contains_expected_code);
2✔
1553
   };
4✔
1554

1555
   check_crl_unusable("crl_with_unknown_critical.pem", "critical unknown CRL extension rejects CRL");
1✔
1556
   check_crl_unusable("crl_entry_with_unknown_critical.pem", "critical unknown CRL entry extension rejects CRL");
1✔
1557
   #endif
1558

1559
   return result;
2✔
1560
}
2✔
1561

1562
Test::Result test_custom_dn_attr(const Botan::Private_Key& ca_key,
11✔
1563
                                 const std::string& sig_algo,
1564
                                 const std::string& sig_padding,
1565
                                 const std::string& hash_fn,
1566
                                 Botan::RandomNumberGenerator& rng) {
1567
   Test::Result result("X509 Custom DN");
11✔
1568

1569
   /* Create the self-signed cert */
1570
   const Botan::X509_Certificate ca_cert =
11✔
1571
      Botan::X509::create_self_signed_cert(ca_opts(sig_padding), ca_key, hash_fn, rng);
11✔
1572

1573
   /* Create the CA object */
1574
   const Botan::X509_CA ca(ca_cert, ca_key, hash_fn, sig_padding, rng);
11✔
1575

1576
   auto user_key = make_a_private_key(sig_algo, rng);
11✔
1577

1578
   Botan::X509_DN subject_dn;
11✔
1579

1580
   const Botan::OID attr1(Botan::OID("1.3.6.1.4.1.25258.9.1.1"));
11✔
1581
   const Botan::OID attr2(Botan::OID("1.3.6.1.4.1.25258.9.1.2"));
11✔
1582
   const Botan::ASN1_String val1("Custom Attr 1", Botan::ASN1_Type::PrintableString);
11✔
1583
   const Botan::ASN1_String val2("12345", Botan::ASN1_Type::Utf8String);
11✔
1584

1585
   subject_dn.add_attribute(attr1, val1);
11✔
1586
   subject_dn.add_attribute(attr2, val2);
11✔
1587

1588
   const Botan::Extensions extensions;
11✔
1589

1590
   const Botan::PKCS10_Request req =
11✔
1591
      Botan::PKCS10_Request::create(*user_key, subject_dn, extensions, hash_fn, rng, sig_padding);
11✔
1592

1593
   const Botan::X509_DN& req_dn = req.subject_dn();
11✔
1594

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

1597
   const Botan::ASN1_String req_val1 = req_dn.get_first_attribute(attr1);
11✔
1598
   const Botan::ASN1_String req_val2 = req_dn.get_first_attribute(attr2);
11✔
1599
   result.test_is_true("Attr1 matches encoded", req_val1 == val1);
11✔
1600
   result.test_is_true("Attr2 matches encoded", req_val2 == val2);
11✔
1601
   result.test_is_true("Attr1 tag matches encoded", req_val1.tagging() == val1.tagging());
11✔
1602
   result.test_is_true("Attr2 tag matches encoded", req_val2.tagging() == val2.tagging());
11✔
1603

1604
   const Botan::X509_Time not_before("100301123001Z", Botan::ASN1_Type::UtcTime);
11✔
1605
   const Botan::X509_Time not_after("300301123001Z", Botan::ASN1_Type::UtcTime);
11✔
1606

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

1609
   const Botan::X509_DN& cert_dn = cert.subject_dn();
11✔
1610

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

1613
   const Botan::ASN1_String cert_val1 = cert_dn.get_first_attribute(attr1);
11✔
1614
   const Botan::ASN1_String cert_val2 = cert_dn.get_first_attribute(attr2);
11✔
1615
   result.test_is_true("Attr1 matches encoded", cert_val1 == val1);
11✔
1616
   result.test_is_true("Attr2 matches encoded", cert_val2 == val2);
11✔
1617
   result.test_is_true("Attr1 tag matches encoded", cert_val1.tagging() == val1.tagging());
11✔
1618
   result.test_is_true("Attr2 tag matches encoded", cert_val2.tagging() == val2.tagging());
11✔
1619

1620
   return result;
22✔
1621
}
99✔
1622

1623
Test::Result test_x509_extensions(const Botan::Private_Key& ca_key,
11✔
1624
                                  const std::string& sig_algo,
1625
                                  const std::string& sig_padding,
1626
                                  const std::string& hash_fn,
1627
                                  Botan::RandomNumberGenerator& rng) {
1628
   using Botan::Key_Constraints;
11✔
1629

1630
   Test::Result result("X509 Extensions");
11✔
1631

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

1636
   /* Create the CA object */
1637
   const Botan::X509_CA ca(ca_cert, ca_key, hash_fn, sig_padding, rng);
11✔
1638

1639
   /* Prepare CDP extension */
1640
   std::vector<std::string> cdp_urls = {
11✔
1641
      "http://example.com/crl1.pem",
1642
      "ldap://ldap.example.com/cn=crl1,dc=example,dc=com?certificateRevocationList;binary"};
11✔
1643

1644
   std::vector<Botan::Cert_Extension::CRL_Distribution_Points::Distribution_Point> dps;
11✔
1645

1646
   for(const auto& uri : cdp_urls) {
33✔
1647
      Botan::AlternativeName cdp_alt_name;
22✔
1648
      cdp_alt_name.add_uri(uri);
22✔
1649
      const Botan::Cert_Extension::CRL_Distribution_Points::Distribution_Point dp(cdp_alt_name);
22✔
1650

1651
      dps.emplace_back(dp);
22✔
1652
   }
22✔
1653

1654
   auto user_key = make_a_private_key(sig_algo, rng);
11✔
1655

1656
   Botan::X509_Cert_Options opts("Test User 1/US/Botan Project/Testing");
11✔
1657
   opts.constraints = Key_Constraints::DigitalSignature;
11✔
1658

1659
   // include a custom extension in the request
1660
   Botan::Extensions req_extensions;
11✔
1661
   const Botan::OID oid("1.2.3.4.5.6.7.8.9.1");
11✔
1662
   const Botan::OID ku_oid = Botan::OID::from_string("X509v3.KeyUsage");
11✔
1663
   req_extensions.add(std::make_unique<String_Extension>("AAAAAAAAAAAAAABCDEF"), false);
22✔
1664
   req_extensions.add(std::make_unique<Botan::Cert_Extension::CRL_Distribution_Points>(dps));
22✔
1665
   opts.extensions = req_extensions;
11✔
1666
   opts.set_padding_scheme(sig_padding);
11✔
1667

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

1671
   result.test_is_true("Extensions::extension_set true for Key_Usage",
11✔
1672
                       self_signed_cert.v3_extensions().extension_set(ku_oid));
11✔
1673

1674
   // check if known Key_Usage extension is present in self-signed cert
1675
   auto key_usage_ext = self_signed_cert.v3_extensions().get(ku_oid);
11✔
1676
   if(result.test_is_true("Key_Usage extension present in self-signed certificate", key_usage_ext != nullptr)) {
11✔
1677
      result.test_is_true(
22✔
1678
         "Key_Usage extension value matches in self-signed certificate",
1679
         dynamic_cast<Botan::Cert_Extension::Key_Usage&>(*key_usage_ext).get_constraints() == opts.constraints);
11✔
1680
   }
1681

1682
   // check if custom extension is present in self-signed cert
1683
   auto string_ext = self_signed_cert.v3_extensions().get_raw<String_Extension>(oid);
11✔
1684
   if(result.test_is_true("Custom extension present in self-signed certificate", string_ext != nullptr)) {
11✔
1685
      result.test_str_eq(
22✔
1686
         "Custom extension value matches in self-signed certificate", string_ext->value(), "AAAAAAAAAAAAAABCDEF");
33✔
1687
   }
1688

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

1693
   if(result.test_is_true("CRL Distribution Points extension present in self-signed certificate",
44✔
1694
                          !cert_cdps->crl_distribution_urls().empty())) {
11✔
1695
      for(const auto& cdp : cert_cdps->distribution_points()) {
33✔
1696
         result.test_is_true("CDP URI present in self-signed certificate",
44✔
1697
                             std::ranges::find(cdp_urls, cdp.point().get_first_attribute("URI")) != cdp_urls.end());
66✔
1698
      }
1699

1700
      // The accessor returns one entry per URI
1701
      const auto& urls = cert_cdps->crl_distribution_urls();
11✔
1702
      result.test_sz_eq("crl_distribution_urls has one entry per URI (self-signed)", urls.size(), cdp_urls.size());
11✔
1703
      for(const auto& url : urls) {
33✔
1704
         result.test_is_true("crl_distribution_urls entry is a bare URI (self-signed)",
44✔
1705
                             std::ranges::find(cdp_urls, url) != cdp_urls.end());
44✔
1706
      }
1707

1708
      // Ensure X509_Certificate's cached accessor returns the same bare URIs
1709
      const auto& cert_dp = self_signed_cert.crl_distribution_point_uris();
11✔
1710
      result.test_sz_eq(
11✔
1711
         "X509_Certificate::crl_distribution_points size (self-signed)", cert_dp.size(), cdp_urls.size());
1712
      for(const auto& url : cert_dp) {
33✔
1713
         result.test_is_true("X509_Certificate::crl_distribution_points entry is a bare URI (self-signed)",
44✔
1714
                             std::ranges::find(cdp_urls, url.original_input()) != cdp_urls.end());
44✔
1715
      }
1716
   }
11✔
1717

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

1720
   /* Create a CA-signed certificate */
1721
   const Botan::X509_Certificate ca_signed_cert =
11✔
1722
      ca.sign_request(user_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
11✔
1723

1724
   // check if known Key_Usage extension is present in CA-signed cert
1725
   result.test_is_true("Extensions::extension_set true for Key_Usage",
11✔
1726
                       ca_signed_cert.v3_extensions().extension_set(ku_oid));
11✔
1727

1728
   key_usage_ext = ca_signed_cert.v3_extensions().get(ku_oid);
22✔
1729
   if(result.test_is_true("Key_Usage extension present in CA-signed certificate", key_usage_ext != nullptr)) {
11✔
1730
      auto constraints = dynamic_cast<Botan::Cert_Extension::Key_Usage&>(*key_usage_ext).get_constraints();
11✔
1731
      result.test_is_true("Key_Usage extension value matches in user certificate",
22✔
1732
                          constraints == Botan::Key_Constraints::DigitalSignature);
11✔
1733
   }
1734

1735
   // check if custom extension is present in CA-signed cert
1736
   result.test_is_true("Extensions::extension_set true for String_Extension",
11✔
1737
                       ca_signed_cert.v3_extensions().extension_set(oid));
11✔
1738
   string_ext = ca_signed_cert.v3_extensions().get_raw<String_Extension>(oid);
22✔
1739
   if(result.test_is_true("Custom extension present in CA-signed certificate", string_ext != nullptr)) {
11✔
1740
      result.test_str_eq(
22✔
1741
         "Custom extension value matches in CA-signed certificate", string_ext->value(), "AAAAAAAAAAAAAABCDEF");
33✔
1742
   }
1743

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

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

1754
      const auto& urls = cert_cdps->crl_distribution_urls();
11✔
1755
      result.test_sz_eq("crl_distribution_urls has one entry per URI (CA-signed)", urls.size(), cdp_urls.size());
11✔
1756
      for(const auto& url : urls) {
33✔
1757
         result.test_is_true("crl_distribution_urls entry is a bare URI (CA-signed)",
44✔
1758
                             std::ranges::find(cdp_urls, url) != cdp_urls.end());
44✔
1759
      }
1760

1761
      const auto& cert_dp = ca_signed_cert.crl_distribution_point_uris();
11✔
1762
      result.test_sz_eq("X509_Certificate::crl_distribution_points size (CA-signed)", cert_dp.size(), cdp_urls.size());
11✔
1763
      for(const auto& url : cert_dp) {
33✔
1764
         result.test_is_true("X509_Certificate::crl_distribution_points entry is a bare URI (CA-signed)",
44✔
1765
                             std::ranges::find(cdp_urls, url.original_input()) != cdp_urls.end());
44✔
1766
      }
1767
   }
11✔
1768

1769
   return result;
11✔
1770
}
55✔
1771

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

1775
   struct TestData {
12✔
1776
         const std::string issuer, subject, issuer_hash, subject_hash;
1777
   } const cases[]{{"",
12✔
1778
                    "",
1779
                    "E4F60D0AA6D7F3D3B6A6494B1C861B99F649C6F9EC51ABAF201B20F297327C95",
1780
                    "E4F60D0AA6D7F3D3B6A6494B1C861B99F649C6F9EC51ABAF201B20F297327C95"},
1781
                   {"a",
1782
                    "b",
1783
                    "BC2E013472F39AC579964880E422737C82BA812CB8BC2FD17E013060D71E6E19",
1784
                    "5E31CFAA3FAFB1A5BA296A0D2BAB9CA44D7936E9BF0BBC54637D0C53DBC4A432"},
1785
                   {"A",
1786
                    "B",
1787
                    "4B3206201C4BC9B6CD6C36532A97687DF9238155D99ADB60C66BF2B2220643D8",
1788
                    "FFF635A52A16618B4A0E9CD26B5E5A2FA573D343C051E6DE8B0811B1ACC89B86"},
1789
                   {
1790
                      "Test Issuer/US/Botan Project/Testing",
1791
                      "Test Subject/US/Botan Project/Testing",
1792
                      "ACB4F373004A56A983A23EB8F60FA4706312B5DB90FD978574FE7ACC84E093A5",
1793
                      "87039231C2205B74B6F1F3830A66272C0B41F71894B03AC3150221766D95267B",
1794
                   },
1795
                   {
1796
                      "Test Subject/US/Botan Project/Testing",
1797
                      "Test Issuer/US/Botan Project/Testing",
1798
                      "87039231C2205B74B6F1F3830A66272C0B41F71894B03AC3150221766D95267B",
1799
                      "ACB4F373004A56A983A23EB8F60FA4706312B5DB90FD978574FE7ACC84E093A5",
1800
                   }};
72✔
1801

1802
   for(const auto& a : cases) {
72✔
1803
      Botan::X509_Cert_Options opts{a.issuer};
60✔
1804
      opts.CA_key();
60✔
1805

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

1808
      result.test_str_eq(a.issuer, Botan::hex_encode(issuer_cert.raw_issuer_dn_sha256()), a.issuer_hash);
60✔
1809
      result.test_str_eq(a.issuer, Botan::hex_encode(issuer_cert.raw_subject_dn_sha256()), a.issuer_hash);
60✔
1810

1811
      const Botan::X509_CA ca(issuer_cert, key, hash_fn, rng);
60✔
1812
      const Botan::PKCS10_Request req =
60✔
1813
         Botan::X509::create_cert_req(Botan::X509_Cert_Options(a.subject), key, hash_fn, rng);
60✔
1814
      const Botan::X509_Certificate subject_cert =
60✔
1815
         ca.sign_request(req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
60✔
1816

1817
      result.test_str_eq(a.subject, Botan::hex_encode(subject_cert.raw_issuer_dn_sha256()), a.issuer_hash);
60✔
1818
      result.test_str_eq(a.subject, Botan::hex_encode(subject_cert.raw_subject_dn_sha256()), a.subject_hash);
60✔
1819
   }
60✔
1820
   return result;
12✔
1821
}
72✔
1822

1823
   #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
1824

1825
Test::Result test_x509_tn_auth_list_extension_decode() {
1✔
1826
   /* cert with TNAuthList extension data was generated by asn1parse cfg:
1827

1828
      asn1=SEQUENCE:tn_auth_list
1829

1830
      [tn_auth_list]
1831
      spc=EXP:0,IA5:1001
1832
      range=EXP:1,SEQUENCE:TelephoneNumberRange
1833
      one=EXP:2,IA5:333
1834

1835
      [TelephoneNumberRange]
1836
      start1=IA5:111
1837
      count1=INT:128
1838
      start2=IA5:222
1839
      count2=INT:256
1840
    */
1841
   const std::string filename("TNAuthList.pem");
1✔
1842
   Test::Result result("X509 TNAuthList decode");
1✔
1843
   result.start_timer();
1✔
1844

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

1847
   using Botan::Cert_Extension::TNAuthList;
1✔
1848

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

1851
   const auto& tn_entries = tn_auth_list->entries();
1✔
1852

1853
   result.test_not_null("cert has TNAuthList extension", tn_auth_list);
1✔
1854

1855
   result.test_throws("wrong telephone_number_range() accessor for spc",
1✔
1856
                      [&tn_entries] { tn_entries[0].telephone_number_range(); });
2✔
1857
   result.test_throws("wrong telephone_number() accessor for range",
1✔
1858
                      [&tn_entries] { tn_entries[1].telephone_number(); });
2✔
1859
   result.test_throws("wrong service_provider_code() accessor for one",
1✔
1860
                      [&tn_entries] { tn_entries[2].service_provider_code(); });
2✔
1861

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

1865
   result.test_is_true("range entry type", tn_entries[1].type() == TNAuthList::Entry::TelephoneNumberRange);
1✔
1866
   const auto& range = tn_entries[1].telephone_number_range();
1✔
1867
   result.test_sz_eq("range entries count", range.size(), 2);
1✔
1868
   result.test_str_eq("range entry 0 start data", range[0].start.value(), "111");
1✔
1869
   result.test_sz_eq("range entry 0 count data", range[0].count, 128);
1✔
1870
   result.test_str_eq("range entry 1 start data", range[1].start.value(), "222");
1✔
1871
   result.test_sz_eq("range entry 1 count data", range[1].count, 256);
1✔
1872

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

1876
   result.end_timer();
1✔
1877
   return result;
2✔
1878
}
1✔
1879

1880
   #endif
1881

1882
std::vector<std::string> get_sig_paddings(const std::string& sig_algo, const std::string& hash) {
12✔
1883
   if(sig_algo == "RSA") {
12✔
1884
      return {
1✔
1885
   #if defined(BOTAN_HAS_EMSA_PKCS1)
1886
         "PKCS1v15(" + hash + ")",
1✔
1887
   #endif
1888
   #if defined(BOTAN_HAS_EMSA_PSS)
1889
            "PSS(" + hash + ")",
1890
   #endif
1891
      };
3✔
1892
   } else if(sig_algo == "DSA" || sig_algo == "ECDSA" || sig_algo == "ECGDSA" || sig_algo == "ECKCDSA" ||
11✔
1893
             sig_algo == "GOST-34.10") {
7✔
1894
      return {hash};
10✔
1895
   } else if(sig_algo == "Ed25519" || sig_algo == "Ed448") {
6✔
1896
      return {"Pure"};
2✔
1897
   } else if(sig_algo == "Dilithium" || sig_algo == "ML-DSA") {
4✔
1898
      return {"Randomized"};
2✔
1899
   } else if(sig_algo == "HSS-LMS") {
2✔
1900
      return {""};
1✔
1901
   } else {
1902
      return {};
1✔
1903
   }
1904
}
6✔
1905

1906
class X509_Cert_Unit_Tests final : public Test {
1✔
1907
   public:
1908
      std::vector<Test::Result> run() override {
1✔
1909
         std::vector<Test::Result> results;
1✔
1910

1911
         auto& rng = this->rng();
1✔
1912

1913
         const std::string sig_algos[]{"RSA",
1✔
1914
                                       "DSA",
1915
                                       "ECDSA",
1916
                                       "ECGDSA",
1917
                                       "ECKCDSA",
1918
                                       "GOST-34.10",
1919
                                       "Ed25519",
1920
                                       "Ed448",
1921
                                       "Dilithium",
1922
                                       "ML-DSA",
1923
                                       "SLH-DSA",
1924
                                       "HSS-LMS"};
13✔
1925

1926
         for(const std::string& algo : sig_algos) {
13✔
1927
   #if !defined(BOTAN_HAS_EMSA_PKCS1)
1928
            if(algo == "RSA")
1929
               continue;
1930
   #endif
1931

1932
            std::string hash = "SHA-256";
12✔
1933

1934
            if(algo == "Ed25519") {
12✔
1935
               hash = "SHA-512";
1✔
1936
            }
1937
            if(algo == "Ed448") {
12✔
1938
               hash = "SHAKE-256(912)";
1✔
1939
            }
1940
            if(algo == "Dilithium" || algo == "ML-DSA") {
12✔
1941
               hash = "SHAKE-256(512)";
2✔
1942
            }
1943

1944
            auto key = make_a_private_key(algo, rng);
12✔
1945

1946
            if(key == nullptr) {
12✔
1947
               continue;
×
1948
            }
1949

1950
            results.push_back(test_hashes(*key, hash, rng));
24✔
1951
            results.push_back(test_valid_constraints(*key, algo));
24✔
1952

1953
            Test::Result usage_result("X509 Usage");
12✔
1954
            try {
12✔
1955
               usage_result.merge(test_usage(*key, algo, hash, rng));
12✔
1956
            } catch(std::exception& e) {
×
1957
               usage_result.test_failure("test_usage " + algo, e.what());
×
1958
            }
×
1959
            results.push_back(usage_result);
12✔
1960

1961
            for(const auto& padding_scheme : get_sig_paddings(algo, hash)) {
23✔
1962
               Test::Result cert_result("X509 Unit");
11✔
1963

1964
               try {
11✔
1965
                  cert_result.merge(test_x509_cert(*key, algo, padding_scheme, hash, rng));
11✔
1966
               } catch(std::exception& e) {
×
1967
                  cert_result.test_failure("test_x509_cert " + algo, e.what());
×
1968
               }
×
1969
               results.push_back(cert_result);
11✔
1970

1971
               Test::Result pkcs10_result("PKCS10 extensions");
11✔
1972
               try {
11✔
1973
                  pkcs10_result.merge(test_pkcs10_ext(*key, padding_scheme, hash, rng));
11✔
1974
               } catch(std::exception& e) {
×
1975
                  pkcs10_result.test_failure("test_pkcs10_ext " + algo, e.what());
×
1976
               }
×
1977
               results.push_back(pkcs10_result);
11✔
1978

1979
               Test::Result self_issued_result("X509 Self Issued");
11✔
1980
               try {
11✔
1981
                  self_issued_result.merge(test_self_issued(*key, algo, padding_scheme, hash, rng));
11✔
1982
               } catch(std::exception& e) {
×
1983
                  self_issued_result.test_failure("test_self_issued " + algo, e.what());
×
1984
               }
×
1985
               results.push_back(self_issued_result);
11✔
1986

1987
               Test::Result extensions_result("X509 Extensions");
11✔
1988
               try {
11✔
1989
                  extensions_result.merge(test_x509_extensions(*key, algo, padding_scheme, hash, rng));
11✔
1990
               } catch(std::exception& e) {
×
1991
                  extensions_result.test_failure("test_extensions " + algo, e.what());
×
1992
               }
×
1993
               results.push_back(extensions_result);
11✔
1994

1995
               Test::Result custom_dn_result("X509 Custom DN");
11✔
1996
               try {
11✔
1997
                  custom_dn_result.merge(test_custom_dn_attr(*key, algo, padding_scheme, hash, rng));
11✔
1998
               } catch(std::exception& e) {
×
1999
                  custom_dn_result.test_failure("test_custom_dn_attr " + algo, e.what());
×
2000
               }
×
2001
               results.push_back(custom_dn_result);
11✔
2002
            }
23✔
2003
         }
24✔
2004

2005
         /*
2006
         These are algos which cannot sign but can be included in certs
2007
         */
2008
         const std::vector<std::string> enc_algos = {
1✔
2009
            "DH", "ECDH", "ElGamal", "Kyber", "ML-KEM", "FrodoKEM", "ClassicMcEliece"};
1✔
2010

2011
         for(const std::string& algo : enc_algos) {
8✔
2012
            auto key = make_a_private_key(algo, rng);
7✔
2013

2014
            if(key) {
7✔
2015
               results.push_back(test_valid_constraints(*key, algo));
14✔
2016
            }
2017
         }
7✔
2018

2019
   #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM) && defined(BOTAN_HAS_EMSA_PKCS1) && defined(BOTAN_HAS_EMSA_PSSR) && \
2020
      defined(BOTAN_HAS_RSA)
2021
         Test::Result pad_config_result("X509 Padding Config");
1✔
2022
         try {
1✔
2023
            pad_config_result.merge(test_padding_config());
1✔
2024
         } catch(const std::exception& e) {
×
2025
            pad_config_result.test_failure("test_padding_config", e.what());
×
2026
         }
×
2027
         results.push_back(pad_config_result);
1✔
2028
   #endif
2029

2030
   #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
2031
         results.push_back(test_x509_utf8());
2✔
2032
         results.push_back(test_x509_any_key_extended_usage());
2✔
2033
         results.push_back(test_x509_bmpstring());
2✔
2034
         results.push_back(test_x509_teletex());
2✔
2035
         results.push_back(test_crl_dn_name());
2✔
2036
         results.push_back(test_rdn_multielement_set_name());
2✔
2037
         results.push_back(test_x509_decode_list());
2✔
2038
         results.push_back(test_rsa_oaep());
2✔
2039
         results.push_back(test_x509_authority_info_access_extension());
2✔
2040
         results.push_back(test_crl_issuing_distribution_point_extension());
2✔
2041
         results.push_back(test_verify_gost2012_cert());
2✔
2042
         results.push_back(test_parse_rsa_pss_cert());
2✔
2043
         results.push_back(test_x509_tn_auth_list_extension_decode());
2✔
2044
   #endif
2045

2046
         results.push_back(test_x509_encode_authority_info_access_extension());
2✔
2047
         results.push_back(test_x509_extension());
2✔
2048
         results.push_back(test_x509_extension_decode_duplicate());
2✔
2049
         results.push_back(test_x509_wrong_context_certificate_extensions());
2✔
2050
         results.push_back(test_x509_wrong_context_crl_extensions());
2✔
2051
         results.push_back(test_x509_dates());
2✔
2052
         results.push_back(test_cert_status_strings());
2✔
2053
         results.push_back(test_x509_uninit());
2✔
2054
         results.push_back(test_crl_entry_negative_serial());
2✔
2055

2056
         return results;
1✔
2057
      }
13✔
2058
};
2059

2060
BOTAN_REGISTER_TEST("x509", "x509_unit", X509_Cert_Unit_Tests);
2061

2062
#endif
2063

2064
}  // namespace
2065

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