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

randombit / botan / 27930394332

22 Jun 2026 02:29AM UTC coverage: 89.361% (-2.3%) from 91.664%
27930394332

push

github

randombit
Escape control chars in X509_Certificate::to_string

111792 of 125101 relevant lines covered (89.36%)

10818223.53 hits per line

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

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

8
#include "tests.h"
9

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

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

31
namespace Botan_Tests {
32

33
namespace {
34

35
#if defined(BOTAN_HAS_X509_CERTIFICATES)
36

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

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

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

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

53
   opts.CA_key(1);
112✔
54

55
   return opts;
112✔
56
}
×
57

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

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

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

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

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

77
   return opts;
37✔
78
}
×
79

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

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

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

90
   return opts;
11✔
91
}
×
92

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

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

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

105
   return opts;
55✔
106
}
×
107

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

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

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

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

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

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

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

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

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

208
   return result;
1✔
209
}
1✔
210

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

214
   Botan::Extensions extn;
1✔
215

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

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

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

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

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

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

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

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

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

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

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

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

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

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

265
   const auto* crl_number = decoded_extns.get_extension_object_as<Botan::Cert_Extension::CRL_Number>();
1✔
266
   if(result.test_is_true("CRL number recognized without explicit context", crl_number != nullptr)) {
1✔
267
      result.test_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_x509_ldap_empty_authority_uris() {
1✔
808
   Test::Result result("X509 LDAP URIs with empty authority");
1✔
809

810
   const auto check_uri = [&](std::string_view label, const Botan::URI& uri, std::string_view expected) {
4✔
811
      result.test_str_eq(std::string(label) + " original URI", uri.original_input(), expected);
9✔
812
      result.test_str_eq(std::string(label) + " scheme", uri.scheme(), "ldap");
6✔
813
      const auto raw_authority = uri.raw_authority();
3✔
814
      result.test_is_true(std::string(label) + " raw authority present", raw_authority.has_value());
9✔
815
      if(raw_authority.has_value()) {
3✔
816
         result.test_str_eq(std::string(label) + " raw authority is empty", std::string(*raw_authority), "");
9✔
817
      }
818
      result.test_is_false(std::string(label) + " has no parsed authority", uri.authority().has_value());
9✔
819
      result.test_is_false(std::string(label) + " has no host", uri.host().has_value());
9✔
820
   };
33✔
821

822
   const auto check_contains_uri =
1✔
823
      [&](std::string_view label, const std::vector<Botan::URI>& uris, std::string_view expected) {
3✔
824
         for(const auto& uri : uris) {
5✔
825
            if(uri.original_input() == expected) {
5✔
826
               check_uri(label, uri, expected);
3✔
827
               return;
3✔
828
            }
829
         }
830
         result.test_failure(std::string(label) + " URI not found");
×
831
      };
1✔
832

833
   const Botan::X509_Certificate aruba_cert(Test::data_file("x509/misc/aruba.pem"));
2✔
834
   const std::string aruba_aia =
1✔
835
      "ldap:///CN=Security1-WIN-05PRGNGEKAO-CA,CN=AIA,CN=Public%20Key%20Services,CN=Services,CN=Configuration,"
836
      "DC=Security1,DC=aruba,DC=com?cACertificate?base?objectClass=certificationAuthority";
1✔
837
   const std::string aruba_cdp =
1✔
838
      "ldap:///CN=Security1-WIN-05PRGNGEKAO-CA,CN=WIN-05PRGNGEKAO,CN=CDP,CN=Public%20Key%20Services,CN=Services,"
839
      "CN=Configuration,DC=Security1,DC=aruba,DC=com?certificateRevocationList?base?objectClass=cRLDistributionPoint";
1✔
840

841
   check_contains_uri("Aruba AIA", aruba_cert.ca_issuer_uris(), aruba_aia);
1✔
842
   check_contains_uri("Aruba CDP", aruba_cert.crl_distribution_point_uris(), aruba_cdp);
1✔
843

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

849
   check_contains_uri("BDE CDP", bde_cert.crl_distribution_point_uris(), bde_cdp);
1✔
850

851
   return result;
2✔
852
}
1✔
853

854
Test::Result test_crl_issuing_distribution_point_extension() {
1✔
855
   Test::Result result("X509 CRL IssuingDistributionPoint extension");
1✔
856

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

860
   result.test_str_eq(
1✔
861
      "CRL IDP URI decoded correctly", crl.crl_issuing_distribution_point(), "http://localhost/subca/crldp/crl.crl");
1✔
862

863
   return result;
1✔
864
}
1✔
865

866
Test::Result test_parse_rsa_pss_cert() {
1✔
867
   Test::Result result("X509 RSA-PSS certificate");
1✔
868

869
   // See https://github.com/randombit/botan/issues/3019 for background
870

871
   try {
1✔
872
      const Botan::X509_Certificate rsa_pss(Test::data_file("x509/misc/rsa_pss.pem"));
2✔
873
      result.test_success("Was able to parse RSA-PSS certificate signed with ECDSA");
1✔
874
   } catch(Botan::Exception& e) {
1✔
875
      result.test_failure("Parsing failed", e.what());
×
876
   }
×
877

878
   return result;
1✔
879
}
×
880

881
Test::Result test_verify_gost2012_cert() {
1✔
882
   Test::Result result("X509 GOST-2012 certificates");
1✔
883

884
      #if defined(BOTAN_HAS_GOST_34_10_2012) && defined(BOTAN_HAS_STREEBOG)
885
   try {
1✔
886
      if(Botan::EC_Group::supports_named_group("gost_256A")) {
1✔
887
         const Botan::X509_Certificate root_cert(Test::data_file("x509/gost/gost_root.pem"));
2✔
888
         const Botan::X509_Certificate root_int(Test::data_file("x509/gost/gost_int.pem"));
2✔
889

890
         Botan::Certificate_Store_In_Memory trusted;
1✔
891
         trusted.add_certificate(root_cert);
1✔
892

893
         const Botan::Path_Validation_Restrictions restrictions(false, 128, false, {"Streebog-256"});
2✔
894
         const auto validation_time = Botan::calendar_point(2024, 1, 1, 0, 0, 0).to_std_timepoint();
1✔
895
         const Botan::Path_Validation_Result validation_result = Botan::x509_path_validate(
1✔
896
            root_int, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
1✔
897

898
         result.test_is_true("GOST certificate validates", validation_result.successful_validation());
1✔
899
      }
1✔
900
   } catch(const Botan::Decoding_Error& e) {
×
901
      result.test_failure(e.what());
×
902
   }
×
903
      #endif
904

905
   return result;
1✔
906
}
×
907

908
   /*
909
 * @brief checks the configurability of the RSA-PSS signature scheme
910
 *
911
 * For the other algorithms than RSA, only one padding is supported right now.
912
 */
913
      #if defined(BOTAN_HAS_EMSA_PKCS1) && defined(BOTAN_HAS_EMSA_PSSR) && defined(BOTAN_HAS_RSA)
914
Test::Result test_padding_config() {
1✔
915
   Test::Result test_result("X509 Padding Config");
1✔
916

917
   auto rng = Test::new_rng(__func__);
1✔
918

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

922
   // Create X509 CA certificate; PKCS1v15 is used for signing by default
923
   Botan::X509_Cert_Options opt("TEST CA");
1✔
924
   opt.CA_key();
1✔
925

926
   const Botan::X509_Certificate ca_cert_def = Botan::X509::create_self_signed_cert(opt, (*sk), "SHA-512", *rng);
1✔
927
   test_result.test_opt_str_eq("CA certificate signature algorithm (default)",
2✔
928
                               ca_cert_def.signature_algorithm().oid().registered_name(),
2✔
929
                               "RSA/PKCS1v15(SHA-512)");
930

931
   // Create X509 CA certificate; RSA-PSS is explicitly set
932
   opt.set_padding_scheme("PSSR");
1✔
933
   const Botan::X509_Certificate ca_cert_exp = Botan::X509::create_self_signed_cert(opt, (*sk), "SHA-512", *rng);
1✔
934
   test_result.test_opt_str_eq("CA certificate signature algorithm (explicit)",
2✔
935
                               ca_cert_exp.signature_algorithm().oid().registered_name(),
2✔
936
                               "RSA/PSS");
937

938
         #if defined(BOTAN_HAS_EMSA_X931)
939
   // Try to set a padding scheme that is not supported for signing with the given key type
940
   opt.set_padding_scheme("X9.31");
1✔
941
   try {
1✔
942
      const Botan::X509_Certificate ca_cert_wrong = Botan::X509::create_self_signed_cert(opt, (*sk), "SHA-512", *rng);
1✔
943
      test_result.test_failure("Could build CA cert with invalid encoding scheme X9.31 for key type " +
×
944
                               sk->algo_name());
×
945
   } catch(const Botan::Invalid_Argument& e) {
1✔
946
      test_result.test_str_eq("Build CA certificate with invalid encoding scheme X9.31 for key type " + sk->algo_name(),
3✔
947
                              e.what(),
1✔
948
                              "Signatures using RSA/X9.31(SHA-512) are not supported");
949
   }
1✔
950
         #endif
951

952
   test_result.test_opt_str_eq("CA certificate signature algorithm (explicit)",
2✔
953
                               ca_cert_exp.signature_algorithm().oid().registered_name(),
2✔
954
                               "RSA/PSS");
955

956
   const Botan::X509_Time not_before = from_date(-1, 1, 1);
1✔
957
   const Botan::X509_Time not_after = from_date(2, 1, 2);
1✔
958

959
   // Prepare a signing request for the end certificate
960
   Botan::X509_Cert_Options req_opt("endpoint");
1✔
961
   req_opt.set_padding_scheme("PSS(SHA-512,MGF1,64)");
1✔
962
   const Botan::PKCS10_Request end_req = Botan::X509::create_cert_req(req_opt, (*sk), "SHA-512", *rng);
1✔
963
   test_result.test_opt_str_eq(
2✔
964
      "Certificate request signature algorithm", end_req.signature_algorithm().oid().registered_name(), "RSA/PSS");
2✔
965

966
   // Create X509 CA object: will fail as the chosen hash functions differ
967
   try {
1✔
968
      const Botan::X509_CA ca_fail(ca_cert_exp, (*sk), "SHA-512", "PSS(SHA-256)", *rng);
1✔
969
      test_result.test_failure("Configured conflicting hash functions for CA");
×
970
   } catch(const Botan::Invalid_Argument& e) {
1✔
971
      test_result.test_str_eq(
1✔
972
         "Configured conflicting hash functions for CA",
973
         e.what(),
1✔
974
         "Specified hash function SHA-512 is incompatible with RSA chose hash function SHA-256 with user specified padding PSS(SHA-256)");
975
   }
1✔
976

977
   // Create X509 CA object: its signer will use the padding scheme from the CA certificate, i.e. PKCS1v15
978
   const Botan::X509_CA ca_def(ca_cert_def, (*sk), "SHA-512", *rng);
1✔
979
   const Botan::X509_Certificate end_cert_pkcs1 = ca_def.sign_request(end_req, *rng, not_before, not_after);
1✔
980
   test_result.test_opt_str_eq("End certificate signature algorithm",
2✔
981
                               end_cert_pkcs1.signature_algorithm().oid().registered_name(),
2✔
982
                               "RSA/PKCS1v15(SHA-512)");
983

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

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

997
   // Check CRL signature algorithm
998
   const Botan::X509_CRL crl = ca_exp.new_crl(*rng);
1✔
999
   test_result.test_opt_str_eq("CRL signature algorithm", crl.signature_algorithm().oid().registered_name(), "RSA/PSS");
2✔
1000

1001
   // sanity check for verification, the heavy lifting is done in the other unit tests
1002
   const Botan::Certificate_Store_In_Memory trusted(ca_exp.ca_certificate());
1✔
1003
   const Botan::Path_Validation_Restrictions restrictions(false, 80);
1✔
1004
   const Botan::Path_Validation_Result validation_result =
1✔
1005
      Botan::x509_path_validate(end_cert_pss, restrictions, trusted);
1✔
1006
   test_result.test_is_true("PSS signed certificate validates", validation_result.successful_validation());
1✔
1007

1008
   return test_result;
2✔
1009
}
3✔
1010
      #endif
1011

1012
   #endif
1013

1014
Test::Result test_pkcs10_ext(const Botan::Private_Key& key,
11✔
1015
                             const std::string& sig_padding,
1016
                             const std::string& hash_fn,
1017
                             Botan::RandomNumberGenerator& rng) {
1018
   Test::Result result("PKCS10 extensions");
11✔
1019

1020
   Botan::X509_Cert_Options opts;
11✔
1021

1022
   opts.dns = "main.example.org";
11✔
1023
   opts.more_dns.push_back("more1.example.org");
22✔
1024
   opts.more_dns.push_back("more2.example.org");
22✔
1025

1026
   opts.padding_scheme = sig_padding;
11✔
1027

1028
   Botan::AlternativeName alt_name;
11✔
1029
   alt_name.add_attribute("DNS", "bonus.example.org");
11✔
1030

1031
   Botan::X509_DN alt_dn;
11✔
1032
   alt_dn.add_attribute("X520.CommonName", "alt_cn");
11✔
1033
   alt_dn.add_attribute("X520.Organization", "testing");
11✔
1034
   alt_name.add_dn(alt_dn);
11✔
1035

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

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

1040
   const auto alt_dns_names = req.subject_alt_name().get_attribute("DNS");
11✔
1041

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

1044
   if(alt_dns_names.size() == 4) {
11✔
1045
      result.test_str_eq("Expected DNS name 1", alt_dns_names.at(0), "bonus.example.org");
11✔
1046
      result.test_str_eq("Expected DNS name 2", alt_dns_names.at(1), "main.example.org");
11✔
1047
      result.test_str_eq("Expected DNS name 3", alt_dns_names.at(2), "more1.example.org");
11✔
1048
      result.test_str_eq("Expected DNS name 3", alt_dns_names.at(3), "more2.example.org");
11✔
1049
   }
1050

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

1054
   return result;
11✔
1055
}
11✔
1056

1057
Test::Result test_x509_cert(const Botan::Private_Key& ca_key,
11✔
1058
                            const std::string& sig_algo,
1059
                            const std::string& sig_padding,
1060
                            const std::string& hash_fn,
1061
                            Botan::RandomNumberGenerator& rng) {
1062
   Test::Result result("X509 Unit");
11✔
1063

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

1067
   {
11✔
1068
      result.test_is_true("ca key usage cert", ca_cert.constraints().includes(Botan::Key_Constraints::KeyCertSign));
11✔
1069
      result.test_is_true("ca key usage crl", ca_cert.constraints().includes(Botan::Key_Constraints::CrlSign));
11✔
1070
   }
1071

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

1075
   const Botan::PKCS10_Request user1_req =
11✔
1076
      Botan::X509::create_cert_req(req_opts1(sig_algo, sig_padding), *user1_key, hash_fn, rng);
11✔
1077

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

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

1083
   const Botan::PKCS10_Request user2_req =
11✔
1084
      Botan::X509::create_cert_req(req_opts2(sig_padding), *user2_key, hash_fn, rng);
11✔
1085

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

1089
   const Botan::PKCS10_Request user3_req =
11✔
1090
      Botan::X509::create_cert_req(req_opts3(sig_padding), *user3_key, hash_fn, rng);
11✔
1091

1092
   /* Create the CA object */
1093
   const Botan::X509_CA ca(ca_cert, ca_key, hash_fn, sig_padding, rng);
11✔
1094

1095
   const BigInt user1_serial(99);
11✔
1096

1097
   /* Sign the requests to create the certs */
1098
   const Botan::X509_Certificate user1_cert =
11✔
1099
      ca.sign_request(user1_req, rng, user1_serial, from_date(-1, 01, 01), from_date(2, 01, 01));
11✔
1100

1101
   result.test_sz_eq("User1 serial size matches expected", user1_cert.serial_number().size(), 1);
11✔
1102
   result.test_sz_eq("User1 serial matches expected", user1_cert.serial_number().at(0), size_t(99));
11✔
1103

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

1108
   const Botan::X509_Certificate user3_cert =
11✔
1109
      ca.sign_request(user3_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
11✔
1110

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

1115
   {
11✔
1116
      auto constraints = req_opts1(sig_algo).constraints;
11✔
1117
      result.test_is_true("user1 key usage", user1_cert.constraints().includes(constraints));
11✔
1118
   }
1119

1120
   /* Copy, assign and compare */
1121
   Botan::X509_Certificate user1_cert_copy(user1_cert);
11✔
1122
   result.test_is_true("certificate copy", user1_cert == user1_cert_copy);
11✔
1123

1124
   user1_cert_copy = user2_cert;
11✔
1125
   result.test_is_true("certificate assignment", user2_cert == user1_cert_copy);
11✔
1126

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

1130
   result.test_is_false("certificate differs", user1_cert == user1_cert_differ);
11✔
1131

1132
   /* Get cert data */
1133
   result.test_sz_eq("x509 version", user1_cert.x509_version(), size_t(3));
11✔
1134

1135
   const Botan::X509_DN& user1_issuer_dn = user1_cert.issuer_dn();
11✔
1136
   result.test_str_eq("issuer info CN", user1_issuer_dn.get_first_attribute("CN"), ca_opts().common_name);
11✔
1137
   result.test_str_eq("issuer info Country", user1_issuer_dn.get_first_attribute("C"), ca_opts().country);
11✔
1138
   result.test_str_eq("issuer info Orga", user1_issuer_dn.get_first_attribute("O"), ca_opts().organization);
11✔
1139
   result.test_str_eq("issuer info OrgaUnit", user1_issuer_dn.get_first_attribute("OU"), ca_opts().org_unit);
11✔
1140

1141
   const Botan::X509_DN& user3_subject_dn = user3_cert.subject_dn();
11✔
1142
   result.test_sz_eq("subject OrgaUnit count",
11✔
1143
                     user3_subject_dn.get_attribute("OU").size(),
22✔
1144
                     req_opts3(sig_algo).more_org_units.size() + 1);
22✔
1145
   result.test_str_eq(
22✔
1146
      "subject OrgaUnit #2", user3_subject_dn.get_attribute("OU").at(1), req_opts3(sig_algo).more_org_units.at(0));
33✔
1147

1148
   const Botan::AlternativeName& user1_altname = user1_cert.subject_alt_name();
11✔
1149
   result.test_str_eq("subject alt email", user1_altname.get_first_attribute("RFC822"), "testing@randombit.net");
11✔
1150
   result.test_str_eq("subject alt dns", user1_altname.get_first_attribute("DNS"), "botan.randombit.net");
11✔
1151
   result.test_str_eq("subject alt uri", user1_altname.get_first_attribute("URI"), "https://botan.randombit.net");
11✔
1152

1153
   const Botan::AlternativeName& user3_altname = user3_cert.subject_alt_name();
11✔
1154
   result.test_sz_eq(
11✔
1155
      "subject alt dns count", user3_altname.get_attribute("DNS").size(), req_opts3(sig_algo).more_dns.size() + 1);
22✔
1156
   result.test_str_eq(
22✔
1157
      "subject alt dns #2", user3_altname.get_attribute("DNS").at(1), req_opts3(sig_algo).more_dns.at(0));
33✔
1158

1159
   const Botan::X509_CRL crl1 = ca.new_crl(rng);
11✔
1160

1161
   /* Verify the certs */
1162
   const Botan::Path_Validation_Restrictions restrictions(false, 80);
11✔
1163
   Botan::Certificate_Store_In_Memory store;
11✔
1164

1165
   // First try with an empty store
1166
   const Botan::Path_Validation_Result result_no_issuer = Botan::x509_path_validate(user1_cert, restrictions, store);
11✔
1167
   result.test_str_eq(
11✔
1168
      "user 1 issuer not found",
1169
      result_no_issuer.result_string(),
11✔
1170
      Botan::Path_Validation_Result::status_string(Botan::Certificate_Status_Code::CERT_ISSUER_NOT_FOUND));
1171

1172
   store.add_certificate(ca.ca_certificate());
11✔
1173

1174
   Botan::Path_Validation_Result result_u1 = Botan::x509_path_validate(user1_cert, restrictions, store);
11✔
1175
   if(!result.test_is_true("user 1 validates", result_u1.successful_validation())) {
11✔
1176
      result.test_note("user 1 validation result", result_u1.result_string());
×
1177
   }
1178

1179
   Botan::Path_Validation_Result result_u2 = Botan::x509_path_validate(user2_cert, restrictions, store);
11✔
1180
   if(!result.test_is_true("user 2 validates", result_u2.successful_validation())) {
11✔
1181
      result.test_note("user 2 validation result", result_u2.result_string());
×
1182
   }
1183

1184
   const Botan::Path_Validation_Result result_self_signed =
11✔
1185
      Botan::x509_path_validate(user1_ss_cert, restrictions, store);
11✔
1186
   result.test_str_eq(
11✔
1187
      "user 1 issuer not found",
1188
      result_no_issuer.result_string(),
11✔
1189
      Botan::Path_Validation_Result::status_string(Botan::Certificate_Status_Code::CERT_ISSUER_NOT_FOUND));
1190
   store.add_crl(crl1);
11✔
1191

1192
   std::vector<Botan::CRL_Entry> revoked;
11✔
1193
   revoked.push_back(Botan::CRL_Entry(user1_cert, Botan::CRL_Code::CessationOfOperation));
22✔
1194
   revoked.push_back(Botan::CRL_Entry(user2_cert));
22✔
1195

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

1198
   store.add_crl(crl2);
11✔
1199

1200
   const std::string revoked_str =
11✔
1201
      Botan::Path_Validation_Result::status_string(Botan::Certificate_Status_Code::CERT_IS_REVOKED);
11✔
1202

1203
   result_u1 = Botan::x509_path_validate(user1_cert, restrictions, store);
11✔
1204
   result.test_str_eq("user 1 revoked", result_u1.result_string(), revoked_str);
11✔
1205

1206
   result_u2 = Botan::x509_path_validate(user2_cert, restrictions, store);
11✔
1207
   result.test_str_eq("user 1 revoked", result_u2.result_string(), revoked_str);
11✔
1208

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

1217
   store.add_crl(crl3);
11✔
1218

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

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

1225
   return result;
11✔
1226
}
44✔
1227

1228
Test::Result test_crl_entry_negative_serial() {
1✔
1229
   Test::Result result("CRL entry with negative serial matches certificate serial");
1✔
1230

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

1239
      Botan::CRL_Entry entry;
3✔
1240
      Botan::BER_Decoder dec(der);
3✔
1241
      entry.decode_from(dec);
3✔
1242
      return entry;
3✔
1243
   };
3✔
1244

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

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

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

1257
   return result;
1✔
1258
}
1✔
1259

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

1267
   Test::Result result("X509 Usage");
12✔
1268

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

1272
   /* Create the CA object */
1273
   const Botan::X509_CA ca(ca_cert, ca_key, hash_fn, rng);
12✔
1274

1275
   auto user1_key = make_a_private_key(sig_algo, rng);
12✔
1276

1277
   Botan::X509_Cert_Options opts("Test User 1/US/Botan Project/Testing");
12✔
1278
   opts.constraints = Key_Constraints::DigitalSignature;
12✔
1279

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

1282
   const Botan::X509_Certificate user1_cert =
12✔
1283
      ca.sign_request(user1_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
12✔
1284

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

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

1295
   opts.constraints = Key_Constraints(Key_Constraints::DigitalSignature | Key_Constraints::CrlSign);
12✔
1296

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

1299
   const Botan::X509_Certificate mult_usage_cert =
12✔
1300
      ca.sign_request(mult_usage_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
12✔
1301

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

1311
   opts.constraints = Key_Constraints();
12✔
1312

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

1315
   const Botan::X509_Certificate no_usage_cert =
12✔
1316
      ca.sign_request(no_usage_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
12✔
1317

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

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

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

1330
      const Botan::X509_Certificate enc_cert =
1✔
1331
         ca.sign_request(enc_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
1✔
1332

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

1337
   return result;
12✔
1338
}
24✔
1339

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

1347
   Test::Result result("X509 Self Issued");
11✔
1348

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

1353
   /* Create the CA object */
1354
   const Botan::X509_CA ca(ca_cert, ca_key, hash_fn, sig_padding, rng);
11✔
1355

1356
   auto user_key = make_a_private_key(sig_algo, rng);
11✔
1357

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

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

1366
   const Botan::X509_Certificate self_issued_cert =
11✔
1367
      ca.sign_request(self_issued_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
11✔
1368

1369
   // check that this chain can can be verified successfully
1370
   const Botan::Certificate_Store_In_Memory trusted(ca.ca_certificate());
11✔
1371

1372
   const Botan::Path_Validation_Restrictions restrictions(false, 80);
11✔
1373

1374
   const Botan::Path_Validation_Result validation_result =
11✔
1375
      Botan::x509_path_validate(self_issued_cert, restrictions, trusted);
11✔
1376

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

1379
   return result;
11✔
1380
}
22✔
1381

1382
Test::Result test_x509_uninit() {
1✔
1383
   Test::Result result("X509 object uninitialized access");
1✔
1384

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

1390
   Botan::X509_CRL crl;
1✔
1391
   result.test_throws(
1✔
1392
      "uninitialized crl access causes exception", "X509_CRL uninitialized", [&crl]() { crl.crl_number(); });
2✔
1393

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

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

1409
   return result;
1✔
1410
}
1✔
1411

1412
Test::Result test_valid_constraints(const Botan::Private_Key& key, const std::string& pk_algo) {
19✔
1413
   using Botan::Key_Constraints;
19✔
1414

1415
   Test::Result result("X509 Valid Constraints " + pk_algo);
19✔
1416

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

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

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

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

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

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

1508
   return result;
19✔
1509
}
×
1510

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

1518
      explicit String_Extension(const std::string& val) : m_contents(val) {}
11✔
1519

1520
      std::string value() const { return m_contents; }
44✔
1521

1522
      std::unique_ptr<Certificate_Extension> copy() const override {
×
1523
         return std::make_unique<String_Extension>(m_contents);
×
1524
      }
1525

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

1528
      bool should_encode() const override { return true; }
22✔
1529

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

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

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

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

1546
   private:
1547
      std::string m_contents;
1548
};
1549

1550
Test::Result test_x509_wrong_context_certificate_extensions() {
1✔
1551
   Test::Result result("X509 wrong-context certificate extensions");
1✔
1552

1553
   #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
1554
   const std::string base = "x509/wrong_context_ext/";
1✔
1555

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

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

1566
   return result;
1✔
1567
}
1✔
1568

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

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

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

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

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

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

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

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

1602
   check_crl_unusable("crl_with_unknown_critical.pem", "critical unknown CRL extension rejects CRL");
1✔
1603
   check_crl_unusable("crl_entry_with_unknown_critical.pem", "critical unknown CRL entry extension rejects CRL");
1✔
1604
   #endif
1605

1606
   return result;
2✔
1607
}
2✔
1608

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

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

1620
   /* Create the CA object */
1621
   const Botan::X509_CA ca(ca_cert, ca_key, hash_fn, sig_padding, rng);
11✔
1622

1623
   auto user_key = make_a_private_key(sig_algo, rng);
11✔
1624

1625
   Botan::X509_DN subject_dn;
11✔
1626

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

1632
   subject_dn.add_attribute(attr1, val1);
11✔
1633
   subject_dn.add_attribute(attr2, val2);
11✔
1634

1635
   const Botan::Extensions extensions;
11✔
1636

1637
   const Botan::PKCS10_Request req =
11✔
1638
      Botan::PKCS10_Request::create(*user_key, subject_dn, extensions, hash_fn, rng, sig_padding);
11✔
1639

1640
   const Botan::X509_DN& req_dn = req.subject_dn();
11✔
1641

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

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

1651
   const Botan::X509_Time not_before("100301123001Z", Botan::ASN1_Type::UtcTime);
11✔
1652
   const Botan::X509_Time not_after("300301123001Z", Botan::ASN1_Type::UtcTime);
11✔
1653

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

1656
   const Botan::X509_DN& cert_dn = cert.subject_dn();
11✔
1657

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

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

1667
   return result;
22✔
1668
}
88✔
1669

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

1677
   Test::Result result("X509 Extensions");
11✔
1678

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

1683
   /* Create the CA object */
1684
   const Botan::X509_CA ca(ca_cert, ca_key, hash_fn, sig_padding, rng);
11✔
1685

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

1691
   std::vector<Botan::Cert_Extension::CRL_Distribution_Points::Distribution_Point> dps;
11✔
1692

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

1698
      dps.emplace_back(dp);
22✔
1699
   }
22✔
1700

1701
   auto user_key = make_a_private_key(sig_algo, rng);
11✔
1702

1703
   Botan::X509_Cert_Options opts("Test User 1/US/Botan Project/Testing");
11✔
1704
   opts.constraints = Key_Constraints::DigitalSignature;
11✔
1705

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

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

1718
   result.test_is_true("Extensions::extension_set true for Key_Usage",
11✔
1719
                       self_signed_cert.v3_extensions().extension_set(ku_oid));
11✔
1720

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1816
   return result;
11✔
1817
}
55✔
1818

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

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

