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

randombit / botan / 22045109103

15 Feb 2026 11:27PM UTC coverage: 90.043% (-0.01%) from 90.054%
22045109103

push

github

web-flow
Merge pull request #5342 from randombit/jack/test-h-arb-eq

Rename test_is_eq to test_arb_eq and require the type be printable

102325 of 113640 relevant lines covered (90.04%)

11463137.71 hits per line

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

95.74
/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
   #if defined(BOTAN_HAS_RSA) && defined(BOTAN_HAS_EMSA_PKCS1)
45

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

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

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

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

69
      m[parts[0]] = parts[1];
573✔
70
   }
573✔
71

72
   return m;
18✔
73
}
9✔
74

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

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

85
   return certs;
80✔
86
}
80✔
87

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

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

95
   return result;
4✔
96
}
×
97

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

294
   return results;
2✔
295
}
154✔
296

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

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

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

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

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

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

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

319
      Botan::Certificate_Store_In_Memory store;
3✔
320

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

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

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

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

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

339
   return results;
1✔
340
}
1✔
341

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

344
      #if defined(BOTAN_HAS_PSS)
345

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

428
   return results;
1✔
429
}
19✔
430

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

433
      #endif
434

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

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

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

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

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

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

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

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

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

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

469
   const Botan::Certificate_Store_In_Memory empty;
1✔
470

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

534
   const std::vector<Test::Result> results;
1✔
535

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

540
   auto validation_time = Botan::calendar_point(2020, 1, 1, 1, 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
   const Botan::Path_Validation_Restrictions restrictions;
2✔
552
   const Botan::Path_Validation_Result validation_result =
1✔
553
      Botan::x509_path_validate(chain, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
1✔
554

555
   Test::Result result("Verifying certificate with alternative SAN violating name constraint");
1✔
556
   result.test_is_false("Path validation failed", validation_result.successful_validation());
1✔
557
   result.test_str_eq(
1✔
558
      "Path validation result", validation_result.result_string(), "Certificate does not pass name constraint");
1✔
559

560
   return {result};
2✔
561
}
3✔
562

563
BOTAN_REGISTER_TEST("x509", "x509_name_constraint_san", Validate_Name_Constraint_SAN_Test);
564

565
class Validate_Name_Constraint_CaseInsensitive final : public Test {
1✔
566
   public:
567
      std::vector<Test::Result> run() override;
568
};
569

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

575
   const std::vector<Test::Result> results;
1✔
576

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

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

583
   const Botan::X509_Certificate root(root_crt);
1✔
584
   const Botan::X509_Certificate intermediate(int_crt);
1✔
585
   const Botan::X509_Certificate ee_cert(ee_crt);
1✔
586

587
   Botan::Certificate_Store_In_Memory trusted;
1✔
588
   trusted.add_certificate(root);
1✔
589

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

592
   const Botan::Path_Validation_Restrictions restrictions;
2✔
593
   const Botan::Path_Validation_Result validation_result =
1✔
594
      Botan::x509_path_validate(chain, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
1✔
595

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

599
   return {result};
2✔
600
}
3✔
601

602
BOTAN_REGISTER_TEST("x509", "x509_name_constraint_ci", Validate_Name_Constraint_CaseInsensitive);
603

604
class Validate_Name_Constraint_NoCheckSelf final : public Test {
1✔
605
   public:
606
      std::vector<Test::Result> run() override;
607
};
608

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

614
   const std::vector<Test::Result> results;
1✔
615

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

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

622
   const Botan::X509_Certificate root(root_crt);
1✔
623
   const Botan::X509_Certificate intermediate(int_crt);
1✔
624
   const Botan::X509_Certificate ee_cert(ee_crt);
1✔
625

626
   Botan::Certificate_Store_In_Memory trusted;
1✔
627
   trusted.add_certificate(root);
1✔
628

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

631
   const Botan::Path_Validation_Restrictions restrictions;
2✔
632
   const Botan::Path_Validation_Result validation_result =
1✔
633
      Botan::x509_path_validate(chain, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
1✔
634

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

638
   return {result};
2✔
639
}
3✔
640

641
BOTAN_REGISTER_TEST("x509", "x509_name_constraint_no_check_self", Validate_Name_Constraint_NoCheckSelf);
642

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

650
         const std::string trusted_root_crt = Test::data_file("x509/misc/root_cert_time_check/root.crt");
1✔
651
         const std::string leaf_crt = Test::data_file("x509/misc/root_cert_time_check/leaf.crt");
1✔
652

653
         const Botan::X509_Certificate trusted_root_cert(trusted_root_crt);
1✔
654
         const Botan::X509_Certificate leaf_cert(leaf_crt);
1✔
655

656
         Botan::Certificate_Store_In_Memory trusted;
1✔
657
         trusted.add_certificate(trusted_root_cert);
1✔
658

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

661
         Test::Result result("Root cert time check");
1✔
662

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

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

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

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

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

739
         return {result};
2✔
740
      }
3✔
741
};
742

743
BOTAN_REGISTER_TEST("x509", "x509_root_cert_time_check", Root_Cert_Time_Check_Test);
744

745
class Non_Self_Signed_Trust_Anchors_Test final : public Test {
1✔
746
   private:
747
      using Cert_Path = std::vector<Botan::X509_Certificate>;
748

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

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

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

769
      std::vector<Test::Result> path_validate_test() {
1✔
770
         std::vector<Test::Result> results;
1✔
771

772
         const auto restrictions = get_allow_non_self_signed_anchors_restrictions(false, false);
1✔
773
         const auto certs = get_valid_cert_chain();
1✔
774
         const auto validation_time = get_validation_time();
1✔
775

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

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

789
            const Botan::Certificate_Store_In_Memory trusted(trust_anchor);
5✔
790

791
            auto path_result = Botan::x509_path_validate(
5✔
792
               end_certs, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
5✔
793

794
            if(path_result.successful_validation() && path_result.trust_root() != trust_anchor) {
5✔
795
               path_result = Botan::Path_Validation_Result(Botan::Certificate_Status_Code::CANNOT_ESTABLISH_TRUST);
×
796
            }
797

798
            result.test_str_eq(
5✔
799
               "path validation failed", path_result.result_string(), to_string(Botan::Certificate_Status_Code::OK));
5✔
800

801
            results.push_back(result);
5✔
802
         }
5✔
803
         return results;
1✔
804
      }
7✔
805

806
      std::vector<Test::Result> build_path_test() {
1✔
807
         std::vector<Test::Result> results;
1✔
808

809
         const auto restrictions = get_allow_non_self_signed_anchors_restrictions(false, false);
1✔
810
         const auto certs = get_valid_cert_chain();
1✔
811

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

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

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

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

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

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

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

865
         return results;
1✔
866
      }
7✔
867

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

873
         Botan::Certificate_Store_In_Memory cert_store(certs.at(3));
1✔
874

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

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

880
         result.test_str_eq("unexpected path validation result",
1✔
881
                            path_result.result_string(),
1✔
882
                            to_string(Botan::Certificate_Status_Code::CANNOT_ESTABLISH_TRUST));
883

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

887
         result.test_str_ne("unexpected check_chain result",
2✔
888
                            Botan::Path_Validation_Result(check_chain_result, {}).result_string(),
2✔
889
                            to_string(Botan::Certificate_Status_Code::OK));
890

891
         return {result};
3✔
892
      }
