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

randombit / botan / 5134090420

31 May 2023 03:12PM UTC coverage: 91.721% (-0.3%) from 91.995%
5134090420

push

github

randombit
Merge GH #3565 Disable noisy/pointless pylint warnings

76048 of 82912 relevant lines covered (91.72%)

11755290.1 hits per line

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

93.25
/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/parsing.h>
20

21
   #include <algorithm>
22
   #include <fstream>
23
   #include <limits>
24
   #include <map>
25
   #include <string>
26
   #include <vector>
27
#endif
28

29
namespace Botan_Tests {
30

31
namespace {
32

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

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

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

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

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

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

60
      m[parts[0]] = parts[1];
400✔
61
   }
400✔
62

63
   return m;
12✔
64
}
6✔
65

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

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

73
   return result;
4✔
74
}
×
75

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

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

83
         std::map<std::string, std::string> expected = read_results(Test::data_file("x509/x509test/expected.txt"));
2✔
84

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

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

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

94
         for(auto i = expected.begin(); i != expected.end(); ++i) {
38✔
95
            Test::Result result("X509test path validation");
37✔
96
            result.start_timer();
37✔
97
            const std::string filename = i->first;
37✔
98
            const std::string expected_result = i->second;
37✔
99

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

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

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

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

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

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

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

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

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

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

158
         return results;
2✔
159
      }
1✔
160

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

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

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

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

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

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

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

190
   /**
191
   * Code to run the X.509v3 processing tests described in "Conformance
192
   *  Testing of Relying Party Client Certificate Path Proccessing Logic",
193
   *  which is available on NIST's web site.
194
   *
195
   * Known Failures/Problems:
196
   *  - Policy extensions are not implemented, so we skip tests #34-#53.
197
   *  - Tests #75 and #76 are skipped as they make use of relatively
198
   *    obscure CRL extensions which are not supported.
199
   */
200
   const std::string nist_test_dir = Test::data_dir() + "/x509/nist";
1✔
201

202
   std::map<std::string, std::string> expected = read_results(Test::data_file("x509/nist/expected.txt"));
2✔
203

204
   const Botan::X509_Certificate root_cert(nist_test_dir + "/root.crt");
1✔
205
   const Botan::X509_CRL root_crl(nist_test_dir + "/root.crl");
1✔
206

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

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

213
      const std::string test_name = i->first;
71✔
214

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

218
         const std::string test_dir = nist_test_dir + "/" + test_name;
71✔
219

220
         const std::vector<std::string> all_files = Botan::get_files_recursive(test_dir);
71✔
221

222
         if(all_files.empty()) {
71✔
223
            result.test_failure("No test files found in " + test_dir);
×
224
            results.push_back(result);
×
225
            continue;
×
226
         }
227

228
         Botan::Certificate_Store_In_Memory store;
71✔
229

230
         store.add_certificate(root_cert);
71✔
231
         store.add_crl(root_crl);
71✔
232

233
         for(const auto& file : all_files) {
361✔
234
            if(file.find(".crt") != std::string::npos && file != "end.crt") {
290✔
235
               store.add_certificate(Botan::X509_Certificate(file));
181✔
236
            } else if(file.find(".crl") != std::string::npos) {
109✔
237
               Botan::DataSource_Stream in(file, true);
109✔
238
               Botan::X509_CRL crl(in);
109✔
239
               store.add_crl(crl);
109✔
240
            }
109✔
241
         }
242

243
         Botan::X509_Certificate end_user(test_dir + "/end.crt");
71✔
244

245
         // 1024 bit root cert
246
         Botan::Path_Validation_Restrictions restrictions(true, 80);
142✔
247

248
         Botan::Path_Validation_Result validation_result = Botan::x509_path_validate(
71✔
249
            end_user, restrictions, store, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
71✔
250

251
         result.test_eq(test_name + " path validation result", validation_result.result_string(), expected_result);
142✔
252
      } catch(std::exception& e) {
173✔
253
         result.test_failure(test_name, e.what());
×
254
      }
×
255

256
      result.end_timer();
71✔
257
      results.push_back(result);
71✔
258
   }
71✔
259

260
   return results;
1✔
261
}
2✔
262

263
BOTAN_REGISTER_TEST("x509", "x509_path_nist", NIST_Path_Validation_Tests);
264

265
class Extended_Path_Validation_Tests final : public Test {
×
266
   public:
267
      std::vector<Test::Result> run() override;
268
};
269

