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

randombit / botan / 16797119192

07 Aug 2025 06:42AM UTC coverage: 90.708% (+0.03%) from 90.677%
16797119192

Pull #5047

github

web-flow
Merge 39793291c into 1f9987555
Pull Request #5047: X.509 Path: Option for Non-Self-Signed Trust Anchors

100162 of 110422 relevant lines covered (90.71%)

12257980.97 hits per line

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

93.73
/src/tests/test_x509_path.cpp
1
/*
2
* (C) 2006,2011,2012,2014,2015 Jack Lloyd
3
* (C) 2022 René Meusel, Rohde & Schwarz Cybersecurity
4
*
5
* Botan is released under the Simplified BSD License (see license.txt)
6
*/
7

8
#include "tests.h"
9

10
#if defined(BOTAN_HAS_X509_CERTIFICATES)
11
   #include <botan/data_src.h>
12
   #include <botan/exceptn.h>
13
   #include <botan/pkcs10.h>
14
   #include <botan/x509_crl.h>
15
   #include <botan/x509_key.h>
16
   #include <botan/x509path.h>
17
   #include <botan/internal/calendar.h>
18
   #include <botan/internal/filesystem.h>
19
   #include <botan/internal/fmt.h>
20
   #include <botan/internal/parsing.h>
21

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

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

34
namespace Botan_Tests {
35

36
namespace {
37

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

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

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

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

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

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

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

68
   return m;
18✔
69
}
9✔
70

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

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

81
   return certs;
80✔
82
}
80✔
83

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

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

91
   return result;
4✔
92
}
×
93

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

186
            if(path_result.successful_validation() && path_result.trust_root() != root) {
2✔
187
               path_result = Botan::Path_Validation_Result(Botan::Certificate_Status_Code::CANNOT_ESTABLISH_TRUST);
×
188
            }
189

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

202
         return results;
2✔
203
      }
2✔
204
};
205

206
BOTAN_REGISTER_TEST("x509", "x509_path_x509test", X509test_Path_Validation_Tests);
207

