https://issues.shibboleth.net/jira/browse/CPPXT-70
authorScott Cantor <cantor.2@osu.edu>
Sun, 4 Sep 2011 01:33:44 +0000 (01:33 +0000)
committerScott Cantor <cantor.2@osu.edu>
Sun, 4 Sep 2011 01:33:44 +0000 (01:33 +0000)
17 files changed:
cpp-xmltooling.sln
xmltooling/Makefile.am
xmltooling/XMLToolingConfig.cpp
xmltooling/XMLToolingConfig.h
xmltooling/internal.h
xmltooling/security/AbstractPKIXTrustEngine.h
xmltooling/security/OpenSSLPathValidator.h [new file with mode: 0644]
xmltooling/security/PKIXPathValidatorParams.h [new file with mode: 0644]
xmltooling/security/PathValidator.h [new file with mode: 0644]
xmltooling/security/impl/AbstractPKIXTrustEngine.cpp
xmltooling/security/impl/PKIXPathValidator.cpp [new file with mode: 0644]
xmltooling/soap/SOAPTransport.h
xmltooling/soap/impl/CURLSOAPTransport.cpp
xmltooling/soap/impl/SOAPClient.cpp
xmltooling/util/Threads.h
xmltooling/xmltooling.vcxproj
xmltooling/xmltooling.vcxproj.filters