270
std::vector<Test::Result> Extended_Path_Validation_Tests::run() {
1✔
271
   if(Botan::has_filesystem_impl() == false) {
1✔
272
      return {Test::Result::Note("Extended x509 path validation", "Skipping due to missing filesystem access")};
×
273
   }
274

275
   std::vector<Test::Result> results;
1✔
276

277
   const std::string extended_x509_test_dir = Test::data_dir() + "/x509/extended";
1✔
278

279
   std::map<std::string, std::string> expected = read_results(Test::data_file("x509/extended/expected.txt"));
2✔
280

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

283
   for(auto i = expected.begin(); i != expected.end(); ++i) {
4✔
284
      const std::string test_name = i->first;
3✔
285
      const std::string expected_result = i->second;
3✔
286

287
      const std::string test_dir = extended_x509_test_dir + "/" + test_name;
3✔
288

289
      Test::Result result("Extended X509 path validation");
3✔
290
      result.start_timer();
3✔
291

292
      const std::vector<std::string> all_files = Botan::get_files_recursive(test_dir);
3✔
293

294
      if(all_files.empty()) {
3✔
295
         result.test_failure("No test files found in " + test_dir);
×
296
         results.push_back(result);
×
297
         continue;
×
298
      }
299

300
      Botan::Certificate_Store_In_Memory store;
3✔
301

302
      for(const auto& file : all_files) {
13✔
303
         if(file.find(".crt") != std::string::npos && file != "end.crt") {
10✔
304
            store.add_certificate(Botan::X509_Certificate(file));
10✔
305
         }
306
      }
307

308
      Botan::X509_Certificate end_user(test_dir + "/end.crt");
3✔
309

310
      Botan::Path_Validation_Restrictions restrictions;
6✔
311
      Botan::Path_Validation_Result validation_result =
3✔
312
         Botan::x509_path_validate(end_user, restrictions, store, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
3✔
313

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

316
      result.end_timer();
3✔
317
      results.push_back(result);
3✔
318
   }
6✔
319

320
   return results;
1✔
321
}
2✔
322

323
BOTAN_REGISTER_TEST("x509", "x509_path_extended", Extended_Path_Validation_Tests);
324

325
class PSS_Path_Validation_Tests : public Test {
×
326
   public:
327
      std::vector<Test::Result> run() override;
328
};
329

330
std::vector<Test::Result> PSS_Path_Validation_Tests::run() {
1✔
331
   if(Botan::has_filesystem_impl() == false) {
1✔
332
      return {Test::Result::Note("RSA-PSS X509 signature validation", "Skipping due to missing filesystem access")};
×
333
   }
334

335
   std::vector<Test::Result> results;
1✔
336

337
   const std::string pss_x509_test_dir = Test::data_dir() + "/x509/pss_certs";
1✔
338

339
   std::map<std::string, std::string> expected = read_results(Test::data_file("x509/pss_certs/expected.txt"));
2✔
340

341
   std::map<std::string, std::string> validation_times =
1✔
342
      read_results(Test::data_file("x509/pss_certs/validation_times.txt"));
2✔
343

344
   auto validation_times_iter = validation_times.begin();
1✔
345
   for(auto i = expected.begin(); i != expected.end(); ++i) {
119✔
346
      const std::string test_name = i->first;
118✔
347
      const std::string expected_result = i->second;
118✔
348

349
      const std::string test_dir = pss_x509_test_dir + "/" + test_name;
118✔
350

351
      Test::Result result("RSA-PSS X509 signature validation");
118✔
352
      result.start_timer();
118✔
353

354
      const std::vector<std::string> all_files = Botan::get_files_recursive(test_dir);
118✔
355

356
      if(all_files.empty()) {
118✔
357
         result.test_failure("No test files found in " + test_dir);
×
358
         results.push_back(result);
×
359
         continue;
×
360
      }
361

362
      std::optional<Botan::X509_CRL> crl;
118✔
363
      std::optional<Botan::X509_Certificate> end;
118✔
364
      std::optional<Botan::X509_Certificate> root;
118✔
365
      Botan::Certificate_Store_In_Memory store;
118✔
366
      std::optional<Botan::PKCS10_Request> csr;
118✔
367

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

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

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

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

394
         result.test_eq(test_name + " check_crl result",
12✔
395
                        Botan::Path_Validation_Result::status_string(Botan::PKIX::overall_status(crl_status)),
396
                        expected_result);
397
      } else if(end && root)  // CRT chain tests
118✔
398
      {
399
         // sha-1 is used
400
         Botan::Path_Validation_Restrictions restrictions(false, 80);
182✔
401

402
         Botan::Path_Validation_Result validation_result =
91✔
403
            Botan::x509_path_validate(*end, restrictions, store, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
91✔
404

405
         result.test_eq(test_name + " path validation result", validation_result.result_string(), expected_result);
182✔
406
      } else if(end && !root)  // CRT self signed tests
112✔
407
      {
408
         auto pubkey = end->subject_public_key();
16✔
409
         result.test_eq(test_name + " verify signature", end->check_signature(*pubkey), !!(std::stoi(expected_result)));
32✔
410
      } else if(csr)  // PKCS#10 Request
21✔
411
      {
412
         auto pubkey = csr->subject_public_key();
5✔
413
         result.test_eq(test_name + " verify signature", csr->check_signature(*pubkey), !!(std::stoi(expected_result)));
10✔
414
      }
5✔
415

416
      result.end_timer();
118✔
417
      results.push_back(result);
118✔
418
   }
454✔
419

420
   return results;
1✔
421
}
2✔
422

423
BOTAN_REGISTER_TEST("x509", "x509_path_rsa_pss", PSS_Path_Validation_Tests);
424

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

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

435
   std::vector<Test::Result> results;
1✔
436

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

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

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

447
   Botan::Certificate_Store_In_Memory trusted;
1✔
448
   trusted.add_certificate(root);
1✔
449

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

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

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

459
   Botan::Certificate_Store_In_Memory empty;
1✔
460

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

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

466
   result.test_eq("Path validation result", validation_result2.result_string(), "Cannot establish trust");
3✔
467

468
   return {result};
2✔
469
}
4✔
470

471
BOTAN_REGISTER_TEST("x509", "x509_v1_ca", Validate_V1Cert_Test);
472

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

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

483
   std::vector<Test::Result> results;
1✔
484

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

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

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

495
   Botan::Certificate_Store_In_Memory trusted;
1✔
496
   trusted.add_certificate(root);
1✔
497

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

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

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

509
   return {result};
2✔
510
}
4✔
511

512
BOTAN_REGISTER_TEST("x509", "x509_v2uid_in_v1", Validate_V2Uid_in_V1_Test);
513

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

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

524
   std::vector<Test::Result> results;
1✔
525

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

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

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

