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

randombit / botan / 13003324174

28 Jan 2025 12:04AM UTC coverage: 91.248% (+0.002%) from 91.246%
13003324174

push

github

web-flow
Merge pull request #4600 from randombit/jack/padding-alias

Avoid using IEEE 1363 EMSA names in OID data or EMSA::name

93990 of 103005 relevant lines covered (91.25%)

11440896.35 hits per line

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

94.3
/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
#endif
22

23
namespace Botan_Tests {
24

25
namespace {
26

27
#if defined(BOTAN_HAS_X509_CERTIFICATES)
28

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

32
   Botan::calendar_point t(static_cast<uint32_t>(this_year + y), m, d, 0, 0, 0);
346✔
33
   return Botan::X509_Time(t.to_std_timepoint());
346✔
34
}
35

36
/* Return some option sets */
37
Botan::X509_Cert_Options ca_opts(const std::string& sig_padding = "") {
121✔
38
   Botan::X509_Cert_Options opts("Test CA/US/Botan Project/Testing");
121✔
39

40
   opts.uri = "https://botan.randombit.net";
121✔
41
   opts.dns = "botan.randombit.net";
121✔
42
   opts.email = "testing@randombit.net";
121✔
43
   opts.set_padding_scheme(sig_padding);
121✔
44

45
   opts.CA_key(1);
121✔
46

47
   return opts;
121✔
48
}
×
49

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

53
   opts.uri = "https://botan.randombit.net";
39✔
54
   opts.dns = "botan.randombit.net";
39✔
55
   opts.email = "testing@randombit.net";
39✔
56
   opts.set_padding_scheme(sig_padding);
39✔
57

58
   opts.not_before("160101200000Z");
39✔
59
   opts.not_after("300101200000Z");
39✔
60

61
   opts.challenge = "zoom";
39✔
62

63
   if(algo == "RSA") {
39✔
64
      opts.constraints = Botan::Key_Constraints::KeyEncipherment;
9✔
65
   } else if(algo == "DSA" || algo == "ECDSA" || algo == "ECGDSA" || algo == "ECKCDSA") {
30✔
66
      opts.constraints = Botan::Key_Constraints::DigitalSignature;
12✔
67
   }
68

69
   return opts;
39✔
70
}
×
71

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

75
   opts.uri = "https://botan.randombit.net";
12✔
76
   opts.dns = "botan.randombit.net";
12✔
77
   opts.email = "testing@randombit.net";
12✔
78
   opts.set_padding_scheme(sig_padding);
12✔
79

80
   opts.add_ex_constraint("PKIX.EmailProtection");
12✔
81

82
   return opts;
12✔
83
}
×
84

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

88
   opts.uri = "https://botan.randombit.net";
60✔
89
   opts.dns = "botan.randombit.net";
60✔
90
   opts.email = "testing@randombit.net";
60✔
91
   opts.set_padding_scheme(sig_padding);
60✔
92

93
   opts.more_org_units.push_back("IT");
120✔
94
   opts.more_org_units.push_back("Security");
120✔
95
   opts.more_dns.push_back("www.botan.randombit.net");
120✔
96

97
   return opts;
60✔
98
}
×
99

100
std::unique_ptr<Botan::Private_Key> make_a_private_key(const std::string& algo, Botan::RandomNumberGenerator& rng) {
105✔
101
   const std::string params = [&] {
105✔
102
      // Here we override defaults as needed
103
      if(algo == "RSA") {
105✔
104
         return "1024";
105
      }
106
      if(algo == "GOST-34.10") {
89✔
107
         return "gost_256A";
108
      }
109
      if(algo == "ECKCDSA" || algo == "ECGDSA") {
81✔
110
         return "brainpool256r1";
111
      }
112
      if(algo == "HSS-LMS") {
65✔
113
         return "SHA-256,HW(5,4),HW(5,4)";
114
      }
115
      if(algo == "SLH-DSA") {
57✔
116
         return "SLH-DSA-SHA2-128f";
2✔
117
      }
118
      return "";  // default "" means choose acceptable algo-specific params
119
   }();
105✔
120

121
   return Botan::create_private_key(algo, rng, params);
105✔
122
}
105✔
123

124
Test::Result test_cert_status_strings() {
1✔
125
   Test::Result result("Certificate_Status_Code to_string");
1✔
126

127
   std::set<std::string> seen;
1✔
128

129
   result.test_eq("Same string",
1✔
130
                  Botan::to_string(Botan::Certificate_Status_Code::OK),
131
                  Botan::to_string(Botan::Certificate_Status_Code::VERIFIED));
132

133
   const Botan::Certificate_Status_Code codes[]{
1✔
134
      Botan::Certificate_Status_Code::OCSP_RESPONSE_GOOD,
135
      Botan::Certificate_Status_Code::OCSP_SIGNATURE_OK,
136
      Botan::Certificate_Status_Code::VALID_CRL_CHECKED,
137
      Botan::Certificate_Status_Code::OCSP_NO_HTTP,
138

139
      Botan::Certificate_Status_Code::CERT_SERIAL_NEGATIVE,
140
      Botan::Certificate_Status_Code::DN_TOO_LONG,
141

142
      Botan::Certificate_Status_Code::SIGNATURE_METHOD_TOO_WEAK,
143
      Botan::Certificate_Status_Code::NO_MATCHING_CRLDP,
144
      Botan::Certificate_Status_Code::UNTRUSTED_HASH,
145
      Botan::Certificate_Status_Code::NO_REVOCATION_DATA,
146
      Botan::Certificate_Status_Code::CERT_NOT_YET_VALID,
147
      Botan::Certificate_Status_Code::CERT_HAS_EXPIRED,
148
      Botan::Certificate_Status_Code::OCSP_NOT_YET_VALID,
149
      Botan::Certificate_Status_Code::OCSP_HAS_EXPIRED,
150
      Botan::Certificate_Status_Code::CRL_NOT_YET_VALID,
151
      Botan::Certificate_Status_Code::CRL_HAS_EXPIRED,
152
      Botan::Certificate_Status_Code::CERT_ISSUER_NOT_FOUND,
153
      Botan::Certificate_Status_Code::CANNOT_ESTABLISH_TRUST,
154
      Botan::Certificate_Status_Code::CERT_CHAIN_LOOP,
155
      Botan::Certificate_Status_Code::CHAIN_LACKS_TRUST_ROOT,
156
      Botan::Certificate_Status_Code::CHAIN_NAME_MISMATCH,
157
      Botan::Certificate_Status_Code::POLICY_ERROR,
158
      Botan::Certificate_Status_Code::DUPLICATE_CERT_POLICY,
159
      Botan::Certificate_Status_Code::INVALID_USAGE,
160
      Botan::Certificate_Status_Code::CERT_CHAIN_TOO_LONG,
161
      Botan::Certificate_Status_Code::CA_CERT_NOT_FOR_CERT_ISSUER,
162
      Botan::Certificate_Status_Code::NAME_CONSTRAINT_ERROR,
163
      Botan::Certificate_Status_Code::CA_CERT_NOT_FOR_CRL_ISSUER,
164
      Botan::Certificate_Status_Code::OCSP_CERT_NOT_LISTED,
165
      Botan::Certificate_Status_Code::OCSP_BAD_STATUS,
166
      Botan::Certificate_Status_Code::CERT_NAME_NOMATCH,
167
      Botan::Certificate_Status_Code::UNKNOWN_CRITICAL_EXTENSION,
168
      Botan::Certificate_Status_Code::DUPLICATE_CERT_EXTENSION,
169
      Botan::Certificate_Status_Code::EXT_IN_V1_V2_CERT,
170
      Botan::Certificate_Status_Code::OCSP_SIGNATURE_ERROR,
171
      Botan::Certificate_Status_Code::OCSP_ISSUER_NOT_FOUND,
172
      Botan::Certificate_Status_Code::OCSP_RESPONSE_MISSING_KEYUSAGE,
173
      Botan::Certificate_Status_Code::OCSP_RESPONSE_INVALID,
174
      Botan::Certificate_Status_Code::CERT_IS_REVOKED,
175
      Botan::Certificate_Status_Code::CRL_BAD_SIGNATURE,
176
      Botan::Certificate_Status_Code::SIGNATURE_ERROR,
177
      Botan::Certificate_Status_Code::CERT_PUBKEY_INVALID,
178
      Botan::Certificate_Status_Code::SIGNATURE_ALGO_UNKNOWN,
179
      Botan::Certificate_Status_Code::SIGNATURE_ALGO_BAD_PARAMS,
180
   };
181

182
   for(const auto code : codes) {
45✔
183
      const std::string s = Botan::to_string(code);
44✔
184
      result.confirm("String is long enough to be informative", s.size() > 12);
88✔
185
      result.test_eq("No duplicates", seen.count(s), 0);
44✔
186
      seen.insert(s);
44✔
187
   }
44✔
188

189
   return result;
1✔
190
}
1✔
191

192
Test::Result test_x509_extension() {
1✔
193
   Test::Result result("X509 Extensions API");
1✔
194

195
   Botan::Extensions extn;
1✔
196

197
   const auto oid_bc = Botan::OID::from_string("X509v3.BasicConstraints");
1✔
198
   const auto oid_skid = Botan::OID::from_string("X509v3.SubjectKeyIdentifier");
1✔
199

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

202
   result.confirm("Basic constraints is set", extn.extension_set(oid_bc));
2✔
203
   result.confirm("Basic constraints is critical", extn.critical_extension_set(oid_bc));
2✔
204
   result.confirm("SKID is not set", !extn.extension_set(oid_skid));
2✔
205
   result.confirm("SKID is not critical", !extn.critical_extension_set(oid_skid));
2✔
206

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

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

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

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

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

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

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

225
   result.confirm("Delete returns false if extn not set", !extn.remove(oid_skid));
2✔
226
   result.confirm("Delete returns true if extn was set", extn.remove(oid_bc));
2✔
227
   result.confirm("Basic constraints is not set", !extn.extension_set(oid_bc));
2✔
228
   result.confirm("Basic constraints is not critical", !extn.critical_extension_set(oid_bc));
2✔
229

230
   return result;
2✔
231
}
2✔
232

