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

randombit / botan / 6703245924

31 Oct 2023 07:13AM UTC coverage: 91.721% (+0.03%) from 91.695%
6703245924

push

github

web-flow
Merge pull request #3784 from martin-schiffner/master

Implement encoding CA Issuer information in X.509 AIA extension

80146 of 87380 relevant lines covered (91.72%)

8570776.04 hits per line

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

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

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

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

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

45
   opts.CA_key(1);
90✔
46

47
   return opts;
90✔
48
}
×
49

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

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

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

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

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

69
   return opts;
30✔
70
}
×
71

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

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

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

82
   return opts;
9✔
83
}
×
84

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

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

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

97
   return opts;
45✔
98
}
×
99

100
std::unique_ptr<Botan::Private_Key> make_a_private_key(const std::string& algo) {
76✔
101
   const std::string params = [&] {
76✔
102
      // Here we override defaults as needed
103
      if(algo == "RSA") {
120✔
104
         return "1024";
105
      }
106
      if(algo == "GOST-34.10") {
60✔
107
         return "gost_256A";
108
      }
109
      if(algo == "ECKCDSA" || algo == "ECGDSA") {
52✔
110
         return "brainpool256r1";
16✔
111
      }
112
      return "";  // default "" means choose acceptable algo-specific params
113
   }();
76✔
114

115
   return Botan::create_private_key(algo, Test::rng(), params);
76✔
116
}
76✔
117

118
Test::Result test_cert_status_strings() {
1✔
119
   Test::Result result("Certificate_Status_Code to_string");
1✔
120

121
   std::set<std::string> seen;
1✔
122

123
   result.test_eq("Same string",
1✔
124
                  Botan::to_string(Botan::Certificate_Status_Code::OK),
125
                  Botan::to_string(Botan::Certificate_Status_Code::VERIFIED));
126

127
   const Botan::Certificate_Status_Code codes[]{
1✔
128
      Botan::Certificate_Status_Code::OCSP_RESPONSE_GOOD,
129
      Botan::Certificate_Status_Code::OCSP_SIGNATURE_OK,
130
      Botan::Certificate_Status_Code::VALID_CRL_CHECKED,
131
      Botan::Certificate_Status_Code::OCSP_NO_HTTP,
132

133
      Botan::Certificate_Status_Code::CERT_SERIAL_NEGATIVE,
134
      Botan::Certificate_Status_Code::DN_TOO_LONG,
135

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

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

183
   return result;
1✔
184
}
1✔
185

186
Test::Result test_x509_extension() {
1✔
187
   Test::Result result("X509 Extensions API");
1✔
188

189
   Botan::Extensions extn;
1✔
190

191
   const auto oid_bc = Botan::OID::from_string("X509v3.BasicConstraints");
1✔
192
   const auto oid_skid = Botan::OID::from_string("X509v3.SubjectKeyIdentifier");
1✔
193

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

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

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

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

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

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

210
   result.confirm("Returns false since extension already existed",
2✔
211
                  !extn.add_new(std::make_unique<Botan::Cert_Extension::Basic_Constraints>(false), false));
2✔
212

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

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

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

224
   return result;
2✔
225
}
2✔
226

227
Test::Result test_x509_dates() {
1✔
228
   Test::Result result("X509 Time");
1✔
229

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

236
   time = Botan::X509_Time("200305100350Z", Botan::ASN1_Type::UtcTime);
1✔
237
   result.test_eq("UTC_TIME readable_string", time.readable_string(), "2020/03/05 10:03:50 UTC");
3✔
238

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

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

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

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

260
      "080201000000-0000",
261
      "080201172412+0000",
262
      "040614233433-0500",
263
      "990614233444+0500",
264
      "000614233455-0530",
265
      "000614233455+0530",
266
   };
13✔
267

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

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

285
      // No seconds
286
      "0802010000Z",
287
      "0802011724Z",
288
      "0406142334Z",
289
      "9906142334Z",
290
      "0006142334Z",
291

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

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

325
      // wrong time zone
326
      "080201000000",
327
      "080201000000z",
328

329
      // Fractional seconds
330
      "170217180154.001Z",
331

332
      // Timezone offset
333
      "170217180154+0100",
334

335
      // Extra digits
336
      "17021718015400Z",
337

338
      // Non-digits
339
      "17021718015aZ",
340

341
      // Trailing garbage
342
      "170217180154Zlongtrailinggarbage",
343

344
      // Swapped type
345
      "20170217180154Z",
346
   };
49✔
347

348
   // valid length 15
349
   const std::string valid_generalized_time[]{
1✔
350
      "20000305100350Z",
351
   };
2✔
352

353
   const std::string invalid_generalized[]{
1✔
354
      // No trailing Z
355
      "20000305100350",
356

357
      // No seconds
358
      "200003051003Z",
359

360
      // Fractional seconds
361
      "20000305100350.001Z",
362

363
      // Timezone offset
364
      "20170217180154+0100",
365

366
      // Extra digits
367
      "2017021718015400Z",
368

369
      // Non-digits
370
      "2017021718015aZ",
371

372
      // Trailing garbage
373
      "20170217180154Zlongtrailinggarbage",
374

375
      // Swapped type
376
      "170217180154Z",
377
   };
9✔
378

379
   for(const auto& v : valid_but_unsup) {
13✔
380
      result.test_throws("valid but unsupported", [v]() { Botan::X509_Time t(v, Botan::ASN1_Type::UtcTime); });
126✔
381
   }
382

383
   for(const auto& v : valid_utc) {
6✔
384
      Botan::X509_Time t(v, Botan::ASN1_Type::UtcTime);
5✔
385
   }
5✔
386

387
   for(const auto& v : valid_generalized_time) {
2✔
388
      Botan::X509_Time t(v, Botan::ASN1_Type::GeneralizedTime);
1✔
389
   }
1✔
390

391
   for(const auto& v : invalid_utc) {
49✔
392
      result.test_throws("invalid", [v]() { Botan::X509_Time t(v, Botan::ASN1_Type::UtcTime); });
441✔
393
   }
394

395
   for(const auto& v : invalid_generalized) {
9✔
396
      result.test_throws("invalid", [v]() { Botan::X509_Time t(v, Botan::ASN1_Type::GeneralizedTime); });
84✔
397
   }
398

399
   return result;
1✔
400
}
80✔
401

402
   #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
403

404
Test::Result test_crl_dn_name() {
1✔
405
   Test::Result result("CRL DN name");
1✔
406

407
      // See GH #1252
408

409
      #if defined(BOTAN_HAS_RSA) && defined(BOTAN_HAS_EMSA_PKCS1)
410
   const Botan::OID dc_oid("0.9.2342.19200300.100.1.25");
1✔
411

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

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

418
   Botan::X509_CRL crl = ca.new_crl(Test::rng());
1✔
419

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

422
   result.confirm("contains DC component", crl.issuer_dn().get_attributes().count(dc_oid) == 1);
4✔
423
      #endif
424

425
   return result;
1✔
426
}
2✔
427

428
Test::Result test_rdn_multielement_set_name() {
1✔
429
   Test::Result result("DN with multiple elements in RDN");
1✔
430

431
   // GH #2611
432

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

435
   result.confirm("issuer DN contains expected name components", cert.issuer_dn().get_attributes().size() == 4);
3✔
436
   result.confirm("subject DN contains expected name components", cert.subject_dn().get_attributes().size() == 4);
3✔
437

438
   return result;
1✔
439
}
1✔
440

441
Test::Result test_rsa_oaep() {
1✔
442
   Test::Result result("RSA OAEP decoding");
1✔
443

444
      #if defined(BOTAN_HAS_RSA)
445
   Botan::X509_Certificate cert(Test::data_file("x509/misc/rsa_oaep.pem"));
2✔
446

447
   auto public_key = cert.subject_public_key();
1✔
448
   result.test_not_null("Decoding RSA-OAEP worked", public_key.get());
1✔
449
   const auto& pk_info = cert.subject_public_key_algo();
1✔
450

451
   result.test_eq("RSA-OAEP OID", pk_info.oid().to_string(), Botan::OID::from_string("RSA/OAEP").to_string());
4✔
452
      #endif
453

454
   return result;
2✔
455
}
1✔
456

457
Test::Result test_x509_decode_list() {
1✔
458
   Test::Result result("X509_Certificate list decode");
1✔
459

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

462
   Botan::BER_Decoder dec(input);
1✔
463
   std::vector<Botan::X509_Certificate> certs;
1✔
464
   dec.decode_list(certs);
1✔
465

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

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

471
   return result;
1✔
472
}
1✔
473

474
Test::Result test_x509_utf8() {
1✔
475
   Test::Result result("X509 with UTF-8 encoded fields");
1✔
476

477
   try {
1✔
478
      Botan::X509_Certificate utf8_cert(Test::data_file("x509/misc/contains_utf8string.pem"));
2✔
479

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

492
      const Botan::X509_DN& issuer_dn = utf8_cert.issuer_dn();
1✔
493

494
      result.test_eq("O", issuer_dn.get_first_attribute("O"), organization);
2✔
495
      result.test_eq("OU", issuer_dn.get_first_attribute("OU"), organization_unit);
2✔
496
      result.test_eq("CN", issuer_dn.get_first_attribute("CN"), common_name);
2✔
497
      result.test_eq("L", issuer_dn.get_first_attribute("L"), location);
2✔
498
   } catch(const Botan::Decoding_Error& ex) {
4✔
499
      result.test_failure(ex.what());
×
500
   }
×
501

502
   return result;
1✔
503
}
×
504

