Botan 2.19.5
Crypto and TLS for C&
Functions
Botan::PKIX Namespace Reference

Functions

Certificate_Status_Code build_all_certificate_paths (std::vector< std::vector< std::shared_ptr< const X509_Certificate > > > &cert_paths, const std::vector< Certificate_Store * > &trusted_certstores, const std::shared_ptr< const X509_Certificate > &end_entity, const std::vector< std::shared_ptr< const X509_Certificate > > &end_entity_extra)
 
Certificate_Status_Code build_certificate_path (std::vector< std::shared_ptr< const X509_Certificate > > &cert_path_out, const std::vector< Certificate_Store * > &trusted_certstores, const std::shared_ptr< const X509_Certificate > &end_entity, const std::vector< std::shared_ptr< const X509_Certificate > > &end_entity_extra)
 
CertificatePathStatusCodes check_chain (const std::vector< std::shared_ptr< const X509_Certificate > > &cert_path, std::chrono::system_clock::time_point ref_time, const std::string &hostname, Usage_Type usage, size_t min_signature_algo_strength, const std::set< std::string > &trusted_hashes)
 
CertificatePathStatusCodes check_crl (const std::vector< std::shared_ptr< const X509_Certificate > > &cert_path, const std::vector< Certificate_Store * > &certstores, std::chrono::system_clock::time_point ref_time)
 
CertificatePathStatusCodes check_crl (const std::vector< std::shared_ptr< const X509_Certificate > > &cert_path, const std::vector< std::shared_ptr< const X509_CRL > > &crls, std::chrono::system_clock::time_point ref_time)
 
CertificatePathStatusCodes check_ocsp (const std::vector< std::shared_ptr< const X509_Certificate > > &cert_path, const std::vector< std::shared_ptr< const OCSP::Response > > &ocsp_responses, const std::vector< Certificate_Store * > &certstores, std::chrono::system_clock::time_point ref_time, std::chrono::seconds max_ocsp_age=std::chrono::seconds::zero())
 
void merge_revocation_status (CertificatePathStatusCodes &chain_status, const CertificatePathStatusCodes &crl_status, const CertificatePathStatusCodes &ocsp_status, bool require_rev_on_end_entity, bool require_rev_on_intermediates)
 
Certificate_Status_Code overall_status (const CertificatePathStatusCodes &cert_status)
 

Detailed Description

namespace PKIX holds the building blocks that are called by x509_path_validate. This allows custom validation logic to be written by applications and makes for easier testing, but unless you're positive you know what you're doing you probably want to just call x509_path_validate instead.

Function Documentation

◆ build_all_certificate_paths()

Certificate_Status_Code Botan::PKIX::build_all_certificate_paths ( std::vector< std::vector< std::shared_ptr< const X509_Certificate > > > &  cert_paths_out,
const std::vector< Certificate_Store * > &  trusted_certstores,
const std::shared_ptr< const X509_Certificate > &  end_entity,
const std::vector< std::shared_ptr< const X509_Certificate > > &  end_entity_extra 
)

Build all possible certificate paths from the end certificate to self-signed trusted roots.

All potentially valid paths are put into the cert_paths vector. If no potentially valid paths are found, one of the encountered errors is returned arbitrarily.

todo add a path building function that returns detailed information on errors encountered while building the potentially numerous path candidates.

Basically, a DFS is performed starting from the end certificate. A stack (vector) serves to control the DFS. At the beginning of each iteration, a pair is popped from the stack that contains (1) the next certificate to add to the path (2) a bool that indicates if the certificate is part of a trusted certstore. Ideally, we follow the unique issuer of the current certificate until a trusted root is reached. However, the issuer DN + authority key id need not be unique among the certificates used for building the path. In such a case, we consider all the matching issuers by pushing <IssuerCert, trusted?> on the stack for each of them.

Definition at line 664 of file x509path.cpp.

