https://issues.shibboleth.net/jira/browse/CPPXT-70
[shibboleth/cpp-xmltooling.git] / xmltooling / security / impl / PKIXPathValidator.cpp
1 /**
2  * Licensed to the University Corporation for Advanced Internet
3  * Development, Inc. (UCAID) under one or more contributor license
4  * agreements. See the NOTICE file distributed with this work for
5  * additional information regarding copyright ownership.
6  *
7  * UCAID licenses this file to you under the Apache License,
8  * Version 2.0 (the "License"); you may not use this file except
9  * in compliance with the License. You may obtain a copy of the
10  * License at
11  *
12  * http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing,
15  * software distributed under the License is distributed on an
16  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
17  * either express or implied. See the License for the specific
18  * language governing permissions and limitations under the License.
19  */
20
21 /**
22  * PKIXPathValidator.cpp
23  * 
24  * A path validator based on PKIX support in OpenSSL.
25  */
26
27 #include "internal.h"
28 #include "logging.h"
29 #include "security/OpenSSLPathValidator.h"
30 #include "security/OpenSSLCryptoX509CRL.h"
31 #include "security/PKIXPathValidatorParams.h"
32 #include "security/SecurityHelper.h"
33 #include "util/NDC.h"
34 #include "util/PathResolver.h"
35 #include "util/Threads.h"
36 #include "util/XMLHelper.h"
37
38 #include <algorithm>
39 #include <fstream>
40 #include <openssl/x509_vfy.h>
41 #include <openssl/x509v3.h>
42 #include <xsec/enc/OpenSSL/OpenSSLCryptoX509.hpp>
43
44 using namespace xmltooling::logging;
45 using namespace xmltooling;
46 using namespace std;
47
48
49 namespace {
50     static int XMLTOOL_DLLLOCAL error_callback(int ok, X509_STORE_CTX* ctx)
51     {
52         if (!ok)
53             Category::getInstance("OpenSSL").error("path validation failure: %s", X509_verify_cert_error_string(ctx->error));
54         return ok;
55     }
56
57     static string XMLTOOL_DLLLOCAL X509_NAME_to_string(X509_NAME* n)
58     {
59         string s;
60         BIO* b = BIO_new(BIO_s_mem());
61         X509_NAME_print_ex(b,n,0,XN_FLAG_RFC2253);
62         BIO_flush(b);
63         BUF_MEM* bptr=nullptr;
64         BIO_get_mem_ptr(b, &bptr);
65         if (bptr && bptr->length > 0) {
66             s.append(bptr->data, bptr->length);
67         }
68         BIO_free(b);
69         return s;
70     }
71
72     static time_t XMLTOOL_DLLLOCAL getCRLTime(const ASN1_TIME *a)
73     {
74         struct tm t;
75         memset(&t, 0, sizeof(t));
76         // RFC 5280, sections 5.1.2.4 and 5.1.2.5 require thisUpdate and nextUpdate
77         // to be encoded as UTCTime until 2049, and RFC 5280 section 4.1.2.5.1
78         // further restricts the format to "YYMMDDHHMMSSZ" ("even where the number
79         // of seconds is zero").
80         // As long as OpenSSL doesn't provide any API to convert ASN1_TIME values
81         // time_t, we therefore have to parse it ourselves, unfortunately.
82         if (sscanf((const char*)a->data, "%2d%2d%2d%2d%2d%2dZ",
83             &t.tm_year, &t.tm_mon, &t.tm_mday,
84             &t.tm_hour, &t.tm_min, &t.tm_sec) == 6) {
85             if (t.tm_year <= 50) {
86                 // RFC 5280, section 4.1.2.5.1
87                 t.tm_year += 100;
88             }
89             t.tm_mon--;
90 #if defined(HAVE_TIMEGM)
91             return timegm(&t);
92 #else
93             // Windows, and hopefully most others...?
94             return mktime(&t) - timezone;
95 #endif
96         }
97         return (time_t)-1;
98     }
99
100     static const XMLCh minRefreshDelay[] =      UNICODE_LITERAL_15(m,i,n,R,e,f,r,e,s,h,D,e,l,a,y);
101     static const XMLCh minSecondsRemaining[] =  UNICODE_LITERAL_19(m,i,n,S,e,c,o,n,d,s,R,e,m,a,i,n,i,n,g);
102     static const XMLCh minPercentRemaining[] =  UNICODE_LITERAL_19(m,i,n,P,e,r,c,e,n,t,R,e,m,a,i,n,i,n,g);
103 };
104
105 namespace xmltooling {
106
107     class XMLTOOL_DLLLOCAL PKIXPathValidator : public OpenSSLPathValidator
108     {
109     public:
110         PKIXPathValidator(const xercesc::DOMElement* e)
111             : m_log(Category::getInstance(XMLTOOLING_LOGCAT".PathValidator.PKIX")),
112               m_lock(XMLToolingConfig::getConfig().getNamedMutex(XMLTOOLING_LOGCAT".PathValidator.PKIX")),
113               m_minRefreshDelay(XMLHelper::getAttrInt(e, 60, minRefreshDelay)),
114               m_minSecondsRemaining(XMLHelper::getAttrInt(e, 86400, minSecondsRemaining)),
115               m_minPercentRemaining(XMLHelper::getAttrInt(e, 10, minPercentRemaining)) {
116         }
117
118         virtual ~PKIXPathValidator() {}
119
120         bool validate(
121             XSECCryptoX509* certEE, const vector<XSECCryptoX509*>& certChain, const PathValidatorParams& params
122             ) const;
123         bool validate(
124             X509* certEE, STACK_OF(X509)* certChain, const PathValidatorParams& params
125             ) const;
126
127     private:
128         XSECCryptoX509CRL* getRemoteCRLs(const char* cdpuri) const;
129         bool isFreshCRL(XSECCryptoX509CRL *c, Category* log=nullptr) const;
130
131         Category& m_log;
132         Mutex& m_lock;
133         time_t m_minRefreshDelay,m_minSecondsRemaining;
134         unsigned short m_minPercentRemaining;
135
136         static map<string,time_t> m_crlUpdateMap;
137     };
138
139     PathValidator* XMLTOOL_DLLLOCAL PKIXPathValidatorFactory(const xercesc::DOMElement* const & e)
140     {
141         return new PKIXPathValidator(e);
142     }
143
144 };
145
146 map<string,time_t> PKIXPathValidator::m_crlUpdateMap;
147
148 void XMLTOOL_API xmltooling::registerPathValidators()
149 {
150     XMLToolingConfig& conf=XMLToolingConfig::getConfig();
151     conf.PathValidatorManager.registerFactory(PKIX_PATHVALIDATOR, PKIXPathValidatorFactory);
152 }
153
154 PathValidator::PathValidator()
155 {
156 }
157
158 PathValidator::~PathValidator()
159 {
160 }
161
162 PathValidator::PathValidatorParams::PathValidatorParams()
163 {
164 }
165
166 PathValidator::PathValidatorParams::~PathValidatorParams()
167 {
168 }
169
170 PKIXPathValidatorParams::PKIXPathValidatorParams()
171 {
172 }
173
174 PKIXPathValidatorParams::~PKIXPathValidatorParams()
175 {
176 }
177
178 OpenSSLPathValidator::OpenSSLPathValidator()
179 {
180 }
181
182 OpenSSLPathValidator::~OpenSSLPathValidator()
183 {
184 }
185
186 bool PKIXPathValidator::validate(
187     XSECCryptoX509* certEE, const vector<XSECCryptoX509*>& certChain, const PathValidatorParams& params
188     ) const
189 {
190     if (certEE->getProviderName()!=DSIGConstants::s_unicodeStrPROVOpenSSL) {
191         m_log.error("only the OpenSSL XSEC provider is supported");
192         return false;
193     }
194
195     STACK_OF(X509)* untrusted=sk_X509_new_null();
196     for (vector<XSECCryptoX509*>::const_iterator i=certChain.begin(); i!=certChain.end(); ++i)
197         sk_X509_push(untrusted,static_cast<OpenSSLCryptoX509*>(*i)->getOpenSSLX509());
198
199     bool ret = validate(static_cast<OpenSSLCryptoX509*>(certEE)->getOpenSSLX509(), untrusted, params);
200     sk_X509_free(untrusted);
201     return ret;
202 }
203
204 bool PKIXPathValidator::validate(X509* EE, STACK_OF(X509)* untrusted, const PathValidatorParams& params) const
205 {
206 #ifdef _DEBUG
207     NDC ndc("validate");
208 #endif
209
210     const PKIXPathValidatorParams* pkixParams = dynamic_cast<const PKIXPathValidatorParams*>(&params);
211     if (!pkixParams) {
212         m_log.error("input parameters were of incorrect type");
213         return false;
214     }
215
216     // First we build a stack of CA certs. These objects are all referenced in place.
217     m_log.debug("supplying PKIX Validation information");
218
219     // We need this for CRL support.
220     X509_STORE* store=X509_STORE_new();
221     if (!store) {
222         log_openssl();
223         return false;
224     }
225
226     // PKIX policy checking (cf. RFCs 3280/5280 section 6)
227     if (pkixParams->isPolicyMappingInhibited() || pkixParams->isAnyPolicyInhibited() || (!pkixParams->getPolicies().empty())) {
228 #if (OPENSSL_VERSION_NUMBER < 0x00908000L)
229         m_log.error("PKIX policy checking option is configured, but OpenSSL version is less than 0.9.8");
230         X509_STORE_free(store);
231         return false;
232 #else
233         unsigned long pflags = 0;
234         X509_VERIFY_PARAM *vpm = X509_VERIFY_PARAM_new();
235         if (!vpm) {
236             log_openssl();
237             X509_STORE_free(store);
238             return false;
239         }
240
241         // populate the "user-initial-policy-set" input variable
242         const set<string>& policies = pkixParams->getPolicies();
243         if (!policies.empty()) {
244             for (set<string>::const_iterator o=policies.begin(); o!=policies.end(); o++) {
245                 ASN1_OBJECT *oid = OBJ_txt2obj(o->c_str(), 1);
246                 if (oid && X509_VERIFY_PARAM_add0_policy(vpm, oid)) {
247                     m_log.debug("OID (%s) added to set of acceptable policies", o->c_str());
248                 }
249                 else {
250                     log_openssl();
251                     m_log.error("unable to parse/configure policy OID value (%s)", o->c_str());
252                     if (oid)
253                         ASN1_OBJECT_free(oid);
254                     X509_VERIFY_PARAM_free(vpm);
255                     X509_STORE_free(store);
256                     return false;
257                 }
258             }
259             // when the user has supplied at least one policy OID, he obviously wants to check
260             // for an explicit policy ("initial-explicit-policy")
261             pflags |= X509_V_FLAG_EXPLICIT_POLICY;
262         }
263
264         // "initial-policy-mapping-inhibit" input variable
265         if (pkixParams->isPolicyMappingInhibited())
266             pflags |= X509_V_FLAG_INHIBIT_MAP;
267         // "initial-any-policy-inhibit" input variable
268         if (pkixParams->isAnyPolicyInhibited())
269             pflags |= X509_V_FLAG_INHIBIT_ANY;
270
271         if (!X509_VERIFY_PARAM_set_flags(vpm, pflags) || !X509_STORE_set1_param(store, vpm)) {
272             log_openssl();
273             m_log.error("unable to set PKIX policy checking parameters");
274             X509_VERIFY_PARAM_free(vpm);
275             X509_STORE_free(store);
276             return false;
277         }
278
279         X509_VERIFY_PARAM_free(vpm);
280 #endif
281     }
282
283     // This contains the state of the validate operation.
284     int count=0;
285     X509_STORE_CTX ctx;
286
287     // AFAICT, EE and untrusted are passed in but not owned by the ctx.
288 #if (OPENSSL_VERSION_NUMBER >= 0x00907000L)
289     if (X509_STORE_CTX_init(&ctx,store,EE,untrusted)!=1) {
290         log_openssl();
291         m_log.error("unable to initialize X509_STORE_CTX");
292         X509_STORE_free(store);
293         return false;
294     }
295 #else
296     X509_STORE_CTX_init(&ctx,store,EE,untrusted);
297 #endif
298
299     STACK_OF(X509)* CAstack = sk_X509_new_null();
300     const vector<XSECCryptoX509*>& CAcerts = pkixParams->getTrustAnchors();
301     for (vector<XSECCryptoX509*>::const_iterator i=CAcerts.begin(); i!=CAcerts.end(); ++i) {
302         if ((*i)->getProviderName()==DSIGConstants::s_unicodeStrPROVOpenSSL) {
303             sk_X509_push(CAstack,static_cast<OpenSSLCryptoX509*>(*i)->getOpenSSLX509());
304             ++count;
305         }
306     }
307     m_log.debug("supplied (%d) CA certificate(s)", count);
308
309     // Seems to be most efficient to just pass in the CA stack.
310     X509_STORE_CTX_trusted_stack(&ctx,CAstack);
311     X509_STORE_CTX_set_depth(&ctx,100);    // we check the depth down below
312     X509_STORE_CTX_set_verify_cb(&ctx,error_callback);
313
314     // Do a first pass verify. If CRLs aren't used, this is the only pass.
315     int ret=X509_verify_cert(&ctx);
316     if (ret==1) {
317         // Now see if the depth was acceptable by counting the number of intermediates.
318         int depth=sk_X509_num(ctx.chain)-2;
319         if (pkixParams->getVerificationDepth() < depth) {
320             m_log.error(
321                 "certificate chain was too long (%d intermediates, only %d allowed)",
322                 (depth==-1) ? 0 : depth,
323                 pkixParams->getVerificationDepth()
324                 );
325             ret=0;
326         }
327     }
328
329     if (pkixParams->getRevocationChecking() != PKIXPathValidatorParams::REVOCATION_OFF) {
330 #if (OPENSSL_VERSION_NUMBER >= 0x00907000L)
331         // When we add CRLs, we have to be sure the nextUpdate hasn't passed, because OpenSSL won't accept
332         // the CRL in that case. If we end up not adding a CRL for a particular link in the chain, the
333         // validation will fail (if the fullChain option was set).
334         set<string> crlissuers;
335         time_t now = time(nullptr);
336
337         const vector<XSECCryptoX509CRL*>& crls = pkixParams->getCRLs();
338         for (vector<XSECCryptoX509CRL*>::const_iterator j=crls.begin(); j!=crls.end(); ++j) {
339             if ((*j)->getProviderName()==DSIGConstants::s_unicodeStrPROVOpenSSL &&
340                 (X509_cmp_time(X509_CRL_get_nextUpdate(static_cast<OpenSSLCryptoX509CRL*>(*j)->getOpenSSLX509CRL()), &now) > 0)) {
341                 // owned by store
342                 X509_STORE_add_crl(store, X509_CRL_dup(static_cast<OpenSSLCryptoX509CRL*>(*j)->getOpenSSLX509CRL()));
343                 string crlissuer(X509_NAME_to_string(X509_CRL_get_issuer(static_cast<OpenSSLCryptoX509CRL*>(*j)->getOpenSSLX509CRL())));
344                 if (!crlissuer.empty()) {
345                     m_log.debug("added CRL issued by (%s)", crlissuer.c_str());
346                     crlissuers.insert(crlissuer);
347                 }
348             }
349         }
350
351         for (int i = 0; i < sk_X509_num(untrusted); ++i) {
352             X509 *cert = sk_X509_value(untrusted, i);
353             string crlissuer(X509_NAME_to_string(X509_get_issuer_name(cert)));
354             if (crlissuers.count(crlissuer)) {
355                // We already have a CRL for this cert, so skip CRLDP processing for this one.
356                continue;
357             }
358
359             bool foundUsableCDP = false;
360             STACK_OF(DIST_POINT)* dps = (STACK_OF(DIST_POINT)*)X509_get_ext_d2i(cert, NID_crl_distribution_points, nullptr, nullptr);
361             for (int ii = 0; !foundUsableCDP && ii < sk_DIST_POINT_num(dps); ++ii) {
362                 DIST_POINT* dp = sk_DIST_POINT_value(dps, ii);
363                 if (!dp->distpoint || dp->distpoint->type != 0)
364                     continue;
365                 for (int iii = 0; !foundUsableCDP && iii < sk_GENERAL_NAME_num(dp->distpoint->name.fullname); ++iii) {
366                     GENERAL_NAME* gen = sk_GENERAL_NAME_value(dp->distpoint->name.fullname, iii);
367                     // Only consider HTTP URIs, and stop after the first one we find.
368 #ifdef HAVE_STRCASECMP
369                     if (gen->type == GEN_URI && (!strncasecmp((const char*)gen->d.ia5->data, "http:", 5))) {
370 #else
371                     if (gen->type == GEN_URI && (!strnicmp((const char*)gen->d.ia5->data, "http:", 5))) {
372 #endif
373                         const char* cdpuri = (const char*)gen->d.ia5->data;
374                         auto_ptr<XSECCryptoX509CRL> crl(getRemoteCRLs(cdpuri));
375                         if (crl.get() && crl->getProviderName()==DSIGConstants::s_unicodeStrPROVOpenSSL &&
376                             (isFreshCRL(crl.get()) || (ii == sk_DIST_POINT_num(dps)-1 && iii == sk_GENERAL_NAME_num(dp->distpoint->name.fullname)-1))) {
377                             // owned by store
378                             X509_STORE_add_crl(store, X509_CRL_dup(static_cast<OpenSSLCryptoX509CRL*>(crl.get())->getOpenSSLX509CRL()));
379                             m_log.debug("added CRL issued by (%s)", crlissuer.c_str());
380                             crlissuers.insert(crlissuer);
381                             foundUsableCDP = true;
382                         }
383                     }
384                 }
385             }
386             sk_DIST_POINT_free(dps);
387         }
388
389         // Do a second pass verify with CRLs in place.
390         if (pkixParams->getRevocationChecking() == PKIXPathValidatorParams::REVOCATION_FULLCHAIN)
391             X509_STORE_CTX_set_flags(&ctx, X509_V_FLAG_CRL_CHECK|X509_V_FLAG_CRL_CHECK_ALL);
392         else
393             X509_STORE_CTX_set_flags(&ctx, X509_V_FLAG_CRL_CHECK);
394         ret=X509_verify_cert(&ctx);
395 #else
396         m_log.warn("CRL checking is enabled, but OpenSSL version is too old");
397         ret = 0;
398 #endif
399     }
400
401     // Clean up...
402     X509_STORE_CTX_cleanup(&ctx);
403     X509_STORE_free(store);
404     sk_X509_free(CAstack);
405
406     if (ret==1) {
407         m_log.debug("successfully validated certificate chain");
408         return true;
409     }
410
411     return false;
412 }
413
414 XSECCryptoX509CRL* PKIXPathValidator::getRemoteCRLs(const char* cdpuri) const
415 {
416     // This is a filesystem-based CRL cache using a shared lock across all instances
417     // of this class.
418
419     // The filenames for the CRL cache are based on a hash of the CRL location.
420     string cdpfile = SecurityHelper::doHash("SHA1", cdpuri, strlen(cdpuri)) + ".crl";
421     XMLToolingConfig::getConfig().getPathResolver()->resolve(cdpfile, PathResolver::XMLTOOLING_RUN_FILE);
422     string cdpstaging = cdpfile + ".tmp";
423
424     time_t now = time(nullptr);
425     vector<XSECCryptoX509CRL*> crls;
426
427     try {
428         // While holding the lock, check for a cached copy of the CRL, and remove "expired" ones.
429         Lock glock(m_lock);
430 #ifdef WIN32
431         struct _stat stat_buf;
432         if (_stat(cdpfile.c_str(), &stat_buf) == 0) {
433 #else
434         struct stat stat_buf;
435         if (stat(cdpfile.c_str(), &stat_buf) == 0) {
436 #endif
437             SecurityHelper::loadCRLsFromFile(crls, cdpfile.c_str());
438             if (crls.empty() || crls.front()->getProviderName() != DSIGConstants::s_unicodeStrPROVOpenSSL ||
439                 X509_cmp_time(X509_CRL_get_nextUpdate(static_cast<OpenSSLCryptoX509CRL*>(crls.front())->getOpenSSLX509CRL()), &now) < 0) {
440                 for_each(crls.begin(), crls.end(), xmltooling::cleanup<XSECCryptoX509CRL>());
441                 crls.clear();
442                 remove(cdpfile.c_str());    // may as well delete the local copy
443                 m_crlUpdateMap.erase(cdpuri);
444                 m_log.info("deleting cached CRL from %s with nextUpdate field in the past", cdpuri);
445             }
446         }
447     }
448     catch (exception& ex) {
449         m_log.error("exception loading cached copy of CRL from %s: %s", cdpuri, ex.what());
450     }
451
452     if (crls.empty() || !isFreshCRL(crls.front(), &m_log)) {
453         bool updateTimestamp = true;
454         try {
455             // If we get here, the cached copy didn't exist yet, or it's time to refresh.
456             // To limit the rate of unsuccessful attempts when a CRLDP is unreachable,
457             // we remember the timestamp of the last attempt (both successful/unsuccessful).
458             time_t ts = 0;
459             m_lock.lock();
460             map<string,time_t>::const_iterator tsit = m_crlUpdateMap.find(cdpuri);
461             if (tsit != m_crlUpdateMap.end())
462                 ts = tsit->second;
463             m_lock.unlock();
464
465             if (difftime(now, ts) > m_minRefreshDelay) {
466                 SOAPTransport::Address addr("AbstractPKIXTrustEngine", cdpuri, cdpuri);
467                 string scheme(addr.m_endpoint, strchr(addr.m_endpoint,':') - addr.m_endpoint);
468                 auto_ptr<SOAPTransport> soap(XMLToolingConfig::getConfig().SOAPTransportManager.newPlugin(scheme.c_str(), addr));
469                 soap->send();
470                 istream& msg = soap->receive();
471                 Lock glock(m_lock);
472                 ofstream out(cdpstaging.c_str(), fstream::trunc|fstream::binary);
473                 out << msg.rdbuf();
474                 out.close();
475                 SecurityHelper::loadCRLsFromFile(crls, cdpstaging.c_str());
476                 if (crls.empty() || crls.front()->getProviderName() != DSIGConstants::s_unicodeStrPROVOpenSSL ||
477                     X509_cmp_time(X509_CRL_get_nextUpdate(static_cast<OpenSSLCryptoX509CRL*>(crls.front())->getOpenSSLX509CRL()), &now) < 0) {
478                     // The "new" CRL wasn't usable, so get rid of it.
479                     for_each(crls.begin(), crls.end(), xmltooling::cleanup<XSECCryptoX509CRL>());
480                     crls.clear();
481                     remove(cdpstaging.c_str());
482                     m_log.error("ignoring CRL retrieved from %s with nextUpdate field in the past", cdpuri);
483                 }
484                 else {
485                     // "Commit" the new CRL. Note that we might add a CRL which doesn't pass
486                     // isFreshCRL, but that's preferrable over adding none at all.
487                     m_log.info("CRL refreshed from %s", cdpuri);
488                     remove(cdpfile.c_str());
489                     if (rename(cdpstaging.c_str(), cdpfile.c_str()) != 0)
490                         m_log.error("unable to rename CRL staging file");
491                 }
492             }
493             else {
494                 updateTimestamp = false;    // don't update if we're within the backoff window
495             }
496         }
497         catch (exception& ex) {
498             m_log.error("exception downloading/caching CRL from %s: %s", cdpuri, ex.what());
499         }
500
501         if (updateTimestamp) {
502             Lock glock(m_lock);
503             m_crlUpdateMap[cdpuri] = now;
504         }
505     }
506
507     if (crls.empty())
508         return nullptr;
509     for_each(crls.begin() + 1, crls.end(), xmltooling::cleanup<XSECCryptoX509CRL>());
510     return crls.front();
511 }
512
513 bool PKIXPathValidator::isFreshCRL(XSECCryptoX509CRL *c, Category* log) const
514 {
515     if (c) {
516         const X509_CRL* crl = static_cast<OpenSSLCryptoX509CRL*>(c)->getOpenSSLX509CRL();
517         time_t thisUpdate = getCRLTime(X509_CRL_get_lastUpdate(crl));
518         time_t nextUpdate = getCRLTime(X509_CRL_get_nextUpdate(crl));
519         time_t now = time(nullptr);
520
521         if (thisUpdate < 0 || nextUpdate < 0) {
522             // we failed to parse at least one of the fields (they were not encoded
523             // as required by RFC 5280, actually)
524             time_t exp = now + m_minSecondsRemaining;
525             if (log) {
526                 log->warn("isFreshCRL (issuer '%s'): improperly encoded thisUpdate or nextUpdate field - falling back to simple time comparison",
527                           (X509_NAME_to_string(X509_CRL_get_issuer(crl))).c_str());
528             }
529             return (X509_cmp_time(X509_CRL_get_nextUpdate(crl), &exp) > 0) ? true : false;
530         }
531         else {
532             if (log && log->isDebugEnabled()) {
533                 log->debug("isFreshCRL (issuer '%s'): %.0f seconds until nextUpdate (%3.2f%% elapsed since thisUpdate)",
534                           (X509_NAME_to_string(X509_CRL_get_issuer(crl))).c_str(),
535                           difftime(nextUpdate, now), (difftime(now, thisUpdate) * 100) / difftime(nextUpdate, thisUpdate));
536             }
537
538             // consider it recent enough if there are at least MIN_SECS_REMAINING
539             // to the nextUpdate, and at least MIN_PERCENT_REMAINING of its
540             // overall "validity" are remaining to the nextUpdate
541             return (now + m_minSecondsRemaining < nextUpdate) &&
542                     ((difftime(nextUpdate, now) * 100) / difftime(nextUpdate, thisUpdate) > m_minPercentRemaining);
543         }
544     }
545     return false;
546 }