208
class NIST_Path_Validation_Tests final : public Test {
×
209
   public:
210
      std::vector<Test::Result> run() override {
1✔
211
         std::vector<Test::Result> results;
1✔
212
         for(const auto& restrictions : restrictions_to_test(true, true)) {
3✔
213
            auto partial_res = run_with_restrictions(restrictions);
2✔
214
            results.insert(results.end(), partial_res.begin(), partial_res.end());
2✔
215
         }
3✔
216
         return results;
1✔
217
      }
×
218

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

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

229
   std::vector<Test::Result> results;
2✔
230

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

245
   const Botan::X509_Certificate root_cert(Test::data_file("x509/nist/root.crt"));
4✔
246
   const Botan::X509_CRL root_crl(Test::data_file("x509/nist/root.crl"));
4✔
247

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

250
   for(const auto& [test_name, expected_result] : expected) {
154✔
251
      Test::Result result("NIST path validation");
152✔
252
      result.start_timer();
152✔
253

254
      try {
152✔
255
         const auto all_files = Test::files_in_data_dir("x509/nist/" + test_name);
152✔
256

257
         Botan::Certificate_Store_In_Memory store;
152✔
258
         store.add_certificate(root_cert);
152✔
259
         store.add_crl(root_crl);
152✔
260

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

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

274
         Botan::Path_Validation_Result validation_result = Botan::x509_path_validate(
152✔
275
            end_certs, restrictions, store, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
152✔
276

277
         result.test_eq(test_name + " path validation result", validation_result.result_string(), expected_result);
304✔
278
      } catch(std::exception& e) {
152✔
279
         result.test_failure(test_name, e.what());
×
280
      }
×
281

282
      result.end_timer();
152✔
283
      results.push_back(result);
152✔
284
   }
152✔
285

286
   return results;
2✔
287
}
154✔
288

289
BOTAN_REGISTER_TEST("x509", "x509_path_nist", NIST_Path_Validation_Tests);
290

291
class Extended_Path_Validation_Tests final : public Test {
×
292
   public:
293
      std::vector<Test::Result> run() override;
294
};
295

296
std::vector<Test::Result> Extended_Path_Validation_Tests::run() {
1✔
297
   if(Botan::has_filesystem_impl() == false) {
1✔
298
      return {Test::Result::Note("Extended x509 path validation", "Skipping due to missing filesystem access")};
×
299
   }
300

301
   std::vector<Test::Result> results;
1✔
302

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

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

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

311
      Botan::Certificate_Store_In_Memory store;
3✔
312

313
      for(const auto& file : all_files) {
13✔
314
         if(file.ends_with(".crt") && file != "end.crt") {
10✔
315
            store.add_certificate(Botan::X509_Certificate(file));
10✔
316
         }
317
      }
318

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

321
      Botan::Path_Validation_Restrictions restrictions;
6✔
322
      Botan::Path_Validation_Result validation_result =
3✔
323
         Botan::x509_path_validate(end_user, restrictions, store, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
3✔
324

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

327
      result.end_timer();
3✔
328
      results.push_back(result);
3✔
329
   }
3✔
330

331
   return results;
1✔
332
}
1✔
333

334
BOTAN_REGISTER_TEST("x509", "x509_path_extended", Extended_Path_Validation_Tests);
335

336
class PSS_Path_Validation_Tests : public Test {
×
337
   public:
338
      std::vector<Test::Result> run() override;
339
};
340

341
std::vector<Test::Result> PSS_Path_Validation_Tests::run() {
1✔
342
   if(Botan::has_filesystem_impl() == false) {
1✔
343
      return {Test::Result::Note("RSA-PSS X509 signature validation", "Skipping due to missing filesystem access")};
×
344
   }
345

346
   std::vector<Test::Result> results;
1✔
347

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

350
   auto validation_times_iter = validation_times.begin();
1✔
351

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

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

358
      std::optional<Botan::X509_CRL> crl;
118✔
359
      std::optional<Botan::X509_Certificate> end;
118✔
360
      std::optional<Botan::X509_Certificate> root;
118✔
361
      Botan::Certificate_Store_In_Memory store;
118✔
362
      std::optional<Botan::PKCS10_Request> csr;
118✔
363

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

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

368
      for(const auto& file : all_files) {
345✔
369
         if(file.find("end.crt") != std::string::npos) {
227✔
370
            end = Botan::X509_Certificate(file);
113✔
371
         } else if(file.find("root.crt") != std::string::npos) {
114✔
372
            root = Botan::X509_Certificate(file);
97✔
373
            store.add_certificate(*root);
97✔
374
         } else if(file.ends_with(".crl")) {
17✔
375
            crl = Botan::X509_CRL(file);
6✔
376
         } else if(file.ends_with(".csr")) {
11✔
377
            csr = Botan::PKCS10_Request(file);
5✔
378
         }
379
      }
380

381
      if(end && crl && root) {
118✔
382
         // CRL test
383
         const std::vector<Botan::X509_Certificate> cert_path = {*end, *root};
18✔
384
         const std::vector<std::optional<Botan::X509_CRL>> crls = {crl};
12✔
385
         auto crl_status = Botan::PKIX::check_crl(
6✔
386
            cert_path,
387
            crls,
388
            validation_time);  // alternatively we could just call crl.check_signature( root_pubkey )
6✔
389

390
         result.test_eq(test_name + " check_crl result",
12✔
391
                        Botan::Path_Validation_Result::status_string(Botan::PKIX::overall_status(crl_status)),
392
                        expected_result);
393
      } else if(end && root) {
118✔
394
         // CRT chain test
395

396
         Botan::Path_Validation_Restrictions restrictions(false, 80);  // SHA-1 is used
182✔
397

398
         Botan::Path_Validation_Result validation_result =
91✔
399
            Botan::x509_path_validate(*end, restrictions, store, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
91✔
400

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

414
      result.end_timer();
118✔
415
      results.push_back(result);
118✔
416
   }
334✔
417

418
   return results;
1✔
419
}
19✔
420

421
BOTAN_REGISTER_TEST("x509", "x509_path_rsa_pss", PSS_Path_Validation_Tests);
422

423
class Validate_V1Cert_Test final : public Test {
×
424
   public:
425
      std::vector<Test::Result> run() override;
426
};
427

428
std::vector<Test::Result> Validate_V1Cert_Test::run() {
1✔
429
   if(Botan::has_filesystem_impl() == false) {
1✔
430
      return {Test::Result::Note("BSI path validation", "Skipping due to missing filesystem access")};
×
431
   }
432

433
   std::vector<Test::Result> results;
1✔
434

435
   const std::string root_crt = Test::data_file("x509/misc/v1ca/root.pem");
1✔
436
   const std::string int_crt = Test::data_file("x509/misc/v1ca/int.pem");
1✔
437
   const std::string ee_crt = Test::data_file("x509/misc/v1ca/ee.pem");
1✔
438

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

441
   Botan::X509_Certificate root(root_crt);
1✔
442
   Botan::X509_Certificate intermediate(int_crt);
1✔
443
   Botan::X509_Certificate ee_cert(ee_crt);
1✔
444

445
   Botan::Certificate_Store_In_Memory trusted;
1✔
446
   trusted.add_certificate(root);
1✔
447

448
   std::vector<Botan::X509_Certificate> chain = {ee_cert, intermediate};
3✔
449

450
   Botan::Path_Validation_Restrictions restrictions;
2✔
451
   Botan::Path_Validation_Result validation_result =
1✔
452
      Botan::x509_path_validate(chain, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
1✔
453

454
   Test::Result result("Verifying using v1 certificate");
1✔
455
   result.test_eq("Path validation result", validation_result.result_string(), "Verified");
2✔
456

457
   Botan::Certificate_Store_In_Memory empty;
1✔
458

459
   std::vector<Botan::X509_Certificate> new_chain = {ee_cert, intermediate, root};
4✔
460

461
   Botan::Path_Validation_Result validation_result2 =
1✔
462
      Botan::x509_path_validate(new_chain, restrictions, empty, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
1✔
463

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

466
   return {result};
2✔
467
}
4✔
468

469
BOTAN_REGISTER_TEST("x509", "x509_v1_ca", Validate_V1Cert_Test);
470

471
class Validate_V2Uid_in_V1_Test final : public Test {
×
472
   public:
473
      std::vector<Test::Result> run() override;
474
};
475

476
std::vector<Test::Result> Validate_V2Uid_in_V1_Test::run() {
1✔
477
   if(Botan::has_filesystem_impl() == false) {
1✔
478
      return {Test::Result::Note("Path validation", "Skipping due to missing filesystem access")};
×
479
   }
480

481
   std::vector<Test::Result> results;
1✔
482

483
   const std::string root_crt = Test::data_file("x509/v2-in-v1/root.pem");
1✔
484
   const std::string int_crt = Test::data_file("x509/v2-in-v1/int.pem");
1✔
485
   const std::string ee_crt = Test::data_file("x509/v2-in-v1/leaf.pem");
1✔
486

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

489
   Botan::X509_Certificate root(root_crt);
1✔
490
   Botan::X509_Certificate intermediate(int_crt);
1✔
491
   Botan::X509_Certificate ee_cert(ee_crt);
1✔
492

493
   Botan::Certificate_Store_In_Memory trusted;
1✔
494
   trusted.add_certificate(root);
1✔
495

496
   std::vector<Botan::X509_Certificate> chain = {ee_cert, intermediate};
3✔
497

498
   Botan::Path_Validation_Restrictions restrictions;
2✔
499
   Botan::Path_Validation_Result validation_result =
1✔
500
      Botan::x509_path_validate(chain, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
1✔
501

502
   Test::Result result("Verifying v1 certificate using v2 uid fields");
1✔
503
   result.test_eq("Path validation failed", validation_result.successful_validation(), false);
1✔
504
   result.test_eq(
3✔
505
      "Path validation result", validation_result.result_string(), "Encountered v2 identifiers in v1 certificate");
2✔
506

507
   return {result};
2✔
508
}
3✔
509

510
BOTAN_REGISTER_TEST("x509", "x509_v2uid_in_v1", Validate_V2Uid_in_V1_Test);
511

512
class Validate_Name_Constraint_SAN_Test final : public Test {
×
513
   public:
514
      std::vector<Test::Result> run() override;
515
};
516

517
std::vector<Test::Result> Validate_Name_Constraint_SAN_Test::run() {
1✔
518
   if(Botan::has_filesystem_impl() == false) {
1✔
519
      return {Test::Result::Note("Path validation", "Skipping due to missing filesystem access")};
×
520
   }
521

522
   std::vector<Test::Result> results;
1✔
523

524
   const std::string root_crt = Test::data_file("x509/name_constraint_san/root.pem");
1✔
525
   const std::string int_crt = Test::data_file("x509/name_constraint_san/int.pem");
1✔
526
   const std::string ee_crt = Test::data_file("x509/name_constraint_san/leaf.pem");
1✔
527

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

530
   Botan::X509_Certificate root(root_crt);
1✔
531
   Botan::X509_Certificate intermediate(int_crt);
1✔
532
   Botan::X509_Certificate ee_cert(ee_crt);
1✔
533

534
   Botan::Certificate_Store_In_Memory trusted;
1✔
535
   trusted.add_certificate(root);
1✔
536

537
   std::vector<Botan::X509_Certificate> chain = {ee_cert, intermediate};
3✔
538

539
   Botan::Path_Validation_Restrictions restrictions;
2✔
540
   Botan::Path_Validation_Result validation_result =
1✔
541
      Botan::x509_path_validate(chain, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
1✔
542

543
   Test::Result result("Verifying certificate with alternative SAN violating name constraint");
1✔
544
   result.test_eq("Path validation failed", validation_result.successful_validation(), false);
1✔
545
   result.test_eq(
3✔
546
      "Path validation result", validation_result.result_string(), "Certificate does not pass name constraint");
2✔
547

548
   return {result};
2✔
549
}
3✔
550

551
BOTAN_REGISTER_TEST("x509", "x509_name_constraint_san", Validate_Name_Constraint_SAN_Test);
552

553
class Validate_Name_Constraint_CaseInsensitive final : public Test {
×
554
   public:
555
      std::vector<Test::Result> run() override;
556
};
557

558
std::vector<Test::Result> Validate_Name_Constraint_CaseInsensitive::run() {
1✔
559
   if(Botan::has_filesystem_impl() == false) {
1✔
560
      return {Test::Result::Note("Path validation", "Skipping due to missing filesystem access")};
×
561
   }
562

563
   std::vector<Test::Result> results;
1✔
564

565
   const std::string root_crt = Test::data_file("x509/misc/name_constraint_ci/root.pem");
1✔
566
   const std::string int_crt = Test::data_file("x509/misc/name_constraint_ci/int.pem");
1✔
567
   const std::string ee_crt = Test::data_file("x509/misc/name_constraint_ci/leaf.pem");
1✔
568

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

571
   Botan::X509_Certificate root(root_crt);
1✔
572
   Botan::X509_Certificate intermediate(int_crt);
1✔
573
   Botan::X509_Certificate ee_cert(ee_crt);
1✔
574

575
   Botan::Certificate_Store_In_Memory trusted;
1✔
576
   trusted.add_certificate(root);
1✔
577

578
   std::vector<Botan::X509_Certificate> chain = {ee_cert, intermediate};
3✔
579

580
   Botan::Path_Validation_Restrictions restrictions;
2✔
581
   Botan::Path_Validation_Result validation_result =
1✔
582
      Botan::x509_path_validate(chain, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
1✔
583

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

587
   return {result};
2✔
588
}
3✔
589

590
BOTAN_REGISTER_TEST("x509", "x509_name_constraint_ci", Validate_Name_Constraint_CaseInsensitive);
591

592
class Validate_Name_Constraint_NoCheckSelf final : public Test {
×
593
   public:
594
      std::vector<Test::Result> run() override;
595
};
596

597
std::vector<Test::Result> Validate_Name_Constraint_NoCheckSelf::run() {
1✔
598
   if(Botan::has_filesystem_impl() == false) {
1✔
599
      return {Test::Result::Note("Path validation", "Skipping due to missing filesystem access")};
×
600
   }
601

602
   std::vector<Test::Result> results;
1✔
603

604
   const std::string root_crt = Test::data_file("x509/misc/nc_skip_self/root.pem");
1✔
605
   const std::string int_crt = Test::data_file("x509/misc/nc_skip_self/int.pem");
1✔
606
   const std::string ee_crt = Test::data_file("x509/misc/nc_skip_self/leaf.pem");
1✔
607

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

610
   Botan::X509_Certificate root(root_crt);
1✔
611
   Botan::X509_Certificate intermediate(int_crt);
1✔
612
   Botan::X509_Certificate ee_cert(ee_crt);
1✔
613

614
   Botan::Certificate_Store_In_Memory trusted;
1✔
615
   trusted.add_certificate(root);
1✔
616

617
   std::vector<Botan::X509_Certificate> chain = {ee_cert, intermediate};
3✔
618

619
   Botan::Path_Validation_Restrictions restrictions;
2✔
620
   Botan::Path_Validation_Result validation_result =
1✔
621
      Botan::x509_path_validate(chain, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
1✔
622

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

626
   return {result};
2✔
627
}
3✔
628

629
BOTAN_REGISTER_TEST("x509", "x509_name_constraint_no_check_self", Validate_Name_Constraint_NoCheckSelf);
630

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

638
         const std::string trusted_root_crt = Test::data_file("x509/misc/root_cert_time_check/root.crt");
1✔
639
         const std::string leaf_crt = Test::data_file("x509/misc/root_cert_time_check/leaf.crt");
1✔
640

641
         const Botan::X509_Certificate trusted_root_cert(trusted_root_crt);
1✔
642
         const Botan::X509_Certificate leaf_cert(leaf_crt);
1✔
643

644
         Botan::Certificate_Store_In_Memory trusted;
1✔
645
         trusted.add_certificate(trusted_root_cert);
1✔
646

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

649
         Test::Result result("Root cert time check");
1✔
650

651
         auto assert_path_validation_result = [&](std::string_view descr,
11✔
652
                                                  bool ignore_trusted_root_time_range,
653
                                                  uint32_t year,
654
                                                  Botan::Certificate_Status_Code exp_status,
655
                                                  std::optional<Botan::Certificate_Status_Code> exp_warning =
656
                                                     std::nullopt) {
657
            const Botan::Path_Validation_Restrictions restrictions(
10✔
658
               false,
659
               110,
660
               false,
661
               std::chrono::seconds::zero(),
662
               std::make_unique<Botan::Certificate_Store_In_Memory>(),
×
663
               ignore_trusted_root_time_range);
20✔
664

665
            const Botan::Path_Validation_Result validation_result =
10✔
666
               Botan::x509_path_validate(chain,
10✔
667
                                         restrictions,
668
                                         trusted,
10✔
669
                                         "",
670
                                         Botan::Usage_Type::UNSPECIFIED,
671
                                         Botan::calendar_point(year, 1, 1, 1, 0, 0).to_std_timepoint());
10✔
672
            const std::string descr_str = Botan::fmt(
10✔
673
               "Root cert validity range {}: {}", ignore_trusted_root_time_range ? "ignored" : "checked", descr);
15✔
674

675
            result.test_is_eq(descr_str, validation_result.result(), exp_status);
10✔
676
            const auto warnings = validation_result.warnings();
10✔
677
            BOTAN_ASSERT_NOMSG(warnings.size() == 2);
10✔
678
            result.confirm("No warning for leaf cert", warnings.at(0).empty());
20✔
679
            if(exp_warning) {
10✔
680
               result.confirm("Warning for root cert",
12✔
681
                              warnings.at(1).size() == 1 && warnings.at(1).contains(*exp_warning));
8✔
682
            } else {
683
               result.confirm("No warning for root cert", warnings.at(1).empty());
12✔
684
            }
685
         };
10✔
686
         // (Trusted) root cert validity range: 2022-2028
687
         // Leaf cert validity range: 2020-2030
688

689
         // Trusted root time range is checked
690
         assert_path_validation_result(
1✔
691
            "Root and leaf certs in validity range", false, 2025, Botan::Certificate_Status_Code::OK);
692
         assert_path_validation_result(
1✔
693
            "Root and leaf certs are expired", false, 2031, Botan::Certificate_Status_Code::CERT_HAS_EXPIRED);
694
         assert_path_validation_result(
1✔
695
            "Root and leaf certs are not yet valid", false, 2019, Botan::Certificate_Status_Code::CERT_NOT_YET_VALID);
696
         assert_path_validation_result(
1✔
697
            "Root cert is expired, leaf cert not", false, 2029, Botan::Certificate_Status_Code::CERT_HAS_EXPIRED);
698
         assert_path_validation_result("Root cert is not yet valid, leaf cert is",
1✔
699
                                       false,
700
                                       2021,
701
                                       Botan::Certificate_Status_Code::CERT_NOT_YET_VALID);
702

703
         // Trusted root time range is ignored
704
         assert_path_validation_result(
1✔
705
            "Root and leaf certs in validity range", true, 2025, Botan::Certificate_Status_Code::OK);
706
         assert_path_validation_result("Root and leaf certs are expired",
2✔
707
                                       true,
708
                                       2031,
709
                                       Botan::Certificate_Status_Code::CERT_HAS_EXPIRED,
710
                                       Botan::Certificate_Status_Code::TRUSTED_CERT_HAS_EXPIRED);
1✔
711
         assert_path_validation_result("Root and leaf certs are not yet valid",
2✔
712
                                       true,
713
                                       2019,
714
                                       Botan::Certificate_Status_Code::CERT_NOT_YET_VALID,
715
                                       Botan::Certificate_Status_Code::TRUSTED_CERT_NOT_YET_VALID);
1✔
716
         assert_path_validation_result("Root cert is expired, leaf cert not",
2✔
717
                                       true,
718
                                       2029,
719
                                       Botan::Certificate_Status_Code::OK,
720
                                       Botan::Certificate_Status_Code::TRUSTED_CERT_HAS_EXPIRED);
1✔
721
         assert_path_validation_result("Root cert is not yet valid, leaf cert is",
2✔
722
                                       true,
723
                                       2021,
724
                                       Botan::Certificate_Status_Code::OK,
725
                                       Botan::Certificate_Status_Code::TRUSTED_CERT_NOT_YET_VALID);
1✔
726

727
         return {result};
2✔
728
      }
3✔
729
};
730

731
BOTAN_REGISTER_TEST("x509", "x509_root_cert_time_check", Root_Cert_Time_Check_Test);
732

733
class Non_Self_Signed_Trust_Anchors_Test final : public Test {
×
734
   private:
735
      std::pair<std::vector<Botan::X509_Certificate>, std::chrono::system_clock::time_point>
736
      test_cert_chain_with_validation_time() {
4✔
737
         // Test certs generated by https://github.com/yymax/x509test
738
         const std::string valid_chain_pem = "x509/x509test/ValidChained.pem";
4✔
739
         auto certs = load_cert_file(Test::data_file(valid_chain_pem));
4✔
740
         if(certs.size() != 5) {
4✔
741
            throw Test_Error(Botan::fmt("Failed to read all certs from {}", valid_chain_pem));
×
742
         }
743

744
         auto validation_time = Botan::calendar_point(2016, 10, 21, 4, 20, 0).to_std_timepoint();
4✔
745
         return {std::move(certs), validation_time};
4✔
746
      }
4✔
747

748
   public:
749
      std::vector<Test::Result> path_validate_test() {
1✔
750
         std::vector<Test::Result> results;
1✔
751

752
         const auto restrictions = get_allow_non_self_signed_anchors_restrictions(false, false);
1✔
753
         const auto [certs, validation_time] = test_cert_chain_with_validation_time();
1✔
754

755
         for(size_t trusted_cert_idx = 0; trusted_cert_idx < certs.size() - 1; ++trusted_cert_idx) {
5✔
756
            Test::Result result(
4✔
757
               Botan::fmt("Non-self-signed trust anchor with allow_non_self_signed_trust_anchors (chain length: {})",
4✔
758
                          trusted_cert_idx));
4✔
759

760
            const Botan::Certificate_Store_In_Memory trusted(certs[trusted_cert_idx]);
4✔
761

762
            std::vector<Botan::X509_Certificate> cert_chain(certs.begin(), certs.begin() + trusted_cert_idx);
4✔
763
            // Always add at least the leaf
764
            if(cert_chain.empty()) {
4✔
765
               cert_chain.push_back(certs.at(0));
1✔
766
            }
767

768
            Botan::Path_Validation_Result path_result = Botan::x509_path_validate(
4✔
769
               cert_chain, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
4✔
770

771
            if(path_result.successful_validation() && path_result.trust_root() != certs[trusted_cert_idx]) {
4✔
772
               path_result = Botan::Path_Validation_Result(Botan::Certificate_Status_Code::CANNOT_ESTABLISH_TRUST);
×
773
            }
774

775
            result.test_eq(
12✔
776
               "path validation failed", path_result.result_string(), to_string(Botan::Certificate_Status_Code::OK));
8✔
777

778
            results.push_back(result);
4✔
779
         }
4✔
780
         return results;
2✔
781
      }
1✔
782

783
      std::vector<Test::Result> build_path_test() {
1✔
784
         std::vector<Test::Result> results;
1✔
785

786
         const auto restrictions = get_allow_non_self_signed_anchors_restrictions(false, false);
1✔
787
         const auto certs = test_cert_chain_with_validation_time().first;
1✔
788

789
         using Cert_Path = std::vector<Botan::X509_Certificate>;
1✔
790

791
         // Helper to create a cert path {certs[0],...,certs[last_cert]}
792
         const auto path_to = [&](size_t last_cert) -> Cert_Path {
20✔
793
            return {certs.begin(), certs.begin() + last_cert + 1};
5✔
794
         };
1✔
795

796
         // Helper to create a cert store of all certificates in certs given by their indices
797
         const auto store_of = [&](auto... cert_indices) -> Botan::Certificate_Store_In_Memory {
6✔
798
            Botan::Certificate_Store_In_Memory cert_store;
5✔
799
            (cert_store.add_certificate(certs.at(cert_indices)), ...);
5✔
800
            return cert_store;
5✔
801
         };
1✔
802

803
         const std::vector<std::tuple<std::string, Botan::Certificate_Store_In_Memory, std::vector<Cert_Path>>>
1✔
804
            test_names_with_stores_with_expected_paths{
805
               {"root in store", store_of(4), std::vector<Cert_Path>{path_to(4)}},
3✔
806
               {"intermediate in store", store_of(2), std::vector<Cert_Path>{path_to(2)}},
2✔
807
               {"leaf in store", store_of(0), std::vector<Cert_Path>{path_to(0)}},
2✔
808
               {"leaf, intermediate, and root in store",
809
                store_of(0, 1, 4),
1✔
810
                std::vector<Cert_Path>{path_to(0), path_to(1), path_to(4)}},
4✔
811
               {"all in store",
812
                store_of(0, 1, 2, 3, 4),
1✔
813
                std::vector<Cert_Path>{path_to(0), path_to(1), path_to(2), path_to(3), path_to(4)}},
6✔
814
            };
6✔
815

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

819
            std::vector<Cert_Path> cert_paths;
5✔
820
            const auto build_all_res =
5✔
821
               Botan::PKIX::build_all_certificate_paths(cert_paths, {&cert_store}, certs.at(0), certs);
5✔
822
            result.test_is_eq("build_all_certificate_paths result",
5✔
823
                              to_string(build_all_res),
5✔
824
                              to_string(Botan::Certificate_Status_Code::OK));
5✔
825
            result.test_is_eq("build_all_certificate_paths paths", cert_paths, expected_paths);
5✔
826

827
            Cert_Path cert_path;
5✔
828
            const auto build_path_res =
5✔
829
               Botan::PKIX::build_certificate_path(cert_path, {&cert_store}, certs.at(0), certs);
5✔
830
            result.test_is_eq("build_certificate_path result",
5✔
831
                              to_string(build_path_res),
5✔
832
                              to_string(Botan::Certificate_Status_Code::OK));
5✔
833

834
            if(std::ranges::find(cert_paths, path_to(4)) != cert_paths.end()) {
10✔
835
               result.test_is_eq("build_certificate_path (with self-signed anchor)", cert_path, path_to(4));
6✔
836
            } else {
837
               result.test_is_eq(
4✔
838
                  "build_certificate_path (without self-signed anchor)", cert_path, expected_paths.at(0));
2✔
839
            }
840
            results.push_back(result);
5✔
841
         }
5✔
842

843
         return results;
1✔
844
      }
7✔
845

846
      std::vector<Test::Result> forbidden_self_signed_trust_anchors_test() {
1✔
847
         const auto restrictions = get_default_restrictions(false, false);  // non-self-signed anchors are forbidden
1✔
848
         const auto [certs, validation_time] = test_cert_chain_with_validation_time();
1✔
849

850
         Botan::Certificate_Store_In_Memory cert_store(certs.at(3));
1✔
851

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

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

857
         result.test_eq("unexpected path validation result",
3✔
858
                        path_result.result_string(),
2✔
859
                        to_string(Botan::Certificate_Status_Code::CANNOT_ESTABLISH_TRUST));
860

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

864
         result.test_ne("unexpected check_chain result",
3✔
865
                        Botan::Path_Validation_Result(check_chain_result, {}).result_string(),
2✔
866
                        to_string(Botan::Certificate_Status_Code::OK));
867

868
         return {result};
3✔
869
      }
3✔
870

871
      std::vector<Test::Result> stand_alone_root_test() {
1✔
872
         const auto restrictions = get_default_restrictions(false, false);
1✔
873
         const auto [certs, validation_time] = test_cert_chain_with_validation_time();
1✔
874
         const auto self_signed_root = certs.at(4);
1✔
875

876
         Botan::Certificate_Store_In_Memory cert_store(certs.at(4));
1✔
877

878
         Test::Result result("Stand alone root certificate path validation");
1✔
879

880
         const auto path_result = Botan::x509_path_validate(
1✔
881
            self_signed_root, restrictions, {&cert_store}, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
2✔
882

883
         result.test_eq("unexpected x509_path_validate result",
3✔
884
                        path_result.result_string(),
2✔
885
                        to_string(Botan::Certificate_Status_Code::OK));
886

887
         return {result};
3✔
888
      }
2✔
889

890
      std::vector<Test::Result> run() override {
1✔
891
         std::vector<Test::Result> results;
1✔
892

893
         auto res = path_validate_test();
1✔
894
         results.insert(results.end(), res.begin(), res.end());
1✔
895

896
         res = build_path_test();
1✔
897
         results.insert(results.end(), res.begin(), res.end());
1✔
898

899
         res = forbidden_self_signed_trust_anchors_test();
1✔
900
         results.insert(results.end(), res.begin(), res.end());
1✔
901

902
         res = stand_alone_root_test();
1✔
903
         results.insert(results.end(), res.begin(), res.end());
1✔
904

905
         return results;
1✔
906
      }
1✔
907
};
908

909
BOTAN_REGISTER_TEST("x509", "x509_non_self_signed_trust_anchors", Non_Self_Signed_Trust_Anchors_Test);
910

911
class BSI_Path_Validation_Tests final : public Test
×
912

913
{
914
   public:
915
      std::vector<Test::Result> run() override {
1✔
916
         std::vector<Test::Result> results;
1✔
917
         for(const auto& restrictions : restrictions_to_test(false, false)) {
3✔
918
            auto partial_res = run_with_restrictions(restrictions);
2✔
919
            results.insert(results.end(), partial_res.begin(), partial_res.end());
2✔
920
         }
3✔
921
         return results;
1✔
922
      }
×
923

924
   private:
925
      std::vector<Test::Result> run_with_restrictions(const Botan::Path_Validation_Restrictions& restrictions);
926
};
927

928
std::vector<Test::Result> BSI_Path_Validation_Tests::run_with_restrictions(
2✔
929
   const Botan::Path_Validation_Restrictions& restriction_template) {
930
   if(Botan::has_filesystem_impl() == false) {
2✔
931
      return {Test::Result::Note("BSI path validation", "Skipping due to missing filesystem access")};
×
932
   }
933

934
   std::vector<Test::Result> results;
2✔
935

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

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

942
      Botan::Certificate_Store_In_Memory trusted;
108✔
943
      std::vector<Botan::X509_Certificate> certs;
108✔
944

945
      #if defined(BOTAN_HAS_MD5)
946
      const bool has_md5 = true;
108✔
947
      #else
948
      const bool has_md5 = false;
949
      #endif
950

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

953
      // By convention: if CRL is a substring if the test name,
954
      // we need to check the CRLs
955
      bool use_crl = false;
108✔
956
      if(test_name.find("CRL") != std::string::npos) {
108✔
957
         use_crl = true;
32✔
958
      }
959

960
      try {
108✔
961
         for(const auto& file : all_files) {
890✔
962
            // found a trust anchor
963
            if(file.find("TA") != std::string::npos) {
790✔
964
               trusted.add_certificate(Botan::X509_Certificate(file));
100✔
965
            }
966
            // found the target certificate. It needs to be at the front of certs
967
            else if(file.find("TC") != std::string::npos) {
690✔
968
               certs.insert(certs.begin(), Botan::X509_Certificate(file));
108✔
969
            }
970
            // found a certificate that might be part of a valid certificate chain to the trust anchor
971
            else if(file.find(".crt") != std::string::npos) {
582✔
972
               certs.push_back(Botan::X509_Certificate(file));
224✔
973
            } else if(file.find(".crl") != std::string::npos) {
470✔
974
               trusted.add_crl(Botan::X509_CRL(file));
56✔
975
            }
976
         }
977

978
         Botan::Path_Validation_Restrictions restrictions(use_crl,
100✔
979
                                                          restriction_template.minimum_key_strength(),
980
                                                          use_crl,
981
                                                          restriction_template.max_ocsp_age(),
982
                                                          std::make_unique<Botan::Certificate_Store_In_Memory>(),
×
983
                                                          restriction_template.ignore_trusted_root_time_range(),
100✔
984
                                                          restriction_template.allow_non_self_signed_trust_anchors());
200✔
985

986
         /*
987
          * Following the test document, the test are executed 16 times with
988
          * randomly chosen order of the available certificates. However, the target
989
          * certificate needs to stay in front.
990
          * For certain test, the order in which the certificates are given to
991
          * the validation function may be relevant, i.e. if issuer DNs are
992
          * ambiguous.
993
          */
994
         class random_bit_generator {
100✔
995
            public:
996
               using result_type = size_t;
997

998
               static constexpr result_type min() { return 0; }
999

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

1002
               result_type operator()() {
160✔
1003
                  size_t s = 0;
160✔
1004
                  m_rng.randomize(reinterpret_cast<uint8_t*>(&s), sizeof(s));
160✔
1005
                  return s;
160✔
1006
               }
1007

1008
               explicit random_bit_generator(Botan::RandomNumberGenerator& rng) : m_rng(rng) {}
100✔
1009

1010
            private:
1011
               Botan::RandomNumberGenerator& m_rng;
1012
         } rbg(this->rng());
100✔
1013

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

1017
            Botan::Path_Validation_Result validation_result = Botan::x509_path_validate(
1,600✔
1018
               certs, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
1,600✔
1019

1020
            // We expect to be warned
1021
            if(expected_result.starts_with("Warning: ")) {
1,600✔
1022
               std::string stripped = expected_result.substr(std::string("Warning: ").size());
64✔
1023
               bool found_warning = false;
64✔
1024
               for(const auto& warning_set : validation_result.warnings()) {
256✔
1025
                  for(const auto& warning : warning_set) {
256✔
1026
                     std::string warning_str(Botan::to_string(warning));
64✔
1027
                     if(stripped == warning_str) {
64✔
1028
                        result.test_eq(test_name + " path validation result", warning_str, stripped);
64✔
1029
                        found_warning = true;
64✔
1030
                     }
1031
                  }
64✔
1032
               }
64✔
1033
               if(!found_warning) {
64✔
1034
                  result.test_failure(test_name, "Did not receive the expected warning: " + stripped);
×
1035
               }
1036
            } else {
64✔
1037
               if(expected_result == "Hash function used is considered too weak for security" && has_md5 == false) {
1,536✔
1038
                  result.test_eq(test_name + " path validation result",
1039
                                 validation_result.result_string(),
1040
                                 "Certificate signed with unknown/unavailable algorithm");
1041
               } else {
1042
                  result.test_eq(
1,536✔
1043
                     test_name + " path validation result", validation_result.result_string(), expected_result);
3,072✔
1044
               }
1045
            }
1046
         }
1,600✔
1047
      }
100✔
1048

1049
      /* Some certificates are rejected when executing the X509_Certificate constructor
1050
       * by throwing a Decoding_Error exception.
1051
       */
1052
      catch(const Botan::Exception& e) {
8✔
1053
         if(e.error_type() == Botan::ErrorType::DecodingFailure) {
8✔
1054
            result.test_eq(test_name + " path validation result", e.what(), expected_result);
16✔
1055
         } else {
1056
            result.test_failure(test_name, e.what());
×
1057
         }
1058
      }
8✔
1059

1060
      result.end_timer();
108✔
1061
      results.push_back(result);
108✔
1062
   }
108✔
1063

1064
   return results;
2✔
1065
}
2✔
1066

1067
BOTAN_REGISTER_TEST("x509", "x509_path_bsi", BSI_Path_Validation_Tests);
1068

1069
class Path_Validation_With_OCSP_Tests final : public Test {
×
1070
   public:
1071
      static Botan::X509_Certificate load_test_X509_cert(const std::string& path) {
24✔
1072
         return Botan::X509_Certificate(Test::data_file(path));
48✔
1073
      }
1074

1075
      static std::optional<Botan::OCSP::Response> load_test_OCSP_resp(const std::string& path) {
12✔
1076
         return Botan::OCSP::Response(Test::read_binary_data_file(path));
36✔
1077
      }
1078

1079
      static Test::Result validate_with_ocsp_with_next_update_without_max_age() {
1✔
1080
         Test::Result result("path check with ocsp with next_update w/o max_age");
1✔
1081
         Botan::Certificate_Store_In_Memory trusted;
1✔
1082

1083
         auto restrictions = Botan::Path_Validation_Restrictions(false, 110, false);
2✔
1084

1085
         auto ee = load_test_X509_cert("x509/ocsp/randombit.pem");
1✔
1086
         auto ca = load_test_X509_cert("x509/ocsp/letsencrypt.pem");
1✔
1087
         auto trust_root = load_test_X509_cert("x509/ocsp/identrust.pem");
1✔
1088
         trusted.add_certificate(trust_root);
1✔
1089

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

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

1094
         auto check_path = [&](const std::chrono::system_clock::time_point valid_time,
5✔
1095
                               const Botan::Certificate_Status_Code expected) {
1096
            const auto path_result = Botan::x509_path_validate(cert_path,
12✔
1097
                                                               restrictions,
1098
                                                               trusted,
4✔
1099
                                                               "",
1100
                                                               Botan::Usage_Type::UNSPECIFIED,
1101
                                                               valid_time,
1102
                                                               std::chrono::milliseconds(0),
4✔
1103
                                                               {ocsp});
8✔
1104

1105
            return result.confirm(std::string("Status: '") + Botan::to_string(expected) + "' should match '" +
24✔
1106
                                     Botan::to_string(path_result.result()) + "'",
8✔
1107
                                  path_result.result() == expected);
8✔
1108
         };
8✔
1109

1110
         check_path(Botan::calendar_point(2016, 11, 11, 12, 30, 0).to_std_timepoint(),
1✔
1111
                    Botan::Certificate_Status_Code::OCSP_NOT_YET_VALID);
1112
         check_path(Botan::calendar_point(2016, 11, 18, 12, 30, 0).to_std_timepoint(),
1✔
1113
                    Botan::Certificate_Status_Code::OK);
1114
         check_path(Botan::calendar_point(2016, 11, 20, 8, 30, 0).to_std_timepoint(),
1✔
1115
                    Botan::Certificate_Status_Code::OK);
1116
         check_path(Botan::calendar_point(2016, 11, 28, 8, 30, 0).to_std_timepoint(),
1✔
1117
                    Botan::Certificate_Status_Code::OCSP_HAS_EXPIRED);
1118

1119
         return result;
2✔
1120
      }
2✔
1121

1122
      static Test::Result validate_with_ocsp_with_next_update_with_max_age() {
1✔
1123
         Test::Result result("path check with ocsp with next_update with max_age");
1✔
1124
         Botan::Certificate_Store_In_Memory trusted;
1✔
1125

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

1128
         auto ee = load_test_X509_cert("x509/ocsp/randombit.pem");
1✔
1129
         auto ca = load_test_X509_cert("x509/ocsp/letsencrypt.pem");
1✔
1130
         auto trust_root = load_test_X509_cert("x509/ocsp/identrust.pem");
1✔
1131
         trusted.add_certificate(trust_root);
1✔
1132

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

1135
         auto ocsp = load_test_OCSP_resp("x509/ocsp/randombit_ocsp.der");
1✔
1136

1137
         auto check_path = [&](const std::chrono::system_clock::time_point valid_time,
5✔
1138
                               const Botan::Certificate_Status_Code expected) {
1139
            const auto path_result = Botan::x509_path_validate(cert_path,
12✔
1140
                                                               restrictions,
1141
                                                               trusted,
4✔
1142
                                                               "",
1143
                                                               Botan::Usage_Type::UNSPECIFIED,
1144
                                                               valid_time,
1145
                                                               std::chrono::milliseconds(0),
4✔
1146
                                                               {ocsp});
8✔
1147

1148
            return result.confirm(std::string("Status: '") + Botan::to_string(expected) + "' should match '" +
24✔
1149
                                     Botan::to_string(path_result.result()) + "'",
8✔
1150
                                  path_result.result() == expected);
8✔
1151
         };
12✔
1152

1153
         check_path(Botan::calendar_point(2016, 11, 11, 12, 30, 0).to_std_timepoint(),
1✔
1154
                    Botan::Certificate_Status_Code::OCSP_NOT_YET_VALID);
1155
         check_path(Botan::calendar_point(2016, 11, 18, 12, 30, 0).to_std_timepoint(),
1✔
1156
                    Botan::Certificate_Status_Code::OK);
1157
         check_path(Botan::calendar_point(2016, 11, 20, 8, 30, 0).to_std_timepoint(),
1✔
1158
                    Botan::Certificate_Status_Code::OK);
1159
         check_path(Botan::calendar_point(2016, 11, 28, 8, 30, 0).to_std_timepoint(),
1✔
1160
                    Botan::Certificate_Status_Code::OCSP_HAS_EXPIRED);
1161

1162
         return result;
2✔
1163
      }
2✔
1164

1165
      static Test::Result validate_with_ocsp_without_next_update_without_max_age() {
1✔
1166
         Test::Result result("path check with ocsp w/o next_update w/o max_age");
1✔
1167
         Botan::Certificate_Store_In_Memory trusted;
1✔
1168

1169
         auto restrictions = Botan::Path_Validation_Restrictions(false, 110, false);
2✔
1170

1171
         auto ee = load_test_X509_cert("x509/ocsp/patrickschmidt.pem");
1✔
1172
         auto ca = load_test_X509_cert("x509/ocsp/bdrive_encryption.pem");
1✔
1173
         auto trust_root = load_test_X509_cert("x509/ocsp/bdrive_root.pem");
1✔
1174

1175
         trusted.add_certificate(trust_root);
1✔
1176

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

1179
         auto ocsp = load_test_OCSP_resp("x509/ocsp/patrickschmidt_ocsp.der");
1✔
1180

1181
         auto check_path = [&](const std::chrono::system_clock::time_point valid_time,
4✔
1182
                               const Botan::Certificate_Status_Code expected) {
1183
            const auto path_result = Botan::x509_path_validate(cert_path,
9✔
1184
                                                               restrictions,
1185
                                                               trusted,
3✔
1186
                                                               "",
1187
                                                               Botan::Usage_Type::UNSPECIFIED,
1188
                                                               valid_time,
1189
                                                               std::chrono::milliseconds(0),
3✔
1190
                                                               {ocsp});
6✔
1191

1192
            return result.confirm(std::string("Status: '") + Botan::to_string(expected) + "' should match '" +
18✔
1193
                                     Botan::to_string(path_result.result()) + "'",
6✔
1194
                                  path_result.result() == expected);
6✔
1195
         };
9✔
1196

1197
         check_path(Botan::calendar_point(2019, 5, 28, 7, 0, 0).to_std_timepoint(),
1✔
1198
                    Botan::Certificate_Status_Code::OCSP_NOT_YET_VALID);
1199
         check_path(Botan::calendar_point(2019, 5, 28, 7, 30, 0).to_std_timepoint(),
1✔
1200
                    Botan::Certificate_Status_Code::OK);
1201
         check_path(Botan::calendar_point(2019, 5, 28, 8, 0, 0).to_std_timepoint(), Botan::Certificate_Status_Code::OK);
1✔
1202

1203
         return result;
2✔
1204
      }
2✔
1205

1206
      static Test::Result validate_with_ocsp_without_next_update_with_max_age() {
1✔
1207
         Test::Result result("path check with ocsp w/o next_update with max_age");
1✔
1208
         Botan::Certificate_Store_In_Memory trusted;
1✔
1209

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

1212
         auto ee = load_test_X509_cert("x509/ocsp/patrickschmidt.pem");
1✔
1213
         auto ca = load_test_X509_cert("x509/ocsp/bdrive_encryption.pem");
1✔
1214
         auto trust_root = load_test_X509_cert("x509/ocsp/bdrive_root.pem");
1✔
1215

1216
         trusted.add_certificate(trust_root);
1✔
1217

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

1220
         auto ocsp = load_test_OCSP_resp("x509/ocsp/patrickschmidt_ocsp.der");
1✔
1221

1222
         auto check_path = [&](const std::chrono::system_clock::time_point valid_time,
4✔
1223
                               const Botan::Certificate_Status_Code expected) {
1224
            const auto path_result = Botan::x509_path_validate(cert_path,
9✔
1225
                                                               restrictions,
1226
                                                               trusted,
3✔
1227
                                                               "",
1228
                                                               Botan::Usage_Type::UNSPECIFIED,
1229
                                                               valid_time,
1230
                                                               std::chrono::milliseconds(0),
3✔
1231
                                                               {ocsp});
6✔
1232

1233
            return result.confirm(std::string("Status: '") + Botan::to_string(expected) + "' should match '" +
18✔
1234
                                     Botan::to_string(path_result.result()) + "'",
6✔
1235
                                  path_result.result() == expected);
6✔
1236
         };
9✔
1237

1238
         check_path(Botan::calendar_point(2019, 5, 28, 7, 0, 0).to_std_timepoint(),
1✔
1239
                    Botan::Certificate_Status_Code::OCSP_NOT_YET_VALID);
1240
         check_path(Botan::calendar_point(2019, 5, 28, 7, 30, 0).to_std_timepoint(),
1✔
1241
                    Botan::Certificate_Status_Code::OK);
1242
         check_path(Botan::calendar_point(2019, 5, 28, 8, 0, 0).to_std_timepoint(),
1✔
1243
                    Botan::Certificate_Status_Code::OCSP_IS_TOO_OLD);
1244

1245
         return result;
2✔
1246
      }
2✔
1247

1248
      static Test::Result validate_with_ocsp_with_authorized_responder() {
1✔
1249
         Test::Result result("path check with ocsp response from authorized responder certificate");
1✔
1250
         Botan::Certificate_Store_In_Memory trusted;
1✔
1251

1252
         auto restrictions = Botan::Path_Validation_Restrictions(true,   // require revocation info
1✔
1253
                                                                 110,    // minimum key strength
1254
                                                                 true);  // OCSP for all intermediates
2✔
1255

1256
         auto ee = load_test_X509_cert("x509/ocsp/bdr.pem");
1✔
1257
         auto ca = load_test_X509_cert("x509/ocsp/bdr-int.pem");
1✔
1258
         auto trust_root = load_test_X509_cert("x509/ocsp/bdr-root.pem");
1✔
1259

1260
         // These OCSP responses are signed by an authorized OCSP responder
1261
         // certificate issued by `ca` and `trust_root` respectively. Note that
1262
         // the responder certificates contain the "OCSP No Check" extension,
1263
         // meaning that they themselves do not need a revocation check via OCSP.
1264
         auto ocsp_ee = load_test_OCSP_resp("x509/ocsp/bdr-ocsp-resp.der");
1✔
1265
         auto ocsp_ca = load_test_OCSP_resp("x509/ocsp/bdr-int-ocsp-resp.der");
1✔
1266

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

1270
         auto check_path = [&](const std::chrono::system_clock::time_point valid_time,
4✔
1271
                               const Botan::Certificate_Status_Code expected) {
1272
            const auto path_result = Botan::x509_path_validate(cert_path,
15✔
1273
                                                               restrictions,
1274
                                                               trusted,
3✔
1275
                                                               "",
1276
                                                               Botan::Usage_Type::UNSPECIFIED,
1277
                                                               valid_time,
1278
                                                               std::chrono::milliseconds(0),
3✔
1279
                                                               {ocsp_ee, ocsp_ca});
9✔
1280

1281
            return result.confirm(std::string("Status: '") + Botan::to_string(expected) + "' should match '" +
18✔
1282
                                     Botan::to_string(path_result.result()) + "'",
6✔
1283
                                  path_result.result() == expected);
6✔
1284
         };
12✔
1285

1286
         check_path(Botan::calendar_point(2022, 9, 18, 16, 30, 0).to_std_timepoint(),
1✔
1287
                    Botan::Certificate_Status_Code::OCSP_NOT_YET_VALID);
1288
         check_path(Botan::calendar_point(2022, 9, 19, 16, 30, 0).to_std_timepoint(),
1✔
1289
                    Botan::Certificate_Status_Code::OK);
1290
         check_path(Botan::calendar_point(2022, 9, 20, 16, 30, 0).to_std_timepoint(),
1✔
1291
                    Botan::Certificate_Status_Code::OCSP_HAS_EXPIRED);
1292

1293
         return result;
1✔
1294
      }
4✔
1295

1296
      static Test::Result validate_with_ocsp_with_authorized_responder_without_keyusage() {
1✔
1297
         Test::Result result(
1✔
1298
            "path check with ocsp response from authorized responder certificate (without sufficient key usage)");
1✔
1299
         Botan::Certificate_Store_In_Memory trusted;
1✔
1300

1301
         auto restrictions = Botan::Path_Validation_Restrictions(true,    // require revocation info
1✔
1302
                                                                 110,     // minimum key strength
1303
                                                                 false);  // OCSP for all intermediates
2✔
1304

1305
         // See `src/scripts/mychain_creater.sh` if you need to recreate those
1306
         auto ee = load_test_X509_cert("x509/ocsp/mychain_ee.pem");
1✔
1307
         auto ca = load_test_X509_cert("x509/ocsp/mychain_int.pem");
1✔
1308
         auto trust_root = load_test_X509_cert("x509/ocsp/mychain_root.pem");
1✔
1309

1310
         auto ocsp_ee_delegate = load_test_OCSP_resp("x509/ocsp/mychain_ocsp_for_ee_delegate_signed.der").value();
2✔
1311
         auto ocsp_ee_delegate_malformed =
1✔
1312
            load_test_OCSP_resp("x509/ocsp/mychain_ocsp_for_ee_delegate_signed_malformed.der").value();
2✔
1313

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

1317
         auto check_path = [&](const std::chrono::system_clock::time_point valid_time,
4✔
1318
                               const Botan::OCSP::Response& ocsp_ee,
1319
                               const Botan::Certificate_Status_Code expected,
1320
                               const std::optional<Botan::Certificate_Status_Code> also_expected = std::nullopt) {
1321
            const auto path_result = Botan::x509_path_validate(cert_path,
9✔
1322
                                                               restrictions,
1323
                                                               trusted,
3✔
1324
                                                               "",
1325
                                                               Botan::Usage_Type::UNSPECIFIED,
1326
                                                               valid_time,
1327
                                                               std::chrono::milliseconds(0),
3✔
1328
                                                               {ocsp_ee});
6✔
1329

1330
            result.test_is_eq("should result in expected validation status code",
3✔
1331
                              static_cast<uint32_t>(path_result.result()),
3✔
1332
                              static_cast<uint32_t>(expected));
3✔
1333
            if(also_expected) {
3✔
1334
               result.confirm("Secondary error is also present",
4✔
1335
                              flatten(path_result.all_statuses()).contains(also_expected.value()));
6✔
1336
            }
1337
         };
6✔
1338

1339
         check_path(Botan::calendar_point(2022, 9, 22, 23, 30, 0).to_std_timepoint(),
1✔
1340
                    ocsp_ee_delegate,
1341
                    Botan::Certificate_Status_Code::VERIFIED);
1342
         check_path(Botan::calendar_point(2022, 10, 8, 23, 30, 0).to_std_timepoint(),
1✔
1343
                    ocsp_ee_delegate,
1344
                    Botan::Certificate_Status_Code::CERT_HAS_EXPIRED,
1345
                    Botan::Certificate_Status_Code::OCSP_ISSUER_NOT_TRUSTED);
1✔
1346
         check_path(Botan::calendar_point(2022, 9, 22, 23, 30, 0).to_std_timepoint(),
1✔
1347
                    ocsp_ee_delegate_malformed,
1348
                    Botan::Certificate_Status_Code::OCSP_RESPONSE_MISSING_KEYUSAGE,
1349
                    Botan::Certificate_Status_Code::OCSP_ISSUER_NOT_TRUSTED);
1✔
1350

1351
         return result;
1✔
1352
      }
2✔
1353

1354
      static Test::Result validate_with_forged_ocsp_using_self_signed_cert() {
1✔
1355
         Test::Result result("path check with forged ocsp using self-signed certificate");
1✔
1356
         Botan::Certificate_Store_In_Memory trusted;
1✔
1357

1358
         auto restrictions = Botan::Path_Validation_Restrictions(true,    // require revocation info
1✔
1359
                                                                 110,     // minimum key strength
1360
                                                                 false);  // OCSP for all intermediates
2✔
1361

1362
         auto ee = load_test_X509_cert("x509/ocsp/randombit.pem");
1✔
1363
         auto ca = load_test_X509_cert("x509/ocsp/letsencrypt.pem");
1✔
1364
         auto trust_root = load_test_X509_cert("x509/ocsp/identrust.pem");
1✔
1365
         trusted.add_certificate(trust_root);
1✔
1366

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

1369
         auto check_path = [&](const std::string& forged_ocsp,
3✔
1370
                               const Botan::Certificate_Status_Code expected,
1371
                               const Botan::Certificate_Status_Code also_expected) {
1372
            auto ocsp = load_test_OCSP_resp(forged_ocsp);
2✔
1373
            const auto path_result =
2✔
1374
               Botan::x509_path_validate(cert_path,
6✔
1375
                                         restrictions,
1376
                                         trusted,
2✔
1377
                                         "",
1378
                                         Botan::Usage_Type::UNSPECIFIED,
1379
                                         Botan::calendar_point(2016, 11, 18, 12, 30, 0).to_std_timepoint(),
4✔
1380
                                         std::chrono::milliseconds(0),
2✔
1381
                                         {ocsp});
4✔
1382

1383
            result.test_is_eq(
2✔
1384
               "Path validation with forged OCSP response should fail with", path_result.result(), expected);
2✔
1385
            result.confirm("Secondary error is also present",
4✔
1386
                           flatten(path_result.all_statuses()).contains(also_expected));
4✔
1387
            result.test_note(std::string("Failed with: ") + Botan::to_string(path_result.result()));
6✔
1388
         };
8✔
1389

1390
         // In both cases the path validation should detect the forged OCSP
1391
         // response and generate an appropriate error. By no means it should
1392
         // follow the unauthentic OCSP response.
1393
         check_path("x509/ocsp/randombit_ocsp_forged_valid.der",
1✔
1394
                    Botan::Certificate_Status_Code::CERT_ISSUER_NOT_FOUND,
1395
                    Botan::Certificate_Status_Code::OCSP_ISSUER_NOT_TRUSTED);
1396
         check_path("x509/ocsp/randombit_ocsp_forged_revoked.der",
1✔
1397
                    Botan::Certificate_Status_Code::CERT_ISSUER_NOT_FOUND,
1398
                    Botan::Certificate_Status_Code::OCSP_ISSUER_NOT_TRUSTED);
1399

1400
         return result;
1✔
1401
      }
2✔
1402

1403
      static Test::Result validate_with_ocsp_self_signed_by_intermediate_cert() {
1✔
1404
         Test::Result result(
1✔
1405
            "path check with ocsp response for intermediate that is (maliciously) self-signed by the intermediate");
1✔
1406
         Botan::Certificate_Store_In_Memory trusted;
1✔
1407

1408
         auto restrictions = Botan::Path_Validation_Restrictions(true,   // require revocation info
1✔
1409
                                                                 110,    // minimum key strength
1410
                                                                 true);  // OCSP for all intermediates
2✔
1411

1412
         // See `src/scripts/mychain_creater.sh` if you need to recreate those
1413
         auto ee = load_test_X509_cert("x509/ocsp/mychain_ee.pem");
1✔
1414
         auto ca = load_test_X509_cert("x509/ocsp/mychain_int.pem");
1✔
1415
         auto trust_root = load_test_X509_cert("x509/ocsp/mychain_root.pem");
1✔
1416

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

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

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

1426
         const auto path_result =
1✔
1427
            Botan::x509_path_validate(cert_path,
5✔
1428
                                      restrictions,
1429
                                      trusted,
1430
                                      "",
1431
                                      Botan::Usage_Type::UNSPECIFIED,
1432
                                      Botan::calendar_point(2022, 9, 22, 22, 30, 0).to_std_timepoint(),
2✔
1433
                                      std::chrono::milliseconds(0),
1✔
1434
                                      {ocsp_ee, ocsp_ca});
3✔
1435
         result.confirm("should reject intermediate OCSP response",
2✔
1436
                        path_result.result() == Botan::Certificate_Status_Code::OCSP_ISSUER_NOT_FOUND);
1✔
1437
         result.test_note(std::string("Failed with: ") + Botan::to_string(path_result.result()));
3✔
1438

1439
         return result;
1✔
1440
      }
7✔
1441

1442
      std::vector<Test::Result> run() override {
1✔
1443
         return {validate_with_ocsp_with_next_update_without_max_age(),
1✔
1444
                 validate_with_ocsp_with_next_update_with_max_age(),
1445
                 validate_with_ocsp_without_next_update_without_max_age(),
1446
                 validate_with_ocsp_without_next_update_with_max_age(),
1447
                 validate_with_ocsp_with_authorized_responder(),
1448
                 validate_with_ocsp_with_authorized_responder_without_keyusage(),
1449
                 validate_with_forged_ocsp_using_self_signed_cert(),
1450
                 validate_with_ocsp_self_signed_by_intermediate_cert()};
9✔
1451
      }
1✔
1452
};
1453

1454
BOTAN_REGISTER_TEST("x509", "x509_path_with_ocsp", Path_Validation_With_OCSP_Tests);
1455

1456
   #endif
1457

1458
   #if defined(BOTAN_HAS_ECDSA)
1459

1460
class CVE_2020_0601_Tests final : public Test {
×
1461
   public:
1462
      std::vector<Test::Result> run() override {
1✔
1463
         Test::Result result("CVE-2020-0601");
1✔
1464

1465
         if(!Botan::EC_Group::supports_application_specific_group()) {
1✔
1466
            result.test_note("Skipping as application specific groups are not supported");
×
1467
            return {result};
×
1468
         }
1469

1470
         if(!Botan::EC_Group::supports_named_group("secp384r1")) {
1✔
1471
            result.test_note("Skipping as secp384r1 is not supported");
×
1472
            return {result};
×
1473
         }
1474

1475
         const auto& secp384r1 = Botan::EC_Group::from_name("secp384r1");
1✔
1476
         Botan::OID curveball_oid("1.3.6.1.4.1.25258.4.2020.0601");
1✔
1477
         Botan::EC_Group curveball(
1✔
1478
            curveball_oid,
1479
            secp384r1.get_p(),
1480
            secp384r1.get_a(),
1481
            secp384r1.get_b(),
1482
            BigInt(
2✔
1483
               "0xC711162A761D568EBEB96265D4C3CEB4F0C330EC8F6DD76E39BCC849ABABB8E34378D581065DEFC77D9FCED6B39075DE"),
1484
            BigInt(
2✔
1485
               "0x0CB090DE23BAC8D13E67E019A91B86311E5F342DEE17FD15FB7E278A32A1EAC98FC97E18CB2F3B2C487A7DA6F40107AC"),
1486
            secp384r1.get_order());
2✔
1487

1488
         auto ca_crt = Botan::X509_Certificate(Test::data_file("x509/cve-2020-0601/ca.pem"));
2✔
1489
         auto fake_ca_crt = Botan::X509_Certificate(Test::data_file("x509/cve-2020-0601/fake_ca.pem"));
2✔
1490
         auto ee_crt = Botan::X509_Certificate(Test::data_file("x509/cve-2020-0601/ee.pem"));
2✔
1491

1492
         Botan::Certificate_Store_In_Memory trusted;
1✔
1493
         trusted.add_certificate(ca_crt);
1✔
1494

1495
         const auto restrictions = Botan::Path_Validation_Restrictions(false, 80, false);
2✔
1496

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

1499
         const auto path_result1 = Botan::x509_path_validate(std::vector<Botan::X509_Certificate>{ee_crt, fake_ca_crt},
5✔
1500
                                                             restrictions,
1501
                                                             trusted,
1502
                                                             "",
1503
                                                             Botan::Usage_Type::UNSPECIFIED,
1504
                                                             valid_time,
1505
                                                             std::chrono::milliseconds(0),
1✔
1506
                                                             {});
2✔
1507

1508
         result.confirm("Validation failed", !path_result1.successful_validation());
2✔
1509

1510
         result.confirm("Expected status",
2✔
1511
                        path_result1.result() == Botan::Certificate_Status_Code::CANNOT_ESTABLISH_TRUST);
1✔
1512

1513
         const auto path_result2 = Botan::x509_path_validate(std::vector<Botan::X509_Certificate>{ee_crt},
3✔
1514
                                                             restrictions,
1515
                                                             trusted,
1516
                                                             "",
1517
                                                             Botan::Usage_Type::UNSPECIFIED,
1518
                                                             valid_time,
1519
                                                             std::chrono::milliseconds(0),
1✔
1520
                                                             {});
2✔
1521

1522
         result.confirm("Validation failed", !path_result2.successful_validation());
2✔
1523

1524
         result.confirm("Expected status",
2✔
1525
                        path_result2.result() == Botan::Certificate_Status_Code::CERT_ISSUER_NOT_FOUND);
1✔
1526

1527
         // Verify the signature from the bad CA is actually correct
1528
         Botan::Certificate_Store_In_Memory frusted;
1✔
1529
         frusted.add_certificate(fake_ca_crt);
1✔
1530

1531
         const auto path_result3 = Botan::x509_path_validate(std::vector<Botan::X509_Certificate>{ee_crt},
3✔
1532
                                                             restrictions,
1533
                                                             frusted,
1534
                                                             "",
1535
                                                             Botan::Usage_Type::UNSPECIFIED,
1536
                                                             valid_time,
1537
                                                             std::chrono::milliseconds(0),
1✔
1538
                                                             {});
2✔
1539

1540
         result.confirm("Validation succeeded", path_result3.successful_validation());
2✔
1541

1542
         return {result};
2✔
1543
      }
5✔
1544
};
1545

1546
BOTAN_REGISTER_TEST("x509", "x509_cve_2020_0601", CVE_2020_0601_Tests);
1547

1548
class Path_Validation_With_Immortal_CRL final : public Test {
×
1549
   public:
1550
      std::vector<Test::Result> run() override {
1✔
1551
         // RFC 5280 defines the nextUpdate field as "optional" (in line with
1552
         // the original X.509 standard), but then requires all conforming CAs
1553
         // to always define it. For best compatibility we must deal with both.
1554
         Test::Result result("Using a CRL without a nextUpdate field");
1✔
1555

1556
         if(Botan::has_filesystem_impl() == false) {
1✔
1557
            result.test_note("Skipping due to missing filesystem access");
×
1558
            return {result};
×
1559
         }
1560

1561
         Botan::X509_Certificate root(Test::data_file("x509/misc/crl_without_nextupdate/ca.pem"));
2✔
1562
         Botan::X509_Certificate revoked_subject(Test::data_file("x509/misc/crl_without_nextupdate/01.pem"));
2✔
1563
         Botan::X509_Certificate valid_subject(Test::data_file("x509/misc/crl_without_nextupdate/42.pem"));
2✔
1564

1565
         // Check that a CRL without nextUpdate is parsable
1566
         auto crl = Botan::X509_CRL(Test::data_file("x509/misc/crl_without_nextupdate/valid_forever.crl"));
2✔
1567
         result.confirm("this update is set", crl.this_update().time_is_set());
2✔
1568
         result.confirm("next update is not set", !crl.next_update().time_is_set());
2✔
1569
         result.confirm("CRL is not empty", !crl.get_revoked().empty());
2✔
1570

1571
         // Ensure that we support the used sig algo, otherwish stop here
1572
         if(!Botan::EC_Group::supports_named_group("brainpool512r1")) {
1✔
1573
            result.test_note("Cannot test path validation because signature algorithm is not support in this build");
×
1574
            return {result};
×
1575
         }
1576

1577
         Botan::Certificate_Store_In_Memory trusted;
1✔
1578
         trusted.add_certificate(root);
1✔
1579
         trusted.add_crl(crl);
1✔
1580

1581
         // Just before the CA and subject certificates expire
1582
         // (validity from 01 March 2025 to 24 Feburary 2026)
1583
         auto valid_time = Botan::calendar_point(2026, 2, 23, 0, 0, 0).to_std_timepoint();
1✔
1584

1585
         Botan::Path_Validation_Restrictions restrictions(true /* require revocation info */);
2✔
1586

1587
         // Validate a certificate that is not listed in the CRL
1588
         const auto valid = Botan::x509_path_validate(
1✔
1589
            valid_subject, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, valid_time);
1✔
1590
         if(!result.confirm("Valid certificate", valid.successful_validation())) {
2✔
1591
            result.test_note(valid.result_string());
×
1592
         }
1593

1594
         // Ensure that a certificate listed in the CRL is recognized as revoked
1595
         const auto revoked = Botan::x509_path_validate(
1✔
1596
            revoked_subject, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, valid_time);
1✔
1597
         if(!result.confirm("No valid certificate", !revoked.successful_validation())) {
2✔
1598
            result.test_note(revoked.result_string());
×
1599
         }
1600
         result.test_is_eq("Certificate is revoked", revoked.result(), Botan::Certificate_Status_Code::CERT_IS_REVOKED);
1✔
1601

1602
         return {result};
2✔
1603
      }
2✔
1604
};
1605

1606
BOTAN_REGISTER_TEST("x509", "x509_path_immortal_crl", Path_Validation_With_Immortal_CRL);
1607

1608
   #endif
1609

1610
   #if defined(BOTAN_HAS_XMSS_RFC8391)
1611

1612
class XMSS_Path_Validation_Tests final : public Test {
×
1613
   public:
1614
      static Test::Result validate_self_signed(const std::string& name, const std::string& file) {
2✔
1615
         Test::Result result(name);
2✔
1616

1617
         Botan::Path_Validation_Restrictions restrictions;
4✔
1618
         auto self_signed = Botan::X509_Certificate(Test::data_file("x509/xmss/" + file));
4✔
1619

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

1623
         auto status = Botan::PKIX::overall_status(
2✔
1624
            Botan::PKIX::check_chain(cert_path, valid_time, "", Botan::Usage_Type::UNSPECIFIED, restrictions));
4✔
1625
         result.test_eq("Cert validation status", Botan::to_string(status), "Verified");
2✔
1626
         return result;
2✔
1627
      }
4✔
1628

1629
      std::vector<Test::Result> run() override {
1✔
1630
         if(Botan::has_filesystem_impl() == false) {
1✔
1631
            return {Test::Result::Note("XMSS path validation", "Skipping due to missing filesystem access")};
×
1632
         }
1633

1634
         return {
1✔
1635
            validate_self_signed("XMSS path validation with certificate created by ISARA corp", "xmss_isara_root.pem"),
1✔
1636
            validate_self_signed("XMSS path validation with certificate created by BouncyCastle",
1✔
1637
                                 "xmss_bouncycastle_sha256_10_root.pem")};
3✔
1638
      }
4✔
1639
};
1640

1641
BOTAN_REGISTER_TEST("x509", "x509_path_xmss", XMSS_Path_Validation_Tests);
1642

1643
   #endif
1644

1645
#endif
1646

1647
}  // namespace
1648

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

© 2026 Coveralls, Inc