505
Test::Result test_x509_bmpstring() {
1✔
506
   Test::Result result("X509 with UCS-2 (BMPString) encoded fields");
1✔
507

508
   try {
1✔
509
      Botan::X509_Certificate ucs2_cert(Test::data_file("x509/misc/contains_bmpstring.pem"));
2✔
510

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

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

519
      const Botan::X509_DN& issuer_dn = ucs2_cert.issuer_dn();
1✔
520

521
      result.test_eq("O", issuer_dn.get_first_attribute("O"), organization);
2✔
522
      result.test_eq("CN", issuer_dn.get_first_attribute("CN"), common_name);
2✔
523
      result.test_eq("L", issuer_dn.get_first_attribute("L"), location);
2✔
524
   } catch(const Botan::Decoding_Error& ex) {
2✔
525
      result.test_failure(ex.what());
×
526
   }
×
527

528
   return result;
1✔
529
}
×
530

531
Test::Result test_x509_teletex() {
1✔
532
   Test::Result result("X509 with TeletexString encoded fields");
1✔
533

534
   try {
1✔
535
      Botan::X509_Certificate teletex_cert(Test::data_file("x509/misc/teletex_dn.der"));
2✔
536

537
      const Botan::X509_DN& issuer_dn = teletex_cert.issuer_dn();
1✔
538

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

541
      result.test_eq("O", issuer_dn.get_first_attribute("O"), "neam CA");
2✔
542
      result.test_eq("CN", issuer_dn.get_first_attribute("CN"), common_name);
3✔
543
   } catch(const Botan::Decoding_Error& ex) {
1✔
544
      result.test_failure(ex.what());
×
545
   }
×
546

547
   return result;
1✔
548
}
×
549

550
Test::Result test_x509_authority_info_access_extension() {
1✔
551
   Test::Result result("X509 with PKIX.AuthorityInformationAccess extension");
1✔
552

553
   // contains no AIA extension
554
   Botan::X509_Certificate no_aia_cert(Test::data_file("x509/misc/contains_utf8string.pem"));
2✔
555

556
   result.test_eq("number of ca_issuers URLs", no_aia_cert.ca_issuers().size(), 0);
2✔
557
   result.test_eq("CA issuer URL matches", no_aia_cert.ocsp_responder(), "");
2✔
558

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

562
   const auto ca_issuers = aia_cert.ca_issuers();
1✔
563

564
   result.test_eq("number of ca_issuers URLs", ca_issuers.size(), 1);
1✔
565
   if(result.tests_failed()) {
1✔
566
      return result;
567
   }
568

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

572
   // contains AIA extension with 2 CA issuer URL and 1 OCSP responder
573
   Botan::X509_Certificate aia_cert_2ca(
1✔
574
      Test::data_file("x509/misc/contains_authority_info_access_with_two_ca_issuers.pem"));
2✔
575

576
   const auto ca_issuers2 = aia_cert_2ca.ca_issuers();
1✔
577

578
   result.test_eq("number of ca_issuers URLs", ca_issuers2.size(), 2);
1✔
579
   if(result.tests_failed()) {
1✔
580
      return result;
581
   }
582

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

591
   return result;
1✔
592
}
1✔
593

594
Test::Result test_x509_encode_authority_info_access_extension() {
1✔
595
   Test::Result result("X509 with encoded PKIX.AuthorityInformationAccess extension");
1✔
596

597
   const std::string sig_algo{"RSA"};
1✔
598
   const std::string hash_fn{"SHA-256"};
1✔
599
   const std::string padding_method{"EMSA3(SHA-256)"};
1✔
600

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

606
   // OCSP
607
   const std::string_view ocsp_uri{"http://staging.ocsp.d-trust.net"};
1✔
608

609
   // create a CA
610
   auto ca_key = make_a_private_key(sig_algo);
1✔
611
   const auto ca_cert = Botan::X509::create_self_signed_cert(ca_opts(), *ca_key, hash_fn, Test::rng());
2✔
612
   Botan::X509_CA ca(ca_cert, *ca_key, hash_fn, padding_method, Test::rng());
1✔
613

614
   // create a certificate with only caIssuer information
615
   auto key = make_a_private_key(sig_algo);
1✔
616

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

620
   Botan::PKCS10_Request req = Botan::X509::create_cert_req(opts1, *key, hash_fn, Test::rng());
1✔
621

622
   Botan::X509_Certificate cert = ca.sign_request(req, Test::rng(), from_date(-1, 01, 01), from_date(2, 01, 01));
1✔
623

624
   if(!result.test_eq("number of ca_issuers URIs", cert.ca_issuers().size(), 2)) {
2✔
625
      return result;
626
   }
627

628
   for(const auto& ca_issuer : cert.ca_issuers()) {
3✔
629
      result.confirm("CA issuer URI present in certificate",
6✔
630
                     std::ranges::find(ca_issuers, ca_issuer) != ca_issuers.end());
4✔
631
   }
1✔
632

633
   result.confirm("no OCSP url available", cert.ocsp_responder().empty());
3✔
634

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

639
   req = Botan::X509::create_cert_req(opts2, *key, hash_fn, Test::rng());
1✔
640

641
   cert = ca.sign_request(req, Test::rng(), from_date(-1, 01, 01), from_date(2, 01, 01));
2✔
642

643
   result.confirm("OCSP URI available", !cert.ocsp_responder().empty());
3✔
644
   result.confirm("no CA Issuer URI available", cert.ca_issuers().empty());
3✔
645
   result.test_eq("OCSP responder URI matches", cert.ocsp_responder(), std::string(ocsp_uri));
4✔
646

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

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

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

655
   result.confirm("OCSP URI available", !cert.ocsp_responder().empty());
3✔
656
   result.confirm("CA Issuer URI available", !cert.ca_issuers().empty());
3✔
657

658
   return result;
1✔
659
}
3✔
660

661
Test::Result test_parse_rsa_pss_cert() {
1✔
662
   Test::Result result("X509 RSA-PSS certificate");
1✔
663

664
   // See https://github.com/randombit/botan/issues/3019 for background
665

666
   try {
1✔
667
      Botan::X509_Certificate rsa_pss(Test::data_file("x509/misc/rsa_pss.pem"));
2✔
668
      result.test_success("Was able to parse RSA-PSS certificate signed with ECDSA");
1✔
669
   } catch(Botan::Exception& e) {
1✔
670
      result.test_failure("Parsing failed", e.what());
×
671
   }
×
672

673
   return result;
1✔
674
}
×
675

676
Test::Result test_verify_gost2012_cert() {
1✔
677
   Test::Result result("X509 GOST-2012 certificates");
1✔
678

679
      #if defined(BOTAN_HAS_GOST_34_10_2012) && defined(BOTAN_HAS_STREEBOG)
680
   try {
1✔
681
      Botan::X509_Certificate root_cert(Test::data_file("x509/gost/gost_root.pem"));
2✔
682
      Botan::X509_Certificate root_int(Test::data_file("x509/gost/gost_int.pem"));
2✔
683

684
      Botan::Certificate_Store_In_Memory trusted;
1✔
685
      trusted.add_certificate(root_cert);
1✔
686

687
      const Botan::Path_Validation_Restrictions restrictions(false, 128, false, {"Streebog-256"});
3✔
688
      const Botan::Path_Validation_Result validation_result =
1✔
689
         Botan::x509_path_validate(root_int, restrictions, trusted);
1✔
690

691
      result.confirm("GOST certificate validates", validation_result.successful_validation());
2✔
692
   } catch(const Botan::Decoding_Error& e) {
1✔
693
      result.test_failure(e.what());
×
694
   }
×
695
      #endif
696

697
   return result;
1✔
698
}
×
699

700
      /*
701
 * @brief checks the configurability of the EMSA4(RSA-PSS) signature scheme
702
 *
703
 * For the other algorithms than RSA, only one padding is supported right now.
704
 */
705
      #if defined(BOTAN_HAS_EMSA_PKCS1) && defined(BOTAN_HAS_EMSA_PSSR) && defined(BOTAN_HAS_RSA)