3✔
893

894
      Test::Result stand_alone_root_test(std::string_view test_name,
6✔
895
                                         const Botan::Path_Validation_Restrictions& restrictions,
896
                                         const Botan::X509_Certificate& standalone_cert,
897
                                         Botan::Certificate_Status_Code expected_result) {
898
         Test::Result result(test_name);
6✔
899

900
         const auto validation_time = get_validation_time();
6✔
901
         Botan::Certificate_Store_In_Memory cert_store(standalone_cert);
6✔
902

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

906
         if(path_result.result() == Botan::Certificate_Status_Code::CERT_PUBKEY_INVALID) {
6✔
907
            result.test_note("CERT_PUBKEY_INVALID encountered - was that key type disabled at build time?");
×
908
         } else {
909
            result.test_str_eq(
6✔
910
               "unexpected x509_path_validate result", path_result.result_string(), to_string(expected_result));
12✔
911
         }
912

913
         return result;
6✔
914
      }
6✔
915

916
      std::vector<Test::Result> stand_alone_root_tests() {
1✔
917
         const auto self_signed_trust_anchor_forbidden = get_default_restrictions(false, false);
1✔
918
         const auto self_signed_trust_anchor_allowed = get_allow_non_self_signed_anchors_restrictions(false, false);
1✔
919

920
         const auto cert_chain = get_valid_cert_chain();
1✔
921
         const auto& self_signed_root = cert_chain.at(4);
1✔
922
         const auto& ica = cert_chain.at(3);
1✔
923
         const auto& self_signed_ee = get_self_signed_ee_cert();
1✔
924

925
         return {
1✔
926
            stand_alone_root_test("Standalone self-signed root (non-self-signed trust anchors forbidden)",
927
                                  self_signed_trust_anchor_forbidden,
928
                                  self_signed_root,
929
                                  Botan::Certificate_Status_Code::OK),
930
            stand_alone_root_test("Standalone self-signed root (non-self-signed trust anchors allowed)",
931
                                  self_signed_trust_anchor_allowed,
932
                                  self_signed_root,
933
                                  Botan::Certificate_Status_Code::OK),
934

935
            stand_alone_root_test("Standalone self-signed end entity (non-self-signed trust anchors forbidden)",
936
                                  self_signed_trust_anchor_forbidden,
937
                                  self_signed_ee,
938
                                  Botan::Certificate_Status_Code::OK),
939
            stand_alone_root_test("Standalone self-signed end entity (non-self-signed trust anchors allowed)",
940
                                  self_signed_trust_anchor_allowed,
941
                                  self_signed_ee,
942
                                  Botan::Certificate_Status_Code::OK),
943

944
            stand_alone_root_test("Standalone intermediate certificate (non-self-signed trust anchors forbidden)",
945
                                  self_signed_trust_anchor_forbidden,
946
                                  ica,
947
                                  Botan::Certificate_Status_Code::CANNOT_ESTABLISH_TRUST),
948
            stand_alone_root_test("Standalone intermediate certificate (non-self-signed trust anchors allowed)",
949
                                  self_signed_trust_anchor_allowed,
950
                                  ica,
951
                                  Botan::Certificate_Status_Code::OK),
952
         };
8✔
953
      }
2✔
954

955
   public:
956
      std::vector<Test::Result> run() override {
1✔
957
         std::vector<Test::Result> results;
1✔
958

959
         auto res = path_validate_test();
1✔
960
         results.insert(results.end(), res.begin(), res.end());
1✔
961

962
         res = build_path_test();
1✔
963
         results.insert(results.end(), res.begin(), res.end());
1✔
964

965
         res = forbidden_self_signed_trust_anchors_test();
1✔
966
         results.insert(results.end(), res.begin(), res.end());
1✔
967

968
         res = stand_alone_root_tests();
1✔
969
         results.insert(results.end(), res.begin(), res.end());
1✔
970

971
         return results;
1✔
972
      }
1✔
973
};
974

975
BOTAN_REGISTER_TEST("x509", "x509_non_self_signed_trust_anchors", Non_Self_Signed_Trust_Anchors_Test);
976

977
class BSI_Path_Validation_Tests final : public Test
1✔
978

979
{
980
   public:
981
      std::vector<Test::Result> run() override;
982

983
   private:
984
      std::vector<Test::Result> run_with_restrictions(const Botan::Path_Validation_Restrictions& restrictions);
985
};
986

987
std::vector<Test::Result> BSI_Path_Validation_Tests::run() {
1✔
988
   std::vector<Test::Result> results;
1✔
989
   for(const auto& restrictions : restrictions_to_test(false, false)) {
3✔
990
      auto partial_res = run_with_restrictions(restrictions);
2✔
991
      results.insert(results.end(), partial_res.begin(), partial_res.end());
2✔
992
   }
3✔
993
   return results;
1✔
994
}
×
995

996
std::vector<Test::Result> BSI_Path_Validation_Tests::run_with_restrictions(
2✔
997
   const Botan::Path_Validation_Restrictions& restriction_template) {
998
   if(Botan::has_filesystem_impl() == false) {
2✔
999
      return {Test::Result::Note("BSI path validation", "Skipping due to missing filesystem access")};
×
1000
   }
1001

1002
   std::vector<Test::Result> results;
2✔
1003

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

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

1010
      Botan::Certificate_Store_In_Memory trusted;
108✔
1011
      std::vector<Botan::X509_Certificate> certs;
108✔
1012

1013
      #if defined(BOTAN_HAS_MD5)
1014
      const bool has_md5 = true;
108✔
1015
      #else
1016
      const bool has_md5 = false;
1017
      #endif
1018

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

1021
      // By convention: if CRL is a substring if the test name,
1022
      // we need to check the CRLs
1023
      bool use_crl = false;
108✔
1024
      if(test_name.find("CRL") != std::string::npos) {
108✔
1025
         use_crl = true;
32✔
1026
      }
1027

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

1046
         const Botan::Path_Validation_Restrictions restrictions(
100✔
1047
            use_crl,
1048
            restriction_template.minimum_key_strength(),
1049
            use_crl,
1050
            restriction_template.max_ocsp_age(),
1051
            std::make_unique<Botan::Certificate_Store_In_Memory>(),
×
1052
            restriction_template.ignore_trusted_root_time_range(),
100✔
1053
            restriction_template.require_self_signed_trust_anchors());
200✔
1054

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

1067
               static constexpr result_type min() { return 0; }
1068

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

1071
               result_type operator()() {
160✔
1072
                  size_t s = 0;
160✔
1073
                  m_rng.randomize(reinterpret_cast<uint8_t*>(&s), sizeof(s));
160✔
1074
                  return s;
160✔
1075
               }
1076

1077
               explicit random_bit_generator(Botan::RandomNumberGenerator& rng) : m_rng(rng) {}
100✔
1078

1079
            private:
1080
               Botan::RandomNumberGenerator& m_rng;
1081
         } rbg(this->rng());
