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

randombit / botan / 11331525401

14 Oct 2024 04:29PM UTC coverage: 91.093% (-0.03%) from 91.12%
11331525401

Pull #4291

github

web-flow
Merge f5ffe99f5 into ed74c9542
Pull Request #4291: PQC: SLH-DSA

90346 of 99180 relevant lines covered (91.09%)

9678761.99 hits per line

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

94.45
/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) {
318✔
30
   const size_t this_year = Botan::calendar_point(std::chrono::system_clock::now()).year();
318✔
31

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

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

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

45
   opts.CA_key(1);
111✔
46

47
   return opts;
111✔
48
}
×
49

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

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

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

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

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

69
   return opts;
36✔
70
}
×
71

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

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

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

82
   return opts;
11✔
83
}
×
84

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

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

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

97
   return opts;
55✔
98
}
×
99

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

121
   return Botan::create_private_key(algo, rng, params);
95✔
122
}
95✔
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,
11✔
823
                             const std::string& sig_padding,
824
                             const std::string& hash_fn,
825
                             Botan::RandomNumberGenerator& rng) {
826
   Test::Result result("PKCS10 extensions");
11✔
827

828
   Botan::X509_Cert_Options opts;
11✔
829

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

834
   opts.padding_scheme = sig_padding;
11✔
835

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

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

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

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

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

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

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

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

862
   return result;
11✔
863
}
11✔
864

865
Test::Result test_x509_cert(const Botan::Private_Key& ca_key,
11✔
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");
11✔
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);
11✔
874

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

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

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

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

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

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

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

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

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

901
   const BigInt user1_serial = 99;
11✔
902

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

975
   Botan::Path_Validation_Result result_u1 = Botan::x509_path_validate(user1_cert, restrictions, store);
11✔
976
   if(!result.confirm("user 1 validates", result_u1.successful_validation())) {
22✔
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);
11✔
981
   if(!result.confirm("user 2 validates", result_u2.successful_validation())) {
22✔
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);
11✔
986
   result.test_eq("user 1 issuer not found",
33✔
987
                  result_no_issuer.result_string(),
22✔
988
                  Botan::Path_Validation_Result::status_string(Botan::Certificate_Status_Code::CERT_ISSUER_NOT_FOUND));
989
   store.add_crl(crl1);
11✔
990

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

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

997
   store.add_crl(crl2);
11✔
998

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

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

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

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

1012
   store.add_crl(crl3);
11✔
1013

1014
   result_u1 = Botan::x509_path_validate(user1_cert, restrictions, store);
11✔
1015
   if(!result.confirm("user 1 validates", result_u1.successful_validation())) {
22✔
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);
11✔
1020
   result.test_eq("user 2 still revoked", result_u2.result_string(), revoked_str);
22✔
1021

1022
   return result;
11✔
1023
}
55✔
1024

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

1032
   Test::Result result("X509 Usage");
11✔
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);
11✔
1036

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

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

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

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

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

1050
   // cert only allows digitalSignature, but we check for both digitalSignature and cRLSign
1051
   result.test_eq(
11✔
1052
      "key usage cRLSign not allowed",
1053
      user1_cert.allowed_usage(Key_Constraints(Key_Constraints::DigitalSignature | Key_Constraints::CrlSign)),
11✔
1054
      false);
1055
   result.test_eq("encryption is not allowed", user1_cert.allowed_usage(Usage_Type::ENCRYPTION), false);
11✔
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));
22✔
1059

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

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

1064
   const Botan::X509_Certificate mult_usage_cert =
11✔
1065
      ca.sign_request(mult_usage_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
11✔
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",
22✔
1069
                  mult_usage_cert.allowed_usage(Key_Constraints::DigitalSignature));
11✔
1070
   result.confirm("key usage multiple cRLSign allowed", mult_usage_cert.allowed_usage(Key_Constraints::CrlSign));
