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

randombit / botan / 20579846577

29 Dec 2025 06:24PM UTC coverage: 90.415% (+0.2%) from 90.243%
20579846577

push

github

web-flow
Merge pull request #5167 from randombit/jack/src-size-reductions

Changes to reduce unnecessary inclusions

101523 of 112285 relevant lines covered (90.42%)

12817276.56 hits per line

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

95.65
/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 <botan/data_src.h>
12
   #include <botan/exceptn.h>
13
   #include <botan/pk_keys.h>
14
   #include <botan/pkcs10.h>
15
   #include <botan/rng.h>
16
   #include <botan/x509_crl.h>
17
   #include <botan/x509path.h>
18
   #include <botan/internal/calendar.h>
19
   #include <botan/internal/filesystem.h>
20
   #include <botan/internal/fmt.h>
21
   #include <botan/internal/parsing.h>
22

23
   #if defined(BOTAN_HAS_ECDSA)
24
      #include <botan/ec_group.h>
25
   #endif
26

27
   #include <algorithm>
28
   #include <fstream>
29
   #include <limits>
30
   #include <map>
31
   #include <string>
32
   #include <vector>
33
#endif
34

35
namespace Botan_Tests {
36

37
namespace {
38

39
#if defined(BOTAN_HAS_X509_CERTIFICATES) && defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
40

41
   #if defined(BOTAN_HAS_RSA) && defined(BOTAN_HAS_EMSA_PKCS1)
42

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

49
   std::map<std::string, std::string> m;
9✔
50
   std::string line;
9✔
51
   while(in.good()) {
609✔
52
      std::getline(in, line);
600✔
53
      if(line.empty()) {
600✔
54
         continue;
27✔
55
      }
56
      if(line[0] == '#') {
583✔
57
         continue;
10✔
58
      }
59

60
      std::vector<std::string> parts = Botan::split_on(line, delim);
573✔
61

62
      if(parts.size() != 2) {
573✔
63
         throw Test_Error("Invalid line " + line);
×
64
      }
65

66
      m[parts[0]] = parts[1];
573✔
67
   }
573✔
68

69
   return m;
18✔
70
}
9✔
71

72
std::vector<Botan::X509_Certificate> load_cert_file(const std::string& filename) {
80✔
73
   Botan::DataSource_Stream in(filename);
80✔
74

75
   std::vector<Botan::X509_Certificate> certs;
80✔
76
   while(!in.end_of_data()) {
508✔
77
      try {
348✔
78
         certs.emplace_back(in);
348✔
79
      } catch(Botan::Decoding_Error&) {}
80✔
80
   }
81

82
   return certs;
80✔
83
}
80✔
84

85
std::set<Botan::Certificate_Status_Code> flatten(const Botan::CertificatePathStatusCodes& codes) {
4✔
86
   std::set<Botan::Certificate_Status_Code> result;
4✔
87

88
   for(const auto& statuses : codes) {
16✔
89
      result.insert(statuses.begin(), statuses.end());
12✔
90
   }
91

92
   return result;
4✔
93
}
×
94

95
Botan::Path_Validation_Restrictions get_default_restrictions(bool req_revocation_info, bool ocsp_all_intermediates) {
5✔
96
   return Botan::Path_Validation_Restrictions(req_revocation_info, 80 /*some tests use SHA-1*/, ocsp_all_intermediates);
10✔
97
}
98

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

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

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

130
   private:
131
      std::vector<Test::Result> run_with_restrictions(const Botan::Path_Validation_Restrictions& restrictions) {
2✔
132
         std::vector<Test::Result> results;
2✔
133

134
         // Test certs generated by https://github.com/yymax/x509test
135
         const Botan::X509_Certificate root(Test::data_file("x509/x509test/root.pem"));
4✔
136
         Botan::Certificate_Store_In_Memory trusted;
2✔
137
         trusted.add_certificate(root);
2✔
138

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

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

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

148
            if(certs.empty()) {
74✔
149
               throw Test_Error("Failed to read certs from " + filename);
×
150
            }
151

152
            Botan::Path_Validation_Result path_result = Botan::x509_path_validate(
74✔
153
               certs, restrictions, trusted, "www.tls.test", Botan::Usage_Type::TLS_SERVER_AUTH, validation_time);
74✔
154

155
            if(path_result.successful_validation() && path_result.trust_root() != root) {
74✔
156
               path_result = Botan::Path_Validation_Result(Botan::Certificate_Status_Code::CANNOT_ESTABLISH_TRUST);
×
157
            }
158

159
            result.test_eq("test " + filename, path_result.result_string(), expected_result);
148✔
160
            result.test_eq("test no warnings string", path_result.warnings_string(), "");
148✔
161
            result.confirm("test no warnings", path_result.no_warnings());
148✔
162
            result.end_timer();
74✔
163
            results.push_back(result);
74✔
164
         }
74✔
165

166
         // test softfail
167
         {
2✔
168
            Test::Result result("X509test path validation softfail");
2✔
169
            result.start_timer();
2✔
170

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

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

189
            if(path_result.successful_validation() && path_result.trust_root() != root) {
2✔
190
               path_result = Botan::Path_Validation_Result(Botan::Certificate_Status_Code::CANNOT_ESTABLISH_TRUST);
×
191
            }
192

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

205
         return results;
2✔
206
      }
2✔
207
};
208

209
BOTAN_REGISTER_TEST("x509", "x509_path_x509test", X509test_Path_Validation_Tests);
210

211
class NIST_Path_Validation_Tests final : public Test {
1✔
212
   public:
213
      std::vector<Test::Result> run() override;
214

215
   private:
216
      std::vector<Test::Result> run_with_restrictions(const Botan::Path_Validation_Restrictions& restrictions);
217
};
218

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

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

234
   std::vector<Test::Result> results;
2✔
235

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

250
   const Botan::X509_Certificate root_cert(Test::data_file("x509/nist/root.crt"));
4✔
251
   const Botan::X509_CRL root_crl(Test::data_file("x509/nist/root.crl"));
4✔
252

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

255
   for(const auto& [test_name, expected_result] : expected) {
154✔
256
      Test::Result result("NIST path validation");
152✔
257
      result.start_timer();
152✔
258

259
      try {
152✔
260
         const auto all_files = Test::files_in_data_dir("x509/nist/" + test_name);
152✔
261

262
         Botan::Certificate_Store_In_Memory store;
152✔
263
         store.add_certificate(root_cert);
152✔
264
         store.add_crl(root_crl);
152✔
265

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

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

279
         const Botan::Path_Validation_Result validation_result = Botan::x509_path_validate(
152✔
280
            end_certs, restrictions, store, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
152✔
281

282
         result.test_eq(test_name + " path validation result", validation_result.result_string(), expected_result);
304✔
283
      } catch(std::exception& e) {
152✔
284
         result.test_failure(test_name, e.what());
×
285
      }
×
286

287
      result.end_timer();
152✔
288
      results.push_back(result);
152✔
289
   }
152✔
290

291
   return results;
2✔
292
}
154✔
293

294
BOTAN_REGISTER_TEST("x509", "x509_path_nist", NIST_Path_Validation_Tests);
295

296
class Extended_Path_Validation_Tests final : public Test {
1✔
297
   public:
298
      std::vector<Test::Result> run() override;
299
};
300

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

306
   std::vector<Test::Result> results;
1✔
307

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

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

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

316
      Botan::Certificate_Store_In_Memory store;
3✔
317

318
      for(const auto& file : all_files) {
13✔
319
         if(file.ends_with(".crt") && file != "end.crt") {
10✔
320
            store.add_certificate(Botan::X509_Certificate(file));
10✔
321
         }
322
      }
323

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

326
      const Botan::Path_Validation_Restrictions restrictions;
6✔
327
      const Botan::Path_Validation_Result validation_result =
3✔
328
         Botan::x509_path_validate(end_user, restrictions, store, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
3✔
329

330
      result.test_eq(test_name + " path validation result", validation_result.result_string(), expected_result);
6✔
331

332
      result.end_timer();
3✔
333
      results.push_back(result);
3✔
334
   }
3✔
335

336
   return results;
1✔
337
}
1✔
338

339
BOTAN_REGISTER_TEST("x509", "x509_path_extended", Extended_Path_Validation_Tests);
340

341
      #if defined(BOTAN_HAS_PSS)
342

343
class PSS_Path_Validation_Tests : public Test {
1✔
344
   public:
345
      std::vector<Test::Result> run() override;
346
};
347

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

353
   std::vector<Test::Result> results;
1✔
354

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

357
   auto validation_times_iter = validation_times.begin();
1✔
358

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

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

365
      std::optional<Botan::X509_CRL> crl;
118✔
366
      std::optional<Botan::X509_Certificate> end;
118✔
367
      std::optional<Botan::X509_Certificate> root;
118✔
368
      Botan::Certificate_Store_In_Memory store;
118✔
369
      std::optional<Botan::PKCS10_Request> csr;
118✔
370

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

373
      const auto validation_time = Botan::calendar_point(validation_year, 0, 0, 0, 0, 0).to_std_timepoint();
118✔
374

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

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

397
         result.test_eq(test_name + " check_crl result",
12✔
398
                        Botan::Path_Validation_Result::status_string(Botan::PKIX::overall_status(crl_status)),
399
                        expected_result);