233
Test::Result test_x509_dates() {
1✔
234
   Test::Result result("X509 Time");
1✔
235

236
   Botan::X509_Time time;
1✔
237
   result.confirm("unset time not set", !time.time_is_set());
2✔
238
   time = Botan::X509_Time("080201182200Z", Botan::ASN1_Type::UtcTime);
1✔
239
   result.confirm("time set after construction", time.time_is_set());
2✔
240
   result.test_eq("time readable_string", time.readable_string(), "2008/02/01 18:22:00 UTC");
2✔
241

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

245
   time = Botan::X509_Time("200305100350Z");
1✔
246
   result.test_eq(
3✔
247
      "UTC_OR_GENERALIZED_TIME from UTC_TIME readable_string", time.readable_string(), "2020/03/05 10:03:50 UTC");
2✔
248

249
   time = Botan::X509_Time("20200305100350Z");
1✔
250
   result.test_eq("UTC_OR_GENERALIZED_TIME from GENERALIZED_TIME readable_string",
3✔
251
                  time.readable_string(),
2✔
252
                  "2020/03/05 10:03:50 UTC");
253

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

257
   // Dates that are valid per X.500 but rejected as unsupported
258
   const std::string valid_but_unsup[]{
1✔
259
      "0802010000-0000",
260
      "0802011724+0000",
261
      "0406142334-0500",
262
      "9906142334+0500",
263
      "0006142334-0530",
264
      "0006142334+0530",
265

266
      "080201000000-0000",
267
      "080201172412+0000",
268
      "040614233433-0500",
269
      "990614233444+0500",
270
      "000614233455-0530",
271
      "000614233455+0530",
272
   };
13✔
273

274
   // valid length 13
275
   const std::string valid_utc[]{
1✔
276
      "080201000000Z",
277
      "080201172412Z",
278
      "040614233433Z",
279
      "990614233444Z",
280
      "000614233455Z",
281
   };
6✔
282

283
   const std::string invalid_utc[]{
1✔
284
      "",
285
      " ",
286
      "2008`02-01",
287
      "9999-02-01",
288
      "2000-02-01 17",
289
      "999921",
290

291
      // No seconds
292
      "0802010000Z",
293
      "0802011724Z",
294
      "0406142334Z",
295
      "9906142334Z",
296
      "0006142334Z",
297

298
      // valid length 13 -> range check
299
      "080201000061Z",  // seconds too big (61)
300
      "080201000060Z",  // seconds too big (60, leap seconds not covered by the standard)
301
      "0802010000-1Z",  // seconds too small (-1)
302
      "080201006000Z",  // minutes too big (60)
303
      "080201240000Z",  // hours too big (24:00)
304

305
      // valid length 13 -> invalid numbers
306
      "08020123112 Z",
307
      "08020123112!Z",
308
      "08020123112,Z",
309
      "08020123112\nZ",
310
      "080201232 33Z",
311
      "080201232!33Z",
312
      "080201232,33Z",
313
      "080201232\n33Z",
314
      "0802012 3344Z",
315
      "0802012!3344Z",
316
      "0802012,3344Z",
317
      "08022\n334455Z",
318
      "08022 334455Z",
319
      "08022!334455Z",
320
      "08022,334455Z",
321
      "08022\n334455Z",
322
      "082 33445511Z",
323
      "082!33445511Z",
324
      "082,33445511Z",
325
      "082\n33445511Z",
326
      "2 2211221122Z",
327
      "2!2211221122Z",
328
      "2,2211221122Z",
329
      "2\n2211221122Z",
330

331
      // wrong time zone
332
      "080201000000",
333
      "080201000000z",
334

335
      // Fractional seconds
336
      "170217180154.001Z",
337

338
      // Timezone offset
339
      "170217180154+0100",
340

341
      // Extra digits
342
      "17021718015400Z",
343

344
      // Non-digits
345
      "17021718015aZ",
346

347
      // Trailing garbage
348
      "170217180154Zlongtrailinggarbage",
349

350
      // Swapped type
351
      "20170217180154Z",
352
   };
49✔
353

354
   // valid length 15
355
   const std::string valid_generalized_time[]{
1✔
356
      "20000305100350Z",
357
   };
2✔
358

359
   const std::string invalid_generalized[]{
1✔
360
      // No trailing Z
361
      "20000305100350",
362

363
      // No seconds
364
      "200003051003Z",
365

366
      // Fractional seconds
367
      "20000305100350.001Z",
368

369
      // Timezone offset
370
      "20170217180154+0100",
371

372
      // Extra digits
373
      "2017021718015400Z",
374

375
      // Non-digits
376
      "2017021718015aZ",
377

378
      // Trailing garbage
379
      "20170217180154Zlongtrailinggarbage",
380

381
      // Swapped type
382
      "170217180154Z",
383
   };
9✔
384

385
   for(const auto& v : valid_but_unsup) {
13✔
386
      result.test_throws("valid but unsupported", [v]() { Botan::X509_Time t(v, Botan::ASN1_Type::UtcTime); });
108✔
387
   }
388

389
   for(const auto& v : valid_utc) {
6✔
390
      Botan::X509_Time t(v, Botan::ASN1_Type::UtcTime);
5✔
391
   }
5✔
392

393
   for(const auto& v : valid_generalized_time) {
2✔
394
      Botan::X509_Time t(v, Botan::ASN1_Type::GeneralizedTime);
1✔
395
   }
1✔
396

397
   for(const auto& v : invalid_utc) {
49✔
398
      result.test_throws("invalid", [v]() { Botan::X509_Time t(v, Botan::ASN1_Type::UtcTime); });
432✔
399
   }
400

401
   for(const auto& v : invalid_generalized) {
9✔
402
      result.test_throws("invalid", [v]() { Botan::X509_Time t(v, Botan::ASN1_Type::GeneralizedTime); });
72✔
403
   }
404

405
   return result;
1✔
406
}
80✔
407

408
Test::Result test_x509_encode_authority_info_access_extension() {
1✔
409
   Test::Result result("X509 with encoded PKIX.AuthorityInformationAccess extension");
1✔
410

411
   #if defined(BOTAN_HAS_RSA)
412
   auto rng = Test::new_rng(__func__);
1✔
413

414
   const std::string sig_algo{"RSA"};
1✔
415
   const std::string hash_fn{"SHA-256"};
1✔
416
   const std::string padding_method{"EMSA3(SHA-256)"};
1✔
417

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

423
   // OCSP
424
   const std::string_view ocsp_uri{"http://staging.ocsp.d-trust.net"};
1✔
425

426
   // create a CA
427
   auto ca_key = make_a_private_key(sig_algo, *rng);
1✔
428
   result.require("CA key", ca_key != nullptr);
1✔
429
   const auto ca_cert = Botan::X509::create_self_signed_cert(ca_opts(), *ca_key, hash_fn, *rng);
1✔
430
   Botan::X509_CA ca(ca_cert, *ca_key, hash_fn, padding_method, *rng);
1✔
431

432
   // create a certificate with only caIssuer information
433
   auto key = make_a_private_key(sig_algo, *rng);
1✔
434

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

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

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

442
   if(!result.test_eq("number of ca_issuers URIs", cert.ca_issuers().size(), 2)) {
1✔
443
      return result;
444
   }
445

446
   for(const auto& ca_issuer : cert.ca_issuers()) {
3✔
447
      result.confirm("CA issuer URI present in certificate",
4✔
448
                     std::ranges::find(ca_issuers, ca_issuer) != ca_issuers.end());
4✔
449
   }
1✔
450

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

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

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

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

461
   result.confirm("OCSP URI available", !cert.ocsp_responder().empty());
2✔
462
   result.confirm("no CA Issuer URI available", cert.ca_issuers().empty());
2✔
463
   result.test_eq("OCSP responder URI matches", cert.ocsp_responder(), std::string(ocsp_uri));
3✔
464

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

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

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

473
   result.confirm("OCSP URI available", !cert.ocsp_responder().empty());
2✔
474
   result.confirm("CA Issuer URI available", !cert.ca_issuers().empty());
2✔
475
   #endif
476

477
   return result;
1✔
478
}
4✔
479

480
   #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
481

482
Test::Result test_crl_dn_name() {
1✔
483
   Test::Result result("CRL DN name");
1✔
484

485
      // See GH #1252
486

487
      #if defined(BOTAN_HAS_RSA) && defined(BOTAN_HAS_EMSA_PKCS1)
488
   auto rng = Test::new_rng(__func__);
1✔
489

490
   const Botan::OID dc_oid("0.9.2342.19200300.100.1.25");
1✔
491

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

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

498
   Botan::X509_CRL crl = ca.new_crl(*rng);
1✔
499

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

502
   result.confirm("contains DC component", crl.issuer_dn().get_attributes().count(dc_oid) == 1);
3✔
503
      #endif
504

505
   return result;
2✔
506
}
3✔
507

508
Test::Result test_rdn_multielement_set_name() {
1✔
509
   Test::Result result("DN with multiple elements in RDN");
1✔
510

511
   // GH #2611
512

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

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

518
   return result;
1✔
519
}
1✔
520

521
Test::Result test_rsa_oaep() {
1✔
522
   Test::Result result("RSA OAEP decoding");
1✔
523

524
      #if defined(BOTAN_HAS_RSA)
525
   Botan::X509_Certificate cert(Test::data_file("x509/misc/rsa_oaep.pem"));
2✔
526

527
   auto public_key = cert.subject_public_key();
1✔
528
   result.test_not_null("Decoding RSA-OAEP worked", public_key.get());
1✔
529
   const auto& pk_info = cert.subject_public_key_algo();
1✔
530

531
   result.test_eq("RSA-OAEP OID", pk_info.oid().to_string(), Botan::OID::from_string("RSA/OAEP").to_string());
2✔
532
      #endif
533

534
   return result;
2✔
535
}
1✔
536

537
Test::Result test_x509_decode_list() {
1✔
538
   Test::Result result("X509_Certificate list decode");
1✔
539

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

542
   Botan::BER_Decoder dec(input);
1✔
543
   std::vector<Botan::X509_Certificate> certs;
1✔
544
   dec.decode_list(certs);
1✔
545

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

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

551
   return result;
1✔
552
}
1✔
553

