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

randombit / botan / 11951261165

21 Nov 2024 10:24AM UTC coverage: 91.24% (+0.2%) from 91.063%
11951261165

push

github

web-flow
Merge pull request #3883 from Rohde-Schwarz/pqc/classic_mceliece

PQC: Classic McEliece

93268 of 102223 relevant lines covered (91.24%)

11456149.0 hits per line

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

94.46
/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
   #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
409

410
Test::Result test_crl_dn_name() {
1✔
411
   Test::Result result("CRL DN name");
1✔
412

413
      // See GH #1252
414

415
      #if defined(BOTAN_HAS_RSA) && defined(BOTAN_HAS_EMSA_PKCS1)
416
   auto rng = Test::new_rng(__func__);
1✔
417

418
   const Botan::OID dc_oid("0.9.2342.19200300.100.1.25");
1✔
419

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

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

426
   Botan::X509_CRL crl = ca.new_crl(*rng);
1✔
427

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

430
   result.confirm("contains DC component", crl.issuer_dn().get_attributes().count(dc_oid) == 1);
3✔
431
      #endif
432

433
   return result;
2✔
434
}
3✔
435

436
Test::Result test_rdn_multielement_set_name() {
1✔
437
   Test::Result result("DN with multiple elements in RDN");
1✔
438

439
   // GH #2611
440

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

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

446
   return result;
1✔
447
}
1✔
448

449
Test::Result test_rsa_oaep() {
1✔
450
   Test::Result result("RSA OAEP decoding");
1✔
451

452
      #if defined(BOTAN_HAS_RSA)
453
   Botan::X509_Certificate cert(Test::data_file("x509/misc/rsa_oaep.pem"));
2✔
454

455
   auto public_key = cert.subject_public_key();
1✔
456
   result.test_not_null("Decoding RSA-OAEP worked", public_key.get());
1✔
457
   const auto& pk_info = cert.subject_public_key_algo();
1✔
458

459
   result.test_eq("RSA-OAEP OID", pk_info.oid().to_string(), Botan::OID::from_string("RSA/OAEP").to_string());
2✔
460
      #endif
461

462
   return result;
2✔
463
}
1✔
464

465
Test::Result test_x509_decode_list() {
1✔
466
   Test::Result result("X509_Certificate list decode");
1✔
467

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

470
   Botan::BER_Decoder dec(input);
1✔
471
   std::vector<Botan::X509_Certificate> certs;
1✔
472
   dec.decode_list(certs);
1✔
473

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

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

479
   return result;
1✔
480
}
1✔
481

482
Test::Result test_x509_utf8() {
1✔
483
   Test::Result result("X509 with UTF-8 encoded fields");
1✔
484

485
   try {
1✔
486
      Botan::X509_Certificate utf8_cert(Test::data_file("x509/misc/contains_utf8string.pem"));
2✔
487

488
      // UTF-8 encoded fields of test certificate (contains cyrillic letters)
489
      const std::string organization =
1✔
490
         "\xD0\x9C\xD0\xBE\xD1\x8F\x20\xD0\xBA\xD0\xBE\xD0"
491
         "\xBC\xD0\xBF\xD0\xB0\xD0\xBD\xD0\xB8\xD1\x8F";
1✔
492
      const std::string organization_unit =
1✔
493
         "\xD0\x9C\xD0\xBE\xD1\x91\x20\xD0\xBF\xD0\xBE\xD0\xB4\xD1\x80\xD0\xB0"
494
         "\xD0\xB7\xD0\xB4\xD0\xB5\xD0\xBB\xD0\xB5\xD0\xBD\xD0\xB8\xD0\xB5";
1✔
495
      const std::string common_name =
1✔
496
         "\xD0\x9E\xD0\xBF\xD0\xB8\xD1\x81\xD0\xB0\xD0\xBD\xD0\xB8"
497
         "\xD0\xB5\x20\xD1\x81\xD0\xB0\xD0\xB9\xD1\x82\xD0\xB0";
1✔
498
      const std::string location = "\xD0\x9C\xD0\xBE\xD1\x81\xD0\xBA\xD0\xB2\xD0\xB0";
1✔
499

500
      const Botan::X509_DN& issuer_dn = utf8_cert.issuer_dn();
1✔
501

502
      result.test_eq("O", issuer_dn.get_first_attribute("O"), organization);
2✔
503
      result.test_eq("OU", issuer_dn.get_first_attribute("OU"), organization_unit);
2✔
504
      result.test_eq("CN", issuer_dn.get_first_attribute("CN"), common_name);
2✔
505
      result.test_eq("L", issuer_dn.get_first_attribute("L"), location);
2✔
506
   } catch(const Botan::Decoding_Error& ex) {
1✔
507
      result.test_failure(ex.what());
×
508
   }
×
509

510
   return result;
1✔
511
}
×
512

