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 		return m_def_trusted_hashes;
104 	}
105 
106     size_t minimumKeyStrength() const
107     { return m_minimum_key_strength; }
108 
109 private:
110     bool m_require_revocation_information = false;
111     bool m_ocsp_all_intermediates = false;
112 	int m_max_cert_chain_length = 9;
113 	RBTree!string m_trusted_hashes;
114     size_t m_minimum_key_strength = 80;
115 
116 	static this() {
117 		m_def_trusted_hashes.insert("SHA-160");		
118 		m_def_trusted_hashes.insert("SHA-224");
119 		m_def_trusted_hashes.insert("SHA-256");
120 		m_def_trusted_hashes.insert("SHA-384");
121 		m_def_trusted_hashes.insert("SHA-512");
122 	}
123 	static ~this() {
124 		m_def_trusted_hashes.clear();
125 		m_def_trusted_hashes.destroy();
126 	}
127 	static RBTree!string m_def_trusted_hashes;
128 }
129 
130 /**
131 * Represents the result of a PKIX path validation
132 */
133 struct PathValidationResult
134 {
135 public:
136     alias Code = CertificateStatusCode;
137 
138     /**
139     * Returns: the set of hash functions you are implicitly
140     * trusting by trusting this result.
141     */
142     RBTreeRef!string trustedHashes() const
143     {
144         RBTreeRef!string hashes;
145         foreach (cert_path; m_cert_path[])
146             hashes.insert(cert_path.hashUsedForSignature());
147         return hashes;
148     }
149 
150     /**
151     * Returns: the trust root of the validation
152     */
153     X509Certificate trustRoot() const
154     {
155         import std.range : back;
156         if (m_cert_path.length == 0) return X509Certificate.init;
157         return m_cert_path[].back;
158     }
159 
160     /**
161     * Returns: the full path from subject to trust root
162     */
163     ref const(Vector!X509Certificate) certPath() const { return m_cert_path; }
164 
165     /**
166     * Returns: true iff the validation was succesful
167     */
168     bool successfulValidation() const
169     {
170         if (result() == CertificateStatusCode.VERIFIED ||
171             result() == CertificateStatusCode.OCSP_RESPONSE_GOOD)
172             return true;
173         return false;
174     }
175 
176     /**
177     * Returns: overall validation result code
178     */
179     CertificateStatusCode result() const { return m_overall; }
180 
181     /**
182     * Return a set of status codes for each certificate in the chain
183     */
184     ref const(Vector!(RBTreeRef!CertificateStatusCode)) allStatuses() const
185     { return m_all_status; }
186 
187     /**
188     * Returns: string representation of the validation result
189     */
190     string resultString() const
191     {
192         return statusString(result());
193     }
194 
195 
196     static string statusString(CertificateStatusCode code)
197     {
198         switch(code)
199         {
200             case CertificateStatusCode.VERIFIED:
201                 return "Verified";
202             case CertificateStatusCode.OCSP_RESPONSE_GOOD:
203                 return "OCSP response good";
204             case CertificateStatusCode.NO_REVOCATION_DATA:
205                 return "No revocation data";
206             case CertificateStatusCode.SIGNATURE_METHOD_TOO_WEAK:
207                 return "Signature method too weak";
208             case CertificateStatusCode.UNTRUSTED_HASH:
209                 return "Untrusted hash";
210                 
211             case CertificateStatusCode.CERT_NOT_YET_VALID:
212                 return "Certificate is not yet valid";
213             case CertificateStatusCode.CERT_HAS_EXPIRED:
214                 return "Certificate has expired";
215             case CertificateStatusCode.OCSP_NOT_YET_VALID:
216                 return "OCSP is not yet valid";
217             case CertificateStatusCode.OCSP_HAS_EXPIRED:
218                 return "OCSP has expired";
219             case CertificateStatusCode.CRL_NOT_YET_VALID:
220                 return "CRL is not yet valid";
221             case CertificateStatusCode.CRL_HAS_EXPIRED:
222                 return "CRL has expired";
223                 
224             case CertificateStatusCode.CERT_ISSUER_NOT_FOUND:
225                 return "Certificate issuer not found";
226             case CertificateStatusCode.CANNOT_ESTABLISH_TRUST:
227                 return "Cannot establish trust";
228                 
229             case CertificateStatusCode.POLICY_ERROR:
230                 return "TLSPolicy error";
231             case CertificateStatusCode.INVALID_USAGE:
232                 return "Invalid usage";
233             case CertificateStatusCode.CERT_CHAIN_TOO_LONG:
234                 return "Certificate chain too long";
235             case CertificateStatusCode.CA_CERT_NOT_FOR_CERT_ISSUER:
236                 return "CA certificate not allowed to issue certs";
237             case CertificateStatusCode.CA_CERT_NOT_FOR_CRL_ISSUER:
238                 return "CA certificate not allowed to issue CRLs";
239             case CertificateStatusCode.OCSP_CERT_NOT_LISTED:
240                 return "OCSP cert not listed";
241             case CertificateStatusCode.OCSP_BAD_STATUS:
242                 return "OCSP bad status";
243                 
244             case CertificateStatusCode.CERT_IS_REVOKED:
245                 return "Certificate is revoked";
246             case CertificateStatusCode.CRL_BAD_SIGNATURE:
247                 return "CRL bad signature";
248             case CertificateStatusCode.SIGNATURE_ERROR:
249                 return "Signature error";
250             default:
251                 return "Unknown error";
252         }
253     }
254 
255     this()(auto ref Vector!(RBTreeRef!CertificateStatusCode ) status,
256            auto ref Vector!X509Certificate cert_chain)
257     {
258         int i = 1;
259         m_overall = CertificateStatusCode.VERIFIED;
260         // take the "worst" error as overall
261         foreach (ref s; status[])
262         {
263             if (!s.empty)
264             {
265                 auto worst = s.back;
266                 // Leave OCSP confirmations on cert-level status only
267                 if (worst != CertificateStatusCode.OCSP_RESPONSE_GOOD)
268                     m_overall = worst;
269             }
270         }
271         m_all_status = status.move();
272         m_cert_path = cert_chain.move();
273     }
274 
275 
276     this(CertificateStatusCode status)  { m_overall = status; }
277 
278 private:
279     CertificateStatusCode m_overall;
280     Vector!( RBTreeRef!CertificateStatusCode ) m_all_status;
281     Vector!X509Certificate m_cert_path;
282 }
283 
284 /**
285 * PKIX Path Validation
286 */
287 PathValidationResult 
288     x509PathValidate()(const ref Vector!X509Certificate end_certs,
289                        auto const ref PathValidationRestrictions restrictions,
290                        const ref Vector!CertificateStore certstores)
291 {
292 	const size_t max_iterations = restrictions.maxCertChainLength();
293     if (end_certs.empty) 
294         throw new InvalidArgument("x509PathValidate called with no subjects");
295     Vector!X509Certificate cert_path = Vector!X509Certificate();
296     cert_path.pushBack(end_certs[0]);
297 
298     Unique!CertificateStoreOverlay extra = new CertificateStoreOverlay(end_certs);
299     CertificateStore cert_store = cast(CertificateStore)*extra;
300 	size_t i;
301     // iterate until we reach a root or cannot find the issuer
302     while (!cert_path.back().isSelfSigned() && ++i < max_iterations)
303     {
304         X509Certificate cert = findIssuingCert(cert_path.back(), cert_store, certstores);
305         if (!cert) {
306             return PathValidationResult(CertificateStatusCode.CERT_ISSUER_NOT_FOUND);
307         }
308         cert_path.pushBack(cert);
309     }
310 	if (i >= max_iterations)
311 		throw new PKCS8Exception("Max iterations reached when attempting to find root certificate");
312     auto chain = checkChain(cert_path, restrictions, certstores);
313 
314     return PathValidationResult(chain, cert_path);
315 }
316 
317 
318 /**
319 * PKIX Path Validation
320 */
321 PathValidationResult x509PathValidate()(in X509Certificate end_cert,
322                                         auto const ref PathValidationRestrictions restrictions,
323                                         const ref Vector!CertificateStore certstores)
324 {
325     Vector!X509Certificate certs;
326     certs.pushBack(cast(X509Certificate)end_cert);
327     return x509PathValidate(certs, restrictions, certstores);
328 }
329 
330 /**
331 * PKIX Path Validation
332 */
333 PathValidationResult x509PathValidate()(in X509Certificate end_cert,
334                                         auto const ref PathValidationRestrictions restrictions,
335                                         in CertificateStore store)
336 {
337     Vector!X509Certificate certs;
338     certs.pushBack(cast(X509Certificate)end_cert);
339     
340     Vector!CertificateStore certstores;
341     certstores.pushBack(cast(CertificateStore) store);
342     
343     return x509PathValidate(certs, restrictions, certstores);
344 }
345 
346 /**
347 * PKIX Path Validation
348 */
349 PathValidationResult x509PathValidate()(const ref Vector!X509Certificate end_certs,
350                                         auto const ref PathValidationRestrictions restrictions,
351                                         in CertificateStore store)
352 {
353     Vector!CertificateStore certstores;
354     certstores.pushBack(cast(CertificateStore)store);
355     
356     return x509PathValidate(end_certs, restrictions, certstores);
357 }
358 
359 X509Certificate findIssuingCert(in X509Certificate cert_,
360                                 ref CertificateStore end_certs, 
361                                 const ref Vector!CertificateStore certstores)
362 {
363 
364     const X509DN issuer_dn = cert_.issuerDn();
365 
366     const Vector!ubyte auth_key_id = cert_.authorityKeyId();
367     
368     if (X509Certificate cert = end_certs.findCert(issuer_dn, auth_key_id)) {
369         //logTrace("Found certificate: ", cert.toString());
370         return cert;
371     } 
372 
373     foreach (certstore; certstores[])
374     {
375 
376         if (X509Certificate cert = certstore.findCert(issuer_dn, auth_key_id))
377             return cert;
378     }
379     
380     return X509Certificate.init;
381 }
382 
383 const(X509CRL) findCrlsFor(in X509Certificate cert,
384                            const ref Vector!CertificateStore certstores)
385 {
386     foreach (certstore; certstores[])
387     {
388         if (const X509CRL crl = certstore.findCrlFor(cert))
389             return crl;
390     }
391 
392     /// todo: use crl distribution point and download the CRL
393     version(none) {
394         /*
395         const string crl_url = cert.crlDistributionPoint();
396         if (crl_url != "")
397         {
398         std::cout << "Downloading CRL " << crl_url << "";
399             auto http = HTTP::GET_sync(crl_url);
400             
401         std::cout << http.status_message() << "";
402             
403             http.throw_unless_ok();
404             // check the mime type
405             
406             auto crl = X509CRL(http.body());
407             
408             return crl;
409         }*/
410     }
411     
412     return X509CRL.init;
413 }
414 
415 Vector!( RBTreeRef!CertificateStatusCode )
416     checkChain(const ref Vector!X509Certificate cert_path,
417                const ref PathValidationRestrictions restrictions,
418                const ref Vector!CertificateStore certstores)
419 {
420 	//import core.memory : GC; GC.disable(); scope(exit) GC.enable();
421 	const RBTree!string* trusted_hashes = &restrictions.trustedHashes();
422     
423     const bool self_signed_ee_cert = (cert_path.length == 1);
424     
425     X509Time current_time = X509Time(Clock.currTime(UTC()));
426     
427     Vector!( Thread ) ocsp_responses;
428 
429 	scope(exit) foreach (Thread thr; ocsp_responses[]) {
430 		ThreadMem.free(thr);
431 	}
432 
433     Vector!(OCSPResponse) ocsp_data = Vector!OCSPResponse(8);
434     
435     Vector!( RBTreeRef!CertificateStatusCode ) cert_status = Vector!( RBTreeRef!CertificateStatusCode )( cert_path.length );
436     
437     foreach (ref e; cert_status) {
438         e.clear(); // touch
439     }
440 
441     //logTrace("Cert path size: ", cert_path.length);
442 
443     foreach (size_t i; 0 .. cert_path.length)
444     {
445         auto status = &*(cert_status[i]);
446         
447         const bool at_self_signed_root = (i == cert_path.length - 1);
448         
449         const X509Certificate subject = cert_path[i];
450         
451         const X509Certificate issuer = cert_path[at_self_signed_root ? (i) : (i + 1)];
452         
453         const CertificateStore* trusted = certstores.ptr;
454         
455 		Mutex mtx = new Mutex;
456 
457         if (i == 0 || restrictions.ocspAllIntermediates()) {
458 
459 			if (certstores.length > 1) {
460 
461 	            //version(Have_vibe_d)
462 	            //    Tid id_ = runTask(&onlineCheck, cast(shared)Tid.getThis(), cast(shared)i, cast(shared)&ocsp_data[i], cast(shared)&issuer, cast(shared)&subject, cast(shared)trusted);
463 	            //else
464 				synchronized(mtx) {
465 					ocsp_data.length = i + 1;
466 		            OnlineCheck oc = OnlineCheck( cast(shared)mtx, cast(shared)i,  cast(shared)&ocsp_data[i], cast(shared)&issuer, cast(shared)&subject, cast(shared)trusted );
467 					Thread thr = ThreadMem.alloc!Thread(&oc.run);
468 					thr.start();
469 					ocsp_responses ~= thr;
470 				}
471 			}
472         }
473         // Check all certs for valid time range
474         if (current_time < X509Time(subject.startTime()))
475             status.insert(CertificateStatusCode.CERT_NOT_YET_VALID);
476         
477         if (current_time > X509Time(subject.endTime()))
478             status.insert(CertificateStatusCode.CERT_HAS_EXPIRED);
479         
480         // Check issuer constraints
481         logTrace("Check issuer constraints");
482         // Don't require CA bit set on self-signed end entity cert
483         if (!issuer.isCACert() && !self_signed_ee_cert)
484             status.insert(CertificateStatusCode.CA_CERT_NOT_FOR_CERT_ISSUER);
485         
486         if (issuer.pathLimit() < i)
487             status.insert(CertificateStatusCode.CERT_CHAIN_TOO_LONG);
488         const PublicKey issuer_key = issuer.subjectPublicKey();
489         logTrace("Got issuer key");
490         if (subject.checkSignature(issuer_key) == false)
491             status.insert(CertificateStatusCode.SIGNATURE_ERROR);
492         logTrace("Get estimated strength");
493         if (issuer_key.estimatedStrength() < restrictions.minimumKeyStrength())
494             status.insert(CertificateStatusCode.SIGNATURE_METHOD_TOO_WEAK);
495         
496         logTrace("Scan untrusted hashes");
497         // Allow untrusted hashes on self-signed roots
498         if (!trusted_hashes.empty && !at_self_signed_root)
499         {
500             if (subject.hashUsedForSignature() !in *trusted_hashes)
501                 status.insert(CertificateStatusCode.UNTRUSTED_HASH);
502         }
503     }
504     logTrace("Certificates to check: ", cert_path.length);
505     foreach (size_t i; 0 .. cert_path.length - 1)
506     {
507         logTrace("Checking status ", i);
508 
509         auto status = &*(cert_status[i]);
510         
511         const X509Certificate subject = cert_path[i];
512         const X509Certificate ca = cert_path[i+1];
513         
514         logTrace("Checking response ", i+1, " of ", ocsp_responses.length);
515         if (i < ocsp_responses.length)
516         {
517             try
518             {
519 				ocsp_responses[i].join();
520 				if (ocsp_data.length <= i) continue;
521 				OCSPResponse ocsp = ocsp_data[i];
522                 logTrace("Got response for ID#", i.to!string);
523                 if (!ocsp || ocsp.empty)
524                     throw new Exception("OSCP.responder is undefined");
525                 auto ocsp_status = ocsp.statusFor(ca, subject);
526                 
527                 status.insert(ocsp_status);
528                 
529                 logTrace("OCSP status: ", ocsp_status.to!string);
530                 //std::cout << "OCSP status: " << statusString(ocsp_status) << "\n";
531                 
532                 // Either way we have a definitive answer, no need to check CRLs
533                 if (ocsp_status == CertificateStatusCode.CERT_IS_REVOKED)
534                     return cert_status.move();
535                 else if (ocsp_status == CertificateStatusCode.OCSP_RESPONSE_GOOD)
536                     continue;
537             }
538             catch(Exception e)
539             {
540                 logTrace("OCSP error: " ~ e.msg ~ "");
541             }
542         }
543         
544         const X509CRL crl = findCrlsFor(subject, certstores);
545         
546         if (!crl)
547         {
548             if (restrictions.requireRevocationInformation())
549                 status.insert(CertificateStatusCode.NO_REVOCATION_DATA);
550             continue;
551         }
552 
553         if (!ca.allowedUsage(KeyConstraints.CRL_SIGN))
554             status.insert(CertificateStatusCode.CA_CERT_NOT_FOR_CRL_ISSUER);
555         
556         if (current_time < crl.thisUpdate())
557             status.insert(CertificateStatusCode.CRL_NOT_YET_VALID);
558         
559         if (current_time > crl.nextUpdate())
560             status.insert(CertificateStatusCode.CRL_HAS_EXPIRED);
561 		Unique!PublicKey pubkey = ca.subjectPublicKey();
562 		if (crl.checkSignature(*pubkey) == false)
563             status.insert(CertificateStatusCode.CRL_BAD_SIGNATURE);
564         
565         if (crl.isRevoked(subject))
566             status.insert(CertificateStatusCode.CERT_IS_REVOKED);
567     }
568 
569     if (self_signed_ee_cert)
570         cert_status.back().insert(CertificateStatusCode.CANNOT_ESTABLISH_TRUST);
571     
572     return cert_status.move();
573 }