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

randombit / botan / 26141725099

19 May 2026 08:32PM UTC coverage: 89.343% (+0.009%) from 89.334%
26141725099

push

github

web-flow
Merge pull request #5609 from randombit/jack/improve-http

Improve the HTTP 1.0 client used for OCSP/CRL

109341 of 122383 relevant lines covered (89.34%)

11264402.07 hits per line

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

94.48
/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<Botan::URI> ca_issuers = {
1✔
460
      Botan::URI::parse("http://www.d-trust.net/cgi-bin/Bdrive_Test_CA_1-2_2017.crt").value(),
1✔
461
      Botan::URI::parse(
2✔
462
         "ldap://directory.d-trust.net/CN=Bdrive%20Test%20CA%201-2%202017,O=Bundesdruckerei%20GmbH,C=DE?cACertificate?base?")
463
         .value()};
3✔
464

465
   // OCSP
466
   const std::string_view ocsp_uri{"http://staging.ocsp.d-trust.net"};
1✔
467
   const auto ocsp_uri_parsed = Botan::URI::parse(ocsp_uri).value();
2✔
468

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

475
   // create a certificate with only caIssuer information
476
   auto key = make_a_private_key(sig_algo, *rng);
1✔
477

478
   Botan::X509_Cert_Options opts1 = req_opts1(sig_algo);
1✔
479
   opts1.extensions.add(
2✔
480
      std::make_unique<Botan::Cert_Extension::Authority_Information_Access>(std::vector<Botan::URI>{}, ca_issuers));
2✔
481

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

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

486
   if(!result.test_sz_eq("number of ca_issuers URIs", cert.ca_issuer_uris().size(), 2)) {
1✔
487
      return result;
488
   }
489

490
   for(const auto& ca_issuer : cert.ca_issuer_uris()) {
3✔
491
      result.test_is_true("CA issuer URI present in certificate",
4✔
492
                          std::ranges::find(ca_issuers, ca_issuer) != ca_issuers.end());
6✔
493
   }
494

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

497
   // create a certificate with only OCSP URI information
498
   Botan::X509_Cert_Options opts2 = req_opts1(sig_algo);
1✔
499
   opts2.extensions.add(
2✔
500
      std::make_unique<Botan::Cert_Extension::Authority_Information_Access>(std::vector<Botan::URI>{ocsp_uri_parsed}));
3✔
501

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

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

506
   result.test_is_true("OCSP URI available", !cert.ocsp_responder_uris().empty());
1✔
507
   result.test_is_true("no CA Issuer URI available", cert.ca_issuer_uris().empty());
1✔
508
   result.test_str_eq("OCSP responder URI matches", cert.ocsp_responder_uris().at(0).original_input(), ocsp_uri);
1✔
509

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

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

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

519
   result.test_is_true("OCSP URI available", !cert.ocsp_responder_uris().empty());
1✔
520
   result.test_is_true("CA Issuer URI available", !cert.ca_issuer_uris().empty());
1✔
521

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

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

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

531
   const auto* aia_ext =
1✔
532
      cert.v3_extensions().get_extension_object_as<Botan::Cert_Extension::Authority_Information_Access>();
1✔
533
   result.test_is_true("AIA extension present", aia_ext != nullptr);
1✔
534

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

540
   const auto& cert_ocsp_responders = cert.ocsp_responder_uris();
1✔
541
   result.test_sz_eq("Certificate: number of OCSP responder URIs", cert_ocsp_responders.size(), 2);
1✔
542
   result.test_str_eq("Certificate: First OCSP responder URI matches",
1✔
543
                      cert_ocsp_responders[0].original_input(),
1✔
544
                      "http://ocsp.example.com");
545
   result.test_str_eq("Certificate: Second OCSP responder URI matches",
1✔
546
                      cert_ocsp_responders[1].original_input(),
1✔
547
                      "http://backup-ocsp.example.com");
548
   #endif
549

550
   return result;
1✔
551
}
9✔
552

553
   #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
554

555
Test::Result test_crl_dn_name() {
1✔
556
   Test::Result result("CRL DN name");
1✔
557

558
      // See GH #1252
559

560
      #if defined(BOTAN_HAS_RSA) && defined(BOTAN_HAS_EMSA_PKCS1)
561
   auto rng = Test::new_rng(__func__);
1✔
562

563
   const Botan::OID dc_oid("0.9.2342.19200300.100.1.25");
1✔
564

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

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

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

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

575
   result.test_is_true("contains DC component", crl.issuer_dn().get_attributes().count(dc_oid) == 1);
2✔
576
      #endif
577

578
   return result;
2✔
579
}
3✔
580

581
Test::Result test_rdn_multielement_set_name() {
1✔
582
   Test::Result result("DN with multiple elements in RDN");
1✔
583

584
   // GH #2611
585

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

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

591
   return result;
1✔
592
}
1✔
593

594
Test::Result test_rsa_oaep() {
1✔
595
   Test::Result result("RSA OAEP decoding");
1✔
596

597
      #if defined(BOTAN_HAS_RSA)
598
   const Botan::X509_Certificate cert(Test::data_file("x509/misc/rsa_oaep.pem"));
2✔
599

600
   auto public_key = cert.subject_public_key();
1✔
601
   result.test_not_null("Decoding RSA-OAEP worked", public_key.get());
1✔
602
   const auto& pk_info = cert.subject_public_key_algo();
1✔
603

604
   result.test_str_eq("RSA-OAEP OID", pk_info.oid().to_string(), Botan::OID::from_string("RSA/OAEP").to_string());
1✔
605
      #endif
606

607
   return result;
2✔
608
}
1✔
609

610
Test::Result test_x509_decode_list() {
1✔
611
   Test::Result result("X509_Certificate list decode");
1✔
612

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

615
   Botan::BER_Decoder dec(input);
1✔
616
   std::vector<Botan::X509_Certificate> certs;
1✔
617
   dec.decode_list(certs);
1✔
618

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

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

624
   return result;
1✔
625
}
1✔
626

627
Test::Result test_x509_utf8() {
1✔
628
   Test::Result result("X509 with UTF-8 encoded fields");
1✔
629

630
   try {
1✔
631
      const Botan::X509_Certificate utf8_cert(Test::data_file("x509/misc/contains_utf8string.pem"));
2✔
632

633
      // UTF-8 encoded fields of test certificate (contains cyrillic letters)
634
      const std::string organization =
1✔
635
         "\xD0\x9C\xD0\xBE\xD1\x8F\x20\xD0\xBA\xD0\xBE\xD0"
636
         "\xBC\xD0\xBF\xD0\xB0\xD0\xBD\xD0\xB8\xD1\x8F";
1✔
637
      const std::string organization_unit =
1✔
638
         "\xD0\x9C\xD0\xBE\xD1\x91\x20\xD0\xBF\xD0\xBE\xD0\xB4\xD1\x80\xD0\xB0"
639
         "\xD0\xB7\xD0\xB4\xD0\xB5\xD0\xBB\xD0\xB5\xD0\xBD\xD0\xB8\xD0\xB5";
1✔
640
      const std::string common_name =
1✔
641
         "\xD0\x9E\xD0\xBF\xD0\xB8\xD1\x81\xD0\xB0\xD0\xBD\xD0\xB8"
642
         "\xD0\xB5\x20\xD1\x81\xD0\xB0\xD0\xB9\xD1\x82\xD0\xB0";
1✔
643
      const std::string location = "\xD0\x9C\xD0\xBE\xD1\x81\xD0\xBA\xD0\xB2\xD0\xB0";
1✔
644

645
      const Botan::X509_DN& issuer_dn = utf8_cert.issuer_dn();
1✔
646

647
      result.test_str_eq("O", issuer_dn.get_first_attribute("O"), organization);
1✔
648
      result.test_str_eq("OU", issuer_dn.get_first_attribute("OU"), organization_unit);
1✔
649
      result.test_str_eq("CN", issuer_dn.get_first_attribute("CN"), common_name);
1✔
650
      result.test_str_eq("L", issuer_dn.get_first_attribute("L"), location);
1✔
651
   } catch(const Botan::Decoding_Error& ex) {
1✔
652
      result.test_failure(ex.what());
×
653
   }
×
654

655
   return result;
1✔
656
}
×
657

658
Test::Result test_x509_any_key_extended_usage() {
1✔
659
   using Botan::Key_Constraints;
1✔
660
   using Botan::Usage_Type;
1✔
661

662
   Test::Result result("X509 with X509v3.AnyExtendedKeyUsage");
1✔
663
   try {
1✔
664
      const Botan::X509_Certificate any_eku_cert(Test::data_file("x509/misc/contains_any_extended_key_usage.pem"));
2✔
665

666
      result.test_is_true("is CA cert", any_eku_cert.is_CA_cert());
1✔
667
      result.test_is_true("DigitalSignature is allowed", any_eku_cert.allowed_usage(Key_Constraints::DigitalSignature));
1✔
668
      result.test_is_true("CrlSign is allowed", any_eku_cert.allowed_usage(Key_Constraints::CrlSign));
1✔
669
      result.test_is_false("OCSP responder is not allowed", any_eku_cert.allowed_usage(Usage_Type::OCSP_RESPONDER));
1✔
670
   } catch(const Botan::Decoding_Error& ex) {
1✔
671
      result.test_failure(ex.what());
×
672
   }
×
673
   return result;
1✔
674
}
×
675