22✔
1071
   result.confirm(
22✔
1072
      "key usage multiple digitalSignature and cRLSign allowed",
1073
      mult_usage_cert.allowed_usage(Key_Constraints(Key_Constraints::DigitalSignature | Key_Constraints::CrlSign)));
11✔
1074
   result.test_eq("encryption is not allowed", mult_usage_cert.allowed_usage(Usage_Type::ENCRYPTION), false);
11✔
1075

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

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

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

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

1088
   if(sig_algo == "RSA") {
11✔
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;
11✔
1102
}
22✔
1103

1104
Test::Result test_self_issued(const Botan::Private_Key& ca_key,
11✔
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;
11✔
1110

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

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

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

1120
   auto user_key = make_a_private_key(sig_algo, rng);
11✔
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();
11✔
1125
   opts.constraints = Key_Constraints::DigitalSignature;
11✔
1126
   opts.set_padding_scheme(sig_padding);
11✔
1127

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

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

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

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

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

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

1143
   return result;
11✔
1144
}
22✔
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) {
16✔
1162
   using Botan::Key_Constraints;
16✔
1163

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

1166
   result.confirm("empty constraints always acceptable", Key_Constraints().compatible_with(key));
32✔
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(
16✔
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);
16✔
1175

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

1190
   if(pk_algo == "DH" || pk_algo == "ECDH") {
16✔
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") {
14✔
1204
      // KEMs can encrypt and agree
1205
      result.test_eq("all constraints not permitted", all.compatible_with(key), false);
2✔
1206
      result.test_eq("cert sign not permitted", ca.compatible_with(key), false);
2✔
1207
      result.test_eq("signature not permitted", sign_data.compatible_with(key), false);
2✔
1208
      result.test_eq("non repudiation not permitted", non_repudiation.compatible_with(key), false);
2✔
1209
      result.test_eq("crl sign not permitted", crl_sign.compatible_with(key), false);
2✔
1210
      result.test_eq("sign", sign_everything.compatible_with(key), false);
2✔
1211
      result.test_eq("key agreement not permitted", key_agreement.compatible_with(key), false);
2✔
1212
      result.test_eq("usage acceptable", data_encipherment.compatible_with(key), false);
2✔
1213
      result.test_eq("usage acceptable", key_encipherment.compatible_with(key), true);
4✔
1214
   } else if(pk_algo == "RSA") {
12✔
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") {
11✔
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" ||
9✔
1240
             pk_algo == "GOST-34.10" || pk_algo == "Dilithium" || pk_algo == "HSS-LMS" || pk_algo == "SLH-DSA") {
16✔
1241
      // these are signature algorithms only
1242
      result.test_eq("all constraints not permitted", all.compatible_with(key), false);
8✔
1243

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

1256
   return result;
16✔
1257
}
×
1258

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

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

1268
      std::string value() const { return m_contents; }
44✔
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"); }
22✔
1275

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

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

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

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

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

1296
Test::Result test_custom_dn_attr(const Botan::Private_Key& ca_key,
11✔
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");
11✔
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);
11✔
1305

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

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

1311
   Botan::X509_DN subject_dn;
11✔
1312

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

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

1321
   Botan::Extensions extensions;
11✔
1322

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

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

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

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

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

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

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

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

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

1353
   return result;
11✔
1354
}
33✔
1355

1356
Test::Result test_x509_extensions(const Botan::Private_Key& ca_key,
11✔
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;
11✔
1362

1363
   Test::Result result("X509 Extensions");
11✔
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);
11✔
1367

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

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

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

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

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

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

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

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

1403
   result.confirm("Extensions::extension_set true for Key_Usage",
22✔
1404
                  self_signed_cert.v3_extensions().extension_set(ku_oid));
