1 /**
2 * X.509 Cert Path Validation
3 * 
4 * Copyright:
5 * (C) 2010-2011 Jack Lloyd
6 * (C) 2014-2015 Etienne Cimon
7 *
8 * License:
9 * Botan is released under the Simplified BSD License (see LICENSE.md)
10 */
11 module botan.cert.x509.x509path;
12 
13 import botan.constants;
14 static if (BOTAN_HAS_X509_CERTIFICATES):
15 
16 public import botan.cert.x509.ocsp;
17 public import botan.cert.x509.x509_crl;
18 import botan.cert.x509.key_constraint;
19 import botan.utils.http_util.http_util;
20 import botan.utils.parsing;
21 import botan.pubkey.pubkey;
22 import botan.asn1.oids;
23 import botan.asn1.asn1_time;
24 import std.algorithm;
25 import std.datetime;
26 import botan.utils.types;
27 import memutils.rbtree : RBTreeRef, RBTree;
28 //version(Have_vibe_d) {
29 //    import vibe.core.concurrency;
30 //}
31 //else {
32     import core.thread;
33 //}
34 import botan.cert.x509.cert_status;
35 import botan.cert.x509.x509cert;
36 import core.sync.mutex;
37 
38 /**
39 * Specifies restrictions on the PKIX path validation
40 */
41 struct PathValidationRestrictions
42 {
43 public:
44     /**
45     * Params:
46     *  require_rev = if true, revocation information is required
47     *  key_strength = is the minimum strength (in terms of
48     *          operations, eg 80 means 2^80) of a signature. Signatures
49     *          weaker than this are rejected. If more than 80, SHA-1
50     *          signatures are also rejected.
51     *  ocsp_all = where to use all intermediates
52     */
53     this(bool require_rev, size_t key_strength = 80, bool ocsp_all = false, int max_cert_chain_length = 9) 
54     {
55         m_require_revocation_information = require_rev;
56         m_ocsp_all_intermediates = ocsp_all;
57         m_minimum_key_strength = key_strength;
58         if (key_strength <= 80)
59             m_trusted_hashes.insert("SHA-160");
60         
61         m_trusted_hashes.insert("SHA-224");
62         m_trusted_hashes.insert("SHA-256");
63         m_trusted_hashes.insert("SHA-384");
64         m_trusted_hashes.insert("SHA-512");
65     }
66 
67     /**
68     * Params:
69     *  require_rev = if true, revocation information is required
70     *  minimum_key_strength = is the minimum strength (in terms of
71     *          operations, eg 80 means 2^80) of a signature. Signatures
72     *          weaker than this are rejected.
73     *  ocsp_all_intermediates = where to use all intermediates
74     *  trusted_hashes = a set of trusted hashes. Any signatures
75     *          created using a hash other than one of these will be
76     *          rejected.
77     */
78     this(bool require_rev, 
79          size_t minimum_key_strength, 
80          bool ocsp_all_intermediates, 
81 		 RBTreeRef!string trusted_hashes, 
82 		 int max_cert_chain_length = 9) 
83     {
84         m_require_revocation_information = require_rev;
85         m_ocsp_all_intermediates = ocsp_all_intermediates;
86         m_trusted_hashes.insert(trusted_hashes[]);
87         m_minimum_key_strength = minimum_key_strength;
88     }
89 
90 	@property int maxCertChainLength() const { return m_max_cert_chain_length; }
91 	@property void maxCertChainLength(int sz) { m_max_cert_chain_length = sz; }
92 
93     bool requireRevocationInformation() const
94     { return m_require_revocation_information; }
95 
96     bool ocspAllIntermediates() const
97     { return m_ocsp_all_intermediates; }
98 
99     ref const(RBTree!string) trustedHashes() const
100     { 
101 		if (m_trusted_hashes.length > 0)
102 			return m_trusted_hashes;
103 		if (m_def_trusted_hashes.length == 0) {
104 			m_def_trusted_hashes.insert("SHA-160");		
105 			m_def_trusted_hashes.insert("SHA-224");
106 			m_def_trusted_hashes.insert("SHA-256");
107 			m_def_trusted_hashes.insert("SHA-384");
108 			m_def_trusted_hashes.insert("SHA-512");
109 		}
110 		return m_def_trusted_hashes;
111 	}
112 
113     size_t minimumKeyStrength() const
114     { return m_minimum_key_strength; }
115 
116 private:
117     bool m_require_revocation_information = false;
118     bool m_ocsp_all_intermediates = false;
119 	int m_max_cert_chain_length = 9;
120 	RBTree!string m_trusted_hashes;
121     size_t m_minimum_key_strength = 80;
122 
123 	static RBTree!string m_def_trusted_hashes;
124 }
125 
126 /**
127 * Represents the result of a PKIX path validation
128 */
129 struct PathValidationResult
130 {
131 public:
132     alias Code = CertificateStatusCode;
133 
134     /**
135     * Returns: the set of hash functions you are implicitly
136     * trusting by trusting this result.
137     */
138     RBTreeRef!string trustedHashes() const
139     {
140         RBTreeRef!string hashes;
141         foreach (cert_path; m_cert_path[])
142             hashes.insert(cert_path.hashUsedForSignature());
143         return hashes;
144     }
145 
146     /**
147     * Returns: the trust root of the validation
148     */
149     X509Certificate trustRoot() const
150     {
151         import std.range : back;
152         if (m_cert_path.length == 0) return X509Certificate.init;
153         return m_cert_path[].back;
154     }
155 
156     /**
157     * Returns: the full path from subject to trust root
158     */
159     ref const(Vector!X509Certificate) certPath() const { return m_cert_path; }
160 
161     /**
162     * Returns: true iff the validation was succesful
163     */
164     bool successfulValidation() const
165     {
166         if (result() == CertificateStatusCode.VERIFIED ||
167             result() == CertificateStatusCode.OCSP_RESPONSE_GOOD)
168             return true;
169         return false;
170     }
171 
172     /**
173     * Returns: overall validation result code
174     */
175     CertificateStatusCode result() const { return m_overall; }
176 
177     /**
178     * Return a set of status codes for each certificate in the chain
179     */
180     ref const(Vector!(RBTreeRef!CertificateStatusCode)) allStatuses() const
181     { return m_all_status; }
182 
183     /**
184     * Returns: string representation of the validation result
185     */
186     string resultString() const
187     {
188         return statusString(result());
189     }
190 
191 
192     static string statusString(CertificateStatusCode code)
193     {
194         switch(code)
195         {
196             case CertificateStatusCode.VERIFIED:
197                 return "Verified";
198             case CertificateStatusCode.OCSP_RESPONSE_GOOD:
199                 return "OCSP response good";
200             case CertificateStatusCode.NO_REVOCATION_DATA:
201                 return "No revocation data";
202             case CertificateStatusCode.SIGNATURE_METHOD_TOO_WEAK:
203                 return "Signature method too weak";
204             case CertificateStatusCode.UNTRUSTED_HASH:
205                 return "Untrusted hash";
206                 
207             case CertificateStatusCode.CERT_NOT_YET_VALID:
208                 return "Certificate is not yet valid";
209             case CertificateStatusCode.CERT_HAS_EXPIRED:
210                 return "Certificate has expired";
211             case CertificateStatusCode.OCSP_NOT_YET_VALID:
212                 return "OCSP is not yet valid";
213             case CertificateStatusCode.OCSP_HAS_EXPIRED:
214                 return "OCSP has expired";
215             case CertificateStatusCode.CRL_NOT_YET_VALID:
216                 return "CRL is not yet valid";
217             case CertificateStatusCode.CRL_HAS_EXPIRED:
218                 return "CRL has expired";
219                 
220             case CertificateStatusCode.CERT_ISSUER_NOT_FOUND:
221                 return "Certificate issuer not found";
222             case CertificateStatusCode.CANNOT_ESTABLISH_TRUST:
223                 return "Cannot establish trust";
224                 
225             case CertificateStatusCode.POLICY_ERROR:
226                 return "TLSPolicy error";
227             case CertificateStatusCode.INVALID_USAGE:
228                 return "Invalid usage";
229             case CertificateStatusCode.CERT_CHAIN_TOO_LONG:
230                 return "Certificate chain too long";
231             case CertificateStatusCode.CA_CERT_NOT_FOR_CERT_ISSUER:
232                 return "CA certificate not allowed to issue certs";
233             case CertificateStatusCode.CA_CERT_NOT_FOR_CRL_ISSUER:
234                 return "CA certificate not allowed to issue CRLs";
235             case CertificateStatusCode.OCSP_CERT_NOT_LISTED:
236                 return "OCSP cert not listed";
237             case CertificateStatusCode.OCSP_BAD_STATUS:
238                 return "OCSP bad status";
239                 
240             case CertificateStatusCode.CERT_IS_REVOKED:
241                 return "Certificate is revoked";
242             case CertificateStatusCode.CRL_BAD_SIGNATURE:
243                 return "CRL bad signature";
244             case CertificateStatusCode.SIGNATURE_ERROR:
245                 return "Signature error";
246             default:
247                 return "Unknown error";
248         }
249     }
250 
251     this()(auto ref Vector!(RBTreeRef!CertificateStatusCode ) status,
252            auto ref Vector!X509Certificate cert_chain)
253     {
254         int i = 1;
255         m_overall = CertificateStatusCode.VERIFIED;
256         // take the "worst" error as overall
257         foreach (ref s; status[])
258         {
259             if (!s.empty)
260             {
261                 auto worst = s.back;
262                 // Leave OCSP confirmations on cert-level status only
263                 if (worst != CertificateStatusCode.OCSP_RESPONSE_GOOD)
264                     m_overall = worst;
265             }
266         }
267         m_all_status = status.move();
268         m_cert_path = cert_chain.move();
269     }
270 
271 
272     this(CertificateStatusCode status)  { m_overall = status; }
273 
274 private:
275     CertificateStatusCode m_overall;
276     Vector!( RBTreeRef!CertificateStatusCode ) m_all_status;
277     Vector!X509Certificate m_cert_path;
278 }
279 
280 /**
281 * PKIX Path Validation
282 */
283 PathValidationResult 
284     x509PathValidate()(const ref Vector!X509Certificate end_certs,
285                        auto const ref PathValidationRestrictions restrictions,
286                        const ref Vector!CertificateStore certstores)
287 {
288 	const size_t max_iterations = restrictions.maxCertChainLength();
289     if (end_certs.empty) 
290         throw new InvalidArgument("x509PathValidate called with no subjects");
291     Vector!X509Certificate cert_path = Vector!X509Certificate();
292     cert_path.pushBack(end_certs[0]);
293 
294     Unique!CertificateStoreOverlay extra = new CertificateStoreOverlay(end_certs);
295     CertificateStore cert_store = cast(CertificateStore)*extra;
296 	size_t i;
297     // iterate until we reach a root or cannot find the issuer
298     while (!cert_path.back().isSelfSigned() && ++i < max_iterations)
299     {
300         X509Certificate cert = findIssuingCert(cert_path.back(), cert_store, certstores);
301         if (!cert) {
302             return PathValidationResult(CertificateStatusCode.CERT_ISSUER_NOT_FOUND);
303         }
304         cert_path.pushBack(cert);
305     }
306 	if (i >= max_iterations)
307 		throw new PKCS8Exception("Max iterations reached when attempting to find root certificate");
308     auto chain = checkChain(cert_path, restrictions, certstores);
309 
310     return PathValidationResult(chain, cert_path);
311 }
312 
313 
314 /**
315 * PKIX Path Validation
316 */
317 PathValidationResult x509PathValidate()(in X509Certificate end_cert,
318                                         auto const ref PathValidationRestrictions restrictions,
319                                         const ref Vector!CertificateStore certstores)
320 {
321     Vector!X509Certificate certs;
322     certs.pushBack(cast(X509Certificate)end_cert);
323     return x509PathValidate(certs, restrictions, certstores);
324 }
325 
326 /**
327 * PKIX Path Validation
328 */
329 PathValidationResult x509PathValidate()(in X509Certificate end_cert,
330                                         auto const ref PathValidationRestrictions restrictions,
331                                         in CertificateStore store)
332 {
333     Vector!X509Certificate certs;
334     certs.pushBack(cast(X509Certificate)end_cert);
335     
336     Vector!CertificateStore certstores;
337     certstores.pushBack(cast(CertificateStore) store);
338     
339     return x509PathValidate(certs, restrictions, certstores);
340 }
341 
342 /**
343 * PKIX Path Validation
344 */
345 PathValidationResult x509PathValidate()(const ref Vector!X509Certificate end_certs,
346                                         auto const ref PathValidationRestrictions restrictions,
347                                         in CertificateStore store)
348 {
349     Vector!CertificateStore certstores;
350     certstores.pushBack(cast(CertificateStore)store);
351     
352     return x509PathValidate(end_certs, restrictions, certstores);
353 }
354 
355 X509Certificate findIssuingCert(in X509Certificate cert_,
356                                 ref CertificateStore end_certs, 
357                                 const ref Vector!CertificateStore certstores)
358 {
359 
360     const X509DN issuer_dn = cert_.issuerDn();
361 
362     const Vector!ubyte auth_key_id = cert_.authorityKeyId();
363     
364     if (X509Certificate cert = end_certs.findCert(issuer_dn, auth_key_id)) {
365         //logTrace("Found certificate: ", cert.toString());
366         return cert;
367     } 
368 
369     foreach (certstore; certstores[])
370     {
371 
372         if (X509Certificate cert = certstore.findCert(issuer_dn, auth_key_id))
373             return cert;
374     }
375     
376     return X509Certificate.init;
377 }
378 
379 const(X509CRL) findCrlsFor(in X509Certificate cert,
380                            const ref Vector!CertificateStore certstores)
381 {
382     foreach (certstore; certstores[])
383     {
384         if (const X509CRL crl = certstore.findCrlFor(cert))
385             return crl;
386     }
387 
388     /// todo: use crl distribution point and download the CRL
389     version(none) {
390         /*
391         const string crl_url = cert.crlDistributionPoint();
392         if (crl_url != "")
393         {
394         std::cout << "Downloading CRL " << crl_url << "";
395             auto http = HTTP::GET_sync(crl_url);
396             
397         std::cout << http.status_message() << "";
398             
399             http.throw_unless_ok();
400             // check the mime type
401             
402             auto crl = X509CRL(http.body());
403             
404             return crl;
405         }*/
406     }
407     
408     return X509CRL.init;
409 }
410 
411 Vector!( RBTreeRef!CertificateStatusCode )
412     checkChain(const ref Vector!X509Certificate cert_path,
413                const ref PathValidationRestrictions restrictions,
414                const ref Vector!CertificateStore certstores)
415 {
416 	//import core.memory : GC; GC.disable(); scope(exit) GC.enable();
417 	const RBTree!string* trusted_hashes = &restrictions.trustedHashes();
418     
419     const bool self_signed_ee_cert = (cert_path.length == 1);
420     
421     X509Time current_time = X509Time(Clock.currTime(UTC()));
422 
423     Vector!(OCSPResponse) ocsp_data = Vector!OCSPResponse(8);
424     
425     Vector!( RBTreeRef!CertificateStatusCode ) cert_status = Vector!( RBTreeRef!CertificateStatusCode )( cert_path.length );
426     
427     foreach (ref e; cert_status) {
428         e.clear(); // touch
429     }
430 
431     logTrace("Cert path size: ", cert_path.length);
432 
433     foreach (size_t i; 0 .. cert_path.length)
434     {
435         auto status = cert_status[i];
436         
437         const bool at_self_signed_root = (i == cert_path.length - 1);
438         
439         const X509Certificate subject = cert_path[i];
440         
441         const X509Certificate issuer = cert_path[at_self_signed_root ? (i) : (i + 1)];
442         
443         const CertificateStore* trusted = certstores.ptr;
444         
445         if (i == 0 || restrictions.ocspAllIntermediates()) {
446 
447 			if (certstores.length >= 1) {
448 				ocsp_data.length = i + 1;
449 	            OnlineCheck oc = OnlineCheck(i,  &ocsp_data[i], &issuer, &subject, trusted );
450 				oc.run();
451 			}
452         }
453         // Check all certs for valid time range
454         if (current_time < X509Time(subject.startTime()))
455             status.insert(CertificateStatusCode.CERT_NOT_YET_VALID);
456         
457         if (current_time > X509Time(subject.endTime()))
458             status.insert(CertificateStatusCode.CERT_HAS_EXPIRED);
459         
460         // Check issuer constraints
461         logTrace("Check issuer constraints");
462         // Don't require CA bit set on self-signed end entity cert
463         if (!issuer.isCACert() && !self_signed_ee_cert)
464             status.insert(CertificateStatusCode.CA_CERT_NOT_FOR_CERT_ISSUER);
465         
466         if (issuer.pathLimit() < i)
467             status.insert(CertificateStatusCode.CERT_CHAIN_TOO_LONG);
468         const PublicKey issuer_key = issuer.subjectPublicKey();
469         logTrace("Got issuer key");
470         if (subject.checkSignature(issuer_key) == false)
471             status.insert(CertificateStatusCode.SIGNATURE_ERROR);
472         logTrace("Get estimated strength");
473         if (issuer_key.estimatedStrength() < restrictions.minimumKeyStrength())
474             status.insert(CertificateStatusCode.SIGNATURE_METHOD_TOO_WEAK);
475         
476         logTrace("Scan untrusted hashes");
477         // Allow untrusted hashes on self-signed roots
478         if (!trusted_hashes.empty && !at_self_signed_root)
479         {
480             if (subject.hashUsedForSignature() !in *trusted_hashes)
481                 status.insert(CertificateStatusCode.UNTRUSTED_HASH);
482         }
483     }
484     logTrace("Certificates to check: ", cert_path.length);
485     foreach (size_t i; 0 .. cert_path.length - 1)
486     {
487         logTrace("Checking status ", i);
488 
489         auto status = cert_status[i];
490         
491         const X509Certificate subject = cert_path[i];
492         const X509Certificate ca = cert_path[i+1];
493         
494 		logTrace("Checking response ", i+1, " of ", ocsp_data.length);
495 		try if (i < ocsp_data.length)
496         {
497 			if (ocsp_data.length <= i) continue;
498 			OCSPResponse ocsp = ocsp_data[i];
499             logTrace("Got response for ID#", i.to!string);
500             if (ocsp !is null && !ocsp.empty)
501 			{
502 	            auto ocsp_status = ocsp.statusFor(ca, subject);
503 	            
504 	            status.insert(ocsp_status);
505 	            
506 	            logTrace("OCSP status: ", ocsp_status.to!string);
507 	            //std::cout << "OCSP status: " << statusString(ocsp_status) << "\n";
508 	            
509 	            // Either way we have a definitive answer, no need to check CRLs
510 	            if (ocsp_status == CertificateStatusCode.CERT_IS_REVOKED)
511 	                return cert_status.move();
512 			} else logTrace("OCSP not found");
513 		} catch (Exception e) { logTrace("OCSP failed with ", e.msg); }
514         
515         const X509CRL crl = findCrlsFor(subject, certstores);
516         
517         if (!crl)
518         {
519             if (restrictions.requireRevocationInformation())
520                 status.insert(CertificateStatusCode.NO_REVOCATION_DATA);
521             continue;
522         }
523 
524         if (!ca.allowedUsage(KeyConstraints.CRL_SIGN))
525             status.insert(CertificateStatusCode.CA_CERT_NOT_FOR_CRL_ISSUER);
526         
527         if (current_time < crl.thisUpdate())
528             status.insert(CertificateStatusCode.CRL_NOT_YET_VALID);
529         
530         if (current_time > crl.nextUpdate())
531             status.insert(CertificateStatusCode.CRL_HAS_EXPIRED);
532 		Unique!PublicKey pubkey = ca.subjectPublicKey();
533 		if (crl.checkSignature(*pubkey) == false)
534             status.insert(CertificateStatusCode.CRL_BAD_SIGNATURE);
535         
536         if (crl.isRevoked(subject))
537             status.insert(CertificateStatusCode.CERT_IS_REVOKED);
538     }
539 
540     if (self_signed_ee_cert)
541         cert_status.back().insert(CertificateStatusCode.CANNOT_ESTABLISH_TRUST);
542     
543     return cert_status.move();
544 }