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

randombit / botan / 24922258954

24 Apr 2026 12:23PM UTC coverage: 89.387% (-0.01%) from 89.401%
24922258954

push

github

web-flow
Merge pull request #5546 from randombit/jack/crldp-fixes

Fix some bugs relating to CRL distribution point handling

106817 of 119500 relevant lines covered (89.39%)

11464956.38 hits per line

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

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

8
#include "tests.h"
9

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

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

30
namespace Botan_Tests {
31

32
namespace {
33

34
#if defined(BOTAN_HAS_X509_CERTIFICATES)
35

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

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

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

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

52
   opts.CA_key(1);
112✔
53

54
   return opts;
112✔
55
}
×
56

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

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

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

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

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

76
   return opts;
37✔
77
}
×
78

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

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

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

89
   return opts;
11✔
90
}
×
91

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

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

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

104
   return opts;
55✔
105
}
×
106

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

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

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

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

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

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

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

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

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

207
   return result;
1✔
208
}
1✔
209

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

213
   Botan::Extensions extn;
1✔
214

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

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

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

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

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

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

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

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

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

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

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

248
   return result;
2✔
249
}
2✔
250

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

423
   return result;
1✔
424
}
80✔
425

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

520
   return result;
1✔
521
}
4✔
522

523
   #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
524

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

528
      // See GH #1252
529

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

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

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

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

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

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

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

548
   return result;
2✔
549
}
3✔
550

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

554
   // GH #2611
555

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

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

561
   return result;
1✔
562
}
1✔
563

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

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

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

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

577
   return result;
2✔
578
}
1✔
579

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

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

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

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

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

594
   return result;
1✔
595
}
1✔
596

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

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

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

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

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

625
   return result;
1✔
626
}
×
627

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

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

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

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

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

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

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

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

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

669
   return result;
1✔
670
}
×
671

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

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

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

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

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

688
   return result;
1✔
689
}
×
690

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

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

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

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

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

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

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

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

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

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

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

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

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

743
   return result;
1✔
744
}
1✔
745

746
Test::Result test_crl_issuing_distribution_point_extension() {
1✔
747
   Test::Result result("X509 CRL IssuingDistributionPoint extension");
1✔
748

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

752
   result.test_str_eq(
1✔
753
      "CRL IDP URI decoded correctly", crl.crl_issuing_distribution_point(), "http://localhost/subca/crldp/crl.crl");
1✔
754

755
   return result;
1✔
756
}
1✔
757

758
Test::Result test_parse_rsa_pss_cert() {
1✔
759
   Test::Result result("X509 RSA-PSS certificate");
1✔
760

761
   // See https://github.com/randombit/botan/issues/3019 for background
762

763
   try {
1✔
764
      const Botan::X509_Certificate rsa_pss(Test::data_file("x509/misc/rsa_pss.pem"));
2✔
765
      result.test_success("Was able to parse RSA-PSS certificate signed with ECDSA");
1✔
766
   } catch(Botan::Exception& e) {
1✔
767
      result.test_failure("Parsing failed", e.what());
×
768
   }
×
769

770
   return result;
1✔
771
}
×
772

773
Test::Result test_verify_gost2012_cert() {
1✔
774
   Test::Result result("X509 GOST-2012 certificates");
1✔
775

776
      #if defined(BOTAN_HAS_GOST_34_10_2012) && defined(BOTAN_HAS_STREEBOG)
777
   try {
1✔
778
      if(Botan::EC_Group::supports_named_group("gost_256A")) {
1✔
779
         const Botan::X509_Certificate root_cert(Test::data_file("x509/gost/gost_root.pem"));
2✔
780
         const Botan::X509_Certificate root_int(Test::data_file("x509/gost/gost_int.pem"));
2✔
781

782
         Botan::Certificate_Store_In_Memory trusted;
1✔
783
         trusted.add_certificate(root_cert);
1✔
784

785
         const Botan::Path_Validation_Restrictions restrictions(false, 128, false, {"Streebog-256"});
2✔
786
         const Botan::Path_Validation_Result validation_result =
1✔
787
            Botan::x509_path_validate(root_int, restrictions, trusted);
1✔
788

789
         result.test_is_true("GOST certificate validates", validation_result.successful_validation());
1✔
790
      }
1✔
791
   } catch(const Botan::Decoding_Error& e) {
×
792
      result.test_failure(e.what());
×
793
   }
×
794
      #endif
795

796
   return result;
1✔
797
}
×
798

799
   /*
800
 * @brief checks the configurability of the RSA-PSS signature scheme
801
 *
802
 * For the other algorithms than RSA, only one padding is supported right now.
803
 */
804
      #if defined(BOTAN_HAS_EMSA_PKCS1) && defined(BOTAN_HAS_EMSA_PSSR) && defined(BOTAN_HAS_RSA)
805
Test::Result test_padding_config() {
1✔
806
   Test::Result test_result("X509 Padding Config");
1✔
807

808
   auto rng = Test::new_rng(__func__);
1✔
809

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

813
   // Create X509 CA certificate; PKCS1v15 is used for signing by default
814
   Botan::X509_Cert_Options opt("TEST CA");
1✔
815
   opt.CA_key();
1✔
816

817
   const Botan::X509_Certificate ca_cert_def = Botan::X509::create_self_signed_cert(opt, (*sk), "SHA-512", *rng);
1✔
818
   test_result.test_str_eq("CA certificate signature algorithm (default)",
1✔
819
                           ca_cert_def.signature_algorithm().oid().to_formatted_string(),
1✔
820
                           "RSA/PKCS1v15(SHA-512)");
821

822
   // Create X509 CA certificate; RSA-PSS is explicitly set
823
   opt.set_padding_scheme("PSSR");
1✔
824
   const Botan::X509_Certificate ca_cert_exp = Botan::X509::create_self_signed_cert(opt, (*sk), "SHA-512", *rng);
1✔
825
   test_result.test_str_eq("CA certificate signature algorithm (explicit)",
1✔
826
                           ca_cert_exp.signature_algorithm().oid().to_formatted_string(),
1✔
827
                           "RSA/PSS");
828

829
         #if defined(BOTAN_HAS_EMSA_X931)
830
   // Try to set a padding scheme that is not supported for signing with the given key type
831
   opt.set_padding_scheme("X9.31");
1✔
832
   try {
1✔
833
      const Botan::X509_Certificate ca_cert_wrong = Botan::X509::create_self_signed_cert(opt, (*sk), "SHA-512", *rng);
1✔
834
      test_result.test_failure("Could build CA cert with invalid encoding scheme X9.31 for key type " +
×
835
                               sk->algo_name());
×
836
   } catch(const Botan::Invalid_Argument& e) {
1✔
837
      test_result.test_str_eq("Build CA certificate with invalid encoding scheme X9.31 for key type " + sk->algo_name(),
3✔
838
                              e.what(),
1✔
839
                              "Signatures using RSA/X9.31(SHA-512) are not supported");
840
   }
1✔
841
         #endif
842

843
   test_result.test_str_eq("CA certificate signature algorithm (explicit)",
1✔
844
                           ca_cert_exp.signature_algorithm().oid().to_formatted_string(),
1✔
845
                           "RSA/PSS");
846

847
   const Botan::X509_Time not_before = from_date(-1, 1, 1);
1✔
848
   const Botan::X509_Time not_after = from_date(2, 1, 2);
1✔
849

850
   // Prepare a signing request for the end certificate
851
   Botan::X509_Cert_Options req_opt("endpoint");
1✔
852
   req_opt.set_padding_scheme("PSS(SHA-512,MGF1,64)");
1✔
853
   const Botan::PKCS10_Request end_req = Botan::X509::create_cert_req(req_opt, (*sk), "SHA-512", *rng);
1✔
854
   test_result.test_str_eq(
1✔
855
      "Certificate request signature algorithm", end_req.signature_algorithm().oid().to_formatted_string(), "RSA/PSS");
1✔
856

857
   // Create X509 CA object: will fail as the chosen hash functions differ
858
   try {
1✔
859
      const Botan::X509_CA ca_fail(ca_cert_exp, (*sk), "SHA-512", "PSS(SHA-256)", *rng);
1✔
860
      test_result.test_failure("Configured conflicting hash functions for CA");
×
861
   } catch(const Botan::Invalid_Argument& e) {
1✔
862
      test_result.test_str_eq(
1✔
863
         "Configured conflicting hash functions for CA",
864
         e.what(),
1✔
865
         "Specified hash function SHA-512 is incompatible with RSA chose hash function SHA-256 with user specified padding PSS(SHA-256)");
866
   }
1✔
867

868
   // Create X509 CA object: its signer will use the padding scheme from the CA certificate, i.e. PKCS1v15
869
   const Botan::X509_CA ca_def(ca_cert_def, (*sk), "SHA-512", *rng);
1✔
870
   const Botan::X509_Certificate end_cert_pkcs1 = ca_def.sign_request(end_req, *rng, not_before, not_after);
1✔
871
   test_result.test_str_eq("End certificate signature algorithm",
1✔
872
                           end_cert_pkcs1.signature_algorithm().oid().to_formatted_string(),
1✔
873
                           "RSA/PKCS1v15(SHA-512)");
874

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

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

888
   // Check CRL signature algorithm
889
   const Botan::X509_CRL crl = ca_exp.new_crl(*rng);
1✔
890
   test_result.test_str_eq("CRL signature algorithm", crl.signature_algorithm().oid().to_formatted_string(), "RSA/PSS");
1✔
891

892
   // sanity check for verification, the heavy lifting is done in the other unit tests
893
   const Botan::Certificate_Store_In_Memory trusted(ca_exp.ca_certificate());
1✔
894
   const Botan::Path_Validation_Restrictions restrictions(false, 80);
2✔
895
   const Botan::Path_Validation_Result validation_result =
1✔
896
      Botan::x509_path_validate(end_cert_pss, restrictions, trusted);
1✔
897
   test_result.test_is_true("PSS signed certificate validates", validation_result.successful_validation());
1✔
898

899
   return test_result;
2✔
900
}
3✔
901
      #endif