536
   Botan::Certificate_Store_In_Memory trusted;
1✔
537
   trusted.add_certificate(root);
1✔
538

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

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

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

550
   return {result};
2✔
551
}
4✔
552

553
BOTAN_REGISTER_TEST("x509", "x509_name_constraint_san", Validate_Name_Constraint_SAN_Test);
554

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

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

565
   std::vector<Test::Result> results;
1✔
566

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

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

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

577
   Botan::Certificate_Store_In_Memory trusted;
1✔
578
   trusted.add_certificate(root);
1✔
579

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

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

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

589
   return {result};
2✔
590
}
4✔
591

592
BOTAN_REGISTER_TEST("x509", "x509_name_constraint_ci", Validate_Name_Constraint_CaseInsensitive);
593

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

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

604
   std::vector<Test::Result> results;
1✔
605

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

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

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

616
   Botan::Certificate_Store_In_Memory trusted;
1✔
617
   trusted.add_certificate(root);
1✔
618

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

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

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

628
   return {result};
2✔
629
}
4✔
630

631
BOTAN_REGISTER_TEST("x509", "x509_name_constraint_no_check_self", Validate_Name_Constraint_NoCheckSelf);
632

633
class BSI_Path_Validation_Tests final : public Test
×
634

635
{
636
   public:
637
      std::vector<Test::Result> run() override;
638
};
639