706
Test::Result test_padding_config() {
1✔
707
   // Throughout the test, some synonyms for EMSA4 are used, e.g. PSSR, EMSA-PSS
708
   Test::Result test_result("X509 Padding Config");
1✔
709

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

713
   // Create X509 CA certificate; EMSA3 is used for signing by default
714
   Botan::X509_Cert_Options opt("TESTCA");
1✔
715
   opt.CA_key();
1✔
716

717
   Botan::X509_Certificate ca_cert_def = Botan::X509::create_self_signed_cert(opt, (*sk), "SHA-512", Test::rng());
1✔
718
   test_result.test_eq("CA certificate signature algorithm (default)",
3✔
719
                       ca_cert_def.signature_algorithm().oid().to_formatted_string(),
2✔
720
                       "RSA/EMSA3(SHA-512)");
721

722
   // Create X509 CA certificate; RSA-PSS is explicitly set
723
   opt.set_padding_scheme("PSSR");
1✔
724
   Botan::X509_Certificate ca_cert_exp = Botan::X509::create_self_signed_cert(opt, (*sk), "SHA-512", Test::rng());
1✔
725
   test_result.test_eq("CA certificate signature algorithm (explicit)",
3✔
726
                       ca_cert_exp.signature_algorithm().oid().to_formatted_string(),
2✔
727
                       "RSA/EMSA4");
728

729
         #if defined(BOTAN_HAS_EMSA2)
730
   // Try to set a padding scheme that is not supported for signing with the given key type
731
   opt.set_padding_scheme("EMSA2");
732
   try {
733
      Botan::X509_Certificate ca_cert_wrong = Botan::X509::create_self_signed_cert(opt, (*sk), "SHA-512", Test::rng());
734
      test_result.test_failure("Could build CA cert with invalid encoding scheme EMSA1 for key type " +
735
                               sk->algo_name());
736
   } catch(const Botan::Invalid_Argument& e) {
737
      test_result.test_eq("Build CA certificate with invalid encoding scheme EMSA1 for key type " + sk->algo_name(),
738
                          e.what(),
739
                          "Signatures using RSA/EMSA2(SHA-512) are not supported");
740
   }
741
         #endif
742

743
   test_result.test_eq("CA certificate signature algorithm (explicit)",
3✔
744
                       ca_cert_exp.signature_algorithm().oid().to_formatted_string(),
2✔
745
                       "RSA/EMSA4");
746

747
   const Botan::X509_Time not_before = from_date(-1, 1, 1);
1✔
748
   const Botan::X509_Time not_after = from_date(2, 1, 2);
1✔
749

750
   // Prepare a signing request for the end certificate
751
   Botan::X509_Cert_Options req_opt("endpoint");
1✔
752
   req_opt.set_padding_scheme("EMSA4(SHA-512,MGF1,64)");
1✔
753
   Botan::PKCS10_Request end_req = Botan::X509::create_cert_req(req_opt, (*sk), "SHA-512", Test::rng());
1✔
754
   test_result.test_eq("Certificate request signature algorithm",
3✔
755
                       end_req.signature_algorithm().oid().to_formatted_string(),
2✔
756
                       "RSA/EMSA4");
757

758
   // Create X509 CA object: will fail as the chosen hash functions differ
759
   try {
1✔
760
      Botan::X509_CA ca_fail(ca_cert_exp, (*sk), "SHA-512", "EMSA4(SHA-256)", Test::rng());
1✔
761
      test_result.test_failure("Configured conflicting hash functions for CA");
×
762
   } catch(const Botan::Invalid_Argument& e) {
1✔
763
      test_result.test_eq(
1✔
764
         "Configured conflicting hash functions for CA",
765
         e.what(),
1✔
766
         "Specified hash function SHA-512 is incompatible with RSA chose hash function SHA-256 with user specified padding EMSA4(SHA-256)");
767
   }
1✔
768

769
   // Create X509 CA object: its signer will use the padding scheme from the CA certificate, i.e. EMSA3
770
   Botan::X509_CA ca_def(ca_cert_def, (*sk), "SHA-512", Test::rng());
1✔
771
   Botan::X509_Certificate end_cert_emsa3 = ca_def.sign_request(end_req, Test::rng(), not_before, not_after);
1✔
772
   test_result.test_eq("End certificate signature algorithm",
3✔
773
                       end_cert_emsa3.signature_algorithm().oid().to_formatted_string(),
2✔
774
                       "RSA/EMSA3(SHA-512)");
775

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

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

790
   // Check CRL signature algorithm
791
   Botan::X509_CRL crl = ca_exp.new_crl(Test::rng());
1✔
792
   test_result.test_eq("CRL signature algorithm", crl.signature_algorithm().oid().to_formatted_string(), "RSA/EMSA4");
2✔
793

794
   // sanity check for verification, the heavy lifting is done in the other unit tests
795
   const Botan::Certificate_Store_In_Memory trusted(ca_exp.ca_certificate());
1✔
796
   const Botan::Path_Validation_Restrictions restrictions(false, 80);
2✔
797
   const Botan::Path_Validation_Result validation_result =
1✔
798
      Botan::x509_path_validate(end_cert_emsa4, restrictions, trusted);
1✔
799
   test_result.confirm("EMSA4-signed certificate validates", validation_result.successful_validation());
2✔
800

801
   return test_result;
1✔
802
}
2✔
803
      #endif
804

805
   #endif
806

807
Test::Result test_pkcs10_ext(const Botan::Private_Key& key,
9✔
808
                             const std::string& sig_padding,
809
                             const std::string& hash_fn) {
810
   Test::Result result("PKCS10 extensions");
9✔
811

812
   Botan::X509_Cert_Options opts;
9✔
813

814
   opts.padding_scheme = sig_padding;
9✔
815

816
   Botan::AlternativeName alt_name;
9✔
817
   alt_name.add_attribute("DNS", "example.org");
9✔
818
   alt_name.add_attribute("DNS", "example.com");
9✔
819
   alt_name.add_attribute("DNS", "example.net");
9✔
820

821
   opts.extensions.add(std::make_unique<Botan::Cert_Extension::Subject_Alternative_Name>(alt_name));
18✔
822

823
   Botan::PKCS10_Request req = Botan::X509::create_cert_req(opts, key, hash_fn, Test::rng());
9✔
824

825
   std::vector<std::string> alt_dns_names = req.subject_alt_name().get_attribute("DNS");
9✔
826

827
   result.test_eq("Expected number of DNS names", alt_dns_names.size(), 3);
9✔
828

829
   // The order is not guaranteed so sort before comparing
830
   std::sort(alt_dns_names.begin(), alt_dns_names.end());
9✔
831

832
   result.test_eq("Expected DNS name 1", alt_dns_names.at(0), "example.com");
27✔
833
   result.test_eq("Expected DNS name 2", alt_dns_names.at(1), "example.net");
27✔
834
   result.test_eq("Expected DNS name 3", alt_dns_names.at(2), "example.org");
27✔
835

836
   return result;
9✔
837
}
9✔
838