668 {
669 if(!cert_paths_out.empty())
670 {
671 throw Invalid_Argument("PKIX::build_all_certificate_paths: cert_paths_out must be empty");
672 }
673
674 if(end_entity->is_self_signed())
675 {
676 return Certificate_Status_Code::CANNOT_ESTABLISH_TRUST;
677 }
678
679 /*
680 * Pile up error messages
681 */
682 std::vector<Certificate_Status_Code> stats;
683
684 Certificate_Store_In_Memory ee_extras;
685 for(size_t i = 0; i != end_entity_extra.size(); ++i)
686 {
687 ee_extras.add_certificate(end_entity_extra[i]);
688 }
689
690 /*
691 * This is an inelegant but functional way of preventing path loops
692 * (where C1 -> C2 -> C3 -> C1). We store a set of all the certificate
693 * fingerprints in the path. If there is a duplicate, we error out.
694 * TODO: save fingerprints in result struct? Maybe useful for blacklists, etc.
695 */
696 std::set<std::string> certs_seen;
697
698 // new certs are added and removed from the path during the DFS
699 // it is copied into cert_paths_out when we encounter a trusted root
700 std::vector<std::shared_ptr<const X509_Certificate>> path_so_far;
701
702 // todo can we assume that the end certificate is not trusted?
703 std::vector<cert_maybe_trusted> stack = { {end_entity, false} };
704
705 while(!stack.empty())
706 {
707 // found a deletion marker that guides the DFS, backtracing
708 if(stack.back().first == nullptr)
709 {
710 stack.pop_back();
711 std::string fprint = path_so_far.back()->fingerprint("SHA-256");
712 certs_seen.erase(fprint);
713 path_so_far.pop_back();
714 }
715 // process next cert on the path
716 else
717 {
718 std::shared_ptr<const X509_Certificate> last = stack.back().first;
719 bool trusted = stack.back().second;
720 stack.pop_back();
721
722 // certificate already seen?
723 const std::string fprint = last->fingerprint("SHA-256");
724 if(certs_seen.count(fprint) == 1)
725 {
726 stats.push_back(Certificate_Status_Code::CERT_CHAIN_LOOP);
727 // the current path ended in a loop
728 continue;
729 }
730
731 // the current path ends here
732 if(last->is_self_signed())
733 {
734 // found a trust anchor
735 if(trusted)
736 {
737 cert_paths_out.push_back(path_so_far);
738 cert_paths_out.back().push_back(last);
739
740 continue;
741 }
742 // found an untrustworthy root
743 else
744 {
745 stats.push_back(Certificate_Status_Code::CANNOT_ESTABLISH_TRUST);
746 continue;
747 }
748 }
749
750 const X509_DN issuer_dn = last->issuer_dn();
751 const std::vector<uint8_t> auth_key_id = last->authority_key_id();
752
753 // search for trusted issuers
754 std::vector<std::shared_ptr<const X509_Certificate>> trusted_issuers;
755 for(Certificate_Store* store : trusted_certstores)
756 {
757 auto new_issuers = store->find_all_certs(issuer_dn, auth_key_id);
758 trusted_issuers.insert(trusted_issuers.end(), new_issuers.begin(), new_issuers.end());
759 }
760
761 // search the supplemental certs
762 std::vector<std::shared_ptr<const X509_Certificate>> misc_issuers =
763 ee_extras.find_all_certs(issuer_dn, auth_key_id);
764
765 // if we could not find any issuers, the current path ends here
766 if(trusted_issuers.size() + misc_issuers.size() == 0)
767 {
768 stats.push_back(Certificate_Status_Code::CERT_ISSUER_NOT_FOUND);
769 continue;
770 }
771
772 // push the latest certificate onto the path_so_far
773 path_so_far.push_back(last);
774 certs_seen.insert(fprint);
775
776 // push a deletion marker on the stack for backtracing later
777 stack.push_back({std::shared_ptr<const X509_Certificate>(nullptr),false});
778
779 for(const auto& trusted_cert : trusted_issuers)
780 {
781 stack.push_back({trusted_cert,true});
782 }
783
784 for(const auto& misc : misc_issuers)
785 {
786 stack.push_back({misc,false});
787 }
788 }
789 }
790
791 // could not construct any potentially valid path
792 if(cert_paths_out.empty())
793 {
794 if(stats.empty())
795 throw Internal_Error("X509 path building failed for unknown reasons");
796 else
797 // arbitrarily return the first error
798 return stats[0];
799 }
800 else
801 {
802 return Certificate_Status_Code::OK;
803 }
804 }

References Botan::Certificate_Store_In_Memory::add_certificate(), Botan::CANNOT_ESTABLISH_TRUST, Botan::CERT_CHAIN_LOOP, Botan::CERT_ISSUER_NOT_FOUND, Botan::Certificate_Store_In_Memory::find_all_certs(), and Botan::OK.

Referenced by Botan::x509_path_validate().

◆ build_certificate_path()