640
std::vector<Test::Result> BSI_Path_Validation_Tests::run() {
1✔
641
   if(Botan::has_filesystem_impl() == false) {
1✔
642
      return {Test::Result::Note("BSI path validation", "Skipping due to missing filesystem access")};
×
643
   }
644

645
   std::vector<Test::Result> results;
1✔
646

647
   const std::string bsi_test_dir = Test::data_dir() + "/x509/bsi";
1✔
648

649
   const std::map<std::string, std::string> expected = read_results(Test::data_file("/x509/bsi/expected.txt"), '$');
2✔
650

651
   for(const auto& i : expected) {
54✔
652
      const std::string test_name = i.first;
53✔
653
      std::string expected_result = i.second;
53✔
654

655
      #if !defined(BOTAN_HAS_MD5)
656
      if(expected_result == "Hash function used is considered too weak for security")
657
         expected_result = "Certificate signed with unknown/unavailable algorithm";
658
      #endif
659

660
      const std::string test_dir = bsi_test_dir + "/" + test_name;
53✔
661

662
      Test::Result result("BSI path validation");
53✔
663
      result.start_timer();
53✔
664

665
      const std::vector<std::string> all_files = Botan::get_files_recursive(test_dir);
53✔
666

667
      if(all_files.empty()) {
53✔
668
         result.test_failure("No test files found in " + test_dir);
×
669
         results.push_back(result);
×
670
         continue;
×
671
      }
672

673
      Botan::Certificate_Store_In_Memory trusted;
53✔
674
      std::vector<Botan::X509_Certificate> certs;
53✔
675

676
      auto validation_time = Botan::calendar_point(2017, 8, 19, 12, 0, 0).to_std_timepoint();
53✔
677

678
      // By convention: if CRL is a substring if the directory name,
679
      // we need to check the CRLs
680
      bool use_crl = false;
53✔
681
      if(test_dir.find("CRL") != std::string::npos) {
53✔
682
         use_crl = true;
16✔
683
      }
684

685
      try {
53✔
686
         for(const auto& file : all_files) {
437✔
687
            // found a trust anchor
688
            if(file.find("TA") != std::string::npos) {
388✔
689
               trusted.add_certificate(Botan::X509_Certificate(file));
49✔
690
            }
691
            // found the target certificate. It needs to be at the front of certs
692
            else if(file.find("TC") != std::string::npos) {
339✔
693
               certs.insert(certs.begin(), Botan::X509_Certificate(file));
53✔
694
            }
695
            // found a certificate that might be part of a valid certificate chain to the trust anchor
696
            else if(file.find(".crt") != std::string::npos) {
286✔
697
               certs.push_back(Botan::X509_Certificate(file));
110✔
698
            } else if(file.find(".crl") != std::string::npos) {
231✔
699
               trusted.add_crl(Botan::X509_CRL(file));
28✔
700
            }
701
         }
702

703
         Botan::Path_Validation_Restrictions restrictions(use_crl, 79, use_crl);
98✔
704

705
         /*
706
          * Following the test document, the test are executed 16 times with
707
          * randomly chosen order of the available certificates. However, the target
708
          * certificate needs to stay in front.
709
          * For certain test, the order in which the certificates are given to
710
          * the validation function may be relevant, i.e. if issuer DNs are
711
          * ambiguous.
712
          */
713
         struct random_bit_generator {
49✔
714
               using result_type = size_t;
715

716
               static constexpr result_type min() { return 0; }
717

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

720
               result_type operator()() {
80✔
721
                  size_t s;
80✔
722
                  Test::rng().randomize(reinterpret_cast<uint8_t*>(&s), sizeof(s));
80✔
723
                  return s;
80✔
724
               }
725
         } rbg;
726

727
         for(size_t r = 0; r < 16; r++) {
833✔
728
            std::shuffle(++(certs.begin()), certs.end(), rbg);
784✔
729

730
            Botan::Path_Validation_Result validation_result = Botan::x509_path_validate(
784✔
731
               certs, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
784✔
732

733
            // We expect to be warned
734
            if(expected_result.find("Warning: ") == 0) {
784✔
735
               std::string stripped = expected_result.substr(std::string("Warning: ").size());
32✔
736
               bool found_warning = false;
32✔
737
               for(const auto& warning_set : validation_result.warnings()) {
128✔
738
                  for(const auto& warning : warning_set) {
128✔
739
                     std::string warning_str(Botan::to_string(warning));
32✔
740
                     if(stripped == warning_str) {
32✔
741
                        result.test_eq(test_name + " path validation result", warning_str, stripped);
32✔
742
                        found_warning = true;
32✔
743
                     }
744
                  }
32✔
745
               }
32✔
746
               if(!found_warning) {
32✔
747
                  result.test_failure(test_name, "Did not receive the expected warning: " + stripped);
×
748
               }
749
            } else {
32✔
750
               result.test_eq(
752✔
751
                  test_name + " path validation result", validation_result.result_string(), expected_result);
2,096✔
752
            }
753
         }
784✔
754
      }
49✔
755

756
      /* Some certificates are rejected when executing the X509_Certificate constructor
757
       * by throwing a Decoding_Error exception.
758
       */
759
      catch(const Botan::Exception& e) {
4✔
760
         if(e.error_type() == Botan::ErrorType::DecodingFailure) {
4✔
761
            result.test_eq(test_name + " path validation result", e.what(), expected_result);
12✔
762
         } else {
763
            result.test_failure(test_name, e.what());
×
764
         }
765
      }
4✔
766

767
      result.end_timer();
53✔
768
      results.push_back(result);
53✔
769
   }
152✔
770

771
   return results;
1✔
772
}
2✔
773

774
BOTAN_REGISTER_TEST("x509", "x509_path_bsi", BSI_Path_Validation_Tests);
775

776
class Path_Validation_With_OCSP_Tests final : public Test {
×
777
   public:
778
      static Botan::X509_Certificate load_test_X509_cert(const std::string& path) {
24✔
779
         return Botan::X509_Certificate(Test::data_file(path));
48✔
780
      }
781

782
      static std::optional<Botan::OCSP::Response> load_test_OCSP_resp(const std::string& path) {
12✔
783
         return Botan::OCSP::Response(Test::read_binary_data_file(path));
24✔
784
      }
785

786
      static Test::Result validate_with_ocsp_with_next_update_without_max_age() {
1✔
787
         Test::Result result("path check with ocsp with next_update w/o max_age");
1✔
788
         Botan::Certificate_Store_In_Memory trusted;
1✔
789

790
         auto restrictions = Botan::Path_Validation_Restrictions(false, 110, false);
2✔
791

792
         auto ee = load_test_X509_cert("x509/ocsp/randombit.pem");
1✔
793
         auto ca = load_test_X509_cert("x509/ocsp/letsencrypt.pem");
1✔
794
         auto trust_root = load_test_X509_cert("x509/ocsp/identrust.pem");
1✔
795
         trusted.add_certificate(trust_root);
1✔
796

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

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

801
         auto check_path = [&](const std::chrono::system_clock::time_point valid_time,
5✔
802
                               const Botan::Certificate_Status_Code expected) {
803
            const auto path_result = Botan::x509_path_validate(cert_path,
8✔
804
                                                               restrictions,
4✔
805
                                                               trusted,
4✔
806
                                                               "",
807
                                                               Botan::Usage_Type::UNSPECIFIED,
808
                                                               valid_time,
809
                                                               std::chrono::milliseconds(0),
4✔
810
                                                               {ocsp});
4✔
811

812
            return result.confirm(std::string("Status: '") + Botan::to_string(expected) + "' should match '" +
12✔
813
                                     Botan::to_string(path_result.result()) + "'",
8✔
814
                                  path_result.result() == expected);
8✔
815
         };
4✔
816

817
         check_path(Botan::calendar_point(2016, 11, 11, 12, 30, 0).to_std_timepoint(),
1✔
818
                    Botan::Certificate_Status_Code::OCSP_NOT_YET_VALID);
819
         check_path(Botan::calendar_point(2016, 11, 18, 12, 30, 0).to_std_timepoint(),
1✔
820
                    Botan::Certificate_Status_Code::OK);
821
         check_path(Botan::calendar_point(2016, 11, 20, 8, 30, 0).to_std_timepoint(),
1✔
822
                    Botan::Certificate_Status_Code::OK);
823
         check_path(Botan::calendar_point(2016, 11, 28, 8, 30, 0).to_std_timepoint(),
1✔
824
                    Botan::Certificate_Status_Code::OCSP_HAS_EXPIRED);
825

826
         return result;
2✔
827
      }
1✔
828

829
      static Test::Result validate_with_ocsp_with_next_update_with_max_age() {
1✔
830
         Test::Result result("path check with ocsp with next_update with max_age");
1✔
831
         Botan::Certificate_Store_In_Memory trusted;
1✔
832

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

835
         auto ee = load_test_X509_cert("x509/ocsp/randombit.pem");
1✔
836
         auto ca = load_test_X509_cert("x509/ocsp/letsencrypt.pem");
1✔
837
         auto trust_root = load_test_X509_cert("x509/ocsp/identrust.pem");
1✔
838
         trusted.add_certificate(trust_root);
1✔
839

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

842
         auto ocsp = load_test_OCSP_resp("x509/ocsp/randombit_ocsp.der");
1✔
843

844
         auto check_path = [&](const std::chrono::system_clock::time_point valid_time,
5✔
845
                               const Botan::Certificate_Status_Code expected) {
846
            const auto path_result = Botan::x509_path_validate(cert_path,
8✔
847
                                                               restrictions,
4✔
848
                                                               trusted,
4✔
849
                                                               "",
850
                                                               Botan::Usage_Type::UNSPECIFIED,
851
                                                               valid_time,
852
                                                               std::chrono::milliseconds(0),
4✔
853
                                                               {ocsp});
8✔
854

855
            return result.confirm(std::string("Status: '") + Botan::to_string(expected) + "' should match '" +
12✔
856
                                     Botan::to_string(path_result.result()) + "'",
8✔
857
                                  path_result.result() == expected);
8✔
858
         };
4✔
859

860
         check_path(Botan::calendar_point(2016, 11, 11, 12, 30, 0).to_std_timepoint(),
1✔
861
                    Botan::Certificate_Status_Code::OCSP_NOT_YET_VALID);
862
         check_path(Botan::calendar_point(2016, 11, 18, 12, 30, 0).to_std_timepoint(),
1✔
863
                    Botan::Certificate_Status_Code::OK);
864
         check_path(Botan::calendar_point(2016, 11, 20, 8, 30, 0).to_std_timepoint(),
1✔
865
                    Botan::Certificate_Status_Code::OK);
866
         check_path(Botan::calendar_point(2016, 11, 28, 8, 30, 0).to_std_timepoint(),
1✔
867
                    Botan::Certificate_Status_Code::OCSP_HAS_EXPIRED);
868

869
         return result;
2✔
870
      }
1✔
871

872
      static Test::Result validate_with_ocsp_without_next_update_without_max_age() {
1✔
873
         Test::Result result("path check with ocsp w/o next_update w/o max_age");
1✔
874
         Botan::Certificate_Store_In_Memory trusted;
1✔
875

876
         auto restrictions = Botan::Path_Validation_Restrictions(false, 110, false);
2✔
877

878
         auto ee = load_test_X509_cert("x509/ocsp/patrickschmidt.pem");
1✔
879
         auto ca = load_test_X509_cert("x509/ocsp/bdrive_encryption.pem");
1✔
880
         auto trust_root = load_test_X509_cert("x509/ocsp/bdrive_root.pem");
1✔
881

882
         trusted.add_certificate(trust_root);
1✔
883

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

886
         auto ocsp = load_test_OCSP_resp("x509/ocsp/patrickschmidt_ocsp.der");
1✔
887

888
         auto check_path = [&](const std::chrono::system_clock::time_point valid_time,
4✔
889
                               const Botan::Certificate_Status_Code expected) {
890
            const auto path_result = Botan::x509_path_validate(cert_path,
6✔
891
                                                               restrictions,
3✔
892
                                                               trusted,
3✔
893
                                                               "",
894
                                                               Botan::Usage_Type::UNSPECIFIED,
895
                                                               valid_time,
896
                                                               std::chrono::milliseconds(0),
3✔
897
                                                               {ocsp});
6✔
898

899
            return result.confirm(std::string("Status: '") + Botan::to_string(expected) + "' should match '" +
9✔
900
                                     Botan::to_string(path_result.result()) + "'",
6✔
901
                                  path_result.result() == expected);
6✔
902
         };
3✔
903

904
         check_path(Botan::calendar_point(2019, 5, 28, 7, 0, 0).to_std_timepoint(),
1✔
905
                    Botan::Certificate_Status_Code::OCSP_NOT_YET_VALID);
906
         check_path(Botan::calendar_point(2019, 5, 28, 7, 30, 0).to_std_timepoint(),
1✔
907
                    Botan::Certificate_Status_Code::OK);
908
         check_path(Botan::calendar_point(2019, 5, 28, 8, 0, 0).to_std_timepoint(), Botan::Certificate_Status_Code::OK);
1✔
909

910
         return result;
2✔
911
      }
1✔
912

913
      static Test::Result validate_with_ocsp_without_next_update_with_max_age() {
1✔
914
         Test::Result result("path check with ocsp w/o next_update with max_age");
1✔
915
         Botan::Certificate_Store_In_Memory trusted;
1✔
916

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

919
         auto ee = load_test_X509_cert("x509/ocsp/patrickschmidt.pem");
1✔
920
         auto ca = load_test_X509_cert("x509/ocsp/bdrive_encryption.pem");
1✔
921
         auto trust_root = load_test_X509_cert("x509/ocsp/bdrive_root.pem");
1✔
922

923
         trusted.add_certificate(trust_root);
1✔
924

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

927
         auto ocsp = load_test_OCSP_resp("x509/ocsp/patrickschmidt_ocsp.der");
1✔
928

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

940
            return result.confirm(std::string("Status: '") + Botan::to_string(expected) + "' should match '" +
9✔
941
                                     Botan::to_string(path_result.result()) + "'",
6✔
942
                                  path_result.result() == expected);
6✔
943
         };
3✔
944

945
         check_path(Botan::calendar_point(2019, 5, 28, 7, 0, 0).to_std_timepoint(),
1✔
946
                    Botan::Certificate_Status_Code::OCSP_NOT_YET_VALID);
947
         check_path(Botan::calendar_point(2019, 5, 28, 7, 30, 0).to_std_timepoint(),
1✔
948
                    Botan::Certificate_Status_Code::OK);
949
         check_path(Botan::calendar_point(2019, 5, 28, 8, 0, 0).to_std_timepoint(),
1✔
950
                    Botan::Certificate_Status_Code::OCSP_IS_TOO_OLD);
951

952
         return result;
2✔
953
      }
1✔
954

955
      static Test::Result validate_with_ocsp_with_authorized_responder() {
1✔
956
         Test::Result result("path check with ocsp response from authorized responder certificate");
1✔
957
         Botan::Certificate_Store_In_Memory trusted;
1✔
958

959
         auto restrictions = Botan::Path_Validation_Restrictions(true,   // require revocation info
1✔
960
                                                                 110,    // minimum key strength
961
                                                                 true);  // OCSP for all intermediates
2✔
962

963
         auto ee = load_test_X509_cert("x509/ocsp/bdr.pem");
1✔
964
         auto ca = load_test_X509_cert("x509/ocsp/bdr-int.pem");
1✔
965
         auto trust_root = load_test_X509_cert("x509/ocsp/bdr-root.pem");
1✔
966

967
         // These OCSP responses are signed by an authorized OCSP responder
968
         // certificate issued by `ca` and `trust_root` respectively. Note that
969
         // the responder certificates contain the "OCSP No Check" extension,
970
         // meaning that they themselves do not need a revocation check via OCSP.
971
         auto ocsp_ee = load_test_OCSP_resp("x509/ocsp/bdr-ocsp-resp.der");
1✔
972
         auto ocsp_ca = load_test_OCSP_resp("x509/ocsp/bdr-int-ocsp-resp.der");
1✔
973

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

977
         auto check_path = [&](const std::chrono::system_clock::time_point valid_time,
4✔
978
                               const Botan::Certificate_Status_Code expected) {
979
            const auto path_result = Botan::x509_path_validate(cert_path,
9✔
980
                                                               restrictions,
3✔
981
                                                               trusted,
3✔
982
                                                               "",
983
                                                               Botan::Usage_Type::UNSPECIFIED,
984
                                                               valid_time,
985
                                                               std::chrono::milliseconds(0),
3✔
986
                                                               {ocsp_ee, ocsp_ca});
9✔
987

988
            return result.confirm(std::string("Status: '") + Botan::to_string(expected) + "' should match '" +
9✔
989
                                     Botan::to_string(path_result.result()) + "'",
6✔
990
                                  path_result.result() == expected);
6✔
991
         };
3✔
992

993
         check_path(Botan::calendar_point(2022, 9, 18, 16, 30, 0).to_std_timepoint(),
1✔
994
                    Botan::Certificate_Status_Code::OCSP_NOT_YET_VALID);
995
         check_path(Botan::calendar_point(2022, 9, 19, 16, 30, 0).to_std_timepoint(),
1✔
996
                    Botan::Certificate_Status_Code::OK);
997
         check_path(Botan::calendar_point(2022, 9, 20, 16, 30, 0).to_std_timepoint(),
1✔
998
                    Botan::Certificate_Status_Code::OCSP_HAS_EXPIRED);
999

1000
         return result;
1✔
1001
      }
3✔
1002

1003
      static Test::Result validate_with_ocsp_with_authorized_responder_without_keyusage() {
1✔
1004
         Test::Result result(
1✔
1005
            "path check with ocsp response from authorized responder certificate (without sufficient key usage)");
1✔
1006
         Botan::Certificate_Store_In_Memory trusted;
1✔
1007

1008
         auto restrictions = Botan::Path_Validation_Restrictions(true,    // require revocation info
1✔
1009
                                                                 110,     // minimum key strength
1010
                                                                 false);  // OCSP for all intermediates
2✔
1011

1012
         // See `src/scripts/mychain_creater.sh` if you need to recreate those
1013
         auto ee = load_test_X509_cert("x509/ocsp/mychain_ee.pem");
1✔
1014
         auto ca = load_test_X509_cert("x509/ocsp/mychain_int.pem");
1✔
1015
         auto trust_root = load_test_X509_cert("x509/ocsp/mychain_root.pem");
1✔
1016

1017
         auto ocsp_ee_delegate = load_test_OCSP_resp("x509/ocsp/mychain_ocsp_for_ee_delegate_signed.der").value();
2✔
1018
         auto ocsp_ee_delegate_malformed =
1✔
1019
            load_test_OCSP_resp("x509/ocsp/mychain_ocsp_for_ee_delegate_signed_malformed.der").value();
2✔
1020

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

1024
         auto check_path = [&](const std::chrono::system_clock::time_point valid_time,
4✔
1025
                               const Botan::OCSP::Response& ocsp_ee,
1026
                               const Botan::Certificate_Status_Code expected,
1027
                               const std::optional<Botan::Certificate_Status_Code> also_expected = std::nullopt) {
1028
            const auto path_result = Botan::x509_path_validate(cert_path,
9✔
1029
                                                               restrictions,
3✔
1030
                                                               trusted,
3✔
1031
                                                               "",
1032
                                                               Botan::Usage_Type::UNSPECIFIED,
1033
                                                               valid_time,
1034
                                                               std::chrono::milliseconds(0),
3✔
1035
                                                               {ocsp_ee});
3✔
1036

1037
            result.test_is_eq("should result in expected validation status code", path_result.result(), expected);
3✔
1038
            if(also_expected) {
3✔
1039
               result.confirm("Secondary error is also present",
6✔
1040
                              flatten(path_result.all_statuses()).contains(also_expected.value()));
6✔
1041
            }
1042
         };
3✔
1043

1044
         check_path(Botan::calendar_point(2022, 9, 22, 23, 30, 0).to_std_timepoint(),
1✔
1045
                    ocsp_ee_delegate,
1046
                    Botan::Certificate_Status_Code::VERIFIED);
1047
         check_path(Botan::calendar_point(2022, 10, 8, 23, 30, 0).to_std_timepoint(),
1✔
1048
                    ocsp_ee_delegate,
1049
                    Botan::Certificate_Status_Code::CERT_HAS_EXPIRED,
1050
                    Botan::Certificate_Status_Code::OCSP_ISSUER_NOT_TRUSTED);
1✔
1051
         check_path(Botan::calendar_point(2022, 9, 22, 23, 30, 0).to_std_timepoint(),
1✔
1052
                    ocsp_ee_delegate_malformed,
1053
                    Botan::Certificate_Status_Code::OCSP_RESPONSE_MISSING_KEYUSAGE,
1054
                    Botan::Certificate_Status_Code::OCSP_ISSUER_NOT_TRUSTED);
1✔
1055

1056
         return result;
1✔
1057
      }
1✔
1058

1059
      static Test::Result validate_with_forged_ocsp_using_self_signed_cert() {
1✔
1060
         Test::Result result("path check with forged ocsp using self-signed certificate");
1✔
1061
         Botan::Certificate_Store_In_Memory trusted;
1✔
1062

1063
         auto restrictions = Botan::Path_Validation_Restrictions(true,    // require revocation info
1✔
1064
                                                                 110,     // minimum key strength
1065
                                                                 false);  // OCSP for all intermediates
2✔
1066

1067
         auto ee = load_test_X509_cert("x509/ocsp/randombit.pem");
1✔
1068
         auto ca = load_test_X509_cert("x509/ocsp/letsencrypt.pem");
1✔
1069
         auto trust_root = load_test_X509_cert("x509/ocsp/identrust.pem");
1✔
1070
         trusted.add_certificate(trust_root);
1✔
1071

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

1074
         auto check_path = [&](const std::string& forged_ocsp,
3✔
1075
                               const Botan::Certificate_Status_Code expected,
1076
                               const Botan::Certificate_Status_Code also_expected) {
1077
            auto ocsp = load_test_OCSP_resp(forged_ocsp);
2✔
1078
            const auto path_result =
2✔
1079
               Botan::x509_path_validate(cert_path,
4✔
1080
                                         restrictions,
2✔
1081
                                         trusted,
2✔
1082
                                         "",
1083
                                         Botan::Usage_Type::UNSPECIFIED,
1084
                                         Botan::calendar_point(2016, 11, 18, 12, 30, 0).to_std_timepoint(),
2✔
1085
                                         std::chrono::milliseconds(0),
2✔
1086
                                         {ocsp});
2✔
1087

1088
            result.test_is_eq(
2✔
1089
               "Path validation with forged OCSP response should fail with", path_result.result(), expected);
2✔
1090
            result.confirm("Secondary error is also present",
6✔
1091
                           flatten(path_result.all_statuses()).contains(also_expected));
4✔
1092
            result.test_note(std::string("Failed with: ") + Botan::to_string(path_result.result()));
4✔
1093
         };
4✔
1094

1095
         // In both cases the path validation should detect the forged OCSP
1096
         // response and generate an appropriate error. By no means it should
1097
         // follow the unauthentic OCSP response.
1098
         check_path("x509/ocsp/randombit_ocsp_forged_valid.der",
1✔
1099
                    Botan::Certificate_Status_Code::CERT_ISSUER_NOT_FOUND,
1100
                    Botan::Certificate_Status_Code::OCSP_ISSUER_NOT_TRUSTED);
1101
         check_path("x509/ocsp/randombit_ocsp_forged_revoked.der",
1✔
1102
                    Botan::Certificate_Status_Code::CERT_ISSUER_NOT_FOUND,
1103
                    Botan::Certificate_Status_Code::OCSP_ISSUER_NOT_TRUSTED);
1104

1105
         return result;
1✔
1106
      }
1✔
1107

1108
      static Test::Result validate_with_ocsp_self_signed_by_intermediate_cert() {
1✔
1109
         Test::Result result(
1✔
1110
            "path check with ocsp response for intermediate that is (maliciously) self-signed by the intermediate");
1✔
1111
         Botan::Certificate_Store_In_Memory trusted;
1✔
1112

1113
         auto restrictions = Botan::Path_Validation_Restrictions(true,   // require revocation info
1✔
1114
                                                                 110,    // minimum key strength
1115
                                                                 true);  // OCSP for all intermediates
2✔
1116

1117
         // See `src/scripts/mychain_creater.sh` if you need to recreate those
1118
         auto ee = load_test_X509_cert("x509/ocsp/mychain_ee.pem");
1✔
1119
         auto ca = load_test_X509_cert("x509/ocsp/mychain_int.pem");
1✔
1120
         auto trust_root = load_test_X509_cert("x509/ocsp/mychain_root.pem");
1✔
1121

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

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

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

1131
         const auto path_result =
1✔
1132
            Botan::x509_path_validate(cert_path,
3✔
1133
                                      restrictions,
1134
                                      trusted,
1135
                                      "",
1136
                                      Botan::Usage_Type::UNSPECIFIED,
1137
                                      Botan::calendar_point(2022, 9, 22, 22, 30, 0).to_std_timepoint(),
1✔
1138
                                      std::chrono::milliseconds(0),
1✔
1139
                                      {ocsp_ee, ocsp_ca});
2✔
1140
         result.confirm("should reject intermediate OCSP response",
2✔
1141
                        path_result.result() == Botan::Certificate_Status_Code::OCSP_ISSUER_NOT_FOUND);
1✔
1142
         result.test_note(std::string("Failed with: ") + Botan::to_string(path_result.result()));
2✔
1143

1144
         return result;
1✔
1145
      }
3✔
1146

1147
      std::vector<Test::Result> run() override {
1✔
1148
         return {validate_with_ocsp_with_next_update_without_max_age(),
1✔
1149
                 validate_with_ocsp_with_next_update_with_max_age(),
1150
                 validate_with_ocsp_without_next_update_without_max_age(),
1151
                 validate_with_ocsp_without_next_update_with_max_age(),
1152
                 validate_with_ocsp_with_authorized_responder(),
1153
                 validate_with_ocsp_with_authorized_responder_without_keyusage(),
1154
                 validate_with_forged_ocsp_using_self_signed_cert(),
1155
                 validate_with_ocsp_self_signed_by_intermediate_cert()};
9✔
1156
      }
1157
};
1158

1159
BOTAN_REGISTER_TEST("x509", "x509_path_with_ocsp", Path_Validation_With_OCSP_Tests);
1160

1161
   #endif
1162

1163
   #if defined(BOTAN_HAS_ECDSA)
1164

1165
class CVE_2020_0601_Tests final : public Test {
×
1166
   public:
1167
      std::vector<Test::Result> run() override {
1✔
1168
         Test::Result result("CVE-2020-0601");
1✔
1169
         auto ca_crt = Botan::X509_Certificate(Test::data_file("x509/cve-2020-0601/ca.pem"));
2✔
1170
         auto fake_ca_crt = Botan::X509_Certificate(Test::data_file("x509/cve-2020-0601/fake_ca.pem"));
2✔
1171
         auto ee_crt = Botan::X509_Certificate(Test::data_file("x509/cve-2020-0601/ee.pem"));
2✔
1172

1173
         Botan::Certificate_Store_In_Memory trusted;
1✔
1174
         trusted.add_certificate(ca_crt);
1✔
1175

1176
         const auto restrictions = Botan::Path_Validation_Restrictions(false, 80, false);
2✔
1177

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

1180
         const auto path_result1 = Botan::x509_path_validate(std::vector<Botan::X509_Certificate>{ee_crt, fake_ca_crt},
4✔
1181
                                                             restrictions,
1182
                                                             trusted,
1183
                                                             "",
1184
                                                             Botan::Usage_Type::UNSPECIFIED,
1185
                                                             valid_time,
1186
                                                             std::chrono::milliseconds(0),
1✔
1187
                                                             {});
3✔
1188

1189
         result.confirm("Validation failed", !path_result1.successful_validation());
2✔
1190

1191
         result.confirm("Expected status",
2✔
1192
                        path_result1.result() == Botan::Certificate_Status_Code::CANNOT_ESTABLISH_TRUST);
1✔
1193

1194
         const auto path_result2 = Botan::x509_path_validate(std::vector<Botan::X509_Certificate>{ee_crt},
3✔
1195
                                                             restrictions,
1196
                                                             trusted,
1197
                                                             "",
1198
                                                             Botan::Usage_Type::UNSPECIFIED,
1199
                                                             valid_time,
1200
                                                             std::chrono::milliseconds(0),
1✔
1201
                                                             {});
3✔
1202

1203
         result.confirm("Validation failed", !path_result2.successful_validation());
2✔
1204

1205
         result.confirm("Expected status",
2✔
1206
                        path_result2.result() == Botan::Certificate_Status_Code::CERT_ISSUER_NOT_FOUND);
1✔
1207

1208
         // Verify the signature from the bad CA is actually correct
1209
         Botan::Certificate_Store_In_Memory frusted;
1✔
1210
         frusted.add_certificate(fake_ca_crt);
1✔
1211

1212
         const auto path_result3 = Botan::x509_path_validate(std::vector<Botan::X509_Certificate>{ee_crt},
3✔
1213
                                                             restrictions,
1214
                                                             frusted,
1215
                                                             "",
1216
                                                             Botan::Usage_Type::UNSPECIFIED,
1217
                                                             valid_time,
1218
                                                             std::chrono::milliseconds(0),
1✔
1219
                                                             {});
3✔
1220

1221
         result.confirm("Validation succeeded", path_result3.successful_validation());
2✔
1222

1223
         return {result};
3✔
1224
      }
1✔
1225
};
1226

1227
BOTAN_REGISTER_TEST("x509", "x509_cve_2020_0601", CVE_2020_0601_Tests);
1228

1229
   #endif
1230

1231
   #if defined(BOTAN_HAS_XMSS_RFC8391)
1232

1233
class XMSS_Path_Validation_Tests final : public Test {
×
1234
   public:
1235
      static Test::Result validate_self_signed(const std::string& name, const std::string& file) {
2✔
1236
         Test::Result result(name);
2✔
1237

1238
         Botan::Path_Validation_Restrictions restrictions;
4✔
1239
         auto self_signed = Botan::X509_Certificate(Test::data_dir() + "/x509/xmss/" + file);
4✔
1240

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

1244
         auto status = Botan::PKIX::overall_status(
2✔
1245
            Botan::PKIX::check_chain(cert_path, valid_time, "", Botan::Usage_Type::UNSPECIFIED, restrictions));
4✔
1246
         result.test_eq("Cert validation status", Botan::to_string(status), "Verified");
2✔
1247
         return result;
2✔
1248
      }
2✔
1249

1250
      std::vector<Test::Result> run() override {
1✔
1251
         if(Botan::has_filesystem_impl() == false) {
1✔
1252
            return {Test::Result::Note("XMSS path validation", "Skipping due to missing filesystem access")};
×
1253
         }
1254

1255
         return {
1✔
1256
            validate_self_signed("XMSS path validation with certificate created by ISARA corp", "xmss_isara_root.pem"),
1257
            validate_self_signed("XMSS path validation with certificate created by BouncyCastle",
1258
                                 "xmss_bouncycastle_sha256_10_root.pem")};
7✔
1259
      }
1260
};
1261

1262
BOTAN_REGISTER_TEST("x509", "x509_path_xmss", XMSS_Path_Validation_Tests);
1263

1264
   #endif
1265

1266
#endif
1267

1268
}  // namespace
1269

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