839
Test::Result test_x509_cert(const Botan::Private_Key& ca_key,
9✔
840
                            const std::string& sig_algo,
841
                            const std::string& sig_padding,
842
                            const std::string& hash_fn) {
843
   Test::Result result("X509 Unit");
9✔
844

845
   /* Create the self-signed cert */
846
   const auto ca_cert = Botan::X509::create_self_signed_cert(ca_opts(sig_padding), ca_key, hash_fn, Test::rng());
9✔
847

848
   {
9✔
849
      result.confirm("ca key usage cert", ca_cert.constraints().includes(Botan::Key_Constraints::KeyCertSign));
18✔
850
      result.confirm("ca key usage crl", ca_cert.constraints().includes(Botan::Key_Constraints::CrlSign));
18✔
851
   }
852

853
   /* Create user #1's key and cert request */
854
   auto user1_key = make_a_private_key(sig_algo);
9✔
855

856
   Botan::PKCS10_Request user1_req =
9✔
857
      Botan::X509::create_cert_req(req_opts1(sig_algo, sig_padding), *user1_key, hash_fn, Test::rng());
9✔
858

859
   result.test_eq("PKCS10 challenge password parsed", user1_req.challenge_password(), "zoom");
18✔
860

861
   /* Create user #2's key and cert request */
862
   auto user2_key = make_a_private_key(sig_algo);
9✔
863

864
   Botan::PKCS10_Request user2_req =
9✔
865
      Botan::X509::create_cert_req(req_opts2(sig_padding), *user2_key, hash_fn, Test::rng());
9✔
866

867
   // /* Create user #3's key and cert request */
868
   auto user3_key = make_a_private_key(sig_algo);
9✔
869

870
   Botan::PKCS10_Request user3_req =
9✔
871
      Botan::X509::create_cert_req(req_opts3(sig_padding), *user3_key, hash_fn, Test::rng());
9✔
872

873
   /* Create the CA object */
874
   Botan::X509_CA ca(ca_cert, ca_key, hash_fn, sig_padding, Test::rng());
9✔
875

876
   const BigInt user1_serial = 99;
9✔
877

878
   /* Sign the requests to create the certs */
879
   Botan::X509_Certificate user1_cert =
9✔
880
      ca.sign_request(user1_req, Test::rng(), user1_serial, from_date(-1, 01, 01), from_date(2, 01, 01));
9✔
881

882
   result.test_eq("User1 serial size matches expected", user1_cert.serial_number().size(), 1);
9✔
883
   result.test_eq("User1 serial matches expected", user1_cert.serial_number().at(0), size_t(99));
9✔
884

885
   Botan::X509_Certificate user2_cert =
9✔
886
      ca.sign_request(user2_req, Test::rng(), from_date(-1, 01, 01), from_date(2, 01, 01));
9✔
887

888
   Botan::X509_Certificate user3_cert =
9✔
889
      ca.sign_request(user3_req, Test::rng(), from_date(-1, 01, 01), from_date(2, 01, 01));
9✔
890

891
   // user#1 creates a self-signed cert on the side
892
   const auto user1_ss_cert =
9✔
893
      Botan::X509::create_self_signed_cert(req_opts1(sig_algo, sig_padding), *user1_key, hash_fn, Test::rng());
9✔
894

895
   {
9✔
896
      auto constraints = req_opts1(sig_algo).constraints;
9✔
897
      result.confirm("user1 key usage", user1_cert.constraints().includes(constraints));
18✔
898
   }
899

900
   /* Copy, assign and compare */
901
   Botan::X509_Certificate user1_cert_copy(user1_cert);
9✔
902
   result.test_eq("certificate copy", user1_cert == user1_cert_copy, true);
9✔
903

904
   user1_cert_copy = user2_cert;
9✔
905
   result.test_eq("certificate assignment", user2_cert == user1_cert_copy, true);
9✔
906

907
   Botan::X509_Certificate user1_cert_differ =
9✔
908
      ca.sign_request(user1_req, Test::rng(), from_date(-1, 01, 01), from_date(2, 01, 01));
9✔
909

910
   result.test_eq("certificate differs", user1_cert == user1_cert_differ, false);
9✔
911

912
   /* Get cert data */
913
   result.test_eq("x509 version", user1_cert.x509_version(), size_t(3));
9✔
914

915
   const Botan::X509_DN& user1_issuer_dn = user1_cert.issuer_dn();
9✔
916
   result.test_eq("issuer info CN", user1_issuer_dn.get_first_attribute("CN"), ca_opts().common_name);
18✔
917
   result.test_eq("issuer info Country", user1_issuer_dn.get_first_attribute("C"), ca_opts().country);
18✔
918
   result.test_eq("issuer info Orga", user1_issuer_dn.get_first_attribute("O"), ca_opts().organization);
18✔
919
   result.test_eq("issuer info OrgaUnit", user1_issuer_dn.get_first_attribute("OU"), ca_opts().org_unit);
18✔
920

921
   const Botan::X509_DN& user3_subject_dn = user3_cert.subject_dn();
9✔
922
   result.test_eq("subject OrgaUnit count",
9✔
923
                  user3_subject_dn.get_attribute("OU").size(),
18✔
924
                  req_opts3(sig_algo).more_org_units.size() + 1);
18✔
925
   result.test_eq(
9✔
926
      "subject OrgaUnit #2", user3_subject_dn.get_attribute("OU").at(1), req_opts3(sig_algo).more_org_units.at(0));
36✔
927

928
   const Botan::AlternativeName& user1_altname = user1_cert.subject_alt_name();
9✔
929
   result.test_eq("subject alt email", user1_altname.get_first_attribute("RFC822"), "testing@randombit.net");
27✔
930
   result.test_eq("subject alt dns", user1_altname.get_first_attribute("DNS"), "botan.randombit.net");
27✔
931
   result.test_eq("subject alt uri", user1_altname.get_first_attribute("URI"), "https://botan.randombit.net");
27✔
932

933
   const Botan::AlternativeName& user3_altname = user3_cert.subject_alt_name();
9✔
934
   result.test_eq(
9✔
935
      "subject alt dns count", user3_altname.get_attribute("DNS").size(), req_opts3(sig_algo).more_dns.size() + 1);
18✔
936
   result.test_eq("subject alt dns #2", user3_altname.get_attribute("DNS").at(1), req_opts3(sig_algo).more_dns.at(0));
36✔
937

938
   const Botan::X509_CRL crl1 = ca.new_crl(Test::rng());
9✔
939

940
   /* Verify the certs */
941
   Botan::Path_Validation_Restrictions restrictions(false, 80);
18✔
942
   Botan::Certificate_Store_In_Memory store;
9✔
943

944
   // First try with an empty store
945
   Botan::Path_Validation_Result result_no_issuer = Botan::x509_path_validate(user1_cert, restrictions, store);
9✔
946
   result.test_eq("user 1 issuer not found",
27✔
947
                  result_no_issuer.result_string(),
18✔
948
                  Botan::Path_Validation_Result::status_string(Botan::Certificate_Status_Code::CERT_ISSUER_NOT_FOUND));
949

950
   store.add_certificate(ca.ca_certificate());
9✔
951

952
   Botan::Path_Validation_Result result_u1 = Botan::x509_path_validate(user1_cert, restrictions, store);
9✔
953
   if(!result.confirm("user 1 validates", result_u1.successful_validation())) {
27✔
954
      result.test_note("user 1 validation result was " + result_u1.result_string());
×
955
   }
956

957
   Botan::Path_Validation_Result result_u2 = Botan::x509_path_validate(user2_cert, restrictions, store);
9✔
958
   if(!result.confirm("user 2 validates", result_u2.successful_validation())) {
27✔
959
      result.test_note("user 2 validation result was " + result_u2.result_string());
×
960
   }
961

962
   Botan::Path_Validation_Result result_self_signed = Botan::x509_path_validate(user1_ss_cert, restrictions, store);
9✔
963
   result.test_eq("user 1 issuer not found",
27✔
964
                  result_no_issuer.result_string(),
18✔
965
                  Botan::Path_Validation_Result::status_string(Botan::Certificate_Status_Code::CERT_ISSUER_NOT_FOUND));
966
   store.add_crl(crl1);
9✔
967

968
   std::vector<Botan::CRL_Entry> revoked;
9✔
969
   revoked.push_back(Botan::CRL_Entry(user1_cert, Botan::CRL_Code::CessationOfOperation));
18✔
970
   revoked.push_back(user2_cert);
18✔
971

972
   const Botan::X509_CRL crl2 = ca.update_crl(crl1, revoked, Test::rng());
9✔
973

974
   store.add_crl(crl2);
9✔
975

976
   const std::string revoked_str =
9✔
977
      Botan::Path_Validation_Result::status_string(Botan::Certificate_Status_Code::CERT_IS_REVOKED);
9✔
978

979
   result_u1 = Botan::x509_path_validate(user1_cert, restrictions, store);
9✔
980
   result.test_eq("user 1 revoked", result_u1.result_string(), revoked_str);
18✔
981

982
   result_u2 = Botan::x509_path_validate(user2_cert, restrictions, store);
9✔
983
   result.test_eq("user 1 revoked", result_u2.result_string(), revoked_str);
18✔
984

985
   revoked.clear();
9✔
986
   revoked.push_back(Botan::CRL_Entry(user1_cert, Botan::CRL_Code::RemoveFromCrl));
18✔
987
   Botan::X509_CRL crl3 = ca.update_crl(crl2, revoked, Test::rng());
9✔
988

989
   store.add_crl(crl3);
9✔
990

991
   result_u1 = Botan::x509_path_validate(user1_cert, restrictions, store);
9✔
992
   if(!result.confirm("user 1 validates", result_u1.successful_validation())) {
27✔
993
      result.test_note("user 1 validation result was " + result_u1.result_string());
×
994
   }
995

996
   result_u2 = Botan::x509_path_validate(user2_cert, restrictions, store);
9✔
997
   result.test_eq("user 2 still revoked", result_u2.result_string(), revoked_str);
18✔
998

999
   return result;
9✔
1000
}
54✔
1001

