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