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

randombit / botan / 29630228535

18 Jul 2026 02:46AM UTC coverage: 89.407% (-2.3%) from 91.66%
29630228535

push

github

web-flow
Merge pull request #5740 from randombit/jack/x509-serial-number

Add an explicit type for X509 certificate serial numbers

114089 of 127606 relevant lines covered (89.41%)

10801388.62 hits per line

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

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

8
#include "tests.h"
9

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

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

31
namespace Botan_Tests {
32

33
namespace {
34

35
#if defined(BOTAN_HAS_X509_CERTIFICATES)
36

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

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

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

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

53
   opts.CA_key(1);
112✔
54

55
   return opts;
112✔
56
}
×
57

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

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

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

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

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

77
   return opts;
37✔
78
}
×
79

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

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

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

90
   return opts;
11✔
91
}
×
92

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

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

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

105
   return opts;
55✔
106
}
×
107

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

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

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

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

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

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

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

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

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

208
   return result;
1✔
209
}
1✔
210

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

214
   Botan::Extensions extn;
1✔
215

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

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

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

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

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

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

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

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

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

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

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

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

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

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

265
   const auto* crl_number = decoded_extns.get_extension_object_as<Botan::Cert_Extension::CRL_Number>();
1✔
266
   if(result.test_is_true("CRL number recognized without explicit context", crl_number != nullptr)) {
1✔
267
      result.test_bn_eq("Decoded CRL number", crl_number->crl_number(), Botan::BigInt(42));
1✔
268
      result.test_sz_eq("Decoded CRL number legacy accessor", crl_number->get_crl_number(), 42);
1✔
269
   }
270

271
   const Botan::BigInt large_crl_number("0xE3F59B2C66C2789E9AC53C545C80CF1F8B9E4BEA");
1✔
272
   const Botan::X509_CRL large_number_crl(Test::read_binary_data_file("x509/misc/crl_number_160bit.pem"));
1✔
273
   if(result.test_opt_not_null("X509_CRL large CRL number is present", large_number_crl.crl_number_bigint())) {
1✔
274
      result.test_bn_eq("X509_CRL large CRL number", large_number_crl.crl_number_bigint().value(), large_crl_number);
1✔
275
   }
276

277
   const auto* large_crl_number_extn =
1✔
278
      large_number_crl.extensions().get_extension_object_as<Botan::Cert_Extension::CRL_Number>();
2✔
279
   if(result.test_is_true("Large CRL number extension is present", large_crl_number_extn != nullptr)) {
1✔
280
      result.test_bn_eq("Large CRL number extension", large_crl_number_extn->crl_number(), large_crl_number);
1✔
281

282
      result.test_throws<Botan::Encoding_Error>("Large CRL number extension legacy accessor throws",
1✔
283
                                                [&]() { large_crl_number_extn->get_crl_number(); });
2✔
284
   }
285

286
   result.test_throws<Botan::Encoding_Error>("X509_CRL legacy CRL number accessor throws",
1✔
287
                                             [&]() { large_number_crl.crl_number(); });
2✔
288

289
   return result;
2✔
290
}
3✔
291

292
Test::Result test_x509_extension_decode_duplicate() {
1✔
293
   Test::Result result("X509 Extensions reject duplicate OID");
1✔
294

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

298
   std::vector<uint8_t> der;
1✔
299
   Botan::DER_Encoder enc(der);
1✔
300
   enc.start_sequence();
1✔
301
   for(size_t i = 0; i != 2; ++i) {
3✔
302
      enc.start_sequence().encode(oid_bc).encode(bc_bits, Botan::ASN1_Type::OctetString).end_cons();
2✔
303
   }
304
   enc.end_cons();
1✔
305

306
   Botan::Extensions extns;
1✔
307
   Botan::BER_Decoder dec(der);
1✔
308
   result.test_throws<Botan::Decoding_Error>("Duplicate extension OID is rejected at decode time",
1✔
309
                                             [&]() { extns.decode_from(dec); });
2✔
310

311
   return result;
1✔
312
}
2✔
313

314
Test::Result test_x509_dates() {
1✔
315
   Test::Result result("X509 Time");
1✔
316

317
   Botan::X509_Time time;
1✔
318
   result.test_is_true("unset time not set", !time.time_is_set());
1✔
319
   time = Botan::X509_Time("080201182200Z", Botan::ASN1_Type::UtcTime);
1✔
320
   result.test_is_true("time set after construction", time.time_is_set());
1✔
321
   result.test_str_eq("time readable_string", time.readable_string(), "2008/02/01 18:22:00 UTC");
1✔
322

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

326
   time = Botan::X509_Time("200305100350Z");
1✔
327
   result.test_str_eq(
1✔
328
      "UTC_OR_GENERALIZED_TIME from UTC_TIME readable_string", time.readable_string(), "2020/03/05 10:03:50 UTC");
1✔
329

330
   time = Botan::X509_Time("20200305100350Z");
1✔
331
   result.test_str_eq("UTC_OR_GENERALIZED_TIME from GENERALIZED_TIME readable_string",
1✔
332
                      time.readable_string(),
1✔
333
                      "2020/03/05 10:03:50 UTC");
334

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

338
   // Dates that are valid per X.500 but rejected as unsupported
339
   const std::string valid_but_unsup[]{
1✔
340
      "0802010000-0000",
341
      "0802011724+0000",
342
      "0406142334-0500",
343
      "9906142334+0500",
344
      "0006142334-0530",
345
      "0006142334+0530",
346

347
      "080201000000-0000",
348
      "080201172412+0000",
349
      "040614233433-0500",
350
      "990614233444+0500",
351
      "000614233455-0530",
352
      "000614233455+0530",
353
   };
13✔
354

355
   // valid length 13
356
   const std::string valid_utc[]{
1✔
357
      "080201000000Z",
358
      "080201172412Z",
359
      "040614233433Z",
360
      "990614233444Z",
361
      "000614233455Z",
362
   };
6✔
363

364
   const std::string invalid_utc[]{
1✔
365
      "",
366
      " ",
367
      "2008`02-01",
368
      "9999-02-01",
369
      "2000-02-01 17",
370
      "999921",
371

372
      // No seconds
373
      "0802010000Z",
374
      "0802011724Z",
375
      "0406142334Z",
376
      "9906142334Z",
377
      "0006142334Z",
378

379
      // valid length 13 -> range check
380
      "080201000061Z",  // seconds too big (61)
381
      "080201000060Z",  // seconds too big (60, leap seconds not covered by the standard)
382
      "0802010000-1Z",  // seconds too small (-1)
383
      "080201006000Z",  // minutes too big (60)
384
      "080201240000Z",  // hours too big (24:00)
385

386
      // valid length 13 -> invalid numbers
387
      "08020123112 Z",
388
      "08020123112!Z",
389
      "08020123112,Z",
390
      "08020123112\nZ",
391
      "080201232 33Z",
392
      "080201232!33Z",
393
      "080201232,33Z",
394
      "080201232\n33Z",
395
      "0802012 3344Z",
396
      "0802012!3344Z",
397
      "0802012,3344Z",
398
      "08022\n334455Z",
399
      "08022 334455Z",
400
      "08022!334455Z",
401
      "08022,334455Z",
402
      "08022\n334455Z",
403
      "082 33445511Z",
404
      "082!33445511Z",
405
      "082,33445511Z",
406
      "082\n33445511Z",
407
      "2 2211221122Z",
408
      "2!2211221122Z",
409
      "2,2211221122Z",
410
      "2\n2211221122Z",
411

412
      // wrong time zone
413
      "080201000000",
414
      "080201000000z",
415

416
      // Fractional seconds
417
      "170217180154.001Z",
418

419
      // Timezone offset
420
      "170217180154+0100",
421

422
      // Extra digits
423
      "17021718015400Z",
424

425
      // Non-digits
426
      "17021718015aZ",
427

428
      // Trailing garbage
429
      "170217180154Zlongtrailinggarbage",
430

431
      // Swapped type
432
      "20170217180154Z",
433
   };
49✔
434

435
   // valid length 15
436
   const std::string valid_generalized_time[]{
1✔
437
      "20000305100350Z",
438
   };
2✔
439

440
   const std::string invalid_generalized[]{
1✔
441
      // No trailing Z
442
      "20000305100350",
443

444
      // No seconds
445
      "200003051003Z",
446

447
      // Fractional seconds
448
      "20000305100350.001Z",
449

450
      // Timezone offset
451
      "20170217180154+0100",
452

453
      // Extra digits
454
      "2017021718015400Z",
455

456
      // Non-digits
457
      "2017021718015aZ",
458

459
      // Trailing garbage
460
      "20170217180154Zlongtrailinggarbage",
461

462
      // Swapped type
463
      "170217180154Z",
464
   };
9✔
465

466
   for(const auto& v : valid_but_unsup) {
13✔
467
      result.test_throws("valid but unsupported", [v]() { const Botan::X509_Time t(v, Botan::ASN1_Type::UtcTime); });
60✔
468
   }
469

470
   for(const auto& v : valid_utc) {
6✔
471
      const Botan::X509_Time t(v, Botan::ASN1_Type::UtcTime);
5✔
472
   }
5✔
473

474
   for(const auto& v : valid_generalized_time) {
2✔
475
      const Botan::X509_Time t(v, Botan::ASN1_Type::GeneralizedTime);
1✔
476
   }
1✔
477

478
   for(const auto& v : invalid_utc) {
49✔
479
      result.test_throws("invalid", [v]() { const Botan::X509_Time t(v, Botan::ASN1_Type::UtcTime); });
240✔
480
   }
481

482
   for(const auto& v : invalid_generalized) {
9✔
483
      result.test_throws("invalid", [v]() { const Botan::X509_Time t(v, Botan::ASN1_Type::GeneralizedTime); });
40✔
484
   }
485

486
   return result;
1✔
487
}
80✔
488

489
Test::Result test_x509_encode_authority_info_access_extension() {
1✔
490
   Test::Result result("X509 with encoded PKIX.AuthorityInformationAccess extension");
1✔
491

492
   #if defined(BOTAN_HAS_RSA) && defined(BOTAN_HAS_EMSA_PKCS1)
493
   auto rng = Test::new_rng(__func__);
1✔
494

495
   const std::string sig_algo{"RSA"};
1✔
496
   const std::string hash_fn{"SHA-256"};
1✔
497
   const std::string padding_method{"PKCS1v15(SHA-256)"};
1✔
498

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

506
   // OCSP
507
   const std::string_view ocsp_uri{"http://staging.ocsp.d-trust.net"};
1✔
508
   const auto ocsp_uri_parsed = Botan::URI::parse(ocsp_uri).value();
2✔
509

510
   // create a CA
511
   auto ca_key = make_a_private_key(sig_algo, *rng);
1✔
512
   result.require("CA key", ca_key != nullptr);
1✔
513
   const auto ca_cert = Botan::X509::create_self_signed_cert(ca_opts(), *ca_key, hash_fn, *rng);
1✔
514
   const Botan::X509_CA ca(ca_cert, *ca_key, hash_fn, padding_method, *rng);
1✔
515

516
   // create a certificate with only caIssuer information
517
   auto key = make_a_private_key(sig_algo, *rng);
1✔
518

519
   Botan::X509_Cert_Options opts1 = req_opts1(sig_algo);
1✔
520
   opts1.extensions.add(
1✔
521
      std::make_unique<Botan::Cert_Extension::Authority_Information_Access>(std::vector<Botan::URI>{}, ca_issuers));
2✔
522

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

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

527
   if(!result.test_sz_eq("number of ca_issuers URIs", cert.ca_issuer_uris().size(), 2)) {
1✔
528
      return result;
529
   }
530

531
   for(const auto& ca_issuer : cert.ca_issuer_uris()) {
3✔
532
      result.test_is_true("CA issuer URI present in certificate",
4✔
533
                          std::ranges::find(ca_issuers, ca_issuer) != ca_issuers.end());
4✔
534
   }
535

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

538
   // create a certificate with only OCSP URI information
539
   Botan::X509_Cert_Options opts2 = req_opts1(sig_algo);
1✔
540
   opts2.extensions.add(
1✔
541
      std::make_unique<Botan::Cert_Extension::Authority_Information_Access>(std::vector<Botan::URI>{ocsp_uri_parsed}));
3✔
542

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

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

547
   result.test_is_true("OCSP URI available", !cert.ocsp_responder_uris().empty());
1✔
548
   result.test_is_true("no CA Issuer URI available", cert.ca_issuer_uris().empty());
1✔
549
   result.test_str_eq("OCSP responder URI matches", cert.ocsp_responder_uris().at(0).original_input(), ocsp_uri);
1✔
550

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

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

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

560
   result.test_is_true("OCSP URI available", !cert.ocsp_responder_uris().empty());
1✔
561
   result.test_is_true("CA Issuer URI available", !cert.ca_issuer_uris().empty());
1✔
562

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

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

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

572
   const auto* aia_ext =
1✔
573
      cert.v3_extensions().get_extension_object_as<Botan::Cert_Extension::Authority_Information_Access>();
1✔
574
   result.test_is_true("AIA extension present", aia_ext != nullptr);
1✔
575

576
   const auto ocsp_responders = aia_ext->ocsp_responders();
1✔
577
   result.test_sz_eq("number of OCSP responder URIs", ocsp_responders.size(), 2);
1✔
578
   result.test_str_eq("First OCSP responder URI matches", ocsp_responders[0], "http://ocsp.example.com");
1✔
579
   result.test_str_eq("Second OCSP responder URI matches", ocsp_responders[1], "http://backup-ocsp.example.com");
1✔
580

581
   const auto& cert_ocsp_responders = cert.ocsp_responder_uris();
1✔
582
   result.test_sz_eq("Certificate: number of OCSP responder URIs", cert_ocsp_responders.size(), 2);
1✔
583
   result.test_str_eq("Certificate: First OCSP responder URI matches",
1✔
584
                      cert_ocsp_responders[0].original_input(),
1✔
585
                      "http://ocsp.example.com");
586
   result.test_str_eq("Certificate: Second OCSP responder URI matches",
1✔
587
                      cert_ocsp_responders[1].original_input(),
1✔
588
                      "http://backup-ocsp.example.com");
589
   #endif
590

591
   return result;
1✔
592
}
9✔
593