1002
Test::Result test_usage(const Botan::Private_Key& ca_key, const std::string& sig_algo, const std::string& hash_fn) {
8✔
1003
   using Botan::Key_Constraints;
8✔
1004
   using Botan::Usage_Type;
8✔
1005

1006
   Test::Result result("X509 Usage");
8✔
1007

1008
   /* Create the self-signed cert */
1009
   const Botan::X509_Certificate ca_cert =
8✔
1010
      Botan::X509::create_self_signed_cert(ca_opts(), ca_key, hash_fn, Test::rng());
16✔
1011

1012
   /* Create the CA object */
1013
   const Botan::X509_CA ca(ca_cert, ca_key, hash_fn, Test::rng());
8✔
1014

1015
   auto user1_key = make_a_private_key(sig_algo);
8✔
1016

1017
   Botan::X509_Cert_Options opts("Test User 1/US/Botan Project/Testing");
8✔
1018
   opts.constraints = Key_Constraints::DigitalSignature;
8✔
1019

1020
   const Botan::PKCS10_Request user1_req = Botan::X509::create_cert_req(opts, *user1_key, hash_fn, Test::rng());
8✔
1021

1022
   const Botan::X509_Certificate user1_cert =
8✔
1023
      ca.sign_request(user1_req, Test::rng(), from_date(-1, 01, 01), from_date(2, 01, 01));
8✔
1024

1025
   // cert only allows digitalSignature, but we check for both digitalSignature and cRLSign
1026
   result.test_eq(
8✔
1027
      "key usage cRLSign not allowed",
1028
      user1_cert.allowed_usage(Key_Constraints(Key_Constraints::DigitalSignature | Key_Constraints::CrlSign)),
8✔
1029
      false);
1030
   result.test_eq("encryption is not allowed", user1_cert.allowed_usage(Usage_Type::ENCRYPTION), false);
8✔
1031

1032
   // cert only allows digitalSignature, so checking for only that should be ok
1033
   result.confirm("key usage digitalSignature allowed", user1_cert.allowed_usage(Key_Constraints::DigitalSignature));
16✔
1034

1035
   opts.constraints = Key_Constraints(Key_Constraints::DigitalSignature | Key_Constraints::CrlSign);
8✔
1036

1037
   const Botan::PKCS10_Request mult_usage_req = Botan::X509::create_cert_req(opts, *user1_key, hash_fn, Test::rng());
8✔
1038

1039
   const Botan::X509_Certificate mult_usage_cert =
8✔
1040
      ca.sign_request(mult_usage_req, Test::rng(), from_date(-1, 01, 01), from_date(2, 01, 01));
8✔
1041

1042
   // cert allows multiple usages, so each one of them as well as both together should be allowed
1043
   result.confirm("key usage multiple digitalSignature allowed",
16✔
1044
                  mult_usage_cert.allowed_usage(Key_Constraints::DigitalSignature));
8✔
1045
   result.confirm("key usage multiple cRLSign allowed", mult_usage_cert.allowed_usage(Key_Constraints::CrlSign));
16✔
1046
   result.confirm(
16✔
1047
      "key usage multiple digitalSignature and cRLSign allowed",
1048
      mult_usage_cert.allowed_usage(Key_Constraints(Key_Constraints::DigitalSignature | Key_Constraints::CrlSign)));
8✔
1049
   result.test_eq("encryption is not allowed", mult_usage_cert.allowed_usage(Usage_Type::ENCRYPTION), false);
8✔
1050

1051
   opts.constraints = Key_Constraints();
8✔
1052

1053
   const Botan::PKCS10_Request no_usage_req = Botan::X509::create_cert_req(opts, *user1_key, hash_fn, Test::rng());
8✔
1054

1055
   const Botan::X509_Certificate no_usage_cert =
8✔
1056
      ca.sign_request(no_usage_req, Test::rng(), from_date(-1, 01, 01), from_date(2, 01, 01));
8✔
1057

1058
   // cert allows every usage
1059
   result.confirm("key usage digitalSignature allowed", no_usage_cert.allowed_usage(Key_Constraints::DigitalSignature));
16✔
1060
   result.confirm("key usage cRLSign allowed", no_usage_cert.allowed_usage(Key_Constraints::CrlSign));
16✔
1061
   result.confirm("key usage encryption allowed", no_usage_cert.allowed_usage(Usage_Type::ENCRYPTION));
16✔
1062

1063
   if(sig_algo == "RSA") {
8✔
1064
      // cert allows data encryption
1065
      opts.constraints = Key_Constraints(Key_Constraints::KeyEncipherment | Key_Constraints::DataEncipherment);
1✔
1066

1067
      const Botan::PKCS10_Request enc_req = Botan::X509::create_cert_req(opts, *user1_key, hash_fn, Test::rng());
1✔
1068

1069
      const Botan::X509_Certificate enc_cert =
1✔
1070
         ca.sign_request(enc_req, Test::rng(), from_date(-1, 01, 01), from_date(2, 01, 01));
1✔
1071

1072
      result.confirm("cert allows encryption", enc_cert.allowed_usage(Usage_Type::ENCRYPTION));
2✔
1073
      result.confirm("cert does not allow TLS client auth", !enc_cert.allowed_usage(Usage_Type::TLS_CLIENT_AUTH));
2✔
1074
   }
1✔
1075

1076
   return result;
8✔
1077
}
16✔
1078

1079
Test::Result test_self_issued(const Botan::Private_Key& ca_key,
9✔
1080
                              const std::string& sig_algo,
1081
                              const std::string& sig_padding,
1082
                              const std::string& hash_fn) {
1083
   using Botan::Key_Constraints;
9✔
1084

1085
   Test::Result result("X509 Self Issued");
9✔
1086

1087
   // create the self-signed cert
1088
   const Botan::X509_Certificate ca_cert =
9✔
1089
      Botan::X509::create_self_signed_cert(ca_opts(sig_padding), ca_key, hash_fn, Test::rng());
9✔
1090

1091
   /* Create the CA object */
1092
   const Botan::X509_CA ca(ca_cert, ca_key, hash_fn, sig_padding, Test::rng());
9✔
1093

1094
   auto user_key = make_a_private_key(sig_algo);
9✔
1095

1096
   // create a self-issued certificate, that is, a certificate with subject dn == issuer dn,
1097
   // but signed by a CA, not signed by it's own private key
1098
   Botan::X509_Cert_Options opts = ca_opts();
9✔
1099
   opts.constraints = Key_Constraints::DigitalSignature;
9✔
1100
   opts.set_padding_scheme(sig_padding);
9✔
1101

1102
   const Botan::PKCS10_Request self_issued_req = Botan::X509::create_cert_req(opts, *user_key, hash_fn, Test::rng());
9✔
1103

1104
   const Botan::X509_Certificate self_issued_cert =
9✔
1105
      ca.sign_request(self_issued_req, Test::rng(), from_date(-1, 01, 01), from_date(2, 01, 01));
9✔
1106

1107
   // check that this chain can can be verified successfully
1108
   const Botan::Certificate_Store_In_Memory trusted(ca.ca_certificate());
9✔
1109

1110
   const Botan::Path_Validation_Restrictions restrictions(false, 80);
18✔
1111

1112
   const Botan::Path_Validation_Result validation_result =
9✔
1113
      Botan::x509_path_validate(self_issued_cert, restrictions, trusted);
9✔
1114

1115
   result.confirm("chain with self-issued cert validates", validation_result.successful_validation());
18✔
1116

1117
   return result;
9✔
1118
}
18✔
1119

1120
Test::Result test_x509_uninit() {
1✔
1121
   Test::Result result("X509 object uninitialized access");
1✔
1122

1123
   Botan::X509_Certificate cert;
1✔
1124
   result.test_throws("uninitialized cert access causes exception", "X509_Certificate uninitialized", [&cert]() {
3✔
1125
      cert.x509_version();
1✔
1126
   });
1127

1128
   Botan::X509_CRL crl;
1✔
1129
   result.test_throws(
3✔
1130
      "uninitialized crl access causes exception", "X509_CRL uninitialized", [&crl]() { crl.crl_number(); });
1✔
1131

1132
   return result;
1✔
1133
}
1✔
1134