400
      } else if(end && root) {
118✔
401
         // CRT chain test
402

403
         const Botan::Path_Validation_Restrictions restrictions(false, 80);  // SHA-1 is used
182✔
404

405
         const Botan::Path_Validation_Result validation_result =
91✔
406
            Botan::x509_path_validate(*end, restrictions, store, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
91✔
407

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

421
      result.end_timer();
118✔
422
      results.push_back(result);
118✔
423
   }
334✔
424

425
   return results;
1✔
426
}
19✔
427

428
BOTAN_REGISTER_TEST("x509", "x509_path_rsa_pss", PSS_Path_Validation_Tests);
429

430
      #endif
431

432
class Validate_V1Cert_Test final : public Test {
1✔
433
   public:
434
      std::vector<Test::Result> run() override;
435
};
436

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

442
   const std::vector<Test::Result> results;
1✔
443

444
   const std::string root_crt = Test::data_file("x509/misc/v1ca/root.pem");
1✔
445
   const std::string int_crt = Test::data_file("x509/misc/v1ca/int.pem");
1✔
446
   const std::string ee_crt = Test::data_file("x509/misc/v1ca/ee.pem");
1✔
447

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

450
   const Botan::X509_Certificate root(root_crt);
1✔
451
   const Botan::X509_Certificate intermediate(int_crt);
1✔
452
   const Botan::X509_Certificate ee_cert(ee_crt);
1✔
453

454
   Botan::Certificate_Store_In_Memory trusted;
1✔
455
   trusted.add_certificate(root);
1✔
456

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

459
   const Botan::Path_Validation_Restrictions restrictions;
2✔
460
   const Botan::Path_Validation_Result validation_result =