Certificate_Status_Code Botan::PKIX::build_certificate_path ( std::vector< std::shared_ptr< const X509_Certificate > > &  cert_path_out,
const std::vector< Certificate_Store * > &  trusted_certstores,
const std::shared_ptr< const X509_Certificate > &  end_entity,
const std::vector< std::shared_ptr< const X509_Certificate > > &  end_entity_extra 
)

Build certificate path

Parameters
cert_path_outoutput parameter, cert_path will be appended to this vector
trusted_certstoreslist of certificate stores that contain trusted certificates
end_entitythe cert to be validated
end_entity_extraoptional list of additional untrusted certs for path building
Returns
result of the path building operation (OK or error)

Definition at line 559 of file x509path.cpp.

563 {
564 if(end_entity->is_self_signed())
565 {
566 return Certificate_Status_Code::CANNOT_ESTABLISH_TRUST;
567 }
568
569 /*
570 * This is an inelegant but functional way of preventing path loops
571 * (where C1 -> C2 -> C3 -> C1). We store a set of all the certificate
572 * fingerprints in the path. If there is a duplicate, we error out.
573 * TODO: save fingerprints in result struct? Maybe useful for blacklists, etc.
574 */
575 std::set<std::string> certs_seen;
576
577 cert_path.push_back(end_entity);
578 certs_seen.insert(end_entity->fingerprint("SHA-256"));
579
581 for(size_t i = 0; i != end_entity_extra.size(); ++i)
582 ee_extras.add_certificate(end_entity_extra[i]);
583
584 // iterate until we reach a root or cannot find the issuer
585 for(;;)
586 {
587 const X509_Certificate& last = *cert_path.back();
588 const X509_DN issuer_dn = last.issuer_dn();
589 const std::vector<uint8_t> auth_key_id = last.authority_key_id();
590
591 std::shared_ptr<const X509_Certificate> issuer;
592 bool trusted_issuer = false;
593
594 for(Certificate_Store* store : trusted_certstores)
595 {
596 issuer = store->find_cert(issuer_dn, auth_key_id);
597 if(issuer)
598 {
599 trusted_issuer = true;
600 break;
601 }
602 }
603
604 if(!issuer)
605 {
606 // fall back to searching supplemental certs
607 issuer = ee_extras.find_cert(issuer_dn, auth_key_id);
608 }
609
610 if(!issuer)
611 return Certificate_Status_Code::CERT_ISSUER_NOT_FOUND;
612
613 const std::string fprint = issuer->fingerprint("SHA-256");
614
615 if(certs_seen.count(fprint) > 0) // already seen?
616 {
617 return Certificate_Status_Code::CERT_CHAIN_LOOP;
618 }
619
620 certs_seen.insert(fprint);
621 cert_path.push_back(issuer);
622
623 if(issuer->is_self_signed())
624 {
625 if(trusted_issuer)
626 {
627 return Certificate_Status_Code::OK;
628 }
629 else
630 {
631 return Certificate_Status_Code::CANNOT_ESTABLISH_TRUST;
632 }
633 }
634 }
635 }
std::shared_ptr< const X509_Certificate > find_cert(const X509_DN &subject_dn, const std::vector< uint8_t > &key_id) const override
Definition: certstor.cpp:65
void add_certificate(const X509_Certificate &cert)
Definition: certstor.cpp:38
const std::vector< uint8_t > & authority_key_id() const
Definition: x509cert.cpp:451
const X509_DN & issuer_dn() const
Definition: x509cert.cpp:472

References Botan::Certificate_Store_In_Memory::add_certificate(), Botan::X509_Certificate::authority_key_id(), Botan::CANNOT_ESTABLISH_TRUST, Botan::CERT_CHAIN_LOOP, Botan::CERT_ISSUER_NOT_FOUND, Botan::Certificate_Store_In_Memory::find_cert(), Botan::X509_Certificate::issuer_dn(), and Botan::OK.

◆ check_chain()

CertificatePathStatusCodes Botan::PKIX::check_chain ( const std::vector< std::shared_ptr< const X509_Certificate > > &  cert_path,
std::chrono::system_clock::time_point  ref_time,
const std::string &  hostname,
Usage_Type  usage,
size_t  min_signature_algo_strength,
const std::set< std::string > &  trusted_hashes 
)

Check the certificate chain, but not any revocation data