1135
Test::Result test_valid_constraints(const Botan::Private_Key& key, const std::string& pk_algo) {
12✔
1136
   using Botan::Key_Constraints;
12✔
1137

1138
   Test::Result result("X509 Valid Constraints " + pk_algo);
12✔
1139

1140
   result.confirm("empty constraints always acceptable", Key_Constraints().compatible_with(key));
24✔
1141

1142
   // Now check some typical usage scenarios for the given key type
1143
   // Taken from RFC 5280, sec. 4.2.1.3
1144
   // ALL constraints are not typical at all, but we use them for a negative test
1145
   const auto all = Key_Constraints(
12✔
1146
      Key_Constraints::DigitalSignature | Key_Constraints::NonRepudiation | Key_Constraints::KeyEncipherment |
1147
      Key_Constraints::DataEncipherment | Key_Constraints::KeyAgreement | Key_Constraints::KeyCertSign |
1148
      Key_Constraints::CrlSign | Key_Constraints::EncipherOnly | Key_Constraints::DecipherOnly);
12✔
1149

1150
   const auto ca = Key_Constraints(Key_Constraints::KeyCertSign);
12✔
1151
   const auto sign_data = Key_Constraints(Key_Constraints::DigitalSignature);
12✔
1152
   const auto non_repudiation = Key_Constraints(Key_Constraints::NonRepudiation | Key_Constraints::DigitalSignature);
12✔
1153
   const auto key_encipherment = Key_Constraints(Key_Constraints::KeyEncipherment);
12✔
1154
   const auto data_encipherment = Key_Constraints(Key_Constraints::DataEncipherment);
12✔
1155
   const auto key_agreement = Key_Constraints(Key_Constraints::KeyAgreement);
12✔
1156
   const auto key_agreement_encipher_only =
12✔
1157
      Key_Constraints(Key_Constraints::KeyAgreement | Key_Constraints::EncipherOnly);
12✔
1158
   const auto key_agreement_decipher_only =
12✔
1159
      Key_Constraints(Key_Constraints::KeyAgreement | Key_Constraints::DecipherOnly);
12✔
1160
   const auto crl_sign = Key_Constraints(Key_Constraints::CrlSign);
12✔
1161
   const auto sign_everything =
12✔
1162
      Key_Constraints(Key_Constraints::DigitalSignature | Key_Constraints::KeyCertSign | Key_Constraints::CrlSign);
12✔
1163

1164
   if(pk_algo == "DH" || pk_algo == "ECDH") {
12✔
1165
      // DH and ECDH only for key agreement
1166
      result.test_eq("all constraints not permitted", all.compatible_with(key), false);
2✔
1167
      result.test_eq("cert sign not permitted", ca.compatible_with(key), false);
2✔
1168
      result.test_eq("signature not permitted", sign_data.compatible_with(key), false);
2✔
1169
      result.test_eq("non repudiation not permitted", non_repudiation.compatible_with(key), false);
2✔
1170
      result.test_eq("key encipherment not permitted", key_encipherment.compatible_with(key), false);
2✔
1171
      result.test_eq("data encipherment not permitted", data_encipherment.compatible_with(key), false);
2✔
1172
      result.test_eq("usage acceptable", key_agreement.compatible_with(key), true);
2✔
1173
      result.test_eq("usage acceptable", key_agreement_encipher_only.compatible_with(key), true);
2✔
1174
      result.test_eq("usage acceptable", key_agreement_decipher_only.compatible_with(key), true);
2✔
1175
      result.test_eq("crl sign not permitted", crl_sign.compatible_with(key), false);
2✔
1176
      result.test_eq("sign", sign_everything.compatible_with(key), false);
4✔
1177
   } else if(pk_algo == "Kyber") {
10✔
1178
      // Kyber can encrypt and agree
1179
      result.test_eq("all constraints not permitted", all.compatible_with(key), false);
1✔
1180
      result.test_eq("cert sign not permitted", ca.compatible_with(key), false);
1✔
1181
      result.test_eq("signature not permitted", sign_data.compatible_with(key), false);
1✔
1182
      result.test_eq("non repudiation not permitted", non_repudiation.compatible_with(key), false);
1✔
1183
      result.test_eq("crl sign not permitted", crl_sign.compatible_with(key), false);
1✔
1184
      result.test_eq("sign", sign_everything.compatible_with(key), false);
1✔
1185
      result.test_eq("key agreement not permitted", key_agreement.compatible_with(key), false);
1✔
1186
      result.test_eq("usage acceptable", data_encipherment.compatible_with(key), true);
1✔
1187
      result.test_eq("usage acceptable", key_encipherment.compatible_with(key), true);
2✔
1188
   } else if(pk_algo == "RSA") {
9✔
1189
      // RSA can do everything except key agreement
1190
      result.test_eq("all constraints not permitted", all.compatible_with(key), false);
1✔
1191

1192
      result.test_eq("usage acceptable", ca.compatible_with(key), true);
1✔
1193
      result.test_eq("usage acceptable", sign_data.compatible_with(key), true);
1✔
1194
      result.test_eq("usage acceptable", non_repudiation.compatible_with(key), true);
1✔
1195
      result.test_eq("usage acceptable", key_encipherment.compatible_with(key), true);
1✔
1196
      result.test_eq("usage acceptable", data_encipherment.compatible_with(key), true);
1✔
1197
      result.test_eq("key agreement not permitted", key_agreement.compatible_with(key), false);
1✔
1198
      result.test_eq("key agreement", key_agreement_encipher_only.compatible_with(key), false);
1✔
1199
      result.test_eq("key agreement", key_agreement_decipher_only.compatible_with(key), false);
1✔
1200
      result.test_eq("usage acceptable", crl_sign.compatible_with(key), true);
1✔
1201
      result.test_eq("usage acceptable", sign_everything.compatible_with(key), true);
2✔
1202
   } else if(pk_algo == "ElGamal") {
8✔
1203
      // only ElGamal encryption is currently implemented
1204
      result.test_eq("all constraints not permitted", all.compatible_with(key), false);
1✔
1205
      result.test_eq("cert sign not permitted", ca.compatible_with(key), false);
1✔
1206
      result.test_eq("data encipherment permitted", data_encipherment.compatible_with(key), true);
1✔
1207
      result.test_eq("key encipherment permitted", key_encipherment.compatible_with(key), true);
1✔
1208
      result.test_eq("key agreement not permitted", key_agreement.compatible_with(key), false);
1✔
1209
      result.test_eq("key agreement", key_agreement_encipher_only.compatible_with(key), false);
1✔
1210
      result.test_eq("key agreement", key_agreement_decipher_only.compatible_with(key), false);
1✔
1211
      result.test_eq("crl sign not permitted", crl_sign.compatible_with(key), false);
1✔
1212
      result.test_eq("sign", sign_everything.compatible_with(key), false);
2✔
1213
   } else if(pk_algo == "DSA" || pk_algo == "ECDSA" || pk_algo == "ECGDSA" || pk_algo == "ECKCDSA" ||
7✔
1214
             pk_algo == "GOST-34.10" || pk_algo == "Dilithium") {
10✔
1215
      // these are signature algorithms only
1216
      result.test_eq("all constraints not permitted", all.compatible_with(key), false);
6✔
1217

1218
      result.test_eq("ca allowed", ca.compatible_with(key), true);
6✔
1219
      result.test_eq("sign allowed", sign_data.compatible_with(key), true);
6✔
1220
      result.test_eq("non-repudiation allowed", non_repudiation.compatible_with(key), true);
6✔
1221
      result.test_eq("key encipherment not permitted", key_encipherment.compatible_with(key), false);
6✔
1222
      result.test_eq("data encipherment not permitted", data_encipherment.compatible_with(key), false);
6✔
1223
      result.test_eq("key agreement not permitted", key_agreement.compatible_with(key), false);
6✔
1224
      result.test_eq("key agreement", key_agreement_encipher_only.compatible_with(key), false);
6✔
1225
      result.test_eq("key agreement", key_agreement_decipher_only.compatible_with(key), false);
6✔
1226
      result.test_eq("crl sign allowed", crl_sign.compatible_with(key), true);
6✔
1227
      result.test_eq("sign allowed", sign_everything.compatible_with(key), true);
12✔
1228
   }
1229

1230
   return result;
12✔
1231
}
×
1232

1233
/**
1234
 * @brief X.509v3 extension that encodes a given string
1235
 */
1236
class String_Extension final : public Botan::Certificate_Extension {
1237
   public:
1238
      String_Extension() = default;
18✔
1239

1240
      explicit String_Extension(const std::string& val) : m_contents(val) {}
9✔
1241

1242
      std::string value() const { return m_contents; }
18✔
1243

1244
      std::unique_ptr<Certificate_Extension> copy() const override {
×
1245
         return std::make_unique<String_Extension>(m_contents);
×
1246
      }
1247

1248
      Botan::OID oid_of() const override { return Botan::OID("1.2.3.4.5.6.7.8.9.1"); }
18✔
1249

1250
      bool should_encode() const override { return true; }
18✔
1251

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

1254
      std::vector<uint8_t> encode_inner() const override {
9✔
1255
         std::vector<uint8_t> bits;
9✔
1256
         Botan::DER_Encoder(bits).encode(Botan::ASN1_String(m_contents, Botan::ASN1_Type::Utf8String));
9✔
1257
         return bits;
9✔
1258
      }
×
1259

1260
      void decode_inner(const std::vector<uint8_t>& in) override {
18✔
1261
         Botan::ASN1_String str;
18✔
1262
         Botan::BER_Decoder(in).decode(str, Botan::ASN1_Type::Utf8String).verify_end();
18✔
1263
         m_contents = str.value();
18✔
1264
      }
18✔
1265

1266
   private:
1267
      std::string m_contents;
1268
};
1269

1270
Test::Result test_custom_dn_attr(const Botan::Private_Key& ca_key,
9✔
1271
                                 const std::string& sig_algo,
1272
                                 const std::string& sig_padding,
1273
                                 const std::string& hash_fn) {
1274
   Test::Result result("X509 Custom DN");
9✔
1275

1276
   /* Create the self-signed cert */
1277
   Botan::X509_Certificate ca_cert =
9✔
1278
      Botan::X509::create_self_signed_cert(ca_opts(sig_padding), ca_key, hash_fn, Test::rng());
9✔
1279

1280
   /* Create the CA object */
1281
   Botan::X509_CA ca(ca_cert, ca_key, hash_fn, sig_padding, Test::rng());
9✔
1282

1283
   auto user_key = make_a_private_key(sig_algo);
9✔
1284

1285
   Botan::X509_DN subject_dn;
9✔
1286

1287
   const Botan::OID attr1(Botan::OID("1.3.6.1.4.1.25258.9.1.1"));
9✔
1288
   const Botan::OID attr2(Botan::OID("1.3.6.1.4.1.25258.9.1.2"));
9✔
1289
   const Botan::ASN1_String val1("Custom Attr 1", Botan::ASN1_Type::PrintableString);
9✔
1290
   const Botan::ASN1_String val2("12345", Botan::ASN1_Type::Utf8String);
9✔
1291

1292
   subject_dn.add_attribute(attr1, val1);
9✔
1293
   subject_dn.add_attribute(attr2, val2);
9✔
1294

1295
   Botan::Extensions extensions;
9✔
1296

1297
   Botan::PKCS10_Request req =
9✔
1298
      Botan::PKCS10_Request::create(*user_key, subject_dn, extensions, hash_fn, Test::rng(), sig_padding);
9✔
1299

1300
   const Botan::X509_DN& req_dn = req.subject_dn();
9✔
1301

1302
   result.test_eq("Expected number of DN entries", req_dn.dn_info().size(), 2);
9✔
1303

1304
   Botan::ASN1_String req_val1 = req_dn.get_first_attribute(attr1);
9✔
1305
   Botan::ASN1_String req_val2 = req_dn.get_first_attribute(attr2);
9✔
1306
   result.confirm("Attr1 matches encoded", req_val1 == val1);
18✔
1307
   result.confirm("Attr2 matches encoded", req_val2 == val2);
18✔
1308
   result.confirm("Attr1 tag matches encoded", req_val1.tagging() == val1.tagging());
18✔
1309
   result.confirm("Attr2 tag matches encoded", req_val2.tagging() == val2.tagging());
18✔
1310

1311
   Botan::X509_Time not_before("100301123001Z", Botan::ASN1_Type::UtcTime);
9✔
1312
   Botan::X509_Time not_after("300301123001Z", Botan::ASN1_Type::UtcTime);
9✔
1313

1314
   auto cert = ca.sign_request(req, Test::rng(), not_before, not_after);
9✔
1315

1316
   const Botan::X509_DN& cert_dn = cert.subject_dn();
9✔
1317

1318
   result.test_eq("Expected number of DN entries", cert_dn.dn_info().size(), 2);
9✔
1319

1320
   Botan::ASN1_String cert_val1 = cert_dn.get_first_attribute(attr1);
9✔
1321
   Botan::ASN1_String cert_val2 = cert_dn.get_first_attribute(attr2);
9✔
1322
   result.confirm("Attr1 matches encoded", cert_val1 == val1);
18✔
1323
   result.confirm("Attr2 matches encoded", cert_val2 == val2);
18✔
1324
   result.confirm("Attr1 tag matches encoded", cert_val1.tagging() == val1.tagging());
18✔
1325
   result.confirm("Attr2 tag matches encoded", cert_val2.tagging() == val2.tagging());
18✔
1326

1327
   return result;
9✔
1328
}
27✔
1329

