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

randombit / botan / 19586471228

21 Nov 2025 11:36PM UTC coverage: 90.802% (+0.2%) from 90.627%
19586471228

Pull #5167

github

web-flow
Merge dc170f795 into f8eb34002
Pull Request #5167: Changes to reduce unnecessary inclusions

100849 of 111065 relevant lines covered (90.8%)

12790156.99 hits per line

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

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

8
#include "tests.h"
9

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

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

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

35
namespace Botan_Tests {
36

37
namespace {
38

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

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

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

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

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

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

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

69
   return m;
12✔
70
}
6✔
71

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

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

79
   return result;
4✔
80
}
×
81

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

160
         return results;
1✔
161
      }
1✔
162

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

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

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

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

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

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

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

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

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

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

211
   for(const auto& [test_name, expected_result] : expected) {
77✔
212
      Test::Result result("NIST path validation");
76✔
213
      result.start_timer();
76✔
214

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

218
         Botan::Certificate_Store_In_Memory store;
76✔
219

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

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

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

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

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

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

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

250
   return results;
1✔
251
}
1✔
252

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

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

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

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

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

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

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

275
      Botan::Certificate_Store_In_Memory store;
3✔
276

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

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

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

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

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

295
   return results;
1✔
296
}
1✔
297

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

300
      #if defined(BOTAN_HAS_PSS)
301

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

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

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

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

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

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

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

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

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

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

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

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

356
         result.test_eq(test_name + " check_crl result",
12✔
357
                        Botan::Path_Validation_Result::status_string(Botan::PKIX::overall_status(crl_status)),
358
                        expected_result);
359
      } else if(end && root) {
118✔
360
         // CRT chain test
361

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

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

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

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

384
   return results;
1✔
385
}
19✔
386

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

389
      #endif
390

391
class Validate_V1Cert_Test final : public Test {
1✔
392
   public:
393
      std::vector<Test::Result> run() override;
394
};
395

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

401
   std::vector<Test::Result> results;
1✔
402

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

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

409
   Botan::X509_Certificate root(root_crt);
1✔
410
   Botan::X509_Certificate intermediate(int_crt);
1✔
411
   Botan::X509_Certificate ee_cert(ee_crt);
1✔
412

413
   Botan::Certificate_Store_In_Memory trusted;
1✔
414
   trusted.add_certificate(root);
1✔
415

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

418
   Botan::Path_Validation_Restrictions restrictions;
2✔
419
   Botan::Path_Validation_Result validation_result =
1✔
420
      Botan::x509_path_validate(chain, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
1✔
421

422
   Test::Result result("Verifying using v1 certificate");
1✔
423
   result.test_eq("Path validation result", validation_result.result_string(), "Verified");
2✔
424

425
   Botan::Certificate_Store_In_Memory empty;
1✔
426

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

429
   Botan::Path_Validation_Result validation_result2 =
1✔
430
      Botan::x509_path_validate(new_chain, restrictions, empty, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
1✔
431

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

434
   return {result};
2✔
435
}
4✔
436

437
BOTAN_REGISTER_TEST("x509", "x509_v1_ca", Validate_V1Cert_Test);
438

439
class Validate_V2Uid_in_V1_Test final : public Test {
1✔
440
   public:
441
      std::vector<Test::Result> run() override;
442
};
443

444
std::vector<Test::Result> Validate_V2Uid_in_V1_Test::run() {
1✔
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
   Test::Result result("Verifying v1 certificate using v2 uid fields");
1✔
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
   return {result};
2✔
476
}
3✔
477

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

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

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

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

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

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

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

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

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

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

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

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

519
BOTAN_REGISTER_TEST("x509", "x509_name_constraint_san", Validate_Name_Constraint_SAN_Test);
520

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

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

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

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

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

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

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

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

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

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

555
   return {result};
2✔
556
}
3✔
557

558
BOTAN_REGISTER_TEST("x509", "x509_name_constraint_ci", Validate_Name_Constraint_CaseInsensitive);
559

560
class Validate_Name_Constraint_NoCheckSelf final : public Test {
1✔
561
   public:
562
      std::vector<Test::Result> run() override;
563
};
564

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

570
   std::vector<Test::Result> results;
1✔
571

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

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

578
   Botan::X509_Certificate root(root_crt);
1✔
579
   Botan::X509_Certificate intermediate(int_crt);
1✔
580
   Botan::X509_Certificate ee_cert(ee_crt);
1✔
581

582
   Botan::Certificate_Store_In_Memory trusted;
1✔
583
   trusted.add_certificate(root);