Parameters
cert_pathpath built by build_certificate_path with OK result
ref_timewhatever time you want to perform the validation against (normally current system clock)
hostnamethe hostname
usageend entity usage checks
min_signature_algo_strength80 or 110 typically Note 80 allows 1024 bit RSA and SHA-1. 110 allows 2048 bit RSA and SHA-2. Using 128 requires ECC (P-256) or ~3000 bit RSA keys.
trusted_hashesset of trusted hash functions, empty means accept any hash we have an OID for
Returns
vector of results on per certificate in the path, each containing a set of results. If all codes in the set are < Certificate_Status_Code::FIRST_ERROR_STATUS, then the result for that certificate is successful. If all results are

Definition at line 32 of file x509path.cpp.

38 {
39 if(cert_path.empty())
40 throw Invalid_Argument("PKIX::check_chain cert_path empty");
41
42 const bool self_signed_ee_cert = (cert_path.size() == 1);
43
44 X509_Time validation_time(ref_time);
45
46 CertificatePathStatusCodes cert_status(cert_path.size());
47
48 if(!hostname.empty() && !cert_path[0]->matches_dns_name(hostname))
49 cert_status[0].insert(Certificate_Status_Code::CERT_NAME_NOMATCH);
50
51 if(!cert_path[0]->allowed_usage(usage))
52 cert_status[0].insert(Certificate_Status_Code::INVALID_USAGE);
53
54 for(size_t i = 0; i != cert_path.size(); ++i)
55 {
56 std::set<Certificate_Status_Code>& status = cert_status.at(i);
57
58 const bool at_self_signed_root = (i == cert_path.size() - 1);
59
60 const std::shared_ptr<const X509_Certificate>& subject = cert_path[i];
61
62 const std::shared_ptr<const X509_Certificate>& issuer = cert_path[at_self_signed_root ? (i) : (i + 1)];
63
64 std::unique_ptr<Public_Key> issuer_key(issuer->subject_public_key());
65
66 // Check the signature algorithm is known
67 if(OIDS::oid2str_or_empty(subject->signature_algorithm().get_oid()).empty())
68 {
69 status.insert(Certificate_Status_Code::SIGNATURE_ALGO_UNKNOWN);
70 }
71 else
72 {
73 // only perform the following checks if the signature algorithm is known
74 if(!issuer_key)
75 {
76 status.insert(Certificate_Status_Code::CERT_PUBKEY_INVALID);
77 }
78 else
79 {
80 const Certificate_Status_Code sig_status = subject->verify_signature(*issuer_key);
81
82 if(sig_status != Certificate_Status_Code::VERIFIED)
83 status.insert(sig_status);
84
85 if(issuer_key->estimated_strength() < min_signature_algo_strength)
86 status.insert(Certificate_Status_Code::SIGNATURE_METHOD_TOO_WEAK);
87 }
88
89 // Ignore untrusted hashes on self-signed roots
90 if(trusted_hashes.size() > 0 && !at_self_signed_root)
91 {
92 if(trusted_hashes.count(subject->hash_used_for_signature()) == 0)
93 status.insert(Certificate_Status_Code::UNTRUSTED_HASH);
94 }
95 }
96 }
97
98
99 // If any of the signatures were invalid, return immediately; we know the
100 // chain is invalid and signature failure is always considered the most
101 // critical result. This does mean other problems in the certificate (eg
102 // expired) will not be reported, but we'd have to assume any such data is
103 // anyway arbitrary considering we couldn't verify the signature chain
104
105 for(size_t i = 0; i != cert_path.size(); ++i)
106 {
107 for(auto status : cert_status.at(i))
108 {
109 // This ignores errors relating to the key or hash being weak since
110 // these are somewhat advisory
111 if(static_cast<uint32_t>(status) >= 5000)
112 {
113 return cert_status;
114 }
115 }
116 }
117
118 if(cert_path[0]->is_CA_cert() == false &&
119 cert_path[0]->has_constraints(KEY_CERT_SIGN))
120 {
121 /*
122 "If the keyCertSign bit is asserted, then the cA bit in the
123 basic constraints extension (Section 4.2.1.9) MUST also be
124 asserted." - RFC 5280
125
126 We don't bother doing this check on the rest of the path since they
127 must have the cA bit asserted or the validation will fail anyway.
128 */
129 cert_status[0].insert(Certificate_Status_Code::INVALID_USAGE);
130 }
131
132 for(size_t i = 0; i != cert_path.size(); ++i)
133 {
134 std::set<Certificate_Status_Code>& status = cert_status.at(i);
135
136 const bool at_self_signed_root = (i == cert_path.size() - 1);
137
138 const std::shared_ptr<const X509_Certificate>& subject = cert_path[i];
139
140 const std::shared_ptr<const X509_Certificate>& issuer = cert_path[at_self_signed_root ? (i) : (i + 1)];
141
142 if(at_self_signed_root && (issuer->is_self_signed() == false))
143 {
144 status.insert(Certificate_Status_Code::CHAIN_LACKS_TRUST_ROOT);
145 }
146
147 if(subject->issuer_dn() != issuer->subject_dn())
148 {
149 status.insert(Certificate_Status_Code::CHAIN_NAME_MISMATCH);
150 }
151
152 // Check the serial number
153 if(subject->is_serial_negative())
154 {
155 status.insert(Certificate_Status_Code::CERT_SERIAL_NEGATIVE);
156 }
157
158 // Check the subject's DN components' length
159
160 for(const auto& dn_pair : subject->subject_dn().dn_info())
161 {
162 const size_t dn_ub = X509_DN::lookup_ub(dn_pair.first);
163 // dn_pair = <OID,str>
164 if(dn_ub > 0 && dn_pair.second.size() > dn_ub)
165 {
166 status.insert(Certificate_Status_Code::DN_TOO_LONG);
167 }
168 }
169
170 // Check all certs for valid time range
171 if(validation_time < subject->not_before())
172 status.insert(Certificate_Status_Code::CERT_NOT_YET_VALID);
173
174 if(validation_time > subject->not_after())
175 status.insert(Certificate_Status_Code::CERT_HAS_EXPIRED);
176
177 // Check issuer constraints
178 if(!issuer->is_CA_cert() && !self_signed_ee_cert)
179 status.insert(Certificate_Status_Code::CA_CERT_NOT_FOR_CERT_ISSUER);
180
181 // Check cert extensions
182
183 if(subject->x509_version() == 1)
184 {
185 if(subject->v2_issuer_key_id().empty() == false ||
186 subject->v2_subject_key_id().empty() == false)
187 {
188 status.insert(Certificate_Status_Code::V2_IDENTIFIERS_IN_V1_CERT);
189 }
190 }
191
192 Extensions extensions = subject->v3_extensions();
193 const auto& extensions_vec = extensions.extensions();
194 if(subject->x509_version() < 3 && !extensions_vec.empty())
195 {
196 status.insert(Certificate_Status_Code::EXT_IN_V1_V2_CERT);
197 }
198 for(auto& extension : extensions_vec)
199 {
200 extension.first->validate(*subject, *issuer, cert_path, cert_status, i);
201 }
202 if(extensions.extensions().size() != extensions.get_extension_oids().size())
203 {
204 status.insert(Certificate_Status_Code::DUPLICATE_CERT_EXTENSION);
205 }
206 }
207
208 // path len check
209 size_t max_path_length = cert_path.size();
210 for(size_t i = cert_path.size() - 1; i > 0 ; --i)
211 {
212 std::set<Certificate_Status_Code>& status = cert_status.at(i);
213 const std::shared_ptr<const X509_Certificate>& subject = cert_path[i];
214
215 /*
216 * If the certificate was not self-issued, verify that max_path_length is
217 * greater than zero and decrement max_path_length by 1.
218 */
219 if(subject->subject_dn() != subject->issuer_dn())
220 {
221 if(max_path_length > 0)
222 {
223 --max_path_length;
224 }
225 else
226 {
227 status.insert(Certificate_Status_Code::CERT_CHAIN_TOO_LONG);
228 }
229 }
230
231 /*
232 * If pathLenConstraint is present in the certificate and is less than max_path_length,
233 * set max_path_length to the value of pathLenConstraint.
234 */
235 if(subject->path_limit() != Cert_Extension::NO_CERT_PATH_LIMIT && subject->path_limit() < max_path_length)
236 {
237 max_path_length = subject->path_limit();
238 }
239 }
240
241 return cert_status;
242 }
@ KEY_CERT_SIGN
Definition: ffi.h:1571
BOTAN_UNSTABLE_API std::string oid2str_or_empty(const OID &oid)
Definition: oids.cpp:111
std::vector< std::set< Certificate_Status_Code > > CertificatePathStatusCodes
Definition: x509path.h:29
Certificate_Status_Code
Definition: pkix_enums.h:17