1849
   for(const auto& a : cases) {
72✔
1850
      Botan::X509_Cert_Options opts{a.issuer};
60✔
1851
      opts.CA_key();
60✔
1852

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

1855
      result.test_str_eq(a.issuer, Botan::hex_encode(issuer_cert.raw_issuer_dn_sha256()), a.issuer_hash);
60✔
1856
      result.test_str_eq(a.issuer, Botan::hex_encode(issuer_cert.raw_subject_dn_sha256()), a.issuer_hash);
60✔
1857

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

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

1870
   #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
1871

1872
Test::Result test_x509_tn_auth_list_extension_decode() {
1✔
1873
   /* cert with TNAuthList extension data was generated by asn1parse cfg:
1874

1875
      asn1=SEQUENCE:tn_auth_list
1876

1877
      [tn_auth_list]
1878
      spc=EXP:0,IA5:1001
1879
      range=EXP:1,SEQUENCE:TelephoneNumberRange
1880
      one=EXP:2,IA5:333
1881

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

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

1894
   using Botan::Cert_Extension::TNAuthList;
1✔
1895

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

1898
   const auto& tn_entries = tn_auth_list->entries();
1✔
1899

1900
   result.test_not_null("cert has TNAuthList extension", tn_auth_list);
1✔
1901

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

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

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

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

1923
   result.end_timer();
1✔
1924
   return result;
2✔
1925
}
1✔
1926

1927
   #endif
1928

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

1953
class X509_Cert_Unit_Tests final : public Test {
1✔
1954
   public:
1955
      std::vector<Test::Result> run() override {
1✔
1956
         std::vector<Test::Result> results;
1✔
1957

1958
         auto& rng = this->rng();
1✔
1959

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

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

1979
            std::string hash = "SHA-256";
12✔
1980

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

1991
            auto key = make_a_private_key(algo, rng);
12✔
1992

1993
            if(key == nullptr) {
12✔
1994
               continue;
×
1995
            }
1996

1997
            results.push_back(test_hashes(*key, hash, rng));
24✔
1998
            results.push_back(test_valid_constraints(*key, algo));
24✔
1999

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

2008
            for(const auto& padding_scheme : get_sig_paddings(algo, hash)) {
23✔
2009
               Test::Result cert_result("X509 Unit");
11✔
2010

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

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

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

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

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

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

2058
         for(const std::string& algo : enc_algos) {
8✔
2059
            auto key = make_a_private_key(algo, rng);
7✔
2060

2061
            if(key) {
7✔
2062
               results.push_back(test_valid_constraints(*key, algo));
14✔
2063
            }
2064
         }
7✔
2065

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

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

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

2104
         return results;
1✔
2105
      }
13✔
2106
};
2107

2108
BOTAN_REGISTER_TEST("x509", "x509_unit", X509_Cert_Unit_Tests);
2109

2110
#endif
2111

2112
}  // namespace
2113

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