1✔
584

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

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

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

594
   return {result};
2✔
595
}
3✔
596

597
BOTAN_REGISTER_TEST("x509", "x509_name_constraint_no_check_self", Validate_Name_Constraint_NoCheckSelf);
598

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

606
         const std::string trusted_root_crt = Test::data_file("x509/misc/root_cert_time_check/root.crt");
1✔
607
         const std::string leaf_crt = Test::data_file("x509/misc/root_cert_time_check/leaf.crt");
1✔
608

609
         const Botan::X509_Certificate trusted_root_cert(trusted_root_crt);
1✔
610
         const Botan::X509_Certificate leaf_cert(leaf_crt);
1✔
611

612
         Botan::Certificate_Store_In_Memory trusted;
1✔
613
         trusted.add_certificate(trusted_root_cert);
1✔
614

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

617
         Test::Result result("Root cert time check");
1✔
618

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

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

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

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

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

695
         return {result};
2✔
696
      }
3✔
697
};
698

699
BOTAN_REGISTER_TEST("x509", "x509_root_cert_time_check", Root_Cert_Time_Check_Test);
700

701
class BSI_Path_Validation_Tests final : public Test
1✔
702

703
{
704
   public:
705
      std::vector<Test::Result> run() override;
706
};
707

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

713
   std::vector<Test::Result> results;
1✔
714

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

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

721
      Botan::Certificate_Store_In_Memory trusted;
54✔
722
      std::vector<Botan::X509_Certificate> certs;
54✔
723

724
      #if defined(BOTAN_HAS_MD5)
725
      const bool has_md5 = true;
54✔
726
      #else
727
      const bool has_md5 = false;
728
      #endif
729

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

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

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

757
         Botan::Path_Validation_Restrictions restrictions(use_crl, 79, use_crl);
100✔
758

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

771
               static constexpr result_type min() { return 0; }
772

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

775
               result_type operator()() {
80✔
776
                  size_t s = 0;
80✔
777
                  m_rng.randomize(reinterpret_cast<uint8_t*>(&s), sizeof(s));
80✔
778
                  return s;
80✔
779
               }
780

781
               explicit random_bit_generator(Botan::RandomNumberGenerator& rng) : m_rng(rng) {}
50✔
782

783
            private:
784
               Botan::RandomNumberGenerator& m_rng;
785
         } rbg(this->rng());
50✔
786

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

790
            Botan::Path_Validation_Result validation_result = Botan::x509_path_validate(
800✔
791
               certs, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
800✔
792

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

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

833
      result.end_timer();
54✔
834
      results.push_back(result);
54✔
835
   }
54✔
836

837
   return results;
1✔
838
}
1✔
839

840
BOTAN_REGISTER_TEST("x509", "x509_path_bsi", BSI_Path_Validation_Tests);
841

842
class Path_Validation_With_OCSP_Tests final : public Test {
1✔
843
   public:
844
      static Botan::X509_Certificate load_test_X509_cert(const std::string& path) {
24✔
845
         return Botan::X509_Certificate(Test::data_file(path));
48✔
846
      }
847

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

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

856
         auto restrictions = Botan::Path_Validation_Restrictions(false, 110, false);
2✔
857

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

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

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

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

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

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

892
         return result;
2✔
893
      }
2✔
894

895
      static Test::Result validate_with_ocsp_with_next_update_with_max_age() {
1✔
896
         Test::Result result("path check with ocsp with next_update with max_age");
1✔
897
         Botan::Certificate_Store_In_Memory trusted;
1✔
898

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

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

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

908
         auto ocsp = load_test_OCSP_resp("x509/ocsp/randombit_ocsp.der");
1✔
909

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

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

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

935
         return result;
2✔
936
      }
2✔
937

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

942
         auto restrictions = Botan::Path_Validation_Restrictions(false, 110, false);
2✔
943

944
         auto ee = load_test_X509_cert("x509/ocsp/patrickschmidt.pem");
1✔
945
         auto ca = load_test_X509_cert("x509/ocsp/bdrive_encryption.pem");
1✔
946
         auto trust_root = load_test_X509_cert("x509/ocsp/bdrive_root.pem");
1✔
947

948
         trusted.add_certificate(trust_root);
1✔
949

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

952
         auto ocsp = load_test_OCSP_resp("x509/ocsp/patrickschmidt_ocsp.der");
1✔
953

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

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

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

976
         return result;
2✔
977
      }
2✔
978

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

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

985
         auto ee = load_test_X509_cert("x509/ocsp/patrickschmidt.pem");
