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

randombit / botan / 27806188297

18 Jun 2026 04:12PM UTC coverage: 89.37% (-0.03%) from 89.397%
27806188297

push

github

web-flow
Merge pull request #5677 from randombit/jack/oid-names

Add OID::registered_name

111637 of 124915 relevant lines covered (89.37%)

10895907.86 hits per line

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

95.98
/src/tests/test_x509_path.cpp
1
/*
2
* (C) 2006,2011,2012,2014,2015 Jack Lloyd
3
* (C) 2022 René Meusel, 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 "test_arb_eq.h"
12
   #include <botan/assert.h>
13
   #include <botan/data_src.h>
14
   #include <botan/exceptn.h>
15
   #include <botan/pk_keys.h>
16
   #include <botan/pkcs10.h>
17
   #include <botan/rng.h>
18
   #include <botan/x509_crl.h>
19
   #include <botan/x509_ext.h>
20
   #include <botan/x509path.h>
21
   #include <botan/internal/calendar.h>
22
   #include <botan/internal/filesystem.h>
23
   #include <botan/internal/fmt.h>
24
   #include <botan/internal/parsing.h>
25

26
   #if defined(BOTAN_HAS_ECDSA)
27
      #include <botan/ec_group.h>
28
   #endif
29

30
   #include <algorithm>
31
   #include <fstream>
32
   #include <limits>
33
   #include <map>
34
   #include <string>
35
   #include <vector>
36
#endif
37

38
namespace Botan_Tests {
39

40
namespace {
41

42
#if defined(BOTAN_HAS_X509_CERTIFICATES) && defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
43

44
[[maybe_unused]] std::map<std::string, std::string> read_results(const std::string& results_file,
10✔
45
                                                                 const char delim = ':') {
46
   std::ifstream in(results_file);
10✔
47
   if(!in.good()) {
10✔
48
      throw Test_Error("Failed reading " + results_file);
×
49
   }
50

51
   std::map<std::string, std::string> m;
10✔
52
   std::string line;
10✔
53
   while(in.good()) {
624✔
54
      std::getline(in, line);
614✔
55
      if(line.empty()) {
614✔
56
         continue;
34✔
57
      }
58
      if(line[0] == '#') {
596✔
59
         continue;
16✔
60
      }
61

62
      std::vector<std::string> parts = Botan::split_on(line, delim);
580✔
63

64
      if(parts.size() != 2) {
580✔
65
         throw Test_Error("Invalid line " + line);
×
66
      }
67

68
      m[parts[0]] = parts[1];
580✔
69
   }
580✔
70

71
   return m;
20✔
72
}
10✔
73

74
   #if defined(BOTAN_HAS_RSA) && defined(BOTAN_HAS_EMSA_PKCS1)
75

76
std::vector<Botan::X509_Certificate> load_cert_file(const std::string& filename) {
80✔
77
   Botan::DataSource_Stream in(filename);
80✔
78

79
   std::vector<Botan::X509_Certificate> certs;
80✔
80
   while(!in.end_of_data()) {
508✔
81
      try {
348✔
82
         certs.emplace_back(in);
348✔
83
      } catch(Botan::Decoding_Error&) {}
80✔
84
   }
85

86
   return certs;
80✔
87
}
80✔
88

89
std::set<Botan::Certificate_Status_Code> flatten(const Botan::CertificatePathStatusCodes& codes) {
4✔
90
   std::set<Botan::Certificate_Status_Code> result;
4✔
91

92
   for(const auto& statuses : codes) {
16✔
93
      result.insert(statuses.begin(), statuses.end());
12✔
94
   }
95

96
   return result;
4✔
97
}
×
98

99
Botan::Path_Validation_Restrictions get_default_restrictions(bool req_revocation_info, bool ocsp_all_intermediates) {
5✔
100
   return Botan::Path_Validation_Restrictions(req_revocation_info, 80 /*some tests use SHA-1*/, ocsp_all_intermediates);
5✔
101
}
102

103
Botan::Path_Validation_Restrictions get_allow_non_self_signed_anchors_restrictions(bool req_revocation_info,
6✔
104
                                                                                   bool ocsp_all_intermediates) {
105
   return Botan::Path_Validation_Restrictions(req_revocation_info,
6✔
106
                                              80, /*some tests use SHA-1*/
107
                                              ocsp_all_intermediates,
108
                                              std::chrono::seconds::zero(),
109
                                              std::make_unique<Botan::Certificate_Store_In_Memory>(),
12✔
110
                                              false,
111
                                              false /* require_self_signed_trust_anchors */);
12✔
112
}
113

114
std::vector<Botan::Path_Validation_Restrictions> restrictions_to_test(bool req_revocation_info,
3✔
115
                                                                      bool ocsp_all_intermediates) {
116
   std::vector<Botan::Path_Validation_Restrictions> restrictions;
3✔
117
   restrictions.emplace_back(get_default_restrictions(req_revocation_info, ocsp_all_intermediates));
3✔
118
   restrictions.emplace_back(
3✔
119
      get_allow_non_self_signed_anchors_restrictions(req_revocation_info, ocsp_all_intermediates));
6✔
120
   return restrictions;
3✔
121
}
×
122

123
class X509test_Path_Validation_Tests final : public Test {
1✔
124
   public:
125
      std::vector<Test::Result> run() override {
1✔
126
         std::vector<Test::Result> results;
1✔
127
         for(const auto& restrictions : restrictions_to_test(false, false)) {
3✔
128
            auto partial_res = run_with_restrictions(restrictions);
2✔
129
            results.insert(results.end(), partial_res.begin(), partial_res.end());
2✔
130
         }
3✔
131
         return results;
1✔
132
      }
×
133

134
   private:
135
      std::vector<Test::Result> run_with_restrictions(const Botan::Path_Validation_Restrictions& restrictions) {
2✔
136
         std::vector<Test::Result> results;
2✔
137

138
         // Test certs generated by https://github.com/yymax/x509test
139
         const Botan::X509_Certificate root(Test::data_file("x509/x509test/root.pem"));
4✔
140
         Botan::Certificate_Store_In_Memory trusted;
2✔
141
         trusted.add_certificate(root);
2✔
142

143
         auto validation_time = Botan::calendar_point(2016, 10, 21, 4, 20, 0).to_std_timepoint();
2✔
144

145
         for(const auto& [filename, expected_result] : read_results(Test::data_file("x509/x509test/expected.txt"))) {
76✔
146
            Test::Result result("X509test path validation");
74✔
147
            result.start_timer();
74✔
148

149
            const std::vector<Botan::X509_Certificate> certs =
74✔
150
               load_cert_file(Test::data_file("x509/x509test/" + filename));
148✔
151

152
            if(certs.empty()) {
74✔
153
               throw Test_Error("Failed to read certs from " + filename);
×
154
            }
155

156
            Botan::Path_Validation_Result path_result = Botan::x509_path_validate(
74✔
157
               certs, restrictions, trusted, "www.tls.test", Botan::Usage_Type::TLS_SERVER_AUTH, validation_time);
74✔
158

159
            if(path_result.successful_validation() && path_result.trust_root() != root) {
74✔
160
               path_result = Botan::Path_Validation_Result(Botan::Certificate_Status_Code::CANNOT_ESTABLISH_TRUST);
×
161
            }
162

163
            result.test_str_eq("test " + filename, path_result.result_string(), expected_result);
148✔
164
            result.test_str_eq("test no warnings string", path_result.warnings_string(), "");
74✔
165
            result.test_is_true("test no warnings", path_result.no_warnings());
74✔
166
            result.end_timer();
74✔
167
            results.push_back(result);
74✔
168
         }
74✔
169

170
         // test softfail
171
         {
2✔
172
            Test::Result result("X509test path validation softfail");
2✔
173
            result.start_timer();
2✔
174

175
            // this certificate must not have a OCSP URL
176
            const std::string filename = "ValidAltName.pem";
2✔
177
            const std::vector<Botan::X509_Certificate> certs =
2✔
178
               load_cert_file(Test::data_file("x509/x509test/" + filename));
4✔
179
            if(certs.empty()) {
2✔
180
               throw Test_Error("Failed to read certs from " + filename);
×
181
            }
182

183
            Botan::Path_Validation_Result path_result =
2✔
184
               Botan::x509_path_validate(certs,
2✔
185
                                         restrictions,
186
                                         trusted,
187
                                         "www.tls.test",
188
                                         Botan::Usage_Type::TLS_SERVER_AUTH,
189
                                         validation_time,
190
                                         /* activate check_ocsp_online */ std::chrono::milliseconds(1000),
2✔
191
                                         {});
2✔
192

193
            if(path_result.successful_validation() && path_result.trust_root() != root) {
2✔
194
               path_result = Botan::Path_Validation_Result(Botan::Certificate_Status_Code::CANNOT_ESTABLISH_TRUST);
×
195
            }
196

197
            // certificate verification succeed even if no OCSP URL (softfail)
198
            result.test_is_true("test success", path_result.successful_validation());
2✔
199
            result.test_str_eq("test " + filename, path_result.result_string(), "Verified");
4✔
200
      #if defined(BOTAN_TARGET_OS_HAS_THREADS) && defined(BOTAN_HAS_HTTP_UTIL)
201
            // if softfail, there is warnings
202
            result.test_is_true("test warnings", !path_result.no_warnings());
2✔
203
            result.test_str_eq("test warnings string", path_result.warnings_string(), "[0] OCSP URL not available");
2✔
204
      #endif
205
            result.end_timer();
2✔
206
            results.push_back(result);
2✔
207
         }
2✔
208

209
         return results;
2✔
210
      }
2✔
211
};
212

213
BOTAN_REGISTER_TEST("x509", "x509_path_x509test", X509test_Path_Validation_Tests);
214

215
class NIST_Path_Validation_Tests final : public Test {
1✔
216
   public:
217
      std::vector<Test::Result> run() override;
218

219
   private:
220
      std::vector<Test::Result> run_with_restrictions(const Botan::Path_Validation_Restrictions& restrictions);
221
};
222

223
std::vector<Test::Result> NIST_Path_Validation_Tests::run() {
1✔
224
   std::vector<Test::Result> results;
1✔
225
   for(const auto& restrictions : restrictions_to_test(true, true)) {
3✔
226
      auto partial_res = run_with_restrictions(restrictions);
2✔
227
      results.insert(results.end(), partial_res.begin(), partial_res.end());
2✔
228
   }
3✔
229
   return results;
1✔
230
}
×
231

