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

randombit / botan / 11844561993

14 Nov 2024 07:58PM UTC coverage: 91.178% (+0.1%) from 91.072%
11844561993

Pull #4435

github

web-flow
Merge 81dcb29da into e430f157a
Pull Request #4435: Test duration values ​​are now presented in seconds with six digits of precision. Tests without time measurements have been edited.

91856 of 100744 relevant lines covered (91.18%)

9311006.71 hits per line

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

94.91
/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
   #include <algorithm>
23
   #include <fstream>
24
   #include <limits>
25
   #include <map>
26
   #include <string>
27
   #include <vector>
28
#endif
29

30
namespace Botan_Tests {
31

32
namespace {
33

34
#if defined(BOTAN_HAS_X509_CERTIFICATES) && defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
35

36
   #if defined(BOTAN_HAS_RSA) && defined(BOTAN_HAS_EMSA_PKCS1)
37

38
std::map<std::string, std::string> read_results(const std::string& results_file, const char delim = ':') {
6✔
39
   std::ifstream in(results_file);
6✔
40
   if(!in.good()) {
6✔
41
      throw Test_Error("Failed reading " + results_file);
×
42
   }
43

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

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

57
      if(parts.size() != 2) {
406✔
58
         throw Test_Error("Invalid line " + line);
×
59
      }
60

61
      m[parts[0]] = parts[1];
406✔
62
   }
406✔
63

64
   return m;
12✔
65
}
6✔
66

67
std::set<Botan::Certificate_Status_Code> flatten(const Botan::CertificatePathStatusCodes& codes) {
4✔
68
   std::set<Botan::Certificate_Status_Code> result;
4✔
69

70
   for(const auto& statuses : codes) {
16✔
71
      result.insert(statuses.begin(), statuses.end());
12✔
72
   }
73

74
   return result;
4✔
75
}
×
76

77
class X509test_Path_Validation_Tests final : public Test {
×
78
   public:
79
      std::vector<Test::Result> run() override {
1✔
80
         std::vector<Test::Result> results;
1✔
81

82
         // Test certs generated by https://github.com/yymax/x509test
83

84
         // Current tests use SHA-1
85
         const Botan::Path_Validation_Restrictions restrictions(false, 80);
2✔
86

87
         Botan::X509_Certificate root(Test::data_file("x509/x509test/root.pem"));
2✔
88
         Botan::Certificate_Store_In_Memory trusted;
1✔
89
         trusted.add_certificate(root);
1✔
90

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

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

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

99
            if(certs.empty()) {
37✔
100
               throw Test_Error("Failed to read certs from " + filename);
×
101
            }
102

103
            Botan::Path_Validation_Result path_result = Botan::x509_path_validate(
37✔
104
               certs, restrictions, trusted, "www.tls.test", Botan::Usage_Type::TLS_SERVER_AUTH, validation_time);
37✔
105

106
            if(path_result.successful_validation() && path_result.trust_root() != root) {
37✔
107
               path_result = Botan::Path_Validation_Result(Botan::Certificate_Status_Code::CANNOT_ESTABLISH_TRUST);
×
108
            }
109

110
            result.test_eq("test " + filename, path_result.result_string(), expected_result);
74✔
111
            result.test_eq("test no warnings string", path_result.warnings_string(), "");
74✔
112
            result.confirm("test no warnings", path_result.no_warnings());
74✔
113
            result.end_timer();
37✔
114
            results.push_back(result);
37✔
115
         }
37✔
116

117
         // test softfail
118
         {
1✔
119
            Test::Result result("X509test path validation softfail");
1✔
120
            result.start_timer();
1✔
121

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

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

139
            if(path_result.successful_validation() && path_result.trust_root() != root) {
1✔
140
               path_result = Botan::Path_Validation_Result(Botan::Certificate_Status_Code::CANNOT_ESTABLISH_TRUST);
×
141
            }
142

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

155
         return results;
1✔
156
      }
1✔
157

158
   private:
159
      static std::vector<Botan::X509_Certificate> load_cert_file(const std::string& filename) {
38✔
160
         Botan::DataSource_Stream in(filename);
38✔
161

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

169
         return certs;
38✔
170
      }
38✔
171
};
172

173
BOTAN_REGISTER_TEST("x509", "x509_path_x509test", X509test_Path_Validation_Tests);
174

175
class NIST_Path_Validation_Tests final : public Test {
×
176
   public:
177
      std::vector<Test::Result> run() override;
178
};
179

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

185
   std::vector<Test::Result> results;
1✔
186

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

201
   const Botan::X509_Certificate root_cert(Test::data_file("x509/nist/root.crt"));
2✔
202
   const Botan::X509_CRL root_crl(Test::data_file("x509/nist/root.crl"));
2✔
203

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

206
   for(auto i = expected.begin(); i != expected.end(); ++i) {
77✔
207
      Test::Result result("NIST path validation");
76✔
208
      result.start_timer();
76✔
209

210
      const std::string test_name = i->first;
76✔
211

212
      try {
76✔
213
         const std::string expected_result = i->second;
76✔
214

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

217
         Botan::Certificate_Store_In_Memory store;
76✔
218

219
         store.add_certificate(root_cert);
76✔
220
         store.add_crl(root_crl);
76✔
221

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

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

234
         // 1024 bit root cert
235
         Botan::Path_Validation_Restrictions restrictions(true, 80);
152✔
236

237
         Botan::Path_Validation_Result validation_result = Botan::x509_path_validate(
76✔
238
            end_user, restrictions, store, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
76✔
239

240
         result.test_eq(test_name + " path validation result", validation_result.result_string(), expected_result);
152✔
241
      } catch(std::exception& e) {
76✔
242
         result.test_failure(test_name, e.what());
×
243
      }
×
244

245
      result.end_timer();
76✔
246
      results.push_back(result);
76✔
247
   }
76✔
248

249
   return results;
1✔
250
}
1✔
251

252
BOTAN_REGISTER_TEST("x509", "x509_path_nist", NIST_Path_Validation_Tests);
253

254
class Extended_Path_Validation_Tests final : public Test {
×
255
   public:
256
      std::vector<Test::Result> run() override;
257
};
258

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

264
   std::vector<Test::Result> results;
1✔
265

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

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

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

274
      Botan::Certificate_Store_In_Memory store;
3✔
275

276
      for(const auto& file : all_files) {
13✔
277
         if(file.ends_with(".crt") && file != "end.crt") {
10✔
278
            store.add_certificate(Botan::X509_Certificate(file));
10✔
279
         }
280
      }
281

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

284
      Botan::Path_Validation_Restrictions restrictions;
6✔
285
      Botan::Path_Validation_Result validation_result =
3✔
286
         Botan::x509_path_validate(end_user, restrictions, store, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
3✔
287

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

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

294
   return results;
1✔
295
}
1✔
296

297
BOTAN_REGISTER_TEST("x509", "x509_path_extended", Extended_Path_Validation_Tests);
298

299
class PSS_Path_Validation_Tests : public Test {
×
300
   public:
301
      std::vector<Test::Result> run() override;
302
};
303

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

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

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

313
   auto validation_times_iter = validation_times.begin();
1✔
314

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

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

321
      std::optional<Botan::X509_CRL> crl;
118✔
322
      std::optional<Botan::X509_Certificate> end;
118✔
323
      std::optional<Botan::X509_Certificate> root;
118✔
324
      Botan::Certificate_Store_In_Memory store;
118✔
325
      std::optional<Botan::PKCS10_Request> csr;
118✔
326

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

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

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

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

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

361
         Botan::Path_Validation_Result validation_result =
91✔
362
            Botan::x509_path_validate(*end, restrictions, store, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
91✔
363

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

375
      result.end_timer();
118✔
376
      results.push_back(result);
118✔
377
   }
334✔
378

379
   return results;
1✔
380
}
19✔
381

