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

randombit / botan / 5178038621

05 Jun 2023 02:34PM UTC coverage: 91.699% (-0.05%) from 91.747%
5178038621

Pull #3579

github

web-flow
Merge 4507756d7 into f89692208
Pull Request #3579: FIX: Missing update of XMSS private key in CLI's X.509 operations

76171 of 83066 relevant lines covered (91.7%)

12221116.37 hits per line

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

80.13
/src/lib/x509/x509path.cpp
1
/*
2
* X.509 Certificate Path Validation
3
* (C) 2010,2011,2012,2014,2016 Jack Lloyd
4
* (C) 2017 Fabian Weissberg, Rohde & Schwarz Cybersecurity
5
*
6
* Botan is released under the Simplified BSD License (see license.txt)
7
*/
8

9
#include <botan/x509path.h>
10

11
#include <botan/ocsp.h>
12
#include <botan/pk_keys.h>
13
#include <botan/x509_ext.h>
14
#include <botan/internal/stl_util.h>
15
#include <algorithm>
16
#include <chrono>
17
#include <set>
18
#include <sstream>
19
#include <string>
20
#include <vector>
21

22
#if defined(BOTAN_HAS_ONLINE_REVOCATION_CHECKS)
23
   #include <botan/internal/http_util.h>
24
   #include <future>
25
#endif
26

27
namespace Botan {
28

29
/*
30
* PKIX path validation
31
*/
32
CertificatePathStatusCodes PKIX::check_chain(const std::vector<X509_Certificate>& cert_path,
1,355✔
33
                                             std::chrono::system_clock::time_point ref_time,
34
                                             std::string_view hostname,
35
                                             Usage_Type usage,
36
                                             const Path_Validation_Restrictions& restrictions) {
37
   if(cert_path.empty()) {
1,355✔
38
      throw Invalid_Argument("PKIX::check_chain cert_path empty");
×
39
   }
40

41
   const bool self_signed_ee_cert = (cert_path.size() == 1);
1,355✔
42

43
   X509_Time validation_time(ref_time);
1,355✔
44

45
   CertificatePathStatusCodes cert_status(cert_path.size());
1,355✔
46

47
   if(!hostname.empty() && !cert_path[0].matches_dns_name(hostname)) {
1,355✔
48
      cert_status[0].insert(Certificate_Status_Code::CERT_NAME_NOMATCH);
14✔
49
   }
50

51
   if(!cert_path[0].allowed_usage(usage)) {
1,355✔
52
      if(usage == Usage_Type::OCSP_RESPONDER) {
3✔
53
         cert_status[0].insert(Certificate_Status_Code::OCSP_RESPONSE_MISSING_KEYUSAGE);
1✔
54
      }
55
      cert_status[0].insert(Certificate_Status_Code::INVALID_USAGE);
3✔
56
   }
57

58
   if(cert_path[0].has_constraints(Key_Constraints::KeyCertSign) && cert_path[0].is_CA_cert() == false) {
1,355✔
59
      /*
60
      "If the keyCertSign bit is asserted, then the cA bit in the
61
      basic constraints extension (Section 4.2.1.9) MUST also be
62
      asserted." - RFC 5280
63

64
      We don't bother doing this check on the rest of the path since they
65
      must have the cA bit asserted or the validation will fail anyway.
66
      */
67
      cert_status[0].insert(Certificate_Status_Code::INVALID_USAGE);
×
68
   }
69

70
   for(size_t i = 0; i != cert_path.size(); ++i) {
5,085✔
71
      std::set<Certificate_Status_Code>& status = cert_status.at(i);
3,730✔
72

73
      const bool at_self_signed_root = (i == cert_path.size() - 1);
3,730✔
74

75
      const X509_Certificate& subject = cert_path[i];
3,730✔
76
      const X509_Certificate& issuer = cert_path[at_self_signed_root ? (i) : (i + 1)];
3,730✔
77

78
      if(at_self_signed_root && (issuer.is_self_signed() == false)) {
3,730✔
79
         status.insert(Certificate_Status_Code::CHAIN_LACKS_TRUST_ROOT);
×
80
      }
81

82
      if(subject.issuer_dn() != issuer.subject_dn()) {
3,730✔
83
         status.insert(Certificate_Status_Code::CHAIN_NAME_MISMATCH);
×
84
      }
85

86
      // Check the serial number
87
      if(subject.is_serial_negative()) {
3,730✔
88
         status.insert(Certificate_Status_Code::CERT_SERIAL_NEGATIVE);
32✔
89
      }
90

91
      // Check the subject's DN components' length
92

93
      for(const auto& dn_pair : subject.subject_dn().dn_info()) {
14,100✔
94
         const size_t dn_ub = X509_DN::lookup_ub(dn_pair.first);
10,370✔
95
         // dn_pair = <OID,str>
96
         if(dn_ub > 0 && dn_pair.second.size() > dn_ub) {
10,370✔
97
            status.insert(Certificate_Status_Code::DN_TOO_LONG);
24✔
98
         }
99
      }
100

101
      // Check all certs for valid time range
102
      if(validation_time < subject.not_before()) {
3,730✔
103
         status.insert(Certificate_Status_Code::CERT_NOT_YET_VALID);
41✔
104
      }
105

106
      if(validation_time > subject.not_after()) {
3,730✔
107
         status.insert(Certificate_Status_Code::CERT_HAS_EXPIRED);
163✔
108
      }
109

110
      // Check issuer constraints
111
      if(!issuer.is_CA_cert() && !self_signed_ee_cert) {
3,730✔
112
         status.insert(Certificate_Status_Code::CA_CERT_NOT_FOR_CERT_ISSUER);
59✔
113
      }
114

115
      auto issuer_key = issuer.subject_public_key();
3,730✔
116

117
      // Check the signature algorithm is known
118
      if(!subject.signature_algorithm().oid().registered_oid()) {
3,730✔
119
         status.insert(Certificate_Status_Code::SIGNATURE_ALGO_UNKNOWN);
16✔
120
      } else {
121
         // only perform the following checks if the signature algorithm is known
122
         if(!issuer_key) {
3,714✔
123
            status.insert(Certificate_Status_Code::CERT_PUBKEY_INVALID);
×
124
         } else {
125
            const auto sig_status = subject.verify_signature(*issuer_key);
3,714✔
126

127
            if(sig_status.first == Certificate_Status_Code::VERIFIED) {
3,714✔
128
               const std::string hash_used_for_signature = sig_status.second;
3,657✔
129
               BOTAN_ASSERT_NOMSG(!hash_used_for_signature.empty());
3,657✔
130
               const auto& trusted_hashes = restrictions.trusted_hashes();
3,657✔
131

132
               // Ignore untrusted hashes on self-signed roots
133
               if(!trusted_hashes.empty() && !at_self_signed_root) {
3,657✔
134
                  if(!trusted_hashes.contains(hash_used_for_signature)) {
2,302✔
135
                     status.insert(Certificate_Status_Code::UNTRUSTED_HASH);
68✔
136
                  }
137
               }
138
            } else {
3,657✔
139
               status.insert(sig_status.first);
57✔
140
            }
141

142
            if(issuer_key->estimated_strength() < restrictions.minimum_key_strength()) {
3,714✔
143
               status.insert(Certificate_Status_Code::SIGNATURE_METHOD_TOO_WEAK);
65✔
144
            }
145
         }
3,714✔
146
      }
147

148
      // Check cert extensions
149

150
      if(subject.x509_version() == 1) {
3,730✔
151
         if(subject.v2_issuer_key_id().empty() == false || subject.v2_subject_key_id().empty() == false) {
20✔
152
            status.insert(Certificate_Status_Code::V2_IDENTIFIERS_IN_V1_CERT);
1✔
153
         }
154
      }
155

156
      const Extensions& extensions = subject.v3_extensions();
3,730✔
157
      const auto& extensions_vec = extensions.extensions();
3,730✔
158
      if(subject.x509_version() < 3 && !extensions_vec.empty()) {
3,730✔
159
         status.insert(Certificate_Status_Code::EXT_IN_V1_V2_CERT);
32✔
160
      }
161
      for(auto& extension : extensions_vec) {
19,607✔
162
         extension.first->validate(subject, issuer, cert_path, cert_status, i);
15,877✔
163
      }
164
      if(extensions_vec.size() != extensions.get_extension_oids().size()) {
3,730✔
165
         status.insert(Certificate_Status_Code::DUPLICATE_CERT_EXTENSION);
16✔
166
      }
167
   }
7,460✔
168

169
   // path len check
170
   size_t max_path_length = cert_path.size();
1,355✔
171
   for(size_t i = cert_path.size() - 1; i > 0; --i) {
3,730✔
172
      std::set<Certificate_Status_Code>& status = cert_status.at(i);
2,375✔
173
      const X509_Certificate& subject = cert_path[i];
2,375✔
174

175
      /*
176
      * If the certificate was not self-issued, verify that max_path_length is
177
      * greater than zero and decrement max_path_length by 1.
178
      */
179
      if(subject.subject_dn() != subject.issuer_dn()) {
2,375✔
180
         if(max_path_length > 0) {
1,005✔
181
            --max_path_length;
954✔
182
         } else {
183
            status.insert(Certificate_Status_Code::CERT_CHAIN_TOO_LONG);
51✔
184
         }
185
      }
186

187
      /*
188
      * If pathLenConstraint is present in the certificate and is less than max_path_length,
189
      * set max_path_length to the value of pathLenConstraint.
190
      */
191
      if(subject.path_limit() != Cert_Extension::NO_CERT_PATH_LIMIT && subject.path_limit() < max_path_length) {
2,375✔
192
         max_path_length = subject.path_limit();
1,184✔
193
      }
194
   }
195

196
   return cert_status;
1,355✔
197
}
1,355✔
198

199
namespace {
200

201
Certificate_Status_Code verify_ocsp_signing_cert(const X509_Certificate& signing_cert,
40✔
202
                                                 const X509_Certificate& ca,
203
                                                 const std::vector<X509_Certificate>& extra_certs,
204
                                                 const std::vector<Certificate_Store*>& certstores,
205
                                                 std::chrono::system_clock::time_point ref_time,
206
                                                 const Path_Validation_Restrictions& restrictions) {
207
   // RFC 6960 4.2.2.2
208
   //    [Applications] MUST reject the response if the certificate
209
   //    required to validate the signature on the response does not
210
   //    meet at least one of the following criteria:
211
   //
212
   //    1. Matches a local configuration of OCSP signing authority
213
   //       for the certificate in question, or
214
   if(restrictions.trusted_ocsp_responders()->certificate_known(signing_cert)) {
40✔
215
      return Certificate_Status_Code::OK;
216
   }
217

218
   // RFC 6960 4.2.2.2
219
   //
220
   //    2. Is the certificate of the CA that issued the certificate
221
   //       in question, or
222
   if(signing_cert == ca) {
40✔
223
      return Certificate_Status_Code::OK;
224
   }
225

226
   // RFC 6960 4.2.2.2
227
   //
228
   //    3. Includes a value of id-kp-OCSPSigning in an extended key
229
   //       usage extension and is issued by the CA that issued the
230
   //       certificate in question as stated above.
231

232
   // TODO: Implement OCSP revocation check of OCSP signer certificate
233
   // Note: This needs special care to prevent endless loops on specifically
234
   //       forged chains of OCSP responses referring to each other.
235
   //
236
   // Currently, we're disabling OCSP-based revocation checks by setting the
237
   // timeout to 0. Additionally, the library's API would not allow an
238
   // application to pass in the required "second order" OCSP responses. I.e.
239
   // "second order" OCSP checks would need to rely on `check_ocsp_online()`
240
   // which is not an option for some applications (e.g. that require a proxy
241
   // for external HTTP requests).
242
   const auto ocsp_timeout = std::chrono::milliseconds::zero();
23✔
243
   const auto relaxed_restrictions =
23✔
244
      Path_Validation_Restrictions(false /* do not enforce revocation data */,
245
                                   restrictions.minimum_key_strength(),
246
                                   false /* OCSP is not available, so don't try for intermediates */,
247
                                   restrictions.trusted_hashes());
23✔
248

249
   const auto validation_result = x509_path_validate(concat(std::vector{signing_cert}, extra_certs),
69✔
250
                                                     relaxed_restrictions,
251
                                                     certstores,
252
                                                     {} /* hostname */,
253
                                                     Botan::Usage_Type::OCSP_RESPONDER,
254
                                                     ref_time,
255
                                                     ocsp_timeout);
69✔
256

257
   return validation_result.result();
23✔
258
}
23✔
259

260
}  // namespace
261

262
CertificatePathStatusCodes PKIX::check_ocsp(const std::vector<X509_Certificate>& cert_path,
45✔
263
                                            const std::vector<std::optional<OCSP::Response>>& ocsp_responses,
264
                                            const std::vector<Certificate_Store*>& certstores,
265
                                            std::chrono::system_clock::time_point ref_time,
266
                                            const Path_Validation_Restrictions& restrictions) {
267
   if(cert_path.empty()) {
45✔
268
      throw Invalid_Argument("PKIX::check_ocsp cert_path empty");
×
269
   }
270

271
   CertificatePathStatusCodes cert_status(cert_path.size() - 1);
45✔
272

273
   for(size_t i = 0; i != cert_path.size() - 1; ++i) {
128✔
274
      std::set<Certificate_Status_Code>& status = cert_status.at(i);
83✔
275

276
      const X509_Certificate& subject = cert_path.at(i);
83✔
277
      const X509_Certificate& ca = cert_path.at(i + 1);
83✔
278

279
      if(i < ocsp_responses.size() && (ocsp_responses.at(i) != std::nullopt) &&
83✔
280
         (ocsp_responses.at(i)->status() == OCSP::Response_Status_Code::Successful)) {
47✔
281
         try {
47✔
282
            const auto& ocsp_response = ocsp_responses.at(i);
47✔
283

284
            if(auto dummy_status = ocsp_response->dummy_status()) {
47✔
285
               // handle softfail conditions
286
               status.insert(dummy_status.value());
53✔
287
            } else if(auto signing_cert =
41✔
288
                         ocsp_response->find_signing_certificate(ca, restrictions.trusted_ocsp_responders());
41✔
289
                      !signing_cert) {
41✔
290
               status.insert(Certificate_Status_Code::OCSP_ISSUER_NOT_FOUND);
1✔
291
            } else if(auto ocsp_signing_cert_status =
40✔
292
                         verify_ocsp_signing_cert(signing_cert.value(),
40✔
293
                                                  ca,
294
                                                  concat(ocsp_response->certificates(), cert_path),
40✔
295
                                                  certstores,
296
                                                  ref_time,
297
                                                  restrictions);
40✔
298
                      ocsp_signing_cert_status > Certificate_Status_Code::FIRST_ERROR_STATUS) {
40✔
299
               status.insert(ocsp_signing_cert_status);
4✔
300
               status.insert(Certificate_Status_Code::OCSP_ISSUER_NOT_TRUSTED);
4✔
301
            } else {
302
               status.insert(ocsp_response->status_for(ca, subject, ref_time, restrictions.max_ocsp_age()));
36✔
303
            }
41✔
304
         } catch(Exception&) {
×
305
            status.insert(Certificate_Status_Code::OCSP_RESPONSE_INVALID);
×
306
         }
×
307
      }
308
   }
309

310
   while(!cert_status.empty() && cert_status.back().empty()) {
79✔
311
      cert_status.pop_back();
117✔
312
   }
313

314
   return cert_status;
45✔
315
}
×
316

317
CertificatePathStatusCodes PKIX::check_crl(const std::vector<X509_Certificate>& cert_path,
1,359✔
318
                                           const std::vector<std::optional<X509_CRL>>& crls,
319
                                           std::chrono::system_clock::time_point ref_time) {
320
   if(cert_path.empty()) {
1,359✔
321
      throw Invalid_Argument("PKIX::check_crl cert_path empty");
×
322
   }
323

324
   CertificatePathStatusCodes cert_status(cert_path.size());
1,359✔
325
   const X509_Time validation_time(ref_time);
1,359✔
326

327
   for(size_t i = 0; i != cert_path.size() - 1; ++i) {
3,740✔
328
      std::set<Certificate_Status_Code>& status = cert_status.at(i);
2,381✔
329

330
      if(i < crls.size() && crls[i].has_value()) {
2,381✔
331
         const X509_Certificate& subject = cert_path.at(i);
738✔
332
         const X509_Certificate& ca = cert_path.at(i + 1);
738✔
333

334
         if(!ca.allowed_usage(Key_Constraints::CrlSign)) {
738✔
335
            status.insert(Certificate_Status_Code::CA_CERT_NOT_FOR_CRL_ISSUER);
2✔
336
         }
337

338
         if(validation_time < crls[i]->this_update()) {
738✔
339
            status.insert(Certificate_Status_Code::CRL_NOT_YET_VALID);
16✔
340
         }
341

342
         if(validation_time > crls[i]->next_update()) {
738✔
343
            status.insert(Certificate_Status_Code::CRL_HAS_EXPIRED);
18✔
344
         }
345

346
         auto ca_key = ca.subject_public_key();
738✔
347
         if(crls[i]->check_signature(*ca_key) == false) {
738✔
348
            status.insert(Certificate_Status_Code::CRL_BAD_SIGNATURE);
18✔
349
         }
350

351
         status.insert(Certificate_Status_Code::VALID_CRL_CHECKED);
738✔
352

353
         if(crls[i]->is_revoked(subject)) {
738✔
354
            status.insert(Certificate_Status_Code::CERT_IS_REVOKED);
148✔
355
         }
356

357
         std::string dp = subject.crl_distribution_point();
738✔
358
         if(!dp.empty()) {
738✔
359
            if(dp != crls[i]->crl_issuing_distribution_point()) {
224✔
360
               status.insert(Certificate_Status_Code::NO_MATCHING_CRLDP);
208✔
361
            }
362
         }
363

364
         for(const auto& extension : crls[i]->extensions().extensions()) {
2,242✔
365
            // XXX this is wrong - the OID might be defined but the extention not full parsed
366
            // for example see #1652
367

368
            // is the extension critical and unknown?
369
            if(extension.second && !extension.first->oid_of().registered_oid()) {
1,504✔
370
               /* NIST Certificate Path Valiadation Testing document: "When an implementation does not recognize a critical extension in the
371
                * crlExtensions field, it shall assume that identified certificates have been revoked and are no longer valid"
372
                */
373
               status.insert(Certificate_Status_Code::CERT_IS_REVOKED);
18✔
374
            }
375
         }
738✔
376
      }
1,476✔
377
   }
378

379
   while(!cert_status.empty() && cert_status.back().empty()) {
3,421✔
380
      cert_status.pop_back();
7,323✔
381
   }
382

383
   return cert_status;
1,359✔
384
}
1,359✔
385

386
CertificatePathStatusCodes PKIX::check_crl(const std::vector<X509_Certificate>& cert_path,
1,353✔
387
                                           const std::vector<Certificate_Store*>& certstores,
388
                                           std::chrono::system_clock::time_point ref_time) {
389
   if(cert_path.empty()) {
1,353✔
390
      throw Invalid_Argument("PKIX::check_crl cert_path empty");
×
391
   }
392

393
   if(certstores.empty()) {
1,353✔
394
      throw Invalid_Argument("PKIX::check_crl certstores empty");
×
395
   }
396

397
   std::vector<std::optional<X509_CRL>> crls(cert_path.size());
1,353✔
398

399
   for(size_t i = 0; i != cert_path.size(); ++i) {
5,081✔
400
      for(auto certstore : certstores) {
6,327✔
401
         crls[i] = certstore->find_crl_for(cert_path[i]);
7,496✔
402
         if(crls[i]) {
3,748✔
403
            break;
404
         }
405
      }
406
   }
407

408
   return PKIX::check_crl(cert_path, crls, ref_time);
2,706✔
409
}
1,353✔
410

411
#if defined(BOTAN_HAS_ONLINE_REVOCATION_CHECKS)
412

413
CertificatePathStatusCodes PKIX::check_ocsp_online(const std::vector<X509_Certificate>& cert_path,
5✔
414
                                                   const std::vector<Certificate_Store*>& trusted_certstores,
415
                                                   std::chrono::system_clock::time_point ref_time,
416
                                                   std::chrono::milliseconds timeout,
417
                                                   const Path_Validation_Restrictions& restrictions) {
418
   if(cert_path.empty()) {
5✔
419
      throw Invalid_Argument("PKIX::check_ocsp_online cert_path empty");
×
420
   }
421

422
   std::vector<std::future<std::optional<OCSP::Response>>> ocsp_response_futures;
5✔
423

424
   size_t to_ocsp = 1;
5✔
425

426
   if(restrictions.ocsp_all_intermediates()) {
5✔
427
      to_ocsp = cert_path.size() - 1;
×
428
   }
429
   if(cert_path.size() == 1) {
5✔
430
      to_ocsp = 0;
×
431
   }
432

433
   for(size_t i = 0; i < to_ocsp; ++i) {
10✔
434
      const X509_Certificate& subject = cert_path.at(i);
5✔
435
      const X509_Certificate& issuer = cert_path.at(i + 1);
5✔
436

437
      if(subject.ocsp_responder().empty()) {
6✔
438
         ocsp_response_futures.emplace_back(std::async(std::launch::deferred, [&]() -> std::optional<OCSP::Response> {
8✔
439
            return OCSP::Response(Certificate_Status_Code::OCSP_NO_REVOCATION_URL);
4✔
440
         }));
441
      } else {
442
         ocsp_response_futures.emplace_back(std::async(std::launch::async, [&]() -> std::optional<OCSP::Response> {
2✔
443
            OCSP::Request req(issuer, BigInt::decode(subject.serial_number()));
1✔
444

445
            HTTP::Response http;
1✔
446
            try {
1✔
447
               http = HTTP::POST_sync(subject.ocsp_responder(),
2✔
448
                                      "application/ocsp-request",
449
                                      req.BER_encode(),
1✔
450
                                      /*redirects*/ 1,
451
                                      timeout);
1✔
452
            } catch(std::exception&) {
1✔
453
               // log e.what() ?
454
            }
1✔
455
            if(http.status_code() != 200) {
1✔
456
               return OCSP::Response(Certificate_Status_Code::OCSP_SERVER_NOT_AVAILABLE);
2✔
457
            }
458
            // Check the MIME type?
459

460
            return OCSP::Response(http.body());
×
461
         }));
2✔
462
      }
463
   }
464

465
   std::vector<std::optional<OCSP::Response>> ocsp_responses;
5✔
466
   ocsp_responses.reserve(ocsp_response_futures.size());
5✔
467

468
   for(auto& ocsp_response_future : ocsp_response_futures) {
10✔
469
      ocsp_responses.push_back(ocsp_response_future.get());
15✔
470
   }
471

472
   return PKIX::check_ocsp(cert_path, ocsp_responses, trusted_certstores, ref_time, restrictions);
10✔
473
}
5✔
474

475
CertificatePathStatusCodes PKIX::check_crl_online(const std::vector<X509_Certificate>& cert_path,
×
476
                                                  const std::vector<Certificate_Store*>& certstores,
477
                                                  Certificate_Store_In_Memory* crl_store,
478
                                                  std::chrono::system_clock::time_point ref_time,
479
                                                  std::chrono::milliseconds timeout) {
480
   if(cert_path.empty()) {
×
481
      throw Invalid_Argument("PKIX::check_crl_online cert_path empty");
×
482
   }
483
   if(certstores.empty()) {
×
484
      throw Invalid_Argument("PKIX::check_crl_online certstores empty");
×
485
   }
486

487
   std::vector<std::future<std::optional<X509_CRL>>> future_crls;
×
488
   std::vector<std::optional<X509_CRL>> crls(cert_path.size());
×
489

490
   for(size_t i = 0; i != cert_path.size(); ++i) {
×
491
      const std::optional<X509_Certificate>& cert = cert_path.at(i);
×
492
      for(auto certstore : certstores) {
×
493
         crls[i] = certstore->find_crl_for(*cert);
×
494
         if(crls[i].has_value()) {
×
495
            break;
496
         }
497
      }
498

499
      // TODO: check if CRL is expired and re-request?
500

501
      // Only request if we don't already have a CRL
502
      if(crls[i]) {
×
503
         /*
504
         We already have a CRL, so just insert this empty one to hold a place in the vector
505
         so that indexes match up
506
         */
507
         future_crls.emplace_back(std::future<std::optional<X509_CRL>>());
×
508
      } else if(cert->crl_distribution_point().empty()) {
×
509
         // Avoid creating a thread for this case
510
         future_crls.emplace_back(std::async(std::launch::deferred, [&]() -> std::optional<X509_CRL> {
×
511
            throw Not_Implemented("No CRL distribution point for this certificate");
×
512
         }));
513
      } else {
514
         future_crls.emplace_back(std::async(std::launch::async, [&]() -> std::optional<X509_CRL> {
×
515
            auto http = HTTP::GET_sync(cert->crl_distribution_point(),
×
516
                                       /*redirects*/ 1,
517
                                       timeout);
×
518

519
            http.throw_unless_ok();
×
520
            // check the mime type?
521
            return X509_CRL(http.body());
×
522
         }));
×
523
      }
524
   }
×
525

526
   for(size_t i = 0; i != future_crls.size(); ++i) {
×
527
      if(future_crls[i].valid()) {
×
528
         try {
×
529
            crls[i] = future_crls[i].get();
×
530
         } catch(std::exception&) {
×
531
            // crls[i] left null
532
            // todo: log exception e.what() ?
533
         }
×
534
      }
535
   }
536

537
   auto crl_status = PKIX::check_crl(cert_path, crls, ref_time);
×
538

539
   if(crl_store) {
×
540
      for(size_t i = 0; i != crl_status.size(); ++i) {
×
541
         if(crl_status[i].contains(Certificate_Status_Code::VALID_CRL_CHECKED)) {
×
542
            // better be non-null, we supposedly validated it
543
            BOTAN_ASSERT_NOMSG(crls[i].has_value());
×
544
            crl_store->add_crl(*crls[i]);
×
545
         }
546
      }
547
   }
548

549
   return crl_status;
×
550
}
×
551

552
#endif
553

554
Certificate_Status_Code PKIX::build_certificate_path(std::vector<X509_Certificate>& cert_path,
×
555
                                                     const std::vector<Certificate_Store*>& trusted_certstores,
556
                                                     const X509_Certificate& end_entity,
557
                                                     const std::vector<X509_Certificate>& end_entity_extra) {
558
   if(end_entity.is_self_signed()) {
×
559
      return Certificate_Status_Code::CANNOT_ESTABLISH_TRUST;
560
   }
561

562
   /*
563
   * This is an inelegant but functional way of preventing path loops
564
   * (where C1 -> C2 -> C3 -> C1). We store a set of all the certificate
565
   * fingerprints in the path. If there is a duplicate, we error out.
566
   * TODO: save fingerprints in result struct? Maybe useful for blacklists, etc.
567
   */
568
   std::set<std::string> certs_seen;
×
569

570
   cert_path.push_back(end_entity);
×
571
   certs_seen.insert(end_entity.fingerprint("SHA-256"));
×
572

573
   Certificate_Store_In_Memory ee_extras;
×
574
   for(const auto& cert : end_entity_extra) {
×
575
      ee_extras.add_certificate(cert);
×
576
   }
577

578
   // iterate until we reach a root or cannot find the issuer
579
   for(;;) {
×
580
      const X509_Certificate& last = cert_path.back();
×
581
      const X509_DN issuer_dn = last.issuer_dn();
×
582
      const std::vector<uint8_t> auth_key_id = last.authority_key_id();
×
583

584
      std::optional<X509_Certificate> issuer;
×
585
      bool trusted_issuer = false;
×
586

587
      for(Certificate_Store* store : trusted_certstores) {
×
588
         issuer = store->find_cert(issuer_dn, auth_key_id);
×
589
         if(issuer) {
×
590
            trusted_issuer = true;
591
            break;
592
         }
593
      }
594

595
      if(!issuer) {
×
596
         // fall back to searching supplemental certs
597
         issuer = ee_extras.find_cert(issuer_dn, auth_key_id);
×
598
      }
599

600
      if(!issuer) {
×
601
         return Certificate_Status_Code::CERT_ISSUER_NOT_FOUND;
602
      }
603

604
      const std::string fprint = issuer->fingerprint("SHA-256");
×
605

606
      if(certs_seen.contains(fprint))  // already seen?
×
607
      {
608
         return Certificate_Status_Code::CERT_CHAIN_LOOP;
609
      }
610

611
      certs_seen.insert(fprint);
×
612
      cert_path.push_back(*issuer);
×
613

614
      if(issuer->is_self_signed()) {
×
615
         if(trusted_issuer) {
×
616
            return Certificate_Status_Code::OK;
617
         } else {
618
            return Certificate_Status_Code::CANNOT_ESTABLISH_TRUST;
×
619
         }
620
      }
621
   }
×
622
}
×
623

624
/**
625
 * utilities for PKIX::build_all_certificate_paths
626
 */
627
namespace {
628
// <certificate, trusted?>
629
using cert_maybe_trusted = std::pair<std::optional<X509_Certificate>, bool>;
630
}  // namespace
631

632
/**
633
 * Build all possible certificate paths from the end certificate to self-signed trusted roots.
634
 *
635
 * All potentially valid paths are put into the cert_paths vector. If no potentially valid paths are found,
636
 * one of the encountered errors is returned arbitrarily.
637
 *
638
 * todo add a path building function that returns detailed information on errors encountered while building
639
 * the potentially numerous path candidates.
640
 *
641
 * Basically, a DFS is performed starting from the end certificate. A stack (vector) serves to control the DFS.
642
 * At the beginning of each iteration, a pair is popped from the stack that contains (1) the next certificate
643
 * to add to the path (2) a bool that indicates if the certificate is part of a trusted certstore. Ideally, we
644
 * follow the unique issuer of the current certificate until a trusted root is reached. However, the issuer DN +
645
 * authority key id need not be unique among the certificates used for building the path. In such a case,
646
 * we consider all the matching issuers by pushing <IssuerCert, trusted?> on the stack for each of them.
647
 *
648
 */
649
Certificate_Status_Code PKIX::build_all_certificate_paths(std::vector<std::vector<X509_Certificate>>& cert_paths_out,
1,534✔
650
                                                          const std::vector<Certificate_Store*>& trusted_certstores,
651
                                                          const std::optional<X509_Certificate>& end_entity,
652
                                                          const std::vector<X509_Certificate>& end_entity_extra) {
653
   if(!cert_paths_out.empty()) {
1,534✔
654
      throw Invalid_Argument("PKIX::build_all_certificate_paths: cert_paths_out must be empty");
×
655
   }
656

657
   if(end_entity->is_self_signed()) {
1,534✔
658
      return Certificate_Status_Code::CANNOT_ESTABLISH_TRUST;
659
   }
660

661
   /*
662
    * Pile up error messages
663
    */
664
   std::vector<Certificate_Status_Code> stats;
1,522✔
665

666
   Certificate_Store_In_Memory ee_extras;
1,522✔
667
   for(const auto& cert : end_entity_extra) {
2,740✔
668
      ee_extras.add_certificate(cert);
1,218✔
669
   }
670

671
   /*
672
   * This is an inelegant but functional way of preventing path loops
673
   * (where C1 -> C2 -> C3 -> C1). We store a set of all the certificate
674
   * fingerprints in the path. If there is a duplicate, we error out.
675
   * TODO: save fingerprints in result struct? Maybe useful for blacklists, etc.
676
   */
677
   std::set<std::string> certs_seen;
1,522✔
678

679
   // new certs are added and removed from the path during the DFS
680
   // it is copied into cert_paths_out when we encounter a trusted root
681
   std::vector<X509_Certificate> path_so_far;
1,522✔
682

683
   // todo can we assume that the end certificate is not trusted?
684
   std::vector<cert_maybe_trusted> stack = {{end_entity, false}};
4,566✔
685

686
   while(!stack.empty()) {
8,383✔
687
      std::optional<X509_Certificate> last = stack.back().first;
6,861✔
688
      // found a deletion marker that guides the DFS, backtracing
689
      if(last == std::nullopt) {
6,861✔
690
         stack.pop_back();
2,571✔
691
         std::string fprint = path_so_far.back().fingerprint("SHA-256");
2,571✔
692
         certs_seen.erase(fprint);
2,571✔
693
         path_so_far.pop_back();
2,571✔
694
      }
2,571✔
695
      // process next cert on the path
696
      else {
697
         const bool trusted = stack.back().second;
4,290✔
698
         stack.pop_back();
4,290✔
699

700
         // certificate already seen?
701
         const std::string fprint = last->fingerprint("SHA-256");
4,290✔
702
         if(certs_seen.count(fprint) == 1) {
4,290✔
703
            stats.push_back(Certificate_Status_Code::CERT_CHAIN_LOOP);
1✔
704
            // the current path ended in a loop
705
            continue;
1✔
706
         }
707

708
         // the current path ends here
709
         if(last->is_self_signed()) {
4,289✔
710
            // found a trust anchor
711
            if(trusted) {
1,553✔
712
               cert_paths_out.push_back(path_so_far);
1,356✔
713
               cert_paths_out.back().push_back(*last);
1,356✔
714

715
               continue;
1,356✔
716
            }
717
            // found an untrustworthy root
718
            else {
719
               stats.push_back(Certificate_Status_Code::CANNOT_ESTABLISH_TRUST);
197✔
720
               continue;
197✔
721
            }
722
         }
723

724
         const X509_DN issuer_dn = last->issuer_dn();
2,736✔
725
         const std::vector<uint8_t> auth_key_id = last->authority_key_id();
2,736✔
726

727
         // search for trusted issuers
728
         std::vector<X509_Certificate> trusted_issuers;
2,736✔
729
         for(Certificate_Store* store : trusted_certstores) {
5,481✔
730
            auto new_issuers = store->find_all_certs(issuer_dn, auth_key_id);
2,745✔
731
            trusted_issuers.insert(trusted_issuers.end(), new_issuers.begin(), new_issuers.end());
2,745✔
732
         }
2,745✔
733

734
         // search the supplemental certs
735
         std::vector<X509_Certificate> misc_issuers = ee_extras.find_all_certs(issuer_dn, auth_key_id);
2,736✔
736

737
         // if we could not find any issuers, the current path ends here
738
         if(trusted_issuers.empty() && misc_issuers.empty()) {
2,736✔
739
            stats.push_back(Certificate_Status_Code::CERT_ISSUER_NOT_FOUND);
165✔
740
            continue;
165✔
741
         }
742

743
         // push the latest certificate onto the path_so_far
744
         path_so_far.push_back(*last);
2,571✔
745
         certs_seen.emplace(fprint);
2,571✔
746

747
         // push a deletion marker on the stack for backtracing later
748
         stack.push_back({std::optional<X509_Certificate>(), false});
2,571✔
749

750
         for(const auto& trusted_cert : trusted_issuers) {
4,176✔
751
            stack.push_back({trusted_cert, true});
3,210✔
752
         }
753

754
         for(const auto& misc : misc_issuers) {
3,734✔
755
            stack.push_back({misc, false});
2,326✔
756
         }
757
      }
9,529✔
758
   }
6,861✔
759

760
   // could not construct any potentially valid path
761
   if(cert_paths_out.empty()) {
1,522✔
762
      if(stats.empty()) {
169✔
763
         throw Internal_Error("X509 path building failed for unknown reasons");
×
764
      } else {
765
         // arbitrarily return the first error
766
         return stats[0];
169✔
767
      }
768
   } else {
769
      return Certificate_Status_Code::OK;
770
   }
771
}
3,056✔
772

773
void PKIX::merge_revocation_status(CertificatePathStatusCodes& chain_status,
1,353✔
774
                                   const CertificatePathStatusCodes& crl,
775
                                   const CertificatePathStatusCodes& ocsp,
776
                                   const Path_Validation_Restrictions& restrictions) {
777
   if(chain_status.empty()) {
1,353✔
778
      throw Invalid_Argument("PKIX::merge_revocation_status chain_status was empty");
×
779
   }
780

781
   for(size_t i = 0; i != chain_status.size() - 1; ++i) {
3,728✔
782
      bool had_crl = false, had_ocsp = false;
2,375✔
783

784
      if(i < crl.size() && !crl[i].empty()) {
2,375✔
785
         for(auto&& code : crl[i]) {
1,889✔
786
            if(code == Certificate_Status_Code::VALID_CRL_CHECKED) {
1,157✔
787
               had_crl = true;
732✔
788
            }
789
            chain_status[i].insert(code);
1,157✔
790
         }
791
      }
792

793
      if(i < ocsp.size() && !ocsp[i].empty()) {
2,375✔
794
         for(auto&& code : ocsp[i]) {
66✔
795
            if(code == Certificate_Status_Code::OCSP_RESPONSE_GOOD ||
35✔
796
               code == Certificate_Status_Code::OCSP_NO_REVOCATION_URL ||   // softfail
20✔
797
               code == Certificate_Status_Code::OCSP_SERVER_NOT_AVAILABLE)  // softfail
798
            {
799
               had_ocsp = true;
15✔
800
            }
801

802
            chain_status[i].insert(code);
35✔
803
         }
804
      }
805

806
      if(had_crl == false && had_ocsp == false) {
2,375✔
807
         if((restrictions.require_revocation_information() && i == 0) ||
1,628✔
808
            (restrictions.ocsp_all_intermediates() && i > 0)) {
1,587✔
809
            chain_status[i].insert(Certificate_Status_Code::NO_REVOCATION_DATA);
76✔
810
         }
811
      }
812
   }
813
}
1,353✔
814

815
Certificate_Status_Code PKIX::overall_status(const CertificatePathStatusCodes& cert_status) {
1,361✔
816
   if(cert_status.empty()) {
1,361✔
817
      throw Invalid_Argument("PKIX::overall_status empty cert status");
×
818
   }
819

820
   Certificate_Status_Code overall_status = Certificate_Status_Code::OK;
821

822
   // take the "worst" error as overall
823
   for(const std::set<Certificate_Status_Code>& s : cert_status) {
5,097✔
824
      if(!s.empty()) {
3,736✔
825
         auto worst = *s.rbegin();
1,495✔
826
         // Leave informative OCSP/CRL confirmations on cert-level status only
827
         if(worst >= Certificate_Status_Code::FIRST_ERROR_STATUS && worst > overall_status) {
1,495✔
828
            overall_status = worst;
879✔
829
         }
830
      }
831
   }
832
   return overall_status;
1,361✔
833
}
834

835
Path_Validation_Result x509_path_validate(const std::vector<X509_Certificate>& end_certs,
1,534✔
836
                                          const Path_Validation_Restrictions& restrictions,
837
                                          const std::vector<Certificate_Store*>& trusted_roots,
838
                                          std::string_view hostname,
839
                                          Usage_Type usage,
840
                                          std::chrono::system_clock::time_point ref_time,
841
                                          std::chrono::milliseconds ocsp_timeout,
842
                                          const std::vector<std::optional<OCSP::Response>>& ocsp_resp) {
843
   if(end_certs.empty()) {
1,534✔
844
      throw Invalid_Argument("x509_path_validate called with no subjects");
×
845
   }
846

847
   X509_Certificate end_entity = end_certs[0];
1,534✔
848
   std::vector<X509_Certificate> end_entity_extra;
1,534✔
849
   for(size_t i = 1; i < end_certs.size(); ++i) {
2,752✔
850
      end_entity_extra.push_back(end_certs[i]);
1,218✔
851
   }
852

853
   std::vector<std::vector<X509_Certificate>> cert_paths;
1,534✔
854
   Certificate_Status_Code path_building_result =
1,534✔
855
      PKIX::build_all_certificate_paths(cert_paths, trusted_roots, end_entity, end_entity_extra);
1,534✔
856

857
   // If we cannot successfully build a chain to a trusted self-signed root, stop now
858
   if(path_building_result != Certificate_Status_Code::OK) {
1,534✔
859
      return Path_Validation_Result(path_building_result);
181✔
860
   }
861

862
   std::vector<Path_Validation_Result> error_results;
1,353✔
863
   // Try validating all the potentially valid paths and return the first one to validate properly
864
   for(auto cert_path : cert_paths) {
2,205✔
865
      CertificatePathStatusCodes status = PKIX::check_chain(cert_path, ref_time, hostname, usage, restrictions);
1,353✔
866

867
      CertificatePathStatusCodes crl_status = PKIX::check_crl(cert_path, trusted_roots, ref_time);
1,353✔
868

869
      CertificatePathStatusCodes ocsp_status;
1,353✔
870

871
      if(!ocsp_resp.empty()) {
1,353✔
872
         ocsp_status = PKIX::check_ocsp(cert_path, ocsp_resp, trusted_roots, ref_time, restrictions);
25✔
873
      }
874

875
      if(ocsp_status.empty() && ocsp_timeout != std::chrono::milliseconds(0)) {
1,353✔
876
#if defined(BOTAN_TARGET_OS_HAS_THREADS) && defined(BOTAN_HAS_HTTP_UTIL)
877
         ocsp_status = PKIX::check_ocsp_online(cert_path, trusted_roots, ref_time, ocsp_timeout, restrictions);
4✔
878
#else
879
         ocsp_status.resize(1);
880
         ocsp_status[0].insert(Certificate_Status_Code::OCSP_NO_HTTP);
881
#endif
882
      }
883

884
      PKIX::merge_revocation_status(status, crl_status, ocsp_status, restrictions);
1,353✔
885

886
      Path_Validation_Result pvd(status, std::move(cert_path));
1,353✔
887
      if(pvd.successful_validation()) {
1,353✔
888
         return pvd;
501✔
889
      } else {
890
         error_results.push_back(std::move(pvd));
852✔
891
      }
892
   }
1,353✔
893
   return error_results[0];
852✔
894
}
2,864✔
895

896
Path_Validation_Result x509_path_validate(const X509_Certificate& end_cert,
276✔
897
                                          const Path_Validation_Restrictions& restrictions,
898
                                          const std::vector<Certificate_Store*>& trusted_roots,
899
                                          std::string_view hostname,
900
                                          Usage_Type usage,
901
                                          std::chrono::system_clock::time_point when,
902
                                          std::chrono::milliseconds ocsp_timeout,
903
                                          const std::vector<std::optional<OCSP::Response>>& ocsp_resp) {
904
   std::vector<X509_Certificate> certs;
276✔
905
   certs.push_back(end_cert);
276✔
906
   return x509_path_validate(certs, restrictions, trusted_roots, hostname, usage, when, ocsp_timeout, ocsp_resp);
552✔
907
}
276✔
908

909
Path_Validation_Result x509_path_validate(const std::vector<X509_Certificate>& end_certs,
854✔
910
                                          const Path_Validation_Restrictions& restrictions,
911
                                          const Certificate_Store& store,
912
                                          std::string_view hostname,
913
                                          Usage_Type usage,
914
                                          std::chrono::system_clock::time_point when,
915
                                          std::chrono::milliseconds ocsp_timeout,
916
                                          const std::vector<std::optional<OCSP::Response>>& ocsp_resp) {
917
   std::vector<Certificate_Store*> trusted_roots;
854✔
918
   trusted_roots.push_back(const_cast<Certificate_Store*>(&store));
854✔
919

920
   return x509_path_validate(end_certs, restrictions, trusted_roots, hostname, usage, when, ocsp_timeout, ocsp_resp);
854✔
921
}
854✔
922

923
Path_Validation_Result x509_path_validate(const X509_Certificate& end_cert,
263✔
924
                                          const Path_Validation_Restrictions& restrictions,
925
                                          const Certificate_Store& store,
926
                                          std::string_view hostname,
927
                                          Usage_Type usage,
928
                                          std::chrono::system_clock::time_point when,
929
                                          std::chrono::milliseconds ocsp_timeout,
930
                                          const std::vector<std::optional<OCSP::Response>>& ocsp_resp) {
931
   std::vector<X509_Certificate> certs;
263✔
932
   certs.push_back(end_cert);
263✔
933

934
   std::vector<Certificate_Store*> trusted_roots;
263✔
935
   trusted_roots.push_back(const_cast<Certificate_Store*>(&store));
263✔
936

937
   return x509_path_validate(certs, restrictions, trusted_roots, hostname, usage, when, ocsp_timeout, ocsp_resp);
526✔
938
}
263✔
939

940
Path_Validation_Restrictions::Path_Validation_Restrictions(bool require_rev,
670✔
941
                                                           size_t key_strength,
942
                                                           bool ocsp_intermediates,
943
                                                           std::chrono::seconds max_ocsp_age,
944
                                                           std::unique_ptr<Certificate_Store> trusted_ocsp_responders) :
670✔
945
      m_require_revocation_information(require_rev),
670✔
946
      m_ocsp_all_intermediates(ocsp_intermediates),
670✔
947
      m_minimum_key_strength(key_strength),
670✔
948
      m_max_ocsp_age(max_ocsp_age),
670✔
949
      m_trusted_ocsp_responders(std::move(trusted_ocsp_responders)) {
670✔
950
   if(key_strength <= 80) {
670✔
951
      m_trusted_hashes.insert("SHA-1");
678✔
952
   }
953

954
   m_trusted_hashes.insert("SHA-224");
1,340✔
955
   m_trusted_hashes.insert("SHA-256");
1,340✔
956
   m_trusted_hashes.insert("SHA-384");
1,340✔
957
   m_trusted_hashes.insert("SHA-512");
1,340✔
958
   m_trusted_hashes.insert("SHAKE-256(512)");
1,340✔
959
}
670✔
960

961
namespace {
962
CertificatePathStatusCodes find_warnings(const CertificatePathStatusCodes& all_statuses) {
1,353✔
963
   CertificatePathStatusCodes warnings;
1,353✔
964
   for(const auto& status_set_i : all_statuses) {
5,081✔
965
      std::set<Certificate_Status_Code> warning_set_i;
3,728✔
966
      for(const auto& code : status_set_i) {
5,795✔
967
         if(code >= Certificate_Status_Code::FIRST_WARNING_STATUS &&
2,067✔
968
            code < Certificate_Status_Code::FIRST_ERROR_STATUS) {
969
            warning_set_i.insert(code);
2,127✔
970
         }
971
      }
972
      warnings.push_back(warning_set_i);
3,728✔
973
   }
3,728✔
974
   return warnings;
1,353✔
975
}
×
976
}  // namespace
977

978
Path_Validation_Result::Path_Validation_Result(CertificatePathStatusCodes status,
1,353✔
979
                                               std::vector<X509_Certificate>&& cert_chain) :
1,353✔
980
      m_all_status(std::move(status)),
1,353✔
981
      m_warnings(find_warnings(m_all_status)),
1,353✔
982
      m_cert_path(cert_chain),
1,353✔
983
      m_overall(PKIX::overall_status(m_all_status)) {}
1,353✔
984

985
const X509_Certificate& Path_Validation_Result::trust_root() const {
10✔
986
   if(m_cert_path.empty()) {
10✔
987
      throw Invalid_State("Path_Validation_Result::trust_root no path set");
×
988
   }
989
   if(result() != Certificate_Status_Code::VERIFIED) {
10✔
990
      throw Invalid_State("Path_Validation_Result::trust_root meaningless with invalid status");
×
991
   }
992

993
   return m_cert_path[m_cert_path.size() - 1];
10✔
994
}
995

996
bool Path_Validation_Result::successful_validation() const {
1,570✔
997
   return (result() == Certificate_Status_Code::VERIFIED || result() == Certificate_Status_Code::OCSP_RESPONSE_GOOD ||
1,570✔
998
           result() == Certificate_Status_Code::VALID_CRL_CHECKED);
904✔
999
}
1000

1001
bool Path_Validation_Result::no_warnings() const {
38✔
1002
   for(const auto& status_set_i : m_warnings) {
151✔
1003
      if(!status_set_i.empty()) {
114✔
1004
         return false;
38✔
1005
      }
1006
   }
1007
   return true;
1008
}
1009

1010
CertificatePathStatusCodes Path_Validation_Result::warnings() const { return m_warnings; }
32✔
1011

1012
std::string Path_Validation_Result::result_string() const { return status_string(result()); }
1,016✔
1013

1014
const char* Path_Validation_Result::status_string(Certificate_Status_Code code) {
1,050✔
1015
   if(const char* s = to_string(code)) {
1,050✔
1016
      return s;
1,050✔
1017
   }
1018

1019
   return "Unknown error";
1020
}
1021

1022
std::string Path_Validation_Result::warnings_string() const {
38✔
1023
   const std::string sep(", ");
38✔
1024
   std::ostringstream oss;
38✔
1025
   for(size_t i = 0; i < m_warnings.size(); i++) {
153✔
1026
      for(auto code : m_warnings[i]) {
116✔
1027
         oss << "[" << std::to_string(i) << "] " << status_string(code) << sep;
2✔
1028
      }
1029
   }
1030

1031
   std::string res = oss.str();
38✔
1032
   // remove last sep
1033
   if(res.size() >= sep.size()) {
38✔
1034
      res = res.substr(0, res.size() - sep.size());
2✔
1035
   }
1036
   return res;
76✔
1037
}
38✔
1038
}  // namespace Botan
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