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

randombit / botan / 5079590438

25 May 2023 12:28PM UTC coverage: 92.228% (+0.5%) from 91.723%
5079590438

Pull #3502

github

Pull Request #3502: Apply clang-format to the codebase

75589 of 81959 relevant lines covered (92.23%)

12139530.51 hits per line

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

80.04
/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,354✔
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,354✔
38
      throw Invalid_Argument("PKIX::check_chain cert_path empty");
×
39

40
   const bool self_signed_ee_cert = (cert_path.size() == 1);
1,354✔
41

42
   X509_Time validation_time(ref_time);
1,354✔
43

44
   CertificatePathStatusCodes cert_status(cert_path.size());
1,354✔
45

46
   if(!hostname.empty() && !cert_path[0].matches_dns_name(hostname))
1,354✔
47
      cert_status[0].insert(Certificate_Status_Code::CERT_NAME_NOMATCH);
14✔
48

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

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

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

67
   for(size_t i = 0; i != cert_path.size(); ++i) {
5,082✔
68
      std::set<Certificate_Status_Code>& status = cert_status.at(i);
3,728✔
69

70
      const bool at_self_signed_root = (i == cert_path.size() - 1);
3,728✔
71

72
      const X509_Certificate& subject = cert_path[i];
3,728✔
73
      const X509_Certificate& issuer = cert_path[at_self_signed_root ? (i) : (i + 1)];
3,728✔
74

75
      if(at_self_signed_root && (issuer.is_self_signed() == false)) {
3,728✔
76
         status.insert(Certificate_Status_Code::CHAIN_LACKS_TRUST_ROOT);
×
77
      }
78

79
      if(subject.issuer_dn() != issuer.subject_dn()) {
3,728✔
80
         status.insert(Certificate_Status_Code::CHAIN_NAME_MISMATCH);
×
81
      }
82

83
      // Check the serial number
84
      if(subject.is_serial_negative()) {
3,728✔
85
         status.insert(Certificate_Status_Code::CERT_SERIAL_NEGATIVE);
32✔
86
      }
87

88
      // Check the subject's DN components' length
89

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

98
      // Check all certs for valid time range
99
      if(validation_time < subject.not_before())
3,728✔
100
         status.insert(Certificate_Status_Code::CERT_NOT_YET_VALID);
41✔
101

102
      if(validation_time > subject.not_after())
3,728✔
103
         status.insert(Certificate_Status_Code::CERT_HAS_EXPIRED);
162✔
104

105
      // Check issuer constraints
106
      if(!issuer.is_CA_cert() && !self_signed_ee_cert)
3,728✔
107
         status.insert(Certificate_Status_Code::CA_CERT_NOT_FOR_CERT_ISSUER);
59✔
108

109
      auto issuer_key = issuer.subject_public_key();
3,728✔
110

111
      // Check the signature algorithm is known
112
      if(!subject.signature_algorithm().oid().registered_oid()) {
3,728✔
113
         status.insert(Certificate_Status_Code::SIGNATURE_ALGO_UNKNOWN);
16✔
114
      } else {
115
         // only perform the following checks if the signature algorithm is known
116
         if(!issuer_key) {
3,712✔
117
            status.insert(Certificate_Status_Code::CERT_PUBKEY_INVALID);
×
118
         } else {
119
            const auto sig_status = subject.verify_signature(*issuer_key);
3,712✔
120

121
            if(sig_status.first == Certificate_Status_Code::VERIFIED) {
3,712✔
122
               const std::string hash_used_for_signature = sig_status.second;
3,655✔
123
               BOTAN_ASSERT_NOMSG(!hash_used_for_signature.empty());
3,655✔
124
               const auto& trusted_hashes = restrictions.trusted_hashes();
3,655✔
125

126
               // Ignore untrusted hashes on self-signed roots
127
               if(!trusted_hashes.empty() && !at_self_signed_root) {
3,655✔
128
                  if(!trusted_hashes.contains(hash_used_for_signature))
2,301✔
129
                     status.insert(Certificate_Status_Code::UNTRUSTED_HASH);
68✔
130
               }
131
            } else {
3,655✔
132
               status.insert(sig_status.first);
57✔
133
            }
134

135
            if(issuer_key->estimated_strength() < restrictions.minimum_key_strength())
3,712✔
136
               status.insert(Certificate_Status_Code::SIGNATURE_METHOD_TOO_WEAK);
65✔
137
         }
3,712✔
138
      }
139

140
      // Check cert extensions
141

142
      if(subject.x509_version() == 1) {
3,728✔
143
         if(subject.v2_issuer_key_id().empty() == false || subject.v2_subject_key_id().empty() == false) {
20✔
144
            status.insert(Certificate_Status_Code::V2_IDENTIFIERS_IN_V1_CERT);
1✔
145
         }
146
      }
147

148
      const Extensions& extensions = subject.v3_extensions();
3,728✔
149
      const auto& extensions_vec = extensions.extensions();
3,728✔
150
      if(subject.x509_version() < 3 && !extensions_vec.empty()) {
3,728✔
151
         status.insert(Certificate_Status_Code::EXT_IN_V1_V2_CERT);
32✔
152
      }
153
      for(auto& extension : extensions_vec) {
19,597✔
154
         extension.first->validate(subject, issuer, cert_path, cert_status, i);
15,869✔
155
      }
156
      if(extensions_vec.size() != extensions.get_extension_oids().size()) {
3,728✔
157
         status.insert(Certificate_Status_Code::DUPLICATE_CERT_EXTENSION);
16✔
158
      }
159
   }