100✔
1082

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

1086
            const Botan::Path_Validation_Result validation_result = Botan::x509_path_validate(
1,600✔
1087
               certs, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
1,600✔
1088

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

1118
      /* Some certificates are rejected when executing the X509_Certificate constructor
1119
       * by throwing a Decoding_Error exception.
1120
       */
1121
      catch(const Botan::Exception& e) {
8✔
1122
         if(e.error_type() == Botan::ErrorType::DecodingFailure) {
8✔
1123
            result.test_str_eq(test_name + " path validation result", e.what(), expected_result);
16✔
1124
         } else {
1125
            result.test_failure(test_name, e.what());
×
1126
         }
1127
      }
8✔
1128

1129
      result.end_timer();
108✔
1130
      results.push_back(result);
108✔
1131
   }
108✔
1132

1133
   return results;
2✔
1134
}
2✔
1135

1136
BOTAN_REGISTER_TEST("x509", "x509_path_bsi", BSI_Path_Validation_Tests);
1137

1138
class Path_Validation_With_OCSP_Tests final : public Test {
1✔
1139
   public:
1140
      static Botan::X509_Certificate load_test_X509_cert(const std::string& path) {
24✔
1141
         return Botan::X509_Certificate(Test::data_file(path));
48✔
1142
      }
1143

1144
      static std::optional<Botan::OCSP::Response> load_test_OCSP_resp(const std::string& path) {
12✔
1145
         return Botan::OCSP::Response(Test::read_binary_data_file(path));
36✔
1146
      }
1147

1148
      static Test::Result validate_with_ocsp_with_next_update_without_max_age() {
1✔
1149
         Test::Result result("path check with ocsp with next_update w/o max_age");
1✔
1150
         Botan::Certificate_Store_In_Memory trusted;
1✔
1151

1152
         auto restrictions = Botan::Path_Validation_Restrictions(false, 110, false);
2✔
1153

1154
         auto ee = load_test_X509_cert("x509/ocsp/randombit.pem");
1✔
1155
         auto ca = load_test_X509_cert("x509/ocsp/letsencrypt.pem");
1✔
1156
         auto trust_root = load_test_X509_cert("x509/ocsp/identrust.pem");
1✔
1157
         trusted.add_certificate(trust_root);
1✔
1158

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

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

1163
         auto check_path = [&](const std::chrono::system_clock::time_point valid_time,
5✔
1164
                               const Botan::Certificate_Status_Code expected) {
1165
            const auto path_result = Botan::x509_path_validate(cert_path,
8✔
1166
                                                               restrictions,
1167
                                                               trusted,
4✔
1168
                                                               "",
1169
                                                               Botan::Usage_Type::UNSPECIFIED,
1170
                                                               valid_time,
1171
                                                               std::chrono::milliseconds(0),
4✔
1172
                                                               {ocsp});
8✔
1173

1174
            return result.test_is_true(std::string("Status: '") + Botan::to_string(expected) + "' should match '" +
24✔
1175
                                          Botan::to_string(path_result.result()) + "'",
8✔
1176
                                       path_result.result() == expected);
8✔
1177
         };
8✔
1178

1179
         check_path(Botan::calendar_point(2016, 11, 11, 12, 30, 0).to_std_timepoint(),
1✔
1180
                    Botan::Certificate_Status_Code::OCSP_NOT_YET_VALID);
1181
         check_path(Botan::calendar_point(2016, 11, 18, 12, 30, 0).to_std_timepoint(),
1✔
1182
                    Botan::Certificate_Status_Code::OK);
1183
         check_path(Botan::calendar_point(2016, 11, 20, 8, 30, 0).to_std_timepoint(),
1✔
1184
                    Botan::Certificate_Status_Code::OK);
1185
         check_path(Botan::calendar_point(2016, 11, 28, 8, 30, 0).to_std_timepoint(),
1✔
1186
                    Botan::Certificate_Status_Code::OCSP_HAS_EXPIRED);
1187

1188
         return result;
2✔
1189
      }
2✔
1190

1191
      static Test::Result validate_with_ocsp_with_next_update_with_max_age() {
1✔
1192
         Test::Result result("path check with ocsp with next_update with max_age");
1✔
1193
         Botan::Certificate_Store_In_Memory trusted;
1✔
1194

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

1197
         auto ee = load_test_X509_cert("x509/ocsp/randombit.pem");
1✔
1198
         auto ca = load_test_X509_cert("x509/ocsp/letsencrypt.pem");
1✔
1199
         auto trust_root = load_test_X509_cert("x509/ocsp/identrust.pem");
1✔
1200
         trusted.add_certificate(trust_root);
1✔
1201

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

1204
         auto ocsp = load_test_OCSP_resp("x509/ocsp/randombit_ocsp.der");
1✔
1205

1206
         auto check_path = [&](const std::chrono::system_clock::time_point valid_time,
5✔
1207
                               const Botan::Certificate_Status_Code expected) {
1208
            const auto path_result = Botan::x509_path_validate(cert_path,
8✔
1209
                                                               restrictions,
1210
                                                               trusted,
4✔
1211
                                                               "",
1212
                                                               Botan::Usage_Type::UNSPECIFIED,
1213
                                                               valid_time,
1214
                                                               std::chrono::milliseconds(0),
4✔
1215
                                                               {ocsp});
8✔
1216

1217
            return result.test_is_true(std::string("Status: '") + Botan::to_string(expected) + "' should match '" +
24✔
1218
                                          Botan::to_string(path_result.result()) + "'",
8✔
1219
                                       path_result.result() == expected);
8✔
1220
         };
12✔
1221

1222
         check_path(Botan::calendar_point(2016, 11, 11, 12, 30, 0).to_std_timepoint(),
1✔
1223
                    Botan::Certificate_Status_Code::OCSP_NOT_YET_VALID);
1224
         check_path(Botan::calendar_point(2016, 11, 18, 12, 30, 0).to_std_timepoint(),
1✔
1225
                    Botan::Certificate_Status_Code::OK);
1226
         check_path(Botan::calendar_point(2016, 11, 20, 8, 30, 0).to_std_timepoint(),
1✔
1227
                    Botan::Certificate_Status_Code::OK);
1228
         check_path(Botan::calendar_point(2016, 11, 28, 8, 30, 0).to_std_timepoint(),
1✔
1229
                    Botan::Certificate_Status_Code::OCSP_HAS_EXPIRED);
1230

1231
         return result;
2✔
1232
      }
2✔
1233

1234
      static Test::Result validate_with_ocsp_without_next_update_without_max_age() {
1✔
1235
         Test::Result result("path check with ocsp w/o next_update w/o max_age");
1✔
1236
         Botan::Certificate_Store_In_Memory trusted;
1✔
1237

1238
         auto restrictions = Botan::Path_Validation_Restrictions(false, 110, false);
2✔
1239

1240
         auto ee = load_test_X509_cert("x509/ocsp/patrickschmidt.pem");
1✔
1241
         auto ca = load_test_X509_cert("x509/ocsp/bdrive_encryption.pem");
1✔
1242
         auto trust_root = load_test_X509_cert("x509/ocsp/bdrive_root.pem");
1✔
1243

1244
         trusted.add_certificate(trust_root);
1✔
1245

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

1248
         auto ocsp = load_test_OCSP_resp("x509/ocsp/patrickschmidt_ocsp.der");
1✔
1249

1250
         auto check_path = [&](const std::chrono::system_clock::time_point valid_time,
4✔
1251
                               const Botan::Certificate_Status_Code expected) {
1252
            const auto path_result = Botan::x509_path_validate(cert_path,
6✔
1253
                                                               restrictions,
1254
                                                               trusted,
3✔
1255
                                                               "",
1256
                                                               Botan::Usage_Type::UNSPECIFIED,
1257
                                                               valid_time,
1258
                                                               std::chrono::milliseconds(0),
3✔
1259
                                                               {ocsp});
6✔
1260

1261
            return result.test_is_true(std::string("Status: '") + Botan::to_string(expected) + "' should match '" +
18✔
1262
                                          Botan::to_string(path_result.result()) + "'",
6✔
1263
                                       path_result.result() == expected);
6✔
1264
         };
9✔
1265

1266
         check_path(Botan::calendar_point(2019, 5, 28, 7, 0, 0).to_std_timepoint(),
1✔
1267
                    Botan::Certificate_Status_Code::OCSP_NOT_YET_VALID);
1268
         check_path(Botan::calendar_point(2019, 5, 28, 7, 30, 0).to_std_timepoint(),
1✔
1269
                    Botan::Certificate_Status_Code::OK);
1270
         check_path(Botan::calendar_point(2019, 5, 28, 8, 0, 0).to_std_timepoint(), Botan::Certificate_Status_Code::OK);
1✔
1271

1272
         return result;
2✔
1273
      }
2✔
1274

1275
      static Test::Result validate_with_ocsp_without_next_update_with_max_age() {
1✔
1276
         Test::Result result("path check with ocsp w/o next_update with max_age");
1✔
1277
         Botan::Certificate_Store_In_Memory trusted;
1✔
1278

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

1281
         auto ee = load_test_X509_cert("x509/ocsp/patrickschmidt.pem");
1✔
1282
         auto ca = load_test_X509_cert("x509/ocsp/bdrive_encryption.pem");
1✔
1283
         auto trust_root = load_test_X509_cert("x509/ocsp/bdrive_root.pem");
1✔
1284

1285
         trusted.add_certificate(trust_root);
1✔
1286

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

1289
         auto ocsp = load_test_OCSP_resp("x509/ocsp/patrickschmidt_ocsp.der");
1✔
1290

1291
         auto check_path = [&](const std::chrono::system_clock::time_point valid_time,
4✔
1292
                               const Botan::Certificate_Status_Code expected) {
1293
            const auto path_result = Botan::x509_path_validate(cert_path,
6✔
1294
                                                               restrictions,
1295
                                                               trusted,
3✔
1296
                                                               "",
1297
                                                               Botan::Usage_Type::UNSPECIFIED,
1298
                                                               valid_time,
1299
                                                               std::chrono::milliseconds(0),
3✔
1300
                                                               {ocsp});
6✔
1301

1302
            return result.test_is_true(std::string("Status: '") + Botan::to_string(expected) + "' should match '" +
18✔
1303
                                          Botan::to_string(path_result.result()) + "'",
6✔
1304
                                       path_result.result() == expected);
6✔
1305
         };
9✔
1306

1307
         check_path(Botan::calendar_point(2019, 5, 28, 7, 0, 0).to_std_timepoint(),
1✔
1308
                    Botan::Certificate_Status_Code::OCSP_NOT_YET_VALID);
1309
         check_path(Botan::calendar_point(2019, 5, 28, 7, 30, 0).to_std_timepoint(),
1✔
1310
                    Botan::Certificate_Status_Code::OK);
1311
         check_path(Botan::calendar_point(2019, 5, 28, 8, 0, 0).to_std_timepoint(),
1✔
1312
                    Botan::Certificate_Status_Code::OCSP_IS_TOO_OLD);
1313

1314
         return result;
2✔
1315
      }
2✔
1316

1317
      static Test::Result validate_with_ocsp_with_authorized_responder() {
1✔
1318
         Test::Result result("path check with ocsp response from authorized responder certificate");
1✔
1319
         Botan::Certificate_Store_In_Memory trusted;
1✔
1320

1321
         auto restrictions = Botan::Path_Validation_Restrictions(true,   // require revocation info
1✔
1322
                                                                 110,    // minimum key strength
1323
                                                                 true);  // OCSP for all intermediates
2✔
1324

1325
         auto ee = load_test_X509_cert("x509/ocsp/bdr.pem");
1✔
1326
         auto ca = load_test_X509_cert("x509/ocsp/bdr-int.pem");
1✔
1327
         auto trust_root = load_test_X509_cert("x509/ocsp/bdr-root.pem");
1✔
1328

1329
         // These OCSP responses are signed by an authorized OCSP responder
1330
         // certificate issued by `ca` and `trust_root` respectively. Note that
1331
         // the responder certificates contain the "OCSP No Check" extension,
1332
         // meaning that they themselves do not need a revocation check via OCSP.
1333
         auto ocsp_ee = load_test_OCSP_resp("x509/ocsp/bdr-ocsp-resp.der");
1✔
1334
         auto ocsp_ca = load_test_OCSP_resp("x509/ocsp/bdr-int-ocsp-resp.der");
1✔
1335

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

1339
         auto check_path = [&](const std::chrono::system_clock::time_point valid_time,
4✔
1340
                               const Botan::Certificate_Status_Code expected) {
1341
            const auto path_result = Botan::x509_path_validate(cert_path,
12✔
1342
                                                               restrictions,
1343
                                                               trusted,
3✔
1344
                                                               "",
1345
                                                               Botan::Usage_Type::UNSPECIFIED,
1346
                                                               valid_time,
1347
                                                               std::chrono::milliseconds(0),
3✔
1348
                                                               {ocsp_ee, ocsp_ca});
9✔
1349

1350
            return result.test_is_true(std::string("Status: '") + Botan::to_string(expected) + "' should match '" +
18✔
1351
                                          Botan::to_string(path_result.result()) + "'",
6✔
1352
                                       path_result.result() == expected);
6✔
1353
         };
12✔
1354

1355
         check_path(Botan::calendar_point(2022, 9, 18, 16, 30, 0).to_std_timepoint(),
1✔
1356
                    Botan::Certificate_Status_Code::OCSP_NOT_YET_VALID);
1357
         check_path(Botan::calendar_point(2022, 9, 19, 16, 30, 0).to_std_timepoint(),
1✔
1358
                    Botan::Certificate_Status_Code::OK);
1359
         check_path(Botan::calendar_point(2022, 9, 20, 16, 30, 0).to_std_timepoint(),
1✔
1360
                    Botan::Certificate_Status_Code::OCSP_HAS_EXPIRED);
1361

1362
         return result;
1✔
1363
      }
4✔
1364

1365
      static Test::Result validate_with_ocsp_with_authorized_responder_without_keyusage() {
1✔
1366
         Test::Result result(
1✔
1367
            "path check with ocsp response from authorized responder certificate (without sufficient key usage)");
1✔
1368
         Botan::Certificate_Store_In_Memory trusted;
1✔
1369

1370
         auto restrictions = Botan::Path_Validation_Restrictions(true,    // require revocation info
1✔
1371
                                                                 110,     // minimum key strength
1372
                                                                 false);  // OCSP for all intermediates
2✔
1373

1374
         // See `src/scripts/mychain_creater.sh` if you need to recreate those
1375
         auto ee = load_test_X509_cert("x509/ocsp/mychain_ee.pem");
1✔
1376
         auto ca = load_test_X509_cert("x509/ocsp/mychain_int.pem");
1✔
1377
         auto trust_root = load_test_X509_cert("x509/ocsp/mychain_root.pem");
1✔
1378

1379
         auto ocsp_ee_delegate = load_test_OCSP_resp("x509/ocsp/mychain_ocsp_for_ee_delegate_signed.der").value();
2✔
1380
         auto ocsp_ee_delegate_malformed =
1✔
1381
            load_test_OCSP_resp("x509/ocsp/mychain_ocsp_for_ee_delegate_signed_malformed.der").value();
2✔
1382

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

1386
         auto check_path = [&](const std::chrono::system_clock::time_point valid_time,
4✔
1387
                               const Botan::OCSP::Response& ocsp_ee,
1388
                               const Botan::Certificate_Status_Code expected,
1389
                               const std::optional<Botan::Certificate_Status_Code> also_expected = std::nullopt) {
1390
            const auto path_result = Botan::x509_path_validate(cert_path,
6✔
1391
                                                               restrictions,
1392
                                                               trusted,
3✔
1393
                                                               "",
1394
                                                               Botan::Usage_Type::UNSPECIFIED,
1395
                                                               valid_time,
1396
                                                               std::chrono::milliseconds(0),
3✔
1397
                                                               {ocsp_ee});
6✔
1398

1399
            result.test_u32_eq("should result in expected validation status code",
3✔
1400
                               static_cast<uint32_t>(path_result.result()),
3✔
1401
                               static_cast<uint32_t>(expected));
1402
            if(also_expected) {
3✔
1403
               result.test_is_true("Secondary error is also present",
4✔
1404
                                   flatten(path_result.all_statuses()).contains(also_expected.value()));
6✔
1405
            }
1406
         };
6✔
1407

1408
         check_path(Botan::calendar_point(2022, 9, 22, 23, 30, 0).to_std_timepoint(),
1✔
1409
                    ocsp_ee_delegate,
1410
                    Botan::Certificate_Status_Code::VERIFIED);
1411
         check_path(Botan::calendar_point(2022, 10, 8, 23, 30, 0).to_std_timepoint(),
1✔
1412
                    ocsp_ee_delegate,
1413
                    Botan::Certificate_Status_Code::CERT_HAS_EXPIRED,
1414
                    Botan::Certificate_Status_Code::OCSP_ISSUER_NOT_TRUSTED);
1✔
1415
         check_path(Botan::calendar_point(2022, 9, 22, 23, 30, 0).to_std_timepoint(),
1✔
1416
                    ocsp_ee_delegate_malformed,
1417
                    Botan::Certificate_Status_Code::OCSP_RESPONSE_MISSING_KEYUSAGE,
1418
                    Botan::Certificate_Status_Code::OCSP_ISSUER_NOT_TRUSTED);
1✔
1419

1420
         return result;
1✔
1421
      }
2✔
1422

1423
      static Test::Result validate_with_forged_ocsp_using_self_signed_cert() {
1✔
1424
         Test::Result result("path check with forged ocsp using self-signed certificate");
1✔
1425
         Botan::Certificate_Store_In_Memory trusted;
1✔
1426

1427
         auto restrictions = Botan::Path_Validation_Restrictions(true,    // require revocation info
1✔
1428
                                                                 110,     // minimum key strength
1429
                                                                 false);  // OCSP for all intermediates
2✔
1430

1431
         auto ee = load_test_X509_cert("x509/ocsp/randombit.pem");
1✔
1432
         auto ca = load_test_X509_cert("x509/ocsp/letsencrypt.pem");
1✔
1433
         auto trust_root = load_test_X509_cert("x509/ocsp/identrust.pem");
1✔
1434
         trusted.add_certificate(trust_root);
1✔
1435

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

1438
         auto check_path = [&](const std::string& forged_ocsp,
3✔
1439
                               const Botan::Certificate_Status_Code expected,
1440
                               const Botan::Certificate_Status_Code also_expected) {
1441
            auto ocsp = load_test_OCSP_resp(forged_ocsp);
2✔
1442
            const auto path_result =
2✔
1443
               Botan::x509_path_validate(cert_path,
4✔
1444
                                         restrictions,
1445
                                         trusted,
2✔
1446
                                         "",
1447
                                         Botan::Usage_Type::UNSPECIFIED,
1448
                                         Botan::calendar_point(2016, 11, 18, 12, 30, 0).to_std_timepoint(),
2✔
1449
                                         std::chrono::milliseconds(0),
2✔
1450
                                         {ocsp});
4✔
1451

1452
            result.test_enum_eq(
2✔
1453
               "Path validation with forged OCSP response should fail with", path_result.result(), expected);
2✔
1454
            result.test_is_true("Secondary error is also present",
4✔
1455
                                flatten(path_result.all_statuses()).contains(also_expected));
4✔
1456
            result.test_note(std::string("Failed with: ") + Botan::to_string(path_result.result()));
6✔
1457
         };
8✔
1458

1459
         // In both cases the path validation should detect the forged OCSP
1460
         // response and generate an appropriate error. By no means it should
1461
         // follow the unauthentic OCSP response.
1462
         check_path("x509/ocsp/randombit_ocsp_forged_valid.der",
1✔
1463
                    Botan::Certificate_Status_Code::CERT_ISSUER_NOT_FOUND,
1464
                    Botan::Certificate_Status_Code::OCSP_ISSUER_NOT_TRUSTED);
1465
         check_path("x509/ocsp/randombit_ocsp_forged_revoked.der",
1✔
1466
                    Botan::Certificate_Status_Code::CERT_ISSUER_NOT_FOUND,
1467
                    Botan::Certificate_Status_Code::OCSP_ISSUER_NOT_TRUSTED);
1468

1469
         return result;
1✔
1470
      }
2✔
1471

1472
      static Test::Result validate_with_ocsp_self_signed_by_intermediate_cert() {
1✔
1473
         Test::Result result(
1✔
1474
            "path check with ocsp response for intermediate that is (maliciously) self-signed by the intermediate");
1✔
1475
         Botan::Certificate_Store_In_Memory trusted;
1✔
1476

1477
         auto restrictions = Botan::Path_Validation_Restrictions(true,   // require revocation info
1✔
1478
                                                                 110,    // minimum key strength
1479
                                                                 true);  // OCSP for all intermediates
2✔
1480

1481
         // See `src/scripts/mychain_creater.sh` if you need to recreate those
1482
         auto ee = load_test_X509_cert("x509/ocsp/mychain_ee.pem");
1✔
1483
         auto ca = load_test_X509_cert("x509/ocsp/mychain_int.pem");
1✔
1484
         auto trust_root = load_test_X509_cert("x509/ocsp/mychain_root.pem");
1✔
1485

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

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

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

1495
         const auto path_result =
1✔
1496
            Botan::x509_path_validate(cert_path,
4✔
1497
                                      restrictions,
1498
                                      trusted,
1499
                                      "",
1500
                                      Botan::Usage_Type::UNSPECIFIED,
1501
                                      Botan::calendar_point(2022, 9, 22, 22, 30, 0).to_std_timepoint(),
1✔
1502
                                      std::chrono::milliseconds(0),
1✔
1503
                                      {ocsp_ee, ocsp_ca});
3✔
1504
         result.test_is_true("should reject intermediate OCSP response",
1✔
1505
                             path_result.result() == Botan::Certificate_Status_Code::OCSP_ISSUER_NOT_FOUND);
1✔
1506
         result.test_note(std::string("Failed with: ") + Botan::to_string(path_result.result()));
3✔
1507

1508
         return result;
1✔
1509
      }
7✔
1510

1511
      std::vector<Test::Result> run() override {
1✔
1512
         return {validate_with_ocsp_with_next_update_without_max_age(),
1✔
1513
                 validate_with_ocsp_with_next_update_with_max_age(),
1514
                 validate_with_ocsp_without_next_update_without_max_age(),
1515
                 validate_with_ocsp_without_next_update_with_max_age(),
1516
                 validate_with_ocsp_with_authorized_responder(),
1517
                 validate_with_ocsp_with_authorized_responder_without_keyusage(),
1518
                 validate_with_forged_ocsp_using_self_signed_cert(),
1519
                 validate_with_ocsp_self_signed_by_intermediate_cert()};
9✔
1520
      }
1✔
1521
};
1522