382
BOTAN_REGISTER_TEST("x509", "x509_path_rsa_pss", PSS_Path_Validation_Tests);
383

384
class Validate_V1Cert_Test final : public Test {
×
385
   public:
386
      std::vector<Test::Result> run() override;
387
};
388

389
std::vector<Test::Result> Validate_V1Cert_Test::run() {
1✔
390
   Test::Result result("Verifying using v1 certificate");
1✔
391
   result.start_timer();
1✔
392

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

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

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

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

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

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

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

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

418
   
419
   result.test_eq("Path validation result", validation_result.result_string(), "Verified");
2✔
420

421
   Botan::Certificate_Store_In_Memory empty;
1✔
422

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

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

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

430
   result.end_timer();
1✔
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
   Test::Result result("Verifying v1 certificate using v2 uid fields");
1✔
443
   result.start_timer();
1✔
444

445
   if(Botan::has_filesystem_impl() == false) {
1✔
446
      return {Test::Result::Note("Path validation", "Skipping due to missing filesystem access")};
×
447
   }
448

449
   std::vector<Test::Result> results;
1✔
450

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

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

457
   Botan::X509_Certificate root(root_crt);
1✔
458
   Botan::X509_Certificate intermediate(int_crt);
1✔
459
   Botan::X509_Certificate ee_cert(ee_crt);
1✔
460

461
   Botan::Certificate_Store_In_Memory trusted;
1✔
462
   trusted.add_certificate(root);
1✔
463

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

466
   Botan::Path_Validation_Restrictions restrictions;
2✔
467
   Botan::Path_Validation_Result validation_result =
1✔
468
      Botan::x509_path_validate(chain, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
1✔
469

470
   
471
   result.test_eq("Path validation failed", validation_result.successful_validation(), false);
1✔
472
   result.test_eq(
3✔
473
      "Path validation result", validation_result.result_string(), "Encountered v2 identifiers in v1 certificate");
2✔
474

475
   result.end_timer();
1✔
476
   return {result};
2✔
477
}
3✔
478

479
BOTAN_REGISTER_TEST("x509", "x509_v2uid_in_v1", Validate_V2Uid_in_V1_Test);
480

481
class Validate_Name_Constraint_SAN_Test final : public Test {
×
482
   public:
483
      std::vector<Test::Result> run() override;
484
};
485

486
std::vector<Test::Result> Validate_Name_Constraint_SAN_Test::run() {
1✔
487
   Test::Result result("Verifying certificate with alternative SAN violating name constraint");
1✔
488
   result.start_timer();
1✔
489

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

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

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

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

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

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

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

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

515
   
516
   result.test_eq("Path validation failed", validation_result.successful_validation(), false);
1✔
517
   result.test_eq(
3✔
518
      "Path validation result", validation_result.result_string(), "Certificate does not pass name constraint");
2✔
519
   
520
   result.end_timer();
1✔
521
   return {result};
2✔
522
}
3✔
523

524
BOTAN_REGISTER_TEST("x509", "x509_name_constraint_san", Validate_Name_Constraint_SAN_Test);
525

526
class Validate_Name_Constraint_CaseInsensitive final : public Test {
×
527
   public:
528
      std::vector<Test::Result> run() override;
529
};
530

531
std::vector<Test::Result> Validate_Name_Constraint_CaseInsensitive::run() {
1✔
532
   Test::Result result("DNS name constraints are case insensitive");
1✔
533
   result.start_timer();
1✔
534

535
   if(Botan::has_filesystem_impl() == false) {
1✔
536
      return {Test::Result::Note("Path validation", "Skipping due to missing filesystem access")};
×
537
   }
538

539
   std::vector<Test::Result> results;
1✔
540

541
   const std::string root_crt = Test::data_file("x509/misc/name_constraint_ci/root.pem");
1✔
542
   const std::string int_crt = Test::data_file("x509/misc/name_constraint_ci/int.pem");
1✔
543
   const std::string ee_crt = Test::data_file("x509/misc/name_constraint_ci/leaf.pem");
1✔
544

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

547
   Botan::X509_Certificate root(root_crt);
1✔
548
   Botan::X509_Certificate intermediate(int_crt);
1✔
549
   Botan::X509_Certificate ee_cert(ee_crt);
1✔
550

551
   Botan::Certificate_Store_In_Memory trusted;
1✔
552
   trusted.add_certificate(root);
1✔
553

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

556
   Botan::Path_Validation_Restrictions restrictions;
2✔
557
   Botan::Path_Validation_Result validation_result =
1✔
558
      Botan::x509_path_validate(chain, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
1✔
559

560
   result.test_eq("Path validation succeeded", validation_result.successful_validation(), true);
1✔
561
   result.end_timer();
1✔
562
   return {result};
2✔
563
}
3✔
564

565
BOTAN_REGISTER_TEST("x509", "x509_name_constraint_ci", Validate_Name_Constraint_CaseInsensitive);
566

567
class Validate_Name_Constraint_NoCheckSelf final : public Test {
×
568
   public:
569
      std::vector<Test::Result> run() override;
570
};
571

572
std::vector<Test::Result> Validate_Name_Constraint_NoCheckSelf::run() {
1✔
573
   Test::Result result("Name constraints do not apply to the certificate which includes them");
1✔
574
   result.start_timer();
1✔
575

576
   if(Botan::has_filesystem_impl() == false) {
1✔
577
      return {Test::Result::Note("Path validation", "Skipping due to missing filesystem access")};
×
578
   }
579

580
   std::vector<Test::Result> results;
1✔
581

582
   const std::string root_crt = Test::data_file("x509/misc/nc_skip_self/root.pem");
1✔
583
   const std::string int_crt = Test::data_file("x509/misc/nc_skip_self/int.pem");
1✔
584
   const std::string ee_crt = Test::data_file("x509/misc/nc_skip_self/leaf.pem");
1✔
585

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

588
   Botan::X509_Certificate root(root_crt);
1✔
589
   Botan::X509_Certificate intermediate(int_crt);
1✔
590
   Botan::X509_Certificate ee_cert(ee_crt);
1✔
591

592
   Botan::Certificate_Store_In_Memory trusted;
1✔
593
   trusted.add_certificate(root);
1✔
594

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

597
   Botan::Path_Validation_Restrictions restrictions;
2✔
598
   Botan::Path_Validation_Result validation_result =
1✔
599
      Botan::x509_path_validate(chain, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
1✔
600

601
   
602
   result.test_eq("Path validation succeeded", validation_result.successful_validation(), true);
1✔
603
   result.end_timer();
1✔
604
   return {result};
2✔
605
}
3✔
606

607
BOTAN_REGISTER_TEST("x509", "x509_name_constraint_no_check_self", Validate_Name_Constraint_NoCheckSelf);
608

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

616
         const std::string trusted_root_crt = Test::data_file("x509/misc/root_cert_time_check/root.crt");
1✔
617
         const std::string leaf_crt = Test::data_file("x509/misc/root_cert_time_check/leaf.crt");
1✔
618

619
         const Botan::X509_Certificate trusted_root_cert(trusted_root_crt);
1✔
620
         const Botan::X509_Certificate leaf_cert(leaf_crt);
1✔
621

622
         Botan::Certificate_Store_In_Memory trusted;
1✔
623
         trusted.add_certificate(trusted_root_cert);
1✔
624

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

627
         Test::Result result("Root cert time check");
1✔
628
         result.start_timer();
1✔
629

630
         auto assert_path_validation_result = [&](std::string_view descr,
11✔
631
                                                  bool ignore_trusted_root_time_range,
632
                                                  uint32_t year,
633
                                                  Botan::Certificate_Status_Code exp_status,
634
                                                  std::optional<Botan::Certificate_Status_Code> exp_warning =
635
                                                     std::nullopt) {
636
            const Botan::Path_Validation_Restrictions restrictions(
10✔
637
               false,
638
               110,
639
               false,
640
               std::chrono::seconds::zero(),
641
               std::make_unique<Botan::Certificate_Store_In_Memory>(),
×
642
               ignore_trusted_root_time_range);
20✔
643

644
            const Botan::Path_Validation_Result validation_result =
10✔
645
               Botan::x509_path_validate(chain,
10✔
646
                                         restrictions,
647
                                         trusted,
10✔
648
                                         "",
649
                                         Botan::Usage_Type::UNSPECIFIED,
650
                                         Botan::calendar_point(year, 1, 1, 1, 0, 0).to_std_timepoint());
10✔
651
            const std::string descr_str = Botan::fmt(
10✔
652
               "Root cert validity range {}: {}", ignore_trusted_root_time_range ? "ignored" : "checked", descr);
15✔
653

654
            result.test_is_eq(descr_str, validation_result.result(), exp_status);
10✔
655
            const auto warnings = validation_result.warnings();
10✔
656
            BOTAN_ASSERT_NOMSG(warnings.size() == 2);
10✔
657
            result.confirm("No warning for leaf cert", warnings.at(0).empty());
20✔
658
            if(exp_warning) {
10✔
659
               result.confirm("Warning for root cert",
12✔
660
                              warnings.at(1).size() == 1 && warnings.at(1).contains(*exp_warning));
8✔
661
            } else {
662
               result.confirm("No warning for root cert", warnings.at(1).empty());
12✔
663
            }
664
         };
10✔
665
         // (Trusted) root cert validity range: 2022-2028
666
         // Leaf cert validity range: 2020-2030
667

668
         // Trusted root time range is checked
669
         assert_path_validation_result(
1✔
670
            "Root and leaf certs in validity range", false, 2025, Botan::Certificate_Status_Code::OK);
671
         assert_path_validation_result(
1✔
672
            "Root and leaf certs are expired", false, 2031, Botan::Certificate_Status_Code::CERT_HAS_EXPIRED);
673
         assert_path_validation_result(
1✔
674
            "Root and leaf certs are not yet valid", false, 2019, Botan::Certificate_Status_Code::CERT_NOT_YET_VALID);
675
         assert_path_validation_result(
1✔
676
            "Root cert is expired, leaf cert not", false, 2029, Botan::Certificate_Status_Code::CERT_HAS_EXPIRED);
677
         assert_path_validation_result("Root cert is not yet valid, leaf cert is",
1✔
678
                                       false,
679
                                       2021,
680
                                       Botan::Certificate_Status_Code::CERT_NOT_YET_VALID);
681

682
         // Trusted root time range is ignored
683
         assert_path_validation_result(
1✔
684
            "Root and leaf certs in validity range", true, 2025, Botan::Certificate_Status_Code::OK);
685
         assert_path_validation_result("Root and leaf certs are expired",
2✔
686
                                       true,
687
                                       2031,
688
                                       Botan::Certificate_Status_Code::CERT_HAS_EXPIRED,
689
                                       Botan::Certificate_Status_Code::TRUSTED_CERT_HAS_EXPIRED);
1✔
690
         assert_path_validation_result("Root and leaf certs are not yet valid",
2✔
691
                                       true,
692
                                       2019,
693
                                       Botan::Certificate_Status_Code::CERT_NOT_YET_VALID,
694
                                       Botan::Certificate_Status_Code::TRUSTED_CERT_NOT_YET_VALID);
1✔
695
         assert_path_validation_result("Root cert is expired, leaf cert not",
2✔
696
                                       true,
697
                                       2029,
698
                                       Botan::Certificate_Status_Code::OK,
699
                                       Botan::Certificate_Status_Code::TRUSTED_CERT_HAS_EXPIRED);
1✔
700
         assert_path_validation_result("Root cert is not yet valid, leaf cert is",
2✔
701
                                       true,
702
                                       2021,
703
                                       Botan::Certificate_Status_Code::OK,
704
                                       Botan::Certificate_Status_Code::TRUSTED_CERT_NOT_YET_VALID);
1✔
705

706
         result.end_timer();
1✔
707
         return {result};
2✔
708
      }
3✔
709
};
710

711
BOTAN_REGISTER_TEST("x509", "x509_root_cert_time_check", Root_Cert_Time_Check_Test);
712

713
class BSI_Path_Validation_Tests final : public Test
×
714

715
{
716
   public:
717
      std::vector<Test::Result> run() override;
718
};
719

720
std::vector<Test::Result> BSI_Path_Validation_Tests::run() {
1✔
721
   if(Botan::has_filesystem_impl() == false) {
1✔
722
      return {Test::Result::Note("BSI path validation", "Skipping due to missing filesystem access")};
×
723
   }
724

725
   std::vector<Test::Result> results;
1✔
726

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

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

733
      Botan::Certificate_Store_In_Memory trusted;
54✔
734
      std::vector<Botan::X509_Certificate> certs;
54✔
735

736
      #if defined(BOTAN_HAS_MD5)
737
      const bool has_md5 = true;
54✔
738
      #else
739
      const bool has_md5 = false;
740
      #endif
741

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

744
      // By convention: if CRL is a substring if the test name,
745
      // we need to check the CRLs
746
      bool use_crl = false;
54✔
747
      if(test_name.find("CRL") != std::string::npos) {
54✔
748
         use_crl = true;
16✔
749
      }
750

751
      try {
54✔
752
         for(const auto& file : all_files) {
445✔
753
            // found a trust anchor
754
            if(file.find("TA") != std::string::npos) {
395✔
755
               trusted.add_certificate(Botan::X509_Certificate(file));
50✔
756
            }
757
            // found the target certificate. It needs to be at the front of certs
758
            else if(file.find("TC") != std::string::npos) {
345✔
759
               certs.insert(certs.begin(), Botan::X509_Certificate(file));
54✔
760
            }
761
            // found a certificate that might be part of a valid certificate chain to the trust anchor
762
            else if(file.find(".crt") != std::string::npos) {
291✔
763
               certs.push_back(Botan::X509_Certificate(file));
112✔
764
            } else if(file.find(".crl") != std::string::npos) {
235✔
765
               trusted.add_crl(Botan::X509_CRL(file));
28✔
766
            }
767
         }
768

769
         Botan::Path_Validation_Restrictions restrictions(use_crl, 79, use_crl);
100✔
770

771
         /*
772
          * Following the test document, the test are executed 16 times with
773
          * randomly chosen order of the available certificates. However, the target
774
          * certificate needs to stay in front.
775
          * For certain test, the order in which the certificates are given to
776
          * the validation function may be relevant, i.e. if issuer DNs are
777
          * ambiguous.
778
          */
779
         class random_bit_generator {
50✔
780
            public:
781
               using result_type = size_t;
782

783
               static constexpr result_type min() { return 0; }
784

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

787
               result_type operator()() {
80✔
788
                  size_t s;
80✔
789
                  m_rng.randomize(reinterpret_cast<uint8_t*>(&s), sizeof(s));
80✔
790
                  return s;
80✔
791
               }
792

793
               random_bit_generator(Botan::RandomNumberGenerator& rng) : m_rng(rng) {}
50✔
794

795
            private:
796
               Botan::RandomNumberGenerator& m_rng;
797
         } rbg(this->rng());
50✔
798

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

802
            Botan::Path_Validation_Result validation_result = Botan::x509_path_validate(
800✔
803
               certs, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
800✔
804

805
            // We expect to be warned
806
            if(expected_result.starts_with("Warning: ")) {
800✔
807
               std::string stripped = expected_result.substr(std::string("Warning: ").size());
32✔
808
               bool found_warning = false;
32✔
809
               for(const auto& warning_set : validation_result.warnings()) {
128✔
810
                  for(const auto& warning : warning_set) {
128✔
811
                     std::string warning_str(Botan::to_string(warning));
32✔
812
                     if(stripped == warning_str) {
32✔
813
                        result.test_eq(test_name + " path validation result", warning_str, stripped);
32✔
814
                        found_warning = true;
32✔
815
                     }
816
                  }
32✔
817
               }
32✔
818
               if(!found_warning) {
32✔
819
                  result.test_failure(test_name, "Did not receive the expected warning: " + stripped);
×
820
               }
821
            } else {
32✔
822
               if(expected_result == "Hash function used is considered too weak for security" && has_md5 == false) {
768✔
823
                  result.test_eq(test_name + " path validation result",
824
                                 validation_result.result_string(),
825
                                 "Certificate signed with unknown/unavailable algorithm");
826
               } else {
827
                  result.test_eq(
768✔
828
                     test_name + " path validation result", validation_result.result_string(), expected_result);
1,536✔
829
               }
830
            }
831
         }
800✔
832
      }
50✔
833

834
      /* Some certificates are rejected when executing the X509_Certificate constructor
835
       * by throwing a Decoding_Error exception.
836
       */
837
      catch(const Botan::Exception& e) {
4✔
838
         if(e.error_type() == Botan::ErrorType::DecodingFailure) {
4✔
839
            result.test_eq(test_name + " path validation result", e.what(), expected_result);
8✔
840
         } else {
841
            result.test_failure(test_name, e.what());
×
842
         }
843
      }
4✔
844

845
      result.end_timer();
54✔
846
      results.push_back(result);
54✔
847
   }
54✔
848

849
   return results;
1✔
850
}
1✔
851

