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

randombit / botan / 24648292556

19 Apr 2026 10:53PM UTC coverage: 89.474% (+0.03%) from 89.442%
24648292556

push

github

web-flow
Merge pull request #5536 from randombit/jack/x509-misc

Various PKIX optimizations and bug fixes

106453 of 118977 relevant lines covered (89.47%)

11452293.24 hits per line

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

94.1
/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/rng.h>
18
   #include <botan/x509_ca.h>
19
   #include <botan/x509_ext.h>
20
   #include <botan/x509path.h>
21
   #include <botan/x509self.h>
22
   #include <botan/internal/calendar.h>
23
   #include <algorithm>
24

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

30
namespace Botan_Tests {
31

32
namespace {
33

34
#if defined(BOTAN_HAS_X509_CERTIFICATES)
35

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

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

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

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

52
   opts.CA_key(1);
112✔
53

54
   return opts;
112✔
55
}
×
56

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

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

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

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

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

76
   return opts;
37✔
77
}
×
78

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

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

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

89
   return opts;
11✔
90
}
×
91

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

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

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

104
   return opts;
55✔
105
}
×
106

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

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

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

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

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

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

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

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

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

207
   return result;
1✔
208
}
1✔
209

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

213
   Botan::Extensions extn;
1✔
214

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

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

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

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

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

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

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

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

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

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

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

248
   return result;
2✔
249
}
2✔
250

251
Test::Result test_x509_dates() {
1✔
252
   Test::Result result("X509 Time");
1✔
253

254
   Botan::X509_Time time;
1✔
255
   result.test_is_true("unset time not set", !time.time_is_set());
1✔
256
   time = Botan::X509_Time("080201182200Z", Botan::ASN1_Type::UtcTime);
1✔
257
   result.test_is_true("time set after construction", time.time_is_set());
1✔
258
   result.test_str_eq("time readable_string", time.readable_string(), "2008/02/01 18:22:00 UTC");
1✔
259

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

263
   time = Botan::X509_Time("200305100350Z");
1✔
264
   result.test_str_eq(
1✔
265
      "UTC_OR_GENERALIZED_TIME from UTC_TIME readable_string", time.readable_string(), "2020/03/05 10:03:50 UTC");
1✔
266

267
   time = Botan::X509_Time("20200305100350Z");
1✔
268
   result.test_str_eq("UTC_OR_GENERALIZED_TIME from GENERALIZED_TIME readable_string",
1✔
269
                      time.readable_string(),
1✔
270
                      "2020/03/05 10:03:50 UTC");
271

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

275
   // Dates that are valid per X.500 but rejected as unsupported
276
   const std::string valid_but_unsup[]{
1✔
277
      "0802010000-0000",
278
      "0802011724+0000",
279
      "0406142334-0500",
280
      "9906142334+0500",
281
      "0006142334-0530",
282
      "0006142334+0530",
283

284
      "080201000000-0000",
285
      "080201172412+0000",
286
      "040614233433-0500",
287
      "990614233444+0500",
288
      "000614233455-0530",
289
      "000614233455+0530",
290
   };
13✔
291

292
   // valid length 13
293
   const std::string valid_utc[]{
1✔
294
      "080201000000Z",
295
      "080201172412Z",
296
      "040614233433Z",
297
      "990614233444Z",
298
      "000614233455Z",
299
   };
6✔
300

301
   const std::string invalid_utc[]{
1✔
302
      "",
303
      " ",
304
      "2008`02-01",
305
      "9999-02-01",
306
      "2000-02-01 17",
307
      "999921",
308

309
      // No seconds
310
      "0802010000Z",
311
      "0802011724Z",
312
      "0406142334Z",
313
      "9906142334Z",
314
      "0006142334Z",
315

316
      // valid length 13 -> range check
317
      "080201000061Z",  // seconds too big (61)
318
      "080201000060Z",  // seconds too big (60, leap seconds not covered by the standard)
319
      "0802010000-1Z",  // seconds too small (-1)
320
      "080201006000Z",  // minutes too big (60)
321
      "080201240000Z",  // hours too big (24:00)
322

323
      // valid length 13 -> invalid numbers
324
      "08020123112 Z",
325
      "08020123112!Z",
326
      "08020123112,Z",
327
      "08020123112\nZ",
328
      "080201232 33Z",
329
      "080201232!33Z",
330
      "080201232,33Z",
331
      "080201232\n33Z",
332
      "0802012 3344Z",
333
      "0802012!3344Z",
334
      "0802012,3344Z",
335
      "08022\n334455Z",
336
      "08022 334455Z",
337
      "08022!334455Z",
338
      "08022,334455Z",
339
      "08022\n334455Z",
340
      "082 33445511Z",
341
      "082!33445511Z",
342
      "082,33445511Z",
343
      "082\n33445511Z",
344
      "2 2211221122Z",
345
      "2!2211221122Z",
346
      "2,2211221122Z",
347
      "2\n2211221122Z",
348

349
      // wrong time zone
350
      "080201000000",
351
      "080201000000z",
352

353
      // Fractional seconds
354
      "170217180154.001Z",
355

356
      // Timezone offset
357
      "170217180154+0100",
358

359
      // Extra digits
360
      "17021718015400Z",
361

362
      // Non-digits
363
      "17021718015aZ",
364

365
      // Trailing garbage
366
      "170217180154Zlongtrailinggarbage",
367

368
      // Swapped type
369
      "20170217180154Z",
370
   };
49✔
371

372
   // valid length 15
373
   const std::string valid_generalized_time[]{
1✔
374
      "20000305100350Z",
375
   };
2✔
376

377
   const std::string invalid_generalized[]{
1✔
378
      // No trailing Z
379
      "20000305100350",
380

381
      // No seconds
382
      "200003051003Z",
383

384
      // Fractional seconds
385
      "20000305100350.001Z",
386

387
      // Timezone offset
388
      "20170217180154+0100",
389

390
      // Extra digits
391
      "2017021718015400Z",
392

393
      // Non-digits
394
      "2017021718015aZ",
395

396
      // Trailing garbage
397
      "20170217180154Zlongtrailinggarbage",
398

399
      // Swapped type
400
      "170217180154Z",
401
   };
9✔
402

403
   for(const auto& v : valid_but_unsup) {
13✔
404
      result.test_throws("valid but unsupported", [v]() { const Botan::X509_Time t(v, Botan::ASN1_Type::UtcTime); });
60✔
405
   }
406

407
   for(const auto& v : valid_utc) {
6✔
408
      const Botan::X509_Time t(v, Botan::ASN1_Type::UtcTime);
5✔
409
   }
5✔
410

411
   for(const auto& v : valid_generalized_time) {
2✔
412
      const Botan::X509_Time t(v, Botan::ASN1_Type::GeneralizedTime);
1✔
413
   }
1✔
414

415
   for(const auto& v : invalid_utc) {
49✔
416
      result.test_throws("invalid", [v]() { const Botan::X509_Time t(v, Botan::ASN1_Type::UtcTime); });
240✔
417
   }
418

419
   for(const auto& v : invalid_generalized) {
9✔
420
      result.test_throws("invalid", [v]() { const Botan::X509_Time t(v, Botan::ASN1_Type::GeneralizedTime); });
40✔
421
   }
422

423
   return result;
1✔
424
}
80✔
425

426
Test::Result test_x509_encode_authority_info_access_extension() {
1✔
427
   Test::Result result("X509 with encoded PKIX.AuthorityInformationAccess extension");
1✔
428

429
   #if defined(BOTAN_HAS_RSA) && defined(BOTAN_HAS_EMSA_PKCS1)
430
   auto rng = Test::new_rng(__func__);
1✔
431

432
   const std::string sig_algo{"RSA"};
1✔
433
   const std::string hash_fn{"SHA-256"};
1✔
434
   const std::string padding_method{"PKCS1v15(SHA-256)"};
1✔
435

436
   // CA Issuer information
437
   const std::vector<std::string> ca_issuers = {
1✔
438
      "http://www.d-trust.net/cgi-bin/Bdrive_Test_CA_1-2_2017.crt",
439
      "ldap://directory.d-trust.net/CN=Bdrive%20Test%20CA%201-2%202017,O=Bundesdruckerei%20GmbH,C=DE?cACertificate?base?"};
1✔
440

441
   // OCSP
442
   const std::string_view ocsp_uri{"http://staging.ocsp.d-trust.net"};
1✔
443

444
   // create a CA
445
   auto ca_key = make_a_private_key(sig_algo, *rng);
1✔
446
   result.require("CA key", ca_key != nullptr);
1✔
447
   const auto ca_cert = Botan::X509::create_self_signed_cert(ca_opts(), *ca_key, hash_fn, *rng);
1✔
448
   const Botan::X509_CA ca(ca_cert, *ca_key, hash_fn, padding_method, *rng);
1✔
449

450
   // create a certificate with only caIssuer information
451
   auto key = make_a_private_key(sig_algo, *rng);
1✔
452

453
   Botan::X509_Cert_Options opts1 = req_opts1(sig_algo);
1✔
454
   opts1.extensions.add(std::make_unique<Botan::Cert_Extension::Authority_Information_Access>("", ca_issuers));
2✔
455

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

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

460
   if(!result.test_sz_eq("number of ca_issuers URIs", cert.ca_issuers().size(), 2)) {
1✔
461
      return result;
462
   }
463

464
   for(const auto& ca_issuer : cert.ca_issuers()) {
3✔
465
      result.test_is_true("CA issuer URI present in certificate",
4✔
466
                          std::ranges::find(ca_issuers, ca_issuer) != ca_issuers.end());
4✔
467
   }
1✔
468

469
   result.test_is_true("no OCSP url available", cert.ocsp_responder().empty());
1✔
470

471
   // create a certificate with only OCSP URI information
472
   Botan::X509_Cert_Options opts2 = req_opts1(sig_algo);
1✔
473
   opts2.extensions.add(std::make_unique<Botan::Cert_Extension::Authority_Information_Access>(ocsp_uri));
2✔
474

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

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

479
   result.test_is_true("OCSP URI available", !cert.ocsp_responder().empty());
1✔
480
   result.test_is_true("no CA Issuer URI available", cert.ca_issuers().empty());
1✔
481
   result.test_str_eq("OCSP responder URI matches", cert.ocsp_responder(), std::string(ocsp_uri));
2✔
482

483
   // create a certificate with OCSP URI and CA Issuer information
484
   Botan::X509_Cert_Options opts3 = req_opts1(sig_algo);
1✔
485
   opts3.extensions.add(std::make_unique<Botan::Cert_Extension::Authority_Information_Access>(ocsp_uri, ca_issuers));
2✔
486

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

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

491
   result.test_is_true("OCSP URI available", !cert.ocsp_responder().empty());
1✔
492
   result.test_is_true("CA Issuer URI available", !cert.ca_issuers().empty());
1✔
493

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

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

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

503
   const auto* aia_ext =
1✔
504
      cert.v3_extensions().get_extension_object_as<Botan::Cert_Extension::Authority_Information_Access>();
1✔
505
   result.test_is_true("AIA extension present", aia_ext != nullptr);
1✔
506

507
   const auto ocsp_responders = aia_ext->ocsp_responders();
1✔
508
   result.test_sz_eq("number of OCSP responder URIs", ocsp_responders.size(), 2);
1✔
509
   result.test_str_eq("First OCSP responder URI matches", ocsp_responders[0], "http://ocsp.example.com");
1✔
510
   result.test_str_eq("Second OCSP responder URI matches", ocsp_responders[1], "http://backup-ocsp.example.com");
1✔
511

512
   const auto cert_ocsp_responders = cert.ocsp_responders();
1✔
513
   result.test_sz_eq("Certificate: number of OCSP responder URIs", cert_ocsp_responders.size(), 2);
1✔
514
   result.test_str_eq(
1✔
515
      "Certificate: First OCSP responder URI matches", cert_ocsp_responders[0], "http://ocsp.example.com");
1✔
516
   result.test_str_eq(
1✔
517
      "Certificate: Second OCSP responder URI matches", cert_ocsp_responders[1], "http://backup-ocsp.example.com");
1✔
518
   #endif
519

520
   return result;
1✔
521
}
4✔
522