1523
BOTAN_REGISTER_TEST("x509", "x509_path_with_ocsp", Path_Validation_With_OCSP_Tests);
1524

1525
   #endif
1526

1527
   #if defined(BOTAN_HAS_ECDSA)
1528

1529
class CVE_2020_0601_Tests final : public Test {
1✔
1530
   public:
1531
      std::vector<Test::Result> run() override {
1✔
1532
         Test::Result result("CVE-2020-0601");
1✔
1533

1534
         if(!Botan::EC_Group::supports_application_specific_group()) {
1✔
1535
            result.test_note("Skipping as application specific groups are not supported");
×
1536
            return {result};
×
1537
         }
1538

1539
         if(!Botan::EC_Group::supports_named_group("secp384r1")) {
1✔
1540
            result.test_note("Skipping as secp384r1 is not supported");
×
1541
            return {result};
×
1542
         }
1543

1544
         const auto& secp384r1 = Botan::EC_Group::from_name("secp384r1");
1✔
1545
         const Botan::OID curveball_oid("1.3.6.1.4.1.25258.4.2020.0601");
1✔
1546
         const Botan::EC_Group curveball(
1✔
1547
            curveball_oid,
1548
            secp384r1.get_p(),
1549
            secp384r1.get_a(),
1550
            secp384r1.get_b(),
1551
            BigInt(
2✔
1552
               "0xC711162A761D568EBEB96265D4C3CEB4F0C330EC8F6DD76E39BCC849ABABB8E34378D581065DEFC77D9FCED6B39075DE"),
1553
            BigInt(
2✔
1554
               "0x0CB090DE23BAC8D13E67E019A91B86311E5F342DEE17FD15FB7E278A32A1EAC98FC97E18CB2F3B2C487A7DA6F40107AC"),
1555
            secp384r1.get_order());
2✔
1556

1557
         auto ca_crt = Botan::X509_Certificate(Test::data_file("x509/cve-2020-0601/ca.pem"));
2✔
1558
         auto fake_ca_crt = Botan::X509_Certificate(Test::data_file("x509/cve-2020-0601/fake_ca.pem"));
2✔
1559
         auto ee_crt = Botan::X509_Certificate(Test::data_file("x509/cve-2020-0601/ee.pem"));
2✔
1560

1561
         Botan::Certificate_Store_In_Memory trusted;
1✔
1562
         trusted.add_certificate(ca_crt);
1✔
1563

1564
         const auto restrictions = Botan::Path_Validation_Restrictions(false, 80, false);
2✔
1565

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

1568
         const auto path_result1 = Botan::x509_path_validate(std::vector<Botan::X509_Certificate>{ee_crt, fake_ca_crt},
5✔
1569
                                                             restrictions,
1570
                                                             trusted,
1571
                                                             "",
1572
                                                             Botan::Usage_Type::UNSPECIFIED,
1573
                                                             valid_time,
1574
                                                             std::chrono::milliseconds(0),
1✔
1575
                                                             {});
3✔
1576

1577
         result.test_is_true("Validation failed", !path_result1.successful_validation());
1✔
1578

1579
         result.test_is_true("Expected status",
1✔
1580
                             path_result1.result() == Botan::Certificate_Status_Code::CANNOT_ESTABLISH_TRUST);
1✔
1581

1582
         const auto path_result2 = Botan::x509_path_validate(std::vector<Botan::X509_Certificate>{ee_crt},
3✔
1583
                                                             restrictions,
1584
                                                             trusted,
1585
                                                             "",
1586
                                                             Botan::Usage_Type::UNSPECIFIED,
1587
                                                             valid_time,
1588
                                                             std::chrono::milliseconds(0),
1✔
1589
                                                             {});
3✔
1590

1591
         result.test_is_true("Validation failed", !path_result2.successful_validation());
1✔
1592

1593
         result.test_is_true("Expected status",
1✔
1594
                             path_result2.result() == Botan::Certificate_Status_Code::CERT_ISSUER_NOT_FOUND);
1✔
1595

1596
         // Verify the signature from the bad CA is actually correct
1597
         Botan::Certificate_Store_In_Memory frusted;
1✔
1598
         frusted.add_certificate(fake_ca_crt);
1✔
1599

1600
         const auto path_result3 = Botan::x509_path_validate(std::vector<Botan::X509_Certificate>{ee_crt},
3✔
1601
                                                             restrictions,
1602
                                                             frusted,
1603
                                                             "",
1604
                                                             Botan::Usage_Type::UNSPECIFIED,
1605
                                                             valid_time,
1606
                                                             std::chrono::milliseconds(0),
1✔
1607
                                                             {});
3✔
1608

1609
         result.test_is_true("Validation succeeded", path_result3.successful_validation());
1✔
1610

1611
         return {result};
2✔
1612
      }
5✔
1613
};
1614