References Botan::CA_CERT_NOT_FOR_CERT_ISSUER, Botan::CERT_CHAIN_TOO_LONG, Botan::CERT_HAS_EXPIRED, Botan::CERT_NAME_NOMATCH, Botan::CERT_NOT_YET_VALID, Botan::CERT_PUBKEY_INVALID, Botan::CERT_SERIAL_NEGATIVE, Botan::CHAIN_LACKS_TRUST_ROOT, Botan::CHAIN_NAME_MISMATCH, Botan::DN_TOO_LONG, Botan::DUPLICATE_CERT_EXTENSION, Botan::EXT_IN_V1_V2_CERT, Botan::Extensions::extensions(), Botan::Extensions::get_extension_oids(), Botan::INVALID_USAGE, Botan::KEY_CERT_SIGN, Botan::X509_DN::lookup_ub(), Botan::OIDS::oid2str_or_empty(), Botan::SIGNATURE_ALGO_UNKNOWN, Botan::SIGNATURE_METHOD_TOO_WEAK, Botan::UNTRUSTED_HASH, Botan::V2_IDENTIFIERS_IN_V1_CERT, and Botan::VERIFIED.

Referenced by Botan::x509_path_validate().

◆ check_crl() [1/2]

CertificatePathStatusCodes Botan::PKIX::check_crl ( const std::vector< std::shared_ptr< const X509_Certificate > > &  cert_path,
const std::vector< Certificate_Store * > &  certstores,
std::chrono::system_clock::time_point  ref_time 
)