513
Test::Result test_x509_bmpstring() {
1✔
514
   Test::Result result("X509 with UCS-2 (BMPString) encoded fields");
1✔
515

516
   try {
1✔
517
      Botan::X509_Certificate ucs2_cert(Test::data_file("x509/misc/contains_bmpstring.pem"));
2✔
518

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

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

527
      const Botan::X509_DN& issuer_dn = ucs2_cert.issuer_dn();
1✔
528

529
      result.test_eq("O", issuer_dn.get_first_attribute("O"), organization);
2✔
530
      result.test_eq("CN", issuer_dn.get_first_attribute("CN"), common_name);
2✔
531
      result.test_eq("L", issuer_dn.get_first_attribute("L"), location);
2✔
532
   } catch(const Botan::Decoding_Error& ex) {
1✔
533
      result.test_failure(ex.what());
×
534
   }
×
535

536
   return result;
1✔
537
}
×
538

539
Test::Result test_x509_teletex() {
1✔
540
   Test::Result result("X509 with TeletexString encoded fields");
1✔
541

542
   try {
1✔
543
      Botan::X509_Certificate teletex_cert(Test::data_file("x509/misc/teletex_dn.der"));
2✔
544

545
      const Botan::X509_DN& issuer_dn = teletex_cert.issuer_dn();
1✔
546

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

549
      result.test_eq("O", issuer_dn.get_first_attribute("O"), "neam CA");
2✔
550
      result.test_eq("CN", issuer_dn.get_first_attribute("CN"), common_name);
2✔
551
   } catch(const Botan::Decoding_Error& ex) {
1✔
552
      result.test_failure(ex.what());
×
553
   }
×
554

555
   return result;
1✔
556
}
×
557

558
Test::Result test_x509_authority_info_access_extension() {
1✔
559
   Test::Result result("X509 with PKIX.AuthorityInformationAccess extension");
1✔
560

561
   // contains no AIA extension
562
   Botan::X509_Certificate no_aia_cert(Test::data_file("x509/misc/contains_utf8string.pem"));
2✔
563

564
   result.test_eq("number of ca_issuers URLs", no_aia_cert.ca_issuers().size(), 0);
2✔
565
   result.test_eq("CA issuer URL matches", no_aia_cert.ocsp_responder(), "");
2✔
566

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

570
   const auto ca_issuers = aia_cert.ca_issuers();
1✔
571

572
   result.test_eq("number of ca_issuers URLs", ca_issuers.size(), 1);
1✔
573
   if(result.tests_failed()) {
1✔
574
      return result;
575
   }
576

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

580
   // contains AIA extension with 2 CA issuer URL and 1 OCSP responder
581
   Botan::X509_Certificate aia_cert_2ca(
1✔
582
      Test::data_file("x509/misc/contains_authority_info_access_with_two_ca_issuers.pem"));
2✔
583

584
   const auto ca_issuers2 = aia_cert_2ca.ca_issuers();
1✔
585

586
   result.test_eq("number of ca_issuers URLs", ca_issuers2.size(), 2);
1✔
587
   if(result.tests_failed()) {
1✔
588
      return result;
589
   }
590

591
   result.test_eq(
2✔
592
      "CA issuer URL matches", ca_issuers2[0], "http://www.d-trust.net/cgi-bin/Bdrive_Test_CA_1-2_2017.crt");
1✔
593
   result.test_eq(
2✔
594
      "CA issuer URL matches",
595
      ca_issuers2[1],
1✔
596
      "ldap://directory.d-trust.net/CN=Bdrive%20Test%20CA%201-2%202017,O=Bundesdruckerei%20GmbH,C=DE?cACertificate?base?");
597
   result.test_eq("OCSP responder URL matches", aia_cert_2ca.ocsp_responder(), "http://staging.ocsp.d-trust.net");
2✔
598

599
   return result;
1✔
600
}
1✔
601