1615
BOTAN_REGISTER_TEST("x509", "x509_cve_2020_0601", CVE_2020_0601_Tests);
1616

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

1625
         if(Botan::has_filesystem_impl() == false) {
1✔
1626
            result.test_note("Skipping due to missing filesystem access");
×
1627
            return {result};
×
1628
         }
1629

1630
         const Botan::X509_Certificate root(Test::data_file("x509/misc/crl_without_nextupdate/ca.pem"));
2✔
1631
         const Botan::X509_Certificate revoked_subject(Test::data_file("x509/misc/crl_without_nextupdate/01.pem"));
2✔
1632
         const Botan::X509_Certificate valid_subject(Test::data_file("x509/misc/crl_without_nextupdate/42.pem"));
2✔
1633

1634
         // Check that a CRL without nextUpdate is parsable
1635
         auto crl = Botan::X509_CRL(Test::data_file("x509/misc/crl_without_nextupdate/valid_forever.crl"));
2✔
1636
         result.test_is_true("this update is set", crl.this_update().time_is_set());
1✔
1637
         result.test_is_true("next update is not set", !crl.next_update().time_is_set());
1✔
1638
         result.test_is_true("CRL is not empty", !crl.get_revoked().empty());
1✔
1639

1640
         // Ensure that we support the used sig algo, otherwish stop here