7,456✔
160

161
   // path len check
162
   size_t max_path_length = cert_path.size();
1,354✔
163
   for(size_t i = cert_path.size() - 1; i > 0; --i) {
3,728✔
164
      std::set<Certificate_Status_Code>& status = cert_status.at(i);
2,374✔
165
      const X509_Certificate& subject = cert_path[i];
2,374✔
166

167
      /*
168
      * If the certificate was not self-issued, verify that max_path_length is
169
      * greater than zero and decrement max_path_length by 1.
170
      */
171
      if(subject.subject_dn() != subject.issuer_dn()) {
2,374✔
172
         if(max_path_length > 0) {
1,005✔
173
            --max_path_length;
954✔
174
         } else {
175
            status.insert(Certificate_Status_Code::CERT_CHAIN_TOO_LONG);
51✔
176
         }
177
      }
178

179
      /*
180
      * If pathLenConstraint is present in the certificate and is less than max_path_length,
181
      * set max_path_length to the value of pathLenConstraint.
182
      */
183
      if(subject.path_limit() != Cert_Extension::NO_CERT_PATH_LIMIT && subject.path_limit() < max_path_length) {
2,374✔
184
         max_path_length = subject.path_limit();
1,184✔
185
      }
186
   }
187

188
   return cert_status;
1,354✔
189
}
1,354✔
190

191
namespace {
192

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

209
   // RFC 6960 4.2.2.2
210
   //
211
   //    2. Is the certificate of the CA that issued the certificate
212
   //       in question, or
213
   if(signing_cert == ca)
41✔
214
      return Certificate_Status_Code::OK;
215

216
   // RFC 6960 4.2.2.2
217
   //
218
   //    3. Includes a value of id-kp-OCSPSigning in an extended key
219
   //       usage extension and is issued by the CA that issued the
220
   //       certificate in question as stated above.
221

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

239
   const auto validation_result = x509_path_validate(concat(std::vector{signing_cert}, extra_certs),
69✔
240
                                                     relaxed_restrictions,
241
                                                     certstores,
242
                                                     {} /* hostname */,
243
                                                     Botan::Usage_Type::OCSP_RESPONDER,
244
                                                     ref_time,
245
                                                     ocsp_timeout);
69✔
246

247
   return validation_result.result();
23✔
248
}
23✔
249

250
}
251

252
CertificatePathStatusCodes PKIX::check_ocsp(const std::vector<X509_Certificate>& cert_path,
45✔
253
                                            const std::vector<std::optional<OCSP::Response>>& ocsp_responses,
254
                                            const std::vector<Certificate_Store*>& certstores,
255
                                            std::chrono::system_clock::time_point ref_time,
256
                                            const Path_Validation_Restrictions& restrictions) {
257
   if(cert_path.empty())
45✔
258
      throw Invalid_Argument("PKIX::check_ocsp cert_path empty");
×
259

260
   CertificatePathStatusCodes cert_status(cert_path.size() - 1);
45✔
261

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

265
      const X509_Certificate& subject = cert_path.at(i);
83✔
266
      const X509_Certificate& ca = cert_path.at(i + 1);
83✔
267

268
      if(i < ocsp_responses.size() && (ocsp_responses.at(i) != std::nullopt) &&
83✔
269
         (ocsp_responses.at(i)->status() == OCSP::Response_Status_Code::Successful)) {
47✔
270
         try {
47✔
271
            const auto& ocsp_response = ocsp_responses.at(i);
47✔
272

273
            if(auto dummy_status = ocsp_response->dummy_status()) {
47✔
274
               // handle softfail conditions
275
               status.insert(dummy_status.value());
52✔
276
            } else if(auto signing_cert =
42✔
277
                         ocsp_response->find_signing_certificate(ca, restrictions.trusted_ocsp_responders());
42✔
278
                      !signing_cert) {
42✔
279
               status.insert(Certificate_Status_Code::OCSP_ISSUER_NOT_FOUND);
1✔
280
            } else if(auto ocsp_signing_cert_status =
41✔
281
                         verify_ocsp_signing_cert(signing_cert.value(),
41✔
282
                                                  ca,
283
                                                  concat(ocsp_response->certificates(), cert_path),
41✔
284
                                                  certstores,
285
                                                  ref_time,
286
                                                  restrictions);
41✔
287
                      ocsp_signing_cert_status > Certificate_Status_Code::FIRST_ERROR_STATUS) {
41✔
288
               status.insert(ocsp_signing_cert_status);
4✔
289
               status.insert(Certificate_Status_Code::OCSP_ISSUER_NOT_TRUSTED);
4✔
290
            } else {
291
               status.insert(ocsp_response->status_for(ca, subject, ref_time, restrictions.max_ocsp_age()));
37✔
292
            }
42✔
293
         } catch(Exception&) { status.insert(Certificate_Status_Code::OCSP_RESPONSE_INVALID); }
×
294
      }
295
   }
296

297
   while(!cert_status.empty() && cert_status.back().empty())
79✔
298
      cert_status.pop_back();
117✔
299

300
   return cert_status;
45✔
301
}
×
302