554
Test::Result test_x509_utf8() {
1✔
555
   Test::Result result("X509 with UTF-8 encoded fields");
1✔
556

557
   try {
1✔
558
      Botan::X509_Certificate utf8_cert(Test::data_file("x509/misc/contains_utf8string.pem"));
2✔
559

560
      // UTF-8 encoded fields of test certificate (contains cyrillic letters)
561
      const std::string organization =
1✔
562
         "\xD0\x9C\xD0\xBE\xD1\x8F\x20\xD0\xBA\xD0\xBE\xD0"
563
         "\xBC\xD0\xBF\xD0\xB0\xD0\xBD\xD0\xB8\xD1\x8F";
1✔
564
      const std::string organization_unit =
1✔
565
         "\xD0\x9C\xD0\xBE\xD1\x91\x20\xD0\xBF\xD0\xBE\xD0\xB4\xD1\x80\xD0\xB0"
566
         "\xD0\xB7\xD0\xB4\xD0\xB5\xD0\xBB\xD0\xB5\xD0\xBD\xD0\xB8\xD0\xB5";
1✔
567
      const std::string common_name =
1✔
568
         "\xD0\x9E\xD0\xBF\xD0\xB8\xD1\x81\xD0\xB0\xD0\xBD\xD0\xB8"
569
         "\xD0\xB5\x20\xD1\x81\xD0\xB0\xD0\xB9\xD1\x82\xD0\xB0";
1✔
570
      const std::string location = "\xD0\x9C\xD0\xBE\xD1\x81\xD0\xBA\xD0\xB2\xD0\xB0";
1✔
571

572
      const Botan::X509_DN& issuer_dn = utf8_cert.issuer_dn();
1✔
573

574
      result.test_eq("O", issuer_dn.get_first_attribute("O"), organization);
2✔
575
      result.test_eq("OU", issuer_dn.get_first_attribute("OU"), organization_unit);
2✔
576
      result.test_eq("CN", issuer_dn.get_first_attribute("CN"), common_name);
2✔
577
      result.test_eq("L", issuer_dn.get_first_attribute("L"), location);
2✔
578
   } catch(const Botan::Decoding_Error& ex) {
1✔
579
      result.test_failure(ex.what());
×
580
   }
×
581

582
   return result;
1✔
583
}
×
584

585
Test::Result test_x509_bmpstring() {
1✔
586
   Test::Result result("X509 with UCS-2 (BMPString) encoded fields");
1✔
587

588
   try {
1✔
589
      Botan::X509_Certificate ucs2_cert(Test::data_file("x509/misc/contains_bmpstring.pem"));
2✔
590

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

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

599
      const Botan::X509_DN& issuer_dn = ucs2_cert.issuer_dn();
1✔
600

601
      result.test_eq("O", issuer_dn.get_first_attribute("O"), organization);
2✔
602
      result.test_eq("CN", issuer_dn.get_first_attribute("CN"), common_name);
2✔
603
      result.test_eq("L", issuer_dn.get_first_attribute("L"), location);
2✔
604
   } catch(const Botan::Decoding_Error& ex) {
1✔
605
      result.test_failure(ex.what());
×
606
   }
×
607

608
   return result;
1✔
609
}
×
610

611
Test::Result test_x509_teletex() {
1✔
612
   Test::Result result("X509 with TeletexString encoded fields");
1✔
613

614
   try {
1✔
615
      Botan::X509_Certificate teletex_cert(Test::data_file("x509/misc/teletex_dn.der"));
2✔
616

617
      const Botan::X509_DN& issuer_dn = teletex_cert.issuer_dn();
1✔
618

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

621
      result.test_eq("O", issuer_dn.get_first_attribute("O"), "neam CA");
2✔
622
      result.test_eq("CN", issuer_dn.get_first_attribute("CN"), common_name);
2✔
623
   } catch(const Botan::Decoding_Error& ex) {
1✔
624
      result.test_failure(ex.what());
×
625
   }
×
626

627
   return result;
1✔
628
}
×
629

630
Test::Result test_x509_authority_info_access_extension() {
1✔
631
   Test::Result result("X509 with PKIX.AuthorityInformationAccess extension");
1✔
632

633
   // contains no AIA extension
634
   Botan::X509_Certificate no_aia_cert(Test::data_file("x509/misc/contains_utf8string.pem"));
2✔
635

636
   result.test_eq("number of ca_issuers URLs", no_aia_cert.ca_issuers().size(), 0);
2✔
637
   result.test_eq("CA issuer URL matches", no_aia_cert.ocsp_responder(), "");
2✔
638

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

642
   const auto ca_issuers = aia_cert.ca_issuers();
1✔
643

644
   result.test_eq("number of ca_issuers URLs", ca_issuers.size(), 1);
1✔
645
   if(result.tests_failed()) {
1✔
646
      return result;
647
   }
648

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

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

656
   const auto ca_issuers2 = aia_cert_2ca.ca_issuers();
1✔
657

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

663
   result.test_eq(
2✔
664
      "CA issuer URL matches", ca_issuers2[0], "http://www.d-trust.net/cgi-bin/Bdrive_Test_CA_1-2_2017.crt");
1✔
665
   result.test_eq(
2✔
666
      "CA issuer URL matches",
667
      ca_issuers2[1],
1✔
668
      "ldap://directory.d-trust.net/CN=Bdrive%20Test%20CA%201-2%202017,O=Bundesdruckerei%20GmbH,C=DE?cACertificate?base?");
669
   result.test_eq("OCSP responder URL matches", aia_cert_2ca.ocsp_responder(), "http://staging.ocsp.d-trust.net");
2✔
670

671
   return result;
1✔
672
}
1✔
673

674
Test::Result test_parse_rsa_pss_cert() {
1✔
675
   Test::Result result("X509 RSA-PSS certificate");
1✔
676

677
   // See https://github.com/randombit/botan/issues/3019 for background
678

679
   try {
1✔
680
      Botan::X509_Certificate rsa_pss(Test::data_file("x509/misc/rsa_pss.pem"));
2✔
681
      result.test_success("Was able to parse RSA-PSS certificate signed with ECDSA");
1✔
682
   } catch(Botan::Exception& e) {
1✔
683
      result.test_failure("Parsing failed", e.what());
×
684
   }
×
685

686
   return result;
1✔
687
}
×
688

689
Test::Result test_verify_gost2012_cert() {
1✔
690
   Test::Result result("X509 GOST-2012 certificates");
1✔
691

692
      #if defined(BOTAN_HAS_GOST_34_10_2012) && defined(BOTAN_HAS_STREEBOG)
693
   try {
1✔
694
      Botan::X509_Certificate root_cert(Test::data_file("x509/gost/gost_root.pem"));
2✔
695
      Botan::X509_Certificate root_int(Test::data_file("x509/gost/gost_int.pem"));
2✔
696

697
      Botan::Certificate_Store_In_Memory trusted;
1✔
698
      trusted.add_certificate(root_cert);
1✔
699

700
      const Botan::Path_Validation_Restrictions restrictions(false, 128, false, {"Streebog-256"});
2✔
701
      const Botan::Path_Validation_Result validation_result =
1✔
702
         Botan::x509_path_validate(root_int, restrictions, trusted);
1✔
703

704
      result.confirm("GOST certificate validates", validation_result.successful_validation());
2✔
705
   } catch(const Botan::Decoding_Error& e) {
1✔
706
      result.test_failure(e.what());
×
707
   }
×
708
      #endif
709

710
   return result;
1✔
711
}
×
712

713
      /*
714
 * @brief checks the configurability of the EMSA4(RSA-PSS) signature scheme
715
 *
716
 * For the other algorithms than RSA, only one padding is supported right now.
717
 */
718
      #if defined(BOTAN_HAS_EMSA_PKCS1) && defined(BOTAN_HAS_EMSA_PSSR) && defined(BOTAN_HAS_RSA)