Check CRLs for revocation information

Parameters
cert_pathpath already validated by check_chain
certstoresa list of certificate stores to query for the CRL
ref_timewhatever time you want to perform the validation against (normally current system clock)
Returns
revocation status

Definition at line 370 of file x509path.cpp.

373 {
374 if(cert_path.empty())
375 throw Invalid_Argument("PKIX::check_crl cert_path empty");
376
377 if(certstores.empty())
378 throw Invalid_Argument("PKIX::check_crl certstores empty");
379
380 std::vector<std::shared_ptr<const X509_CRL>> crls(cert_path.size());
381
382 for(size_t i = 0; i != cert_path.size(); ++i)
383 {
384 BOTAN_ASSERT_NONNULL(cert_path[i]);
385 for(size_t c = 0; c != certstores.size(); ++c)
386 {
387 crls[i] = certstores[c]->find_crl_for(*cert_path[i]);
388 if(crls[i])
389 break;
390 }
391 }
392
393 return PKIX::check_crl(cert_path, crls, ref_time);
394 }
#define BOTAN_ASSERT_NONNULL(ptr)
Definition: assert.h:107
CertificatePathStatusCodes check_crl(const std::vector< std::shared_ptr< const X509_Certificate > > &cert_path, const std::vector< std::shared_ptr< const X509_CRL > > &crls, std::chrono::system_clock::time_point ref_time)
Definition: x509path.cpp:300

References BOTAN_ASSERT_NONNULL, and check_crl().

◆ check_crl() [2/2]

CertificatePathStatusCodes Botan::PKIX::check_crl ( const std::vector< std::shared_ptr< const X509_Certificate > > &  cert_path,
const std::vector< std::shared_ptr< const X509_CRL > > &  crls,
std::chrono::system_clock::time_point  ref_time 
)

Check CRLs for revocation information

Parameters
cert_pathpath already validated by check_chain
crlsthe list of CRLs to check, it is assumed that crls[i] (if not null) is the associated CRL for the subject in cert_path[i].
ref_timewhatever time you want to perform the validation against (normally current system clock)
Returns
revocation status

Definition at line 300 of file x509path.cpp.