852
BOTAN_REGISTER_TEST("x509", "x509_path_bsi", BSI_Path_Validation_Tests);
853

854
class Path_Validation_With_OCSP_Tests final : public Test {
×
855
   public:
856
      static Botan::X509_Certificate load_test_X509_cert(const std::string& path) {
24✔
857
         return Botan::X509_Certificate(Test::data_file(path));
48✔
858
      }
859

860
      static std::optional<Botan::OCSP::Response> load_test_OCSP_resp(const std::string& path) {
12✔
861
         return Botan::OCSP::Response(Test::read_binary_data_file(path));
36✔
862
      }
863

864
      static Test::Result validate_with_ocsp_with_next_update_without_max_age() {
1✔
865
         Test::Result result("path check with ocsp with next_update w/o max_age");
1✔
866
         result.start_timer();
1✔
867

868
         Botan::Certificate_Store_In_Memory trusted;
1✔
869

870
         auto restrictions = Botan::Path_Validation_Restrictions(false, 110, false);
2✔
871

872
         auto ee = load_test_X509_cert("x509/ocsp/randombit.pem");
1✔
873
         auto ca = load_test_X509_cert("x509/ocsp/letsencrypt.pem");
1✔
874
         auto trust_root = load_test_X509_cert("x509/ocsp/identrust.pem");
1✔
875
         trusted.add_certificate(trust_root);
1✔
876

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

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

881
         auto check_path = [&](const std::chrono::system_clock::time_point valid_time,
5✔
882
                               const Botan::Certificate_Status_Code expected) {
883
            const auto path_result = Botan::x509_path_validate(cert_path,
8✔
884
                                                               restrictions,
885
                                                               trusted,
4✔
886
                                                               "",
887
                                                               Botan::Usage_Type::UNSPECIFIED,
888
                                                               valid_time,
889
                                                               std::chrono::milliseconds(0),
4✔
890
                                                               {ocsp});
8✔
891

892
            return result.confirm(std::string("Status: '") + Botan::to_string(expected) + "' should match '" +
24✔
893
                                     Botan::to_string(path_result.result()) + "'",
8✔
894
                                  path_result.result() == expected);
8✔
895
         };
8✔
896

897
         check_path(Botan::calendar_point(2016, 11, 11, 12, 30, 0).to_std_timepoint(),
1✔
898
                    Botan::Certificate_Status_Code::OCSP_NOT_YET_VALID);
899
         check_path(Botan::calendar_point(2016, 11, 18, 12, 30, 0).to_std_timepoint(),
1✔
900
                    Botan::Certificate_Status_Code::OK);
901
         check_path(Botan::calendar_point(2016, 11, 20, 8, 30, 0).to_std_timepoint(),
1✔
902
                    Botan::Certificate_Status_Code::OK);
903
         check_path(Botan::calendar_point(2016, 11, 28, 8, 30, 0).to_std_timepoint(),
1✔
904
                    Botan::Certificate_Status_Code::OCSP_HAS_EXPIRED);
905

906
         result.end_timer();
1✔
907
         return result;
2✔
908
      }
2✔
909

910
      static Test::Result validate_with_ocsp_with_next_update_with_max_age() {
1✔
911
         Test::Result result("path check with ocsp with next_update with max_age");
1✔
912
         result.start_timer();
1✔
913

914
         Botan::Certificate_Store_In_Memory trusted;
1✔
915

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

918
         auto ee = load_test_X509_cert("x509/ocsp/randombit.pem");
1✔
919
         auto ca = load_test_X509_cert("x509/ocsp/letsencrypt.pem");
1✔
920
         auto trust_root = load_test_X509_cert("x509/ocsp/identrust.pem");
1✔
921
         trusted.add_certificate(trust_root);
1✔
922

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

925
         auto ocsp = load_test_OCSP_resp("x509/ocsp/randombit_ocsp.der");
1✔
926

927
         auto check_path = [&](const std::chrono::system_clock::time_point valid_time,
5✔
928
                               const Botan::Certificate_Status_Code expected) {
929
            const auto path_result = Botan::x509_path_validate(cert_path,
8✔
930
                                                               restrictions,
931
                                                               trusted,
4✔
932
                                                               "",
933
                                                               Botan::Usage_Type::UNSPECIFIED,
934
                                                               valid_time,
935
                                                               std::chrono::milliseconds(0),
4✔
936
                                                               {ocsp});
8✔
937

938
            return result.confirm(std::string("Status: '") + Botan::to_string(expected) + "' should match '" +
24✔
939
                                     Botan::to_string(path_result.result()) + "'",
8✔
940
                                  path_result.result() == expected);
8✔
941
         };
12✔
942

943
         check_path(Botan::calendar_point(2016, 11, 11, 12, 30, 0).to_std_timepoint(),
1✔
944
                    Botan::Certificate_Status_Code::OCSP_NOT_YET_VALID);
945
         check_path(Botan::calendar_point(2016, 11, 18, 12, 30, 0).to_std_timepoint(),
1✔
946
                    Botan::Certificate_Status_Code::OK);
947
         check_path(Botan::calendar_point(2016, 11, 20, 8, 30, 0).to_std_timepoint(),
1✔
948
                    Botan::Certificate_Status_Code::OK);
949
         check_path(Botan::calendar_point(2016, 11, 28, 8, 30, 0).to_std_timepoint(),
1✔
950
                    Botan::Certificate_Status_Code::OCSP_HAS_EXPIRED);
951

952
         result.end_timer();
1✔
953
         return result;
2✔
954
      }
2✔
955

956
      static Test::Result validate_with_ocsp_without_next_update_without_max_age() {
1✔
957
         Test::Result result("path check with ocsp w/o next_update w/o max_age");
1✔
958
         result.start_timer();
1✔
959

960
         Botan::Certificate_Store_In_Memory trusted;
1✔
961

962
         auto restrictions = Botan::Path_Validation_Restrictions(false, 110, false);
2✔
963

964
         auto ee = load_test_X509_cert("x509/ocsp/patrickschmidt.pem");
1✔
965
         auto ca = load_test_X509_cert("x509/ocsp/bdrive_encryption.pem");
1✔
966
         auto trust_root = load_test_X509_cert("x509/ocsp/bdrive_root.pem");
1✔
967

968
         trusted.add_certificate(trust_root);
1✔
969

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

972
         auto ocsp = load_test_OCSP_resp("x509/ocsp/patrickschmidt_ocsp.der");
1✔
973

974
         auto check_path = [&](const std::chrono::system_clock::time_point valid_time,
4✔
975
                               const Botan::Certificate_Status_Code expected) {
976
            const auto path_result = Botan::x509_path_validate(cert_path,
6✔
977
                                                               restrictions,
978
                                                               trusted,
3✔
979
                                                               "",
980
                                                               Botan::Usage_Type::UNSPECIFIED,
981
                                                               valid_time,
982
                                                               std::chrono::milliseconds(0),
3✔
983
                                                               {ocsp});
6✔
984

985
            return result.confirm(std::string("Status: '") + Botan::to_string(expected) + "' should match '" +
18✔
986
                                     Botan::to_string(path_result.result()) + "'",
6✔
987
                                  path_result.result() == expected);
6✔
988
         };
9✔
989

990
         check_path(Botan::calendar_point(2019, 5, 28, 7, 0, 0).to_std_timepoint(),
1✔
991
                    Botan::Certificate_Status_Code::OCSP_NOT_YET_VALID);
992
         check_path(Botan::calendar_point(2019, 5, 28, 7, 30, 0).to_std_timepoint(),
1✔
993
                    Botan::Certificate_Status_Code::OK);
994
         check_path(Botan::calendar_point(2019, 5, 28, 8, 0, 0).to_std_timepoint(), Botan::Certificate_Status_Code::OK);
1✔
995

996
         result.end_timer();
1✔
997
         return result;
2✔
998
      }
2✔
999

1000
      static Test::Result validate_with_ocsp_without_next_update_with_max_age() {
1✔
1001
         Test::Result result("path check with ocsp w/o next_update with max_age");
1✔
1002
         result.start_timer();
1✔
1003

1004
         Botan::Certificate_Store_In_Memory trusted;
1✔
1005

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

1008
         auto ee = load_test_X509_cert("x509/ocsp/patrickschmidt.pem");
1✔
1009
         auto ca = load_test_X509_cert("x509/ocsp/bdrive_encryption.pem");
1✔
1010
         auto trust_root = load_test_X509_cert("x509/ocsp/bdrive_root.pem");
1✔
1011

1012
         trusted.add_certificate(trust_root);
1✔
1013

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

1016
         auto ocsp = load_test_OCSP_resp("x509/ocsp/patrickschmidt_ocsp.der");
1✔
1017

1018
         auto check_path = [&](const std::chrono::system_clock::time_point valid_time,
4✔
1019
                               const Botan::Certificate_Status_Code expected) {
1020
            const auto path_result = Botan::x509_path_validate(cert_path,
6✔
1021
                                                               restrictions,
1022
                                                               trusted,
3✔
1023
                                                               "",
1024
                                                               Botan::Usage_Type::UNSPECIFIED,
1025
                                                               valid_time,
1026
                                                               std::chrono::milliseconds(0),
3✔
1027
                                                               {ocsp});
6✔
1028

1029
            return result.confirm(std::string("Status: '") + Botan::to_string(expected) + "' should match '" +
18✔
1030
                                     Botan::to_string(path_result.result()) + "'",
6✔
1031
                                  path_result.result() == expected);
6✔
1032
         };
9✔
1033

1034
         check_path(Botan::calendar_point(2019, 5, 28, 7, 0, 0).to_std_timepoint(),
1✔
1035
                    Botan::Certificate_Status_Code::OCSP_NOT_YET_VALID);
1036
         check_path(Botan::calendar_point(2019, 5, 28, 7, 30, 0).to_std_timepoint(),
1✔
1037
                    Botan::Certificate_Status_Code::OK);
1038
         check_path(Botan::calendar_point(2019, 5, 28, 8, 0, 0).to_std_timepoint(),
1✔
1039
                    Botan::Certificate_Status_Code::OCSP_IS_TOO_OLD);
1040

1041
         result.end_timer();
1✔
1042
         return result;
2✔
1043
      }
2✔
1044

1045
      static Test::Result validate_with_ocsp_with_authorized_responder() {
1✔
1046
         Test::Result result("path check with ocsp response from authorized responder certificate");
1✔
1047
         result.start_timer();
1✔
1048

1049
         Botan::Certificate_Store_In_Memory trusted;
1✔
1050

1051
         auto restrictions = Botan::Path_Validation_Restrictions(true,   // require revocation info
1✔
1052
                                                                 110,    // minimum key strength
1053
                                                                 true);  // OCSP for all intermediates
2✔
1054

1055
         auto ee = load_test_X509_cert("x509/ocsp/bdr.pem");
1✔
1056
         auto ca = load_test_X509_cert("x509/ocsp/bdr-int.pem");
1✔
1057
         auto trust_root = load_test_X509_cert("x509/ocsp/bdr-root.pem");
1✔
1058

1059
         // These OCSP responses are signed by an authorized OCSP responder
1060
         // certificate issued by `ca` and `trust_root` respectively. Note that
1061
         // the responder certificates contain the "OCSP No Check" extension,
1062
         // meaning that they themselves do not need a revocation check via OCSP.
1063
         auto ocsp_ee = load_test_OCSP_resp("x509/ocsp/bdr-ocsp-resp.der");
1✔
1064
         auto ocsp_ca = load_test_OCSP_resp("x509/ocsp/bdr-int-ocsp-resp.der");
1✔
1065

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

1069
         auto check_path = [&](const std::chrono::system_clock::time_point valid_time,
4✔
1070
                               const Botan::Certificate_Status_Code expected) {
1071
            const auto path_result = Botan::x509_path_validate(cert_path,
12✔
1072
                                                               restrictions,
1073
                                                               trusted,
3✔
1074
                                                               "",
1075
                                                               Botan::Usage_Type::UNSPECIFIED,
1076
                                                               valid_time,
1077
                                                               std::chrono::milliseconds(0),
3✔
1078
                                                               {ocsp_ee, ocsp_ca});
9✔
1079

1080
            return result.confirm(std::string("Status: '") + Botan::to_string(expected) + "' should match '" +
18✔
1081
                                     Botan::to_string(path_result.result()) + "'",
6✔
1082
                                  path_result.result() == expected);
6✔
1083
         };
12✔
1084

1085
         check_path(Botan::calendar_point(2022, 9, 18, 16, 30, 0).to_std_timepoint(),
1✔
1086
                    Botan::Certificate_Status_Code::OCSP_NOT_YET_VALID);
1087
         check_path(Botan::calendar_point(2022, 9, 19, 16, 30, 0).to_std_timepoint(),
1✔
1088
                    Botan::Certificate_Status_Code::OK);
1089
         check_path(Botan::calendar_point(2022, 9, 20, 16, 30, 0).to_std_timepoint(),
1✔
1090
                    Botan::Certificate_Status_Code::OCSP_HAS_EXPIRED);
1091

1092
         result.end_timer();
1✔
1093
         return result;
1✔
1094
      }
4✔
1095

1096
      static Test::Result validate_with_ocsp_with_authorized_responder_without_keyusage() {
1✔
1097
         Test::Result result(
1✔
1098
            "path check with ocsp response from authorized responder certificate (without sufficient key usage)");
1✔
1099
         result.start_timer();
1✔
1100

1101
         Botan::Certificate_Store_In_Memory trusted;
1✔
1102

1103
         auto restrictions = Botan::Path_Validation_Restrictions(true,    // require revocation info
1✔
1104
                                                                 110,     // minimum key strength
1105
                                                                 false);  // OCSP for all intermediates
2✔
1106

1107
         // See `src/scripts/mychain_creater.sh` if you need to recreate those
1108
         auto ee = load_test_X509_cert("x509/ocsp/mychain_ee.pem");
1✔
1109
         auto ca = load_test_X509_cert("x509/ocsp/mychain_int.pem");
1✔
1110
         auto trust_root = load_test_X509_cert("x509/ocsp/mychain_root.pem");
1✔
1111

1112
         auto ocsp_ee_delegate = load_test_OCSP_resp("x509/ocsp/mychain_ocsp_for_ee_delegate_signed.der").value();
2✔
1113
         auto ocsp_ee_delegate_malformed =
1✔
1114
            load_test_OCSP_resp("x509/ocsp/mychain_ocsp_for_ee_delegate_signed_malformed.der").value();
2✔
1115

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

1119
         auto check_path = [&](const std::chrono::system_clock::time_point valid_time,
4✔
1120
                               const Botan::OCSP::Response& ocsp_ee,
1121
                               const Botan::Certificate_Status_Code expected,
1122
                               const std::optional<Botan::Certificate_Status_Code> also_expected = std::nullopt) {
1123
            const auto path_result = Botan::x509_path_validate(cert_path,
6✔
1124
                                                               restrictions,
1125
                                                               trusted,
3✔
1126
                                                               "",
1127
                                                               Botan::Usage_Type::UNSPECIFIED,
1128
                                                               valid_time,
1129
                                                               std::chrono::milliseconds(0),
3✔
1130
                                                               {ocsp_ee});
6✔
1131

1132
            result.test_is_eq("should result in expected validation status code",
3✔
1133
                              static_cast<uint32_t>(path_result.result()),
3✔
1134
                              static_cast<uint32_t>(expected));
3✔
1135
            if(also_expected) {
3✔
1136
               result.confirm("Secondary error is also present",
4✔
1137
                              flatten(path_result.all_statuses()).contains(also_expected.value()));
6✔
1138
            }
1139
         };
6✔
1140

1141
         check_path(Botan::calendar_point(2022, 9, 22, 23, 30, 0).to_std_timepoint(),
1✔
1142
                    ocsp_ee_delegate,
1143
                    Botan::Certificate_Status_Code::VERIFIED);
1144
         check_path(Botan::calendar_point(2022, 10, 8, 23, 30, 0).to_std_timepoint(),
1✔
1145
                    ocsp_ee_delegate,
1146
                    Botan::Certificate_Status_Code::CERT_HAS_EXPIRED,
1147
                    Botan::Certificate_Status_Code::OCSP_ISSUER_NOT_TRUSTED);
1✔
1148
         check_path(Botan::calendar_point(2022, 9, 22, 23, 30, 0).to_std_timepoint(),
1✔
1149
                    ocsp_ee_delegate_malformed,
1150
                    Botan::Certificate_Status_Code::OCSP_RESPONSE_MISSING_KEYUSAGE,
1151
                    Botan::Certificate_Status_Code::OCSP_ISSUER_NOT_TRUSTED);
1✔
1152

1153
         result.end_timer();
1✔
1154
         return result;
1✔
1155
      }
2✔
1156

1157
      static Test::Result validate_with_forged_ocsp_using_self_signed_cert() {
1✔
1158
         Test::Result result("path check with forged ocsp using self-signed certificate");
1✔
1159
         result.start_timer();
1✔
1160

1161
         Botan::Certificate_Store_In_Memory trusted;
1✔
1162

1163
         auto restrictions = Botan::Path_Validation_Restrictions(true,    // require revocation info
1✔
1164
                                                                 110,     // minimum key strength
1165
                                                                 false);  // OCSP for all intermediates
2✔
1166

1167
         auto ee = load_test_X509_cert("x509/ocsp/randombit.pem");
1✔
1168
         auto ca = load_test_X509_cert("x509/ocsp/letsencrypt.pem");
1✔
1169
         auto trust_root = load_test_X509_cert("x509/ocsp/identrust.pem");
1✔
1170
         trusted.add_certificate(trust_root);
1✔
1171

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

1174
         auto check_path = [&](const std::string& forged_ocsp,
3✔
1175
                               const Botan::Certificate_Status_Code expected,
1176
                               const Botan::Certificate_Status_Code also_expected) {
1177
            auto ocsp = load_test_OCSP_resp(forged_ocsp);
2✔
1178
            const auto path_result =
2✔
1179
               Botan::x509_path_validate(cert_path,
4✔
1180
                                         restrictions,
1181
                                         trusted,
2✔
1182
                                         "",
1183
                                         Botan::Usage_Type::UNSPECIFIED,
1184
                                         Botan::calendar_point(2016, 11, 18, 12, 30, 0).to_std_timepoint(),
2✔
1185
                                         std::chrono::milliseconds(0),
2✔
1186
                                         {ocsp});
4✔
1187

1188
            result.test_is_eq(
2✔
1189
               "Path validation with forged OCSP response should fail with", path_result.result(), expected);
2✔
1190
            result.confirm("Secondary error is also present",
4✔
1191
                           flatten(path_result.all_statuses()).contains(also_expected));
4✔
1192
            result.test_note(std::string("Failed with: ") + Botan::to_string(path_result.result()));
6✔
1193
         };
8✔
1194

1195
         // In both cases the path validation should detect the forged OCSP
1196
         // response and generate an appropriate error. By no means it should
1197
         // follow the unauthentic OCSP response.
1198
         check_path("x509/ocsp/randombit_ocsp_forged_valid.der",
1✔
1199
                    Botan::Certificate_Status_Code::CERT_ISSUER_NOT_FOUND,
1200
                    Botan::Certificate_Status_Code::OCSP_ISSUER_NOT_TRUSTED);
1201
         check_path("x509/ocsp/randombit_ocsp_forged_revoked.der",
1✔
1202
                    Botan::Certificate_Status_Code::CERT_ISSUER_NOT_FOUND,
1203
                    Botan::Certificate_Status_Code::OCSP_ISSUER_NOT_TRUSTED);
1204

1205
         result.end_timer();
1✔
1206
         return result;
1✔
1207
      }
2✔
1208

1209
      static Test::Result validate_with_ocsp_self_signed_by_intermediate_cert() {
1✔
1210
         Test::Result result(
1✔
1211
            "path check with ocsp response for intermediate that is (maliciously) self-signed by the intermediate");
1✔
1212
         result.start_timer();
1✔
1213

1214
         Botan::Certificate_Store_In_Memory trusted;
1✔
1215

1216
         auto restrictions = Botan::Path_Validation_Restrictions(true,   // require revocation info
1✔
1217
                                                                 110,    // minimum key strength
1218
                                                                 true);  // OCSP for all intermediates
2✔
1219

1220
         // See `src/scripts/mychain_creater.sh` if you need to recreate those
1221
         auto ee = load_test_X509_cert("x509/ocsp/mychain_ee.pem");
1✔
1222
         auto ca = load_test_X509_cert("x509/ocsp/mychain_int.pem");
1✔
1223
         auto trust_root = load_test_X509_cert("x509/ocsp/mychain_root.pem");
1✔
1224

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

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

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

1234
         const auto path_result =
1✔
1235
            Botan::x509_path_validate(cert_path,
4✔
1236
                                      restrictions,
1237
                                      trusted,
1238
                                      "",
1239
                                      Botan::Usage_Type::UNSPECIFIED,
1240
                                      Botan::calendar_point(2022, 9, 22, 22, 30, 0).to_std_timepoint(),
1✔
1241
                                      std::chrono::milliseconds(0),
1✔
1242
                                      {ocsp_ee, ocsp_ca});
3✔
1243
         result.confirm("should reject intermediate OCSP response",
2✔
1244
                        path_result.result() == Botan::Certificate_Status_Code::OCSP_ISSUER_NOT_FOUND);
1✔
1245
         result.test_note(std::string("Failed with: ") + Botan::to_string(path_result.result()));
3✔
1246

1247
         result.end_timer();
1✔
1248
         return result;
1✔
1249
      }
7✔
1250

1251
      std::vector<Test::Result> run() override {
1✔
1252
         return {validate_with_ocsp_with_next_update_without_max_age(),
1✔
1253
                 validate_with_ocsp_with_next_update_with_max_age(),
1254
                 validate_with_ocsp_without_next_update_without_max_age(),
1255
                 validate_with_ocsp_without_next_update_with_max_age(),
1256
                 validate_with_ocsp_with_authorized_responder(),
1257
                 validate_with_ocsp_with_authorized_responder_without_keyusage(),
1258
                 validate_with_forged_ocsp_using_self_signed_cert(),
1259
                 validate_with_ocsp_self_signed_by_intermediate_cert()};
9✔
1260
      }
1✔
1261
};
1262