594
Test::Result test_x509_serial_number_type() {
1✔
595
   Test::Result result("X509_Serial_Number");
1✔
596

597
   auto der_of = [](const Botan::X509_Serial_Number& sn) {
8✔
598
      std::vector<uint8_t> der;
7✔
599
      Botan::DER_Encoder enc(der);
7✔
600
      enc.encode(sn);
7✔
601
      return der;
7✔
602
   };
7✔
603

604
   auto from_bigint = [](int64_t v) {
34✔
605
      const auto mag = Botan::BigInt::from_u64(static_cast<uint64_t>(v < 0 ? -v : v));
33✔
606
      return Botan::X509_Serial_Number(v < 0 ? -mag : mag);
85✔
607
   };
33✔
608

609
   // Default construction is zero
610
   const Botan::X509_Serial_Number zero;
1✔
611
   result.test_is_true("default is zero", zero.is_zero());
1✔
612
   result.test_is_false("zero is not negative", zero.is_negative());
1✔
613
   result.test_is_false("zero does not conform", zero.conforms_to_rfc5280());
1✔
614
   result.test_str_eq("zero to_string", zero.to_string(), "00");
1✔
615
   result.test_is_true("zero magnitude is empty", zero.magnitude().empty());
1✔
616

617
   // Golden DER encodings
618
   result.test_bin_eq("encode 0", der_of(zero), "020100");
1✔
619
   result.test_bin_eq("encode 127", der_of(from_bigint(127)), "02017F");
1✔
620
   result.test_bin_eq("encode 128", der_of(from_bigint(128)), "02020080");
1✔
621
   result.test_bin_eq("encode 255", der_of(from_bigint(255)), "020200FF");
1✔
622
   result.test_bin_eq("encode -1", der_of(from_bigint(-1)), "0201FF");
1✔
623
   result.test_bin_eq("encode -129", der_of(from_bigint(-129)), "0202FF7F");
1✔
624
   result.test_bin_eq("encode -255", der_of(from_bigint(-255)), "0202FF01");
1✔
625

626
   // Construction paths agree
627
   const std::vector<uint8_t> ff{0xFF};
1✔
628
   const std::vector<uint8_t> zero_ff{0x00, 0xFF};
1✔
629
   result.test_is_true("from_bytes strips leading zeros",
1✔
630
                       Botan::X509_Serial_Number::from_bytes(zero_ff) == from_bigint(255));
2✔
631
   result.test_is_true("from_bytes is unsigned", Botan::X509_Serial_Number::from_bytes(ff) == from_bigint(255));
1✔
632
   result.test_is_true("from_der_contents normalizes",
1✔
633
                       Botan::X509_Serial_Number::from_der_contents(zero_ff) == from_bigint(255));
2✔
634
   const std::vector<uint8_t> redundant_neg{0xFF, 0xFF, 0x80};
1✔
635
   result.test_is_true("from_der_contents normalizes negative",
1✔
636
                       Botan::X509_Serial_Number::from_der_contents(redundant_neg) == from_bigint(-128));
2✔
637
   result.test_is_true("from_bytes of empty is zero", Botan::X509_Serial_Number::from_bytes({}).is_zero());
1✔
638

639
   // BigInt round trip
640
   result.test_is_true("to_bigint round trip", from_bigint(-129).to_bigint() == -Botan::BigInt::from_u64(129));
2✔
641
   result.test_str_eq("to_string negative", from_bigint(-255).to_string(), "-FF");
1✔
642
   result.test_str_eq("to_string positive", from_bigint(255).to_string(), "FF");
1✔
643

644
   // Decoding rejects an empty INTEGER encoding
645
   {
1✔
646
      const auto empty_int = Botan::hex_decode("0200");
1✔
647
      Botan::BER_Decoder dec(empty_int);
1✔
648
      Botan::X509_Serial_Number sn;
1✔
649
      result.test_throws("empty INTEGER rejected", [&] { sn.decode_from(dec); });
2✔
650
   }
1✔
651

652
   // Numeric ordering
653
   const std::vector<int64_t> ordered{-256, -129, -2, -1, 0, 1, 127, 128, 255, 256};
1✔
654
   for(size_t i = 1; i != ordered.size(); ++i) {
10✔
655
      result.test_is_true("ordering " + std::to_string(ordered[i - 1]) + " < " + std::to_string(ordered[i]),
45✔
656
                          from_bigint(ordered[i - 1]) < from_bigint(ordered[i]));
18✔
657
   }
658

659
   // Conformance predicate
660
   result.test_is_true("1 conforms", from_bigint(1).conforms_to_rfc5280());
1✔
661
   result.test_is_false("-1 does not conform", from_bigint(-1).conforms_to_rfc5280());
1✔
662
   const std::vector<uint8_t> twenty(20, 0x7F);
1✔
663
   result.test_is_true("20 octets conforms", Botan::X509_Serial_Number::from_bytes(twenty).conforms_to_rfc5280());
1✔
664
   const std::vector<uint8_t> twentyone(21, 0x7F);
1✔
665
   result.test_is_false("21 octets does not conform",
2✔
666
                        Botan::X509_Serial_Number::from_bytes(twentyone).conforms_to_rfc5280());
1✔
667

668
   // Random serials suit certificate issuance
669
   auto rng = Test::new_rng(__func__);
1✔
670
   std::set<Botan::X509_Serial_Number> seen;
1✔
671
   for(size_t i = 0; i != 20; ++i) {
21✔
672
      const auto sn = Botan::X509_Serial_Number::random(*rng);
20✔
673
      result.test_is_true("random serial conforms", sn.conforms_to_rfc5280());
20✔
674
      result.test_is_true("random serial has expected size", sn.octet_length() <= 17);
20✔
675
      seen.insert(sn);
20✔
676
   }
20✔
677
   result.test_sz_eq("random serials are distinct", seen.size(), 20);
1✔
678

679
   return result;
1✔
680
}
2✔
681

682
   #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
683

684
Test::Result test_crl_dn_name() {
1✔
685
   Test::Result result("CRL DN name");
1✔
686

687
      // See GH #1252
688

689
      #if defined(BOTAN_HAS_RSA) && defined(BOTAN_HAS_EMSA_PKCS1)
690
   auto rng = Test::new_rng(__func__);
1✔
691

692
   const Botan::OID dc_oid("0.9.2342.19200300.100.1.25");
1✔
693

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

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

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

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

704
   result.test_is_true("contains DC component", crl.issuer_dn().get_attributes().count(dc_oid) == 1);
2✔
705
      #endif
706

707
   return result;
2✔
708
}
3✔
709

710
Test::Result test_rdn_multielement_set_name() {
1✔
711
   Test::Result result("DN with multiple elements in RDN");
1✔
712

713
   // GH #2611
714

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

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

720
   return result;
1✔
721
}
1✔
722

723
Test::Result test_rsa_oaep() {
1✔
724
   Test::Result result("RSA OAEP decoding");
1✔
725

726
      #if defined(BOTAN_HAS_RSA)
727
   const Botan::X509_Certificate cert(Test::data_file("x509/misc/rsa_oaep.pem"));
2✔
728

729
   auto public_key = cert.subject_public_key();
1✔
730
   result.test_not_null("Decoding RSA-OAEP worked", public_key.get());
1✔
731
   const auto& pk_info = cert.subject_public_key_algo();
1✔
732

733
   result.test_str_eq("RSA-OAEP OID", pk_info.oid().to_string(), Botan::OID::from_string("RSA/OAEP").to_string());
1✔
734
      #endif
735

736
   return result;
2✔
737
}
1✔
738

739
Test::Result test_x509_decode_list() {
1✔
740
   Test::Result result("X509_Certificate list decode");
1✔
741

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

744
   Botan::BER_Decoder dec(input);
1✔
745
   std::vector<Botan::X509_Certificate> certs;
1✔
746
   dec.decode_list(certs);
1✔
747

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

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

753
   return result;
1✔
754
}
1✔
755

756
Test::Result test_x509_serial_decoding() {
1✔
757
   Test::Result result("X509 certificate serial number decoding");
1✔
758

759
   const std::string base = "x509/serial_numbers/";
1✔
760

761
   const Botan::X509_Certificate pos(Test::data_file(base + "pos255.pem"));
2✔
762
   result.test_is_false("pos255 not negative", pos.serial().is_negative());
1✔
763
   result.test_is_true("pos255 conforms", pos.serial().conforms_to_rfc5280());
1✔
764
   result.test_str_eq("pos255 to_string", pos.serial().to_string(), "FF");
1✔
765
   result.test_bin_eq("pos255 magnitude matches legacy accessor", pos.serial_number(), pos.serial().magnitude());
1✔
766

767
   const Botan::X509_Certificate neg(Test::data_file(base + "neg255.pem"));
2✔
768
   result.test_is_true("neg255 negative", neg.serial().is_negative());
1✔
769
   result.test_is_true("neg255 value", neg.serial().to_bigint() == -Botan::BigInt::from_u64(255));
2✔
770
   result.test_bin_eq("neg255 DER contents", neg.serial().der_contents(), "FF01");
1✔
771
   result.test_bin_eq("neg255 magnitude collides with pos255", neg.serial_number(), pos.serial_number());
1✔
772
   result.test_is_true("neg255 and pos255 serials differ", neg.serial() != pos.serial());
1✔
773
   result.test_str_eq("neg255 to_string", neg.serial().to_string(), "-FF");
1✔
774
   result.test_is_false("neg255 does not conform", neg.serial().conforms_to_rfc5280());
1✔
775

776
   const Botan::X509_Certificate zero(Test::data_file(base + "zero.pem"));
2✔
777
   result.test_is_true("zero serial is zero", zero.serial().is_zero());
1✔
778
   result.test_is_true("zero legacy accessor is empty", zero.serial_number().empty());
1✔
779
   result.test_is_false("zero does not conform", zero.serial().conforms_to_rfc5280());
1✔
780

781
   const Botan::X509_Certificate twenty(Test::data_file(base + "twenty_octets.pem"));
2✔
782
   result.test_sz_eq("twenty octet serial length", twenty.serial().octet_length(), 20);
1✔
783
   result.test_is_true("twenty octet serial conforms", twenty.serial().conforms_to_rfc5280());
1✔
784

785
   const Botan::X509_Certificate twentyone(Test::data_file(base + "twentyone_octets.pem"));
2✔
786
   result.test_sz_eq("twentyone octet serial length", twentyone.serial().octet_length(), 21);
1✔
787
   result.test_is_false("twentyone octet serial does not conform", twentyone.serial().conforms_to_rfc5280());
1✔
788

789
   return result;
2✔
790
}
1✔
791

792
      #if defined(BOTAN_HAS_ECDSA) && defined(BOTAN_HAS_SHA2_32)
793
Test::Result test_x509_serial_revocation_matching() {
1✔
794
   Test::Result result("X509 CRL serial matching respects sign");
1✔
795

796
   auto rng = Test::new_rng(__func__);
1✔
797
   const std::string base = "x509/serial_numbers/";
1✔
798

799
   const Botan::X509_Certificate ca_cert(Test::data_file(base + "ca.pem"));
2✔
800
   Botan::DataSource_Stream key_in(Test::data_file(base + "ca_key.pem"));
2✔
801
   auto ca_key = Botan::PKCS8::load_key(key_in);
1✔
802

803
   const Botan::X509_Certificate pos(Test::data_file(base + "pos255.pem"));
3✔
804
   const Botan::X509_Certificate neg(Test::data_file(base + "neg255.pem"));
2✔
805

806
   const Botan::X509_CA ca(ca_cert, *ca_key, "SHA-256", "", *rng);
1✔
807

808
   // Revoke the +255 cert: the -255 cert shares its magnitude but must not match
809
   const auto crl_pos = ca.update_crl(ca.new_crl(*rng), {Botan::CRL_Entry(pos, Botan::CRL_Code::KeyCompromise)}, *rng);
2✔
810
   result.test_is_true("pos255 is revoked", crl_pos.is_revoked(pos));
1✔
811
   result.test_is_false("neg255 with the same magnitude is not revoked", crl_pos.is_revoked(neg));
1✔
812

813
   // Revoke the -255 cert: the entry's sign survives encoding of the CRL
814
   const auto crl_neg = ca.update_crl(ca.new_crl(*rng), {Botan::CRL_Entry(neg, Botan::CRL_Code::KeyCompromise)}, *rng);
2✔
815
   const Botan::X509_CRL crl_neg_rt(crl_neg.BER_encode());
1✔
816
   result.test_sz_eq("one revoked entry", crl_neg_rt.get_revoked().size(), 1);
1✔
817
   result.test_is_true("entry serial still negative after round trip",
1✔
818
                       crl_neg_rt.get_revoked().at(0).serial().is_negative());
1✔
819
   result.test_is_true("neg255 is revoked", crl_neg_rt.is_revoked(neg));
1✔
820
   result.test_is_false("pos255 with the same magnitude is not revoked", crl_neg_rt.is_revoked(pos));
1✔
821

822
   return result;
2✔
823
}
5✔
824
      #endif
825