1330
Test::Result test_x509_extensions(const Botan::Private_Key& ca_key,
9✔
1331
                                  const std::string& sig_algo,
1332
                                  const std::string& sig_padding,
1333
                                  const std::string& hash_fn) {
1334
   using Botan::Key_Constraints;
9✔
1335

1336
   Test::Result result("X509 Extensions");
9✔
1337

1338
   /* Create the self-signed cert */
1339
   Botan::X509_Certificate ca_cert =
9✔
1340
      Botan::X509::create_self_signed_cert(ca_opts(sig_padding), ca_key, hash_fn, Test::rng());
9✔
1341

1342
   /* Create the CA object */
1343
   Botan::X509_CA ca(ca_cert, ca_key, hash_fn, sig_padding, Test::rng());
9✔
1344

1345
   /* Prepare CDP extension */
1346
   std::vector<std::string> cdp_urls = {
9✔
1347
      "http://example.com/crl1.pem",
1348
      "ldap://ldap.example.com/cn=crl1,dc=example,dc=com?certificateRevocationList;binary"};
27✔
1349

1350
   std::vector<Botan::Cert_Extension::CRL_Distribution_Points::Distribution_Point> dps;
9✔
1351

1352
   for(const auto& uri : cdp_urls) {
27✔
1353
      Botan::AlternativeName cdp_alt_name("", uri);
18✔
1354
      Botan::Cert_Extension::CRL_Distribution_Points::Distribution_Point dp(cdp_alt_name);
18✔
1355

1356
      dps.emplace_back(dp);
18✔
1357
   }
18✔
1358

1359
   auto user_key = make_a_private_key(sig_algo);
9✔
1360

1361
   Botan::X509_Cert_Options opts("Test User 1/US/Botan Project/Testing");
9✔
1362
   opts.constraints = Key_Constraints::DigitalSignature;
9✔
1363

1364
   // include a custom extension in the request
1365
   Botan::Extensions req_extensions;
9✔
1366
   const Botan::OID oid("1.2.3.4.5.6.7.8.9.1");
9✔
1367
   const Botan::OID ku_oid = Botan::OID::from_string("X509v3.KeyUsage");
9✔
1368
   req_extensions.add(std::make_unique<String_Extension>("AAAAAAAAAAAAAABCDEF"), false);
18✔
1369
   req_extensions.add(std::make_unique<Botan::Cert_Extension::CRL_Distribution_Points>(dps));
18✔
1370
   opts.extensions = req_extensions;
9✔
1371
   opts.set_padding_scheme(sig_padding);
9✔
1372

1373
   /* Create a self-signed certificate */
1374
   const Botan::X509_Certificate self_signed_cert =
9✔
1375
      Botan::X509::create_self_signed_cert(opts, *user_key, hash_fn, Test::rng());
9✔
1376

1377
   result.confirm("Extensions::extension_set true for Key_Usage",
18✔
1378
                  self_signed_cert.v3_extensions().extension_set(ku_oid));
9✔
1379

1380
   // check if known Key_Usage extension is present in self-signed cert
1381
   auto key_usage_ext = self_signed_cert.v3_extensions().get(ku_oid);
9✔
1382
   if(result.confirm("Key_Usage extension present in self-signed certificate", key_usage_ext != nullptr)) {
27✔
1383
      result.confirm(
27✔
1384
         "Key_Usage extension value matches in self-signed certificate",
1385
         dynamic_cast<Botan::Cert_Extension::Key_Usage&>(*key_usage_ext).get_constraints() == opts.constraints);
9✔
1386
   }
1387

1388
   // check if custom extension is present in self-signed cert
1389
   auto string_ext = self_signed_cert.v3_extensions().get_raw<String_Extension>(oid);
9✔
1390
   if(result.confirm("Custom extension present in self-signed certificate", string_ext != nullptr)) {
27✔
1391
      result.test_eq(
36✔
1392
         "Custom extension value matches in self-signed certificate", string_ext->value(), "AAAAAAAAAAAAAABCDEF");
27✔
1393
   }
1394

1395
   // check if CDPs are present in the self-signed cert
1396
   auto cert_cdps =
9✔
1397
      self_signed_cert.v3_extensions().get_extension_object_as<Botan::Cert_Extension::CRL_Distribution_Points>();
18✔
1398

1399
   if(result.confirm("CRL Distribution Points extension present in self-signed certificate",
27✔
1400
                     !cert_cdps->crl_distribution_urls().empty())) {
9✔
1401
      for(const auto& cdp : cert_cdps->distribution_points()) {
27✔
1402
         result.confirm("CDP URI present in self-signed certificate",
36✔
1403
                        std::ranges::find(cdp_urls, cdp.point().get_first_attribute("URI")) != cdp_urls.end());
72✔
1404
      }
1405
   }
1406

1407
   const Botan::PKCS10_Request user_req = Botan::X509::create_cert_req(opts, *user_key, hash_fn, Test::rng());
9✔
1408

1409
   /* Create a CA-signed certificate */
1410
   const Botan::X509_Certificate ca_signed_cert =
9✔
1411
      ca.sign_request(user_req, Test::rng(), from_date(-1, 01, 01), from_date(2, 01, 01));
9✔
1412

1413
   // check if known Key_Usage extension is present in CA-signed cert
1414
   result.confirm("Extensions::extension_set true for Key_Usage", ca_signed_cert.v3_extensions().extension_set(ku_oid));
18✔
1415

1416
   key_usage_ext = ca_signed_cert.v3_extensions().get(ku_oid);
18✔
1417
   if(result.confirm("Key_Usage extension present in CA-signed certificate", key_usage_ext != nullptr)) {
27✔
1418
      auto constraints = dynamic_cast<Botan::Cert_Extension::Key_Usage&>(*key_usage_ext).get_constraints();
9✔
1419
      result.confirm("Key_Usage extension value matches in user certificate",
27✔
1420
                     constraints == Botan::Key_Constraints::DigitalSignature);
9✔
1421
   }
1422

1423
   // check if custom extension is present in CA-signed cert
1424
   result.confirm("Extensions::extension_set true for String_Extension",
18✔
1425
                  ca_signed_cert.v3_extensions().extension_set(oid));
9✔
1426
   string_ext = ca_signed_cert.v3_extensions().get_raw<String_Extension>(oid);
18✔
1427
   if(result.confirm("Custom extension present in CA-signed certificate", string_ext != nullptr)) {
27✔
1428
      result.test_eq(
36✔
1429
         "Custom extension value matches in CA-signed certificate", string_ext->value(), "AAAAAAAAAAAAAABCDEF");
27✔
1430
   }
1431

1432
   // check if CDPs are present in the CA-signed cert
1433
   cert_cdps = ca_signed_cert.v3_extensions().get_extension_object_as<Botan::Cert_Extension::CRL_Distribution_Points>();
18✔
1434

1435
   if(result.confirm("CRL Distribution Points extension present in self-signed certificate",
27✔
1436
                     !cert_cdps->crl_distribution_urls().empty())) {
9✔
1437
      for(const auto& cdp : cert_cdps->distribution_points()) {
27✔
1438
         result.confirm("CDP URI present in self-signed certificate",
36✔
1439
                        std::ranges::find(cdp_urls, cdp.point().get_first_attribute("URI")) != cdp_urls.end());
72✔
1440
      }
1441
   }
1442

1443
   return result;
9✔
1444
}
45✔
1445

