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

randombit / botan / 6657874305

26 Oct 2023 05:49PM UTC coverage: 91.694% (+0.07%) from 91.627%
6657874305

push

github

web-flow
Merge pull request #3765 from randombit/fix/asio_compat

Introduce <botan/boost_compat.h> as feature flag

80176 of 87439 relevant lines covered (91.69%)

8602162.63 hits per line

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

79.78
/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
#if defined(BOTAN_HAS_ECC_KEY)
28
   #include <botan/ecc_key.h>
29
#endif
30

31
namespace Botan {
32

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

45
   const bool self_signed_ee_cert = (cert_path.size() == 1);
1,355✔
46

47
   X509_Time validation_time(ref_time);
1,355✔
48

49
   CertificatePathStatusCodes cert_status(cert_path.size());
1,355✔
50

51
   if(!hostname.empty() && !cert_path[0].matches_dns_name(hostname)) {
1,355✔
52
      cert_status[0].insert(Certificate_Status_Code::CERT_NAME_NOMATCH);
14✔
53
   }
54

55
   if(!cert_path[0].allowed_usage(usage)) {
1,355✔
56
      if(usage == Usage_Type::OCSP_RESPONDER) {
3✔
57
         cert_status[0].insert(Certificate_Status_Code::OCSP_RESPONSE_MISSING_KEYUSAGE);
1✔
58
      }
59
      cert_status[0].insert(Certificate_Status_Code::INVALID_USAGE);
3✔
60
   }
61

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

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

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

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

79
      const X509_Certificate& subject = cert_path[i];
3,730✔
80
      const X509_Certificate& issuer = cert_path[at_self_signed_root ? (i) : (i + 1)];
3,730✔
81

82
      if(at_self_signed_root && (issuer.is_self_signed() == false)) {
3,730✔
83
         status.insert(Certificate_Status_Code::CHAIN_LACKS_TRUST_ROOT);
×
84
      }
85

86
      if(subject.issuer_dn() != issuer.subject_dn()) {
3,730✔
87
         status.insert(Certificate_Status_Code::CHAIN_NAME_MISMATCH);
×
88
      }
89

90
      // Check the serial number
91
      if(subject.is_serial_negative()) {
3,730✔
92
         status.insert(Certificate_Status_Code::CERT_SERIAL_NEGATIVE);
32✔
93
      }
94

95
      // Check the subject's DN components' length
96

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

105
      // Check all certs for valid time range
106
      if(validation_time < subject.not_before()) {
3,730✔
107
         status.insert(Certificate_Status_Code::CERT_NOT_YET_VALID);
41✔
108
      }
109

110
      if(validation_time > subject.not_after()) {
3,730✔
111
         status.insert(Certificate_Status_Code::CERT_HAS_EXPIRED);
169✔
112
      }
113

114
      // Check issuer constraints
115
      if(!issuer.is_CA_cert() && !self_signed_ee_cert) {
3,730✔
116
         status.insert(Certificate_Status_Code::CA_CERT_NOT_FOR_CERT_ISSUER);
59✔
117
      }
118

119
      auto issuer_key = issuer.subject_public_key();
3,730✔
120

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

131
            if(sig_status.first == Certificate_Status_Code::VERIFIED) {
3,714✔
132
               const std::string hash_used_for_signature = sig_status.second;
3,657✔
133
               BOTAN_ASSERT_NOMSG(!hash_used_for_signature.empty());
3,657✔
134
               const auto& trusted_hashes = restrictions.trusted_hashes();
3,657✔
135

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

146
            if(issuer_key->estimated_strength() < restrictions.minimum_key_strength()) {
3,714✔
147
               status.insert(Certificate_Status_Code::SIGNATURE_METHOD_TOO_WEAK);
65✔
148
            }
149
         }
3,714✔
150
      }
151

152
      // Check cert extensions
153

154
      if(subject.x509_version() == 1) {
3,730✔
155
         if(subject.v2_issuer_key_id().empty() == false || subject.v2_subject_key_id().empty() == false) {
20✔
156
            status.insert(Certificate_Status_Code::V2_IDENTIFIERS_IN_V1_CERT);
1✔
157
         }
158
      }
159

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

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

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

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

200
   return cert_status;
1,355✔
201
}
1,355✔
202