826
Test::Result test_x509_utf8() {
1✔
827
   Test::Result result("X509 with UTF-8 encoded fields");
1✔
828

829
   try {
1✔
830
      const Botan::X509_Certificate utf8_cert(Test::data_file("x509/misc/contains_utf8string.pem"));
2✔
831

832
      // UTF-8 encoded fields of test certificate (contains cyrillic letters)
833
      const std::string organization =
1✔
834
         "\xD0\x9C\xD0\xBE\xD1\x8F\x20\xD0\xBA\xD0\xBE\xD0"
835
         "\xBC\xD0\xBF\xD0\xB0\xD0\xBD\xD0\xB8\xD1\x8F";
1✔
836
      const std::string organization_unit =
1✔
837
         "\xD0\x9C\xD0\xBE\xD1\x91\x20\xD0\xBF\xD0\xBE\xD0\xB4\xD1\x80\xD0\xB0"
838
         "\xD0\xB7\xD0\xB4\xD0\xB5\xD0\xBB\xD0\xB5\xD0\xBD\xD0\xB8\xD0\xB5";
1✔
839
      const std::string common_name =
1✔
840
         "\xD0\x9E\xD0\xBF\xD0\xB8\xD1\x81\xD0\xB0\xD0\xBD\xD0\xB8"
841
         "\xD0\xB5\x20\xD1\x81\xD0\xB0\xD0\xB9\xD1\x82\xD0\xB0";
1✔
842
      const std::string location = "\xD0\x9C\xD0\xBE\xD1\x81\xD0\xBA\xD0\xB2\xD0\xB0";
1✔
843

844
      const Botan::X509_DN& issuer_dn = utf8_cert.issuer_dn();
1✔
845

846
      result.test_str_eq("O", issuer_dn.get_first_attribute("O"), organization);
1✔
847
      result.test_str_eq("OU", issuer_dn.get_first_attribute("OU"), organization_unit);
1✔
848
      result.test_str_eq("CN", issuer_dn.get_first_attribute("CN"), common_name);
1✔
849
      result.test_str_eq("L", issuer_dn.get_first_attribute("L"), location);
1✔
850
   } catch(const Botan::Decoding_Error& ex) {
1✔
851
      result.test_failure(ex.what());
×
852
   }
×
853

854
   return result;
1✔
855
}
×
856

857
Test::Result test_x509_subject_key_id_derivation() {
1✔
858
   Test::Result result("X509 subject key id derivation");
1✔
859

860
   /*
861
   * Externally generated certificates whose subject key identifier was
862
   * derived using RFC 5280 4.2.1.2 method (1), covering RSA and ECDSA keys
863
   */
864
   for(const auto* file : {"name_constraints/root.pem", "misc/aruba.pem", "misc/contains_any_extended_key_usage.pem"}) {
4✔
865
      const Botan::X509_Certificate cert(Test::data_file(std::string("x509/") + file));
9✔
866
      const Botan::Cert_Extension::Subject_Key_ID skid(*cert.subject_public_key());
3✔
867
      result.test_bin_eq(std::string(file) + " subject key id", skid.get_key_id(), cert.subject_key_id());
9✔
868
   }
3✔
869

870
   return result;
1✔
871
}
×
872

873
Test::Result test_x509_any_key_extended_usage() {
1✔
874
   using Botan::Key_Constraints;
1✔
875
   using Botan::Usage_Type;
1✔
876

877
   Test::Result result("X509 with X509v3.AnyExtendedKeyUsage");
1✔
878
   try {
1✔
879
      const Botan::X509_Certificate any_eku_cert(Test::data_file("x509/misc/contains_any_extended_key_usage.pem"));
2✔
880

881
      result.test_is_true("is CA cert", any_eku_cert.is_CA_cert());
1✔
882
      result.test_is_true("DigitalSignature is allowed", any_eku_cert.allowed_usage(Key_Constraints::DigitalSignature));
1✔
883
      result.test_is_true("CrlSign is allowed", any_eku_cert.allowed_usage(Key_Constraints::CrlSign));
1✔
884
      result.test_is_false("OCSP responder is not allowed", any_eku_cert.allowed_usage(Usage_Type::OCSP_RESPONDER));
1✔
885
   } catch(const Botan::Decoding_Error& ex) {
1✔
886
      result.test_failure(ex.what());
×
887
   }
×
888
   return result;
1✔
889
}
×
890

891
Test::Result test_x509_bmpstring() {
1✔
892
   Test::Result result("X509 with UCS-2 (BMPString) encoded fields");
1✔
893

894
   try {
1✔
895
      const Botan::X509_Certificate ucs2_cert(Test::data_file("x509/misc/contains_bmpstring.pem"));
2✔
896

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

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

905
      const Botan::X509_DN& issuer_dn = ucs2_cert.issuer_dn();
1✔
906

907
      result.test_str_eq("O", issuer_dn.get_first_attribute("O"), organization);
1✔
908
      result.test_str_eq("CN", issuer_dn.get_first_attribute("CN"), common_name);
1✔
909
      result.test_str_eq("L", issuer_dn.get_first_attribute("L"), location);
1✔
910
   } catch(const Botan::Decoding_Error& ex) {
1✔
911
      result.test_failure(ex.what());
×
912
   }
×
913

914
   return result;
1✔
915
}
×
916

917
Test::Result test_x509_teletex() {
1✔
918
   Test::Result result("X509 with TeletexString encoded fields");
1✔
919

920
   try {
1✔
921
      const Botan::X509_Certificate teletex_cert(Test::data_file("x509/misc/teletex_dn.der"));
2✔
922

923
      const Botan::X509_DN& issuer_dn = teletex_cert.issuer_dn();
1✔
924

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

927
      result.test_str_eq("O", issuer_dn.get_first_attribute("O"), "neam CA");
1✔
928
      result.test_str_eq("CN", issuer_dn.get_first_attribute("CN"), common_name);
1✔
929
   } catch(const Botan::Decoding_Error& ex) {
1✔
930
      result.test_failure(ex.what());
×
931
   }
×
932

933
   return result;
1✔
934
}
×
935

936
Test::Result test_x509_authority_info_access_extension() {
1✔
937
   Test::Result result("X509 with PKIX.AuthorityInformationAccess extension");
1✔
938

939
   // contains no AIA extension
940
   const Botan::X509_Certificate no_aia_cert(Test::data_file("x509/misc/contains_utf8string.pem"));
2✔
941

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

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

948
   const auto& ca_issuers = aia_cert.ca_issuer_uris();
1✔
949

950
   result.test_sz_eq("number of ca_issuers URLs", ca_issuers.size(), 1);
1✔
951
   if(result.tests_failed() > 0) {
1✔
952
      return result;
953
   }
954

955
   result.test_str_eq("CA issuer URL matches", ca_issuers[0].original_input(), "http://gp.symcb.com/gp.crt");
1✔
956
   result.test_sz_eq("one OCSP responder URI", aia_cert.ocsp_responder_uris().size(), 1);
1✔
957
   result.test_str_eq(
2✔
958
      "OCSP responder URL matches", aia_cert.ocsp_responder_uris().at(0).original_input(), "http://gp.symcd.com");
1✔
959

960
   // contains AIA extension with 2 CA issuer URL and 1 OCSP responder
961
   const Botan::X509_Certificate aia_cert_2ca(
1✔
962
      Test::data_file("x509/misc/contains_authority_info_access_with_two_ca_issuers.pem"));
2✔
963

964
   const auto& ca_issuers2 = aia_cert_2ca.ca_issuer_uris();
1✔
965

966
   result.test_sz_eq("number of ca_issuers URLs", ca_issuers2.size(), 2);
1✔
967
   if(result.tests_failed() > 0) {
1✔
968
      return result;
969
   }
970

971
   result.test_str_eq("CA issuer URL matches",
1✔
972
                      ca_issuers2[0].original_input(),
1✔
973
                      "http://www.d-trust.net/cgi-bin/Bdrive_Test_CA_1-2_2017.crt");
974
   result.test_str_eq(
1✔
975
      "CA issuer URL matches",
976
      ca_issuers2[1].original_input(),
1✔
977
      "ldap://directory.d-trust.net/CN=Bdrive%20Test%20CA%201-2%202017,O=Bundesdruckerei%20GmbH,C=DE?cACertificate?base?");
978
   result.test_sz_eq("one OCSP responder URI", aia_cert_2ca.ocsp_responder_uris().size(), 1);
1✔
979
   result.test_str_eq("OCSP responder URL matches",
2✔
980
                      aia_cert_2ca.ocsp_responder_uris().at(0).original_input(),
1✔
981
                      "http://staging.ocsp.d-trust.net");
982

983
   // contains AIA extension with multiple OCSP responders
984
   const Botan::X509_Certificate aia_cert_multi_ocsp(
1✔
985
      Test::data_file("x509/misc/contains_multiple_ocsp_responders.pem"));
2✔
986

987
   const auto& ocsp_responders_multi = aia_cert_multi_ocsp.ocsp_responder_uris();
1✔
988
   result.test_sz_eq("number of OCSP responders", ocsp_responders_multi.size(), 3);
1✔
989
   result.test_str_eq(
1✔
990
      "First OCSP responder URL matches", ocsp_responders_multi[0].original_input(), "http://ocsp1.example.com");
1✔
991
   result.test_str_eq(
1✔
992
      "Second OCSP responder URL matches", ocsp_responders_multi[1].original_input(), "http://ocsp2.example.com");
1✔
993
   result.test_str_eq(
1✔
994
      "Third OCSP responder URL matches", ocsp_responders_multi[2].original_input(), "http://ocsp3.example.com");
1✔
995
   result.test_is_true("no CA Issuer URI available", aia_cert_multi_ocsp.ca_issuer_uris().empty());
1✔
996

997
   return result;
1✔
998
}
1✔
999

1000
Test::Result test_x509_ldap_empty_authority_uris() {
1✔
1001
   Test::Result result("X509 LDAP URIs with empty authority");
1✔
1002

1003
   const auto check_uri = [&](std::string_view label, const Botan::URI& uri, std::string_view expected) {
4✔
1004
      result.test_str_eq(std::string(label) + " original URI", uri.original_input(), expected);
9✔
1005
      result.test_str_eq(std::string(label) + " scheme", uri.scheme(), "ldap");
6✔
1006
      const auto raw_authority = uri.raw_authority();
3✔
1007
      result.test_is_true(std::string(label) + " raw authority present", raw_authority.has_value());
9✔
1008
      if(raw_authority.has_value()) {
3✔
1009
         result.test_str_eq(std::string(label) + " raw authority is empty", std::string(*raw_authority), "");
9✔
1010
      }
1011
      result.test_is_false(std::string(label) + " has no parsed authority", uri.authority().has_value());
9✔
1012
      result.test_is_false(std::string(label) + " has no host", uri.host().has_value());
9✔
1013
   };
33✔
1014

1015
   const auto check_contains_uri =
1✔
1016
      [&](std::string_view label, const std::vector<Botan::URI>& uris, std::string_view expected) {
3✔
1017
         for(const auto& uri : uris) {
5✔
1018
            if(uri.original_input() == expected) {
5✔
1019
               check_uri(label, uri, expected);
3✔
1020
               return;
3✔
1021
            }
1022
         }
1023
         result.test_failure(std::string(label) + " URI not found");
×
1024
      };
1✔
1025

1026
   const Botan::X509_Certificate aruba_cert(Test::data_file("x509/misc/aruba.pem"));
2✔
1027
   const std::string aruba_aia =
1✔
1028
      "ldap:///CN=Security1-WIN-05PRGNGEKAO-CA,CN=AIA,CN=Public%20Key%20Services,CN=Services,CN=Configuration,"
1029
      "DC=Security1,DC=aruba,DC=com?cACertificate?base?objectClass=certificationAuthority";
1✔
1030
   const std::string aruba_cdp =
1✔
1031
      "ldap:///CN=Security1-WIN-05PRGNGEKAO-CA,CN=WIN-05PRGNGEKAO,CN=CDP,CN=Public%20Key%20Services,CN=Services,"
1032
      "CN=Configuration,DC=Security1,DC=aruba,DC=com?certificateRevocationList?base?objectClass=cRLDistributionPoint";
1✔
1033

1034
   check_contains_uri("Aruba AIA", aruba_cert.ca_issuer_uris(), aruba_aia);
1✔
1035
   check_contains_uri("Aruba CDP", aruba_cert.crl_distribution_point_uris(), aruba_cdp);
1✔
1036

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

1042
   check_contains_uri("BDE CDP", bde_cert.crl_distribution_point_uris(), bde_cdp);
1✔
1043

1044
   return result;
2✔
1045
}
1✔
1046

1047
Test::Result test_crl_issuing_distribution_point_extension() {
1✔
1048
   Test::Result result("X509 CRL IssuingDistributionPoint extension");
1✔
1049

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

1053
   result.test_str_eq(
1✔
1054
      "CRL IDP URI decoded correctly", crl.crl_issuing_distribution_point(), "http://localhost/subca/crldp/crl.crl");
1✔
1055

1056
   return result;
1✔
1057
}
1✔
1058

1059
Test::Result test_parse_rsa_pss_cert() {
1✔
1060
   Test::Result result("X509 RSA-PSS certificate");
1✔
1061

1062
   // See https://github.com/randombit/botan/issues/3019 for background
1063

1064
   try {
1✔
1065
      const Botan::X509_Certificate rsa_pss(Test::data_file("x509/misc/rsa_pss.pem"));
2✔
1066
      result.test_success("Was able to parse RSA-PSS certificate signed with ECDSA");
1✔
1067
   } catch(Botan::Exception& e) {
1✔
1068
      result.test_failure("Parsing failed", e.what());
×
1069
   }
×
1070

1071
   return result;
1✔
1072
}
×
1073