602
Test::Result test_x509_encode_authority_info_access_extension() {
1✔
603
   Test::Result result("X509 with encoded PKIX.AuthorityInformationAccess extension");
1✔
604

605
      #if defined(BOTAN_HAS_RSA)
606
   auto rng = Test::new_rng(__func__);
1✔
607

608
   const std::string sig_algo{"RSA"};
1✔
609
   const std::string hash_fn{"SHA-256"};
1✔
610
   const std::string padding_method{"EMSA3(SHA-256)"};
1✔
611

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

617
   // OCSP
618
   const std::string_view ocsp_uri{"http://staging.ocsp.d-trust.net"};
1✔
619

620
   // create a CA
621
   auto ca_key = make_a_private_key(sig_algo, *rng);
1✔
622
   result.require("CA key", ca_key != nullptr);
1✔
623
   const auto ca_cert = Botan::X509::create_self_signed_cert(ca_opts(), *ca_key, hash_fn, *rng);
1✔
624
   Botan::X509_CA ca(ca_cert, *ca_key, hash_fn, padding_method, *rng);
1✔
625

626
   // create a certificate with only caIssuer information
627
   auto key = make_a_private_key(sig_algo, *rng);
1✔
628

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

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

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

636
   if(!result.test_eq("number of ca_issuers URIs", cert.ca_issuers().size(), 2)) {
1✔
637
      return result;
638
   }
639

640
   for(const auto& ca_issuer : cert.ca_issuers()) {
3✔
641
      result.confirm("CA issuer URI present in certificate",
4✔
642
                     std::ranges::find(ca_issuers, ca_issuer) != ca_issuers.end());
4✔
643
   }
1✔
644

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

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

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

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

655
   result.confirm("OCSP URI available", !cert.ocsp_responder().empty());
2✔
656
   result.confirm("no CA Issuer URI available", cert.ca_issuers().empty());
2✔
657
   result.test_eq("OCSP responder URI matches", cert.ocsp_responder(), std::string(ocsp_uri));
3✔
658

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

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

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

667
   result.confirm("OCSP URI available", !cert.ocsp_responder().empty());
2✔
668
   result.confirm("CA Issuer URI available", !cert.ca_issuers().empty());
2✔
669
      #endif
670

671
   return result;
1✔
672
}
4✔
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/EMSA3(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/EMSA4");
743

744
         #if defined(BOTAN_HAS_EMSA2)
745
   // Try to set a padding scheme that is not supported for signing with the given key type
746
   opt.set_padding_scheme("EMSA2");
747
   try {
748
      Botan::X509_Certificate ca_cert_wrong = Botan::X509::create_self_signed_cert(opt, (*sk), "SHA-512", *rng);
749
      test_result.test_failure("Could build CA cert with invalid encoding scheme EMSA1 for key type " +
750
                               sk->algo_name());
751
   } catch(const Botan::Invalid_Argument& e) {
752
      test_result.test_eq("Build CA certificate with invalid encoding scheme EMSA1 for key type " + sk->algo_name(),
753
                          e.what(),
754
                          "Signatures using RSA/EMSA2(SHA-512) are not supported");
755
   }
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/EMSA4");
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("Certificate request signature algorithm",
3✔
770
                       end_req.signature_algorithm().oid().to_formatted_string(),
2✔
771
                       "RSA/EMSA4");
772

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

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

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

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

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

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

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

820
   #endif
821

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

828
   Botan::X509_Cert_Options opts;
12✔
829

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

834
   opts.padding_scheme = sig_padding;
12✔
835

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

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

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

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

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

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

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

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

862
   return result;
12✔
863
}
12✔
864

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

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

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

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

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

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

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

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

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

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

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

901
   const BigInt user1_serial = 99;
12✔
902

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

997
   store.add_crl(crl2);
12✔
998

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

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

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

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

1012
   store.add_crl(crl3);
12✔
1013

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

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

1022
   return result;
12✔
1023
}
60✔
1024

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1101
   return result;
12✔
1102
}
24✔
1103

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

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

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

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

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

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

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

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

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

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

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

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

1143
   return result;
12✔
1144
}
24✔
1145

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

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

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

1158
   return result;
1✔
1159
}
1✔
1160

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

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

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

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

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

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

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

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

1257
   return result;
19✔
1258
}
×
1259

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1312
   Botan::X509_DN subject_dn;
12✔
1313

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

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

1322
   Botan::Extensions extensions;
12✔
1323

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

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

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

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

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

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

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

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

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

1354
   return result;
12✔
1355
}
36✔
1356

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1470
   return result;
12✔
1471
}
60✔
1472

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

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

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

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

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

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

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

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

1527
      asn1=SEQUENCE:tn_auth_list
1528

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

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

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

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

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

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

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

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

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

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

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

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

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

1596
class X509_Cert_Unit_Tests final : public Test {
×
1597
   public:
1598
      std::vector<Test::Result> run() override {
1✔
1599
         std::vector<Test::Result> results;
1✔
1600

1601
         auto& rng = this->rng();
1✔
1602

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

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

1622
            std::string hash = "SHA-256";
12✔
1623

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

1634
            auto key = make_a_private_key(algo, rng);
12✔
1635

1636
            if(key == nullptr) {
12✔
1637
               continue;
×
1638
            }
1639

1640
            results.push_back(test_hashes(*key, hash, rng));
24✔
1641
            results.push_back(test_valid_constraints(*key, algo));
24✔
1642

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

1651
            for(const auto& padding_scheme : get_sig_paddings(algo, hash)) {
24✔
1652
               Test::Result cert_result("X509 Unit");
12✔
1653

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

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

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

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

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

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

1701
         for(const std::string& algo : enc_algos) {
8✔
1702
            auto key = make_a_private_key(algo, rng);
7✔
1703

1704
            if(key) {
7✔
1705
               results.push_back(test_valid_constraints(*key, algo));
14✔
1706
            }
1707
         }
7✔
1708

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

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

1734
         results.push_back(test_x509_encode_authority_info_access_extension());
2✔
1735
         results.push_back(test_x509_extension());
2✔
1736
         results.push_back(test_x509_dates());
2✔
1737
         results.push_back(test_cert_status_strings());
2✔
1738
         results.push_back(test_x509_uninit());
2✔
1739

1740
         return results;
1✔
1741
      }
13✔
1742
};
1743

1744
BOTAN_REGISTER_TEST("x509", "x509_unit", X509_Cert_Unit_Tests);
1745

1746
#endif
1747

1748
}  // namespace
1749

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

© 2025 Coveralls, Inc