203
namespace {
204

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

222
   // RFC 6960 4.2.2.2
223
   //
224
   //    2. Is the certificate of the CA that issued the certificate
225
   //       in question, or
226
   if(signing_cert == ca) {
41✔
227
      return Certificate_Status_Code::OK;
228
   }
229

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

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

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

261
   return validation_result.result();
23✔
262
}
23✔
263

264
}  // namespace
265

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

275
   CertificatePathStatusCodes cert_status(cert_path.size() - 1);
49✔
276

277
   for(size_t i = 0; i != cert_path.size() - 1; ++i) {
136✔
278
      std::set<Certificate_Status_Code>& status = cert_status.at(i);
87✔
279

280
      const X509_Certificate& subject = cert_path.at(i);
87✔
281
      const X509_Certificate& ca = cert_path.at(i + 1);
87✔
282

283
      if(i < ocsp_responses.size() && (ocsp_responses.at(i) != std::nullopt) &&
87✔
284
         (ocsp_responses.at(i)->status() == OCSP::Response_Status_Code::Successful)) {
49✔
285
         try {
49✔
286
            const auto& ocsp_response = ocsp_responses.at(i);
49✔
287

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

314
   while(!cert_status.empty() && cert_status.back().empty()) {
83✔
315
      cert_status.pop_back();
125✔
316
   }
317

318
   return cert_status;
49✔
319
}
×
320

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

328
   CertificatePathStatusCodes cert_status(cert_path.size());
1,359✔
329
   const X509_Time validation_time(ref_time);
1,359✔
330

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

334
      if(i < crls.size() && crls[i].has_value()) {
2,381✔
335
         const X509_Certificate& subject = cert_path.at(i);
736✔
336
         const X509_Certificate& ca = cert_path.at(i + 1);
736✔
337

338
         if(!ca.allowed_usage(Key_Constraints::CrlSign)) {
736✔
339
            status.insert(Certificate_Status_Code::CA_CERT_NOT_FOR_CRL_ISSUER);
2✔
340
         }
341

342
         if(validation_time < crls[i]->this_update()) {
736✔
343
            status.insert(Certificate_Status_Code::CRL_NOT_YET_VALID);
16✔
344
         }
345

346
         if(validation_time > crls[i]->next_update()) {
736✔
347
            status.insert(Certificate_Status_Code::CRL_HAS_EXPIRED);
18✔
348
         }
349

350
         auto ca_key = ca.subject_public_key();
736✔
351
         if(crls[i]->check_signature(*ca_key) == false) {
736✔
352
            status.insert(Certificate_Status_Code::CRL_BAD_SIGNATURE);
18✔
353
         }
354

355
         status.insert(Certificate_Status_Code::VALID_CRL_CHECKED);
736✔
356

357
         if(crls[i]->is_revoked(subject)) {
736✔
358
            status.insert(Certificate_Status_Code::CERT_IS_REVOKED);
148✔
359
         }
360

361
         std::string dp = subject.crl_distribution_point();
736✔
362
         if(!dp.empty()) {
736✔
363
            if(dp != crls[i]->crl_issuing_distribution_point()) {
224✔
364
               status.insert(Certificate_Status_Code::NO_MATCHING_CRLDP);
208✔
365
            }
366
         }
367

368
         for(const auto& extension : crls[i]->extensions().extensions()) {
2,236✔
369
            // XXX this is wrong - the OID might be defined but the extention not full parsed
370
            // for example see #1652
371

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

383
   while(!cert_status.empty() && cert_status.back().empty()) {
3,421✔
384
      cert_status.pop_back();
7,327✔
385
   }
386

387
   return cert_status;
1,359✔
388
}
1,359✔
389

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

397
   if(certstores.empty()) {
1,353✔
398
      throw Invalid_Argument("PKIX::check_crl certstores empty");
×
399
   }
400

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

403
   for(size_t i = 0; i != cert_path.size(); ++i) {
5,081✔
404
      for(auto certstore : certstores) {
6,335✔
405
         crls[i] = certstore->find_crl_for(cert_path[i]);
7,504✔
406
         if(crls[i]) {
3,752✔
407
            break;
408
         }
409
      }
410
   }
411

412
   return PKIX::check_crl(cert_path, crls, ref_time);
2,706✔
413
}
1,353✔
414

415
#if defined(BOTAN_HAS_ONLINE_REVOCATION_CHECKS)
416

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

426
   std::vector<std::future<std::optional<OCSP::Response>>> ocsp_response_futures;
7✔
427

428
   size_t to_ocsp = 1;
7✔
429

430
   if(restrictions.ocsp_all_intermediates()) {
7✔
431
      to_ocsp = cert_path.size() - 1;
×
432
   }
433
   if(cert_path.size() == 1) {
7✔
434
      to_ocsp = 0;
×
435
   }
436

437
   for(size_t i = 0; i < to_ocsp; ++i) {
14✔
438
      const X509_Certificate& subject = cert_path.at(i);
7✔
439
      const X509_Certificate& issuer = cert_path.at(i + 1);
7✔
440

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

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

464
            return OCSP::Response(http.body());
1✔
465
         }));