719
Test::Result test_padding_config() {
1✔
720
   // Throughout the test, some synonyms for EMSA4 are used, e.g. PSSR, EMSA-PSS
721
   Test::Result test_result("X509 Padding Config");
1✔
722

723
   auto rng = Test::new_rng(__func__);
1✔
724

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

728
   // Create X509 CA certificate; EMSA3 is used for signing by default
729
   Botan::X509_Cert_Options opt("TESTCA");
1✔
730
   opt.CA_key();
1✔
731

732
   Botan::X509_Certificate ca_cert_def = Botan::X509::create_self_signed_cert(opt, (*sk), "SHA-512", *rng);
1✔
733
   test_result.test_eq("CA certificate signature algorithm (default)",
3✔
734
                       ca_cert_def.signature_algorithm().oid().to_formatted_string(),
2✔
735
                       "RSA/PKCS1v15(SHA-512)");
736

737
   // Create X509 CA certificate; RSA-PSS is explicitly set
738
   opt.set_padding_scheme("PSSR");
1✔
739
   Botan::X509_Certificate ca_cert_exp = Botan::X509::create_self_signed_cert(opt, (*sk), "SHA-512", *rng);
1✔
740
   test_result.test_eq("CA certificate signature algorithm (explicit)",
3✔
741
                       ca_cert_exp.signature_algorithm().oid().to_formatted_string(),
2✔
742
                       "RSA/PSS");
743

744
         #if defined(BOTAN_HAS_EMSA_X931)
745
   // Try to set a padding scheme that is not supported for signing with the given key type
746
   opt.set_padding_scheme("EMSA2");
1✔
747
   try {
1✔
748
      Botan::X509_Certificate ca_cert_wrong = Botan::X509::create_self_signed_cert(opt, (*sk), "SHA-512", *rng);
1✔
749
      test_result.test_failure("Could build CA cert with invalid encoding scheme X9.31 for key type " +
×
750
                               sk->algo_name());
×
751
   } catch(const Botan::Invalid_Argument& e) {
1✔
752
      test_result.test_eq("Build CA certificate with invalid encoding scheme X9.31 for key type " + sk->algo_name(),
3✔
753
                          e.what(),
1✔
754
                          "Signatures using RSA/X9.31(SHA-512) are not supported");
755
   }
1✔
756
         #endif
757

758
   test_result.test_eq("CA certificate signature algorithm (explicit)",
3✔
759
                       ca_cert_exp.signature_algorithm().oid().to_formatted_string(),
2✔
760
                       "RSA/PSS");
761

762
   const Botan::X509_Time not_before = from_date(-1, 1, 1);
1✔
763
   const Botan::X509_Time not_after = from_date(2, 1, 2);
1✔
764

765
   // Prepare a signing request for the end certificate
766
   Botan::X509_Cert_Options req_opt("endpoint");
1✔
767
   req_opt.set_padding_scheme("EMSA4(SHA-512,MGF1,64)");
1✔
768
   Botan::PKCS10_Request end_req = Botan::X509::create_cert_req(req_opt, (*sk), "SHA-512", *rng);
1✔
769
   test_result.test_eq(
3✔
770
      "Certificate request signature algorithm", end_req.signature_algorithm().oid().to_formatted_string(), "RSA/PSS");
2✔
771

772
   // Create X509 CA object: will fail as the chosen hash functions differ
773
   try {
1✔
774
      Botan::X509_CA ca_fail(ca_cert_exp, (*sk), "SHA-512", "EMSA4(SHA-256)", *rng);
1✔
775
      test_result.test_failure("Configured conflicting hash functions for CA");
×
776
   } catch(const Botan::Invalid_Argument& e) {
1✔
777
      test_result.test_eq(
1✔
778
         "Configured conflicting hash functions for CA",
779
         e.what(),
1✔
780
         "Specified hash function SHA-512 is incompatible with RSA chose hash function SHA-256 with user specified padding EMSA4(SHA-256)");
781
   }
1✔
782

783
   // Create X509 CA object: its signer will use the padding scheme from the CA certificate, i.e. EMSA3
784
   Botan::X509_CA ca_def(ca_cert_def, (*sk), "SHA-512", *rng);
1✔
785
   Botan::X509_Certificate end_cert_emsa3 = ca_def.sign_request(end_req, *rng, not_before, not_after);
1✔
786
   test_result.test_eq("End certificate signature algorithm",
3✔
787
                       end_cert_emsa3.signature_algorithm().oid().to_formatted_string(),
2✔
788
                       "RSA/PKCS1v15(SHA-512)");
789

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

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

804
   // Check CRL signature algorithm
805
   Botan::X509_CRL crl = ca_exp.new_crl(*rng);
1✔
806
   test_result.test_eq("CRL signature algorithm", crl.signature_algorithm().oid().to_formatted_string(), "RSA/PSS");
2✔
807

808
   // sanity check for verification, the heavy lifting is done in the other unit tests
809
   const Botan::Certificate_Store_In_Memory trusted(ca_exp.ca_certificate());
1✔
810
   const Botan::Path_Validation_Restrictions restrictions(false, 80);
2✔
811
   const Botan::Path_Validation_Result validation_result =
1✔
812
      Botan::x509_path_validate(end_cert_emsa4, restrictions, trusted);
1✔
813
   test_result.confirm("EMSA4-signed certificate validates", validation_result.successful_validation());
2✔
814

815
   return test_result;
2✔
816
}
3✔
817
      #endif
818

819
   #endif
820

821
Test::Result test_pkcs10_ext(const Botan::Private_Key& key,
12✔
822
                             const std::string& sig_padding,
823
                             const std::string& hash_fn,
824
                             Botan::RandomNumberGenerator& rng) {
825
   Test::Result result("PKCS10 extensions");
12✔
826

827
   Botan::X509_Cert_Options opts;
12✔
828

829
   opts.dns = "main.example.org";
12✔
830
   opts.more_dns.push_back("more1.example.org");
24✔
831
   opts.more_dns.push_back("more2.example.org");
24✔
832

833
   opts.padding_scheme = sig_padding;
12✔
834

835
   Botan::AlternativeName alt_name;
12✔
836
   alt_name.add_attribute("DNS", "bonus.example.org");
12✔
837

838
   Botan::X509_DN alt_dn;
12✔
839
   alt_dn.add_attribute("X520.CommonName", "alt_cn");
12✔
840
   alt_dn.add_attribute("X520.Organization", "testing");
12✔
841
   alt_name.add_dn(alt_dn);
12✔
842

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

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

847
   const auto alt_dns_names = req.subject_alt_name().get_attribute("DNS");
12✔
848

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

851
   if(alt_dns_names.size() == 4) {
12✔
852
      result.test_eq("Expected DNS name 1", alt_dns_names.at(0), "bonus.example.org");
36✔
853
      result.test_eq("Expected DNS name 2", alt_dns_names.at(1), "main.example.org");
36✔
854
      result.test_eq("Expected DNS name 3", alt_dns_names.at(2), "more1.example.org");
36✔
855
      result.test_eq("Expected DNS name 3", alt_dns_names.at(3), "more2.example.org");
36✔
856
   }
857

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

861
   return result;
12✔
862
}
12✔
863