676
Test::Result test_x509_bmpstring() {
1✔
677
   Test::Result result("X509 with UCS-2 (BMPString) encoded fields");
1✔
678

679
   try {
1✔
680
      const Botan::X509_Certificate ucs2_cert(Test::data_file("x509/misc/contains_bmpstring.pem"));
2✔
681

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

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

690
      const Botan::X509_DN& issuer_dn = ucs2_cert.issuer_dn();
1✔
691

692
      result.test_str_eq("O", issuer_dn.get_first_attribute("O"), organization);
1✔
693
      result.test_str_eq("CN", issuer_dn.get_first_attribute("CN"), common_name);
1✔
694
      result.test_str_eq("L", issuer_dn.get_first_attribute("L"), location);
1✔
695
   } catch(const Botan::Decoding_Error& ex) {
1✔
696
      result.test_failure(ex.what());
×
697
   }
×
698

699
   return result;
1✔
700
}
×
701

702
Test::Result test_x509_teletex() {
1✔
703
   Test::Result result("X509 with TeletexString encoded fields");
1✔
704

705
   try {
1✔
706
      const Botan::X509_Certificate teletex_cert(Test::data_file("x509/misc/teletex_dn.der"));
2✔
707

708
      const Botan::X509_DN& issuer_dn = teletex_cert.issuer_dn();
1✔
709

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

712
      result.test_str_eq("O", issuer_dn.get_first_attribute("O"), "neam CA");
1✔
713
      result.test_str_eq("CN", issuer_dn.get_first_attribute("CN"), common_name);
1✔
714
   } catch(const Botan::Decoding_Error& ex) {
1✔
715
      result.test_failure(ex.what());
×
716
   }
×
717

718
   return result;
1✔
719
}
×
720

721
Test::Result test_x509_authority_info_access_extension() {
1✔
722
   Test::Result result("X509 with PKIX.AuthorityInformationAccess extension");
1✔
723

724
   // contains no AIA extension
725
   const Botan::X509_Certificate no_aia_cert(Test::data_file("x509/misc/contains_utf8string.pem"));
2✔
726

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

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

733
   const auto& ca_issuers = aia_cert.ca_issuer_uris();
1✔
734

735
   result.test_sz_eq("number of ca_issuers URLs", ca_issuers.size(), 1);
1✔
736
   if(result.tests_failed() > 0) {
1✔
737
      return result;
738
   }
739

740
   result.test_str_eq("CA issuer URL matches", ca_issuers[0].original_input(), "http://gp.symcb.com/gp.crt");
1✔
741
   result.test_sz_eq("one OCSP responder URI", aia_cert.ocsp_responder_uris().size(), 1);
1✔
742
   result.test_str_eq(
2✔
743
      "OCSP responder URL matches", aia_cert.ocsp_responder_uris().at(0).original_input(), "http://gp.symcd.com");
1✔
744

745
   // contains AIA extension with 2 CA issuer URL and 1 OCSP responder
746
   const Botan::X509_Certificate aia_cert_2ca(
1✔
747
      Test::data_file("x509/misc/contains_authority_info_access_with_two_ca_issuers.pem"));
2✔
748

749
   const auto& ca_issuers2 = aia_cert_2ca.ca_issuer_uris();
1✔
750

751
   result.test_sz_eq("number of ca_issuers URLs", ca_issuers2.size(), 2);
1✔
752
   if(result.tests_failed() > 0) {
1✔
753
      return result;
754
   }
755

756
   result.test_str_eq("CA issuer URL matches",
1✔
757
                      ca_issuers2[0].original_input(),
1✔
758
                      "http://www.d-trust.net/cgi-bin/Bdrive_Test_CA_1-2_2017.crt");
759
   result.test_str_eq(
1✔
760
      "CA issuer URL matches",
761
      ca_issuers2[1].original_input(),
1✔
762
      "ldap://directory.d-trust.net/CN=Bdrive%20Test%20CA%201-2%202017,O=Bundesdruckerei%20GmbH,C=DE?cACertificate?base?");
763
   result.test_sz_eq("one OCSP responder URI", aia_cert_2ca.ocsp_responder_uris().size(), 1);
1✔
764
   result.test_str_eq("OCSP responder URL matches",
2✔
765
                      aia_cert_2ca.ocsp_responder_uris().at(0).original_input(),
1✔
766
                      "http://staging.ocsp.d-trust.net");
767

768
   // contains AIA extension with multiple OCSP responders
769
   const Botan::X509_Certificate aia_cert_multi_ocsp(
1✔
770
      Test::data_file("x509/misc/contains_multiple_ocsp_responders.pem"));
2✔
771

772
   const auto& ocsp_responders_multi = aia_cert_multi_ocsp.ocsp_responder_uris();
1✔
773
   result.test_sz_eq("number of OCSP responders", ocsp_responders_multi.size(), 3);
1✔
774
   result.test_str_eq(
1✔
775
      "First OCSP responder URL matches", ocsp_responders_multi[0].original_input(), "http://ocsp1.example.com");
1✔
776
   result.test_str_eq(
1✔
777
      "Second OCSP responder URL matches", ocsp_responders_multi[1].original_input(), "http://ocsp2.example.com");
1✔
778
   result.test_str_eq(
1✔
779
      "Third OCSP responder URL matches", ocsp_responders_multi[2].original_input(), "http://ocsp3.example.com");
1✔
780
   result.test_is_true("no CA Issuer URI available", aia_cert_multi_ocsp.ca_issuer_uris().empty());
1✔
781

782
   return result;
1✔
783
}
1✔
784

785
Test::Result test_crl_issuing_distribution_point_extension() {
1✔
786
   Test::Result result("X509 CRL IssuingDistributionPoint extension");
1✔
787

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

791
   result.test_str_eq(
1✔
792
      "CRL IDP URI decoded correctly", crl.crl_issuing_distribution_point(), "http://localhost/subca/crldp/crl.crl");
1✔
793

794
   return result;
1✔
795
}
1✔
796

797
Test::Result test_parse_rsa_pss_cert() {
1✔
798
   Test::Result result("X509 RSA-PSS certificate");
1✔
799

800
   // See https://github.com/randombit/botan/issues/3019 for background
801

802
   try {
1✔
803
      const Botan::X509_Certificate rsa_pss(Test::data_file("x509/misc/rsa_pss.pem"));
3✔
804
      result.test_success("Was able to parse RSA-PSS certificate signed with ECDSA");
1✔
805
   } catch(Botan::Exception& e) {
1✔
806
      result.test_failure("Parsing failed", e.what());
×
807
   }
×
808

809
   return result;
1✔
810
}
×
811

