X-Git-Url: http://www.project-moonshot.org/gitweb/?a=blobdiff_plain;f=xmltooling%2Fsecurity%2Fimpl%2FFilesystemCredentialResolver.cpp;h=5f1929de58902c3a3f12baa728ce71b08a4b630a;hb=81b488b2790e7bdeb2f43560b1d4a7d22c3dfdf5;hp=bd63dae99e415bb70a606db0e7b029f31119b38d;hpb=64dcaec957e9befd960779498d7fe35bbb62141a;p=shibboleth%2Fcpp-xmltooling.git diff --git a/xmltooling/security/impl/FilesystemCredentialResolver.cpp b/xmltooling/security/impl/FilesystemCredentialResolver.cpp index bd63dae..5f1929d 100644 --- a/xmltooling/security/impl/FilesystemCredentialResolver.cpp +++ b/xmltooling/security/impl/FilesystemCredentialResolver.cpp @@ -1,607 +1,762 @@ -/* - * Copyright 2001-2007 Internet2 - * - * Licensed 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 +/** + * 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 + * 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. + * 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. */ /** * FilesystemCredentialResolver.cpp - * - * Supplies credentials from local files + * + * Supplies credentials from local files. */ #include "internal.h" #include "logging.h" +#include "io/HTTPResponse.h" #include "security/BasicX509Credential.h" #include "security/CredentialCriteria.h" #include "security/CredentialResolver.h" #include "security/KeyInfoResolver.h" #include "security/OpenSSLCredential.h" +#include "security/SecurityHelper.h" +#include "security/XSECCryptoX509CRL.h" #include "util/NDC.h" +#include "util/PathResolver.h" +#include "util/Threads.h" #include "util/XMLHelper.h" +#include +#include #include #include #include #include #include -#include -#include using namespace xmlsignature; using namespace xmltooling::logging; using namespace xmltooling; using namespace std; -// OpenSSL password callback... -static int passwd_callback(char* buf, int len, int verify, void* passwd) -{ - if(!verify) - { - if(passwd && len > strlen(reinterpret_cast(passwd))) - { - strcpy(buf,reinterpret_cast(passwd)); - return strlen(buf); - } - } - return 0; -} +using xercesc::DOMElement; +using xercesc::chLatin_f; +using xercesc::chDigit_0; namespace xmltooling { -#if defined (_MSC_VER) - #pragma warning( push ) - #pragma warning( disable : 4250 ) + // The ManagedResource classes handle memory management, loading of the files + // and staleness detection. A copy of the active objects is always stored in + // these instances. + + class XMLTOOL_DLLLOCAL ManagedResource { + protected: + ManagedResource() : local(true), reloadChanges(true), filestamp(0), reloadInterval(0) {} + ~ManagedResource() {} + + SOAPTransport* getTransport() { + SOAPTransport::Address addr("FilesystemCredentialResolver", source.c_str(), source.c_str()); + string scheme(addr.m_endpoint, strchr(addr.m_endpoint,':') - addr.m_endpoint); + SOAPTransport* ret = XMLToolingConfig::getConfig().SOAPTransportManager.newPlugin(scheme.c_str(), addr); + if (ret) + ret->setCacheTag(&cacheTag); + return ret; + } + + public: + bool stale(Category& log, RWLock* lock=nullptr) { + if (local) { +#ifdef WIN32 + struct _stat stat_buf; + if (_stat(source.c_str(), &stat_buf) != 0) + return false; +#else + struct stat stat_buf; + if (stat(source.c_str(), &stat_buf) != 0) + return false; #endif + if (filestamp >= stat_buf.st_mtime) + return false; + + // If necessary, elevate lock and recheck. + if (lock) { + log.debug("timestamp of local resource changed, elevating to a write lock"); + lock->unlock(); + lock->wrlock(); + if (filestamp >= stat_buf.st_mtime) { + // Somebody else handled it, just downgrade. + log.debug("update of local resource handled by another thread, downgrading lock"); + lock->unlock(); + lock->rdlock(); + return false; + } + } - class XMLTOOL_DLLLOCAL FilesystemCredentialResolver; - class XMLTOOL_DLLLOCAL FilesystemCredential : public OpenSSLCredential, public BasicX509Credential - { + // Update the timestamp regardless. No point in repeatedly trying. + filestamp = stat_buf.st_mtime; + log.info("change detected, reloading local resource..."); + } + else { + time_t now = time(nullptr); + + // Time to reload? + if (now - filestamp < reloadInterval) + return false; + + // If necessary, elevate lock and recheck. + if (lock) { + log.debug("reload interval for remote resource elapsed, elevating to a write lock"); + lock->unlock(); + lock->wrlock(); + if (now - filestamp < reloadInterval) { + // Somebody else handled it, just downgrade. + log.debug("update of remote resource handled by another thread, downgrading lock"); + lock->unlock(); + lock->rdlock(); + return false; + } + } + + filestamp = now; + log.info("reloading remote resource..."); + } + return true; + } + + bool local,reloadChanges; + string format,source,backing,cacheTag; + time_t filestamp,reloadInterval; + }; + + class XMLTOOL_DLLLOCAL ManagedKey : public ManagedResource { public: - FilesystemCredential(FilesystemCredentialResolver* resolver, XSECCryptoKey* key, const std::vector& xseccerts) - : BasicX509Credential(key, xseccerts), m_resolver(resolver) { - if (!m_xseccerts.empty()) - extractNames(m_xseccerts.front(), m_keyNames); - initKeyInfo(); + ManagedKey() : key(nullptr) {} + ~ManagedKey() { delete key; } + void load(Category& log, const char* password) { + if (source.empty()) + return; + XSECCryptoKey* nkey=nullptr; + if (local) { + nkey = SecurityHelper::loadKeyFromFile(source.c_str(), format.c_str(), password); + } + else { + auto_ptr t(getTransport()); + log.info("loading private key from URL (%s)", source.c_str()); + nkey = SecurityHelper::loadKeyFromURL(*t.get(), backing.c_str(), format.c_str(), password); + } + delete key; + key = nkey; + if (format.empty()) + format = SecurityHelper::guessEncodingFormat(local ? source.c_str() : backing.c_str()); } - virtual ~FilesystemCredential() { + + XSECCryptoKey* key; + }; + + class XMLTOOL_DLLLOCAL ManagedCert : public ManagedResource { + public: + ManagedCert() {} + ~ManagedCert() { for_each(certs.begin(), certs.end(), xmltooling::cleanup()); } + void load(Category& log, const char* password) { + if (source.empty()) + return; + vector ncerts; + if (local) { + SecurityHelper::loadCertificatesFromFile(ncerts, source.c_str(), format.c_str(), password); + } + else { + auto_ptr t(getTransport()); + log.info("loading certificate(s) from URL (%s)", source.c_str()); + SecurityHelper::loadCertificatesFromURL(ncerts, *t.get(), backing.c_str(), format.c_str(), password); + } + for_each(certs.begin(), certs.end(), xmltooling::cleanup()); + certs = ncerts; + if (format.empty()) + format = SecurityHelper::guessEncodingFormat(local ? source.c_str() : backing.c_str()); + } + vector certs; + }; + + class XMLTOOL_DLLLOCAL ManagedCRL : public ManagedResource { + public: + ManagedCRL() {} + ~ManagedCRL() { for_each(crls.begin(), crls.end(), xmltooling::cleanup()); } + void load(Category& log) { + if (source.empty()) + return; + vector ncrls; + if (local) { + SecurityHelper::loadCRLsFromFile(ncrls, source.c_str(), format.c_str()); + } + else { + auto_ptr t(getTransport()); + log.info("loading CRL(s) from URL (%s)", source.c_str()); + SecurityHelper::loadCRLsFromURL(ncrls, *t.get(), backing.c_str(), format.c_str()); + } + for_each(crls.begin(), crls.end(), xmltooling::cleanup()); + crls = ncrls; + if (format.empty()) + format = SecurityHelper::guessEncodingFormat(local ? source.c_str() : backing.c_str()); + } + vector crls; + }; + + class XMLTOOL_DLLLOCAL FilesystemCredential; + class XMLTOOL_DLLLOCAL FilesystemCredentialResolver : public CredentialResolver + { + public: + FilesystemCredentialResolver(const DOMElement* e); + virtual ~FilesystemCredentialResolver(); + + Lockable* lock(); + void unlock() { + m_lock->unlock(); } - void addKeyNames(const DOMElement* e); + const Credential* resolve(const CredentialCriteria* criteria=nullptr) const; + + virtual vector::size_type resolve( + vector& results, const CredentialCriteria* criteria=nullptr + ) const; - void attach(SSL_CTX* ctx) const; - private: - FilesystemCredentialResolver* m_resolver; + Credential* getCredential(); + + RWLock* m_lock; + Credential* m_credential; + string m_keypass,m_certpass; + unsigned int m_keyinfomask,m_usage; + bool m_extractNames; + vector m_keynames; + + ManagedKey m_key; + vector m_certs; + vector m_crls; + + friend class XMLTOOL_DLLLOCAL FilesystemCredential; }; #if defined (_MSC_VER) - #pragma warning( pop ) + #pragma warning( push ) + #pragma warning( disable : 4250 ) #endif - class XMLTOOL_DLLLOCAL FilesystemCredentialResolver : public CredentialResolver + class XMLTOOL_DLLLOCAL FilesystemCredential : public OpenSSLCredential, public BasicX509Credential { public: - FilesystemCredentialResolver(const DOMElement* e); - virtual ~FilesystemCredentialResolver() { - delete m_credential; - for_each(m_certs.begin(),m_certs.end(),X509_free); + FilesystemCredential( + FilesystemCredentialResolver* resolver, + XSECCryptoKey* key, + const vector& xseccerts, + const vector& crls + ) : BasicX509Credential(key ? key : (xseccerts.empty() ? nullptr : xseccerts.front()->clonePublicKey()), xseccerts, crls), m_resolver(resolver) { + if (m_resolver->m_extractNames) + extract(); + m_keyNames.insert(m_resolver->m_keynames.begin(), m_resolver->m_keynames.end()); } - Lockable* lock() { return this; } - void unlock() {} - - const Credential* resolve(const CredentialCriteria* criteria=NULL) const { - return (criteria ? (criteria->matches(*m_credential) ? m_credential : NULL) : m_credential); + virtual ~FilesystemCredential() { } - virtual vector::size_type resolve( - vector& results, const CredentialCriteria* criteria=NULL - ) const { - if (!criteria || criteria->matches(*m_credential)) { - results.push_back(m_credential); - return 1; - } - return 0; + unsigned int getUsage() const { + return m_resolver->m_usage; + } + + void initKeyInfo(unsigned int types=0) { + BasicX509Credential::initKeyInfo(types); } void attach(SSL_CTX* ctx) const; private: - XSECCryptoKey* loadKey(); - - enum format_t { PEM=SSL_FILETYPE_PEM, DER=SSL_FILETYPE_ASN1, _PKCS12, UNKNOWN }; - - format_t getEncodingFormat(BIO* in) const; - string formatToString(format_t format) const; - format_t xmlFormatToFormat(const XMLCh* format_xml) const; - - format_t m_keyformat; - string m_keypath,m_keypass; - vector m_certs; - FilesystemCredential* m_credential; + FilesystemCredentialResolver* m_resolver; }; +#if defined (_MSC_VER) + #pragma warning( pop ) +#endif + CredentialResolver* XMLTOOL_DLLLOCAL FilesystemCredentialResolverFactory(const DOMElement* const & e) { return new FilesystemCredentialResolver(e); } + static const XMLCh backingFilePath[] = UNICODE_LITERAL_15(b,a,c,k,i,n,g,F,i,l,e,P,a,t,h); + static const XMLCh _CredentialResolver[] = UNICODE_LITERAL_18(C,r,e,d,e,n,t,i,a,l,R,e,s,o,l,v,e,r); static const XMLCh CAPath[] = UNICODE_LITERAL_6(C,A,P,a,t,h); static const XMLCh Certificate[] = UNICODE_LITERAL_11(C,e,r,t,i,f,i,c,a,t,e); - static const XMLCh format[] = UNICODE_LITERAL_6(f,o,r,m,a,t); + static const XMLCh _certificate[] = UNICODE_LITERAL_11(c,e,r,t,i,f,i,c,a,t,e); + static const XMLCh CRL[] = UNICODE_LITERAL_3(C,R,L); + static const XMLCh extractNames[] = UNICODE_LITERAL_12(e,x,t,r,a,c,t,N,a,m,e,s); + static const XMLCh _format[] = UNICODE_LITERAL_6(f,o,r,m,a,t); static const XMLCh Key[] = UNICODE_LITERAL_3(K,e,y); + static const XMLCh _key[] = UNICODE_LITERAL_3(k,e,y); + static const XMLCh keyInfoMask[] = UNICODE_LITERAL_11(k,e,y,I,n,f,o,M,a,s,k); + static const XMLCh keyName[] = UNICODE_LITERAL_7(k,e,y,N,a,m,e); static const XMLCh Name[] = UNICODE_LITERAL_4(N,a,m,e); static const XMLCh password[] = UNICODE_LITERAL_8(p,a,s,s,w,o,r,d); static const XMLCh Path[] = UNICODE_LITERAL_4(P,a,t,h); + static const XMLCh _reloadChanges[] = UNICODE_LITERAL_13(r,e,l,o,a,d,C,h,a,n,g,e,s); + static const XMLCh _reloadInterval[] = UNICODE_LITERAL_14(r,e,l,o,a,d,I,n,t,e,r,v,a,l); + static const XMLCh _URL[] = UNICODE_LITERAL_3(U,R,L); + static const XMLCh _use[] = UNICODE_LITERAL_3(u,s,e); }; -FilesystemCredentialResolver::FilesystemCredentialResolver(const DOMElement* e) : m_credential(NULL) +FilesystemCredentialResolver::FilesystemCredentialResolver(const DOMElement* e) + : m_lock(nullptr), m_credential(nullptr), m_keyinfomask(XMLHelper::getAttrInt(e, 0, keyInfoMask)), + m_usage(Credential::UNSPECIFIED_CREDENTIAL), m_extractNames(true) { #ifdef _DEBUG NDC ndc("FilesystemCredentialResolver"); #endif Category& log=Category::getInstance(XMLTOOLING_LOGCAT".CredentialResolver."FILESYSTEM_CREDENTIAL_RESOLVER); - const DOMElement* root=e; + if (e && (e->hasAttributeNS(nullptr,_certificate) || e->hasAttributeNS(nullptr,_key))) { + // Dummy up a simple file resolver config using these attributes. + DOMElement* dummy = e->getOwnerDocument()->createElementNS(nullptr,_CredentialResolver); + DOMElement* child; + DOMElement* path; + if (e->hasAttributeNS(nullptr,_key)) { + child = e->getOwnerDocument()->createElementNS(nullptr,Key); + dummy->appendChild(child); + path = e->getOwnerDocument()->createElementNS(nullptr,Path); + child->appendChild(path); + path->appendChild(e->getOwnerDocument()->createTextNode(e->getAttributeNS(nullptr,_key))); + if (e->hasAttributeNS(nullptr,password)) + child->setAttributeNS(nullptr,password,e->getAttributeNS(nullptr,password)); + if (e->hasAttributeNS(nullptr,keyName)) { + path = e->getOwnerDocument()->createElementNS(nullptr,Name); + child->appendChild(path); + path->appendChild(e->getOwnerDocument()->createTextNode(e->getAttributeNS(nullptr,keyName))); + } + } + if (e->hasAttributeNS(nullptr,_certificate)) { + child = e->getOwnerDocument()->createElementNS(nullptr,Certificate); + dummy->appendChild(child); + path = e->getOwnerDocument()->createElementNS(nullptr,Path); + child->appendChild(path); + path->appendChild(e->getOwnerDocument()->createTextNode(e->getAttributeNS(nullptr,_certificate))); + if (e->hasAttributeNS(nullptr, extractNames)) + child->setAttributeNS(nullptr, extractNames, e->getAttributeNS(nullptr, extractNames)); + } + if (e->hasAttributeNS(nullptr, _use)) { + dummy->setAttributeNS(nullptr, _use, e->getAttributeNS(nullptr, _use)); + } - XSECCryptoKey* key=NULL; - vector xseccerts; + e = dummy; // reset "root" to the dummy config element + } - format_t fformat; - const XMLCh* format_xml=NULL; - BIO* in = NULL; - - // Move to Key - const DOMElement* keynode=XMLHelper::getFirstChildElement(root,Key); - if (keynode) { + const XMLCh* prop; + const DOMElement* root = e; - // Get raw format attrib value, but defer processing til later since may need to - // determine format dynamically, and we need the Path for that. - format_xml=keynode->getAttributeNS(NULL,format); - - const XMLCh* password_xml=keynode->getAttributeNS(NULL,password); - if (password_xml) { - auto_ptr_char kp(password_xml); - m_keypass=kp.get(); - } - - e=XMLHelper::getFirstChildElement(keynode,Path); - if (e && e->hasChildNodes()) { - const XMLCh* s=e->getFirstChild()->getNodeValue(); - auto_ptr_char kpath(s); -#ifdef WIN32 - struct _stat stat_buf; - if (_stat(kpath.get(), &stat_buf) != 0) -#else - struct stat stat_buf; - if (stat(kpath.get(), &stat_buf) != 0) -#endif - { - log.error("key file (%s) can't be opened", kpath.get()); - throw XMLSecurityException("FilesystemCredentialResolver can't access key file ($1)",params(1,kpath.get())); - } - m_keypath=kpath.get(); + // Save off usage bits. + string usage = XMLHelper::getAttrString(root, nullptr, _use); + if (usage == "signing") + m_usage = Credential::SIGNING_CREDENTIAL | Credential::TLS_CREDENTIAL; + else if (usage == "TLS") + m_usage = Credential::TLS_CREDENTIAL; + else if (usage == "encryption") + m_usage = Credential::ENCRYPTION_CREDENTIAL; + + // Move to Key. + const DOMElement* keynode = XMLHelper::getFirstChildElement(root,Key); + if (keynode) { + m_key.format = XMLHelper::getAttrString(keynode, nullptr, _format); + m_keypass = XMLHelper::getAttrString(keynode, nullptr, password); + + if ((e=XMLHelper::getFirstChildElement(keynode,Path)) && e->hasChildNodes()) { + prop = e->getFirstChild()->getNodeValue(); + auto_ptr_char kpath(prop); + m_key.source = kpath.get(); + XMLToolingConfig::getConfig().getPathResolver()->resolve(m_key.source, PathResolver::XMLTOOLING_CFG_FILE); + m_key.local = true; + m_key.reloadChanges = XMLHelper::getAttrBool(e, true, _reloadChanges); + } + else if ((e=XMLHelper::getFirstChildElement(keynode,_URL)) && e->hasChildNodes()) { + prop = e->getFirstChild()->getNodeValue(); + auto_ptr_char kpath(prop); + m_key.source = kpath.get(); + m_key.local = false; + m_key.backing = XMLHelper::getAttrString(e, nullptr, backingFilePath); + if (m_key.backing.empty()) + throw XMLSecurityException("FilesystemCredentialResolver can't access key, backingFilePath missing from URL element."); + XMLToolingConfig::getConfig().getPathResolver()->resolve(m_key.backing, PathResolver::XMLTOOLING_RUN_FILE); + m_key.reloadInterval = XMLHelper::getAttrInt(e, 0, _reloadInterval); } else { - log.error("Path element missing inside Key element"); - throw XMLSecurityException("FilesystemCredentialResolver can't access key file, no Path element specified."); + log.error("Path/URL element missing inside Key element"); + throw XMLSecurityException("FilesystemCredentialResolver can't access key, no Path or URL element specified."); } - // Determine the key encoding format dynamically, if not explicitly specified - if (format_xml && *format_xml) { - fformat = xmlFormatToFormat(format_xml); - if (fformat != UNKNOWN) { - m_keyformat = fformat; - } - else { - auto_ptr_char unknown(format_xml); - log.error("configuration specifies unknown key encoding format (%s)", unknown.get()); - throw XMLSecurityException("FilesystemCredentialResolver configuration contains unknown key encoding format ($1)",params(1,unknown.get())); + e = XMLHelper::getFirstChildElement(keynode, Name); + while (e) { + if (e->hasChildNodes()) { + auto_ptr_char n(e->getFirstChild()->getNodeValue()); + if (n.get() && *n.get()) + m_keynames.push_back(n.get()); } + e = XMLHelper::getNextSiblingElement(e, Name); } - else { - in=BIO_new(BIO_s_file_internal()); - if (in && BIO_read_filename(in,m_keypath.c_str())>0) { - m_keyformat = getEncodingFormat(in); - log.debug("key encoding format for (%s) dynamically resolved as (%s)", m_keypath.c_str(), formatToString(m_keyformat).c_str()); + } + + // Check for CRL. + const DOMElement* crlnode = XMLHelper::getFirstChildElement(root, CRL); + if (crlnode) { + string crlformat = XMLHelper::getAttrString(crlnode, nullptr, _format); + e = XMLHelper::getFirstChildElement(crlnode, Path); + while (e) { + if (e->hasChildNodes()) { + m_crls.push_back(ManagedCRL()); + ManagedResource& crl = m_crls.back(); + crl.format = crlformat; + prop = e->getFirstChild()->getNodeValue(); + auto_ptr_char crlpath(prop); + crl.source = crlpath.get(); + XMLToolingConfig::getConfig().getPathResolver()->resolve(crl.source, PathResolver::XMLTOOLING_CFG_FILE); + crl.local = true; + crl.reloadChanges = XMLHelper::getAttrBool(e, true, _reloadChanges); } - else { - log.error("key file (%s) can't be read to determine encoding format", m_keypath.c_str()); - throw XMLSecurityException("FilesystemCredentialResolver can't read key file ($1) to determine encoding format",params(1,m_keypath.c_str())); + e = XMLHelper::getNextSiblingElement(e, Path); + } + + e = XMLHelper::getFirstChildElement(crlnode, _URL); + while (e) { + if (e->hasChildNodes()) { + m_crls.push_back(ManagedCRL()); + ManagedResource& crl = m_crls.back(); + crl.format = crlformat; + prop = e->getFirstChild()->getNodeValue(); + auto_ptr_char crlpath(prop); + crl.source = crlpath.get(); + crl.local = false; + crl.backing = XMLHelper::getAttrString(e, nullptr, backingFilePath); + if (crl.backing.empty()) + throw XMLSecurityException("FilesystemCredentialResolver can't access CRL, backingFilePath missing from URL element."); + XMLToolingConfig::getConfig().getPathResolver()->resolve(crl.backing, PathResolver::XMLTOOLING_RUN_FILE); + crl.reloadInterval = XMLHelper::getAttrInt(e, 0, _reloadInterval); } - if (in) - BIO_free(in); - in = NULL; + e = XMLHelper::getNextSiblingElement(e, _URL); } - - // Load the key. - key = loadKey(); - } - - // Check for Certificate - e=XMLHelper::getFirstChildElement(root,Certificate); - if (!e) { - m_credential = new FilesystemCredential(this,key,xseccerts); - m_credential->addKeyNames(keynode); - return; - } - auto_ptr_char certpass(e->getAttributeNS(NULL,password)); - - const DOMElement* ep=XMLHelper::getFirstChildElement(e,Path); - if (!ep || !ep->hasChildNodes()) { - log.error("Path element missing inside Certificate element or is empty"); - delete key; - throw XMLSecurityException("FilesystemCredentialResolver can't access certificate file, missing or empty Path element."); - } - - auto_ptr_char certpath(ep->getFirstChild()->getNodeValue()); - format_xml=e->getAttributeNS(NULL,format); - if (format_xml && *format_xml) { - fformat = xmlFormatToFormat(format_xml); - if (fformat == UNKNOWN) { - auto_ptr_char unknown(format_xml); - log.error("configuration specifies unknown certificate encoding format (%s)", unknown.get()); - delete key; - throw XMLSecurityException("FilesystemCredentialResolver configuration contains unknown certificate encoding format ($1)",params(1,unknown.get())); + if (m_crls.empty()) { + log.error("Path/URL element missing inside CRL element"); + throw XMLSecurityException("FilesystemCredentialResolver can't access CRL, no Path or URL element specified."); } } - - try { - X509* x=NULL; - PKCS12* p12=NULL; - in=BIO_new(BIO_s_file_internal()); - if (in && BIO_read_filename(in,certpath.get())>0) { - if (!format_xml || !*format_xml) { - // Determine the cert encoding format dynamically, if not explicitly specified - fformat = getEncodingFormat(in); - log.debug("certificate encoding format for (%s) dynamically resolved as (%s)", certpath.get(), formatToString(fformat).c_str()); - } - switch(fformat) { - case PEM: - while (x=PEM_read_bio_X509(in,NULL,passwd_callback,const_cast(certpass.get()))) - m_certs.push_back(x); - break; - - case DER: - x=d2i_X509_bio(in,NULL); - if (x) - m_certs.push_back(x); - else { - log_openssl(); - BIO_free(in); - throw XMLSecurityException("FilesystemCredentialResolver unable to load DER certificate from file ($1)",params(1,certpath.get())); - } - break; - - case _PKCS12: - p12=d2i_PKCS12_bio(in,NULL); - if (p12) { - PKCS12_parse(p12, certpass.get(), NULL, &x, NULL); - PKCS12_free(p12); - } - if (x) { - m_certs.push_back(x); - x=NULL; - } else { - log_openssl(); - BIO_free(in); - throw XMLSecurityException("FilesystemCredentialResolver unable to load PKCS12 certificate from file ($1)",params(1,certpath.get())); - } - break; - } // end switch - - } else { - log_openssl(); - if (in) { - BIO_free(in); - in=NULL; + // Check for Certificate + DOMElement* certnode = XMLHelper::getFirstChildElement(root,Certificate); + if (certnode) { + m_certpass = XMLHelper::getAttrString(certnode, nullptr, password); + string certformat = XMLHelper::getAttrString(certnode, nullptr, _format); + m_extractNames = XMLHelper::getAttrBool(certnode, true, extractNames); + + e = XMLHelper::getFirstChildElement(certnode); + while (e) { + if (e->hasChildNodes() && (XMLString::equals(e->getLocalName(), Path) || XMLString::equals(e->getLocalName(), CAPath))) { + m_certs.push_back(ManagedCert()); + ManagedResource& cert = m_certs.back(); + cert.format = certformat; + prop = e->getFirstChild()->getNodeValue(); + auto_ptr_char certpath(prop); + cert.source = certpath.get(); + XMLToolingConfig::getConfig().getPathResolver()->resolve(cert.source, PathResolver::XMLTOOLING_CFG_FILE); + cert.local = true; + cert.reloadChanges = XMLHelper::getAttrBool(e, true, _reloadChanges); + } + else if (e->hasChildNodes() && XMLString::equals(e->getLocalName(), _URL)) { + m_certs.push_back(ManagedCert()); + ManagedResource& cert = m_certs.back(); + cert.format = certformat; + prop = e->getFirstChild()->getNodeValue(); + auto_ptr_char certpath(prop); + cert.source = certpath.get(); + cert.local = false; + cert.backing = XMLHelper::getAttrString(e, nullptr, backingFilePath); + if (cert.backing.empty()) + throw XMLSecurityException("FilesystemCredentialResolver can't access certificate, backingFilePath missing from URL element."); + XMLToolingConfig::getConfig().getPathResolver()->resolve(cert.backing, PathResolver::XMLTOOLING_RUN_FILE); + cert.reloadInterval = XMLHelper::getAttrInt(e, 0, _reloadInterval); } - throw XMLSecurityException("FilesystemCredentialResolver unable to load certificate(s) from file ($1)",params(1,certpath.get())); + e = XMLHelper::getNextSiblingElement(e); } - if (in) { - BIO_free(in); - in=NULL; + if (m_certs.empty()) { + log.error("Path/URL element missing inside Certificate element"); + throw XMLSecurityException("FilesystemCredentialResolver can't access certificate, no Path or URL element specified."); } + } - if (m_certs.empty()) - throw XMLSecurityException("FilesystemCredentialResolver unable to load any certificate(s)"); + // Do an initial load of all the objects. If anything blows up here, whatever's + // been loaded should be freed during teardown of the embedded objects. + time_t now = time(nullptr); + m_key.filestamp = now; + m_key.load(log, m_keypass.c_str()); + for (vector::iterator i = m_certs.begin(); i != m_certs.end(); ++i) { + i->load(log, (i==m_certs.begin()) ? m_certpass.c_str() : nullptr); + i->filestamp = now; + } + for (vector::iterator j = m_crls.begin(); j != m_crls.end(); ++j) { + j->load(log); + j->filestamp = now; + } - // Load any extra CA files. - const DOMElement* extra=XMLHelper::getFirstChildElement(e,CAPath); - while (extra) { - if (!extra->hasChildNodes()) { - log.warn("skipping empty CAPath element"); - extra = XMLHelper::getNextSiblingElement(extra,CAPath); - continue; - } - auto_ptr_char capath(extra->getFirstChild()->getNodeValue()); - x=NULL; - p12=NULL; - in=BIO_new(BIO_s_file_internal()); - if (in && BIO_read_filename(in,capath.get())>0) { - if (!format_xml || !*format_xml) { - // Determine the cert encoding format dynamically, if not explicitly specified - fformat = getEncodingFormat(in); - log.debug("CA certificate encoding format for (%s) dynamically resolved as (%s)", certpath.get(), formatToString(fformat).c_str()); - } + // Load it all into a credential object and then create the lock. + auto_ptr credential(getCredential()); + m_lock = RWLock::create(); + m_credential = credential.release(); +} - switch (fformat) { - case PEM: - while (x=PEM_read_bio_X509(in,NULL,passwd_callback,const_cast(certpass.get()))) - m_certs.push_back(x); - break; - - case DER: - x=d2i_X509_bio(in,NULL); - if (x) - m_certs.push_back(x); - else { - log_openssl(); - BIO_free(in); - throw XMLSecurityException("FilesystemCredentialResolver unable to load DER CA certificate from file ($1)",params(1,capath.get())); - } - break; - - case _PKCS12: - p12 = d2i_PKCS12_bio(in, NULL); - if (p12) { - PKCS12_parse(p12, certpass.get(), NULL, &x, NULL); - PKCS12_free(p12); - } - if (x) { - m_certs.push_back(x); - x=NULL; - } - else { - log_openssl(); - BIO_free(in); - throw XMLSecurityException("FilesystemCredentialResolver unable to load PKCS12 CA certificate from file ($1)",params(1,capath.get())); - } - break; - } //end switch - - BIO_free(in); - } - else { - if (in) - BIO_free(in); - log_openssl(); - log.error("CA file (%s) can't be opened", capath.get()); - throw XMLSecurityException("FilesystemCredentialResolver can't open CA file ($1)",params(1,capath.get())); - } - - extra = XMLHelper::getNextSiblingElement(extra,CAPath); +FilesystemCredentialResolver::~FilesystemCredentialResolver() +{ + delete m_credential; + delete m_lock; +} + +Credential* FilesystemCredentialResolver::getCredential() +{ + // First, verify that the key and certificate match. + if (m_key.key && !m_certs.empty()) { + auto_ptr temp(m_certs.front().certs.front()->clonePublicKey()); + if (!SecurityHelper::matches(*m_key.key, *temp.get())) + throw XMLSecurityException("FilesystemCredentialResolver given mismatched key/certificate, check for consistency."); + } + + // We (unfortunately) need to duplicate all the objects and put them in one set of arrays + // in order to create the credential wrapper. + FilesystemCredential* credential=nullptr; + auto_ptr xseckey(m_key.key ? m_key.key->clone() : nullptr); + vector xseccerts; + vector xseccrls; + try { + for (vector::iterator i = m_certs.begin(); i != m_certs.end(); ++i) { + for (vector::const_iterator y = i->certs.begin(); y != i->certs.end(); ++y) + xseccerts.push_back(new OpenSSLCryptoX509(static_cast(*y)->getOpenSSLX509())); + } + for (vector::iterator j = m_crls.begin(); j != m_crls.end(); ++j) { + for (vector::const_iterator z = j->crls.begin(); z != j->crls.end(); ++z) + xseccrls.push_back((*z)->clone()); } + credential = new FilesystemCredential(this, xseckey.get(), xseccerts, xseccrls); + xseckey.release(); + } + catch (exception&) { + for_each(xseccerts.begin(), xseccerts.end(), xmltooling::cleanup()); + for_each(xseccrls.begin(), xseccrls.end(), xmltooling::cleanup()); + throw; + } + + // At this point the copies are owned by the credential. + try { + credential->initKeyInfo(m_keyinfomask); } - catch (XMLToolingException&) { - delete key; - for_each(m_certs.begin(), m_certs.end(), X509_free); + catch (exception&) { + delete credential; throw; } - // Reflect certs over to XSEC form and wrap with credential object. - for (vector::iterator j=m_certs.begin(); j!=m_certs.end(); j++) - xseccerts.push_back(new OpenSSLCryptoX509(*j)); - if (!key && !xseccerts.empty()) - key = xseccerts.front()->clonePublicKey(); - m_credential = new FilesystemCredential(this, key, xseccerts); - m_credential->addKeyNames(keynode); + return credential; } -XSECCryptoKey* FilesystemCredentialResolver::loadKey() +Lockable* FilesystemCredentialResolver::lock() { #ifdef _DEBUG - NDC ndc("loadKey"); + NDC ndc("lock"); #endif + Category& log=Category::getInstance(XMLTOOLING_LOGCAT".CredentialResolver."FILESYSTEM_CREDENTIAL_RESOLVER); - // Get a EVP_PKEY. - EVP_PKEY* pkey=NULL; - BIO* in=BIO_new(BIO_s_file_internal()); - if (in && BIO_read_filename(in,m_keypath.c_str())>0) { - switch (m_keyformat) { - case PEM: - pkey=PEM_read_bio_PrivateKey(in, NULL, passwd_callback, const_cast(m_keypass.c_str())); - break; - - case DER: - pkey=d2i_PrivateKey_bio(in, NULL); - break; - - default: { - PKCS12* p12 = d2i_PKCS12_bio(in, NULL); - if (p12) { - PKCS12_parse(p12, const_cast(m_keypass.c_str()), &pkey, NULL, NULL); - PKCS12_free(p12); - } - } - } - } - if (in) - BIO_free(in); - - // Now map it to an XSEC wrapper. - if (pkey) { - XSECCryptoKey* ret=NULL; - switch (pkey->type) { - case EVP_PKEY_RSA: - ret=new OpenSSLCryptoKeyRSA(pkey); - break; - - case EVP_PKEY_DSA: - ret=new OpenSSLCryptoKeyDSA(pkey); - break; - - default: - Category::getInstance(XMLTOOLING_LOGCAT".CredentialResolver."FILESYSTEM_CREDENTIAL_RESOLVER).error("unsupported private key type"); - } - EVP_PKEY_free(pkey); - if (ret) - return ret; - } + m_lock->rdlock(); - log_openssl(); - throw XMLSecurityException("FilesystemCredentialResolver unable to load private key from file."); -} + // Check each managed resource while holding a read lock for staleness. + // If it comes back false, the lock is left as is, and the resource was stable. + // If it comes back true, the lock was elevated to a write lock, and the resource + // needs to be reloaded, and the credential replaced. + // Once a stale check comes back true, further checks leave the lock alone. -// Used to determine the encoding format of credentials files -// dynamically. Supports: PEM, DER, PKCS12. -FilesystemCredentialResolver::format_t FilesystemCredentialResolver::getEncodingFormat(BIO* in) const -{ - PKCS12* p12 = NULL; - format_t format; + bool writelock = false, updated = false; - const int READSIZE = 1; - char buf[READSIZE]; - char b1; - int mark; + if (m_key.stale(log, m_lock)) { + writelock = true; + try { + m_key.load(log, m_keypass.c_str()); + updated = true; + } + catch (long& ex) { + if (ex == HTTPResponse::XMLTOOLING_HTTP_STATUS_NOTMODIFIED) { + log.info("remote key (%s) unchanged from cached version", m_key.source.c_str()); + } + else { + // Shouldn't happen, we should only get codes intended to be gracefully handled. + log.crit("maintaining existing key, remote fetch returned atypical status code (%d)", ex); + } + } + catch (exception& ex) { + log.crit("maintaining existing key: %s", ex.what()); + } + } - try { - if ( (mark = BIO_tell(in)) < 0 ) - throw XMLSecurityException("getEncodingFormat: BIO_tell() can't get the file position"); - if ( BIO_read(in, buf, READSIZE) <= 0 ) - throw XMLSecurityException("getEncodingFormat: BIO_read() can't read from the stream"); - if ( BIO_seek(in, mark) < 0 ) - throw XMLSecurityException("getEncodingFormat: BIO_seek() can't reset the file position"); + for (vector::iterator i = m_certs.begin(); i != m_certs.end(); ++i) { + if (i->stale(log, writelock ? nullptr : m_lock)) { + writelock = true; + try { + i->load(log, (i==m_certs.begin()) ? m_certpass.c_str() : nullptr); + updated = true; + } + catch (long& ex) { + if (ex == HTTPResponse::XMLTOOLING_HTTP_STATUS_NOTMODIFIED) { + log.info("remote certificate(s) (%s) unchanged from cached version", i->source.c_str()); + } + else { + // Shouldn't happen, we should only get codes intended to be gracefully handled. + log.crit("maintaining existing certificate(s), remote fetch returned atypical status code (%d)", ex); + } + } + catch (exception& ex) { + log.crit("maintaining existing certificate(s): %s", ex.what()); + } + } } - catch (...) { - log_openssl(); - throw; + + for (vector::iterator j = m_crls.begin(); j != m_crls.end(); ++j) { + if (j->stale(log, writelock ? nullptr : m_lock)) { + writelock = true; + try { + j->load(log); + updated = true; + } + catch (long& ex) { + if (ex == HTTPResponse::XMLTOOLING_HTTP_STATUS_NOTMODIFIED) { + log.info("remote CRL(s) (%s) unchanged from cached version", j->source.c_str()); + } + else { + // Shouldn't happen, we should only get codes intended to be gracefully handled. + log.crit("maintaining existing CRL(s), remote fetch returned atypical status code (%d)", ex); + } + } + catch (exception& ex) { + log.crit("maintaining existing CRL(s): %s", ex.what()); + } + } } - b1 = buf[0]; - - // This is a slight variation of the Java code by Chad La Joie. - // - // Check the first byte of the file. If it's some kind of - // DER-encoded structure (including PKCS12), it will begin with ASCII 048. - // Otherwise, assume it's PEM. - if (b1 != 48) { - format = PEM; - } else { - // Here we know it's DER-encoded, now try to parse it as a PKCS12 - // ASN.1 structure. If it fails, must be another kind of DER-encoded - // key/cert structure. A little inefficient...but it works. - if ( (p12=d2i_PKCS12_bio(in,NULL)) == NULL ) { - format = DER; - } else { - format = _PKCS12; - } - if (p12) - PKCS12_free(p12); - if ( BIO_seek(in, mark) < 0 ) { - log_openssl(); - throw XMLSecurityException("getEncodingFormat: BIO_seek() can't reset the file position"); + if (updated) { + try { + auto_ptr credential(getCredential()); + delete m_credential; + m_credential = credential.release(); + } + catch (exception& ex) { + log.crit("maintaining existing credentials, error reloading: %s", ex.what()); } } - return format; + if (writelock) { + m_lock->unlock(); + m_lock->rdlock(); + } + return this; +} + +const Credential* FilesystemCredentialResolver::resolve(const CredentialCriteria* criteria) const +{ + return (criteria ? (criteria->matches(*m_credential) ? m_credential : nullptr) : m_credential); } -// Convert key/cert format_t types to a human-meaningful string for debug output -string FilesystemCredentialResolver::formatToString(format_t format) const +vector::size_type FilesystemCredentialResolver::resolve( + vector& results, const CredentialCriteria* criteria + ) const { - switch(format) { - case PEM: - return "PEM"; - case DER: - return "DER"; - case _PKCS12: - return "PKCS12"; - default: - return "UNKNOWN"; + if (!criteria || criteria->matches(*m_credential)) { + results.push_back(m_credential); + return 1; } + return 0; } -// Convert key/cert raw XML format attribute (XMLCh[]) to format_t type -FilesystemCredentialResolver::format_t FilesystemCredentialResolver::xmlFormatToFormat(const XMLCh* format_xml) const +// OpenSSL password callback... +static int passwd_callback(char* buf, int len, int verify, void* passwd) { - static const XMLCh cPEM[] = UNICODE_LITERAL_3(P,E,M); - static const XMLCh cDER[] = UNICODE_LITERAL_3(D,E,R); - static const XMLCh cPKCS12[] = { chLatin_P, chLatin_K, chLatin_C, chLatin_S, chDigit_1, chDigit_2, chNull }; - format_t format; - - if (!XMLString::compareString(format_xml,cPEM)) - format=PEM; - else if (!XMLString::compareString(format_xml,cDER)) - format=DER; - else if (!XMLString::compareString(format_xml,cPKCS12)) - format=_PKCS12; - else - format=UNKNOWN; - - return format; + if(!verify) + { + if(passwd && len > strlen(reinterpret_cast(passwd))) + { + strcpy(buf,reinterpret_cast(passwd)); + return strlen(buf); + } + } + return 0; } -void FilesystemCredentialResolver::attach(SSL_CTX* ctx) const +void FilesystemCredential::attach(SSL_CTX* ctx) const { #ifdef _DEBUG NDC ndc("attach"); #endif - if (m_keypath.empty()) + int ret=0; + const char* path = m_resolver->m_key.local ? m_resolver->m_key.source.c_str() : m_resolver->m_key.backing.c_str(); + if (!path || !*path) throw XMLSecurityException("No key available, unable to attach private key to SSL context."); - // Attach key. - SSL_CTX_set_default_passwd_cb(ctx, passwd_callback); - SSL_CTX_set_default_passwd_cb_userdata(ctx, const_cast(m_keypass.c_str())); + if (!m_resolver->m_keypass.empty()) { + SSL_CTX_set_default_passwd_cb(ctx, passwd_callback); + SSL_CTX_set_default_passwd_cb_userdata(ctx, const_cast(m_resolver->m_keypass.c_str())); + } - int ret=0; - switch (m_keyformat) { - case PEM: - ret=SSL_CTX_use_PrivateKey_file(ctx, m_keypath.c_str(), m_keyformat); - break; - - case DER: - ret=SSL_CTX_use_RSAPrivateKey_file(ctx, m_keypath.c_str(), m_keyformat); - break; - - default: { - BIO* in=BIO_new(BIO_s_file_internal()); - if (in && BIO_read_filename(in,m_keypath.c_str())>0) { - EVP_PKEY* pkey=NULL; - PKCS12* p12 = d2i_PKCS12_bio(in, NULL); - if (p12) { - PKCS12_parse(p12, const_cast(m_keypass.c_str()), &pkey, NULL, NULL); - PKCS12_free(p12); - if (pkey) { - ret=SSL_CTX_use_PrivateKey(ctx, pkey); - EVP_PKEY_free(pkey); - } + if (m_resolver->m_key.format == "PEM") { + ret=SSL_CTX_use_PrivateKey_file(ctx, path, SSL_FILETYPE_PEM); + } + else if (m_resolver->m_key.format == "DER") { + ret=SSL_CTX_use_RSAPrivateKey_file(ctx, path, SSL_FILETYPE_ASN1); + } + else if (m_resolver->m_key.format == "PKCS12") { + BIO* in=BIO_new(BIO_s_file_internal()); + if (in && BIO_read_filename(in,path)>0) { + PKCS12* p12 = d2i_PKCS12_bio(in, nullptr); + if (p12) { + EVP_PKEY* pkey=nullptr; + X509* x=nullptr; + PKCS12_parse(p12, const_cast(m_resolver->m_keypass.c_str()), &pkey, &x, nullptr); + PKCS12_free(p12); + if (x) + X509_free(x); + if (pkey) { + ret=SSL_CTX_use_PrivateKey(ctx, pkey); + EVP_PKEY_free(pkey); } } - if (in) - BIO_free(in); } + if (in) + BIO_free(in); } - + if (ret!=1) { log_openssl(); throw XMLSecurityException("Unable to attach private key to SSL context."); } // Attach certs. - for (vector::const_iterator i=m_certs.begin(); i!=m_certs.end(); i++) { - if (i==m_certs.begin()) { - if (SSL_CTX_use_certificate(ctx, *i) != 1) { + for (vector::const_iterator i=m_xseccerts.begin(); i!=m_xseccerts.end(); i++) { + if (i==m_xseccerts.begin()) { + if (SSL_CTX_use_certificate(ctx, static_cast(*i)->getOpenSSLX509()) != 1) { log_openssl(); throw XMLSecurityException("Unable to attach client certificate to SSL context."); } } else { // When we add certs, they don't get ref counted, so we need to duplicate them. - X509* dup = X509_dup(*i); + X509* dup = X509_dup(static_cast(*i)->getOpenSSLX509()); if (SSL_CTX_add_extra_chain_cert(ctx, dup) != 1) { X509_free(dup); log_openssl(); @@ -610,21 +765,3 @@ void FilesystemCredentialResolver::attach(SSL_CTX* ctx) const } } } - -void FilesystemCredential::addKeyNames(const DOMElement* e) -{ - e = e ? XMLHelper::getFirstChildElement(e, Name) : NULL; - while (e) { - if (e->hasChildNodes()) { - auto_ptr_char n(e->getFirstChild()->getNodeValue()); - if (n.get() && *n.get()) - m_keyNames.insert(n.get()); - } - e = XMLHelper::getNextSiblingElement(e, Name); - } -} - -void FilesystemCredential::attach(SSL_CTX* ctx) const -{ - return m_resolver->attach(ctx); -}