1263
BOTAN_REGISTER_TEST("x509", "x509_path_with_ocsp", Path_Validation_With_OCSP_Tests);
1264

1265
   #endif
1266

1267
   #if defined(BOTAN_HAS_ECDSA)
1268

1269
class CVE_2020_0601_Tests final : public Test {
×
1270
   public:
1271
      std::vector<Test::Result> run() override {
1✔
1272
         Test::Result result("CVE-2020-0601");
1✔
1273
         result.start_timer();
1✔
1274

1275
         auto ca_crt = Botan::X509_Certificate(Test::data_file("x509/cve-2020-0601/ca.pem"));
2✔
1276
         auto fake_ca_crt = Botan::X509_Certificate(Test::data_file("x509/cve-2020-0601/fake_ca.pem"));
2✔
1277
         auto ee_crt = Botan::X509_Certificate(Test::data_file("x509/cve-2020-0601/ee.pem"));
2✔
1278

1279
         Botan::Certificate_Store_In_Memory trusted;
1✔
1280
         trusted.add_certificate(ca_crt);
1✔
1281

1282
         const auto restrictions = Botan::Path_Validation_Restrictions(false, 80, false);
2✔
1283

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

1286
         const auto path_result1 = Botan::x509_path_validate(std::vector<Botan::X509_Certificate>{ee_crt, fake_ca_crt},
5✔
1287
                                                             restrictions,
1288
                                                             trusted,
1289
                                                             "",
1290
                                                             Botan::Usage_Type::UNSPECIFIED,
1291
                                                             valid_time,
1292
                                                             std::chrono::milliseconds(0),
1✔
1293
                                                             {});
3✔
1294

1295
         result.confirm("Validation failed", !path_result1.successful_validation());
2✔
1296

1297
         result.confirm("Expected status",
2✔
1298
                        path_result1.result() == Botan::Certificate_Status_Code::CANNOT_ESTABLISH_TRUST);
1✔
1299

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

1309
         result.confirm("Validation failed", !path_result2.successful_validation());
2✔
1310

1311
         result.confirm("Expected status",
2✔
1312
                        path_result2.result() == Botan::Certificate_Status_Code::CERT_ISSUER_NOT_FOUND);
1✔
1313

1314
         // Verify the signature from the bad CA is actually correct
1315
         Botan::Certificate_Store_In_Memory frusted;
1✔
1316
         frusted.add_certificate(fake_ca_crt);
1✔
1317

1318
         const auto path_result3 = Botan::x509_path_validate(std::vector<Botan::X509_Certificate>{ee_crt},
3✔
1319
                                                             restrictions,
1320
                                                             frusted,
1321
                                                             "",
1322
                                                             Botan::Usage_Type::UNSPECIFIED,
1323
                                                             valid_time,
1324
                                                             std::chrono::milliseconds(0),
1✔
1325
                                                             {});
3✔
1326

1327
         result.confirm("Validation succeeded", path_result3.successful_validation());
2✔
1328

1329
         result.end_timer();
1✔
1330
         return {result};
3✔
1331
      }
5✔
1332
};
1333