1641
         if(!Botan::EC_Group::supports_named_group("brainpool512r1")) {
1✔
1642
            result.test_note("Cannot test path validation because signature algorithm is not support in this build");
×
1643
            return {result};
×
1644
         }
1645

1646
         Botan::Certificate_Store_In_Memory trusted;
1✔
1647
         trusted.add_certificate(root);
1✔
1648
         trusted.add_crl(crl);
1✔
1649

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

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

1656
         // Validate a certificate that is not listed in the CRL
1657
         const auto valid = Botan::x509_path_validate(
1✔
1658
            valid_subject, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, valid_time);
1✔
1659
         if(!result.test_is_true("Valid certificate", valid.successful_validation())) {
1✔
1660
            result.test_note(valid.result_string());
×
1661
         }
1662

1663
         // Ensure that a certificate listed in the CRL is recognized as revoked
1664
         const auto revoked = Botan::x509_path_validate(
1✔
1665
            revoked_subject, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, valid_time);
1✔
1666
         if(!result.test_is_true("No valid certificate", !revoked.successful_validation())) {
1✔
1667
            result.test_note(revoked.result_string());
×
1668
         }
1669
         result.test_enum_eq(
2✔
1670
            "Certificate is revoked", revoked.result(), Botan::Certificate_Status_Code::CERT_IS_REVOKED);
