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

randombit / botan / 25457312714

06 May 2026 07:43PM UTC coverage: 89.331% (-2.3%) from 91.667%
25457312714

push

github

randombit
In TLS 1.3 verification of client certs, check the correct extension for OCSP

This was checking if the client asked us (the server) for OCSP, instead of
checking if we asked the client for OCSP when we sent the CertificateRequest.

107574 of 120422 relevant lines covered (89.33%)

11482758.98 hits per line

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

94.42
/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_extension_decode_duplicate() {
1✔
252
   Test::Result result("X509 Extensions reject duplicate OID");
1✔
253

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

257
   std::vector<uint8_t> der;
1✔
258
   Botan::DER_Encoder enc(der);
1✔
259
   enc.start_sequence();
1✔
260
   for(size_t i = 0; i != 2; ++i) {
3✔
261
      enc.start_sequence().encode(oid_bc).encode(bc_bits, Botan::ASN1_Type::OctetString).end_cons();
2✔
262
   }
263
   enc.end_cons();
1✔
264

265
   Botan::Extensions extns;
1✔
266
   Botan::BER_Decoder dec(der);
1✔
267
   result.test_throws<Botan::Decoding_Error>("Duplicate extension OID is rejected at decode time",
1✔
268
                                             [&]() { extns.decode_from(dec); });
2✔
269

270
   return result;
1✔
271
}
2✔
272

273
Test::Result test_x509_dates() {
1✔
274
   Test::Result result("X509 Time");
1✔
275

276
   Botan::X509_Time time;
1✔
277
   result.test_is_true("unset time not set", !time.time_is_set());
1✔
278
   time = Botan::X509_Time("080201182200Z", Botan::ASN1_Type::UtcTime);
1✔
279
   result.test_is_true("time set after construction", time.time_is_set());
1✔
280
   result.test_str_eq("time readable_string", time.readable_string(), "2008/02/01 18:22:00 UTC");
1✔
281

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

285
   time = Botan::X509_Time("200305100350Z");
1✔
286
   result.test_str_eq(
1✔
287
      "UTC_OR_GENERALIZED_TIME from UTC_TIME readable_string", time.readable_string(), "2020/03/05 10:03:50 UTC");
1✔
288

289
   time = Botan::X509_Time("20200305100350Z");
1✔
290
   result.test_str_eq("UTC_OR_GENERALIZED_TIME from GENERALIZED_TIME readable_string",
1✔
291
                      time.readable_string(),
1✔
292
                      "2020/03/05 10:03:50 UTC");
293

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

297
   // Dates that are valid per X.500 but rejected as unsupported
298
   const std::string valid_but_unsup[]{
1✔
299
      "0802010000-0000",
300
      "0802011724+0000",
301
      "0406142334-0500",
302
      "9906142334+0500",
303
      "0006142334-0530",
304
      "0006142334+0530",
305

306
      "080201000000-0000",
307
      "080201172412+0000",
308
      "040614233433-0500",
309
      "990614233444+0500",
310
      "000614233455-0530",
311
      "000614233455+0530",
312
   };
13✔
313

314
   // valid length 13
315
   const std::string valid_utc[]{
1✔
316
      "080201000000Z",
317
      "080201172412Z",
318
      "040614233433Z",
319
      "990614233444Z",
320
      "000614233455Z",
321
   };
6✔
322

323
   const std::string invalid_utc[]{
1✔
324
      "",
325
      " ",
326
      "2008`02-01",
327
      "9999-02-01",
328
      "2000-02-01 17",
329
      "999921",
330

331
      // No seconds
332
      "0802010000Z",
333
      "0802011724Z",
334
      "0406142334Z",
335
      "9906142334Z",
336
      "0006142334Z",
337

338
      // valid length 13 -> range check
339
      "080201000061Z",  // seconds too big (61)
340
      "080201000060Z",  // seconds too big (60, leap seconds not covered by the standard)
341
      "0802010000-1Z",  // seconds too small (-1)
342
      "080201006000Z",  // minutes too big (60)
343
      "080201240000Z",  // hours too big (24:00)
344

345
      // valid length 13 -> invalid numbers
346
      "08020123112 Z",
347
      "08020123112!Z",
348
      "08020123112,Z",
349
      "08020123112\nZ",
350
      "080201232 33Z",
351
      "080201232!33Z",
352
      "080201232,33Z",
353
      "080201232\n33Z",
354
      "0802012 3344Z",
355
      "0802012!3344Z",
356
      "0802012,3344Z",
357
      "08022\n334455Z",
358
      "08022 334455Z",
359
      "08022!334455Z",
360
      "08022,334455Z",
361
      "08022\n334455Z",
362
      "082 33445511Z",
363
      "082!33445511Z",
364
      "082,33445511Z",
365
      "082\n33445511Z",
366
      "2 2211221122Z",
367
      "2!2211221122Z",
368
      "2,2211221122Z",
369
      "2\n2211221122Z",
370

371
      // wrong time zone
372
      "080201000000",
373
      "080201000000z",
374

375
      // Fractional seconds
376
      "170217180154.001Z",
377

378
      // Timezone offset
379
      "170217180154+0100",
380

381
      // Extra digits
382
      "17021718015400Z",
383

384
      // Non-digits
385
      "17021718015aZ",
386

387
      // Trailing garbage
388
      "170217180154Zlongtrailinggarbage",
389

390
      // Swapped type
391
      "20170217180154Z",
392
   };
49✔
393

394
   // valid length 15
395
   const std::string valid_generalized_time[]{
1✔
396
      "20000305100350Z",
397
   };
2✔
398

399
   const std::string invalid_generalized[]{
1✔
400
      // No trailing Z
401
      "20000305100350",
402

403
      // No seconds
404
      "200003051003Z",
405

406
      // Fractional seconds
407
      "20000305100350.001Z",
408

409
      // Timezone offset
410
      "20170217180154+0100",
411

412
      // Extra digits
413
      "2017021718015400Z",
414

415
      // Non-digits
416
      "2017021718015aZ",
417

418
      // Trailing garbage
419
      "20170217180154Zlongtrailinggarbage",
420

421
      // Swapped type
422
      "170217180154Z",
423
   };
9✔
424

425
   for(const auto& v : valid_but_unsup) {
13✔
426
      result.test_throws("valid but unsupported", [v]() { const Botan::X509_Time t(v, Botan::ASN1_Type::UtcTime); });
60✔
427
   }
428

429
   for(const auto& v : valid_utc) {
6✔
430
      const Botan::X509_Time t(v, Botan::ASN1_Type::UtcTime);
5✔
431
   }
5✔
432

433
   for(const auto& v : valid_generalized_time) {
2✔
434
      const Botan::X509_Time t(v, Botan::ASN1_Type::GeneralizedTime);
1✔
435
   }
1✔
436

437
   for(const auto& v : invalid_utc) {
49✔
438
      result.test_throws("invalid", [v]() { const Botan::X509_Time t(v, Botan::ASN1_Type::UtcTime); });
240✔
439
   }
440

441
   for(const auto& v : invalid_generalized) {
9✔
442
      result.test_throws("invalid", [v]() { const Botan::X509_Time t(v, Botan::ASN1_Type::GeneralizedTime); });
40✔
443
   }
444

445
   return result;
1✔
446
}
80✔
447

448
Test::Result test_x509_encode_authority_info_access_extension() {
1✔
449
   Test::Result result("X509 with encoded PKIX.AuthorityInformationAccess extension");
1✔
450

451
   #if defined(BOTAN_HAS_RSA) && defined(BOTAN_HAS_EMSA_PKCS1)
452
   auto rng = Test::new_rng(__func__);
1✔
453

454
   const std::string sig_algo{"RSA"};
1✔
455
   const std::string hash_fn{"SHA-256"};
1✔
456
   const std::string padding_method{"PKCS1v15(SHA-256)"};
1✔
457

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

463
   // OCSP
464
   const std::string_view ocsp_uri{"http://staging.ocsp.d-trust.net"};
1✔
465

466
   // create a CA
467
   auto ca_key = make_a_private_key(sig_algo, *rng);
1✔
468
   result.require("CA key", ca_key != nullptr);
1✔
469
   const auto ca_cert = Botan::X509::create_self_signed_cert(ca_opts(), *ca_key, hash_fn, *rng);
1✔
470
   const Botan::X509_CA ca(ca_cert, *ca_key, hash_fn, padding_method, *rng);
1✔
471

472
   // create a certificate with only caIssuer information
473
   auto key = make_a_private_key(sig_algo, *rng);
1✔
474

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

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

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

482
   if(!result.test_sz_eq("number of ca_issuers URIs", cert.ca_issuers().size(), 2)) {
1✔
483
      return result;
484
   }
485

486
   for(const auto& ca_issuer : cert.ca_issuers()) {
3✔
487
      result.test_is_true("CA issuer URI present in certificate",
4✔
488
                          std::ranges::find(ca_issuers, ca_issuer) != ca_issuers.end());
4✔
489
   }
1✔
490

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

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

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

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

501
   result.test_is_true("OCSP URI available", !cert.ocsp_responder().empty());
1✔
502
   result.test_is_true("no CA Issuer URI available", cert.ca_issuers().empty());
1✔
503
   result.test_str_eq("OCSP responder URI matches", cert.ocsp_responder(), std::string(ocsp_uri));
2✔
504

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

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

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

513
   result.test_is_true("OCSP URI available", !cert.ocsp_responder().empty());
1✔
514
   result.test_is_true("CA Issuer URI available", !cert.ca_issuers().empty());
1✔
515

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

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

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

525
   const auto* aia_ext =
1✔
526
      cert.v3_extensions().get_extension_object_as<Botan::Cert_Extension::Authority_Information_Access>();
1✔
527
   result.test_is_true("AIA extension present", aia_ext != nullptr);
1✔
528

529
   const auto ocsp_responders = aia_ext->ocsp_responders();
1✔
530
   result.test_sz_eq("number of OCSP responder URIs", ocsp_responders.size(), 2);
1✔
531
   result.test_str_eq("First OCSP responder URI matches", ocsp_responders[0], "http://ocsp.example.com");
1✔
532
   result.test_str_eq("Second OCSP responder URI matches", ocsp_responders[1], "http://backup-ocsp.example.com");
1✔
533

534
   const auto cert_ocsp_responders = cert.ocsp_responders();
1✔
535
   result.test_sz_eq("Certificate: number of OCSP responder URIs", cert_ocsp_responders.size(), 2);
1✔
536
   result.test_str_eq(
1✔
537
      "Certificate: First OCSP responder URI matches", cert_ocsp_responders[0], "http://ocsp.example.com");
1✔
538
   result.test_str_eq(
1✔
539
      "Certificate: Second OCSP responder URI matches", cert_ocsp_responders[1], "http://backup-ocsp.example.com");
1✔
540
   #endif
541

542
   return result;
1✔
543
}
4✔
544

545
   #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
546

547
Test::Result test_crl_dn_name() {
1✔
548
   Test::Result result("CRL DN name");
1✔
549

550
      // See GH #1252
551

552
      #if defined(BOTAN_HAS_RSA) && defined(BOTAN_HAS_EMSA_PKCS1)
553
   auto rng = Test::new_rng(__func__);
1✔
554

555
   const Botan::OID dc_oid("0.9.2342.19200300.100.1.25");
1✔
556

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

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

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

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

567
   result.test_is_true("contains DC component", crl.issuer_dn().get_attributes().count(dc_oid) == 1);
2✔
568
      #endif
569

570
   return result;
2✔
571
}
3✔
572

573
Test::Result test_rdn_multielement_set_name() {
1✔
574
   Test::Result result("DN with multiple elements in RDN");
1✔
575

576
   // GH #2611
577

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

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

583
   return result;
1✔
584
}
1✔
585

586
Test::Result test_rsa_oaep() {
1✔
587
   Test::Result result("RSA OAEP decoding");
1✔
588

589
      #if defined(BOTAN_HAS_RSA)
590
   const Botan::X509_Certificate cert(Test::data_file("x509/misc/rsa_oaep.pem"));
2✔
591

592
   auto public_key = cert.subject_public_key();
1✔
593
   result.test_not_null("Decoding RSA-OAEP worked", public_key.get());
1✔
594
   const auto& pk_info = cert.subject_public_key_algo();
1✔
595

596
   result.test_str_eq("RSA-OAEP OID", pk_info.oid().to_string(), Botan::OID::from_string("RSA/OAEP").to_string());
1✔
597
      #endif
598

599
   return result;
2✔
600
}
1✔
601

602
Test::Result test_x509_decode_list() {
1✔
603
   Test::Result result("X509_Certificate list decode");
1✔
604

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

607
   Botan::BER_Decoder dec(input);
1✔
608
   std::vector<Botan::X509_Certificate> certs;
1✔
609
   dec.decode_list(certs);
1✔
610

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

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

616
   return result;
1✔
617
}
1✔
618

619
Test::Result test_x509_utf8() {
1✔
620
   Test::Result result("X509 with UTF-8 encoded fields");
1✔
621

622
   try {
1✔
623
      const Botan::X509_Certificate utf8_cert(Test::data_file("x509/misc/contains_utf8string.pem"));
2✔
624

625
      // UTF-8 encoded fields of test certificate (contains cyrillic letters)
626
      const std::string organization =
1✔
627
         "\xD0\x9C\xD0\xBE\xD1\x8F\x20\xD0\xBA\xD0\xBE\xD0"
628
         "\xBC\xD0\xBF\xD0\xB0\xD0\xBD\xD0\xB8\xD1\x8F";
1✔
629
      const std::string organization_unit =
1✔
630
         "\xD0\x9C\xD0\xBE\xD1\x91\x20\xD0\xBF\xD0\xBE\xD0\xB4\xD1\x80\xD0\xB0"
631
         "\xD0\xB7\xD0\xB4\xD0\xB5\xD0\xBB\xD0\xB5\xD0\xBD\xD0\xB8\xD0\xB5";
1✔
632
      const std::string common_name =
1✔
633
         "\xD0\x9E\xD0\xBF\xD0\xB8\xD1\x81\xD0\xB0\xD0\xBD\xD0\xB8"
634
         "\xD0\xB5\x20\xD1\x81\xD0\xB0\xD0\xB9\xD1\x82\xD0\xB0";
1✔
635
      const std::string location = "\xD0\x9C\xD0\xBE\xD1\x81\xD0\xBA\xD0\xB2\xD0\xB0";
1✔
636

637
      const Botan::X509_DN& issuer_dn = utf8_cert.issuer_dn();
1✔
638

639
      result.test_str_eq("O", issuer_dn.get_first_attribute("O"), organization);
1✔
640
      result.test_str_eq("OU", issuer_dn.get_first_attribute("OU"), organization_unit);
1✔
641
      result.test_str_eq("CN", issuer_dn.get_first_attribute("CN"), common_name);
1✔
642
      result.test_str_eq("L", issuer_dn.get_first_attribute("L"), location);
1✔
643
   } catch(const Botan::Decoding_Error& ex) {
1✔
644
      result.test_failure(ex.what());
×
645
   }
×
646

647
   return result;
1✔
648
}
×
649

650
Test::Result test_x509_any_key_extended_usage() {
1✔
651
   using Botan::Key_Constraints;
1✔
652
   using Botan::Usage_Type;
1✔
653

654
   Test::Result result("X509 with X509v3.AnyExtendedKeyUsage");
1✔
655
   try {
1✔
656
      const Botan::X509_Certificate any_eku_cert(Test::data_file("x509/misc/contains_any_extended_key_usage.pem"));
2✔
657

658
      result.test_is_true("is CA cert", any_eku_cert.is_CA_cert());
1✔
659
      result.test_is_true("DigitalSignature is allowed", any_eku_cert.allowed_usage(Key_Constraints::DigitalSignature));
1✔
660
      result.test_is_true("CrlSign is allowed", any_eku_cert.allowed_usage(Key_Constraints::CrlSign));
1✔
661
      result.test_is_false("OCSP responder is not allowed", any_eku_cert.allowed_usage(Usage_Type::OCSP_RESPONDER));
1✔
662
   } catch(const Botan::Decoding_Error& ex) {
1✔
663
      result.test_failure(ex.what());
×
664
   }
×
665
   return result;
1✔
666
}
×
667

668
Test::Result test_x509_bmpstring() {
1✔
669
   Test::Result result("X509 with UCS-2 (BMPString) encoded fields");
1✔
670

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

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

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

682
      const Botan::X509_DN& issuer_dn = ucs2_cert.issuer_dn();
1✔
683

684
      result.test_str_eq("O", issuer_dn.get_first_attribute("O"), organization);
1✔
685
      result.test_str_eq("CN", issuer_dn.get_first_attribute("CN"), common_name);
1✔
686
      result.test_str_eq("L", issuer_dn.get_first_attribute("L"), location);
1✔
687
   } catch(const Botan::Decoding_Error& ex) {
1✔
688
      result.test_failure(ex.what());
×
689
   }
×
690

691
   return result;
1✔
692
}
×
693

694
Test::Result test_x509_teletex() {
1✔
695
   Test::Result result("X509 with TeletexString encoded fields");
1✔
696

697
   try {
1✔
698
      const Botan::X509_Certificate teletex_cert(Test::data_file("x509/misc/teletex_dn.der"));
2✔
699

700
      const Botan::X509_DN& issuer_dn = teletex_cert.issuer_dn();
1✔
701

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

704
      result.test_str_eq("O", issuer_dn.get_first_attribute("O"), "neam CA");
1✔
705
      result.test_str_eq("CN", issuer_dn.get_first_attribute("CN"), common_name);
1✔
706
   } catch(const Botan::Decoding_Error& ex) {
1✔
707
      result.test_failure(ex.what());
×
708
   }
×
709

710
   return result;
1✔
711
}
×
712

713
Test::Result test_x509_authority_info_access_extension() {
1✔
714
   Test::Result result("X509 with PKIX.AuthorityInformationAccess extension");
1✔
715

716
   // contains no AIA extension
717
   const Botan::X509_Certificate no_aia_cert(Test::data_file("x509/misc/contains_utf8string.pem"));
2✔
718

719
   result.test_sz_eq("number of ca_issuers URLs", no_aia_cert.ca_issuers().size(), 0);
1✔
720
   result.test_str_eq("CA issuer URL matches", no_aia_cert.ocsp_responder(), "");
1✔
721

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

725
   const auto ca_issuers = aia_cert.ca_issuers();
1✔
726

727
   result.test_sz_eq("number of ca_issuers URLs", ca_issuers.size(), 1);
1✔
728
   if(result.tests_failed() > 0) {
1✔
729
      return result;
730
   }
731

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

735
   // contains AIA extension with 2 CA issuer URL and 1 OCSP responder
736
   const Botan::X509_Certificate aia_cert_2ca(
1✔
737
      Test::data_file("x509/misc/contains_authority_info_access_with_two_ca_issuers.pem"));
2✔
738

739
   const auto ca_issuers2 = aia_cert_2ca.ca_issuers();
1✔
740

741
   result.test_sz_eq("number of ca_issuers URLs", ca_issuers2.size(), 2);
1✔
742
   if(result.tests_failed() > 0) {
1✔
743
      return result;
744
   }
745

746
   result.test_str_eq(
1✔
747
      "CA issuer URL matches", ca_issuers2[0], "http://www.d-trust.net/cgi-bin/Bdrive_Test_CA_1-2_2017.crt");
1✔
748
   result.test_str_eq(
1✔
749
      "CA issuer URL matches",
750
      ca_issuers2[1],
1✔
751
      "ldap://directory.d-trust.net/CN=Bdrive%20Test%20CA%201-2%202017,O=Bundesdruckerei%20GmbH,C=DE?cACertificate?base?");
752
   result.test_str_eq("OCSP responder URL matches", aia_cert_2ca.ocsp_responder(), "http://staging.ocsp.d-trust.net");
1✔
753

754
   // contains AIA extension with multiple OCSP responders
755
   const Botan::X509_Certificate aia_cert_multi_ocsp(
1✔
756
      Test::data_file("x509/misc/contains_multiple_ocsp_responders.pem"));
2✔
757

758
   const auto& ocsp_responders_multi = aia_cert_multi_ocsp.ocsp_responders();
1✔
759
   result.test_sz_eq("number of OCSP responders", ocsp_responders_multi.size(), 3);
1✔
760
   result.test_str_eq("First OCSP responder URL matches", ocsp_responders_multi[0], "http://ocsp1.example.com");
1✔
761
   result.test_str_eq("Second OCSP responder URL matches", ocsp_responders_multi[1], "http://ocsp2.example.com");
1✔
762
   result.test_str_eq("Third OCSP responder URL matches", ocsp_responders_multi[2], "http://ocsp3.example.com");
1✔
763
   result.test_is_true("no CA Issuer URI available", aia_cert_multi_ocsp.ca_issuers().empty());
1✔
764

765
   return result;
1✔
766
}
1✔
767

