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