1✔
1671

1672
         return {result};
2✔
1673
      }
2✔
1674
};
1675

1676
BOTAN_REGISTER_TEST("x509", "x509_path_immortal_crl", Path_Validation_With_Immortal_CRL);
1677

1678
   #endif
1679

1680
   #if defined(BOTAN_HAS_XMSS_RFC8391)
1681

1682
class XMSS_Path_Validation_Tests final : public Test {
1✔
1683
   public:
1684
      static Test::Result validate_self_signed(const std::string& name, const std::string& file) {
2✔
1685
         Test::Result result(name);
2✔
1686

1687
         const Botan::Path_Validation_Restrictions restrictions;
4✔
1688
         auto self_signed = Botan::X509_Certificate(Test::data_file("x509/xmss/" + file));
4✔
1689

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

1693
         auto status = Botan::PKIX::overall_status(
2✔
1694
            Botan::PKIX::check_chain(cert_path, valid_time, "", Botan::Usage_Type::UNSPECIFIED, restrictions));
4✔
1695
         result.test_str_eq("Cert validation status", Botan::to_string(status), "Verified");
2✔
1696
         return result;
2✔
1697
      }
4✔
1698

1699
      std::vector<Test::Result> run() override {
1✔
1700
         if(Botan::has_filesystem_impl() == false) {
1✔
1701
            return {Test::Result::Note("XMSS path validation", "Skipping due to missing filesystem access")};
×
1702
         }
1703

1704
         return {
1✔
1705
            validate_self_signed("XMSS path validation with certificate created by ISARA corp", "xmss_isara_root.pem"),
1✔
1706
            validate_self_signed("XMSS path validation with certificate created by BouncyCastle",
1✔
1707
                                 "xmss_bouncycastle_sha256_10_root.pem")};
3✔
1708
      }
4✔
1709
};
1710