11✔
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);
11✔
1408
   if(result.confirm("Key_Usage extension present in self-signed certificate", key_usage_ext != nullptr)) {
22✔
1409
      result.confirm(
22✔
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);
11✔
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);
11✔
1416
   if(result.confirm("Custom extension present in self-signed certificate", string_ext != nullptr)) {
22✔
1417
      result.test_eq(
44✔
1418
         "Custom extension value matches in self-signed certificate", string_ext->value(), "AAAAAAAAAAAAAABCDEF");
33✔
1419
   }
1420

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

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

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

1435
   /* Create a CA-signed certificate */
1436
   const Botan::X509_Certificate ca_signed_cert =
11✔
1437
      ca.sign_request(user_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
11✔
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));
22✔
1441

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

1449
   // check if custom extension is present in CA-signed cert
1450
   result.confirm("Extensions::extension_set true for String_Extension",
22✔
1451
                  ca_signed_cert.v3_extensions().extension_set(oid));
11✔
1452
   string_ext = ca_signed_cert.v3_extensions().get_raw<String_Extension>(oid);
22✔
1453
   if(result.confirm("Custom extension present in CA-signed certificate", string_ext != nullptr)) {
22✔
1454
      result.test_eq(
44✔
1455
         "Custom extension value matches in CA-signed certificate", string_ext->value(), "AAAAAAAAAAAAAABCDEF");
33✔
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>();
22✔
1460

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

1469
   return result;
11✔
1470
}
55✔
1471

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

1475
   struct TestData {
11✔
1476
         const std::string issuer, subject, issuer_hash, subject_hash;
1477
   } const cases[]{{"",
11✔
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
                   }};
66✔
1501

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

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

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

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

1517
      result.test_eq(a.subject, Botan::hex_encode(subject_cert.raw_issuer_dn_sha256()), a.issuer_hash);
110✔
1518
      result.test_eq(a.subject, Botan::hex_encode(subject_cert.raw_subject_dn_sha256()), a.subject_hash);
110✔
1519
   }
55✔
1520
   return result;
11✔
1521
}
66✔
1522

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

1526
      asn1=SEQUENCE:tn_auth_list
1527

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1620
            std::string hash = "SHA-256";
11✔
1621

1622
            if(algo == "Ed25519") {
11✔
1623
               hash = "SHA-512";
1✔
1624
            }
1625
            if(algo == "Ed448") {
11✔
1626
               hash = "SHAKE-256(912)";
1✔
1627
            }
1628
            if(algo == "Dilithium") {
11✔
1629
               hash = "SHAKE-256(512)";
1✔
1630
            }
1631

1632
            auto key = make_a_private_key(algo, rng);
11✔
1633

1634
            if(key == nullptr) {
11✔
1635
               continue;
×
1636
            }
1637

1638
            results.push_back(test_hashes(*key, hash, rng));
22✔
1639
            results.push_back(test_valid_constraints(*key, algo));
22✔
1640

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

1649
            for(const auto& padding_scheme : get_sig_paddings(algo, hash)) {
22✔
1650
               Test::Result cert_result("X509 Unit");
11✔
1651

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

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

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

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

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

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

1698
         for(const std::string& algo : enc_algos) {
6✔
1699
            auto key = make_a_private_key(algo, rng);
5✔
1700

1701
            if(key) {
5✔
1702
               results.push_back(test_valid_constraints(*key, algo));
10✔
1703
            }
1704
         }
5✔
1705

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

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

1731
         results.push_back(test_x509_encode_authority_info_access_extension());
2✔
1732
         results.push_back(test_x509_extension());
2✔
1733
         results.push_back(test_x509_dates());
2✔
1734
         results.push_back(test_cert_status_strings());
2✔
1735
         results.push_back(test_x509_uninit());
2✔
1736

1737
         return results;
1✔
1738
      }
12✔
1739
};
1740

1741
BOTAN_REGISTER_TEST("x509", "x509_unit", X509_Cert_Unit_Tests);
1742

1743
#endif
1744

1745
}  // namespace
1746

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