303
CertificatePathStatusCodes PKIX::check_crl(const std::vector<X509_Certificate>& cert_path,
1,358✔
304
                                           const std::vector<std::optional<X509_CRL>>& crls,
305
                                           std::chrono::system_clock::time_point ref_time) {
306
   if(cert_path.empty())
1,358✔
307
      throw Invalid_Argument("PKIX::check_crl cert_path empty");
×
308

309
   CertificatePathStatusCodes cert_status(cert_path.size());
1,358✔
310
   const X509_Time validation_time(ref_time);
1,358✔
311

312
   for(size_t i = 0; i != cert_path.size() - 1; ++i) {
3,738✔
313
      std::set<Certificate_Status_Code>& status = cert_status.at(i);
2,380✔
314

315
      if(i < crls.size() && crls[i].has_value()) {
2,380✔
316
         const X509_Certificate& subject = cert_path.at(i);
738✔
317
         const X509_Certificate& ca = cert_path.at(i + 1);
738✔
318

319
         if(!ca.allowed_usage(Key_Constraints::CrlSign))
738✔
320
            status.insert(Certificate_Status_Code::CA_CERT_NOT_FOR_CRL_ISSUER);
2✔
321

322
         if(validation_time < crls[i]->this_update())
738✔
323
            status.insert(Certificate_Status_Code::CRL_NOT_YET_VALID);
16✔
324

325
         if(validation_time > crls[i]->next_update())
738✔
326
            status.insert(Certificate_Status_Code::CRL_HAS_EXPIRED);
18✔
327

328
         auto ca_key = ca.subject_public_key();
738✔
329
         if(crls[i]->check_signature(*ca_key) == false)
738✔
330
            status.insert(Certificate_Status_Code::CRL_BAD_SIGNATURE);
18✔
331

332
         status.insert(Certificate_Status_Code::VALID_CRL_CHECKED);
738✔
333

334
         if(crls[i]->is_revoked(subject))
738✔
335
            status.insert(Certificate_Status_Code::CERT_IS_REVOKED);
148✔
336

337
         std::string dp = subject.crl_distribution_point();
738✔
338
         if(!dp.empty()) {
738✔
339
            if(dp != crls[i]->crl_issuing_distribution_point()) {
224✔
340
               status.insert(Certificate_Status_Code::NO_MATCHING_CRLDP);
208✔
341
            }
342
         }
343

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

348
            // is the extension critical and unknown?
349
            if(extension.second && !extension.first->oid_of().registered_oid()) {
1,504✔
350
               /* NIST Certificate Path Valiadation Testing document: "When an implementation does not recognize a critical extension in the
351
                * crlExtensions field, it shall assume that identified certificates have been revoked and are no longer valid"
352
                */
353
               status.insert(Certificate_Status_Code::CERT_IS_REVOKED);
18✔
354
            }
355
         }
738✔
356
      }
1,476✔
357
   }
358

359
   while(!cert_status.empty() && cert_status.back().empty())
3,419✔
360
      cert_status.pop_back();
7,318✔
361

362
   return cert_status;
1,358✔
363
}
1,358✔
364

365
CertificatePathStatusCodes PKIX::check_crl(const std::vector<X509_Certificate>& cert_path,
1,352✔
366
                                           const std::vector<Certificate_Store*>& certstores,
367
                                           std::chrono::system_clock::time_point ref_time) {
368
   if(cert_path.empty())
1,352✔
369
      throw Invalid_Argument("PKIX::check_crl cert_path empty");
×
370

371
   if(certstores.empty())
1,352✔
372
      throw Invalid_Argument("PKIX::check_crl certstores empty");
×
373

374
   std::vector<std::optional<X509_CRL>> crls(cert_path.size());
1,352✔
375

376
   for(size_t i = 0; i != cert_path.size(); ++i) {
5,078✔
377
      for(auto certstore : certstores) {
6,323✔
378
         crls[i] = certstore->find_crl_for(cert_path[i]);
7,492✔
379
         if(crls[i])
3,746✔
380
            break;
381
      }
382
   }
383

384
   return PKIX::check_crl(cert_path, crls, ref_time);
2,704✔
385
}
1,352✔
386

387
#if defined(BOTAN_HAS_ONLINE_REVOCATION_CHECKS)
388

389
CertificatePathStatusCodes PKIX::check_ocsp_online(const std::vector<X509_Certificate>& cert_path,
5✔
390
                                                   const std::vector<Certificate_Store*>& trusted_certstores,
391
                                                   std::chrono::system_clock::time_point ref_time,
392
                                                   std::chrono::milliseconds timeout,
393
                                                   const Path_Validation_Restrictions& restrictions) {
394
   if(cert_path.empty())
5✔
395
      throw Invalid_Argument("PKIX::check_ocsp_online cert_path empty");
×
396

397
   std::vector<std::future<std::optional<OCSP::Response>>> ocsp_response_futures;
5✔
398

399
   size_t to_ocsp = 1;
5✔
400

401
   if(restrictions.ocsp_all_intermediates())
5✔
402
      to_ocsp = cert_path.size() - 1;
×
403
   if(cert_path.size() == 1)
5✔
404
      to_ocsp = 0;
×
405

406
   for(size_t i = 0; i < to_ocsp; ++i) {
10✔
407
      const X509_Certificate& subject = cert_path.at(i);
5✔
408
      const X509_Certificate& issuer = cert_path.at(i + 1);
5✔
409

410
      if(subject.ocsp_responder().empty()) {
6✔
411
         ocsp_response_futures.emplace_back(std::async(std::launch::deferred, [&]() -> std::optional<OCSP::Response> {
8✔
412
            return OCSP::Response(Certificate_Status_Code::OCSP_NO_REVOCATION_URL);
4✔
413
         }));
414
      } else {
415
         ocsp_response_futures.emplace_back(std::async(std::launch::async, [&]() -> std::optional<OCSP::Response> {
2✔
416
            OCSP::Request req(issuer, BigInt::decode(subject.serial_number()));
1✔
417

418
            HTTP::Response http;
1✔
419
            try {
1✔
420
               http = HTTP::POST_sync(subject.ocsp_responder(),
3✔
421
                                      "application/ocsp-request",
422
                                      req.BER_encode(),
1✔
423
                                      /*redirects*/ 1,
424
                                      timeout);
2✔
425
            } catch(std::exception&) {
×
426
               // log e.what() ?
427
            }
×
428
            if(http.status_code() != 200)
1✔
429
               return OCSP::Response(Certificate_Status_Code::OCSP_SERVER_NOT_AVAILABLE);
×
430
            // Check the MIME type?
431

432
            return OCSP::Response(http.body());
1✔
433
         }));
2✔
434
      }
435
   }
436

437
   std::vector<std::optional<OCSP::Response>> ocsp_responses;
5✔
438
   ocsp_responses.reserve(ocsp_response_futures.size());
5✔
439

440
   for(auto& ocsp_response_future : ocsp_response_futures) {
10✔
441
      ocsp_responses.push_back(ocsp_response_future.get());
15✔
442
   }
443

444
   return PKIX::check_ocsp(cert_path, ocsp_responses, trusted_certstores, ref_time, restrictions);
10✔
445
}
5✔
446