1334
BOTAN_REGISTER_TEST("x509", "x509_cve_2020_0601", CVE_2020_0601_Tests);
1335

1336
   #endif
1337

1338
   #if defined(BOTAN_HAS_XMSS_RFC8391)
1339

1340
class XMSS_Path_Validation_Tests final : public Test {
×
1341
   public:
1342
      static Test::Result validate_self_signed(const std::string& name, const std::string& file) {
2✔
1343
         Test::Result result(name);
2✔
1344
         result.start_timer();
2✔
1345

1346
         Botan::Path_Validation_Restrictions restrictions;
4✔
1347
         auto self_signed = Botan::X509_Certificate(Test::data_file("x509/xmss/" + file));
4✔
1348

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

1352
         auto status = Botan::PKIX::overall_status(
2✔
1353
            Botan::PKIX::check_chain(cert_path, valid_time, "", Botan::Usage_Type::UNSPECIFIED, restrictions));
4✔
1354
         result.test_eq("Cert validation status", Botan::to_string(status), "Verified");
2✔
1355
         result.end_timer();
2✔
1356
         return result;
2✔
1357
      }
4✔
1358

1359
      std::vector<Test::Result> run() override {
1✔
1360
         if(Botan::has_filesystem_impl() == false) {
1✔
1361
            return {Test::Result::Note("XMSS path validation", "Skipping due to missing filesystem access")};
×
1362
         }
1363

1364
         return {
1✔
1365
            validate_self_signed("XMSS path validation with certificate created by ISARA corp", "xmss_isara_root.pem"),
1✔
1366
            validate_self_signed("XMSS path validation with certificate created by BouncyCastle",
1✔
1367
                                 "xmss_bouncycastle_sha256_10_root.pem")};
3✔
1368
      }
4✔
1369
};
1370

1371
BOTAN_REGISTER_TEST("x509", "x509_path_xmss", XMSS_Path_Validation_Tests);
1372

1373
   #endif
1374

1375
#endif
1376

1377
}  // namespace
1378

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

© 2025 Coveralls, Inc