index f90a032..be5fd75 100644 (file)
@@ -12,7 +12,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Misc", "Misc", "{61BF324D-2
                doxygen.cfg = doxygen.cfg\r
                m4\doxygen.m4 = m4\doxygen.m4\r
                Makefile.am = Makefile.am\r
-               xmltooling.spec.in = xmltooling.spec.in\r
        EndProjectSection\r
 EndProject\r
 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Schemas", "Schemas", "{23EF5C29-2A13-4F73-99D1-96B8120F148E}"\r
@@ -75,7 +74,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Doc", "Doc", "{847EC34E-006
                doc\LICENSE.txt = doc\LICENSE.txt\r
                doc\LOG4CPP.LICENSE = doc\LOG4CPP.LICENSE\r
                doc\Makefile.am = doc\Makefile.am\r
-               doc\NOTICE.txt = doc\NOTICE.txt\r
                doc\OPENSSL.LICENSE = doc\OPENSSL.LICENSE\r
                doc\README.txt = doc\README.txt\r
        EndProjectSection\r
index e6e946a..8b987bd 100644 (file)
@@ -78,6 +78,9 @@ secinclude_HEADERS = \
        security/KeyInfoCredentialContext.h \
        security/KeyInfoResolver.h \
        security/OpenSSLCredential.h \
+       security/OpenSSLPathValidator.h \
+       security/PathValidator.h \
+       security/PKIXPathValidatorParams.h \
        security/SecurityHelper.h \
        security/SignatureTrustEngine.h \
        security/TrustEngine.h \
@@ -143,6 +146,7 @@ xmlsec_sources = \
        security/impl/InlineKeyResolver.cpp \
        security/impl/KeyInfoResolver.cpp \
        security/impl/OpenSSLCryptoX509CRL.cpp \
+       security/impl/PKIXPathValidator.cpp \
        security/impl/SecurityHelper.cpp \
        security/impl/StaticPKIXTrustEngine.cpp \
        security/impl/TrustEngine.cpp \
index f5b1e40..740e100 100644 (file)
 #include "security/OpenSSLCryptoX509CRL.h"
 #include "security/CredentialResolver.h"
 #include "security/KeyInfoResolver.h"
+#include "security/PathValidator.h"
 #include "signature/KeyInfo.h"
 #include "signature/Signature.h"
 #include "soap/SOAP.h"
+#include "soap/SOAPTransport.h"
 #include "util/NDC.h"
 #include "util/PathResolver.h"
 #include "util/ReplayCache.h"
@@ -453,16 +455,20 @@ bool XMLToolingInternalConfig::init()
         REGISTER_XMLTOOLING_EXCEPTION_FACTORY(EncryptionException,xmlencryption);
         registerKeyInfoClasses();
         registerEncryptionClasses();
-        registerKeyInfoResolvers();
         registerCredentialResolvers();
+        registerKeyInfoResolvers();
+        registerPathValidators();
         registerTrustEngines();
         registerXMLAlgorithms();
-        registerSOAPTransports();
-        initSOAPTransports();
-        registerStorageServices();
         m_keyInfoResolver = KeyInfoResolverManager.newPlugin(INLINE_KEYINFO_RESOLVER,nullptr);
 #endif
 
+#ifndef XMLTOOLING_LITE
+        registerStorageServices();
+#endif
+        registerSOAPTransports();
+        initSOAPTransports();
+
         m_pathResolver = new PathResolver();
         m_urlEncoder = new URLEncoder();
 
@@ -522,10 +528,14 @@ void XMLToolingInternalConfig::term()
     XMLToolingException::deregisterFactories();
     AttributeExtensibleXMLObject::deregisterIDAttributes();
 
-#ifndef XMLTOOLING_NO_XMLSEC
-    StorageServiceManager.deregisterFactories();
     termSOAPTransports();
     SOAPTransportManager.deregisterFactories();
+
+#ifndef XMLTOOLING_LITE
+    StorageServiceManager.deregisterFactories();
+#endif
+
+#ifndef XMLTOOLING_NO_XMLSEC
     TrustEngineManager.deregisterFactories();
     CredentialResolverManager.deregisterFactories();
     KeyInfoResolverManager.deregisterFactories();
@@ -569,6 +579,9 @@ void XMLToolingInternalConfig::term()
     delete m_validatingPool;
     m_validatingPool=nullptr;
 
+    for_each(m_namedLocks.begin(), m_namedLocks.end(), cleanup_pair<string,Mutex>());
+    m_namedLocks.clear();
+
 #ifndef XMLTOOLING_NO_XMLSEC
     delete m_xsecProvider;
     m_xsecProvider=nullptr;
@@ -594,6 +607,17 @@ void XMLToolingInternalConfig::unlock()
     m_lock->unlock();
 }
 
+Mutex& XMLToolingInternalConfig::getNamedMutex(const char* name)
+{
+    Locker glock(this);
+    map<string,Mutex*>::const_iterator m = m_namedLocks.find(name);
+    if (m != m_namedLocks.end())
+        return *(m->second);
+    Mutex* newlock = Mutex::create();
+    m_namedLocks[name] = newlock;
+    return *newlock;
+}
+
 bool XMLToolingInternalConfig::load_library(const char* path, void* context)
 {
 #ifdef _DEBUG
index 26e01a9..928ce71 100644 (file)
@@ -41,6 +41,7 @@
 
 namespace xmltooling {
     
+    class XMLTOOL_API Mutex;
     class XMLTOOL_API ParserPool;
     class XMLTOOL_API PathResolver;
     class XMLTOOL_API TemplateEngine;
@@ -52,6 +53,7 @@ namespace xmltooling {
 #ifndef XMLTOOLING_NO_XMLSEC
     class XMLTOOL_API CredentialResolver;
     class XMLTOOL_API KeyInfoResolver;
+    class XMLTOOL_API PathValidator;
     class XMLTOOL_API TrustEngine;
     class XMLTOOL_API XSECCryptoX509CRL;
 #endif
@@ -156,6 +158,15 @@ namespace xmltooling {
          */
         virtual ParserPool& getValidatingParser() const=0;
 
+        /**
+         * Returns a reference to a named mutex.
+         * <p>The first access to a given name will create the object.
+         *
+         * @param name  name of mutex to access
+         * @return  reference to a mutex object
+         */
+        virtual Mutex& getNamedMutex(const char* name)=0;
+
 #ifndef XMLTOOLING_NO_XMLSEC
         /**
          * Returns the global KeyInfoResolver instance.
@@ -254,6 +265,13 @@ namespace xmltooling {
          */
         unsigned int clock_skew_secs;
 
+#ifndef XMLTOOLING_LITE
+        /**
+         * Manages factories for StorageService plugins.
+         */
+        PluginManager<StorageService,std::string,const xercesc::DOMElement*> StorageServiceManager;
+#endif
+
 #ifndef XMLTOOLING_NO_XMLSEC
         /**
          * Returns an X.509 CRL implementation object.
@@ -261,14 +279,19 @@ namespace xmltooling {
         virtual XSECCryptoX509CRL* X509CRL() const=0;
 
         /**
+         * Manages factories for CredentialResolver plugins.
+         */
+        PluginManager<CredentialResolver,std::string,const xercesc::DOMElement*> CredentialResolverManager;
+
+        /**
          * Manages factories for KeyInfoResolver plugins.
          */
         PluginManager<KeyInfoResolver,std::string,const xercesc::DOMElement*> KeyInfoResolverManager;
 
         /**
-         * Manages factories for CredentialResolver plugins.
+         * Manages factories for PathValidator plugins.
          */
-        PluginManager<CredentialResolver,std::string,const xercesc::DOMElement*> CredentialResolverManager;
+        PluginManager<PathValidator,std::string,const xercesc::DOMElement*> PathValidatorManager;
 
         /**
          * Manages factories for TrustEngine plugins.
@@ -276,11 +299,6 @@ namespace xmltooling {
         PluginManager<TrustEngine,std::string,const xercesc::DOMElement*> TrustEngineManager;
 
         /**
-         * Manages factories for StorageService plugins.
-         */
-        PluginManager<StorageService,std::string,const xercesc::DOMElement*> StorageServiceManager;
-
-        /**
          * Maps an XML Signature/Encryption algorithm identifier to a library-specific
          * key algorithm and size for use in resolving credentials.
          *
index d441ad4..caf1be0 100644 (file)
 #include "XMLToolingConfig.h"
 #include "util/ParserPool.h"
 
+#include <map>
+#include <string>
 #include <vector>
+
 #ifndef XMLTOOLING_NO_XMLSEC
     #include <xsec/framework/XSECProvider.hpp>
 #endif
@@ -95,6 +98,9 @@ namespace xmltooling {
         Lockable* lock();
         void unlock();
 
+        // named mutexes to limit lock scope
+        Mutex& getNamedMutex(const char* name);
+
         // configuration
         bool load_library(const char* path, void* context=nullptr);
         bool log_config(const char* config=nullptr);
@@ -126,6 +132,7 @@ namespace xmltooling {
     private:
         int m_initCount;
         Mutex* m_lock;
+        std::map<std::string,Mutex*> m_namedLocks;
         std::vector<void*> m_libhandles;
         ParserPool* m_parserPool;
         ParserPool* m_validatingPool;
index 2437e2d..b828fbe 100644 (file)
@@ -36,6 +36,7 @@
 
 namespace xmltooling {
 
+    class XMLTOOL_API OpenSSLPathValidator;
     class XMLTOOL_API XSECCryptoX509CRL;
 
     /**
@@ -54,6 +55,7 @@ namespace xmltooling {
          *  <li>checkRevocation attribute (off, entityOnly, fullChain)
          *  <li>policyMappingInhibit attribute (boolean)
          *  <li>anyPolicyInhibit attribute (boolean)
+         *  <li>&t;PathValidator&gt; element (zero or more)
          *  <li>&lt;TrustedName&gt; element (zero or more)
          *  <li>&lt;PolicyOID&gt; element (zero or more)
          * </ul>
@@ -62,6 +64,9 @@ namespace xmltooling {
          */
         AbstractPKIXTrustEngine(const xercesc::DOMElement* e=nullptr);
 
+        /** Plugins used to perform path validation. */
+        std::vector<OpenSSLPathValidator*> m_pathValidators;
+
         /** Controls revocation checking, currently limited to CRLs and supports "off", "entityOnly", "fullChain". */
         std::string m_checkRevocation;
 
@@ -198,6 +203,8 @@ namespace xmltooling {
             CredentialCriteria* criteria=nullptr,
             const std::vector<XSECCryptoX509CRL*>* inlineCRLs=nullptr
             ) const;
+
+        friend class XMLTOOL_DLLLOCAL PKIXParams;
     };
 };
 
diff --git a/xmltooling/security/OpenSSLPathValidator.h b/xmltooling/security/OpenSSLPathValidator.h
new file mode 100644 (file)
index 0000000..cd365ed
--- /dev/null
@@ -0,0 +1,68 @@
+/**
+ * Licensed to the University Corporation for Advanced Internet
+ * Development, Inc. (UCAID) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ *
+ * UCAID licenses this file to you under the Apache License,
+ * Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the
+ * License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific
+ * language governing permissions and limitations under the License.
+ */
+
+/**
+ * @file xmltooling/security/OpenSSLPathValidator.h
+ * 
+ * Extended PathValidator interface that adds validation
+ * using OpenSSL data types directly for efficiency.
+ */
+
+#if !defined(__xmltooling_opensslpathval_h__) && !defined(XMLTOOLING_NO_XMLSEC)
+#define __xmltooling_opensslpathval_h__
+
+#include <xmltooling/security/PathValidator.h>
+
+#include <openssl/x509.h>
+
+
+namespace xmltooling {
+
+    /**
+     * Extended PathValidator interface that adds validation
+     * using OpenSSL data types directly for efficiency.
+     */
+    class XMLTOOL_API OpenSSLPathValidator : public PathValidator
+    {
+        MAKE_NONCOPYABLE(OpenSSLPathValidator);
+    protected:
+        OpenSSLPathValidator();
+
+    public:
+        virtual ~OpenSSLPathValidator();
+
+        /**
+         * Validates an end-entity certificate.
+         * 
+         * @param certEE    end-entity certificate
+         * @param certChain the complete untrusted certificate chain
+         * @param params    plugin-specific parameters to the validation process
+         * @return  true iff validaton succeeds
+         */
+        virtual bool validate(
+            X509* certEE,
+            STACK_OF(X509)* certChain,
+            const PathValidatorParams& params
+            ) const=0;
+
+    };
+};
+
+#endif /* __xmltooling_opensslpathval_h__ */
diff --git a/xmltooling/security/PKIXPathValidatorParams.h b/xmltooling/security/PKIXPathValidatorParams.h
new file mode 100644 (file)
index 0000000..e460e4e
--- /dev/null
@@ -0,0 +1,108 @@
+/**
+ * Licensed to the University Corporation for Advanced Internet
+ * Development, Inc. (UCAID) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ *
+ * UCAID licenses this file to you under the Apache License,
+ * Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the
+ * License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific
+ * language governing permissions and limitations under the License.
+ */
+
+/**
+ * @file xmltooling/security/PKIXPathValidatorParams.h
+ * 
+ * PKIX-specific parameters to a PathValidator.
+ */
+
+#if !defined(__xmltooling_pkixvalparam_h__) && !defined(XMLTOOLING_NO_XMLSEC)
+#define __xmltooling_pkixvalparam_h__
+
+#include <xmltooling/security/PathValidator.h>
+
+#include <set>
+#include <string>
+
+namespace xmltooling {
+
+    class XMLTOOL_API XSECCryptoX509CRL;
+
+    /**
+     * PKIX-specific parameters to a PathValidator.
+     */
+    class XMLTOOL_API PKIXPathValidatorParams : public PathValidator::PathValidatorParams
+    {
+    protected:
+        PKIXPathValidatorParams();
+
+    public:
+        virtual ~PKIXPathValidatorParams();
+
+        /**
+         * Returns the allowable trust chain verification depth.
+         * 
+         * @return  allowable trust chain verification depth
+         */
+        virtual int getVerificationDepth() const=0;
+
+        /**
+         * Checks whether the any policy OID should be processed
+         * if it is included in a certificate.
+         *
+         * @return true iff the any policy OID should *not* be processed
+         */
+        virtual bool isAnyPolicyInhibited() const=0;
+
+        /**
+         * Checks if policy mapping is inhibited.
+         *
+         * @return true iff policy mapping should not be allowed
+         */
+        virtual bool isPolicyMappingInhibited() const=0;
+
+        /**
+         * Returns a set of policy OIDs.
+         *
+         * @return set of policy OIDs
+         */
+        virtual const std::set<std::string>& getPolicies() const=0;
+
+        /**
+         * Returns a set of trust anchors.
+         * 
+         * @return  set of trust anchors
+         */
+        virtual const std::vector<XSECCryptoX509*>& getTrustAnchors() const=0;
+
+        enum revocation_t {
+            REVOCATION_OFF = 0,
+            REVOCATION_ENTITYONLY = 1,
+            REVOCATION_FULLCHAIN = 2
+        };
+
+        /**
+         * Returns the type of revocation checking to perform.
+         *
+         * @return  revocation checking option
+         */
+        virtual revocation_t getRevocationChecking() const=0;
+
+        /**
+         * Returns a set of CRLs.
+         * 
+         * @return  set of CRLs
+         */
+        virtual const std::vector<XSECCryptoX509CRL*>& getCRLs() const=0;
+    };
+};
+
+#endif /* __xmltooling_pkixvalparam_h__ */
diff --git a/xmltooling/security/PathValidator.h b/xmltooling/security/PathValidator.h
new file mode 100644 (file)
index 0000000..736eeb3
--- /dev/null
@@ -0,0 +1,87 @@
+/**
+ * Licensed to the University Corporation for Advanced Internet
+ * Development, Inc. (UCAID) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ *
+ * UCAID licenses this file to you under the Apache License,
+ * Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the
+ * License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific
+ * language governing permissions and limitations under the License.
+ */
+
+/**
+ * @file xmltooling/security/PathValidator.h
+ * 
+ * Plugin interface to certificate path validation.
+ */
+
+#if !defined(__xmltooling_pathval_h__) && !defined(XMLTOOLING_NO_XMLSEC)
+#define __xmltooling_pathval_h__
+
+#include <vector>
+
+class XSECCryptoX509;
+
+namespace xmltooling {
+
+    /**
+     * Plugin interface to certificate path validation, independent of context.
+     * <p>This interface assumes that the end-entity certificate is "correctly"
+     * bound to a party, and solely addresses the validity of that certificate.
+     */
+    class XMLTOOL_API PathValidator
+    {
+        MAKE_NONCOPYABLE(PathValidator);
+    protected:
+        PathValidator();
+
+    public:
+        virtual ~PathValidator();
+
+        /**
+         * Marker interface for plugin-specific parameters into the validation
+         * process.
+         */
+        class XMLTOOL_API PathValidatorParams {
+            MAKE_NONCOPYABLE(PathValidatorParams);
+        protected:
+            PathValidatorParams();
+            
+        public:
+            virtual ~PathValidatorParams();
+        };
+        
+        /**
+         * Validates an end-entity certificate.
+         * 
+         * @param certEE    end-entity certificate
+         * @param certChain the complete untrusted certificate chain
+         * @param params    plugin-specific parameters to the validation process
+         * @return  true iff validaton succeeds
+         */
+        virtual bool validate(
+            XSECCryptoX509* certEE,
+            const std::vector<XSECCryptoX509*>& certChain,
+            const PathValidatorParams& params
+            ) const=0;
+    };
+
+    /**
+     * Registers PathValidator classes into the runtime.
+     */
+    void XMLTOOL_API registerPathValidators();
+
+    /** PathValidator based on PKIX. */
+    #define PKIX_PATHVALIDATOR  "PKIX"
+};
+
+#endif /* __xmltooling_pathval_h__ */
index 5d7a598..dd450b6 100644 (file)
@@ -34,6 +34,8 @@
 #include "security/CredentialResolver.h"
 #include "security/KeyInfoResolver.h"
 #include "security/OpenSSLCryptoX509CRL.h"
+#include "security/OpenSSLPathValidator.h"
+#include "security/PKIXPathValidatorParams.h"
 #include "security/SecurityHelper.h"
 #include "security/X509Credential.h"
 #include "signature/SignatureValidator.h"
@@ -41,8 +43,6 @@
 #include "util/PathResolver.h"
 
 #include <fstream>
-#include <openssl/x509_vfy.h>
-#include <openssl/x509v3.h>
 #include <xercesc/util/XMLUniDefs.hpp>
 #include <xsec/enc/OpenSSL/OpenSSLCryptoX509.hpp>
 
@@ -52,434 +52,65 @@ using namespace xmltooling;
 using namespace std;
 
 
-namespace {
-    static int XMLTOOL_DLLLOCAL error_callback(int ok, X509_STORE_CTX* ctx)
+namespace xmltooling {
+    // Adapter between TrustEngine and PathValidator
+    class XMLTOOL_DLLLOCAL PKIXParams : public PKIXPathValidatorParams
     {
-        if (!ok)
-            Category::getInstance("OpenSSL").error("path validation failure: %s", X509_verify_cert_error_string(ctx->error));
-        return ok;
-    }
-
-    static string XMLTOOL_DLLLOCAL X509_NAME_to_string(X509_NAME* n)
-    {
-        string s;
-        BIO* b = BIO_new(BIO_s_mem());
-        X509_NAME_print_ex(b,n,0,XN_FLAG_RFC2253);
-        BIO_flush(b);
-        BUF_MEM* bptr=nullptr;
-        BIO_get_mem_ptr(b, &bptr);
-        if (bptr && bptr->length > 0) {
-            s.append(bptr->data, bptr->length);
-        }
-        BIO_free(b);
-        return s;
-    }
-
-    static time_t XMLTOOL_DLLLOCAL getCRLTime(const ASN1_TIME *a)
-    {
-        struct tm t;
-        memset(&t, 0, sizeof(t));
-        // RFC 5280, sections 5.1.2.4 and 5.1.2.5 require thisUpdate and nextUpdate
-        // to be encoded as UTCTime until 2049, and RFC 5280 section 4.1.2.5.1
-        // further restricts the format to "YYMMDDHHMMSSZ" ("even where the number
-        // of seconds is zero").
-        // As long as OpenSSL doesn't provide any API to convert ASN1_TIME values
-        // time_t, we therefore have to parse it ourselves, unfortunately.
-        if (sscanf((const char*)a->data, "%2d%2d%2d%2d%2d%2dZ",
-            &t.tm_year, &t.tm_mon, &t.tm_mday,
-            &t.tm_hour, &t.tm_min, &t.tm_sec) == 6) {
-            if (t.tm_year <= 50) {
-                // RFC 5280, section 4.1.2.5.1
-                t.tm_year += 100;
-            }
-            t.tm_mon--;
-#if defined(HAVE_TIMEGM)
-            return timegm(&t);
-#else
-            // Windows, and hopefully most others...?
-            return mktime(&t) - timezone;
-#endif
-        }
-        return (time_t)-1;
-    }
-
-    static bool XMLTOOL_DLLLOCAL isFreshCRL(XSECCryptoX509CRL *c, Category* log=nullptr)
-    {
-        // eventually, these should be made configurable
-        #define MIN_SECS_REMAINING 86400
-        #define MIN_PERCENT_REMAINING 10
-        if (c) {
-            const X509_CRL* crl = static_cast<OpenSSLCryptoX509CRL*>(c)->getOpenSSLX509CRL();
-            time_t thisUpdate = getCRLTime(X509_CRL_get_lastUpdate(crl));
-            time_t nextUpdate = getCRLTime(X509_CRL_get_nextUpdate(crl));
-            time_t now = time(nullptr);
-
-            if (thisUpdate < 0 || nextUpdate < 0) {
-                // we failed to parse at least one of the fields (they were not encoded
-                // as required by RFC 5280, actually)
-                time_t exp = now + MIN_SECS_REMAINING;
-                if (log) {
-                    log->warn("isFreshCRL (issuer '%s'): improperly encoded thisUpdate or nextUpdate field - falling back to simple time comparison",
-                              (X509_NAME_to_string(X509_CRL_get_issuer(crl))).c_str());
-                }
-                return (X509_cmp_time(X509_CRL_get_nextUpdate(crl), &exp) > 0) ? true : false;
-            }
-            else {
-                if (log && log->isDebugEnabled()) {
-                    log->debug("isFreshCRL (issuer '%s'): %.0f seconds until nextUpdate (%3.2f%% elapsed since thisUpdate)",
-                              (X509_NAME_to_string(X509_CRL_get_issuer(crl))).c_str(),
-                              difftime(nextUpdate, now), (difftime(now, thisUpdate) * 100) / difftime(nextUpdate, thisUpdate));
-                }
-
-                // consider it recent enough if there are at least MIN_SECS_REMAINING
-                // to the nextUpdate, and at least MIN_PERCENT_REMAINING of its
-                // overall "validity" are remaining to the nextUpdate
-                return (now + MIN_SECS_REMAINING < nextUpdate) &&
-                        ((difftime(nextUpdate, now) * 100) / difftime(nextUpdate, thisUpdate) > MIN_PERCENT_REMAINING);
-            }
-        }
-        return false;
-    }
-
-    static XSECCryptoX509CRL* XMLTOOL_DLLLOCAL getRemoteCRLs(const char* cdpuri, Category& log) {
-        // This is a temporary CRL cache implementation to avoid breaking binary compatibility
-        // for the library. Caching can't rely on any member objects within the TrustEngine,
-        // including locks, so we're using the global library lock for the time being.
-        // All other state is kept in the file system.
-
-        // minimum number of seconds between re-attempting a download from one particular CRLDP
-        #define MIN_RETRY_WAIT 60
-
-        // The filenames for the CRL cache are based on a hash of the CRL location.
-        string cdpfile = SecurityHelper::doHash("SHA1", cdpuri, strlen(cdpuri)) + ".crl";
-        XMLToolingConfig::getConfig().getPathResolver()->resolve(cdpfile, PathResolver::XMLTOOLING_RUN_FILE);
-        string cdpstaging = cdpfile + ".tmp";
-        string tsfile = cdpfile + ".ts";
-
-        time_t now = time(nullptr);
-        vector<XSECCryptoX509CRL*> crls;
-
-        try {
-            // While holding the lock, check for a cached copy of the CRL, and remove "expired" ones.
-            Locker glock(&XMLToolingConfig::getConfig());
-#ifdef WIN32
-            struct _stat stat_buf;
-            if (_stat(cdpfile.c_str(), &stat_buf) == 0) {
-#else
-            struct stat stat_buf;
-            if (stat(cdpfile.c_str(), &stat_buf) == 0) {
-#endif
-                SecurityHelper::loadCRLsFromFile(crls, cdpfile.c_str());
-                if (crls.empty() || crls.front()->getProviderName() != DSIGConstants::s_unicodeStrPROVOpenSSL ||
-                    X509_cmp_time(X509_CRL_get_nextUpdate(static_cast<OpenSSLCryptoX509CRL*>(crls.front())->getOpenSSLX509CRL()), &now) < 0) {
-                    for_each(crls.begin(), crls.end(), xmltooling::cleanup<XSECCryptoX509CRL>());
-                    crls.clear();
-                    remove(cdpfile.c_str());    // may as well delete the local copy
-                    remove(tsfile.c_str());
-                    log.info("deleting cached CRL from %s with nextUpdate field in the past", cdpuri);
-                }
+        const AbstractPKIXTrustEngine& m_trust;
+        const AbstractPKIXTrustEngine::PKIXValidationInfoIterator& m_pkixInfo;
+        vector<XSECCryptoX509CRL*> m_crls;
+    public:
+        PKIXParams(
+            const AbstractPKIXTrustEngine& t,
+            const AbstractPKIXTrustEngine::PKIXValidationInfoIterator& pkixInfo,
+            const vector<XSECCryptoX509CRL*>* inlineCRLs
+            ) : m_trust(t), m_pkixInfo(pkixInfo) {
+            if (inlineCRLs && !inlineCRLs->empty()) {
+                m_crls = *inlineCRLs;
+                m_crls.insert(m_crls.end(), pkixInfo.getCRLs().begin(), pkixInfo.getCRLs().end());
             }
         }
-        catch (exception& ex) {
-            log.error("exception loading cached copy of CRL from %s: %s", cdpuri, ex.what());
-        }
 
-        if (crls.empty() || !isFreshCRL(crls.front(), &log)) {
-            bool updateTimestamp = true;
-            try {
-                // If we get here, the cached copy didn't exist yet, or it's time to refresh.
-                // To limit the rate of unsuccessful attempts when a CRLDP is unreachable,
-                // we remember the timestamp of the last attempt (both successful/unsuccessful).
-                // We store this in the file system because of the binary compatibility issue.
-                time_t ts = 0;
-                try {
-                    Locker glock(&XMLToolingConfig::getConfig());
-                    ifstream tssrc(tsfile.c_str());
-                    if (tssrc)
-                        tssrc >> ts;
-                }
-                catch (exception&) {
-                    ts = 0;
-                }
+        virtual ~PKIXParams() {}
 
-                if (difftime(now, ts) > MIN_RETRY_WAIT) {
-                    SOAPTransport::Address addr("AbstractPKIXTrustEngine", cdpuri, cdpuri);
-                    string scheme(addr.m_endpoint, strchr(addr.m_endpoint,':') - addr.m_endpoint);
-                    auto_ptr<SOAPTransport> soap(XMLToolingConfig::getConfig().SOAPTransportManager.newPlugin(scheme.c_str(), addr));
-                    soap->send();
-                    istream& msg = soap->receive();
-                    Locker glock(&XMLToolingConfig::getConfig());
-                    ofstream out(cdpstaging.c_str(), fstream::trunc|fstream::binary);
-                    out << msg.rdbuf();
-                    out.close();
-                    SecurityHelper::loadCRLsFromFile(crls, cdpstaging.c_str());
-                    if (crls.empty() || crls.front()->getProviderName() != DSIGConstants::s_unicodeStrPROVOpenSSL ||
-                        X509_cmp_time(X509_CRL_get_nextUpdate(static_cast<OpenSSLCryptoX509CRL*>(crls.front())->getOpenSSLX509CRL()), &now) < 0) {
-                        // The "new" CRL wasn't usable, so get rid of it.
-                        for_each(crls.begin(), crls.end(), xmltooling::cleanup<XSECCryptoX509CRL>());
-                        crls.clear();
-                        remove(cdpstaging.c_str());
-                        log.error("ignoring CRL retrieved from %s with nextUpdate field in the past", cdpuri);
-                    }
-                    else {
-                        // "Commit" the new CRL. Note that we might add a CRL which doesn't pass
-                        // isFreshCRL, but that's preferrable over adding none at all.
-                        log.info("CRL refreshed from %s", cdpuri);
-                        remove(cdpfile.c_str());
-                        if (rename(cdpstaging.c_str(), cdpfile.c_str()) != 0)
-                            log.error("unable to rename CRL staging file");
-                    }
-                }
-                else {
-                    updateTimestamp = false;    // don't update if we're within the backoff window
-                }
-            }
-            catch (exception& ex) {
-                log.error("exception downloading/caching CRL from %s: %s", cdpuri, ex.what());
-            }
-
-            if (updateTimestamp) {
-                // update the timestamp file
-                Locker glock(&XMLToolingConfig::getConfig());
-                ofstream tssink(tsfile.c_str(), fstream::trunc);
-                tssink << now;
-                tssink.close();
-            }
+        int getVerificationDepth() const {
+            return m_pkixInfo.getVerificationDepth();
         }
-
-        if (crls.empty())
-            return nullptr;
-        for_each(crls.begin() + 1, crls.end(), xmltooling::cleanup<XSECCryptoX509CRL>());
-        return crls.front();
-    }
-
-    static bool XMLTOOL_DLLLOCAL validate(
-        X509* EE,
-        STACK_OF(X509)* untrusted,
-        AbstractPKIXTrustEngine::PKIXValidationInfoIterator* pkixInfo,
-        bool useCRL,
-        bool fullCRLChain,
-        const vector<XSECCryptoX509CRL*>* inlineCRLs=nullptr,
-        bool policyMappingInhibit=false,
-        bool anyPolicyInhibit=false,
-        const set<string>* policyOIDs=nullptr
-        )
-    {
-        Category& log=Category::getInstance(XMLTOOLING_LOGCAT".TrustEngine");
-    
-        // First we build a stack of CA certs. These objects are all referenced in place.
-        log.debug("supplying PKIX Validation information");
-    
-        // We need this for CRL support.
-        X509_STORE* store=X509_STORE_new();
-        if (!store) {
-            log_openssl();
-            return false;
+        bool isAnyPolicyInhibited() const {
+            return m_trust.m_anyPolicyInhibit;
         }
-
-        // PKIX policy checking (cf. RFCs 3280/5280 section 6)
-        if (policyMappingInhibit || anyPolicyInhibit || (policyOIDs && !policyOIDs->empty())) {
-#if (OPENSSL_VERSION_NUMBER < 0x00908000L)
-            log.error("PKIX policy checking option is configured, but OpenSSL version is less than 0.9.8");
-            X509_STORE_free(store);
-            return false;
-#else
-            unsigned long pflags = 0;
-            X509_VERIFY_PARAM *vpm = X509_VERIFY_PARAM_new();
-            if (!vpm) {
-                log_openssl();
-                X509_STORE_free(store);
-                return false;
-            }
-
-            // populate the "user-initial-policy-set" input variable
-            if (policyOIDs && !policyOIDs->empty()) {
-                for (set<string>::const_iterator o=policyOIDs->begin(); o!=policyOIDs->end(); o++) {
-                    ASN1_OBJECT *oid = OBJ_txt2obj(o->c_str(), 1);
-                    if (oid && X509_VERIFY_PARAM_add0_policy(vpm, oid)) {
-                        log.debug("OID (%s) added to set of acceptable policies", o->c_str());
-                    }
-                    else {
-                        log_openssl();
-                        log.error("unable to parse/configure policy OID value (%s)", o->c_str());
-                        if (oid)
-                            ASN1_OBJECT_free(oid);
-                        X509_VERIFY_PARAM_free(vpm);
-                        X509_STORE_free(store);
-                        return false;
-                    }
-                }
-                // when the user has supplied at least one policy OID, he obviously wants to check
-                // for an explicit policy ("initial-explicit-policy")
-                pflags |= X509_V_FLAG_EXPLICIT_POLICY;
-            }
-
-            // "initial-policy-mapping-inhibit" input variable
-            if (policyMappingInhibit)
-                pflags |= X509_V_FLAG_INHIBIT_MAP;
-            // "initial-any-policy-inhibit" input variable
-            if (anyPolicyInhibit)
-                pflags |= X509_V_FLAG_INHIBIT_ANY;
-
-            if (!X509_VERIFY_PARAM_set_flags(vpm, pflags) || !X509_STORE_set1_param(store, vpm)) {
-                log_openssl();
-                log.error("unable to set PKIX policy checking parameters");
-                X509_VERIFY_PARAM_free(vpm);
-                X509_STORE_free(store);
-                return false;
-            }
-
-            X509_VERIFY_PARAM_free(vpm);
-#endif
+        bool isPolicyMappingInhibited() const {
+            return m_trust.m_policyMappingInhibit;
         }
-    
-        // This contains the state of the validate operation.
-        int count=0;
-        X509_STORE_CTX ctx;
-
-        // AFAICT, EE and untrusted are passed in but not owned by the ctx.
-#if (OPENSSL_VERSION_NUMBER >= 0x00907000L)
-        if (X509_STORE_CTX_init(&ctx,store,EE,untrusted)!=1) {
-            log_openssl();
-            log.error("unable to initialize X509_STORE_CTX");
-            X509_STORE_free(store);
-            return false;
+        const set<string>& getPolicies() const {
+            return m_trust.m_policyOIDs;
         }
-#else
-        X509_STORE_CTX_init(&ctx,store,EE,untrusted);
-#endif
-
-        STACK_OF(X509)* CAstack = sk_X509_new_null();
-        const vector<XSECCryptoX509*>& CAcerts = pkixInfo->getTrustAnchors();
-        for (vector<XSECCryptoX509*>::const_iterator i=CAcerts.begin(); i!=CAcerts.end(); ++i) {
-            if ((*i)->getProviderName()==DSIGConstants::s_unicodeStrPROVOpenSSL) {
-                sk_X509_push(CAstack,static_cast<OpenSSLCryptoX509*>(*i)->getOpenSSLX509());
-                ++count;
-            }
+        const vector<XSECCryptoX509*>& getTrustAnchors() const {
+            return m_pkixInfo.getTrustAnchors();
         }
-        log.debug("supplied (%d) CA certificate(s)", count);
-
-        // Seems to be most efficient to just pass in the CA stack.
-        X509_STORE_CTX_trusted_stack(&ctx,CAstack);
-        X509_STORE_CTX_set_depth(&ctx,100);    // we check the depth down below
-        X509_STORE_CTX_set_verify_cb(&ctx,error_callback);
-
-        // Do a first pass verify. If CRLs aren't used, this is the only pass.
-        int ret=X509_verify_cert(&ctx);
-        if (ret==1) {
-            // Now see if the depth was acceptable by counting the number of intermediates.
-            int depth=sk_X509_num(ctx.chain)-2;
-            if (pkixInfo->getVerificationDepth() < depth) {
-                log.error(
-                    "certificate chain was too long (%d intermediates, only %d allowed)",
-                    (depth==-1) ? 0 : depth,
-                    pkixInfo->getVerificationDepth()
-                    );
-                ret=0;
-            }
+        PKIXPathValidatorParams::revocation_t getRevocationChecking() const {
+            if (m_trust.m_checkRevocation.empty() || m_trust.m_checkRevocation == "off")
+                return PKIXPathValidatorParams::REVOCATION_OFF;
+            else if (m_trust.m_checkRevocation == "entityOnly")
+                return PKIXPathValidatorParams::REVOCATION_ENTITYONLY;
+            else if (m_trust.m_checkRevocation == "fullChain")
+                return PKIXPathValidatorParams::REVOCATION_FULLCHAIN;
+            return PKIXPathValidatorParams::REVOCATION_OFF;
         }
-
-        if (useCRL) {
-#if (OPENSSL_VERSION_NUMBER >= 0x00907000L)
-            // When we add CRLs, we have to be sure the nextUpdate hasn't passed, because OpenSSL won't accept
-            // the CRL in that case. If we end up not adding a CRL for a particular link in the chain, the
-            // validation will fail (if the fullChain option was set).
-            set<string> crlissuers;
-            time_t now = time(nullptr);
-            if (inlineCRLs) {
-                for (vector<XSECCryptoX509CRL*>::const_iterator j=inlineCRLs->begin(); j!=inlineCRLs->end(); ++j) {
-                    if ((*j)->getProviderName()==DSIGConstants::s_unicodeStrPROVOpenSSL &&
-                        (X509_cmp_time(X509_CRL_get_nextUpdate(static_cast<OpenSSLCryptoX509CRL*>(*j)->getOpenSSLX509CRL()), &now) > 0)) {
-                        // owned by store
-                        X509_STORE_add_crl(store, X509_CRL_dup(static_cast<OpenSSLCryptoX509CRL*>(*j)->getOpenSSLX509CRL()));
-                        string crlissuer(X509_NAME_to_string(X509_CRL_get_issuer(static_cast<OpenSSLCryptoX509CRL*>(*j)->getOpenSSLX509CRL())));
-                        if (!crlissuer.empty()) {
-                            log.debug("added inline CRL issued by (%s)", crlissuer.c_str());
-                            crlissuers.insert(crlissuer);
-                        }
-                    }
-                }
-            }
-            const vector<XSECCryptoX509CRL*>& crls = pkixInfo->getCRLs();
-            for (vector<XSECCryptoX509CRL*>::const_iterator j=crls.begin(); j!=crls.end(); ++j) {
-                if ((*j)->getProviderName()==DSIGConstants::s_unicodeStrPROVOpenSSL &&
-                    (X509_cmp_time(X509_CRL_get_nextUpdate(static_cast<OpenSSLCryptoX509CRL*>(*j)->getOpenSSLX509CRL()), &now) > 0)) {
-                    // owned by store
-                    X509_STORE_add_crl(store, X509_CRL_dup(static_cast<OpenSSLCryptoX509CRL*>(*j)->getOpenSSLX509CRL()));
-                    string crlissuer(X509_NAME_to_string(X509_CRL_get_issuer(static_cast<OpenSSLCryptoX509CRL*>(*j)->getOpenSSLX509CRL())));
-                    if (!crlissuer.empty()) {
-                        log.debug("added CRL issued by (%s)", crlissuer.c_str());
-                        crlissuers.insert(crlissuer);
-                    }
-                }
-            }
-
-            for (int i = 0; i < sk_X509_num(untrusted); ++i) {
-                X509 *cert = sk_X509_value(untrusted, i);
-                string crlissuer(X509_NAME_to_string(X509_get_issuer_name(cert)));
-                if (crlissuers.count(crlissuer)) {
-                   // We already have a CRL for this cert, so skip CRLDP processing for this one.
-                   continue;
-                }
-
-                bool foundUsableCDP = false;
-                STACK_OF(DIST_POINT)* dps = (STACK_OF(DIST_POINT)*)X509_get_ext_d2i(cert, NID_crl_distribution_points, nullptr, nullptr);
-                for (int ii = 0; !foundUsableCDP && ii < sk_DIST_POINT_num(dps); ++ii) {
-                    DIST_POINT* dp = sk_DIST_POINT_value(dps, ii);
-                    if (!dp->distpoint || dp->distpoint->type != 0)
-                        continue;
-                    for (int iii = 0; !foundUsableCDP && iii < sk_GENERAL_NAME_num(dp->distpoint->name.fullname); ++iii) {
-                        GENERAL_NAME* gen = sk_GENERAL_NAME_value(dp->distpoint->name.fullname, iii);
-                        // Only consider HTTP URIs, and stop after the first one we find.
-#ifdef HAVE_STRCASECMP
-                        if (gen->type == GEN_URI && (!strncasecmp((const char*)gen->d.ia5->data, "http:", 5))) {
-#else
-                        if (gen->type == GEN_URI && (!strnicmp((const char*)gen->d.ia5->data, "http:", 5))) {
-#endif
-                            const char* cdpuri = (const char*)gen->d.ia5->data;
-                            auto_ptr<XSECCryptoX509CRL> crl(getRemoteCRLs(cdpuri, log));
-                            if (crl.get() && crl->getProviderName()==DSIGConstants::s_unicodeStrPROVOpenSSL &&
-                                (isFreshCRL(crl.get()) || (ii == sk_DIST_POINT_num(dps)-1 && iii == sk_GENERAL_NAME_num(dp->distpoint->name.fullname)-1))) {
-                                // owned by store
-                                X509_STORE_add_crl(store, X509_CRL_dup(static_cast<OpenSSLCryptoX509CRL*>(crl.get())->getOpenSSLX509CRL()));
-                                log.debug("added CRL issued by (%s)", crlissuer.c_str());
-                                crlissuers.insert(crlissuer);
-                                foundUsableCDP = true;
-                            }
-                        }
-                    }
-                }
-                sk_DIST_POINT_free(dps);
-            }
-
-            // Do a second pass verify with CRLs in place.
-            X509_STORE_CTX_set_flags(&ctx, fullCRLChain ? (X509_V_FLAG_CRL_CHECK|X509_V_FLAG_CRL_CHECK_ALL) : (X509_V_FLAG_CRL_CHECK));
-            ret=X509_verify_cert(&ctx);
-#else
-            log.warn("CRL checking is enabled, but OpenSSL version is too old");
-            ret = 0;
-#endif
+        const vector<XSECCryptoX509CRL*>& getCRLs() const {
+            return m_crls.empty() ? m_pkixInfo.getCRLs() : m_crls;
         }
+    };
 
-        // Clean up...
-        X509_STORE_CTX_cleanup(&ctx);
-        X509_STORE_free(store);
-        sk_X509_free(CAstack);
-    
-        if (ret==1) {
-            log.debug("successfully validated certificate chain");
-            return true;
-        }
-        
-        return false;
-    }
 
     static XMLCh fullCRLChain[] =                  UNICODE_LITERAL_12(f,u,l,l,C,R,L,C,h,a,i,n);
     static XMLCh checkRevocation[] =       UNICODE_LITERAL_15(c,h,e,c,k,R,e,v,o,c,a,t,i,o,n);
     static XMLCh policyMappingInhibit[] =   UNICODE_LITERAL_20(p,o,l,i,c,y,M,a,p,p,i,n,g,I,n,h,i,b,i,t);
     static XMLCh anyPolicyInhibit[] =      UNICODE_LITERAL_16(a,n,y,P,o,l,i,c,y,I,n,h,i,b,i,t);
+    static XMLCh _PathValidator[] =         UNICODE_LITERAL_13(P,a,t,h,V,a,l,i,d,a,t,o,r);
     static XMLCh PolicyOID[] =                     UNICODE_LITERAL_9(P,o,l,i,c,y,O,I,D);
     static XMLCh TrustedName[] =                   UNICODE_LITERAL_11(T,r,u,s,t,e,d,N,a,m,e);
+    static XMLCh type[] =                   UNICODE_LITERAL_4(t,y,p,e);
 };
 
 AbstractPKIXTrustEngine::PKIXValidationInfoIterator::PKIXValidationInfoIterator()
@@ -518,12 +149,43 @@ AbstractPKIXTrustEngine::AbstractPKIXTrustEngine(const xercesc::DOMElement* e)
                     m_trustedNames.insert(v.get());
             }
         }
+        else if (XMLString::equals(c->getLocalName(), _PathValidator)) {
+            try {
+                string t = XMLHelper::getAttrString(c, nullptr, type);
+                if (!t.empty()) {
+                    Category::getInstance(XMLTOOLING_LOGCAT".TrustEngine.PKIX").info(
+                        "building PathValidator of type %s", t.c_str()
+                        );
+                    PathValidator* pv = XMLToolingConfig::getConfig().PathValidatorManager.newPlugin(t.c_str(), c);
+                    OpenSSLPathValidator* ospv = dynamic_cast<OpenSSLPathValidator*>(pv);
+                    if (!ospv) {
+                        delete pv;
+                        throw XMLSecurityException("PathValidator doesn't support OpenSSL interface.");
+                    }
+                    m_pathValidators.push_back(ospv);
+                }
+            }
+            catch (exception& ex) {
+                Category::getInstance(XMLTOOLING_LOGCAT".TrustEngine.PKIX").error(
+                    "error building PathValidator: %s", ex.what()
+                    );
+            }
+        }
         c = XMLHelper::getNextSiblingElement(c);
     }
+
+    if (m_pathValidators.empty()) {
+        m_pathValidators.push_back(
+            dynamic_cast<OpenSSLPathValidator*>(
+                XMLToolingConfig::getConfig().PathValidatorManager.newPlugin(PKIX_PATHVALIDATOR, e)
+                )
+            );
+    }
 }
 
 AbstractPKIXTrustEngine::~AbstractPKIXTrustEngine()
 {
+    for_each(m_pathValidators.begin(), m_pathValidators.end(), xmltooling::cleanup<PathValidator>());
 }
 
 bool AbstractPKIXTrustEngine::checkEntityNames(
@@ -679,7 +341,7 @@ bool AbstractPKIXTrustEngine::validateWithCRLs(
     STACK_OF(X509)* certChain,
     const CredentialResolver& credResolver,
     CredentialCriteria* criteria,
-    const std::vector<XSECCryptoX509CRL*>* inlineCRLs
+    const vector<XSECCryptoX509CRL*>* inlineCRLs
     ) const
 {
 #ifdef _DEBUG
@@ -715,18 +377,11 @@ bool AbstractPKIXTrustEngine::validateWithCRLs(
 
     auto_ptr<PKIXValidationInfoIterator> pkix(getPKIXValidationInfoIterator(credResolver, criteria));
     while (pkix->next()) {
-        if (::validate(
-                               certEE,
-                               certChain,
-                               pkix.get(),
-                               (m_checkRevocation=="entityOnly" || m_checkRevocation=="fullChain"),
-                               (m_checkRevocation=="fullChain"),
-                               (m_checkRevocation=="entityOnly" || m_checkRevocation=="fullChain") ? inlineCRLs : nullptr,
-                               m_policyMappingInhibit,
-                               m_anyPolicyInhibit,
-                               &m_policyOIDs
-                               )) {
-            return true;
+        PKIXParams params(*this, *pkix.get(), inlineCRLs);
+        for (vector<OpenSSLPathValidator*>::const_iterator v = m_pathValidators.begin(); v != m_pathValidators.end(); ++v) {
+            if ((*v)->validate(certEE, certChain, params)) {
+                return true;
+            }
         }
     }
 
diff --git a/xmltooling/security/impl/PKIXPathValidator.cpp b/xmltooling/security/impl/PKIXPathValidator.cpp
new file mode 100644 (file)
index 0000000..a142aa0
--- /dev/null
@@ -0,0 +1,546 @@
+/**
+ * Licensed to the University Corporation for Advanced Internet
+ * Development, Inc. (UCAID) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ *
+ * UCAID licenses this file to you under the Apache License,
+ * Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the
+ * License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific
+ * language governing permissions and limitations under the License.
+ */
+
+/**
+ * PKIXPathValidator.cpp
+ * 
+ * A path validator based on PKIX support in OpenSSL.
+ */
+
+#include "internal.h"
+#include "logging.h"
+#include "security/OpenSSLPathValidator.h"
+#include "security/OpenSSLCryptoX509CRL.h"
+#include "security/PKIXPathValidatorParams.h"
+#include "security/SecurityHelper.h"
+#include "util/NDC.h"
+#include "util/PathResolver.h"
+#include "util/Threads.h"
+#include "util/XMLHelper.h"
+
+#include <algorithm>
+#include <fstream>
+#include <openssl/x509_vfy.h>
+#include <openssl/x509v3.h>
+#include <xsec/enc/OpenSSL/OpenSSLCryptoX509.hpp>
+
+using namespace xmltooling::logging;
+using namespace xmltooling;
+using namespace std;
+
+
+namespace {
+    static int XMLTOOL_DLLLOCAL error_callback(int ok, X509_STORE_CTX* ctx)
+    {
+        if (!ok)
+            Category::getInstance("OpenSSL").error("path validation failure: %s", X509_verify_cert_error_string(ctx->error));
+        return ok;
+    }
+
+    static string XMLTOOL_DLLLOCAL X509_NAME_to_string(X509_NAME* n)
+    {
+        string s;
+        BIO* b = BIO_new(BIO_s_mem());
+        X509_NAME_print_ex(b,n,0,XN_FLAG_RFC2253);
+        BIO_flush(b);
+        BUF_MEM* bptr=nullptr;
+        BIO_get_mem_ptr(b, &bptr);
+        if (bptr && bptr->length > 0) {
+            s.append(bptr->data, bptr->length);
+        }
+        BIO_free(b);
+        return s;
+    }
+
+    static time_t XMLTOOL_DLLLOCAL getCRLTime(const ASN1_TIME *a)
+    {
+        struct tm t;
+        memset(&t, 0, sizeof(t));
+        // RFC 5280, sections 5.1.2.4 and 5.1.2.5 require thisUpdate and nextUpdate
+        // to be encoded as UTCTime until 2049, and RFC 5280 section 4.1.2.5.1
+        // further restricts the format to "YYMMDDHHMMSSZ" ("even where the number
+        // of seconds is zero").
+        // As long as OpenSSL doesn't provide any API to convert ASN1_TIME values
+        // time_t, we therefore have to parse it ourselves, unfortunately.
+        if (sscanf((const char*)a->data, "%2d%2d%2d%2d%2d%2dZ",
+            &t.tm_year, &t.tm_mon, &t.tm_mday,
+            &t.tm_hour, &t.tm_min, &t.tm_sec) == 6) {
+            if (t.tm_year <= 50) {
+                // RFC 5280, section 4.1.2.5.1
+                t.tm_year += 100;
+            }
+            t.tm_mon--;
+#if defined(HAVE_TIMEGM)
+            return timegm(&t);
+#else
+            // Windows, and hopefully most others...?
+            return mktime(&t) - timezone;
+#endif
+        }
+        return (time_t)-1;
+    }
+
+    static const XMLCh minRefreshDelay[] =      UNICODE_LITERAL_15(m,i,n,R,e,f,r,e,s,h,D,e,l,a,y);
+    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);
+    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);
+};
+
+namespace xmltooling {
+
+    class XMLTOOL_DLLLOCAL PKIXPathValidator : public OpenSSLPathValidator
+    {
+    public:
+        PKIXPathValidator(const xercesc::DOMElement* e)
+            : m_log(Category::getInstance(XMLTOOLING_LOGCAT".PathValidator.PKIX")),
+              m_lock(XMLToolingConfig::getConfig().getNamedMutex(XMLTOOLING_LOGCAT".PathValidator.PKIX")),
+              m_minRefreshDelay(XMLHelper::getAttrInt(e, 60, minRefreshDelay)),
+              m_minSecondsRemaining(XMLHelper::getAttrInt(e, 86400, minSecondsRemaining)),
+              m_minPercentRemaining(XMLHelper::getAttrInt(e, 10, minPercentRemaining)) {
+        }
+
+        virtual ~PKIXPathValidator() {}
+
+        bool validate(
+            XSECCryptoX509* certEE, const vector<XSECCryptoX509*>& certChain, const PathValidatorParams& params
+            ) const;
+        bool validate(
+            X509* certEE, STACK_OF(X509)* certChain, const PathValidatorParams& params
+            ) const;
+
+    private:
+        XSECCryptoX509CRL* getRemoteCRLs(const char* cdpuri) const;
+        bool isFreshCRL(XSECCryptoX509CRL *c, Category* log=nullptr) const;
+
+        Category& m_log;
+        Mutex& m_lock;
+        time_t m_minRefreshDelay,m_minSecondsRemaining;
+        unsigned short m_minPercentRemaining;
+
+        static map<string,time_t> m_crlUpdateMap;
+    };
+
+    PathValidator* XMLTOOL_DLLLOCAL PKIXPathValidatorFactory(const xercesc::DOMElement* const & e)
+    {
+        return new PKIXPathValidator(e);
+    }
+
+};
+
+map<string,time_t> PKIXPathValidator::m_crlUpdateMap;
+
+void XMLTOOL_API xmltooling::registerPathValidators()
+{
+    XMLToolingConfig& conf=XMLToolingConfig::getConfig();
+    conf.PathValidatorManager.registerFactory(PKIX_PATHVALIDATOR, PKIXPathValidatorFactory);
+}
+
+PathValidator::PathValidator()
+{
+}
+
+PathValidator::~PathValidator()
+{
+}
+
+PathValidator::PathValidatorParams::PathValidatorParams()
+{
+}
+
+PathValidator::PathValidatorParams::~PathValidatorParams()
+{
+}
+
+PKIXPathValidatorParams::PKIXPathValidatorParams()
+{
+}
+
+PKIXPathValidatorParams::~PKIXPathValidatorParams()
+{
+}
+
+OpenSSLPathValidator::OpenSSLPathValidator()
+{
+}
+
+OpenSSLPathValidator::~OpenSSLPathValidator()
+{
+}
+
+bool PKIXPathValidator::validate(
+    XSECCryptoX509* certEE, const vector<XSECCryptoX509*>& certChain, const PathValidatorParams& params
+    ) const
+{
+    if (certEE->getProviderName()!=DSIGConstants::s_unicodeStrPROVOpenSSL) {
+        m_log.error("only the OpenSSL XSEC provider is supported");
+        return false;
+    }
+
+    STACK_OF(X509)* untrusted=sk_X509_new_null();
+    for (vector<XSECCryptoX509*>::const_iterator i=certChain.begin(); i!=certChain.end(); ++i)
+        sk_X509_push(untrusted,static_cast<OpenSSLCryptoX509*>(*i)->getOpenSSLX509());
+
+    bool ret = validate(static_cast<OpenSSLCryptoX509*>(certEE)->getOpenSSLX509(), untrusted, params);
+    sk_X509_free(untrusted);
+    return ret;
+}
+
+bool PKIXPathValidator::validate(X509* EE, STACK_OF(X509)* untrusted, const PathValidatorParams& params) const
+{
+#ifdef _DEBUG
+    NDC ndc("validate");
+#endif
+
+    const PKIXPathValidatorParams* pkixParams = dynamic_cast<const PKIXPathValidatorParams*>(&params);
+    if (!pkixParams) {
+        m_log.error("input parameters were of incorrect type");
+        return false;
+    }
+
+    // First we build a stack of CA certs. These objects are all referenced in place.
+    m_log.debug("supplying PKIX Validation information");
+
+    // We need this for CRL support.
+    X509_STORE* store=X509_STORE_new();
+    if (!store) {
+        log_openssl();
+        return false;
+    }
+
+    // PKIX policy checking (cf. RFCs 3280/5280 section 6)
+    if (pkixParams->isPolicyMappingInhibited() || pkixParams->isAnyPolicyInhibited() || (!pkixParams->getPolicies().empty())) {
+#if (OPENSSL_VERSION_NUMBER < 0x00908000L)
+        m_log.error("PKIX policy checking option is configured, but OpenSSL version is less than 0.9.8");
+        X509_STORE_free(store);
+        return false;
+#else
+        unsigned long pflags = 0;
+        X509_VERIFY_PARAM *vpm = X509_VERIFY_PARAM_new();
+        if (!vpm) {
+            log_openssl();
+            X509_STORE_free(store);
+            return false;
+        }
+
+        // populate the "user-initial-policy-set" input variable
+        const set<string>& policies = pkixParams->getPolicies();
+        if (!policies.empty()) {
+            for (set<string>::const_iterator o=policies.begin(); o!=policies.end(); o++) {
+                ASN1_OBJECT *oid = OBJ_txt2obj(o->c_str(), 1);
+                if (oid && X509_VERIFY_PARAM_add0_policy(vpm, oid)) {
+                    m_log.debug("OID (%s) added to set of acceptable policies", o->c_str());
+                }
+                else {
+                    log_openssl();
+                    m_log.error("unable to parse/configure policy OID value (%s)", o->c_str());
+                    if (oid)
+                        ASN1_OBJECT_free(oid);
+                    X509_VERIFY_PARAM_free(vpm);
+                    X509_STORE_free(store);
+                    return false;
+                }
+            }
+            // when the user has supplied at least one policy OID, he obviously wants to check
+            // for an explicit policy ("initial-explicit-policy")
+            pflags |= X509_V_FLAG_EXPLICIT_POLICY;
+        }
+
+        // "initial-policy-mapping-inhibit" input variable
+        if (pkixParams->isPolicyMappingInhibited())
+            pflags |= X509_V_FLAG_INHIBIT_MAP;
+        // "initial-any-policy-inhibit" input variable
+        if (pkixParams->isAnyPolicyInhibited())
+            pflags |= X509_V_FLAG_INHIBIT_ANY;
+
+        if (!X509_VERIFY_PARAM_set_flags(vpm, pflags) || !X509_STORE_set1_param(store, vpm)) {
+            log_openssl();
+            m_log.error("unable to set PKIX policy checking parameters");
+            X509_VERIFY_PARAM_free(vpm);
+            X509_STORE_free(store);
+            return false;
+        }
+
+        X509_VERIFY_PARAM_free(vpm);
+#endif
+    }
+
+    // This contains the state of the validate operation.
+    int count=0;
+    X509_STORE_CTX ctx;
+
+    // AFAICT, EE and untrusted are passed in but not owned by the ctx.
+#if (OPENSSL_VERSION_NUMBER >= 0x00907000L)
+    if (X509_STORE_CTX_init(&ctx,store,EE,untrusted)!=1) {
+        log_openssl();
+        m_log.error("unable to initialize X509_STORE_CTX");
+        X509_STORE_free(store);
+        return false;
+    }
+#else
+    X509_STORE_CTX_init(&ctx,store,EE,untrusted);
+#endif
+
+    STACK_OF(X509)* CAstack = sk_X509_new_null();
+    const vector<XSECCryptoX509*>& CAcerts = pkixParams->getTrustAnchors();
+    for (vector<XSECCryptoX509*>::const_iterator i=CAcerts.begin(); i!=CAcerts.end(); ++i) {
+        if ((*i)->getProviderName()==DSIGConstants::s_unicodeStrPROVOpenSSL) {
+            sk_X509_push(CAstack,static_cast<OpenSSLCryptoX509*>(*i)->getOpenSSLX509());
+            ++count;
+        }
+    }
+    m_log.debug("supplied (%d) CA certificate(s)", count);
+
+    // Seems to be most efficient to just pass in the CA stack.
+    X509_STORE_CTX_trusted_stack(&ctx,CAstack);
+    X509_STORE_CTX_set_depth(&ctx,100);    // we check the depth down below
+    X509_STORE_CTX_set_verify_cb(&ctx,error_callback);
+
+    // Do a first pass verify. If CRLs aren't used, this is the only pass.
+    int ret=X509_verify_cert(&ctx);
+    if (ret==1) {
+        // Now see if the depth was acceptable by counting the number of intermediates.
+        int depth=sk_X509_num(ctx.chain)-2;
+        if (pkixParams->getVerificationDepth() < depth) {
+            m_log.error(
+                "certificate chain was too long (%d intermediates, only %d allowed)",
+                (depth==-1) ? 0 : depth,
+                pkixParams->getVerificationDepth()
+                );
+            ret=0;
+        }
+    }
+
+    if (pkixParams->getRevocationChecking() != PKIXPathValidatorParams::REVOCATION_OFF) {
+#if (OPENSSL_VERSION_NUMBER >= 0x00907000L)
+        // When we add CRLs, we have to be sure the nextUpdate hasn't passed, because OpenSSL won't accept
+        // the CRL in that case. If we end up not adding a CRL for a particular link in the chain, the
+        // validation will fail (if the fullChain option was set).
+        set<string> crlissuers;
+        time_t now = time(nullptr);
+
+        const vector<XSECCryptoX509CRL*>& crls = pkixParams->getCRLs();
+        for (vector<XSECCryptoX509CRL*>::const_iterator j=crls.begin(); j!=crls.end(); ++j) {
+            if ((*j)->getProviderName()==DSIGConstants::s_unicodeStrPROVOpenSSL &&
+                (X509_cmp_time(X509_CRL_get_nextUpdate(static_cast<OpenSSLCryptoX509CRL*>(*j)->getOpenSSLX509CRL()), &now) > 0)) {
+                // owned by store
+                X509_STORE_add_crl(store, X509_CRL_dup(static_cast<OpenSSLCryptoX509CRL*>(*j)->getOpenSSLX509CRL()));
+                string crlissuer(X509_NAME_to_string(X509_CRL_get_issuer(static_cast<OpenSSLCryptoX509CRL*>(*j)->getOpenSSLX509CRL())));
+                if (!crlissuer.empty()) {
+                    m_log.debug("added CRL issued by (%s)", crlissuer.c_str());
+                    crlissuers.insert(crlissuer);
+                }
+            }
+        }
+
+        for (int i = 0; i < sk_X509_num(untrusted); ++i) {
+            X509 *cert = sk_X509_value(untrusted, i);
+            string crlissuer(X509_NAME_to_string(X509_get_issuer_name(cert)));
+            if (crlissuers.count(crlissuer)) {
+               // We already have a CRL for this cert, so skip CRLDP processing for this one.
+               continue;
+            }
+
+            bool foundUsableCDP = false;
+            STACK_OF(DIST_POINT)* dps = (STACK_OF(DIST_POINT)*)X509_get_ext_d2i(cert, NID_crl_distribution_points, nullptr, nullptr);
+            for (int ii = 0; !foundUsableCDP && ii < sk_DIST_POINT_num(dps); ++ii) {
+                DIST_POINT* dp = sk_DIST_POINT_value(dps, ii);
+                if (!dp->distpoint || dp->distpoint->type != 0)
+                    continue;
+                for (int iii = 0; !foundUsableCDP && iii < sk_GENERAL_NAME_num(dp->distpoint->name.fullname); ++iii) {
+                    GENERAL_NAME* gen = sk_GENERAL_NAME_value(dp->distpoint->name.fullname, iii);
+                    // Only consider HTTP URIs, and stop after the first one we find.
+#ifdef HAVE_STRCASECMP
+                    if (gen->type == GEN_URI && (!strncasecmp((const char*)gen->d.ia5->data, "http:", 5))) {
+#else
+                    if (gen->type == GEN_URI && (!strnicmp((const char*)gen->d.ia5->data, "http:", 5))) {
+#endif
+                        const char* cdpuri = (const char*)gen->d.ia5->data;
+                        auto_ptr<XSECCryptoX509CRL> crl(getRemoteCRLs(cdpuri));
+                        if (crl.get() && crl->getProviderName()==DSIGConstants::s_unicodeStrPROVOpenSSL &&
+                            (isFreshCRL(crl.get()) || (ii == sk_DIST_POINT_num(dps)-1 && iii == sk_GENERAL_NAME_num(dp->distpoint->name.fullname)-1))) {
+                            // owned by store
+                            X509_STORE_add_crl(store, X509_CRL_dup(static_cast<OpenSSLCryptoX509CRL*>(crl.get())->getOpenSSLX509CRL()));
+                            m_log.debug("added CRL issued by (%s)", crlissuer.c_str());
+                            crlissuers.insert(crlissuer);
+                            foundUsableCDP = true;
+                        }
+                    }
+                }
+            }
+            sk_DIST_POINT_free(dps);
+        }
+
+        // Do a second pass verify with CRLs in place.
+        if (pkixParams->getRevocationChecking() == PKIXPathValidatorParams::REVOCATION_FULLCHAIN)
+            X509_STORE_CTX_set_flags(&ctx, X509_V_FLAG_CRL_CHECK|X509_V_FLAG_CRL_CHECK_ALL);
+        else
+            X509_STORE_CTX_set_flags(&ctx, X509_V_FLAG_CRL_CHECK);
+        ret=X509_verify_cert(&ctx);
+#else
+        m_log.warn("CRL checking is enabled, but OpenSSL version is too old");
+        ret = 0;
+#endif
+    }
+
+    // Clean up...
+    X509_STORE_CTX_cleanup(&ctx);
+    X509_STORE_free(store);
+    sk_X509_free(CAstack);
+
+    if (ret==1) {
+        m_log.debug("successfully validated certificate chain");
+        return true;
+    }
+
+    return false;
+}
+
+XSECCryptoX509CRL* PKIXPathValidator::getRemoteCRLs(const char* cdpuri) const
+{
+    // This is a filesystem-based CRL cache using a shared lock across all instances
+    // of this class.
+
+    // The filenames for the CRL cache are based on a hash of the CRL location.
+    string cdpfile = SecurityHelper::doHash("SHA1", cdpuri, strlen(cdpuri)) + ".crl";
+    XMLToolingConfig::getConfig().getPathResolver()->resolve(cdpfile, PathResolver::XMLTOOLING_RUN_FILE);
+    string cdpstaging = cdpfile + ".tmp";
+
+    time_t now = time(nullptr);
+    vector<XSECCryptoX509CRL*> crls;
+
+    try {
+        // While holding the lock, check for a cached copy of the CRL, and remove "expired" ones.
+        Lock glock(m_lock);
+#ifdef WIN32
+        struct _stat stat_buf;
+        if (_stat(cdpfile.c_str(), &stat_buf) == 0) {
+#else
+        struct stat stat_buf;
+        if (stat(cdpfile.c_str(), &stat_buf) == 0) {
+#endif
+            SecurityHelper::loadCRLsFromFile(crls, cdpfile.c_str());
+            if (crls.empty() || crls.front()->getProviderName() != DSIGConstants::s_unicodeStrPROVOpenSSL ||
+                X509_cmp_time(X509_CRL_get_nextUpdate(static_cast<OpenSSLCryptoX509CRL*>(crls.front())->getOpenSSLX509CRL()), &now) < 0) {
+                for_each(crls.begin(), crls.end(), xmltooling::cleanup<XSECCryptoX509CRL>());
+                crls.clear();
+                remove(cdpfile.c_str());    // may as well delete the local copy
+                m_crlUpdateMap.erase(cdpuri);
+                m_log.info("deleting cached CRL from %s with nextUpdate field in the past", cdpuri);
+            }
+        }
+    }
+    catch (exception& ex) {
+        m_log.error("exception loading cached copy of CRL from %s: %s", cdpuri, ex.what());
+    }
+
+    if (crls.empty() || !isFreshCRL(crls.front(), &m_log)) {
+        bool updateTimestamp = true;
+        try {
+            // If we get here, the cached copy didn't exist yet, or it's time to refresh.
+            // To limit the rate of unsuccessful attempts when a CRLDP is unreachable,
+            // we remember the timestamp of the last attempt (both successful/unsuccessful).
+            time_t ts = 0;
+            m_lock.lock();
+            map<string,time_t>::const_iterator tsit = m_crlUpdateMap.find(cdpuri);
+            if (tsit != m_crlUpdateMap.end())
+                ts = tsit->second;
+            m_lock.unlock();
+
+            if (difftime(now, ts) > m_minRefreshDelay) {
+                SOAPTransport::Address addr("AbstractPKIXTrustEngine", cdpuri, cdpuri);
+                string scheme(addr.m_endpoint, strchr(addr.m_endpoint,':') - addr.m_endpoint);
+                auto_ptr<SOAPTransport> soap(XMLToolingConfig::getConfig().SOAPTransportManager.newPlugin(scheme.c_str(), addr));
+                soap->send();
+                istream& msg = soap->receive();
+                Lock glock(m_lock);
+                ofstream out(cdpstaging.c_str(), fstream::trunc|fstream::binary);
+                out << msg.rdbuf();
+                out.close();
+                SecurityHelper::loadCRLsFromFile(crls, cdpstaging.c_str());
+                if (crls.empty() || crls.front()->getProviderName() != DSIGConstants::s_unicodeStrPROVOpenSSL ||
+                    X509_cmp_time(X509_CRL_get_nextUpdate(static_cast<OpenSSLCryptoX509CRL*>(crls.front())->getOpenSSLX509CRL()), &now) < 0) {
+                    // The "new" CRL wasn't usable, so get rid of it.
+                    for_each(crls.begin(), crls.end(), xmltooling::cleanup<XSECCryptoX509CRL>());
+                    crls.clear();
+                    remove(cdpstaging.c_str());
+                    m_log.error("ignoring CRL retrieved from %s with nextUpdate field in the past", cdpuri);
+                }
+                else {
+                    // "Commit" the new CRL. Note that we might add a CRL which doesn't pass
+                    // isFreshCRL, but that's preferrable over adding none at all.
+                    m_log.info("CRL refreshed from %s", cdpuri);
+                    remove(cdpfile.c_str());
+                    if (rename(cdpstaging.c_str(), cdpfile.c_str()) != 0)
+                        m_log.error("unable to rename CRL staging file");
+                }
+            }
+            else {
+                updateTimestamp = false;    // don't update if we're within the backoff window
+            }
+        }
+        catch (exception& ex) {
+            m_log.error("exception downloading/caching CRL from %s: %s", cdpuri, ex.what());
+        }
+
+        if (updateTimestamp) {
+            Lock glock(m_lock);
+            m_crlUpdateMap[cdpuri] = now;
+        }
+    }
+
+    if (crls.empty())
+        return nullptr;
+    for_each(crls.begin() + 1, crls.end(), xmltooling::cleanup<XSECCryptoX509CRL>());
+    return crls.front();
+}
+
+bool PKIXPathValidator::isFreshCRL(XSECCryptoX509CRL *c, Category* log) const
+{
+    if (c) {
+        const X509_CRL* crl = static_cast<OpenSSLCryptoX509CRL*>(c)->getOpenSSLX509CRL();
+        time_t thisUpdate = getCRLTime(X509_CRL_get_lastUpdate(crl));
+        time_t nextUpdate = getCRLTime(X509_CRL_get_nextUpdate(crl));
+        time_t now = time(nullptr);
+
+        if (thisUpdate < 0 || nextUpdate < 0) {
+            // we failed to parse at least one of the fields (they were not encoded
+            // as required by RFC 5280, actually)
+            time_t exp = now + m_minSecondsRemaining;
+            if (log) {
+                log->warn("isFreshCRL (issuer '%s'): improperly encoded thisUpdate or nextUpdate field - falling back to simple time comparison",
+                          (X509_NAME_to_string(X509_CRL_get_issuer(crl))).c_str());
+            }
+            return (X509_cmp_time(X509_CRL_get_nextUpdate(crl), &exp) > 0) ? true : false;
+        }
+        else {
+            if (log && log->isDebugEnabled()) {
+                log->debug("isFreshCRL (issuer '%s'): %.0f seconds until nextUpdate (%3.2f%% elapsed since thisUpdate)",
+                          (X509_NAME_to_string(X509_CRL_get_issuer(crl))).c_str(),
+                          difftime(nextUpdate, now), (difftime(now, thisUpdate) * 100) / difftime(nextUpdate, thisUpdate));
+            }
+
+            // consider it recent enough if there are at least MIN_SECS_REMAINING
+            // to the nextUpdate, and at least MIN_PERCENT_REMAINING of its
+            // overall "validity" are remaining to the nextUpdate
+            return (now + m_minSecondsRemaining < nextUpdate) &&
+                    ((difftime(nextUpdate, now) * 100) / difftime(nextUpdate, thisUpdate) > m_minPercentRemaining);
+        }
+    }
+    return false;
+}
index 48c67bb..8969808 100644 (file)
@@ -235,7 +235,6 @@ namespace xmltooling {
         virtual long getStatusCode() const;
     };
 
-#ifndef XMLTOOLING_NO_XMLSEC
     /**
      * Registers SOAPTransport classes into the runtime.
      */
@@ -250,7 +249,6 @@ namespace xmltooling {
      * Notifies transport infrastructure to shutdown.
      */
     void XMLTOOL_API termSOAPTransports();
-#endif
 
 };
 
index 60d6238..e6a2c66 100644 (file)
@@ -247,13 +247,6 @@ namespace xmltooling {
     }
 };
 
-void xmltooling::registerSOAPTransports()
-{
-    XMLToolingConfig& conf=XMLToolingConfig::getConfig();
-    conf.SOAPTransportManager.registerFactory("http", CURLSOAPTransportFactory);
-    conf.SOAPTransportManager.registerFactory("https", CURLSOAPTransportFactory);
-}
-
 void xmltooling::initSOAPTransports()
 {
     g_CURLPool=new CURLPool();
@@ -265,14 +258,6 @@ void xmltooling::termSOAPTransports()
     g_CURLPool = nullptr;
 }
 
-OpenSSLSOAPTransport::OpenSSLSOAPTransport()
-{
-}
-
-OpenSSLSOAPTransport::~OpenSSLSOAPTransport()
-{
-}
-
 CURLPool::~CURLPool()
 {
     for (poolmap_t::iterator i=m_bindingMap.begin(); i!=m_bindingMap.end(); i++) {
index b3f7c6d..01a1789 100644 (file)
@@ -28,6 +28,7 @@
 #include "exceptions.h"
 #include "logging.h"
 #include "soap/HTTPSOAPTransport.h"
+#include "soap/OpenSSLSOAPTransport.h"
 #include "soap/SOAP.h"
 #include "soap/SOAPClient.h"
 #include "util/XMLHelper.h"
@@ -41,6 +42,32 @@ using namespace xmltooling;
 using namespace xercesc;
 using namespace std;
 
+#if !defined(XMLTOOLING_NO_XMLSEC) && !defined(XMLTOOLING_LITE)
+namespace xmltooling {
+    PluginManager<SOAPTransport,string,SOAPTransport::Address>::Factory CURLSOAPTransportFactory;
+};
+#endif
+
+void xmltooling::registerSOAPTransports()
+{
+#if !defined(XMLTOOLING_NO_XMLSEC) && !defined(XMLTOOLING_LITE)
+    XMLToolingConfig& conf=XMLToolingConfig::getConfig();
+    conf.SOAPTransportManager.registerFactory("http", CURLSOAPTransportFactory);
+    conf.SOAPTransportManager.registerFactory("https", CURLSOAPTransportFactory);
+#endif
+}
+
+
+#ifdef XMLTOOLING_NO_XMLSEC
+void xmltooling::initSOAPTransports()
+{
+}
+
+void xmltooling::termSOAPTransports()
+{
+}
+#endif
+
 SOAPTransport::SOAPTransport()
 {
 }
@@ -84,6 +111,16 @@ bool HTTPSOAPTransport::followRedirects(bool follow, unsigned int maxRedirs)
     return false;
 }
 
+#ifndef XMLTOOLING_NO_XMLSEC
+OpenSSLSOAPTransport::OpenSSLSOAPTransport()
+{
+}
+
+OpenSSLSOAPTransport::~OpenSSLSOAPTransport()
+{
+}
+#endif
+
 SOAPClient::SOAPClient(bool validate) : m_validate(validate), m_transport(nullptr)
 {
 }
index abb8c10..959031d 100644 (file)
@@ -296,6 +296,15 @@ namespace xmltooling
         }
 
         /**
+         * Locks and wraps the designated mutex.
+         *
+         * @param mtx mutex to lock
+         */
+        Lock(Mutex& mtx) : mutex(&mtx) {
+            mtx.lock();
+        }
+
+        /**
          * Unlocks the wrapped mutex.
          */
         ~Lock() {
index 2e204be..b508703 100644 (file)
     <ClCompile Include="Lockable.cpp" />\r
     <ClCompile Include="Namespace.cpp" />\r
     <ClCompile Include="QName.cpp" />\r
+    <ClCompile Include="security\impl\PKIXPathValidator.cpp" />\r
     <ClCompile Include="unicode.cpp" />\r
     <ClCompile Include="version.cpp" />\r
     <ClCompile Include="XMLObjectBuilder.cpp" />\r
     <ClInclude Include="Namespace.h" />\r
     <ClInclude Include="PluginManager.h" />\r
     <ClInclude Include="QName.h" />\r
+    <ClInclude Include="security\OpenSSLPathValidator.h" />\r
+    <ClInclude Include="security\PathValidator.h" />\r
+    <ClInclude Include="security\PKIXPathValidatorParams.h" />\r
     <ClInclude Include="unicode.h" />\r
     <ClInclude Include="version.h" />\r
     <ClInclude Include="XMLObject.h" />\r
index c8c0c38..99d1289 100644 (file)
     <ClCompile Include="version.cpp">\r
       <Filter>Source Files</Filter>\r
     </ClCompile>\r
+    <ClCompile Include="security\impl\PKIXPathValidator.cpp">\r
+      <Filter>Source Files\security\impl</Filter>\r
+    </ClCompile>\r
   </ItemGroup>\r
   <ItemGroup>\r
     <ClInclude Include="AbstractAttributeExtensibleXMLObject.h">\r
     <ClInclude Include="soap\SOAPTransport.h">\r
       <Filter>Header Files\soap</Filter>\r
     </ClInclude>\r
+    <ClInclude Include="security\OpenSSLPathValidator.h">\r
+      <Filter>Header Files\security</Filter>\r
+    </ClInclude>\r
+    <ClInclude Include="security\PathValidator.h">\r
+      <Filter>Header Files\security</Filter>\r
+    </ClInclude>\r
+    <ClInclude Include="security\PKIXPathValidatorParams.h">\r
+      <Filter>Header Files\security</Filter>\r
+    </ClInclude>\r
   </ItemGroup>\r
   <ItemGroup>\r
     <ResourceCompile Include="xmltooling.rc">\r