447
CertificatePathStatusCodes PKIX::check_crl_online(const std::vector<X509_Certificate>& cert_path,
×
448
                                                  const std::vector<Certificate_Store*>& certstores,
449
                                                  Certificate_Store_In_Memory* crl_store,
450
                                                  std::chrono::system_clock::time_point ref_time,
451
                                                  std::chrono::milliseconds timeout) {
452
   if(cert_path.empty())
×
453
      throw Invalid_Argument("PKIX::check_crl_online cert_path empty");
×
454
   if(certstores.empty())
×
455
      throw Invalid_Argument("PKIX::check_crl_online certstores empty");
×
456

457
   std::vector<std::future<std::optional<X509_CRL>>> future_crls;
×
458
   std::vector<std::optional<X509_CRL>> crls(cert_path.size());
×
459

460
   for(size_t i = 0; i != cert_path.size(); ++i) {
×
461
      const std::optional<X509_Certificate>& cert = cert_path.at(i);
×
462
      for(auto certstore : certstores) {
×
463
         crls[i] = certstore->find_crl_for(*cert);
×
464
         if(crls[i].has_value())
×
465
            break;
466
      }
467

468
      // TODO: check if CRL is expired and re-request?
469

470
      // Only request if we don't already have a CRL
471
      if(crls[i]) {
×
472
         /*
473
         We already have a CRL, so just insert this empty one to hold a place in the vector
474
         so that indexes match up
475
         */
476
         future_crls.emplace_back(std::future<std::optional<X509_CRL>>());
×
477
      } else if(cert->crl_distribution_point().empty()) {
×
478
         // Avoid creating a thread for this case
479
         future_crls.emplace_back(std::async(std::launch::deferred, [&]() -> std::optional<X509_CRL> {
×
480
            throw Not_Implemented("No CRL distribution point for this certificate");
×
481
         }));
482
      } else {
483
         future_crls.emplace_back(std::async(std::launch::async, [&]() -> std::optional<X509_CRL> {
×
484
            auto http = HTTP::GET_sync(cert->crl_distribution_point(),
×
485
                                       /*redirects*/ 1,
486
                                       timeout);
×
487

488
            http.throw_unless_ok();
×
489
            // check the mime type?
490
            return X509_CRL(http.body());
×
491
         }));
×
492
      }
493
   }
×
494

495
   for(size_t i = 0; i != future_crls.size(); ++i) {
×
496
      if(future_crls[i].valid()) {
×
497
         try {
×
498
            crls[i] = future_crls[i].get();
×
499
         } catch(std::exception&) {
×
500
            // crls[i] left null
501
            // todo: log exception e.what() ?
502
         }
×
503
      }
504
   }
505

506
   auto crl_status = PKIX::check_crl(cert_path, crls, ref_time);
×
507

508
   if(crl_store) {
×
509
      for(size_t i = 0; i != crl_status.size(); ++i) {
×
510
         if(crl_status[i].contains(Certificate_Status_Code::VALID_CRL_CHECKED)) {
×
511
            // better be non-null, we supposedly validated it
512
            BOTAN_ASSERT_NOMSG(crls[i].has_value());
×
513
            crl_store->add_crl(*crls[i]);
×
514
         }
515
      }
516
   }
517

518
   return crl_status;
×
519
}
×
520

521
#endif
522