303 {
304 if(cert_path.empty())
305 throw Invalid_Argument("PKIX::check_crl cert_path empty");
306
307 CertificatePathStatusCodes cert_status(cert_path.size());
308 const X509_Time validation_time(ref_time);
309
310 for(size_t i = 0; i != cert_path.size() - 1; ++i)
311 {
312 std::set<Certificate_Status_Code>& status = cert_status.at(i);
313
314 if(i < crls.size() && crls.at(i))
315 {
316 std::shared_ptr<const X509_Certificate> subject = cert_path.at(i);
317 std::shared_ptr<const X509_Certificate> ca = cert_path.at(i+1);
318
319 if(!ca->allowed_usage(CRL_SIGN))
320 status.insert(Certificate_Status_Code::CA_CERT_NOT_FOR_CRL_ISSUER);
321
322 if(validation_time < crls[i]->this_update())
323 status.insert(Certificate_Status_Code::CRL_NOT_YET_VALID);
324
325 if(validation_time > crls[i]->next_update())
326 status.insert(Certificate_Status_Code::CRL_HAS_EXPIRED);
327
328 if(crls[i]->check_signature(ca->subject_public_key()) == false)
329 status.insert(Certificate_Status_Code::CRL_BAD_SIGNATURE);
330
331 status.insert(Certificate_Status_Code::VALID_CRL_CHECKED);
332
333 if(crls[i]->is_revoked(*subject))
334 status.insert(Certificate_Status_Code::CERT_IS_REVOKED);
335
336 std::string dp = subject->crl_distribution_point();
337 if(!dp.empty())
338 {
339 if(dp != crls[i]->crl_issuing_distribution_point())
340 {
341 status.insert(Certificate_Status_Code::NO_MATCHING_CRLDP);
342 }
343 }
344
345 for(const auto& extension : crls[i]->extensions().extensions())
346 {
347 // XXX this is wrong - the OID might be defined but the extention not full parsed
348 // for example see #1652
349
350 // is the extension critical and unknown?
351 if(extension.second && OIDS::oid2str_or_empty(extension.first->oid_of()) == "")
352 {
353 /* NIST Certificate Path Valiadation Testing document: "When an implementation does not recognize a critical extension in the
354 * crlExtensions field, it shall assume that identified certificates have been revoked and are no longer valid"
355 */
356 status.insert(Certificate_Status_Code::CERT_IS_REVOKED);
357 }
358 }
359
360 }
361 }
362
363 while(cert_status.size() > 0 && cert_status.back().empty())
364 cert_status.pop_back();
365
366 return cert_status;
367 }
@ CRL_SIGN
Definition: ffi.h:1572

References Botan::CA_CERT_NOT_FOR_CRL_ISSUER, Botan::CERT_IS_REVOKED, Botan::CRL_BAD_SIGNATURE, Botan::CRL_HAS_EXPIRED, Botan::CRL_NOT_YET_VALID, Botan::CRL_SIGN, Botan::NO_MATCHING_CRLDP, Botan::OIDS::oid2str_or_empty(), and Botan::VALID_CRL_CHECKED.

Referenced by check_crl(), and Botan::x509_path_validate().

◆ check_ocsp()

CertificatePathStatusCodes Botan::PKIX::check_ocsp ( const std::vector< std::shared_ptr< const X509_Certificate > > &  cert_path,
const std::vector< std::shared_ptr< const OCSP::Response > > &  ocsp_responses,
const std::vector< Certificate_Store * > &  certstores,
std::chrono::system_clock::time_point  ref_time,
std::chrono::seconds  max_ocsp_age = std::chrono::seconds::zero() 
)

Check OCSP responses for revocation information

Parameters
cert_pathpath already validated by check_chain
ocsp_responsesthe OCSP responses to consider
certstorestrusted roots
ref_timewhatever time you want to perform the validation against (normally current system clock)
max_ocsp_agemaximum age of OCSP responses w/o next_update. If zero, there is no maximum age
Returns
revocation status

Definition at line 245 of file x509path.cpp.

250 {
251 if(cert_path.empty())
252 throw Invalid_Argument("PKIX::check_ocsp cert_path empty");
253
254 CertificatePathStatusCodes cert_status(cert_path.size() - 1);
255
256 for(size_t i = 0; i != cert_path.size() - 1; ++i)
257 {
258 std::set<Certificate_Status_Code>& status = cert_status.at(i);
259
260 std::shared_ptr<const X509_Certificate> subject = cert_path.at(i);
261 std::shared_ptr<const X509_Certificate> ca = cert_path.at(i+1);
262
263 if(i < ocsp_responses.size() && (ocsp_responses.at(i) != nullptr)
264 && (ocsp_responses.at(i)->status() == OCSP::Response_Status_Code::Successful))
265 {
266 try
267 {
268 // When verifying intermediate certificates we need to truncate the
269 // cert_path so that the intermediate under investigation becomes the
270 // last certificate in the chain.
271 std::vector<std::shared_ptr<const X509_Certificate>> ocsp_cert_path(cert_path.begin() + i, cert_path.end());
272 Certificate_Status_Code ocsp_signature_status = ocsp_responses.at(i)->check_signature(trusted_certstores, ocsp_cert_path);
273
274 if(ocsp_signature_status == Certificate_Status_Code::OCSP_SIGNATURE_OK)
275 {
276 // Signature ok, so check the claimed status
277 Certificate_Status_Code ocsp_status = ocsp_responses.at(i)->status_for(*ca, *subject, ref_time, max_ocsp_age);
278 status.insert(ocsp_status);
279 }
280 else
281 {
282 // Some signature problem
283 status.insert(ocsp_signature_status);
284 }
285 }
286 catch(Exception&)
287 {
288 status.insert(Certificate_Status_Code::OCSP_RESPONSE_INVALID);
289 }
290 }
291 }
292
293 while(cert_status.size() > 0 && cert_status.back().empty())
294 cert_status.pop_back();
295
296 return cert_status;
297 }

References Botan::OCSP_RESPONSE_INVALID, Botan::OCSP_SIGNATURE_OK, and Botan::OCSP::Successful.

Referenced by Botan::x509_path_validate().

◆ merge_revocation_status()

void Botan::PKIX::merge_revocation_status ( CertificatePathStatusCodes chain_status,
const CertificatePathStatusCodes crl_status,
const CertificatePathStatusCodes ocsp_status,
bool  require_rev_on_end_entity,
bool  require_rev_on_intermediates 
)

Merge the results from CRL and/or OCSP checks into chain_status

Parameters
chain_statusthe certificate status
crl_statusresults from check_crl
ocsp_statusresults from check_ocsp
require_rev_on_end_entityrequire valid CRL or OCSP on end-entity cert
require_rev_on_intermediatesrequire valid CRL or OCSP on all intermediate certificates

Definition at line 807 of file x509path.cpp.

812 {
813 if(chain_status.empty())
814 throw Invalid_Argument("PKIX::merge_revocation_status chain_status was empty");
815
816 for(size_t i = 0; i != chain_status.size() - 1; ++i)
817 {
818 bool had_crl = false, had_ocsp = false;
819
820 if(i < crl.size() && crl[i].size() > 0)
821 {
822 for(auto&& code : crl[i])
823 {
824 if(code == Certificate_Status_Code::VALID_CRL_CHECKED)
825 {
826 had_crl = true;
827 }
828 chain_status[i].insert(code);
829 }
830 }
831
832 if(i < ocsp.size() && ocsp[i].size() > 0)
833 {
834 for(auto&& code : ocsp[i])
835 {
836 if(code == Certificate_Status_Code::OCSP_RESPONSE_GOOD ||
837 code == Certificate_Status_Code::OCSP_NO_REVOCATION_URL || // softfail
838 code == Certificate_Status_Code::OCSP_SERVER_NOT_AVAILABLE) // softfail
839 {
840 had_ocsp = true;
841 }
842
843 chain_status[i].insert(code);
844 }
845 }
846
847 if(had_crl == false && had_ocsp == false)
848 {
849 if((require_rev_on_end_entity && i == 0) ||
850 (require_rev_on_intermediates && i > 0))
851 {
852 chain_status[i].insert(Certificate_Status_Code::NO_REVOCATION_DATA);
853 }
854 }
855 }
856 }

References Botan::NO_REVOCATION_DATA, Botan::OCSP_NO_REVOCATION_URL, Botan::OCSP_RESPONSE_GOOD, Botan::OCSP_SERVER_NOT_AVAILABLE, and Botan::VALID_CRL_CHECKED.

Referenced by Botan::x509_path_validate().

◆ overall_status()

Certificate_Status_Code Botan::PKIX::overall_status ( const CertificatePathStatusCodes cert_status)

Find overall status (OK, error) of a validation

Parameters
cert_statusresult of merge_revocation_status or check_chain

Definition at line 858 of file x509path.cpp.

859 {
860 if(cert_status.empty())
861 throw Invalid_Argument("PKIX::overall_status empty cert status");
862
863 Certificate_Status_Code overall_status = Certificate_Status_Code::OK;
864
865 // take the "worst" error as overall
866 for(const std::set<Certificate_Status_Code>& s : cert_status)
867 {
868 if(!s.empty())
869 {
870 auto worst = *s.rbegin();
871 // Leave informative OCSP/CRL confirmations on cert-level status only
872 if(worst >= Certificate_Status_Code::FIRST_ERROR_STATUS && worst > overall_status)
873 {
874 overall_status = worst;
875 }
876 }
877 }
878 return overall_status;
879 }
Certificate_Status_Code overall_status(const CertificatePathStatusCodes &cert_status)
Definition: x509path.cpp:858

References Botan::FIRST_ERROR_STATUS, Botan::OK, and overall_status().

Referenced by overall_status().