2✔
466
      }
467
   }
468

469
   std::vector<std::optional<OCSP::Response>> ocsp_responses;
7✔
470
   ocsp_responses.reserve(ocsp_response_futures.size());
7✔
471

472
   for(auto& ocsp_response_future : ocsp_response_futures) {
14✔
473
      ocsp_responses.push_back(ocsp_response_future.get());
21✔
474
   }
475

476
   return PKIX::check_ocsp(cert_path, ocsp_responses, trusted_certstores, ref_time, restrictions);
14✔
477
}
7✔
478

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

491
   std::vector<std::future<std::optional<X509_CRL>>> future_crls;
×
492
   std::vector<std::optional<X509_CRL>> crls(cert_path.size());
×
493

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

503
      // TODO: check if CRL is expired and re-request?
504

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

523
            http.throw_unless_ok();
×
524
            // check the mime type?
525
            return X509_CRL(http.body());
×
526
         }));
×
527
      }
528
   }
×
529

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

541
   auto crl_status = PKIX::check_crl(cert_path, crls, ref_time);
×
542

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

553
   return crl_status;
×
554
}
×
555

556
#endif
557

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

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

574
   cert_path.push_back(end_entity);
×
575
   certs_seen.insert(end_entity.fingerprint("SHA-256"));
×
576

577
   Certificate_Store_In_Memory ee_extras;
×
578
   for(const auto& cert : end_entity_extra) {
×
579
      ee_extras.add_certificate(cert);
×
580
   }
581

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

588
      std::optional<X509_Certificate> issuer;
×
589
      bool trusted_issuer = false;
×
590

591
      for(Certificate_Store* store : trusted_certstores) {
×
592
         issuer = store->find_cert(issuer_dn, auth_key_id);
×
593
         if(issuer) {
×
594
            trusted_issuer = true;
595
            break;
596
         }
597
      }
598

599
      if(!issuer) {
×
600
         // fall back to searching supplemental certs
601
         issuer = ee_extras.find_cert(issuer_dn, auth_key_id);
×
602
      }
603

604
      if(!issuer) {
×
605
         return Certificate_Status_Code::CERT_ISSUER_NOT_FOUND;
606
      }
607

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

610
      if(certs_seen.contains(fprint))  // already seen?
×
611
      {
612
         return Certificate_Status_Code::CERT_CHAIN_LOOP;
613
      }
614

615
      certs_seen.insert(fprint);
×
616
      cert_path.push_back(*issuer);
×
617

618
      if(issuer->is_self_signed()) {
×
619
         if(trusted_issuer) {
×
620
            return Certificate_Status_Code::OK;
621
         } else {
622
            return Certificate_Status_Code::CANNOT_ESTABLISH_TRUST;
×
623
         }
624
      }
625
   }
×
626
}
×
627

628
/**
629
 * utilities for PKIX::build_all_certificate_paths
630
 */
631
namespace {
632
// <certificate, trusted?>
633
using cert_maybe_trusted = std::pair<std::optional<X509_Certificate>, bool>;
634
}  // namespace
635

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

661
   if(end_entity->is_self_signed()) {
1,534✔
662
      return Certificate_Status_Code::CANNOT_ESTABLISH_TRUST;
663
   }
664

665
   /*
666
    * Pile up error messages
667
    */
668
   std::vector<Certificate_Status_Code> stats;
1,522✔
669

670
   Certificate_Store_In_Memory ee_extras;
1,522✔
671
   for(const auto& cert : end_entity_extra) {
2,738✔
672
      ee_extras.add_certificate(cert);
1,216✔
673
   }
674

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

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

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

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

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

712
         // the current path ends here