523
Certificate_Status_Code PKIX::build_certificate_path(std::vector<X509_Certificate>& cert_path,
×
524
                                                     const std::vector<Certificate_Store*>& trusted_certstores,
525
                                                     const X509_Certificate& end_entity,
526
                                                     const std::vector<X509_Certificate>& end_entity_extra) {
527
   if(end_entity.is_self_signed()) {
×
528
      return Certificate_Status_Code::CANNOT_ESTABLISH_TRUST;
529
   }
530

531
   /*
532
   * This is an inelegant but functional way of preventing path loops
533
   * (where C1 -> C2 -> C3 -> C1). We store a set of all the certificate
534
   * fingerprints in the path. If there is a duplicate, we error out.
535
   * TODO: save fingerprints in result struct? Maybe useful for blacklists, etc.
536
   */
537
   std::set<std::string> certs_seen;
×
538

539
   cert_path.push_back(end_entity);
×
540
   certs_seen.insert(end_entity.fingerprint("SHA-256"));
×
541

542
   Certificate_Store_In_Memory ee_extras;
×
543
   for(const auto& cert : end_entity_extra)
×
544
      ee_extras.add_certificate(cert);
×
545

546
   // iterate until we reach a root or cannot find the issuer
547
   for(;;) {
×
548
      const X509_Certificate& last = cert_path.back();
×
549
      const X509_DN issuer_dn = last.issuer_dn();
×
550
      const std::vector<uint8_t> auth_key_id = last.authority_key_id();
×
551

552
      std::optional<X509_Certificate> issuer;
×
553
      bool trusted_issuer = false;
×
554

555
      for(Certificate_Store* store : trusted_certstores) {
×
556
         issuer = store->find_cert(issuer_dn, auth_key_id);
×
557
         if(issuer) {
×
558
            trusted_issuer = true;
559
            break;
560
         }
561
      }
562

563
      if(!issuer) {
×
564
         // fall back to searching supplemental certs
565
         issuer = ee_extras.find_cert(issuer_dn, auth_key_id);
×
566
      }
567

568
      if(!issuer)
×
569
         return Certificate_Status_Code::CERT_ISSUER_NOT_FOUND;
570

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

573
      if(certs_seen.contains(fprint))  // already seen?
×
574
      {
575
         return Certificate_Status_Code::CERT_CHAIN_LOOP;
576
      }
577

578
      certs_seen.insert(fprint);
×
579
      cert_path.push_back(*issuer);
×
580

581
      if(issuer->is_self_signed()) {
×
582
         if(trusted_issuer) {
×
583
            return Certificate_Status_Code::OK;
584
         } else {
585
            return Certificate_Status_Code::CANNOT_ESTABLISH_TRUST;
×
586
         }
587
      }
588
   }
×
589
}
×
590

591
/**
592
 * utilities for PKIX::build_all_certificate_paths
593
 */
594
namespace {
595
// <certificate, trusted?>
596
using cert_maybe_trusted = std::pair<std::optional<X509_Certificate>, bool>;
597
}
598

599
/**
600
 * Build all possible certificate paths from the end certificate to self-signed trusted roots.
601
 *
602
 * All potentially valid paths are put into the cert_paths vector. If no potentially valid paths are found,
603
 * one of the encountered errors is returned arbitrarily.
604
 *
605
 * todo add a path building function that returns detailed information on errors encountered while building
606
 * the potentially numerous path candidates.
607
 *
608
 * Basically, a DFS is performed starting from the end certificate. A stack (vector) serves to control the DFS.
609
 * At the beginning of each iteration, a pair is popped from the stack that contains (1) the next certificate
610
 * to add to the path (2) a bool that indicates if the certificate is part of a trusted certstore. Ideally, we
611
 * follow the unique issuer of the current certificate until a trusted root is reached. However, the issuer DN +
612
 * authority key id need not be unique among the certificates used for building the path. In such a case,
613
 * we consider all the matching issuers by pushing <IssuerCert, trusted?> on the stack for each of them.
614
 *
615
 */