1711
BOTAN_REGISTER_TEST("x509", "x509_path_xmss", XMSS_Path_Validation_Tests);
1712

1713
   #endif
1714

1715
   #if defined(BOTAN_HAS_RSA) && defined(BOTAN_HAS_EMSA_PKCS1)
1716

1717
class Name_Constraint_DN_Prefix_Test final : public Test {
1✔
1718
   public:
1719
      static Test::Result validate_name_constraint_dn_prefix_accepted() {
1✔
1720
         // Regression test case taken from strongSwan that originally failed with Botan.
1721
         // OpenSSL also accepts this case.
1722
         // Excluded constraint: C=CH, O=another
1723
         // Subject DN: C=CH, CN=tester, O=another
1724
         // The validation should accept the chain since CN at position 1 breaks prefix match.
1725
         Test::Result result("strongSwan name constraint DN prefix matching accepted");
1✔
1726

1727
         const auto validation_time = Botan::calendar_point(2025, 12, 29, 9, 50, 0).to_std_timepoint();
1✔
1728

1729
         // These files were created with strongSwan using the OpenSSL plugin based on the
1730
         // strongSwan `test_excluded_dn` test case of the `certnames` test suite.
1731
         const Botan::X509_Certificate ca_accepted(
1✔
1732
            Test::data_file("x509/name_constraint_prefix/nc_prefix_strongswan_ca_accepted.pem"));
2✔
1733
         const Botan::X509_Certificate intermediate_accepted(
1✔
1734
            Test::data_file("x509/name_constraint_prefix/nc_prefix_strongswan_im_accepted.pem"));
2✔
1735
         const Botan::X509_Certificate subject_cert_accepted(
1✔
1736
            Test::data_file("x509/name_constraint_prefix/nc_prefix_strongswan_subject_accepted.pem"));
2✔
1737

1738
         result.test_str_eq("CA DN", ca_accepted.subject_dn().to_string(), R"(C="CH",O="strongSwan",CN="CA")");
1✔
1739
         result.test_str_eq(
2✔
1740
            "IM DN", intermediate_accepted.subject_dn().to_string(), R"(C="CH",O="strongSwan",CN="IM")");
1✔
1741
         result.test_str_eq(
2✔
1742
            "Subject DN", subject_cert_accepted.subject_dn().to_string(), R"(C="CH",CN="tester",O="another")");
1✔
1743

1744
         Botan::Certificate_Store_In_Memory trusted;
1✔
1745
         trusted.add_certificate(ca_accepted);
1✔
1746
         const std::vector<Botan::X509_Certificate> chain = {subject_cert_accepted, intermediate_accepted};
3✔
1747

1748
         const Botan::Path_Validation_Restrictions restrictions(false, 80);
2✔
1749

1750
         const Botan::Path_Validation_Result validation_result = Botan::x509_path_validate(
1✔
1751
            chain, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
1✔
1752

1753
         result.test_is_true("Verification passes since Name Constraints are not a prefix",
1✔
1754
                             validation_result.successful_validation());
1✔
1755
         return result;
1✔
1756
      }
2✔
1757

1758
      static Test::Result validate_name_constraint_dn_prefix_not_accepted() {
1✔
1759
         // Excluded constraint: C=CH, O=another
1760
         // Subject DN: C=CH, O=another, CN=tester
1761
         // The validation should *not* accept the chain since the constraint is a prefix.
1762
         // This test catches changes in order of the RDNs to be compared in name constraint checks,
1763
         // which was an issue due to constructors of Botan::X509_DN taking a multimap.
1764
         Test::Result result("strongSwan name constraint DN prefix matching not accepted");
1✔
1765

1766
         const auto validation_time = Botan::calendar_point(2025, 12, 29, 9, 50, 0).to_std_timepoint();
1✔
1767

1768
         // These files were created with strongSwan using the OpenSSL plugin based on the
1769
         // strongSwan `test_excluded_dn` test case of the `certnames` test suite.
1770
         const Botan::X509_Certificate ca_not_accepted(
1✔
1771
            Test::data_file("x509/name_constraint_prefix/nc_prefix_strongswan_ca_not_accepted.pem"));
2✔
1772
         const Botan::X509_Certificate intermediate_not_accepted(
1✔
1773
            Test::data_file("x509/name_constraint_prefix/nc_prefix_strongswan_im_not_accepted.pem"));
2✔
1774
         const Botan::X509_Certificate subject_cert_not_accepted(
1✔
1775
            Test::data_file("x509/name_constraint_prefix/nc_prefix_strongswan_subject_not_accepted.pem"));
2✔
1776

1777
         result.test_str_eq("CA DN", ca_not_accepted.subject_dn().to_string(), R"(C="CH",O="strongSwan",CN="CA")");
1✔
1778
         result.test_str_eq(
2✔
1779
            "IM DN", intermediate_not_accepted.subject_dn().to_string(), R"(C="CH",O="strongSwan",CN="IM")");
1✔
1780
         result.test_str_eq(
2✔
1781
            "Subject DN", subject_cert_not_accepted.subject_dn().to_string(), R"(C="CH",O="another",CN="tester")");
1✔
1782

1783
         Botan::Certificate_Store_In_Memory trusted;
1✔
1784
         trusted.add_certificate(ca_not_accepted);
1✔
1785
         const std::vector<Botan::X509_Certificate> chain = {subject_cert_not_accepted, intermediate_not_accepted};
3✔
1786

1787
         const Botan::Path_Validation_Restrictions restrictions(false, 80);
2✔
1788

1789
         const Botan::Path_Validation_Result validation_result = Botan::x509_path_validate(
1✔
1790
            chain, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
1✔
1791

1792
         result.test_is_false("Verification does not pass since Name Constraints is a prefix",
1✔
1793
                              validation_result.successful_validation());
1✔
1794
         return result;
1✔
1795
      }
2✔
1796

1797
      std::vector<Test::Result> run() override {
1✔
1798
         return {validate_name_constraint_dn_prefix_accepted(), validate_name_constraint_dn_prefix_not_accepted()};
3✔
1799
      }
1✔
1800
};
1801

1802
BOTAN_REGISTER_TEST("x509", "x509_path_nc_prefix_dn", Name_Constraint_DN_Prefix_Test);
1803

1804
   #endif
1805

1806
#endif
1807

1808
}  // namespace
1809

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