713
         if(last->is_self_signed()) {
4,287✔
714
            // found a trust anchor
715
            if(trusted) {
1,551✔
716
               cert_paths_out.push_back(path_so_far);
1,356✔
717
               cert_paths_out.back().push_back(*last);
1,356✔
718

719
               continue;
1,356✔
720
            }
721
            // found an untrustworthy root
722
            else {
723
               stats.push_back(Certificate_Status_Code::CANNOT_ESTABLISH_TRUST);
195✔
724
               continue;
195✔
725
            }
726
         }
727

728
         const X509_DN issuer_dn = last->issuer_dn();
2,736✔
729
         const std::vector<uint8_t> auth_key_id = last->authority_key_id();
2,736✔
730

731
         // search for trusted issuers
732
         std::vector<X509_Certificate> trusted_issuers;
2,736✔
733
         for(Certificate_Store* store : trusted_certstores) {
5,483✔
734
            auto new_issuers = store->find_all_certs(issuer_dn, auth_key_id);
2,747✔
735
            trusted_issuers.insert(trusted_issuers.end(), new_issuers.begin(), new_issuers.end());
2,747✔
736
         }
2,747✔
737

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

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

747
         // push the latest certificate onto the path_so_far
748
         path_so_far.push_back(*last);
2,571✔
749
         certs_seen.emplace(fprint);
2,571✔
750

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

754
         for(const auto& trusted_cert : trusted_issuers) {
4,176✔
755
            stack.push_back({trusted_cert, true});
3,210✔
756
         }
757

758
         for(const auto& misc : misc_issuers) {
3,732✔
759
            stack.push_back({misc, false});
2,322✔
760
         }
761
      }
9,527✔
762
   }
6,859✔
763

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

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

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

788
      if(i < crl.size() && !crl[i].empty()) {
2,375✔
789
         for(auto&& code : crl[i]) {
1,885✔
790
            if(code == Certificate_Status_Code::VALID_CRL_CHECKED) {
1,155✔
791
               had_crl = true;
730✔
792
            }
793
            chain_status[i].insert(code);
1,155✔
794
         }
795
      }
796

797
      if(i < ocsp.size() && !ocsp[i].empty()) {
2,375✔
798
         for(auto&& code : ocsp[i]) {
70✔
799
            if(code == Certificate_Status_Code::OCSP_RESPONSE_GOOD ||
37✔
800
               code == Certificate_Status_Code::OCSP_NO_REVOCATION_URL ||   // softfail
20✔
801
               code == Certificate_Status_Code::OCSP_SERVER_NOT_AVAILABLE)  // softfail
802
            {
803
               had_ocsp = true;
17✔
804
            }
805

806
            chain_status[i].insert(code);
37✔
807
         }
808
      }
809

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

819
Certificate_Status_Code PKIX::overall_status(const CertificatePathStatusCodes& cert_status) {
1,361✔
820
   if(cert_status.empty()) {
1,361✔
821
      throw Invalid_Argument("PKIX::overall_status empty cert status");
×
822
   }
823

824
   Certificate_Status_Code overall_status = Certificate_Status_Code::OK;
825

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

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

851
   X509_Certificate end_entity = end_certs[0];
1,534✔
852
   std::vector<X509_Certificate> end_entity_extra;
1,534✔
853
   for(size_t i = 1; i < end_certs.size(); ++i) {
2,750✔
854
      end_entity_extra.push_back(end_certs[i]);
1,216✔
855
   }
856

857
   std::vector<std::vector<X509_Certificate>> cert_paths;
1,534✔
858
   Certificate_Status_Code path_building_result =
1,534✔
859
      PKIX::build_all_certificate_paths(cert_paths, trusted_roots, end_entity, end_entity_extra);
1,534✔
860

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

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

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

873
      CertificatePathStatusCodes ocsp_status;
1,353✔
874

875
      if(!ocsp_resp.empty()) {
1,353✔
876
         ocsp_status = PKIX::check_ocsp(cert_path, ocsp_resp, trusted_roots, ref_time, restrictions);
27✔
877
      }
878

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

888
      PKIX::merge_revocation_status(status, crl_status, ocsp_status, restrictions);
1,353✔
889

890
      Path_Validation_Result pvd(status, std::move(cert_path));
1,353✔
891
      if(pvd.successful_validation()) {
1,353✔
892
         return pvd;
500✔
893
      } else {
894
         error_results.push_back(std::move(pvd));
853✔
895
      }
896
   }
1,353✔
897
   return error_results[0];
853✔
898
}
2,864✔
899

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

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

924
   return x509_path_validate(end_certs, restrictions, trusted_roots, hostname, usage, when, ocsp_timeout, ocsp_resp);
854✔
925
}
854✔
926

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

938
   std::vector<Certificate_Store*> trusted_roots;
263✔
939
   trusted_roots.push_back(const_cast<Certificate_Store*>(&store));
263✔
940

941
   return x509_path_validate(certs, restrictions, trusted_roots, hostname, usage, when, ocsp_timeout, ocsp_resp);
526✔
942
}
263✔
943

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

958
   m_trusted_hashes.insert("SHA-224");
1,340✔
959
   m_trusted_hashes.insert("SHA-256");
1,340✔
960
   m_trusted_hashes.insert("SHA-384");
1,340✔
961
   m_trusted_hashes.insert("SHA-512");
1,340✔
962
   m_trusted_hashes.insert("SHAKE-256(512)");
1,340✔
963
}
670✔
964

965
namespace {
966
CertificatePathStatusCodes find_warnings(const CertificatePathStatusCodes& all_statuses) {
1,353✔
967
   CertificatePathStatusCodes warnings;
1,353✔
968
   for(const auto& status_set_i : all_statuses) {
5,081✔
969
      std::set<Certificate_Status_Code> warning_set_i;
3,728✔
970
      for(const auto& code : status_set_i) {
5,801✔
971
         if(code >= Certificate_Status_Code::FIRST_WARNING_STATUS &&
2,073✔
972
            code < Certificate_Status_Code::FIRST_ERROR_STATUS) {
973
            warning_set_i.insert(code);
2,135✔
974
         }
975
      }
976
      warnings.push_back(warning_set_i);
3,728✔
977
   }
3,728✔
978
   return warnings;
1,353✔
979
}
×
980
}  // namespace
981

982
Path_Validation_Result::Path_Validation_Result(CertificatePathStatusCodes status,
1,353✔
983
                                               std::vector<X509_Certificate>&& cert_chain) :
1,353✔
984
      m_all_status(std::move(status)),
1,353✔
985
      m_warnings(find_warnings(m_all_status)),
1,353✔
986
      m_cert_path(cert_chain),
1,353✔
987
      m_overall(PKIX::overall_status(m_all_status)) {}
1,353✔
988

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

997
   return m_cert_path[m_cert_path.size() - 1];
10✔
998
}
999

1000
bool Path_Validation_Result::successful_validation() const {
1,570✔
1001
   return (result() == Certificate_Status_Code::VERIFIED || result() == Certificate_Status_Code::OCSP_RESPONSE_GOOD ||
1,570✔
1002
           result() == Certificate_Status_Code::VALID_CRL_CHECKED);
905✔
1003
}
1004

1005
bool Path_Validation_Result::no_warnings() const {
38✔
1006
   for(const auto& status_set_i : m_warnings) {
151✔
1007
      if(!status_set_i.empty()) {
114✔
1008
         return false;
38✔
1009
      }
1010
   }
1011
   return true;
1012
}
1013

1014
CertificatePathStatusCodes Path_Validation_Result::warnings() const {
32✔
1015
   return m_warnings;
32✔
1016
}
1017

1018
std::string Path_Validation_Result::result_string() const {
1,018✔
1019
   return status_string(result());
1,018✔
1020
}
1021

1022
const char* Path_Validation_Result::status_string(Certificate_Status_Code code) {
1,052✔
1023
   if(const char* s = to_string(code)) {
1,052✔
1024
      return s;
1,052✔
1025
   }
1026

1027
   return "Unknown error";
1028
}
1029

1030
std::string Path_Validation_Result::warnings_string() const {
38✔
1031
   const std::string sep(", ");
38✔
1032
   std::ostringstream oss;
38✔
1033
   for(size_t i = 0; i < m_warnings.size(); i++) {
153✔
1034
      for(auto code : m_warnings[i]) {
116✔
1035
         oss << "[" << std::to_string(i) << "] " << status_string(code) << sep;
2✔
1036
      }
1037
   }
1038

1039
   std::string res = oss.str();
38✔
1040
   // remove last sep
1041
   if(res.size() >= sep.size()) {
38✔
1042
      res = res.substr(0, res.size() - sep.size());
2✔
1043
   }
1044
   return res;
76✔
1045
}
38✔
1046
}  // 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

© 2026 Coveralls, Inc