1074
Test::Result test_verify_gost2012_cert() {
1✔
1075
   Test::Result result("X509 GOST-2012 certificates");
1✔
1076

1077
      #if defined(BOTAN_HAS_GOST_34_10_2012) && defined(BOTAN_HAS_STREEBOG)
1078
   try {
1✔
1079
      if(Botan::EC_Group::supports_named_group("gost_256A")) {
1✔
1080
         const Botan::X509_Certificate root_cert(Test::data_file("x509/gost/gost_root.pem"));
2✔
1081
         const Botan::X509_Certificate root_int(Test::data_file("x509/gost/gost_int.pem"));
2✔
1082

1083
         Botan::Certificate_Store_In_Memory trusted;
1✔
1084
         trusted.add_certificate(root_cert);
1✔
1085

1086
         const Botan::Path_Validation_Restrictions restrictions(false, 128, false, {"Streebog-256"});
2✔
1087
         const auto validation_time = Botan::calendar_point(2024, 1, 1, 0, 0, 0).to_std_timepoint();
1✔
1088
         const Botan::Path_Validation_Result validation_result = Botan::x509_path_validate(
1✔
1089
            root_int, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
1✔
1090

1091
         result.test_is_true("GOST certificate validates", validation_result.successful_validation());
1✔
1092
      }
1✔
1093
   } catch(const Botan::Decoding_Error& e) {
×
1094
      result.test_failure(e.what());
×
1095
   }
×
1096
      #endif
1097

1098
   return result;
1✔
1099
}
×
1100

1101
   /*
1102
 * @brief checks the configurability of the RSA-PSS signature scheme
1103
 *
1104
 * For the other algorithms than RSA, only one padding is supported right now.
1105
 */
1106
      #if defined(BOTAN_HAS_EMSA_PKCS1) && defined(BOTAN_HAS_EMSA_PSSR) && defined(BOTAN_HAS_RSA)
1107
Test::Result test_padding_config() {
1✔
1108
   Test::Result test_result("X509 Padding Config");
1✔
1109

1110
   auto rng = Test::new_rng(__func__);
1✔
1111

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

1115
   // Create X509 CA certificate; PKCS1v15 is used for signing by default
1116
   Botan::X509_Cert_Options opt("TEST CA");
1✔
1117
   opt.CA_key();
1✔
1118

1119
   const Botan::X509_Certificate ca_cert_def = Botan::X509::create_self_signed_cert(opt, (*sk), "SHA-512", *rng);
1✔
1120
   test_result.test_opt_str_eq("CA certificate signature algorithm (default)",
2✔
1121
                               ca_cert_def.signature_algorithm().oid().registered_name(),
2✔
1122
                               "RSA/PKCS1v15(SHA-512)");
1123

1124
   // Create X509 CA certificate; RSA-PSS is explicitly set
1125
   opt.set_padding_scheme("PSSR");
1✔
1126
   const Botan::X509_Certificate ca_cert_exp = Botan::X509::create_self_signed_cert(opt, (*sk), "SHA-512", *rng);
1✔
1127
   test_result.test_opt_str_eq("CA certificate signature algorithm (explicit)",
2✔
1128
                               ca_cert_exp.signature_algorithm().oid().registered_name(),
2✔
1129
                               "RSA/PSS");
1130

1131
         #if defined(BOTAN_HAS_EMSA_X931)
1132
   // Try to set a padding scheme that is not supported for signing with the given key type
1133
   opt.set_padding_scheme("X9.31");
1✔
1134
   try {
1✔
1135
      const Botan::X509_Certificate ca_cert_wrong = Botan::X509::create_self_signed_cert(opt, (*sk), "SHA-512", *rng);
1✔
1136
      test_result.test_failure("Could build CA cert with invalid encoding scheme X9.31 for key type " +
×
1137
                               sk->algo_name());
×
1138
   } catch(const Botan::Invalid_Argument& e) {
1✔
1139
      test_result.test_str_eq("Build CA certificate with invalid encoding scheme X9.31 for key type " + sk->algo_name(),
3✔
1140
                              e.what(),
1✔
1141
                              "Signatures using RSA/X9.31(SHA-512) are not supported");
1142
   }
1✔
1143
         #endif
1144

1145
   test_result.test_opt_str_eq("CA certificate signature algorithm (explicit)",
2✔
1146
                               ca_cert_exp.signature_algorithm().oid().registered_name(),
2✔
1147
                               "RSA/PSS");
1148

1149
   const Botan::X509_Time not_before = from_date(-1, 1, 1);
1✔
1150
   const Botan::X509_Time not_after = from_date(2, 1, 2);
1✔
1151

1152
   // Prepare a signing request for the end certificate
1153
   Botan::X509_Cert_Options req_opt("endpoint");
1✔
1154
   req_opt.set_padding_scheme("PSS(SHA-512,MGF1,64)");
1✔
1155
   const Botan::PKCS10_Request end_req = Botan::X509::create_cert_req(req_opt, (*sk), "SHA-512", *rng);
1✔
1156
   test_result.test_opt_str_eq(
2✔
1157
      "Certificate request signature algorithm", end_req.signature_algorithm().oid().registered_name(), "RSA/PSS");
2✔
1158

1159
   // Create X509 CA object: will fail as the chosen hash functions differ
1160
   try {
1✔
1161
      const Botan::X509_CA ca_fail(ca_cert_exp, (*sk), "SHA-512", "PSS(SHA-256)", *rng);
1✔
1162
      test_result.test_failure("Configured conflicting hash functions for CA");
×
1163
   } catch(const Botan::Invalid_Argument& e) {
1✔
1164
      test_result.test_str_eq(
1✔
1165
         "Configured conflicting hash functions for CA",
1166
         e.what(),
1✔
1167
         "Specified hash function SHA-512 is incompatible with RSA chose hash function SHA-256 with user specified padding PSS(SHA-256)");
1168
   }
1✔
1169

1170
   // Create X509 CA object: its signer will use the padding scheme from the CA certificate, i.e. PKCS1v15
1171
   const Botan::X509_CA ca_def(ca_cert_def, (*sk), "SHA-512", *rng);
1✔
1172
   const Botan::X509_Certificate end_cert_pkcs1 = ca_def.sign_request(end_req, *rng, not_before, not_after);
1✔
1173
   test_result.test_opt_str_eq("End certificate signature algorithm",
2✔
1174
                               end_cert_pkcs1.signature_algorithm().oid().registered_name(),
2✔
1175
                               "RSA/PKCS1v15(SHA-512)");
1176

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

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

1190
   // Check CRL signature algorithm
1191
   const Botan::X509_CRL crl = ca_exp.new_crl(*rng);
1✔
1192
   test_result.test_opt_str_eq("CRL signature algorithm", crl.signature_algorithm().oid().registered_name(), "RSA/PSS");
2✔
1193

1194
   // sanity check for verification, the heavy lifting is done in the other unit tests
1195
   const Botan::Certificate_Store_In_Memory trusted(ca_exp.ca_certificate());
1✔
1196
   const Botan::Path_Validation_Restrictions restrictions(false, 80);
1✔
1197
   const Botan::Path_Validation_Result validation_result =
1✔
1198
      Botan::x509_path_validate(end_cert_pss, restrictions, trusted);
1✔
1199
   test_result.test_is_true("PSS signed certificate validates", validation_result.successful_validation());
1✔
1200

1201
   return test_result;
2✔
1202
}
3✔
1203
      #endif
1204

1205
   #endif
1206

1207
Test::Result test_pkcs10_ext(const Botan::Private_Key& key,
11✔
1208
                             const std::string& sig_padding,
1209
                             const std::string& hash_fn,
1210
                             Botan::RandomNumberGenerator& rng) {
1211
   Test::Result result("PKCS10 extensions");
11✔
1212

1213
   Botan::X509_Cert_Options opts;
11✔
1214

1215
   opts.dns = "main.example.org";
11✔
1216
   opts.more_dns.push_back("more1.example.org");
22✔
1217
   opts.more_dns.push_back("more2.example.org");
22✔
1218

1219
   opts.padding_scheme = sig_padding;
11✔
1220

1221
   Botan::AlternativeName alt_name;
11✔
1222
   alt_name.add_attribute("DNS", "bonus.example.org");
11✔
1223

1224
   Botan::X509_DN alt_dn;
11✔
1225
   alt_dn.add_attribute("X520.CommonName", "alt_cn");
11✔
1226
   alt_dn.add_attribute("X520.Organization", "testing");
11✔
1227
   alt_name.add_dn(alt_dn);
11✔
1228

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

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

1233
   const auto alt_dns_names = req.subject_alt_name().get_attribute("DNS");
11✔
1234

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

1237
   if(alt_dns_names.size() == 4) {
11✔
1238
      result.test_str_eq("Expected DNS name 1", alt_dns_names.at(0), "bonus.example.org");
11✔
1239
      result.test_str_eq("Expected DNS name 2", alt_dns_names.at(1), "main.example.org");
11✔
1240
      result.test_str_eq("Expected DNS name 3", alt_dns_names.at(2), "more1.example.org");
11✔
1241
      result.test_str_eq("Expected DNS name 3", alt_dns_names.at(3), "more2.example.org");
11✔
1242
   }
1243

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

1247
   return result;
11✔
1248
}
11✔
1249

1250
Test::Result test_x509_cert(const Botan::Private_Key& ca_key,
11✔
1251
                            const std::string& sig_algo,
1252
                            const std::string& sig_padding,
1253
                            const std::string& hash_fn,
1254
                            Botan::RandomNumberGenerator& rng) {
1255
   Test::Result result("X509 Unit");
11✔
1256

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

1260
   {
11✔
1261
      result.test_is_true("ca key usage cert", ca_cert.constraints().includes(Botan::Key_Constraints::KeyCertSign));
11✔
1262
      result.test_is_true("ca key usage crl", ca_cert.constraints().includes(Botan::Key_Constraints::CrlSign));
11✔
1263

1264
      // The subject key id is derived using SHA-1 regardless of the signature hash
1265
      result.test_bin_eq("ca cert subject key id is SHA-1 of subjectPublicKey",
11✔
1266
                         ca_cert.subject_key_id(),
1267
                         ca_cert.subject_public_key_bitstring_sha1());
1268
   }
1269

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

1273
   const Botan::PKCS10_Request user1_req =
11✔
1274
      Botan::X509::create_cert_req(req_opts1(sig_algo, sig_padding), *user1_key, hash_fn, rng);
11✔
1275

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

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

1281
   const Botan::PKCS10_Request user2_req =
11✔
1282
      Botan::X509::create_cert_req(req_opts2(sig_padding), *user2_key, hash_fn, rng);
11✔
1283

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

1287
   const Botan::PKCS10_Request user3_req =
11✔
1288
      Botan::X509::create_cert_req(req_opts3(sig_padding), *user3_key, hash_fn, rng);
11✔
1289

1290
   /* Create the CA object */
1291
   const Botan::X509_CA ca(ca_cert, ca_key, hash_fn, sig_padding, rng);
11✔
1292

1293
   const BigInt user1_serial(99);
11✔
1294

1295
   /* Sign the requests to create the certs */
1296
   const Botan::X509_Certificate user1_cert =
11✔
1297
      ca.sign_request(user1_req, rng, user1_serial, from_date(-1, 01, 01), from_date(2, 01, 01));
11✔
1298

1299
   result.test_sz_eq("User1 serial size matches expected", user1_cert.serial_number().size(), 1);
11✔
1300
   result.test_sz_eq("User1 serial matches expected", user1_cert.serial_number().at(0), size_t(99));
11✔
1301

1302
   result.test_bin_eq("user1 subject key id is SHA-1 of subjectPublicKey",
11✔
1303
                      user1_cert.subject_key_id(),
1304
                      user1_cert.subject_public_key_bitstring_sha1());
1305

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

1310
   const Botan::X509_Certificate user3_cert =
11✔
1311
      ca.sign_request(user3_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
11✔
1312

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

1317
   {
11✔
1318
      auto constraints = req_opts1(sig_algo).constraints;
11✔
1319
      result.test_is_true("user1 key usage", user1_cert.constraints().includes(constraints));
11✔
1320
   }
1321

1322
   /* Copy, assign and compare */
1323
   Botan::X509_Certificate user1_cert_copy(user1_cert);
11✔
1324
   result.test_is_true("certificate copy", user1_cert == user1_cert_copy);
11✔
1325

1326
   user1_cert_copy = user2_cert;
11✔
1327
   result.test_is_true("certificate assignment", user2_cert == user1_cert_copy);
11✔
1328

1329
   const Botan::X509_Certificate user1_cert_differ =
11✔
1330
      ca.sign_request(user1_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
11✔
1331

1332
   result.test_is_false("certificate differs", user1_cert == user1_cert_differ);
11✔
1333

1334
   /* Get cert data */
1335
   result.test_sz_eq("x509 version", user1_cert.x509_version(), size_t(3));
11✔
1336

1337
   const Botan::X509_DN& user1_issuer_dn = user1_cert.issuer_dn();
11✔
1338
   result.test_str_eq("issuer info CN", user1_issuer_dn.get_first_attribute("CN"), ca_opts().common_name);
11✔
1339
   result.test_str_eq("issuer info Country", user1_issuer_dn.get_first_attribute("C"), ca_opts().country);
11✔
1340
   result.test_str_eq("issuer info Orga", user1_issuer_dn.get_first_attribute("O"), ca_opts().organization);
11✔
1341
   result.test_str_eq("issuer info OrgaUnit", user1_issuer_dn.get_first_attribute("OU"), ca_opts().org_unit);
11✔
1342

1343
   const Botan::X509_DN& user3_subject_dn = user3_cert.subject_dn();
11✔
1344
   result.test_sz_eq("subject OrgaUnit count",
11✔
1345
                     user3_subject_dn.get_attribute("OU").size(),
22✔
1346
                     req_opts3(sig_algo).more_org_units.size() + 1);
22✔
1347
   result.test_str_eq(
22✔
1348
      "subject OrgaUnit #2", user3_subject_dn.get_attribute("OU").at(1), req_opts3(sig_algo).more_org_units.at(0));
33✔
1349

1350
   const Botan::AlternativeName& user1_altname = user1_cert.subject_alt_name();
11✔
1351
   result.test_str_eq("subject alt email", user1_altname.get_first_attribute("RFC822"), "testing@randombit.net");
11✔
1352
   result.test_str_eq("subject alt dns", user1_altname.get_first_attribute("DNS"), "botan.randombit.net");
11✔
1353
   result.test_str_eq("subject alt uri", user1_altname.get_first_attribute("URI"), "https://botan.randombit.net");
11✔
1354

1355
   const Botan::AlternativeName& user3_altname = user3_cert.subject_alt_name();
11✔
1356
   result.test_sz_eq(
11✔
1357
      "subject alt dns count", user3_altname.get_attribute("DNS").size(), req_opts3(sig_algo).more_dns.size() + 1);
22✔
1358
   result.test_str_eq(
22✔
1359
      "subject alt dns #2", user3_altname.get_attribute("DNS").at(1), req_opts3(sig_algo).more_dns.at(0));
33✔
1360

1361
   const Botan::X509_CRL crl1 = ca.new_crl(rng);
11✔
1362

1363
   /* Verify the certs */
1364
   const Botan::Path_Validation_Restrictions restrictions(false, 80);
11✔
1365
   Botan::Certificate_Store_In_Memory store;
11✔
1366

1367
   // First try with an empty store
1368
   const Botan::Path_Validation_Result result_no_issuer = Botan::x509_path_validate(user1_cert, restrictions, store);
11✔
1369
   result.test_str_eq(
11✔
1370
      "user 1 issuer not found",
1371
      result_no_issuer.result_string(),
11✔
1372
      Botan::Path_Validation_Result::status_string(Botan::Certificate_Status_Code::CERT_ISSUER_NOT_FOUND));
1373

1374
   store.add_certificate(ca.ca_certificate());
11✔
1375

1376
   Botan::Path_Validation_Result result_u1 = Botan::x509_path_validate(user1_cert, restrictions, store);
11✔
1377
   if(!result.test_is_true("user 1 validates", result_u1.successful_validation())) {
11✔
1378
      result.test_note("user 1 validation result", result_u1.result_string());
×
1379
   }
1380

1381
   Botan::Path_Validation_Result result_u2 = Botan::x509_path_validate(user2_cert, restrictions, store);
11✔
1382
   if(!result.test_is_true("user 2 validates", result_u2.successful_validation())) {
11✔
1383
      result.test_note("user 2 validation result", result_u2.result_string());
×
1384
   }
1385

1386
   const Botan::Path_Validation_Result result_self_signed =
11✔
1387
      Botan::x509_path_validate(user1_ss_cert, restrictions, store);
11✔
1388
   result.test_str_eq(
11✔
1389
      "user 1 issuer not found",
1390
      result_no_issuer.result_string(),
11✔
1391
      Botan::Path_Validation_Result::status_string(Botan::Certificate_Status_Code::CERT_ISSUER_NOT_FOUND));
1392
   store.add_crl(crl1);
11✔
1393

1394
   std::vector<Botan::CRL_Entry> revoked;
11✔
1395
   revoked.push_back(Botan::CRL_Entry(user1_cert, Botan::CRL_Code::CessationOfOperation));
22✔
1396
   revoked.push_back(Botan::CRL_Entry(user2_cert));
22✔
1397

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

1400
   store.add_crl(crl2);
11✔
1401

1402
   const std::string revoked_str =
11✔
1403
      Botan::Path_Validation_Result::status_string(Botan::Certificate_Status_Code::CERT_IS_REVOKED);
11✔
1404

1405
   result_u1 = Botan::x509_path_validate(user1_cert, restrictions, store);
11✔
1406
   result.test_str_eq("user 1 revoked", result_u1.result_string(), revoked_str);
11✔
1407

1408
   result_u2 = Botan::x509_path_validate(user2_cert, restrictions, store);
11✔
1409
   result.test_str_eq("user 1 revoked", result_u2.result_string(), revoked_str);
11✔
1410

1411
   return result;
22✔
1412
}
44✔
1413

1414
Test::Result test_crl_entry_negative_serial() {
1✔
1415
   Test::Result result("CRL entry serial number preserves sign");
1✔
1416

1417
   const auto build_crl_entry = [](const std::vector<uint8_t>& serial_bytes) {
4✔
1418
      std::vector<uint8_t> der;
3✔
1419
      Botan::DER_Encoder enc(der);
3✔
1420
      enc.start_sequence()
3✔
1421
         .add_object(Botan::ASN1_Type::Integer, Botan::ASN1_Class::Universal, serial_bytes.data(), serial_bytes.size())
3✔
1422
         .encode(Botan::X509_Time(std::chrono::system_clock::now()))
3✔
1423
         .end_cons();
3✔
1424

1425
      Botan::CRL_Entry entry;
3✔
1426
      Botan::BER_Decoder dec(der);
3✔
1427
      entry.decode_from(dec);
3✔
1428
      return entry;
3✔
1429
   };
3✔
1430

1431
   // -129 in two's complement
1432
   auto entry = build_crl_entry({0xFF, 0x7F});
1✔
1433
   result.test_bin_eq("negative serial -129 magnitude", entry.serial_number(), Botan::BigInt(129).serialize());
2✔
1434
   result.test_is_true("negative serial detected", entry.serial().is_negative());
1✔
1435
   result.test_is_true("negative serial value", entry.serial().to_bigint() == -Botan::BigInt::from_u64(129));
2✔
1436

1437
   // The sign survives re-encoding
1438
   {
1✔
1439
      std::vector<uint8_t> der;
1✔
1440
      Botan::DER_Encoder enc(der);
1✔
1441
      entry.encode_into(enc);
1✔
1442
      Botan::CRL_Entry rt;
1✔
1443
      Botan::BER_Decoder dec(der);
1✔
1444
      rt.decode_from(dec);
1✔
1445
      result.test_is_true("re-encoded entry serial is unchanged", rt.serial() == entry.serial());
1✔
1446
   }
2✔
1447

1448
   // -1 in two's complement
1449
   entry = build_crl_entry({0xFF});
2✔
1450
   result.test_bin_eq("negative serial -1 magnitude", entry.serial_number(), Botan::BigInt(1).serialize());
2✔
1451
   result.test_is_true("-1 is negative", entry.serial().is_negative());
1✔
1452

1453
   // 128 (positive, high bit set so DER requires the leading zero)
1454
   entry = build_crl_entry({0x00, 0x80});
2✔
1455
   result.test_bin_eq("positive serial 128", entry.serial_number(), Botan::BigInt(128).serialize());
2✔
1456
   result.test_is_false("128 is not negative", entry.serial().is_negative());
1✔
1457

1458
   return result;
1✔
1459
}
1✔
1460

1461
Test::Result test_usage(const Botan::Private_Key& ca_key,
12✔
1462
                        const std::string& sig_algo,
1463
                        const std::string& hash_fn,
1464
                        Botan::RandomNumberGenerator& rng) {
1465
   using Botan::Key_Constraints;
12✔
1466
   using Botan::Usage_Type;
12✔
1467

1468
   Test::Result result("X509 Usage");
12✔
1469

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

1473
   /* Create the CA object */
1474
   const Botan::X509_CA ca(ca_cert, ca_key, hash_fn, rng);
12✔
1475

1476
   auto user1_key = make_a_private_key(sig_algo, rng);
12✔
1477

1478
   Botan::X509_Cert_Options opts("Test User 1/US/Botan Project/Testing");
12✔
1479
   opts.constraints = Key_Constraints::DigitalSignature;
12✔
1480

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

1483
   const Botan::X509_Certificate user1_cert =
12✔
1484
      ca.sign_request(user1_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
12✔
1485

1486
   // cert only allows digitalSignature, but we check for both digitalSignature and cRLSign
1487
   result.test_is_false(
12✔
1488
      "key usage cRLSign not allowed",
1489
      user1_cert.allowed_usage(Key_Constraints(Key_Constraints::DigitalSignature | Key_Constraints::CrlSign)));
12✔
1490
   result.test_is_false("encryption is not allowed", user1_cert.allowed_usage(Usage_Type::ENCRYPTION));
12✔
1491

1492
   // cert only allows digitalSignature, so checking for only that should be ok
1493
   result.test_is_true("key usage digitalSignature allowed",
12✔
1494
                       user1_cert.allowed_usage(Key_Constraints::DigitalSignature));
12✔
1495

1496
   opts.constraints = Key_Constraints(Key_Constraints::DigitalSignature | Key_Constraints::CrlSign);
12✔
1497

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

1500
   const Botan::X509_Certificate mult_usage_cert =
12✔
1501
      ca.sign_request(mult_usage_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
12✔
1502

1503
   // cert allows multiple usages, so each one of them as well as both together should be allowed
1504
   result.test_is_true("key usage multiple digitalSignature allowed",
12✔
1505
                       mult_usage_cert.allowed_usage(Key_Constraints::DigitalSignature));
12✔
1506
   result.test_is_true("key usage multiple cRLSign allowed", mult_usage_cert.allowed_usage(Key_Constraints::CrlSign));
12✔
1507
   result.test_is_true(
12✔
1508
      "key usage multiple digitalSignature and cRLSign allowed",
1509
      mult_usage_cert.allowed_usage(Key_Constraints(Key_Constraints::DigitalSignature | Key_Constraints::CrlSign)));
12✔
1510
   result.test_is_false("encryption is not allowed", mult_usage_cert.allowed_usage(Usage_Type::ENCRYPTION));
12✔
1511

1512
   opts.constraints = Key_Constraints();
12✔
1513

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

1516
   const Botan::X509_Certificate no_usage_cert =
12✔
1517
      ca.sign_request(no_usage_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
12✔
1518

1519
   // cert allows every usage
1520
   result.test_is_true("key usage digitalSignature allowed",
12✔
1521
                       no_usage_cert.allowed_usage(Key_Constraints::DigitalSignature));
12✔
1522
   result.test_is_true("key usage cRLSign allowed", no_usage_cert.allowed_usage(Key_Constraints::CrlSign));
12✔
1523
   result.test_is_true("key usage encryption allowed", no_usage_cert.allowed_usage(Usage_Type::ENCRYPTION));
12✔
1524

1525
   if(sig_algo == "RSA") {
12✔
1526
      // cert allows data encryption
1527
      opts.constraints = Key_Constraints(Key_Constraints::KeyEncipherment | Key_Constraints::DataEncipherment);
1✔
1528

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

1531
      const Botan::X509_Certificate enc_cert =
1✔
1532
         ca.sign_request(enc_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
1✔
1533

1534
      result.test_is_true("cert allows encryption", enc_cert.allowed_usage(Usage_Type::ENCRYPTION));
1✔
1535
      result.test_is_true("cert does not allow TLS client auth", !enc_cert.allowed_usage(Usage_Type::TLS_CLIENT_AUTH));
1✔
1536
   }
1✔
1537

1538
   return result;
12✔
1539
}
24✔
1540

1541
Test::Result test_self_issued(const Botan::Private_Key& ca_key,
11✔
1542
                              const std::string& sig_algo,
1543
                              const std::string& sig_padding,
1544
                              const std::string& hash_fn,
1545
                              Botan::RandomNumberGenerator& rng) {
1546
   using Botan::Key_Constraints;
11✔
1547

1548
   Test::Result result("X509 Self Issued");
11✔
1549

1550
   // create the self-signed cert
1551
   const Botan::X509_Certificate ca_cert =
11✔
1552
      Botan::X509::create_self_signed_cert(ca_opts(sig_padding), ca_key, hash_fn, rng);
11✔
1553

1554
   /* Create the CA object */
1555
   const Botan::X509_CA ca(ca_cert, ca_key, hash_fn, sig_padding, rng);
11✔
1556

1557
   auto user_key = make_a_private_key(sig_algo, rng);
11✔
1558

1559
   // create a self-issued certificate, that is, a certificate with subject dn == issuer dn,
1560
   // but signed by a CA, not signed by it's own private key
1561
   Botan::X509_Cert_Options opts = ca_opts();
11✔
1562
   opts.constraints = Key_Constraints::DigitalSignature;
11✔
1563
   opts.set_padding_scheme(sig_padding);
11✔
1564

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

1567
   const Botan::X509_Certificate self_issued_cert =
11✔
1568
      ca.sign_request(self_issued_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
11✔
1569

1570
   // check that this chain can can be verified successfully
1571
   const Botan::Certificate_Store_In_Memory trusted(ca.ca_certificate());
11✔
1572

1573
   const Botan::Path_Validation_Restrictions restrictions(false, 80);
11✔
1574

1575
   const Botan::Path_Validation_Result validation_result =
11✔
1576
      Botan::x509_path_validate(self_issued_cert, restrictions, trusted);
11✔
1577

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

1580
   return result;
11✔
1581
}
22✔
1582

1583
Test::Result test_x509_uninit() {
1✔
1584
   Test::Result result("X509 object uninitialized access");
1✔
1585

1586
   Botan::X509_Certificate cert;
1✔
1587
   result.test_throws("uninitialized cert access causes exception", "X509_Certificate uninitialized", [&cert]() {
1✔
1588
      cert.x509_version();
1✔
1589
   });
1590

1591
   Botan::X509_CRL crl;
1✔
1592
   result.test_throws(
1✔
1593
      "uninitialized crl access causes exception", "X509_CRL uninitialized", [&crl]() { crl.crl_number_bigint(); });
2✔
1594

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

1601
   result.test_throws("synth crl signature() throws", "X509_Object uninitialized", [&]() { synth_crl.signature(); });
2✔
1602
   result.test_throws(
1✔
1603
      "synth crl signed_body() throws", "X509_Object uninitialized", [&]() { synth_crl.signed_body(); });
2✔
1604
   result.test_throws("synth crl signature_algorithm() throws", "X509_Object uninitialized", [&]() {
1✔
1605
      synth_crl.signature_algorithm();
1✔
1606
   });
1607
   result.test_throws("synth crl tbs_data() throws", "X509_Object uninitialized", [&]() { synth_crl.tbs_data(); });
2✔
1608
   result.test_throws("synth crl BER_encode() throws", "X509_Object uninitialized", [&]() { synth_crl.BER_encode(); });
2✔
1609

1610
   return result;
1✔
1611
}
1✔
1612

1613
Test::Result test_valid_constraints(const Botan::Private_Key& key, const std::string& pk_algo) {
19✔
1614
   using Botan::Key_Constraints;
19✔
1615

1616
   Test::Result result("X509 Valid Constraints " + pk_algo);
19✔
1617

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

1620
   // Now check some typical usage scenarios for the given key type
1621
   // Taken from RFC 5280, sec. 4.2.1.3
1622
   // ALL constraints are not typical at all, but we use them for a negative test
1623
   const auto all = Key_Constraints(
19✔
1624
      Key_Constraints::DigitalSignature | Key_Constraints::NonRepudiation | Key_Constraints::KeyEncipherment |
1625
      Key_Constraints::DataEncipherment | Key_Constraints::KeyAgreement | Key_Constraints::KeyCertSign |
1626
      Key_Constraints::CrlSign | Key_Constraints::EncipherOnly | Key_Constraints::DecipherOnly);
19✔
1627

1628
   const auto ca = Key_Constraints(Key_Constraints::KeyCertSign);
19✔
1629
   const auto sign_data = Key_Constraints(Key_Constraints::DigitalSignature);
19✔
1630
   const auto non_repudiation = Key_Constraints(Key_Constraints::NonRepudiation | Key_Constraints::DigitalSignature);
19✔
1631
   const auto key_encipherment = Key_Constraints(Key_Constraints::KeyEncipherment);
19✔
1632
   const auto data_encipherment = Key_Constraints(Key_Constraints::DataEncipherment);
19✔
1633
   const auto key_agreement = Key_Constraints(Key_Constraints::KeyAgreement);
19✔
1634
   const auto key_agreement_encipher_only =
19✔
1635
      Key_Constraints(Key_Constraints::KeyAgreement | Key_Constraints::EncipherOnly);
19✔
1636
   const auto key_agreement_decipher_only =
19✔
1637
      Key_Constraints(Key_Constraints::KeyAgreement | Key_Constraints::DecipherOnly);
19✔
1638
   const auto crl_sign = Key_Constraints(Key_Constraints::CrlSign);
19✔
1639
   const auto sign_everything =
19✔
1640
      Key_Constraints(Key_Constraints::DigitalSignature | Key_Constraints::KeyCertSign | Key_Constraints::CrlSign);
19✔
1641

1642
   if(pk_algo == "DH" || pk_algo == "ECDH") {
19✔
1643
      // DH and ECDH only for key agreement
1644
      result.test_is_false("all constraints not permitted", all.compatible_with(key));
2✔
1645
      result.test_is_false("cert sign not permitted", ca.compatible_with(key));
2✔
1646
      result.test_is_false("signature not permitted", sign_data.compatible_with(key));
2✔
1647
      result.test_is_false("non repudiation not permitted", non_repudiation.compatible_with(key));
2✔
1648
      result.test_is_false("key encipherment not permitted", key_encipherment.compatible_with(key));
2✔
1649
      result.test_is_false("data encipherment not permitted", data_encipherment.compatible_with(key));
2✔
1650
      result.test_is_true("usage acceptable", key_agreement.compatible_with(key));
2✔
1651
      result.test_is_true("usage acceptable", key_agreement_encipher_only.compatible_with(key));
2✔
1652
      result.test_is_true("usage acceptable", key_agreement_decipher_only.compatible_with(key));
2✔
1653
      result.test_is_false("crl sign not permitted", crl_sign.compatible_with(key));
2✔
1654
      result.test_is_false("sign", sign_everything.compatible_with(key));
2✔
1655
   } else if(pk_algo == "Kyber" || pk_algo == "FrodoKEM" || pk_algo == "ML-KEM" || pk_algo == "ClassicMcEliece") {
17✔
1656
      // KEMs can encrypt and agree
1657
      result.test_is_false("all constraints not permitted", all.compatible_with(key));
4✔
1658
      result.test_is_false("cert sign not permitted", ca.compatible_with(key));
4✔
1659
      result.test_is_false("signature not permitted", sign_data.compatible_with(key));
4✔
1660
      result.test_is_false("non repudiation not permitted", non_repudiation.compatible_with(key));
4✔
1661
      result.test_is_false("crl sign not permitted", crl_sign.compatible_with(key));
4✔
1662
      result.test_is_false("sign", sign_everything.compatible_with(key));
4✔
1663
      result.test_is_false("key agreement not permitted", key_agreement.compatible_with(key));
4✔
1664
      result.test_is_false("usage acceptable", data_encipherment.compatible_with(key));
4✔
1665
      result.test_is_true("usage acceptable", key_encipherment.compatible_with(key));
4✔
1666
   } else if(pk_algo == "RSA") {
13✔
1667
      // RSA can do everything except key agreement
1668
      result.test_is_false("all constraints not permitted", all.compatible_with(key));
1✔
1669

1670
      result.test_is_true("usage acceptable", ca.compatible_with(key));
1✔
1671
      result.test_is_true("usage acceptable", sign_data.compatible_with(key));
1✔
1672
      result.test_is_true("usage acceptable", non_repudiation.compatible_with(key));
1✔
1673
      result.test_is_true("usage acceptable", key_encipherment.compatible_with(key));
1✔
1674
      result.test_is_true("usage acceptable", data_encipherment.compatible_with(key));
1✔
1675
      result.test_is_false("key agreement not permitted", key_agreement.compatible_with(key));
1✔
1676
      result.test_is_false("key agreement", key_agreement_encipher_only.compatible_with(key));
1✔
1677
      result.test_is_false("key agreement", key_agreement_decipher_only.compatible_with(key));
1✔
1678
      result.test_is_true("usage acceptable", crl_sign.compatible_with(key));
1✔
1679
      result.test_is_true("usage acceptable", sign_everything.compatible_with(key));
1✔
1680
   } else if(pk_algo == "ElGamal") {
12✔
1681
      // only ElGamal encryption is currently implemented
1682
      result.test_is_false("all constraints not permitted", all.compatible_with(key));
1✔
1683
      result.test_is_false("cert sign not permitted", ca.compatible_with(key));
1✔
1684
      result.test_is_true("data encipherment permitted", data_encipherment.compatible_with(key));
1✔
1685
      result.test_is_true("key encipherment permitted", key_encipherment.compatible_with(key));
1✔
1686
      result.test_is_false("key agreement not permitted", key_agreement.compatible_with(key));
1✔
1687
      result.test_is_false("key agreement", key_agreement_encipher_only.compatible_with(key));
1✔
1688
      result.test_is_false("key agreement", key_agreement_decipher_only.compatible_with(key));
1✔
1689
      result.test_is_false("crl sign not permitted", crl_sign.compatible_with(key));
1✔
1690
      result.test_is_false("sign", sign_everything.compatible_with(key));
1✔
1691
   } else if(pk_algo == "DSA" || pk_algo == "ECDSA" || pk_algo == "ECGDSA" || pk_algo == "ECKCDSA" ||
10✔
1692
             pk_algo == "GOST-34.10" || pk_algo == "Dilithium" || pk_algo == "ML-DSA" || pk_algo == "SLH-DSA" ||
18✔
1693
             pk_algo == "HSS-LMS") {
3✔
1694
      // these are signature algorithms only
1695
      result.test_is_false("all constraints not permitted", all.compatible_with(key));
9✔
1696

1697
      result.test_is_true("ca allowed", ca.compatible_with(key));
9✔
1698
      result.test_is_true("sign allowed", sign_data.compatible_with(key));
9✔
1699
      result.test_is_true("non-repudiation allowed", non_repudiation.compatible_with(key));
9✔
1700
      result.test_is_false("key encipherment not permitted", key_encipherment.compatible_with(key));
9✔
1701
      result.test_is_false("data encipherment not permitted", data_encipherment.compatible_with(key));
9✔
1702
      result.test_is_false("key agreement not permitted", key_agreement.compatible_with(key));
9✔
1703
      result.test_is_false("key agreement", key_agreement_encipher_only.compatible_with(key));
9✔
1704
      result.test_is_false("key agreement", key_agreement_decipher_only.compatible_with(key));
9✔
1705
      result.test_is_true("crl sign allowed", crl_sign.compatible_with(key));
9✔
1706
      result.test_is_true("sign allowed", sign_everything.compatible_with(key));
9✔
1707
   }
1708

1709
   return result;
19✔
1710
}
×
1711

1712
/**
1713
 * @brief X.509v3 extension that encodes a given string
1714
 */
1715
class String_Extension final : public Botan::Certificate_Extension {
22✔
1716
   public:
1717
      String_Extension() = default;
22✔
1718

1719
      explicit String_Extension(const std::string& val) : m_contents(val) {}
11✔
1720

1721
      std::string value() const { return m_contents; }
44✔
1722

1723
      std::unique_ptr<Certificate_Extension> copy() const override {
×
1724
         return std::make_unique<String_Extension>(m_contents);
×
1725
      }
1726

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

1729
      bool should_encode() const override { return true; }
22✔
1730

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

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

1735
      std::vector<uint8_t> encode_inner() const override {
11✔
1736
         std::vector<uint8_t> bits;
11✔
1737
         Botan::DER_Encoder(bits).encode(Botan::ASN1_String(m_contents, Botan::ASN1_Type::Utf8String));
22✔
1738
         return bits;
11✔
1739
      }
×
1740

1741
      void decode_inner(const std::vector<uint8_t>& in) override {
22✔
1742
         Botan::ASN1_String str;
22✔
1743
         Botan::BER_Decoder(in).decode(str, Botan::ASN1_Type::Utf8String).verify_end();
22✔
1744
         m_contents = str.value();
44✔
1745
      }
22✔
1746

1747
   private:
1748
      std::string m_contents;
1749
};
1750

1751
Test::Result test_x509_wrong_context_certificate_extensions() {
1✔
1752
   Test::Result result("X509 wrong-context certificate extensions");
1✔
1753

1754
   #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
1755
   const std::string base = "x509/wrong_context_ext/";
1✔
1756

1757
   const auto test_rejected = [&](const std::string& filename, std::string_view what) {
4✔
1758
      result.test_throws<Botan::Decoding_Error>(
3✔
1759
         std::string(what), [&]() { const Botan::X509_Certificate cert(Test::data_file(base + filename)); });
12✔
1760
   };
3✔
1761

1762
   test_rejected("cert_with_crl_number.pem", "CRL number rejected in certificate");
1✔
1763
   test_rejected("cert_with_reason_code.pem", "CRL reason rejected in certificate");
1✔
1764
   test_rejected("cert_with_idp.pem", "CRL issuing distribution point rejected in certificate");
1✔
1765
   #endif
1766

1767
   return result;
1✔
1768
}
1✔
1769

1770
Test::Result test_x509_wrong_context_crl_extensions() {
1✔
1771
   Test::Result result("X509 wrong-context CRL extensions");
1✔
1772

1773
   #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
1774
   const std::string base_dir = "x509/wrong_context_ext";
1✔
1775

1776
   auto load_crl_from_pem = [&](std::string_view filename) -> Botan::X509_CRL {
5✔
1777
      return Botan::X509_CRL(Test::data_file(base_dir, filename));
6✔
1778
   };
1✔
1779

1780
   result.test_throws<Botan::Decoding_Error>("SubjectAltName rejected in CRL",
1✔
1781
                                             [&]() { load_crl_from_pem("crl_with_san.pem"); });
2✔
1782
   result.test_throws<Botan::Decoding_Error>("SubjectAltName rejected in CRL entry",
1✔
1783
                                             [&]() { load_crl_from_pem("crl_entry_with_san.pem"); });
2✔
1784

1785
   const Botan::X509_Certificate ca_cert(Test::data_file(base_dir + "/ca.pem"));
2✔
1786
   const Botan::X509_Certificate user_cert(Test::data_file(base_dir + "/user.pem"));
2✔
1787
   const std::vector<Botan::X509_Certificate> cert_path = {user_cert, ca_cert};
3✔
1788
   const auto validation_time = Botan::calendar_point(2027, 1, 1, 0, 0, 0).to_std_timepoint();
1✔
1789

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

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

1797
      const bool contains_expected_code =
2✔
1798
         !crl_status.empty() &&
2✔
1799
         crl_status[0].contains(Botan::Certificate_Status_Code::CRL_HAS_UNKNOWN_CRITICAL_EXTENSION);
4✔
1800
      result.test_is_true(what, contains_expected_code);
2✔
1801
   };
4✔
1802

1803
   check_crl_unusable("crl_with_unknown_critical.pem", "critical unknown CRL extension rejects CRL");
1✔
1804
   check_crl_unusable("crl_entry_with_unknown_critical.pem", "critical unknown CRL entry extension rejects CRL");
1✔
1805
   #endif
1806

1807
   return result;
2✔
1808
}
2✔
1809

1810
Test::Result test_custom_dn_attr(const Botan::Private_Key& ca_key,
11✔
1811
                                 const std::string& sig_algo,
1812
                                 const std::string& sig_padding,
1813
                                 const std::string& hash_fn,
1814
                                 Botan::RandomNumberGenerator& rng) {
1815
   Test::Result result("X509 Custom DN");
11✔
1816

1817
   /* Create the self-signed cert */
1818
   const Botan::X509_Certificate ca_cert =
11✔
1819
      Botan::X509::create_self_signed_cert(ca_opts(sig_padding), ca_key, hash_fn, rng);
11✔
1820

1821
   /* Create the CA object */
1822
   const Botan::X509_CA ca(ca_cert, ca_key, hash_fn, sig_padding, rng);
11✔
1823

1824
   auto user_key = make_a_private_key(sig_algo, rng);
11✔
1825

1826
   Botan::X509_DN subject_dn;
11✔
1827

1828
   const Botan::OID attr1(Botan::OID("1.3.6.1.4.1.25258.9.1.1"));
11✔
1829
   const Botan::OID attr2(Botan::OID("1.3.6.1.4.1.25258.9.1.2"));
11✔
1830
   const Botan::ASN1_String val1("Custom Attr 1", Botan::ASN1_Type::PrintableString);
11✔
1831
   const Botan::ASN1_String val2("12345", Botan::ASN1_Type::Utf8String);
11✔
1832

1833
   subject_dn.add_attribute(attr1, val1);
11✔
1834
   subject_dn.add_attribute(attr2, val2);
11✔
1835

1836
   const Botan::Extensions extensions;
11✔
1837

1838
   const Botan::PKCS10_Request req =
11✔
1839
      Botan::PKCS10_Request::create(*user_key, subject_dn, extensions, hash_fn, rng, sig_padding);
11✔
1840

1841
   const Botan::X509_DN& req_dn = req.subject_dn();
11✔
1842

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

1845
   const Botan::ASN1_String req_val1 = req_dn.get_first_attribute(attr1);
11✔
1846
   const Botan::ASN1_String req_val2 = req_dn.get_first_attribute(attr2);
11✔
1847
   result.test_is_true("Attr1 matches encoded", req_val1 == val1);
11✔
1848
   result.test_is_true("Attr2 matches encoded", req_val2 == val2);
11✔
1849
   result.test_is_true("Attr1 tag matches encoded", req_val1.tagging() == val1.tagging());
11✔
1850
   result.test_is_true("Attr2 tag matches encoded", req_val2.tagging() == val2.tagging());
11✔
1851

1852
   const Botan::X509_Time not_before("100301123001Z", Botan::ASN1_Type::UtcTime);
11✔
1853
   const Botan::X509_Time not_after("300301123001Z", Botan::ASN1_Type::UtcTime);
11✔
1854

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

1857
   const Botan::X509_DN& cert_dn = cert.subject_dn();
11✔
1858

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

1861
   const Botan::ASN1_String cert_val1 = cert_dn.get_first_attribute(attr1);
11✔
1862
   const Botan::ASN1_String cert_val2 = cert_dn.get_first_attribute(attr2);
11✔
1863
   result.test_is_true("Attr1 matches encoded", cert_val1 == val1);
11✔
1864
   result.test_is_true("Attr2 matches encoded", cert_val2 == val2);
11✔
1865
   result.test_is_true("Attr1 tag matches encoded", cert_val1.tagging() == val1.tagging());
11✔
1866
   result.test_is_true("Attr2 tag matches encoded", cert_val2.tagging() == val2.tagging());
11✔
1867

1868
   return result;
22✔
1869
}
88✔
1870

1871
Test::Result test_x509_extensions(const Botan::Private_Key& ca_key,
11✔
1872
                                  const std::string& sig_algo,
1873
                                  const std::string& sig_padding,
1874
                                  const std::string& hash_fn,
1875
                                  Botan::RandomNumberGenerator& rng) {
1876
   using Botan::Key_Constraints;
11✔
1877

1878
   Test::Result result("X509 Extensions");
11✔
1879

1880
   /* Create the self-signed cert */
1881
   const Botan::X509_Certificate ca_cert =
11✔
1882
      Botan::X509::create_self_signed_cert(ca_opts(sig_padding), ca_key, hash_fn, rng);
11✔
1883

1884
   /* Create the CA object */
1885
   const Botan::X509_CA ca(ca_cert, ca_key, hash_fn, sig_padding, rng);
11✔
1886

1887
   /* Prepare CDP extension */
1888
   std::vector<std::string> cdp_urls = {
11✔
1889
      "http://example.com/crl1.pem",
1890
      "ldap://ldap.example.com/cn=crl1,dc=example,dc=com?certificateRevocationList;binary"};
11✔
1891

1892
   std::vector<Botan::Cert_Extension::CRL_Distribution_Points::Distribution_Point> dps;
11✔
1893

1894
   for(const auto& uri : cdp_urls) {
33✔
1895
      Botan::AlternativeName cdp_alt_name;
22✔
1896
      cdp_alt_name.add_uri(uri);
22✔
1897
      const Botan::Cert_Extension::CRL_Distribution_Points::Distribution_Point dp(cdp_alt_name);
22✔
1898

1899
      dps.emplace_back(dp);
22✔
1900
   }
22✔
1901

1902
   auto user_key = make_a_private_key(sig_algo, rng);
11✔
1903

1904
   Botan::X509_Cert_Options opts("Test User 1/US/Botan Project/Testing");
11✔
1905
   opts.constraints = Key_Constraints::DigitalSignature;
11✔
1906

1907
   // include a custom extension in the request
1908
   Botan::Extensions req_extensions;
11✔
1909
   const Botan::OID oid("1.2.3.4.5.6.7.8.9.1");
11✔
1910
   const Botan::OID ku_oid = Botan::OID::from_string("X509v3.KeyUsage");
11✔
1911
   req_extensions.add(std::make_unique<String_Extension>("AAAAAAAAAAAAAABCDEF"), false);
22✔
1912
   req_extensions.add(std::make_unique<Botan::Cert_Extension::CRL_Distribution_Points>(dps));
22✔
1913
   opts.extensions = req_extensions;
11✔
1914
   opts.set_padding_scheme(sig_padding);
11✔
1915

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

1919
   result.test_is_true("Extensions::extension_set true for Key_Usage",
11✔
1920
                       self_signed_cert.v3_extensions().extension_set(ku_oid));
11✔
1921

1922
   // check if known Key_Usage extension is present in self-signed cert
1923
   auto key_usage_ext = self_signed_cert.v3_extensions().get(ku_oid);
11✔
1924
   if(result.test_is_true("Key_Usage extension present in self-signed certificate", key_usage_ext != nullptr)) {
11✔
1925
      result.test_is_true(
22✔
1926
         "Key_Usage extension value matches in self-signed certificate",
1927
         dynamic_cast<Botan::Cert_Extension::Key_Usage&>(*key_usage_ext).get_constraints() == opts.constraints);
11✔
1928
   }
1929

1930
   // check if custom extension is present in self-signed cert
1931
   auto string_ext = self_signed_cert.v3_extensions().get_raw<String_Extension>(oid);
11✔
1932
   if(result.test_is_true("Custom extension present in self-signed certificate", string_ext != nullptr)) {
11✔
1933
      result.test_str_eq(
22✔
1934
         "Custom extension value matches in self-signed certificate", string_ext->value(), "AAAAAAAAAAAAAABCDEF");
33✔
1935
   }
1936

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

1941
   if(result.test_is_true("CRL Distribution Points extension present in self-signed certificate",
44✔
1942
                          !cert_cdps->crl_distribution_urls().empty())) {
11✔
1943
      for(const auto& cdp : cert_cdps->distribution_points()) {
33✔
1944
         const auto& dpn = cdp.distribution_point_name();
22✔
1945
         result.test_is_true(
22✔
1946
            "CDP URI present in self-signed certificate",
1947
            dpn.has_value() && dpn->full_name().has_value() &&
22✔
1948
               std::ranges::find(cdp_urls, dpn->full_name()->get_first_attribute("URI")) != cdp_urls.end());
88✔
1949
      }
1950

1951
      // The accessor returns one entry per URI
1952
      const auto& urls = cert_cdps->crl_distribution_urls();
11✔
1953
      result.test_sz_eq("crl_distribution_urls has one entry per URI (self-signed)", urls.size(), cdp_urls.size());
11✔
1954
      for(const auto& url : urls) {
33✔
1955
         result.test_is_true("crl_distribution_urls entry is a bare URI (self-signed)",
44✔
1956
                             std::ranges::find(cdp_urls, url) != cdp_urls.end());
44✔
1957
      }
1958

1959
      // Ensure X509_Certificate's cached accessor returns the same bare URIs
1960
      const auto& cert_dp = self_signed_cert.crl_distribution_point_uris();
11✔
1961
      result.test_sz_eq(
11✔
1962
         "X509_Certificate::crl_distribution_points size (self-signed)", cert_dp.size(), cdp_urls.size());
1963
      for(const auto& url : cert_dp) {
33✔
1964
         result.test_is_true("X509_Certificate::crl_distribution_points entry is a bare URI (self-signed)",
44✔
1965
                             std::ranges::find(cdp_urls, url.original_input()) != cdp_urls.end());
44✔
1966
      }
1967
   }
11✔
1968

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

1971
   /* Create a CA-signed certificate */
1972
   const Botan::X509_Certificate ca_signed_cert =
11✔
1973
      ca.sign_request(user_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
11✔
1974

1975
   // check if known Key_Usage extension is present in CA-signed cert
1976
   result.test_is_true("Extensions::extension_set true for Key_Usage",
11✔
1977
                       ca_signed_cert.v3_extensions().extension_set(ku_oid));
11✔
1978

1979
   key_usage_ext = ca_signed_cert.v3_extensions().get(ku_oid);
22✔
1980
   if(result.test_is_true("Key_Usage extension present in CA-signed certificate", key_usage_ext != nullptr)) {
11✔
1981
      auto constraints = dynamic_cast<Botan::Cert_Extension::Key_Usage&>(*key_usage_ext).get_constraints();
11✔
1982
      result.test_is_true("Key_Usage extension value matches in user certificate",
22✔
1983
                          constraints == Botan::Key_Constraints::DigitalSignature);
11✔
1984
   }
1985

1986
   // check if custom extension is present in CA-signed cert
1987
   result.test_is_true("Extensions::extension_set true for String_Extension",
11✔
1988
                       ca_signed_cert.v3_extensions().extension_set(oid));
11✔
1989
   string_ext = ca_signed_cert.v3_extensions().get_raw<String_Extension>(oid);
22✔
1990
   if(result.test_is_true("Custom extension present in CA-signed certificate", string_ext != nullptr)) {
11✔
1991
      result.test_str_eq(
22✔
1992
         "Custom extension value matches in CA-signed certificate", string_ext->value(), "AAAAAAAAAAAAAABCDEF");
33✔
1993
   }
1994

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

1998
   if(result.test_is_true("CRL Distribution Points extension present in CA-signed certificate",
44✔
1999
                          !cert_cdps->crl_distribution_urls().empty())) {
11✔
2000
      for(const auto& cdp : cert_cdps->distribution_points()) {
33✔
2001
         const auto& dpn = cdp.distribution_point_name();
22✔
2002
         result.test_is_true(
22✔
2003
            "CDP URI present in CA-signed certificate",
2004
            dpn.has_value() && dpn->full_name().has_value() &&
22✔
2005
               std::ranges::find(cdp_urls, dpn->full_name()->get_first_attribute("URI")) != cdp_urls.end());
88✔
2006
      }
2007

2008
      const auto& urls = cert_cdps->crl_distribution_urls();
11✔
2009
      result.test_sz_eq("crl_distribution_urls has one entry per URI (CA-signed)", urls.size(), cdp_urls.size());
11✔
2010
      for(const auto& url : urls) {
33✔
2011
         result.test_is_true("crl_distribution_urls entry is a bare URI (CA-signed)",
44✔
2012
                             std::ranges::find(cdp_urls, url) != cdp_urls.end());
44✔
2013
      }
2014

2015
      const auto& cert_dp = ca_signed_cert.crl_distribution_point_uris();
11✔
2016
      result.test_sz_eq("X509_Certificate::crl_distribution_points size (CA-signed)", cert_dp.size(), cdp_urls.size());
11✔
2017
      for(const auto& url : cert_dp) {
33✔
2018
         result.test_is_true("X509_Certificate::crl_distribution_points entry is a bare URI (CA-signed)",
44✔
2019
                             std::ranges::find(cdp_urls, url.original_input()) != cdp_urls.end());
44✔
2020
      }
2021
   }
11✔
2022

2023
   return result;
11✔
2024
}
55✔
2025

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

2029
   struct TestData {
12✔
2030
         const std::string issuer, subject, issuer_hash, subject_hash;
2031
   } const cases[]{{"",
12✔
2032
                    "",
2033
                    "E4F60D0AA6D7F3D3B6A6494B1C861B99F649C6F9EC51ABAF201B20F297327C95",
2034
                    "E4F60D0AA6D7F3D3B6A6494B1C861B99F649C6F9EC51ABAF201B20F297327C95"},
2035
                   {"a",
2036
                    "b",
2037
                    "BC2E013472F39AC579964880E422737C82BA812CB8BC2FD17E013060D71E6E19",
2038
                    "5E31CFAA3FAFB1A5BA296A0D2BAB9CA44D7936E9BF0BBC54637D0C53DBC4A432"},
2039
                   {"A",
2040
                    "B",
2041
                    "4B3206201C4BC9B6CD6C36532A97687DF9238155D99ADB60C66BF2B2220643D8",
2042
                    "FFF635A52A16618B4A0E9CD26B5E5A2FA573D343C051E6DE8B0811B1ACC89B86"},
2043
                   {
2044
                      "Test Issuer/US/Botan Project/Testing",
2045
                      "Test Subject/US/Botan Project/Testing",
2046
                      "ACB4F373004A56A983A23EB8F60FA4706312B5DB90FD978574FE7ACC84E093A5",
2047
                      "87039231C2205B74B6F1F3830A66272C0B41F71894B03AC3150221766D95267B",
2048
                   },
2049
                   {
2050
                      "Test Subject/US/Botan Project/Testing",
2051
                      "Test Issuer/US/Botan Project/Testing",
2052
                      "87039231C2205B74B6F1F3830A66272C0B41F71894B03AC3150221766D95267B",
2053
                      "ACB4F373004A56A983A23EB8F60FA4706312B5DB90FD978574FE7ACC84E093A5",
2054
                   }};
72✔
2055

2056
   for(const auto& a : cases) {
72✔
2057
      Botan::X509_Cert_Options opts{a.issuer};
60✔
2058
      opts.CA_key();
60✔
2059

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

2062
      result.test_str_eq(a.issuer, Botan::hex_encode(issuer_cert.raw_issuer_dn_sha256()), a.issuer_hash);
60✔
2063
      result.test_str_eq(a.issuer, Botan::hex_encode(issuer_cert.raw_subject_dn_sha256()), a.issuer_hash);
60✔
2064

2065
      const Botan::X509_CA ca(issuer_cert, key, hash_fn, rng);
60✔
2066
      const Botan::PKCS10_Request req =
60✔
2067
         Botan::X509::create_cert_req(Botan::X509_Cert_Options(a.subject), key, hash_fn, rng);
60✔
2068
      const Botan::X509_Certificate subject_cert =
60✔
2069
         ca.sign_request(req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
60✔
2070

2071
      result.test_str_eq(a.subject, Botan::hex_encode(subject_cert.raw_issuer_dn_sha256()), a.issuer_hash);
60✔
2072
      result.test_str_eq(a.subject, Botan::hex_encode(subject_cert.raw_subject_dn_sha256()), a.subject_hash);
60✔
2073
   }
60✔
2074
   return result;
12✔
2075
}
72✔
2076

2077
   #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
2078

2079
Test::Result test_x509_tn_auth_list_extension_decode() {
1✔
2080
   /* cert with TNAuthList extension data was generated by asn1parse cfg:
2081

2082
      asn1=SEQUENCE:tn_auth_list
2083

2084
      [tn_auth_list]
2085
      spc=EXP:0,IA5:1001
2086
      range=EXP:1,SEQUENCE:TelephoneNumberRange
2087
      one=EXP:2,IA5:333
2088

2089
      [TelephoneNumberRange]
2090
      start1=IA5:111
2091
      count1=INT:128
2092
      start2=IA5:222
2093
      count2=INT:256
2094
    */
2095
   const std::string filename("TNAuthList.pem");
1✔
2096
   Test::Result result("X509 TNAuthList decode");
1✔
2097
   result.start_timer();
1✔
2098

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

2101
   using Botan::Cert_Extension::TNAuthList;
1✔
2102

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

2105
   const auto& tn_entries = tn_auth_list->entries();
1✔
2106

2107
   result.test_not_null("cert has TNAuthList extension", tn_auth_list);
1✔
2108

2109
   result.test_throws("wrong telephone_number_range() accessor for spc",
1✔
2110
                      [&tn_entries] { tn_entries[0].telephone_number_range(); });
2✔
2111
   result.test_throws("wrong telephone_number() accessor for range",
1✔
2112
                      [&tn_entries] { tn_entries[1].telephone_number(); });
2✔
2113
   result.test_throws("wrong service_provider_code() accessor for one",
1✔
2114
                      [&tn_entries] { tn_entries[2].service_provider_code(); });
2✔
2115

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

2119
   result.test_is_true("range entry type", tn_entries[1].type() == TNAuthList::Entry::TelephoneNumberRange);
1✔
2120
   const auto& range = tn_entries[1].telephone_number_range();
1✔
2121
   result.test_sz_eq("range entries count", range.size(), 2);
1✔
2122
   result.test_str_eq("range entry 0 start data", range[0].start.value(), "111");
1✔
2123
   result.test_sz_eq("range entry 0 count data", range[0].count, 128);
1✔
2124
   result.test_str_eq("range entry 1 start data", range[1].start.value(), "222");
1✔
2125
   result.test_sz_eq("range entry 1 count data", range[1].count, 256);
1✔
2126

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

2130
   result.end_timer();
1✔
2131
   return result;
2✔
2132
}
1✔
2133

2134
   #endif
2135

2136
std::vector<std::string> get_sig_paddings(const std::string& sig_algo, const std::string& hash) {
12✔
2137
   if(sig_algo == "RSA") {
12✔
2138
      return {
1✔
2139
   #if defined(BOTAN_HAS_EMSA_PKCS1)
2140
         "PKCS1v15(" + hash + ")",
1✔
2141
   #endif
2142
   #if defined(BOTAN_HAS_EMSA_PSS)
2143
            "PSS(" + hash + ")",
2144
   #endif
2145
      };
3✔
2146
   } else if(sig_algo == "DSA" || sig_algo == "ECDSA" || sig_algo == "ECGDSA" || sig_algo == "ECKCDSA" ||
11✔
2147
             sig_algo == "GOST-34.10") {
7✔
2148
      return {hash};
10✔
2149
   } else if(sig_algo == "Ed25519" || sig_algo == "Ed448") {
6✔
2150
      return {"Pure"};
2✔
2151
   } else if(sig_algo == "Dilithium" || sig_algo == "ML-DSA") {
4✔
2152
      return {"Randomized"};
2✔
2153
   } else if(sig_algo == "HSS-LMS") {
2✔
2154
      return {""};
1✔
2155
   } else {
2156
      return {};
1✔
2157
   }
2158
}
6✔
2159

2160
class X509_Cert_Unit_Tests final : public Test {
1✔
2161
   public:
2162
      std::vector<Test::Result> run() override {
1✔
2163
         std::vector<Test::Result> results;
1✔
2164

2165
         auto& rng = this->rng();
1✔
2166

2167
         const std::string sig_algos[]{"RSA",
1✔
2168
                                       "DSA",
2169
                                       "ECDSA",
2170
                                       "ECGDSA",
2171
                                       "ECKCDSA",
2172
                                       "GOST-34.10",
2173
                                       "Ed25519",
2174
                                       "Ed448",
2175
                                       "Dilithium",
2176
                                       "ML-DSA",
2177
                                       "SLH-DSA",
2178
                                       "HSS-LMS"};
13✔
2179

2180
         for(const std::string& algo : sig_algos) {
13✔
2181
   #if !defined(BOTAN_HAS_EMSA_PKCS1)
2182
            if(algo == "RSA")
2183
               continue;
2184
   #endif
2185

2186
            std::string hash = "SHA-256";
12✔
2187

2188
            if(algo == "Ed25519") {
12✔
2189
               hash = "SHA-512";
1✔
2190
            }
2191
            if(algo == "Ed448") {
12✔
2192
               hash = "SHAKE-256(912)";
1✔
2193
            }
2194
            if(algo == "Dilithium" || algo == "ML-DSA") {
12✔
2195
               hash = "SHAKE-256(512)";
2✔
2196
            }
2197

2198
            auto key = make_a_private_key(algo, rng);
12✔
2199

2200
            if(key == nullptr) {
12✔
2201
               continue;
×
2202
            }
2203

2204
            results.push_back(test_hashes(*key, hash, rng));
24✔
2205
            results.push_back(test_valid_constraints(*key, algo));
24✔
2206

2207
            Test::Result usage_result("X509 Usage");
12✔
2208
            try {
12✔
2209
               usage_result.merge(test_usage(*key, algo, hash, rng));
12✔
2210
            } catch(std::exception& e) {
×
2211
               usage_result.test_failure("test_usage " + algo, e.what());
×
2212
            }
×
2213
            results.push_back(usage_result);
12✔
2214

2215
            for(const auto& padding_scheme : get_sig_paddings(algo, hash)) {
23✔
2216
               Test::Result cert_result("X509 Unit");
11✔
2217

2218
               try {
11✔
2219
                  cert_result.merge(test_x509_cert(*key, algo, padding_scheme, hash, rng));
11✔
2220
               } catch(std::exception& e) {
×
2221
                  cert_result.test_failure("test_x509_cert " + algo, e.what());
×
2222
               }
×
2223
               results.push_back(cert_result);
11✔
2224

2225
               Test::Result pkcs10_result("PKCS10 extensions");
11✔
2226
               try {
11✔
2227
                  pkcs10_result.merge(test_pkcs10_ext(*key, padding_scheme, hash, rng));
11✔
2228
               } catch(std::exception& e) {
×
2229
                  pkcs10_result.test_failure("test_pkcs10_ext " + algo, e.what());
×
2230
               }
×
2231
               results.push_back(pkcs10_result);
11✔
2232

2233
               Test::Result self_issued_result("X509 Self Issued");
11✔
2234
               try {
11✔
2235
                  self_issued_result.merge(test_self_issued(*key, algo, padding_scheme, hash, rng));
11✔
2236
               } catch(std::exception& e) {
×
2237
                  self_issued_result.test_failure("test_self_issued " + algo, e.what());
×
2238
               }
×
2239
               results.push_back(self_issued_result);
11✔
2240

2241
               Test::Result extensions_result("X509 Extensions");
11✔
2242
               try {
11✔
2243
                  extensions_result.merge(test_x509_extensions(*key, algo, padding_scheme, hash, rng));
11✔
2244
               } catch(std::exception& e) {
×
2245
                  extensions_result.test_failure("test_extensions " + algo, e.what());
×
2246
               }
×
2247
               results.push_back(extensions_result);
11✔
2248

2249
               Test::Result custom_dn_result("X509 Custom DN");
11✔
2250
               try {
11✔
2251
                  custom_dn_result.merge(test_custom_dn_attr(*key, algo, padding_scheme, hash, rng));
11✔
2252
               } catch(std::exception& e) {
×
2253
                  custom_dn_result.test_failure("test_custom_dn_attr " + algo, e.what());
×
2254
               }
×
2255
               results.push_back(custom_dn_result);
11✔
2256
            }
23✔
2257
         }
24✔
2258

2259
         /*
2260
         These are algos which cannot sign but can be included in certs
2261
         */
2262
         const std::vector<std::string> enc_algos = {
1✔
2263
            "DH", "ECDH", "ElGamal", "Kyber", "ML-KEM", "FrodoKEM", "ClassicMcEliece"};
1✔
2264

2265
         for(const std::string& algo : enc_algos) {
8✔
2266
            auto key = make_a_private_key(algo, rng);
7✔
2267

2268
            if(key) {
7✔
2269
               results.push_back(test_valid_constraints(*key, algo));
14✔
2270
            }
2271
         }
7✔
2272

2273
   #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM) && defined(BOTAN_HAS_EMSA_PKCS1) && defined(BOTAN_HAS_EMSA_PSSR) && \
2274
      defined(BOTAN_HAS_RSA)
2275
         Test::Result pad_config_result("X509 Padding Config");
1✔
2276
         try {
1✔
2277
            pad_config_result.merge(test_padding_config());
1✔
2278
         } catch(const std::exception& e) {
×
2279
            pad_config_result.test_failure("test_padding_config", e.what());
×
2280
         }
×
2281
         results.push_back(pad_config_result);
1✔
2282
   #endif
2283

2284
   #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
2285
         results.push_back(test_x509_utf8());
2✔
2286
         results.push_back(test_x509_subject_key_id_derivation());
2✔
2287
         results.push_back(test_x509_serial_decoding());
2✔
2288
      #if defined(BOTAN_HAS_ECDSA) && defined(BOTAN_HAS_SHA2_32)
2289
         results.push_back(test_x509_serial_revocation_matching());
2✔
2290
      #endif
2291
         results.push_back(test_x509_any_key_extended_usage());
2✔
2292
         results.push_back(test_x509_bmpstring());
2✔
2293
         results.push_back(test_x509_teletex());
2✔
2294
         results.push_back(test_crl_dn_name());
2✔
2295
         results.push_back(test_rdn_multielement_set_name());
2✔
2296
         results.push_back(test_x509_decode_list());
2✔
2297
         results.push_back(test_rsa_oaep());
2✔
2298
         results.push_back(test_x509_authority_info_access_extension());
2✔
2299
         results.push_back(test_x509_ldap_empty_authority_uris());
2✔
2300
         results.push_back(test_crl_issuing_distribution_point_extension());
2✔
2301
         results.push_back(test_verify_gost2012_cert());
2✔
2302
         results.push_back(test_parse_rsa_pss_cert());
2✔
2303
         results.push_back(test_x509_tn_auth_list_extension_decode());
2✔
2304
   #endif
2305

2306
         results.push_back(test_x509_encode_authority_info_access_extension());
2✔
2307
         results.push_back(test_x509_serial_number_type());
2✔
2308
         results.push_back(test_x509_extension());
2✔
2309
         results.push_back(test_x509_extension_decode_duplicate());
2✔
2310
         results.push_back(test_x509_wrong_context_certificate_extensions());
2✔
2311
         results.push_back(test_x509_wrong_context_crl_extensions());
2✔
2312
         results.push_back(test_x509_dates());
2✔
2313
         results.push_back(test_cert_status_strings());
2✔
2314
         results.push_back(test_x509_uninit());
2✔
2315
         results.push_back(test_crl_entry_negative_serial());
2✔
2316

2317
         return results;
1✔
2318
      }
13✔
2319
};
2320

2321
BOTAN_REGISTER_TEST("x509", "x509_unit", X509_Cert_Unit_Tests);
2322

2323
#endif
2324

2325
}  // namespace
2326

2327
}  // namespace Botan_Tests
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc