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

randombit / botan / 13696792100

06 Mar 2025 10:40AM UTC coverage: 91.686% (+0.002%) from 91.684%
13696792100

push

github

web-flow
Merge pull request #4732 from Rohde-Schwarz/rene/crl_without_nextupdate

95863 of 104556 relevant lines covered (91.69%)

11297210.28 hits per line

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

93.5
/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 = ':') {
6✔
43
   std::ifstream in(results_file);
6✔
44
   if(!in.good()) {
6✔
45
      throw Test_Error("Failed reading " + results_file);
×
46
   }
47

48
   std::map<std::string, std::string> m;
6✔
49
   std::string line;
6✔
50
   while(in.good()) {
426✔
51
      std::getline(in, line);
420✔
52
      if(line.empty()) {
420✔
53
         continue;
14✔
54
      }
55
      if(line[0] == '#') {
411✔
56
         continue;
5✔
57
      }
58

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

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

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

68
   return m;
12✔
69
}
6✔
70

71
std::set<Botan::Certificate_Status_Code> flatten(const Botan::CertificatePathStatusCodes& codes) {
4✔
72
   std::set<Botan::Certificate_Status_Code> result;
4✔
73

74
   for(const auto& statuses : codes) {
16✔
75
      result.insert(statuses.begin(), statuses.end());
12✔
76
   }
77

78
   return result;
4✔
79
}
×
80

81
class X509test_Path_Validation_Tests final : public Test {
×
82
   public:
83
      std::vector<Test::Result> run() override {
1✔
84
         std::vector<Test::Result> results;
1✔
85

86
         // Test certs generated by https://github.com/yymax/x509test
87

88
         // Current tests use SHA-1
89
         const Botan::Path_Validation_Restrictions restrictions(false, 80);
2✔
90

91
         Botan::X509_Certificate root(Test::data_file("x509/x509test/root.pem"));
2✔
92
         Botan::Certificate_Store_In_Memory trusted;
1✔
93
         trusted.add_certificate(root);
1✔
94

95
         auto validation_time = Botan::calendar_point(2016, 10, 21, 4, 20, 0).to_std_timepoint();
1✔
96

97
         for(const auto& [filename, expected_result] : read_results(Test::data_file("x509/x509test/expected.txt"))) {
38✔
98
            Test::Result result("X509test path validation");
37✔
99
            result.start_timer();
37✔
100

101
            std::vector<Botan::X509_Certificate> certs = load_cert_file(Test::data_file("x509/x509test/" + filename));
74✔
102

103
            if(certs.empty()) {
37✔
104
               throw Test_Error("Failed to read certs from " + filename);
×
105
            }
106

107
            Botan::Path_Validation_Result path_result = Botan::x509_path_validate(
37✔
108
               certs, restrictions, trusted, "www.tls.test", Botan::Usage_Type::TLS_SERVER_AUTH, validation_time);
37✔
109

110
            if(path_result.successful_validation() && path_result.trust_root() != root) {
37✔
111
               path_result = Botan::Path_Validation_Result(Botan::Certificate_Status_Code::CANNOT_ESTABLISH_TRUST);
×
112
            }
113

114
            result.test_eq("test " + filename, path_result.result_string(), expected_result);
74✔
115
            result.test_eq("test no warnings string", path_result.warnings_string(), "");
74✔
116
            result.confirm("test no warnings", path_result.no_warnings());
74✔
117
            result.end_timer();
37✔
118
            results.push_back(result);
37✔
119
         }
37✔
120

121
         // test softfail
122
         {
1✔
123
            Test::Result result("X509test path validation softfail");
1✔
124
            result.start_timer();
1✔
125

126
            // this certificate must not have a OCSP URL
127
            const std::string filename = "ValidAltName.pem";
1✔
128
            std::vector<Botan::X509_Certificate> certs = load_cert_file(Test::data_file("x509/x509test/" + filename));
2✔
129
            if(certs.empty()) {
1✔
130
               throw Test_Error("Failed to read certs from " + filename);
×
131
            }
132

133
            Botan::Path_Validation_Result path_result =
1✔
134
               Botan::x509_path_validate(certs,
1✔
135
                                         restrictions,
136
                                         trusted,
137
                                         "www.tls.test",
138
                                         Botan::Usage_Type::TLS_SERVER_AUTH,
139
                                         validation_time,
140
                                         /* activate check_ocsp_online */ std::chrono::milliseconds(1000),
1✔
141
                                         {});
1✔
142

143
            if(path_result.successful_validation() && path_result.trust_root() != root) {
1✔
144
               path_result = Botan::Path_Validation_Result(Botan::Certificate_Status_Code::CANNOT_ESTABLISH_TRUST);
×
145
            }
146

147
            // certificate verification succeed even if no OCSP URL (softfail)
148
            result.confirm("test success", path_result.successful_validation());
2✔
149
            result.test_eq("test " + filename, path_result.result_string(), "Verified");
2✔
150
      #if defined(BOTAN_TARGET_OS_HAS_THREADS) && defined(BOTAN_HAS_HTTP_UTIL)
151
            // if softfail, there is warnings
152
            result.confirm("test warnings", !path_result.no_warnings());
2✔
153
            result.test_eq("test warnings string", path_result.warnings_string(), "[0] OCSP URL not available");
2✔
154
      #endif
155
            result.end_timer();
1✔
156
            results.push_back(result);
1✔
157
         }
1✔
158

159
         return results;
1✔
160
      }
1✔
161

162
   private:
163
      static std::vector<Botan::X509_Certificate> load_cert_file(const std::string& filename) {
38✔
164
         Botan::DataSource_Stream in(filename);
38✔
165

166
         std::vector<Botan::X509_Certificate> certs;
38✔
167
         while(!in.end_of_data()) {
238✔
168
            try {
162✔
169
               certs.emplace_back(in);
162✔
170
            } catch(Botan::Decoding_Error&) {}
38✔
171
         }
172

173
         return certs;
38✔
174
      }
38✔
175
};
176

177
BOTAN_REGISTER_TEST("x509", "x509_path_x509test", X509test_Path_Validation_Tests);
178

179
class NIST_Path_Validation_Tests final : public Test {
×
180
   public:
181
      std::vector<Test::Result> run() override;
182
};
183

184
std::vector<Test::Result> NIST_Path_Validation_Tests::run() {
1✔
185
   if(Botan::has_filesystem_impl() == false) {
1✔
186
      return {Test::Result::Note("NIST path validation", "Skipping due to missing filesystem access")};
×
187
   }
188

189
   std::vector<Test::Result> results;
1✔
190

191
   /**
192
   * Code to run the X.509v3 processing tests described in "Conformance
193
   *  Testing of Relying Party Client Certificate Path Proccessing Logic",
194
   *  which is available on NIST's web site.
195
   *
196
   * https://csrc.nist.gov/projects/pki-testing/x-509-path-validation-test-suite
197
   *
198
   * Known Failures/Problems:
199
   *  - Policy extensions are not implemented, so we skip tests #34-#53.
200
   *  - Tests #75 and #76 are skipped as they make use of relatively
201
   *    obscure CRL extensions which are not supported.
202
   */
203
   std::map<std::string, std::string> expected = read_results(Test::data_file("x509/nist/expected.txt"));
2✔
204

205
   const Botan::X509_Certificate root_cert(Test::data_file("x509/nist/root.crt"));
2✔
206
   const Botan::X509_CRL root_crl(Test::data_file("x509/nist/root.crl"));
2✔
207

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

210
   for(auto i = expected.begin(); i != expected.end(); ++i) {
77✔
211
      Test::Result result("NIST path validation");
76✔
212
      result.start_timer();
76✔
213

214
      const std::string test_name = i->first;
76✔
215

216
      try {
76✔
217
         const std::string expected_result = i->second;
76✔
218

219
         const auto all_files = Test::files_in_data_dir("x509/nist/" + test_name);
76✔
220

221
         Botan::Certificate_Store_In_Memory store;
76✔
222

223
         store.add_certificate(root_cert);
76✔
224
         store.add_crl(root_crl);
76✔
225

226
         for(const auto& file : all_files) {
399✔
227
            if(file.ends_with(".crt") && file != "end.crt") {
323✔
228
               store.add_certificate(Botan::X509_Certificate(file));
200✔
229
            } else if(file.ends_with(".crl")) {
123✔
230
               Botan::DataSource_Stream in(file, true);
123✔
231
               Botan::X509_CRL crl(in);
123✔
232
               store.add_crl(crl);
123✔
233
            }
123✔
234
         }
235

236
         Botan::X509_Certificate end_user(Test::data_file("x509/nist/" + test_name + "/end.crt"));
228✔
237

238
         // 1024 bit root cert
239
         Botan::Path_Validation_Restrictions restrictions(true, 80);
152✔
240

241
         Botan::Path_Validation_Result validation_result = Botan::x509_path_validate(
76✔
242
            end_user, restrictions, store, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
76✔
243

244
         result.test_eq(test_name + " path validation result", validation_result.result_string(), expected_result);
152✔
245
      } catch(std::exception& e) {
76✔
246
         result.test_failure(test_name, e.what());
×
247
      }
×
248

249
      result.end_timer();
76✔
250
      results.push_back(result);
76✔
251
   }
76✔
252

253
   return results;
1✔
254
}
1✔
255

256
BOTAN_REGISTER_TEST("x509", "x509_path_nist", NIST_Path_Validation_Tests);
257

258
class Extended_Path_Validation_Tests final : public Test {
×
259
   public:
260
      std::vector<Test::Result> run() override;
261
};
262

263
std::vector<Test::Result> Extended_Path_Validation_Tests::run() {
1✔
264
   if(Botan::has_filesystem_impl() == false) {
1✔
265
      return {Test::Result::Note("Extended x509 path validation", "Skipping due to missing filesystem access")};
×
266
   }
267

268
   std::vector<Test::Result> results;
1✔
269

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

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

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

278
      Botan::Certificate_Store_In_Memory store;
3✔
279

280
      for(const auto& file : all_files) {
13✔
281
         if(file.ends_with(".crt") && file != "end.crt") {
10✔
282
            store.add_certificate(Botan::X509_Certificate(file));
10✔
283
         }
284
      }
285

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

288
      Botan::Path_Validation_Restrictions restrictions;
6✔
289
      Botan::Path_Validation_Result validation_result =
3✔
290
         Botan::x509_path_validate(end_user, restrictions, store, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
3✔
291

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

294
      result.end_timer();
3✔
295
      results.push_back(result);
3✔
296
   }
3✔
297

298
   return results;
1✔
299
}
1✔
300

301
BOTAN_REGISTER_TEST("x509", "x509_path_extended", Extended_Path_Validation_Tests);
302

303
class PSS_Path_Validation_Tests : public Test {
×
304
   public:
305
      std::vector<Test::Result> run() override;
306
};
307

308
std::vector<Test::Result> PSS_Path_Validation_Tests::run() {
1✔
309
   if(Botan::has_filesystem_impl() == false) {
1✔
310
      return {Test::Result::Note("RSA-PSS X509 signature validation", "Skipping due to missing filesystem access")};
×
311
   }
312

313
   std::vector<Test::Result> results;
1✔
314

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

317
   auto validation_times_iter = validation_times.begin();
1✔
318

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

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

325
      std::optional<Botan::X509_CRL> crl;
118✔
326
      std::optional<Botan::X509_Certificate> end;
118✔
327
      std::optional<Botan::X509_Certificate> root;
118✔
328
      Botan::Certificate_Store_In_Memory store;
118✔
329
      std::optional<Botan::PKCS10_Request> csr;
118✔
330

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

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

335
      for(const auto& file : all_files) {
345✔
336
         if(file.find("end.crt") != std::string::npos) {
227✔
337
            end = Botan::X509_Certificate(file);
113✔
338
         } else if(file.find("root.crt") != std::string::npos) {
114✔
339
            root = Botan::X509_Certificate(file);
97✔
340
            store.add_certificate(*root);
97✔
341
         } else if(file.ends_with(".crl")) {
17✔
342
            crl = Botan::X509_CRL(file);
6✔
343
         } else if(file.ends_with(".csr")) {
11✔
344
            csr = Botan::PKCS10_Request(file);
5✔
345
         }
346
      }
347

348
      if(end && crl && root)  // CRL tests
118✔
349
      {
350
         const std::vector<Botan::X509_Certificate> cert_path = {*end, *root};
18✔
351
         const std::vector<std::optional<Botan::X509_CRL>> crls = {crl};
12✔
352
         auto crl_status = Botan::PKIX::check_crl(
6✔
353
            cert_path,
354
            crls,
355
            validation_time);  // alternatively we could just call crl.check_signature( root_pubkey )
6✔
356

357
         result.test_eq(test_name + " check_crl result",
12✔
358
                        Botan::Path_Validation_Result::status_string(Botan::PKIX::overall_status(crl_status)),
359
                        expected_result);
360
      } else if(end && root)  // CRT chain tests
118✔
361
      {
362
         // sha-1 is used
363
         Botan::Path_Validation_Restrictions restrictions(false, 80);
182✔
364

365
         Botan::Path_Validation_Result validation_result =
91✔
366
            Botan::x509_path_validate(*end, restrictions, store, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
91✔
367

368
         result.test_eq(test_name + " path validation result", validation_result.result_string(), expected_result);
182✔
369
      } else if(end && !root)  // CRT self signed tests
112✔
370
      {
371
         auto pubkey = end->subject_public_key();
16✔
372
         result.test_eq(test_name + " verify signature", end->check_signature(*pubkey), !!(std::stoi(expected_result)));
32✔
373
      } else if(csr)  // PKCS#10 Request
21✔
374
      {
375
         auto pubkey = csr->subject_public_key();
5✔
376
         result.test_eq(test_name + " verify signature", csr->check_signature(*pubkey), !!(std::stoi(expected_result)));
10✔
377
      }
5✔
378

379
      result.end_timer();
118✔
380
      results.push_back(result);
118✔
381
   }
334✔
382

383
   return results;
1✔
384
}
19✔
385

386
BOTAN_REGISTER_TEST("x509", "x509_path_rsa_pss", PSS_Path_Validation_Tests);
387

388
class Validate_V1Cert_Test final : public Test {
×
389
   public:
390
      std::vector<Test::Result> run() override;
391
};
392

393
std::vector<Test::Result> Validate_V1Cert_Test::run() {
1✔
394
   if(Botan::has_filesystem_impl() == false) {
1✔
395
      return {Test::Result::Note("BSI path validation", "Skipping due to missing filesystem access")};
×
396
   }
397

398
   std::vector<Test::Result> results;
1✔
399

400
   const std::string root_crt = Test::data_file("x509/misc/v1ca/root.pem");
1✔
401
   const std::string int_crt = Test::data_file("x509/misc/v1ca/int.pem");
1✔
402
   const std::string ee_crt = Test::data_file("x509/misc/v1ca/ee.pem");
1✔
403

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

406
   Botan::X509_Certificate root(root_crt);
1✔
407
   Botan::X509_Certificate intermediate(int_crt);
1✔
408
   Botan::X509_Certificate ee_cert(ee_crt);
1✔
409

410
   Botan::Certificate_Store_In_Memory trusted;
1✔
411
   trusted.add_certificate(root);
1✔
412

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

415
   Botan::Path_Validation_Restrictions restrictions;
2✔
416
   Botan::Path_Validation_Result validation_result =
1✔
417
      Botan::x509_path_validate(chain, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
1✔
418

419
   Test::Result result("Verifying using v1 certificate");
1✔
420
   result.test_eq("Path validation result", validation_result.result_string(), "Verified");
2✔
421

422
   Botan::Certificate_Store_In_Memory empty;
1✔
423

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

426
   Botan::Path_Validation_Result validation_result2 =
1✔
427
      Botan::x509_path_validate(new_chain, restrictions, empty, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
1✔
428

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

431
   return {result};
2✔
432
}
4✔
433

434
BOTAN_REGISTER_TEST("x509", "x509_v1_ca", Validate_V1Cert_Test);
435

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

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

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

448
   const std::string root_crt = Test::data_file("x509/v2-in-v1/root.pem");
1✔
449
   const std::string int_crt = Test::data_file("x509/v2-in-v1/int.pem");
1✔
450
   const std::string ee_crt = Test::data_file("x509/v2-in-v1/leaf.pem");
1✔
451

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

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

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

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

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

467
   Test::Result result("Verifying v1 certificate using v2 uid fields");
1✔
468
   result.test_eq("Path validation failed", validation_result.successful_validation(), false);
1✔
469
   result.test_eq(
3✔
470
      "Path validation result", validation_result.result_string(), "Encountered v2 identifiers in v1 certificate");
2✔
471

472
   return {result};
2✔
473
}
3✔
474

475
BOTAN_REGISTER_TEST("x509", "x509_v2uid_in_v1", Validate_V2Uid_in_V1_Test);
476

477
class Validate_Name_Constraint_SAN_Test final : public Test {
×
478
   public:
479
      std::vector<Test::Result> run() override;
480
};
481

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

487
   std::vector<Test::Result> results;
1✔
488

489
   const std::string root_crt = Test::data_file("x509/name_constraint_san/root.pem");
1✔
490
   const std::string int_crt = Test::data_file("x509/name_constraint_san/int.pem");
1✔
491
   const std::string ee_crt = Test::data_file("x509/name_constraint_san/leaf.pem");
1✔
492

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

495
   Botan::X509_Certificate root(root_crt);
1✔
496
   Botan::X509_Certificate intermediate(int_crt);
1✔
497
   Botan::X509_Certificate ee_cert(ee_crt);
1✔
498

499
   Botan::Certificate_Store_In_Memory trusted;
1✔
500
   trusted.add_certificate(root);
1✔
501

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

504
   Botan::Path_Validation_Restrictions restrictions;
2✔
505
   Botan::Path_Validation_Result validation_result =
1✔
506
      Botan::x509_path_validate(chain, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
1✔
507

508
   Test::Result result("Verifying certificate with alternative SAN violating name constraint");
1✔
509
   result.test_eq("Path validation failed", validation_result.successful_validation(), false);
1✔
510
   result.test_eq(
3✔
511
      "Path validation result", validation_result.result_string(), "Certificate does not pass name constraint");
2✔
512

513
   return {result};
2✔
514
}
3✔
515

516
BOTAN_REGISTER_TEST("x509", "x509_name_constraint_san", Validate_Name_Constraint_SAN_Test);
517

518
class Validate_Name_Constraint_CaseInsensitive final : public Test {
×
519
   public:
520
      std::vector<Test::Result> run() override;
521
};
522

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

528
   std::vector<Test::Result> results;
1✔
529

530
   const std::string root_crt = Test::data_file("x509/misc/name_constraint_ci/root.pem");
1✔
531
   const std::string int_crt = Test::data_file("x509/misc/name_constraint_ci/int.pem");
1✔
532
   const std::string ee_crt = Test::data_file("x509/misc/name_constraint_ci/leaf.pem");
1✔
533

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

536
   Botan::X509_Certificate root(root_crt);
1✔
537
   Botan::X509_Certificate intermediate(int_crt);
1✔
538
   Botan::X509_Certificate ee_cert(ee_crt);
1✔
539

540
   Botan::Certificate_Store_In_Memory trusted;
1✔
541
   trusted.add_certificate(root);
1✔
542

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

545
   Botan::Path_Validation_Restrictions restrictions;
2✔
546
   Botan::Path_Validation_Result validation_result =
1✔
547
      Botan::x509_path_validate(chain, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
1✔
548

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

552
   return {result};
2✔
553
}
3✔
554

555
BOTAN_REGISTER_TEST("x509", "x509_name_constraint_ci", Validate_Name_Constraint_CaseInsensitive);
556

557
class Validate_Name_Constraint_NoCheckSelf final : public Test {
×
558
   public:
559
      std::vector<Test::Result> run() override;
560
};
561

562
std::vector<Test::Result> Validate_Name_Constraint_NoCheckSelf::run() {
1✔
563
   if(Botan::has_filesystem_impl() == false) {
1✔
564
      return {Test::Result::Note("Path validation", "Skipping due to missing filesystem access")};
×
565
   }
566

567
   std::vector<Test::Result> results;
1✔
568

569
   const std::string root_crt = Test::data_file("x509/misc/nc_skip_self/root.pem");
1✔
570
   const std::string int_crt = Test::data_file("x509/misc/nc_skip_self/int.pem");
1✔
571
   const std::string ee_crt = Test::data_file("x509/misc/nc_skip_self/leaf.pem");
1✔
572

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

575
   Botan::X509_Certificate root(root_crt);
1✔
576
   Botan::X509_Certificate intermediate(int_crt);
1✔
577
   Botan::X509_Certificate ee_cert(ee_crt);
1✔
578

579
   Botan::Certificate_Store_In_Memory trusted;
1✔
580
   trusted.add_certificate(root);
1✔
581

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

584
   Botan::Path_Validation_Restrictions restrictions;
2✔
585
   Botan::Path_Validation_Result validation_result =
1✔
586
      Botan::x509_path_validate(chain, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
1✔
587

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

591
   return {result};
2✔
592
}
3✔
593

594
BOTAN_REGISTER_TEST("x509", "x509_name_constraint_no_check_self", Validate_Name_Constraint_NoCheckSelf);
595

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

603
         const std::string trusted_root_crt = Test::data_file("x509/misc/root_cert_time_check/root.crt");
1✔
604
         const std::string leaf_crt = Test::data_file("x509/misc/root_cert_time_check/leaf.crt");
1✔
605

606
         const Botan::X509_Certificate trusted_root_cert(trusted_root_crt);
1✔
607
         const Botan::X509_Certificate leaf_cert(leaf_crt);
1✔
608

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

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

614
         Test::Result result("Root cert time check");
1✔
615

616
         auto assert_path_validation_result = [&](std::string_view descr,
11✔
617
                                                  bool ignore_trusted_root_time_range,
618
                                                  uint32_t year,
619
                                                  Botan::Certificate_Status_Code exp_status,
620
                                                  std::optional<Botan::Certificate_Status_Code> exp_warning =
621
                                                     std::nullopt) {
622
            const Botan::Path_Validation_Restrictions restrictions(
10✔
623
               false,
624
               110,
625
               false,
626
               std::chrono::seconds::zero(),
627
               std::make_unique<Botan::Certificate_Store_In_Memory>(),
×
628
               ignore_trusted_root_time_range);
20✔
629

630
            const Botan::Path_Validation_Result validation_result =
10✔
631
               Botan::x509_path_validate(chain,
10✔
632
                                         restrictions,
633
                                         trusted,
10✔
634
                                         "",
635
                                         Botan::Usage_Type::UNSPECIFIED,
636
                                         Botan::calendar_point(year, 1, 1, 1, 0, 0).to_std_timepoint());
10✔
637
            const std::string descr_str = Botan::fmt(
10✔
638
               "Root cert validity range {}: {}", ignore_trusted_root_time_range ? "ignored" : "checked", descr);
15✔
639

640
            result.test_is_eq(descr_str, validation_result.result(), exp_status);
10✔
641
            const auto warnings = validation_result.warnings();
10✔
642
            BOTAN_ASSERT_NOMSG(warnings.size() == 2);
10✔
643
            result.confirm("No warning for leaf cert", warnings.at(0).empty());
20✔
644
            if(exp_warning) {
10✔
645
               result.confirm("Warning for root cert",
12✔
646
                              warnings.at(1).size() == 1 && warnings.at(1).contains(*exp_warning));
8✔
647
            } else {
648
               result.confirm("No warning for root cert", warnings.at(1).empty());
12✔
649
            }
650
         };
10✔
651
         // (Trusted) root cert validity range: 2022-2028
652
         // Leaf cert validity range: 2020-2030
653

654
         // Trusted root time range is checked
655
         assert_path_validation_result(
1✔
656
            "Root and leaf certs in validity range", false, 2025, Botan::Certificate_Status_Code::OK);
657
         assert_path_validation_result(
1✔
658
            "Root and leaf certs are expired", false, 2031, Botan::Certificate_Status_Code::CERT_HAS_EXPIRED);
659
         assert_path_validation_result(
1✔
660
            "Root and leaf certs are not yet valid", false, 2019, Botan::Certificate_Status_Code::CERT_NOT_YET_VALID);
661
         assert_path_validation_result(
1✔
662
            "Root cert is expired, leaf cert not", false, 2029, Botan::Certificate_Status_Code::CERT_HAS_EXPIRED);
663
         assert_path_validation_result("Root cert is not yet valid, leaf cert is",
1✔
664
                                       false,
665
                                       2021,
666
                                       Botan::Certificate_Status_Code::CERT_NOT_YET_VALID);
667

668
         // Trusted root time range is ignored
669
         assert_path_validation_result(
1✔
670
            "Root and leaf certs in validity range", true, 2025, Botan::Certificate_Status_Code::OK);
671
         assert_path_validation_result("Root and leaf certs are expired",
2✔
672
                                       true,
673
                                       2031,
674
                                       Botan::Certificate_Status_Code::CERT_HAS_EXPIRED,
675
                                       Botan::Certificate_Status_Code::TRUSTED_CERT_HAS_EXPIRED);
1✔
676
         assert_path_validation_result("Root and leaf certs are not yet valid",
2✔
677
                                       true,
678
                                       2019,
679
                                       Botan::Certificate_Status_Code::CERT_NOT_YET_VALID,
680
                                       Botan::Certificate_Status_Code::TRUSTED_CERT_NOT_YET_VALID);
1✔
681
         assert_path_validation_result("Root cert is expired, leaf cert not",
2✔
682
                                       true,
683
                                       2029,
684
                                       Botan::Certificate_Status_Code::OK,
685
                                       Botan::Certificate_Status_Code::TRUSTED_CERT_HAS_EXPIRED);
1✔
686
         assert_path_validation_result("Root cert is not yet valid, leaf cert is",
2✔
687
                                       true,
688
                                       2021,
689
                                       Botan::Certificate_Status_Code::OK,
690
                                       Botan::Certificate_Status_Code::TRUSTED_CERT_NOT_YET_VALID);
1✔
691

692
         return {result};
2✔
693
      }
3✔
694
};
695

696
BOTAN_REGISTER_TEST("x509", "x509_root_cert_time_check", Root_Cert_Time_Check_Test);
697

698
class BSI_Path_Validation_Tests final : public Test
×
699

700
{
701
   public:
702
      std::vector<Test::Result> run() override;
703
};
704

705
std::vector<Test::Result> BSI_Path_Validation_Tests::run() {
1✔
706
   if(Botan::has_filesystem_impl() == false) {
1✔
707
      return {Test::Result::Note("BSI path validation", "Skipping due to missing filesystem access")};
×
708
   }
709

710
   std::vector<Test::Result> results;
1✔
711

712
   for(const auto& [test_name, expected_result] : read_results(Test::data_file("x509/bsi/expected.txt"), '$')) {
55✔
713
      Test::Result result("BSI path validation");
54✔
714
      result.start_timer();
54✔
715

716
      const auto all_files = Test::files_in_data_dir("x509/bsi/" + test_name);
54✔
717

718
      Botan::Certificate_Store_In_Memory trusted;
54✔
719
      std::vector<Botan::X509_Certificate> certs;
54✔
720

721
      #if defined(BOTAN_HAS_MD5)
722
      const bool has_md5 = true;
54✔
723
      #else
724
      const bool has_md5 = false;
725
      #endif
726

727
      auto validation_time = Botan::calendar_point(2017, 8, 19, 12, 0, 0).to_std_timepoint();
54✔
728

729
      // By convention: if CRL is a substring if the test name,
730
      // we need to check the CRLs
731
      bool use_crl = false;
54✔
732
      if(test_name.find("CRL") != std::string::npos) {
54✔
733
         use_crl = true;
16✔
734
      }
735

736
      try {
54✔
737
         for(const auto& file : all_files) {
445✔
738
            // found a trust anchor
739
            if(file.find("TA") != std::string::npos) {
395✔
740
               trusted.add_certificate(Botan::X509_Certificate(file));
50✔
741
            }
742
            // found the target certificate. It needs to be at the front of certs
743
            else if(file.find("TC") != std::string::npos) {
345✔
744
               certs.insert(certs.begin(), Botan::X509_Certificate(file));
54✔
745
            }
746
            // found a certificate that might be part of a valid certificate chain to the trust anchor
747
            else if(file.find(".crt") != std::string::npos) {
291✔
748
               certs.push_back(Botan::X509_Certificate(file));
112✔
749
            } else if(file.find(".crl") != std::string::npos) {
235✔
750
               trusted.add_crl(Botan::X509_CRL(file));
28✔
751
            }
752
         }
753

754
         Botan::Path_Validation_Restrictions restrictions(use_crl, 79, use_crl);
100✔
755

756
         /*
757
          * Following the test document, the test are executed 16 times with
758
          * randomly chosen order of the available certificates. However, the target
759
          * certificate needs to stay in front.
760
          * For certain test, the order in which the certificates are given to
761
          * the validation function may be relevant, i.e. if issuer DNs are
762
          * ambiguous.
763
          */
764
         class random_bit_generator {
50✔
765
            public:
766
               using result_type = size_t;
767

768
               static constexpr result_type min() { return 0; }
769

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

772
               result_type operator()() {
80✔
773
                  size_t s;
80✔
774
                  m_rng.randomize(reinterpret_cast<uint8_t*>(&s), sizeof(s));
80✔
775
                  return s;
80✔
776
               }
777

778
               random_bit_generator(Botan::RandomNumberGenerator& rng) : m_rng(rng) {}
50✔
779

780
            private:
781
               Botan::RandomNumberGenerator& m_rng;
782
         } rbg(this->rng());
50✔
783

784
         for(size_t r = 0; r < 16; r++) {
850✔
785
            std::shuffle(++(certs.begin()), certs.end(), rbg);
800✔
786

787
            Botan::Path_Validation_Result validation_result = Botan::x509_path_validate(
800✔
788
               certs, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
800✔
789

790
            // We expect to be warned
791
            if(expected_result.starts_with("Warning: ")) {
800✔
792
               std::string stripped = expected_result.substr(std::string("Warning: ").size());
32✔
793
               bool found_warning = false;
32✔
794
               for(const auto& warning_set : validation_result.warnings()) {
128✔
795
                  for(const auto& warning : warning_set) {
128✔
796
                     std::string warning_str(Botan::to_string(warning));
32✔
797
                     if(stripped == warning_str) {
32✔
798
                        result.test_eq(test_name + " path validation result", warning_str, stripped);
32✔
799
                        found_warning = true;
32✔
800
                     }
801
                  }
32✔
802
               }
32✔
803
               if(!found_warning) {
32✔
804
                  result.test_failure(test_name, "Did not receive the expected warning: " + stripped);
×
805
               }
806
            } else {
32✔
807
               if(expected_result == "Hash function used is considered too weak for security" && has_md5 == false) {
768✔
808
                  result.test_eq(test_name + " path validation result",
809
                                 validation_result.result_string(),
810
                                 "Certificate signed with unknown/unavailable algorithm");
811
               } else {
812
                  result.test_eq(
768✔
813
                     test_name + " path validation result", validation_result.result_string(), expected_result);
1,536✔
814
               }
815
            }
816
         }
800✔
817
      }
50✔
818

819
      /* Some certificates are rejected when executing the X509_Certificate constructor
820
       * by throwing a Decoding_Error exception.
821
       */
822
      catch(const Botan::Exception& e) {
4✔
823
         if(e.error_type() == Botan::ErrorType::DecodingFailure) {
4✔
824
            result.test_eq(test_name + " path validation result", e.what(), expected_result);
8✔
825
         } else {
826
            result.test_failure(test_name, e.what());
×
827
         }
828
      }
4✔
829

830
      result.end_timer();
54✔
831
      results.push_back(result);
54✔
832
   }
54✔
833

834
   return results;
1✔
835
}
1✔
836

837
BOTAN_REGISTER_TEST("x509", "x509_path_bsi", BSI_Path_Validation_Tests);
838

839
class Path_Validation_With_OCSP_Tests final : public Test {
×
840
   public:
841
      static Botan::X509_Certificate load_test_X509_cert(const std::string& path) {
24✔
842
         return Botan::X509_Certificate(Test::data_file(path));
48✔
843
      }
844

845
      static std::optional<Botan::OCSP::Response> load_test_OCSP_resp(const std::string& path) {
12✔
846
         return Botan::OCSP::Response(Test::read_binary_data_file(path));
36✔
847
      }
848

849
      static Test::Result validate_with_ocsp_with_next_update_without_max_age() {
1✔
850
         Test::Result result("path check with ocsp with next_update w/o max_age");
1✔
851
         Botan::Certificate_Store_In_Memory trusted;
1✔
852

853
         auto restrictions = Botan::Path_Validation_Restrictions(false, 110, false);
2✔
854

855
         auto ee = load_test_X509_cert("x509/ocsp/randombit.pem");
1✔
856
         auto ca = load_test_X509_cert("x509/ocsp/letsencrypt.pem");
1✔
857
         auto trust_root = load_test_X509_cert("x509/ocsp/identrust.pem");
1✔
858
         trusted.add_certificate(trust_root);
1✔
859

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

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

864
         auto check_path = [&](const std::chrono::system_clock::time_point valid_time,
5✔
865
                               const Botan::Certificate_Status_Code expected) {
866
            const auto path_result = Botan::x509_path_validate(cert_path,
12✔
867
                                                               restrictions,
868
                                                               trusted,
4✔
869
                                                               "",
870
                                                               Botan::Usage_Type::UNSPECIFIED,
871
                                                               valid_time,
872
                                                               std::chrono::milliseconds(0),
4✔
873
                                                               {ocsp});
8✔
874

875
            return result.confirm(std::string("Status: '") + Botan::to_string(expected) + "' should match '" +
24✔
876
                                     Botan::to_string(path_result.result()) + "'",
8✔
877
                                  path_result.result() == expected);
8✔
878
         };
8✔
879

880
         check_path(Botan::calendar_point(2016, 11, 11, 12, 30, 0).to_std_timepoint(),
1✔
881
                    Botan::Certificate_Status_Code::OCSP_NOT_YET_VALID);
882
         check_path(Botan::calendar_point(2016, 11, 18, 12, 30, 0).to_std_timepoint(),
1✔
883
                    Botan::Certificate_Status_Code::OK);
884
         check_path(Botan::calendar_point(2016, 11, 20, 8, 30, 0).to_std_timepoint(),
1✔
885
                    Botan::Certificate_Status_Code::OK);
886
         check_path(Botan::calendar_point(2016, 11, 28, 8, 30, 0).to_std_timepoint(),
1✔
887
                    Botan::Certificate_Status_Code::OCSP_HAS_EXPIRED);
888

889
         return result;
2✔
890
      }
2✔
891

892
      static Test::Result validate_with_ocsp_with_next_update_with_max_age() {
1✔
893
         Test::Result result("path check with ocsp with next_update with max_age");
1✔
894
         Botan::Certificate_Store_In_Memory trusted;
1✔
895

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

898
         auto ee = load_test_X509_cert("x509/ocsp/randombit.pem");
1✔
899
         auto ca = load_test_X509_cert("x509/ocsp/letsencrypt.pem");
1✔
900
         auto trust_root = load_test_X509_cert("x509/ocsp/identrust.pem");
1✔
901
         trusted.add_certificate(trust_root);
1✔
902

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

905
         auto ocsp = load_test_OCSP_resp("x509/ocsp/randombit_ocsp.der");
1✔
906

907
         auto check_path = [&](const std::chrono::system_clock::time_point valid_time,
5✔
908
                               const Botan::Certificate_Status_Code expected) {
909
            const auto path_result = Botan::x509_path_validate(cert_path,
12✔
910
                                                               restrictions,
911
                                                               trusted,
4✔
912
                                                               "",
913
                                                               Botan::Usage_Type::UNSPECIFIED,
914
                                                               valid_time,
915
                                                               std::chrono::milliseconds(0),
4✔
916
                                                               {ocsp});
8✔
917

918
            return result.confirm(std::string("Status: '") + Botan::to_string(expected) + "' should match '" +
24✔
919
                                     Botan::to_string(path_result.result()) + "'",
8✔
920
                                  path_result.result() == expected);
8✔
921
         };
12✔
922

923
         check_path(Botan::calendar_point(2016, 11, 11, 12, 30, 0).to_std_timepoint(),
1✔
924
                    Botan::Certificate_Status_Code::OCSP_NOT_YET_VALID);
925
         check_path(Botan::calendar_point(2016, 11, 18, 12, 30, 0).to_std_timepoint(),
1✔
926
                    Botan::Certificate_Status_Code::OK);
927
         check_path(Botan::calendar_point(2016, 11, 20, 8, 30, 0).to_std_timepoint(),
1✔
928
                    Botan::Certificate_Status_Code::OK);
929
         check_path(Botan::calendar_point(2016, 11, 28, 8, 30, 0).to_std_timepoint(),
1✔
930
                    Botan::Certificate_Status_Code::OCSP_HAS_EXPIRED);
931

932
         return result;
2✔
933
      }
2✔
934

935
      static Test::Result validate_with_ocsp_without_next_update_without_max_age() {
1✔
936
         Test::Result result("path check with ocsp w/o next_update w/o max_age");
1✔
937
         Botan::Certificate_Store_In_Memory trusted;
1✔
938

939
         auto restrictions = Botan::Path_Validation_Restrictions(false, 110, false);
2✔
940

941
         auto ee = load_test_X509_cert("x509/ocsp/patrickschmidt.pem");
1✔
942
         auto ca = load_test_X509_cert("x509/ocsp/bdrive_encryption.pem");
1✔
943
         auto trust_root = load_test_X509_cert("x509/ocsp/bdrive_root.pem");
1✔
944

945
         trusted.add_certificate(trust_root);
1✔
946

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

949
         auto ocsp = load_test_OCSP_resp("x509/ocsp/patrickschmidt_ocsp.der");
1✔
950

951
         auto check_path = [&](const std::chrono::system_clock::time_point valid_time,
4✔
952
                               const Botan::Certificate_Status_Code expected) {
953
            const auto path_result = Botan::x509_path_validate(cert_path,
9✔
954
                                                               restrictions,
955
                                                               trusted,
3✔
956
                                                               "",
957
                                                               Botan::Usage_Type::UNSPECIFIED,
958
                                                               valid_time,
959
                                                               std::chrono::milliseconds(0),
3✔
960
                                                               {ocsp});
6✔
961

962
            return result.confirm(std::string("Status: '") + Botan::to_string(expected) + "' should match '" +
18✔
963
                                     Botan::to_string(path_result.result()) + "'",
6✔
964
                                  path_result.result() == expected);
6✔
965
         };
9✔
966

967
         check_path(Botan::calendar_point(2019, 5, 28, 7, 0, 0).to_std_timepoint(),
1✔
968
                    Botan::Certificate_Status_Code::OCSP_NOT_YET_VALID);
969
         check_path(Botan::calendar_point(2019, 5, 28, 7, 30, 0).to_std_timepoint(),
1✔
970
                    Botan::Certificate_Status_Code::OK);
971
         check_path(Botan::calendar_point(2019, 5, 28, 8, 0, 0).to_std_timepoint(), Botan::Certificate_Status_Code::OK);
1✔
972

973
         return result;
2✔
974
      }
2✔
975

976
      static Test::Result validate_with_ocsp_without_next_update_with_max_age() {
1✔
977
         Test::Result result("path check with ocsp w/o next_update with max_age");
1✔
978
         Botan::Certificate_Store_In_Memory trusted;
1✔
979

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

982
         auto ee = load_test_X509_cert("x509/ocsp/patrickschmidt.pem");
1✔
983
         auto ca = load_test_X509_cert("x509/ocsp/bdrive_encryption.pem");
1✔
984
         auto trust_root = load_test_X509_cert("x509/ocsp/bdrive_root.pem");
1✔
985

986
         trusted.add_certificate(trust_root);
1✔
987

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

990
         auto ocsp = load_test_OCSP_resp("x509/ocsp/patrickschmidt_ocsp.der");
1✔
991

992
         auto check_path = [&](const std::chrono::system_clock::time_point valid_time,
4✔
993
                               const Botan::Certificate_Status_Code expected) {
994
            const auto path_result = Botan::x509_path_validate(cert_path,
9✔
995
                                                               restrictions,
996
                                                               trusted,
3✔
997
                                                               "",
998
                                                               Botan::Usage_Type::UNSPECIFIED,
999
                                                               valid_time,
1000
                                                               std::chrono::milliseconds(0),
3✔
1001
                                                               {ocsp});
6✔
1002

1003
            return result.confirm(std::string("Status: '") + Botan::to_string(expected) + "' should match '" +
18✔
1004
                                     Botan::to_string(path_result.result()) + "'",
6✔
1005
                                  path_result.result() == expected);
6✔
1006
         };
9✔
1007

1008
         check_path(Botan::calendar_point(2019, 5, 28, 7, 0, 0).to_std_timepoint(),
1✔
1009
                    Botan::Certificate_Status_Code::OCSP_NOT_YET_VALID);
1010
         check_path(Botan::calendar_point(2019, 5, 28, 7, 30, 0).to_std_timepoint(),
1✔
1011
                    Botan::Certificate_Status_Code::OK);
1012
         check_path(Botan::calendar_point(2019, 5, 28, 8, 0, 0).to_std_timepoint(),
1✔
1013
                    Botan::Certificate_Status_Code::OCSP_IS_TOO_OLD);
1014

1015
         return result;
2✔
1016
      }
2✔
1017

1018
      static Test::Result validate_with_ocsp_with_authorized_responder() {
1✔
1019
         Test::Result result("path check with ocsp response from authorized responder certificate");
1✔
1020
         Botan::Certificate_Store_In_Memory trusted;
1✔
1021

1022
         auto restrictions = Botan::Path_Validation_Restrictions(true,   // require revocation info
1✔
1023
                                                                 110,    // minimum key strength
1024
                                                                 true);  // OCSP for all intermediates
2✔
1025

1026
         auto ee = load_test_X509_cert("x509/ocsp/bdr.pem");
1✔
1027
         auto ca = load_test_X509_cert("x509/ocsp/bdr-int.pem");
1✔
1028
         auto trust_root = load_test_X509_cert("x509/ocsp/bdr-root.pem");
1✔
1029

1030
         // These OCSP responses are signed by an authorized OCSP responder
1031
         // certificate issued by `ca` and `trust_root` respectively. Note that
1032
         // the responder certificates contain the "OCSP No Check" extension,
1033
         // meaning that they themselves do not need a revocation check via OCSP.
1034
         auto ocsp_ee = load_test_OCSP_resp("x509/ocsp/bdr-ocsp-resp.der");
1✔
1035
         auto ocsp_ca = load_test_OCSP_resp("x509/ocsp/bdr-int-ocsp-resp.der");
1✔
1036

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

1040
         auto check_path = [&](const std::chrono::system_clock::time_point valid_time,
4✔
1041
                               const Botan::Certificate_Status_Code expected) {
1042
            const auto path_result = Botan::x509_path_validate(cert_path,
15✔
1043
                                                               restrictions,
1044
                                                               trusted,
3✔
1045
                                                               "",
1046
                                                               Botan::Usage_Type::UNSPECIFIED,
1047
                                                               valid_time,
1048
                                                               std::chrono::milliseconds(0),
3✔
1049
                                                               {ocsp_ee, ocsp_ca});
9✔
1050

1051
            return result.confirm(std::string("Status: '") + Botan::to_string(expected) + "' should match '" +
18✔
1052
                                     Botan::to_string(path_result.result()) + "'",
6✔
1053
                                  path_result.result() == expected);
6✔
1054
         };
12✔
1055

1056
         check_path(Botan::calendar_point(2022, 9, 18, 16, 30, 0).to_std_timepoint(),
1✔
1057
                    Botan::Certificate_Status_Code::OCSP_NOT_YET_VALID);
1058
         check_path(Botan::calendar_point(2022, 9, 19, 16, 30, 0).to_std_timepoint(),
1✔
1059
                    Botan::Certificate_Status_Code::OK);
1060
         check_path(Botan::calendar_point(2022, 9, 20, 16, 30, 0).to_std_timepoint(),
1✔
1061
                    Botan::Certificate_Status_Code::OCSP_HAS_EXPIRED);
1062

1063
         return result;
1✔
1064
      }
4✔
1065

1066
      static Test::Result validate_with_ocsp_with_authorized_responder_without_keyusage() {
1✔
1067
         Test::Result result(
1✔
1068
            "path check with ocsp response from authorized responder certificate (without sufficient key usage)");
1✔
1069
         Botan::Certificate_Store_In_Memory trusted;
1✔
1070

1071
         auto restrictions = Botan::Path_Validation_Restrictions(true,    // require revocation info
1✔
1072
                                                                 110,     // minimum key strength
1073
                                                                 false);  // OCSP for all intermediates
2✔
1074

1075
         // See `src/scripts/mychain_creater.sh` if you need to recreate those
1076
         auto ee = load_test_X509_cert("x509/ocsp/mychain_ee.pem");
1✔
1077
         auto ca = load_test_X509_cert("x509/ocsp/mychain_int.pem");
1✔
1078
         auto trust_root = load_test_X509_cert("x509/ocsp/mychain_root.pem");
1✔
1079

1080
         auto ocsp_ee_delegate = load_test_OCSP_resp("x509/ocsp/mychain_ocsp_for_ee_delegate_signed.der").value();
2✔
1081
         auto ocsp_ee_delegate_malformed =
1✔
1082
            load_test_OCSP_resp("x509/ocsp/mychain_ocsp_for_ee_delegate_signed_malformed.der").value();
2✔
1083

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

1087
         auto check_path = [&](const std::chrono::system_clock::time_point valid_time,
4✔
1088
                               const Botan::OCSP::Response& ocsp_ee,
1089
                               const Botan::Certificate_Status_Code expected,
1090
                               const std::optional<Botan::Certificate_Status_Code> also_expected = std::nullopt) {
1091
            const auto path_result = Botan::x509_path_validate(cert_path,
9✔
1092
                                                               restrictions,
1093
                                                               trusted,
3✔
1094
                                                               "",
1095
                                                               Botan::Usage_Type::UNSPECIFIED,
1096
                                                               valid_time,
1097
                                                               std::chrono::milliseconds(0),
3✔
1098
                                                               {ocsp_ee});
6✔
1099

1100
            result.test_is_eq("should result in expected validation status code",
3✔
1101
                              static_cast<uint32_t>(path_result.result()),
3✔
1102
                              static_cast<uint32_t>(expected));
3✔
1103
            if(also_expected) {
3✔
1104
               result.confirm("Secondary error is also present",
4✔
1105
                              flatten(path_result.all_statuses()).contains(also_expected.value()));
6✔
1106
            }
1107
         };
6✔
1108

1109
         check_path(Botan::calendar_point(2022, 9, 22, 23, 30, 0).to_std_timepoint(),
1✔
1110
                    ocsp_ee_delegate,
1111
                    Botan::Certificate_Status_Code::VERIFIED);
1112
         check_path(Botan::calendar_point(2022, 10, 8, 23, 30, 0).to_std_timepoint(),
1✔
1113
                    ocsp_ee_delegate,
1114
                    Botan::Certificate_Status_Code::CERT_HAS_EXPIRED,
1115
                    Botan::Certificate_Status_Code::OCSP_ISSUER_NOT_TRUSTED);
1✔
1116
         check_path(Botan::calendar_point(2022, 9, 22, 23, 30, 0).to_std_timepoint(),
1✔
1117
                    ocsp_ee_delegate_malformed,
1118
                    Botan::Certificate_Status_Code::OCSP_RESPONSE_MISSING_KEYUSAGE,
1119
                    Botan::Certificate_Status_Code::OCSP_ISSUER_NOT_TRUSTED);
1✔
1120

1121
         return result;
1✔
1122
      }
2✔
1123

1124
      static Test::Result validate_with_forged_ocsp_using_self_signed_cert() {
1✔
1125
         Test::Result result("path check with forged ocsp using self-signed certificate");
1✔
1126
         Botan::Certificate_Store_In_Memory trusted;
1✔
1127

1128
         auto restrictions = Botan::Path_Validation_Restrictions(true,    // require revocation info
1✔
1129
                                                                 110,     // minimum key strength
1130
                                                                 false);  // OCSP for all intermediates
2✔
1131

1132
         auto ee = load_test_X509_cert("x509/ocsp/randombit.pem");
1✔
1133
         auto ca = load_test_X509_cert("x509/ocsp/letsencrypt.pem");
1✔
1134
         auto trust_root = load_test_X509_cert("x509/ocsp/identrust.pem");
1✔
1135
         trusted.add_certificate(trust_root);
1✔
1136

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

1139
         auto check_path = [&](const std::string& forged_ocsp,
3✔
1140
                               const Botan::Certificate_Status_Code expected,
1141
                               const Botan::Certificate_Status_Code also_expected) {
1142
            auto ocsp = load_test_OCSP_resp(forged_ocsp);
2✔
1143
            const auto path_result =
2✔
1144
               Botan::x509_path_validate(cert_path,
6✔
1145
                                         restrictions,
1146
                                         trusted,
2✔
1147
                                         "",
1148
                                         Botan::Usage_Type::UNSPECIFIED,
1149
                                         Botan::calendar_point(2016, 11, 18, 12, 30, 0).to_std_timepoint(),
4✔
1150
                                         std::chrono::milliseconds(0),
2✔
1151
                                         {ocsp});
4✔
1152

1153
            result.test_is_eq(
2✔
1154
               "Path validation with forged OCSP response should fail with", path_result.result(), expected);
2✔
1155
            result.confirm("Secondary error is also present",
4✔
1156
                           flatten(path_result.all_statuses()).contains(also_expected));
4✔
1157
            result.test_note(std::string("Failed with: ") + Botan::to_string(path_result.result()));
6✔
1158
         };
8✔
1159

1160
         // In both cases the path validation should detect the forged OCSP
1161
         // response and generate an appropriate error. By no means it should
1162
         // follow the unauthentic OCSP response.
1163
         check_path("x509/ocsp/randombit_ocsp_forged_valid.der",
1✔
1164
                    Botan::Certificate_Status_Code::CERT_ISSUER_NOT_FOUND,
1165
                    Botan::Certificate_Status_Code::OCSP_ISSUER_NOT_TRUSTED);
1166
         check_path("x509/ocsp/randombit_ocsp_forged_revoked.der",
1✔
1167
                    Botan::Certificate_Status_Code::CERT_ISSUER_NOT_FOUND,
1168
                    Botan::Certificate_Status_Code::OCSP_ISSUER_NOT_TRUSTED);
1169

1170
         return result;
1✔
1171
      }
2✔
1172

1173
      static Test::Result validate_with_ocsp_self_signed_by_intermediate_cert() {
1✔
1174
         Test::Result result(
1✔
1175
            "path check with ocsp response for intermediate that is (maliciously) self-signed by the intermediate");
1✔
1176
         Botan::Certificate_Store_In_Memory trusted;
1✔
1177

1178
         auto restrictions = Botan::Path_Validation_Restrictions(true,   // require revocation info
1✔
1179
                                                                 110,    // minimum key strength
1180
                                                                 true);  // OCSP for all intermediates
2✔
1181

1182
         // See `src/scripts/mychain_creater.sh` if you need to recreate those
1183
         auto ee = load_test_X509_cert("x509/ocsp/mychain_ee.pem");
1✔
1184
         auto ca = load_test_X509_cert("x509/ocsp/mychain_int.pem");
1✔
1185
         auto trust_root = load_test_X509_cert("x509/ocsp/mychain_root.pem");
1✔
1186

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

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

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

1196
         const auto path_result =
1✔
1197
            Botan::x509_path_validate(cert_path,
5✔
1198
                                      restrictions,
1199
                                      trusted,
1200
                                      "",
1201
                                      Botan::Usage_Type::UNSPECIFIED,
1202
                                      Botan::calendar_point(2022, 9, 22, 22, 30, 0).to_std_timepoint(),
2✔
1203
                                      std::chrono::milliseconds(0),
1✔
1204
                                      {ocsp_ee, ocsp_ca});
3✔
1205
         result.confirm("should reject intermediate OCSP response",
2✔
1206
                        path_result.result() == Botan::Certificate_Status_Code::OCSP_ISSUER_NOT_FOUND);
1✔
1207
         result.test_note(std::string("Failed with: ") + Botan::to_string(path_result.result()));
3✔
1208

1209
         return result;
1✔
1210
      }
7✔
1211

1212
      std::vector<Test::Result> run() override {
1✔
1213
         return {validate_with_ocsp_with_next_update_without_max_age(),
1✔
1214
                 validate_with_ocsp_with_next_update_with_max_age(),
1215
                 validate_with_ocsp_without_next_update_without_max_age(),
1216
                 validate_with_ocsp_without_next_update_with_max_age(),
1217
                 validate_with_ocsp_with_authorized_responder(),
1218
                 validate_with_ocsp_with_authorized_responder_without_keyusage(),
1219
                 validate_with_forged_ocsp_using_self_signed_cert(),
1220
                 validate_with_ocsp_self_signed_by_intermediate_cert()};
9✔
1221
      }
1✔
1222
};
1223

1224
BOTAN_REGISTER_TEST("x509", "x509_path_with_ocsp", Path_Validation_With_OCSP_Tests);
1225

1226
   #endif
1227

1228
   #if defined(BOTAN_HAS_ECDSA)
1229

1230
class CVE_2020_0601_Tests final : public Test {
×
1231
   public:
1232
      std::vector<Test::Result> run() override {
1✔
1233
         Test::Result result("CVE-2020-0601");
1✔
1234

1235
         if(!Botan::EC_Group::supports_application_specific_group()) {
1✔
1236
            result.test_note("Skipping as application specific groups are not supported");
×
1237
            return {result};
×
1238
         }
1239

1240
         if(!Botan::EC_Group::supports_named_group("secp384r1")) {
1✔
1241
            result.test_note("Skipping as secp384r1 is not supported");
×
1242
            return {result};
×
1243
         }
1244

1245
         const auto& secp384r1 = Botan::EC_Group::from_name("secp384r1");
1✔
1246
         Botan::OID curveball_oid("1.3.6.1.4.1.25258.4.2020.0601");
1✔
1247
         Botan::EC_Group curveball(
1✔
1248
            curveball_oid,
1249
            secp384r1.get_p(),
1250
            secp384r1.get_a(),
1251
            secp384r1.get_b(),
1252
            BigInt(
2✔
1253
               "0xC711162A761D568EBEB96265D4C3CEB4F0C330EC8F6DD76E39BCC849ABABB8E34378D581065DEFC77D9FCED6B39075DE"),
1254
            BigInt(
2✔
1255
               "0x0CB090DE23BAC8D13E67E019A91B86311E5F342DEE17FD15FB7E278A32A1EAC98FC97E18CB2F3B2C487A7DA6F40107AC"),
1256
            secp384r1.get_order());
2✔
1257

1258
         auto ca_crt = Botan::X509_Certificate(Test::data_file("x509/cve-2020-0601/ca.pem"));
2✔
1259
         auto fake_ca_crt = Botan::X509_Certificate(Test::data_file("x509/cve-2020-0601/fake_ca.pem"));
2✔
1260
         auto ee_crt = Botan::X509_Certificate(Test::data_file("x509/cve-2020-0601/ee.pem"));
2✔
1261

1262
         Botan::Certificate_Store_In_Memory trusted;
1✔
1263
         trusted.add_certificate(ca_crt);
1✔
1264

1265
         const auto restrictions = Botan::Path_Validation_Restrictions(false, 80, false);
2✔
1266

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

1269
         const auto path_result1 = Botan::x509_path_validate(std::vector<Botan::X509_Certificate>{ee_crt, fake_ca_crt},
5✔
1270
                                                             restrictions,
1271
                                                             trusted,
1272
                                                             "",
1273
                                                             Botan::Usage_Type::UNSPECIFIED,
1274
                                                             valid_time,
1275
                                                             std::chrono::milliseconds(0),
1✔
1276
                                                             {});
2✔
1277

1278
         result.confirm("Validation failed", !path_result1.successful_validation());
2✔
1279

1280
         result.confirm("Expected status",
2✔
1281
                        path_result1.result() == Botan::Certificate_Status_Code::CANNOT_ESTABLISH_TRUST);
1✔
1282

1283
         const auto path_result2 = Botan::x509_path_validate(std::vector<Botan::X509_Certificate>{ee_crt},
3✔
1284
                                                             restrictions,
1285
                                                             trusted,
1286
                                                             "",
1287
                                                             Botan::Usage_Type::UNSPECIFIED,
1288
                                                             valid_time,
1289
                                                             std::chrono::milliseconds(0),
1✔
1290
                                                             {});
2✔
1291

1292
         result.confirm("Validation failed", !path_result2.successful_validation());
2✔
1293

1294
         result.confirm("Expected status",
2✔
1295
                        path_result2.result() == Botan::Certificate_Status_Code::CERT_ISSUER_NOT_FOUND);
1✔
1296

1297
         // Verify the signature from the bad CA is actually correct
1298
         Botan::Certificate_Store_In_Memory frusted;
1✔
1299
         frusted.add_certificate(fake_ca_crt);
1✔
1300

1301
         const auto path_result3 = Botan::x509_path_validate(std::vector<Botan::X509_Certificate>{ee_crt},
3✔
1302
                                                             restrictions,
1303
                                                             frusted,
1304
                                                             "",
1305
                                                             Botan::Usage_Type::UNSPECIFIED,
1306
                                                             valid_time,
1307
                                                             std::chrono::milliseconds(0),
1✔
1308
                                                             {});
2✔
1309

1310
         result.confirm("Validation succeeded", path_result3.successful_validation());
2✔
1311

1312
         return {result};
2✔
1313
      }
5✔
1314
};
1315

1316
BOTAN_REGISTER_TEST("x509", "x509_cve_2020_0601", CVE_2020_0601_Tests);
1317

1318
class Path_Validation_With_Immortal_CRL final : public Test {
×
1319
   public:
1320
      std::vector<Test::Result> run() override {
1✔
1321
         // RFC 5280 defines the nextUpdate field as "optional" (in line with
1322
         // the original X.509 standard), but then requires all conforming CAs
1323
         // to always define it. For best compatibility we must deal with both.
1324
         Test::Result result("Using a CRL without a nextUpdate field");
1✔
1325

1326
         if(Botan::has_filesystem_impl() == false) {
1✔
1327
            result.test_note("Skipping due to missing filesystem access");
×
1328
            return {result};
×
1329
         }
1330

1331
         Botan::X509_Certificate root(Test::data_file("x509/misc/crl_without_nextupdate/ca.pem"));
2✔
1332
         Botan::X509_Certificate revoked_subject(Test::data_file("x509/misc/crl_without_nextupdate/01.pem"));
2✔
1333
         Botan::X509_Certificate valid_subject(Test::data_file("x509/misc/crl_without_nextupdate/42.pem"));
2✔
1334

1335
         // Check that a CRL without nextUpdate is parsable
1336
         auto crl = Botan::X509_CRL(Test::data_file("x509/misc/crl_without_nextupdate/valid_forever.crl"));
2✔
1337
         result.confirm("this update is set", crl.this_update().time_is_set());
2✔
1338
         result.confirm("next update is not set", !crl.next_update().time_is_set());
2✔
1339
         result.confirm("CRL is not empty", !crl.get_revoked().empty());
2✔
1340

1341
         // Ensure that we support the used sig algo, otherwish stop here
1342
         if(!Botan::EC_Group::supports_named_group("brainpool512r1")) {
1✔
1343
            result.test_note("Cannot test path validation because signature algorithm is not support in this build");
×
1344
            return {result};
×
1345
         }
1346

1347
         Botan::Certificate_Store_In_Memory trusted;
1✔
1348
         trusted.add_certificate(root);
1✔
1349
         trusted.add_crl(crl);
1✔
1350

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

1355
         Botan::Path_Validation_Restrictions restrictions(true /* require revocation info */);
2✔
1356

1357
         // Validate a certificate that is not listed in the CRL
1358
         const auto valid = Botan::x509_path_validate(
1✔
1359
            valid_subject, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, valid_time);
1✔
1360
         if(!result.confirm("Valid certificate", valid.successful_validation())) {
2✔
1361
            result.test_note(valid.result_string());
×
1362
         }
1363

1364
         // Ensure that a certificate listed in the CRL is recognized as revoked
1365
         const auto revoked = Botan::x509_path_validate(
1✔
1366
            revoked_subject, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, valid_time);
1✔
1367
         if(!result.confirm("No valid certificate", !revoked.successful_validation())) {
2✔
1368
            result.test_note(revoked.result_string());
×
1369
         }
1370
         result.test_is_eq("Certificate is revoked", revoked.result(), Botan::Certificate_Status_Code::CERT_IS_REVOKED);
1✔
1371

1372
         return {result};
2✔
1373
      }
2✔
1374
};
1375

1376
BOTAN_REGISTER_TEST("x509", "x509_path_immortal_crl", Path_Validation_With_Immortal_CRL);
1377

1378
   #endif
1379

1380
   #if defined(BOTAN_HAS_XMSS_RFC8391)
1381

1382
class XMSS_Path_Validation_Tests final : public Test {
×
1383
   public:
1384
      static Test::Result validate_self_signed(const std::string& name, const std::string& file) {
2✔
1385
         Test::Result result(name);
2✔
1386

1387
         Botan::Path_Validation_Restrictions restrictions;
4✔
1388
         auto self_signed = Botan::X509_Certificate(Test::data_file("x509/xmss/" + file));
4✔
1389

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

1393
         auto status = Botan::PKIX::overall_status(
2✔
1394
            Botan::PKIX::check_chain(cert_path, valid_time, "", Botan::Usage_Type::UNSPECIFIED, restrictions));
4✔
1395
         result.test_eq("Cert validation status", Botan::to_string(status), "Verified");
2✔
1396
         return result;
2✔
1397
      }
4✔
1398

1399
      std::vector<Test::Result> run() override {
1✔
1400
         if(Botan::has_filesystem_impl() == false) {
1✔
1401
            return {Test::Result::Note("XMSS path validation", "Skipping due to missing filesystem access")};
×
1402
         }
1403

1404
         return {
1✔
1405
            validate_self_signed("XMSS path validation with certificate created by ISARA corp", "xmss_isara_root.pem"),
1✔
1406
            validate_self_signed("XMSS path validation with certificate created by BouncyCastle",
1✔
1407
                                 "xmss_bouncycastle_sha256_10_root.pem")};
3✔
1408
      }
4✔
1409
};
1410

1411
BOTAN_REGISTER_TEST("x509", "x509_path_xmss", XMSS_Path_Validation_Tests);
1412

1413
   #endif
1414

1415
#endif
1416

1417
}  // namespace
1418

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