1✔
461
      Botan::x509_path_validate(chain, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
1✔
462

463
   Test::Result result("Verifying using v1 certificate");
1✔
464
   result.test_eq("Path validation result", validation_result.result_string(), "Verified");
2✔
465

466
   const Botan::Certificate_Store_In_Memory empty;
1✔
467

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

470
   const Botan::Path_Validation_Result validation_result2 =
1✔
471
      Botan::x509_path_validate(new_chain, restrictions, empty, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
1✔
472

473
   result.test_eq("Path validation result", validation_result2.result_string(), "Cannot establish trust");
2✔
474

475
   return {result};
2✔
476
}
4✔
477

478
BOTAN_REGISTER_TEST("x509", "x509_v1_ca", Validate_V1Cert_Test);
479

480
class Validate_V2Uid_in_V1_Test final : public Test {
1✔
481
   public:
482
      std::vector<Test::Result> run() override;
483
};
484

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

490
   const std::vector<Test::Result> results;
1✔
491

492
   const std::string root_crt = Test::data_file("x509/v2-in-v1/root.pem");
1✔
493
   const std::string int_crt = Test::data_file("x509/v2-in-v1/int.pem");
1✔
494
   const std::string ee_crt = Test::data_file("x509/v2-in-v1/leaf.pem");
1✔
495

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

498
   const Botan::X509_Certificate root(root_crt);
1✔
499
   const Botan::X509_Certificate intermediate(int_crt);
1✔
500
   const Botan::X509_Certificate ee_cert(ee_crt);
1✔
501

502
   Botan::Certificate_Store_In_Memory trusted;
1✔
503
   trusted.add_certificate(root);
1✔
504

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

507
   const Botan::Path_Validation_Restrictions restrictions;
2✔
508
   const Botan::Path_Validation_Result validation_result =
1✔
509
      Botan::x509_path_validate(chain, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
1✔
510

511
   Test::Result result("Verifying v1 certificate using v2 uid fields");
1✔
512
   result.test_eq("Path validation failed", validation_result.successful_validation(), false);
1✔
513
   result.test_eq(
3✔
514
      "Path validation result", validation_result.result_string(), "Encountered v2 identifiers in v1 certificate");
2✔
515

516
   return {result};
2✔
517
}
3✔
518

519
BOTAN_REGISTER_TEST("x509", "x509_v2uid_in_v1", Validate_V2Uid_in_V1_Test);
520

521
class Validate_Name_Constraint_SAN_Test final : public Test {
1✔
522
   public:
523
      std::vector<Test::Result> run() override;
524
};
525

526
std::vector<Test::Result> Validate_Name_Constraint_SAN_Test::run() {
1✔
527
   if(Botan::has_filesystem_impl() == false) {
1✔
528
      return {Test::Result::Note("Path validation", "Skipping due to missing filesystem access")};
×
529
   }
530

531
   const std::vector<Test::Result> results;
1✔
532

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

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

539
   const Botan::X509_Certificate root(root_crt);
1✔
540
   const Botan::X509_Certificate intermediate(int_crt);
1✔
541
   const Botan::X509_Certificate ee_cert(ee_crt);
1✔
542

543
   Botan::Certificate_Store_In_Memory trusted;
1✔
544
   trusted.add_certificate(root);
1✔
545

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

548
   const Botan::Path_Validation_Restrictions restrictions;
2✔
549
   const Botan::Path_Validation_Result validation_result =
1✔
550
      Botan::x509_path_validate(chain, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
1✔
551

552
   Test::Result result("Verifying certificate with alternative SAN violating name constraint");
1✔
553
   result.test_eq("Path validation failed", validation_result.successful_validation(), false);
1✔
554
   result.test_eq(
3✔
555
      "Path validation result", validation_result.result_string(), "Certificate does not pass name constraint");
2✔
556

557
   return {result};
2✔
558
}
3✔
559

560
BOTAN_REGISTER_TEST("x509", "x509_name_constraint_san", Validate_Name_Constraint_SAN_Test);
561

562
class Validate_Name_Constraint_CaseInsensitive final : public Test {
1✔
563
   public:
564
      std::vector<Test::Result> run() override;
565
};
566

567
std::vector<Test::Result> Validate_Name_Constraint_CaseInsensitive::run() {
1✔
568
   if(Botan::has_filesystem_impl() == false) {
1✔
569
      return {Test::Result::Note("Path validation", "Skipping due to missing filesystem access")};
×
570
   }
571

572
   const std::vector<Test::Result> results;
1✔
573

574
   const std::string root_crt = Test::data_file("x509/misc/name_constraint_ci/root.pem");
1✔
575
   const std::string int_crt = Test::data_file("x509/misc/name_constraint_ci/int.pem");
1✔
576
   const std::string ee_crt = Test::data_file("x509/misc/name_constraint_ci/leaf.pem");
1✔
577

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

580
   const Botan::X509_Certificate root(root_crt);
1✔
581
   const Botan::X509_Certificate intermediate(int_crt);
1✔
582
   const Botan::X509_Certificate ee_cert(ee_crt);
1✔
583

584
   Botan::Certificate_Store_In_Memory trusted;
1✔
585
   trusted.add_certificate(root);
1✔
586

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

589
   const Botan::Path_Validation_Restrictions restrictions;
2✔
590
   const Botan::Path_Validation_Result validation_result =
1✔
591
      Botan::x509_path_validate(chain, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
1✔
592

593
   Test::Result result("DNS name constraints are case insensitive");
1✔
594
   result.test_eq("Path validation succeeded", validation_result.successful_validation(), true);
1✔
595

596
   return {result};
2✔
597
}
3✔
598

599
BOTAN_REGISTER_TEST("x509", "x509_name_constraint_ci", Validate_Name_Constraint_CaseInsensitive);
600

601
class Validate_Name_Constraint_NoCheckSelf final : public Test {
1✔
602
   public:
603
      std::vector<Test::Result> run() override;
604
};
605

606
std::vector<Test::Result> Validate_Name_Constraint_NoCheckSelf::run() {
1✔
607
   if(Botan::has_filesystem_impl() == false) {
1✔
608
      return {Test::Result::Note("Path validation", "Skipping due to missing filesystem access")};
×
609
   }
610

611
   const std::vector<Test::Result> results;
1✔
612

613
   const std::string root_crt = Test::data_file("x509/misc/nc_skip_self/root.pem");
1✔
614
   const std::string int_crt = Test::data_file("x509/misc/nc_skip_self/int.pem");
1✔
615
   const std::string ee_crt = Test::data_file("x509/misc/nc_skip_self/leaf.pem");
1✔
616

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

619
   const Botan::X509_Certificate root(root_crt);
1✔
620
   const Botan::X509_Certificate intermediate(int_crt);
1✔
621
   const Botan::X509_Certificate ee_cert(ee_crt);
1✔
622

623
   Botan::Certificate_Store_In_Memory trusted;
1✔
624
   trusted.add_certificate(root);
1✔
625

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

628
   const Botan::Path_Validation_Restrictions restrictions;
2✔
629
   const Botan::Path_Validation_Result validation_result =
1✔
630
      Botan::x509_path_validate(chain, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
1✔
631

632
   Test::Result result("Name constraints do not apply to the certificate which includes them");
1✔
633
   result.test_eq("Path validation succeeded", validation_result.successful_validation(), true);
1✔
634

635
   return {result};
2✔
636
}
3✔
637

638
BOTAN_REGISTER_TEST("x509", "x509_name_constraint_no_check_self", Validate_Name_Constraint_NoCheckSelf);
639

640
class Root_Cert_Time_Check_Test final : public Test {
1✔
641
   public:
642
      std::vector<Test::Result> run() override {
1✔
643
         if(Botan::has_filesystem_impl() == false) {
1✔
644
            return {Test::Result::Note("Path validation", "Skipping due to missing filesystem access")};
×
645
         }
646

647
         const std::string trusted_root_crt = Test::data_file("x509/misc/root_cert_time_check/root.crt");
1✔
648
         const std::string leaf_crt = Test::data_file("x509/misc/root_cert_time_check/leaf.crt");
1✔
649

650
         const Botan::X509_Certificate trusted_root_cert(trusted_root_crt);
1✔
651
         const Botan::X509_Certificate leaf_cert(leaf_crt);
1✔
652

653
         Botan::Certificate_Store_In_Memory trusted;
1✔
654
         trusted.add_certificate(trusted_root_cert);
1✔
655

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

658
         Test::Result result("Root cert time check");
1✔
659

660
         auto assert_path_validation_result = [&](std::string_view descr,
11✔
661
                                                  bool ignore_trusted_root_time_range,
662
                                                  uint32_t year,
663
                                                  Botan::Certificate_Status_Code exp_status,
664
                                                  std::optional<Botan::Certificate_Status_Code> exp_warning =
665
                                                     std::nullopt) {
666
            const Botan::Path_Validation_Restrictions restrictions(
10✔
667
               false,
668
               110,
669
               false,
670
               std::chrono::seconds::zero(),
671
               std::make_unique<Botan::Certificate_Store_In_Memory>(),
×
672
               ignore_trusted_root_time_range);
20✔
673

674
            const Botan::Path_Validation_Result validation_result =
10✔
675
               Botan::x509_path_validate(chain,
10✔
676
                                         restrictions,
677
                                         trusted,
10✔
678
                                         "",
679
                                         Botan::Usage_Type::UNSPECIFIED,
680
                                         Botan::calendar_point(year, 1, 1, 1, 0, 0).to_std_timepoint());
10✔
681
            const std::string descr_str = Botan::fmt(
10✔
682
               "Root cert validity range {}: {}", ignore_trusted_root_time_range ? "ignored" : "checked", descr);
15✔
683

684
            result.test_is_eq(descr_str, validation_result.result(), exp_status);
10✔
685
            const auto warnings = validation_result.warnings();
10✔
686
            BOTAN_ASSERT_NOMSG(warnings.size() == 2);
10✔
687
            result.confirm("No warning for leaf cert", warnings.at(0).empty());
20✔
688
            if(exp_warning) {
10✔
689
               result.confirm("Warning for root cert",
12✔
690
                              warnings.at(1).size() == 1 && warnings.at(1).contains(*exp_warning));
8✔
691
            } else {
692
               result.confirm("No warning for root cert", warnings.at(1).empty());
12✔
693
            }
694
         };
10✔
695
         // (Trusted) root cert validity range: 2022-2028
696
         // Leaf cert validity range: 2020-2030
697

698
         // Trusted root time range is checked
699
         assert_path_validation_result(
1✔
700
            "Root and leaf certs in validity range", false, 2025, Botan::Certificate_Status_Code::OK);
701
         assert_path_validation_result(
1✔
702
            "Root and leaf certs are expired", false, 2031, Botan::Certificate_Status_Code::CERT_HAS_EXPIRED);
703
         assert_path_validation_result(
1✔
704
            "Root and leaf certs are not yet valid", false, 2019, Botan::Certificate_Status_Code::CERT_NOT_YET_VALID);
705
         assert_path_validation_result(
1✔
706
            "Root cert is expired, leaf cert not", false, 2029, Botan::Certificate_Status_Code::CERT_HAS_EXPIRED);
707
         assert_path_validation_result("Root cert is not yet valid, leaf cert is",
1✔
708
                                       false,
709
                                       2021,
710
                                       Botan::Certificate_Status_Code::CERT_NOT_YET_VALID);
711

712
         // Trusted root time range is ignored
713
         assert_path_validation_result(
1✔
714
            "Root and leaf certs in validity range", true, 2025, Botan::Certificate_Status_Code::OK);
715
         assert_path_validation_result("Root and leaf certs are expired",
2✔
716
                                       true,
717
                                       2031,
718
                                       Botan::Certificate_Status_Code::CERT_HAS_EXPIRED,
719
                                       Botan::Certificate_Status_Code::TRUSTED_CERT_HAS_EXPIRED);
1✔
720
         assert_path_validation_result("Root and leaf certs are not yet valid",
2✔
721
                                       true,
722
                                       2019,
723
                                       Botan::Certificate_Status_Code::CERT_NOT_YET_VALID,
724
                                       Botan::Certificate_Status_Code::TRUSTED_CERT_NOT_YET_VALID);
1✔
725
         assert_path_validation_result("Root cert is expired, leaf cert not",
2✔
726
                                       true,
727
                                       2029,
728
                                       Botan::Certificate_Status_Code::OK,
729
                                       Botan::Certificate_Status_Code::TRUSTED_CERT_HAS_EXPIRED);
1✔
730
         assert_path_validation_result("Root cert is not yet valid, leaf cert is",
2✔
731
                                       true,
732
                                       2021,
733
                                       Botan::Certificate_Status_Code::OK,
734
                                       Botan::Certificate_Status_Code::TRUSTED_CERT_NOT_YET_VALID);
1✔
735

736
         return {result};
2✔
737
      }
3✔
738
};
739

740
BOTAN_REGISTER_TEST("x509", "x509_root_cert_time_check", Root_Cert_Time_Check_Test);
741

742
class Non_Self_Signed_Trust_Anchors_Test final : public Test {
1✔
743
   private:
744
      using Cert_Path = std::vector<Botan::X509_Certificate>;
745

746
      std::vector<Botan::X509_Certificate> get_valid_cert_chain() {
4✔
747
         // Test certs generated by https://github.com/yymax/x509test
748
         const std::string valid_chain_pem = "x509/x509test/ValidChained.pem";
4✔
749
         auto certs = load_cert_file(Test::data_file(valid_chain_pem));
4✔
750
         if(certs.size() != 5) {
4✔
751
            throw Test_Error(Botan::fmt("Failed to read all certs from {}", valid_chain_pem));
×
752
         }
753
         return certs;
4✔
754
      }
4✔
755

756
      /// Time point fits for all certificates used in this class
757
      std::chrono::system_clock::time_point get_validation_time() {
8✔
758
         return Botan::calendar_point(2016, 10, 21, 4, 20, 0).to_std_timepoint();
16✔
759
      }
760

761
      Botan::X509_Certificate get_self_signed_ee_cert() {
1✔
762
         const std::string valid_chain_pem = "x509/misc/self-signed-end-entity.pem";
1✔
763
         return Botan::X509_Certificate(Test::data_file(valid_chain_pem));
2✔
764
      }
1✔
765

766
      std::vector<Test::Result> path_validate_test() {
1✔
767
         std::vector<Test::Result> results;
1✔
768

769
         const auto restrictions = get_allow_non_self_signed_anchors_restrictions(false, false);
1✔
770
         const auto certs = get_valid_cert_chain();
1✔
771
         const auto validation_time = get_validation_time();
1✔
772

773
         const std::vector<std::tuple<std::string_view, Cert_Path, Botan::X509_Certificate>>
1✔
774
            info_w_end_certs_w_trust_anchor{
775
               {"length 1", Cert_Path{certs.at(0)}, certs.at(0)},
4✔
776
               {"length 2", Cert_Path{certs.at(0), certs.at(1)}, certs.at(1)},
5✔
777
               {"length 3 (store not in chain)", Cert_Path{certs.at(0), certs.at(1)}, certs.at(2)},
5✔
778
               {"length 4 (shuffled)", Cert_Path{certs.at(0), certs.at(3), certs.at(2), certs.at(1)}, certs.at(3)},
7✔
779
               {"full", Cert_Path{certs.at(0), certs.at(1), certs.at(2), certs.at(3), certs.at(4)}, certs.at(4)},
8✔
780
            };
6✔
781

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

786
            const Botan::Certificate_Store_In_Memory trusted(trust_anchor);
5✔
787

788
            auto path_result = Botan::x509_path_validate(
5✔
789
               end_certs, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
5✔
790

791
            if(path_result.successful_validation() && path_result.trust_root() != trust_anchor) {
5✔
792
               path_result = Botan::Path_Validation_Result(Botan::Certificate_Status_Code::CANNOT_ESTABLISH_TRUST);
×
793
            }
794

795
            result.test_eq(
15✔
796
               "path validation failed", path_result.result_string(), to_string(Botan::Certificate_Status_Code::OK));
10✔
797

798
            results.push_back(result);
5✔
799
         }
5✔
800
         return results;
1✔
801
      }
7✔
802

803
      std::vector<Test::Result> build_path_test() {
1✔
804
         std::vector<Test::Result> results;
1✔
805

806
         const auto restrictions = get_allow_non_self_signed_anchors_restrictions(false, false);
1✔
807
         const auto certs = get_valid_cert_chain();
1✔
808

809
         // Helper to create a cert path {certs[0],...,certs[last_cert]}
810
         const auto path_to = [&](size_t last_cert) -> Cert_Path {
20✔
811
            BOTAN_ASSERT_NOMSG(last_cert <= certs.size());
19✔
812
            return {certs.begin(), certs.begin() + last_cert + 1};
19✔
813
         };
1✔
814

815
         // Helper to create a cert store of all certificates in certs given by their indices
816
         const auto store_of = [&](auto... cert_indices) -> Botan::Certificate_Store_In_Memory {
6✔
817
            Botan::Certificate_Store_In_Memory cert_store;
5✔
818
            (cert_store.add_certificate(certs.at(cert_indices)), ...);
5✔
819
            return cert_store;
5✔
820
         };
1✔
821

822
         const std::vector<std::tuple<std::string, Botan::Certificate_Store_In_Memory, std::vector<Cert_Path>>>
1✔
823
            test_names_with_stores_with_expected_paths{
824
               {"root in store", store_of(4), std::vector<Cert_Path>{path_to(4)}},
4✔
825
               {"intermediate in store", store_of(2), std::vector<Cert_Path>{path_to(2)}},
3✔
826
               {"leaf in store", store_of(0), std::vector<Cert_Path>{path_to(0)}},
3✔
827
               {"leaf, intermediate, and root in store",
828
                store_of(0, 1, 4),
1✔
829
                std::vector<Cert_Path>{path_to(0), path_to(1), path_to(4)}},
5✔
830
               {"all in store",
831
                store_of(0, 1, 2, 3, 4),
1✔
832
                std::vector<Cert_Path>{path_to(0), path_to(1), path_to(2), path_to(3), path_to(4)}},
7✔
833
            };
6✔
834

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

838
            std::vector<Cert_Path> cert_paths;
5✔
839
            const auto build_all_res =
5✔
840
               Botan::PKIX::build_all_certificate_paths(cert_paths, {&cert_store}, certs.at(0), certs);
5✔
841
            result.test_is_eq("build_all_certificate_paths result",
5✔
842
                              to_string(build_all_res),
5✔
843
                              to_string(Botan::Certificate_Status_Code::OK));
5✔
844
            result.test_is_eq("build_all_certificate_paths paths", cert_paths, expected_paths);
5✔
845

846
            Cert_Path cert_path;
5✔
847
            const auto build_path_res =
5✔
848
               Botan::PKIX::build_certificate_path(cert_path, {&cert_store}, certs.at(0), certs);
5✔
849
            result.test_is_eq("build_certificate_path result",
5✔
850
                              to_string(build_path_res),
5✔
851
                              to_string(Botan::Certificate_Status_Code::OK));
5✔
852

853
            if(std::ranges::find(cert_paths, path_to(4)) != cert_paths.end()) {
10✔
854
               result.test_is_eq("build_certificate_path (with self-signed anchor)", cert_path, path_to(4));
6✔
855
            } else {
856
               result.test_is_eq(
4✔
857
                  "build_certificate_path (without self-signed anchor)", cert_path, expected_paths.at(0));
2✔
858
            }
859
            results.push_back(result);
5✔
860
         }
5✔
861

862
         return results;
1✔
863
      }
7✔
864

865
      std::vector<Test::Result> forbidden_self_signed_trust_anchors_test() {
1✔
866
         const auto restrictions = get_default_restrictions(false, false);  // non-self-signed anchors are forbidden
1✔
867
         const auto certs = get_valid_cert_chain();
1✔
868
         const auto validation_time = get_validation_time();
1✔
869

870
         Botan::Certificate_Store_In_Memory cert_store(certs.at(3));
1✔
871

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

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

877
         result.test_eq("unexpected path validation result",
3✔
878
                        path_result.result_string(),
2✔
879
                        to_string(Botan::Certificate_Status_Code::CANNOT_ESTABLISH_TRUST));
880

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

884
         result.test_ne("unexpected check_chain result",
3✔
885
                        Botan::Path_Validation_Result(check_chain_result, {}).result_string(),
2✔
886
                        to_string(Botan::Certificate_Status_Code::OK));
887

888
         return {result};
3✔
889
      }
3✔
890

891
      Test::Result stand_alone_root_test(std::string test_name,
6✔
892
                                         const Botan::Path_Validation_Restrictions& restrictions,
893
                                         const Botan::X509_Certificate& standalone_cert,
894
                                         Botan::Certificate_Status_Code expected_result) {
895
         Test::Result result(std::move(test_name));
6✔
896

897
         const auto validation_time = get_validation_time();
6✔
898
         Botan::Certificate_Store_In_Memory cert_store(standalone_cert);
6✔
899

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

903
         result.test_eq(
18✔
904
            "unexpected x509_path_validate result", path_result.result_string(), to_string(expected_result));
12✔
905

906
         return result;
6✔
907
      }
6✔
908

909
      std::vector<Test::Result> stand_alone_root_tests() {
1✔
910
         const auto self_signed_trust_anchor_forbidden = get_default_restrictions(false, false);
1✔
911
         const auto self_signed_trust_anchor_allowed = get_allow_non_self_signed_anchors_restrictions(false, false);
1✔
912

913
         const auto cert_chain = get_valid_cert_chain();
1✔
914
         const auto& self_signed_root = cert_chain.at(4);
1✔
915
         const auto& ica = cert_chain.at(3);
1✔
916
         const auto& self_signed_ee = get_self_signed_ee_cert();
1✔
917

918
         return {
1✔
919
            stand_alone_root_test("Standalone self-signed root (non-self-signed trust anchors forbidden)",
1✔
920
                                  self_signed_trust_anchor_forbidden,
921
                                  self_signed_root,
922
                                  Botan::Certificate_Status_Code::OK),
923
            stand_alone_root_test("Standalone self-signed root (non-self-signed trust anchors allowed)",
1✔
924
                                  self_signed_trust_anchor_allowed,
925
                                  self_signed_root,
926
                                  Botan::Certificate_Status_Code::OK),
927

928
            stand_alone_root_test("Standalone self-signed end entity (non-self-signed trust anchors forbidden)",
1✔
929
                                  self_signed_trust_anchor_forbidden,
930
                                  self_signed_ee,
931
                                  Botan::Certificate_Status_Code::OK),
932
            stand_alone_root_test("Standalone self-signed end entity (non-self-signed trust anchors allowed)",
1✔
933
                                  self_signed_trust_anchor_allowed,
934
                                  self_signed_ee,
935
                                  Botan::Certificate_Status_Code::OK),
936

937
            stand_alone_root_test("Standalone intermediate certificate (non-self-signed trust anchors forbidden)",
1✔
938
                                  self_signed_trust_anchor_forbidden,
939
                                  ica,
940
                                  Botan::Certificate_Status_Code::CANNOT_ESTABLISH_TRUST),
941
            stand_alone_root_test("Standalone intermediate certificate (non-self-signed trust anchors allowed)",
1✔
942
                                  self_signed_trust_anchor_allowed,
943
                                  ica,
944
                                  Botan::Certificate_Status_Code::OK),
945
         };
8✔
946
      }
7✔
947

948
   public:
949
      std::vector<Test::Result> run() override {
1✔
950
         std::vector<Test::Result> results;
1✔
951

952
         auto res = path_validate_test();
1✔
953
         results.insert(results.end(), res.begin(), res.end());
1✔
954

955
         res = build_path_test();
1✔
956
         results.insert(results.end(), res.begin(), res.end());
1✔
957

958
         res = forbidden_self_signed_trust_anchors_test();
1✔
959
         results.insert(results.end(), res.begin(), res.end());
1✔
960

961
         res = stand_alone_root_tests();
1✔
962
         results.insert(results.end(), res.begin(), res.end());
1✔
963

964
         return results;
1✔
965
      }
1✔
966
};
967

968
BOTAN_REGISTER_TEST("x509", "x509_non_self_signed_trust_anchors", Non_Self_Signed_Trust_Anchors_Test);
969

970
class BSI_Path_Validation_Tests final : public Test
1✔
971

972
{
973
   public:
974
      std::vector<Test::Result> run() override;
975

976
   private:
977
      std::vector<Test::Result> run_with_restrictions(const Botan::Path_Validation_Restrictions& restrictions);
978
};
979

980
std::vector<Test::Result> BSI_Path_Validation_Tests::run() {
1✔
981
   std::vector<Test::Result> results;
1✔
982
   for(const auto& restrictions : restrictions_to_test(false, false)) {
3✔
983
      auto partial_res = run_with_restrictions(restrictions);
2✔
984
      results.insert(results.end(), partial_res.begin(), partial_res.end());
2✔
985
   }
3✔
986
   return results;
1✔
987
}
×
988

989
std::vector<Test::Result> BSI_Path_Validation_Tests::run_with_restrictions(
2✔
990
   const Botan::Path_Validation_Restrictions& restriction_template) {
991
   if(Botan::has_filesystem_impl() == false) {
2✔
992
      return {Test::Result::Note("BSI path validation", "Skipping due to missing filesystem access")};
×
993
   }
994

995
   std::vector<Test::Result> results;
2✔
996

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

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

1003
      Botan::Certificate_Store_In_Memory trusted;
108✔
1004
      std::vector<Botan::X509_Certificate> certs;
108✔
1005

1006
      #if defined(BOTAN_HAS_MD5)
1007
      const bool has_md5 = true;
108✔
1008
      #else
1009
      const bool has_md5 = false;
1010
      #endif
1011

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

1014
      // By convention: if CRL is a substring if the test name,
1015
      // we need to check the CRLs
1016
      bool use_crl = false;
108✔
1017
      if(test_name.find("CRL") != std::string::npos) {
108✔
1018
         use_crl = true;
32✔
1019
      }
1020

1021
      try {
108✔
1022
         for(const auto& file : all_files) {
890✔
1023
            // found a trust anchor
1024
            if(file.find("TA") != std::string::npos) {
790✔
1025
               trusted.add_certificate(Botan::X509_Certificate(file));
100✔
1026
            }
1027
            // found the target certificate. It needs to be at the front of certs
1028
            else if(file.find("TC") != std::string::npos) {
690✔
1029
               certs.insert(certs.begin(), Botan::X509_Certificate(file));
108✔
1030
            }
1031
            // found a certificate that might be part of a valid certificate chain to the trust anchor
1032
            else if(file.find(".crt") != std::string::npos) {
582✔
1033
               certs.push_back(Botan::X509_Certificate(file));
224✔
1034
            } else if(file.find(".crl") != std::string::npos) {
470✔
1035
               trusted.add_crl(Botan::X509_CRL(file));
56✔
1036
            }
1037
         }
1038

1039
         const Botan::Path_Validation_Restrictions restrictions(
100✔
1040
            use_crl,
1041
            restriction_template.minimum_key_strength(),
1042
            use_crl,
1043
            restriction_template.max_ocsp_age(),
1044
            std::make_unique<Botan::Certificate_Store_In_Memory>(),
×
1045
            restriction_template.ignore_trusted_root_time_range(),
100✔
1046
            restriction_template.require_self_signed_trust_anchors());
200✔
1047

1048
         /*
1049
          * Following the test document, the test are executed 16 times with
1050
          * randomly chosen order of the available certificates. However, the target
1051
          * certificate needs to stay in front.
1052
          * For certain test, the order in which the certificates are given to
1053
          * the validation function may be relevant, i.e. if issuer DNs are
1054
          * ambiguous.
1055
          */
1056
         class random_bit_generator {
100✔
1057
            public:
1058
               using result_type = size_t;
1059

1060
               static constexpr result_type min() { return 0; }
1061

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

1064
               result_type operator()() {
160✔
1065
                  size_t s = 0;
160✔
1066
                  m_rng.randomize(reinterpret_cast<uint8_t*>(&s), sizeof(s));
160✔
1067
                  return s;
160✔
1068
               }
1069

1070
               explicit random_bit_generator(Botan::RandomNumberGenerator& rng) : m_rng(rng) {}
100✔
1071

1072
            private:
1073
               Botan::RandomNumberGenerator& m_rng;
1074
         } rbg(this->rng());
100✔
1075

1076
         for(size_t r = 0; r < 16; r++) {
1,700✔
1077
            std::shuffle(++(certs.begin()), certs.end(), rbg);
1,600✔
1078

1079
            const Botan::Path_Validation_Result validation_result = Botan::x509_path_validate(
1,600✔
1080
               certs, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
1,600✔
1081

1082
            // We expect to be warned
1083
            if(expected_result.starts_with("Warning: ")) {
1,600✔
1084
               const std::string stripped = expected_result.substr(std::string("Warning: ").size());
64✔
1085
               bool found_warning = false;
64✔
1086
               for(const auto& warning_set : validation_result.warnings()) {
256✔
1087
                  for(const auto& warning : warning_set) {
256✔
1088
                     const std::string warning_str(Botan::to_string(warning));
64✔
1089
                     if(stripped == warning_str) {
64✔
1090
                        result.test_eq(test_name + " path validation result", warning_str, stripped);
64✔
1091
                        found_warning = true;
64✔
1092
                     }
1093
                  }
64✔
1094
               }
64✔
1095
               if(!found_warning) {
64✔
1096
                  result.test_failure(test_name, "Did not receive the expected warning: " + stripped);
×
1097
               }
1098
            } else {
64✔
1099
               if(expected_result == "Hash function used is considered too weak for security" && has_md5 == false) {
1,536✔
1100
                  result.test_eq(test_name + " path validation result",
1101
                                 validation_result.result_string(),
1102
                                 "Certificate signed with unknown/unavailable algorithm");
1103
               } else {
1104
                  result.test_eq(
1,536✔
1105
                     test_name + " path validation result", validation_result.result_string(), expected_result);
3,072✔
1106
               }
1107
            }
1108
         }
1,600✔
1109
      }
100✔
1110

1111
      /* Some certificates are rejected when executing the X509_Certificate constructor
1112
       * by throwing a Decoding_Error exception.
1113
       */
1114
      catch(const Botan::Exception& e) {
8✔
1115
         if(e.error_type() == Botan::ErrorType::DecodingFailure) {
8✔
1116
            result.test_eq(test_name + " path validation result", e.what(), expected_result);
16✔
1117
         } else {
1118
            result.test_failure(test_name, e.what());
×
1119
         }
1120
      }
8✔
1121

1122
      result.end_timer();
108✔
1123
      results.push_back(result);
108✔
1124
   }
108✔
1125

1126
   return results;
2✔
1127
}
2✔
1128

1129
BOTAN_REGISTER_TEST("x509", "x509_path_bsi", BSI_Path_Validation_Tests);
1130

1131
class Path_Validation_With_OCSP_Tests final : public Test {
1✔
1132
   public:
1133
      static Botan::X509_Certificate load_test_X509_cert(const std::string& path) {
24✔
1134
         return Botan::X509_Certificate(Test::data_file(path));
48✔
1135
      }
1136

1137
      static std::optional<Botan::OCSP::Response> load_test_OCSP_resp(const std::string& path) {
12✔
1138
         return Botan::OCSP::Response(Test::read_binary_data_file(path));
36✔
1139
      }
1140

1141
      static Test::Result validate_with_ocsp_with_next_update_without_max_age() {
1✔
1142
         Test::Result result("path check with ocsp with next_update w/o max_age");
1✔
1143
         Botan::Certificate_Store_In_Memory trusted;
1✔
1144

1145
         auto restrictions = Botan::Path_Validation_Restrictions(false, 110, false);
2✔
1146

1147
         auto ee = load_test_X509_cert("x509/ocsp/randombit.pem");
1✔
1148
         auto ca = load_test_X509_cert("x509/ocsp/letsencrypt.pem");
1✔
1149
         auto trust_root = load_test_X509_cert("x509/ocsp/identrust.pem");
1✔
1150
         trusted.add_certificate(trust_root);
1✔
1151

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

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

1156
         auto check_path = [&](const std::chrono::system_clock::time_point valid_time,
5✔
1157
                               const Botan::Certificate_Status_Code expected) {
1158
            const auto path_result = Botan::x509_path_validate(cert_path,
12✔
1159
                                                               restrictions,
1160
                                                               trusted,
4✔
1161
                                                               "",
1162
                                                               Botan::Usage_Type::UNSPECIFIED,
1163
                                                               valid_time,
1164
                                                               std::chrono::milliseconds(0),
4✔
1165
                                                               {ocsp});
8✔
1166

1167
            return result.confirm(std::string("Status: '") + Botan::to_string(expected) + "' should match '" +
24✔
1168
                                     Botan::to_string(path_result.result()) + "'",
8✔
1169
                                  path_result.result() == expected);
8✔
1170
         };
8✔
1171

1172
         check_path(Botan::calendar_point(2016, 11, 11, 12, 30, 0).to_std_timepoint(),
1✔
1173
                    Botan::Certificate_Status_Code::OCSP_NOT_YET_VALID);
1174
         check_path(Botan::calendar_point(2016, 11, 18, 12, 30, 0).to_std_timepoint(),
1✔
1175
                    Botan::Certificate_Status_Code::OK);
1176
         check_path(Botan::calendar_point(2016, 11, 20, 8, 30, 0).to_std_timepoint(),
1✔
1177
                    Botan::Certificate_Status_Code::OK);
1178
         check_path(Botan::calendar_point(2016, 11, 28, 8, 30, 0).to_std_timepoint(),
1✔
1179
                    Botan::Certificate_Status_Code::OCSP_HAS_EXPIRED);
1180

1181
         return result;
2✔
1182
      }
2✔
1183

1184
      static Test::Result validate_with_ocsp_with_next_update_with_max_age() {
1✔
1185
         Test::Result result("path check with ocsp with next_update with max_age");
1✔
1186
         Botan::Certificate_Store_In_Memory trusted;
1✔
1187

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

1190
         auto ee = load_test_X509_cert("x509/ocsp/randombit.pem");
1✔
1191
         auto ca = load_test_X509_cert("x509/ocsp/letsencrypt.pem");
1✔
1192
         auto trust_root = load_test_X509_cert("x509/ocsp/identrust.pem");
1✔
1193
         trusted.add_certificate(trust_root);
1✔
1194

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

1197
         auto ocsp = load_test_OCSP_resp("x509/ocsp/randombit_ocsp.der");
1✔
1198

1199
         auto check_path = [&](const std::chrono::system_clock::time_point valid_time,
5✔
1200
                               const Botan::Certificate_Status_Code expected) {
1201
            const auto path_result = Botan::x509_path_validate(cert_path,
12✔
1202
                                                               restrictions,
1203
                                                               trusted,
4✔
1204
                                                               "",
1205
                                                               Botan::Usage_Type::UNSPECIFIED,
1206
                                                               valid_time,
1207
                                                               std::chrono::milliseconds(0),
4✔
1208
                                                               {ocsp});
8✔
1209

1210
            return result.confirm(std::string("Status: '") + Botan::to_string(expected) + "' should match '" +
24✔
1211
                                     Botan::to_string(path_result.result()) + "'",
8✔
1212
                                  path_result.result() == expected);
8✔
1213
         };
12✔
1214

1215
         check_path(Botan::calendar_point(2016, 11, 11, 12, 30, 0).to_std_timepoint(),
1✔
1216
                    Botan::Certificate_Status_Code::OCSP_NOT_YET_VALID);
1217
         check_path(Botan::calendar_point(2016, 11, 18, 12, 30, 0).to_std_timepoint(),
1✔
1218
                    Botan::Certificate_Status_Code::OK);
1219
         check_path(Botan::calendar_point(2016, 11, 20, 8, 30, 0).to_std_timepoint(),
1✔
1220
                    Botan::Certificate_Status_Code::OK);
1221
         check_path(Botan::calendar_point(2016, 11, 28, 8, 30, 0).to_std_timepoint(),
1✔
1222
                    Botan::Certificate_Status_Code::OCSP_HAS_EXPIRED);
1223

1224
         return result;
2✔
1225
      }
2✔
1226

1227
      static Test::Result validate_with_ocsp_without_next_update_without_max_age() {
1✔
1228
         Test::Result result("path check with ocsp w/o next_update w/o max_age");
1✔
1229
         Botan::Certificate_Store_In_Memory trusted;
1✔
1230

1231
         auto restrictions = Botan::Path_Validation_Restrictions(false, 110, false);
2✔
1232

1233
         auto ee = load_test_X509_cert("x509/ocsp/patrickschmidt.pem");
1✔
1234
         auto ca = load_test_X509_cert("x509/ocsp/bdrive_encryption.pem");
1✔
1235
         auto trust_root = load_test_X509_cert("x509/ocsp/bdrive_root.pem");
1✔
1236

1237
         trusted.add_certificate(trust_root);
1✔
1238

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

1241
         auto ocsp = load_test_OCSP_resp("x509/ocsp/patrickschmidt_ocsp.der");
1✔
1242

1243
         auto check_path = [&](const std::chrono::system_clock::time_point valid_time,
4✔
1244
                               const Botan::Certificate_Status_Code expected) {
1245
            const auto path_result = Botan::x509_path_validate(cert_path,
9✔
1246
                                                               restrictions,
1247
                                                               trusted,
3✔
1248
                                                               "",
1249
                                                               Botan::Usage_Type::UNSPECIFIED,
1250
                                                               valid_time,
1251
                                                               std::chrono::milliseconds(0),
3✔
1252
                                                               {ocsp});
6✔
1253

1254
            return result.confirm(std::string("Status: '") + Botan::to_string(expected) + "' should match '" +
18✔
1255
                                     Botan::to_string(path_result.result()) + "'",
6✔
1256
                                  path_result.result() == expected);
6✔
1257
         };
9✔
1258

1259
         check_path(Botan::calendar_point(2019, 5, 28, 7, 0, 0).to_std_timepoint(),
1✔
1260
                    Botan::Certificate_Status_Code::OCSP_NOT_YET_VALID);
1261
         check_path(Botan::calendar_point(2019, 5, 28, 7, 30, 0).to_std_timepoint(),
1✔
1262
                    Botan::Certificate_Status_Code::OK);
1263
         check_path(Botan::calendar_point(2019, 5, 28, 8, 0, 0).to_std_timepoint(), Botan::Certificate_Status_Code::OK);
1✔
1264

1265
         return result;
2✔
1266
      }
2✔
1267

1268
      static Test::Result validate_with_ocsp_without_next_update_with_max_age() {
1✔
1269
         Test::Result result("path check with ocsp w/o next_update with max_age");
1✔
1270
         Botan::Certificate_Store_In_Memory trusted;
1✔
1271

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

1274
         auto ee = load_test_X509_cert("x509/ocsp/patrickschmidt.pem");
1✔
1275
         auto ca = load_test_X509_cert("x509/ocsp/bdrive_encryption.pem");
1✔
1276
         auto trust_root = load_test_X509_cert("x509/ocsp/bdrive_root.pem");
1✔
1277

1278
         trusted.add_certificate(trust_root);
1✔
1279

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

1282
         auto ocsp = load_test_OCSP_resp("x509/ocsp/patrickschmidt_ocsp.der");
1✔
1283

1284
         auto check_path = [&](const std::chrono::system_clock::time_point valid_time,
4✔
1285
                               const Botan::Certificate_Status_Code expected) {
1286
            const auto path_result = Botan::x509_path_validate(cert_path,
9✔
1287
                                                               restrictions,
1288
                                                               trusted,
3✔
1289
                                                               "",
1290
                                                               Botan::Usage_Type::UNSPECIFIED,
1291
                                                               valid_time,
1292
                                                               std::chrono::milliseconds(0),
3✔
1293
                                                               {ocsp});
6✔
1294

1295
            return result.confirm(std::string("Status: '") + Botan::to_string(expected) + "' should match '" +
18✔
1296
                                     Botan::to_string(path_result.result()) + "'",
6✔
1297
                                  path_result.result() == expected);
6✔
1298
         };
9✔
1299

1300
         check_path(Botan::calendar_point(2019, 5, 28, 7, 0, 0).to_std_timepoint(),
1✔
1301
                    Botan::Certificate_Status_Code::OCSP_NOT_YET_VALID);
1302
         check_path(Botan::calendar_point(2019, 5, 28, 7, 30, 0).to_std_timepoint(),
1✔
1303
                    Botan::Certificate_Status_Code::OK);
1304
         check_path(Botan::calendar_point(2019, 5, 28, 8, 0, 0).to_std_timepoint(),
1✔
1305
                    Botan::Certificate_Status_Code::OCSP_IS_TOO_OLD);
1306

1307
         return result;
2✔
1308
      }
2✔
1309

1310
      static Test::Result validate_with_ocsp_with_authorized_responder() {
1✔
1311
         Test::Result result("path check with ocsp response from authorized responder certificate");
1✔
1312
         Botan::Certificate_Store_In_Memory trusted;
1✔
1313

1314
         auto restrictions = Botan::Path_Validation_Restrictions(true,   // require revocation info
1✔
1315
                                                                 110,    // minimum key strength
1316
                                                                 true);  // OCSP for all intermediates
2✔
1317

1318
         auto ee = load_test_X509_cert("x509/ocsp/bdr.pem");
1✔
1319
         auto ca = load_test_X509_cert("x509/ocsp/bdr-int.pem");
1✔
1320
         auto trust_root = load_test_X509_cert("x509/ocsp/bdr-root.pem");
1✔
1321

1322
         // These OCSP responses are signed by an authorized OCSP responder
1323
         // certificate issued by `ca` and `trust_root` respectively. Note that
1324
         // the responder certificates contain the "OCSP No Check" extension,
1325
         // meaning that they themselves do not need a revocation check via OCSP.
1326
         auto ocsp_ee = load_test_OCSP_resp("x509/ocsp/bdr-ocsp-resp.der");
1✔
1327
         auto ocsp_ca = load_test_OCSP_resp("x509/ocsp/bdr-int-ocsp-resp.der");
1✔
1328

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

1332
         auto check_path = [&](const std::chrono::system_clock::time_point valid_time,
4✔
1333
                               const Botan::Certificate_Status_Code expected) {
1334
            const auto path_result = Botan::x509_path_validate(cert_path,
15✔
1335
                                                               restrictions,
1336
                                                               trusted,
3✔
1337
                                                               "",
1338
                                                               Botan::Usage_Type::UNSPECIFIED,
1339
                                                               valid_time,
1340
                                                               std::chrono::milliseconds(0),
3✔
1341
                                                               {ocsp_ee, ocsp_ca});
9✔
1342

1343
            return result.confirm(std::string("Status: '") + Botan::to_string(expected) + "' should match '" +
18✔
1344
                                     Botan::to_string(path_result.result()) + "'",
6✔
1345
                                  path_result.result() == expected);
6✔
1346
         };
12✔
1347

1348
         check_path(Botan::calendar_point(2022, 9, 18, 16, 30, 0).to_std_timepoint(),
1✔
1349
                    Botan::Certificate_Status_Code::OCSP_NOT_YET_VALID);
1350
         check_path(Botan::calendar_point(2022, 9, 19, 16, 30, 0).to_std_timepoint(),
1✔
1351
                    Botan::Certificate_Status_Code::OK);
1352
         check_path(Botan::calendar_point(2022, 9, 20, 16, 30, 0).to_std_timepoint(),
1✔
1353
                    Botan::Certificate_Status_Code::OCSP_HAS_EXPIRED);
1354

1355
         return result;
1✔
1356
      }
4✔
1357

1358
      static Test::Result validate_with_ocsp_with_authorized_responder_without_keyusage() {
1✔
1359
         Test::Result result(
1✔
1360
            "path check with ocsp response from authorized responder certificate (without sufficient key usage)");
1✔
1361
         Botan::Certificate_Store_In_Memory trusted;
1✔
1362

1363
         auto restrictions = Botan::Path_Validation_Restrictions(true,    // require revocation info
1✔
1364
                                                                 110,     // minimum key strength
1365
                                                                 false);  // OCSP for all intermediates
2✔
1366

1367
         // See `src/scripts/mychain_creater.sh` if you need to recreate those
1368
         auto ee = load_test_X509_cert("x509/ocsp/mychain_ee.pem");
1✔
1369
         auto ca = load_test_X509_cert("x509/ocsp/mychain_int.pem");
1✔
1370
         auto trust_root = load_test_X509_cert("x509/ocsp/mychain_root.pem");
1✔
1371

1372
         auto ocsp_ee_delegate = load_test_OCSP_resp("x509/ocsp/mychain_ocsp_for_ee_delegate_signed.der").value();
2✔
1373
         auto ocsp_ee_delegate_malformed =
1✔
1374
            load_test_OCSP_resp("x509/ocsp/mychain_ocsp_for_ee_delegate_signed_malformed.der").value();
2✔
1375

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

1379
         auto check_path = [&](const std::chrono::system_clock::time_point valid_time,
4✔
1380
                               const Botan::OCSP::Response& ocsp_ee,
1381
                               const Botan::Certificate_Status_Code expected,
1382
                               const std::optional<Botan::Certificate_Status_Code> also_expected = std::nullopt) {
1383
            const auto path_result = Botan::x509_path_validate(cert_path,
9✔
1384
                                                               restrictions,
1385
                                                               trusted,
3✔
1386
                                                               "",
1387
                                                               Botan::Usage_Type::UNSPECIFIED,
1388
                                                               valid_time,
1389
                                                               std::chrono::milliseconds(0),
3✔
1390
                                                               {ocsp_ee});
6✔
1391

1392
            result.test_is_eq("should result in expected validation status code",
3✔
1393
                              static_cast<uint32_t>(path_result.result()),
3✔
1394
                              static_cast<uint32_t>(expected));
3✔
1395
            if(also_expected) {
3✔
1396
               result.confirm("Secondary error is also present",
4✔
1397
                              flatten(path_result.all_statuses()).contains(also_expected.value()));
6✔
1398
            }
1399
         };
6✔
1400

1401
         check_path(Botan::calendar_point(2022, 9, 22, 23, 30, 0).to_std_timepoint(),
1✔
1402
                    ocsp_ee_delegate,
1403
                    Botan::Certificate_Status_Code::VERIFIED);
1404
         check_path(Botan::calendar_point(2022, 10, 8, 23, 30, 0).to_std_timepoint(),
1✔
1405
                    ocsp_ee_delegate,
1406
                    Botan::Certificate_Status_Code::CERT_HAS_EXPIRED,
1407
                    Botan::Certificate_Status_Code::OCSP_ISSUER_NOT_TRUSTED);
1✔
1408
         check_path(Botan::calendar_point(2022, 9, 22, 23, 30, 0).to_std_timepoint(),
1✔
1409
                    ocsp_ee_delegate_malformed,
1410
                    Botan::Certificate_Status_Code::OCSP_RESPONSE_MISSING_KEYUSAGE,
1411
                    Botan::Certificate_Status_Code::OCSP_ISSUER_NOT_TRUSTED);
1✔
1412

1413
         return result;
1✔
1414
      }
2✔
1415

1416
      static Test::Result validate_with_forged_ocsp_using_self_signed_cert() {
1✔
1417
         Test::Result result("path check with forged ocsp using self-signed certificate");
1✔
1418
         Botan::Certificate_Store_In_Memory trusted;
1✔
1419

1420
         auto restrictions = Botan::Path_Validation_Restrictions(true,    // require revocation info
1✔
1421
                                                                 110,     // minimum key strength
1422
                                                                 false);  // OCSP for all intermediates
2✔
1423

1424
         auto ee = load_test_X509_cert("x509/ocsp/randombit.pem");
1✔
1425
         auto ca = load_test_X509_cert("x509/ocsp/letsencrypt.pem");
1✔
1426
         auto trust_root = load_test_X509_cert("x509/ocsp/identrust.pem");
1✔
1427
         trusted.add_certificate(trust_root);
1✔
1428

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

1431
         auto check_path = [&](const std::string& forged_ocsp,
3✔
1432
                               const Botan::Certificate_Status_Code expected,
1433
                               const Botan::Certificate_Status_Code also_expected) {
1434
            auto ocsp = load_test_OCSP_resp(forged_ocsp);
2✔
1435
            const auto path_result =
2✔
1436
               Botan::x509_path_validate(cert_path,
6✔
1437
                                         restrictions,
1438
                                         trusted,
2✔
1439
                                         "",
1440
                                         Botan::Usage_Type::UNSPECIFIED,
1441
                                         Botan::calendar_point(2016, 11, 18, 12, 30, 0).to_std_timepoint(),
4✔
1442
                                         std::chrono::milliseconds(0),
2✔
1443
                                         {ocsp});
4✔
1444

1445
            result.test_is_eq(
2✔
1446
               "Path validation with forged OCSP response should fail with", path_result.result(), expected);
2✔
1447
            result.confirm("Secondary error is also present",
4✔
1448
                           flatten(path_result.all_statuses()).contains(also_expected));
4✔
1449
            result.test_note(std::string("Failed with: ") + Botan::to_string(path_result.result()));
6✔
1450
         };
8✔
1451

1452
         // In both cases the path validation should detect the forged OCSP
1453
         // response and generate an appropriate error. By no means it should
1454
         // follow the unauthentic OCSP response.
1455
         check_path("x509/ocsp/randombit_ocsp_forged_valid.der",
1✔
1456
                    Botan::Certificate_Status_Code::CERT_ISSUER_NOT_FOUND,
1457
                    Botan::Certificate_Status_Code::OCSP_ISSUER_NOT_TRUSTED);
1458
         check_path("x509/ocsp/randombit_ocsp_forged_revoked.der",
1✔
1459
                    Botan::Certificate_Status_Code::CERT_ISSUER_NOT_FOUND,
1460
                    Botan::Certificate_Status_Code::OCSP_ISSUER_NOT_TRUSTED);
1461

1462
         return result;
1✔
1463
      }
2✔
1464

1465
      static Test::Result validate_with_ocsp_self_signed_by_intermediate_cert() {
1✔
1466
         Test::Result result(
1✔
1467
            "path check with ocsp response for intermediate that is (maliciously) self-signed by the intermediate");
1✔
1468
         Botan::Certificate_Store_In_Memory trusted;
1✔
1469

1470
         auto restrictions = Botan::Path_Validation_Restrictions(true,   // require revocation info
1✔
1471
                                                                 110,    // minimum key strength
1472
                                                                 true);  // OCSP for all intermediates
2✔
1473

1474
         // See `src/scripts/mychain_creater.sh` if you need to recreate those
1475
         auto ee = load_test_X509_cert("x509/ocsp/mychain_ee.pem");
1✔
1476
         auto ca = load_test_X509_cert("x509/ocsp/mychain_int.pem");
1✔
1477
         auto trust_root = load_test_X509_cert("x509/ocsp/mychain_root.pem");
1✔
1478

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

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

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

1488
         const auto path_result =
1✔
1489
            Botan::x509_path_validate(cert_path,
5✔
1490
                                      restrictions,
1491
                                      trusted,
1492
                                      "",
1493
                                      Botan::Usage_Type::UNSPECIFIED,
1494
                                      Botan::calendar_point(2022, 9, 22, 22, 30, 0).to_std_timepoint(),
2✔
1495
                                      std::chrono::milliseconds(0),
1✔
1496
                                      {ocsp_ee, ocsp_ca});
3✔
1497
         result.confirm("should reject intermediate OCSP response",
2✔
1498
                        path_result.result() == Botan::Certificate_Status_Code::OCSP_ISSUER_NOT_FOUND);
1✔
1499
         result.test_note(std::string("Failed with: ") + Botan::to_string(path_result.result()));
3✔
1500

1501
         return result;
1✔
1502
      }
7✔
1503

1504
      std::vector<Test::Result> run() override {
1✔
1505
         return {validate_with_ocsp_with_next_update_without_max_age(),
1✔
1506
                 validate_with_ocsp_with_next_update_with_max_age(),
1507
                 validate_with_ocsp_without_next_update_without_max_age(),
1508
                 validate_with_ocsp_without_next_update_with_max_age(),
1509
                 validate_with_ocsp_with_authorized_responder(),
1510
                 validate_with_ocsp_with_authorized_responder_without_keyusage(),
1511
                 validate_with_forged_ocsp_using_self_signed_cert(),
1512
                 validate_with_ocsp_self_signed_by_intermediate_cert()};
9✔
1513
      }
1✔
1514
};
1515

1516
BOTAN_REGISTER_TEST("x509", "x509_path_with_ocsp", Path_Validation_With_OCSP_Tests);
1517

1518
   #endif
1519

1520
   #if defined(BOTAN_HAS_ECDSA)
1521

1522
class CVE_2020_0601_Tests final : public Test {
1✔
1523
   public:
1524
      std::vector<Test::Result> run() override {
1✔
1525
         Test::Result result("CVE-2020-0601");
1✔
1526

1527
         if(!Botan::EC_Group::supports_application_specific_group()) {
1✔
1528
            result.test_note("Skipping as application specific groups are not supported");
×
1529
            return {result};
×
1530
         }
1531

1532
         if(!Botan::EC_Group::supports_named_group("secp384r1")) {
1✔
1533
            result.test_note("Skipping as secp384r1 is not supported");
×
1534
            return {result};
×
1535
         }
1536

1537
         const auto& secp384r1 = Botan::EC_Group::from_name("secp384r1");
1✔
1538
         const Botan::OID curveball_oid("1.3.6.1.4.1.25258.4.2020.0601");
1✔
1539
         const Botan::EC_Group curveball(
1✔
1540
            curveball_oid,
1541
            secp384r1.get_p(),
1542
            secp384r1.get_a(),
1543
            secp384r1.get_b(),
1544
            BigInt(
2✔
1545
               "0xC711162A761D568EBEB96265D4C3CEB4F0C330EC8F6DD76E39BCC849ABABB8E34378D581065DEFC77D9FCED6B39075DE"),
1546
            BigInt(
2✔
1547
               "0x0CB090DE23BAC8D13E67E019A91B86311E5F342DEE17FD15FB7E278A32A1EAC98FC97E18CB2F3B2C487A7DA6F40107AC"),
1548
            secp384r1.get_order());
2✔
1549

1550
         auto ca_crt = Botan::X509_Certificate(Test::data_file("x509/cve-2020-0601/ca.pem"));
2✔
1551
         auto fake_ca_crt = Botan::X509_Certificate(Test::data_file("x509/cve-2020-0601/fake_ca.pem"));
2✔
1552
         auto ee_crt = Botan::X509_Certificate(Test::data_file("x509/cve-2020-0601/ee.pem"));
2✔
1553

1554
         Botan::Certificate_Store_In_Memory trusted;
1✔
1555
         trusted.add_certificate(ca_crt);
1✔
1556

1557
         const auto restrictions = Botan::Path_Validation_Restrictions(false, 80, false);
2✔
1558

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

1561
         const auto path_result1 = Botan::x509_path_validate(std::vector<Botan::X509_Certificate>{ee_crt, fake_ca_crt},
5✔
1562
                                                             restrictions,
1563
                                                             trusted,
1564
                                                             "",
1565
                                                             Botan::Usage_Type::UNSPECIFIED,
1566
                                                             valid_time,
1567
                                                             std::chrono::milliseconds(0),
1✔
1568
                                                             {});
2✔
1569

1570
         result.confirm("Validation failed", !path_result1.successful_validation());
2✔
1571

1572
         result.confirm("Expected status",
2✔
1573
                        path_result1.result() == Botan::Certificate_Status_Code::CANNOT_ESTABLISH_TRUST);
1✔
1574

1575
         const auto path_result2 = Botan::x509_path_validate(std::vector<Botan::X509_Certificate>{ee_crt},
3✔
1576
                                                             restrictions,
1577
                                                             trusted,
1578
                                                             "",
1579
                                                             Botan::Usage_Type::UNSPECIFIED,
1580
                                                             valid_time,
1581
                                                             std::chrono::milliseconds(0),
1✔
1582
                                                             {});
2✔
1583

1584
         result.confirm("Validation failed", !path_result2.successful_validation());
2✔
1585

1586
         result.confirm("Expected status",
2✔
1587
                        path_result2.result() == Botan::Certificate_Status_Code::CERT_ISSUER_NOT_FOUND);
1✔
1588

1589
         // Verify the signature from the bad CA is actually correct
1590
         Botan::Certificate_Store_In_Memory frusted;
1✔
1591
         frusted.add_certificate(fake_ca_crt);
1✔
1592

1593
         const auto path_result3 = Botan::x509_path_validate(std::vector<Botan::X509_Certificate>{ee_crt},
3✔
1594
                                                             restrictions,
1595
                                                             frusted,
1596
                                                             "",
1597
                                                             Botan::Usage_Type::UNSPECIFIED,
1598
                                                             valid_time,
1599
                                                             std::chrono::milliseconds(0),
1✔
1600
                                                             {});
2✔
1601

1602
         result.confirm("Validation succeeded", path_result3.successful_validation());
2✔
1603

1604
         return {result};
2✔
1605
      }
5✔
1606
};
1607

1608
BOTAN_REGISTER_TEST("x509", "x509_cve_2020_0601", CVE_2020_0601_Tests);
1609

1610
class Path_Validation_With_Immortal_CRL final : public Test {
1✔
1611
   public:
1612
      std::vector<Test::Result> run() override {
1✔
1613
         // RFC 5280 defines the nextUpdate field as "optional" (in line with
1614
         // the original X.509 standard), but then requires all conforming CAs
1615
         // to always define it. For best compatibility we must deal with both.
1616
         Test::Result result("Using a CRL without a nextUpdate field");
1✔
1617

1618
         if(Botan::has_filesystem_impl() == false) {
1✔
1619
            result.test_note("Skipping due to missing filesystem access");
×
1620
            return {result};
×
1621
         }
1622

1623
         const Botan::X509_Certificate root(Test::data_file("x509/misc/crl_without_nextupdate/ca.pem"));
2✔
1624
         const Botan::X509_Certificate revoked_subject(Test::data_file("x509/misc/crl_without_nextupdate/01.pem"));
2✔
1625
         const Botan::X509_Certificate valid_subject(Test::data_file("x509/misc/crl_without_nextupdate/42.pem"));
2✔
1626

1627
         // Check that a CRL without nextUpdate is parsable
1628
         auto crl = Botan::X509_CRL(Test::data_file("x509/misc/crl_without_nextupdate/valid_forever.crl"));
2✔
1629
         result.confirm("this update is set", crl.this_update().time_is_set());
2✔
1630
         result.confirm("next update is not set", !crl.next_update().time_is_set());
2✔
1631
         result.confirm("CRL is not empty", !crl.get_revoked().empty());
2✔
1632

1633
         // Ensure that we support the used sig algo, otherwish stop here
1634
         if(!Botan::EC_Group::supports_named_group("brainpool512r1")) {
1✔
1635
            result.test_note("Cannot test path validation because signature algorithm is not support in this build");
×
1636
            return {result};
×
1637
         }
1638

1639
         Botan::Certificate_Store_In_Memory trusted;
1✔
1640
         trusted.add_certificate(root);
1✔
1641
         trusted.add_crl(crl);
1✔
1642

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

1647
         const Botan::Path_Validation_Restrictions restrictions(true /* require revocation info */);
2✔
1648

1649
         // Validate a certificate that is not listed in the CRL
1650
         const auto valid = Botan::x509_path_validate(
1✔
1651
            valid_subject, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, valid_time);
1✔
1652
         if(!result.confirm("Valid certificate", valid.successful_validation())) {
2✔
1653
            result.test_note(valid.result_string());
×
1654
         }
1655

1656
         // Ensure that a certificate listed in the CRL is recognized as revoked
1657
         const auto revoked = Botan::x509_path_validate(
1✔
1658
            revoked_subject, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, valid_time);
1✔
1659
         if(!result.confirm("No valid certificate", !revoked.successful_validation())) {
2✔
1660
            result.test_note(revoked.result_string());
×
1661
         }
1662
         result.test_is_eq("Certificate is revoked", revoked.result(), Botan::Certificate_Status_Code::CERT_IS_REVOKED);
1✔
1663

1664
         return {result};
2✔
1665
      }
2✔
1666
};
1667

1668
BOTAN_REGISTER_TEST("x509", "x509_path_immortal_crl", Path_Validation_With_Immortal_CRL);
1669

1670
   #endif
1671

1672
   #if defined(BOTAN_HAS_XMSS_RFC8391)
1673

1674
class XMSS_Path_Validation_Tests final : public Test {
1✔
1675
   public:
1676
      static Test::Result validate_self_signed(const std::string& name, const std::string& file) {
2✔
1677
         Test::Result result(name);
2✔
1678

1679
         const Botan::Path_Validation_Restrictions restrictions;
4✔
1680
         auto self_signed = Botan::X509_Certificate(Test::data_file("x509/xmss/" + file));
4✔
1681

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

1685
         auto status = Botan::PKIX::overall_status(
2✔
1686
            Botan::PKIX::check_chain(cert_path, valid_time, "", Botan::Usage_Type::UNSPECIFIED, restrictions));
4✔
1687
         result.test_eq("Cert validation status", Botan::to_string(status), "Verified");
2✔
1688
         return result;
2✔
1689
      }
4✔
1690

1691
      std::vector<Test::Result> run() override {
1✔
1692
         if(Botan::has_filesystem_impl() == false) {
1✔
1693
            return {Test::Result::Note("XMSS path validation", "Skipping due to missing filesystem access")};
×
1694
         }
1695

1696
         return {
1✔
1697
            validate_self_signed("XMSS path validation with certificate created by ISARA corp", "xmss_isara_root.pem"),
1✔
1698
            validate_self_signed("XMSS path validation with certificate created by BouncyCastle",
1✔
1699
                                 "xmss_bouncycastle_sha256_10_root.pem")};
3✔
1700
      }
4✔
1701
};
1702

1703
BOTAN_REGISTER_TEST("x509", "x509_path_xmss", XMSS_Path_Validation_Tests);
1704

1705
   #endif
1706

1707
#endif
1708

1709
}  // namespace
1710

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