864
Test::Result test_x509_cert(const Botan::Private_Key& ca_key,
12✔
865
                            const std::string& sig_algo,
866
                            const std::string& sig_padding,
867
                            const std::string& hash_fn,
868
                            Botan::RandomNumberGenerator& rng) {
869
   Test::Result result("X509 Unit");
12✔
870

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

874
   {
12✔
875
      result.confirm("ca key usage cert", ca_cert.constraints().includes(Botan::Key_Constraints::KeyCertSign));
24✔
876
      result.confirm("ca key usage crl", ca_cert.constraints().includes(Botan::Key_Constraints::CrlSign));
24✔
877
   }
878

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

882
   Botan::PKCS10_Request user1_req =
12✔
883
      Botan::X509::create_cert_req(req_opts1(sig_algo, sig_padding), *user1_key, hash_fn, rng);
12✔
884

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

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

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

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

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

897
   /* Create the CA object */
898
   Botan::X509_CA ca(ca_cert, ca_key, hash_fn, sig_padding, rng);
12✔
899

900
   const BigInt user1_serial = 99;
12✔
901

902
   /* Sign the requests to create the certs */
903
   Botan::X509_Certificate user1_cert =
12✔
904
      ca.sign_request(user1_req, rng, user1_serial, from_date(-1, 01, 01), from_date(2, 01, 01));
12✔
905

906
   result.test_eq("User1 serial size matches expected", user1_cert.serial_number().size(), 1);
12✔
907
   result.test_eq("User1 serial matches expected", user1_cert.serial_number().at(0), size_t(99));
12✔
908

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

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

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

917
   {
12✔
918
      auto constraints = req_opts1(sig_algo).constraints;
12✔
919
      result.confirm("user1 key usage", user1_cert.constraints().includes(constraints));
24✔
920
   }
921

922
   /* Copy, assign and compare */
923
   Botan::X509_Certificate user1_cert_copy(user1_cert);
12✔
924
   result.test_eq("certificate copy", user1_cert == user1_cert_copy, true);
12✔
925

926
   user1_cert_copy = user2_cert;
12✔
927
   result.test_eq("certificate assignment", user2_cert == user1_cert_copy, true);
12✔
928

929
   Botan::X509_Certificate user1_cert_differ =
12✔
930
      ca.sign_request(user1_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
12✔
931

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

934
   /* Get cert data */
935
   result.test_eq("x509 version", user1_cert.x509_version(), size_t(3));
12✔
936

937
   const Botan::X509_DN& user1_issuer_dn = user1_cert.issuer_dn();
12✔
938
   result.test_eq("issuer info CN", user1_issuer_dn.get_first_attribute("CN"), ca_opts().common_name);
24✔
939
   result.test_eq("issuer info Country", user1_issuer_dn.get_first_attribute("C"), ca_opts().country);
24✔
940
   result.test_eq("issuer info Orga", user1_issuer_dn.get_first_attribute("O"), ca_opts().organization);
24✔
941
   result.test_eq("issuer info OrgaUnit", user1_issuer_dn.get_first_attribute("OU"), ca_opts().org_unit);
24✔
942

943
   const Botan::X509_DN& user3_subject_dn = user3_cert.subject_dn();
12✔
944
   result.test_eq("subject OrgaUnit count",
12✔
945
                  user3_subject_dn.get_attribute("OU").size(),
24✔
946
                  req_opts3(sig_algo).more_org_units.size() + 1);
24✔
947
   result.test_eq(
12✔
948
      "subject OrgaUnit #2", user3_subject_dn.get_attribute("OU").at(1), req_opts3(sig_algo).more_org_units.at(0));
48✔
949

950
   const Botan::AlternativeName& user1_altname = user1_cert.subject_alt_name();
12✔
951
   result.test_eq("subject alt email", user1_altname.get_first_attribute("RFC822"), "testing@randombit.net");
24✔
952
   result.test_eq("subject alt dns", user1_altname.get_first_attribute("DNS"), "botan.randombit.net");
24✔
953
   result.test_eq("subject alt uri", user1_altname.get_first_attribute("URI"), "https://botan.randombit.net");
24✔
954

955
   const Botan::AlternativeName& user3_altname = user3_cert.subject_alt_name();
12✔
956
   result.test_eq(
12✔
957
      "subject alt dns count", user3_altname.get_attribute("DNS").size(), req_opts3(sig_algo).more_dns.size() + 1);
24✔
958
   result.test_eq("subject alt dns #2", user3_altname.get_attribute("DNS").at(1), req_opts3(sig_algo).more_dns.at(0));
48✔
959

960
   const Botan::X509_CRL crl1 = ca.new_crl(rng);
12✔
961

962
   /* Verify the certs */
963
   Botan::Path_Validation_Restrictions restrictions(false, 80);
24✔
964
   Botan::Certificate_Store_In_Memory store;
12✔
965

966
   // First try with an empty store
967
   Botan::Path_Validation_Result result_no_issuer = Botan::x509_path_validate(user1_cert, restrictions, store);
12✔
968
   result.test_eq("user 1 issuer not found",
36✔
969
                  result_no_issuer.result_string(),
24✔
970
                  Botan::Path_Validation_Result::status_string(Botan::Certificate_Status_Code::CERT_ISSUER_NOT_FOUND));
971

972
   store.add_certificate(ca.ca_certificate());
12✔
973

974
   Botan::Path_Validation_Result result_u1 = Botan::x509_path_validate(user1_cert, restrictions, store);
12✔
975
   if(!result.confirm("user 1 validates", result_u1.successful_validation())) {
24✔
976
      result.test_note("user 1 validation result was " + result_u1.result_string());
×
977
   }
978

979
   Botan::Path_Validation_Result result_u2 = Botan::x509_path_validate(user2_cert, restrictions, store);
12✔
980
   if(!result.confirm("user 2 validates", result_u2.successful_validation())) {
24✔
981
      result.test_note("user 2 validation result was " + result_u2.result_string());
×
982
   }
983

984
   Botan::Path_Validation_Result result_self_signed = Botan::x509_path_validate(user1_ss_cert, restrictions, store);
12✔
985
   result.test_eq("user 1 issuer not found",
36✔
986
                  result_no_issuer.result_string(),
24✔
987
                  Botan::Path_Validation_Result::status_string(Botan::Certificate_Status_Code::CERT_ISSUER_NOT_FOUND));
988
   store.add_crl(crl1);
12✔
989

990
   std::vector<Botan::CRL_Entry> revoked;
12✔
991
   revoked.push_back(Botan::CRL_Entry(user1_cert, Botan::CRL_Code::CessationOfOperation));
24✔
992
   revoked.push_back(user2_cert);
24✔
993

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

996
   store.add_crl(crl2);
12✔
997

998
   const std::string revoked_str =
12✔
999
      Botan::Path_Validation_Result::status_string(Botan::Certificate_Status_Code::CERT_IS_REVOKED);
12✔
1000

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

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

1007
   revoked.clear();
12✔
1008
   revoked.push_back(Botan::CRL_Entry(user1_cert, Botan::CRL_Code::RemoveFromCrl));
24✔
1009
   Botan::X509_CRL crl3 = ca.update_crl(crl2, revoked, rng);
12✔
1010

1011
   store.add_crl(crl3);
12✔
1012

1013
   result_u1 = Botan::x509_path_validate(user1_cert, restrictions, store);
12✔
1014
   if(!result.confirm("user 1 validates", result_u1.successful_validation())) {
24✔
1015
      result.test_note("user 1 validation result was " + result_u1.result_string());
×
1016
   }
1017

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

1021
   return result;
12✔
1022
}
60✔
1023

1024
Test::Result test_usage(const Botan::Private_Key& ca_key,
12✔
1025
                        const std::string& sig_algo,
1026
                        const std::string& hash_fn,
1027
                        Botan::RandomNumberGenerator& rng) {
1028
   using Botan::Key_Constraints;
12✔
1029
   using Botan::Usage_Type;
12✔
1030

1031
   Test::Result result("X509 Usage");
12✔
1032

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

1036
   /* Create the CA object */
1037
   const Botan::X509_CA ca(ca_cert, ca_key, hash_fn, rng);
12✔
1038

1039
   auto user1_key = make_a_private_key(sig_algo, rng);
12✔
1040

1041
   Botan::X509_Cert_Options opts("Test User 1/US/Botan Project/Testing");
12✔
1042
   opts.constraints = Key_Constraints::DigitalSignature;
12✔
1043

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

1046
   const Botan::X509_Certificate user1_cert =
12✔
1047
      ca.sign_request(user1_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
12✔
1048

1049
   // cert only allows digitalSignature, but we check for both digitalSignature and cRLSign
1050
   result.test_eq(
12✔
1051
      "key usage cRLSign not allowed",
1052
      user1_cert.allowed_usage(Key_Constraints(Key_Constraints::DigitalSignature | Key_Constraints::CrlSign)),
12✔
1053
      false);
1054
   result.test_eq("encryption is not allowed", user1_cert.allowed_usage(Usage_Type::ENCRYPTION), false);
12✔
1055

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

1059
   opts.constraints = Key_Constraints(Key_Constraints::DigitalSignature | Key_Constraints::CrlSign);
12✔
1060

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

1063
   const Botan::X509_Certificate mult_usage_cert =
12✔
1064
      ca.sign_request(mult_usage_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
12✔
1065

1066
   // cert allows multiple usages, so each one of them as well as both together should be allowed
1067
   result.confirm("key usage multiple digitalSignature allowed",
24✔
1068
                  mult_usage_cert.allowed_usage(Key_Constraints::DigitalSignature));
12✔
1069
   result.confirm("key usage multiple cRLSign allowed", mult_usage_cert.allowed_usage(Key_Constraints::CrlSign));
24✔
1070
   result.confirm(
24✔
1071
      "key usage multiple digitalSignature and cRLSign allowed",
1072
      mult_usage_cert.allowed_usage(Key_Constraints(Key_Constraints::DigitalSignature | Key_Constraints::CrlSign)));
12✔
1073
   result.test_eq("encryption is not allowed", mult_usage_cert.allowed_usage(Usage_Type::ENCRYPTION), false);
12✔
1074

1075
   opts.constraints = Key_Constraints();
12✔
1076

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

1079
   const Botan::X509_Certificate no_usage_cert =
12✔
1080
      ca.sign_request(no_usage_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
12✔
1081

1082
   // cert allows every usage
1083
   result.confirm("key usage digitalSignature allowed", no_usage_cert.allowed_usage(Key_Constraints::DigitalSignature));
24✔
1084
   result.confirm("key usage cRLSign allowed", no_usage_cert.allowed_usage(Key_Constraints::CrlSign));
24✔
1085
   result.confirm("key usage encryption allowed", no_usage_cert.allowed_usage(Usage_Type::ENCRYPTION));
24✔
1086

1087
   if(sig_algo == "RSA") {
12✔
1088
      // cert allows data encryption
1089
      opts.constraints = Key_Constraints(Key_Constraints::KeyEncipherment | Key_Constraints::DataEncipherment);
1✔
1090

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

1093
      const Botan::X509_Certificate enc_cert =
1✔
1094
         ca.sign_request(enc_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
1✔
1095

1096
      result.confirm("cert allows encryption", enc_cert.allowed_usage(Usage_Type::ENCRYPTION));
2✔
1097
      result.confirm("cert does not allow TLS client auth", !enc_cert.allowed_usage(Usage_Type::TLS_CLIENT_AUTH));
2✔
1098
   }
1✔
1099

1100
   return result;
12✔
1101
}
24✔
1102

1103
Test::Result test_self_issued(const Botan::Private_Key& ca_key,
12✔
1104
                              const std::string& sig_algo,
1105
                              const std::string& sig_padding,
1106
                              const std::string& hash_fn,
1107
                              Botan::RandomNumberGenerator& rng) {
1108
   using Botan::Key_Constraints;
12✔
1109

1110
   Test::Result result("X509 Self Issued");
12✔
1111

1112
   // create the self-signed cert
1113
   const Botan::X509_Certificate ca_cert =
12✔
1114
      Botan::X509::create_self_signed_cert(ca_opts(sig_padding), ca_key, hash_fn, rng);
12✔
1115

1116
   /* Create the CA object */
1117
   const Botan::X509_CA ca(ca_cert, ca_key, hash_fn, sig_padding, rng);
12✔
1118

1119
   auto user_key = make_a_private_key(sig_algo, rng);
12✔
1120

1121
   // create a self-issued certificate, that is, a certificate with subject dn == issuer dn,
1122
   // but signed by a CA, not signed by it's own private key
1123
   Botan::X509_Cert_Options opts = ca_opts();
12✔
1124
   opts.constraints = Key_Constraints::DigitalSignature;
12✔
1125
   opts.set_padding_scheme(sig_padding);
12✔
1126

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

1129
   const Botan::X509_Certificate self_issued_cert =
12✔
1130
      ca.sign_request(self_issued_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
12✔
1131

1132
   // check that this chain can can be verified successfully
1133
   const Botan::Certificate_Store_In_Memory trusted(ca.ca_certificate());
12✔
1134

1135
   const Botan::Path_Validation_Restrictions restrictions(false, 80);
24✔
1136

1137
   const Botan::Path_Validation_Result validation_result =
12✔
1138
      Botan::x509_path_validate(self_issued_cert, restrictions, trusted);
12✔
1139

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

1142
   return result;
12✔
1143
}
24✔
1144

1145
Test::Result test_x509_uninit() {
1✔
1146
   Test::Result result("X509 object uninitialized access");
1✔
1147

1148
   Botan::X509_Certificate cert;
1✔
1149
   result.test_throws("uninitialized cert access causes exception", "X509_Certificate uninitialized", [&cert]() {
2✔
1150
      cert.x509_version();
1✔
1151
   });
1152

1153
   Botan::X509_CRL crl;
1✔
1154
   result.test_throws(
2✔
1155
      "uninitialized crl access causes exception", "X509_CRL uninitialized", [&crl]() { crl.crl_number(); });
2✔
1156

1157
   return result;
1✔
1158
}
1✔
1159

1160
Test::Result test_valid_constraints(const Botan::Private_Key& key, const std::string& pk_algo) {
19✔
1161
   using Botan::Key_Constraints;
19✔
1162

1163
   Test::Result result("X509 Valid Constraints " + pk_algo);
19✔
1164

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

1167
   // Now check some typical usage scenarios for the given key type
1168
   // Taken from RFC 5280, sec. 4.2.1.3
1169
   // ALL constraints are not typical at all, but we use them for a negative test
1170
   const auto all = Key_Constraints(
19✔
1171
      Key_Constraints::DigitalSignature | Key_Constraints::NonRepudiation | Key_Constraints::KeyEncipherment |
1172
      Key_Constraints::DataEncipherment | Key_Constraints::KeyAgreement | Key_Constraints::KeyCertSign |
1173
      Key_Constraints::CrlSign | Key_Constraints::EncipherOnly | Key_Constraints::DecipherOnly);
19✔
1174

1175
   const auto ca = Key_Constraints(Key_Constraints::KeyCertSign);
19✔
1176
   const auto sign_data = Key_Constraints(Key_Constraints::DigitalSignature);
19✔
1177
   const auto non_repudiation = Key_Constraints(Key_Constraints::NonRepudiation | Key_Constraints::DigitalSignature);
19✔
1178
   const auto key_encipherment = Key_Constraints(Key_Constraints::KeyEncipherment);
19✔
1179
   const auto data_encipherment = Key_Constraints(Key_Constraints::DataEncipherment);
19✔
1180
   const auto key_agreement = Key_Constraints(Key_Constraints::KeyAgreement);
19✔
1181
   const auto key_agreement_encipher_only =
19✔
1182
      Key_Constraints(Key_Constraints::KeyAgreement | Key_Constraints::EncipherOnly);
19✔
1183
   const auto key_agreement_decipher_only =
19✔
1184
      Key_Constraints(Key_Constraints::KeyAgreement | Key_Constraints::DecipherOnly);
19✔
1185
   const auto crl_sign = Key_Constraints(Key_Constraints::CrlSign);
19✔
1186
   const auto sign_everything =
19✔
1187
      Key_Constraints(Key_Constraints::DigitalSignature | Key_Constraints::KeyCertSign | Key_Constraints::CrlSign);
19✔
1188

1189
   if(pk_algo == "DH" || pk_algo == "ECDH") {
19✔
1190
      // DH and ECDH only for key agreement
1191
      result.test_eq("all constraints not permitted", all.compatible_with(key), false);
2✔
1192
      result.test_eq("cert sign not permitted", ca.compatible_with(key), false);
2✔
1193
      result.test_eq("signature not permitted", sign_data.compatible_with(key), false);
2✔
1194
      result.test_eq("non repudiation not permitted", non_repudiation.compatible_with(key), false);
2✔
1195
      result.test_eq("key encipherment not permitted", key_encipherment.compatible_with(key), false);
2✔
1196
      result.test_eq("data encipherment not permitted", data_encipherment.compatible_with(key), false);
2✔
1197
      result.test_eq("usage acceptable", key_agreement.compatible_with(key), true);
2✔
1198
      result.test_eq("usage acceptable", key_agreement_encipher_only.compatible_with(key), true);
2✔
1199
      result.test_eq("usage acceptable", key_agreement_decipher_only.compatible_with(key), true);
2✔
1200
      result.test_eq("crl sign not permitted", crl_sign.compatible_with(key), false);
2✔
1201
      result.test_eq("sign", sign_everything.compatible_with(key), false);
4✔
1202
   } else if(pk_algo == "Kyber" || pk_algo == "FrodoKEM" || pk_algo == "ML-KEM" || pk_algo == "ClassicMcEliece") {
17✔
1203
      // KEMs can encrypt and agree
1204
      result.test_eq("all constraints not permitted", all.compatible_with(key), false);
4✔
1205
      result.test_eq("cert sign not permitted", ca.compatible_with(key), false);
4✔
1206
      result.test_eq("signature not permitted", sign_data.compatible_with(key), false);
4✔
1207
      result.test_eq("non repudiation not permitted", non_repudiation.compatible_with(key), false);
4✔
1208
      result.test_eq("crl sign not permitted", crl_sign.compatible_with(key), false);
4✔
1209
      result.test_eq("sign", sign_everything.compatible_with(key), false);
4✔
1210
      result.test_eq("key agreement not permitted", key_agreement.compatible_with(key), false);
4✔
1211
      result.test_eq("usage acceptable", data_encipherment.compatible_with(key), false);
4✔
1212
      result.test_eq("usage acceptable", key_encipherment.compatible_with(key), true);
8✔
1213
   } else if(pk_algo == "RSA") {
13✔
1214
      // RSA can do everything except key agreement
1215
      result.test_eq("all constraints not permitted", all.compatible_with(key), false);
1✔
1216

1217
      result.test_eq("usage acceptable", ca.compatible_with(key), true);
1✔
1218
      result.test_eq("usage acceptable", sign_data.compatible_with(key), true);
1✔
1219
      result.test_eq("usage acceptable", non_repudiation.compatible_with(key), true);
1✔
1220
      result.test_eq("usage acceptable", key_encipherment.compatible_with(key), true);
1✔
1221
      result.test_eq("usage acceptable", data_encipherment.compatible_with(key), true);
1✔
1222
      result.test_eq("key agreement not permitted", key_agreement.compatible_with(key), false);
1✔
1223
      result.test_eq("key agreement", key_agreement_encipher_only.compatible_with(key), false);
1✔
1224
      result.test_eq("key agreement", key_agreement_decipher_only.compatible_with(key), false);
1✔
1225
      result.test_eq("usage acceptable", crl_sign.compatible_with(key), true);
1✔
1226
      result.test_eq("usage acceptable", sign_everything.compatible_with(key), true);
2✔
1227
   } else if(pk_algo == "ElGamal") {
12✔
1228
      // only ElGamal encryption is currently implemented
1229
      result.test_eq("all constraints not permitted", all.compatible_with(key), false);
1✔
1230
      result.test_eq("cert sign not permitted", ca.compatible_with(key), false);
1✔
1231
      result.test_eq("data encipherment permitted", data_encipherment.compatible_with(key), true);
1✔
1232
      result.test_eq("key encipherment permitted", key_encipherment.compatible_with(key), true);
1✔
1233
      result.test_eq("key agreement not permitted", key_agreement.compatible_with(key), false);
1✔
1234
      result.test_eq("key agreement", key_agreement_encipher_only.compatible_with(key), false);
1✔
1235
      result.test_eq("key agreement", key_agreement_decipher_only.compatible_with(key), false);
1✔
1236
      result.test_eq("crl sign not permitted", crl_sign.compatible_with(key), false);
1✔
1237
      result.test_eq("sign", sign_everything.compatible_with(key), false);
2✔
1238
   } else if(pk_algo == "DSA" || pk_algo == "ECDSA" || pk_algo == "ECGDSA" || pk_algo == "ECKCDSA" ||
10✔
1239
             pk_algo == "GOST-34.10" || pk_algo == "Dilithium" || pk_algo == "ML-DSA" || pk_algo == "SLH-DSA" ||
18✔
1240
             pk_algo == "HSS-LMS") {
3✔
1241
      // these are signature algorithms only
1242
      result.test_eq("all constraints not permitted", all.compatible_with(key), false);
9✔
1243

1244
      result.test_eq("ca allowed", ca.compatible_with(key), true);
9✔
1245
      result.test_eq("sign allowed", sign_data.compatible_with(key), true);
9✔
1246
      result.test_eq("non-repudiation allowed", non_repudiation.compatible_with(key), true);
9✔
1247
      result.test_eq("key encipherment not permitted", key_encipherment.compatible_with(key), false);
9✔
1248
      result.test_eq("data encipherment not permitted", data_encipherment.compatible_with(key), false);
9✔
1249
      result.test_eq("key agreement not permitted", key_agreement.compatible_with(key), false);
9✔
1250
      result.test_eq("key agreement", key_agreement_encipher_only.compatible_with(key), false);
9✔
1251
      result.test_eq("key agreement", key_agreement_decipher_only.compatible_with(key), false);
9✔
1252
      result.test_eq("crl sign allowed", crl_sign.compatible_with(key), true);
9✔
1253
      result.test_eq("sign allowed", sign_everything.compatible_with(key), true);
18✔
1254
   }
1255

1256
   return result;
19✔
1257
}
×
1258

1259
/**
1260
 * @brief X.509v3 extension that encodes a given string
1261
 */
1262
class String_Extension final : public Botan::Certificate_Extension {
24✔
1263
   public:
1264
      String_Extension() = default;
24✔
1265

1266
      explicit String_Extension(const std::string& val) : m_contents(val) {}
12✔
1267

1268
      std::string value() const { return m_contents; }
48✔
1269

1270
      std::unique_ptr<Certificate_Extension> copy() const override {
×
1271
         return std::make_unique<String_Extension>(m_contents);
×
1272
      }
1273

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

1276
      bool should_encode() const override { return true; }
24✔
1277

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

1280
      std::vector<uint8_t> encode_inner() const override {
12✔
1281
         std::vector<uint8_t> bits;
12✔
1282
         Botan::DER_Encoder(bits).encode(Botan::ASN1_String(m_contents, Botan::ASN1_Type::Utf8String));
12✔
1283
         return bits;
12✔
1284
      }
×
1285

1286
      void decode_inner(const std::vector<uint8_t>& in) override {
24✔
1287
         Botan::ASN1_String str;
24✔
1288
         Botan::BER_Decoder(in).decode(str, Botan::ASN1_Type::Utf8String).verify_end();
24✔
1289
         m_contents = str.value();
24✔
1290
      }
24✔
1291

1292
   private:
1293
      std::string m_contents;
1294
};
1295

1296
Test::Result test_custom_dn_attr(const Botan::Private_Key& ca_key,
12✔
1297
                                 const std::string& sig_algo,
1298
                                 const std::string& sig_padding,
1299
                                 const std::string& hash_fn,
1300
                                 Botan::RandomNumberGenerator& rng) {
1301
   Test::Result result("X509 Custom DN");
12✔
1302

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

1306
   /* Create the CA object */
1307
   Botan::X509_CA ca(ca_cert, ca_key, hash_fn, sig_padding, rng);
12✔
1308

1309
   auto user_key = make_a_private_key(sig_algo, rng);
12✔
1310

1311
   Botan::X509_DN subject_dn;
12✔
1312

1313
   const Botan::OID attr1(Botan::OID("1.3.6.1.4.1.25258.9.1.1"));
12✔
1314
   const Botan::OID attr2(Botan::OID("1.3.6.1.4.1.25258.9.1.2"));
12✔
1315
   const Botan::ASN1_String val1("Custom Attr 1", Botan::ASN1_Type::PrintableString);
12✔
1316
   const Botan::ASN1_String val2("12345", Botan::ASN1_Type::Utf8String);
12✔
1317

1318
   subject_dn.add_attribute(attr1, val1);
12✔
1319
   subject_dn.add_attribute(attr2, val2);
12✔
1320

1321
   Botan::Extensions extensions;
12✔
1322

1323
   Botan::PKCS10_Request req =
12✔
1324
      Botan::PKCS10_Request::create(*user_key, subject_dn, extensions, hash_fn, rng, sig_padding);
12✔
1325

1326
   const Botan::X509_DN& req_dn = req.subject_dn();
12✔
1327

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

1330
   Botan::ASN1_String req_val1 = req_dn.get_first_attribute(attr1);
12✔
1331
   Botan::ASN1_String req_val2 = req_dn.get_first_attribute(attr2);
12✔
1332
   result.confirm("Attr1 matches encoded", req_val1 == val1);
24✔
1333
   result.confirm("Attr2 matches encoded", req_val2 == val2);
24✔
1334
   result.confirm("Attr1 tag matches encoded", req_val1.tagging() == val1.tagging());
24✔
1335
   result.confirm("Attr2 tag matches encoded", req_val2.tagging() == val2.tagging());
24✔
1336

1337
   Botan::X509_Time not_before("100301123001Z", Botan::ASN1_Type::UtcTime);
12✔
1338
   Botan::X509_Time not_after("300301123001Z", Botan::ASN1_Type::UtcTime);
12✔
1339

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

1342
   const Botan::X509_DN& cert_dn = cert.subject_dn();
12✔
1343

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

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

1353
   return result;
12✔
1354
}
36✔
1355

1356
Test::Result test_x509_extensions(const Botan::Private_Key& ca_key,
12✔
1357
                                  const std::string& sig_algo,
1358
                                  const std::string& sig_padding,
1359
                                  const std::string& hash_fn,
1360
                                  Botan::RandomNumberGenerator& rng) {
1361
   using Botan::Key_Constraints;
12✔
1362

1363
   Test::Result result("X509 Extensions");
12✔
1364

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

1368
   /* Create the CA object */
1369
   Botan::X509_CA ca(ca_cert, ca_key, hash_fn, sig_padding, rng);
12✔
1370

1371
   /* Prepare CDP extension */
1372
   std::vector<std::string> cdp_urls = {
12✔
1373
      "http://example.com/crl1.pem",
1374
      "ldap://ldap.example.com/cn=crl1,dc=example,dc=com?certificateRevocationList;binary"};
12✔
1375

1376
   std::vector<Botan::Cert_Extension::CRL_Distribution_Points::Distribution_Point> dps;
12✔
1377

1378
   for(const auto& uri : cdp_urls) {
36✔
1379
      Botan::AlternativeName cdp_alt_name;
24✔
1380
      cdp_alt_name.add_uri(uri);
24✔
1381
      Botan::Cert_Extension::CRL_Distribution_Points::Distribution_Point dp(cdp_alt_name);
24✔
1382

1383
      dps.emplace_back(dp);
24✔
1384
   }
24✔
1385

1386
   auto user_key = make_a_private_key(sig_algo, rng);
12✔
1387

1388
   Botan::X509_Cert_Options opts("Test User 1/US/Botan Project/Testing");
12✔
1389
   opts.constraints = Key_Constraints::DigitalSignature;
12✔
1390

1391
   // include a custom extension in the request
1392
   Botan::Extensions req_extensions;
12✔
1393
   const Botan::OID oid("1.2.3.4.5.6.7.8.9.1");
12✔
1394
   const Botan::OID ku_oid = Botan::OID::from_string("X509v3.KeyUsage");
12✔
1395
   req_extensions.add(std::make_unique<String_Extension>("AAAAAAAAAAAAAABCDEF"), false);
24✔
1396
   req_extensions.add(std::make_unique<Botan::Cert_Extension::CRL_Distribution_Points>(dps));
24✔
1397
   opts.extensions = req_extensions;
12✔
1398
   opts.set_padding_scheme(sig_padding);
12✔
1399

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

1403
   result.confirm("Extensions::extension_set true for Key_Usage",
24✔
1404
                  self_signed_cert.v3_extensions().extension_set(ku_oid));
12✔
1405

1406
   // check if known Key_Usage extension is present in self-signed cert
1407
   auto key_usage_ext = self_signed_cert.v3_extensions().get(ku_oid);
12✔
1408
   if(result.confirm("Key_Usage extension present in self-signed certificate", key_usage_ext != nullptr)) {
24✔
1409
      result.confirm(
24✔
1410
         "Key_Usage extension value matches in self-signed certificate",
1411
         dynamic_cast<Botan::Cert_Extension::Key_Usage&>(*key_usage_ext).get_constraints() == opts.constraints);
12✔
1412
   }
1413

1414
   // check if custom extension is present in self-signed cert
1415
   auto string_ext = self_signed_cert.v3_extensions().get_raw<String_Extension>(oid);
12✔
1416
   if(result.confirm("Custom extension present in self-signed certificate", string_ext != nullptr)) {
24✔
1417
      result.test_eq(
48✔
1418
         "Custom extension value matches in self-signed certificate", string_ext->value(), "AAAAAAAAAAAAAABCDEF");
36✔
1419
   }
1420

1421
   // check if CDPs are present in the self-signed cert
1422
   auto cert_cdps =
12✔
1423
      self_signed_cert.v3_extensions().get_extension_object_as<Botan::Cert_Extension::CRL_Distribution_Points>();
24✔
1424

1425
   if(result.confirm("CRL Distribution Points extension present in self-signed certificate",
24✔
1426
                     !cert_cdps->crl_distribution_urls().empty())) {
12✔
1427
      for(const auto& cdp : cert_cdps->distribution_points()) {
36✔
1428
         result.confirm("CDP URI present in self-signed certificate",
48✔
1429
                        std::ranges::find(cdp_urls, cdp.point().get_first_attribute("URI")) != cdp_urls.end());
72✔
1430
      }
1431
   }
1432

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

1435
   /* Create a CA-signed certificate */
1436
   const Botan::X509_Certificate ca_signed_cert =
12✔
1437
      ca.sign_request(user_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
12✔
1438

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

1442
   key_usage_ext = ca_signed_cert.v3_extensions().get(ku_oid);
24✔
1443
   if(result.confirm("Key_Usage extension present in CA-signed certificate", key_usage_ext != nullptr)) {
24✔
1444
      auto constraints = dynamic_cast<Botan::Cert_Extension::Key_Usage&>(*key_usage_ext).get_constraints();
12✔
1445
      result.confirm("Key_Usage extension value matches in user certificate",
24✔
1446
                     constraints == Botan::Key_Constraints::DigitalSignature);
12✔
1447
   }
1448

1449
   // check if custom extension is present in CA-signed cert
1450
   result.confirm("Extensions::extension_set true for String_Extension",
24✔
1451
                  ca_signed_cert.v3_extensions().extension_set(oid));
12✔
1452
   string_ext = ca_signed_cert.v3_extensions().get_raw<String_Extension>(oid);
24✔
1453
   if(result.confirm("Custom extension present in CA-signed certificate", string_ext != nullptr)) {
24✔
1454
      result.test_eq(
48✔
1455
         "Custom extension value matches in CA-signed certificate", string_ext->value(), "AAAAAAAAAAAAAABCDEF");
36✔
1456
   }
1457

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

1461
   if(result.confirm("CRL Distribution Points extension present in self-signed certificate",
24✔
1462
                     !cert_cdps->crl_distribution_urls().empty())) {
12✔
1463
      for(const auto& cdp : cert_cdps->distribution_points()) {
36✔
1464
         result.confirm("CDP URI present in self-signed certificate",
48✔
1465
                        std::ranges::find(cdp_urls, cdp.point().get_first_attribute("URI")) != cdp_urls.end());
72✔
1466
      }
1467
   }
1468

1469
   return result;
12✔
1470
}
60✔
1471

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

1475
   struct TestData {
12✔
1476
         const std::string issuer, subject, issuer_hash, subject_hash;
1477
   } const cases[]{{"",
12✔
1478
                    "",
1479
                    "E4F60D0AA6D7F3D3B6A6494B1C861B99F649C6F9EC51ABAF201B20F297327C95",
1480
                    "E4F60D0AA6D7F3D3B6A6494B1C861B99F649C6F9EC51ABAF201B20F297327C95"},
1481
                   {"a",
1482
                    "b",
1483
                    "BC2E013472F39AC579964880E422737C82BA812CB8BC2FD17E013060D71E6E19",
1484
                    "5E31CFAA3FAFB1A5BA296A0D2BAB9CA44D7936E9BF0BBC54637D0C53DBC4A432"},
1485
                   {"A",
1486
                    "B",
1487
                    "4B3206201C4BC9B6CD6C36532A97687DF9238155D99ADB60C66BF2B2220643D8",
1488
                    "FFF635A52A16618B4A0E9CD26B5E5A2FA573D343C051E6DE8B0811B1ACC89B86"},
1489
                   {
1490
                      "Test Issuer/US/Botan Project/Testing",
1491
                      "Test Subject/US/Botan Project/Testing",
1492
                      "ACB4F373004A56A983A23EB8F60FA4706312B5DB90FD978574FE7ACC84E093A5",
1493
                      "87039231C2205B74B6F1F3830A66272C0B41F71894B03AC3150221766D95267B",
1494
                   },
1495
                   {
1496
                      "Test Subject/US/Botan Project/Testing",
1497
                      "Test Issuer/US/Botan Project/Testing",
1498
                      "87039231C2205B74B6F1F3830A66272C0B41F71894B03AC3150221766D95267B",
1499
                      "ACB4F373004A56A983A23EB8F60FA4706312B5DB90FD978574FE7ACC84E093A5",
1500
                   }};
72✔
1501

1502
   for(const auto& a : cases) {
72✔
1503
      Botan::X509_Cert_Options opts{a.issuer};
60✔
1504
      opts.CA_key();
60✔
1505

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

1508
      result.test_eq(a.issuer, Botan::hex_encode(issuer_cert.raw_issuer_dn_sha256()), a.issuer_hash);
120✔
1509
      result.test_eq(a.issuer, Botan::hex_encode(issuer_cert.raw_subject_dn_sha256()), a.issuer_hash);
120✔
1510

1511
      const Botan::X509_CA ca(issuer_cert, key, hash_fn, rng);
60✔
1512
      const Botan::PKCS10_Request req =
60✔
1513
         Botan::X509::create_cert_req(Botan::X509_Cert_Options(a.subject), key, hash_fn, rng);
60✔
1514
      const Botan::X509_Certificate subject_cert =
60✔
1515
         ca.sign_request(req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
60✔
1516

1517
      result.test_eq(a.subject, Botan::hex_encode(subject_cert.raw_issuer_dn_sha256()), a.issuer_hash);
120✔
1518
      result.test_eq(a.subject, Botan::hex_encode(subject_cert.raw_subject_dn_sha256()), a.subject_hash);
120✔
1519
   }
60✔
1520
   return result;
12✔
1521
}
72✔
1522

1523
   #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
1524

1525
Test::Result test_x509_tn_auth_list_extension_decode() {
1✔
1526
   /* cert with TNAuthList extension data was generated by asn1parse cfg:
1527

1528
      asn1=SEQUENCE:tn_auth_list
1529

1530
      [tn_auth_list]
1531
      spc=EXP:0,IA5:1001
1532
      range=EXP:1,SEQUENCE:TelephoneNumberRange
1533
      one=EXP:2,IA5:333
1534

1535
      [TelephoneNumberRange]
1536
      start1=IA5:111
1537
      count1=INT:128
1538
      start2=IA5:222
1539
      count2=INT:256
1540
    */
1541
   const std::string filename("TNAuthList.pem");
1✔
1542
   Test::Result result("X509 TNAuthList decode");
1✔
1543
   result.start_timer();
1✔
1544

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

1547
   using Botan::Cert_Extension::TNAuthList;
1✔
1548

1549
   auto tn_auth_list = cert.v3_extensions().get_extension_object_as<TNAuthList>();
2✔
1550

1551
   auto& tn_entries = tn_auth_list->entries();
1✔
1552

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

1555
   result.test_throws("wrong telephone_number_range() accessor for spc",
2✔
1556
                      [&tn_entries] { tn_entries[0].telephone_number_range(); });
2✔
1557
   result.test_throws("wrong telephone_number() accessor for range",
2✔
1558
                      [&tn_entries] { tn_entries[1].telephone_number(); });
2✔
1559
   result.test_throws("wrong service_provider_code() accessor for one",
2✔
1560
                      [&tn_entries] { tn_entries[2].service_provider_code(); });
2✔
1561

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

1565
   result.test_eq("range entry type", tn_entries[1].type() == TNAuthList::Entry::TelephoneNumberRange, true);
1✔
1566
   auto& range = tn_entries[1].telephone_number_range();
1✔
1567
   result.test_eq("range entries count", range.size(), 2);
1✔
1568
   result.test_eq("range entry 0 start data", range[0].start.value(), "111");
2✔
1569
   result.test_eq("range entry 0 count data", range[0].count, 128);
1✔
1570
   result.test_eq("range entry 1 start data", range[1].start.value(), "222");
2✔
1571
   result.test_eq("range entry 1 count data", range[1].count, 256);
1✔
1572

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

1576
   result.end_timer();
1✔
1577
   return result;
2✔
1578
}
1✔
1579

1580
   #endif
1581

1582
std::vector<std::string> get_sig_paddings(const std::string& sig_algo, const std::string& hash) {
12✔
1583
   if(sig_algo == "RSA") {
12✔
1584
      return {"EMSA3(" + hash + ")", "EMSA4(" + hash + ")"};
5✔
1585
   } else if(sig_algo == "DSA" || sig_algo == "ECDSA" || sig_algo == "ECGDSA" || sig_algo == "ECKCDSA" ||
11✔
1586
             sig_algo == "GOST-34.10") {
7✔
1587
      return {hash};
10✔
1588
   } else if(sig_algo == "Ed25519" || sig_algo == "Ed448") {
6✔
1589
      return {"Pure"};
2✔
1590
   } else if(sig_algo == "Dilithium" || sig_algo == "ML-DSA") {
4✔
1591
      return {"Randomized"};
2✔
1592
   } else if(sig_algo == "HSS-LMS") {
2✔
1593
      return {""};
1✔
1594
   } else {
1595
      return {};
1✔
1596
   }
1597
}
7✔
1598

1599
class X509_Cert_Unit_Tests final : public Test {
×
1600
   public:
1601
      std::vector<Test::Result> run() override {
1✔
1602
         std::vector<Test::Result> results;
1✔
1603

1604
         auto& rng = this->rng();
1✔
1605

1606
         const std::string sig_algos[]{"RSA",
1✔
1607
                                       "DSA",
1608
                                       "ECDSA",
1609
                                       "ECGDSA",
1610
                                       "ECKCDSA",
1611
                                       "GOST-34.10",
1612
                                       "Ed25519",
1613
                                       "Ed448",
1614
                                       "Dilithium",
1615
                                       "ML-DSA",
1616
                                       "SLH-DSA",
1617
                                       "HSS-LMS"};
13✔
1618

1619
         for(const std::string& algo : sig_algos) {
13✔
1620
   #if !defined(BOTAN_HAS_EMSA_PKCS1)
1621
            if(algo == "RSA")
1622
               continue;
1623
   #endif
1624

1625
            std::string hash = "SHA-256";
12✔
1626

1627
            if(algo == "Ed25519") {
12✔
1628
               hash = "SHA-512";
1✔
1629
            }
1630
            if(algo == "Ed448") {
12✔
1631
               hash = "SHAKE-256(912)";
1✔
1632
            }
1633
            if(algo == "Dilithium" || algo == "ML-DSA") {
12✔
1634
               hash = "SHAKE-256(512)";
2✔
1635
            }
1636

1637
            auto key = make_a_private_key(algo, rng);
12✔
1638

1639
            if(key == nullptr) {
12✔
1640
               continue;
×
1641
            }
1642

1643
            results.push_back(test_hashes(*key, hash, rng));
24✔
1644
            results.push_back(test_valid_constraints(*key, algo));
24✔
1645

1646
            Test::Result usage_result("X509 Usage");
12✔
1647
            try {
12✔
1648
               usage_result.merge(test_usage(*key, algo, hash, rng));
12✔
1649
            } catch(std::exception& e) {
×
1650
               usage_result.test_failure("test_usage " + algo, e.what());
×
1651
            }
×
1652
            results.push_back(usage_result);
12✔
1653

1654
            for(const auto& padding_scheme : get_sig_paddings(algo, hash)) {
24✔
1655
               Test::Result cert_result("X509 Unit");
12✔
1656

1657
               try {
12✔
1658
                  cert_result.merge(test_x509_cert(*key, algo, padding_scheme, hash, rng));
12✔
1659
               } catch(std::exception& e) {
×
1660
                  cert_result.test_failure("test_x509_cert " + algo, e.what());
×
1661
               }
×
1662
               results.push_back(cert_result);
12✔
1663

1664
               Test::Result pkcs10_result("PKCS10 extensions");
12✔
1665
               try {
12✔
1666
                  pkcs10_result.merge(test_pkcs10_ext(*key, padding_scheme, hash, rng));
12✔
1667
               } catch(std::exception& e) {
×
1668
                  pkcs10_result.test_failure("test_pkcs10_ext " + algo, e.what());
×
1669
               }
×
1670
               results.push_back(pkcs10_result);
12✔
1671

1672
               Test::Result self_issued_result("X509 Self Issued");
12✔
1673
               try {
12✔
1674
                  self_issued_result.merge(test_self_issued(*key, algo, padding_scheme, hash, rng));
12✔
1675
               } catch(std::exception& e) {
×
1676
                  self_issued_result.test_failure("test_self_issued " + algo, e.what());
×
1677
               }
×
1678
               results.push_back(self_issued_result);
12✔
1679

1680
               Test::Result extensions_result("X509 Extensions");
12✔
1681
               try {
12✔
1682
                  extensions_result.merge(test_x509_extensions(*key, algo, padding_scheme, hash, rng));
12✔
1683
               } catch(std::exception& e) {
×
1684
                  extensions_result.test_failure("test_extensions " + algo, e.what());
×
1685
               }
×
1686
               results.push_back(extensions_result);
12✔
1687

1688
               Test::Result custom_dn_result("X509 Custom DN");
12✔
1689
               try {
12✔
1690
                  custom_dn_result.merge(test_custom_dn_attr(*key, algo, padding_scheme, hash, rng));
12✔
1691
               } catch(std::exception& e) {
×
1692
                  custom_dn_result.test_failure("test_custom_dn_attr " + algo, e.what());
×
1693
               }
×
1694
               results.push_back(custom_dn_result);
12✔
1695
            }
24✔
1696
         }
24✔
1697

1698
         /*
1699
         These are algos which cannot sign but can be included in certs
1700
         */
1701
         const std::vector<std::string> enc_algos = {
1✔
1702
            "DH", "ECDH", "ElGamal", "Kyber", "ML-KEM", "FrodoKEM", "ClassicMcEliece"};
1✔
1703

1704
         for(const std::string& algo : enc_algos) {
8✔
1705
            auto key = make_a_private_key(algo, rng);
7✔
1706

1707
            if(key) {
7✔
1708
               results.push_back(test_valid_constraints(*key, algo));
14✔
1709
            }
1710
         }
7✔
1711

1712
   #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM) && defined(BOTAN_HAS_EMSA_PKCS1) && defined(BOTAN_HAS_EMSA_PSSR) && \
1713
      defined(BOTAN_HAS_RSA)
1714
         Test::Result pad_config_result("X509 Padding Config");
1✔
1715
         try {
1✔
1716
            pad_config_result.merge(test_padding_config());
1✔
1717
         } catch(const std::exception& e) {
×
1718
            pad_config_result.test_failure("test_padding_config", e.what());
×
1719
         }
×
1720
         results.push_back(pad_config_result);
1✔
1721
   #endif
1722

1723
   #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
1724
         results.push_back(test_x509_utf8());
2✔
1725
         results.push_back(test_x509_bmpstring());
2✔
1726
         results.push_back(test_x509_teletex());
2✔
1727
         results.push_back(test_crl_dn_name());
2✔
1728
         results.push_back(test_rdn_multielement_set_name());
2✔
1729
         results.push_back(test_x509_decode_list());
2✔
1730
         results.push_back(test_rsa_oaep());
2✔
1731
         results.push_back(test_x509_authority_info_access_extension());
2✔
1732
         results.push_back(test_verify_gost2012_cert());
2✔
1733
         results.push_back(test_parse_rsa_pss_cert());
2✔
1734
         results.push_back(test_x509_tn_auth_list_extension_decode());
2✔
1735
   #endif
1736

1737
         results.push_back(test_x509_encode_authority_info_access_extension());
2✔
1738
         results.push_back(test_x509_extension());
2✔
1739
         results.push_back(test_x509_dates());
2✔
1740
         results.push_back(test_cert_status_strings());
2✔
1741
         results.push_back(test_x509_uninit());
2✔
1742

1743
         return results;
1✔
1744
      }
13✔
1745
};
1746

1747
BOTAN_REGISTER_TEST("x509", "x509_unit", X509_Cert_Unit_Tests);
1748

1749
#endif
1750

1751
}  // namespace
1752

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