523
   #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
524

525
Test::Result test_crl_dn_name() {
1✔
526
   Test::Result result("CRL DN name");
1✔
527

528
      // See GH #1252
529

530
      #if defined(BOTAN_HAS_RSA) && defined(BOTAN_HAS_EMSA_PKCS1)
531
   auto rng = Test::new_rng(__func__);
1✔
532

533
   const Botan::OID dc_oid("0.9.2342.19200300.100.1.25");
1✔
534

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

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

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

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

545
   result.test_is_true("contains DC component", crl.issuer_dn().get_attributes().count(dc_oid) == 1);
2✔
546
      #endif
547

548
   return result;
2✔
549
}
3✔
550

551
Test::Result test_rdn_multielement_set_name() {
1✔
552
   Test::Result result("DN with multiple elements in RDN");
1✔
553

554
   // GH #2611
555

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

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

561
   return result;
1✔
562
}
1✔
563

564
Test::Result test_rsa_oaep() {
1✔
565
   Test::Result result("RSA OAEP decoding");
1✔
566

567
      #if defined(BOTAN_HAS_RSA)
568
   const Botan::X509_Certificate cert(Test::data_file("x509/misc/rsa_oaep.pem"));
2✔
569

570
   auto public_key = cert.subject_public_key();
1✔
571
   result.test_not_null("Decoding RSA-OAEP worked", public_key.get());
1✔
572
   const auto& pk_info = cert.subject_public_key_algo();
1✔
573

574
   result.test_str_eq("RSA-OAEP OID", pk_info.oid().to_string(), Botan::OID::from_string("RSA/OAEP").to_string());
1✔
575
      #endif
576

577
   return result;
2✔
578
}
1✔
579

580
Test::Result test_x509_decode_list() {
1✔
581
   Test::Result result("X509_Certificate list decode");
1✔
582

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

585
   Botan::BER_Decoder dec(input);
1✔
586
   std::vector<Botan::X509_Certificate> certs;
1✔
587
   dec.decode_list(certs);
1✔
588

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

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

594
   return result;
1✔
595
}
1✔
596

597
Test::Result test_x509_utf8() {
1✔
598
   Test::Result result("X509 with UTF-8 encoded fields");
1✔
599

600
   try {
1✔
601
      const Botan::X509_Certificate utf8_cert(Test::data_file("x509/misc/contains_utf8string.pem"));
2✔
602

603
      // UTF-8 encoded fields of test certificate (contains cyrillic letters)
604
      const std::string organization =
1✔
605
         "\xD0\x9C\xD0\xBE\xD1\x8F\x20\xD0\xBA\xD0\xBE\xD0"
606
         "\xBC\xD0\xBF\xD0\xB0\xD0\xBD\xD0\xB8\xD1\x8F";
1✔
607
      const std::string organization_unit =
1✔
608
         "\xD0\x9C\xD0\xBE\xD1\x91\x20\xD0\xBF\xD0\xBE\xD0\xB4\xD1\x80\xD0\xB0"
609
         "\xD0\xB7\xD0\xB4\xD0\xB5\xD0\xBB\xD0\xB5\xD0\xBD\xD0\xB8\xD0\xB5";
1✔
610
      const std::string common_name =
1✔
611
         "\xD0\x9E\xD0\xBF\xD0\xB8\xD1\x81\xD0\xB0\xD0\xBD\xD0\xB8"
612
         "\xD0\xB5\x20\xD1\x81\xD0\xB0\xD0\xB9\xD1\x82\xD0\xB0";
1✔
613
      const std::string location = "\xD0\x9C\xD0\xBE\xD1\x81\xD0\xBA\xD0\xB2\xD0\xB0";
1✔
614

615
      const Botan::X509_DN& issuer_dn = utf8_cert.issuer_dn();
1✔
616

617
      result.test_str_eq("O", issuer_dn.get_first_attribute("O"), organization);
1✔
618
      result.test_str_eq("OU", issuer_dn.get_first_attribute("OU"), organization_unit);
1✔
619
      result.test_str_eq("CN", issuer_dn.get_first_attribute("CN"), common_name);
1✔
620
      result.test_str_eq("L", issuer_dn.get_first_attribute("L"), location);
1✔
621
   } catch(const Botan::Decoding_Error& ex) {
1✔
622
      result.test_failure(ex.what());
×
623
   }
×
624

625
   return result;
1✔
626
}
×
627

628
Test::Result test_x509_any_key_extended_usage() {
1✔
629
   using Botan::Key_Constraints;
1✔
630
   using Botan::Usage_Type;
1✔
631

632
   Test::Result result("X509 with X509v3.AnyExtendedKeyUsage");
1✔
633
   try {
1✔
634
      const Botan::X509_Certificate any_eku_cert(Test::data_file("x509/misc/contains_any_extended_key_usage.pem"));
2✔
635

636
      result.test_is_true("is CA cert", any_eku_cert.is_CA_cert());
1✔
637
      result.test_is_true("DigitalSignature is allowed", any_eku_cert.allowed_usage(Key_Constraints::DigitalSignature));
1✔
638
      result.test_is_true("CrlSign is allowed", any_eku_cert.allowed_usage(Key_Constraints::CrlSign));
1✔
639
      result.test_is_true("OCSP responder is allowed", any_eku_cert.allowed_usage(Usage_Type::OCSP_RESPONDER));
1✔
640
   } catch(const Botan::Decoding_Error& ex) {
1✔
641
      result.test_failure(ex.what());
×
642
   }
×
643
   return result;
1✔
644
}
×
645

646
Test::Result test_x509_bmpstring() {
1✔
647
   Test::Result result("X509 with UCS-2 (BMPString) encoded fields");
1✔
648

649
   try {
1✔
650
      const Botan::X509_Certificate ucs2_cert(Test::data_file("x509/misc/contains_bmpstring.pem"));
2✔
651

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

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

660
      const Botan::X509_DN& issuer_dn = ucs2_cert.issuer_dn();
1✔
661

662
      result.test_str_eq("O", issuer_dn.get_first_attribute("O"), organization);
1✔
663
      result.test_str_eq("CN", issuer_dn.get_first_attribute("CN"), common_name);
1✔
664
      result.test_str_eq("L", issuer_dn.get_first_attribute("L"), location);
1✔
665
   } catch(const Botan::Decoding_Error& ex) {
1✔
666
      result.test_failure(ex.what());
×
667
   }
×
668

669
   return result;
1✔
670
}
×
671

672
Test::Result test_x509_teletex() {
1✔
673
   Test::Result result("X509 with TeletexString encoded fields");
1✔
674

675
   try {
1✔
676
      const Botan::X509_Certificate teletex_cert(Test::data_file("x509/misc/teletex_dn.der"));
2✔
677

678
      const Botan::X509_DN& issuer_dn = teletex_cert.issuer_dn();
1✔
679

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

682
      result.test_str_eq("O", issuer_dn.get_first_attribute("O"), "neam CA");
1✔
683
      result.test_str_eq("CN", issuer_dn.get_first_attribute("CN"), common_name);
1✔
684
   } catch(const Botan::Decoding_Error& ex) {
1✔
685
      result.test_failure(ex.what());
×
686
   }
×
687

688
   return result;
1✔
689
}
×
690

