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

randombit / botan / 13064650714

31 Jan 2025 01:33AM UTC coverage: 91.22% (-0.009%) from 91.229%
13064650714

Pull #4617

github

web-flow
Merge 8770e3cff into 1564edf7b
Pull Request #4617: Fix build/test errors caught by test_all_configs.py

94145 of 103207 relevant lines covered (91.22%)

11401116.62 hits per line

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

93.82
/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/pk_algs.h>
14
   #include <botan/pkcs10.h>
15
   #include <botan/pkcs8.h>
16
   #include <botan/x509_ca.h>
17
   #include <botan/x509_ext.h>
18
   #include <botan/x509path.h>
19
   #include <botan/x509self.h>
20
   #include <botan/internal/calendar.h>
21

22
   #if defined(BOTAN_HAS_ECC_GROUP)
23
      #include <botan/ec_group.h>
24
   #endif
25
#endif
26

27
namespace Botan_Tests {
28

29
namespace {
30

31
#if defined(BOTAN_HAS_X509_CERTIFICATES)
32

33
Botan::X509_Time from_date(const int y, const int m, const int d) {
346✔
34
   const size_t this_year = Botan::calendar_point(std::chrono::system_clock::now()).year();
346✔
35

36
   Botan::calendar_point t(static_cast<uint32_t>(this_year + y), m, d, 0, 0, 0);
346✔
37
   return Botan::X509_Time(t.to_std_timepoint());
346✔
38
}
39

40
/* Return some option sets */
41
Botan::X509_Cert_Options ca_opts(const std::string& sig_padding = "") {
121✔
42
   Botan::X509_Cert_Options opts("Test CA/US/Botan Project/Testing");
121✔
43

44
   opts.uri = "https://botan.randombit.net";
121✔
45
   opts.dns = "botan.randombit.net";
121✔
46
   opts.email = "testing@randombit.net";
121✔
47
   opts.set_padding_scheme(sig_padding);
121✔
48

49
   opts.CA_key(1);
121✔
50

51
   return opts;
121✔
52
}
×
53

54
Botan::X509_Cert_Options req_opts1(const std::string& algo, const std::string& sig_padding = "") {
39✔
55
   Botan::X509_Cert_Options opts("Test User 1/US/Botan Project/Testing");
39✔
56

57
   opts.uri = "https://botan.randombit.net";
39✔
58
   opts.dns = "botan.randombit.net";
39✔
59
   opts.email = "testing@randombit.net";
39✔
60
   opts.set_padding_scheme(sig_padding);
39✔
61

62
   opts.not_before("160101200000Z");
39✔
63
   opts.not_after("300101200000Z");
39✔
64

65
   opts.challenge = "zoom";
39✔
66

67
   if(algo == "RSA") {
39✔
68
      opts.constraints = Botan::Key_Constraints::KeyEncipherment;
9✔
69
   } else if(algo == "DSA" || algo == "ECDSA" || algo == "ECGDSA" || algo == "ECKCDSA") {
30✔
70
      opts.constraints = Botan::Key_Constraints::DigitalSignature;
12✔
71
   }
72

73
   return opts;
39✔
74
}
×
75

76
Botan::X509_Cert_Options req_opts2(const std::string& sig_padding = "") {
12✔
77
   Botan::X509_Cert_Options opts("Test User 2/US/Botan Project/Testing");
12✔
78

79
   opts.uri = "https://botan.randombit.net";
12✔
80
   opts.dns = "botan.randombit.net";
12✔
81
   opts.email = "testing@randombit.net";
12✔
82
   opts.set_padding_scheme(sig_padding);
12✔
83

84
   opts.add_ex_constraint("PKIX.EmailProtection");
12✔
85

86
   return opts;
12✔
87
}
×
88

89
Botan::X509_Cert_Options req_opts3(const std::string& sig_padding = "") {
60✔
90
   Botan::X509_Cert_Options opts("Test User 2/US/Botan Project/Testing");
60✔
91

92
   opts.uri = "https://botan.randombit.net";
60✔
93
   opts.dns = "botan.randombit.net";
60✔
94
   opts.email = "testing@randombit.net";
60✔
95
   opts.set_padding_scheme(sig_padding);
60✔
96

97
   opts.more_org_units.push_back("IT");
120✔
98
   opts.more_org_units.push_back("Security");
120✔
99
   opts.more_dns.push_back("www.botan.randombit.net");
120✔
100

101
   return opts;
60✔
102
}
×
103

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

130
   try {
105✔
131
      return Botan::create_private_key(algo, rng, params);
105✔
132
   } catch(Botan::Not_Implemented&) {
×
133
      return {};
×
134
   }
×
135
}
105✔
136

137
Test::Result test_cert_status_strings() {
1✔
138
   Test::Result result("Certificate_Status_Code to_string");
1✔
139

140
   std::set<std::string> seen;
1✔
141

142
   result.test_eq("Same string",
1✔
143
                  Botan::to_string(Botan::Certificate_Status_Code::OK),
144
                  Botan::to_string(Botan::Certificate_Status_Code::VERIFIED));
145

146
   const Botan::Certificate_Status_Code codes[]{
1✔
147
      Botan::Certificate_Status_Code::OCSP_RESPONSE_GOOD,
148
      Botan::Certificate_Status_Code::OCSP_SIGNATURE_OK,
149
      Botan::Certificate_Status_Code::VALID_CRL_CHECKED,
150
      Botan::Certificate_Status_Code::OCSP_NO_HTTP,
151

152
      Botan::Certificate_Status_Code::CERT_SERIAL_NEGATIVE,
153
      Botan::Certificate_Status_Code::DN_TOO_LONG,
154

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

195
   for(const auto code : codes) {
45✔
196
      const std::string s = Botan::to_string(code);
44✔
197
      result.confirm("String is long enough to be informative", s.size() > 12);
88✔
198
      result.test_eq("No duplicates", seen.count(s), 0);
44✔
199
      seen.insert(s);
44✔
200
   }
44✔
201

202
   return result;
1✔
203
}
1✔
204

205
Test::Result test_x509_extension() {
1✔
206
   Test::Result result("X509 Extensions API");
1✔
207

208
   Botan::Extensions extn;
1✔
209

210
   const auto oid_bc = Botan::OID::from_string("X509v3.BasicConstraints");
1✔
211
   const auto oid_skid = Botan::OID::from_string("X509v3.SubjectKeyIdentifier");
1✔
212

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

215
   result.confirm("Basic constraints is set", extn.extension_set(oid_bc));
2✔
216
   result.confirm("Basic constraints is critical", extn.critical_extension_set(oid_bc));
2✔
217
   result.confirm("SKID is not set", !extn.extension_set(oid_skid));
2✔
218
   result.confirm("SKID is not critical", !extn.critical_extension_set(oid_skid));
2✔
219

220
   result.test_eq("Extension::get_extension_bits", extn.get_extension_bits(oid_bc), "30060101FF020100");
2✔
221

222
   result.test_throws("Extension::get_extension_bits throws if not set", [&]() { extn.get_extension_bits(oid_skid); });
3✔
223

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

227
   result.test_eq("Extension::get_extension_bits", extn.get_extension_bits(oid_bc), "30060101FF020100");
2✔
228

229
   result.confirm("Returns false since extension already existed",
1✔
230
                  !extn.add_new(std::make_unique<Botan::Cert_Extension::Basic_Constraints>(false), false));
2✔
231

232
   result.confirm("Basic constraints is still critical", extn.critical_extension_set(oid_bc));
2✔
233

234
   extn.replace(std::make_unique<Botan::Cert_Extension::Basic_Constraints>(false), false);
2✔
235
   result.confirm("Replaced basic constraints is not critical", !extn.critical_extension_set(oid_bc));
2✔
236
   result.test_eq("Extension::get_extension_bits", extn.get_extension_bits(oid_bc), "3000");
2✔
237

238
   result.confirm("Delete returns false if extn not set", !extn.remove(oid_skid));
2✔
239
   result.confirm("Delete returns true if extn was set", extn.remove(oid_bc));
2✔
240
   result.confirm("Basic constraints is not set", !extn.extension_set(oid_bc));
2✔
241
   result.confirm("Basic constraints is not critical", !extn.critical_extension_set(oid_bc));
2✔
242

243
   return result;
2✔
244
}
2✔
245

246
Test::Result test_x509_dates() {
1✔
247
   Test::Result result("X509 Time");
1✔
248

249
   Botan::X509_Time time;
1✔
250
   result.confirm("unset time not set", !time.time_is_set());
2✔
251
   time = Botan::X509_Time("080201182200Z", Botan::ASN1_Type::UtcTime);
1✔
252
   result.confirm("time set after construction", time.time_is_set());
2✔
253
   result.test_eq("time readable_string", time.readable_string(), "2008/02/01 18:22:00 UTC");
2✔
254

255
   time = Botan::X509_Time("200305100350Z", Botan::ASN1_Type::UtcTime);
1✔
256
   result.test_eq("UTC_TIME readable_string", time.readable_string(), "2020/03/05 10:03:50 UTC");
2✔
257

258
   time = Botan::X509_Time("200305100350Z");
1✔
259
   result.test_eq(
3✔
260
      "UTC_OR_GENERALIZED_TIME from UTC_TIME readable_string", time.readable_string(), "2020/03/05 10:03:50 UTC");
2✔
261

262
   time = Botan::X509_Time("20200305100350Z");
1✔
263
   result.test_eq("UTC_OR_GENERALIZED_TIME from GENERALIZED_TIME readable_string",
3✔
264
                  time.readable_string(),
2✔
265
                  "2020/03/05 10:03:50 UTC");
266

267
   time = Botan::X509_Time("20200305100350Z", Botan::ASN1_Type::GeneralizedTime);
1✔
268
   result.test_eq("GENERALIZED_TIME readable_string", time.readable_string(), "2020/03/05 10:03:50 UTC");
2✔
269

270
   // Dates that are valid per X.500 but rejected as unsupported
271
   const std::string valid_but_unsup[]{
1✔
272
      "0802010000-0000",
273
      "0802011724+0000",
274
      "0406142334-0500",
275
      "9906142334+0500",
276
      "0006142334-0530",
277
      "0006142334+0530",
278

279
      "080201000000-0000",
280
      "080201172412+0000",
281
      "040614233433-0500",
282
      "990614233444+0500",
283
      "000614233455-0530",
284
      "000614233455+0530",
285
   };
13✔
286

287
   // valid length 13
288
   const std::string valid_utc[]{
1✔
289
      "080201000000Z",
290
      "080201172412Z",
291
      "040614233433Z",
292
      "990614233444Z",
293
      "000614233455Z",
294
   };
6✔
295

296
   const std::string invalid_utc[]{
1✔
297
      "",
298
      " ",
299
      "2008`02-01",
300
      "9999-02-01",
301
      "2000-02-01 17",
302
      "999921",
303

304
      // No seconds
305
      "0802010000Z",
306
      "0802011724Z",
307
      "0406142334Z",
308
      "9906142334Z",
309
      "0006142334Z",
310

311
      // valid length 13 -> range check
312
      "080201000061Z",  // seconds too big (61)
313
      "080201000060Z",  // seconds too big (60, leap seconds not covered by the standard)
314
      "0802010000-1Z",  // seconds too small (-1)
315
      "080201006000Z",  // minutes too big (60)
316
      "080201240000Z",  // hours too big (24:00)
317

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

344
      // wrong time zone
345
      "080201000000",
346
      "080201000000z",
347

348
      // Fractional seconds
349
      "170217180154.001Z",
350

351
      // Timezone offset
352
      "170217180154+0100",
353

354
      // Extra digits
355
      "17021718015400Z",
356

357
      // Non-digits
358
      "17021718015aZ",
359

360
      // Trailing garbage
361
      "170217180154Zlongtrailinggarbage",
362

363
      // Swapped type
364
      "20170217180154Z",
365
   };
49✔
366

367
   // valid length 15
368
   const std::string valid_generalized_time[]{
1✔
369
      "20000305100350Z",
370
   };
2✔
371

372
   const std::string invalid_generalized[]{
1✔
373
      // No trailing Z
374
      "20000305100350",
375

376
      // No seconds
377
      "200003051003Z",
378

379
      // Fractional seconds
380
      "20000305100350.001Z",
381

382
      // Timezone offset
383
      "20170217180154+0100",
384

385
      // Extra digits
386
      "2017021718015400Z",
387

388
      // Non-digits
389
      "2017021718015aZ",
390

391
      // Trailing garbage
392
      "20170217180154Zlongtrailinggarbage",
393

394
      // Swapped type
395
      "170217180154Z",
396
   };
9✔
397

398
   for(const auto& v : valid_but_unsup) {
13✔
399
      result.test_throws("valid but unsupported", [v]() { Botan::X509_Time t(v, Botan::ASN1_Type::UtcTime); });
108✔
400
   }
401

402
   for(const auto& v : valid_utc) {
6✔
403
      Botan::X509_Time t(v, Botan::ASN1_Type::UtcTime);
5✔
404
   }
5✔
405

406
   for(const auto& v : valid_generalized_time) {
2✔
407
      Botan::X509_Time t(v, Botan::ASN1_Type::GeneralizedTime);
1✔
408
   }
1✔
409

410
   for(const auto& v : invalid_utc) {
49✔
411
      result.test_throws("invalid", [v]() { Botan::X509_Time t(v, Botan::ASN1_Type::UtcTime); });
432✔
412
   }
413

414
   for(const auto& v : invalid_generalized) {
9✔
415
      result.test_throws("invalid", [v]() { Botan::X509_Time t(v, Botan::ASN1_Type::GeneralizedTime); });
72✔
416
   }
417

418
   return result;
1✔
419
}
80✔
420

421
Test::Result test_x509_encode_authority_info_access_extension() {
1✔
422
   Test::Result result("X509 with encoded PKIX.AuthorityInformationAccess extension");
1✔
423

424
   #if defined(BOTAN_HAS_RSA)
425
   auto rng = Test::new_rng(__func__);
1✔
426

427
   const std::string sig_algo{"RSA"};
1✔
428
   const std::string hash_fn{"SHA-256"};
1✔
429
   const std::string padding_method{"EMSA3(SHA-256)"};
1✔
430

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

436
   // OCSP
437
   const std::string_view ocsp_uri{"http://staging.ocsp.d-trust.net"};
1✔
438

439
   // create a CA
440
   auto ca_key = make_a_private_key(sig_algo, *rng);
1✔
441
   result.require("CA key", ca_key != nullptr);
1✔
442
   const auto ca_cert = Botan::X509::create_self_signed_cert(ca_opts(), *ca_key, hash_fn, *rng);
1✔
443
   Botan::X509_CA ca(ca_cert, *ca_key, hash_fn, padding_method, *rng);
1✔
444

445
   // create a certificate with only caIssuer information
446
   auto key = make_a_private_key(sig_algo, *rng);
1✔
447

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

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

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

455
   if(!result.test_eq("number of ca_issuers URIs", cert.ca_issuers().size(), 2)) {
1✔
456
      return result;
457
   }
458

459
   for(const auto& ca_issuer : cert.ca_issuers()) {
3✔
460
      result.confirm("CA issuer URI present in certificate",
4✔
461
                     std::ranges::find(ca_issuers, ca_issuer) != ca_issuers.end());
4✔
462
   }
1✔
463

464
   result.confirm("no OCSP url available", cert.ocsp_responder().empty());
2✔
465

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

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

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

474
   result.confirm("OCSP URI available", !cert.ocsp_responder().empty());
2✔
475
   result.confirm("no CA Issuer URI available", cert.ca_issuers().empty());
2✔
476
   result.test_eq("OCSP responder URI matches", cert.ocsp_responder(), std::string(ocsp_uri));
3✔
477

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

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

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

486
   result.confirm("OCSP URI available", !cert.ocsp_responder().empty());
2✔
487
   result.confirm("CA Issuer URI available", !cert.ca_issuers().empty());
2✔
488
   #endif
489

490
   return result;
1✔
491
}
4✔
492

493
   #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
494

495
Test::Result test_crl_dn_name() {
1✔
496
   Test::Result result("CRL DN name");
1✔
497

498
      // See GH #1252
499

500
      #if defined(BOTAN_HAS_RSA) && defined(BOTAN_HAS_EMSA_PKCS1)
501
   auto rng = Test::new_rng(__func__);
1✔
502

503
   const Botan::OID dc_oid("0.9.2342.19200300.100.1.25");
1✔
504

505
   Botan::X509_Certificate cert(Test::data_file("x509/misc/opcuactt_ca.der"));
2✔
506

507
   Botan::DataSource_Stream key_input(Test::data_file("x509/misc/opcuactt_ca.pem"));
2✔
508
   auto key = Botan::PKCS8::load_key(key_input);
1✔
509
   Botan::X509_CA ca(cert, *key, "SHA-256", *rng);
1✔
510

511
   Botan::X509_CRL crl = ca.new_crl(*rng);
1✔
512

513
   result.confirm("matches issuer cert", crl.issuer_dn() == cert.subject_dn());
2✔
514

515
   result.confirm("contains DC component", crl.issuer_dn().get_attributes().count(dc_oid) == 1);
3✔
516
      #endif
517

518
   return result;
2✔
519
}
3✔
520

521
Test::Result test_rdn_multielement_set_name() {
1✔
522
   Test::Result result("DN with multiple elements in RDN");
1✔
523

524
   // GH #2611
525

526
   Botan::X509_Certificate cert(Test::data_file("x509/misc/rdn_set.crt"));
2✔
527

528
   result.confirm("issuer DN contains expected name components", cert.issuer_dn().get_attributes().size() == 4);
2✔
529
   result.confirm("subject DN contains expected name components", cert.subject_dn().get_attributes().size() == 4);
2✔
530

531
   return result;
1✔
532
}
1✔
533

534
Test::Result test_rsa_oaep() {
1✔
535
   Test::Result result("RSA OAEP decoding");
1✔
536

537
      #if defined(BOTAN_HAS_RSA)
538
   Botan::X509_Certificate cert(Test::data_file("x509/misc/rsa_oaep.pem"));
2✔
539

540
   auto public_key = cert.subject_public_key();
1✔
541
   result.test_not_null("Decoding RSA-OAEP worked", public_key.get());
1✔
542
   const auto& pk_info = cert.subject_public_key_algo();
1✔
543

544
   result.test_eq("RSA-OAEP OID", pk_info.oid().to_string(), Botan::OID::from_string("RSA/OAEP").to_string());
2✔
545
      #endif
546

547
   return result;
2✔
548
}
1✔
549

550
Test::Result test_x509_decode_list() {
1✔
551
   Test::Result result("X509_Certificate list decode");
1✔
552

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

555
   Botan::BER_Decoder dec(input);
1✔
556
   std::vector<Botan::X509_Certificate> certs;
1✔
557
   dec.decode_list(certs);
1✔
558

559
   result.test_eq("Expected number of certs in list", certs.size(), 2);
1✔
560

561
   result.test_eq("Expected cert 1 CN", certs[0].subject_dn().get_first_attribute("CN"), "CA1-PP.01.02");
2✔
562
   result.test_eq("Expected cert 2 CN", certs[1].subject_dn().get_first_attribute("CN"), "User1-PP.01.02");
2✔
563

564
   return result;
1✔
565
}
1✔
566

567
Test::Result test_x509_utf8() {
1✔
568
   Test::Result result("X509 with UTF-8 encoded fields");
1✔
569

570
   try {
1✔
571
      Botan::X509_Certificate utf8_cert(Test::data_file("x509/misc/contains_utf8string.pem"));
2✔
572

573
      // UTF-8 encoded fields of test certificate (contains cyrillic letters)
574
      const std::string organization =
1✔
575
         "\xD0\x9C\xD0\xBE\xD1\x8F\x20\xD0\xBA\xD0\xBE\xD0"
576
         "\xBC\xD0\xBF\xD0\xB0\xD0\xBD\xD0\xB8\xD1\x8F";
1✔
577
      const std::string organization_unit =
1✔
578
         "\xD0\x9C\xD0\xBE\xD1\x91\x20\xD0\xBF\xD0\xBE\xD0\xB4\xD1\x80\xD0\xB0"
579
         "\xD0\xB7\xD0\xB4\xD0\xB5\xD0\xBB\xD0\xB5\xD0\xBD\xD0\xB8\xD0\xB5";
1✔
580
      const std::string common_name =
1✔
581
         "\xD0\x9E\xD0\xBF\xD0\xB8\xD1\x81\xD0\xB0\xD0\xBD\xD0\xB8"
582
         "\xD0\xB5\x20\xD1\x81\xD0\xB0\xD0\xB9\xD1\x82\xD0\xB0";
1✔
583
      const std::string location = "\xD0\x9C\xD0\xBE\xD1\x81\xD0\xBA\xD0\xB2\xD0\xB0";
1✔
584

585
      const Botan::X509_DN& issuer_dn = utf8_cert.issuer_dn();
1✔
586

587
      result.test_eq("O", issuer_dn.get_first_attribute("O"), organization);
2✔
588
      result.test_eq("OU", issuer_dn.get_first_attribute("OU"), organization_unit);
2✔
589
      result.test_eq("CN", issuer_dn.get_first_attribute("CN"), common_name);
2✔
590
      result.test_eq("L", issuer_dn.get_first_attribute("L"), location);
2✔
591
   } catch(const Botan::Decoding_Error& ex) {
1✔
592
      result.test_failure(ex.what());
×
593
   }
×
594

595
   return result;
1✔
596
}
×
597

598
Test::Result test_x509_bmpstring() {
1✔
599
   Test::Result result("X509 with UCS-2 (BMPString) encoded fields");
1✔
600

601
   try {
1✔
602
      Botan::X509_Certificate ucs2_cert(Test::data_file("x509/misc/contains_bmpstring.pem"));
2✔
603

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

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

612
      const Botan::X509_DN& issuer_dn = ucs2_cert.issuer_dn();
1✔
613

614
      result.test_eq("O", issuer_dn.get_first_attribute("O"), organization);
2✔
615
      result.test_eq("CN", issuer_dn.get_first_attribute("CN"), common_name);
2✔
616
      result.test_eq("L", issuer_dn.get_first_attribute("L"), location);
2✔
617
   } catch(const Botan::Decoding_Error& ex) {
1✔
618
      result.test_failure(ex.what());
×
619
   }
×
620

621
   return result;
1✔
622
}
×
623

624
Test::Result test_x509_teletex() {
1✔
625
   Test::Result result("X509 with TeletexString encoded fields");
1✔
626

627
   try {
1✔
628
      Botan::X509_Certificate teletex_cert(Test::data_file("x509/misc/teletex_dn.der"));
2✔
629

630
      const Botan::X509_DN& issuer_dn = teletex_cert.issuer_dn();
1✔
631

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

634
      result.test_eq("O", issuer_dn.get_first_attribute("O"), "neam CA");
2✔
635
      result.test_eq("CN", issuer_dn.get_first_attribute("CN"), common_name);
2✔
636
   } catch(const Botan::Decoding_Error& ex) {
1✔
637
      result.test_failure(ex.what());
×
638
   }
×
639

640
   return result;
1✔
641
}
×
642

643
Test::Result test_x509_authority_info_access_extension() {
1✔
644
   Test::Result result("X509 with PKIX.AuthorityInformationAccess extension");
1✔
645

646
   // contains no AIA extension
647
   Botan::X509_Certificate no_aia_cert(Test::data_file("x509/misc/contains_utf8string.pem"));
2✔
648

649
   result.test_eq("number of ca_issuers URLs", no_aia_cert.ca_issuers().size(), 0);
2✔
650
   result.test_eq("CA issuer URL matches", no_aia_cert.ocsp_responder(), "");
2✔
651

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

655
   const auto ca_issuers = aia_cert.ca_issuers();
1✔
656

657
   result.test_eq("number of ca_issuers URLs", ca_issuers.size(), 1);
1✔
658
   if(result.tests_failed()) {
1✔
659
      return result;
660
   }
661

662
   result.test_eq("CA issuer URL matches", ca_issuers[0], "http://gp.symcb.com/gp.crt");
2✔
663
   result.test_eq("OCSP responder URL matches", aia_cert.ocsp_responder(), "http://gp.symcd.com");
2✔
664

665
   // contains AIA extension with 2 CA issuer URL and 1 OCSP responder
666
   Botan::X509_Certificate aia_cert_2ca(
1✔
667
      Test::data_file("x509/misc/contains_authority_info_access_with_two_ca_issuers.pem"));
2✔
668

669
   const auto ca_issuers2 = aia_cert_2ca.ca_issuers();
1✔
670

671
   result.test_eq("number of ca_issuers URLs", ca_issuers2.size(), 2);
1✔
672
   if(result.tests_failed()) {
1✔
673
      return result;
674
   }
675

676
   result.test_eq(
2✔
677
      "CA issuer URL matches", ca_issuers2[0], "http://www.d-trust.net/cgi-bin/Bdrive_Test_CA_1-2_2017.crt");
1✔
678
   result.test_eq(
2✔
679
      "CA issuer URL matches",
680
      ca_issuers2[1],
1✔
681
      "ldap://directory.d-trust.net/CN=Bdrive%20Test%20CA%201-2%202017,O=Bundesdruckerei%20GmbH,C=DE?cACertificate?base?");
682
   result.test_eq("OCSP responder URL matches", aia_cert_2ca.ocsp_responder(), "http://staging.ocsp.d-trust.net");
2✔
683

684
   return result;
1✔
685
}
1✔
686

687
Test::Result test_parse_rsa_pss_cert() {
1✔
688
   Test::Result result("X509 RSA-PSS certificate");
1✔
689

690
   // See https://github.com/randombit/botan/issues/3019 for background
691

692
   try {
1✔
693
      Botan::X509_Certificate rsa_pss(Test::data_file("x509/misc/rsa_pss.pem"));
2✔
694
      result.test_success("Was able to parse RSA-PSS certificate signed with ECDSA");
1✔
695
   } catch(Botan::Exception& e) {
1✔
696
      result.test_failure("Parsing failed", e.what());
×
697
   }
×
698

699
   return result;
1✔
700
}
×
701

702
Test::Result test_verify_gost2012_cert() {
1✔
703
   Test::Result result("X509 GOST-2012 certificates");
1✔
704

705
      #if defined(BOTAN_HAS_GOST_34_10_2012) && defined(BOTAN_HAS_STREEBOG)
706
   try {
1✔
707
      if(Botan::EC_Group::supports_named_group("gost_256A")) {
1✔
708
         Botan::X509_Certificate root_cert(Test::data_file("x509/gost/gost_root.pem"));
2✔
709
         Botan::X509_Certificate root_int(Test::data_file("x509/gost/gost_int.pem"));
2✔
710

711
         Botan::Certificate_Store_In_Memory trusted;
1✔
712
         trusted.add_certificate(root_cert);
1✔
713

714
         const Botan::Path_Validation_Restrictions restrictions(false, 128, false, {"Streebog-256"});
2✔
715
         const Botan::Path_Validation_Result validation_result =
1✔
716
            Botan::x509_path_validate(root_int, restrictions, trusted);
1✔
717

718
         result.confirm("GOST certificate validates", validation_result.successful_validation());
2✔
719
      }
1✔
720
   } catch(const Botan::Decoding_Error& e) {
×
721
      result.test_failure(e.what());
×
722
   }
×
723
      #endif
724

725
   return result;
1✔
726
}
×
727

728
      /*
729
 * @brief checks the configurability of the EMSA4(RSA-PSS) signature scheme
730
 *
731
 * For the other algorithms than RSA, only one padding is supported right now.
732
 */
733
      #if defined(BOTAN_HAS_EMSA_PKCS1) && defined(BOTAN_HAS_EMSA_PSSR) && defined(BOTAN_HAS_RSA)
734
Test::Result test_padding_config() {
1✔
735
   // Throughout the test, some synonyms for EMSA4 are used, e.g. PSSR, EMSA-PSS
736
   Test::Result test_result("X509 Padding Config");
1✔
737

738
   auto rng = Test::new_rng(__func__);
1✔
739

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

743
   // Create X509 CA certificate; EMSA3 is used for signing by default
744
   Botan::X509_Cert_Options opt("TESTCA");
1✔
745
   opt.CA_key();
1✔
746

747
   Botan::X509_Certificate ca_cert_def = Botan::X509::create_self_signed_cert(opt, (*sk), "SHA-512", *rng);
1✔
748
   test_result.test_eq("CA certificate signature algorithm (default)",
3✔
749
                       ca_cert_def.signature_algorithm().oid().to_formatted_string(),
2✔
750
                       "RSA/PKCS1v15(SHA-512)");
751

752
   // Create X509 CA certificate; RSA-PSS is explicitly set
753
   opt.set_padding_scheme("PSSR");
1✔
754
   Botan::X509_Certificate ca_cert_exp = Botan::X509::create_self_signed_cert(opt, (*sk), "SHA-512", *rng);
1✔
755
   test_result.test_eq("CA certificate signature algorithm (explicit)",
3✔
756
                       ca_cert_exp.signature_algorithm().oid().to_formatted_string(),
2✔
757
                       "RSA/PSS");
758

759
         #if defined(BOTAN_HAS_EMSA_X931)
760
   // Try to set a padding scheme that is not supported for signing with the given key type
761
   opt.set_padding_scheme("EMSA2");
1✔
762
   try {
1✔
763
      Botan::X509_Certificate ca_cert_wrong = Botan::X509::create_self_signed_cert(opt, (*sk), "SHA-512", *rng);
1✔
764
      test_result.test_failure("Could build CA cert with invalid encoding scheme X9.31 for key type " +
×
765
                               sk->algo_name());
×
766
   } catch(const Botan::Invalid_Argument& e) {
1✔
767
      test_result.test_eq("Build CA certificate with invalid encoding scheme X9.31 for key type " + sk->algo_name(),
3✔
768
                          e.what(),
1✔
769
                          "Signatures using RSA/X9.31(SHA-512) are not supported");
770
   }
1✔
771
         #endif
772

773
   test_result.test_eq("CA certificate signature algorithm (explicit)",
3✔
774
                       ca_cert_exp.signature_algorithm().oid().to_formatted_string(),
2✔
775
                       "RSA/PSS");
776

777
   const Botan::X509_Time not_before = from_date(-1, 1, 1);
1✔
778
   const Botan::X509_Time not_after = from_date(2, 1, 2);
1✔
779

780
   // Prepare a signing request for the end certificate
781
   Botan::X509_Cert_Options req_opt("endpoint");
1✔
782
   req_opt.set_padding_scheme("EMSA4(SHA-512,MGF1,64)");
1✔
783
   Botan::PKCS10_Request end_req = Botan::X509::create_cert_req(req_opt, (*sk), "SHA-512", *rng);
1✔
784
   test_result.test_eq(
3✔
785
      "Certificate request signature algorithm", end_req.signature_algorithm().oid().to_formatted_string(), "RSA/PSS");
2✔
786

787
   // Create X509 CA object: will fail as the chosen hash functions differ
788
   try {
1✔
789
      Botan::X509_CA ca_fail(ca_cert_exp, (*sk), "SHA-512", "EMSA4(SHA-256)", *rng);
1✔
790
      test_result.test_failure("Configured conflicting hash functions for CA");
×
791
   } catch(const Botan::Invalid_Argument& e) {
1✔
792
      test_result.test_eq(
1✔
793
         "Configured conflicting hash functions for CA",
794
         e.what(),
1✔
795
         "Specified hash function SHA-512 is incompatible with RSA chose hash function SHA-256 with user specified padding EMSA4(SHA-256)");
796
   }
1✔
797

798
   // Create X509 CA object: its signer will use the padding scheme from the CA certificate, i.e. EMSA3
799
   Botan::X509_CA ca_def(ca_cert_def, (*sk), "SHA-512", *rng);
1✔
800
   Botan::X509_Certificate end_cert_emsa3 = ca_def.sign_request(end_req, *rng, not_before, not_after);
1✔
801
   test_result.test_eq("End certificate signature algorithm",
3✔
802
                       end_cert_emsa3.signature_algorithm().oid().to_formatted_string(),
2✔
803
                       "RSA/PKCS1v15(SHA-512)");
804

805
   // Create X509 CA object: its signer will use the explicitly configured padding scheme, which is different from the CA certificate's scheme
806
   Botan::X509_CA ca_diff(ca_cert_def, (*sk), "SHA-512", "EMSA-PSS", *rng);
1✔
807
   Botan::X509_Certificate end_cert_diff_emsa4 = ca_diff.sign_request(end_req, *rng, not_before, not_after);
1✔
808
   test_result.test_eq("End certificate signature algorithm",
3✔
809
                       end_cert_diff_emsa4.signature_algorithm().oid().to_formatted_string(),
2✔
810
                       "RSA/PSS");
811

812
   // Create X509 CA object: its signer will use the explicitly configured padding scheme, which is identical to the CA certificate's scheme
813
   Botan::X509_CA ca_exp(ca_cert_exp, (*sk), "SHA-512", "EMSA4(SHA-512,MGF1,64)", *rng);
1✔
814
   Botan::X509_Certificate end_cert_emsa4 = ca_exp.sign_request(end_req, *rng, not_before, not_after);
1✔
815
   test_result.test_eq("End certificate signature algorithm",
3✔
816
                       end_cert_emsa4.signature_algorithm().oid().to_formatted_string(),
2✔
817
                       "RSA/PSS");
818

819
   // Check CRL signature algorithm
820
   Botan::X509_CRL crl = ca_exp.new_crl(*rng);
1✔
821
   test_result.test_eq("CRL signature algorithm", crl.signature_algorithm().oid().to_formatted_string(), "RSA/PSS");
2✔
822

823
   // sanity check for verification, the heavy lifting is done in the other unit tests
824
   const Botan::Certificate_Store_In_Memory trusted(ca_exp.ca_certificate());
1✔
825
   const Botan::Path_Validation_Restrictions restrictions(false, 80);
2✔
826
   const Botan::Path_Validation_Result validation_result =
1✔
827
      Botan::x509_path_validate(end_cert_emsa4, restrictions, trusted);
1✔
828
   test_result.confirm("EMSA4-signed certificate validates", validation_result.successful_validation());
2✔
829

830
   return test_result;
2✔
831
}
3✔
832
      #endif
833

834
   #endif
835

836
Test::Result test_pkcs10_ext(const Botan::Private_Key& key,
12✔
837
                             const std::string& sig_padding,
838
                             const std::string& hash_fn,
839
                             Botan::RandomNumberGenerator& rng) {
840
   Test::Result result("PKCS10 extensions");
12✔
841

842
   Botan::X509_Cert_Options opts;
12✔
843

844
   opts.dns = "main.example.org";
12✔
845
   opts.more_dns.push_back("more1.example.org");
24✔
846
   opts.more_dns.push_back("more2.example.org");
24✔
847

848
   opts.padding_scheme = sig_padding;
12✔
849

850
   Botan::AlternativeName alt_name;
12✔
851
   alt_name.add_attribute("DNS", "bonus.example.org");
12✔
852

853
   Botan::X509_DN alt_dn;
12✔
854
   alt_dn.add_attribute("X520.CommonName", "alt_cn");
12✔
855
   alt_dn.add_attribute("X520.Organization", "testing");
12✔
856
   alt_name.add_dn(alt_dn);
12✔
857

858
   opts.extensions.add(std::make_unique<Botan::Cert_Extension::Subject_Alternative_Name>(alt_name));
24✔
859

860
   const auto req = Botan::X509::create_cert_req(opts, key, hash_fn, rng);
12✔
861

862
   const auto alt_dns_names = req.subject_alt_name().get_attribute("DNS");
12✔
863

864
   result.test_eq("Expected number of DNS names", alt_dns_names.size(), 4);
12✔
865

866
   if(alt_dns_names.size() == 4) {
12✔
867
      result.test_eq("Expected DNS name 1", alt_dns_names.at(0), "bonus.example.org");
36✔
868
      result.test_eq("Expected DNS name 2", alt_dns_names.at(1), "main.example.org");
36✔
869
      result.test_eq("Expected DNS name 3", alt_dns_names.at(2), "more1.example.org");
36✔
870
      result.test_eq("Expected DNS name 3", alt_dns_names.at(3), "more2.example.org");
36✔
871
   }
872

873
   result.test_eq("Expected number of alt DNs", req.subject_alt_name().directory_names().size(), 1);
12✔
874
   result.confirm("Alt DN is correct", *req.subject_alt_name().directory_names().begin() == alt_dn);
24✔
875

876
   return result;
12✔
877
}
12✔
878

879
Test::Result test_x509_cert(const Botan::Private_Key& ca_key,
12✔
880
                            const std::string& sig_algo,
881
                            const std::string& sig_padding,
882
                            const std::string& hash_fn,
883
                            Botan::RandomNumberGenerator& rng) {
884
   Test::Result result("X509 Unit");
12✔
885

886
   /* Create the self-signed cert */
887
   const auto ca_cert = Botan::X509::create_self_signed_cert(ca_opts(sig_padding), ca_key, hash_fn, rng);
12✔
888

889
   {
12✔
890
      result.confirm("ca key usage cert", ca_cert.constraints().includes(Botan::Key_Constraints::KeyCertSign));
24✔
891
      result.confirm("ca key usage crl", ca_cert.constraints().includes(Botan::Key_Constraints::CrlSign));
24✔
892
   }
893

894
   /* Create user #1's key and cert request */
895
   auto user1_key = make_a_private_key(sig_algo, rng);
12✔
896

897
   Botan::PKCS10_Request user1_req =
12✔
898
      Botan::X509::create_cert_req(req_opts1(sig_algo, sig_padding), *user1_key, hash_fn, rng);
12✔
899

900
   result.test_eq("PKCS10 challenge password parsed", user1_req.challenge_password(), "zoom");
24✔
901

902
   /* Create user #2's key and cert request */
903
   auto user2_key = make_a_private_key(sig_algo, rng);
12✔
904

905
   Botan::PKCS10_Request user2_req = Botan::X509::create_cert_req(req_opts2(sig_padding), *user2_key, hash_fn, rng);
12✔
906

907
   // /* Create user #3's key and cert request */
908
   auto user3_key = make_a_private_key(sig_algo, rng);
12✔
909

910
   Botan::PKCS10_Request user3_req = Botan::X509::create_cert_req(req_opts3(sig_padding), *user3_key, hash_fn, rng);
12✔
911

912
   /* Create the CA object */
913
   Botan::X509_CA ca(ca_cert, ca_key, hash_fn, sig_padding, rng);
12✔
914

915
   const BigInt user1_serial = 99;
12✔
916

917
   /* Sign the requests to create the certs */
918
   Botan::X509_Certificate user1_cert =
12✔
919
      ca.sign_request(user1_req, rng, user1_serial, from_date(-1, 01, 01), from_date(2, 01, 01));
12✔
920

921
   result.test_eq("User1 serial size matches expected", user1_cert.serial_number().size(), 1);
12✔
922
   result.test_eq("User1 serial matches expected", user1_cert.serial_number().at(0), size_t(99));
12✔
923

924
   Botan::X509_Certificate user2_cert = ca.sign_request(user2_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
12✔
925

926
   Botan::X509_Certificate user3_cert = ca.sign_request(user3_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
12✔
927

928
   // user#1 creates a self-signed cert on the side
929
   const auto user1_ss_cert =
12✔
930
      Botan::X509::create_self_signed_cert(req_opts1(sig_algo, sig_padding), *user1_key, hash_fn, rng);
12✔
931

932
   {
12✔
933
      auto constraints = req_opts1(sig_algo).constraints;
12✔
934
      result.confirm("user1 key usage", user1_cert.constraints().includes(constraints));
24✔
935
   }
936

937
   /* Copy, assign and compare */
938
   Botan::X509_Certificate user1_cert_copy(user1_cert);
12✔
939
   result.test_eq("certificate copy", user1_cert == user1_cert_copy, true);
12✔
940

941
   user1_cert_copy = user2_cert;
12✔
942
   result.test_eq("certificate assignment", user2_cert == user1_cert_copy, true);
12✔
943

944
   Botan::X509_Certificate user1_cert_differ =
12✔
945
      ca.sign_request(user1_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
12✔
946

947
   result.test_eq("certificate differs", user1_cert == user1_cert_differ, false);
12✔
948

949
   /* Get cert data */
950
   result.test_eq("x509 version", user1_cert.x509_version(), size_t(3));
12✔
951

952
   const Botan::X509_DN& user1_issuer_dn = user1_cert.issuer_dn();
12✔
953
   result.test_eq("issuer info CN", user1_issuer_dn.get_first_attribute("CN"), ca_opts().common_name);
24✔
954
   result.test_eq("issuer info Country", user1_issuer_dn.get_first_attribute("C"), ca_opts().country);
24✔
955
   result.test_eq("issuer info Orga", user1_issuer_dn.get_first_attribute("O"), ca_opts().organization);
24✔
956
   result.test_eq("issuer info OrgaUnit", user1_issuer_dn.get_first_attribute("OU"), ca_opts().org_unit);
24✔
957

958
   const Botan::X509_DN& user3_subject_dn = user3_cert.subject_dn();
12✔
959
   result.test_eq("subject OrgaUnit count",
12✔
960
                  user3_subject_dn.get_attribute("OU").size(),
24✔
961
                  req_opts3(sig_algo).more_org_units.size() + 1);
24✔
962
   result.test_eq(
12✔
963
      "subject OrgaUnit #2", user3_subject_dn.get_attribute("OU").at(1), req_opts3(sig_algo).more_org_units.at(0));
48✔
964

965
   const Botan::AlternativeName& user1_altname = user1_cert.subject_alt_name();
12✔
966
   result.test_eq("subject alt email", user1_altname.get_first_attribute("RFC822"), "testing@randombit.net");
24✔
967
   result.test_eq("subject alt dns", user1_altname.get_first_attribute("DNS"), "botan.randombit.net");
24✔
968
   result.test_eq("subject alt uri", user1_altname.get_first_attribute("URI"), "https://botan.randombit.net");
24✔
969

970
   const Botan::AlternativeName& user3_altname = user3_cert.subject_alt_name();
12✔
971
   result.test_eq(
12✔
972
      "subject alt dns count", user3_altname.get_attribute("DNS").size(), req_opts3(sig_algo).more_dns.size() + 1);
24✔
973
   result.test_eq("subject alt dns #2", user3_altname.get_attribute("DNS").at(1), req_opts3(sig_algo).more_dns.at(0));
48✔
974

975
   const Botan::X509_CRL crl1 = ca.new_crl(rng);
12✔
976

977
   /* Verify the certs */
978
   Botan::Path_Validation_Restrictions restrictions(false, 80);
24✔
979
   Botan::Certificate_Store_In_Memory store;
12✔
980

981
   // First try with an empty store
982
   Botan::Path_Validation_Result result_no_issuer = Botan::x509_path_validate(user1_cert, restrictions, store);
12✔
983
   result.test_eq("user 1 issuer not found",
36✔
984
                  result_no_issuer.result_string(),
24✔
985
                  Botan::Path_Validation_Result::status_string(Botan::Certificate_Status_Code::CERT_ISSUER_NOT_FOUND));
986

987
   store.add_certificate(ca.ca_certificate());
12✔
988

989
   Botan::Path_Validation_Result result_u1 = Botan::x509_path_validate(user1_cert, restrictions, store);
12✔
990
   if(!result.confirm("user 1 validates", result_u1.successful_validation())) {
24✔
991
      result.test_note("user 1 validation result was " + result_u1.result_string());
×
992
   }
993

994
   Botan::Path_Validation_Result result_u2 = Botan::x509_path_validate(user2_cert, restrictions, store);
12✔
995
   if(!result.confirm("user 2 validates", result_u2.successful_validation())) {
24✔
996
      result.test_note("user 2 validation result was " + result_u2.result_string());
×
997
   }
998

999
   Botan::Path_Validation_Result result_self_signed = Botan::x509_path_validate(user1_ss_cert, restrictions, store);
12✔
1000
   result.test_eq("user 1 issuer not found",
36✔
1001
                  result_no_issuer.result_string(),
24✔
1002
                  Botan::Path_Validation_Result::status_string(Botan::Certificate_Status_Code::CERT_ISSUER_NOT_FOUND));
1003
   store.add_crl(crl1);
12✔
1004

1005
   std::vector<Botan::CRL_Entry> revoked;
12✔
1006
   revoked.push_back(Botan::CRL_Entry(user1_cert, Botan::CRL_Code::CessationOfOperation));
24✔
1007
   revoked.push_back(user2_cert);
24✔
1008

1009
   const Botan::X509_CRL crl2 = ca.update_crl(crl1, revoked, rng);
12✔
1010

1011
   store.add_crl(crl2);
12✔
1012

1013
   const std::string revoked_str =
12✔
1014
      Botan::Path_Validation_Result::status_string(Botan::Certificate_Status_Code::CERT_IS_REVOKED);
12✔
1015

1016
   result_u1 = Botan::x509_path_validate(user1_cert, restrictions, store);
12✔
1017
   result.test_eq("user 1 revoked", result_u1.result_string(), revoked_str);
24✔
1018

1019
   result_u2 = Botan::x509_path_validate(user2_cert, restrictions, store);
12✔
1020
   result.test_eq("user 1 revoked", result_u2.result_string(), revoked_str);
24✔
1021

1022
   revoked.clear();
12✔
1023
   revoked.push_back(Botan::CRL_Entry(user1_cert, Botan::CRL_Code::RemoveFromCrl));
24✔
1024
   Botan::X509_CRL crl3 = ca.update_crl(crl2, revoked, rng);
12✔
1025

1026
   store.add_crl(crl3);
12✔
1027

1028
   result_u1 = Botan::x509_path_validate(user1_cert, restrictions, store);
12✔
1029
   if(!result.confirm("user 1 validates", result_u1.successful_validation())) {
24✔
1030
      result.test_note("user 1 validation result was " + result_u1.result_string());
×
1031
   }
1032

1033
   result_u2 = Botan::x509_path_validate(user2_cert, restrictions, store);
12✔
1034
   result.test_eq("user 2 still revoked", result_u2.result_string(), revoked_str);
24✔
1035

1036
   return result;
12✔
1037
}
60✔
1038

1039
Test::Result test_usage(const Botan::Private_Key& ca_key,
12✔
1040
                        const std::string& sig_algo,
1041
                        const std::string& hash_fn,
1042
                        Botan::RandomNumberGenerator& rng) {
1043
   using Botan::Key_Constraints;
12✔
1044
   using Botan::Usage_Type;
12✔
1045

1046
   Test::Result result("X509 Usage");
12✔
1047

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

1051
   /* Create the CA object */
1052
   const Botan::X509_CA ca(ca_cert, ca_key, hash_fn, rng);
12✔
1053

1054
   auto user1_key = make_a_private_key(sig_algo, rng);
12✔
1055

1056
   Botan::X509_Cert_Options opts("Test User 1/US/Botan Project/Testing");
12✔
1057
   opts.constraints = Key_Constraints::DigitalSignature;
12✔
1058

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

1061
   const Botan::X509_Certificate user1_cert =
12✔
1062
      ca.sign_request(user1_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
12✔
1063

1064
   // cert only allows digitalSignature, but we check for both digitalSignature and cRLSign
1065
   result.test_eq(
12✔
1066
      "key usage cRLSign not allowed",
1067
      user1_cert.allowed_usage(Key_Constraints(Key_Constraints::DigitalSignature | Key_Constraints::CrlSign)),
12✔
1068
      false);
1069
   result.test_eq("encryption is not allowed", user1_cert.allowed_usage(Usage_Type::ENCRYPTION), false);
12✔
1070

1071
   // cert only allows digitalSignature, so checking for only that should be ok
1072
   result.confirm("key usage digitalSignature allowed", user1_cert.allowed_usage(Key_Constraints::DigitalSignature));
24✔
1073

1074
   opts.constraints = Key_Constraints(Key_Constraints::DigitalSignature | Key_Constraints::CrlSign);
12✔
1075

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

1078
   const Botan::X509_Certificate mult_usage_cert =
12✔
1079
      ca.sign_request(mult_usage_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
12✔
1080

1081
   // cert allows multiple usages, so each one of them as well as both together should be allowed
1082
   result.confirm("key usage multiple digitalSignature allowed",
24✔
1083
                  mult_usage_cert.allowed_usage(Key_Constraints::DigitalSignature));
12✔
1084
   result.confirm("key usage multiple cRLSign allowed", mult_usage_cert.allowed_usage(Key_Constraints::CrlSign));
24✔
1085
   result.confirm(
24✔
1086
      "key usage multiple digitalSignature and cRLSign allowed",
1087
      mult_usage_cert.allowed_usage(Key_Constraints(Key_Constraints::DigitalSignature | Key_Constraints::CrlSign)));
12✔
1088
   result.test_eq("encryption is not allowed", mult_usage_cert.allowed_usage(Usage_Type::ENCRYPTION), false);
12✔
1089

1090
   opts.constraints = Key_Constraints();
12✔
1091

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

1094
   const Botan::X509_Certificate no_usage_cert =
12✔
1095
      ca.sign_request(no_usage_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
12✔
1096

1097
   // cert allows every usage
1098
   result.confirm("key usage digitalSignature allowed", no_usage_cert.allowed_usage(Key_Constraints::DigitalSignature));
24✔
1099
   result.confirm("key usage cRLSign allowed", no_usage_cert.allowed_usage(Key_Constraints::CrlSign));
24✔
1100
   result.confirm("key usage encryption allowed", no_usage_cert.allowed_usage(Usage_Type::ENCRYPTION));
24✔
1101

1102
   if(sig_algo == "RSA") {
12✔
1103
      // cert allows data encryption
1104
      opts.constraints = Key_Constraints(Key_Constraints::KeyEncipherment | Key_Constraints::DataEncipherment);
1✔
1105

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

1108
      const Botan::X509_Certificate enc_cert =
1✔
1109
         ca.sign_request(enc_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
1✔
1110

1111
      result.confirm("cert allows encryption", enc_cert.allowed_usage(Usage_Type::ENCRYPTION));
2✔
1112
      result.confirm("cert does not allow TLS client auth", !enc_cert.allowed_usage(Usage_Type::TLS_CLIENT_AUTH));
2✔
1113
   }
1✔
1114

1115
   return result;
12✔
1116
}
24✔
1117

1118
Test::Result test_self_issued(const Botan::Private_Key& ca_key,
12✔
1119
                              const std::string& sig_algo,
1120
                              const std::string& sig_padding,
1121
                              const std::string& hash_fn,
1122
                              Botan::RandomNumberGenerator& rng) {
1123
   using Botan::Key_Constraints;
12✔
1124

1125
   Test::Result result("X509 Self Issued");
12✔
1126

1127
   // create the self-signed cert
1128
   const Botan::X509_Certificate ca_cert =
12✔
1129
      Botan::X509::create_self_signed_cert(ca_opts(sig_padding), ca_key, hash_fn, rng);
12✔
1130

1131
   /* Create the CA object */
1132
   const Botan::X509_CA ca(ca_cert, ca_key, hash_fn, sig_padding, rng);
12✔
1133

1134
   auto user_key = make_a_private_key(sig_algo, rng);
12✔
1135

1136
   // create a self-issued certificate, that is, a certificate with subject dn == issuer dn,
1137
   // but signed by a CA, not signed by it's own private key
1138
   Botan::X509_Cert_Options opts = ca_opts();
12✔
1139
   opts.constraints = Key_Constraints::DigitalSignature;
12✔
1140
   opts.set_padding_scheme(sig_padding);
12✔
1141

1142
   const Botan::PKCS10_Request self_issued_req = Botan::X509::create_cert_req(opts, *user_key, hash_fn, rng);
12✔
1143

1144
   const Botan::X509_Certificate self_issued_cert =
12✔
1145
      ca.sign_request(self_issued_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
12✔
1146

1147
   // check that this chain can can be verified successfully
1148
   const Botan::Certificate_Store_In_Memory trusted(ca.ca_certificate());
12✔
1149

1150
   const Botan::Path_Validation_Restrictions restrictions(false, 80);
24✔
1151

1152
   const Botan::Path_Validation_Result validation_result =
12✔
1153
      Botan::x509_path_validate(self_issued_cert, restrictions, trusted);
12✔
1154

1155
   result.confirm("chain with self-issued cert validates", validation_result.successful_validation());
24✔
1156

1157
   return result;
12✔
1158
}
24✔
1159

1160
Test::Result test_x509_uninit() {
1✔
1161
   Test::Result result("X509 object uninitialized access");
1✔
1162

1163
   Botan::X509_Certificate cert;
1✔
1164
   result.test_throws("uninitialized cert access causes exception", "X509_Certificate uninitialized", [&cert]() {
2✔
1165
      cert.x509_version();
1✔
1166
   });
1167

1168
   Botan::X509_CRL crl;
1✔
1169
   result.test_throws(
2✔
1170
      "uninitialized crl access causes exception", "X509_CRL uninitialized", [&crl]() { crl.crl_number(); });
2✔
1171

1172
   return result;
1✔
1173
}
1✔
1174

1175
Test::Result test_valid_constraints(const Botan::Private_Key& key, const std::string& pk_algo) {
19✔
1176
   using Botan::Key_Constraints;
19✔
1177

1178
   Test::Result result("X509 Valid Constraints " + pk_algo);
19✔
1179

1180
   result.confirm("empty constraints always acceptable", Key_Constraints().compatible_with(key));
38✔
1181

1182
   // Now check some typical usage scenarios for the given key type
1183
   // Taken from RFC 5280, sec. 4.2.1.3
1184
   // ALL constraints are not typical at all, but we use them for a negative test
1185
   const auto all = Key_Constraints(
19✔
1186
      Key_Constraints::DigitalSignature | Key_Constraints::NonRepudiation | Key_Constraints::KeyEncipherment |
1187
      Key_Constraints::DataEncipherment | Key_Constraints::KeyAgreement | Key_Constraints::KeyCertSign |
1188
      Key_Constraints::CrlSign | Key_Constraints::EncipherOnly | Key_Constraints::DecipherOnly);
19✔
1189

1190
   const auto ca = Key_Constraints(Key_Constraints::KeyCertSign);
19✔
1191
   const auto sign_data = Key_Constraints(Key_Constraints::DigitalSignature);
19✔
1192
   const auto non_repudiation = Key_Constraints(Key_Constraints::NonRepudiation | Key_Constraints::DigitalSignature);
19✔
1193
   const auto key_encipherment = Key_Constraints(Key_Constraints::KeyEncipherment);
19✔
1194
   const auto data_encipherment = Key_Constraints(Key_Constraints::DataEncipherment);
19✔
1195
   const auto key_agreement = Key_Constraints(Key_Constraints::KeyAgreement);
19✔
1196
   const auto key_agreement_encipher_only =
19✔
1197
      Key_Constraints(Key_Constraints::KeyAgreement | Key_Constraints::EncipherOnly);
19✔
1198
   const auto key_agreement_decipher_only =
19✔
1199
      Key_Constraints(Key_Constraints::KeyAgreement | Key_Constraints::DecipherOnly);
19✔
1200
   const auto crl_sign = Key_Constraints(Key_Constraints::CrlSign);
19✔
1201
   const auto sign_everything =
19✔
1202
      Key_Constraints(Key_Constraints::DigitalSignature | Key_Constraints::KeyCertSign | Key_Constraints::CrlSign);
19✔
1203

1204
   if(pk_algo == "DH" || pk_algo == "ECDH") {
19✔
1205
      // DH and ECDH only for key agreement
1206
      result.test_eq("all constraints not permitted", all.compatible_with(key), false);
2✔
1207
      result.test_eq("cert sign not permitted", ca.compatible_with(key), false);
2✔
1208
      result.test_eq("signature not permitted", sign_data.compatible_with(key), false);
2✔
1209
      result.test_eq("non repudiation not permitted", non_repudiation.compatible_with(key), false);
2✔
1210
      result.test_eq("key encipherment not permitted", key_encipherment.compatible_with(key), false);
2✔
1211
      result.test_eq("data encipherment not permitted", data_encipherment.compatible_with(key), false);
2✔
1212
      result.test_eq("usage acceptable", key_agreement.compatible_with(key), true);
2✔
1213
      result.test_eq("usage acceptable", key_agreement_encipher_only.compatible_with(key), true);
2✔
1214
      result.test_eq("usage acceptable", key_agreement_decipher_only.compatible_with(key), true);
2✔
1215
      result.test_eq("crl sign not permitted", crl_sign.compatible_with(key), false);
2✔
1216
      result.test_eq("sign", sign_everything.compatible_with(key), false);
4✔
1217
   } else if(pk_algo == "Kyber" || pk_algo == "FrodoKEM" || pk_algo == "ML-KEM" || pk_algo == "ClassicMcEliece") {
17✔
1218
      // KEMs can encrypt and agree
1219
      result.test_eq("all constraints not permitted", all.compatible_with(key), false);
4✔
1220
      result.test_eq("cert sign not permitted", ca.compatible_with(key), false);
4✔
1221
      result.test_eq("signature not permitted", sign_data.compatible_with(key), false);
4✔
1222
      result.test_eq("non repudiation not permitted", non_repudiation.compatible_with(key), false);
4✔
1223
      result.test_eq("crl sign not permitted", crl_sign.compatible_with(key), false);
4✔
1224
      result.test_eq("sign", sign_everything.compatible_with(key), false);
4✔
1225
      result.test_eq("key agreement not permitted", key_agreement.compatible_with(key), false);
4✔
1226
      result.test_eq("usage acceptable", data_encipherment.compatible_with(key), false);
4✔
1227
      result.test_eq("usage acceptable", key_encipherment.compatible_with(key), true);
8✔
1228
   } else if(pk_algo == "RSA") {
13✔
1229
      // RSA can do everything except key agreement
1230
      result.test_eq("all constraints not permitted", all.compatible_with(key), false);
1✔
1231

1232
      result.test_eq("usage acceptable", ca.compatible_with(key), true);
1✔
1233
      result.test_eq("usage acceptable", sign_data.compatible_with(key), true);
1✔
1234
      result.test_eq("usage acceptable", non_repudiation.compatible_with(key), true);
1✔
1235
      result.test_eq("usage acceptable", key_encipherment.compatible_with(key), true);
1✔
1236
      result.test_eq("usage acceptable", data_encipherment.compatible_with(key), true);
1✔
1237
      result.test_eq("key agreement not permitted", key_agreement.compatible_with(key), false);
1✔
1238
      result.test_eq("key agreement", key_agreement_encipher_only.compatible_with(key), false);
1✔
1239
      result.test_eq("key agreement", key_agreement_decipher_only.compatible_with(key), false);
1✔
1240
      result.test_eq("usage acceptable", crl_sign.compatible_with(key), true);
1✔
1241
      result.test_eq("usage acceptable", sign_everything.compatible_with(key), true);
2✔
1242
   } else if(pk_algo == "ElGamal") {
12✔
1243
      // only ElGamal encryption is currently implemented
1244
      result.test_eq("all constraints not permitted", all.compatible_with(key), false);
1✔
1245
      result.test_eq("cert sign not permitted", ca.compatible_with(key), false);
1✔
1246
      result.test_eq("data encipherment permitted", data_encipherment.compatible_with(key), true);
1✔
1247
      result.test_eq("key encipherment permitted", key_encipherment.compatible_with(key), true);
1✔
1248
      result.test_eq("key agreement not permitted", key_agreement.compatible_with(key), false);
1✔
1249
      result.test_eq("key agreement", key_agreement_encipher_only.compatible_with(key), false);
1✔
1250
      result.test_eq("key agreement", key_agreement_decipher_only.compatible_with(key), false);
1✔
1251
      result.test_eq("crl sign not permitted", crl_sign.compatible_with(key), false);
1✔
1252
      result.test_eq("sign", sign_everything.compatible_with(key), false);
2✔
1253
   } else if(pk_algo == "DSA" || pk_algo == "ECDSA" || pk_algo == "ECGDSA" || pk_algo == "ECKCDSA" ||
10✔
1254
             pk_algo == "GOST-34.10" || pk_algo == "Dilithium" || pk_algo == "ML-DSA" || pk_algo == "SLH-DSA" ||
18✔
1255
             pk_algo == "HSS-LMS") {
3✔
1256
      // these are signature algorithms only
1257
      result.test_eq("all constraints not permitted", all.compatible_with(key), false);
9✔
1258

1259
      result.test_eq("ca allowed", ca.compatible_with(key), true);
9✔
1260
      result.test_eq("sign allowed", sign_data.compatible_with(key), true);
9✔
1261
      result.test_eq("non-repudiation allowed", non_repudiation.compatible_with(key), true);
9✔
1262
      result.test_eq("key encipherment not permitted", key_encipherment.compatible_with(key), false);
9✔
1263
      result.test_eq("data encipherment not permitted", data_encipherment.compatible_with(key), false);
9✔
1264
      result.test_eq("key agreement not permitted", key_agreement.compatible_with(key), false);
9✔
1265
      result.test_eq("key agreement", key_agreement_encipher_only.compatible_with(key), false);
9✔
1266
      result.test_eq("key agreement", key_agreement_decipher_only.compatible_with(key), false);
9✔
1267
      result.test_eq("crl sign allowed", crl_sign.compatible_with(key), true);
9✔
1268
      result.test_eq("sign allowed", sign_everything.compatible_with(key), true);
18✔
1269
   }
1270

1271
   return result;
19✔
1272
}
×
1273

1274
/**
1275
 * @brief X.509v3 extension that encodes a given string
1276
 */
1277
class String_Extension final : public Botan::Certificate_Extension {
24✔
1278
   public:
1279
      String_Extension() = default;
24✔
1280

1281
      explicit String_Extension(const std::string& val) : m_contents(val) {}
12✔
1282

1283
      std::string value() const { return m_contents; }
48✔
1284

1285
      std::unique_ptr<Certificate_Extension> copy() const override {
×
1286
         return std::make_unique<String_Extension>(m_contents);
×
1287
      }
1288

1289
      Botan::OID oid_of() const override { return Botan::OID("1.2.3.4.5.6.7.8.9.1"); }
24✔
1290

1291
      bool should_encode() const override { return true; }
24✔
1292

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

1295
      std::vector<uint8_t> encode_inner() const override {
12✔
1296
         std::vector<uint8_t> bits;
12✔
1297
         Botan::DER_Encoder(bits).encode(Botan::ASN1_String(m_contents, Botan::ASN1_Type::Utf8String));
12✔
1298
         return bits;
12✔
1299
      }
×
1300

1301
      void decode_inner(const std::vector<uint8_t>& in) override {
24✔
1302
         Botan::ASN1_String str;
24✔
1303
         Botan::BER_Decoder(in).decode(str, Botan::ASN1_Type::Utf8String).verify_end();
24✔
1304
         m_contents = str.value();
24✔
1305
      }
24✔
1306

1307
   private:
1308
      std::string m_contents;
1309
};
1310

1311
Test::Result test_custom_dn_attr(const Botan::Private_Key& ca_key,
12✔
1312
                                 const std::string& sig_algo,
1313
                                 const std::string& sig_padding,
1314
                                 const std::string& hash_fn,
1315
                                 Botan::RandomNumberGenerator& rng) {
1316
   Test::Result result("X509 Custom DN");
12✔
1317

1318
   /* Create the self-signed cert */
1319
   Botan::X509_Certificate ca_cert = Botan::X509::create_self_signed_cert(ca_opts(sig_padding), ca_key, hash_fn, rng);
12✔
1320

1321
   /* Create the CA object */
1322
   Botan::X509_CA ca(ca_cert, ca_key, hash_fn, sig_padding, rng);
12✔
1323

1324
   auto user_key = make_a_private_key(sig_algo, rng);
12✔
1325

1326
   Botan::X509_DN subject_dn;
12✔
1327

1328
   const Botan::OID attr1(Botan::OID("1.3.6.1.4.1.25258.9.1.1"));
12✔
1329
   const Botan::OID attr2(Botan::OID("1.3.6.1.4.1.25258.9.1.2"));
12✔
1330
   const Botan::ASN1_String val1("Custom Attr 1", Botan::ASN1_Type::PrintableString);
12✔
1331
   const Botan::ASN1_String val2("12345", Botan::ASN1_Type::Utf8String);
12✔
1332

1333
   subject_dn.add_attribute(attr1, val1);
12✔
1334
   subject_dn.add_attribute(attr2, val2);
12✔
1335

1336
   Botan::Extensions extensions;
12✔
1337

1338
   Botan::PKCS10_Request req =
12✔
1339
      Botan::PKCS10_Request::create(*user_key, subject_dn, extensions, hash_fn, rng, sig_padding);
12✔
1340

1341
   const Botan::X509_DN& req_dn = req.subject_dn();
12✔
1342

1343
   result.test_eq("Expected number of DN entries", req_dn.dn_info().size(), 2);
12✔
1344

1345
   Botan::ASN1_String req_val1 = req_dn.get_first_attribute(attr1);
12✔
1346
   Botan::ASN1_String req_val2 = req_dn.get_first_attribute(attr2);
12✔
1347
   result.confirm("Attr1 matches encoded", req_val1 == val1);
24✔
1348
   result.confirm("Attr2 matches encoded", req_val2 == val2);
24✔
1349
   result.confirm("Attr1 tag matches encoded", req_val1.tagging() == val1.tagging());
24✔
1350
   result.confirm("Attr2 tag matches encoded", req_val2.tagging() == val2.tagging());
24✔
1351

1352
   Botan::X509_Time not_before("100301123001Z", Botan::ASN1_Type::UtcTime);
12✔
1353
   Botan::X509_Time not_after("300301123001Z", Botan::ASN1_Type::UtcTime);
12✔
1354

1355
   auto cert = ca.sign_request(req, rng, not_before, not_after);
12✔
1356

1357
   const Botan::X509_DN& cert_dn = cert.subject_dn();
12✔
1358

1359
   result.test_eq("Expected number of DN entries", cert_dn.dn_info().size(), 2);
12✔
1360

1361
   Botan::ASN1_String cert_val1 = cert_dn.get_first_attribute(attr1);
12✔
1362
   Botan::ASN1_String cert_val2 = cert_dn.get_first_attribute(attr2);
12✔
1363
   result.confirm("Attr1 matches encoded", cert_val1 == val1);
24✔
1364
   result.confirm("Attr2 matches encoded", cert_val2 == val2);
24✔
1365
   result.confirm("Attr1 tag matches encoded", cert_val1.tagging() == val1.tagging());
24✔
1366
   result.confirm("Attr2 tag matches encoded", cert_val2.tagging() == val2.tagging());
24✔
1367

1368
   return result;
12✔
1369
}
36✔
1370

1371
Test::Result test_x509_extensions(const Botan::Private_Key& ca_key,
12✔
1372
                                  const std::string& sig_algo,
1373
                                  const std::string& sig_padding,
1374
                                  const std::string& hash_fn,
1375
                                  Botan::RandomNumberGenerator& rng) {
1376
   using Botan::Key_Constraints;
12✔
1377

1378
   Test::Result result("X509 Extensions");
12✔
1379

1380
   /* Create the self-signed cert */
1381
   Botan::X509_Certificate ca_cert = Botan::X509::create_self_signed_cert(ca_opts(sig_padding), ca_key, hash_fn, rng);
12✔
1382

1383
   /* Create the CA object */
1384
   Botan::X509_CA ca(ca_cert, ca_key, hash_fn, sig_padding, rng);
12✔
1385

1386
   /* Prepare CDP extension */
1387
   std::vector<std::string> cdp_urls = {
12✔
1388
      "http://example.com/crl1.pem",
1389
      "ldap://ldap.example.com/cn=crl1,dc=example,dc=com?certificateRevocationList;binary"};
12✔
1390

1391
   std::vector<Botan::Cert_Extension::CRL_Distribution_Points::Distribution_Point> dps;
12✔
1392

1393
   for(const auto& uri : cdp_urls) {
36✔
1394
      Botan::AlternativeName cdp_alt_name;
24✔
1395
      cdp_alt_name.add_uri(uri);
24✔
1396
      Botan::Cert_Extension::CRL_Distribution_Points::Distribution_Point dp(cdp_alt_name);
24✔
1397

1398
      dps.emplace_back(dp);
24✔
1399
   }
24✔
1400

1401
   auto user_key = make_a_private_key(sig_algo, rng);
12✔
1402

1403
   Botan::X509_Cert_Options opts("Test User 1/US/Botan Project/Testing");
12✔
1404
   opts.constraints = Key_Constraints::DigitalSignature;
12✔
1405

1406
   // include a custom extension in the request
1407
   Botan::Extensions req_extensions;
12✔
1408
   const Botan::OID oid("1.2.3.4.5.6.7.8.9.1");
12✔
1409
   const Botan::OID ku_oid = Botan::OID::from_string("X509v3.KeyUsage");
12✔
1410
   req_extensions.add(std::make_unique<String_Extension>("AAAAAAAAAAAAAABCDEF"), false);
24✔
1411
   req_extensions.add(std::make_unique<Botan::Cert_Extension::CRL_Distribution_Points>(dps));
24✔
1412
   opts.extensions = req_extensions;
12✔
1413
   opts.set_padding_scheme(sig_padding);
12✔
1414

1415
   /* Create a self-signed certificate */
1416
   const Botan::X509_Certificate self_signed_cert = Botan::X509::create_self_signed_cert(opts, *user_key, hash_fn, rng);
12✔
1417

1418
   result.confirm("Extensions::extension_set true for Key_Usage",
24✔
1419
                  self_signed_cert.v3_extensions().extension_set(ku_oid));
12✔
1420

1421
   // check if known Key_Usage extension is present in self-signed cert
1422
   auto key_usage_ext = self_signed_cert.v3_extensions().get(ku_oid);
12✔
1423
   if(result.confirm("Key_Usage extension present in self-signed certificate", key_usage_ext != nullptr)) {
24✔
1424
      result.confirm(
24✔
1425
         "Key_Usage extension value matches in self-signed certificate",
1426
         dynamic_cast<Botan::Cert_Extension::Key_Usage&>(*key_usage_ext).get_constraints() == opts.constraints);
12✔
1427
   }
1428

1429
   // check if custom extension is present in self-signed cert
1430
   auto string_ext = self_signed_cert.v3_extensions().get_raw<String_Extension>(oid);
12✔
1431
   if(result.confirm("Custom extension present in self-signed certificate", string_ext != nullptr)) {
24✔
1432
      result.test_eq(
48✔
1433
         "Custom extension value matches in self-signed certificate", string_ext->value(), "AAAAAAAAAAAAAABCDEF");
36✔
1434
   }
1435

1436
   // check if CDPs are present in the self-signed cert
1437
   auto cert_cdps =
12✔
1438
      self_signed_cert.v3_extensions().get_extension_object_as<Botan::Cert_Extension::CRL_Distribution_Points>();
24✔
1439

1440
   if(result.confirm("CRL Distribution Points extension present in self-signed certificate",
24✔
1441
                     !cert_cdps->crl_distribution_urls().empty())) {
12✔
1442
      for(const auto& cdp : cert_cdps->distribution_points()) {
36✔
1443
         result.confirm("CDP URI present in self-signed certificate",
48✔
1444
                        std::ranges::find(cdp_urls, cdp.point().get_first_attribute("URI")) != cdp_urls.end());
72✔
1445
      }
1446
   }
1447

1448
   const Botan::PKCS10_Request user_req = Botan::X509::create_cert_req(opts, *user_key, hash_fn, rng);
12✔
1449

1450
   /* Create a CA-signed certificate */
1451
   const Botan::X509_Certificate ca_signed_cert =
12✔
1452
      ca.sign_request(user_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
12✔
1453

1454
   // check if known Key_Usage extension is present in CA-signed cert
1455
   result.confirm("Extensions::extension_set true for Key_Usage", ca_signed_cert.v3_extensions().extension_set(ku_oid));
24✔
1456

1457
   key_usage_ext = ca_signed_cert.v3_extensions().get(ku_oid);
24✔
1458
   if(result.confirm("Key_Usage extension present in CA-signed certificate", key_usage_ext != nullptr)) {
24✔
1459
      auto constraints = dynamic_cast<Botan::Cert_Extension::Key_Usage&>(*key_usage_ext).get_constraints();
12✔
1460
      result.confirm("Key_Usage extension value matches in user certificate",
24✔
1461
                     constraints == Botan::Key_Constraints::DigitalSignature);
12✔
1462
   }
1463

1464
   // check if custom extension is present in CA-signed cert
1465
   result.confirm("Extensions::extension_set true for String_Extension",
24✔
1466
                  ca_signed_cert.v3_extensions().extension_set(oid));
12✔
1467
   string_ext = ca_signed_cert.v3_extensions().get_raw<String_Extension>(oid);
24✔
1468
   if(result.confirm("Custom extension present in CA-signed certificate", string_ext != nullptr)) {
24✔
1469
      result.test_eq(
48✔
1470
         "Custom extension value matches in CA-signed certificate", string_ext->value(), "AAAAAAAAAAAAAABCDEF");
36✔
1471
   }
1472

1473
   // check if CDPs are present in the CA-signed cert
1474
   cert_cdps = ca_signed_cert.v3_extensions().get_extension_object_as<Botan::Cert_Extension::CRL_Distribution_Points>();
24✔
1475

1476
   if(result.confirm("CRL Distribution Points extension present in self-signed certificate",
24✔
1477
                     !cert_cdps->crl_distribution_urls().empty())) {
12✔
1478
      for(const auto& cdp : cert_cdps->distribution_points()) {
36✔
1479
         result.confirm("CDP URI present in self-signed certificate",
48✔
1480
                        std::ranges::find(cdp_urls, cdp.point().get_first_attribute("URI")) != cdp_urls.end());
72✔
1481
      }
1482
   }
1483

1484
   return result;
12✔
1485
}
60✔
1486

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

1490
   struct TestData {
12✔
1491
         const std::string issuer, subject, issuer_hash, subject_hash;
1492
   } const cases[]{{"",
12✔
1493
                    "",
1494
                    "E4F60D0AA6D7F3D3B6A6494B1C861B99F649C6F9EC51ABAF201B20F297327C95",
1495
                    "E4F60D0AA6D7F3D3B6A6494B1C861B99F649C6F9EC51ABAF201B20F297327C95"},
1496
                   {"a",
1497
                    "b",
1498
                    "BC2E013472F39AC579964880E422737C82BA812CB8BC2FD17E013060D71E6E19",
1499
                    "5E31CFAA3FAFB1A5BA296A0D2BAB9CA44D7936E9BF0BBC54637D0C53DBC4A432"},
1500
                   {"A",
1501
                    "B",
1502
                    "4B3206201C4BC9B6CD6C36532A97687DF9238155D99ADB60C66BF2B2220643D8",
1503
                    "FFF635A52A16618B4A0E9CD26B5E5A2FA573D343C051E6DE8B0811B1ACC89B86"},
1504
                   {
1505
                      "Test Issuer/US/Botan Project/Testing",
1506
                      "Test Subject/US/Botan Project/Testing",
1507
                      "ACB4F373004A56A983A23EB8F60FA4706312B5DB90FD978574FE7ACC84E093A5",
1508
                      "87039231C2205B74B6F1F3830A66272C0B41F71894B03AC3150221766D95267B",
1509
                   },
1510
                   {
1511
                      "Test Subject/US/Botan Project/Testing",
1512
                      "Test Issuer/US/Botan Project/Testing",
1513
                      "87039231C2205B74B6F1F3830A66272C0B41F71894B03AC3150221766D95267B",
1514
                      "ACB4F373004A56A983A23EB8F60FA4706312B5DB90FD978574FE7ACC84E093A5",
1515
                   }};
72✔
1516

1517
   for(const auto& a : cases) {
72✔
1518
      Botan::X509_Cert_Options opts{a.issuer};
60✔
1519
      opts.CA_key();
60✔
1520

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

1523
      result.test_eq(a.issuer, Botan::hex_encode(issuer_cert.raw_issuer_dn_sha256()), a.issuer_hash);
120✔
1524
      result.test_eq(a.issuer, Botan::hex_encode(issuer_cert.raw_subject_dn_sha256()), a.issuer_hash);
120✔
1525

1526
      const Botan::X509_CA ca(issuer_cert, key, hash_fn, rng);
60✔
1527
      const Botan::PKCS10_Request req =
60✔
1528
         Botan::X509::create_cert_req(Botan::X509_Cert_Options(a.subject), key, hash_fn, rng);
60✔
1529
      const Botan::X509_Certificate subject_cert =
60✔
1530
         ca.sign_request(req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
60✔
1531

1532
      result.test_eq(a.subject, Botan::hex_encode(subject_cert.raw_issuer_dn_sha256()), a.issuer_hash);
120✔
1533
      result.test_eq(a.subject, Botan::hex_encode(subject_cert.raw_subject_dn_sha256()), a.subject_hash);
120✔
1534
   }
60✔
1535
   return result;
12✔
1536
}
72✔
1537

1538
   #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
1539

1540
Test::Result test_x509_tn_auth_list_extension_decode() {
1✔
1541
   /* cert with TNAuthList extension data was generated by asn1parse cfg:
1542

1543
      asn1=SEQUENCE:tn_auth_list
1544

1545
      [tn_auth_list]
1546
      spc=EXP:0,IA5:1001
1547
      range=EXP:1,SEQUENCE:TelephoneNumberRange
1548
      one=EXP:2,IA5:333
1549

1550
      [TelephoneNumberRange]
1551
      start1=IA5:111
1552
      count1=INT:128
1553
      start2=IA5:222
1554
      count2=INT:256
1555
    */
1556
   const std::string filename("TNAuthList.pem");
1✔
1557
   Test::Result result("X509 TNAuthList decode");
1✔
1558
   result.start_timer();
1✔
1559

1560
   Botan::X509_Certificate cert(Test::data_file("x509/x509test/" + filename));
2✔
1561

1562
   using Botan::Cert_Extension::TNAuthList;
1✔
1563

1564
   auto tn_auth_list = cert.v3_extensions().get_extension_object_as<TNAuthList>();
2✔
1565

1566
   auto& tn_entries = tn_auth_list->entries();
1✔
1567

1568
   result.confirm("cert has TNAuthList extension", tn_auth_list != nullptr, true);
2✔
1569

1570
   result.test_throws("wrong telephone_number_range() accessor for spc",
2✔
1571
                      [&tn_entries] { tn_entries[0].telephone_number_range(); });
2✔
1572
   result.test_throws("wrong telephone_number() accessor for range",
2✔
1573
                      [&tn_entries] { tn_entries[1].telephone_number(); });
2✔
1574
   result.test_throws("wrong service_provider_code() accessor for one",
2✔
1575
                      [&tn_entries] { tn_entries[2].service_provider_code(); });
2✔
1576

1577
   result.test_eq("spc entry type", tn_entries[0].type() == TNAuthList::Entry::ServiceProviderCode, true);
1✔
1578
   result.test_eq("spc entry data", tn_entries[0].service_provider_code(), "1001");
2✔
1579

1580
   result.test_eq("range entry type", tn_entries[1].type() == TNAuthList::Entry::TelephoneNumberRange, true);
1✔
1581
   auto& range = tn_entries[1].telephone_number_range();
1✔
1582
   result.test_eq("range entries count", range.size(), 2);
1✔
1583
   result.test_eq("range entry 0 start data", range[0].start.value(), "111");
2✔
1584
   result.test_eq("range entry 0 count data", range[0].count, 128);
1✔
1585
   result.test_eq("range entry 1 start data", range[1].start.value(), "222");
2✔
1586
   result.test_eq("range entry 1 count data", range[1].count, 256);
1✔
1587

1588
   result.test_eq("one entry type", tn_entries[2].type() == TNAuthList::Entry::TelephoneNumber, true);
1✔
1589
   result.test_eq("one entry data", tn_entries[2].telephone_number(), "333");
2✔
1590

1591
   result.end_timer();
1✔
1592
   return result;
2✔
1593
}
1✔
1594

1595
   #endif
1596

1597
std::vector<std::string> get_sig_paddings(const std::string& sig_algo, const std::string& hash) {
12✔
1598
   if(sig_algo == "RSA") {
12✔
1599
      return {"EMSA3(" + hash + ")", "EMSA4(" + hash + ")"};
5✔
1600
   } else if(sig_algo == "DSA" || sig_algo == "ECDSA" || sig_algo == "ECGDSA" || sig_algo == "ECKCDSA" ||
11✔
1601
             sig_algo == "GOST-34.10") {
7✔
1602
      return {hash};
10✔
1603
   } else if(sig_algo == "Ed25519" || sig_algo == "Ed448") {
6✔
1604
      return {"Pure"};
2✔
1605
   } else if(sig_algo == "Dilithium" || sig_algo == "ML-DSA") {
4✔
1606
      return {"Randomized"};
2✔
1607
   } else if(sig_algo == "HSS-LMS") {
2✔
1608
      return {""};
1✔
1609
   } else {
1610
      return {};
1✔
1611
   }
1612
}
7✔
1613

1614
class X509_Cert_Unit_Tests final : public Test {
×
1615
   public:
1616
      std::vector<Test::Result> run() override {
1✔
1617
         std::vector<Test::Result> results;
1✔
1618

1619
         auto& rng = this->rng();
1✔
1620

1621
         const std::string sig_algos[]{"RSA",
1✔
1622
                                       "DSA",
1623
                                       "ECDSA",
1624
                                       "ECGDSA",
1625
                                       "ECKCDSA",
1626
                                       "GOST-34.10",
1627
                                       "Ed25519",
1628
                                       "Ed448",
1629
                                       "Dilithium",
1630
                                       "ML-DSA",
1631
                                       "SLH-DSA",
1632
                                       "HSS-LMS"};
13✔
1633

1634
         for(const std::string& algo : sig_algos) {
13✔
1635
   #if !defined(BOTAN_HAS_EMSA_PKCS1)
1636
            if(algo == "RSA")
1637
               continue;
1638
   #endif
1639

1640
            std::string hash = "SHA-256";
12✔
1641

1642
            if(algo == "Ed25519") {
12✔
1643
               hash = "SHA-512";
1✔
1644
            }
1645
            if(algo == "Ed448") {
12✔
1646
               hash = "SHAKE-256(912)";
1✔
1647
            }
1648
            if(algo == "Dilithium" || algo == "ML-DSA") {
12✔
1649
               hash = "SHAKE-256(512)";
2✔
1650
            }
1651

1652
            auto key = make_a_private_key(algo, rng);
12✔
1653

1654
            if(key == nullptr) {
12✔
1655
               continue;
×
1656
            }
1657

1658
            results.push_back(test_hashes(*key, hash, rng));
24✔
1659
            results.push_back(test_valid_constraints(*key, algo));
24✔
1660

1661
            Test::Result usage_result("X509 Usage");
12✔
1662
            try {
12✔
1663
               usage_result.merge(test_usage(*key, algo, hash, rng));
12✔
1664
            } catch(std::exception& e) {
×
1665
               usage_result.test_failure("test_usage " + algo, e.what());
×
1666
            }
×
1667
            results.push_back(usage_result);
12✔
1668

1669
            for(const auto& padding_scheme : get_sig_paddings(algo, hash)) {
24✔
1670
               Test::Result cert_result("X509 Unit");
12✔
1671

1672
               try {
12✔
1673
                  cert_result.merge(test_x509_cert(*key, algo, padding_scheme, hash, rng));
12✔
1674
               } catch(std::exception& e) {
×
1675
                  cert_result.test_failure("test_x509_cert " + algo, e.what());
×
1676
               }
×
1677
               results.push_back(cert_result);
12✔
1678

1679
               Test::Result pkcs10_result("PKCS10 extensions");
12✔
1680
               try {
12✔
1681
                  pkcs10_result.merge(test_pkcs10_ext(*key, padding_scheme, hash, rng));
12✔
1682
               } catch(std::exception& e) {
×
1683
                  pkcs10_result.test_failure("test_pkcs10_ext " + algo, e.what());
×
1684
               }
×
1685
               results.push_back(pkcs10_result);
12✔
1686

1687
               Test::Result self_issued_result("X509 Self Issued");
12✔
1688
               try {
12✔
1689
                  self_issued_result.merge(test_self_issued(*key, algo, padding_scheme, hash, rng));
12✔
1690
               } catch(std::exception& e) {
×
1691
                  self_issued_result.test_failure("test_self_issued " + algo, e.what());
×
1692
               }
×
1693
               results.push_back(self_issued_result);
12✔
1694

1695
               Test::Result extensions_result("X509 Extensions");
12✔
1696
               try {
12✔
1697
                  extensions_result.merge(test_x509_extensions(*key, algo, padding_scheme, hash, rng));
12✔
1698
               } catch(std::exception& e) {
×
1699
                  extensions_result.test_failure("test_extensions " + algo, e.what());
×
1700
               }
×
1701
               results.push_back(extensions_result);
12✔
1702

1703
               Test::Result custom_dn_result("X509 Custom DN");
12✔
1704
               try {
12✔
1705
                  custom_dn_result.merge(test_custom_dn_attr(*key, algo, padding_scheme, hash, rng));
12✔
1706
               } catch(std::exception& e) {
×
1707
                  custom_dn_result.test_failure("test_custom_dn_attr " + algo, e.what());
×
1708
               }
×
1709
               results.push_back(custom_dn_result);
12✔
1710
            }
24✔
1711
         }
24✔
1712

1713
         /*
1714
         These are algos which cannot sign but can be included in certs
1715
         */
1716
         const std::vector<std::string> enc_algos = {
1✔
1717
            "DH", "ECDH", "ElGamal", "Kyber", "ML-KEM", "FrodoKEM", "ClassicMcEliece"};
1✔
1718

1719
         for(const std::string& algo : enc_algos) {
8✔
1720
            auto key = make_a_private_key(algo, rng);
7✔
1721

1722
            if(key) {
7✔
1723
               results.push_back(test_valid_constraints(*key, algo));
14✔
1724
            }
1725
         }
7✔
1726

1727
   #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM) && defined(BOTAN_HAS_EMSA_PKCS1) && defined(BOTAN_HAS_EMSA_PSSR) && \
1728
      defined(BOTAN_HAS_RSA)
1729
         Test::Result pad_config_result("X509 Padding Config");
1✔
1730
         try {
1✔
1731
            pad_config_result.merge(test_padding_config());
1✔
1732
         } catch(const std::exception& e) {
×
1733
            pad_config_result.test_failure("test_padding_config", e.what());
×
1734
         }
×
1735
         results.push_back(pad_config_result);
1✔
1736
   #endif
1737

1738
   #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
1739
         results.push_back(test_x509_utf8());
2✔
1740
         results.push_back(test_x509_bmpstring());
2✔
1741
         results.push_back(test_x509_teletex());
2✔
1742
         results.push_back(test_crl_dn_name());
2✔
1743
         results.push_back(test_rdn_multielement_set_name());
2✔
1744
         results.push_back(test_x509_decode_list());
2✔
1745
         results.push_back(test_rsa_oaep());
2✔
1746
         results.push_back(test_x509_authority_info_access_extension());
2✔
1747
         results.push_back(test_verify_gost2012_cert());
2✔
1748
         results.push_back(test_parse_rsa_pss_cert());
2✔
1749
         results.push_back(test_x509_tn_auth_list_extension_decode());
2✔
1750
   #endif
1751

1752
         results.push_back(test_x509_encode_authority_info_access_extension());
2✔
1753
         results.push_back(test_x509_extension());
2✔
1754
         results.push_back(test_x509_dates());
2✔
1755
         results.push_back(test_cert_status_strings());
2✔
1756
         results.push_back(test_x509_uninit());
2✔
1757

1758
         return results;
1✔
1759
      }
13✔
1760
};
1761

1762
BOTAN_REGISTER_TEST("x509", "x509_unit", X509_Cert_Unit_Tests);
1763

1764
#endif
1765

1766
}  // namespace
1767

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