232
std::vector<Test::Result> NIST_Path_Validation_Tests::run_with_restrictions(
2✔
233
   const Botan::Path_Validation_Restrictions& restrictions) {
234
   if(Botan::has_filesystem_impl() == false) {
2✔
235
      return {Test::Result::Note("NIST path validation", "Skipping due to missing filesystem access")};
×
236
   }
237

238
   std::vector<Test::Result> results;
2✔
239

240
   /**
241
   * Code to run the X.509v3 processing tests described in "Conformance
242
   *  Testing of Relying Party Client Certificate Path Processing Logic",
243
   *  which is available on NIST's web site.
244
   *
245
   * https://csrc.nist.gov/projects/pki-testing/x-509-path-validation-test-suite
246
   *
247
   * Known Failures/Problems:
248
   *  - Policy extensions are not implemented, so we skip tests #34-#53.
249
   *  - Tests #75 and #76 are skipped as they make use of relatively
250
   *    obscure CRL extensions which are not supported.
251
   */
252
   const std::map<std::string, std::string> expected = read_results(Test::data_file("x509/nist/expected.txt"));
4✔
253

254
   const Botan::X509_Certificate root_cert(Test::data_file("x509/nist/root.crt"));
4✔
255
   const Botan::X509_CRL root_crl(Test::data_file("x509/nist/root.crl"));
4✔
256

257
   const auto validation_time = Botan::calendar_point(2018, 4, 1, 9, 30, 33).to_std_timepoint();
2✔
258

259
   for(const auto& [test_name, expected_result] : expected) {
154✔
260
      Test::Result result("NIST path validation");
152✔
261
      result.start_timer();
152✔
262

263
      try {
152✔
264
         const auto all_files = Test::files_in_data_dir("x509/nist/" + test_name);
152✔
265

266
         Botan::Certificate_Store_In_Memory store;
152✔
267
         store.add_certificate(root_cert);
152✔
268
         store.add_crl(root_crl);
152✔
269

270
         std::vector<Botan::X509_Certificate> end_certs = {
152✔
271
            Botan::X509_Certificate(Test::data_file("x509/nist/" + test_name + "/end.crt"))};
760✔
272

273
         for(const auto& file : all_files) {
798✔
274
            if(file.ends_with(".crt") && file != "end.crt") {
646✔
275
               end_certs.emplace_back(file);
400✔
276
            } else if(file.ends_with(".crl")) {
246✔
277
               Botan::DataSource_Stream in(file, true);
246✔
278
               const Botan::X509_CRL crl(in);
246✔
279
               store.add_crl(crl);
246✔
280
            }
246✔
281
         }
282

283
         const Botan::Path_Validation_Result validation_result = Botan::x509_path_validate(
152✔
284
            end_certs, restrictions, store, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
152✔
285

286
         result.test_str_eq(test_name + " path validation result", validation_result.result_string(), expected_result);
304✔
287
      } catch(std::exception& e) {
152✔
288
         result.test_failure(test_name, e.what());
×
289
      }
×
290

291
      result.end_timer();
152✔
292
      results.push_back(result);
152✔
293
   }
152✔
294

295
   return results;
2✔
296
}
154✔
297

298
BOTAN_REGISTER_TEST("x509", "x509_path_nist", NIST_Path_Validation_Tests);
299

300
class Extended_Path_Validation_Tests final : public Test {
1✔
301
   public:
302
      std::vector<Test::Result> run() override;
303
};
304

305
std::vector<Test::Result> Extended_Path_Validation_Tests::run() {
1✔
306
   if(Botan::has_filesystem_impl() == false) {
1✔
307
      return {Test::Result::Note("Extended x509 path validation", "Skipping due to missing filesystem access")};
×
308
   }
309

310
   std::vector<Test::Result> results;
1✔
311

312
   auto validation_time = Botan::calendar_point(2017, 9, 1, 9, 30, 33).to_std_timepoint();
1✔
313

314
   for(const auto& [test_name, expected_result] : read_results(Test::data_file("x509/extended/expected.txt"))) {
4✔
315
      Test::Result result("Extended X509 path validation");
3✔
316
      result.start_timer();
3✔
317

318
      const auto all_files = Test::files_in_data_dir("x509/extended/" + test_name);
3✔
319

320
      Botan::Certificate_Store_In_Memory store;
3✔
321

322
      for(const auto& file : all_files) {
13✔
323
         if(file.ends_with(".crt") && file != "end.crt") {
10✔
324
            store.add_certificate(Botan::X509_Certificate(file));
10✔
325
         }
326
      }
327

328
      const Botan::X509_Certificate end_user(Test::data_file("x509/extended/" + test_name + "/end.crt"));
9✔
329

330
      const Botan::Path_Validation_Restrictions restrictions;
3✔
331
      const Botan::Path_Validation_Result validation_result =
3✔
332
         Botan::x509_path_validate(end_user, restrictions, store, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
3✔
333

334
      result.test_str_eq(test_name + " path validation result", validation_result.result_string(), expected_result);
6✔
335

336
      result.end_timer();
3✔
337
      results.push_back(result);
3✔
338
   }
3✔
339

340
   return results;
1✔
341
}
1✔
342

343
BOTAN_REGISTER_TEST("x509", "x509_path_extended", Extended_Path_Validation_Tests);
344

345
      #if defined(BOTAN_HAS_PSS)
346

347
class PSS_Path_Validation_Tests : public Test {
1✔
348
   public:
349
      std::vector<Test::Result> run() override;
350
};
351

352
std::vector<Test::Result> PSS_Path_Validation_Tests::run() {
1✔
353
   if(Botan::has_filesystem_impl() == false) {
1✔
354
      return {Test::Result::Note("RSA-PSS X509 signature validation", "Skipping due to missing filesystem access")};
×
355
   }
356

357
   std::vector<Test::Result> results;
1✔
358

359
   const auto validation_times = read_results(Test::data_file("x509/pss_certs/validation_times.txt"));
2✔
360

361
   auto validation_times_iter = validation_times.begin();
1✔
362

363
   for(const auto& [test_name, expected_result] : read_results(Test::data_file("x509/pss_certs/expected.txt"))) {
119✔
364
      Test::Result result("RSA-PSS X509 signature validation");
118✔
365
      result.start_timer();
118✔
366

367
      const auto all_files = Test::files_in_data_dir("x509/pss_certs/" + test_name);
118✔
368

369
      std::optional<Botan::X509_CRL> crl;
118✔
370
      std::optional<Botan::X509_Certificate> end;
118✔
371
      std::optional<Botan::X509_Certificate> root;
118✔
372
      Botan::Certificate_Store_In_Memory store;
118✔
373
      std::optional<Botan::PKCS10_Request> csr;
118✔
374

375
      const auto validation_year = Botan::to_u32bit((validation_times_iter++)->second);
118✔
376

377
      const auto validation_time = Botan::calendar_point(validation_year, 1, 1, 0, 0, 0).to_std_timepoint();
118✔
378

379
      for(const auto& file : all_files) {
345✔
380
         if(file.find("end.crt") != std::string::npos) {
227✔
381
            end = Botan::X509_Certificate(file);
113✔
382
         } else if(file.find("root.crt") != std::string::npos) {
114✔
383
            root = Botan::X509_Certificate(file);
97✔
384
            store.add_certificate(*root);
97✔
385
         } else if(file.ends_with(".crl")) {
17✔
386
            crl = Botan::X509_CRL(file);
6✔
387
         } else if(file.ends_with(".csr")) {
11✔
388
            csr = Botan::PKCS10_Request(file);
5✔
389
         }
390
      }
391

392
      if(end && crl && root) {
118✔
393
         // CRL test
394
         const std::vector<Botan::X509_Certificate> cert_path = {*end, *root};
18✔
395
         const std::vector<std::optional<Botan::X509_CRL>> crls = {crl};
12✔
396
         auto crl_status = Botan::PKIX::check_crl(
6✔
397
            cert_path,
398
            crls,
399
            validation_time);  // alternatively we could just call crl.check_signature( root_pubkey )
6✔
400

401
         result.test_str_eq(test_name + " check_crl result",
6✔
402
                            Botan::Path_Validation_Result::status_string(Botan::PKIX::overall_status(crl_status)),
403
                            expected_result);
404
      } else if(end && root) {
118✔
405
         // CRT chain test
406

407
         const Botan::Path_Validation_Restrictions restrictions(false, 80);  // SHA-1 is used
91✔
408

409
         const Botan::Path_Validation_Result validation_result =
91✔
410
            Botan::x509_path_validate(*end, restrictions, store, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
91✔
411

412
         result.test_str_eq(test_name + " path validation result", validation_result.result_string(), expected_result);
182✔
413
      } else if(end && !root) {
112✔
414
         // CRT self signed test
415
         auto pubkey = end->subject_public_key();
16✔
416
         const bool accept = expected_result == "Verified";
16✔
417
         result.test_bool_eq(test_name + " verify signature", end->check_signature(*pubkey), accept);
32✔
418
      } else if(csr) {
21✔
419
         // PKCS#10 Request test
420
         auto pubkey = csr->subject_public_key();
5✔
421
         const bool accept = expected_result == "Verified";
5✔
422
         result.test_bool_eq(test_name + " verify signature", csr->check_signature(*pubkey), accept);
10✔
423
      }
5✔
424

425
      result.end_timer();
118✔
426
      results.push_back(result);
118✔
427
   }
334✔
428

429
   return results;
1✔
430
}
13✔
431

432
BOTAN_REGISTER_TEST("x509", "x509_path_rsa_pss", PSS_Path_Validation_Tests);
433

434
      #endif
435

436
class Validate_V1Cert_Test final : public Test {
1✔
437
   public:
438
      std::vector<Test::Result> run() override;
439
};
440

441
std::vector<Test::Result> Validate_V1Cert_Test::run() {
1✔
442
   if(Botan::has_filesystem_impl() == false) {
1✔
443
      return {Test::Result::Note("BSI path validation", "Skipping due to missing filesystem access")};
×
444
   }
445

446
   const std::vector<Test::Result> results;
1✔
447

448
   const std::string root_crt = Test::data_file("x509/misc/v1ca/root.pem");
1✔
449
   const std::string int_crt = Test::data_file("x509/misc/v1ca/int.pem");
1✔
450
   const std::string ee_crt = Test::data_file("x509/misc/v1ca/ee.pem");
1✔
451

452
   auto validation_time = Botan::calendar_point(2019, 4, 19, 23, 0, 0).to_std_timepoint();
1✔
453

454
   const Botan::X509_Certificate root(root_crt);
1✔
455
   const Botan::X509_Certificate intermediate(int_crt);
1✔
456
   const Botan::X509_Certificate ee_cert(ee_crt);
1✔
457

458
   Botan::Certificate_Store_In_Memory trusted;
1✔
459
   trusted.add_certificate(root);
1✔
460

461
   const std::vector<Botan::X509_Certificate> chain = {ee_cert, intermediate};
3✔
462

463
   const Botan::Path_Validation_Restrictions restrictions;
1✔
464
   const Botan::Path_Validation_Result validation_result =
1✔
465
      Botan::x509_path_validate(chain, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
1✔
466

467
   Test::Result result("Verifying using v1 certificate");
1✔
468
   result.test_str_eq("Path validation result", validation_result.result_string(), "Verified");
1✔
469

470
   const Botan::Certificate_Store_In_Memory empty;
1✔
471

472
   const std::vector<Botan::X509_Certificate> new_chain = {ee_cert, intermediate, root};
4✔
473

474
   const Botan::Path_Validation_Result validation_result2 =
1✔
475
      Botan::x509_path_validate(new_chain, restrictions, empty, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
1✔
476

477
   result.test_str_eq("Path validation result", validation_result2.result_string(), "Cannot establish trust");
1✔
478

479
   return {result};
2✔
480
}
4✔
481

482
BOTAN_REGISTER_TEST("x509", "x509_v1_ca", Validate_V1Cert_Test);
483

484
class Validate_V2Uid_in_V1_Test final : public Test {
1✔
485
   public:
486
      std::vector<Test::Result> run() override;
487
};
488

489
std::vector<Test::Result> Validate_V2Uid_in_V1_Test::run() {
1✔
490
   if(Botan::has_filesystem_impl() == false) {
1✔
491
      return {Test::Result::Note("Path validation", "Skipping due to missing filesystem access")};
×
492
   }
493

494
   const std::vector<Test::Result> results;
1✔
495

496
   const std::string root_crt = Test::data_file("x509/v2-in-v1/root.pem");
1✔
497
   const std::string int_crt = Test::data_file("x509/v2-in-v1/int.pem");
1✔
498
   const std::string ee_crt = Test::data_file("x509/v2-in-v1/leaf.pem");
1✔
499

500
   auto validation_time = Botan::calendar_point(2020, 1, 1, 1, 0, 0).to_std_timepoint();
1✔
501

502
   const Botan::X509_Certificate root(root_crt);
1✔
503
   const Botan::X509_Certificate intermediate(int_crt);
1✔
504
   const Botan::X509_Certificate ee_cert(ee_crt);
1✔
505

506
   Botan::Certificate_Store_In_Memory trusted;
1✔
507
   trusted.add_certificate(root);
1✔
508

509
   const std::vector<Botan::X509_Certificate> chain = {ee_cert, intermediate};
3✔
510

511
   const Botan::Path_Validation_Restrictions restrictions;
1✔
512
   const Botan::Path_Validation_Result validation_result =
1✔
513
      Botan::x509_path_validate(chain, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
1✔
514

515
   Test::Result result("Verifying v1 certificate using v2 uid fields");
1✔
516
   result.test_is_false("Path validation failed", validation_result.successful_validation());
1✔
517
   result.test_str_eq(
1✔
518
      "Path validation result", validation_result.result_string(), "Encountered v2 identifiers in v1 certificate");
1✔
519

520
   return {result};
2✔
521
}
3✔
522

523
BOTAN_REGISTER_TEST("x509", "x509_v2uid_in_v1", Validate_V2Uid_in_V1_Test);
524

525
class Validate_NoRevAvail_Test final : public Test {
1✔
526
   public:
527
      std::vector<Test::Result> run() override;
528
};
529

530
std::vector<Test::Result> Validate_NoRevAvail_Test::run() {
1✔
531
   if(Botan::has_filesystem_impl() == false) {
1✔
532
      return {Test::Result::Note("Path validation", "Skipping due to missing filesystem access")};
×
533
   }
534

535
   const std::string root_crt = Test::data_file("x509/short_lived/root.pem");
1✔
536
   const std::string int_crt = Test::data_file("x509/short_lived/int.pem");
1✔
537
   const std::string ee_crt = Test::data_file("x509/short_lived/leaf.pem");
1✔
538

539
   // Leaf is valid Mar 23 12:02:41 - Mar 24 12:02:40 2026 UTC
540
   auto validation_time = Botan::calendar_point(2026, 3, 23, 13, 0, 0).to_std_timepoint();
1✔
541

542
   const Botan::X509_Certificate root(root_crt);
1✔
543
   const Botan::X509_Certificate intermediate(int_crt);
1✔
544
   const Botan::X509_Certificate ee_cert(ee_crt);
1✔
545

546
   Botan::Certificate_Store_In_Memory trusted;
1✔
547
   trusted.add_certificate(root);
1✔
548

549
   const std::vector<Botan::X509_Certificate> chain = {ee_cert, intermediate};
3✔
550

551
   Test::Result result("RFC 9608 noRevAvail extension");
1✔
552

553
   const auto* nra = ee_cert.v3_extensions().get_extension_object_as<Botan::Cert_Extension::NoRevocationAvailable>();
2✔
554
   result.test_not_null("leaf cert has noRevAvail extension", nra);
1✔
555
   result.test_is_true(
3✔
556
      "noRevAvail is not critical",
557
      !ee_cert.v3_extensions().critical_extension_set(Botan::Cert_Extension::NoRevocationAvailable::static_oid()));
2✔
558

559
   const Botan::Path_Validation_Restrictions default_restrictions;
1✔
560
   const Botan::Path_Validation_Result default_result = Botan::x509_path_validate(
1✔
561
      chain, default_restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
1✔
562

563
   result.test_is_true("Default path validation succeeds", default_result.successful_validation());
1✔
564

565
   // require_revocation_information=true: still passes because noRevAvail says to skip the check
566
   const Botan::Path_Validation_Restrictions require_rev_restrictions(true);
1✔
567
   const Botan::Path_Validation_Result require_rev_result = Botan::x509_path_validate(
1✔
568
      chain, require_rev_restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
1✔
569

570
   result.test_is_true("Path validation skips revocation when noRevAvail present",
1✔
571
                       require_rev_result.successful_validation());
1✔
572
   result.test_str_eq("Path validation result", require_rev_result.result_string(), "Verified");
1✔
573

574
   return {result};
2✔
575
}
3✔
576

577
BOTAN_REGISTER_TEST("x509", "x509_no_rev_avail", Validate_NoRevAvail_Test);
578

579
class Cross_Signed_Mesh_Path_Test final : public Test {
1✔
580
   public:
581
      std::vector<Test::Result> run() override;
582
};
583

584
std::vector<Test::Result> Cross_Signed_Mesh_Path_Test::run() {
1✔
585
   if(Botan::has_filesystem_impl() == false) {
1✔
586
      return {Test::Result::Note("Path validation", "Skipping due to missing filesystem access")};
×
587
   }
588

589
   Test::Result result("X509 cross-signed mesh path building");
1✔
590

591
   /*
592
   * Roots A, B, C are mutually cross-signed (six cross certificates), so
593
   * many valid paths exist from the leaf to the trusted self-signed Root A.
594
   * Since the trusted root issued the intermediate directly, path building
595
   * should return the minimal path leaf -> A1 -> Root A rather than one
596
   * winding through the cross-signatures.
597
   */
598
   const Botan::X509_Certificate root(Test::data_file("x509/cross_signed_mesh/root_a.pem"));
2✔
599

600
   std::vector<Botan::X509_Certificate> chain;
1✔
601
   chain.push_back(Botan::X509_Certificate(Test::data_file("x509/cross_signed_mesh/leaf.pem")));
2✔
602
   chain.push_back(Botan::X509_Certificate(Test::data_file("x509/cross_signed_mesh/int_a1.pem")));
2✔
603
   for(const std::string cross : {"a_by_b", "a_by_c", "b_by_a", "b_by_c", "c_by_a", "c_by_b"}) {
7✔
604
      chain.push_back(Botan::X509_Certificate(Test::data_file("x509/cross_signed_mesh/cross_" + cross + ".pem")));
18✔
605
   }
6✔
606

607
   auto validation_time = Botan::calendar_point(2030, 1, 1, 0, 0, 0).to_std_timepoint();
1✔
608

609
   Botan::Certificate_Store_In_Memory trusted;
1✔
610
   trusted.add_certificate(root);
1✔
611

612
   const Botan::Path_Validation_Restrictions restrictions;
1✔
613
   const Botan::Path_Validation_Result path_result = Botan::x509_path_validate(
1✔
614
      chain, restrictions, trusted, "mesh.example.com", Botan::Usage_Type::UNSPECIFIED, validation_time);
1✔
615

616
   result.test_is_true("Cross-signed mesh validates", path_result.successful_validation());
1✔
617

618
   if(path_result.successful_validation()) {
1✔
619
      result.test_sz_eq("Validated path is minimal", path_result.cert_path().size(), 3);
1✔
620
      result.test_is_true("Path ends at the trust anchor", path_result.trust_root() == root);
1✔
621
   }
622

623
   return {result};
2✔
624
}
2✔
625

626
BOTAN_REGISTER_TEST("x509", "x509_path_cross_signed_mesh", Cross_Signed_Mesh_Path_Test);
627

628
class Future_Dates_Path_Test final : public Test {
1✔
629
   public:
630
      std::vector<Test::Result> run() override;
631
};
632

633
std::vector<Test::Result> Future_Dates_Path_Test::run() {
1✔
634
   if(Botan::has_filesystem_impl() == false) {
1✔
635
      return {Test::Result::Note("Path validation", "Skipping due to missing filesystem access")};
×
636
   }
637

638
   Test::Result result("X509 far future date handling");
1✔
639

640
   /*
641
   * The issuing CA uses the RFC 5280 99991231235959Z notAfter sentinel and the
642
   * leaf is valid only during the year 2981, exercising decoding of GeneralizedTime
643
   * values at the top of the representable range and well past 2050.
644
   *
645
   * The leaf potentially can't be validated, if std::chrono::system_clock::time_point
646
   * cannot represent year 2981, which is the case for libstdc++
647
   */
648
   const Botan::X509_Certificate ca(Test::data_file("x509/future_dates/ca.pem"));
2✔
649
   const Botan::X509_Certificate leaf(Test::data_file("x509/future_dates/leaf.pem"));
2✔
650

651
   result.test_str_eq("CA notAfter is the 9999 sentinel", ca.not_after().to_string(), "99991231235959Z");
1✔
652
   result.test_str_eq("CA notAfter readable form", ca.not_after().readable_string(), "9999/12/31 23:59:59 UTC");
1✔
653
   result.test_str_eq("Leaf notBefore is in 2981", leaf.not_before().to_string(), "29810101000000Z");
1✔
654
   result.test_str_eq("Leaf notAfter is in 2981", leaf.not_after().to_string(), "29811231235959Z");
1✔
655

656
   // The chain is structurally sound: the leaf signature verifies under the CA key
657
   const auto ca_key = ca.subject_public_key();
1✔
658
   result.test_is_true("Leaf is signed by the CA", leaf.check_signature(*ca_key));
1✔
659
   result.test_is_true("CA is self-signed", ca.is_self_signed());
1✔
660

661
   Botan::Certificate_Store_In_Memory trusted;
1✔
662
   trusted.add_certificate(ca);
1✔
663

664
   const std::vector<Botan::X509_Certificate> chain = {leaf};
2✔
665

666
   // At a present-day reference time the CA (notAfter 9999) is current, so path
667
   // validation fails specifically because the leaf is not yet valid, confirming
668
   // the sentinel notAfter is decoded and compared as a valid future date.
669
   const auto validation_time = Botan::calendar_point(2026, 1, 1, 0, 0, 0).to_std_timepoint();
1✔
670

671
   const Botan::Path_Validation_Restrictions restrictions;
1✔
672
   const Botan::Path_Validation_Result path_result = Botan::x509_path_validate(
1✔
673
      chain, restrictions, trusted, "future.example.com", Botan::Usage_Type::UNSPECIFIED, validation_time);
1✔
674

675
   result.test_is_true("Leaf is not yet valid at present", !path_result.successful_validation());
1✔
676
   result.test_str_eq(
1✔
677
      "Validation fails on the leaf, not the 9999 CA", path_result.result_string(), "Certificate is not yet valid");
1✔
678

679
   return {result};
2✔
680
}
4✔
681

682
BOTAN_REGISTER_TEST("x509", "x509_path_future_dates", Future_Dates_Path_Test);
683

684
class Validate_Invalid_OCSP_NoCheck_Test final : public Test {
1✔
685
   public:
686
      std::vector<Test::Result> run() override;
687
};
688

689
std::vector<Test::Result> Validate_Invalid_OCSP_NoCheck_Test::run() {
1✔
690
   if(Botan::has_filesystem_impl() == false) {
1✔
691
      return {Test::Result::Note("Path validation", "Skipping due to missing filesystem access")};
×
692
   }
693

694
   Test::Result result("Invalid use of OCSP nocheck extension");
1✔
695

696
   const std::string root_crt = Test::data_file("x509/invalid_ocsp_nocheck/root.pem");
1✔
697
   const std::string ee_crt = Test::data_file("x509/invalid_ocsp_nocheck/leaf.pem");
1✔
698

699
   auto validation_time = Botan::calendar_point(2026, 5, 19, 4, 58, 33).to_std_timepoint();
1✔
700

701
   const Botan::X509_Certificate root(root_crt);
1✔
702
   const Botan::X509_Certificate ee_cert(ee_crt);
1✔
703

704
   Botan::Certificate_Store_In_Memory trusted;
1✔
705
   trusted.add_certificate(root);
1✔
706

707
   const std::vector<Botan::X509_Certificate> chain = {ee_cert};
2✔
708

709
   const auto ocsp_nocheck_oid = Botan::OID::from_name("PKIX.OCSP.NoCheck").value();
2✔
710
   result.test_is_true("leaf has OCSP nocheck extension", ee_cert.v3_extensions().extension_set(ocsp_nocheck_oid));
1✔
711
   result.test_is_false("leaf is not an OCSP responder", ee_cert.allowed_usage(Botan::Usage_Type::OCSP_RESPONDER));
1✔
712

713
   const Botan::Path_Validation_Restrictions restrictions;
1✔
714
   const Botan::Path_Validation_Result validation_result =
1✔
715
      Botan::x509_path_validate(chain, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
1✔
716

717
   result.test_is_false("Path validation failed", validation_result.successful_validation());
1✔
718
   result.test_enum_eq("Result code is INVALID_OCSP_NOCHECK",
2✔
719
                       validation_result.result(),
1✔
720
                       Botan::Certificate_Status_Code::INVALID_OCSP_NOCHECK);
1✔
721
   result.test_str_eq(
1✔
722
      "Path validation result string", validation_result.result_string(), "Invalid use of OCSP NoCheck extension");
1✔
723

724
   return {result};
2✔
725
}
3✔
726

727
BOTAN_REGISTER_TEST("x509", "x509_invalid_ocsp_nocheck", Validate_Invalid_OCSP_NoCheck_Test);
728

729
class Validate_Name_Constraint_CaseInsensitive final : public Test {
1✔
730
   public:
731
      std::vector<Test::Result> run() override;
732
};
733

734
std::vector<Test::Result> Validate_Name_Constraint_CaseInsensitive::run() {
1✔
735
   if(Botan::has_filesystem_impl() == false) {
1✔
736
      return {Test::Result::Note("Path validation", "Skipping due to missing filesystem access")};
×
737
   }
738

739
   const std::vector<Test::Result> results;
1✔
740

741
   const std::string root_crt = Test::data_file("x509/misc/name_constraint_ci/root.pem");
1✔
742
   const std::string int_crt = Test::data_file("x509/misc/name_constraint_ci/int.pem");
1✔
743
   const std::string ee_crt = Test::data_file("x509/misc/name_constraint_ci/leaf.pem");
1✔
744

745
   auto validation_time = Botan::calendar_point(2021, 5, 8, 1, 0, 0).to_std_timepoint();
1✔
746

747
   const Botan::X509_Certificate root(root_crt);
1✔
748
   const Botan::X509_Certificate intermediate(int_crt);
1✔
749
   const Botan::X509_Certificate ee_cert(ee_crt);
1✔
750

751
   Botan::Certificate_Store_In_Memory trusted;
1✔
752
   trusted.add_certificate(root);
1✔
753

754
   const std::vector<Botan::X509_Certificate> chain = {ee_cert, intermediate};
3✔
755

756
   const Botan::Path_Validation_Restrictions restrictions;
1✔
757
   const Botan::Path_Validation_Result validation_result =
1✔
758
      Botan::x509_path_validate(chain, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
1✔
759

760
   Test::Result result("DNS name constraints are case insensitive");
1✔
761
   result.test_is_true("Path validation succeeded", validation_result.successful_validation());
1✔
762

763
   return {result};
2✔
764
}
3✔
765

766
BOTAN_REGISTER_TEST("x509", "x509_name_constraint_ci", Validate_Name_Constraint_CaseInsensitive);
767

768
class Validate_Name_Constraint_NoCheckSelf final : public Test {
1✔
769
   public:
770
      std::vector<Test::Result> run() override;
771
};
772

773
std::vector<Test::Result> Validate_Name_Constraint_NoCheckSelf::run() {
1✔
774
   if(Botan::has_filesystem_impl() == false) {
1✔
775
      return {Test::Result::Note("Path validation", "Skipping due to missing filesystem access")};
×
776
   }
777

778
   const std::vector<Test::Result> results;
1✔
779

780
   const std::string root_crt = Test::data_file("x509/misc/nc_skip_self/root.pem");
1✔
781
   const std::string int_crt = Test::data_file("x509/misc/nc_skip_self/int.pem");
1✔
782
   const std::string ee_crt = Test::data_file("x509/misc/nc_skip_self/leaf.pem");
1✔
783

784
   auto validation_time = Botan::calendar_point(2021, 5, 8, 1, 0, 0).to_std_timepoint();
1✔
785

786
   const Botan::X509_Certificate root(root_crt);
1✔
787
   const Botan::X509_Certificate intermediate(int_crt);
1✔
788
   const Botan::X509_Certificate ee_cert(ee_crt);
1✔
789

790
   Botan::Certificate_Store_In_Memory trusted;
1✔
791
   trusted.add_certificate(root);
1✔
792

793
   const std::vector<Botan::X509_Certificate> chain = {ee_cert, intermediate};
3✔
794

795
   const Botan::Path_Validation_Restrictions restrictions;
1✔
796
   const Botan::Path_Validation_Result validation_result =
1✔
797
      Botan::x509_path_validate(chain, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
1✔
798

799
   Test::Result result("Name constraints do not apply to the certificate which includes them");
1✔
800
   result.test_is_true("Path validation succeeded", validation_result.successful_validation());
1✔
801

802
   return {result};
2✔
803
}
3✔
804

805
BOTAN_REGISTER_TEST("x509", "x509_name_constraint_no_check_self", Validate_Name_Constraint_NoCheckSelf);
806

807
class Root_Cert_Time_Check_Test final : public Test {
1✔
808
   public:
809
      std::vector<Test::Result> run() override {
1✔
810
         if(Botan::has_filesystem_impl() == false) {
1✔
811
            return {Test::Result::Note("Path validation", "Skipping due to missing filesystem access")};
×
812
         }
813

814
         const std::string trusted_root_crt = Test::data_file("x509/misc/root_cert_time_check/root.crt");
1✔
815
         const std::string leaf_crt = Test::data_file("x509/misc/root_cert_time_check/leaf.crt");
1✔
816

817
         const Botan::X509_Certificate trusted_root_cert(trusted_root_crt);
1✔
818
         const Botan::X509_Certificate leaf_cert(leaf_crt);
1✔
819

820
         Botan::Certificate_Store_In_Memory trusted;
1✔
821
         trusted.add_certificate(trusted_root_cert);
1✔
822

823
         const std::vector<Botan::X509_Certificate> chain = {leaf_cert};
2✔
824

825
         Test::Result result("Root cert time check");
1✔
826

827
         auto assert_path_validation_result = [&](std::string_view descr,
11✔
828
                                                  bool ignore_trusted_root_time_range,
829
                                                  uint32_t year,
830
                                                  Botan::Certificate_Status_Code exp_status,
831
                                                  std::optional<Botan::Certificate_Status_Code> exp_warning =
832
                                                     std::nullopt) {
833
            const Botan::Path_Validation_Restrictions restrictions(
10✔
834
               false,
835
               110,
836
               false,
837
               std::chrono::seconds::zero(),
838
               std::make_unique<Botan::Certificate_Store_In_Memory>(),
20✔
839
               ignore_trusted_root_time_range);
20✔
840

841
            const Botan::Path_Validation_Result validation_result =
10✔
842
               Botan::x509_path_validate(chain,
10✔
843
                                         restrictions,
844
                                         trusted,
10✔
845
                                         "",
846
                                         Botan::Usage_Type::UNSPECIFIED,
847
                                         Botan::calendar_point(year, 1, 1, 1, 0, 0).to_std_timepoint());
10✔
848
            const std::string descr_str = Botan::fmt(
10✔
849
               "Root cert validity range {}: {}", ignore_trusted_root_time_range ? "ignored" : "checked", descr);
15✔
850

851
            result.test_enum_eq(descr_str, validation_result.result(), exp_status);
10✔
852
            const auto warnings = validation_result.warnings();
10✔
853
            BOTAN_ASSERT_NOMSG(warnings.size() == 2);
10✔
854
            result.test_is_true("No warning for leaf cert", warnings.at(0).empty());
10✔
855
            if(exp_warning) {
10✔
856
               result.test_is_true("Warning for root cert",
8✔
857
                                   warnings.at(1).size() == 1 && warnings.at(1).contains(*exp_warning));
8✔
858
            } else {
859
               result.test_is_true("No warning for root cert", warnings.at(1).empty());
6✔
860
            }
861
         };
10✔
862
         // (Trusted) root cert validity range: 2022-2028
863
         // Leaf cert validity range: 2020-2030
864

865
         // Trusted root time range is checked
866
         assert_path_validation_result(
1✔
867
            "Root and leaf certs in validity range", false, 2025, Botan::Certificate_Status_Code::OK);
868
         assert_path_validation_result(
1✔
869
            "Root and leaf certs are expired", false, 2031, Botan::Certificate_Status_Code::CERT_HAS_EXPIRED);
870
         assert_path_validation_result(
1✔
871
            "Root and leaf certs are not yet valid", false, 2019, Botan::Certificate_Status_Code::CERT_NOT_YET_VALID);
872
         assert_path_validation_result(
1✔
873
            "Root cert is expired, leaf cert not", false, 2029, Botan::Certificate_Status_Code::CERT_HAS_EXPIRED);
874
         assert_path_validation_result("Root cert is not yet valid, leaf cert is",
1✔
875
                                       false,
876
                                       2021,
877
                                       Botan::Certificate_Status_Code::CERT_NOT_YET_VALID);
878

879
         // Trusted root time range is ignored
880
         assert_path_validation_result(
1✔
881
            "Root and leaf certs in validity range", true, 2025, Botan::Certificate_Status_Code::OK);
882
         assert_path_validation_result("Root and leaf certs are expired",
2✔
883
                                       true,
884
                                       2031,
885
                                       Botan::Certificate_Status_Code::CERT_HAS_EXPIRED,
886
                                       Botan::Certificate_Status_Code::TRUSTED_CERT_HAS_EXPIRED);
1✔
887
         assert_path_validation_result("Root and leaf certs are not yet valid",
2✔
888
                                       true,
889
                                       2019,
890
                                       Botan::Certificate_Status_Code::CERT_NOT_YET_VALID,
891
                                       Botan::Certificate_Status_Code::TRUSTED_CERT_NOT_YET_VALID);
1✔
892
         assert_path_validation_result("Root cert is expired, leaf cert not",
2✔
893
                                       true,
894
                                       2029,
895
                                       Botan::Certificate_Status_Code::OK,
896
                                       Botan::Certificate_Status_Code::TRUSTED_CERT_HAS_EXPIRED);
1✔
897
         assert_path_validation_result("Root cert is not yet valid, leaf cert is",
2✔
898
                                       true,
899
                                       2021,
900
                                       Botan::Certificate_Status_Code::OK,
901
                                       Botan::Certificate_Status_Code::TRUSTED_CERT_NOT_YET_VALID);
1✔
902

903
         return {result};
2✔
904
      }
3✔
905
};
906

907
BOTAN_REGISTER_TEST("x509", "x509_root_cert_time_check", Root_Cert_Time_Check_Test);
908

909
class Non_Self_Signed_Trust_Anchors_Test final : public Test {
1✔
910
   private:
911
      using Cert_Path = std::vector<Botan::X509_Certificate>;
912

913
      std::vector<Botan::X509_Certificate> get_valid_cert_chain() {
4✔
914
         // Test certs generated by https://github.com/yymax/x509test
915
         const std::string valid_chain_pem = "x509/x509test/ValidChained.pem";
4✔
916
         auto certs = load_cert_file(Test::data_file(valid_chain_pem));
4✔
917
         if(certs.size() != 5) {
4✔
918
            throw Test_Error(Botan::fmt("Failed to read all certs from {}", valid_chain_pem));
×
919
         }
920
         return certs;
4✔
921
      }
4✔
922

923
      /// Time point fits for all certificates used in this class
924
      std::chrono::system_clock::time_point get_validation_time() {
8✔
925
         return Botan::calendar_point(2016, 10, 21, 4, 20, 0).to_std_timepoint();
8✔
926
      }
927

928
      Botan::X509_Certificate get_self_signed_ee_cert() {
1✔
929
         const std::string valid_chain_pem = "x509/misc/self-signed-end-entity.pem";
1✔
930
         return Botan::X509_Certificate(Test::data_file(valid_chain_pem));
2✔
931
      }
1✔
932

933
      std::vector<Test::Result> path_validate_test() {
1✔
934
         std::vector<Test::Result> results;
1✔
935

936
         const auto restrictions = get_allow_non_self_signed_anchors_restrictions(false, false);
1✔
937
         const auto certs = get_valid_cert_chain();
1✔
938
         const auto validation_time = get_validation_time();
1✔
939

940
         const std::vector<std::tuple<std::string_view, Cert_Path, Botan::X509_Certificate>>
1✔
941
            info_w_end_certs_w_trust_anchor{
942
               {"length 1", Cert_Path{certs.at(0)}, certs.at(0)},
4✔
943
               {"length 2", Cert_Path{certs.at(0), certs.at(1)}, certs.at(1)},
5✔
944
               {"length 3 (store not in chain)", Cert_Path{certs.at(0), certs.at(1)}, certs.at(2)},
5✔
945
               {"length 4 (shuffled)", Cert_Path{certs.at(0), certs.at(3), certs.at(2), certs.at(1)}, certs.at(3)},
7✔
946
               {"full", Cert_Path{certs.at(0), certs.at(1), certs.at(2), certs.at(3), certs.at(4)}, certs.at(4)},
8✔
947
            };
6✔
948

949
         for(const auto& [info, end_certs, trust_anchor] : info_w_end_certs_w_trust_anchor) {
6✔
950
            Test::Result result(
5✔
951
               Botan::fmt("Non-self-signed trust anchor without require_self_signed_trust_anchors ({})", info));
5✔
952

953
            const Botan::Certificate_Store_In_Memory trusted(trust_anchor);
5✔
954

955
            auto path_result = Botan::x509_path_validate(
5✔
956
               end_certs, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
5✔
957

958
            if(path_result.successful_validation() && path_result.trust_root() != trust_anchor) {
5✔
959
               path_result = Botan::Path_Validation_Result(Botan::Certificate_Status_Code::CANNOT_ESTABLISH_TRUST);
×
960
            }
961

962
            result.test_str_eq(
5✔
963
               "path validation failed", path_result.result_string(), to_string(Botan::Certificate_Status_Code::OK));
5✔
964

965
            results.push_back(result);
5✔
966
         }
5✔
967
         return results;
1✔
968
      }
7✔
969

970
      std::vector<Test::Result> build_path_test() {
1✔
971
         std::vector<Test::Result> results;
1✔
972

973
         const auto restrictions = get_allow_non_self_signed_anchors_restrictions(false, false);
1✔
974
         const auto certs = get_valid_cert_chain();
1✔
975

976
         // Helper to create a cert path {certs[0],...,certs[last_cert]}
977
         const auto path_to = [&](size_t last_cert) -> Cert_Path {
20✔
978
            BOTAN_ASSERT_NOMSG(last_cert <= certs.size());
19✔
979
            return {certs.begin(), certs.begin() + last_cert + 1};
19✔
980
         };
1✔
981

982
         // Helper to create a cert store of all certificates in certs given by their indices
983
         const auto store_of = [&](auto... cert_indices) -> Botan::Certificate_Store_In_Memory {
6✔
984
            Botan::Certificate_Store_In_Memory cert_store;
5✔
985
            (cert_store.add_certificate(certs.at(cert_indices)), ...);
5✔
986
            return cert_store;
5✔
987
         };
1✔
988

989
         const std::vector<std::tuple<std::string, Botan::Certificate_Store_In_Memory, std::vector<Cert_Path>>>
1✔
990
            test_names_with_stores_with_expected_paths{
991
               {"root in store", store_of(4), std::vector<Cert_Path>{path_to(4)}},
4✔
992
               {"intermediate in store", store_of(2), std::vector<Cert_Path>{path_to(2)}},
3✔
993
               {"leaf in store", store_of(0), std::vector<Cert_Path>{path_to(0)}},
3✔
994
               {"leaf, intermediate, and root in store",
995
                store_of(0, 1, 4),
1✔
996
                std::vector<Cert_Path>{path_to(0), path_to(1), path_to(4)}},
5✔
997
               {"all in store",
998
                store_of(0, 1, 2, 3, 4),
1✔
999
                std::vector<Cert_Path>{path_to(0), path_to(1), path_to(2), path_to(3), path_to(4)}},
7✔
1000
            };
6✔
1001

1002
         for(auto [test_name, cert_store, expected_paths] : test_names_with_stores_with_expected_paths) {
6✔
1003
            Test::Result result(Botan::fmt("Build certificate paths ({})", test_name));
5✔
1004

1005
            std::vector<Cert_Path> cert_paths;
5✔
1006
            const auto build_all_res =
5✔
1007
               Botan::PKIX::build_all_certificate_paths(cert_paths, {&cert_store}, certs.at(0), certs);
5✔
1008
            result.test_str_eq("build_all_certificate_paths result",
5✔
1009
                               to_string(build_all_res),
1010
                               to_string(Botan::Certificate_Status_Code::OK));
1011
            test_arb_eq(result, "build_all_certificate_paths paths", cert_paths, expected_paths);
5✔
1012

1013
            // Capped enumeration: asking for fewer than the available paths returns
1014
            // EXCEEDED_SEARCH_LIMITS with exactly that many paths.
1015
            if(expected_paths.size() > 1) {
5✔
1016
               std::vector<Cert_Path> capped_paths;
2✔
1017
               const auto capped_res = Botan::PKIX::build_all_certificate_paths(
4✔
1018
                  capped_paths, {&cert_store}, certs.at(0), certs, expected_paths.size() - 1);
4✔
1019
               result.test_enum_eq("build_all_certificate_paths capped result",
4✔
1020
                                   capped_res,
1021
                                   Botan::Certificate_Status_Code::EXCEEDED_SEARCH_LIMITS);
2✔
1022
               result.test_sz_eq(
2✔
1023
                  "build_all_certificate_paths capped count", capped_paths.size(), expected_paths.size() - 1);
2✔
1024
            }
2✔
1025

1026
            Cert_Path cert_path;
5✔
1027
            const auto build_path_res =
5✔
1028
               Botan::PKIX::build_certificate_path(cert_path, {&cert_store}, certs.at(0), certs);
5✔
1029
            result.test_str_eq("build_certificate_path result",
5✔
1030
                               to_string(build_path_res),
1031
                               to_string(Botan::Certificate_Status_Code::OK));
1032

1033
            if(std::ranges::find(cert_paths, path_to(4)) != cert_paths.end()) {
10✔
1034
               test_arb_eq(result, "build_certificate_path (with self-signed anchor)", cert_path, path_to(4));
3✔
1035
            } else {
1036
               test_arb_eq(
2✔
1037
                  result, "build_certificate_path (without self-signed anchor)", cert_path, expected_paths.at(0));
2✔
1038
            }
1039
            results.push_back(result);
5✔
1040
         }
5✔
1041

1042
         return results;
1✔
1043
      }
7✔
1044

1045
      std::vector<Test::Result> forbidden_self_signed_trust_anchors_test() {
1✔
1046
         const auto restrictions = get_default_restrictions(false, false);  // non-self-signed anchors are forbidden
1✔
1047
         const auto certs = get_valid_cert_chain();
1✔
1048
         const auto validation_time = get_validation_time();
1✔
1049

1050
         Botan::Certificate_Store_In_Memory cert_store(certs.at(3));
1✔
1051

1052
         Test::Result result("Build certificate paths with only non-self-signed trust anchors (forbidden)");
1✔
1053

1054
         const auto path_result = Botan::x509_path_validate(
1✔
1055
            certs, restrictions, {&cert_store}, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
2✔
1056

1057
         result.test_str_eq("unexpected path validation result",
1✔
1058
                            path_result.result_string(),
1✔
1059
                            to_string(Botan::Certificate_Status_Code::CANNOT_ESTABLISH_TRUST));
1060

1061
         const auto check_chain_result = Botan::PKIX::check_chain(
5✔
1062
            {certs.at(0), certs.at(1), certs.at(2)}, validation_time, "", Botan::Usage_Type::UNSPECIFIED, restrictions);
4✔
1063

1064
         result.test_str_ne("unexpected check_chain result",
2✔
1065
                            Botan::Path_Validation_Result(check_chain_result, {}).result_string(),
2✔
1066
                            to_string(Botan::Certificate_Status_Code::OK));
1067

1068
         return {result};
3✔
1069
      }
3✔
1070

1071
      Test::Result stand_alone_root_test(std::string_view test_name,
6✔
1072
                                         const Botan::Path_Validation_Restrictions& restrictions,
1073
                                         const Botan::X509_Certificate& standalone_cert,
1074
                                         Botan::Certificate_Status_Code expected_result) {
1075
         Test::Result result(test_name);
6✔
1076

1077
         const auto validation_time = get_validation_time();
6✔
1078
         Botan::Certificate_Store_In_Memory cert_store(standalone_cert);
6✔
1079

1080
         const auto path_result = Botan::x509_path_validate(
6✔
1081
            standalone_cert, restrictions, {&cert_store}, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
12✔
1082

1083
         if(path_result.result() == Botan::Certificate_Status_Code::CERT_PUBKEY_INVALID) {
6✔
1084
            result.test_note("CERT_PUBKEY_INVALID encountered - was that key type disabled at build time?");
×
1085
         } else {
1086
            result.test_str_eq(
6✔
1087
               "unexpected x509_path_validate result", path_result.result_string(), to_string(expected_result));
12✔
1088
         }
1089

1090
         return result;
6✔
1091
      }
6✔
1092

1093
      std::vector<Test::Result> stand_alone_root_tests() {
1✔
1094
         const auto self_signed_trust_anchor_forbidden = get_default_restrictions(false, false);
1✔
1095
         const auto self_signed_trust_anchor_allowed = get_allow_non_self_signed_anchors_restrictions(false, false);
1✔
1096

1097
         const auto cert_chain = get_valid_cert_chain();
1✔
1098
         const auto& self_signed_root = cert_chain.at(4);
1✔
1099
         const auto& ica = cert_chain.at(3);
1✔
1100
         const auto& self_signed_ee = get_self_signed_ee_cert();
1✔
1101

1102
         return {
1✔
1103
            stand_alone_root_test("Standalone self-signed root (non-self-signed trust anchors forbidden)",
1104
                                  self_signed_trust_anchor_forbidden,
1105
                                  self_signed_root,
1106
                                  Botan::Certificate_Status_Code::OK),
1107
            stand_alone_root_test("Standalone self-signed root (non-self-signed trust anchors allowed)",
1108
                                  self_signed_trust_anchor_allowed,
1109
                                  self_signed_root,
1110
                                  Botan::Certificate_Status_Code::OK),
1111

1112
            stand_alone_root_test("Standalone self-signed end entity (non-self-signed trust anchors forbidden)",
1113
                                  self_signed_trust_anchor_forbidden,
1114
                                  self_signed_ee,
1115
                                  Botan::Certificate_Status_Code::OK),
1116
            stand_alone_root_test("Standalone self-signed end entity (non-self-signed trust anchors allowed)",
1117
                                  self_signed_trust_anchor_allowed,
1118
                                  self_signed_ee,
1119
                                  Botan::Certificate_Status_Code::OK),
1120

1121
            stand_alone_root_test("Standalone intermediate certificate (non-self-signed trust anchors forbidden)",
1122
                                  self_signed_trust_anchor_forbidden,
1123
                                  ica,
1124
                                  Botan::Certificate_Status_Code::CANNOT_ESTABLISH_TRUST),
1125
            stand_alone_root_test("Standalone intermediate certificate (non-self-signed trust anchors allowed)",
1126
                                  self_signed_trust_anchor_allowed,
1127
                                  ica,
1128
                                  Botan::Certificate_Status_Code::OK),
1129
         };
8✔
1130
      }
2✔
1131

1132
   public:
1133
      std::vector<Test::Result> run() override {
1✔
1134
         std::vector<Test::Result> results;
1✔
1135

1136
         auto res = path_validate_test();
1✔
1137
         results.insert(results.end(), res.begin(), res.end());
1✔
1138

1139
         res = build_path_test();
1✔
1140
         results.insert(results.end(), res.begin(), res.end());
1✔
1141

1142
         res = forbidden_self_signed_trust_anchors_test();
1✔
1143
         results.insert(results.end(), res.begin(), res.end());
1✔
1144

1145
         res = stand_alone_root_tests();
1✔
1146
         results.insert(results.end(), res.begin(), res.end());
1✔
1147

1148
         return results;
1✔
1149
      }
1✔
1150
};
1151

1152
BOTAN_REGISTER_TEST("x509", "x509_non_self_signed_trust_anchors", Non_Self_Signed_Trust_Anchors_Test);
1153

1154
class BSI_Path_Validation_Tests final : public Test
1✔
1155

1156
{
1157
   public:
1158
      std::vector<Test::Result> run() override;
1159

1160
   private:
1161
      std::vector<Test::Result> run_with_restrictions(const Botan::Path_Validation_Restrictions& restrictions);
1162
};
1163

1164
std::vector<Test::Result> BSI_Path_Validation_Tests::run() {
1✔
1165
   std::vector<Test::Result> results;
1✔
1166
   for(const auto& restrictions : restrictions_to_test(false, false)) {
3✔
1167
      auto partial_res = run_with_restrictions(restrictions);
2✔
1168
      results.insert(results.end(), partial_res.begin(), partial_res.end());
2✔
1169
   }
3✔
1170
   return results;
1✔
1171
}
×
1172

1173
std::vector<Test::Result> BSI_Path_Validation_Tests::run_with_restrictions(
2✔
1174
   const Botan::Path_Validation_Restrictions& restriction_template) {
1175
   if(Botan::has_filesystem_impl() == false) {
2✔
1176
      return {Test::Result::Note("BSI path validation", "Skipping due to missing filesystem access")};
×
1177
   }
1178

1179
   std::vector<Test::Result> results;
2✔
1180

1181
   for(const auto& [test_name, expected_result] : read_results(Test::data_file("x509/bsi/expected.txt"), '$')) {
110✔
1182
      Test::Result result("BSI path validation");
108✔
1183
      result.start_timer();
108✔
1184

1185
      const auto all_files = Test::files_in_data_dir("x509/bsi/" + test_name);
108✔
1186

1187
      Botan::Certificate_Store_In_Memory trusted;
108✔
1188
      std::vector<Botan::X509_Certificate> certs;
108✔
1189

1190
      #if defined(BOTAN_HAS_MD5)
1191
      const bool has_md5 = true;
108✔
1192
      #else
1193
      const bool has_md5 = false;
1194
      #endif
1195

1196
      auto validation_time = Botan::calendar_point(2017, 8, 19, 12, 0, 0).to_std_timepoint();
108✔
1197

1198
      // By convention: if CRL is a substring if the test name,
1199
      // we need to check the CRLs
1200
      bool use_crl = false;
108✔
1201
      if(test_name.find("CRL") != std::string::npos) {
108✔
1202
         use_crl = true;
32✔
1203
      }
1204

1205
      try {
108✔
1206
         for(const auto& file : all_files) {
878✔
1207
            // found a trust anchor
1208
            if(file.find("TA") != std::string::npos) {
780✔
1209
               trusted.add_certificate(Botan::X509_Certificate(file));
98✔
1210
            }
1211
            // found the target certificate. It needs to be at the front of certs
1212
            else if(file.find("TC") != std::string::npos) {
682✔
1213
               certs.insert(certs.begin(), Botan::X509_Certificate(file));
108✔
1214
            }
1215
            // found a certificate that might be part of a valid certificate chain to the trust anchor
1216
            else if(file.find(".crt") != std::string::npos) {
574✔
1217
               certs.push_back(Botan::X509_Certificate(file));
220✔
1218
            } else if(file.find(".crl") != std::string::npos) {
464✔
1219
               trusted.add_crl(Botan::X509_CRL(file));
64✔
1220
            }
1221
         }
1222

1223
         const Botan::Path_Validation_Restrictions restrictions(
98✔
1224
            use_crl,
1225
            restriction_template.minimum_key_strength(),
1226
            use_crl,
1227
            restriction_template.max_ocsp_age(),
1228
            std::make_unique<Botan::Certificate_Store_In_Memory>(),
206✔
1229
            restriction_template.ignore_trusted_root_time_range(),
98✔
1230
            restriction_template.require_self_signed_trust_anchors());
196✔
1231

1232
         /*
1233
          * Following the test document, the test are executed 16 times with
1234
          * randomly chosen order of the available certificates. However, the target
1235
          * certificate needs to stay in front.
1236
          * For certain test, the order in which the certificates are given to
1237
          * the validation function may be relevant, i.e. if issuer DNs are
1238
          * ambiguous.
1239
          */
1240
         class random_bit_generator {
98✔
1241
            public:
1242
               using result_type = size_t;
1243

1244
               static constexpr result_type min() { return 0; }
1245

1246
               static constexpr result_type max() { return std::numeric_limits<size_t>::max(); }
1247

1248
               result_type operator()() {
160✔
1249
                  size_t s = 0;
160✔
1250
                  m_rng.randomize(reinterpret_cast<uint8_t*>(&s), sizeof(s));
160✔
1251
                  return s;
160✔
1252
               }
1253

1254
               explicit random_bit_generator(Botan::RandomNumberGenerator& rng) : m_rng(rng) {}
98✔
1255

1256
            private:
1257
               Botan::RandomNumberGenerator& m_rng;
1258
         } rbg(this->rng());
98✔
1259

1260
         for(size_t r = 0; r < 16; r++) {
1,666✔
1261
            std::shuffle(++(certs.begin()), certs.end(), rbg);
1,568✔
1262

1263
            const Botan::Path_Validation_Result validation_result = Botan::x509_path_validate(
1,568✔
1264
               certs, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
1,568✔
1265

1266
            // We expect to be warned
1267
            if(expected_result.starts_with("Warning: ")) {
1,568✔
1268
               const std::string stripped = expected_result.substr(std::string("Warning: ").size());
64✔
1269
               bool found_warning = false;
64✔
1270
               for(const auto& warning_set : validation_result.warnings()) {
256✔
1271
                  for(const auto& warning : warning_set) {
256✔
1272
                     const std::string warning_str(Botan::to_string(warning));
64✔
1273
                     if(stripped == warning_str) {
64✔
1274
                        result.test_str_eq(test_name + " path validation result", warning_str, stripped);
64✔
1275
                        found_warning = true;
64✔
1276
                     }
1277
                  }
64✔
1278
               }
64✔
1279
               if(!found_warning) {
64✔
1280
                  result.test_failure(test_name, "Did not receive the expected warning: " + stripped);
×
1281
               }
1282
            } else {
64✔
1283
               if(expected_result == "Hash function used is considered too weak for security" && has_md5 == false) {
1,504✔
1284
                  result.test_str_eq(test_name + " path validation result",
1285
                                     validation_result.result_string(),
1286
                                     "Certificate signed with unknown/unavailable algorithm");
1287
               } else {
1288
                  result.test_str_eq(
3,008✔
1289
                     test_name + " path validation result", validation_result.result_string(), expected_result);
3,008✔
1290
               }
1291
            }
1292
         }
1,568✔
1293
      }
98✔
1294

1295
      /* Some certificates are rejected when executing the X509_Certificate constructor
1296
       * by throwing a Decoding_Error exception.
1297
       */
1298
      catch(const Botan::Exception& e) {
10✔
1299
         if(e.error_type() == Botan::ErrorType::DecodingFailure) {
10✔
1300
            result.test_str_eq(test_name + " path validation result", e.what(), expected_result);
20✔
1301
         } else {
1302
            result.test_failure(test_name, e.what());
×
1303
         }
1304
      }
10✔
1305

1306
      result.end_timer();
108✔
1307
      results.push_back(result);
108✔
1308
   }
108✔
1309

1310
   return results;
2✔
1311
}
2✔
1312

1313
BOTAN_REGISTER_TEST("x509", "x509_path_bsi", BSI_Path_Validation_Tests);
1314

1315
class Path_Validation_With_OCSP_Tests final : public Test {
1✔
1316
   public:
1317
      static Botan::X509_Certificate load_test_X509_cert(const std::string& path) {
27✔
1318
         return Botan::X509_Certificate(Test::data_file(path));
54✔
1319
      }
1320

1321
      static std::optional<Botan::OCSP::Response> load_test_OCSP_resp(const std::string& path) {
13✔
1322
         return Botan::OCSP::Response(Test::read_binary_data_file(path));
39✔
1323
      }
1324

1325
      static Test::Result validate_with_ocsp_with_next_update_without_max_age() {
1✔
1326
         Test::Result result("path check with ocsp with next_update w/o max_age");
1✔
1327
         Botan::Certificate_Store_In_Memory trusted;
1✔
1328

1329
         auto restrictions = Botan::Path_Validation_Restrictions(false, 110, false);
1✔
1330

1331
         auto ee = load_test_X509_cert("x509/ocsp/randombit.pem");
1✔
1332
         auto ca = load_test_X509_cert("x509/ocsp/letsencrypt.pem");
1✔
1333
         auto trust_root = load_test_X509_cert("x509/ocsp/identrust.pem");
1✔
1334
         trusted.add_certificate(trust_root);
1✔
1335

1336
         const std::vector<Botan::X509_Certificate> cert_path = {ee, ca, trust_root};
4✔
1337

1338
         std::optional<const Botan::OCSP::Response> ocsp = load_test_OCSP_resp("x509/ocsp/randombit_ocsp.der");
3✔
1339

1340
         auto check_path = [&](const std::chrono::system_clock::time_point valid_time,
5✔
1341
                               const Botan::Certificate_Status_Code expected_status) {
1342
            const auto path_result = Botan::x509_path_validate(cert_path,
8✔
1343
                                                               restrictions,
1344
                                                               trusted,
4✔
1345
                                                               "",
1346
                                                               Botan::Usage_Type::UNSPECIFIED,
1347
                                                               valid_time,
1348
                                                               std::chrono::milliseconds(0),
4✔
1349
                                                               {ocsp});
8✔
1350

1351
            return result.test_str_eq(
4✔
1352
               "Expected status", path_result.result_string(), Botan::to_string(expected_status));
8✔
1353
         };
8✔
1354

1355
         check_path(Botan::calendar_point(2016, 11, 11, 12, 30, 0).to_std_timepoint(),
1✔
1356
                    Botan::Certificate_Status_Code::OCSP_NOT_YET_VALID);
1357
         check_path(Botan::calendar_point(2016, 11, 18, 12, 30, 0).to_std_timepoint(),
1✔
1358
                    Botan::Certificate_Status_Code::OK);
1359
         check_path(Botan::calendar_point(2016, 11, 20, 8, 30, 0).to_std_timepoint(),
1✔
1360
                    Botan::Certificate_Status_Code::OK);
1361
         check_path(Botan::calendar_point(2016, 11, 28, 8, 30, 0).to_std_timepoint(),
1✔
1362
                    Botan::Certificate_Status_Code::OCSP_HAS_EXPIRED);
1363

1364
         return result;
2✔
1365
      }
2✔
1366

1367
      static Test::Result validate_with_ocsp_with_next_update_with_max_age() {
1✔
1368
         Test::Result result("path check with ocsp with next_update with max_age");
1✔
1369
         Botan::Certificate_Store_In_Memory trusted;
1✔
1370

1371
         auto restrictions = Botan::Path_Validation_Restrictions(false, 110, false, std::chrono::minutes(59));
1✔
1372

1373
         auto ee = load_test_X509_cert("x509/ocsp/randombit.pem");
1✔
1374
         auto ca = load_test_X509_cert("x509/ocsp/letsencrypt.pem");
1✔
1375
         auto trust_root = load_test_X509_cert("x509/ocsp/identrust.pem");
1✔
1376
         trusted.add_certificate(trust_root);
1✔
1377

1378
         const std::vector<Botan::X509_Certificate> cert_path = {ee, ca, trust_root};
4✔
1379

1380
         auto ocsp = load_test_OCSP_resp("x509/ocsp/randombit_ocsp.der");
1✔
1381

1382
         auto check_path = [&](const std::chrono::system_clock::time_point valid_time,
5✔
1383
                               const Botan::Certificate_Status_Code expected) {
1384
            const auto path_result = Botan::x509_path_validate(cert_path,
8✔
1385
                                                               restrictions,
1386
                                                               trusted,
4✔
1387
                                                               "",
1388
                                                               Botan::Usage_Type::UNSPECIFIED,
1389
                                                               valid_time,
1390
                                                               std::chrono::milliseconds(0),
4✔
1391
                                                               {ocsp});
8✔
1392

1393
            return result.test_is_true(std::string("Status: '") + Botan::to_string(expected) + "' should match '" +
24✔
1394
                                          Botan::to_string(path_result.result()) + "'",
8✔
1395
                                       path_result.result() == expected);
8✔
1396
         };
12✔
1397

1398
         check_path(Botan::calendar_point(2016, 11, 11, 12, 30, 0).to_std_timepoint(),
1✔
1399
                    Botan::Certificate_Status_Code::OCSP_NOT_YET_VALID);
1400
         check_path(Botan::calendar_point(2016, 11, 18, 12, 30, 0).to_std_timepoint(),
1✔
1401
                    Botan::Certificate_Status_Code::OK);
1402
         check_path(Botan::calendar_point(2016, 11, 20, 8, 30, 0).to_std_timepoint(),
1✔
1403
                    Botan::Certificate_Status_Code::OK);
1404
         check_path(Botan::calendar_point(2016, 11, 28, 8, 30, 0).to_std_timepoint(),
1✔
1405
                    Botan::Certificate_Status_Code::OCSP_HAS_EXPIRED);
1406

1407
         return result;
2✔
1408
      }
2✔
1409

1410
      static Test::Result validate_with_ocsp_without_next_update_without_max_age() {
1✔
1411
         Test::Result result("path check with ocsp w/o next_update w/o max_age");
1✔
1412
         Botan::Certificate_Store_In_Memory trusted;
1✔
1413

1414
         // max_age=0 means unbounded (the default is now finite)
1415
         auto restrictions = Botan::Path_Validation_Restrictions(false, 110, false, std::chrono::seconds(0));
1✔
1416

1417
         auto ee = load_test_X509_cert("x509/ocsp/patrickschmidt.pem");
1✔
1418
         auto ca = load_test_X509_cert("x509/ocsp/bdrive_encryption.pem");
1✔
1419
         auto trust_root = load_test_X509_cert("x509/ocsp/bdrive_root.pem");
1✔
1420

1421
         trusted.add_certificate(trust_root);
1✔
1422

1423
         const std::vector<Botan::X509_Certificate> cert_path = {ee, ca, trust_root};
4✔
1424

1425
         auto ocsp = load_test_OCSP_resp("x509/ocsp/patrickschmidt_ocsp.der");
1✔
1426

1427
         auto check_path = [&](const std::chrono::system_clock::time_point valid_time,
5✔
1428
                               const Botan::Certificate_Status_Code expected) {
1429
            const auto path_result = Botan::x509_path_validate(cert_path,
8✔
1430
                                                               restrictions,
1431
                                                               trusted,
4✔
1432
                                                               "",
1433
                                                               Botan::Usage_Type::UNSPECIFIED,
1434
                                                               valid_time,
1435
                                                               std::chrono::milliseconds(0),
4✔
1436
                                                               {ocsp});
8✔
1437

1438
            return result.test_is_true(std::string("Status: '") + Botan::to_string(expected) + "' should match '" +
24✔
1439
                                          Botan::to_string(path_result.result()) + "'",
8✔
1440
                                       path_result.result() == expected);
8✔
1441
         };
12✔
1442

1443
         check_path(Botan::calendar_point(2019, 5, 28, 7, 0, 0).to_std_timepoint(),
1✔
1444
                    Botan::Certificate_Status_Code::OCSP_NOT_YET_VALID);
1445
         check_path(Botan::calendar_point(2019, 5, 28, 7, 30, 0).to_std_timepoint(),
1✔
1446
                    Botan::Certificate_Status_Code::OK);
1447
         check_path(Botan::calendar_point(2019, 5, 28, 8, 0, 0).to_std_timepoint(), Botan::Certificate_Status_Code::OK);
1✔
1448
         // Well past 7 days: unbounded, so still OK
1449
         check_path(Botan::calendar_point(2019, 6, 10, 7, 30, 0).to_std_timepoint(),
1✔
1450
                    Botan::Certificate_Status_Code::OK);
1451

1452
         return result;
2✔
1453
      }
2✔
1454

1455
      static Test::Result validate_with_ocsp_without_next_update_default_restrictions() {
1✔
1456
         Test::Result result("path check with ocsp w/o next_update under default max_age");
1✔
1457
         Botan::Certificate_Store_In_Memory trusted;
1✔
1458

1459
         auto restrictions = Botan::Path_Validation_Restrictions(false, 110, false);
1✔
1460

1461
         auto ee = load_test_X509_cert("x509/ocsp/patrickschmidt.pem");
1✔
1462
         auto ca = load_test_X509_cert("x509/ocsp/bdrive_encryption.pem");
1✔
1463
         auto trust_root = load_test_X509_cert("x509/ocsp/bdrive_root.pem");
1✔
1464

1465
         trusted.add_certificate(trust_root);
1✔
1466

1467
         const std::vector<Botan::X509_Certificate> cert_path = {ee, ca, trust_root};
4✔
1468

1469
         auto ocsp = load_test_OCSP_resp("x509/ocsp/patrickschmidt_ocsp.der");
1✔
1470

1471
         auto check_path = [&](const std::chrono::system_clock::time_point valid_time,
3✔
1472
                               const Botan::Certificate_Status_Code expected) {
1473
            const auto path_result = Botan::x509_path_validate(cert_path,
4✔
1474
                                                               restrictions,
1475
                                                               trusted,
2✔
1476
                                                               "",
1477
                                                               Botan::Usage_Type::UNSPECIFIED,
1478
                                                               valid_time,
1479
                                                               std::chrono::milliseconds(0),
2✔
1480
                                                               {ocsp});
4✔
1481

1482
            return result.test_is_true(std::string("Status: '") + Botan::to_string(expected) + "' should match '" +
12✔
1483
                                          Botan::to_string(path_result.result()) + "'",
4✔
1484
                                       path_result.result() == expected);
4✔
1485
         };
6✔
1486

1487
         check_path(Botan::calendar_point(2019, 5, 28, 7, 30, 0).to_std_timepoint(),
1✔
1488
                    Botan::Certificate_Status_Code::OK);
1489
         check_path(Botan::calendar_point(2019, 6, 10, 7, 30, 0).to_std_timepoint(),
1✔
1490
                    Botan::Certificate_Status_Code::OCSP_IS_TOO_OLD);
1491

1492
         return result;
2✔
1493
      }
2✔
1494

1495
      static Test::Result validate_with_ocsp_without_next_update_with_max_age() {
1✔
1496
         Test::Result result("path check with ocsp w/o next_update with max_age");
1✔
1497
         Botan::Certificate_Store_In_Memory trusted;
1✔
1498

1499
         auto restrictions = Botan::Path_Validation_Restrictions(false, 110, false, std::chrono::minutes(59));
1✔
1500

1501
         auto ee = load_test_X509_cert("x509/ocsp/patrickschmidt.pem");
1✔
1502
         auto ca = load_test_X509_cert("x509/ocsp/bdrive_encryption.pem");
1✔
1503
         auto trust_root = load_test_X509_cert("x509/ocsp/bdrive_root.pem");
1✔
1504

1505
         trusted.add_certificate(trust_root);
1✔
1506

1507
         const std::vector<Botan::X509_Certificate> cert_path = {ee, ca, trust_root};
4✔
1508

1509
         auto ocsp = load_test_OCSP_resp("x509/ocsp/patrickschmidt_ocsp.der");
1✔
1510

1511
         auto check_path = [&](const std::chrono::system_clock::time_point valid_time,
4✔
1512
                               const Botan::Certificate_Status_Code expected) {
1513
            const auto path_result = Botan::x509_path_validate(cert_path,
6✔
1514
                                                               restrictions,
1515
                                                               trusted,
3✔
1516
                                                               "",
1517
                                                               Botan::Usage_Type::UNSPECIFIED,
1518
                                                               valid_time,
1519
                                                               std::chrono::milliseconds(0),
3✔
1520
                                                               {ocsp});
6✔
1521

1522
            return result.test_is_true(std::string("Status: '") + Botan::to_string(expected) + "' should match '" +
18✔
1523
                                          Botan::to_string(path_result.result()) + "'",
6✔
1524
                                       path_result.result() == expected);
6✔
1525
         };
9✔
1526

1527
         check_path(Botan::calendar_point(2019, 5, 28, 7, 0, 0).to_std_timepoint(),
1✔
1528
                    Botan::Certificate_Status_Code::OCSP_NOT_YET_VALID);
1529
         check_path(Botan::calendar_point(2019, 5, 28, 7, 30, 0).to_std_timepoint(),
1✔
1530
                    Botan::Certificate_Status_Code::OK);
1531
         check_path(Botan::calendar_point(2019, 5, 28, 8, 0, 0).to_std_timepoint(),
1✔
1532
                    Botan::Certificate_Status_Code::OCSP_IS_TOO_OLD);
1533

1534
         return result;
2✔
1535
      }
2✔
1536

1537
      static Test::Result validate_with_ocsp_with_authorized_responder() {
1✔
1538
         Test::Result result("path check with ocsp response from authorized responder certificate");
1✔
1539
         Botan::Certificate_Store_In_Memory trusted;
1✔
1540

1541
         auto restrictions = Botan::Path_Validation_Restrictions(true,   // require revocation info
1✔
1542
                                                                 110,    // minimum key strength
1543
                                                                 true);  // OCSP for all intermediates
1✔
1544

1545
         auto ee = load_test_X509_cert("x509/ocsp/bdr.pem");
1✔
1546
         auto ca = load_test_X509_cert("x509/ocsp/bdr-int.pem");
1✔
1547
         auto trust_root = load_test_X509_cert("x509/ocsp/bdr-root.pem");
1✔
1548

1549
         // These OCSP responses are signed by an authorized OCSP responder
1550
         // certificate issued by `ca` and `trust_root` respectively. Note that
1551
         // the responder certificates contain the "OCSP No Check" extension,
1552
         // meaning that they themselves do not need a revocation check via OCSP.
1553
         auto ocsp_ee = load_test_OCSP_resp("x509/ocsp/bdr-ocsp-resp.der");
1✔
1554
         auto ocsp_ca = load_test_OCSP_resp("x509/ocsp/bdr-int-ocsp-resp.der");
1✔
1555

1556
         trusted.add_certificate(trust_root);
1✔
1557
         const std::vector<Botan::X509_Certificate> cert_path = {ee, ca, trust_root};
4✔
1558

1559
         auto check_path = [&](const std::chrono::system_clock::time_point valid_time,
4✔
1560
                               const Botan::Certificate_Status_Code expected) {
1561
            const auto path_result = Botan::x509_path_validate(cert_path,
12✔
1562
                                                               restrictions,
1563
                                                               trusted,
3✔
1564
                                                               "",
1565
                                                               Botan::Usage_Type::UNSPECIFIED,
1566
                                                               valid_time,
1567
                                                               std::chrono::milliseconds(0),
3✔
1568
                                                               {ocsp_ee, ocsp_ca});
9✔
1569

1570
            return result.test_is_true(std::string("Status: '") + Botan::to_string(expected) + "' should match '" +
18✔
1571
                                          Botan::to_string(path_result.result()) + "'",
6✔
1572
                                       path_result.result() == expected);
6✔
1573
         };
12✔
1574

1575
         check_path(Botan::calendar_point(2022, 9, 18, 16, 30, 0).to_std_timepoint(),
1✔
1576
                    Botan::Certificate_Status_Code::OCSP_NOT_YET_VALID);
1577
         check_path(Botan::calendar_point(2022, 9, 19, 16, 30, 0).to_std_timepoint(),
1✔
1578
                    Botan::Certificate_Status_Code::OK);
1579
         check_path(Botan::calendar_point(2022, 9, 20, 16, 30, 0).to_std_timepoint(),
1✔
1580
                    Botan::Certificate_Status_Code::OCSP_HAS_EXPIRED);
1581

1582
         return result;
1✔
1583
      }
4✔
1584

1585
      static Test::Result validate_with_ocsp_with_authorized_responder_without_keyusage() {
1✔
1586
         Test::Result result(
1✔
1587
            "path check with ocsp response from authorized responder certificate (without sufficient key usage)");
1✔
1588
         Botan::Certificate_Store_In_Memory trusted;
1✔
1589

1590
         auto restrictions = Botan::Path_Validation_Restrictions(true,    // require revocation info
1✔
1591
                                                                 110,     // minimum key strength
1592
                                                                 false);  // OCSP for all intermediates
1✔
1593

1594
         // See `src/scripts/mychain_creater.sh` if you need to recreate those
1595
         auto ee = load_test_X509_cert("x509/ocsp/mychain_ee.pem");
1✔
1596
         auto ca = load_test_X509_cert("x509/ocsp/mychain_int.pem");
1✔
1597
         auto trust_root = load_test_X509_cert("x509/ocsp/mychain_root.pem");
1✔
1598

1599
         auto ocsp_ee_delegate = load_test_OCSP_resp("x509/ocsp/mychain_ocsp_for_ee_delegate_signed.der").value();
2✔
1600
         auto ocsp_ee_delegate_malformed =
1✔
1601
            load_test_OCSP_resp("x509/ocsp/mychain_ocsp_for_ee_delegate_signed_malformed.der").value();
2✔
1602

1603
         trusted.add_certificate(trust_root);
1✔
1604
         const std::vector<Botan::X509_Certificate> cert_path = {ee, ca, trust_root};
4✔
1605

1606
         auto check_path = [&](const std::chrono::system_clock::time_point valid_time,
4✔
1607
                               const Botan::OCSP::Response& ocsp_ee,
1608
                               const Botan::Certificate_Status_Code expected,
1609
                               const std::optional<Botan::Certificate_Status_Code> also_expected = std::nullopt) {
1610
            const auto path_result = Botan::x509_path_validate(cert_path,
6✔
1611
                                                               restrictions,
1612
                                                               trusted,
3✔
1613
                                                               "",
1614
                                                               Botan::Usage_Type::UNSPECIFIED,
1615
                                                               valid_time,
1616
                                                               std::chrono::milliseconds(0),
3✔
1617
                                                               {ocsp_ee});
6✔
1618

1619
            result.test_u32_eq("should result in expected validation status code",
3✔
1620
                               static_cast<uint32_t>(path_result.result()),
3✔
1621
                               static_cast<uint32_t>(expected));
1622
            if(also_expected) {
3✔
1623
               result.test_is_true("Secondary error is also present",
4✔
1624
                                   flatten(path_result.all_statuses()).contains(also_expected.value()));
6✔
1625
            }
1626
         };
6✔
1627

1628
         check_path(Botan::calendar_point(2022, 9, 22, 23, 30, 0).to_std_timepoint(),
1✔
1629
                    ocsp_ee_delegate,
1630
                    Botan::Certificate_Status_Code::VERIFIED);
1631
         check_path(Botan::calendar_point(2022, 10, 8, 23, 30, 0).to_std_timepoint(),
1✔
1632
                    ocsp_ee_delegate,
1633
                    Botan::Certificate_Status_Code::CERT_HAS_EXPIRED,
1634
                    Botan::Certificate_Status_Code::OCSP_ISSUER_NOT_TRUSTED);
1✔
1635
         check_path(Botan::calendar_point(2022, 9, 22, 23, 30, 0).to_std_timepoint(),
1✔
1636
                    ocsp_ee_delegate_malformed,
1637
                    Botan::Certificate_Status_Code::OCSP_RESPONSE_MISSING_KEYUSAGE,
1638
                    Botan::Certificate_Status_Code::OCSP_ISSUER_NOT_TRUSTED);
1✔
1639

1640
         return result;
1✔
1641
      }
2✔
1642

1643
      static Test::Result validate_with_forged_ocsp_using_self_signed_cert() {
1✔
1644
         Test::Result result("path check with forged ocsp using self-signed certificate");
1✔
1645
         Botan::Certificate_Store_In_Memory trusted;
1✔
1646

1647
         auto restrictions = Botan::Path_Validation_Restrictions(true,    // require revocation info
1✔
1648
                                                                 110,     // minimum key strength
1649
                                                                 false);  // OCSP for all intermediates
1✔
1650

1651
         auto ee = load_test_X509_cert("x509/ocsp/randombit.pem");
1✔
1652
         auto ca = load_test_X509_cert("x509/ocsp/letsencrypt.pem");
1✔
1653
         auto trust_root = load_test_X509_cert("x509/ocsp/identrust.pem");
1✔
1654
         trusted.add_certificate(trust_root);
1✔
1655

1656
         const std::vector<Botan::X509_Certificate> cert_path = {ee, ca, trust_root};
4✔
1657

1658
         auto check_path = [&](const std::string& forged_ocsp,
3✔
1659
                               const Botan::Certificate_Status_Code expected,
1660
                               const Botan::Certificate_Status_Code also_expected) {
1661
            auto ocsp = load_test_OCSP_resp(forged_ocsp);
2✔
1662
            const auto path_result =
2✔
1663
               Botan::x509_path_validate(cert_path,
4✔
1664
                                         restrictions,
1665
                                         trusted,
2✔
1666
                                         "",
1667
                                         Botan::Usage_Type::UNSPECIFIED,
1668
                                         Botan::calendar_point(2016, 11, 18, 12, 30, 0).to_std_timepoint(),
4✔
1669
                                         std::chrono::milliseconds(0),
2✔
1670
                                         {ocsp});
4✔
1671

1672
            result.test_enum_eq(
2✔
1673
               "Path validation with forged OCSP response should fail with", path_result.result(), expected);
2✔
1674
            result.test_is_true("Secondary error is also present",
4✔
1675
                                flatten(path_result.all_statuses()).contains(also_expected));
4✔
1676
            result.test_note("Validation result", Botan::to_string(path_result.result()));
2✔
1677
         };
8✔
1678

1679
         // In both cases the path validation should detect the forged OCSP
1680
         // response and generate an appropriate error. By no means it should
1681
         // follow the unauthentic OCSP response.
1682
         check_path("x509/ocsp/randombit_ocsp_forged_valid.der",
1✔
1683
                    Botan::Certificate_Status_Code::OCSP_ISSUER_NOT_TRUSTED,
1684
                    Botan::Certificate_Status_Code::OCSP_ISSUER_NOT_TRUSTED);
1685
         check_path("x509/ocsp/randombit_ocsp_forged_revoked.der",
1✔
1686
                    Botan::Certificate_Status_Code::OCSP_ISSUER_NOT_TRUSTED,
1687
                    Botan::Certificate_Status_Code::OCSP_ISSUER_NOT_TRUSTED);
1688

1689
         return result;
1✔
1690
      }
2✔
1691

1692
      static Test::Result validate_with_ocsp_self_signed_by_intermediate_cert() {
1✔
1693
         Test::Result result(
1✔
1694
            "path check with ocsp response for intermediate that is (maliciously) self-signed by the intermediate");
1✔
1695
         Botan::Certificate_Store_In_Memory trusted;
1✔
1696

1697
         auto restrictions = Botan::Path_Validation_Restrictions(true,   // require revocation info
1✔
1698
                                                                 110,    // minimum key strength
1699
                                                                 true);  // OCSP for all intermediates
1✔
1700

1701
         // See `src/scripts/mychain_creater.sh` if you need to recreate those
1702
         auto ee = load_test_X509_cert("x509/ocsp/mychain_ee.pem");
1✔
1703
         auto ca = load_test_X509_cert("x509/ocsp/mychain_int.pem");
1✔
1704
         auto trust_root = load_test_X509_cert("x509/ocsp/mychain_root.pem");
1✔
1705

1706
         // this OCSP response for EE is valid (signed by intermediate cert)
1707
         auto ocsp_ee = load_test_OCSP_resp("x509/ocsp/mychain_ocsp_for_ee.der");
1✔
1708

1709
         // this OCSP response for Intermediate is malicious (signed by intermediate itself)
1710
         auto ocsp_ca = load_test_OCSP_resp("x509/ocsp/mychain_ocsp_for_int_self_signed.der");
1✔
1711

1712
         trusted.add_certificate(trust_root);
1✔
1713
         const std::vector<Botan::X509_Certificate> cert_path = {ee, ca, trust_root};
4✔
1714

1715
         const auto path_result =
1✔
1716
            Botan::x509_path_validate(cert_path,
4✔
1717
                                      restrictions,
1718
                                      trusted,
1719
                                      "",
1720
                                      Botan::Usage_Type::UNSPECIFIED,
1721
                                      Botan::calendar_point(2022, 9, 22, 22, 30, 0).to_std_timepoint(),
2✔
1722
                                      std::chrono::milliseconds(0),
1✔
1723
                                      {ocsp_ee, ocsp_ca});
3✔
1724
         result.test_is_true("should reject intermediate OCSP response",
1✔
1725
                             path_result.result() == Botan::Certificate_Status_Code::OCSP_ISSUER_NOT_FOUND);
1✔
1726
         result.test_note("Validation result", Botan::to_string(path_result.result()));
1✔
1727

1728
         return result;
1✔
1729
      }
7✔
1730

1731
      std::vector<Test::Result> run() override {
1✔
1732
         return {validate_with_ocsp_with_next_update_without_max_age(),
1✔
1733
                 validate_with_ocsp_with_next_update_with_max_age(),
1734
                 validate_with_ocsp_without_next_update_without_max_age(),
1735
                 validate_with_ocsp_without_next_update_with_max_age(),
1736
                 validate_with_ocsp_without_next_update_default_restrictions(),
1737
                 validate_with_ocsp_with_authorized_responder(),
1738
                 validate_with_ocsp_with_authorized_responder_without_keyusage(),
1739
                 validate_with_forged_ocsp_using_self_signed_cert(),
1740
                 validate_with_ocsp_self_signed_by_intermediate_cert()};
10✔
1741
      }
1✔
1742
};
1743

1744
BOTAN_REGISTER_TEST("x509", "x509_path_with_ocsp", Path_Validation_With_OCSP_Tests);
1745

1746
   #endif
1747

1748
   #if defined(BOTAN_HAS_ECDSA)
1749

1750
class CVE_2020_0601_Tests final : public Test {
1✔
1751
   public:
1752
      std::vector<Test::Result> run() override {
1✔
1753
         Test::Result result("CVE-2020-0601");
1✔
1754

1755
         if(!Botan::EC_Group::supports_application_specific_group()) {
1✔
1756
            result.test_note("Skipping as application specific groups are not supported");
×
1757
            return {result};
×
1758
         }
1759

1760
         if(!Botan::EC_Group::supports_named_group("secp384r1")) {
1✔
1761
            result.test_note("Skipping as secp384r1 is not supported");
×
1762
            return {result};
×
1763
         }
1764

1765
         const auto& secp384r1 = Botan::EC_Group::from_name("secp384r1");
1✔
1766
         const Botan::OID curveball_oid("1.3.6.1.4.1.25258.4.2020.0601");
1✔
1767
         const Botan::EC_Group curveball(
1✔
1768
            curveball_oid,
1769
            secp384r1.get_p(),
1770
            secp384r1.get_a(),
1771
            secp384r1.get_b(),
1772
            BigInt(
2✔
1773
               "0xC711162A761D568EBEB96265D4C3CEB4F0C330EC8F6DD76E39BCC849ABABB8E34378D581065DEFC77D9FCED6B39075DE"),
1774
            BigInt(
2✔
1775
               "0x0CB090DE23BAC8D13E67E019A91B86311E5F342DEE17FD15FB7E278A32A1EAC98FC97E18CB2F3B2C487A7DA6F40107AC"),
1776
            secp384r1.get_order());
2✔
1777

1778
         auto ca_crt = Botan::X509_Certificate(Test::data_file("x509/cve-2020-0601/ca.pem"));
2✔
1779
         auto fake_ca_crt = Botan::X509_Certificate(Test::data_file("x509/cve-2020-0601/fake_ca.pem"));
2✔
1780
         auto ee_crt = Botan::X509_Certificate(Test::data_file("x509/cve-2020-0601/ee.pem"));
2✔
1781

1782
         Botan::Certificate_Store_In_Memory trusted;
1✔
1783
         trusted.add_certificate(ca_crt);
1✔
1784

1785
         const auto restrictions = Botan::Path_Validation_Restrictions(false, 80, false);
1✔
1786

1787
         const auto valid_time = Botan::calendar_point(2020, 1, 20, 0, 0, 0).to_std_timepoint();
1✔
1788

1789
         const auto path_result1 = Botan::x509_path_validate(std::vector<Botan::X509_Certificate>{ee_crt, fake_ca_crt},
5✔
1790
                                                             restrictions,
1791
                                                             trusted,
1792
                                                             "",
1793
                                                             Botan::Usage_Type::UNSPECIFIED,
1794
                                                             valid_time,
1795
                                                             std::chrono::milliseconds(0),
1✔
1796
                                                             {});
3✔
1797

1798
         result.test_is_true("Validation failed", !path_result1.successful_validation());
1✔
1799

1800
         result.test_is_true("Expected status",
1✔
1801
                             path_result1.result() == Botan::Certificate_Status_Code::CANNOT_ESTABLISH_TRUST);
1✔
1802

1803
         const auto path_result2 = Botan::x509_path_validate(std::vector<Botan::X509_Certificate>{ee_crt},
3✔
1804
                                                             restrictions,
1805
                                                             trusted,
1806
                                                             "",
1807
                                                             Botan::Usage_Type::UNSPECIFIED,
1808
                                                             valid_time,
1809
                                                             std::chrono::milliseconds(0),
1✔
1810
                                                             {});
3✔
1811

1812
         result.test_is_true("Validation failed", !path_result2.successful_validation());
1✔
1813

1814
         result.test_is_true("Expected status",
1✔
1815
                             path_result2.result() == Botan::Certificate_Status_Code::CERT_ISSUER_NOT_FOUND);
1✔
1816

1817
         // Verify the signature from the bad CA is actually correct
1818
         Botan::Certificate_Store_In_Memory frusted;
1✔
1819
         frusted.add_certificate(fake_ca_crt);
1✔
1820

1821
         const auto path_result3 = Botan::x509_path_validate(std::vector<Botan::X509_Certificate>{ee_crt},
3✔
1822
                                                             restrictions,
1823
                                                             frusted,
1824
                                                             "",
1825
                                                             Botan::Usage_Type::UNSPECIFIED,
1826
                                                             valid_time,
1827
                                                             std::chrono::milliseconds(0),
1✔
1828
                                                             {});
3✔
1829

1830
         result.test_is_true("Validation succeeded", path_result3.successful_validation());
1✔
1831

1832
         return {result};
2✔
1833
      }
5✔
1834
};
1835

1836
BOTAN_REGISTER_TEST("x509", "x509_cve_2020_0601", CVE_2020_0601_Tests);
1837

1838
class Path_Validation_With_Immortal_CRL final : public Test {
1✔
1839
   public:
1840
      std::vector<Test::Result> run() override {
1✔
1841
         // RFC 5280 defines the nextUpdate field as "optional" (in line with
1842
         // the original X.509 standard), but then requires all conforming CAs
1843
         // to always define it. For best compatibility we must deal with both.
1844
         Test::Result result("Using a CRL without a nextUpdate field");
1✔
1845

1846
         if(Botan::has_filesystem_impl() == false) {
1✔
1847
            result.test_note("Skipping due to missing filesystem access");
×
1848
            return {result};
×
1849
         }
1850

1851
         const Botan::X509_Certificate root(Test::data_file("x509/misc/crl_without_nextupdate/ca.pem"));
2✔
1852
         const Botan::X509_Certificate revoked_subject(Test::data_file("x509/misc/crl_without_nextupdate/01.pem"));
2✔
1853
         const Botan::X509_Certificate valid_subject(Test::data_file("x509/misc/crl_without_nextupdate/42.pem"));
2✔
1854

1855
         // Check that a CRL without nextUpdate is parsable
1856
         auto crl = Botan::X509_CRL(Test::data_file("x509/misc/crl_without_nextupdate/valid_forever.crl"));
2✔
1857
         result.test_is_true("this update is set", crl.this_update().time_is_set());
1✔
1858
         result.test_is_true("next update is not set", !crl.next_update().time_is_set());
1✔
1859
         result.test_is_true("CRL is not empty", !crl.get_revoked().empty());
1✔
1860

1861
         // Ensure that we support the used sig algo, otherwish stop here
1862
         if(!Botan::EC_Group::supports_named_group("brainpool512r1")) {
1✔
1863
            result.test_note("Cannot test path validation because signature algorithm is not support in this build");
×
1864
            return {result};
×
1865
         }
1866

1867
         Botan::Certificate_Store_In_Memory trusted;
1✔
1868
         trusted.add_certificate(root);
1✔
1869
         trusted.add_crl(crl);
1✔
1870

1871
         // Just before the CA and subject certificates expire
1872
         // (validity from 01 March 2025 to 24 February 2026)
1873
         auto valid_time = Botan::calendar_point(2026, 2, 23, 0, 0, 0).to_std_timepoint();
1✔
1874

1875
         const Botan::Path_Validation_Restrictions restrictions(true /* require revocation info */);
1✔
1876

1877
         // Validate a certificate that is not listed in the CRL
1878
         const auto valid = Botan::x509_path_validate(
1✔
1879
            valid_subject, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, valid_time);
1✔
1880
         if(!result.test_is_true("Valid certificate", valid.successful_validation())) {
1✔
1881
            result.test_note(valid.result_string());
×
1882
         }
1883

1884
         // Ensure that a certificate listed in the CRL is recognized as revoked
1885
         const auto revoked = Botan::x509_path_validate(
1✔
1886
            revoked_subject, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, valid_time);
1✔
1887
         if(!result.test_is_true("No valid certificate", !revoked.successful_validation())) {
1✔
1888
            result.test_note(revoked.result_string());
×
1889
         }
1890
         result.test_enum_eq(
2✔
1891
            "Certificate is revoked", revoked.result(), Botan::Certificate_Status_Code::CERT_IS_REVOKED);
1✔
1892

1893
         return {result};
2✔
1894
      }
2✔
1895
};
1896

1897
BOTAN_REGISTER_TEST("x509", "x509_path_immortal_crl", Path_Validation_With_Immortal_CRL);
1898

1899
   #endif
1900

1901
   #if defined(BOTAN_HAS_XMSS_RFC8391)
1902

1903
class XMSS_Path_Validation_Tests final : public Test {
1✔
1904
   public:
1905
      static Test::Result validate_self_signed(const std::string& name, const std::string& file) {
2✔
1906
         Test::Result result(name);
2✔
1907

1908
         const Botan::Path_Validation_Restrictions restrictions;
2✔
1909
         auto self_signed = Botan::X509_Certificate(Test::data_file("x509/xmss/" + file));
4✔
1910

1911
         auto cert_path = std::vector<Botan::X509_Certificate>{self_signed};
4✔
1912
         auto valid_time = Botan::calendar_point(2019, 10, 8, 4, 45, 0).to_std_timepoint();
2✔
1913

1914
         auto status = Botan::PKIX::overall_status(
2✔
1915
            Botan::PKIX::check_chain(cert_path, valid_time, "", Botan::Usage_Type::UNSPECIFIED, restrictions));
4✔
1916
         result.test_str_eq("Cert validation status", Botan::to_string(status), "Verified");
2✔
1917
         return result;
2✔
1918
      }
4✔
1919

1920
      std::vector<Test::Result> run() override {
1✔
1921
         if(Botan::has_filesystem_impl() == false) {
1✔
1922
            return {Test::Result::Note("XMSS path validation", "Skipping due to missing filesystem access")};
×
1923
         }
1924

1925
         return {
1✔
1926
            validate_self_signed("XMSS path validation with certificate created by ISARA corp", "xmss_isara_root.pem"),
1✔
1927
            validate_self_signed("XMSS path validation with certificate created by BouncyCastle",
1✔
1928
                                 "xmss_bouncycastle_sha256_10_root.pem")};
3✔
1929
      }
4✔
1930
};
1931

1932
BOTAN_REGISTER_TEST("x509", "x509_path_xmss", XMSS_Path_Validation_Tests);
1933

1934
   #endif
1935

1936
   #if defined(BOTAN_HAS_RSA) && defined(BOTAN_HAS_EMSA_PKCS1)
1937

1938
class CVE_2026_35580_Test final : public Test {
1✔
1939
   public:
1940
      std::vector<Test::Result> run() override {
1✔
1941
         Test::Result result("CVE-2026-35580");
1✔
1942

1943
         const Botan::Path_Validation_Restrictions restrictions;
1✔
1944

1945
         auto end_entity = Botan::X509_Certificate(Test::data_file("x509/cve_2026_35580/end_entity.pem"));
2✔
1946
         auto trusted_root = Botan::X509_Certificate(Test::data_file("x509/cve_2026_35580/root.pem"));
2✔
1947

1948
         auto validation_time = Botan::calendar_point(2026, 3, 29, 0, 0, 0).to_std_timepoint();
1✔
1949

1950
         Botan::Certificate_Store_In_Memory trusted;
1✔
1951
         trusted.add_certificate(trusted_root);
1✔
1952

1953
         const auto validation_result = Botan::x509_path_validate(
1✔
1954
            {end_entity}, restrictions, {&trusted}, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
2✔
1955

1956
         result.test_str_eq(
1✔
1957
            "Cert validation status", Botan::to_string(validation_result.result()), "Cannot establish trust");
1958

1959
         return {result};
3✔
1960
      }
2✔
1961
};
1962

1963
BOTAN_REGISTER_TEST("x509", "x509_cve_2026_35580", CVE_2026_35580_Test);
1964

1965
   #endif
1966

1967
   #if defined(BOTAN_HAS_ECDSA)
1968

1969
/**
1970
* Test that the certificate path building DFS correctly handles constructing
1971
* paths with many possible intermediates.
1972
*/
1973
class Path_Building_Tests final : public Test {
1✔
1974
   public:
1975
      std::vector<Test::Result> run() override {
1✔
1976
         if(Botan::has_filesystem_impl() == false) {
1✔
1977
            return {Test::Result::Note("X509 path building", "Skipping due to missing filesystem access")};
×
1978
         }
1979

1980
         const std::string base_dir = "x509/path_building";
1✔
1981
         auto validation_time = Botan::calendar_point(2026, 4, 1, 4, 20, 0).to_std_timepoint();
1✔
1982

1983
         // Load root into trust store
1984
         Botan::Certificate_Store_In_Memory trust_store;
1✔
1985
         trust_store.add_certificate(Botan::X509_Certificate(Test::data_file(base_dir + "/root.pem")));
1✔
1986

1987
         // Load the intermediates
1988
         std::vector<Botan::X509_Certificate> intermediates;
1✔
1989
         for(const auto& file : Test::files_in_data_dir(base_dir)) {
24✔
1990
            if(file.find("level1_") != std::string::npos || file.find("level2_") != std::string::npos) {
23✔
1991
               intermediates.emplace_back(file);
14✔
1992
            }
1993
         }
1✔
1994

1995
         Test::Result result("X509 path building DFS");
1✔
1996
         result.start_timer();
1✔
1997

1998
         const Botan::Path_Validation_Restrictions restrictions(false, 80, false);
1✔
1999

2000
         for(const auto& [test_name, expected_result] : read_results(Test::data_file(base_dir + "/expected.txt"))) {
8✔
2001
            const Botan::X509_Certificate end_entity(Test::data_file(Botan::fmt("{}/{}", base_dir, test_name)));
14✔
2002

2003
            std::vector<Botan::X509_Certificate> end_certs;
7✔
2004
            end_certs.push_back(end_entity);
7✔
2005
            end_certs.insert(end_certs.end(), intermediates.begin(), intermediates.end());
7✔
2006

2007
            const Botan::Path_Validation_Result validation_result = Botan::x509_path_validate(
7✔
2008
               end_certs, restrictions, {&trust_store}, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
14✔
2009

2010
            result.test_str_eq(
14✔
2011
               test_name + " path validation result", validation_result.result_string(), expected_result);
14✔
2012
         }
7✔
2013

2014
         result.end_timer();
1✔
2015
         return {result};
2✔
2016
      }
2✔
2017
};
2018

2019
BOTAN_REGISTER_TEST("x509", "x509_path_building", Path_Building_Tests);
2020

2021
   #endif
2022

2023
#endif
2024

2025
}  // namespace
2026

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