812
Test::Result test_verify_gost2012_cert() {
1✔
813
   Test::Result result("X509 GOST-2012 certificates");
1✔
814

815
      #if defined(BOTAN_HAS_GOST_34_10_2012) && defined(BOTAN_HAS_STREEBOG)
816
   try {
1✔
817
      if(Botan::EC_Group::supports_named_group("gost_256A")) {
1✔
818
         const Botan::X509_Certificate root_cert(Test::data_file("x509/gost/gost_root.pem"));
2✔
819
         const Botan::X509_Certificate root_int(Test::data_file("x509/gost/gost_int.pem"));
2✔
820

821
         Botan::Certificate_Store_In_Memory trusted;
1✔
822
         trusted.add_certificate(root_cert);
1✔
823

824
         const Botan::Path_Validation_Restrictions restrictions(false, 128, false, {"Streebog-256"});
2✔
825
         const auto validation_time = Botan::calendar_point(2024, 1, 1, 0, 0, 0).to_std_timepoint();
1✔
826
         const Botan::Path_Validation_Result validation_result = Botan::x509_path_validate(
1✔
827
            root_int, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
1✔
828

829
         result.test_is_true("GOST certificate validates", validation_result.successful_validation());
1✔
830
      }
1✔
831
   } catch(const Botan::Decoding_Error& e) {
×
832
      result.test_failure(e.what());
×
833
   }
×
834
      #endif
835

836
   return result;
1✔
837
}
×
838

839
   /*
840
 * @brief checks the configurability of the RSA-PSS signature scheme
841
 *
842
 * For the other algorithms than RSA, only one padding is supported right now.
843
 */
844
      #if defined(BOTAN_HAS_EMSA_PKCS1) && defined(BOTAN_HAS_EMSA_PSSR) && defined(BOTAN_HAS_RSA)
845
Test::Result test_padding_config() {
1✔
846
   Test::Result test_result("X509 Padding Config");
1✔
847

848
   auto rng = Test::new_rng(__func__);
1✔
849

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

853
   // Create X509 CA certificate; PKCS1v15 is used for signing by default
854
   Botan::X509_Cert_Options opt("TEST CA");
1✔
855
   opt.CA_key();
1✔
856

857
   const Botan::X509_Certificate ca_cert_def = Botan::X509::create_self_signed_cert(opt, (*sk), "SHA-512", *rng);
1✔
858
   test_result.test_str_eq("CA certificate signature algorithm (default)",
2✔
859
                           ca_cert_def.signature_algorithm().oid().to_formatted_string(),
1✔
860
                           "RSA/PKCS1v15(SHA-512)");
861

862
   // Create X509 CA certificate; RSA-PSS is explicitly set
863
   opt.set_padding_scheme("PSSR");
1✔
864
   const Botan::X509_Certificate ca_cert_exp = Botan::X509::create_self_signed_cert(opt, (*sk), "SHA-512", *rng);
1✔
865
   test_result.test_str_eq("CA certificate signature algorithm (explicit)",
2✔
866
                           ca_cert_exp.signature_algorithm().oid().to_formatted_string(),
1✔
867
                           "RSA/PSS");
868

869
         #if defined(BOTAN_HAS_EMSA_X931)
870
   // Try to set a padding scheme that is not supported for signing with the given key type
871
   opt.set_padding_scheme("X9.31");
1✔
872
   try {
1✔
873
      const Botan::X509_Certificate ca_cert_wrong = Botan::X509::create_self_signed_cert(opt, (*sk), "SHA-512", *rng);
1✔
874
      test_result.test_failure("Could build CA cert with invalid encoding scheme X9.31 for key type " +
×
875
                               sk->algo_name());
×
876
   } catch(const Botan::Invalid_Argument& e) {
1✔
877
      test_result.test_str_eq("Build CA certificate with invalid encoding scheme X9.31 for key type " + sk->algo_name(),
3✔
878
                              e.what(),
1✔
879
                              "Signatures using RSA/X9.31(SHA-512) are not supported");
880
   }
1✔
881
         #endif
882

883
   test_result.test_str_eq("CA certificate signature algorithm (explicit)",
2✔
884
                           ca_cert_exp.signature_algorithm().oid().to_formatted_string(),
1✔
885
                           "RSA/PSS");
886

887
   const Botan::X509_Time not_before = from_date(-1, 1, 1);
1✔
888
   const Botan::X509_Time not_after = from_date(2, 1, 2);
1✔
889

890
   // Prepare a signing request for the end certificate
891
   Botan::X509_Cert_Options req_opt("endpoint");
1✔
892
   req_opt.set_padding_scheme("PSS(SHA-512,MGF1,64)");
1✔
893
   const Botan::PKCS10_Request end_req = Botan::X509::create_cert_req(req_opt, (*sk), "SHA-512", *rng);
1✔
894
   test_result.test_str_eq(
2✔
895
      "Certificate request signature algorithm", end_req.signature_algorithm().oid().to_formatted_string(), "RSA/PSS");
1✔
896

897
   // Create X509 CA object: will fail as the chosen hash functions differ
898
   try {
1✔
899
      const Botan::X509_CA ca_fail(ca_cert_exp, (*sk), "SHA-512", "PSS(SHA-256)", *rng);
1✔
900
      test_result.test_failure("Configured conflicting hash functions for CA");
×
901
   } catch(const Botan::Invalid_Argument& e) {
1✔
902
      test_result.test_str_eq(
1✔
903
         "Configured conflicting hash functions for CA",
904
         e.what(),
1✔
905
         "Specified hash function SHA-512 is incompatible with RSA chose hash function SHA-256 with user specified padding PSS(SHA-256)");
906
   }
1✔
907

908
   // Create X509 CA object: its signer will use the padding scheme from the CA certificate, i.e. PKCS1v15
909
   const Botan::X509_CA ca_def(ca_cert_def, (*sk), "SHA-512", *rng);
1✔
910
   const Botan::X509_Certificate end_cert_pkcs1 = ca_def.sign_request(end_req, *rng, not_before, not_after);
1✔
911
   test_result.test_str_eq("End certificate signature algorithm",
2✔
912
                           end_cert_pkcs1.signature_algorithm().oid().to_formatted_string(),
1✔
913
                           "RSA/PKCS1v15(SHA-512)");
914

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

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

928
   // Check CRL signature algorithm
929
   const Botan::X509_CRL crl = ca_exp.new_crl(*rng);
1✔
930
   test_result.test_str_eq("CRL signature algorithm", crl.signature_algorithm().oid().to_formatted_string(), "RSA/PSS");
1✔
931

932
   // sanity check for verification, the heavy lifting is done in the other unit tests
933
   const Botan::Certificate_Store_In_Memory trusted(ca_exp.ca_certificate());
1✔
934
   const Botan::Path_Validation_Restrictions restrictions(false, 80);
2✔
935
   const Botan::Path_Validation_Result validation_result =
1✔
936
      Botan::x509_path_validate(end_cert_pss, restrictions, trusted);
1✔
937
   test_result.test_is_true("PSS signed certificate validates", validation_result.successful_validation());
1✔
938

939
   return test_result;
2✔
940
}
3✔
941
      #endif
942

943
   #endif
944

945
Test::Result test_pkcs10_ext(const Botan::Private_Key& key,
11✔
946
                             const std::string& sig_padding,
947
                             const std::string& hash_fn,
948
                             Botan::RandomNumberGenerator& rng) {
949
   Test::Result result("PKCS10 extensions");
11✔
950

951
   Botan::X509_Cert_Options opts;
11✔
952

953
   opts.dns = "main.example.org";
11✔
954
   opts.more_dns.push_back("more1.example.org");
22✔
955
   opts.more_dns.push_back("more2.example.org");
22✔
956

957
   opts.padding_scheme = sig_padding;
11✔
958

959
   Botan::AlternativeName alt_name;
11✔
960
   alt_name.add_attribute("DNS", "bonus.example.org");
11✔
961

962
   Botan::X509_DN alt_dn;
11✔
963
   alt_dn.add_attribute("X520.CommonName", "alt_cn");
11✔
964
   alt_dn.add_attribute("X520.Organization", "testing");
11✔
965
   alt_name.add_dn(alt_dn);
11✔
966

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

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

971
   const auto alt_dns_names = req.subject_alt_name().get_attribute("DNS");
11✔
972

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

975
   if(alt_dns_names.size() == 4) {
11✔
976
      result.test_str_eq("Expected DNS name 1", alt_dns_names.at(0), "bonus.example.org");
11✔
977
      result.test_str_eq("Expected DNS name 2", alt_dns_names.at(1), "main.example.org");
11✔
978
      result.test_str_eq("Expected DNS name 3", alt_dns_names.at(2), "more1.example.org");
11✔
979
      result.test_str_eq("Expected DNS name 3", alt_dns_names.at(3), "more2.example.org");
11✔
980
   }
981

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

985
   return result;
11✔
986
}
22✔
987

988
Test::Result test_x509_cert(const Botan::Private_Key& ca_key,
11✔
989
                            const std::string& sig_algo,
990
                            const std::string& sig_padding,
991
                            const std::string& hash_fn,
992
                            Botan::RandomNumberGenerator& rng) {
993
   Test::Result result("X509 Unit");
11✔
994

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

998
   {
11✔
999
      result.test_is_true("ca key usage cert", ca_cert.constraints().includes(Botan::Key_Constraints::KeyCertSign));
11✔
1000
      result.test_is_true("ca key usage crl", ca_cert.constraints().includes(Botan::Key_Constraints::CrlSign));
11✔
1001
   }
1002

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

1006
   const Botan::PKCS10_Request user1_req =
11✔
1007
      Botan::X509::create_cert_req(req_opts1(sig_algo, sig_padding), *user1_key, hash_fn, rng);
11✔
1008

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

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

1014
   const Botan::PKCS10_Request user2_req =
11✔
1015
      Botan::X509::create_cert_req(req_opts2(sig_padding), *user2_key, hash_fn, rng);
11✔
1016

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

1020
   const Botan::PKCS10_Request user3_req =
11✔
1021
      Botan::X509::create_cert_req(req_opts3(sig_padding), *user3_key, hash_fn, rng);
11✔
1022

1023
   /* Create the CA object */
1024
   const Botan::X509_CA ca(ca_cert, ca_key, hash_fn, sig_padding, rng);
11✔
1025

1026
   const BigInt user1_serial(99);
11✔
1027

1028
   /* Sign the requests to create the certs */
1029
   const Botan::X509_Certificate user1_cert =
11✔
1030
      ca.sign_request(user1_req, rng, user1_serial, from_date(-1, 01, 01), from_date(2, 01, 01));
11✔
1031

1032
   result.test_sz_eq("User1 serial size matches expected", user1_cert.serial_number().size(), 1);
11✔
1033
   result.test_sz_eq("User1 serial matches expected", user1_cert.serial_number().at(0), size_t(99));
11✔
1034

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

1039
   const Botan::X509_Certificate user3_cert =
11✔
1040
      ca.sign_request(user3_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
11✔
1041

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

1046
   {
11✔
1047
      auto constraints = req_opts1(sig_algo).constraints;
11✔
1048
      result.test_is_true("user1 key usage", user1_cert.constraints().includes(constraints));
11✔
1049
   }
1050

1051
   /* Copy, assign and compare */
1052
   Botan::X509_Certificate user1_cert_copy(user1_cert);
11✔
1053
   result.test_is_true("certificate copy", user1_cert == user1_cert_copy);
11✔
1054

1055
   user1_cert_copy = user2_cert;
11✔
1056
   result.test_is_true("certificate assignment", user2_cert == user1_cert_copy);
11✔
1057

1058
   const Botan::X509_Certificate user1_cert_differ =
11✔
1059
      ca.sign_request(user1_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
11✔
1060

1061
   result.test_is_false("certificate differs", user1_cert == user1_cert_differ);
11✔
1062

1063
   /* Get cert data */
1064
   result.test_sz_eq("x509 version", user1_cert.x509_version(), size_t(3));
11✔
1065

1066
   const Botan::X509_DN& user1_issuer_dn = user1_cert.issuer_dn();
11✔
1067
   result.test_str_eq("issuer info CN", user1_issuer_dn.get_first_attribute("CN"), ca_opts().common_name);
11✔
1068
   result.test_str_eq("issuer info Country", user1_issuer_dn.get_first_attribute("C"), ca_opts().country);
11✔
1069
   result.test_str_eq("issuer info Orga", user1_issuer_dn.get_first_attribute("O"), ca_opts().organization);
11✔
1070
   result.test_str_eq("issuer info OrgaUnit", user1_issuer_dn.get_first_attribute("OU"), ca_opts().org_unit);
11✔
1071

1072
   const Botan::X509_DN& user3_subject_dn = user3_cert.subject_dn();
11✔
1073
   result.test_sz_eq("subject OrgaUnit count",
11✔
1074
                     user3_subject_dn.get_attribute("OU").size(),
22✔
1075
                     req_opts3(sig_algo).more_org_units.size() + 1);
22✔
1076
   result.test_str_eq(
22✔
1077
      "subject OrgaUnit #2", user3_subject_dn.get_attribute("OU").at(1), req_opts3(sig_algo).more_org_units.at(0));
33✔
1078

1079
   const Botan::AlternativeName& user1_altname = user1_cert.subject_alt_name();
11✔
1080
   result.test_str_eq("subject alt email", user1_altname.get_first_attribute("RFC822"), "testing@randombit.net");
11✔
1081
   result.test_str_eq("subject alt dns", user1_altname.get_first_attribute("DNS"), "botan.randombit.net");
11✔
1082
   result.test_str_eq("subject alt uri", user1_altname.get_first_attribute("URI"), "https://botan.randombit.net");
11✔
1083

1084
   const Botan::AlternativeName& user3_altname = user3_cert.subject_alt_name();
11✔
1085
   result.test_sz_eq(
11✔
1086
      "subject alt dns count", user3_altname.get_attribute("DNS").size(), req_opts3(sig_algo).more_dns.size() + 1);
22✔
1087
   result.test_str_eq(
22✔
1088
      "subject alt dns #2", user3_altname.get_attribute("DNS").at(1), req_opts3(sig_algo).more_dns.at(0));
33✔
1089

1090
   const Botan::X509_CRL crl1 = ca.new_crl(rng);
11✔
1091

1092
   /* Verify the certs */
1093
   const Botan::Path_Validation_Restrictions restrictions(false, 80);
22✔
1094
   Botan::Certificate_Store_In_Memory store;
11✔
1095

1096
   // First try with an empty store
1097
   const Botan::Path_Validation_Result result_no_issuer = Botan::x509_path_validate(user1_cert, restrictions, store);
11✔
1098
   result.test_str_eq(
11✔
1099
      "user 1 issuer not found",
1100
      result_no_issuer.result_string(),
11✔
1101
      Botan::Path_Validation_Result::status_string(Botan::Certificate_Status_Code::CERT_ISSUER_NOT_FOUND));
1102

1103
   store.add_certificate(ca.ca_certificate());
11✔
1104

1105
   Botan::Path_Validation_Result result_u1 = Botan::x509_path_validate(user1_cert, restrictions, store);
11✔
1106
   if(!result.test_is_true("user 1 validates", result_u1.successful_validation())) {
11✔
1107
      result.test_note("user 1 validation result", result_u1.result_string());
×
1108
   }
1109

1110
   Botan::Path_Validation_Result result_u2 = Botan::x509_path_validate(user2_cert, restrictions, store);
11✔
1111
   if(!result.test_is_true("user 2 validates", result_u2.successful_validation())) {
11✔
1112
      result.test_note("user 2 validation result", result_u2.result_string());
×
1113
   }
1114

1115
   const Botan::Path_Validation_Result result_self_signed =
11✔
1116
      Botan::x509_path_validate(user1_ss_cert, restrictions, store);
11✔
1117
   result.test_str_eq(
11✔
1118
      "user 1 issuer not found",
1119
      result_no_issuer.result_string(),
11✔
1120
      Botan::Path_Validation_Result::status_string(Botan::Certificate_Status_Code::CERT_ISSUER_NOT_FOUND));
1121
   store.add_crl(crl1);
11✔
1122

1123
   std::vector<Botan::CRL_Entry> revoked;
11✔
1124
   revoked.push_back(Botan::CRL_Entry(user1_cert, Botan::CRL_Code::CessationOfOperation));
22✔
1125
   revoked.push_back(Botan::CRL_Entry(user2_cert));
22✔
1126

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

1129
   store.add_crl(crl2);
11✔
1130

1131
   const std::string revoked_str =
11✔
1132
      Botan::Path_Validation_Result::status_string(Botan::Certificate_Status_Code::CERT_IS_REVOKED);
11✔
1133

1134
   result_u1 = Botan::x509_path_validate(user1_cert, restrictions, store);
11✔
1135
   result.test_str_eq("user 1 revoked", result_u1.result_string(), revoked_str);
11✔
1136

1137
   result_u2 = Botan::x509_path_validate(user2_cert, restrictions, store);
11✔
1138
   result.test_str_eq("user 1 revoked", result_u2.result_string(), revoked_str);
11✔
1139

1140
   revoked.clear();
11✔
1141
   revoked.push_back(Botan::CRL_Entry(user1_cert, Botan::CRL_Code::RemoveFromCrl));
22✔
1142
   const Botan::X509_CRL crl3 = ca.update_crl(crl2, revoked, rng);
11✔
1143

1144
   store.add_crl(crl3);
11✔
1145

1146
   result_u1 = Botan::x509_path_validate(user1_cert, restrictions, store);
11✔
1147
   if(!result.test_is_true("user 1 validates", result_u1.successful_validation())) {
11✔
1148
      result.test_note("user 1 validation result", result_u1.result_string());
×
1149
   }
1150

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

1154
   return result;
11✔
1155
}
44✔
1156

1157
Test::Result test_crl_entry_negative_serial() {
1✔
1158
   Test::Result result("CRL entry with negative serial matches certificate serial");
1✔
1159

1160
   const auto build_crl_entry = [](const std::vector<uint8_t>& serial_bytes) {
4✔
1161
      std::vector<uint8_t> der;
3✔
1162
      Botan::DER_Encoder enc(der);
3✔
1163
      enc.start_sequence()
3✔
1164
         .add_object(Botan::ASN1_Type::Integer, Botan::ASN1_Class::Universal, serial_bytes.data(), serial_bytes.size())
3✔
1165
         .encode(Botan::X509_Time(std::chrono::system_clock::now()))
3✔
1166
         .end_cons();
3✔
1167

1168
      Botan::CRL_Entry entry;
3✔
1169
      Botan::BER_Decoder dec(der);
3✔
1170
      entry.decode_from(dec);
3✔
1171
      return entry;
3✔
1172
   };
3✔
1173

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

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

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

1186
   return result;
1✔
1187
}
1✔
1188

1189
Test::Result test_usage(const Botan::Private_Key& ca_key,
12✔
1190
                        const std::string& sig_algo,
1191
                        const std::string& hash_fn,
1192
                        Botan::RandomNumberGenerator& rng) {
1193
   using Botan::Key_Constraints;
12✔
1194
   using Botan::Usage_Type;
12✔
1195

1196
   Test::Result result("X509 Usage");
12✔
1197

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

1201
   /* Create the CA object */
1202
   const Botan::X509_CA ca(ca_cert, ca_key, hash_fn, rng);
12✔
1203

1204
   auto user1_key = make_a_private_key(sig_algo, rng);
12✔
1205

1206
   Botan::X509_Cert_Options opts("Test User 1/US/Botan Project/Testing");
12✔
1207
   opts.constraints = Key_Constraints::DigitalSignature;
12✔
1208

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

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

1214
   // cert only allows digitalSignature, but we check for both digitalSignature and cRLSign
1215
   result.test_is_false(
12✔
1216
      "key usage cRLSign not allowed",
1217
      user1_cert.allowed_usage(Key_Constraints(Key_Constraints::DigitalSignature | Key_Constraints::CrlSign)));
12✔
1218
   result.test_is_false("encryption is not allowed", user1_cert.allowed_usage(Usage_Type::ENCRYPTION));
12✔
1219

1220
   // cert only allows digitalSignature, so checking for only that should be ok
1221
   result.test_is_true("key usage digitalSignature allowed",
12✔
1222
                       user1_cert.allowed_usage(Key_Constraints::DigitalSignature));
12✔
1223

1224
   opts.constraints = Key_Constraints(Key_Constraints::DigitalSignature | Key_Constraints::CrlSign);
12✔
1225

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

1228
   const Botan::X509_Certificate mult_usage_cert =
12✔
1229
      ca.sign_request(mult_usage_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
12✔
1230

1231
   // cert allows multiple usages, so each one of them as well as both together should be allowed
1232
   result.test_is_true("key usage multiple digitalSignature allowed",
12✔
1233
                       mult_usage_cert.allowed_usage(Key_Constraints::DigitalSignature));
12✔
1234
   result.test_is_true("key usage multiple cRLSign allowed", mult_usage_cert.allowed_usage(Key_Constraints::CrlSign));
12✔
1235
   result.test_is_true(
12✔
1236
      "key usage multiple digitalSignature and cRLSign allowed",
1237
      mult_usage_cert.allowed_usage(Key_Constraints(Key_Constraints::DigitalSignature | Key_Constraints::CrlSign)));
12✔
1238
   result.test_is_false("encryption is not allowed", mult_usage_cert.allowed_usage(Usage_Type::ENCRYPTION));
12✔
1239

1240
   opts.constraints = Key_Constraints();
12✔
1241

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

1244
   const Botan::X509_Certificate no_usage_cert =
12✔
1245
      ca.sign_request(no_usage_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
12✔
1246

1247
   // cert allows every usage
1248
   result.test_is_true("key usage digitalSignature allowed",
12✔
1249
                       no_usage_cert.allowed_usage(Key_Constraints::DigitalSignature));
12✔
1250
   result.test_is_true("key usage cRLSign allowed", no_usage_cert.allowed_usage(Key_Constraints::CrlSign));
12✔
1251
   result.test_is_true("key usage encryption allowed", no_usage_cert.allowed_usage(Usage_Type::ENCRYPTION));
12✔
1252

1253
   if(sig_algo == "RSA") {
12✔
1254
      // cert allows data encryption
1255
      opts.constraints = Key_Constraints(Key_Constraints::KeyEncipherment | Key_Constraints::DataEncipherment);
1✔
1256

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

1259
      const Botan::X509_Certificate enc_cert =
1✔
1260
         ca.sign_request(enc_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
1✔
1261

1262
      result.test_is_true("cert allows encryption", enc_cert.allowed_usage(Usage_Type::ENCRYPTION));
1✔
1263
      result.test_is_true("cert does not allow TLS client auth", !enc_cert.allowed_usage(Usage_Type::TLS_CLIENT_AUTH));
1✔
1264
   }
1✔
1265

1266
   return result;
12✔
1267
}
24✔
1268

1269
Test::Result test_self_issued(const Botan::Private_Key& ca_key,
11✔
1270
                              const std::string& sig_algo,
1271
                              const std::string& sig_padding,
1272
                              const std::string& hash_fn,
1273
                              Botan::RandomNumberGenerator& rng) {
1274
   using Botan::Key_Constraints;
11✔
1275

1276
   Test::Result result("X509 Self Issued");
11✔
1277

1278
   // create the self-signed cert
1279
   const Botan::X509_Certificate ca_cert =
11✔
1280
      Botan::X509::create_self_signed_cert(ca_opts(sig_padding), ca_key, hash_fn, rng);
11✔
1281

1282
   /* Create the CA object */
1283
   const Botan::X509_CA ca(ca_cert, ca_key, hash_fn, sig_padding, rng);
11✔
1284

1285
   auto user_key = make_a_private_key(sig_algo, rng);
11✔
1286

1287
   // create a self-issued certificate, that is, a certificate with subject dn == issuer dn,
1288
   // but signed by a CA, not signed by it's own private key
1289
   Botan::X509_Cert_Options opts = ca_opts();
11✔
1290
   opts.constraints = Key_Constraints::DigitalSignature;
11✔
1291
   opts.set_padding_scheme(sig_padding);
11✔
1292

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

1295
   const Botan::X509_Certificate self_issued_cert =
11✔
1296
      ca.sign_request(self_issued_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
11✔
1297

1298
   // check that this chain can can be verified successfully
1299
   const Botan::Certificate_Store_In_Memory trusted(ca.ca_certificate());
11✔
1300

1301
   const Botan::Path_Validation_Restrictions restrictions(false, 80);
22✔
1302

1303
   const Botan::Path_Validation_Result validation_result =
11✔
1304
      Botan::x509_path_validate(self_issued_cert, restrictions, trusted);
11✔
1305

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

1308
   return result;
11✔
1309
}
22✔
1310

1311
Test::Result test_x509_uninit() {
1✔
1312
   Test::Result result("X509 object uninitialized access");
1✔
1313

1314
   Botan::X509_Certificate cert;
1✔
1315
   result.test_throws("uninitialized cert access causes exception", "X509_Certificate uninitialized", [&cert]() {
1✔
1316
      cert.x509_version();
1✔
1317
   });
1318

1319
   Botan::X509_CRL crl;
1✔
1320
   result.test_throws(
1✔
1321
      "uninitialized crl access causes exception", "X509_CRL uninitialized", [&crl]() { crl.crl_number(); });
2✔
1322

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

1329
   result.test_throws("synth crl signature() throws", "X509_Object uninitialized", [&]() { synth_crl.signature(); });
2✔
1330
   result.test_throws(
1✔
1331
      "synth crl signed_body() throws", "X509_Object uninitialized", [&]() { synth_crl.signed_body(); });
2✔
1332
   result.test_throws("synth crl signature_algorithm() throws", "X509_Object uninitialized", [&]() {
1✔
1333
      synth_crl.signature_algorithm();
1✔
1334
   });
1335
   result.test_throws("synth crl tbs_data() throws", "X509_Object uninitialized", [&]() { synth_crl.tbs_data(); });
2✔
1336
   result.test_throws("synth crl BER_encode() throws", "X509_Object uninitialized", [&]() { synth_crl.BER_encode(); });
2✔
1337

1338
   return result;
1✔
1339
}
2✔
1340

1341
Test::Result test_valid_constraints(const Botan::Private_Key& key, const std::string& pk_algo) {
19✔
1342
   using Botan::Key_Constraints;
19✔
1343

1344
   Test::Result result("X509 Valid Constraints " + pk_algo);
19✔
1345

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

1348
   // Now check some typical usage scenarios for the given key type
1349
   // Taken from RFC 5280, sec. 4.2.1.3
1350
   // ALL constraints are not typical at all, but we use them for a negative test
1351
   const auto all = Key_Constraints(
19✔
1352
      Key_Constraints::DigitalSignature | Key_Constraints::NonRepudiation | Key_Constraints::KeyEncipherment |
1353
      Key_Constraints::DataEncipherment | Key_Constraints::KeyAgreement | Key_Constraints::KeyCertSign |
1354
      Key_Constraints::CrlSign | Key_Constraints::EncipherOnly | Key_Constraints::DecipherOnly);
19✔
1355

1356
   const auto ca = Key_Constraints(Key_Constraints::KeyCertSign);
19✔
1357
   const auto sign_data = Key_Constraints(Key_Constraints::DigitalSignature);
19✔
1358
   const auto non_repudiation = Key_Constraints(Key_Constraints::NonRepudiation | Key_Constraints::DigitalSignature);
19✔
1359
   const auto key_encipherment = Key_Constraints(Key_Constraints::KeyEncipherment);
19✔
1360
   const auto data_encipherment = Key_Constraints(Key_Constraints::DataEncipherment);
19✔
1361
   const auto key_agreement = Key_Constraints(Key_Constraints::KeyAgreement);
19✔
1362
   const auto key_agreement_encipher_only =
19✔
1363
      Key_Constraints(Key_Constraints::KeyAgreement | Key_Constraints::EncipherOnly);
19✔
1364
   const auto key_agreement_decipher_only =
19✔
1365
      Key_Constraints(Key_Constraints::KeyAgreement | Key_Constraints::DecipherOnly);
19✔
1366
   const auto crl_sign = Key_Constraints(Key_Constraints::CrlSign);
19✔
1367
   const auto sign_everything =
19✔
1368
      Key_Constraints(Key_Constraints::DigitalSignature | Key_Constraints::KeyCertSign | Key_Constraints::CrlSign);
19✔
1369

1370
   if(pk_algo == "DH" || pk_algo == "ECDH") {
19✔
1371
      // DH and ECDH only for key agreement
1372
      result.test_is_false("all constraints not permitted", all.compatible_with(key));
2✔
1373
      result.test_is_false("cert sign not permitted", ca.compatible_with(key));
2✔
1374
      result.test_is_false("signature not permitted", sign_data.compatible_with(key));
2✔
1375
      result.test_is_false("non repudiation not permitted", non_repudiation.compatible_with(key));
2✔
1376
      result.test_is_false("key encipherment not permitted", key_encipherment.compatible_with(key));
2✔
1377
      result.test_is_false("data encipherment not permitted", data_encipherment.compatible_with(key));
2✔
1378
      result.test_is_true("usage acceptable", key_agreement.compatible_with(key));
2✔
1379
      result.test_is_true("usage acceptable", key_agreement_encipher_only.compatible_with(key));
2✔
1380
      result.test_is_true("usage acceptable", key_agreement_decipher_only.compatible_with(key));
2✔
1381
      result.test_is_false("crl sign not permitted", crl_sign.compatible_with(key));
2✔
1382
      result.test_is_false("sign", sign_everything.compatible_with(key));
2✔
1383
   } else if(pk_algo == "Kyber" || pk_algo == "FrodoKEM" || pk_algo == "ML-KEM" || pk_algo == "ClassicMcEliece") {
17✔
1384
      // KEMs can encrypt and agree
1385
      result.test_is_false("all constraints not permitted", all.compatible_with(key));
4✔
1386
      result.test_is_false("cert sign not permitted", ca.compatible_with(key));
4✔
1387
      result.test_is_false("signature not permitted", sign_data.compatible_with(key));
4✔
1388
      result.test_is_false("non repudiation not permitted", non_repudiation.compatible_with(key));
4✔
1389
      result.test_is_false("crl sign not permitted", crl_sign.compatible_with(key));
4✔
1390
      result.test_is_false("sign", sign_everything.compatible_with(key));
4✔
1391
      result.test_is_false("key agreement not permitted", key_agreement.compatible_with(key));
4✔
1392
      result.test_is_false("usage acceptable", data_encipherment.compatible_with(key));
4✔
1393
      result.test_is_true("usage acceptable", key_encipherment.compatible_with(key));
4✔
1394
   } else if(pk_algo == "RSA") {
13✔
1395
      // RSA can do everything except key agreement
1396
      result.test_is_false("all constraints not permitted", all.compatible_with(key));
1✔
1397

1398
      result.test_is_true("usage acceptable", ca.compatible_with(key));
1✔
1399
      result.test_is_true("usage acceptable", sign_data.compatible_with(key));
1✔
1400
      result.test_is_true("usage acceptable", non_repudiation.compatible_with(key));
1✔
1401
      result.test_is_true("usage acceptable", key_encipherment.compatible_with(key));
1✔
1402
      result.test_is_true("usage acceptable", data_encipherment.compatible_with(key));
1✔
1403
      result.test_is_false("key agreement not permitted", key_agreement.compatible_with(key));
1✔
1404
      result.test_is_false("key agreement", key_agreement_encipher_only.compatible_with(key));
1✔
1405
      result.test_is_false("key agreement", key_agreement_decipher_only.compatible_with(key));
1✔
1406
      result.test_is_true("usage acceptable", crl_sign.compatible_with(key));
1✔
1407
      result.test_is_true("usage acceptable", sign_everything.compatible_with(key));
1✔
1408
   } else if(pk_algo == "ElGamal") {
12✔
1409
      // only ElGamal encryption is currently implemented
1410
      result.test_is_false("all constraints not permitted", all.compatible_with(key));
1✔
1411
      result.test_is_false("cert sign not permitted", ca.compatible_with(key));
1✔
1412
      result.test_is_true("data encipherment permitted", data_encipherment.compatible_with(key));
1✔
1413
      result.test_is_true("key encipherment permitted", key_encipherment.compatible_with(key));
1✔
1414
      result.test_is_false("key agreement not permitted", key_agreement.compatible_with(key));
1✔
1415
      result.test_is_false("key agreement", key_agreement_encipher_only.compatible_with(key));
1✔
1416
      result.test_is_false("key agreement", key_agreement_decipher_only.compatible_with(key));
1✔
1417
      result.test_is_false("crl sign not permitted", crl_sign.compatible_with(key));
1✔
1418
      result.test_is_false("sign", sign_everything.compatible_with(key));
1✔
1419
   } else if(pk_algo == "DSA" || pk_algo == "ECDSA" || pk_algo == "ECGDSA" || pk_algo == "ECKCDSA" ||
10✔
1420
             pk_algo == "GOST-34.10" || pk_algo == "Dilithium" || pk_algo == "ML-DSA" || pk_algo == "SLH-DSA" ||
18✔
1421
             pk_algo == "HSS-LMS") {
3✔
1422
      // these are signature algorithms only
1423
      result.test_is_false("all constraints not permitted", all.compatible_with(key));
9✔
1424

1425
      result.test_is_true("ca allowed", ca.compatible_with(key));
9✔
1426
      result.test_is_true("sign allowed", sign_data.compatible_with(key));
9✔
1427
      result.test_is_true("non-repudiation allowed", non_repudiation.compatible_with(key));
9✔
1428
      result.test_is_false("key encipherment not permitted", key_encipherment.compatible_with(key));
9✔
1429
      result.test_is_false("data encipherment not permitted", data_encipherment.compatible_with(key));
9✔
1430
      result.test_is_false("key agreement not permitted", key_agreement.compatible_with(key));
9✔
1431
      result.test_is_false("key agreement", key_agreement_encipher_only.compatible_with(key));
9✔
1432
      result.test_is_false("key agreement", key_agreement_decipher_only.compatible_with(key));
9✔
1433
      result.test_is_true("crl sign allowed", crl_sign.compatible_with(key));
9✔
1434
      result.test_is_true("sign allowed", sign_everything.compatible_with(key));
9✔
1435
   }
1436

1437
   return result;
19✔
1438
}
×
1439

1440
/**
1441
 * @brief X.509v3 extension that encodes a given string
1442
 */
1443
class String_Extension final : public Botan::Certificate_Extension {
22✔
1444
   public:
1445
      String_Extension() = default;
22✔
1446

1447
      explicit String_Extension(const std::string& val) : m_contents(val) {}
11✔
1448

1449
      std::string value() const { return m_contents; }
44✔
1450

1451
      std::unique_ptr<Certificate_Extension> copy() const override {
×
1452
         return std::make_unique<String_Extension>(m_contents);
×
1453
      }
1454

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

1457
      bool should_encode() const override { return true; }
22✔
1458

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

1461
      std::vector<uint8_t> encode_inner() const override {
11✔
1462
         std::vector<uint8_t> bits;
11✔
1463
         Botan::DER_Encoder(bits).encode(Botan::ASN1_String(m_contents, Botan::ASN1_Type::Utf8String));
22✔
1464
         return bits;
11✔
1465
      }
×
1466

1467
      void decode_inner(const std::vector<uint8_t>& in) override {
22✔
1468
         Botan::ASN1_String str;
22✔
1469
         Botan::BER_Decoder(in).decode(str, Botan::ASN1_Type::Utf8String).verify_end();
22✔
1470
         m_contents = str.value();
44✔
1471
      }
22✔
1472

1473
   private:
1474
      std::string m_contents;
1475
};
1476

1477
Test::Result test_custom_dn_attr(const Botan::Private_Key& ca_key,
11✔
1478
                                 const std::string& sig_algo,
1479
                                 const std::string& sig_padding,
1480
                                 const std::string& hash_fn,
1481
                                 Botan::RandomNumberGenerator& rng) {
1482
   Test::Result result("X509 Custom DN");
11✔
1483

1484
   /* Create the self-signed cert */
1485
   const Botan::X509_Certificate ca_cert =
11✔
1486
      Botan::X509::create_self_signed_cert(ca_opts(sig_padding), ca_key, hash_fn, rng);
11✔
1487

1488
   /* Create the CA object */
1489
   const Botan::X509_CA ca(ca_cert, ca_key, hash_fn, sig_padding, rng);
11✔
1490

1491
   auto user_key = make_a_private_key(sig_algo, rng);
11✔
1492

1493
   Botan::X509_DN subject_dn;
11✔
1494

1495
   const Botan::OID attr1(Botan::OID("1.3.6.1.4.1.25258.9.1.1"));
11✔
1496
   const Botan::OID attr2(Botan::OID("1.3.6.1.4.1.25258.9.1.2"));
11✔
1497
   const Botan::ASN1_String val1("Custom Attr 1", Botan::ASN1_Type::PrintableString);
11✔
1498
   const Botan::ASN1_String val2("12345", Botan::ASN1_Type::Utf8String);
11✔
1499

1500
   subject_dn.add_attribute(attr1, val1);
11✔
1501
   subject_dn.add_attribute(attr2, val2);
11✔
1502

1503
   const Botan::Extensions extensions;
11✔
1504

1505
   const Botan::PKCS10_Request req =
11✔
1506
      Botan::PKCS10_Request::create(*user_key, subject_dn, extensions, hash_fn, rng, sig_padding);
11✔
1507

1508
   const Botan::X509_DN& req_dn = req.subject_dn();
11✔
1509

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

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

1519
   const Botan::X509_Time not_before("100301123001Z", Botan::ASN1_Type::UtcTime);
11✔
1520
   const Botan::X509_Time not_after("300301123001Z", Botan::ASN1_Type::UtcTime);
11✔
1521

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

1524
   const Botan::X509_DN& cert_dn = cert.subject_dn();
11✔
1525

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

1528
   const Botan::ASN1_String cert_val1 = cert_dn.get_first_attribute(attr1);
11✔
1529
   const Botan::ASN1_String cert_val2 = cert_dn.get_first_attribute(attr2);
11✔
1530
   result.test_is_true("Attr1 matches encoded", cert_val1 == val1);
11✔
1531
   result.test_is_true("Attr2 matches encoded", cert_val2 == val2);
11✔
1532
   result.test_is_true("Attr1 tag matches encoded", cert_val1.tagging() == val1.tagging());
11✔
1533
   result.test_is_true("Attr2 tag matches encoded", cert_val2.tagging() == val2.tagging());
11✔
1534

1535
   return result;
22✔
1536
}
99✔
1537

1538
Test::Result test_x509_extensions(const Botan::Private_Key& ca_key,
11✔
1539
                                  const std::string& sig_algo,
1540
                                  const std::string& sig_padding,
1541
                                  const std::string& hash_fn,
1542
                                  Botan::RandomNumberGenerator& rng) {
1543
   using Botan::Key_Constraints;
11✔
1544

1545
   Test::Result result("X509 Extensions");
11✔
1546

1547
   /* Create the self-signed cert */
1548
   const Botan::X509_Certificate ca_cert =
11✔
1549
      Botan::X509::create_self_signed_cert(ca_opts(sig_padding), ca_key, hash_fn, rng);
11✔
1550

1551
   /* Create the CA object */
1552
   const Botan::X509_CA ca(ca_cert, ca_key, hash_fn, sig_padding, rng);
11✔
1553

1554
   /* Prepare CDP extension */
1555
   std::vector<std::string> cdp_urls = {
11✔
1556
      "http://example.com/crl1.pem",
1557
      "ldap://ldap.example.com/cn=crl1,dc=example,dc=com?certificateRevocationList;binary"};
11✔
1558

1559
   std::vector<Botan::Cert_Extension::CRL_Distribution_Points::Distribution_Point> dps;
11✔
1560

1561
   for(const auto& uri : cdp_urls) {
33✔
1562
      Botan::AlternativeName cdp_alt_name;
22✔
1563
      cdp_alt_name.add_uri(uri);
22✔
1564
      const Botan::Cert_Extension::CRL_Distribution_Points::Distribution_Point dp(cdp_alt_name);
22✔
1565

1566
      dps.emplace_back(dp);
22✔
1567
   }
22✔
1568

1569
   auto user_key = make_a_private_key(sig_algo, rng);
11✔
1570

1571
   Botan::X509_Cert_Options opts("Test User 1/US/Botan Project/Testing");
11✔
1572
   opts.constraints = Key_Constraints::DigitalSignature;
11✔
1573

1574
   // include a custom extension in the request
1575
   Botan::Extensions req_extensions;
11✔
1576
   const Botan::OID oid("1.2.3.4.5.6.7.8.9.1");
11✔
1577
   const Botan::OID ku_oid = Botan::OID::from_string("X509v3.KeyUsage");
11✔
1578
   req_extensions.add(std::make_unique<String_Extension>("AAAAAAAAAAAAAABCDEF"), false);
22✔
1579
   req_extensions.add(std::make_unique<Botan::Cert_Extension::CRL_Distribution_Points>(dps));
22✔
1580
   opts.extensions = req_extensions;
11✔
1581
   opts.set_padding_scheme(sig_padding);
11✔
1582

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

1586
   result.test_is_true("Extensions::extension_set true for Key_Usage",
11✔
1587
                       self_signed_cert.v3_extensions().extension_set(ku_oid));
11✔
1588

1589
   // check if known Key_Usage extension is present in self-signed cert
1590
   auto key_usage_ext = self_signed_cert.v3_extensions().get(ku_oid);
11✔
1591
   if(result.test_is_true("Key_Usage extension present in self-signed certificate", key_usage_ext != nullptr)) {
11✔
1592
      result.test_is_true(
22✔
1593
         "Key_Usage extension value matches in self-signed certificate",
1594
         dynamic_cast<Botan::Cert_Extension::Key_Usage&>(*key_usage_ext).get_constraints() == opts.constraints);
11✔
1595
   }
1596

1597
   // check if custom extension is present in self-signed cert
1598
   auto string_ext = self_signed_cert.v3_extensions().get_raw<String_Extension>(oid);
11✔
1599
   if(result.test_is_true("Custom extension present in self-signed certificate", string_ext != nullptr)) {
11✔
1600
      result.test_str_eq(
22✔
1601
         "Custom extension value matches in self-signed certificate", string_ext->value(), "AAAAAAAAAAAAAABCDEF");
33✔
1602
   }
1603

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

1608
   if(result.test_is_true("CRL Distribution Points extension present in self-signed certificate",
44✔
1609
                          !cert_cdps->crl_distribution_urls().empty())) {
11✔
1610
      for(const auto& cdp : cert_cdps->distribution_points()) {
33✔
1611
         result.test_is_true("CDP URI present in self-signed certificate",
44✔
1612
                             std::ranges::find(cdp_urls, cdp.point().get_first_attribute("URI")) != cdp_urls.end());
66✔
1613
      }
1614

1615
      // The accessor returns one entry per URI
1616
      const auto& urls = cert_cdps->crl_distribution_urls();
11✔
1617
      result.test_sz_eq("crl_distribution_urls has one entry per URI (self-signed)", urls.size(), cdp_urls.size());
11✔
1618
      for(const auto& url : urls) {
33✔
1619
         result.test_is_true("crl_distribution_urls entry is a bare URI (self-signed)",
44✔
1620
                             std::ranges::find(cdp_urls, url) != cdp_urls.end());
44✔
1621
      }
1622

1623
      // Ensure X509_Certificate's cached accessor returns the same bare URIs
1624
      const auto& cert_dp = self_signed_cert.crl_distribution_point_uris();
11✔
1625
      result.test_sz_eq(
11✔
1626
         "X509_Certificate::crl_distribution_points size (self-signed)", cert_dp.size(), cdp_urls.size());
1627
      for(const auto& url : cert_dp) {
33✔
1628
         result.test_is_true("X509_Certificate::crl_distribution_points entry is a bare URI (self-signed)",
44✔
1629
                             std::ranges::find(cdp_urls, url.original_input()) != cdp_urls.end());
44✔
1630
      }
1631
   }
11✔
1632

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

1635
   /* Create a CA-signed certificate */
1636
   const Botan::X509_Certificate ca_signed_cert =
11✔
1637
      ca.sign_request(user_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
11✔
1638

1639
   // check if known Key_Usage extension is present in CA-signed cert
1640
   result.test_is_true("Extensions::extension_set true for Key_Usage",
11✔
1641
                       ca_signed_cert.v3_extensions().extension_set(ku_oid));
11✔
1642

1643
   key_usage_ext = ca_signed_cert.v3_extensions().get(ku_oid);
22✔
1644
   if(result.test_is_true("Key_Usage extension present in CA-signed certificate", key_usage_ext != nullptr)) {
11✔
1645
      auto constraints = dynamic_cast<Botan::Cert_Extension::Key_Usage&>(*key_usage_ext).get_constraints();
11✔
1646
      result.test_is_true("Key_Usage extension value matches in user certificate",
22✔
1647
                          constraints == Botan::Key_Constraints::DigitalSignature);
11✔
1648
   }
1649

1650
   // check if custom extension is present in CA-signed cert
1651
   result.test_is_true("Extensions::extension_set true for String_Extension",
11✔
1652
                       ca_signed_cert.v3_extensions().extension_set(oid));
11✔
1653
   string_ext = ca_signed_cert.v3_extensions().get_raw<String_Extension>(oid);
22✔
1654
   if(result.test_is_true("Custom extension present in CA-signed certificate", string_ext != nullptr)) {
11✔
1655
      result.test_str_eq(
22✔
1656
         "Custom extension value matches in CA-signed certificate", string_ext->value(), "AAAAAAAAAAAAAABCDEF");
33✔
1657
   }
1658

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

1662
   if(result.test_is_true("CRL Distribution Points extension present in CA-signed certificate",
44✔
1663
                          !cert_cdps->crl_distribution_urls().empty())) {
11✔
1664
      for(const auto& cdp : cert_cdps->distribution_points()) {
33✔
1665
         result.test_is_true("CDP URI present in CA-signed certificate",
44✔
1666
                             std::ranges::find(cdp_urls, cdp.point().get_first_attribute("URI")) != cdp_urls.end());
66✔
1667
      }
1668

1669
      const auto& urls = cert_cdps->crl_distribution_urls();
11✔
1670
      result.test_sz_eq("crl_distribution_urls has one entry per URI (CA-signed)", urls.size(), cdp_urls.size());
11✔
1671
      for(const auto& url : urls) {
33✔
1672
         result.test_is_true("crl_distribution_urls entry is a bare URI (CA-signed)",
44✔
1673
                             std::ranges::find(cdp_urls, url) != cdp_urls.end());
44✔
1674
      }
1675

1676
      const auto& cert_dp = ca_signed_cert.crl_distribution_point_uris();
11✔
1677
      result.test_sz_eq("X509_Certificate::crl_distribution_points size (CA-signed)", cert_dp.size(), cdp_urls.size());
11✔
1678
      for(const auto& url : cert_dp) {
33✔
1679
         result.test_is_true("X509_Certificate::crl_distribution_points entry is a bare URI (CA-signed)",
44✔
1680
                             std::ranges::find(cdp_urls, url.original_input()) != cdp_urls.end());
44✔
1681
      }
1682
   }
11✔
1683

1684
   return result;
11✔
1685
}
55✔
1686

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

1690
   struct TestData {
12✔
1691
         const std::string issuer, subject, issuer_hash, subject_hash;
1692
   } const cases[]{{"",
12✔
1693
                    "",
1694
                    "E4F60D0AA6D7F3D3B6A6494B1C861B99F649C6F9EC51ABAF201B20F297327C95",
1695
                    "E4F60D0AA6D7F3D3B6A6494B1C861B99F649C6F9EC51ABAF201B20F297327C95"},
1696
                   {"a",
1697
                    "b",
1698
                    "BC2E013472F39AC579964880E422737C82BA812CB8BC2FD17E013060D71E6E19",
1699
                    "5E31CFAA3FAFB1A5BA296A0D2BAB9CA44D7936E9BF0BBC54637D0C53DBC4A432"},
1700
                   {"A",
1701
                    "B",
1702
                    "4B3206201C4BC9B6CD6C36532A97687DF9238155D99ADB60C66BF2B2220643D8",
1703
                    "FFF635A52A16618B4A0E9CD26B5E5A2FA573D343C051E6DE8B0811B1ACC89B86"},
1704
                   {
1705
                      "Test Issuer/US/Botan Project/Testing",
1706
                      "Test Subject/US/Botan Project/Testing",
1707
                      "ACB4F373004A56A983A23EB8F60FA4706312B5DB90FD978574FE7ACC84E093A5",
1708
                      "87039231C2205B74B6F1F3830A66272C0B41F71894B03AC3150221766D95267B",
1709
                   },
1710
                   {
1711
                      "Test Subject/US/Botan Project/Testing",
1712
                      "Test Issuer/US/Botan Project/Testing",
1713
                      "87039231C2205B74B6F1F3830A66272C0B41F71894B03AC3150221766D95267B",
1714
                      "ACB4F373004A56A983A23EB8F60FA4706312B5DB90FD978574FE7ACC84E093A5",
1715
                   }};
72✔
1716

1717
   for(const auto& a : cases) {
72✔
1718
      Botan::X509_Cert_Options opts{a.issuer};
60✔
1719
      opts.CA_key();
60✔
1720

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

1723
      result.test_str_eq(a.issuer, Botan::hex_encode(issuer_cert.raw_issuer_dn_sha256()), a.issuer_hash);
60✔
1724
      result.test_str_eq(a.issuer, Botan::hex_encode(issuer_cert.raw_subject_dn_sha256()), a.issuer_hash);
60✔
1725

1726
      const Botan::X509_CA ca(issuer_cert, key, hash_fn, rng);
60✔
1727
      const Botan::PKCS10_Request req =
60✔
1728
         Botan::X509::create_cert_req(Botan::X509_Cert_Options(a.subject), key, hash_fn, rng);
60✔
1729
      const Botan::X509_Certificate subject_cert =
60✔
1730
         ca.sign_request(req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
60✔
1731

1732
      result.test_str_eq(a.subject, Botan::hex_encode(subject_cert.raw_issuer_dn_sha256()), a.issuer_hash);
60✔
1733
      result.test_str_eq(a.subject, Botan::hex_encode(subject_cert.raw_subject_dn_sha256()), a.subject_hash);
60✔
1734
   }
60✔
1735
   return result;
12✔
1736
}
72✔
1737

1738
   #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
1739

1740
Test::Result test_x509_tn_auth_list_extension_decode() {
1✔
1741
   /* cert with TNAuthList extension data was generated by asn1parse cfg:
1742

1743
      asn1=SEQUENCE:tn_auth_list
1744

1745
      [tn_auth_list]
1746
      spc=EXP:0,IA5:1001
1747
      range=EXP:1,SEQUENCE:TelephoneNumberRange
1748
      one=EXP:2,IA5:333
1749

1750
      [TelephoneNumberRange]
1751
      start1=IA5:111
1752
      count1=INT:128
1753
      start2=IA5:222
1754
      count2=INT:256
1755
    */
1756
   const std::string filename("TNAuthList.pem");
1✔
1757
   Test::Result result("X509 TNAuthList decode");
1✔
1758
   result.start_timer();
1✔
1759

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

1762
   using Botan::Cert_Extension::TNAuthList;
1✔
1763

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

1766
   const auto& tn_entries = tn_auth_list->entries();
1✔
1767

1768
   result.test_not_null("cert has TNAuthList extension", tn_auth_list);
1✔
1769

1770
   result.test_throws("wrong telephone_number_range() accessor for spc",
1✔
1771
                      [&tn_entries] { tn_entries[0].telephone_number_range(); });
2✔
1772
   result.test_throws("wrong telephone_number() accessor for range",
1✔
1773
                      [&tn_entries] { tn_entries[1].telephone_number(); });
2✔
1774
   result.test_throws("wrong service_provider_code() accessor for one",
1✔
1775
                      [&tn_entries] { tn_entries[2].service_provider_code(); });
2✔
1776

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

1780
   result.test_is_true("range entry type", tn_entries[1].type() == TNAuthList::Entry::TelephoneNumberRange);
1✔
1781
   const auto& range = tn_entries[1].telephone_number_range();
1✔
1782
   result.test_sz_eq("range entries count", range.size(), 2);
1✔
1783
   result.test_str_eq("range entry 0 start data", range[0].start.value(), "111");
1✔
1784
   result.test_sz_eq("range entry 0 count data", range[0].count, 128);
1✔
1785
   result.test_str_eq("range entry 1 start data", range[1].start.value(), "222");
1✔
1786
   result.test_sz_eq("range entry 1 count data", range[1].count, 256);
1✔
1787

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

1791
   result.end_timer();
1✔
1792
   return result;
2✔
1793
}
1✔
1794

1795
   #endif
1796

1797
std::vector<std::string> get_sig_paddings(const std::string& sig_algo, const std::string& hash) {
12✔
1798
   if(sig_algo == "RSA") {
12✔
1799
      return {
1✔
1800
   #if defined(BOTAN_HAS_EMSA_PKCS1)
1801
         "PKCS1v15(" + hash + ")",
1✔
1802
   #endif
1803
   #if defined(BOTAN_HAS_EMSA_PSS)
1804
            "PSS(" + hash + ")",
1805
   #endif
1806
      };
3✔
1807
   } else if(sig_algo == "DSA" || sig_algo == "ECDSA" || sig_algo == "ECGDSA" || sig_algo == "ECKCDSA" ||
11✔
1808
             sig_algo == "GOST-34.10") {
7✔
1809
      return {hash};
10✔
1810
   } else if(sig_algo == "Ed25519" || sig_algo == "Ed448") {
6✔
1811
      return {"Pure"};
2✔
1812
   } else if(sig_algo == "Dilithium" || sig_algo == "ML-DSA") {
4✔
1813
      return {"Randomized"};
2✔
1814
   } else if(sig_algo == "HSS-LMS") {
2✔
1815
      return {""};
1✔
1816
   } else {
1817
      return {};
1✔
1818
   }
1819
}
6✔
1820

1821
class X509_Cert_Unit_Tests final : public Test {
1✔
1822
   public:
1823
      std::vector<Test::Result> run() override {
1✔
1824
         std::vector<Test::Result> results;
1✔
1825

1826
         auto& rng = this->rng();
1✔
1827

1828
         const std::string sig_algos[]{"RSA",
1✔
1829
                                       "DSA",
1830
                                       "ECDSA",
1831
                                       "ECGDSA",
1832
                                       "ECKCDSA",
1833
                                       "GOST-34.10",
1834
                                       "Ed25519",
1835
                                       "Ed448",
1836
                                       "Dilithium",
1837
                                       "ML-DSA",
1838
                                       "SLH-DSA",
1839
                                       "HSS-LMS"};
13✔
1840

1841
         for(const std::string& algo : sig_algos) {
13✔
1842
   #if !defined(BOTAN_HAS_EMSA_PKCS1)
1843
            if(algo == "RSA")
1844
               continue;
1845
   #endif
1846

1847
            std::string hash = "SHA-256";
12✔
1848

1849
            if(algo == "Ed25519") {
12✔
1850
               hash = "SHA-512";
1✔
1851
            }
1852
            if(algo == "Ed448") {
12✔
1853
               hash = "SHAKE-256(912)";
1✔
1854
            }
1855
            if(algo == "Dilithium" || algo == "ML-DSA") {
12✔
1856
               hash = "SHAKE-256(512)";
2✔
1857
            }
1858

1859
            auto key = make_a_private_key(algo, rng);
12✔
1860

1861
            if(key == nullptr) {
12✔
1862
               continue;
×
1863
            }
1864

1865
            results.push_back(test_hashes(*key, hash, rng));
24✔
1866
            results.push_back(test_valid_constraints(*key, algo));
24✔
1867

1868
            Test::Result usage_result("X509 Usage");
12✔
1869
            try {
12✔
1870
               usage_result.merge(test_usage(*key, algo, hash, rng));
12✔
1871
            } catch(std::exception& e) {
×
1872
               usage_result.test_failure("test_usage " + algo, e.what());
×
1873
            }
×
1874
            results.push_back(usage_result);
12✔
1875

1876
            for(const auto& padding_scheme : get_sig_paddings(algo, hash)) {
23✔
1877
               Test::Result cert_result("X509 Unit");
11✔
1878

1879
               try {
11✔
1880
                  cert_result.merge(test_x509_cert(*key, algo, padding_scheme, hash, rng));
11✔
1881
               } catch(std::exception& e) {
×
1882
                  cert_result.test_failure("test_x509_cert " + algo, e.what());
×
1883
               }
×
1884
               results.push_back(cert_result);
11✔
1885

1886
               Test::Result pkcs10_result("PKCS10 extensions");
11✔
1887
               try {
11✔
1888
                  pkcs10_result.merge(test_pkcs10_ext(*key, padding_scheme, hash, rng));
11✔
1889
               } catch(std::exception& e) {
×
1890
                  pkcs10_result.test_failure("test_pkcs10_ext " + algo, e.what());
×
1891
               }
×
1892
               results.push_back(pkcs10_result);
11✔
1893

1894
               Test::Result self_issued_result("X509 Self Issued");
11✔
1895
               try {
11✔
1896
                  self_issued_result.merge(test_self_issued(*key, algo, padding_scheme, hash, rng));
11✔
1897
               } catch(std::exception& e) {
×
1898
                  self_issued_result.test_failure("test_self_issued " + algo, e.what());
×
1899
               }
×
1900
               results.push_back(self_issued_result);
11✔
1901

1902
               Test::Result extensions_result("X509 Extensions");
11✔
1903
               try {
11✔
1904
                  extensions_result.merge(test_x509_extensions(*key, algo, padding_scheme, hash, rng));
11✔
1905
               } catch(std::exception& e) {
×
1906
                  extensions_result.test_failure("test_extensions " + algo, e.what());
×
1907
               }
×
1908
               results.push_back(extensions_result);
11✔
1909

1910
               Test::Result custom_dn_result("X509 Custom DN");
11✔
1911
               try {
11✔
1912
                  custom_dn_result.merge(test_custom_dn_attr(*key, algo, padding_scheme, hash, rng));
11✔
1913
               } catch(std::exception& e) {
×
1914
                  custom_dn_result.test_failure("test_custom_dn_attr " + algo, e.what());
×
1915
               }
×
1916
               results.push_back(custom_dn_result);
11✔
1917
            }
23✔
1918
         }
24✔
1919

1920
         /*
1921
         These are algos which cannot sign but can be included in certs
1922
         */
1923
         const std::vector<std::string> enc_algos = {
1✔
1924
            "DH", "ECDH", "ElGamal", "Kyber", "ML-KEM", "FrodoKEM", "ClassicMcEliece"};
1✔
1925

1926
         for(const std::string& algo : enc_algos) {
8✔
1927
            auto key = make_a_private_key(algo, rng);
7✔
1928

1929
            if(key) {
7✔
1930
               results.push_back(test_valid_constraints(*key, algo));
14✔
1931
            }
1932
         }
7✔
1933

1934
   #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM) && defined(BOTAN_HAS_EMSA_PKCS1) && defined(BOTAN_HAS_EMSA_PSSR) && \
1935
      defined(BOTAN_HAS_RSA)
1936
         Test::Result pad_config_result("X509 Padding Config");
1✔
1937
         try {
1✔
1938
            pad_config_result.merge(test_padding_config());
1✔
1939
         } catch(const std::exception& e) {
×
1940
            pad_config_result.test_failure("test_padding_config", e.what());
×
1941
         }
×
1942
         results.push_back(pad_config_result);
1✔
1943
   #endif
1944

1945
   #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
1946
         results.push_back(test_x509_utf8());
2✔
1947
         results.push_back(test_x509_any_key_extended_usage());
2✔
1948
         results.push_back(test_x509_bmpstring());
2✔
1949
         results.push_back(test_x509_teletex());
2✔
1950
         results.push_back(test_crl_dn_name());
2✔
1951
         results.push_back(test_rdn_multielement_set_name());
2✔
1952
         results.push_back(test_x509_decode_list());
2✔
1953
         results.push_back(test_rsa_oaep());
2✔
1954
         results.push_back(test_x509_authority_info_access_extension());
2✔
1955
         results.push_back(test_crl_issuing_distribution_point_extension());
2✔
1956
         results.push_back(test_verify_gost2012_cert());
2✔
1957
         results.push_back(test_parse_rsa_pss_cert());
2✔
1958
         results.push_back(test_x509_tn_auth_list_extension_decode());
2✔
1959
   #endif
1960

1961
         results.push_back(test_x509_encode_authority_info_access_extension());
2✔
1962
         results.push_back(test_x509_extension());
2✔
1963
         results.push_back(test_x509_extension_decode_duplicate());
2✔
1964
         results.push_back(test_x509_dates());
2✔
1965
         results.push_back(test_cert_status_strings());
2✔
1966
         results.push_back(test_x509_uninit());
2✔
1967
         results.push_back(test_crl_entry_negative_serial());
2✔
1968

1969
         return results;
1✔
1970
      }
13✔
1971
};
1972

1973
BOTAN_REGISTER_TEST("x509", "x509_unit", X509_Cert_Unit_Tests);
1974

1975
#endif
1976

1977
}  // namespace
1978

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