616
Certificate_Status_Code PKIX::build_all_certificate_paths(std::vector<std::vector<X509_Certificate>>& cert_paths_out,
1,533✔
617
                                                          const std::vector<Certificate_Store*>& trusted_certstores,
618
                                                          const std::optional<X509_Certificate>& end_entity,
619
                                                          const std::vector<X509_Certificate>& end_entity_extra) {
620
   if(!cert_paths_out.empty()) {
1,533✔
621
      throw Invalid_Argument("PKIX::build_all_certificate_paths: cert_paths_out must be empty");
×
622
   }
623

624
   if(end_entity->is_self_signed()) {
1,533✔
625
      return Certificate_Status_Code::CANNOT_ESTABLISH_TRUST;
626
   }
627

628
   /*
629
    * Pile up error messages
630
    */
631
   std::vector<Certificate_Status_Code> stats;
1,521✔
632

633
   Certificate_Store_In_Memory ee_extras;
1,521✔
634
   for(const auto& cert : end_entity_extra) {
2,739✔
635
      ee_extras.add_certificate(cert);
1,218✔
636
   }
637

638
   /*
639
   * This is an inelegant but functional way of preventing path loops
640
   * (where C1 -> C2 -> C3 -> C1). We store a set of all the certificate
641
   * fingerprints in the path. If there is a duplicate, we error out.
642
   * TODO: save fingerprints in result struct? Maybe useful for blacklists, etc.
643
   */
644
   std::set<std::string> certs_seen;
1,521✔
645

646
   // new certs are added and removed from the path during the DFS
647
   // it is copied into cert_paths_out when we encounter a trusted root
648
   std::vector<X509_Certificate> path_so_far;
1,521✔
649

650
   // todo can we assume that the end certificate is not trusted?
651
   std::vector<cert_maybe_trusted> stack = {{end_entity, false}};
4,563✔
652

653
   while(!stack.empty()) {
8,379✔
654
      std::optional<X509_Certificate> last = stack.back().first;
6,858✔
655
      // found a deletion marker that guides the DFS, backtracing
656
      if(last == std::nullopt) {
6,858✔
657
         stack.pop_back();
2,570✔
658
         std::string fprint = path_so_far.back().fingerprint("SHA-256");
2,570✔
659
         certs_seen.erase(fprint);
2,570✔
660
         path_so_far.pop_back();
2,570✔
661
      }
2,570✔
662
      // process next cert on the path
663
      else {
664
         const bool trusted = stack.back().second;
4,288✔
665
         stack.pop_back();
4,288✔
666

667
         // certificate already seen?
668
         const std::string fprint = last->fingerprint("SHA-256");
4,288✔
669
         if(certs_seen.count(fprint) == 1) {
4,288✔
670
            stats.push_back(Certificate_Status_Code::CERT_CHAIN_LOOP);
1✔
671
            // the current path ended in a loop
672
            continue;
1✔
673
         }
674

675
         // the current path ends here
676
         if(last->is_self_signed()) {
4,287✔
677
            // found a trust anchor
678
            if(trusted) {
1,552✔
679
               cert_paths_out.push_back(path_so_far);
1,355✔
680
               cert_paths_out.back().push_back(*last);
1,355✔
681

682
               continue;
1,355✔
683
            }
684
            // found an untrustworthy root
685
            else {
686
               stats.push_back(Certificate_Status_Code::CANNOT_ESTABLISH_TRUST);
197✔
687
               continue;
197✔
688
            }
689
         }
690

691
         const X509_DN issuer_dn = last->issuer_dn();
2,735✔
692
         const std::vector<uint8_t> auth_key_id = last->authority_key_id();
2,735✔
693

694
         // search for trusted issuers
695
         std::vector<X509_Certificate> trusted_issuers;
2,735✔
696
         for(Certificate_Store* store : trusted_certstores) {
5,479✔
697
            auto new_issuers = store->find_all_certs(issuer_dn, auth_key_id);
2,744✔
698
            trusted_issuers.insert(trusted_issuers.end(), new_issuers.begin(), new_issuers.end());
2,744✔
699
         }
2,744✔
700

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

704
         // if we could not find any issuers, the current path ends here
705
         if(trusted_issuers.empty() && misc_issuers.empty()) {
2,735✔
706
            stats.push_back(Certificate_Status_Code::CERT_ISSUER_NOT_FOUND);
165✔
707
            continue;
165✔
708
         }
709

710
         // push the latest certificate onto the path_so_far
711
         path_so_far.push_back(*last);
2,570✔
712
         certs_seen.emplace(fprint);
2,570✔
713

714
         // push a deletion marker on the stack for backtracing later
715
         stack.push_back({std::optional<X509_Certificate>(), false});
2,570✔
716

717
         for(const auto& trusted_cert : trusted_issuers) {
4,174✔
718
            stack.push_back({trusted_cert, true});
3,208✔
719
         }
720

721
         for(const auto& misc : misc_issuers) {
3,733✔
722
            stack.push_back({misc, false});
2,326✔
723
         }
724
      }
9,525✔
725
   }
6,858✔
726

727
   // could not construct any potentially valid path
728
   if(cert_paths_out.empty()) {
1,521✔
729
      if(stats.empty())
169✔
730
         throw Internal_Error("X509 path building failed for unknown reasons");
×
731
      else
732
         // arbitrarily return the first error
733
         return stats[0];
169✔
734
   } else {
735
      return Certificate_Status_Code::OK;
736
   }
737
}
3,054✔
738

739
void PKIX::merge_revocation_status(CertificatePathStatusCodes& chain_status,
1,352✔
740
                                   const CertificatePathStatusCodes& crl,
741
                                   const CertificatePathStatusCodes& ocsp,
742
                                   const Path_Validation_Restrictions& restrictions) {
743
   if(chain_status.empty())
1,352✔
744
      throw Invalid_Argument("PKIX::merge_revocation_status chain_status was empty");
×
745

746
   for(size_t i = 0; i != chain_status.size() - 1; ++i) {
3,726✔
747
      bool had_crl = false, had_ocsp = false;
2,374✔
748

749
      if(i < crl.size() && !crl[i].empty()) {
2,374✔
750
         for(auto&& code : crl[i]) {
1,889✔
751
            if(code == Certificate_Status_Code::VALID_CRL_CHECKED) {
1,157✔
752
               had_crl = true;
732✔
753
            }
754
            chain_status[i].insert(code);
1,157✔
755
         }
756
      }
757

758
      if(i < ocsp.size() && !ocsp[i].empty()) {
2,374✔
759
         for(auto&& code : ocsp[i]) {
66✔
760
            if(code == Certificate_Status_Code::OCSP_RESPONSE_GOOD ||
35✔
761
               code == Certificate_Status_Code::OCSP_NO_REVOCATION_URL ||   // softfail
20✔
762
               code == Certificate_Status_Code::OCSP_SERVER_NOT_AVAILABLE)  // softfail
763
            {
764
               had_ocsp = true;
15✔
765
            }
766

767
            chain_status[i].insert(code);
35✔
768
         }
769
      }
770

771
      if(had_crl == false && had_ocsp == false) {
2,374✔
772
         if((restrictions.require_revocation_information() && i == 0) ||
1,627✔
773
            (restrictions.ocsp_all_intermediates() && i > 0)) {
1,586✔
774
            chain_status[i].insert(Certificate_Status_Code::NO_REVOCATION_DATA);
76✔
775
         }
776
      }
777
   }
778
}
1,352✔
779

780
Certificate_Status_Code PKIX::overall_status(const CertificatePathStatusCodes& cert_status) {
1,360✔
781
   if(cert_status.empty())
1,360✔
782
      throw Invalid_Argument("PKIX::overall_status empty cert status");
×
783

784
   Certificate_Status_Code overall_status = Certificate_Status_Code::OK;
785

786
   // take the "worst" error as overall
787
   for(const std::set<Certificate_Status_Code>& s : cert_status) {
5,094✔
788
      if(!s.empty()) {
3,734✔
789
         auto worst = *s.rbegin();
1,494✔
790
         // Leave informative OCSP/CRL confirmations on cert-level status only
791
         if(worst >= Certificate_Status_Code::FIRST_ERROR_STATUS && worst > overall_status) {
1,494✔
792
            overall_status = worst;
878✔
793
         }
794
      }
795
   }
796
   return overall_status;
1,360✔
797
}
798

