/src/botan/src/lib/x509/x509path.cpp
Line | Count | Source (jump to first uncovered line) |
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 | | #include <botan/x509_ext.h> |
11 | | #include <botan/pk_keys.h> |
12 | | #include <botan/ocsp.h> |
13 | | #include <botan/oids.h> |
14 | | #include <algorithm> |
15 | | #include <chrono> |
16 | | #include <vector> |
17 | | #include <set> |
18 | | #include <string> |
19 | | #include <sstream> |
20 | | |
21 | | #if defined(BOTAN_HAS_ONLINE_REVOCATION_CHECKS) |
22 | | #include <future> |
23 | | #include <botan/http_util.h> |
24 | | #endif |
25 | | |
26 | | namespace Botan { |
27 | | |
28 | | /* |
29 | | * PKIX path validation |
30 | | */ |
31 | | CertificatePathStatusCodes |
32 | | PKIX::check_chain(const std::vector<std::shared_ptr<const X509_Certificate>>& cert_path, |
33 | | std::chrono::system_clock::time_point ref_time, |
34 | | const std::string& hostname, |
35 | | Usage_Type usage, |
36 | | size_t min_signature_algo_strength, |
37 | | const std::set<std::string>& trusted_hashes) |
38 | 0 | { |
39 | 0 | if(cert_path.empty()) |
40 | 0 | throw Invalid_Argument("PKIX::check_chain cert_path empty"); |
41 | 0 | |
42 | 0 | const bool self_signed_ee_cert = (cert_path.size() == 1); |
43 | 0 |
|
44 | 0 | X509_Time validation_time(ref_time); |
45 | 0 |
|
46 | 0 | CertificatePathStatusCodes cert_status(cert_path.size()); |
47 | 0 |
|
48 | 0 | if(!hostname.empty() && !cert_path[0]->matches_dns_name(hostname)) |
49 | 0 | cert_status[0].insert(Certificate_Status_Code::CERT_NAME_NOMATCH); |
50 | 0 |
|
51 | 0 | if(!cert_path[0]->allowed_usage(usage)) |
52 | 0 | cert_status[0].insert(Certificate_Status_Code::INVALID_USAGE); |
53 | 0 |
|
54 | 0 | if(cert_path[0]->is_CA_cert() == false && |
55 | 0 | cert_path[0]->has_constraints(KEY_CERT_SIGN)) |
56 | 0 | { |
57 | 0 | /* |
58 | 0 | "If the keyCertSign bit is asserted, then the cA bit in the |
59 | 0 | basic constraints extension (Section 4.2.1.9) MUST also be |
60 | 0 | asserted." - RFC 5280 |
61 | 0 |
|
62 | 0 | We don't bother doing this check on the rest of the path since they |
63 | 0 | must have the cA bit asserted or the validation will fail anyway. |
64 | 0 | */ |
65 | 0 | cert_status[0].insert(Certificate_Status_Code::INVALID_USAGE); |
66 | 0 | } |
67 | 0 |
|
68 | 0 | for(size_t i = 0; i != cert_path.size(); ++i) |
69 | 0 | { |
70 | 0 | std::set<Certificate_Status_Code>& status = cert_status.at(i); |
71 | 0 |
|
72 | 0 | const bool at_self_signed_root = (i == cert_path.size() - 1); |
73 | 0 |
|
74 | 0 | const std::shared_ptr<const X509_Certificate>& subject = cert_path[i]; |
75 | 0 |
|
76 | 0 | const std::shared_ptr<const X509_Certificate>& issuer = cert_path[at_self_signed_root ? (i) : (i + 1)]; |
77 | 0 |
|
78 | 0 | if(at_self_signed_root && (issuer->is_self_signed() == false)) |
79 | 0 | { |
80 | 0 | status.insert(Certificate_Status_Code::CHAIN_LACKS_TRUST_ROOT); |
81 | 0 | } |
82 | 0 |
|
83 | 0 | if(subject->issuer_dn() != issuer->subject_dn()) |
84 | 0 | { |
85 | 0 | status.insert(Certificate_Status_Code::CHAIN_NAME_MISMATCH); |
86 | 0 | } |
87 | 0 |
|
88 | 0 | // Check the serial number |
89 | 0 | if(subject->is_serial_negative()) |
90 | 0 | { |
91 | 0 | status.insert(Certificate_Status_Code::CERT_SERIAL_NEGATIVE); |
92 | 0 | } |
93 | 0 |
|
94 | 0 | // Check the subject's DN components' length |
95 | 0 |
|
96 | 0 | for(const auto& dn_pair : subject->subject_dn().dn_info()) |
97 | 0 | { |
98 | 0 | const size_t dn_ub = X509_DN::lookup_ub(dn_pair.first); |
99 | 0 | // dn_pair = <OID,str> |
100 | 0 | if(dn_ub > 0 && dn_pair.second.size() > dn_ub) |
101 | 0 | { |
102 | 0 | status.insert(Certificate_Status_Code::DN_TOO_LONG); |
103 | 0 | } |
104 | 0 | } |
105 | 0 |
|
106 | 0 | // Check all certs for valid time range |
107 | 0 | if(validation_time < subject->not_before()) |
108 | 0 | status.insert(Certificate_Status_Code::CERT_NOT_YET_VALID); |
109 | 0 |
|
110 | 0 | if(validation_time > subject->not_after()) |
111 | 0 | status.insert(Certificate_Status_Code::CERT_HAS_EXPIRED); |
112 | 0 |
|
113 | 0 | // Check issuer constraints |
114 | 0 | if(!issuer->is_CA_cert() && !self_signed_ee_cert) |
115 | 0 | status.insert(Certificate_Status_Code::CA_CERT_NOT_FOR_CERT_ISSUER); |
116 | 0 |
|
117 | 0 | std::unique_ptr<Public_Key> issuer_key(issuer->subject_public_key()); |
118 | 0 |
|
119 | 0 | // Check the signature algorithm is known |
120 | 0 | if(OIDS::oid2str_or_empty(subject->signature_algorithm().get_oid()).empty()) |
121 | 0 | { |
122 | 0 | status.insert(Certificate_Status_Code::SIGNATURE_ALGO_UNKNOWN); |
123 | 0 | } |
124 | 0 | else |
125 | 0 | { |
126 | 0 | // only perform the following checks if the signature algorithm is known |
127 | 0 | if(!issuer_key) |
128 | 0 | { |
129 | 0 | status.insert(Certificate_Status_Code::CERT_PUBKEY_INVALID); |
130 | 0 | } |
131 | 0 | else |
132 | 0 | { |
133 | 0 | const Certificate_Status_Code sig_status = subject->verify_signature(*issuer_key); |
134 | 0 |
|
135 | 0 | if(sig_status != Certificate_Status_Code::VERIFIED) |
136 | 0 | status.insert(sig_status); |
137 | 0 |
|
138 | 0 | if(issuer_key->estimated_strength() < min_signature_algo_strength) |
139 | 0 | status.insert(Certificate_Status_Code::SIGNATURE_METHOD_TOO_WEAK); |
140 | 0 | } |
141 | 0 |
|
142 | 0 | // Ignore untrusted hashes on self-signed roots |
143 | 0 | if(trusted_hashes.size() > 0 && !at_self_signed_root) |
144 | 0 | { |
145 | 0 | if(trusted_hashes.count(subject->hash_used_for_signature()) == 0) |
146 | 0 | status.insert(Certificate_Status_Code::UNTRUSTED_HASH); |
147 | 0 | } |
148 | 0 | } |
149 | 0 |
|
150 | 0 | // Check cert extensions |
151 | 0 | Extensions extensions = subject->v3_extensions(); |
152 | 0 | const auto& extensions_vec = extensions.extensions(); |
153 | 0 | if(subject->x509_version() < 3 && !extensions_vec.empty()) |
154 | 0 | { |
155 | 0 | status.insert(Certificate_Status_Code::EXT_IN_V1_V2_CERT); |
156 | 0 | } |
157 | 0 | for(auto& extension : extensions_vec) |
158 | 0 | { |
159 | 0 | extension.first->validate(*subject, *issuer, cert_path, cert_status, i); |
160 | 0 | } |
161 | 0 | if(extensions.extensions().size() != extensions.get_extension_oids().size()) |
162 | 0 | { |
163 | 0 | status.insert(Certificate_Status_Code::DUPLICATE_CERT_EXTENSION); |
164 | 0 | } |
165 | 0 | } |
166 | 0 |
|
167 | 0 | // path len check |
168 | 0 | size_t max_path_length = cert_path.size(); |
169 | 0 | for(size_t i = cert_path.size() - 1; i > 0 ; --i) |
170 | 0 | { |
171 | 0 | std::set<Certificate_Status_Code>& status = cert_status.at(i); |
172 | 0 | const std::shared_ptr<const X509_Certificate>& subject = cert_path[i]; |
173 | 0 |
|
174 | 0 | /* |
175 | 0 | * If the certificate was not self-issued, verify that max_path_length is |
176 | 0 | * greater than zero and decrement max_path_length by 1. |
177 | 0 | */ |
178 | 0 | if(subject->subject_dn() != subject->issuer_dn()) |
179 | 0 | { |
180 | 0 | if(max_path_length > 0) |
181 | 0 | { |
182 | 0 | --max_path_length; |
183 | 0 | } |
184 | 0 | else |
185 | 0 | { |
186 | 0 | status.insert(Certificate_Status_Code::CERT_CHAIN_TOO_LONG); |
187 | 0 | } |
188 | 0 | } |
189 | 0 |
|
190 | 0 | /* |
191 | 0 | * If pathLenConstraint is present in the certificate and is less than max_path_length, |
192 | 0 | * set max_path_length to the value of pathLenConstraint. |
193 | 0 | */ |
194 | 0 | if(subject->path_limit() != Cert_Extension::NO_CERT_PATH_LIMIT && subject->path_limit() < max_path_length) |
195 | 0 | { |
196 | 0 | max_path_length = subject->path_limit(); |
197 | 0 | } |
198 | 0 | } |
199 | 0 |
|
200 | 0 | return cert_status; |
201 | 0 | } |
202 | | |
203 | | CertificatePathStatusCodes |
204 | | PKIX::check_ocsp(const std::vector<std::shared_ptr<const X509_Certificate>>& cert_path, |
205 | | const std::vector<std::shared_ptr<const OCSP::Response>>& ocsp_responses, |
206 | | const std::vector<Certificate_Store*>& trusted_certstores, |
207 | | std::chrono::system_clock::time_point ref_time, |
208 | | std::chrono::seconds max_ocsp_age) |
209 | 0 | { |
210 | 0 | if(cert_path.empty()) |
211 | 0 | throw Invalid_Argument("PKIX::check_ocsp cert_path empty"); |
212 | 0 | |
213 | 0 | CertificatePathStatusCodes cert_status(cert_path.size() - 1); |
214 | 0 |
|
215 | 0 | for(size_t i = 0; i != cert_path.size() - 1; ++i) |
216 | 0 | { |
217 | 0 | std::set<Certificate_Status_Code>& status = cert_status.at(i); |
218 | 0 |
|
219 | 0 | std::shared_ptr<const X509_Certificate> subject = cert_path.at(i); |
220 | 0 | std::shared_ptr<const X509_Certificate> ca = cert_path.at(i+1); |
221 | 0 |
|
222 | 0 | if(i < ocsp_responses.size() && (ocsp_responses.at(i) != nullptr) |
223 | 0 | && (ocsp_responses.at(i)->status() == OCSP::Response_Status_Code::Successful)) |
224 | 0 | { |
225 | 0 | try |
226 | 0 | { |
227 | 0 | Certificate_Status_Code ocsp_signature_status = ocsp_responses.at(i)->check_signature(trusted_certstores, cert_path); |
228 | 0 |
|
229 | 0 | if(ocsp_signature_status == Certificate_Status_Code::OCSP_SIGNATURE_OK) |
230 | 0 | { |
231 | 0 | // Signature ok, so check the claimed status |
232 | 0 | Certificate_Status_Code ocsp_status = ocsp_responses.at(i)->status_for(*ca, *subject, ref_time, max_ocsp_age); |
233 | 0 | status.insert(ocsp_status); |
234 | 0 | } |
235 | 0 | else |
236 | 0 | { |
237 | 0 | // Some signature problem |
238 | 0 | status.insert(ocsp_signature_status); |
239 | 0 | } |
240 | 0 | } |
241 | 0 | catch(Exception&) |
242 | 0 | { |
243 | 0 | status.insert(Certificate_Status_Code::OCSP_RESPONSE_INVALID); |
244 | 0 | } |
245 | 0 | } |
246 | 0 | } |
247 | 0 |
|
248 | 0 | while(cert_status.size() > 0 && cert_status.back().empty()) |
249 | 0 | cert_status.pop_back(); |
250 | 0 |
|
251 | 0 | return cert_status; |
252 | 0 | } |
253 | | |
254 | | CertificatePathStatusCodes |
255 | | PKIX::check_crl(const std::vector<std::shared_ptr<const X509_Certificate>>& cert_path, |
256 | | const std::vector<std::shared_ptr<const X509_CRL>>& crls, |
257 | | std::chrono::system_clock::time_point ref_time) |
258 | 0 | { |
259 | 0 | if(cert_path.empty()) |
260 | 0 | throw Invalid_Argument("PKIX::check_crl cert_path empty"); |
261 | 0 | |
262 | 0 | CertificatePathStatusCodes cert_status(cert_path.size()); |
263 | 0 | const X509_Time validation_time(ref_time); |
264 | 0 |
|
265 | 0 | for(size_t i = 0; i != cert_path.size() - 1; ++i) |
266 | 0 | { |
267 | 0 | std::set<Certificate_Status_Code>& status = cert_status.at(i); |
268 | 0 |
|
269 | 0 | if(i < crls.size() && crls.at(i)) |
270 | 0 | { |
271 | 0 | std::shared_ptr<const X509_Certificate> subject = cert_path.at(i); |
272 | 0 | std::shared_ptr<const X509_Certificate> ca = cert_path.at(i+1); |
273 | 0 |
|
274 | 0 | if(!ca->allowed_usage(CRL_SIGN)) |
275 | 0 | status.insert(Certificate_Status_Code::CA_CERT_NOT_FOR_CRL_ISSUER); |
276 | 0 |
|
277 | 0 | if(validation_time < crls[i]->this_update()) |
278 | 0 | status.insert(Certificate_Status_Code::CRL_NOT_YET_VALID); |
279 | 0 |
|
280 | 0 | if(validation_time > crls[i]->next_update()) |
281 | 0 | status.insert(Certificate_Status_Code::CRL_HAS_EXPIRED); |
282 | 0 |
|
283 | 0 | if(crls[i]->check_signature(ca->subject_public_key()) == false) |
284 | 0 | status.insert(Certificate_Status_Code::CRL_BAD_SIGNATURE); |
285 | 0 |
|
286 | 0 | status.insert(Certificate_Status_Code::VALID_CRL_CHECKED); |
287 | 0 |
|
288 | 0 | if(crls[i]->is_revoked(*subject)) |
289 | 0 | status.insert(Certificate_Status_Code::CERT_IS_REVOKED); |
290 | 0 |
|
291 | 0 | std::string dp = subject->crl_distribution_point(); |
292 | 0 | if(!dp.empty()) |
293 | 0 | { |
294 | 0 | if(dp != crls[i]->crl_issuing_distribution_point()) |
295 | 0 | { |
296 | 0 | status.insert(Certificate_Status_Code::NO_MATCHING_CRLDP); |
297 | 0 | } |
298 | 0 | } |
299 | 0 |
|
300 | 0 | for(const auto& extension : crls[i]->extensions().extensions()) |
301 | 0 | { |
302 | 0 | // XXX this is wrong - the OID might be defined but the extention not full parsed |
303 | 0 | // for example see #1652 |
304 | 0 |
|
305 | 0 | // is the extension critical and unknown? |
306 | 0 | if(extension.second && OIDS::oid2str_or_empty(extension.first->oid_of()) == "") |
307 | 0 | { |
308 | 0 | /* NIST Certificate Path Valiadation Testing document: "When an implementation does not recognize a critical extension in the |
309 | 0 | * crlExtensions field, it shall assume that identified certificates have been revoked and are no longer valid" |
310 | 0 | */ |
311 | 0 | status.insert(Certificate_Status_Code::CERT_IS_REVOKED); |
312 | 0 | } |
313 | 0 | } |
314 | 0 |
|
315 | 0 | } |
316 | 0 | } |
317 | 0 |
|
318 | 0 | while(cert_status.size() > 0 && cert_status.back().empty()) |
319 | 0 | cert_status.pop_back(); |
320 | 0 |
|
321 | 0 | return cert_status; |
322 | 0 | } |
323 | | |
324 | | CertificatePathStatusCodes |
325 | | PKIX::check_crl(const std::vector<std::shared_ptr<const X509_Certificate>>& cert_path, |
326 | | const std::vector<Certificate_Store*>& certstores, |
327 | | std::chrono::system_clock::time_point ref_time) |
328 | 0 | { |
329 | 0 | if(cert_path.empty()) |
330 | 0 | throw Invalid_Argument("PKIX::check_crl cert_path empty"); |
331 | 0 | |
332 | 0 | if(certstores.empty()) |
333 | 0 | throw Invalid_Argument("PKIX::check_crl certstores empty"); |
334 | 0 | |
335 | 0 | std::vector<std::shared_ptr<const X509_CRL>> crls(cert_path.size()); |
336 | 0 |
|
337 | 0 | for(size_t i = 0; i != cert_path.size(); ++i) |
338 | 0 | { |
339 | 0 | BOTAN_ASSERT_NONNULL(cert_path[i]); |
340 | 0 | for(size_t c = 0; c != certstores.size(); ++c) |
341 | 0 | { |
342 | 0 | crls[i] = certstores[c]->find_crl_for(*cert_path[i]); |
343 | 0 | if(crls[i]) |
344 | 0 | break; |
345 | 0 | } |
346 | 0 | } |
347 | 0 |
|
348 | 0 | return PKIX::check_crl(cert_path, crls, ref_time); |
349 | 0 | } |
350 | | |
351 | | #if defined(BOTAN_HAS_ONLINE_REVOCATION_CHECKS) |
352 | | |
353 | | CertificatePathStatusCodes |
354 | | PKIX::check_ocsp_online(const std::vector<std::shared_ptr<const X509_Certificate>>& cert_path, |
355 | | const std::vector<Certificate_Store*>& trusted_certstores, |
356 | | std::chrono::system_clock::time_point ref_time, |
357 | | std::chrono::milliseconds timeout, |
358 | | bool ocsp_check_intermediate_CAs, |
359 | | std::chrono::seconds max_ocsp_age) |
360 | 0 | { |
361 | 0 | if(cert_path.empty()) |
362 | 0 | throw Invalid_Argument("PKIX::check_ocsp_online cert_path empty"); |
363 | 0 | |
364 | 0 | std::vector<std::future<std::shared_ptr<const OCSP::Response>>> ocsp_response_futures; |
365 | 0 |
|
366 | 0 | size_t to_ocsp = 1; |
367 | 0 |
|
368 | 0 | if(ocsp_check_intermediate_CAs) |
369 | 0 | to_ocsp = cert_path.size() - 1; |
370 | 0 | if(cert_path.size() == 1) |
371 | 0 | to_ocsp = 0; |
372 | 0 |
|
373 | 0 | for(size_t i = 0; i < to_ocsp; ++i) |
374 | 0 | { |
375 | 0 | const std::shared_ptr<const X509_Certificate>& subject = cert_path.at(i); |
376 | 0 | const std::shared_ptr<const X509_Certificate>& issuer = cert_path.at(i+1); |
377 | 0 |
|
378 | 0 | if(subject->ocsp_responder() == "") |
379 | 0 | { |
380 | 0 | ocsp_response_futures.emplace_back(std::async(std::launch::deferred, [&]() -> std::shared_ptr<const OCSP::Response> { |
381 | 0 | return std::make_shared<const OCSP::Response>(Certificate_Status_Code::OCSP_NO_REVOCATION_URL); |
382 | 0 | })); |
383 | 0 | } |
384 | 0 | else |
385 | 0 | { |
386 | 0 | ocsp_response_futures.emplace_back(std::async(std::launch::async, [&]() -> std::shared_ptr<const OCSP::Response> { |
387 | 0 | OCSP::Request req(*issuer, BigInt::decode(subject->serial_number())); |
388 | 0 |
|
389 | 0 | HTTP::Response http; |
390 | 0 | try |
391 | 0 | { |
392 | 0 | http = HTTP::POST_sync(subject->ocsp_responder(), |
393 | 0 | "application/ocsp-request", |
394 | 0 | req.BER_encode(), |
395 | 0 | /*redirects*/1, |
396 | 0 | timeout); |
397 | 0 | } |
398 | 0 | catch(std::exception&) |
399 | 0 | { |
400 | 0 | // log e.what() ? |
401 | 0 | } |
402 | 0 | if (http.status_code() != 200) |
403 | 0 | return std::make_shared<const OCSP::Response>(Certificate_Status_Code::OCSP_SERVER_NOT_AVAILABLE); |
404 | 0 | // Check the MIME type? |
405 | 0 | |
406 | 0 | return std::make_shared<const OCSP::Response>(http.body()); |
407 | 0 | })); |
408 | 0 | } |
409 | 0 | } |
410 | 0 |
|
411 | 0 | std::vector<std::shared_ptr<const OCSP::Response>> ocsp_responses; |
412 | 0 |
|
413 | 0 | for(size_t i = 0; i < ocsp_response_futures.size(); ++i) |
414 | 0 | { |
415 | 0 | ocsp_responses.push_back(ocsp_response_futures[i].get()); |
416 | 0 | } |
417 | 0 |
|
418 | 0 | return PKIX::check_ocsp(cert_path, ocsp_responses, trusted_certstores, ref_time, max_ocsp_age); |
419 | 0 | } |
420 | | |
421 | | CertificatePathStatusCodes |
422 | | PKIX::check_crl_online(const std::vector<std::shared_ptr<const X509_Certificate>>& cert_path, |
423 | | const std::vector<Certificate_Store*>& certstores, |
424 | | Certificate_Store_In_Memory* crl_store, |
425 | | std::chrono::system_clock::time_point ref_time, |
426 | | std::chrono::milliseconds timeout) |
427 | 0 | { |
428 | 0 | if(cert_path.empty()) |
429 | 0 | throw Invalid_Argument("PKIX::check_crl_online cert_path empty"); |
430 | 0 | if(certstores.empty()) |
431 | 0 | throw Invalid_Argument("PKIX::check_crl_online certstores empty"); |
432 | 0 | |
433 | 0 | std::vector<std::future<std::shared_ptr<const X509_CRL>>> future_crls; |
434 | 0 | std::vector<std::shared_ptr<const X509_CRL>> crls(cert_path.size()); |
435 | 0 |
|
436 | 0 | for(size_t i = 0; i != cert_path.size(); ++i) |
437 | 0 | { |
438 | 0 | const std::shared_ptr<const X509_Certificate>& cert = cert_path.at(i); |
439 | 0 | for(size_t c = 0; c != certstores.size(); ++c) |
440 | 0 | { |
441 | 0 | crls[i] = certstores[c]->find_crl_for(*cert); |
442 | 0 | if(crls[i]) |
443 | 0 | break; |
444 | 0 | } |
445 | 0 |
|
446 | 0 | // TODO: check if CRL is expired and re-request? |
447 | 0 |
|
448 | 0 | // Only request if we don't already have a CRL |
449 | 0 | if(crls[i]) |
450 | 0 | { |
451 | 0 | /* |
452 | 0 | We already have a CRL, so just insert this empty one to hold a place in the vector |
453 | 0 | so that indexes match up |
454 | 0 | */ |
455 | 0 | future_crls.emplace_back(std::future<std::shared_ptr<const X509_CRL>>()); |
456 | 0 | } |
457 | 0 | else if(cert->crl_distribution_point() == "") |
458 | 0 | { |
459 | 0 | // Avoid creating a thread for this case |
460 | 0 | future_crls.emplace_back(std::async(std::launch::deferred, [&]() -> std::shared_ptr<const X509_CRL> { |
461 | 0 | throw Not_Implemented("No CRL distribution point for this certificate"); |
462 | 0 | })); |
463 | 0 | } |
464 | 0 | else |
465 | 0 | { |
466 | 0 | future_crls.emplace_back(std::async(std::launch::async, [&]() -> std::shared_ptr<const X509_CRL> { |
467 | 0 | auto http = HTTP::GET_sync(cert->crl_distribution_point(), |
468 | 0 | /*redirects*/ 1, timeout); |
469 | 0 |
|
470 | 0 | http.throw_unless_ok(); |
471 | 0 | // check the mime type? |
472 | 0 | return std::make_shared<const X509_CRL>(http.body()); |
473 | 0 | })); |
474 | 0 | } |
475 | 0 | } |
476 | 0 |
|
477 | 0 | for(size_t i = 0; i != future_crls.size(); ++i) |
478 | 0 | { |
479 | 0 | if(future_crls[i].valid()) |
480 | 0 | { |
481 | 0 | try |
482 | 0 | { |
483 | 0 | crls[i] = future_crls[i].get(); |
484 | 0 | } |
485 | 0 | catch(std::exception&) |
486 | 0 | { |
487 | 0 | // crls[i] left null |
488 | 0 | // todo: log exception e.what() ? |
489 | 0 | } |
490 | 0 | } |
491 | 0 | } |
492 | 0 |
|
493 | 0 | const CertificatePathStatusCodes crl_status = PKIX::check_crl(cert_path, crls, ref_time); |
494 | 0 |
|
495 | 0 | if(crl_store) |
496 | 0 | { |
497 | 0 | for(size_t i = 0; i != crl_status.size(); ++i) |
498 | 0 | { |
499 | 0 | if(crl_status[i].count(Certificate_Status_Code::VALID_CRL_CHECKED)) |
500 | 0 | { |
501 | 0 | // better be non-null, we supposedly validated it |
502 | 0 | BOTAN_ASSERT_NONNULL(crls[i]); |
503 | 0 | crl_store->add_crl(crls[i]); |
504 | 0 | } |
505 | 0 | } |
506 | 0 | } |
507 | 0 |
|
508 | 0 | return crl_status; |
509 | 0 | } |
510 | | |
511 | | #endif |
512 | | |
513 | | Certificate_Status_Code |
514 | | PKIX::build_certificate_path(std::vector<std::shared_ptr<const X509_Certificate>>& cert_path, |
515 | | const std::vector<Certificate_Store*>& trusted_certstores, |
516 | | const std::shared_ptr<const X509_Certificate>& end_entity, |
517 | | const std::vector<std::shared_ptr<const X509_Certificate>>& end_entity_extra) |
518 | 0 | { |
519 | 0 | if(end_entity->is_self_signed()) |
520 | 0 | { |
521 | 0 | return Certificate_Status_Code::CANNOT_ESTABLISH_TRUST; |
522 | 0 | } |
523 | 0 | |
524 | 0 | /* |
525 | 0 | * This is an inelegant but functional way of preventing path loops |
526 | 0 | * (where C1 -> C2 -> C3 -> C1). We store a set of all the certificate |
527 | 0 | * fingerprints in the path. If there is a duplicate, we error out. |
528 | 0 | * TODO: save fingerprints in result struct? Maybe useful for blacklists, etc. |
529 | 0 | */ |
530 | 0 | std::set<std::string> certs_seen; |
531 | 0 |
|
532 | 0 | cert_path.push_back(end_entity); |
533 | 0 | certs_seen.insert(end_entity->fingerprint("SHA-256")); |
534 | 0 |
|
535 | 0 | Certificate_Store_In_Memory ee_extras; |
536 | 0 | for(size_t i = 0; i != end_entity_extra.size(); ++i) |
537 | 0 | ee_extras.add_certificate(end_entity_extra[i]); |
538 | 0 |
|
539 | 0 | // iterate until we reach a root or cannot find the issuer |
540 | 0 | for(;;) |
541 | 0 | { |
542 | 0 | const X509_Certificate& last = *cert_path.back(); |
543 | 0 | const X509_DN issuer_dn = last.issuer_dn(); |
544 | 0 | const std::vector<uint8_t> auth_key_id = last.authority_key_id(); |
545 | 0 |
|
546 | 0 | std::shared_ptr<const X509_Certificate> issuer; |
547 | 0 | bool trusted_issuer = false; |
548 | 0 |
|
549 | 0 | for(Certificate_Store* store : trusted_certstores) |
550 | 0 | { |
551 | 0 | issuer = store->find_cert(issuer_dn, auth_key_id); |
552 | 0 | if(issuer) |
553 | 0 | { |
554 | 0 | trusted_issuer = true; |
555 | 0 | break; |
556 | 0 | } |
557 | 0 | } |
558 | 0 |
|
559 | 0 | if(!issuer) |
560 | 0 | { |
561 | 0 | // fall back to searching supplemental certs |
562 | 0 | issuer = ee_extras.find_cert(issuer_dn, auth_key_id); |
563 | 0 | } |
564 | 0 |
|
565 | 0 | if(!issuer) |
566 | 0 | return Certificate_Status_Code::CERT_ISSUER_NOT_FOUND; |
567 | 0 | |
568 | 0 | const std::string fprint = issuer->fingerprint("SHA-256"); |
569 | 0 |
|
570 | 0 | if(certs_seen.count(fprint) > 0) // already seen? |
571 | 0 | { |
572 | 0 | return Certificate_Status_Code::CERT_CHAIN_LOOP; |
573 | 0 | } |
574 | 0 | |
575 | 0 | certs_seen.insert(fprint); |
576 | 0 | cert_path.push_back(issuer); |
577 | 0 |
|
578 | 0 | if(issuer->is_self_signed()) |
579 | 0 | { |
580 | 0 | if(trusted_issuer) |
581 | 0 | { |
582 | 0 | return Certificate_Status_Code::OK; |
583 | 0 | } |
584 | 0 | else |
585 | 0 | { |
586 | 0 | return Certificate_Status_Code::CANNOT_ESTABLISH_TRUST; |
587 | 0 | } |
588 | 0 | } |
589 | 0 | } |
590 | 0 | } |
591 | | |
592 | | /** |
593 | | * utilities for PKIX::build_all_certificate_paths |
594 | | */ |
595 | | namespace |
596 | | { |
597 | | // <certificate, trusted?> |
598 | | using cert_maybe_trusted = std::pair<std::shared_ptr<const X509_Certificate>,bool>; |
599 | | } |
600 | | |
601 | | /** |
602 | | * Build all possible certificate paths from the end certificate to self-signed trusted roots. |
603 | | * |
604 | | * All potentially valid paths are put into the cert_paths vector. If no potentially valid paths are found, |
605 | | * one of the encountered errors is returned arbitrarily. |
606 | | * |
607 | | * todo add a path building function that returns detailed information on errors encountered while building |
608 | | * the potentially numerous path candidates. |
609 | | * |
610 | | * Basically, a DFS is performed starting from the end certificate. A stack (vector) serves to control the DFS. |
611 | | * At the beginning of each iteration, a pair is popped from the stack that contains (1) the next certificate |
612 | | * to add to the path (2) a bool that indicates if the certificate is part of a trusted certstore. Ideally, we |
613 | | * follow the unique issuer of the current certificate until a trusted root is reached. However, the issuer DN + |
614 | | * authority key id need not be unique among the certificates used for building the path. In such a case, |
615 | | * we consider all the matching issuers by pushing <IssuerCert, trusted?> on the stack for each of them. |
616 | | * |
617 | | */ |
618 | | Certificate_Status_Code |
619 | | PKIX::build_all_certificate_paths(std::vector<std::vector<std::shared_ptr<const X509_Certificate>>>& cert_paths_out, |
620 | | const std::vector<Certificate_Store*>& trusted_certstores, |
621 | | const std::shared_ptr<const X509_Certificate>& end_entity, |
622 | | const std::vector<std::shared_ptr<const X509_Certificate>>& end_entity_extra) |
623 | 773 | { |
624 | 773 | if(!cert_paths_out.empty()) |
625 | 0 | { |
626 | 0 | throw Invalid_Argument("PKIX::build_all_certificate_paths: cert_paths_out must be empty"); |
627 | 0 | } |
628 | 773 | |
629 | 773 | if(end_entity->is_self_signed()) |
630 | 0 | { |
631 | 0 | return Certificate_Status_Code::CANNOT_ESTABLISH_TRUST; |
632 | 0 | } |
633 | 773 | |
634 | 773 | /* |
635 | 773 | * Pile up error messages |
636 | 773 | */ |
637 | 773 | std::vector<Certificate_Status_Code> stats; |
638 | 773 | |
639 | 773 | Certificate_Store_In_Memory ee_extras; |
640 | 1.55k | for(size_t i = 0; i != end_entity_extra.size(); ++i) |
641 | 782 | { |
642 | 782 | ee_extras.add_certificate(end_entity_extra[i]); |
643 | 782 | } |
644 | 773 | |
645 | 773 | /* |
646 | 773 | * This is an inelegant but functional way of preventing path loops |
647 | 773 | * (where C1 -> C2 -> C3 -> C1). We store a set of all the certificate |
648 | 773 | * fingerprints in the path. If there is a duplicate, we error out. |
649 | 773 | * TODO: save fingerprints in result struct? Maybe useful for blacklists, etc. |
650 | 773 | */ |
651 | 773 | std::set<std::string> certs_seen; |
652 | 773 | |
653 | 773 | // new certs are added and removed from the path during the DFS |
654 | 773 | // it is copied into cert_paths_out when we encounter a trusted root |
655 | 773 | std::vector<std::shared_ptr<const X509_Certificate>> path_so_far; |
656 | 773 | |
657 | 773 | // todo can we assume that the end certificate is not trusted? |
658 | 773 | std::vector<cert_maybe_trusted> stack = { {end_entity, false} }; |
659 | 773 | |
660 | 11.3k | while(!stack.empty()) |
661 | 10.6k | { |
662 | 10.6k | // found a deletion marker that guides the DFS, backtracing |
663 | 10.6k | if(stack.back().first == nullptr) |
664 | 2.34k | { |
665 | 2.34k | stack.pop_back(); |
666 | 2.34k | std::string fprint = path_so_far.back()->fingerprint("SHA-256"); |
667 | 2.34k | certs_seen.erase(fprint); |
668 | 2.34k | path_so_far.pop_back(); |
669 | 2.34k | } |
670 | 8.27k | // process next cert on the path |
671 | 8.27k | else |
672 | 8.27k | { |
673 | 8.27k | std::shared_ptr<const X509_Certificate> last = stack.back().first; |
674 | 8.27k | bool trusted = stack.back().second; |
675 | 8.27k | stack.pop_back(); |
676 | 8.27k | |
677 | 8.27k | // certificate already seen? |
678 | 8.27k | const std::string fprint = last->fingerprint("SHA-256"); |
679 | 8.27k | if(certs_seen.count(fprint) == 1) |
680 | 4.59k | { |
681 | 4.59k | stats.push_back(Certificate_Status_Code::CERT_CHAIN_LOOP); |
682 | 4.59k | // the current path ended in a loop |
683 | 4.59k | continue; |
684 | 4.59k | } |
685 | 3.67k | |
686 | 3.67k | // the current path ends here |
687 | 3.67k | if(last->is_self_signed()) |
688 | 148 | { |
689 | 148 | // found a trust anchor |
690 | 148 | if(trusted) |
691 | 0 | { |
692 | 0 | cert_paths_out.push_back(path_so_far); |
693 | 0 | cert_paths_out.back().push_back(last); |
694 | 0 |
|
695 | 0 | continue; |
696 | 0 | } |
697 | 148 | // found an untrustworthy root |
698 | 148 | else |
699 | 148 | { |
700 | 148 | stats.push_back(Certificate_Status_Code::CANNOT_ESTABLISH_TRUST); |
701 | 148 | continue; |
702 | 148 | } |
703 | 3.52k | } |
704 | 3.52k | |
705 | 3.52k | const X509_DN issuer_dn = last->issuer_dn(); |
706 | 3.52k | const std::vector<uint8_t> auth_key_id = last->authority_key_id(); |
707 | 3.52k | |
708 | 3.52k | // search for trusted issuers |
709 | 3.52k | std::vector<std::shared_ptr<const X509_Certificate>> trusted_issuers; |
710 | 3.52k | for(Certificate_Store* store : trusted_certstores) |
711 | 0 | { |
712 | 0 | auto new_issuers = store->find_all_certs(issuer_dn, auth_key_id); |
713 | 0 | trusted_issuers.insert(trusted_issuers.end(), new_issuers.begin(), new_issuers.end()); |
714 | 0 | } |
715 | 3.52k | |
716 | 3.52k | // search the supplemental certs |
717 | 3.52k | std::vector<std::shared_ptr<const X509_Certificate>> misc_issuers = |
718 | 3.52k | ee_extras.find_all_certs(issuer_dn, auth_key_id); |
719 | 3.52k | |
720 | 3.52k | // if we could not find any issuers, the current path ends here |
721 | 3.52k | if(trusted_issuers.size() + misc_issuers.size() == 0) |
722 | 1.18k | { |
723 | 1.18k | stats.push_back(Certificate_Status_Code::CERT_ISSUER_NOT_FOUND); |
724 | 1.18k | continue; |
725 | 1.18k | } |
726 | 2.34k | |
727 | 2.34k | // push the latest certificate onto the path_so_far |
728 | 2.34k | path_so_far.push_back(last); |
729 | 2.34k | certs_seen.emplace(fprint); |
730 | 2.34k | |
731 | 2.34k | // push a deletion marker on the stack for backtracing later |
732 | 2.34k | stack.push_back({std::shared_ptr<const X509_Certificate>(nullptr),false}); |
733 | 2.34k | |
734 | 2.34k | for(const auto trusted_cert : trusted_issuers) |
735 | 0 | { |
736 | 0 | stack.push_back({trusted_cert,true}); |
737 | 0 | } |
738 | 2.34k | |
739 | 2.34k | for(const auto misc : misc_issuers) |
740 | 7.50k | { |
741 | 7.50k | stack.push_back({misc,false}); |
742 | 7.50k | } |
743 | 2.34k | } |
744 | 10.6k | } |
745 | 773 | |
746 | 773 | // could not construct any potentially valid path |
747 | 773 | if(cert_paths_out.empty()) |
748 | 773 | { |
749 | 773 | if(stats.empty()) |
750 | 0 | throw Internal_Error("X509 path building failed for unknown reasons"); |
751 | 773 | else |
752 | 773 | // arbitrarily return the first error |
753 | 773 | return stats[0]; |
754 | 0 | } |
755 | 0 | else |
756 | 0 | { |
757 | 0 | return Certificate_Status_Code::OK; |
758 | 0 | } |
759 | 773 | } |
760 | | |
761 | | |
762 | | void PKIX::merge_revocation_status(CertificatePathStatusCodes& chain_status, |
763 | | const CertificatePathStatusCodes& crl, |
764 | | const CertificatePathStatusCodes& ocsp, |
765 | | bool require_rev_on_end_entity, |
766 | | bool require_rev_on_intermediates) |
767 | 0 | { |
768 | 0 | if(chain_status.empty()) |
769 | 0 | throw Invalid_Argument("PKIX::merge_revocation_status chain_status was empty"); |
770 | 0 | |
771 | 0 | for(size_t i = 0; i != chain_status.size() - 1; ++i) |
772 | 0 | { |
773 | 0 | bool had_crl = false, had_ocsp = false; |
774 | 0 |
|
775 | 0 | if(i < crl.size() && crl[i].size() > 0) |
776 | 0 | { |
777 | 0 | for(auto&& code : crl[i]) |
778 | 0 | { |
779 | 0 | if(code == Certificate_Status_Code::VALID_CRL_CHECKED) |
780 | 0 | { |
781 | 0 | had_crl = true; |
782 | 0 | } |
783 | 0 | chain_status[i].insert(code); |
784 | 0 | } |
785 | 0 | } |
786 | 0 |
|
787 | 0 | if(i < ocsp.size() && ocsp[i].size() > 0) |
788 | 0 | { |
789 | 0 | for(auto&& code : ocsp[i]) |
790 | 0 | { |
791 | 0 | if(code == Certificate_Status_Code::OCSP_RESPONSE_GOOD || |
792 | 0 | code == Certificate_Status_Code::OCSP_NO_REVOCATION_URL || // softfail |
793 | 0 | code == Certificate_Status_Code::OCSP_SERVER_NOT_AVAILABLE) // softfail |
794 | 0 | { |
795 | 0 | had_ocsp = true; |
796 | 0 | } |
797 | 0 |
|
798 | 0 | chain_status[i].insert(code); |
799 | 0 | } |
800 | 0 | } |
801 | 0 |
|
802 | 0 | if(had_crl == false && had_ocsp == false) |
803 | 0 | { |
804 | 0 | if((require_rev_on_end_entity && i == 0) || |
805 | 0 | (require_rev_on_intermediates && i > 0)) |
806 | 0 | { |
807 | 0 | chain_status[i].insert(Certificate_Status_Code::NO_REVOCATION_DATA); |
808 | 0 | } |
809 | 0 | } |
810 | 0 | } |
811 | 0 | } |
812 | | |
813 | | Certificate_Status_Code PKIX::overall_status(const CertificatePathStatusCodes& cert_status) |
814 | 0 | { |
815 | 0 | if(cert_status.empty()) |
816 | 0 | throw Invalid_Argument("PKIX::overall_status empty cert status"); |
817 | 0 | |
818 | 0 | Certificate_Status_Code overall_status = Certificate_Status_Code::OK; |
819 | 0 |
|
820 | 0 | // take the "worst" error as overall |
821 | 0 | for(const std::set<Certificate_Status_Code>& s : cert_status) |
822 | 0 | { |
823 | 0 | if(!s.empty()) |
824 | 0 | { |
825 | 0 | auto worst = *s.rbegin(); |
826 | 0 | // Leave informative OCSP/CRL confirmations on cert-level status only |
827 | 0 | if(worst >= Certificate_Status_Code::FIRST_ERROR_STATUS && worst > overall_status) |
828 | 0 | { |
829 | 0 | overall_status = worst; |
830 | 0 | } |
831 | 0 | } |
832 | 0 | } |
833 | 0 | return overall_status; |
834 | 0 | } |
835 | | |
836 | | Path_Validation_Result x509_path_validate( |
837 | | const std::vector<X509_Certificate>& end_certs, |
838 | | const Path_Validation_Restrictions& restrictions, |
839 | | const std::vector<Certificate_Store*>& trusted_roots, |
840 | | const std::string& hostname, |
841 | | Usage_Type usage, |
842 | | std::chrono::system_clock::time_point ref_time, |
843 | | std::chrono::milliseconds ocsp_timeout, |
844 | | const std::vector<std::shared_ptr<const OCSP::Response>>& ocsp_resp) |
845 | 773 | { |
846 | 773 | if(end_certs.empty()) |
847 | 0 | { |
848 | 0 | throw Invalid_Argument("x509_path_validate called with no subjects"); |
849 | 0 | } |
850 | 773 | |
851 | 773 | std::shared_ptr<const X509_Certificate> end_entity(std::make_shared<const X509_Certificate>(end_certs[0])); |
852 | 773 | std::vector<std::shared_ptr<const X509_Certificate>> end_entity_extra; |
853 | 1.55k | for(size_t i = 1; i < end_certs.size(); ++i) |
854 | 782 | { |
855 | 782 | end_entity_extra.push_back(std::make_shared<const X509_Certificate>(end_certs[i])); |
856 | 782 | } |
857 | 773 | |
858 | 773 | std::vector<std::vector<std::shared_ptr<const X509_Certificate>>> cert_paths; |
859 | 773 | Certificate_Status_Code path_building_result = PKIX::build_all_certificate_paths(cert_paths, trusted_roots, end_entity, end_entity_extra); |
860 | 773 | |
861 | 773 | // If we cannot successfully build a chain to a trusted self-signed root, stop now |
862 | 773 | if(path_building_result != Certificate_Status_Code::OK) |
863 | 773 | { |
864 | 773 | return Path_Validation_Result(path_building_result); |
865 | 773 | } |
866 | 0 | |
867 | 0 | std::vector<Path_Validation_Result> error_results; |
868 | 0 | // Try validating all the potentially valid paths and return the first one to validate properly |
869 | 0 | for(auto cert_path : cert_paths) |
870 | 0 | { |
871 | 0 | CertificatePathStatusCodes status = |
872 | 0 | PKIX::check_chain(cert_path, ref_time, |
873 | 0 | hostname, usage, |
874 | 0 | restrictions.minimum_key_strength(), |
875 | 0 | restrictions.trusted_hashes()); |
876 | 0 |
|
877 | 0 | CertificatePathStatusCodes crl_status = |
878 | 0 | PKIX::check_crl(cert_path, trusted_roots, ref_time); |
879 | 0 |
|
880 | 0 | CertificatePathStatusCodes ocsp_status; |
881 | 0 |
|
882 | 0 | if(ocsp_resp.size() > 0) |
883 | 0 | { |
884 | 0 | ocsp_status = PKIX::check_ocsp(cert_path, ocsp_resp, trusted_roots, ref_time, restrictions.max_ocsp_age()); |
885 | 0 | } |
886 | 0 |
|
887 | 0 | if(ocsp_status.empty() && ocsp_timeout != std::chrono::milliseconds(0)) |
888 | 0 | { |
889 | 0 | #if defined(BOTAN_TARGET_OS_HAS_THREADS) && defined(BOTAN_HAS_HTTP_UTIL) |
890 | 0 | ocsp_status = PKIX::check_ocsp_online(cert_path, trusted_roots, ref_time, |
891 | 0 | ocsp_timeout, restrictions.ocsp_all_intermediates()); |
892 | | #else |
893 | | ocsp_status.resize(1); |
894 | | ocsp_status[0].insert(Certificate_Status_Code::OCSP_NO_HTTP); |
895 | | #endif |
896 | | } |
897 | 0 |
|
898 | 0 | PKIX::merge_revocation_status(status, crl_status, ocsp_status, |
899 | 0 | restrictions.require_revocation_information(), |
900 | 0 | restrictions.ocsp_all_intermediates()); |
901 | 0 |
|
902 | 0 | Path_Validation_Result pvd(status, std::move(cert_path)); |
903 | 0 | if(pvd.successful_validation()) |
904 | 0 | { |
905 | 0 | return pvd; |
906 | 0 | } |
907 | 0 | else |
908 | 0 | { |
909 | 0 | error_results.push_back(std::move(pvd)); |
910 | 0 | } |
911 | 0 | } |
912 | 0 | return error_results[0]; |
913 | 0 | } |
914 | | |
915 | | Path_Validation_Result x509_path_validate( |
916 | | const X509_Certificate& end_cert, |
917 | | const Path_Validation_Restrictions& restrictions, |
918 | | const std::vector<Certificate_Store*>& trusted_roots, |
919 | | const std::string& hostname, |
920 | | Usage_Type usage, |
921 | | std::chrono::system_clock::time_point when, |
922 | | std::chrono::milliseconds ocsp_timeout, |
923 | | const std::vector<std::shared_ptr<const OCSP::Response>>& ocsp_resp) |
924 | 0 | { |
925 | 0 | std::vector<X509_Certificate> certs; |
926 | 0 | certs.push_back(end_cert); |
927 | 0 | return x509_path_validate(certs, restrictions, trusted_roots, hostname, usage, when, ocsp_timeout, ocsp_resp); |
928 | 0 | } |
929 | | |
930 | | Path_Validation_Result x509_path_validate( |
931 | | const std::vector<X509_Certificate>& end_certs, |
932 | | const Path_Validation_Restrictions& restrictions, |
933 | | const Certificate_Store& store, |
934 | | const std::string& hostname, |
935 | | Usage_Type usage, |
936 | | std::chrono::system_clock::time_point when, |
937 | | std::chrono::milliseconds ocsp_timeout, |
938 | | const std::vector<std::shared_ptr<const OCSP::Response>>& ocsp_resp) |
939 | 0 | { |
940 | 0 | std::vector<Certificate_Store*> trusted_roots; |
941 | 0 | trusted_roots.push_back(const_cast<Certificate_Store*>(&store)); |
942 | 0 |
|
943 | 0 | return x509_path_validate(end_certs, restrictions, trusted_roots, hostname, usage, when, ocsp_timeout, ocsp_resp); |
944 | 0 | } |
945 | | |
946 | | Path_Validation_Result x509_path_validate( |
947 | | const X509_Certificate& end_cert, |
948 | | const Path_Validation_Restrictions& restrictions, |
949 | | const Certificate_Store& store, |
950 | | const std::string& hostname, |
951 | | Usage_Type usage, |
952 | | std::chrono::system_clock::time_point when, |
953 | | std::chrono::milliseconds ocsp_timeout, |
954 | | const std::vector<std::shared_ptr<const OCSP::Response>>& ocsp_resp) |
955 | 0 | { |
956 | 0 | std::vector<X509_Certificate> certs; |
957 | 0 | certs.push_back(end_cert); |
958 | 0 |
|
959 | 0 | std::vector<Certificate_Store*> trusted_roots; |
960 | 0 | trusted_roots.push_back(const_cast<Certificate_Store*>(&store)); |
961 | 0 |
|
962 | 0 | return x509_path_validate(certs, restrictions, trusted_roots, hostname, usage, when, ocsp_timeout, ocsp_resp); |
963 | 0 | } |
964 | | |
965 | | Path_Validation_Restrictions::Path_Validation_Restrictions(bool require_rev, |
966 | | size_t key_strength, |
967 | | bool ocsp_intermediates, |
968 | | std::chrono::seconds max_ocsp_age) : |
969 | | m_require_revocation_information(require_rev), |
970 | | m_ocsp_all_intermediates(ocsp_intermediates), |
971 | | m_minimum_key_strength(key_strength), |
972 | | m_max_ocsp_age(max_ocsp_age) |
973 | 773 | { |
974 | 773 | if(key_strength <= 80) |
975 | 0 | { m_trusted_hashes.insert("SHA-160"); } |
976 | 773 | |
977 | 773 | m_trusted_hashes.insert("SHA-224"); |
978 | 773 | m_trusted_hashes.insert("SHA-256"); |
979 | 773 | m_trusted_hashes.insert("SHA-384"); |
980 | 773 | m_trusted_hashes.insert("SHA-512"); |
981 | 773 | } |
982 | | |
983 | | namespace { |
984 | | CertificatePathStatusCodes find_warnings(const CertificatePathStatusCodes& all_statuses) |
985 | 0 | { |
986 | 0 | CertificatePathStatusCodes warnings; |
987 | 0 | for(const auto& status_set_i : all_statuses) |
988 | 0 | { |
989 | 0 | std::set<Certificate_Status_Code> warning_set_i; |
990 | 0 | for(const auto& code : status_set_i) |
991 | 0 | { |
992 | 0 | if(code >= Certificate_Status_Code::FIRST_WARNING_STATUS && |
993 | 0 | code < Certificate_Status_Code::FIRST_ERROR_STATUS) |
994 | 0 | { |
995 | 0 | warning_set_i.insert(code); |
996 | 0 | } |
997 | 0 | } |
998 | 0 | warnings.push_back(warning_set_i); |
999 | 0 | } |
1000 | 0 | return warnings; |
1001 | 0 | } |
1002 | | } |
1003 | | |
1004 | | Path_Validation_Result::Path_Validation_Result(CertificatePathStatusCodes status, |
1005 | | std::vector<std::shared_ptr<const X509_Certificate>>&& cert_chain) : |
1006 | | m_all_status(status), |
1007 | | m_warnings(find_warnings(m_all_status)), |
1008 | | m_cert_path(cert_chain), |
1009 | | m_overall(PKIX::overall_status(m_all_status)) |
1010 | 0 | { |
1011 | 0 | } |
1012 | | |
1013 | | const X509_Certificate& Path_Validation_Result::trust_root() const |
1014 | 0 | { |
1015 | 0 | if(m_cert_path.empty()) |
1016 | 0 | throw Invalid_State("Path_Validation_Result::trust_root no path set"); |
1017 | 0 | if(result() != Certificate_Status_Code::VERIFIED) |
1018 | 0 | throw Invalid_State("Path_Validation_Result::trust_root meaningless with invalid status"); |
1019 | 0 | |
1020 | 0 | return *m_cert_path[m_cert_path.size()-1]; |
1021 | 0 | } |
1022 | | |
1023 | | std::set<std::string> Path_Validation_Result::trusted_hashes() const |
1024 | 0 | { |
1025 | 0 | std::set<std::string> hashes; |
1026 | 0 | for(size_t i = 0; i != m_cert_path.size(); ++i) |
1027 | 0 | hashes.insert(m_cert_path[i]->hash_used_for_signature()); |
1028 | 0 | return hashes; |
1029 | 0 | } |
1030 | | |
1031 | | bool Path_Validation_Result::successful_validation() const |
1032 | 773 | { |
1033 | 773 | return (result() == Certificate_Status_Code::VERIFIED || |
1034 | 773 | result() == Certificate_Status_Code::OCSP_RESPONSE_GOOD || |
1035 | 773 | result() == Certificate_Status_Code::VALID_CRL_CHECKED); |
1036 | 773 | } |
1037 | | |
1038 | | bool Path_Validation_Result::no_warnings() const |
1039 | 0 | { |
1040 | 0 | for(auto status_set_i : m_warnings) |
1041 | 0 | if(!status_set_i.empty()) |
1042 | 0 | return false; |
1043 | 0 | return true; |
1044 | 0 | } |
1045 | | |
1046 | | CertificatePathStatusCodes Path_Validation_Result::warnings() const |
1047 | 0 | { |
1048 | 0 | return m_warnings; |
1049 | 0 | } |
1050 | | |
1051 | | std::string Path_Validation_Result::result_string() const |
1052 | 773 | { |
1053 | 773 | return status_string(result()); |
1054 | 773 | } |
1055 | | |
1056 | | const char* Path_Validation_Result::status_string(Certificate_Status_Code code) |
1057 | 773 | { |
1058 | 773 | if(const char* s = to_string(code)) |
1059 | 773 | return s; |
1060 | 0 | |
1061 | 0 | return "Unknown error"; |
1062 | 0 | } |
1063 | | |
1064 | | std::string Path_Validation_Result::warnings_string() const |
1065 | 0 | { |
1066 | 0 | const std::string sep(", "); |
1067 | 0 | std::string res; |
1068 | 0 | for(size_t i = 0; i < m_warnings.size(); i++) |
1069 | 0 | { |
1070 | 0 | for(auto code : m_warnings[i]) |
1071 | 0 | res += "[" + std::to_string(i) + "] " + status_string(code) + sep; |
1072 | 0 | } |
1073 | 0 | // remove last sep |
1074 | 0 | if(res.size() >= sep.size()) |
1075 | 0 | res = res.substr(0, res.size() - sep.size()); |
1076 | 0 | return res; |
1077 | 0 | } |
1078 | | } |