691
Test::Result test_x509_authority_info_access_extension() {
1✔
692
   Test::Result result("X509 with PKIX.AuthorityInformationAccess extension");
1✔
693

694
   // contains no AIA extension
695
   const Botan::X509_Certificate no_aia_cert(Test::data_file("x509/misc/contains_utf8string.pem"));
2✔
696

697
   result.test_sz_eq("number of ca_issuers URLs", no_aia_cert.ca_issuers().size(), 0);
1✔
698
   result.test_str_eq("CA issuer URL matches", no_aia_cert.ocsp_responder(), "");
1✔
699

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

703
   const auto ca_issuers = aia_cert.ca_issuers();
1✔
704

705
   result.test_sz_eq("number of ca_issuers URLs", ca_issuers.size(), 1);
1✔
706
   if(result.tests_failed() > 0) {
1✔
707
      return result;
708
   }
709

710
   result.test_str_eq("CA issuer URL matches", ca_issuers[0], "http://gp.symcb.com/gp.crt");
1✔
711
   result.test_str_eq("OCSP responder URL matches", aia_cert.ocsp_responder(), "http://gp.symcd.com");
1✔
712

713
   // contains AIA extension with 2 CA issuer URL and 1 OCSP responder
714
   const Botan::X509_Certificate aia_cert_2ca(
1✔
715
      Test::data_file("x509/misc/contains_authority_info_access_with_two_ca_issuers.pem"));
2✔
716

717
   const auto ca_issuers2 = aia_cert_2ca.ca_issuers();
1✔
718

719
   result.test_sz_eq("number of ca_issuers URLs", ca_issuers2.size(), 2);
1✔
720
   if(result.tests_failed() > 0) {
1✔
721
      return result;
722
   }
723

724
   result.test_str_eq(
1✔
725
      "CA issuer URL matches", ca_issuers2[0], "http://www.d-trust.net/cgi-bin/Bdrive_Test_CA_1-2_2017.crt");
1✔
726
   result.test_str_eq(
1✔
727
      "CA issuer URL matches",
728
      ca_issuers2[1],
1✔
729
      "ldap://directory.d-trust.net/CN=Bdrive%20Test%20CA%201-2%202017,O=Bundesdruckerei%20GmbH,C=DE?cACertificate?base?");
730
   result.test_str_eq("OCSP responder URL matches", aia_cert_2ca.ocsp_responder(), "http://staging.ocsp.d-trust.net");
1✔
731

732
   // contains AIA extension with multiple OCSP responders
733
   const Botan::X509_Certificate aia_cert_multi_ocsp(
1✔
734
      Test::data_file("x509/misc/contains_multiple_ocsp_responders.pem"));
2✔
735

736
   const auto& ocsp_responders_multi = aia_cert_multi_ocsp.ocsp_responders();
1✔
737
   result.test_sz_eq("number of OCSP responders", ocsp_responders_multi.size(), 3);
1✔
738
   result.test_str_eq("First OCSP responder URL matches", ocsp_responders_multi[0], "http://ocsp1.example.com");
1✔
739
   result.test_str_eq("Second OCSP responder URL matches", ocsp_responders_multi[1], "http://ocsp2.example.com");
1✔
740
   result.test_str_eq("Third OCSP responder URL matches", ocsp_responders_multi[2], "http://ocsp3.example.com");
1✔
741
   result.test_is_true("no CA Issuer URI available", aia_cert_multi_ocsp.ca_issuers().empty());
1✔
742

743
   return result;
1✔
744
}
1✔
745

746
Test::Result test_parse_rsa_pss_cert() {
1✔
747
   Test::Result result("X509 RSA-PSS certificate");
1✔
748

749
   // See https://github.com/randombit/botan/issues/3019 for background
750

751
   try {
1✔
752
      const Botan::X509_Certificate rsa_pss(Test::data_file("x509/misc/rsa_pss.pem"));
2✔
753
      result.test_success("Was able to parse RSA-PSS certificate signed with ECDSA");
1✔
754
   } catch(Botan::Exception& e) {
1✔
755
      result.test_failure("Parsing failed", e.what());
×
756
   }
×
757

758
   return result;
1✔
759
}
×
760

761
Test::Result test_verify_gost2012_cert() {
1✔
762
   Test::Result result("X509 GOST-2012 certificates");
1✔
763

764
      #if defined(BOTAN_HAS_GOST_34_10_2012) && defined(BOTAN_HAS_STREEBOG)
765
   try {
1✔
766
      if(Botan::EC_Group::supports_named_group("gost_256A")) {
1✔
767
         const Botan::X509_Certificate root_cert(Test::data_file("x509/gost/gost_root.pem"));
2✔
768
         const Botan::X509_Certificate root_int(Test::data_file("x509/gost/gost_int.pem"));
2✔
769

770
         Botan::Certificate_Store_In_Memory trusted;
1✔
771
         trusted.add_certificate(root_cert);
1✔
772

773
         const Botan::Path_Validation_Restrictions restrictions(false, 128, false, {"Streebog-256"});
2✔
774
         const Botan::Path_Validation_Result validation_result =
1✔
775
            Botan::x509_path_validate(root_int, restrictions, trusted);
1✔
776

777
         result.test_is_true("GOST certificate validates", validation_result.successful_validation());
1✔
778
      }
1✔
779
   } catch(const Botan::Decoding_Error& e) {
×
780
      result.test_failure(e.what());
×
781
   }
×
782
      #endif
783

784
   return result;
1✔
785
}
×
786

787
   /*
788
 * @brief checks the configurability of the RSA-PSS signature scheme
789
 *
790
 * For the other algorithms than RSA, only one padding is supported right now.
791
 */
792
      #if defined(BOTAN_HAS_EMSA_PKCS1) && defined(BOTAN_HAS_EMSA_PSSR) && defined(BOTAN_HAS_RSA)
793
Test::Result test_padding_config() {
1✔
794
   Test::Result test_result("X509 Padding Config");
1✔
795

796
   auto rng = Test::new_rng(__func__);
1✔
797

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

801
   // Create X509 CA certificate; PKCS1v15 is used for signing by default
802
   Botan::X509_Cert_Options opt("TEST CA");
1✔
803
   opt.CA_key();
1✔
804

805
   const Botan::X509_Certificate ca_cert_def = Botan::X509::create_self_signed_cert(opt, (*sk), "SHA-512", *rng);
1✔
806
   test_result.test_str_eq("CA certificate signature algorithm (default)",
1✔
807
                           ca_cert_def.signature_algorithm().oid().to_formatted_string(),
1✔
808
                           "RSA/PKCS1v15(SHA-512)");
809

810
   // Create X509 CA certificate; RSA-PSS is explicitly set
811
   opt.set_padding_scheme("PSSR");
1✔
812
   const Botan::X509_Certificate ca_cert_exp = Botan::X509::create_self_signed_cert(opt, (*sk), "SHA-512", *rng);
1✔
813
   test_result.test_str_eq("CA certificate signature algorithm (explicit)",
1✔
814
                           ca_cert_exp.signature_algorithm().oid().to_formatted_string(),
1✔
815
                           "RSA/PSS");
816

817
         #if defined(BOTAN_HAS_EMSA_X931)
818
   // Try to set a padding scheme that is not supported for signing with the given key type
819
   opt.set_padding_scheme("X9.31");
1✔
820
   try {
1✔
821
      const Botan::X509_Certificate ca_cert_wrong = Botan::X509::create_self_signed_cert(opt, (*sk), "SHA-512", *rng);
1✔
822
      test_result.test_failure("Could build CA cert with invalid encoding scheme X9.31 for key type " +
×
823
                               sk->algo_name());
×
824
   } catch(const Botan::Invalid_Argument& e) {
1✔
825
      test_result.test_str_eq("Build CA certificate with invalid encoding scheme X9.31 for key type " + sk->algo_name(),
3✔
826
                              e.what(),
1✔
827
                              "Signatures using RSA/X9.31(SHA-512) are not supported");
828
   }
1✔
829
         #endif
830

831
   test_result.test_str_eq("CA certificate signature algorithm (explicit)",
1✔
832
                           ca_cert_exp.signature_algorithm().oid().to_formatted_string(),
1✔
833
                           "RSA/PSS");
834

835
   const Botan::X509_Time not_before = from_date(-1, 1, 1);
1✔
836
   const Botan::X509_Time not_after = from_date(2, 1, 2);
1✔
837

838
   // Prepare a signing request for the end certificate
839
   Botan::X509_Cert_Options req_opt("endpoint");
1✔
840
   req_opt.set_padding_scheme("PSS(SHA-512,MGF1,64)");
1✔
841
   const Botan::PKCS10_Request end_req = Botan::X509::create_cert_req(req_opt, (*sk), "SHA-512", *rng);
1✔
842
   test_result.test_str_eq(
1✔
843
      "Certificate request signature algorithm", end_req.signature_algorithm().oid().to_formatted_string(), "RSA/PSS");
1✔
844

845
   // Create X509 CA object: will fail as the chosen hash functions differ
846
   try {
1✔
847
      const Botan::X509_CA ca_fail(ca_cert_exp, (*sk), "SHA-512", "PSS(SHA-256)", *rng);
1✔
848
      test_result.test_failure("Configured conflicting hash functions for CA");
×
849
   } catch(const Botan::Invalid_Argument& e) {
1✔
850
      test_result.test_str_eq(
1✔
851
         "Configured conflicting hash functions for CA",
852
         e.what(),
1✔
853
         "Specified hash function SHA-512 is incompatible with RSA chose hash function SHA-256 with user specified padding PSS(SHA-256)");
854
   }
1✔
855

856
   // Create X509 CA object: its signer will use the padding scheme from the CA certificate, i.e. PKCS1v15
857
   const Botan::X509_CA ca_def(ca_cert_def, (*sk), "SHA-512", *rng);
1✔
858
   const Botan::X509_Certificate end_cert_pkcs1 = ca_def.sign_request(end_req, *rng, not_before, not_after);
1✔
859
   test_result.test_str_eq("End certificate signature algorithm",
1✔
860
                           end_cert_pkcs1.signature_algorithm().oid().to_formatted_string(),
1✔
861
                           "RSA/PKCS1v15(SHA-512)");
862

863
   // Create X509 CA object: its signer will use the explicitly configured padding scheme, which is different from the CA certificate's scheme
864
   const Botan::X509_CA ca_diff(ca_cert_def, (*sk), "SHA-512", "PSS", *rng);
1✔
865
   const Botan::X509_Certificate end_cert_diff_pss = ca_diff.sign_request(end_req, *rng, not_before, not_after);
1✔
866
   test_result.test_str_eq("End certificate signature algorithm",
1✔
867
                           end_cert_diff_pss.signature_algorithm().oid().to_formatted_string(),
1✔
868
                           "RSA/PSS");
869

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

876
   // Check CRL signature algorithm
877
   const Botan::X509_CRL crl = ca_exp.new_crl(*rng);
1✔
878
   test_result.test_str_eq("CRL signature algorithm", crl.signature_algorithm().oid().to_formatted_string(), "RSA/PSS");
1✔
879

880
   // sanity check for verification, the heavy lifting is done in the other unit tests
881
   const Botan::Certificate_Store_In_Memory trusted(ca_exp.ca_certificate());
1✔
882
   const Botan::Path_Validation_Restrictions restrictions(false, 80);
2✔
883
   const Botan::Path_Validation_Result validation_result =
1✔
884
      Botan::x509_path_validate(end_cert_pss, restrictions, trusted);
1✔
885
   test_result.test_is_true("PSS signed certificate validates", validation_result.successful_validation());
1✔
886

887
   return test_result;
2✔
888
}
3✔
889
      #endif
890

891
   #endif
892