799
Path_Validation_Result x509_path_validate(const std::vector<X509_Certificate>& end_certs,
1,533✔
800
                                          const Path_Validation_Restrictions& restrictions,
801
                                          const std::vector<Certificate_Store*>& trusted_roots,
802
                                          std::string_view hostname,
803
                                          Usage_Type usage,
804
                                          std::chrono::system_clock::time_point ref_time,
805
                                          std::chrono::milliseconds ocsp_timeout,
806
                                          const std::vector<std::optional<OCSP::Response>>& ocsp_resp) {
807
   if(end_certs.empty()) {
1,533✔
808
      throw Invalid_Argument("x509_path_validate called with no subjects");
×
809
   }
810

811
   X509_Certificate end_entity = end_certs[0];
1,533✔
812
   std::vector<X509_Certificate> end_entity_extra;
1,533✔
813
   for(size_t i = 1; i < end_certs.size(); ++i) {
2,751✔
814
      end_entity_extra.push_back(end_certs[i]);
1,218✔
815
   }
816

817
   std::vector<std::vector<X509_Certificate>> cert_paths;
1,533✔
818
   Certificate_Status_Code path_building_result =
1,533✔
819
      PKIX::build_all_certificate_paths(cert_paths, trusted_roots, end_entity, end_entity_extra);
1,533✔
820

821
   // If we cannot successfully build a chain to a trusted self-signed root, stop now
822
   if(path_building_result != Certificate_Status_Code::OK) {
1,533✔
823
      return Path_Validation_Result(path_building_result);
181✔
824
   }
825

826
   std::vector<Path_Validation_Result> error_results;
1,352✔
827
   // Try validating all the potentially valid paths and return the first one to validate properly
828
   for(auto cert_path : cert_paths) {
2,203✔
829
      CertificatePathStatusCodes status = PKIX::check_chain(cert_path, ref_time, hostname, usage, restrictions);
1,352✔
830

831
      CertificatePathStatusCodes crl_status = PKIX::check_crl(cert_path, trusted_roots, ref_time);
1,352✔
832

833
      CertificatePathStatusCodes ocsp_status;
1,352✔
834

835
      if(!ocsp_resp.empty()) {
1,352✔
836
         ocsp_status = PKIX::check_ocsp(cert_path, ocsp_resp, trusted_roots, ref_time, restrictions);
25✔
837
      }
838

839
      if(ocsp_status.empty() && ocsp_timeout != std::chrono::milliseconds(0)) {
1,352✔
840
#if defined(BOTAN_TARGET_OS_HAS_THREADS) && defined(BOTAN_HAS_HTTP_UTIL)
841
         ocsp_status = PKIX::check_ocsp_online(cert_path, trusted_roots, ref_time, ocsp_timeout, restrictions);
4✔
842
#else
843
         ocsp_status.resize(1);
844
         ocsp_status[0].insert(Certificate_Status_Code::OCSP_NO_HTTP);
845
#endif
846
      }
847

848
      PKIX::merge_revocation_status(status, crl_status, ocsp_status, restrictions);
1,352✔
849

850
      Path_Validation_Result pvd(status, std::move(cert_path));
1,352✔
851
      if(pvd.successful_validation()) {
1,352✔
852
         return pvd;
501✔
853
      } else {
854
         error_results.push_back(std::move(pvd));
851✔
855
      }
856
   }
1,352✔
857
   return error_results[0];
851✔
858
}
2,862✔
859

860
Path_Validation_Result x509_path_validate(const X509_Certificate& end_cert,
276✔
861
                                          const Path_Validation_Restrictions& restrictions,
862
                                          const std::vector<Certificate_Store*>& trusted_roots,
863
                                          std::string_view hostname,
864
                                          Usage_Type usage,
865
                                          std::chrono::system_clock::time_point when,
866
                                          std::chrono::milliseconds ocsp_timeout,
867
                                          const std::vector<std::optional<OCSP::Response>>& ocsp_resp) {
868
   std::vector<X509_Certificate> certs;
276✔
869
   certs.push_back(end_cert);
276✔
870
   return x509_path_validate(certs, restrictions, trusted_roots, hostname, usage, when, ocsp_timeout, ocsp_resp);
552✔
871
}
276✔
872

873
Path_Validation_Result x509_path_validate(const std::vector<X509_Certificate>& end_certs,
854✔
874
                                          const Path_Validation_Restrictions& restrictions,
875
                                          const Certificate_Store& store,
876
                                          std::string_view hostname,
877
                                          Usage_Type usage,
878
                                          std::chrono::system_clock::time_point when,
879
                                          std::chrono::milliseconds ocsp_timeout,
880
                                          const std::vector<std::optional<OCSP::Response>>& ocsp_resp) {
881
   std::vector<Certificate_Store*> trusted_roots;
854✔
882
   trusted_roots.push_back(const_cast<Certificate_Store*>(&store));
854✔
883

884
   return x509_path_validate(end_certs, restrictions, trusted_roots, hostname, usage, when, ocsp_timeout, ocsp_resp);
854✔
885
}
854✔
886