1446
Test::Result test_hashes(const Botan::Private_Key& key, const std::string& hash_fn) {
8✔
1447
   Test::Result result("X509 Hashes");
8✔
1448

1449
   struct TestData {
8✔
1450
         const std::string issuer, subject, issuer_hash, subject_hash;
1451
   } const cases[]{{"",
1452
                    "",
1453
                    "E4F60D0AA6D7F3D3B6A6494B1C861B99F649C6F9EC51ABAF201B20F297327C95",
1454
                    "E4F60D0AA6D7F3D3B6A6494B1C861B99F649C6F9EC51ABAF201B20F297327C95"},
1455
                   {"a",
1456
                    "b",
1457
                    "BC2E013472F39AC579964880E422737C82BA812CB8BC2FD17E013060D71E6E19",
1458
                    "5E31CFAA3FAFB1A5BA296A0D2BAB9CA44D7936E9BF0BBC54637D0C53DBC4A432"},
1459
                   {"A",
1460
                    "B",
1461
                    "4B3206201C4BC9B6CD6C36532A97687DF9238155D99ADB60C66BF2B2220643D8",
1462
                    "FFF635A52A16618B4A0E9CD26B5E5A2FA573D343C051E6DE8B0811B1ACC89B86"},
1463
                   {
1464
                      "Test Issuer/US/Botan Project/Testing",
1465
                      "Test Subject/US/Botan Project/Testing",
1466
                      "ACB4F373004A56A983A23EB8F60FA4706312B5DB90FD978574FE7ACC84E093A5",
1467
                      "87039231C2205B74B6F1F3830A66272C0B41F71894B03AC3150221766D95267B",
1468
                   },
1469
                   {
1470
                      "Test Subject/US/Botan Project/Testing",
1471
                      "Test Issuer/US/Botan Project/Testing",
1472
                      "87039231C2205B74B6F1F3830A66272C0B41F71894B03AC3150221766D95267B",
1473
                      "ACB4F373004A56A983A23EB8F60FA4706312B5DB90FD978574FE7ACC84E093A5",
1474
                   }};
48✔
1475

1476
   for(const auto& a : cases) {
48✔
1477
      Botan::X509_Cert_Options opts{a.issuer};
40✔
1478
      opts.CA_key();
40✔
1479

1480
      const Botan::X509_Certificate issuer_cert = Botan::X509::create_self_signed_cert(opts, key, hash_fn, Test::rng());
40✔
1481

1482
      result.test_eq(a.issuer, Botan::hex_encode(issuer_cert.raw_issuer_dn_sha256()), a.issuer_hash);
80✔
1483
      result.test_eq(a.issuer, Botan::hex_encode(issuer_cert.raw_subject_dn_sha256()), a.issuer_hash);
80✔
1484

1485
      const Botan::X509_CA ca(issuer_cert, key, hash_fn, Test::rng());
40✔
1486
      const Botan::PKCS10_Request req =
40✔
1487
         Botan::X509::create_cert_req(Botan::X509_Cert_Options(a.subject), key, hash_fn, Test::rng());
40✔
1488
      const Botan::X509_Certificate subject_cert =
40✔
1489
         ca.sign_request(req, Test::rng(), from_date(-1, 01, 01), from_date(2, 01, 01));
40✔
1490

1491
      result.test_eq(a.subject, Botan::hex_encode(subject_cert.raw_issuer_dn_sha256()), a.issuer_hash);
80✔
1492
      result.test_eq(a.subject, Botan::hex_encode(subject_cert.raw_subject_dn_sha256()), a.subject_hash);
80✔
1493
   }
40✔
1494
   return result;
8✔
1495
}
48✔
1496

1497
std::vector<std::string> get_sig_paddings(const std::string& sig_algo, const std::string& hash) {
8✔
1498
   if(sig_algo == "RSA") {
8✔
1499
      return {"EMSA3(" + hash + ")", "EMSA4(" + hash + ")"};
3✔
1500
   } else if(sig_algo == "DSA" || sig_algo == "ECDSA" || sig_algo == "ECGDSA" || sig_algo == "ECKCDSA" ||
7✔
1501
             sig_algo == "GOST-34.10") {
3✔
1502
      return {hash};
10✔
1503
   } else if(sig_algo == "Ed25519") {
2✔
1504
      return {"Pure"};
2✔
1505
   } else if(sig_algo == "Dilithium") {
1✔
1506
      return {"Randomized"};
2✔
1507
   } else {
1508
      return {};
8✔
1509
   }
1510
}
1511

1512
class X509_Cert_Unit_Tests final : public Test {
×
1513
   public:
1514
      std::vector<Test::Result> run() override {
1✔
1515
         std::vector<Test::Result> results;
1✔
1516

1517
         const std::string sig_algos[]{
1✔
1518
            "RSA", "DSA", "ECDSA", "ECGDSA", "ECKCDSA", "GOST-34.10", "Ed25519", "Dilithium"};
9✔
1519

1520
         for(const std::string& algo : sig_algos) {
9✔
1521
   #if !defined(BOTAN_HAS_EMSA_PKCS1)
1522
            if(algo == "RSA")
1523
               continue;
1524
   #endif
1525

1526
            std::string hash = "SHA-256";
8✔
1527

1528
            if(algo == "Ed25519") {
8✔
1529
               hash = "SHA-512";
1✔
1530
            }
1531
            if(algo == "Dilithium") {
8✔
1532
               hash = "SHAKE-256(512)";
1✔
1533
            }
1534

1535
            auto key = make_a_private_key(algo);
8✔
1536

1537
            if(key == nullptr) {
8✔
1538
               continue;
×
1539
            }
1540

1541
            results.push_back(test_hashes(*key, hash));
16✔
1542
            results.push_back(test_valid_constraints(*key, algo));
16✔
1543

1544
            Test::Result usage_result("X509 Usage");
8✔
1545
            try {
8✔
1546
               usage_result.merge(test_usage(*key, algo, hash));
8✔
1547
            } catch(std::exception& e) {
×
1548
               usage_result.test_failure("test_usage " + algo, e.what());
×
1549
            }
×
1550
            results.push_back(usage_result);
8✔
1551

1552
            for(const auto& padding_scheme : get_sig_paddings(algo, hash)) {
17✔
1553
               Test::Result cert_result("X509 Unit");
9✔
1554

1555
               try {
9✔
1556
                  cert_result.merge(test_x509_cert(*key, algo, padding_scheme, hash));
9✔
1557
               } catch(std::exception& e) {
×
1558
                  cert_result.test_failure("test_x509_cert " + algo, e.what());
×
1559
               }
×
1560
               results.push_back(cert_result);
9✔
1561

1562
               Test::Result pkcs10_result("PKCS10 extensions");
9✔
1563
               try {
9✔
1564
                  pkcs10_result.merge(test_pkcs10_ext(*key, padding_scheme, hash));
9✔
1565
               } catch(std::exception& e) {
×
1566
                  pkcs10_result.test_failure("test_pkcs10_ext " + algo, e.what());
×
1567
               }
×
1568
               results.push_back(pkcs10_result);
9✔
1569

1570
               Test::Result self_issued_result("X509 Self Issued");
9✔
1571
               try {
9✔
1572
                  self_issued_result.merge(test_self_issued(*key, algo, padding_scheme, hash));
9✔
1573
               } catch(std::exception& e) {
×
1574
                  self_issued_result.test_failure("test_self_issued " + algo, e.what());
×
1575
               }
×
1576
               results.push_back(self_issued_result);
9✔
1577

1578
               Test::Result extensions_result("X509 Extensions");
9✔
1579
               try {
9✔
1580
                  extensions_result.merge(test_x509_extensions(*key, algo, padding_scheme, hash));
9✔
1581
               } catch(std::exception& e) {
×
1582
                  extensions_result.test_failure("test_extensions " + algo, e.what());
×
1583
               }
×
1584
               results.push_back(extensions_result);
9✔
1585

1586
               Test::Result custom_dn_result("X509 Custom DN");
9✔
1587
               try {
9✔
1588
                  custom_dn_result.merge(test_custom_dn_attr(*key, algo, padding_scheme, hash));
9✔
1589
               } catch(std::exception& e) {
×
1590
                  custom_dn_result.test_failure("test_custom_dn_attr " + algo, e.what());
×
1591
               }
×
1592
               results.push_back(custom_dn_result);
9✔
1593
            }
17✔
1594
         }
16✔
1595

1596
         /*
1597
         These are algos which cannot sign but can be included in certs
1598
         */
1599
         const std::vector<std::string> enc_algos = {"DH", "ECDH", "ElGamal", "Kyber"};
5✔
1600

1601
         for(const std::string& algo : enc_algos) {
5✔
1602
            auto key = make_a_private_key(algo);
4✔
1603

1604
            if(key) {
4✔
1605
               results.push_back(test_valid_constraints(*key, algo));
8✔
1606
            }
1607
         }
4✔
1608

1609
   #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM) && defined(BOTAN_HAS_EMSA_PKCS1) && defined(BOTAN_HAS_EMSA_PSSR) && \
1610
      defined(BOTAN_HAS_RSA)
1611
         Test::Result pad_config_result("X509 Padding Config");
1✔
1612
         try {
1✔
1613
            pad_config_result.merge(test_padding_config());
1✔
1614
         } catch(const std::exception& e) {
×
1615
            pad_config_result.test_failure("test_padding_config", e.what());
×
1616
         }
×
1617
         results.push_back(pad_config_result);
1✔
1618
   #endif
1619

1620
   #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
1621
         results.push_back(test_x509_utf8());
2✔
1622
         results.push_back(test_x509_bmpstring());
2✔
1623
         results.push_back(test_x509_teletex());
2✔
1624
         results.push_back(test_crl_dn_name());
2✔
1625
         results.push_back(test_rdn_multielement_set_name());
2✔
1626
         results.push_back(test_x509_decode_list());
2✔
1627
         results.push_back(test_rsa_oaep());
2✔
1628
         results.push_back(test_x509_authority_info_access_extension());
2✔
1629
         results.push_back(test_verify_gost2012_cert());
2✔
1630
         results.push_back(test_parse_rsa_pss_cert());
2✔
1631
   #endif
1632

1633
         results.push_back(test_x509_encode_authority_info_access_extension());
2✔
1634
         results.push_back(test_x509_extension());
2✔
1635
         results.push_back(test_x509_dates());
2✔
1636
         results.push_back(test_cert_status_strings());
2✔
1637
         results.push_back(test_x509_uninit());
2✔
1638

1639
         return results;
1✔
1640
      }
9✔
1641
};
1642

1643
BOTAN_REGISTER_TEST("x509", "x509_unit", X509_Cert_Unit_Tests);
1644

1645
#endif
1646

1647
}  // namespace
1648

1649
}  // namespace Botan_Tests
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc