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

randombit / botan / 11345717360

15 Oct 2024 12:09PM UTC coverage: 91.512% (+0.4%) from 91.131%
11345717360

push

github

web-flow
Merge pull request #4270 from Rohde-Schwarz/feature/ml-dsa-ipd-after-refactoring

PQC: ML-DSA

73266 of 80062 relevant lines covered (91.51%)

11223399.72 hits per line

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

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

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

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

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

45
   opts.CA_key(1);
120✔
46

47
   return opts;
120✔
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) {
102✔
101
   const std::string params = [&] {
102✔
102
      // Here we override defaults as needed
103
      if(algo == "RSA") {
102✔
104
         return "1024";
105
      }
106
      if(algo == "GOST-34.10") {
86✔
107
         return "gost_256A";
108
      }
109
      if(algo == "ECKCDSA" || algo == "ECGDSA") {
78✔
110
         return "brainpool256r1";
111
      }
112
      if(algo == "HSS-LMS") {
62✔
113
         return "SHA-256,HW(5,4),HW(5,4)";
8✔
114
      }
115
      return "";  // default "" means choose acceptable algo-specific params
116
   }();
102✔
117

118
   return Botan::create_private_key(algo, rng, params);
102✔
119
}
102✔
120

121
Test::Result test_cert_status_strings() {
1✔
122
   Test::Result result("Certificate_Status_Code to_string");
1✔
123

124
   std::set<std::string> seen;
1✔
125

126
   result.test_eq("Same string",
1✔
127
                  Botan::to_string(Botan::Certificate_Status_Code::OK),
128
                  Botan::to_string(Botan::Certificate_Status_Code::VERIFIED));
129

130
   const Botan::Certificate_Status_Code codes[]{
1✔
131
      Botan::Certificate_Status_Code::OCSP_RESPONSE_GOOD,
132
      Botan::Certificate_Status_Code::OCSP_SIGNATURE_OK,
133
      Botan::Certificate_Status_Code::VALID_CRL_CHECKED,
134
      Botan::Certificate_Status_Code::OCSP_NO_HTTP,
135

136
      Botan::Certificate_Status_Code::CERT_SERIAL_NEGATIVE,
137
      Botan::Certificate_Status_Code::DN_TOO_LONG,
138

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

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

186
   return result;
1✔
187
}
1✔
188

189
Test::Result test_x509_extension() {
1✔
190
   Test::Result result("X509 Extensions API");
1✔
191

192
   Botan::Extensions extn;
1✔
193

194
   const auto oid_bc = Botan::OID::from_string("X509v3.BasicConstraints");
1✔
195
   const auto oid_skid = Botan::OID::from_string("X509v3.SubjectKeyIdentifier");
1✔
196

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

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

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

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

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

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

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

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

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

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

227
   return result;
2✔
228
}
2✔
229

230
Test::Result test_x509_dates() {
1✔
231
   Test::Result result("X509 Time");
1✔
232

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

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

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

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

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

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

263
      "080201000000-0000",
264
      "080201172412+0000",
265
      "040614233433-0500",
266
      "990614233444+0500",
267
      "000614233455-0530",
268
      "000614233455+0530",
269
   };
13✔
270

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

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

288
      // No seconds
289
      "0802010000Z",
290
      "0802011724Z",
291
      "0406142334Z",
292
      "9906142334Z",
293
      "0006142334Z",
294

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

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

328
      // wrong time zone
329
      "080201000000",
330
      "080201000000z",
331

332
      // Fractional seconds
333
      "170217180154.001Z",
334

335
      // Timezone offset
336
      "170217180154+0100",
337

338
      // Extra digits
339
      "17021718015400Z",
340

341
      // Non-digits
342
      "17021718015aZ",
343

344
      // Trailing garbage
345
      "170217180154Zlongtrailinggarbage",
346

347
      // Swapped type
348
      "20170217180154Z",
349
   };
49✔
350

351
   // valid length 15
352
   const std::string valid_generalized_time[]{
1✔
353
      "20000305100350Z",
354
   };
2✔
355

356
   const std::string invalid_generalized[]{
1✔
357
      // No trailing Z
358
      "20000305100350",
359

360
      // No seconds
361
      "200003051003Z",
362

363
      // Fractional seconds
364
      "20000305100350.001Z",
365

366
      // Timezone offset
367
      "20170217180154+0100",
368

369
      // Extra digits
370
      "2017021718015400Z",
371

372
      // Non-digits
373
      "2017021718015aZ",
374

375
      // Trailing garbage
376
      "20170217180154Zlongtrailinggarbage",
377

378
      // Swapped type
379
      "170217180154Z",
380
   };
9✔
381

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

386
   for(const auto& v : valid_utc) {
6✔
387
      Botan::X509_Time t(v, Botan::ASN1_Type::UtcTime);
5✔
388
   }
5✔
389

390
   for(const auto& v : valid_generalized_time) {
2✔
391
      Botan::X509_Time t(v, Botan::ASN1_Type::GeneralizedTime);
1✔
392
   }
1✔
393

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

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

402
   return result;
1✔
403
}
80✔
404

405
   #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
406

407
Test::Result test_crl_dn_name() {
1✔
408
   Test::Result result("CRL DN name");
1✔
409

410
      // See GH #1252
411

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

415
   const Botan::OID dc_oid("0.9.2342.19200300.100.1.25");
1✔
416

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

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

423
   Botan::X509_CRL crl = ca.new_crl(*rng);
1✔
424

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

427
   result.confirm("contains DC component", crl.issuer_dn().get_attributes().count(dc_oid) == 1);
3✔
428
      #endif
429

430
   return result;
2✔
431
}
3✔
432

433
Test::Result test_rdn_multielement_set_name() {
1✔
434
   Test::Result result("DN with multiple elements in RDN");
1✔
435

436
   // GH #2611
437

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

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

443
   return result;
1✔
444
}
1✔
445

446
Test::Result test_rsa_oaep() {
1✔
447
   Test::Result result("RSA OAEP decoding");
1✔
448

449
      #if defined(BOTAN_HAS_RSA)
450
   Botan::X509_Certificate cert(Test::data_file("x509/misc/rsa_oaep.pem"));
2✔
451

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

456
   result.test_eq("RSA-OAEP OID", pk_info.oid().to_string(), Botan::OID::from_string("RSA/OAEP").to_string());
2✔
457
      #endif
458

459
   return result;
2✔
460
}
1✔
461

462
Test::Result test_x509_decode_list() {
1✔
463
   Test::Result result("X509_Certificate list decode");
1✔
464

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

467
   Botan::BER_Decoder dec(input);
1✔
468
   std::vector<Botan::X509_Certificate> certs;
1✔
469
   dec.decode_list(certs);
1✔
470

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

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

476
   return result;
1✔
477
}
1✔
478

479
Test::Result test_x509_utf8() {
1✔
480
   Test::Result result("X509 with UTF-8 encoded fields");
1✔
481

482
   try {
1✔
483
      Botan::X509_Certificate utf8_cert(Test::data_file("x509/misc/contains_utf8string.pem"));
2✔
484

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

497
      const Botan::X509_DN& issuer_dn = utf8_cert.issuer_dn();
1✔
498

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

507
   return result;
1✔
508
}
×
509

510
Test::Result test_x509_bmpstring() {
1✔
511
   Test::Result result("X509 with UCS-2 (BMPString) encoded fields");
1✔
512

513
   try {
1✔
514
      Botan::X509_Certificate ucs2_cert(Test::data_file("x509/misc/contains_bmpstring.pem"));
2✔
515

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

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

524
      const Botan::X509_DN& issuer_dn = ucs2_cert.issuer_dn();
1✔
525

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

533
   return result;
1✔
534
}
×
535

536
Test::Result test_x509_teletex() {
1✔
537
   Test::Result result("X509 with TeletexString encoded fields");
1✔
538

539
   try {
1✔
540
      Botan::X509_Certificate teletex_cert(Test::data_file("x509/misc/teletex_dn.der"));
2✔
541

542
      const Botan::X509_DN& issuer_dn = teletex_cert.issuer_dn();
1✔
543

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

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

552
   return result;
1✔
553
}
×
554

555
Test::Result test_x509_authority_info_access_extension() {
1✔
556
   Test::Result result("X509 with PKIX.AuthorityInformationAccess extension");
1✔
557

558
   // contains no AIA extension
559
   Botan::X509_Certificate no_aia_cert(Test::data_file("x509/misc/contains_utf8string.pem"));
2✔
560

561
   result.test_eq("number of ca_issuers URLs", no_aia_cert.ca_issuers().size(), 0);
2✔
562
   result.test_eq("CA issuer URL matches", no_aia_cert.ocsp_responder(), "");
2✔
563

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

567
   const auto ca_issuers = aia_cert.ca_issuers();
1✔
568

569
   result.test_eq("number of ca_issuers URLs", ca_issuers.size(), 1);
1✔
570
   if(result.tests_failed()) {
1✔
571
      return result;
572
   }
573

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

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

581
   const auto ca_issuers2 = aia_cert_2ca.ca_issuers();
1✔
582

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

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

596
   return result;
1✔
597
}
1✔
598

599
Test::Result test_x509_encode_authority_info_access_extension() {
1✔
600
   Test::Result result("X509 with encoded PKIX.AuthorityInformationAccess extension");
1✔
601

602
      #if defined(BOTAN_HAS_RSA)
603
   auto rng = Test::new_rng(__func__);
1✔
604

605
   const std::string sig_algo{"RSA"};
1✔
606
   const std::string hash_fn{"SHA-256"};
1✔
607
   const std::string padding_method{"EMSA3(SHA-256)"};
1✔
608

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

614
   // OCSP
615
   const std::string_view ocsp_uri{"http://staging.ocsp.d-trust.net"};
1✔
616

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

623
   // create a certificate with only caIssuer information
624
   auto key = make_a_private_key(sig_algo, *rng);
1✔
625

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

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

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

633
   if(!result.test_eq("number of ca_issuers URIs", cert.ca_issuers().size(), 2)) {
1✔
634
      return result;
635
   }
636

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

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

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

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

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

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

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

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

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

664
   result.confirm("OCSP URI available", !cert.ocsp_responder().empty());
2✔
665
   result.confirm("CA Issuer URI available", !cert.ca_issuers().empty());
2✔
666
      #endif
667

668
   return result;
1✔
669
}
4✔
670

671
Test::Result test_parse_rsa_pss_cert() {
1✔
672
   Test::Result result("X509 RSA-PSS certificate");
1✔
673

674
   // See https://github.com/randombit/botan/issues/3019 for background
675

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

683
   return result;
1✔
684
}
×
685

686
Test::Result test_verify_gost2012_cert() {
1✔
687
   Test::Result result("X509 GOST-2012 certificates");
1✔
688

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

694
      Botan::Certificate_Store_In_Memory trusted;
1✔
695
      trusted.add_certificate(root_cert);
1✔
696

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

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

707
   return result;
1✔
708
}
×
709

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

720
   auto rng = Test::new_rng(__func__);
1✔
721

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

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

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

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

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

755
   test_result.test_eq("CA certificate signature algorithm (explicit)",
3✔
756
                       ca_cert_exp.signature_algorithm().oid().to_formatted_string(),
2✔
757
                       "RSA/EMSA4");
758

759
   const Botan::X509_Time not_before = from_date(-1, 1, 1);
1✔
760
   const Botan::X509_Time not_after = from_date(2, 1, 2);
1✔
761

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

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

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

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

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

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

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

813
   return test_result;
2✔
814
}
3✔
815
      #endif
816

817
   #endif
818

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

825
   Botan::X509_Cert_Options opts;
12✔
826

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

831
   opts.padding_scheme = sig_padding;
12✔
832

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

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

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

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

845
   const auto alt_dns_names = req.subject_alt_name().get_attribute("DNS");
12✔
846

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

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

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

859
   return result;
12✔
860
}
12✔
861

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

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

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

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

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

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

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

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

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

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

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

898
   const BigInt user1_serial = 99;
12✔
899

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

958
   const Botan::X509_CRL crl1 = ca.new_crl(rng);
12✔
959

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

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

970
   store.add_certificate(ca.ca_certificate());
12✔
971

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

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

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

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

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

994
   store.add_crl(crl2);
12✔
995

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

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

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

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

1009
   store.add_crl(crl3);
12✔
1010

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

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

1019
   return result;
12✔
1020
}
60✔
1021

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

1029
   Test::Result result("X509 Usage");
11✔
1030

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

1034
   /* Create the CA object */
1035
   const Botan::X509_CA ca(ca_cert, ca_key, hash_fn, rng);
11✔
1036

1037
   auto user1_key = make_a_private_key(sig_algo, rng);
11✔
1038

1039
   Botan::X509_Cert_Options opts("Test User 1/US/Botan Project/Testing");
11✔
1040
   opts.constraints = Key_Constraints::DigitalSignature;
11✔
1041

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

1044
   const Botan::X509_Certificate user1_cert =
11✔
1045
      ca.sign_request(user1_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
11✔
1046

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

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

1057
   opts.constraints = Key_Constraints(Key_Constraints::DigitalSignature | Key_Constraints::CrlSign);
11✔
1058

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

1061
   const Botan::X509_Certificate mult_usage_cert =
11✔
1062
      ca.sign_request(mult_usage_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
11✔
1063

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

1073
   opts.constraints = Key_Constraints();
11✔
1074

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

1077
   const Botan::X509_Certificate no_usage_cert =
11✔
1078
      ca.sign_request(no_usage_req, rng, from_date(-1, 01, 01), from_date(2, 01, 01));
11✔
1079

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

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

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

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

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

1098
   return result;
11✔
1099
}
22✔
1100

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

1108
   Test::Result result("X509 Self Issued");
12✔
1109

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

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

1117
   auto user_key = make_a_private_key(sig_algo, rng);
12✔
1118

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

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

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

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

1133
   const Botan::Path_Validation_Restrictions restrictions(false, 80);
24✔
1134

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

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

1140
   return result;
12✔
1141
}
24✔
1142

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

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

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

1155
   return result;
1✔
1156
}
1✔
1157

1158
Test::Result test_valid_constraints(const Botan::Private_Key& key, const std::string& pk_algo) {
17✔
1159
   using Botan::Key_Constraints;
17✔
1160

1161
   Test::Result result("X509 Valid Constraints " + pk_algo);
17✔
1162

1163
   result.confirm("empty constraints always acceptable", Key_Constraints().compatible_with(key));
34✔
1164

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

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

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

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

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

1253
   return result;
17✔
1254
}
×
1255

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

1263
      explicit String_Extension(const std::string& val) : m_contents(val) {}
12✔
1264

1265
      std::string value() const { return m_contents; }
48✔
1266

1267
      std::unique_ptr<Certificate_Extension> copy() const override {
×
1268
         return std::make_unique<String_Extension>(m_contents);
×
1269
      }
1270

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

1273
      bool should_encode() const override { return true; }
24✔
1274

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

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

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

1289
   private:
1290
      std::string m_contents;
1291
};
1292

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

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

1303
   /* Create the CA object */
1304
   Botan::X509_CA ca(ca_cert, ca_key, hash_fn, sig_padding, rng);
12✔
1305

1306
   auto user_key = make_a_private_key(sig_algo, rng);
12✔
1307

1308
   Botan::X509_DN subject_dn;
12✔
1309

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

1315
   subject_dn.add_attribute(attr1, val1);
12✔
1316
   subject_dn.add_attribute(attr2, val2);
12✔
1317

1318
   Botan::Extensions extensions;
12✔
1319

1320
   Botan::PKCS10_Request req =
12✔
1321
      Botan::PKCS10_Request::create(*user_key, subject_dn, extensions, hash_fn, rng, sig_padding);
12✔
1322

1323
   const Botan::X509_DN& req_dn = req.subject_dn();
12✔
1324

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

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

1334
   Botan::X509_Time not_before("100301123001Z", Botan::ASN1_Type::UtcTime);
12✔
1335
   Botan::X509_Time not_after("300301123001Z", Botan::ASN1_Type::UtcTime);
12✔
1336

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

1339
   const Botan::X509_DN& cert_dn = cert.subject_dn();
12✔
1340

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

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

1350
   return result;
12✔
1351
}
36✔
1352

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

1360
   Test::Result result("X509 Extensions");
12✔
1361

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

1365
   /* Create the CA object */
1366
   Botan::X509_CA ca(ca_cert, ca_key, hash_fn, sig_padding, rng);
12✔
1367

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

1373
   std::vector<Botan::Cert_Extension::CRL_Distribution_Points::Distribution_Point> dps;
12✔
1374

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

1380
      dps.emplace_back(dp);
24✔
1381
   }
24✔
1382

1383
   auto user_key = make_a_private_key(sig_algo, rng);
12✔
1384

1385
   Botan::X509_Cert_Options opts("Test User 1/US/Botan Project/Testing");
12✔
1386
   opts.constraints = Key_Constraints::DigitalSignature;
12✔
1387

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

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

1400
   result.confirm("Extensions::extension_set true for Key_Usage",
24✔
1401
                  self_signed_cert.v3_extensions().extension_set(ku_oid));
12✔
1402

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

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

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

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

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

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

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

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

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

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

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

1466
   return result;
12✔
1467
}
60✔
1468

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

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

1499
   for(const auto& a : cases) {
66✔
1500
      Botan::X509_Cert_Options opts{a.issuer};
55✔
1501
      opts.CA_key();
55✔
1502

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

1505
      result.test_eq(a.issuer, Botan::hex_encode(issuer_cert.raw_issuer_dn_sha256()), a.issuer_hash);
110✔
1506
      result.test_eq(a.issuer, Botan::hex_encode(issuer_cert.raw_subject_dn_sha256()), a.issuer_hash);
110✔
1507

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

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

1520
Test::Result test_x509_tn_auth_list_extension_decode() {
1✔
1521
   /* cert with TNAuthList extension data was generated by asn1parse cfg:
1522

1523
      asn1=SEQUENCE:tn_auth_list
1524

1525
      [tn_auth_list]
1526
      spc=EXP:0,IA5:1001
1527
      range=EXP:1,SEQUENCE:TelephoneNumberRange
1528
      one=EXP:2,IA5:333
1529

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

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

1542
   using Botan::Cert_Extension::TNAuthList;
1✔
1543

1544
   auto tn_auth_list = cert.v3_extensions().get_extension_object_as<TNAuthList>();
2✔
1545

1546
   auto& tn_entries = tn_auth_list->entries();
1✔
1547

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

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

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

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

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

1571
   result.end_timer();
1✔
1572
   return result;
2✔
1573
}
1✔
1574

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

1592
class X509_Cert_Unit_Tests final : public Test {
×
1593
   public:
1594
      std::vector<Test::Result> run() override {
1✔
1595
         std::vector<Test::Result> results;
1✔
1596

1597
         auto& rng = this->rng();
1✔
1598

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

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

1617
            std::string hash = "SHA-256";
11✔
1618

1619
            if(algo == "Ed25519") {
11✔
1620
               hash = "SHA-512";
1✔
1621
            }
1622
            if(algo == "Ed448") {
11✔
1623
               hash = "SHAKE-256(912)";
1✔
1624
            }
1625
            if(algo == "Dilithium" || algo == "ML-DSA") {
11✔
1626
               hash = "SHAKE-256(512)";
2✔
1627
            }
1628

1629
            auto key = make_a_private_key(algo, rng);
11✔
1630

1631
            if(key == nullptr) {
11✔
1632
               continue;
×
1633
            }
1634

1635
            results.push_back(test_hashes(*key, hash, rng));
22✔
1636
            results.push_back(test_valid_constraints(*key, algo));
22✔
1637

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

1646
            for(const auto& padding_scheme : get_sig_paddings(algo, hash)) {
23✔
1647
               Test::Result cert_result("X509 Unit");
12✔
1648

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

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

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

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

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

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

1695
         for(const std::string& algo : enc_algos) {
7✔
1696
            auto key = make_a_private_key(algo, rng);
6✔
1697

1698
            if(key) {
6✔
1699
               results.push_back(test_valid_constraints(*key, algo));
12✔
1700
            }
1701
         }
6✔
1702

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

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

1728
         results.push_back(test_x509_encode_authority_info_access_extension());
2✔
1729
         results.push_back(test_x509_extension());
2✔
1730
         results.push_back(test_x509_dates());
2✔
1731
         results.push_back(test_cert_status_strings());
2✔
1732
         results.push_back(test_x509_uninit());
2✔
1733

1734
         return results;
1✔
1735
      }
12✔
1736
};
1737

1738
BOTAN_REGISTER_TEST("x509", "x509_unit", X509_Cert_Unit_Tests);
1739

1740
#endif
1741

1742
}  // namespace
1743

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