887
Path_Validation_Result x509_path_validate(const X509_Certificate& end_cert,
262✔
888
                                          const Path_Validation_Restrictions& restrictions,
889
                                          const Certificate_Store& store,
890
                                          std::string_view hostname,
891
                                          Usage_Type usage,
892
                                          std::chrono::system_clock::time_point when,
893
                                          std::chrono::milliseconds ocsp_timeout,
894
                                          const std::vector<std::optional<OCSP::Response>>& ocsp_resp) {
895
   std::vector<X509_Certificate> certs;
262✔
896
   certs.push_back(end_cert);
262✔
897

898
   std::vector<Certificate_Store*> trusted_roots;
262✔
899
   trusted_roots.push_back(const_cast<Certificate_Store*>(&store));
262✔
900

901
   return x509_path_validate(certs, restrictions, trusted_roots, hostname, usage, when, ocsp_timeout, ocsp_resp);
524✔
902
}
262✔
903

904
Path_Validation_Restrictions::Path_Validation_Restrictions(bool require_rev,
669✔
905
                                                           size_t key_strength,
906
                                                           bool ocsp_intermediates,
907
                                                           std::chrono::seconds max_ocsp_age,
908
                                                           std::unique_ptr<Certificate_Store> trusted_ocsp_responders) :
669✔
909
      m_require_revocation_information(require_rev),
669✔
910
      m_ocsp_all_intermediates(ocsp_intermediates),
669✔
911
      m_minimum_key_strength(key_strength),
669✔
912
      m_max_ocsp_age(max_ocsp_age),
669✔
913
      m_trusted_ocsp_responders(std::move(trusted_ocsp_responders)) {
669✔
914
   if(key_strength <= 80) {
669✔
915
      m_trusted_hashes.insert("SHA-1");
678✔
916
   }
917

918
   m_trusted_hashes.insert("SHA-224");
1,338✔
919
   m_trusted_hashes.insert("SHA-256");
1,338✔
920
   m_trusted_hashes.insert("SHA-384");
1,338✔
921
   m_trusted_hashes.insert("SHA-512");
1,338✔
922
   m_trusted_hashes.insert("SHAKE-256(512)");
1,338✔
923
}
669✔
924

925
namespace {
926
CertificatePathStatusCodes find_warnings(const CertificatePathStatusCodes& all_statuses) {
1,352✔
927
   CertificatePathStatusCodes warnings;
1,352✔
928
   for(const auto& status_set_i : all_statuses) {
5,078✔
929
      std::set<Certificate_Status_Code> warning_set_i;
3,726✔
930
      for(const auto& code : status_set_i) {
5,792✔
931
         if(code >= Certificate_Status_Code::FIRST_WARNING_STATUS &&
2,066✔
932
            code < Certificate_Status_Code::FIRST_ERROR_STATUS) {
933
            warning_set_i.insert(code);
2,126✔
934
         }
935
      }
936
      warnings.push_back(warning_set_i);
3,726✔
937
   }
3,726✔
938
   return warnings;
1,352✔
939
}
×
940
}
941

942
Path_Validation_Result::Path_Validation_Result(CertificatePathStatusCodes status,
1,352✔
943
                                               std::vector<X509_Certificate>&& cert_chain) :
1,352✔
944
      m_all_status(std::move(status)),
1,352✔
945
      m_warnings(find_warnings(m_all_status)),
1,352✔
946
      m_cert_path(cert_chain),
1,352✔
947
      m_overall(PKIX::overall_status(m_all_status)) {}
1,352✔
948

949
const X509_Certificate& Path_Validation_Result::trust_root() const {
10✔
950
   if(m_cert_path.empty())
10✔
951
      throw Invalid_State("Path_Validation_Result::trust_root no path set");
×
952
   if(result() != Certificate_Status_Code::VERIFIED)
10✔
953
      throw Invalid_State("Path_Validation_Result::trust_root meaningless with invalid status");
×
954

955
   return m_cert_path[m_cert_path.size() - 1];
10✔
956
}
957

958
bool Path_Validation_Result::successful_validation() const {
1,568✔
959
   return (result() == Certificate_Status_Code::VERIFIED || result() == Certificate_Status_Code::OCSP_RESPONSE_GOOD ||
1,568✔
960
           result() == Certificate_Status_Code::VALID_CRL_CHECKED);
903✔
961
}
962

963
bool Path_Validation_Result::no_warnings() const {
38✔
964
   for(const auto& status_set_i : m_warnings)
151✔
965
      if(!status_set_i.empty())
114✔
966
         return false;
38✔
967
   return true;
968
}
969

970
CertificatePathStatusCodes Path_Validation_Result::warnings() const { return m_warnings; }
32✔
971

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

974
const char* Path_Validation_Result::status_string(Certificate_Status_Code code) {
1,050✔
975
   if(const char* s = to_string(code))
1,050✔
976
      return s;
1,050✔
977

978
   return "Unknown error";
979
}
980

981
std::string Path_Validation_Result::warnings_string() const {
38✔
982
   const std::string sep(", ");
38✔
983
   std::ostringstream oss;
38✔
984
   for(size_t i = 0; i < m_warnings.size(); i++) {
153✔
985
      for(auto code : m_warnings[i]) {
116✔
986
         oss << "[" << std::to_string(i) << "] " << status_string(code) << sep;
2✔
987
      }
988
   }
989

990
   std::string res = oss.str();
38✔
991
   // remove last sep
992
   if(res.size() >= sep.size())
38✔
993
      res = res.substr(0, res.size() - sep.size());
2✔
994
   return res;
76✔
995
}
38✔
996
}
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