768
Test::Result test_crl_issuing_distribution_point_extension() {
1✔
769
   Test::Result result("X509 CRL IssuingDistributionPoint extension");
1✔
770

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

774
   result.test_str_eq(
1✔
775
      "CRL IDP URI decoded correctly", crl.crl_issuing_distribution_point(), "http://localhost/subca/crldp/crl.crl");
1✔
776

777
   return result;
1✔
778
}
1✔
779

780
Test::Result test_parse_rsa_pss_cert() {
1✔
781
   Test::Result result("X509 RSA-PSS certificate");
1✔
782

783
   // See https://github.com/randombit/botan/issues/3019 for background
784

785
   try {
1✔
786
      const Botan::X509_Certificate rsa_pss(Test::data_file("x509/misc/rsa_pss.pem"));
2✔
787
      result.test_success("Was able to parse RSA-PSS certificate signed with ECDSA");
1✔
788
   } catch(Botan::Exception& e) {
1✔
789
      result.test_failure("Parsing failed", e.what());
×
790
   }
×
791

792
   return result;
1✔
793
}
×
794

795
Test::Result test_verify_gost2012_cert() {
1✔
796
   Test::Result result("X509 GOST-2012 certificates");
1✔
797

798
      #if defined(BOTAN_HAS_GOST_34_10_2012) && defined(BOTAN_HAS_STREEBOG)
799
   try {
1✔
800
      if(Botan::EC_Group::supports_named_group("gost_256A")) {
1✔
801
         const Botan::X509_Certificate root_cert(Test::data_file("x509/gost/gost_root.pem"));
2✔
802
         const Botan::X509_Certificate root_int(Test::data_file("x509/gost/gost_int.pem"));
2✔
803

804
         Botan::Certificate_Store_In_Memory trusted;
1✔
805
         trusted.add_certificate(root_cert);
1✔
806

807
         const Botan::Path_Validation_Restrictions restrictions(false, 128, false, {"Streebog-256"});
2✔
808
         const auto validation_time = Botan::calendar_point(2024, 1, 1, 0, 0, 0).to_std_timepoint();
1✔
809
         const Botan::Path_Validation_Result validation_result = Botan::x509_path_validate(
1✔
810
            root_int, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
1✔
811

812
         result.test_is_true("GOST certificate validates", validation_result.successful_validation());
1✔
813
      }
1✔
814
   } catch(const Botan::Decoding_Error& e) {
×
815
      result.test_failure(e.what());
×
816
   }
×
817
      #endif
818

819
   return result;
1✔
820
}
×
821

822
   /*
823
 * @brief checks the configurability of the RSA-PSS signature scheme
824
 *
825
 * For the other algorithms than RSA, only one padding is supported right now.
826
 */
827
      #if defined(BOTAN_HAS_EMSA_PKCS1) && defined(BOTAN_HAS_EMSA_PSSR) && defined(BOTAN_HAS_RSA)
828
Test::Result test_padding_config() {
1✔
829
   Test::Result test_result("X509 Padding Config");
1✔
830

831
   auto rng = Test::new_rng(__func__);
1✔
832

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

836
   // Create X509 CA certificate; PKCS1v15 is used for signing by default
837
   Botan::X509_Cert_Options opt("TEST CA");
1✔
838
   opt.CA_key();
1✔
839

840
   const Botan::X509_Certificate ca_cert_def = Botan::X509::create_self_signed_cert(opt, (*sk), "SHA-512", *rng);
1✔
841
   test_result.test_str_eq("CA certificate signature algorithm (default)",
2✔
842
                           ca_cert_def.signature_algorithm().oid().to_formatted_string(),
1✔
843
                           "RSA/PKCS1v15(SHA-512)");
844

845
   // Create X509 CA certificate; RSA-PSS is explicitly set
846
   opt.set_padding_scheme("PSSR");
1✔
847
   const Botan::X509_Certificate ca_cert_exp = Botan::X509::create_self_signed_cert(opt, (*sk), "SHA-512", *rng);
1✔
848
   test_result.test_str_eq("CA certificate signature algorithm (explicit)",
2✔
849
                           ca_cert_exp.signature_algorithm().oid().to_formatted_string(),
1✔
850
                           "RSA/PSS");
851

852
         #if defined(BOTAN_HAS_EMSA_X931)
853
   // Try to set a padding scheme that is not supported for signing with the given key type
854
   opt.set_padding_scheme("X9.31");
1✔
855
   try {
1✔
856
      const Botan::X509_Certificate ca_cert_wrong = Botan::X509::create_self_signed_cert(opt, (*sk), "SHA-512", *rng);
1✔
857
      test_result.test_failure("Could build CA cert with invalid encoding scheme X9.31 for key type " +
×
858
                               sk->algo_name());
×
859
   } catch(const Botan::Invalid_Argument& e) {
1✔
860
      test_result.test_str_eq("Build CA certificate with invalid encoding scheme X9.31 for key type " + sk->algo_name(),
3✔
861
                              e.what(),
1✔
862
                              "Signatures using RSA/X9.31(SHA-512) are not supported");
863
   }
1✔
864
         #endif
865

866
   test_result.test_str_eq("CA certificate signature algorithm (explicit)",
2✔
867
                           ca_cert_exp.signature_algorithm().oid().to_formatted_string(),
1✔
868
                           "RSA/PSS");
869

870
   const Botan::X509_Time not_before = from_date(-1, 1, 1);
1✔
871
   const Botan::X509_Time not_after = from_date(2, 1, 2);
1✔
872

873
   // Prepare a signing request for the end certificate
874
   Botan::X509_Cert_Options req_opt("endpoint");
1✔
875
   req_opt.set_padding_scheme("PSS(SHA-512,MGF1,64)");
1✔
876
   const Botan::PKCS10_Request end_req = Botan::X509::create_cert_req(req_opt, (*sk), "SHA-512", *rng);
1✔
877
   test_result.test_str_eq(
2✔
878
      "Certificate request signature algorithm", end_req.signature_algorithm().oid().to_formatted_string(), "RSA/PSS");
1✔
879

880
   // Create X509 CA object: will fail as the chosen hash functions differ
881
   try {
1✔
882
      const Botan::X509_CA ca_fail(ca_cert_exp, (*sk), "SHA-512", "PSS(SHA-256)", *rng);
1✔
883
      test_result.test_failure("Configured conflicting hash functions for CA");
×
884
   } catch(const Botan::Invalid_Argument& e) {
1✔
885
      test_result.test_str_eq(
1✔
886
         "Configured conflicting hash functions for CA",
887
         e.what(),
1✔
888
         "Specified hash function SHA-512 is incompatible with RSA chose hash function SHA-256 with user specified padding PSS(SHA-256)");
889
   }
1✔
890

891
   // Create X509 CA object: its signer will use the padding scheme from the CA certificate, i.e. PKCS1v15
892
   const Botan::X509_CA ca_def(ca_cert_def, (*sk), "SHA-512", *rng);
1✔
893
   const Botan::X509_Certificate end_cert_pkcs1 = ca_def.sign_request(end_req, *rng, not_before, not_after);
1✔
894
   test_result.test_str_eq("End certificate signature algorithm",
2✔
895
                           end_cert_pkcs1.signature_algorithm().oid().to_formatted_string(),
1✔
896
                           "RSA/PKCS1v15(SHA-512)");
897

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

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

911
   // Check CRL signature algorithm
912
   const Botan::X509_CRL crl = ca_exp.new_crl(*rng);
1✔
913
   test_result.test_str_eq("CRL signature algorithm", crl.signature_algorithm().oid().to_formatted_string(), "RSA/PSS");
1✔
914

915
   // sanity check for verification, the heavy lifting is done in the other unit tests
916
   const Botan::Certificate_Store_In_Memory trusted(ca_exp.ca_certificate());
1✔
917
   const Botan::Path_Validation_Restrictions restrictions(false, 80);
2✔
918
   const Botan::Path_Validation_Result validation_result =
1✔
919
      Botan::x509_path_validate(end_cert_pss, restrictions, trusted);
1✔
920
   test_result.test_is_true("PSS signed certificate validates", validation_result.successful_validation());
1✔
921

922
   return test_result;
2✔
923
}
3✔
924
      #endif
925

926
   #endif
927

928
Test::Result test_pkcs10_ext(const Botan::Private_Key& key,
11✔
929
                             const std::string& sig_padding,
930
                             const std::string& hash_fn,
931
                             Botan::RandomNumberGenerator& rng) {
932
   Test::Result result("PKCS10 extensions");
11✔
933

934
   Botan::X509_Cert_Options opts;
11✔
935

936
   opts.dns = "main.example.org";
11✔
937
   opts.more_dns.push_back("more1.example.org");
22✔
938
   opts.more_dns.push_back("more2.example.org");
22✔
939

940
   opts.padding_scheme = sig_padding;
11✔
941

942
   Botan::AlternativeName alt_name;
11✔
943
   alt_name.add_attribute("DNS", "bonus.example.org");
11✔
944

945
   Botan::X509_DN alt_dn;
11✔
946
   alt_dn.add_attribute("X520.CommonName", "alt_cn");
11✔
947
   alt_dn.add_attribute("X520.Organization", "testing");
11✔
948
   alt_name.add_dn(alt_dn);
11✔
949

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

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

954
   const auto alt_dns_names = req.subject_alt_name().get_attribute("DNS");
11✔
955

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

958
   if(alt_dns_names.size() == 4) {
11✔
959
      result.test_str_eq("Expected DNS name 1", alt_dns_names.at(0), "bonus.example.org");
11✔
960
      result.test_str_eq("Expected DNS name 2", alt_dns_names.at(1), "main.example.org");
11✔
961
      result.test_str_eq("Expected DNS name 3", alt_dns_names.at(2), "more1.example.org");
11✔
962
      result.test_str_eq("Expected DNS name 3", alt_dns_names.at(3), "more2.example.org");
11✔
963
   }
964

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

968
   return result;
11✔
969
}
22✔
970

971
Test::Result test_x509_cert(const Botan::Private_Key& ca_key,
11✔
972
                            const std::string& sig_algo,
973
                            const std::string& sig_padding,
974
                            const std::string& hash_fn,
975
                            Botan::RandomNumberGenerator& rng) {
976
   Test::Result result("X509 Unit");
11✔
977

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

981
   {
11✔
982
      result.test_is_true("ca key usage cert", ca_cert.constraints().includes(Botan::Key_Constraints::KeyCertSign));
11✔
983
      result.test_is_true("ca key usage crl", ca_cert.constraints().includes(Botan::Key_Constraints::CrlSign));
11✔
984
   }
985

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

989
   const Botan::PKCS10_Request user1_req =
11✔
990
      Botan::X509::create_cert_req(req_opts1(sig_algo, sig_padding), *user1_key, hash_fn, rng);
11✔
991

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

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

997
   const Botan::PKCS10_Request user2_req =
11✔
998
      Botan::X509::create_cert_req(req_opts2(sig_padding), *user2_key, hash_fn, rng);
11✔
999

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

1003
   const Botan::PKCS10_Request user3_req =
11✔
1004
      Botan::X509::create_cert_req(req_opts3(sig_padding), *user3_key, hash_fn, rng);
11✔
1005

1006
   /* Create the CA object */
1007
   const Botan::X509_CA ca(ca_cert, ca_key, hash_fn, sig_padding, rng);
11✔
1008

1009
   const BigInt user1_serial(99);
11✔
1010

1011
   /* Sign the requests to create the certs */
1012
   const Botan::X509_Certificate user1_cert =
11✔
1013
      ca.sign_request(user1_req, rng, user1_serial, from_date(-1, 01, 01), from_date(2, 01, 01));
11✔
1014

1015
   result.test_sz_eq("User1 serial size matches expected", user1_cert.serial_number().size(), 1);
11✔
1016
   result.test_sz_eq("User1 serial matches expected", user1_cert.serial_number().at(0), size_t(99));
11✔
1017

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

1022
   const Botan::X509_Certificate user3_cert =
11✔
1023
      ca.sign_request(user3_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
11✔
1024

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

1029
   {
11✔
1030
      auto constraints = req_opts1(sig_algo).constraints;
11✔
1031
      result.test_is_true("user1 key usage", user1_cert.constraints().includes(constraints));
11✔
1032
   }
1033

1034
   /* Copy, assign and compare */
1035
   Botan::X509_Certificate user1_cert_copy(user1_cert);
11✔
1036
   result.test_is_true("certificate copy", user1_cert == user1_cert_copy);
11✔
1037

1038
   user1_cert_copy = user2_cert;
11✔
1039
   result.test_is_true("certificate assignment", user2_cert == user1_cert_copy);
11✔
1040

1041
   const Botan::X509_Certificate user1_cert_differ =
11✔
1042
      ca.sign_request(user1_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
11✔
1043

1044
   result.test_is_false("certificate differs", user1_cert == user1_cert_differ);
11✔
1045

1046
   /* Get cert data */
1047
   result.test_sz_eq("x509 version", user1_cert.x509_version(), size_t(3));
11✔
1048

1049
   const Botan::X509_DN& user1_issuer_dn = user1_cert.issuer_dn();
11✔
1050
   result.test_str_eq("issuer info CN", user1_issuer_dn.get_first_attribute("CN"), ca_opts().common_name);
11✔
1051
   result.test_str_eq("issuer info Country", user1_issuer_dn.get_first_attribute("C"), ca_opts().country);
11✔
1052
   result.test_str_eq("issuer info Orga", user1_issuer_dn.get_first_attribute("O"), ca_opts().organization);
11✔
1053
   result.test_str_eq("issuer info OrgaUnit", user1_issuer_dn.get_first_attribute("OU"), ca_opts().org_unit);
11✔
1054

1055
   const Botan::X509_DN& user3_subject_dn = user3_cert.subject_dn();
11✔
1056
   result.test_sz_eq("subject OrgaUnit count",
11✔
1057
                     user3_subject_dn.get_attribute("OU").size(),
22✔
1058
                     req_opts3(sig_algo).more_org_units.size() + 1);
22✔
1059
   result.test_str_eq(
22✔
1060
      "subject OrgaUnit #2", user3_subject_dn.get_attribute("OU").at(1), req_opts3(sig_algo).more_org_units.at(0));
33✔
1061

1062
   const Botan::AlternativeName& user1_altname = user1_cert.subject_alt_name();
11✔
1063
   result.test_str_eq("subject alt email", user1_altname.get_first_attribute("RFC822"), "testing@randombit.net");
11✔
1064
   result.test_str_eq("subject alt dns", user1_altname.get_first_attribute("DNS"), "botan.randombit.net");
11✔
1065
   result.test_str_eq("subject alt uri", user1_altname.get_first_attribute("URI"), "https://botan.randombit.net");
11✔
1066

1067
   const Botan::AlternativeName& user3_altname = user3_cert.subject_alt_name();
11✔
1068
   result.test_sz_eq(
11✔
1069
      "subject alt dns count", user3_altname.get_attribute("DNS").size(), req_opts3(sig_algo).more_dns.size() + 1);
22✔
1070
   result.test_str_eq(
22✔
1071
      "subject alt dns #2", user3_altname.get_attribute("DNS").at(1), req_opts3(sig_algo).more_dns.at(0));
33✔
1072

1073
   const Botan::X509_CRL crl1 = ca.new_crl(rng);
11✔
1074

1075
   /* Verify the certs */
1076
   const Botan::Path_Validation_Restrictions restrictions(false, 80);
22✔
1077
   Botan::Certificate_Store_In_Memory store;
11✔
1078

1079
   // First try with an empty store
1080
   const Botan::Path_Validation_Result result_no_issuer = Botan::x509_path_validate(user1_cert, restrictions, store);
11✔
1081
   result.test_str_eq(
11✔
1082
      "user 1 issuer not found",
1083
      result_no_issuer.result_string(),
11✔
1084
      Botan::Path_Validation_Result::status_string(Botan::Certificate_Status_Code::CERT_ISSUER_NOT_FOUND));
1085

1086
   store.add_certificate(ca.ca_certificate());
11✔
1087

1088
   Botan::Path_Validation_Result result_u1 = Botan::x509_path_validate(user1_cert, restrictions, store);
11✔
1089
   if(!result.test_is_true("user 1 validates", result_u1.successful_validation())) {
11✔
1090
      result.test_note("user 1 validation result", result_u1.result_string());
×
1091
   }
1092

1093
   Botan::Path_Validation_Result result_u2 = Botan::x509_path_validate(user2_cert, restrictions, store);
11✔
1094
   if(!result.test_is_true("user 2 validates", result_u2.successful_validation())) {
11✔
1095
      result.test_note("user 2 validation result", result_u2.result_string());
×
1096
   }
1097

1098
   const Botan::Path_Validation_Result result_self_signed =
11✔
1099
      Botan::x509_path_validate(user1_ss_cert, restrictions, store);
11✔
1100
   result.test_str_eq(
11✔
1101
      "user 1 issuer not found",
1102
      result_no_issuer.result_string(),
11✔
1103
      Botan::Path_Validation_Result::status_string(Botan::Certificate_Status_Code::CERT_ISSUER_NOT_FOUND));
1104
   store.add_crl(crl1);
11✔
1105

1106
   std::vector<Botan::CRL_Entry> revoked;
11✔
1107
   revoked.push_back(Botan::CRL_Entry(user1_cert, Botan::CRL_Code::CessationOfOperation));
22✔
1108
   revoked.push_back(Botan::CRL_Entry(user2_cert));
22✔
1109

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

1112
   store.add_crl(crl2);
11✔
1113

1114
   const std::string revoked_str =
11✔
1115
      Botan::Path_Validation_Result::status_string(Botan::Certificate_Status_Code::CERT_IS_REVOKED);
11✔
1116

1117
   result_u1 = Botan::x509_path_validate(user1_cert, restrictions, store);
11✔
1118
   result.test_str_eq("user 1 revoked", result_u1.result_string(), revoked_str);
11✔
1119

1120
   result_u2 = Botan::x509_path_validate(user2_cert, restrictions, store);
11✔
1121
   result.test_str_eq("user 1 revoked", result_u2.result_string(), revoked_str);
11✔
1122

1123
   revoked.clear();
11✔
1124
   revoked.push_back(Botan::CRL_Entry(user1_cert, Botan::CRL_Code::RemoveFromCrl));
22✔
1125
   const Botan::X509_CRL crl3 = ca.update_crl(crl2, revoked, rng);
11✔
1126

1127
   store.add_crl(crl3);
11✔
1128

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

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

1137
   return result;
11✔
1138
}
44✔
1139

1140
Test::Result test_crl_entry_negative_serial() {
1✔
1141
   Test::Result result("CRL entry with negative serial matches certificate serial");
1✔
1142

1143
   const auto build_crl_entry = [](const std::vector<uint8_t>& serial_bytes) {
4✔
1144
      std::vector<uint8_t> der;
3✔
1145
      Botan::DER_Encoder enc(der);
3✔
1146
      enc.start_sequence()
3✔
1147
         .add_object(Botan::ASN1_Type::Integer, Botan::ASN1_Class::Universal, serial_bytes.data(), serial_bytes.size())
3✔
1148
         .encode(Botan::X509_Time(std::chrono::system_clock::now()))
3✔
1149
         .end_cons();
3✔
1150

1151
      Botan::CRL_Entry entry;
3✔
1152
      Botan::BER_Decoder dec(der);
3✔
1153
      entry.decode_from(dec);
3✔
1154
      return entry;
3✔
1155
   };
3✔
1156

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

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

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

1169
   return result;
1✔
1170
}
1✔
1171

1172
Test::Result test_usage(const Botan::Private_Key& ca_key,
12✔
1173
                        const std::string& sig_algo,
1174
                        const std::string& hash_fn,
1175
                        Botan::RandomNumberGenerator& rng) {
1176
   using Botan::Key_Constraints;
12✔
1177
   using Botan::Usage_Type;
12✔
1178

1179
   Test::Result result("X509 Usage");
12✔
1180

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

1184
   /* Create the CA object */
1185
   const Botan::X509_CA ca(ca_cert, ca_key, hash_fn, rng);
12✔
1186

1187
   auto user1_key = make_a_private_key(sig_algo, rng);
12✔
1188

1189
   Botan::X509_Cert_Options opts("Test User 1/US/Botan Project/Testing");
12✔
1190
   opts.constraints = Key_Constraints::DigitalSignature;
12✔
1191

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

1194
   const Botan::X509_Certificate user1_cert =
12✔
1195
      ca.sign_request(user1_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
12✔
1196

1197
   // cert only allows digitalSignature, but we check for both digitalSignature and cRLSign
1198
   result.test_is_false(
12✔
1199
      "key usage cRLSign not allowed",
1200
      user1_cert.allowed_usage(Key_Constraints(Key_Constraints::DigitalSignature | Key_Constraints::CrlSign)));
12✔
1201
   result.test_is_false("encryption is not allowed", user1_cert.allowed_usage(Usage_Type::ENCRYPTION));
12✔
1202

1203
   // cert only allows digitalSignature, so checking for only that should be ok
1204
   result.test_is_true("key usage digitalSignature allowed",
12✔
1205
                       user1_cert.allowed_usage(Key_Constraints::DigitalSignature));
12✔
1206

1207
   opts.constraints = Key_Constraints(Key_Constraints::DigitalSignature | Key_Constraints::CrlSign);
12✔
1208

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

1211
   const Botan::X509_Certificate mult_usage_cert =
12✔
1212
      ca.sign_request(mult_usage_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
12✔
1213

1214
   // cert allows multiple usages, so each one of them as well as both together should be allowed
1215
   result.test_is_true("key usage multiple digitalSignature allowed",
12✔
1216
                       mult_usage_cert.allowed_usage(Key_Constraints::DigitalSignature));
12✔
1217
   result.test_is_true("key usage multiple cRLSign allowed", mult_usage_cert.allowed_usage(Key_Constraints::CrlSign));
12✔
1218
   result.test_is_true(
12✔
1219
      "key usage multiple digitalSignature and cRLSign allowed",
1220
      mult_usage_cert.allowed_usage(Key_Constraints(Key_Constraints::DigitalSignature | Key_Constraints::CrlSign)));
12✔
1221
   result.test_is_false("encryption is not allowed", mult_usage_cert.allowed_usage(Usage_Type::ENCRYPTION));
12✔
1222

1223
   opts.constraints = Key_Constraints();
12✔
1224

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

1227
   const Botan::X509_Certificate no_usage_cert =
12✔
1228
      ca.sign_request(no_usage_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
12✔
1229

1230
   // cert allows every usage
1231
   result.test_is_true("key usage digitalSignature allowed",
12✔
1232
                       no_usage_cert.allowed_usage(Key_Constraints::DigitalSignature));
12✔
1233
   result.test_is_true("key usage cRLSign allowed", no_usage_cert.allowed_usage(Key_Constraints::CrlSign));
12✔
1234
   result.test_is_true("key usage encryption allowed", no_usage_cert.allowed_usage(Usage_Type::ENCRYPTION));
12✔
1235

1236
   if(sig_algo == "RSA") {
12✔
1237
      // cert allows data encryption
1238
      opts.constraints = Key_Constraints(Key_Constraints::KeyEncipherment | Key_Constraints::DataEncipherment);
1✔
1239

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

1242
      const Botan::X509_Certificate enc_cert =
1✔
1243
         ca.sign_request(enc_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
1✔
1244

1245
      result.test_is_true("cert allows encryption", enc_cert.allowed_usage(Usage_Type::ENCRYPTION));
1✔
1246
      result.test_is_true("cert does not allow TLS client auth", !enc_cert.allowed_usage(Usage_Type::TLS_CLIENT_AUTH));
1✔
1247
   }
1✔
1248

1249
   return result;
12✔
1250
}
24✔
1251

1252
Test::Result test_self_issued(const Botan::Private_Key& ca_key,
11✔
1253
                              const std::string& sig_algo,
1254
                              const std::string& sig_padding,
1255
                              const std::string& hash_fn,
1256
                              Botan::RandomNumberGenerator& rng) {
1257
   using Botan::Key_Constraints;
11✔
1258

1259
   Test::Result result("X509 Self Issued");
11✔
1260

1261
   // create the self-signed cert
1262
   const Botan::X509_Certificate ca_cert =
11✔
1263
      Botan::X509::create_self_signed_cert(ca_opts(sig_padding), ca_key, hash_fn, rng);
11✔
1264

1265
   /* Create the CA object */
1266
   const Botan::X509_CA ca(ca_cert, ca_key, hash_fn, sig_padding, rng);
11✔
1267

1268
   auto user_key = make_a_private_key(sig_algo, rng);
11✔
1269

1270
   // create a self-issued certificate, that is, a certificate with subject dn == issuer dn,
1271
   // but signed by a CA, not signed by it's own private key
1272
   Botan::X509_Cert_Options opts = ca_opts();
11✔
1273
   opts.constraints = Key_Constraints::DigitalSignature;
11✔
1274
   opts.set_padding_scheme(sig_padding);
11✔
1275

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

1278
   const Botan::X509_Certificate self_issued_cert =
11✔
1279
      ca.sign_request(self_issued_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
11✔
1280

1281
   // check that this chain can can be verified successfully
1282
   const Botan::Certificate_Store_In_Memory trusted(ca.ca_certificate());
11✔
1283

1284
   const Botan::Path_Validation_Restrictions restrictions(false, 80);
22✔
1285

1286
   const Botan::Path_Validation_Result validation_result =
11✔
1287
      Botan::x509_path_validate(self_issued_cert, restrictions, trusted);
11✔
1288

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

1291
   return result;
11✔
1292
}
22✔
1293

1294
Test::Result test_x509_uninit() {
1✔
1295
   Test::Result result("X509 object uninitialized access");
1✔
1296

1297
   Botan::X509_Certificate cert;
1✔
1298
   result.test_throws("uninitialized cert access causes exception", "X509_Certificate uninitialized", [&cert]() {
1✔
1299
      cert.x509_version();
1✔
1300
   });
1301

1302
   Botan::X509_CRL crl;
1✔
1303
   result.test_throws(
1✔
1304
      "uninitialized crl access causes exception", "X509_CRL uninitialized", [&crl]() { crl.crl_number(); });
2✔
1305

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

1312
   result.test_throws("synth crl signature() throws", "X509_Object uninitialized", [&]() { synth_crl.signature(); });
2✔
1313
   result.test_throws(
1✔
1314
      "synth crl signed_body() throws", "X509_Object uninitialized", [&]() { synth_crl.signed_body(); });
2✔
1315
   result.test_throws("synth crl signature_algorithm() throws", "X509_Object uninitialized", [&]() {
1✔
1316
      synth_crl.signature_algorithm();
1✔
1317
   });
1318
   result.test_throws("synth crl tbs_data() throws", "X509_Object uninitialized", [&]() { synth_crl.tbs_data(); });
2✔
1319
   result.test_throws("synth crl BER_encode() throws", "X509_Object uninitialized", [&]() { synth_crl.BER_encode(); });
2✔
1320

1321
   return result;
1✔
1322
}
2✔
1323

1324
Test::Result test_valid_constraints(const Botan::Private_Key& key, const std::string& pk_algo) {
19✔
1325
   using Botan::Key_Constraints;
19✔
1326

1327
   Test::Result result("X509 Valid Constraints " + pk_algo);
19✔
1328

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

1331
   // Now check some typical usage scenarios for the given key type
1332
   // Taken from RFC 5280, sec. 4.2.1.3
1333
   // ALL constraints are not typical at all, but we use them for a negative test
1334
   const auto all = Key_Constraints(
19✔
1335
      Key_Constraints::DigitalSignature | Key_Constraints::NonRepudiation | Key_Constraints::KeyEncipherment |
1336
      Key_Constraints::DataEncipherment | Key_Constraints::KeyAgreement | Key_Constraints::KeyCertSign |
1337
      Key_Constraints::CrlSign | Key_Constraints::EncipherOnly | Key_Constraints::DecipherOnly);
19✔
1338

1339
   const auto ca = Key_Constraints(Key_Constraints::KeyCertSign);
19✔
1340
   const auto sign_data = Key_Constraints(Key_Constraints::DigitalSignature);
19✔
1341
   const auto non_repudiation = Key_Constraints(Key_Constraints::NonRepudiation | Key_Constraints::DigitalSignature);
19✔
1342
   const auto key_encipherment = Key_Constraints(Key_Constraints::KeyEncipherment);
19✔
1343
   const auto data_encipherment = Key_Constraints(Key_Constraints::DataEncipherment);
19✔
1344
   const auto key_agreement = Key_Constraints(Key_Constraints::KeyAgreement);
19✔
1345
   const auto key_agreement_encipher_only =
19✔
1346
      Key_Constraints(Key_Constraints::KeyAgreement | Key_Constraints::EncipherOnly);
19✔
1347
   const auto key_agreement_decipher_only =
19✔
1348
      Key_Constraints(Key_Constraints::KeyAgreement | Key_Constraints::DecipherOnly);
19✔
1349
   const auto crl_sign = Key_Constraints(Key_Constraints::CrlSign);
19✔
1350
   const auto sign_everything =
19✔
1351
      Key_Constraints(Key_Constraints::DigitalSignature | Key_Constraints::KeyCertSign | Key_Constraints::CrlSign);
19✔
1352

1353
   if(pk_algo == "DH" || pk_algo == "ECDH") {
19✔
1354
      // DH and ECDH only for key agreement
1355
      result.test_is_false("all constraints not permitted", all.compatible_with(key));
2✔
1356
      result.test_is_false("cert sign not permitted", ca.compatible_with(key));
2✔
1357
      result.test_is_false("signature not permitted", sign_data.compatible_with(key));
2✔
1358
      result.test_is_false("non repudiation not permitted", non_repudiation.compatible_with(key));
2✔
1359
      result.test_is_false("key encipherment not permitted", key_encipherment.compatible_with(key));
2✔
1360
      result.test_is_false("data encipherment not permitted", data_encipherment.compatible_with(key));
2✔
1361
      result.test_is_true("usage acceptable", key_agreement.compatible_with(key));
2✔
1362
      result.test_is_true("usage acceptable", key_agreement_encipher_only.compatible_with(key));
2✔
1363
      result.test_is_true("usage acceptable", key_agreement_decipher_only.compatible_with(key));
2✔
1364
      result.test_is_false("crl sign not permitted", crl_sign.compatible_with(key));
2✔
1365
      result.test_is_false("sign", sign_everything.compatible_with(key));
2✔
1366
   } else if(pk_algo == "Kyber" || pk_algo == "FrodoKEM" || pk_algo == "ML-KEM" || pk_algo == "ClassicMcEliece") {
17✔
1367
      // KEMs can encrypt and agree
1368
      result.test_is_false("all constraints not permitted", all.compatible_with(key));
4✔
1369
      result.test_is_false("cert sign not permitted", ca.compatible_with(key));
4✔
1370
      result.test_is_false("signature not permitted", sign_data.compatible_with(key));
4✔
1371
      result.test_is_false("non repudiation not permitted", non_repudiation.compatible_with(key));
4✔
1372
      result.test_is_false("crl sign not permitted", crl_sign.compatible_with(key));
4✔
1373
      result.test_is_false("sign", sign_everything.compatible_with(key));
4✔
1374
      result.test_is_false("key agreement not permitted", key_agreement.compatible_with(key));
4✔
1375
      result.test_is_false("usage acceptable", data_encipherment.compatible_with(key));
4✔
1376
      result.test_is_true("usage acceptable", key_encipherment.compatible_with(key));
4✔
1377
   } else if(pk_algo == "RSA") {
13✔
1378
      // RSA can do everything except key agreement
1379
      result.test_is_false("all constraints not permitted", all.compatible_with(key));
1✔
1380

1381
      result.test_is_true("usage acceptable", ca.compatible_with(key));
1✔
1382
      result.test_is_true("usage acceptable", sign_data.compatible_with(key));
1✔
1383
      result.test_is_true("usage acceptable", non_repudiation.compatible_with(key));
1✔
1384
      result.test_is_true("usage acceptable", key_encipherment.compatible_with(key));
1✔
1385
      result.test_is_true("usage acceptable", data_encipherment.compatible_with(key));
1✔
1386
      result.test_is_false("key agreement not permitted", key_agreement.compatible_with(key));
1✔
1387
      result.test_is_false("key agreement", key_agreement_encipher_only.compatible_with(key));
1✔
1388
      result.test_is_false("key agreement", key_agreement_decipher_only.compatible_with(key));
1✔
1389
      result.test_is_true("usage acceptable", crl_sign.compatible_with(key));
1✔
1390
      result.test_is_true("usage acceptable", sign_everything.compatible_with(key));
1✔
1391
   } else if(pk_algo == "ElGamal") {
12✔
1392
      // only ElGamal encryption is currently implemented
1393
      result.test_is_false("all constraints not permitted", all.compatible_with(key));
1✔
1394
      result.test_is_false("cert sign not permitted", ca.compatible_with(key));
1✔
1395
      result.test_is_true("data encipherment permitted", data_encipherment.compatible_with(key));
1✔
1396
      result.test_is_true("key encipherment permitted", key_encipherment.compatible_with(key));
1✔
1397
      result.test_is_false("key agreement not permitted", key_agreement.compatible_with(key));
1✔
1398
      result.test_is_false("key agreement", key_agreement_encipher_only.compatible_with(key));
1✔
1399
      result.test_is_false("key agreement", key_agreement_decipher_only.compatible_with(key));
1✔
1400
      result.test_is_false("crl sign not permitted", crl_sign.compatible_with(key));
1✔
1401
      result.test_is_false("sign", sign_everything.compatible_with(key));
1✔
1402
   } else if(pk_algo == "DSA" || pk_algo == "ECDSA" || pk_algo == "ECGDSA" || pk_algo == "ECKCDSA" ||
10✔
1403
             pk_algo == "GOST-34.10" || pk_algo == "Dilithium" || pk_algo == "ML-DSA" || pk_algo == "SLH-DSA" ||
18✔
1404
             pk_algo == "HSS-LMS") {
3✔
1405
      // these are signature algorithms only
1406
      result.test_is_false("all constraints not permitted", all.compatible_with(key));
9✔
1407

1408
      result.test_is_true("ca allowed", ca.compatible_with(key));
9✔
1409
      result.test_is_true("sign allowed", sign_data.compatible_with(key));
9✔
1410
      result.test_is_true("non-repudiation allowed", non_repudiation.compatible_with(key));
9✔
1411
      result.test_is_false("key encipherment not permitted", key_encipherment.compatible_with(key));
9✔
1412
      result.test_is_false("data encipherment not permitted", data_encipherment.compatible_with(key));
9✔
1413
      result.test_is_false("key agreement not permitted", key_agreement.compatible_with(key));
9✔
1414
      result.test_is_false("key agreement", key_agreement_encipher_only.compatible_with(key));
9✔
1415
      result.test_is_false("key agreement", key_agreement_decipher_only.compatible_with(key));
9✔
1416
      result.test_is_true("crl sign allowed", crl_sign.compatible_with(key));
9✔
1417
      result.test_is_true("sign allowed", sign_everything.compatible_with(key));
9✔
1418
   }
1419

1420
   return result;
19✔
1421
}
×
1422

1423
/**
1424
 * @brief X.509v3 extension that encodes a given string
1425
 */
1426
class String_Extension final : public Botan::Certificate_Extension {
22✔
1427
   public:
1428
      String_Extension() = default;
22✔
1429

1430
      explicit String_Extension(const std::string& val) : m_contents(val) {}
11✔
1431

1432
      std::string value() const { return m_contents; }
44✔
1433

1434
      std::unique_ptr<Certificate_Extension> copy() const override {
×
1435
         return std::make_unique<String_Extension>(m_contents);
×
1436
      }
1437

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

1440
      bool should_encode() const override { return true; }
22✔
1441

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

1444
      std::vector<uint8_t> encode_inner() const override {
11✔
1445
         std::vector<uint8_t> bits;
11✔
1446
         Botan::DER_Encoder(bits).encode(Botan::ASN1_String(m_contents, Botan::ASN1_Type::Utf8String));
22✔
1447
         return bits;
11✔
1448
      }
×
1449

1450
      void decode_inner(const std::vector<uint8_t>& in) override {
22✔
1451
         Botan::ASN1_String str;
22✔
1452
         Botan::BER_Decoder(in).decode(str, Botan::ASN1_Type::Utf8String).verify_end();
22✔
1453
         m_contents = str.value();
44✔
1454
      }
22✔
1455

1456
   private:
1457
      std::string m_contents;
1458
};
1459

1460
Test::Result test_custom_dn_attr(const Botan::Private_Key& ca_key,
11✔
1461
                                 const std::string& sig_algo,
1462
                                 const std::string& sig_padding,
1463
                                 const std::string& hash_fn,
1464
                                 Botan::RandomNumberGenerator& rng) {
1465
   Test::Result result("X509 Custom DN");
11✔
1466

1467
   /* Create the self-signed cert */
1468
   const Botan::X509_Certificate ca_cert =
11✔
1469
      Botan::X509::create_self_signed_cert(ca_opts(sig_padding), ca_key, hash_fn, rng);
11✔
1470

1471
   /* Create the CA object */
1472
   const Botan::X509_CA ca(ca_cert, ca_key, hash_fn, sig_padding, rng);
11✔
1473

1474
   auto user_key = make_a_private_key(sig_algo, rng);
11✔
1475

1476
   Botan::X509_DN subject_dn;
11✔
1477

1478
   const Botan::OID attr1(Botan::OID("1.3.6.1.4.1.25258.9.1.1"));
11✔
1479
   const Botan::OID attr2(Botan::OID("1.3.6.1.4.1.25258.9.1.2"));
11✔
1480
   const Botan::ASN1_String val1("Custom Attr 1", Botan::ASN1_Type::PrintableString);
11✔
1481
   const Botan::ASN1_String val2("12345", Botan::ASN1_Type::Utf8String);
11✔
1482

1483
   subject_dn.add_attribute(attr1, val1);
11✔
1484
   subject_dn.add_attribute(attr2, val2);
11✔
1485

1486
   const Botan::Extensions extensions;
11✔
1487

1488
   const Botan::PKCS10_Request req =
11✔
1489
      Botan::PKCS10_Request::create(*user_key, subject_dn, extensions, hash_fn, rng, sig_padding);
11✔
1490

1491
   const Botan::X509_DN& req_dn = req.subject_dn();
11✔
1492

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

1495
   const Botan::ASN1_String req_val1 = req_dn.get_first_attribute(attr1);
11✔
1496
   const Botan::ASN1_String req_val2 = req_dn.get_first_attribute(attr2);
11✔
1497
   result.test_is_true("Attr1 matches encoded", req_val1 == val1);
11✔
1498
   result.test_is_true("Attr2 matches encoded", req_val2 == val2);
11✔
1499
   result.test_is_true("Attr1 tag matches encoded", req_val1.tagging() == val1.tagging());
11✔
1500
   result.test_is_true("Attr2 tag matches encoded", req_val2.tagging() == val2.tagging());
11✔
1501

1502
   const Botan::X509_Time not_before("100301123001Z", Botan::ASN1_Type::UtcTime);
11✔
1503
   const Botan::X509_Time not_after("300301123001Z", Botan::ASN1_Type::UtcTime);
11✔
1504

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

1507
   const Botan::X509_DN& cert_dn = cert.subject_dn();
11✔
1508

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

1511
   const Botan::ASN1_String cert_val1 = cert_dn.get_first_attribute(attr1);
11✔
1512
   const Botan::ASN1_String cert_val2 = cert_dn.get_first_attribute(attr2);
11✔
1513
   result.test_is_true("Attr1 matches encoded", cert_val1 == val1);
11✔
1514
   result.test_is_true("Attr2 matches encoded", cert_val2 == val2);
11✔
1515
   result.test_is_true("Attr1 tag matches encoded", cert_val1.tagging() == val1.tagging());
11✔
1516
   result.test_is_true("Attr2 tag matches encoded", cert_val2.tagging() == val2.tagging());
11✔
1517

1518
   return result;
22✔
1519
}
99✔
1520

1521
Test::Result test_x509_extensions(const Botan::Private_Key& ca_key,
11✔
1522
                                  const std::string& sig_algo,
1523
                                  const std::string& sig_padding,
1524
                                  const std::string& hash_fn,
1525
                                  Botan::RandomNumberGenerator& rng) {
1526
   using Botan::Key_Constraints;
11✔
1527

1528
   Test::Result result("X509 Extensions");
11✔
1529

1530
   /* Create the self-signed cert */
1531
   const Botan::X509_Certificate ca_cert =
11✔
1532
      Botan::X509::create_self_signed_cert(ca_opts(sig_padding), ca_key, hash_fn, rng);
11✔
1533

1534
   /* Create the CA object */
1535
   const Botan::X509_CA ca(ca_cert, ca_key, hash_fn, sig_padding, rng);
11✔
1536

1537
   /* Prepare CDP extension */
1538
   std::vector<std::string> cdp_urls = {
11✔
1539
      "http://example.com/crl1.pem",
1540
      "ldap://ldap.example.com/cn=crl1,dc=example,dc=com?certificateRevocationList;binary"};
11✔
1541

1542
   std::vector<Botan::Cert_Extension::CRL_Distribution_Points::Distribution_Point> dps;
11✔
1543

1544
   for(const auto& uri : cdp_urls) {
33✔
1545
      Botan::AlternativeName cdp_alt_name;
22✔
1546
      cdp_alt_name.add_uri(uri);
22✔
1547
      const Botan::Cert_Extension::CRL_Distribution_Points::Distribution_Point dp(cdp_alt_name);
22✔
1548

1549
      dps.emplace_back(dp);
22✔
1550
   }
22✔
1551

1552
   auto user_key = make_a_private_key(sig_algo, rng);
11✔
1553

1554
   Botan::X509_Cert_Options opts("Test User 1/US/Botan Project/Testing");
11✔
1555
   opts.constraints = Key_Constraints::DigitalSignature;
11✔
1556

1557
   // include a custom extension in the request
1558
   Botan::Extensions req_extensions;
11✔
1559
   const Botan::OID oid("1.2.3.4.5.6.7.8.9.1");
11✔
1560
   const Botan::OID ku_oid = Botan::OID::from_string("X509v3.KeyUsage");
11✔
1561
   req_extensions.add(std::make_unique<String_Extension>("AAAAAAAAAAAAAABCDEF"), false);
22✔
1562
   req_extensions.add(std::make_unique<Botan::Cert_Extension::CRL_Distribution_Points>(dps));
22✔
1563
   opts.extensions = req_extensions;
11✔
1564
   opts.set_padding_scheme(sig_padding);
11✔
1565

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

1569
   result.test_is_true("Extensions::extension_set true for Key_Usage",
11✔
1570
                       self_signed_cert.v3_extensions().extension_set(ku_oid));
11✔
1571

1572
   // check if known Key_Usage extension is present in self-signed cert
1573
   auto key_usage_ext = self_signed_cert.v3_extensions().get(ku_oid);
11✔
1574
   if(result.test_is_true("Key_Usage extension present in self-signed certificate", key_usage_ext != nullptr)) {
11✔
1575
      result.test_is_true(
22✔
1576
         "Key_Usage extension value matches in self-signed certificate",
1577
         dynamic_cast<Botan::Cert_Extension::Key_Usage&>(*key_usage_ext).get_constraints() == opts.constraints);
11✔
1578
   }
1579

1580
   // check if custom extension is present in self-signed cert
1581
   auto string_ext = self_signed_cert.v3_extensions().get_raw<String_Extension>(oid);
11✔
1582
   if(result.test_is_true("Custom extension present in self-signed certificate", string_ext != nullptr)) {
11✔
1583
      result.test_str_eq(
22✔
1584
         "Custom extension value matches in self-signed certificate", string_ext->value(), "AAAAAAAAAAAAAABCDEF");
33✔
1585
   }
1586

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

1591
   if(result.test_is_true("CRL Distribution Points extension present in self-signed certificate",
11✔
1592
                          !cert_cdps->crl_distribution_urls().empty())) {
11✔
1593
      for(const auto& cdp : cert_cdps->distribution_points()) {
33✔
1594
         result.test_is_true("CDP URI present in self-signed certificate",
44✔
1595
                             std::ranges::find(cdp_urls, cdp.point().get_first_attribute("URI")) != cdp_urls.end());
66✔
1596
      }
1597

1598
      // The accessor returns one entry per URI
1599
      const auto& urls = cert_cdps->crl_distribution_urls();
11✔
1600
      result.test_sz_eq("crl_distribution_urls has one entry per URI (self-signed)", urls.size(), cdp_urls.size());
11✔
1601
      for(const auto& url : urls) {
33✔
1602
         result.test_is_true("crl_distribution_urls entry is a bare URI (self-signed)",
44✔
1603
                             std::ranges::find(cdp_urls, url) != cdp_urls.end());
44✔
1604
      }
1605

1606
      // Ensure X509_Certificate's cached accessor returns the same bare URIs
1607
      const auto cert_dp = self_signed_cert.crl_distribution_points();
11✔
1608
      result.test_sz_eq(
11✔
1609
         "X509_Certificate::crl_distribution_points size (self-signed)", cert_dp.size(), cdp_urls.size());
1610
      for(const auto& url : cert_dp) {
33✔
1611
         result.test_is_true("X509_Certificate::crl_distribution_points entry is a bare URI (self-signed)",
44✔
1612
                             std::ranges::find(cdp_urls, url) != cdp_urls.end());
44✔
1613
      }
1614
   }
11✔
1615

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

1618
   /* Create a CA-signed certificate */
1619
   const Botan::X509_Certificate ca_signed_cert =
11✔
1620
      ca.sign_request(user_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
11✔
1621

1622
   // check if known Key_Usage extension is present in CA-signed cert
1623
   result.test_is_true("Extensions::extension_set true for Key_Usage",
11✔
1624
                       ca_signed_cert.v3_extensions().extension_set(ku_oid));
11✔
1625

1626
   key_usage_ext = ca_signed_cert.v3_extensions().get(ku_oid);
22✔
1627
   if(result.test_is_true("Key_Usage extension present in CA-signed certificate", key_usage_ext != nullptr)) {
11✔
1628
      auto constraints = dynamic_cast<Botan::Cert_Extension::Key_Usage&>(*key_usage_ext).get_constraints();
11✔
1629
      result.test_is_true("Key_Usage extension value matches in user certificate",
22✔
1630
                          constraints == Botan::Key_Constraints::DigitalSignature);
11✔
1631
   }
1632

1633
   // check if custom extension is present in CA-signed cert
1634
   result.test_is_true("Extensions::extension_set true for String_Extension",
11✔
1635
                       ca_signed_cert.v3_extensions().extension_set(oid));
11✔
1636
   string_ext = ca_signed_cert.v3_extensions().get_raw<String_Extension>(oid);
22✔
1637
   if(result.test_is_true("Custom extension present in CA-signed certificate", string_ext != nullptr)) {
11✔
1638
      result.test_str_eq(
22✔
1639
         "Custom extension value matches in CA-signed certificate", string_ext->value(), "AAAAAAAAAAAAAABCDEF");
33✔
1640
   }
1641

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

1645
   if(result.test_is_true("CRL Distribution Points extension present in CA-signed certificate",
11✔
1646
                          !cert_cdps->crl_distribution_urls().empty())) {
11✔
1647
      for(const auto& cdp : cert_cdps->distribution_points()) {
33✔
1648
         result.test_is_true("CDP URI present in CA-signed certificate",
44✔
1649
                             std::ranges::find(cdp_urls, cdp.point().get_first_attribute("URI")) != cdp_urls.end());
66✔
1650
      }
1651

1652
      const auto& urls = cert_cdps->crl_distribution_urls();
11✔
1653
      result.test_sz_eq("crl_distribution_urls has one entry per URI (CA-signed)", urls.size(), cdp_urls.size());
11✔
1654
      for(const auto& url : urls) {
33✔
1655
         result.test_is_true("crl_distribution_urls entry is a bare URI (CA-signed)",
44✔
1656
                             std::ranges::find(cdp_urls, url) != cdp_urls.end());
44✔
1657
      }
1658

1659
      const auto cert_dp = ca_signed_cert.crl_distribution_points();
11✔
1660
      result.test_sz_eq("X509_Certificate::crl_distribution_points size (CA-signed)", cert_dp.size(), cdp_urls.size());
11✔
1661
      for(const auto& url : cert_dp) {
33✔
1662
         result.test_is_true("X509_Certificate::crl_distribution_points entry is a bare URI (CA-signed)",
44✔
1663
                             std::ranges::find(cdp_urls, url) != cdp_urls.end());
44✔
1664
      }
1665
   }
11✔
1666

1667
   return result;
11✔
1668
}
55✔
1669

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

1673
   struct TestData {
12✔
1674
         const std::string issuer, subject, issuer_hash, subject_hash;
1675
   } const cases[]{{"",
12✔
1676
                    "",
1677
                    "E4F60D0AA6D7F3D3B6A6494B1C861B99F649C6F9EC51ABAF201B20F297327C95",
1678
                    "E4F60D0AA6D7F3D3B6A6494B1C861B99F649C6F9EC51ABAF201B20F297327C95"},
1679
                   {"a",
1680
                    "b",
1681
                    "BC2E013472F39AC579964880E422737C82BA812CB8BC2FD17E013060D71E6E19",
1682
                    "5E31CFAA3FAFB1A5BA296A0D2BAB9CA44D7936E9BF0BBC54637D0C53DBC4A432"},
1683
                   {"A",
1684
                    "B",
1685
                    "4B3206201C4BC9B6CD6C36532A97687DF9238155D99ADB60C66BF2B2220643D8",
1686
                    "FFF635A52A16618B4A0E9CD26B5E5A2FA573D343C051E6DE8B0811B1ACC89B86"},
1687
                   {
1688
                      "Test Issuer/US/Botan Project/Testing",
1689
                      "Test Subject/US/Botan Project/Testing",
1690
                      "ACB4F373004A56A983A23EB8F60FA4706312B5DB90FD978574FE7ACC84E093A5",
1691
                      "87039231C2205B74B6F1F3830A66272C0B41F71894B03AC3150221766D95267B",
1692
                   },
1693
                   {
1694
                      "Test Subject/US/Botan Project/Testing",
1695
                      "Test Issuer/US/Botan Project/Testing",
1696
                      "87039231C2205B74B6F1F3830A66272C0B41F71894B03AC3150221766D95267B",
1697
                      "ACB4F373004A56A983A23EB8F60FA4706312B5DB90FD978574FE7ACC84E093A5",
1698
                   }};
72✔
1699

1700
   for(const auto& a : cases) {
72✔
1701
      Botan::X509_Cert_Options opts{a.issuer};
60✔
1702
      opts.CA_key();
60✔
1703

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

1706
      result.test_str_eq(a.issuer, Botan::hex_encode(issuer_cert.raw_issuer_dn_sha256()), a.issuer_hash);
60✔
1707
      result.test_str_eq(a.issuer, Botan::hex_encode(issuer_cert.raw_subject_dn_sha256()), a.issuer_hash);
60✔
1708

1709
      const Botan::X509_CA ca(issuer_cert, key, hash_fn, rng);
60✔
1710
      const Botan::PKCS10_Request req =
60✔
1711
         Botan::X509::create_cert_req(Botan::X509_Cert_Options(a.subject), key, hash_fn, rng);
60✔
1712
      const Botan::X509_Certificate subject_cert =
60✔
1713
         ca.sign_request(req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
60✔
1714

1715
      result.test_str_eq(a.subject, Botan::hex_encode(subject_cert.raw_issuer_dn_sha256()), a.issuer_hash);
60✔
1716
      result.test_str_eq(a.subject, Botan::hex_encode(subject_cert.raw_subject_dn_sha256()), a.subject_hash);
60✔
1717
   }
60✔
1718
   return result;
12✔
1719
}
72✔
1720

1721
   #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
1722

1723
Test::Result test_x509_tn_auth_list_extension_decode() {
1✔
1724
   /* cert with TNAuthList extension data was generated by asn1parse cfg:
1725

1726
      asn1=SEQUENCE:tn_auth_list
1727

1728
      [tn_auth_list]
1729
      spc=EXP:0,IA5:1001
1730
      range=EXP:1,SEQUENCE:TelephoneNumberRange
1731
      one=EXP:2,IA5:333
1732

1733
      [TelephoneNumberRange]
1734
      start1=IA5:111
1735
      count1=INT:128
1736
      start2=IA5:222
1737
      count2=INT:256
1738
    */
1739
   const std::string filename("TNAuthList.pem");
1✔
1740
   Test::Result result("X509 TNAuthList decode");
1✔
1741
   result.start_timer();
1✔
1742

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

1745
   using Botan::Cert_Extension::TNAuthList;
1✔
1746

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

1749
   const auto& tn_entries = tn_auth_list->entries();
1✔
1750

1751
   result.test_not_null("cert has TNAuthList extension", tn_auth_list);
1✔
1752

1753
   result.test_throws("wrong telephone_number_range() accessor for spc",
1✔
1754
                      [&tn_entries] { tn_entries[0].telephone_number_range(); });
2✔
1755
   result.test_throws("wrong telephone_number() accessor for range",
1✔
1756
                      [&tn_entries] { tn_entries[1].telephone_number(); });
2✔
1757
   result.test_throws("wrong service_provider_code() accessor for one",
1✔
1758
                      [&tn_entries] { tn_entries[2].service_provider_code(); });
2✔
1759

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

1763
   result.test_is_true("range entry type", tn_entries[1].type() == TNAuthList::Entry::TelephoneNumberRange);
1✔
1764
   const auto& range = tn_entries[1].telephone_number_range();
1✔
1765
   result.test_sz_eq("range entries count", range.size(), 2);
1✔
1766
   result.test_str_eq("range entry 0 start data", range[0].start.value(), "111");
1✔
1767
   result.test_sz_eq("range entry 0 count data", range[0].count, 128);
1✔
1768
   result.test_str_eq("range entry 1 start data", range[1].start.value(), "222");
1✔
1769
   result.test_sz_eq("range entry 1 count data", range[1].count, 256);
1✔
1770

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

1774
   result.end_timer();
1✔
1775
   return result;
2✔
1776
}
1✔
1777

1778
   #endif
1779

1780
std::vector<std::string> get_sig_paddings(const std::string& sig_algo, const std::string& hash) {
12✔
1781
   if(sig_algo == "RSA") {
12✔
1782
      return {
1✔
1783
   #if defined(BOTAN_HAS_EMSA_PKCS1)
1784
         "PKCS1v15(" + hash + ")",
1✔
1785
   #endif
1786
   #if defined(BOTAN_HAS_EMSA_PSS)
1787
            "PSS(" + hash + ")",
1788
   #endif
1789
      };
3✔
1790
   } else if(sig_algo == "DSA" || sig_algo == "ECDSA" || sig_algo == "ECGDSA" || sig_algo == "ECKCDSA" ||
11✔
1791
             sig_algo == "GOST-34.10") {
7✔
1792
      return {hash};
10✔
1793
   } else if(sig_algo == "Ed25519" || sig_algo == "Ed448") {
6✔
1794
      return {"Pure"};
2✔
1795
   } else if(sig_algo == "Dilithium" || sig_algo == "ML-DSA") {
4✔
1796
      return {"Randomized"};
2✔
1797
   } else if(sig_algo == "HSS-LMS") {
2✔
1798
      return {""};
1✔
1799
   } else {
1800
      return {};
1✔
1801
   }
1802
}
6✔
1803

1804
class X509_Cert_Unit_Tests final : public Test {
1✔
1805
   public:
1806
      std::vector<Test::Result> run() override {
1✔
1807
         std::vector<Test::Result> results;
1✔
1808

1809
         auto& rng = this->rng();
1✔
1810

1811
         const std::string sig_algos[]{"RSA",
1✔
1812
                                       "DSA",
1813
                                       "ECDSA",
1814
                                       "ECGDSA",
1815
                                       "ECKCDSA",
1816
                                       "GOST-34.10",
1817
                                       "Ed25519",
1818
                                       "Ed448",
1819
                                       "Dilithium",
1820
                                       "ML-DSA",
1821
                                       "SLH-DSA",
1822
                                       "HSS-LMS"};
13✔
1823

1824
         for(const std::string& algo : sig_algos) {
13✔
1825
   #if !defined(BOTAN_HAS_EMSA_PKCS1)
1826
            if(algo == "RSA")
1827
               continue;
1828
   #endif
1829

1830
            std::string hash = "SHA-256";
12✔
1831

1832
            if(algo == "Ed25519") {
12✔
1833
               hash = "SHA-512";
1✔
1834
            }
1835
            if(algo == "Ed448") {
12✔
1836
               hash = "SHAKE-256(912)";
1✔
1837
            }
1838
            if(algo == "Dilithium" || algo == "ML-DSA") {
12✔
1839
               hash = "SHAKE-256(512)";
2✔
1840
            }
1841

1842
            auto key = make_a_private_key(algo, rng);
12✔
1843

1844
            if(key == nullptr) {
12✔
1845
               continue;
×
1846
            }
1847

1848
            results.push_back(test_hashes(*key, hash, rng));
24✔
1849
            results.push_back(test_valid_constraints(*key, algo));
24✔
1850

1851
            Test::Result usage_result("X509 Usage");
12✔
1852
            try {
12✔
1853
               usage_result.merge(test_usage(*key, algo, hash, rng));
12✔
1854
            } catch(std::exception& e) {
×
1855
               usage_result.test_failure("test_usage " + algo, e.what());
×
1856
            }
×
1857
            results.push_back(usage_result);
12✔
1858

1859
            for(const auto& padding_scheme : get_sig_paddings(algo, hash)) {
23✔
1860
               Test::Result cert_result("X509 Unit");
11✔
1861

1862
               try {
11✔
1863
                  cert_result.merge(test_x509_cert(*key, algo, padding_scheme, hash, rng));
11✔
1864
               } catch(std::exception& e) {
×
1865
                  cert_result.test_failure("test_x509_cert " + algo, e.what());
×
1866
               }
×
1867
               results.push_back(cert_result);
11✔
1868

1869
               Test::Result pkcs10_result("PKCS10 extensions");
11✔
1870
               try {
11✔
1871
                  pkcs10_result.merge(test_pkcs10_ext(*key, padding_scheme, hash, rng));
11✔
1872
               } catch(std::exception& e) {
×
1873
                  pkcs10_result.test_failure("test_pkcs10_ext " + algo, e.what());
×
1874
               }
×
1875
               results.push_back(pkcs10_result);
11✔
1876

1877
               Test::Result self_issued_result("X509 Self Issued");
11✔
1878
               try {
11✔
1879
                  self_issued_result.merge(test_self_issued(*key, algo, padding_scheme, hash, rng));
11✔
1880
               } catch(std::exception& e) {
×
1881
                  self_issued_result.test_failure("test_self_issued " + algo, e.what());
×
1882
               }
×
1883
               results.push_back(self_issued_result);
11✔
1884

1885
               Test::Result extensions_result("X509 Extensions");
11✔
1886
               try {
11✔
1887
                  extensions_result.merge(test_x509_extensions(*key, algo, padding_scheme, hash, rng));
11✔
1888
               } catch(std::exception& e) {
×
1889
                  extensions_result.test_failure("test_extensions " + algo, e.what());
×
1890
               }
×
1891
               results.push_back(extensions_result);
11✔
1892

1893
               Test::Result custom_dn_result("X509 Custom DN");
11✔
1894
               try {
11✔
1895
                  custom_dn_result.merge(test_custom_dn_attr(*key, algo, padding_scheme, hash, rng));
11✔
1896
               } catch(std::exception& e) {
×
1897
                  custom_dn_result.test_failure("test_custom_dn_attr " + algo, e.what());
×
1898
               }
×
1899
               results.push_back(custom_dn_result);
11✔
1900
            }
23✔
1901
         }
24✔
1902

1903
         /*
1904
         These are algos which cannot sign but can be included in certs
1905
         */
1906
         const std::vector<std::string> enc_algos = {
1✔
1907
            "DH", "ECDH", "ElGamal", "Kyber", "ML-KEM", "FrodoKEM", "ClassicMcEliece"};
1✔
1908

1909
         for(const std::string& algo : enc_algos) {
8✔
1910
            auto key = make_a_private_key(algo, rng);
7✔
1911

1912
            if(key) {
7✔
1913
               results.push_back(test_valid_constraints(*key, algo));
14✔
1914
            }
1915
         }
7✔
1916

1917
   #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM) && defined(BOTAN_HAS_EMSA_PKCS1) && defined(BOTAN_HAS_EMSA_PSSR) && \
1918
      defined(BOTAN_HAS_RSA)
1919
         Test::Result pad_config_result("X509 Padding Config");
1✔
1920
         try {
1✔
1921
            pad_config_result.merge(test_padding_config());
1✔
1922
         } catch(const std::exception& e) {
×
1923
            pad_config_result.test_failure("test_padding_config", e.what());
×
1924
         }
×
1925
         results.push_back(pad_config_result);
1✔
1926
   #endif
1927

1928
   #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
1929
         results.push_back(test_x509_utf8());
2✔
1930
         results.push_back(test_x509_any_key_extended_usage());
2✔
1931
         results.push_back(test_x509_bmpstring());
2✔
1932
         results.push_back(test_x509_teletex());
2✔
1933
         results.push_back(test_crl_dn_name());
2✔
1934
         results.push_back(test_rdn_multielement_set_name());
2✔
1935
         results.push_back(test_x509_decode_list());
2✔
1936
         results.push_back(test_rsa_oaep());
2✔
1937
         results.push_back(test_x509_authority_info_access_extension());
2✔
1938
         results.push_back(test_crl_issuing_distribution_point_extension());
2✔
1939
         results.push_back(test_verify_gost2012_cert());
2✔
1940
         results.push_back(test_parse_rsa_pss_cert());
2✔
1941
         results.push_back(test_x509_tn_auth_list_extension_decode());
2✔
1942
   #endif
1943

1944
         results.push_back(test_x509_encode_authority_info_access_extension());
2✔
1945
         results.push_back(test_x509_extension());
2✔
1946
         results.push_back(test_x509_extension_decode_duplicate());
2✔
1947
         results.push_back(test_x509_dates());
2✔
1948
         results.push_back(test_cert_status_strings());
2✔
1949
         results.push_back(test_x509_uninit());
2✔
1950
         results.push_back(test_crl_entry_negative_serial());
2✔
1951

1952
         return results;
1✔
1953
      }
13✔
1954
};
1955

1956
BOTAN_REGISTER_TEST("x509", "x509_unit", X509_Cert_Unit_Tests);
1957

1958
#endif
1959

1960
}  // namespace
1961

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