1✔
986
         auto ca = load_test_X509_cert("x509/ocsp/bdrive_encryption.pem");
1✔
987
         auto trust_root = load_test_X509_cert("x509/ocsp/bdrive_root.pem");
1✔
988

989
         trusted.add_certificate(trust_root);
1✔
990

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

993
         auto ocsp = load_test_OCSP_resp("x509/ocsp/patrickschmidt_ocsp.der");
1✔
994

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

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

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

1018
         return result;
2✔
1019
      }
2✔
1020

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

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

1029
         auto ee = load_test_X509_cert("x509/ocsp/bdr.pem");
1✔
1030
         auto ca = load_test_X509_cert("x509/ocsp/bdr-int.pem");
1✔
1031
         auto trust_root = load_test_X509_cert("x509/ocsp/bdr-root.pem");
1✔
1032

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

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

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

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

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

1066
         return result;
1✔
1067
      }
4✔
1068

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

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

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

1083
         auto ocsp_ee_delegate = load_test_OCSP_resp("x509/ocsp/mychain_ocsp_for_ee_delegate_signed.der").value();
2✔
1084
         auto ocsp_ee_delegate_malformed =
1✔
1085
            load_test_OCSP_resp("x509/ocsp/mychain_ocsp_for_ee_delegate_signed_malformed.der").value();
2✔
1086

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

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

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

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

1124
         return result;
1✔
1125
      }
2✔
1126

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

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

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

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

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

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

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

1173
         return result;
1✔
1174
      }
2✔
1175

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

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

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

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

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

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

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

1212
         return result;
1✔
1213
      }
7✔
1214

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

1227
BOTAN_REGISTER_TEST("x509", "x509_path_with_ocsp", Path_Validation_With_OCSP_Tests);
1228

1229
   #endif
1230

1231
   #if defined(BOTAN_HAS_ECDSA)
1232

1233
class CVE_2020_0601_Tests final : public Test {
1✔
1234
   public:
1235
      std::vector<Test::Result> run() override {
1✔
1236
         Test::Result result("CVE-2020-0601");
1✔
1237

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

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

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

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

1265
         Botan::Certificate_Store_In_Memory trusted;
1✔
1266
         trusted.add_certificate(ca_crt);
1✔
1267

1268
         const auto restrictions = Botan::Path_Validation_Restrictions(false, 80, false);
2✔
1269

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

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

1281
         result.confirm("Validation failed", !path_result1.successful_validation());
2✔
1282

1283
         result.confirm("Expected status",
2✔
1284
                        path_result1.result() == Botan::Certificate_Status_Code::CANNOT_ESTABLISH_TRUST);
1✔
1285

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

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

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

1300
         // Verify the signature from the bad CA is actually correct
1301
         Botan::Certificate_Store_In_Memory frusted;
1✔
1302
         frusted.add_certificate(fake_ca_crt);
1✔
1303

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

1313
         result.confirm("Validation succeeded", path_result3.successful_validation());
2✔
1314

1315
         return {result};
2✔
1316
      }
5✔
1317
};
1318

1319
BOTAN_REGISTER_TEST("x509", "x509_cve_2020_0601", CVE_2020_0601_Tests);
1320

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

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

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

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

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

1350
         Botan::Certificate_Store_In_Memory trusted;
1✔
1351
         trusted.add_certificate(root);
1✔
1352
         trusted.add_crl(crl);
1✔
1353

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

1358
         Botan::Path_Validation_Restrictions restrictions(true /* require revocation info */);
2✔
1359

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

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

1375
         return {result};
2✔
1376
      }
2✔
1377
};
1378

1379
BOTAN_REGISTER_TEST("x509", "x509_path_immortal_crl", Path_Validation_With_Immortal_CRL);
1380

1381
   #endif
1382

1383
   #if defined(BOTAN_HAS_XMSS_RFC8391)
1384

1385
class XMSS_Path_Validation_Tests final : public Test {
1✔
1386
   public:
1387
      static Test::Result validate_self_signed(const std::string& name, const std::string& file) {
2✔
1388
         Test::Result result(name);
2✔
1389

1390
         Botan::Path_Validation_Restrictions restrictions;
4✔
1391
         auto self_signed = Botan::X509_Certificate(Test::data_file("x509/xmss/" + file));
4✔
1392

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

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

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

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

1414
BOTAN_REGISTER_TEST("x509", "x509_path_xmss", XMSS_Path_Validation_Tests);
1415

1416
   #endif
1417

1418
#endif
1419

1420
}  // namespace
1421

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