902

903
   #endif
904

905
Test::Result test_pkcs10_ext(const Botan::Private_Key& key,
11✔
906
                             const std::string& sig_padding,
907
                             const std::string& hash_fn,
908
                             Botan::RandomNumberGenerator& rng) {
909
   Test::Result result("PKCS10 extensions");
11✔
910

911
   Botan::X509_Cert_Options opts;
11✔
912

913
   opts.dns = "main.example.org";
11✔
914
   opts.more_dns.push_back("more1.example.org");
22✔
915
   opts.more_dns.push_back("more2.example.org");
22✔
916

917
   opts.padding_scheme = sig_padding;
11✔
918

919
   Botan::AlternativeName alt_name;
11✔
920
   alt_name.add_attribute("DNS", "bonus.example.org");
11✔
921

922
   Botan::X509_DN alt_dn;
11✔
923
   alt_dn.add_attribute("X520.CommonName", "alt_cn");
11✔
924
   alt_dn.add_attribute("X520.Organization", "testing");
11✔
925
   alt_name.add_dn(alt_dn);
11✔
926

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

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

931
   const auto alt_dns_names = req.subject_alt_name().get_attribute("DNS");
11✔
932

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

935
   if(alt_dns_names.size() == 4) {
11✔
936
      result.test_str_eq("Expected DNS name 1", alt_dns_names.at(0), "bonus.example.org");
11✔
937
      result.test_str_eq("Expected DNS name 2", alt_dns_names.at(1), "main.example.org");
11✔
938
      result.test_str_eq("Expected DNS name 3", alt_dns_names.at(2), "more1.example.org");
11✔
939
      result.test_str_eq("Expected DNS name 3", alt_dns_names.at(3), "more2.example.org");
11✔
940
   }
941

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

945
   return result;
11✔
946
}
22✔
947

948
Test::Result test_x509_cert(const Botan::Private_Key& ca_key,
11✔
949
                            const std::string& sig_algo,
950
                            const std::string& sig_padding,
951
                            const std::string& hash_fn,
952
                            Botan::RandomNumberGenerator& rng) {
953
   Test::Result result("X509 Unit");
11✔
954

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

958
   {
11✔
959
      result.test_is_true("ca key usage cert", ca_cert.constraints().includes(Botan::Key_Constraints::KeyCertSign));
11✔
960
      result.test_is_true("ca key usage crl", ca_cert.constraints().includes(Botan::Key_Constraints::CrlSign));
11✔
961
   }
962

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

966
   const Botan::PKCS10_Request user1_req =
11✔
967
      Botan::X509::create_cert_req(req_opts1(sig_algo, sig_padding), *user1_key, hash_fn, rng);
11✔
968

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

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

974
   const Botan::PKCS10_Request user2_req =
11✔
975
      Botan::X509::create_cert_req(req_opts2(sig_padding), *user2_key, hash_fn, rng);
11✔
976

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

980
   const Botan::PKCS10_Request user3_req =
11✔
981
      Botan::X509::create_cert_req(req_opts3(sig_padding), *user3_key, hash_fn, rng);
11✔
982

983
   /* Create the CA object */
984
   const Botan::X509_CA ca(ca_cert, ca_key, hash_fn, sig_padding, rng);
11✔
985

986
   const BigInt user1_serial(99);
11✔
987

988
   /* Sign the requests to create the certs */
989
   const Botan::X509_Certificate user1_cert =
11✔
990
      ca.sign_request(user1_req, rng, user1_serial, from_date(-1, 01, 01), from_date(2, 01, 01));
11✔
991

992
   result.test_sz_eq("User1 serial size matches expected", user1_cert.serial_number().size(), 1);
11✔
993
   result.test_sz_eq("User1 serial matches expected", user1_cert.serial_number().at(0), size_t(99));
11✔
994

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

999
   const Botan::X509_Certificate user3_cert =
11✔
1000
      ca.sign_request(user3_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
11✔
1001

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

1006
   {
11✔
1007
      auto constraints = req_opts1(sig_algo).constraints;
11✔
1008
      result.test_is_true("user1 key usage", user1_cert.constraints().includes(constraints));
11✔
1009
   }
1010

1011
   /* Copy, assign and compare */
1012
   Botan::X509_Certificate user1_cert_copy(user1_cert);
11✔
1013
   result.test_is_true("certificate copy", user1_cert == user1_cert_copy);
11✔
1014

1015
   user1_cert_copy = user2_cert;
11✔
1016
   result.test_is_true("certificate assignment", user2_cert == user1_cert_copy);
11✔
1017

1018
   const Botan::X509_Certificate user1_cert_differ =
11✔
1019
      ca.sign_request(user1_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
11✔
1020

1021
   result.test_is_false("certificate differs", user1_cert == user1_cert_differ);
11✔
1022

1023
   /* Get cert data */
1024
   result.test_sz_eq("x509 version", user1_cert.x509_version(), size_t(3));
11✔
1025

1026
   const Botan::X509_DN& user1_issuer_dn = user1_cert.issuer_dn();
11✔
1027
   result.test_str_eq("issuer info CN", user1_issuer_dn.get_first_attribute("CN"), ca_opts().common_name);
11✔
1028
   result.test_str_eq("issuer info Country", user1_issuer_dn.get_first_attribute("C"), ca_opts().country);
11✔
1029
   result.test_str_eq("issuer info Orga", user1_issuer_dn.get_first_attribute("O"), ca_opts().organization);
11✔
1030
   result.test_str_eq("issuer info OrgaUnit", user1_issuer_dn.get_first_attribute("OU"), ca_opts().org_unit);
11✔
1031

1032
   const Botan::X509_DN& user3_subject_dn = user3_cert.subject_dn();
11✔
1033
   result.test_sz_eq("subject OrgaUnit count",
11✔
1034
                     user3_subject_dn.get_attribute("OU").size(),
22✔
1035
                     req_opts3(sig_algo).more_org_units.size() + 1);
22✔
1036
   result.test_str_eq(
22✔
1037
      "subject OrgaUnit #2", user3_subject_dn.get_attribute("OU").at(1), req_opts3(sig_algo).more_org_units.at(0));
33✔
1038

1039
   const Botan::AlternativeName& user1_altname = user1_cert.subject_alt_name();
11✔
1040
   result.test_str_eq("subject alt email", user1_altname.get_first_attribute("RFC822"), "testing@randombit.net");
11✔
1041
   result.test_str_eq("subject alt dns", user1_altname.get_first_attribute("DNS"), "botan.randombit.net");
11✔
1042
   result.test_str_eq("subject alt uri", user1_altname.get_first_attribute("URI"), "https://botan.randombit.net");
11✔
1043

1044
   const Botan::AlternativeName& user3_altname = user3_cert.subject_alt_name();
11✔
1045
   result.test_sz_eq(
11✔
1046
      "subject alt dns count", user3_altname.get_attribute("DNS").size(), req_opts3(sig_algo).more_dns.size() + 1);
22✔
1047
   result.test_str_eq(
22✔
1048
      "subject alt dns #2", user3_altname.get_attribute("DNS").at(1), req_opts3(sig_algo).more_dns.at(0));
33✔
1049

1050
   const Botan::X509_CRL crl1 = ca.new_crl(rng);
11✔
1051

1052
   /* Verify the certs */
1053
   const Botan::Path_Validation_Restrictions restrictions(false, 80);
22✔
1054
   Botan::Certificate_Store_In_Memory store;
11✔
1055

1056
   // First try with an empty store
1057
   const Botan::Path_Validation_Result result_no_issuer = Botan::x509_path_validate(user1_cert, restrictions, store);
11✔
1058
   result.test_str_eq(
11✔
1059
      "user 1 issuer not found",
1060
      result_no_issuer.result_string(),
11✔
1061
      Botan::Path_Validation_Result::status_string(Botan::Certificate_Status_Code::CERT_ISSUER_NOT_FOUND));
1062

1063
   store.add_certificate(ca.ca_certificate());
11✔
1064

1065
   Botan::Path_Validation_Result result_u1 = Botan::x509_path_validate(user1_cert, restrictions, store);
11✔
1066
   if(!result.test_is_true("user 1 validates", result_u1.successful_validation())) {
11✔
1067
      result.test_note("user 1 validation result", result_u1.result_string());
×
1068
   }
1069

1070
   Botan::Path_Validation_Result result_u2 = Botan::x509_path_validate(user2_cert, restrictions, store);
11✔
1071
   if(!result.test_is_true("user 2 validates", result_u2.successful_validation())) {
11✔
1072
      result.test_note("user 2 validation result", result_u2.result_string());
×
1073
   }
1074

1075
   const Botan::Path_Validation_Result result_self_signed =
11✔
1076
      Botan::x509_path_validate(user1_ss_cert, restrictions, store);
11✔
1077
   result.test_str_eq(
11✔
1078
      "user 1 issuer not found",
1079
      result_no_issuer.result_string(),
11✔
1080
      Botan::Path_Validation_Result::status_string(Botan::Certificate_Status_Code::CERT_ISSUER_NOT_FOUND));
1081
   store.add_crl(crl1);
11✔
1082

1083
   std::vector<Botan::CRL_Entry> revoked;
11✔
1084
   revoked.push_back(Botan::CRL_Entry(user1_cert, Botan::CRL_Code::CessationOfOperation));
22✔
1085
   revoked.push_back(Botan::CRL_Entry(user2_cert));
22✔
1086

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

1089
   store.add_crl(crl2);
11✔
1090

1091
   const std::string revoked_str =
11✔
1092
      Botan::Path_Validation_Result::status_string(Botan::Certificate_Status_Code::CERT_IS_REVOKED);
11✔
1093

1094
   result_u1 = Botan::x509_path_validate(user1_cert, restrictions, store);
11✔
1095
   result.test_str_eq("user 1 revoked", result_u1.result_string(), revoked_str);
11✔
1096

1097
   result_u2 = Botan::x509_path_validate(user2_cert, restrictions, store);
11✔
1098
   result.test_str_eq("user 1 revoked", result_u2.result_string(), revoked_str);
11✔
1099

1100
   revoked.clear();
11✔
1101
   revoked.push_back(Botan::CRL_Entry(user1_cert, Botan::CRL_Code::RemoveFromCrl));
22✔
1102
   const Botan::X509_CRL crl3 = ca.update_crl(crl2, revoked, rng);
11✔
1103

1104
   store.add_crl(crl3);
11✔
1105

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

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

1114
   return result;
11✔
1115
}
44✔
1116

1117
Test::Result test_crl_entry_negative_serial() {
1✔
1118
   Test::Result result("CRL entry with negative serial matches certificate serial");
1✔
1119

1120
   const auto build_crl_entry = [](const std::vector<uint8_t>& serial_bytes) {
4✔
1121
      std::vector<uint8_t> der;
3✔
1122
      Botan::DER_Encoder enc(der);
3✔
1123
      enc.start_sequence()
3✔
1124
         .add_object(Botan::ASN1_Type::Integer, Botan::ASN1_Class::Universal, serial_bytes.data(), serial_bytes.size())
3✔
1125
         .encode(Botan::X509_Time(std::chrono::system_clock::now()))
3✔
1126
         .end_cons();
3✔
1127

1128
      Botan::CRL_Entry entry;
3✔
1129
      Botan::BER_Decoder dec(der);
3✔
1130
      entry.decode_from(dec);
3✔
1131
      return entry;
3✔
1132
   };
3✔
1133

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

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

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

1146
   return result;
1✔
1147
}
1✔
1148

1149
Test::Result test_usage(const Botan::Private_Key& ca_key,
12✔
1150
                        const std::string& sig_algo,
1151
                        const std::string& hash_fn,
1152
                        Botan::RandomNumberGenerator& rng) {
1153
   using Botan::Key_Constraints;
12✔
1154
   using Botan::Usage_Type;
12✔
1155

1156
   Test::Result result("X509 Usage");
12✔
1157

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

1161
   /* Create the CA object */
1162
   const Botan::X509_CA ca(ca_cert, ca_key, hash_fn, rng);
12✔
1163

1164
   auto user1_key = make_a_private_key(sig_algo, rng);
12✔
1165

1166
   Botan::X509_Cert_Options opts("Test User 1/US/Botan Project/Testing");
12✔
1167
   opts.constraints = Key_Constraints::DigitalSignature;
12✔
1168

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

1171
   const Botan::X509_Certificate user1_cert =
12✔
1172
      ca.sign_request(user1_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
12✔
1173

1174
   // cert only allows digitalSignature, but we check for both digitalSignature and cRLSign
1175
   result.test_is_false(
12✔
1176
      "key usage cRLSign not allowed",
1177
      user1_cert.allowed_usage(Key_Constraints(Key_Constraints::DigitalSignature | Key_Constraints::CrlSign)));
12✔
1178
   result.test_is_false("encryption is not allowed", user1_cert.allowed_usage(Usage_Type::ENCRYPTION));
12✔
1179

1180
   // cert only allows digitalSignature, so checking for only that should be ok
1181
   result.test_is_true("key usage digitalSignature allowed",
12✔
1182
                       user1_cert.allowed_usage(Key_Constraints::DigitalSignature));
12✔
1183

1184
   opts.constraints = Key_Constraints(Key_Constraints::DigitalSignature | Key_Constraints::CrlSign);
12✔
1185

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

1188
   const Botan::X509_Certificate mult_usage_cert =
12✔
1189
      ca.sign_request(mult_usage_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
12✔
1190

1191
   // cert allows multiple usages, so each one of them as well as both together should be allowed
1192
   result.test_is_true("key usage multiple digitalSignature allowed",
12✔
1193
                       mult_usage_cert.allowed_usage(Key_Constraints::DigitalSignature));
12✔
1194
   result.test_is_true("key usage multiple cRLSign allowed", mult_usage_cert.allowed_usage(Key_Constraints::CrlSign));
12✔
1195
   result.test_is_true(
12✔
1196
      "key usage multiple digitalSignature and cRLSign allowed",
1197
      mult_usage_cert.allowed_usage(Key_Constraints(Key_Constraints::DigitalSignature | Key_Constraints::CrlSign)));
12✔
1198
   result.test_is_false("encryption is not allowed", mult_usage_cert.allowed_usage(Usage_Type::ENCRYPTION));
12✔
1199

1200
   opts.constraints = Key_Constraints();
12✔
1201

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

1204
   const Botan::X509_Certificate no_usage_cert =
12✔
1205
      ca.sign_request(no_usage_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
12✔
1206

1207
   // cert allows every usage
1208
   result.test_is_true("key usage digitalSignature allowed",
12✔
1209
                       no_usage_cert.allowed_usage(Key_Constraints::DigitalSignature));
12✔
1210
   result.test_is_true("key usage cRLSign allowed", no_usage_cert.allowed_usage(Key_Constraints::CrlSign));
12✔
1211
   result.test_is_true("key usage encryption allowed", no_usage_cert.allowed_usage(Usage_Type::ENCRYPTION));
12✔
1212

1213
   if(sig_algo == "RSA") {
12✔
1214
      // cert allows data encryption
1215
      opts.constraints = Key_Constraints(Key_Constraints::KeyEncipherment | Key_Constraints::DataEncipherment);
1✔
1216

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

1219
      const Botan::X509_Certificate enc_cert =
1✔
1220
         ca.sign_request(enc_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
1✔
1221

1222
      result.test_is_true("cert allows encryption", enc_cert.allowed_usage(Usage_Type::ENCRYPTION));
1✔
1223
      result.test_is_true("cert does not allow TLS client auth", !enc_cert.allowed_usage(Usage_Type::TLS_CLIENT_AUTH));
1✔
1224
   }
1✔
1225

1226
   return result;
12✔
1227
}
24✔
1228

1229
Test::Result test_self_issued(const Botan::Private_Key& ca_key,
11✔
1230
                              const std::string& sig_algo,
1231
                              const std::string& sig_padding,
1232
                              const std::string& hash_fn,
1233
                              Botan::RandomNumberGenerator& rng) {
1234
   using Botan::Key_Constraints;
11✔
1235

1236
   Test::Result result("X509 Self Issued");
11✔
1237

1238
   // create the self-signed cert
1239
   const Botan::X509_Certificate ca_cert =
11✔
1240
      Botan::X509::create_self_signed_cert(ca_opts(sig_padding), ca_key, hash_fn, rng);
11✔
1241

1242
   /* Create the CA object */
1243
   const Botan::X509_CA ca(ca_cert, ca_key, hash_fn, sig_padding, rng);
11✔
1244

1245
   auto user_key = make_a_private_key(sig_algo, rng);
11✔
1246

1247
   // create a self-issued certificate, that is, a certificate with subject dn == issuer dn,
1248
   // but signed by a CA, not signed by it's own private key
1249
   Botan::X509_Cert_Options opts = ca_opts();
11✔
1250
   opts.constraints = Key_Constraints::DigitalSignature;
11✔
1251
   opts.set_padding_scheme(sig_padding);
11✔
1252

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

1255
   const Botan::X509_Certificate self_issued_cert =
11✔
1256
      ca.sign_request(self_issued_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
11✔
1257

1258
   // check that this chain can can be verified successfully
1259
   const Botan::Certificate_Store_In_Memory trusted(ca.ca_certificate());
11✔
1260

1261
   const Botan::Path_Validation_Restrictions restrictions(false, 80);
22✔
1262

1263
   const Botan::Path_Validation_Result validation_result =
11✔
1264
      Botan::x509_path_validate(self_issued_cert, restrictions, trusted);
11✔
1265

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

1268
   return result;
11✔
1269
}
22✔
1270

1271
Test::Result test_x509_uninit() {
1✔
1272
   Test::Result result("X509 object uninitialized access");
1✔
1273

1274
   Botan::X509_Certificate cert;
1✔
1275
   result.test_throws("uninitialized cert access causes exception", "X509_Certificate uninitialized", [&cert]() {
1✔
1276
      cert.x509_version();
1✔
1277
   });
1278

1279
   Botan::X509_CRL crl;
1✔
1280
   result.test_throws(
1✔
1281
      "uninitialized crl access causes exception", "X509_CRL uninitialized", [&crl]() { crl.crl_number(); });
2✔
1282

1283
   return result;
1✔
1284
}
1✔
1285

1286
Test::Result test_valid_constraints(const Botan::Private_Key& key, const std::string& pk_algo) {
19✔
1287
   using Botan::Key_Constraints;
19✔
1288

1289
   Test::Result result("X509 Valid Constraints " + pk_algo);
19✔
1290

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

1293
   // Now check some typical usage scenarios for the given key type
1294
   // Taken from RFC 5280, sec. 4.2.1.3
1295
   // ALL constraints are not typical at all, but we use them for a negative test
1296
   const auto all = Key_Constraints(
19✔
1297
      Key_Constraints::DigitalSignature | Key_Constraints::NonRepudiation | Key_Constraints::KeyEncipherment |
1298
      Key_Constraints::DataEncipherment | Key_Constraints::KeyAgreement | Key_Constraints::KeyCertSign |
1299
      Key_Constraints::CrlSign | Key_Constraints::EncipherOnly | Key_Constraints::DecipherOnly);
19✔
1300

1301
   const auto ca = Key_Constraints(Key_Constraints::KeyCertSign);
19✔
1302
   const auto sign_data = Key_Constraints(Key_Constraints::DigitalSignature);
19✔
1303
   const auto non_repudiation = Key_Constraints(Key_Constraints::NonRepudiation | Key_Constraints::DigitalSignature);
19✔
1304
   const auto key_encipherment = Key_Constraints(Key_Constraints::KeyEncipherment);
19✔
1305
   const auto data_encipherment = Key_Constraints(Key_Constraints::DataEncipherment);
19✔
1306
   const auto key_agreement = Key_Constraints(Key_Constraints::KeyAgreement);
19✔
1307
   const auto key_agreement_encipher_only =
19✔
1308
      Key_Constraints(Key_Constraints::KeyAgreement | Key_Constraints::EncipherOnly);
19✔
1309
   const auto key_agreement_decipher_only =
19✔
1310
      Key_Constraints(Key_Constraints::KeyAgreement | Key_Constraints::DecipherOnly);
19✔
1311
   const auto crl_sign = Key_Constraints(Key_Constraints::CrlSign);
19✔
1312
   const auto sign_everything =
19✔
1313
      Key_Constraints(Key_Constraints::DigitalSignature | Key_Constraints::KeyCertSign | Key_Constraints::CrlSign);
19✔
1314

1315
   if(pk_algo == "DH" || pk_algo == "ECDH") {
19✔
1316
      // DH and ECDH only for key agreement
1317
      result.test_is_false("all constraints not permitted", all.compatible_with(key));
2✔
1318
      result.test_is_false("cert sign not permitted", ca.compatible_with(key));
2✔
1319
      result.test_is_false("signature not permitted", sign_data.compatible_with(key));
2✔
1320
      result.test_is_false("non repudiation not permitted", non_repudiation.compatible_with(key));
2✔
1321
      result.test_is_false("key encipherment not permitted", key_encipherment.compatible_with(key));
2✔
1322
      result.test_is_false("data encipherment not permitted", data_encipherment.compatible_with(key));
2✔
1323
      result.test_is_true("usage acceptable", key_agreement.compatible_with(key));
2✔
1324
      result.test_is_true("usage acceptable", key_agreement_encipher_only.compatible_with(key));
2✔
1325
      result.test_is_true("usage acceptable", key_agreement_decipher_only.compatible_with(key));
2✔
1326
      result.test_is_false("crl sign not permitted", crl_sign.compatible_with(key));
2✔
1327
      result.test_is_false("sign", sign_everything.compatible_with(key));
2✔
1328
   } else if(pk_algo == "Kyber" || pk_algo == "FrodoKEM" || pk_algo == "ML-KEM" || pk_algo == "ClassicMcEliece") {
17✔
1329
      // KEMs can encrypt and agree
1330
      result.test_is_false("all constraints not permitted", all.compatible_with(key));
4✔
1331
      result.test_is_false("cert sign not permitted", ca.compatible_with(key));
4✔
1332
      result.test_is_false("signature not permitted", sign_data.compatible_with(key));
4✔
1333
      result.test_is_false("non repudiation not permitted", non_repudiation.compatible_with(key));
4✔
1334
      result.test_is_false("crl sign not permitted", crl_sign.compatible_with(key));
4✔
1335
      result.test_is_false("sign", sign_everything.compatible_with(key));
4✔
1336
      result.test_is_false("key agreement not permitted", key_agreement.compatible_with(key));
4✔
1337
      result.test_is_false("usage acceptable", data_encipherment.compatible_with(key));
4✔
1338
      result.test_is_true("usage acceptable", key_encipherment.compatible_with(key));
4✔
1339
   } else if(pk_algo == "RSA") {
13✔
1340
      // RSA can do everything except key agreement
1341
      result.test_is_false("all constraints not permitted", all.compatible_with(key));
1✔
1342

1343
      result.test_is_true("usage acceptable", ca.compatible_with(key));
1✔
1344
      result.test_is_true("usage acceptable", sign_data.compatible_with(key));
1✔
1345
      result.test_is_true("usage acceptable", non_repudiation.compatible_with(key));
1✔
1346
      result.test_is_true("usage acceptable", key_encipherment.compatible_with(key));
1✔
1347
      result.test_is_true("usage acceptable", data_encipherment.compatible_with(key));
1✔
1348
      result.test_is_false("key agreement not permitted", key_agreement.compatible_with(key));
1✔
1349
      result.test_is_false("key agreement", key_agreement_encipher_only.compatible_with(key));
1✔
1350
      result.test_is_false("key agreement", key_agreement_decipher_only.compatible_with(key));
1✔
1351
      result.test_is_true("usage acceptable", crl_sign.compatible_with(key));
1✔
1352
      result.test_is_true("usage acceptable", sign_everything.compatible_with(key));
1✔
1353
   } else if(pk_algo == "ElGamal") {
12✔
1354
      // only ElGamal encryption is currently implemented
1355
      result.test_is_false("all constraints not permitted", all.compatible_with(key));
1✔
1356
      result.test_is_false("cert sign not permitted", ca.compatible_with(key));
1✔
1357
      result.test_is_true("data encipherment permitted", data_encipherment.compatible_with(key));
1✔
1358
      result.test_is_true("key encipherment permitted", key_encipherment.compatible_with(key));
1✔
1359
      result.test_is_false("key agreement not permitted", key_agreement.compatible_with(key));
1✔
1360
      result.test_is_false("key agreement", key_agreement_encipher_only.compatible_with(key));
1✔
1361
      result.test_is_false("key agreement", key_agreement_decipher_only.compatible_with(key));
1✔
1362
      result.test_is_false("crl sign not permitted", crl_sign.compatible_with(key));
1✔
1363
      result.test_is_false("sign", sign_everything.compatible_with(key));
1✔
1364
   } else if(pk_algo == "DSA" || pk_algo == "ECDSA" || pk_algo == "ECGDSA" || pk_algo == "ECKCDSA" ||
10✔
1365
             pk_algo == "GOST-34.10" || pk_algo == "Dilithium" || pk_algo == "ML-DSA" || pk_algo == "SLH-DSA" ||
18✔
1366
             pk_algo == "HSS-LMS") {
3✔
1367
      // these are signature algorithms only
1368
      result.test_is_false("all constraints not permitted", all.compatible_with(key));
9✔
1369

1370
      result.test_is_true("ca allowed", ca.compatible_with(key));
9✔
1371
      result.test_is_true("sign allowed", sign_data.compatible_with(key));
9✔
1372
      result.test_is_true("non-repudiation allowed", non_repudiation.compatible_with(key));
9✔
1373
      result.test_is_false("key encipherment not permitted", key_encipherment.compatible_with(key));
9✔
1374
      result.test_is_false("data encipherment not permitted", data_encipherment.compatible_with(key));
9✔
1375
      result.test_is_false("key agreement not permitted", key_agreement.compatible_with(key));
9✔
1376
      result.test_is_false("key agreement", key_agreement_encipher_only.compatible_with(key));
9✔
1377
      result.test_is_false("key agreement", key_agreement_decipher_only.compatible_with(key));
9✔
1378
      result.test_is_true("crl sign allowed", crl_sign.compatible_with(key));
9✔
1379
      result.test_is_true("sign allowed", sign_everything.compatible_with(key));
9✔
1380
   }
1381

1382
   return result;
19✔
1383
}
×
1384

1385
/**
1386
 * @brief X.509v3 extension that encodes a given string
1387
 */
1388
class String_Extension final : public Botan::Certificate_Extension {
22✔
1389
   public:
1390
      String_Extension() = default;
22✔
1391

1392
      explicit String_Extension(const std::string& val) : m_contents(val) {}
11✔
1393

1394
      std::string value() const { return m_contents; }
44✔
1395

1396
      std::unique_ptr<Certificate_Extension> copy() const override {
×
1397
         return std::make_unique<String_Extension>(m_contents);
×
1398
      }
1399

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

1402
      bool should_encode() const override { return true; }
22✔
1403

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

1406
      std::vector<uint8_t> encode_inner() const override {
11✔
1407
         std::vector<uint8_t> bits;
11✔
1408
         Botan::DER_Encoder(bits).encode(Botan::ASN1_String(m_contents, Botan::ASN1_Type::Utf8String));
22✔
1409
         return bits;
11✔
1410
      }
×
1411

1412
      void decode_inner(const std::vector<uint8_t>& in) override {
22✔
1413
         Botan::ASN1_String str;
22✔
1414
         Botan::BER_Decoder(in).decode(str, Botan::ASN1_Type::Utf8String).verify_end();
22✔
1415
         m_contents = str.value();
44✔
1416
      }
22✔
1417

1418
   private:
1419
      std::string m_contents;
1420
};
1421

1422
Test::Result test_custom_dn_attr(const Botan::Private_Key& ca_key,
11✔
1423
                                 const std::string& sig_algo,
1424
                                 const std::string& sig_padding,
1425
                                 const std::string& hash_fn,
1426
                                 Botan::RandomNumberGenerator& rng) {
1427
   Test::Result result("X509 Custom DN");
11✔
1428

1429
   /* Create the self-signed cert */
1430
   const Botan::X509_Certificate ca_cert =
11✔
1431
      Botan::X509::create_self_signed_cert(ca_opts(sig_padding), ca_key, hash_fn, rng);
11✔
1432

1433
   /* Create the CA object */
1434
   const Botan::X509_CA ca(ca_cert, ca_key, hash_fn, sig_padding, rng);
11✔
1435

1436
   auto user_key = make_a_private_key(sig_algo, rng);
11✔
1437

1438
   Botan::X509_DN subject_dn;
11✔
1439

1440
   const Botan::OID attr1(Botan::OID("1.3.6.1.4.1.25258.9.1.1"));
11✔
1441
   const Botan::OID attr2(Botan::OID("1.3.6.1.4.1.25258.9.1.2"));
11✔
1442
   const Botan::ASN1_String val1("Custom Attr 1", Botan::ASN1_Type::PrintableString);
11✔
1443
   const Botan::ASN1_String val2("12345", Botan::ASN1_Type::Utf8String);
11✔
1444

1445
   subject_dn.add_attribute(attr1, val1);
11✔
1446
   subject_dn.add_attribute(attr2, val2);
11✔
1447

1448
   const Botan::Extensions extensions;
11✔
1449

1450
   const Botan::PKCS10_Request req =
11✔
1451
      Botan::PKCS10_Request::create(*user_key, subject_dn, extensions, hash_fn, rng, sig_padding);
11✔
1452

1453
   const Botan::X509_DN& req_dn = req.subject_dn();
11✔
1454

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

1457
   const Botan::ASN1_String req_val1 = req_dn.get_first_attribute(attr1);
11✔
1458
   const Botan::ASN1_String req_val2 = req_dn.get_first_attribute(attr2);
11✔
1459
   result.test_is_true("Attr1 matches encoded", req_val1 == val1);
11✔
1460
   result.test_is_true("Attr2 matches encoded", req_val2 == val2);
11✔
1461
   result.test_is_true("Attr1 tag matches encoded", req_val1.tagging() == val1.tagging());
11✔
1462
   result.test_is_true("Attr2 tag matches encoded", req_val2.tagging() == val2.tagging());
11✔
1463

1464
   const Botan::X509_Time not_before("100301123001Z", Botan::ASN1_Type::UtcTime);
11✔
1465
   const Botan::X509_Time not_after("300301123001Z", Botan::ASN1_Type::UtcTime);
11✔
1466

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

1469
   const Botan::X509_DN& cert_dn = cert.subject_dn();
11✔
1470

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

1473
   const Botan::ASN1_String cert_val1 = cert_dn.get_first_attribute(attr1);
11✔
1474
   const Botan::ASN1_String cert_val2 = cert_dn.get_first_attribute(attr2);
11✔
1475
   result.test_is_true("Attr1 matches encoded", cert_val1 == val1);
11✔
1476
   result.test_is_true("Attr2 matches encoded", cert_val2 == val2);
11✔
1477
   result.test_is_true("Attr1 tag matches encoded", cert_val1.tagging() == val1.tagging());
11✔
1478
   result.test_is_true("Attr2 tag matches encoded", cert_val2.tagging() == val2.tagging());
11✔
1479

1480
   return result;
22✔
1481
}
99✔
1482

1483
Test::Result test_x509_extensions(const Botan::Private_Key& ca_key,
11✔
1484
                                  const std::string& sig_algo,
1485
                                  const std::string& sig_padding,
1486
                                  const std::string& hash_fn,
1487
                                  Botan::RandomNumberGenerator& rng) {
1488
   using Botan::Key_Constraints;
11✔
1489

1490
   Test::Result result("X509 Extensions");
11✔
1491

1492
   /* Create the self-signed cert */
1493
   const Botan::X509_Certificate ca_cert =
11✔
1494
      Botan::X509::create_self_signed_cert(ca_opts(sig_padding), ca_key, hash_fn, rng);
11✔
1495

1496
   /* Create the CA object */
1497
   const Botan::X509_CA ca(ca_cert, ca_key, hash_fn, sig_padding, rng);
11✔
1498

1499
   /* Prepare CDP extension */
1500
   std::vector<std::string> cdp_urls = {
11✔
1501
      "http://example.com/crl1.pem",
1502
      "ldap://ldap.example.com/cn=crl1,dc=example,dc=com?certificateRevocationList;binary"};
11✔
1503

1504
   std::vector<Botan::Cert_Extension::CRL_Distribution_Points::Distribution_Point> dps;
11✔
1505

1506
   for(const auto& uri : cdp_urls) {
33✔
1507
      Botan::AlternativeName cdp_alt_name;
22✔
1508
      cdp_alt_name.add_uri(uri);
22✔
1509
      const Botan::Cert_Extension::CRL_Distribution_Points::Distribution_Point dp(cdp_alt_name);
22✔
1510

1511
      dps.emplace_back(dp);
22✔
1512
   }
22✔
1513

1514
   auto user_key = make_a_private_key(sig_algo, rng);
11✔
1515

1516
   Botan::X509_Cert_Options opts("Test User 1/US/Botan Project/Testing");
11✔
1517
   opts.constraints = Key_Constraints::DigitalSignature;
11✔
1518

1519
   // include a custom extension in the request
1520
   Botan::Extensions req_extensions;
11✔
1521
   const Botan::OID oid("1.2.3.4.5.6.7.8.9.1");
11✔
1522
   const Botan::OID ku_oid = Botan::OID::from_string("X509v3.KeyUsage");
11✔
1523
   req_extensions.add(std::make_unique<String_Extension>("AAAAAAAAAAAAAABCDEF"), false);
22✔
1524
   req_extensions.add(std::make_unique<Botan::Cert_Extension::CRL_Distribution_Points>(dps));
22✔
1525
   opts.extensions = req_extensions;
11✔
1526
   opts.set_padding_scheme(sig_padding);
11✔
1527

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

1531
   result.test_is_true("Extensions::extension_set true for Key_Usage",
11✔
1532
                       self_signed_cert.v3_extensions().extension_set(ku_oid));
11✔
1533

1534
   // check if known Key_Usage extension is present in self-signed cert
1535
   auto key_usage_ext = self_signed_cert.v3_extensions().get(ku_oid);
11✔
1536
   if(result.test_is_true("Key_Usage extension present in self-signed certificate", key_usage_ext != nullptr)) {
11✔
1537
      result.test_is_true(
22✔
1538
         "Key_Usage extension value matches in self-signed certificate",
1539
         dynamic_cast<Botan::Cert_Extension::Key_Usage&>(*key_usage_ext).get_constraints() == opts.constraints);
11✔
1540
   }
1541

1542
   // check if custom extension is present in self-signed cert
1543
   auto string_ext = self_signed_cert.v3_extensions().get_raw<String_Extension>(oid);
11✔
1544
   if(result.test_is_true("Custom extension present in self-signed certificate", string_ext != nullptr)) {
11✔
1545
      result.test_str_eq(
22✔
1546
         "Custom extension value matches in self-signed certificate", string_ext->value(), "AAAAAAAAAAAAAABCDEF");
33✔
1547
   }
1548

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

1553
   if(result.test_is_true("CRL Distribution Points extension present in self-signed certificate",
11✔
1554
                          !cert_cdps->crl_distribution_urls().empty())) {
11✔
1555
      for(const auto& cdp : cert_cdps->distribution_points()) {
33✔
1556
         result.test_is_true("CDP URI present in self-signed certificate",
44✔
1557
                             std::ranges::find(cdp_urls, cdp.point().get_first_attribute("URI")) != cdp_urls.end());
66✔
1558
      }
1559

1560
      // The accessor returns one entry per URI
1561
      const auto& urls = cert_cdps->crl_distribution_urls();
11✔
1562
      result.test_sz_eq("crl_distribution_urls has one entry per URI (self-signed)", urls.size(), cdp_urls.size());
11✔
1563
      for(const auto& url : urls) {
33✔
1564
         result.test_is_true("crl_distribution_urls entry is a bare URI (self-signed)",
44✔
1565
                             std::ranges::find(cdp_urls, url) != cdp_urls.end());
44✔
1566
      }
1567

1568
      // Ensure X509_Certificate's cached accessor returns the same bare URIs
1569
      const auto cert_dp = self_signed_cert.crl_distribution_points();
11✔
1570
      result.test_sz_eq(
11✔
1571
         "X509_Certificate::crl_distribution_points size (self-signed)", cert_dp.size(), cdp_urls.size());
1572
      for(const auto& url : cert_dp) {
33✔
1573
         result.test_is_true("X509_Certificate::crl_distribution_points entry is a bare URI (self-signed)",
44✔
1574
                             std::ranges::find(cdp_urls, url) != cdp_urls.end());
44✔
1575
      }
1576
   }
11✔
1577

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

1580
   /* Create a CA-signed certificate */
1581
   const Botan::X509_Certificate ca_signed_cert =
11✔
1582
      ca.sign_request(user_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
11✔
1583

1584
   // check if known Key_Usage extension is present in CA-signed cert
1585
   result.test_is_true("Extensions::extension_set true for Key_Usage",
11✔
1586
                       ca_signed_cert.v3_extensions().extension_set(ku_oid));
11✔
1587

1588
   key_usage_ext = ca_signed_cert.v3_extensions().get(ku_oid);
22✔
1589
   if(result.test_is_true("Key_Usage extension present in CA-signed certificate", key_usage_ext != nullptr)) {
11✔
1590
      auto constraints = dynamic_cast<Botan::Cert_Extension::Key_Usage&>(*key_usage_ext).get_constraints();
11✔
1591
      result.test_is_true("Key_Usage extension value matches in user certificate",
22✔
1592
                          constraints == Botan::Key_Constraints::DigitalSignature);
11✔
1593
   }
1594

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

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

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

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

1621
      const auto cert_dp = ca_signed_cert.crl_distribution_points();
11✔
1622
      result.test_sz_eq("X509_Certificate::crl_distribution_points size (CA-signed)", cert_dp.size(), cdp_urls.size());
11✔
1623
      for(const auto& url : cert_dp) {
33✔
1624
         result.test_is_true("X509_Certificate::crl_distribution_points entry is a bare URI (CA-signed)",
44✔
1625
                             std::ranges::find(cdp_urls, url) != cdp_urls.end());
44✔
1626
      }
1627
   }
11✔
1628

1629
   return result;
11✔
1630
}
55✔
1631

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

1635
   struct TestData {
12✔
1636
         const std::string issuer, subject, issuer_hash, subject_hash;
1637
   } const cases[]{{"",
12✔
1638
                    "",
1639
                    "E4F60D0AA6D7F3D3B6A6494B1C861B99F649C6F9EC51ABAF201B20F297327C95",
1640
                    "E4F60D0AA6D7F3D3B6A6494B1C861B99F649C6F9EC51ABAF201B20F297327C95"},
1641
                   {"a",
1642
                    "b",
1643
                    "BC2E013472F39AC579964880E422737C82BA812CB8BC2FD17E013060D71E6E19",
1644
                    "5E31CFAA3FAFB1A5BA296A0D2BAB9CA44D7936E9BF0BBC54637D0C53DBC4A432"},
1645
                   {"A",
1646
                    "B",
1647
                    "4B3206201C4BC9B6CD6C36532A97687DF9238155D99ADB60C66BF2B2220643D8",
1648
                    "FFF635A52A16618B4A0E9CD26B5E5A2FA573D343C051E6DE8B0811B1ACC89B86"},
1649
                   {
1650
                      "Test Issuer/US/Botan Project/Testing",
1651
                      "Test Subject/US/Botan Project/Testing",
1652
                      "ACB4F373004A56A983A23EB8F60FA4706312B5DB90FD978574FE7ACC84E093A5",
1653
                      "87039231C2205B74B6F1F3830A66272C0B41F71894B03AC3150221766D95267B",
1654
                   },
1655
                   {
1656
                      "Test Subject/US/Botan Project/Testing",
1657
                      "Test Issuer/US/Botan Project/Testing",
1658
                      "87039231C2205B74B6F1F3830A66272C0B41F71894B03AC3150221766D95267B",
1659
                      "ACB4F373004A56A983A23EB8F60FA4706312B5DB90FD978574FE7ACC84E093A5",
1660
                   }};
72✔
1661

1662
   for(const auto& a : cases) {
72✔
1663
      Botan::X509_Cert_Options opts{a.issuer};
60✔
1664
      opts.CA_key();
60✔
1665

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

1668
      result.test_str_eq(a.issuer, Botan::hex_encode(issuer_cert.raw_issuer_dn_sha256()), a.issuer_hash);
60✔
1669
      result.test_str_eq(a.issuer, Botan::hex_encode(issuer_cert.raw_subject_dn_sha256()), a.issuer_hash);
60✔
1670

1671
      const Botan::X509_CA ca(issuer_cert, key, hash_fn, rng);
60✔
1672
      const Botan::PKCS10_Request req =
60✔
1673
         Botan::X509::create_cert_req(Botan::X509_Cert_Options(a.subject), key, hash_fn, rng);
60✔
1674
      const Botan::X509_Certificate subject_cert =
60✔
1675
         ca.sign_request(req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
60✔
1676

1677
      result.test_str_eq(a.subject, Botan::hex_encode(subject_cert.raw_issuer_dn_sha256()), a.issuer_hash);
60✔
1678
      result.test_str_eq(a.subject, Botan::hex_encode(subject_cert.raw_subject_dn_sha256()), a.subject_hash);
60✔
1679
   }
60✔
1680
   return result;
12✔
1681
}
72✔
1682

1683
   #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
1684

1685
Test::Result test_x509_tn_auth_list_extension_decode() {
1✔
1686
   /* cert with TNAuthList extension data was generated by asn1parse cfg:
1687

1688
      asn1=SEQUENCE:tn_auth_list
1689

1690
      [tn_auth_list]
1691
      spc=EXP:0,IA5:1001
1692
      range=EXP:1,SEQUENCE:TelephoneNumberRange
1693
      one=EXP:2,IA5:333
1694

1695
      [TelephoneNumberRange]
1696
      start1=IA5:111
1697
      count1=INT:128
1698
      start2=IA5:222
1699
      count2=INT:256
1700
    */
1701
   const std::string filename("TNAuthList.pem");
1✔
1702
   Test::Result result("X509 TNAuthList decode");
1✔
1703
   result.start_timer();
1✔
1704

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

1707
   using Botan::Cert_Extension::TNAuthList;
1✔
1708

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

1711
   const auto& tn_entries = tn_auth_list->entries();
1✔
1712

1713
   result.test_not_null("cert has TNAuthList extension", tn_auth_list);
1✔
1714

1715
   result.test_throws("wrong telephone_number_range() accessor for spc",
1✔
1716
                      [&tn_entries] { tn_entries[0].telephone_number_range(); });
2✔
1717
   result.test_throws("wrong telephone_number() accessor for range",
1✔
1718
                      [&tn_entries] { tn_entries[1].telephone_number(); });
2✔
1719
   result.test_throws("wrong service_provider_code() accessor for one",
1✔
1720
                      [&tn_entries] { tn_entries[2].service_provider_code(); });
2✔
1721

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

1725
   result.test_is_true("range entry type", tn_entries[1].type() == TNAuthList::Entry::TelephoneNumberRange);
1✔
1726
   const auto& range = tn_entries[1].telephone_number_range();
1✔
1727
   result.test_sz_eq("range entries count", range.size(), 2);
1✔
1728
   result.test_str_eq("range entry 0 start data", range[0].start.value(), "111");
1✔
1729
   result.test_sz_eq("range entry 0 count data", range[0].count, 128);
1✔
1730
   result.test_str_eq("range entry 1 start data", range[1].start.value(), "222");
1✔
1731
   result.test_sz_eq("range entry 1 count data", range[1].count, 256);
1✔
1732

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

1736
   result.end_timer();
1✔
1737
   return result;
2✔
1738
}
1✔
1739

1740
   #endif
1741

1742
std::vector<std::string> get_sig_paddings(const std::string& sig_algo, const std::string& hash) {
12✔
1743
   if(sig_algo == "RSA") {
12✔
1744
      return {
1✔
1745
   #if defined(BOTAN_HAS_EMSA_PKCS1)
1746
         "PKCS1v15(" + hash + ")",
1✔
1747
   #endif
1748
   #if defined(BOTAN_HAS_EMSA_PSS)
1749
            "PSS(" + hash + ")",
1750
   #endif
1751
      };
3✔
1752
   } else if(sig_algo == "DSA" || sig_algo == "ECDSA" || sig_algo == "ECGDSA" || sig_algo == "ECKCDSA" ||
11✔
1753
             sig_algo == "GOST-34.10") {
7✔
1754
      return {hash};
10✔
1755
   } else if(sig_algo == "Ed25519" || sig_algo == "Ed448") {
6✔
1756
      return {"Pure"};
2✔
1757
   } else if(sig_algo == "Dilithium" || sig_algo == "ML-DSA") {
4✔
1758
      return {"Randomized"};
2✔
1759
   } else if(sig_algo == "HSS-LMS") {
2✔
1760
      return {""};
1✔
1761
   } else {
1762
      return {};
1✔
1763
   }
1764
}
6✔
1765

1766
class X509_Cert_Unit_Tests final : public Test {
1✔
1767
   public:
1768
      std::vector<Test::Result> run() override {
1✔
1769
         std::vector<Test::Result> results;
1✔
1770

1771
         auto& rng = this->rng();
1✔
1772

1773
         const std::string sig_algos[]{"RSA",
1✔
1774
                                       "DSA",
1775
                                       "ECDSA",
1776
                                       "ECGDSA",
1777
                                       "ECKCDSA",
1778
                                       "GOST-34.10",
1779
                                       "Ed25519",
1780
                                       "Ed448",
1781
                                       "Dilithium",
1782
                                       "ML-DSA",
1783
                                       "SLH-DSA",
1784
                                       "HSS-LMS"};
13✔
1785

1786
         for(const std::string& algo : sig_algos) {
13✔
1787
   #if !defined(BOTAN_HAS_EMSA_PKCS1)
1788
            if(algo == "RSA")
1789
               continue;
1790
   #endif
1791

1792
            std::string hash = "SHA-256";
12✔
1793

1794
            if(algo == "Ed25519") {
12✔
1795
               hash = "SHA-512";
1✔
1796
            }
1797
            if(algo == "Ed448") {
12✔
1798
               hash = "SHAKE-256(912)";
1✔
1799
            }
1800
            if(algo == "Dilithium" || algo == "ML-DSA") {
12✔
1801
               hash = "SHAKE-256(512)";
2✔
1802
            }
1803

1804
            auto key = make_a_private_key(algo, rng);
12✔
1805

1806
            if(key == nullptr) {
12✔
1807
               continue;
×
1808
            }
1809

1810
            results.push_back(test_hashes(*key, hash, rng));
24✔
1811
            results.push_back(test_valid_constraints(*key, algo));
24✔
1812

1813
            Test::Result usage_result("X509 Usage");
12✔
1814
            try {
12✔
1815
               usage_result.merge(test_usage(*key, algo, hash, rng));
12✔
1816
            } catch(std::exception& e) {
×
1817
               usage_result.test_failure("test_usage " + algo, e.what());
×
1818
            }
×
1819
            results.push_back(usage_result);
12✔
1820

1821
            for(const auto& padding_scheme : get_sig_paddings(algo, hash)) {
23✔
1822
               Test::Result cert_result("X509 Unit");
11✔
1823

1824
               try {
11✔
1825
                  cert_result.merge(test_x509_cert(*key, algo, padding_scheme, hash, rng));
11✔
1826
               } catch(std::exception& e) {
×
1827
                  cert_result.test_failure("test_x509_cert " + algo, e.what());
×
1828
               }
×
1829
               results.push_back(cert_result);
11✔
1830

1831
               Test::Result pkcs10_result("PKCS10 extensions");
11✔
1832
               try {
11✔
1833
                  pkcs10_result.merge(test_pkcs10_ext(*key, padding_scheme, hash, rng));
11✔
1834
               } catch(std::exception& e) {
×
1835
                  pkcs10_result.test_failure("test_pkcs10_ext " + algo, e.what());
×
1836
               }
×
1837
               results.push_back(pkcs10_result);
11✔
1838

1839
               Test::Result self_issued_result("X509 Self Issued");
11✔
1840
               try {
11✔
1841
                  self_issued_result.merge(test_self_issued(*key, algo, padding_scheme, hash, rng));
11✔
1842
               } catch(std::exception& e) {
×
1843
                  self_issued_result.test_failure("test_self_issued " + algo, e.what());
×
1844
               }
×
1845
               results.push_back(self_issued_result);
11✔
1846

1847
               Test::Result extensions_result("X509 Extensions");
11✔
1848
               try {
11✔
1849
                  extensions_result.merge(test_x509_extensions(*key, algo, padding_scheme, hash, rng));
11✔
1850
               } catch(std::exception& e) {
×
1851
                  extensions_result.test_failure("test_extensions " + algo, e.what());
×
1852
               }
×
1853
               results.push_back(extensions_result);
11✔
1854

1855
               Test::Result custom_dn_result("X509 Custom DN");
11✔
1856
               try {
11✔
1857
                  custom_dn_result.merge(test_custom_dn_attr(*key, algo, padding_scheme, hash, rng));
11✔
1858
               } catch(std::exception& e) {
×
1859
                  custom_dn_result.test_failure("test_custom_dn_attr " + algo, e.what());
×
1860
               }
×
1861
               results.push_back(custom_dn_result);
11✔
1862
            }
23✔
1863
         }
24✔
1864

1865
         /*
1866
         These are algos which cannot sign but can be included in certs
1867
         */
1868
         const std::vector<std::string> enc_algos = {
1✔
1869
            "DH", "ECDH", "ElGamal", "Kyber", "ML-KEM", "FrodoKEM", "ClassicMcEliece"};
1✔
1870

1871
         for(const std::string& algo : enc_algos) {
8✔
1872
            auto key = make_a_private_key(algo, rng);
7✔
1873

1874
            if(key) {
7✔
1875
               results.push_back(test_valid_constraints(*key, algo));
14✔
1876
            }
1877
         }
7✔
1878

1879
   #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM) && defined(BOTAN_HAS_EMSA_PKCS1) && defined(BOTAN_HAS_EMSA_PSSR) && \
1880
      defined(BOTAN_HAS_RSA)
1881
         Test::Result pad_config_result("X509 Padding Config");
1✔
1882
         try {
1✔
1883
            pad_config_result.merge(test_padding_config());
1✔
1884
         } catch(const std::exception& e) {
×
1885
            pad_config_result.test_failure("test_padding_config", e.what());
×
1886
         }
×
1887
         results.push_back(pad_config_result);
1✔
1888
   #endif
1889

1890
   #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
1891
         results.push_back(test_x509_utf8());
2✔
1892
         results.push_back(test_x509_any_key_extended_usage());
2✔
1893
         results.push_back(test_x509_bmpstring());
2✔
1894
         results.push_back(test_x509_teletex());
2✔
1895
         results.push_back(test_crl_dn_name());
2✔
1896
         results.push_back(test_rdn_multielement_set_name());
2✔
1897
         results.push_back(test_x509_decode_list());
2✔
1898
         results.push_back(test_rsa_oaep());
2✔
1899
         results.push_back(test_x509_authority_info_access_extension());
2✔
1900
         results.push_back(test_crl_issuing_distribution_point_extension());
2✔
1901
         results.push_back(test_verify_gost2012_cert());
2✔
1902
         results.push_back(test_parse_rsa_pss_cert());
2✔
1903
         results.push_back(test_x509_tn_auth_list_extension_decode());
2✔
1904
   #endif
1905

1906
         results.push_back(test_x509_encode_authority_info_access_extension());
2✔
1907
         results.push_back(test_x509_extension());
2✔
1908
         results.push_back(test_x509_dates());
2✔
1909
         results.push_back(test_cert_status_strings());
2✔
1910
         results.push_back(test_x509_uninit());
2✔
1911
         results.push_back(test_crl_entry_negative_serial());
2✔
1912

1913
         return results;
1✔
1914
      }
13✔
1915
};
1916

1917
BOTAN_REGISTER_TEST("x509", "x509_unit", X509_Cert_Unit_Tests);
1918

1919
#endif
1920

1921
}  // namespace
1922

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

© 2026 Coveralls, Inc