893
Test::Result test_pkcs10_ext(const Botan::Private_Key& key,
11✔
894
                             const std::string& sig_padding,
895
                             const std::string& hash_fn,
896
                             Botan::RandomNumberGenerator& rng) {
897
   Test::Result result("PKCS10 extensions");
11✔
898

899
   Botan::X509_Cert_Options opts;
11✔
900

901
   opts.dns = "main.example.org";
11✔
902
   opts.more_dns.push_back("more1.example.org");
22✔
903
   opts.more_dns.push_back("more2.example.org");
22✔
904

905
   opts.padding_scheme = sig_padding;
11✔
906

907
   Botan::AlternativeName alt_name;
11✔
908
   alt_name.add_attribute("DNS", "bonus.example.org");
11✔
909

910
   Botan::X509_DN alt_dn;
11✔
911
   alt_dn.add_attribute("X520.CommonName", "alt_cn");
11✔
912
   alt_dn.add_attribute("X520.Organization", "testing");
11✔
913
   alt_name.add_dn(alt_dn);
11✔
914

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

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

919
   const auto alt_dns_names = req.subject_alt_name().get_attribute("DNS");
11✔
920

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

923
   if(alt_dns_names.size() == 4) {
11✔
924
      result.test_str_eq("Expected DNS name 1", alt_dns_names.at(0), "bonus.example.org");
11✔
925
      result.test_str_eq("Expected DNS name 2", alt_dns_names.at(1), "main.example.org");
11✔
926
      result.test_str_eq("Expected DNS name 3", alt_dns_names.at(2), "more1.example.org");
11✔
927
      result.test_str_eq("Expected DNS name 3", alt_dns_names.at(3), "more2.example.org");
11✔
928
   }
929

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

933
   return result;
11✔
934
}
22✔
935

936
Test::Result test_x509_cert(const Botan::Private_Key& ca_key,
11✔
937
                            const std::string& sig_algo,
938
                            const std::string& sig_padding,
939
                            const std::string& hash_fn,
940
                            Botan::RandomNumberGenerator& rng) {
941
   Test::Result result("X509 Unit");
11✔
942

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

946
   {
11✔
947
      result.test_is_true("ca key usage cert", ca_cert.constraints().includes(Botan::Key_Constraints::KeyCertSign));
11✔
948
      result.test_is_true("ca key usage crl", ca_cert.constraints().includes(Botan::Key_Constraints::CrlSign));
11✔
949
   }
950

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

954
   const Botan::PKCS10_Request user1_req =
11✔
955
      Botan::X509::create_cert_req(req_opts1(sig_algo, sig_padding), *user1_key, hash_fn, rng);
11✔
956

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

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

962
   const Botan::PKCS10_Request user2_req =
11✔
963
      Botan::X509::create_cert_req(req_opts2(sig_padding), *user2_key, hash_fn, rng);
11✔
964

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

968
   const Botan::PKCS10_Request user3_req =
11✔
969
      Botan::X509::create_cert_req(req_opts3(sig_padding), *user3_key, hash_fn, rng);
11✔
970

971
   /* Create the CA object */
972
   const Botan::X509_CA ca(ca_cert, ca_key, hash_fn, sig_padding, rng);
11✔
973

974
   const BigInt user1_serial(99);
11✔
975

976
   /* Sign the requests to create the certs */
977
   const Botan::X509_Certificate user1_cert =
11✔
978
      ca.sign_request(user1_req, rng, user1_serial, from_date(-1, 01, 01), from_date(2, 01, 01));
11✔
979

980
   result.test_sz_eq("User1 serial size matches expected", user1_cert.serial_number().size(), 1);
11✔
981
   result.test_sz_eq("User1 serial matches expected", user1_cert.serial_number().at(0), size_t(99));
11✔
982

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

987
   const Botan::X509_Certificate user3_cert =
11✔
988
      ca.sign_request(user3_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
11✔
989

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

994
   {
11✔
995
      auto constraints = req_opts1(sig_algo).constraints;
11✔
996
      result.test_is_true("user1 key usage", user1_cert.constraints().includes(constraints));
11✔
997
   }
998

999
   /* Copy, assign and compare */
1000
   Botan::X509_Certificate user1_cert_copy(user1_cert);
11✔
1001
   result.test_is_true("certificate copy", user1_cert == user1_cert_copy);
11✔
1002

1003
   user1_cert_copy = user2_cert;
11✔
1004
   result.test_is_true("certificate assignment", user2_cert == user1_cert_copy);
11✔
1005

1006
   const Botan::X509_Certificate user1_cert_differ =
11✔
1007
      ca.sign_request(user1_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
11✔
1008

1009
   result.test_is_false("certificate differs", user1_cert == user1_cert_differ);
11✔
1010

1011
   /* Get cert data */
1012
   result.test_sz_eq("x509 version", user1_cert.x509_version(), size_t(3));
11✔
1013

1014
   const Botan::X509_DN& user1_issuer_dn = user1_cert.issuer_dn();
11✔
1015
   result.test_str_eq("issuer info CN", user1_issuer_dn.get_first_attribute("CN"), ca_opts().common_name);
11✔
1016
   result.test_str_eq("issuer info Country", user1_issuer_dn.get_first_attribute("C"), ca_opts().country);
11✔
1017
   result.test_str_eq("issuer info Orga", user1_issuer_dn.get_first_attribute("O"), ca_opts().organization);
11✔
1018
   result.test_str_eq("issuer info OrgaUnit", user1_issuer_dn.get_first_attribute("OU"), ca_opts().org_unit);
11✔
1019

1020
   const Botan::X509_DN& user3_subject_dn = user3_cert.subject_dn();
11✔
1021
   result.test_sz_eq("subject OrgaUnit count",
11✔
1022
                     user3_subject_dn.get_attribute("OU").size(),
22✔
1023
                     req_opts3(sig_algo).more_org_units.size() + 1);
22✔
1024
   result.test_str_eq(
22✔
1025
      "subject OrgaUnit #2", user3_subject_dn.get_attribute("OU").at(1), req_opts3(sig_algo).more_org_units.at(0));
33✔
1026

1027
   const Botan::AlternativeName& user1_altname = user1_cert.subject_alt_name();
11✔
1028
   result.test_str_eq("subject alt email", user1_altname.get_first_attribute("RFC822"), "testing@randombit.net");
11✔
1029
   result.test_str_eq("subject alt dns", user1_altname.get_first_attribute("DNS"), "botan.randombit.net");
11✔
1030
   result.test_str_eq("subject alt uri", user1_altname.get_first_attribute("URI"), "https://botan.randombit.net");
11✔
1031

1032
   const Botan::AlternativeName& user3_altname = user3_cert.subject_alt_name();
11✔
1033
   result.test_sz_eq(
11✔
1034
      "subject alt dns count", user3_altname.get_attribute("DNS").size(), req_opts3(sig_algo).more_dns.size() + 1);
22✔
1035
   result.test_str_eq(
22✔
1036
      "subject alt dns #2", user3_altname.get_attribute("DNS").at(1), req_opts3(sig_algo).more_dns.at(0));
33✔
1037

1038
   const Botan::X509_CRL crl1 = ca.new_crl(rng);
11✔
1039

1040
   /* Verify the certs */
1041
   const Botan::Path_Validation_Restrictions restrictions(false, 80);
22✔
1042
   Botan::Certificate_Store_In_Memory store;
11✔
1043

1044
   // First try with an empty store
1045
   const Botan::Path_Validation_Result result_no_issuer = Botan::x509_path_validate(user1_cert, restrictions, store);
11✔
1046
   result.test_str_eq(
11✔
1047
      "user 1 issuer not found",
1048
      result_no_issuer.result_string(),
11✔
1049
      Botan::Path_Validation_Result::status_string(Botan::Certificate_Status_Code::CERT_ISSUER_NOT_FOUND));
1050

1051
   store.add_certificate(ca.ca_certificate());
11✔
1052

1053
   Botan::Path_Validation_Result result_u1 = Botan::x509_path_validate(user1_cert, restrictions, store);
11✔
1054
   if(!result.test_is_true("user 1 validates", result_u1.successful_validation())) {
11✔
1055
      result.test_note("user 1 validation result", result_u1.result_string());
×
1056
   }
1057

1058
   Botan::Path_Validation_Result result_u2 = Botan::x509_path_validate(user2_cert, restrictions, store);
11✔
1059
   if(!result.test_is_true("user 2 validates", result_u2.successful_validation())) {
11✔
1060
      result.test_note("user 2 validation result", result_u2.result_string());
×
1061
   }
1062

1063
   const Botan::Path_Validation_Result result_self_signed =
11✔
1064
      Botan::x509_path_validate(user1_ss_cert, restrictions, store);
11✔
1065
   result.test_str_eq(
11✔
1066
      "user 1 issuer not found",
1067
      result_no_issuer.result_string(),
11✔
1068
      Botan::Path_Validation_Result::status_string(Botan::Certificate_Status_Code::CERT_ISSUER_NOT_FOUND));
1069
   store.add_crl(crl1);
11✔
1070

1071
   std::vector<Botan::CRL_Entry> revoked;
11✔
1072
   revoked.push_back(Botan::CRL_Entry(user1_cert, Botan::CRL_Code::CessationOfOperation));
22✔
1073
   revoked.push_back(Botan::CRL_Entry(user2_cert));
22✔
1074

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

1077
   store.add_crl(crl2);
11✔
1078

1079
   const std::string revoked_str =
11✔
1080
      Botan::Path_Validation_Result::status_string(Botan::Certificate_Status_Code::CERT_IS_REVOKED);
11✔
1081

1082
   result_u1 = Botan::x509_path_validate(user1_cert, restrictions, store);
11✔
1083
   result.test_str_eq("user 1 revoked", result_u1.result_string(), revoked_str);
11✔
1084

1085
   result_u2 = Botan::x509_path_validate(user2_cert, restrictions, store);
11✔
1086
   result.test_str_eq("user 1 revoked", result_u2.result_string(), revoked_str);
11✔
1087

1088
   revoked.clear();
11✔
1089
   revoked.push_back(Botan::CRL_Entry(user1_cert, Botan::CRL_Code::RemoveFromCrl));
22✔
1090
   const Botan::X509_CRL crl3 = ca.update_crl(crl2, revoked, rng);
11✔
1091

1092
   store.add_crl(crl3);
11✔
1093

1094
   result_u1 = Botan::x509_path_validate(user1_cert, restrictions, store);
11✔
1095
   if(!result.test_is_true("user 1 validates", result_u1.successful_validation())) {
11✔
1096
      result.test_note("user 1 validation result", result_u1.result_string());
×
1097
   }
1098

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

1102
   return result;
11✔
1103
}
44✔
1104

1105
Test::Result test_crl_entry_negative_serial() {
1✔
1106
   Test::Result result("CRL entry with negative serial matches certificate serial");
1✔
1107

1108
   const auto build_crl_entry = [](const std::vector<uint8_t>& serial_bytes) {
4✔
1109
      std::vector<uint8_t> der;
3✔
1110
      Botan::DER_Encoder enc(der);
3✔
1111
      enc.start_sequence()
3✔
1112
         .add_object(Botan::ASN1_Type::Integer, Botan::ASN1_Class::Universal, serial_bytes.data(), serial_bytes.size())
3✔
1113
         .encode(Botan::X509_Time(std::chrono::system_clock::now()))
3✔
1114
         .end_cons();
3✔
1115

1116
      Botan::CRL_Entry entry;
3✔
1117
      Botan::BER_Decoder dec(der);
3✔
1118
      entry.decode_from(dec);
3✔
1119
      return entry;
3✔
1120
   };
3✔
1121

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

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

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

1134
   return result;
1✔
1135
}
1✔
1136

1137
Test::Result test_usage(const Botan::Private_Key& ca_key,
12✔
1138
                        const std::string& sig_algo,
1139
                        const std::string& hash_fn,
1140
                        Botan::RandomNumberGenerator& rng) {
1141
   using Botan::Key_Constraints;
12✔
1142
   using Botan::Usage_Type;
12✔
1143

1144
   Test::Result result("X509 Usage");
12✔
1145

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

1149
   /* Create the CA object */
1150
   const Botan::X509_CA ca(ca_cert, ca_key, hash_fn, rng);
12✔
1151

1152
   auto user1_key = make_a_private_key(sig_algo, rng);
12✔
1153

1154
   Botan::X509_Cert_Options opts("Test User 1/US/Botan Project/Testing");
12✔
1155
   opts.constraints = Key_Constraints::DigitalSignature;
12✔
1156

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

1159
   const Botan::X509_Certificate user1_cert =
12✔
1160
      ca.sign_request(user1_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
12✔
1161

1162
   // cert only allows digitalSignature, but we check for both digitalSignature and cRLSign
1163
   result.test_is_false(
12✔
1164
      "key usage cRLSign not allowed",
1165
      user1_cert.allowed_usage(Key_Constraints(Key_Constraints::DigitalSignature | Key_Constraints::CrlSign)));
12✔
1166
   result.test_is_false("encryption is not allowed", user1_cert.allowed_usage(Usage_Type::ENCRYPTION));
12✔
1167

1168
   // cert only allows digitalSignature, so checking for only that should be ok
1169
   result.test_is_true("key usage digitalSignature allowed",
12✔
1170
                       user1_cert.allowed_usage(Key_Constraints::DigitalSignature));
12✔
1171

1172
   opts.constraints = Key_Constraints(Key_Constraints::DigitalSignature | Key_Constraints::CrlSign);
12✔
1173

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

1176
   const Botan::X509_Certificate mult_usage_cert =
12✔
1177
      ca.sign_request(mult_usage_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
12✔
1178

1179
   // cert allows multiple usages, so each one of them as well as both together should be allowed
1180
   result.test_is_true("key usage multiple digitalSignature allowed",
12✔
1181
                       mult_usage_cert.allowed_usage(Key_Constraints::DigitalSignature));
12✔
1182
   result.test_is_true("key usage multiple cRLSign allowed", mult_usage_cert.allowed_usage(Key_Constraints::CrlSign));
12✔
1183
   result.test_is_true(
12✔
1184
      "key usage multiple digitalSignature and cRLSign allowed",
1185
      mult_usage_cert.allowed_usage(Key_Constraints(Key_Constraints::DigitalSignature | Key_Constraints::CrlSign)));
12✔
1186
   result.test_is_false("encryption is not allowed", mult_usage_cert.allowed_usage(Usage_Type::ENCRYPTION));
12✔
1187

1188
   opts.constraints = Key_Constraints();
12✔
1189

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

1192
   const Botan::X509_Certificate no_usage_cert =
12✔
1193
      ca.sign_request(no_usage_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
12✔
1194

1195
   // cert allows every usage
1196
   result.test_is_true("key usage digitalSignature allowed",
12✔
1197
                       no_usage_cert.allowed_usage(Key_Constraints::DigitalSignature));
12✔
1198
   result.test_is_true("key usage cRLSign allowed", no_usage_cert.allowed_usage(Key_Constraints::CrlSign));
12✔
1199
   result.test_is_true("key usage encryption allowed", no_usage_cert.allowed_usage(Usage_Type::ENCRYPTION));
12✔
1200

1201
   if(sig_algo == "RSA") {
12✔
1202
      // cert allows data encryption
1203
      opts.constraints = Key_Constraints(Key_Constraints::KeyEncipherment | Key_Constraints::DataEncipherment);
1✔
1204

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

1207
      const Botan::X509_Certificate enc_cert =
1✔
1208
         ca.sign_request(enc_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
1✔
1209

1210
      result.test_is_true("cert allows encryption", enc_cert.allowed_usage(Usage_Type::ENCRYPTION));
1✔
1211
      result.test_is_true("cert does not allow TLS client auth", !enc_cert.allowed_usage(Usage_Type::TLS_CLIENT_AUTH));
1✔
1212
   }
1✔
1213

1214
   return result;
12✔
1215
}
24✔
1216

1217
Test::Result test_self_issued(const Botan::Private_Key& ca_key,
11✔
1218
                              const std::string& sig_algo,
1219
                              const std::string& sig_padding,
1220
                              const std::string& hash_fn,
1221
                              Botan::RandomNumberGenerator& rng) {
1222
   using Botan::Key_Constraints;
11✔
1223

1224
   Test::Result result("X509 Self Issued");
11✔
1225

1226
   // create the self-signed cert
1227
   const Botan::X509_Certificate ca_cert =
11✔
1228
      Botan::X509::create_self_signed_cert(ca_opts(sig_padding), ca_key, hash_fn, rng);
11✔
1229

1230
   /* Create the CA object */
1231
   const Botan::X509_CA ca(ca_cert, ca_key, hash_fn, sig_padding, rng);
11✔
1232

1233
   auto user_key = make_a_private_key(sig_algo, rng);
11✔
1234

1235
   // create a self-issued certificate, that is, a certificate with subject dn == issuer dn,
1236
   // but signed by a CA, not signed by it's own private key
1237
   Botan::X509_Cert_Options opts = ca_opts();
11✔
1238
   opts.constraints = Key_Constraints::DigitalSignature;
11✔
1239
   opts.set_padding_scheme(sig_padding);
11✔
1240

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

1243
   const Botan::X509_Certificate self_issued_cert =
11✔
1244
      ca.sign_request(self_issued_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
11✔
1245

1246
   // check that this chain can can be verified successfully
1247
   const Botan::Certificate_Store_In_Memory trusted(ca.ca_certificate());
11✔
1248

1249
   const Botan::Path_Validation_Restrictions restrictions(false, 80);
22✔
1250

1251
   const Botan::Path_Validation_Result validation_result =
11✔
1252
      Botan::x509_path_validate(self_issued_cert, restrictions, trusted);
11✔
1253

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

1256
   return result;
11✔
1257
}
22✔
1258

1259
Test::Result test_x509_uninit() {
1✔
1260
   Test::Result result("X509 object uninitialized access");
1✔
1261

1262
   Botan::X509_Certificate cert;
1✔
1263
   result.test_throws("uninitialized cert access causes exception", "X509_Certificate uninitialized", [&cert]() {
1✔
1264
      cert.x509_version();
1✔
1265
   });
1266

1267
   Botan::X509_CRL crl;
1✔
1268
   result.test_throws(
1✔
1269
      "uninitialized crl access causes exception", "X509_CRL uninitialized", [&crl]() { crl.crl_number(); });
2✔
1270

1271
   return result;
1✔
1272
}
1✔
1273

1274
Test::Result test_valid_constraints(const Botan::Private_Key& key, const std::string& pk_algo) {
19✔
1275
   using Botan::Key_Constraints;
19✔
1276

1277
   Test::Result result("X509 Valid Constraints " + pk_algo);
19✔
1278

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

1281
   // Now check some typical usage scenarios for the given key type
1282
   // Taken from RFC 5280, sec. 4.2.1.3
1283
   // ALL constraints are not typical at all, but we use them for a negative test
1284
   const auto all = Key_Constraints(
19✔
1285
      Key_Constraints::DigitalSignature | Key_Constraints::NonRepudiation | Key_Constraints::KeyEncipherment |
1286
      Key_Constraints::DataEncipherment | Key_Constraints::KeyAgreement | Key_Constraints::KeyCertSign |
1287
      Key_Constraints::CrlSign | Key_Constraints::EncipherOnly | Key_Constraints::DecipherOnly);
19✔
1288

1289
   const auto ca = Key_Constraints(Key_Constraints::KeyCertSign);
19✔
1290
   const auto sign_data = Key_Constraints(Key_Constraints::DigitalSignature);
19✔
1291
   const auto non_repudiation = Key_Constraints(Key_Constraints::NonRepudiation | Key_Constraints::DigitalSignature);
19✔
1292
   const auto key_encipherment = Key_Constraints(Key_Constraints::KeyEncipherment);
19✔
1293
   const auto data_encipherment = Key_Constraints(Key_Constraints::DataEncipherment);
19✔
1294
   const auto key_agreement = Key_Constraints(Key_Constraints::KeyAgreement);
19✔
1295
   const auto key_agreement_encipher_only =
19✔
1296
      Key_Constraints(Key_Constraints::KeyAgreement | Key_Constraints::EncipherOnly);
19✔
1297
   const auto key_agreement_decipher_only =
19✔
1298
      Key_Constraints(Key_Constraints::KeyAgreement | Key_Constraints::DecipherOnly);
19✔
1299
   const auto crl_sign = Key_Constraints(Key_Constraints::CrlSign);
19✔
1300
   const auto sign_everything =
19✔
1301
      Key_Constraints(Key_Constraints::DigitalSignature | Key_Constraints::KeyCertSign | Key_Constraints::CrlSign);
19✔
1302

1303
   if(pk_algo == "DH" || pk_algo == "ECDH") {
19✔
1304
      // DH and ECDH only for key agreement
1305
      result.test_is_false("all constraints not permitted", all.compatible_with(key));
2✔
1306
      result.test_is_false("cert sign not permitted", ca.compatible_with(key));
2✔
1307
      result.test_is_false("signature not permitted", sign_data.compatible_with(key));
2✔
1308
      result.test_is_false("non repudiation not permitted", non_repudiation.compatible_with(key));
2✔
1309
      result.test_is_false("key encipherment not permitted", key_encipherment.compatible_with(key));
2✔
1310
      result.test_is_false("data encipherment not permitted", data_encipherment.compatible_with(key));
2✔
1311
      result.test_is_true("usage acceptable", key_agreement.compatible_with(key));
2✔
1312
      result.test_is_true("usage acceptable", key_agreement_encipher_only.compatible_with(key));
2✔
1313
      result.test_is_true("usage acceptable", key_agreement_decipher_only.compatible_with(key));
2✔
1314
      result.test_is_false("crl sign not permitted", crl_sign.compatible_with(key));
2✔
1315
      result.test_is_false("sign", sign_everything.compatible_with(key));
2✔
1316
   } else if(pk_algo == "Kyber" || pk_algo == "FrodoKEM" || pk_algo == "ML-KEM" || pk_algo == "ClassicMcEliece") {
17✔
1317
      // KEMs can encrypt and agree
1318
      result.test_is_false("all constraints not permitted", all.compatible_with(key));
4✔
1319
      result.test_is_false("cert sign not permitted", ca.compatible_with(key));
4✔
1320
      result.test_is_false("signature not permitted", sign_data.compatible_with(key));
4✔
1321
      result.test_is_false("non repudiation not permitted", non_repudiation.compatible_with(key));
4✔
1322
      result.test_is_false("crl sign not permitted", crl_sign.compatible_with(key));
4✔
1323
      result.test_is_false("sign", sign_everything.compatible_with(key));
4✔
1324
      result.test_is_false("key agreement not permitted", key_agreement.compatible_with(key));
4✔
1325
      result.test_is_false("usage acceptable", data_encipherment.compatible_with(key));
4✔
1326
      result.test_is_true("usage acceptable", key_encipherment.compatible_with(key));
4✔
1327
   } else if(pk_algo == "RSA") {
13✔
1328
      // RSA can do everything except key agreement
1329
      result.test_is_false("all constraints not permitted", all.compatible_with(key));
1✔
1330

1331
      result.test_is_true("usage acceptable", ca.compatible_with(key));
1✔
1332
      result.test_is_true("usage acceptable", sign_data.compatible_with(key));
1✔
1333
      result.test_is_true("usage acceptable", non_repudiation.compatible_with(key));
1✔
1334
      result.test_is_true("usage acceptable", key_encipherment.compatible_with(key));
1✔
1335
      result.test_is_true("usage acceptable", data_encipherment.compatible_with(key));
1✔
1336
      result.test_is_false("key agreement not permitted", key_agreement.compatible_with(key));
1✔
1337
      result.test_is_false("key agreement", key_agreement_encipher_only.compatible_with(key));
1✔
1338
      result.test_is_false("key agreement", key_agreement_decipher_only.compatible_with(key));
1✔
1339
      result.test_is_true("usage acceptable", crl_sign.compatible_with(key));
1✔
1340
      result.test_is_true("usage acceptable", sign_everything.compatible_with(key));
1✔
1341
   } else if(pk_algo == "ElGamal") {
12✔
1342
      // only ElGamal encryption is currently implemented
1343
      result.test_is_false("all constraints not permitted", all.compatible_with(key));
1✔
1344
      result.test_is_false("cert sign not permitted", ca.compatible_with(key));
1✔
1345
      result.test_is_true("data encipherment permitted", data_encipherment.compatible_with(key));
1✔
1346
      result.test_is_true("key encipherment permitted", key_encipherment.compatible_with(key));
1✔
1347
      result.test_is_false("key agreement not permitted", key_agreement.compatible_with(key));
1✔
1348
      result.test_is_false("key agreement", key_agreement_encipher_only.compatible_with(key));
1✔
1349
      result.test_is_false("key agreement", key_agreement_decipher_only.compatible_with(key));
1✔
1350
      result.test_is_false("crl sign not permitted", crl_sign.compatible_with(key));
1✔
1351
      result.test_is_false("sign", sign_everything.compatible_with(key));
1✔
1352
   } else if(pk_algo == "DSA" || pk_algo == "ECDSA" || pk_algo == "ECGDSA" || pk_algo == "ECKCDSA" ||
10✔
1353
             pk_algo == "GOST-34.10" || pk_algo == "Dilithium" || pk_algo == "ML-DSA" || pk_algo == "SLH-DSA" ||
18✔
1354
             pk_algo == "HSS-LMS") {
3✔
1355
      // these are signature algorithms only
1356
      result.test_is_false("all constraints not permitted", all.compatible_with(key));
9✔
1357

1358
      result.test_is_true("ca allowed", ca.compatible_with(key));
9✔
1359
      result.test_is_true("sign allowed", sign_data.compatible_with(key));
9✔
1360
      result.test_is_true("non-repudiation allowed", non_repudiation.compatible_with(key));
9✔
1361
      result.test_is_false("key encipherment not permitted", key_encipherment.compatible_with(key));
9✔
1362
      result.test_is_false("data encipherment not permitted", data_encipherment.compatible_with(key));
9✔
1363
      result.test_is_false("key agreement not permitted", key_agreement.compatible_with(key));
9✔
1364
      result.test_is_false("key agreement", key_agreement_encipher_only.compatible_with(key));
9✔
1365
      result.test_is_false("key agreement", key_agreement_decipher_only.compatible_with(key));
9✔
1366
      result.test_is_true("crl sign allowed", crl_sign.compatible_with(key));
9✔
1367
      result.test_is_true("sign allowed", sign_everything.compatible_with(key));
9✔
1368
   }
1369

1370
   return result;
19✔
1371
}
×
1372

1373
/**
1374
 * @brief X.509v3 extension that encodes a given string
1375
 */
1376
class String_Extension final : public Botan::Certificate_Extension {
22✔
1377
   public:
1378
      String_Extension() = default;
22✔
1379

1380
      explicit String_Extension(const std::string& val) : m_contents(val) {}
11✔
1381

1382
      std::string value() const { return m_contents; }
44✔
1383

1384
      std::unique_ptr<Certificate_Extension> copy() const override {
×
1385
         return std::make_unique<String_Extension>(m_contents);
×
1386
      }
1387

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

1390
      bool should_encode() const override { return true; }
22✔
1391

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

1394
      std::vector<uint8_t> encode_inner() const override {
11✔
1395
         std::vector<uint8_t> bits;
11✔
1396
         Botan::DER_Encoder(bits).encode(Botan::ASN1_String(m_contents, Botan::ASN1_Type::Utf8String));
22✔
1397
         return bits;
11✔
1398
      }
×
1399

1400
      void decode_inner(const std::vector<uint8_t>& in) override {
22✔
1401
         Botan::ASN1_String str;
22✔
1402
         Botan::BER_Decoder(in).decode(str, Botan::ASN1_Type::Utf8String).verify_end();
22✔
1403
         m_contents = str.value();
44✔
1404
      }
22✔
1405

1406
   private:
1407
      std::string m_contents;
1408
};
1409

1410
Test::Result test_custom_dn_attr(const Botan::Private_Key& ca_key,
11✔
1411
                                 const std::string& sig_algo,
1412
                                 const std::string& sig_padding,
1413
                                 const std::string& hash_fn,
1414
                                 Botan::RandomNumberGenerator& rng) {
1415
   Test::Result result("X509 Custom DN");
11✔
1416

1417
   /* Create the self-signed cert */
1418
   const Botan::X509_Certificate ca_cert =
11✔
1419
      Botan::X509::create_self_signed_cert(ca_opts(sig_padding), ca_key, hash_fn, rng);
11✔
1420

1421
   /* Create the CA object */
1422
   const Botan::X509_CA ca(ca_cert, ca_key, hash_fn, sig_padding, rng);
11✔
1423

1424
   auto user_key = make_a_private_key(sig_algo, rng);
11✔
1425

1426
   Botan::X509_DN subject_dn;
11✔
1427

1428
   const Botan::OID attr1(Botan::OID("1.3.6.1.4.1.25258.9.1.1"));
11✔
1429
   const Botan::OID attr2(Botan::OID("1.3.6.1.4.1.25258.9.1.2"));
11✔
1430
   const Botan::ASN1_String val1("Custom Attr 1", Botan::ASN1_Type::PrintableString);
11✔
1431
   const Botan::ASN1_String val2("12345", Botan::ASN1_Type::Utf8String);
11✔
1432

1433
   subject_dn.add_attribute(attr1, val1);
11✔
1434
   subject_dn.add_attribute(attr2, val2);
11✔
1435

1436
   const Botan::Extensions extensions;
11✔
1437

1438
   const Botan::PKCS10_Request req =
11✔
1439
      Botan::PKCS10_Request::create(*user_key, subject_dn, extensions, hash_fn, rng, sig_padding);
11✔
1440

1441
   const Botan::X509_DN& req_dn = req.subject_dn();
11✔
1442

1443
   result.test_sz_eq("Expected number of DN entries", req_dn.dn_info().size(), 2);
11✔
1444

1445
   const Botan::ASN1_String req_val1 = req_dn.get_first_attribute(attr1);
11✔
1446
   const Botan::ASN1_String req_val2 = req_dn.get_first_attribute(attr2);
11✔
1447
   result.test_is_true("Attr1 matches encoded", req_val1 == val1);
11✔
1448
   result.test_is_true("Attr2 matches encoded", req_val2 == val2);
11✔
1449
   result.test_is_true("Attr1 tag matches encoded", req_val1.tagging() == val1.tagging());
11✔
1450
   result.test_is_true("Attr2 tag matches encoded", req_val2.tagging() == val2.tagging());
11✔
1451

1452
   const Botan::X509_Time not_before("100301123001Z", Botan::ASN1_Type::UtcTime);
11✔
1453
   const Botan::X509_Time not_after("300301123001Z", Botan::ASN1_Type::UtcTime);
11✔
1454

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

1457
   const Botan::X509_DN& cert_dn = cert.subject_dn();
11✔
1458

1459
   result.test_sz_eq("Expected number of DN entries", cert_dn.dn_info().size(), 2);
11✔
1460

1461
   const Botan::ASN1_String cert_val1 = cert_dn.get_first_attribute(attr1);
11✔
1462
   const Botan::ASN1_String cert_val2 = cert_dn.get_first_attribute(attr2);
11✔
1463
   result.test_is_true("Attr1 matches encoded", cert_val1 == val1);
11✔
1464
   result.test_is_true("Attr2 matches encoded", cert_val2 == val2);
11✔
1465
   result.test_is_true("Attr1 tag matches encoded", cert_val1.tagging() == val1.tagging());
11✔
1466
   result.test_is_true("Attr2 tag matches encoded", cert_val2.tagging() == val2.tagging());
11✔
1467

1468
   return result;
22✔
1469
}
99✔
1470

1471
Test::Result test_x509_extensions(const Botan::Private_Key& ca_key,
11✔
1472
                                  const std::string& sig_algo,
1473
                                  const std::string& sig_padding,
1474
                                  const std::string& hash_fn,
1475
                                  Botan::RandomNumberGenerator& rng) {
1476
   using Botan::Key_Constraints;
11✔
1477

1478
   Test::Result result("X509 Extensions");
11✔
1479

1480
   /* Create the self-signed cert */
1481
   const Botan::X509_Certificate ca_cert =
11✔
1482
      Botan::X509::create_self_signed_cert(ca_opts(sig_padding), ca_key, hash_fn, rng);
11✔
1483

1484
   /* Create the CA object */
1485
   const Botan::X509_CA ca(ca_cert, ca_key, hash_fn, sig_padding, rng);
11✔
1486

1487
   /* Prepare CDP extension */
1488
   std::vector<std::string> cdp_urls = {
11✔
1489
      "http://example.com/crl1.pem",
1490
      "ldap://ldap.example.com/cn=crl1,dc=example,dc=com?certificateRevocationList;binary"};
11✔
1491

1492
   std::vector<Botan::Cert_Extension::CRL_Distribution_Points::Distribution_Point> dps;
11✔
1493

1494
   for(const auto& uri : cdp_urls) {
33✔
1495
      Botan::AlternativeName cdp_alt_name;
22✔
1496
      cdp_alt_name.add_uri(uri);
22✔
1497
      const Botan::Cert_Extension::CRL_Distribution_Points::Distribution_Point dp(cdp_alt_name);
22✔
1498

1499
      dps.emplace_back(dp);
22✔
1500
   }
22✔
1501

1502
   auto user_key = make_a_private_key(sig_algo, rng);
11✔
1503

1504
   Botan::X509_Cert_Options opts("Test User 1/US/Botan Project/Testing");
11✔
1505
   opts.constraints = Key_Constraints::DigitalSignature;
11✔
1506

1507
   // include a custom extension in the request
1508
   Botan::Extensions req_extensions;
11✔
1509
   const Botan::OID oid("1.2.3.4.5.6.7.8.9.1");
11✔
1510
   const Botan::OID ku_oid = Botan::OID::from_string("X509v3.KeyUsage");
11✔
1511
   req_extensions.add(std::make_unique<String_Extension>("AAAAAAAAAAAAAABCDEF"), false);
22✔
1512
   req_extensions.add(std::make_unique<Botan::Cert_Extension::CRL_Distribution_Points>(dps));
22✔
1513
   opts.extensions = req_extensions;
11✔
1514
   opts.set_padding_scheme(sig_padding);
11✔
1515

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

1519
   result.test_is_true("Extensions::extension_set true for Key_Usage",
11✔
1520
                       self_signed_cert.v3_extensions().extension_set(ku_oid));
11✔
1521

1522
   // check if known Key_Usage extension is present in self-signed cert
1523
   auto key_usage_ext = self_signed_cert.v3_extensions().get(ku_oid);
11✔
1524
   if(result.test_is_true("Key_Usage extension present in self-signed certificate", key_usage_ext != nullptr)) {
11✔
1525
      result.test_is_true(
22✔
1526
         "Key_Usage extension value matches in self-signed certificate",
1527
         dynamic_cast<Botan::Cert_Extension::Key_Usage&>(*key_usage_ext).get_constraints() == opts.constraints);
11✔
1528
   }
1529

1530
   // check if custom extension is present in self-signed cert
1531
   auto string_ext = self_signed_cert.v3_extensions().get_raw<String_Extension>(oid);
11✔
1532
   if(result.test_is_true("Custom extension present in self-signed certificate", string_ext != nullptr)) {
11✔
1533
      result.test_str_eq(
22✔
1534
         "Custom extension value matches in self-signed certificate", string_ext->value(), "AAAAAAAAAAAAAABCDEF");
33✔
1535
   }
1536

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

1541
   if(result.test_is_true("CRL Distribution Points extension present in self-signed certificate",
11✔
1542
                          !cert_cdps->crl_distribution_urls().empty())) {
11✔
1543
      for(const auto& cdp : cert_cdps->distribution_points()) {
33✔
1544
         result.test_is_true("CDP URI present in self-signed certificate",
44✔
1545
                             std::ranges::find(cdp_urls, cdp.point().get_first_attribute("URI")) != cdp_urls.end());
66✔
1546
      }
1547
   }
1548

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

1551
   /* Create a CA-signed certificate */
1552
   const Botan::X509_Certificate ca_signed_cert =
11✔
1553
      ca.sign_request(user_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
11✔
1554

1555
   // check if known Key_Usage extension is present in CA-signed cert
1556
   result.test_is_true("Extensions::extension_set true for Key_Usage",
11✔
1557
                       ca_signed_cert.v3_extensions().extension_set(ku_oid));
11✔
1558

1559
   key_usage_ext = ca_signed_cert.v3_extensions().get(ku_oid);
22✔
1560
   if(result.test_is_true("Key_Usage extension present in CA-signed certificate", key_usage_ext != nullptr)) {
11✔
1561
      auto constraints = dynamic_cast<Botan::Cert_Extension::Key_Usage&>(*key_usage_ext).get_constraints();
11✔
1562
      result.test_is_true("Key_Usage extension value matches in user certificate",
22✔
1563
                          constraints == Botan::Key_Constraints::DigitalSignature);
11✔
1564
   }
1565

1566
   // check if custom extension is present in CA-signed cert
1567
   result.test_is_true("Extensions::extension_set true for String_Extension",
11✔
1568
                       ca_signed_cert.v3_extensions().extension_set(oid));
11✔
1569
   string_ext = ca_signed_cert.v3_extensions().get_raw<String_Extension>(oid);
22✔
1570
   if(result.test_is_true("Custom extension present in CA-signed certificate", string_ext != nullptr)) {
11✔
1571
      result.test_str_eq(
22✔
1572
         "Custom extension value matches in CA-signed certificate", string_ext->value(), "AAAAAAAAAAAAAABCDEF");
33✔
1573
   }
1574

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

1578
   if(result.test_is_true("CRL Distribution Points extension present in self-signed certificate",
11✔
1579
                          !cert_cdps->crl_distribution_urls().empty())) {
11✔
1580
      for(const auto& cdp : cert_cdps->distribution_points()) {
33✔
1581
         result.test_is_true("CDP URI present in self-signed certificate",
44✔
1582
                             std::ranges::find(cdp_urls, cdp.point().get_first_attribute("URI")) != cdp_urls.end());
66✔
1583
      }
1584
   }
1585

1586
   return result;
11✔
1587
}
55✔
1588

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

1592
   struct TestData {
12✔
1593
         const std::string issuer, subject, issuer_hash, subject_hash;
1594
   } const cases[]{{"",
12✔
1595
                    "",
1596
                    "E4F60D0AA6D7F3D3B6A6494B1C861B99F649C6F9EC51ABAF201B20F297327C95",
1597
                    "E4F60D0AA6D7F3D3B6A6494B1C861B99F649C6F9EC51ABAF201B20F297327C95"},
1598
                   {"a",
1599
                    "b",
1600
                    "BC2E013472F39AC579964880E422737C82BA812CB8BC2FD17E013060D71E6E19",
1601
                    "5E31CFAA3FAFB1A5BA296A0D2BAB9CA44D7936E9BF0BBC54637D0C53DBC4A432"},
1602
                   {"A",
1603
                    "B",
1604
                    "4B3206201C4BC9B6CD6C36532A97687DF9238155D99ADB60C66BF2B2220643D8",
1605
                    "FFF635A52A16618B4A0E9CD26B5E5A2FA573D343C051E6DE8B0811B1ACC89B86"},
1606
                   {
1607
                      "Test Issuer/US/Botan Project/Testing",
1608
                      "Test Subject/US/Botan Project/Testing",
1609
                      "ACB4F373004A56A983A23EB8F60FA4706312B5DB90FD978574FE7ACC84E093A5",
1610
                      "87039231C2205B74B6F1F3830A66272C0B41F71894B03AC3150221766D95267B",
1611
                   },
1612
                   {
1613
                      "Test Subject/US/Botan Project/Testing",
1614
                      "Test Issuer/US/Botan Project/Testing",
1615
                      "87039231C2205B74B6F1F3830A66272C0B41F71894B03AC3150221766D95267B",
1616
                      "ACB4F373004A56A983A23EB8F60FA4706312B5DB90FD978574FE7ACC84E093A5",
1617
                   }};
72✔
1618

1619
   for(const auto& a : cases) {
72✔
1620
      Botan::X509_Cert_Options opts{a.issuer};
60✔
1621
      opts.CA_key();
60✔
1622

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

1625
      result.test_str_eq(a.issuer, Botan::hex_encode(issuer_cert.raw_issuer_dn_sha256()), a.issuer_hash);
60✔
1626
      result.test_str_eq(a.issuer, Botan::hex_encode(issuer_cert.raw_subject_dn_sha256()), a.issuer_hash);
60✔
1627

1628
      const Botan::X509_CA ca(issuer_cert, key, hash_fn, rng);
60✔
1629
      const Botan::PKCS10_Request req =
60✔
1630
         Botan::X509::create_cert_req(Botan::X509_Cert_Options(a.subject), key, hash_fn, rng);
60✔
1631
      const Botan::X509_Certificate subject_cert =
60✔
1632
         ca.sign_request(req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
60✔
1633

1634
      result.test_str_eq(a.subject, Botan::hex_encode(subject_cert.raw_issuer_dn_sha256()), a.issuer_hash);
60✔
1635
      result.test_str_eq(a.subject, Botan::hex_encode(subject_cert.raw_subject_dn_sha256()), a.subject_hash);
60✔
1636
   }
60✔
1637
   return result;
12✔
1638
}
72✔
1639

1640
   #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
1641

1642
Test::Result test_x509_tn_auth_list_extension_decode() {
1✔
1643
   /* cert with TNAuthList extension data was generated by asn1parse cfg:
1644

1645
      asn1=SEQUENCE:tn_auth_list
1646

1647
      [tn_auth_list]
1648
      spc=EXP:0,IA5:1001
1649
      range=EXP:1,SEQUENCE:TelephoneNumberRange
1650
      one=EXP:2,IA5:333
1651

1652
      [TelephoneNumberRange]
1653
      start1=IA5:111
1654
      count1=INT:128
1655
      start2=IA5:222
1656
      count2=INT:256
1657
    */
1658
   const std::string filename("TNAuthList.pem");
1✔
1659
   Test::Result result("X509 TNAuthList decode");
1✔
1660
   result.start_timer();
1✔
1661

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

1664
   using Botan::Cert_Extension::TNAuthList;
1✔
1665

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

1668
   const auto& tn_entries = tn_auth_list->entries();
1✔
1669

1670
   result.test_not_null("cert has TNAuthList extension", tn_auth_list);
1✔
1671

1672
   result.test_throws("wrong telephone_number_range() accessor for spc",
1✔
1673
                      [&tn_entries] { tn_entries[0].telephone_number_range(); });
2✔
1674
   result.test_throws("wrong telephone_number() accessor for range",
1✔
1675
                      [&tn_entries] { tn_entries[1].telephone_number(); });
2✔
1676
   result.test_throws("wrong service_provider_code() accessor for one",
1✔
1677
                      [&tn_entries] { tn_entries[2].service_provider_code(); });
2✔
1678

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

1682
   result.test_is_true("range entry type", tn_entries[1].type() == TNAuthList::Entry::TelephoneNumberRange);
1✔
1683
   const auto& range = tn_entries[1].telephone_number_range();
1✔
1684
   result.test_sz_eq("range entries count", range.size(), 2);
1✔
1685
   result.test_str_eq("range entry 0 start data", range[0].start.value(), "111");
1✔
1686
   result.test_sz_eq("range entry 0 count data", range[0].count, 128);
1✔
1687
   result.test_str_eq("range entry 1 start data", range[1].start.value(), "222");
1✔
1688
   result.test_sz_eq("range entry 1 count data", range[1].count, 256);
1✔
1689

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

1693
   result.end_timer();
1✔
1694
   return result;
2✔
1695
}
1✔
1696

1697
   #endif
1698

1699
std::vector<std::string> get_sig_paddings(const std::string& sig_algo, const std::string& hash) {
12✔
1700
   if(sig_algo == "RSA") {
12✔
1701
      return {
1✔
1702
   #if defined(BOTAN_HAS_EMSA_PKCS1)
1703
         "PKCS1v15(" + hash + ")",
1✔
1704
   #endif
1705
   #if defined(BOTAN_HAS_EMSA_PSS)
1706
            "PSS(" + hash + ")",
1707
   #endif
1708
      };
3✔
1709
   } else if(sig_algo == "DSA" || sig_algo == "ECDSA" || sig_algo == "ECGDSA" || sig_algo == "ECKCDSA" ||
11✔
1710
             sig_algo == "GOST-34.10") {
7✔
1711
      return {hash};
10✔
1712
   } else if(sig_algo == "Ed25519" || sig_algo == "Ed448") {
6✔
1713
      return {"Pure"};
2✔
1714
   } else if(sig_algo == "Dilithium" || sig_algo == "ML-DSA") {
4✔
1715
      return {"Randomized"};
2✔
1716
   } else if(sig_algo == "HSS-LMS") {
2✔
1717
      return {""};
1✔
1718
   } else {
1719
      return {};
1✔
1720
   }
1721
}
6✔
1722

1723
class X509_Cert_Unit_Tests final : public Test {
1✔
1724
   public:
1725
      std::vector<Test::Result> run() override {
1✔
1726
         std::vector<Test::Result> results;
1✔
1727

1728
         auto& rng = this->rng();
1✔
1729

1730
         const std::string sig_algos[]{"RSA",
1✔
1731
                                       "DSA",
1732
                                       "ECDSA",
1733
                                       "ECGDSA",
1734
                                       "ECKCDSA",
1735
                                       "GOST-34.10",
1736
                                       "Ed25519",
1737
                                       "Ed448",
1738
                                       "Dilithium",
1739
                                       "ML-DSA",
1740
                                       "SLH-DSA",
1741
                                       "HSS-LMS"};
13✔
1742

1743
         for(const std::string& algo : sig_algos) {
13✔
1744
   #if !defined(BOTAN_HAS_EMSA_PKCS1)
1745
            if(algo == "RSA")
1746
               continue;
1747
   #endif
1748

1749
            std::string hash = "SHA-256";
12✔
1750

1751
            if(algo == "Ed25519") {
12✔
1752
               hash = "SHA-512";
1✔
1753
            }
1754
            if(algo == "Ed448") {
12✔
1755
               hash = "SHAKE-256(912)";
1✔
1756
            }
1757
            if(algo == "Dilithium" || algo == "ML-DSA") {
12✔
1758
               hash = "SHAKE-256(512)";
2✔
1759
            }
1760

1761
            auto key = make_a_private_key(algo, rng);
12✔
1762

1763
            if(key == nullptr) {
12✔
1764
               continue;
×
1765
            }
1766

1767
            results.push_back(test_hashes(*key, hash, rng));
24✔
1768
            results.push_back(test_valid_constraints(*key, algo));
24✔
1769

1770
            Test::Result usage_result("X509 Usage");
12✔
1771
            try {
12✔
1772
               usage_result.merge(test_usage(*key, algo, hash, rng));
12✔
1773
            } catch(std::exception& e) {
×
1774
               usage_result.test_failure("test_usage " + algo, e.what());
×
1775
            }
×
1776
            results.push_back(usage_result);
12✔
1777

1778
            for(const auto& padding_scheme : get_sig_paddings(algo, hash)) {
23✔
1779
               Test::Result cert_result("X509 Unit");
11✔
1780

1781
               try {
11✔
1782
                  cert_result.merge(test_x509_cert(*key, algo, padding_scheme, hash, rng));
11✔
1783
               } catch(std::exception& e) {
×
1784
                  cert_result.test_failure("test_x509_cert " + algo, e.what());
×
1785
               }
×
1786
               results.push_back(cert_result);
11✔
1787

1788
               Test::Result pkcs10_result("PKCS10 extensions");
11✔
1789
               try {
11✔
1790
                  pkcs10_result.merge(test_pkcs10_ext(*key, padding_scheme, hash, rng));
11✔
1791
               } catch(std::exception& e) {
×
1792
                  pkcs10_result.test_failure("test_pkcs10_ext " + algo, e.what());
×
1793
               }
×
1794
               results.push_back(pkcs10_result);
11✔
1795

1796
               Test::Result self_issued_result("X509 Self Issued");
11✔
1797
               try {
11✔
1798
                  self_issued_result.merge(test_self_issued(*key, algo, padding_scheme, hash, rng));
11✔
1799
               } catch(std::exception& e) {
×
1800
                  self_issued_result.test_failure("test_self_issued " + algo, e.what());
×
1801
               }
×
1802
               results.push_back(self_issued_result);
11✔
1803

1804
               Test::Result extensions_result("X509 Extensions");
11✔
1805
               try {
11✔
1806
                  extensions_result.merge(test_x509_extensions(*key, algo, padding_scheme, hash, rng));
11✔
1807
               } catch(std::exception& e) {
×
1808
                  extensions_result.test_failure("test_extensions " + algo, e.what());
×
1809
               }
×
1810
               results.push_back(extensions_result);
11✔
1811

1812
               Test::Result custom_dn_result("X509 Custom DN");
11✔
1813
               try {
11✔
1814
                  custom_dn_result.merge(test_custom_dn_attr(*key, algo, padding_scheme, hash, rng));
11✔
1815
               } catch(std::exception& e) {
×
1816
                  custom_dn_result.test_failure("test_custom_dn_attr " + algo, e.what());
×
1817
               }
×
1818
               results.push_back(custom_dn_result);
11✔
1819
            }
23✔
1820
         }
24✔
1821

1822
         /*
1823
         These are algos which cannot sign but can be included in certs
1824
         */
1825
         const std::vector<std::string> enc_algos = {
1✔
1826
            "DH", "ECDH", "ElGamal", "Kyber", "ML-KEM", "FrodoKEM", "ClassicMcEliece"};
1✔
1827

1828
         for(const std::string& algo : enc_algos) {
8✔
1829
            auto key = make_a_private_key(algo, rng);
7✔
1830

1831
            if(key) {
7✔
1832
               results.push_back(test_valid_constraints(*key, algo));
14✔
1833
            }
1834
         }
7✔
1835

1836
   #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM) && defined(BOTAN_HAS_EMSA_PKCS1) && defined(BOTAN_HAS_EMSA_PSSR) && \
1837
      defined(BOTAN_HAS_RSA)
1838
         Test::Result pad_config_result("X509 Padding Config");
1✔
1839
         try {
1✔
1840
            pad_config_result.merge(test_padding_config());
1✔
1841
         } catch(const std::exception& e) {
×
1842
            pad_config_result.test_failure("test_padding_config", e.what());
×
1843
         }
×
1844
         results.push_back(pad_config_result);
1✔
1845
   #endif
1846

1847
   #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
1848
         results.push_back(test_x509_utf8());
2✔
1849
         results.push_back(test_x509_any_key_extended_usage());
2✔
1850
         results.push_back(test_x509_bmpstring());
2✔
1851
         results.push_back(test_x509_teletex());
2✔
1852
         results.push_back(test_crl_dn_name());
2✔
1853
         results.push_back(test_rdn_multielement_set_name());
2✔
1854
         results.push_back(test_x509_decode_list());
2✔
1855
         results.push_back(test_rsa_oaep());
2✔
1856
         results.push_back(test_x509_authority_info_access_extension());
2✔
1857
         results.push_back(test_verify_gost2012_cert());
2✔
1858
         results.push_back(test_parse_rsa_pss_cert());
2✔
1859
         results.push_back(test_x509_tn_auth_list_extension_decode());
2✔
1860
   #endif
1861

1862
         results.push_back(test_x509_encode_authority_info_access_extension());
2✔
1863
         results.push_back(test_x509_extension());
2✔
1864
         results.push_back(test_x509_dates());
2✔
1865
         results.push_back(test_cert_status_strings());
2✔
1866
         results.push_back(test_x509_uninit());
2✔
1867
         results.push_back(test_crl_entry_negative_serial());
2✔
1868

1869
         return results;
1✔
1870
      }
13✔
1871
};
1872

1873
BOTAN_REGISTER_TEST("x509", "x509_unit", X509_Cert_Unit_Tests);
1874

1875
#endif
1876

1877
}  // namespace
1878

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