https://issues.shibboleth.net/jira/browse/CPPXT-94
[shibboleth/cpp-xmltooling.git] / xmltooling / security / impl / FilesystemCredentialResolver.cpp
1 /**
2  * Licensed to the University Corporation for Advanced Internet
3  * Development, Inc. (UCAID) under one or more contributor license
4  * agreements. See the NOTICE file distributed with this work for
5  * additional information regarding copyright ownership.
6  *
7  * UCAID licenses this file to you under the Apache License,
8  * Version 2.0 (the "License"); you may not use this file except
9  * in compliance with the License. You may obtain a copy of the
10  * License at
11  *
12  * http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing,
15  * software distributed under the License is distributed on an
16  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
17  * either express or implied. See the License for the specific
18  * language governing permissions and limitations under the License.
19  */
20
21 /**
22  * FilesystemCredentialResolver.cpp
23  *
24  * Supplies credentials from local files.
25  */
26
27 #include "internal.h"
28 #include "logging.h"
29 #include "io/HTTPResponse.h"
30 #include "security/BasicX509Credential.h"
31 #include "security/CredentialCriteria.h"
32 #include "security/CredentialResolver.h"
33 #include "security/KeyInfoResolver.h"
34 #include "security/OpenSSLCredential.h"
35 #include "security/SecurityHelper.h"
36 #include "security/XSECCryptoX509CRL.h"
37 #include "util/NDC.h"
38 #include "util/PathResolver.h"
39 #include "util/Threads.h"
40 #include "util/XMLHelper.h"
41
42 #include <memory>
43 #include <algorithm>
44 #include <sys/types.h>
45 #include <sys/stat.h>
46 #include <openssl/pkcs12.h>
47 #include <xercesc/util/XMLUniDefs.hpp>
48 #include <xsec/enc/OpenSSL/OpenSSLCryptoX509.hpp>
49
50 using namespace xmlsignature;
51 using namespace xmltooling::logging;
52 using namespace xmltooling;
53 using namespace std;
54
55 using xercesc::DOMElement;
56 using xercesc::chLatin_f;
57 using xercesc::chDigit_0;
58
59 namespace xmltooling {
60
61     // The ManagedResource classes handle memory management, loading of the files
62     // and staleness detection. A copy of the active objects is always stored in
63     // these instances.
64
65     class XMLTOOL_DLLLOCAL ManagedResource {
66     protected:
67         ManagedResource() : local(true), reloadChanges(true), filestamp(0), reloadInterval(0) {}
68         ~ManagedResource() {}
69
70         SOAPTransport* getTransport() {
71             SOAPTransport::Address addr("FilesystemCredentialResolver", source.c_str(), source.c_str());
72             string scheme(addr.m_endpoint, strchr(addr.m_endpoint,':') - addr.m_endpoint);
73             SOAPTransport* ret = XMLToolingConfig::getConfig().SOAPTransportManager.newPlugin(scheme.c_str(), addr);
74             if (ret)
75                 ret->setCacheTag(&cacheTag);
76             return ret;
77         }
78
79     public:
80         bool stale(Category& log, RWLock* lock=nullptr) {
81             if (local) {
82 #ifdef WIN32
83                 struct _stat stat_buf;
84                 if (_stat(source.c_str(), &stat_buf) != 0)
85                     return false;
86 #else
87                 struct stat stat_buf;
88                 if (stat(source.c_str(), &stat_buf) != 0)
89                     return false;
90 #endif
91                 if (filestamp >= stat_buf.st_mtime)
92                     return false;
93
94                 // If necessary, elevate lock and recheck.
95                 if (lock) {
96                     log.debug("timestamp of local resource changed, elevating to a write lock");
97                     lock->unlock();
98                     lock->wrlock();
99                     if (filestamp >= stat_buf.st_mtime) {
100                         // Somebody else handled it, just downgrade.
101                         log.debug("update of local resource handled by another thread, downgrading lock");
102                         lock->unlock();
103                         lock->rdlock();
104                         return false;
105                     }
106                 }
107
108                 // Update the timestamp regardless. No point in repeatedly trying.
109                 filestamp = stat_buf.st_mtime;
110                 log.info("change detected, reloading local resource...");
111             }
112             else {
113                 time_t now = time(nullptr);
114
115                 // Time to reload?
116                 if (now - filestamp < reloadInterval)
117                     return false;
118
119                 // If necessary, elevate lock and recheck.
120                 if (lock) {
121                     log.debug("reload interval for remote resource elapsed, elevating to a write lock");
122                     lock->unlock();
123                     lock->wrlock();
124                     if (now - filestamp < reloadInterval) {
125                         // Somebody else handled it, just downgrade.
126                         log.debug("update of remote resource handled by another thread, downgrading lock");
127                         lock->unlock();
128                         lock->rdlock();
129                         return false;
130                     }
131                 }
132
133                 filestamp = now;
134                 log.info("reloading remote resource...");
135             }
136             return true;
137         }
138
139         bool local,reloadChanges;
140         string format,source,backing,cacheTag;
141         time_t filestamp,reloadInterval;
142     };
143
144     class XMLTOOL_DLLLOCAL ManagedKey : public ManagedResource {
145     public:
146         ManagedKey() : key(nullptr) {}
147         ~ManagedKey() { delete key; }
148         void load(Category& log, const char* password) {
149             if (source.empty())
150                 return;
151             XSECCryptoKey* nkey=nullptr;
152             if (local) {
153                 nkey = SecurityHelper::loadKeyFromFile(source.c_str(), format.c_str(), password);
154             }
155             else {
156                 auto_ptr<SOAPTransport> t(getTransport());
157                 log.info("loading private key from URL (%s)", source.c_str());
158                 nkey = SecurityHelper::loadKeyFromURL(*t.get(), backing.c_str(), format.c_str(), password);
159             }
160             delete key;
161             key = nkey;
162             if (format.empty())
163                 format = SecurityHelper::guessEncodingFormat(local ? source.c_str() : backing.c_str());
164         }
165
166         XSECCryptoKey* key;
167     };
168
169     class XMLTOOL_DLLLOCAL ManagedCert : public ManagedResource {
170     public:
171         ManagedCert() {}
172         ~ManagedCert() { for_each(certs.begin(), certs.end(), xmltooling::cleanup<XSECCryptoX509>()); }
173         void load(Category& log, const char* password) {
174             if (source.empty())
175                 return;
176             vector<XSECCryptoX509*> ncerts;
177             if (local) {
178                 SecurityHelper::loadCertificatesFromFile(ncerts, source.c_str(), format.c_str(), password);
179             }
180             else {
181                 auto_ptr<SOAPTransport> t(getTransport());
182                 log.info("loading certificate(s) from URL (%s)", source.c_str());
183                 SecurityHelper::loadCertificatesFromURL(ncerts, *t.get(), backing.c_str(), format.c_str(), password);
184             }
185             for_each(certs.begin(), certs.end(), xmltooling::cleanup<XSECCryptoX509>());
186             certs = ncerts;
187             if (format.empty())
188                 format = SecurityHelper::guessEncodingFormat(local ? source.c_str() : backing.c_str());
189         }
190         vector<XSECCryptoX509*> certs;
191     };
192
193     class XMLTOOL_DLLLOCAL ManagedCRL : public ManagedResource {
194     public:
195         ManagedCRL() {}
196         ~ManagedCRL() { for_each(crls.begin(), crls.end(), xmltooling::cleanup<XSECCryptoX509CRL>()); }
197         void load(Category& log) {
198             if (source.empty())
199                 return;
200             vector<XSECCryptoX509CRL*> ncrls;
201             if (local) {
202                 SecurityHelper::loadCRLsFromFile(ncrls, source.c_str(), format.c_str());
203             }
204             else {
205                 auto_ptr<SOAPTransport> t(getTransport());
206                 log.info("loading CRL(s) from URL (%s)", source.c_str());
207                 SecurityHelper::loadCRLsFromURL(ncrls, *t.get(), backing.c_str(), format.c_str());
208             }
209             for_each(crls.begin(), crls.end(), xmltooling::cleanup<XSECCryptoX509CRL>());
210             crls = ncrls;
211             if (format.empty())
212                 format = SecurityHelper::guessEncodingFormat(local ? source.c_str() : backing.c_str());
213         }
214         vector<XSECCryptoX509CRL*> crls;
215     };
216
217     class XMLTOOL_DLLLOCAL FilesystemCredential;
218     class XMLTOOL_DLLLOCAL FilesystemCredentialResolver : public CredentialResolver
219     {
220     public:
221         FilesystemCredentialResolver(const DOMElement* e);
222         virtual ~FilesystemCredentialResolver();
223
224         Lockable* lock();
225         void unlock() {
226             m_lock->unlock();
227         }
228
229         const Credential* resolve(const CredentialCriteria* criteria=nullptr) const;
230
231         virtual vector<const Credential*>::size_type resolve(
232             vector<const Credential*>& results, const CredentialCriteria* criteria=nullptr
233             ) const;
234
235     private:
236         Credential* getCredential();
237
238         RWLock* m_lock;
239         Credential* m_credential;
240         string m_keypass,m_certpass;
241         unsigned int m_keyinfomask,m_usage;
242         bool m_extractNames;
243         vector<string> m_keynames;
244
245         ManagedKey m_key;
246         vector<ManagedCert> m_certs;
247         vector<ManagedCRL> m_crls;
248
249         friend class XMLTOOL_DLLLOCAL FilesystemCredential;
250     };
251
252 #if defined (_MSC_VER)
253     #pragma warning( push )
254     #pragma warning( disable : 4250 )
255 #endif
256
257     class XMLTOOL_DLLLOCAL FilesystemCredential : public OpenSSLCredential, public BasicX509Credential
258     {
259     public:
260         FilesystemCredential(
261             FilesystemCredentialResolver* resolver,
262             XSECCryptoKey* key,
263             const vector<XSECCryptoX509*>& xseccerts,
264             const vector<XSECCryptoX509CRL*>& crls
265             ) : BasicX509Credential(key ? key : (xseccerts.empty() ? nullptr : xseccerts.front()->clonePublicKey()), xseccerts, crls), m_resolver(resolver) {
266             if (m_resolver->m_extractNames)
267                 extract();
268             m_keyNames.insert(m_resolver->m_keynames.begin(), m_resolver->m_keynames.end());
269         }
270
271         virtual ~FilesystemCredential() {
272         }
273
274         unsigned int getUsage() const {
275             return m_resolver->m_usage;
276         }
277
278         void initKeyInfo(unsigned int types=0) {
279             BasicX509Credential::initKeyInfo(types);
280         }
281
282         void attach(SSL_CTX* ctx) const;
283
284     private:
285         FilesystemCredentialResolver* m_resolver;
286     };
287
288 #if defined (_MSC_VER)
289     #pragma warning( pop )
290 #endif
291
292     CredentialResolver* XMLTOOL_DLLLOCAL FilesystemCredentialResolverFactory(const DOMElement* const & e)
293     {
294         return new FilesystemCredentialResolver(e);
295     }
296
297     static const XMLCh backingFilePath[] =  UNICODE_LITERAL_15(b,a,c,k,i,n,g,F,i,l,e,P,a,t,h);
298     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);
299     static const XMLCh CAPath[] =           UNICODE_LITERAL_6(C,A,P,a,t,h);
300     static const XMLCh Certificate[] =      UNICODE_LITERAL_11(C,e,r,t,i,f,i,c,a,t,e);
301     static const XMLCh _certificate[] =     UNICODE_LITERAL_11(c,e,r,t,i,f,i,c,a,t,e);
302     static const XMLCh CRL[] =              UNICODE_LITERAL_3(C,R,L);
303     static const XMLCh extractNames[] =     UNICODE_LITERAL_12(e,x,t,r,a,c,t,N,a,m,e,s);
304     static const XMLCh _format[] =          UNICODE_LITERAL_6(f,o,r,m,a,t);
305     static const XMLCh Key[] =              UNICODE_LITERAL_3(K,e,y);
306     static const XMLCh _key[] =             UNICODE_LITERAL_3(k,e,y);
307     static const XMLCh keyInfoMask[] =      UNICODE_LITERAL_11(k,e,y,I,n,f,o,M,a,s,k);
308     static const XMLCh keyName[] =          UNICODE_LITERAL_7(k,e,y,N,a,m,e);
309     static const XMLCh Name[] =             UNICODE_LITERAL_4(N,a,m,e);
310     static const XMLCh password[] =         UNICODE_LITERAL_8(p,a,s,s,w,o,r,d);
311     static const XMLCh Path[] =             UNICODE_LITERAL_4(P,a,t,h);
312     static const XMLCh _reloadChanges[] =   UNICODE_LITERAL_13(r,e,l,o,a,d,C,h,a,n,g,e,s);
313     static const XMLCh _reloadInterval[] =  UNICODE_LITERAL_14(r,e,l,o,a,d,I,n,t,e,r,v,a,l);
314     static const XMLCh _URL[] =             UNICODE_LITERAL_3(U,R,L);
315     static const XMLCh _use[] =             UNICODE_LITERAL_3(u,s,e);
316 };
317
318 FilesystemCredentialResolver::FilesystemCredentialResolver(const DOMElement* e)
319     : m_lock(nullptr), m_credential(nullptr), m_keyinfomask(XMLHelper::getAttrInt(e, 0, keyInfoMask)),
320         m_usage(Credential::UNSPECIFIED_CREDENTIAL), m_extractNames(true)
321 {
322 #ifdef _DEBUG
323     NDC ndc("FilesystemCredentialResolver");
324 #endif
325     Category& log=Category::getInstance(XMLTOOLING_LOGCAT".CredentialResolver."FILESYSTEM_CREDENTIAL_RESOLVER);
326
327     if (e && (e->hasAttributeNS(nullptr,_certificate) || e->hasAttributeNS(nullptr,_key))) {
328         // Dummy up a simple file resolver config using these attributes.
329         DOMElement* dummy = e->getOwnerDocument()->createElementNS(nullptr,_CredentialResolver);
330         DOMElement* child;
331         DOMElement* path;
332         if (e->hasAttributeNS(nullptr,_key)) {
333             child = e->getOwnerDocument()->createElementNS(nullptr,Key);
334             dummy->appendChild(child);
335             path = e->getOwnerDocument()->createElementNS(nullptr,Path);
336             child->appendChild(path);
337             path->appendChild(e->getOwnerDocument()->createTextNode(e->getAttributeNS(nullptr,_key)));
338             if (e->hasAttributeNS(nullptr,password))
339                 child->setAttributeNS(nullptr,password,e->getAttributeNS(nullptr,password));
340             if (e->hasAttributeNS(nullptr,keyName)) {
341                 path = e->getOwnerDocument()->createElementNS(nullptr,Name);
342                 child->appendChild(path);
343                 path->appendChild(e->getOwnerDocument()->createTextNode(e->getAttributeNS(nullptr,keyName)));
344             }
345         }
346         if (e->hasAttributeNS(nullptr,_certificate)) {
347             child = e->getOwnerDocument()->createElementNS(nullptr,Certificate);
348             dummy->appendChild(child);
349             path = e->getOwnerDocument()->createElementNS(nullptr,Path);
350             child->appendChild(path);
351             path->appendChild(e->getOwnerDocument()->createTextNode(e->getAttributeNS(nullptr,_certificate)));
352             if (e->hasAttributeNS(nullptr, extractNames))
353                 child->setAttributeNS(nullptr, extractNames, e->getAttributeNS(nullptr, extractNames));
354         }
355         if (e->hasAttributeNS(nullptr, _use)) {
356             dummy->setAttributeNS(nullptr, _use, e->getAttributeNS(nullptr, _use));
357         }
358
359         e = dummy;  // reset "root" to the dummy config element
360     }
361
362     const XMLCh* prop;
363     const DOMElement* root = e;
364
365     // Save off usage bits.
366     string usage = XMLHelper::getAttrString(root, nullptr, _use);
367     if (usage == "signing")
368         m_usage = Credential::SIGNING_CREDENTIAL | Credential::TLS_CREDENTIAL;
369     else if (usage == "TLS")
370         m_usage = Credential::TLS_CREDENTIAL;
371     else if (usage == "encryption")
372         m_usage = Credential::ENCRYPTION_CREDENTIAL;
373
374     // Move to Key.
375     const DOMElement* keynode = XMLHelper::getFirstChildElement(root,Key);
376     if (keynode) {
377         m_key.format = XMLHelper::getAttrString(keynode, nullptr, _format);
378         m_keypass = XMLHelper::getAttrString(keynode, nullptr, password);
379
380         if ((e=XMLHelper::getFirstChildElement(keynode,Path)) && e->hasChildNodes()) {
381             prop = e->getFirstChild()->getNodeValue();
382             auto_ptr_char kpath(prop);
383             m_key.source = kpath.get();
384             XMLToolingConfig::getConfig().getPathResolver()->resolve(m_key.source, PathResolver::XMLTOOLING_CFG_FILE);
385             m_key.local = true;
386             m_key.reloadChanges = XMLHelper::getAttrBool(e, true, _reloadChanges);
387         }
388         else if ((e=XMLHelper::getFirstChildElement(keynode,_URL)) && e->hasChildNodes()) {
389             prop = e->getFirstChild()->getNodeValue();
390             auto_ptr_char kpath(prop);
391             m_key.source = kpath.get();
392             m_key.local = false;
393             m_key.backing = XMLHelper::getAttrString(e, nullptr, backingFilePath);
394             if (m_key.backing.empty())
395                 throw XMLSecurityException("FilesystemCredentialResolver can't access key, backingFilePath missing from URL element.");
396             XMLToolingConfig::getConfig().getPathResolver()->resolve(m_key.backing, PathResolver::XMLTOOLING_CACHE_FILE);
397             m_key.reloadInterval = XMLHelper::getAttrInt(e, 0, _reloadInterval);
398         }
399         else {
400             log.error("Path/URL element missing inside Key element");
401             throw XMLSecurityException("FilesystemCredentialResolver can't access key, no Path or URL element specified.");
402         }
403
404         e = XMLHelper::getFirstChildElement(keynode, Name);
405         while (e) {
406             if (e->hasChildNodes()) {
407                 auto_ptr_char n(e->getFirstChild()->getNodeValue());
408                 if (n.get() && *n.get())
409                     m_keynames.push_back(n.get());
410             }
411             e = XMLHelper::getNextSiblingElement(e, Name);
412         }
413     }
414
415     // Check for CRL.
416     const DOMElement* crlnode = XMLHelper::getFirstChildElement(root, CRL);
417     if (crlnode) {
418         string crlformat = XMLHelper::getAttrString(crlnode, nullptr, _format);
419         e = XMLHelper::getFirstChildElement(crlnode, Path);
420         while (e) {
421             if (e->hasChildNodes()) {
422                 m_crls.push_back(ManagedCRL());
423                 ManagedResource& crl = m_crls.back();
424                 crl.format = crlformat;
425                 prop = e->getFirstChild()->getNodeValue();
426                 auto_ptr_char crlpath(prop);
427                 crl.source = crlpath.get();
428                 XMLToolingConfig::getConfig().getPathResolver()->resolve(crl.source, PathResolver::XMLTOOLING_CFG_FILE);
429                 crl.local = true;
430                 crl.reloadChanges = XMLHelper::getAttrBool(e, true, _reloadChanges);
431             }
432             e = XMLHelper::getNextSiblingElement(e, Path);
433         }
434
435         e = XMLHelper::getFirstChildElement(crlnode, _URL);
436         while (e) {
437             if (e->hasChildNodes()) {
438                 m_crls.push_back(ManagedCRL());
439                 ManagedResource& crl = m_crls.back();
440                 crl.format = crlformat;
441                 prop = e->getFirstChild()->getNodeValue();
442                 auto_ptr_char crlpath(prop);
443                 crl.source = crlpath.get();
444                 crl.local = false;
445                 crl.backing = XMLHelper::getAttrString(e, nullptr, backingFilePath);
446                 if (crl.backing.empty())
447                     throw XMLSecurityException("FilesystemCredentialResolver can't access CRL, backingFilePath missing from URL element.");
448                 XMLToolingConfig::getConfig().getPathResolver()->resolve(crl.backing, PathResolver::XMLTOOLING_CACHE_FILE);
449                 crl.reloadInterval = XMLHelper::getAttrInt(e, 0, _reloadInterval);
450             }
451             e = XMLHelper::getNextSiblingElement(e, _URL);
452         }
453         if (m_crls.empty()) {
454             log.error("Path/URL element missing inside CRL element");
455             throw XMLSecurityException("FilesystemCredentialResolver can't access CRL, no Path or URL element specified.");
456         }
457     }
458
459     // Check for Certificate
460     DOMElement* certnode = XMLHelper::getFirstChildElement(root,Certificate);
461     if (certnode) {
462         m_certpass = XMLHelper::getAttrString(certnode, nullptr, password);
463         string certformat = XMLHelper::getAttrString(certnode, nullptr, _format);
464         m_extractNames = XMLHelper::getAttrBool(certnode, true, extractNames);
465
466         e = XMLHelper::getFirstChildElement(certnode);
467         while (e) {
468             if (e->hasChildNodes() && (XMLString::equals(e->getLocalName(), Path) || XMLString::equals(e->getLocalName(), CAPath))) {
469                 m_certs.push_back(ManagedCert());
470                 ManagedResource& cert = m_certs.back();
471                 cert.format = certformat;
472                 prop = e->getFirstChild()->getNodeValue();
473                 auto_ptr_char certpath(prop);
474                 cert.source = certpath.get();
475                 XMLToolingConfig::getConfig().getPathResolver()->resolve(cert.source, PathResolver::XMLTOOLING_CFG_FILE);
476                 cert.local = true;
477                 cert.reloadChanges = XMLHelper::getAttrBool(e, true, _reloadChanges);
478             }
479             else if (e->hasChildNodes() && XMLString::equals(e->getLocalName(), _URL)) {
480                 m_certs.push_back(ManagedCert());
481                 ManagedResource& cert = m_certs.back();
482                 cert.format = certformat;
483                 prop = e->getFirstChild()->getNodeValue();
484                 auto_ptr_char certpath(prop);
485                 cert.source = certpath.get();
486                 cert.local = false;
487                 cert.backing = XMLHelper::getAttrString(e, nullptr, backingFilePath);
488                 if (cert.backing.empty())
489                     throw XMLSecurityException("FilesystemCredentialResolver can't access certificate, backingFilePath missing from URL element.");
490                 XMLToolingConfig::getConfig().getPathResolver()->resolve(cert.backing, PathResolver::XMLTOOLING_CACHE_FILE);
491                 cert.reloadInterval = XMLHelper::getAttrInt(e, 0, _reloadInterval);
492             }
493             e = XMLHelper::getNextSiblingElement(e);
494         }
495         if (m_certs.empty()) {
496             log.error("Path/URL element missing inside Certificate element");
497             throw XMLSecurityException("FilesystemCredentialResolver can't access certificate, no Path or URL element specified.");
498         }
499     }
500
501     // Do an initial load of all the objects. If anything blows up here, whatever's
502     // been loaded should be freed during teardown of the embedded objects.
503     time_t now = time(nullptr);
504     m_key.filestamp = now;
505     m_key.load(log, m_keypass.c_str());
506     for (vector<ManagedCert>::iterator i = m_certs.begin(); i != m_certs.end(); ++i) {
507         i->load(log, (i==m_certs.begin()) ? m_certpass.c_str() : nullptr);
508         i->filestamp = now;
509     }
510     for (vector<ManagedCRL>::iterator j = m_crls.begin(); j != m_crls.end(); ++j) {
511         j->load(log);
512         j->filestamp = now;
513     }
514
515     // Load it all into a credential object and then create the lock.
516     auto_ptr<Credential> credential(getCredential());
517     m_lock = RWLock::create();
518     m_credential = credential.release();
519     if (m_credential->getPrivateKey() == nullptr) {
520         log.info("no private key resolved, usable for verification/trust only");
521     }
522 }
523
524 FilesystemCredentialResolver::~FilesystemCredentialResolver()
525 {
526     delete m_credential;
527     delete m_lock;
528 }
529
530 Credential* FilesystemCredentialResolver::getCredential()
531 {
532     // First, verify that the key and certificate match.
533     if (m_key.key && !m_certs.empty()) {
534         auto_ptr<XSECCryptoKey> temp(m_certs.front().certs.front()->clonePublicKey());
535         if (!SecurityHelper::matches(*m_key.key, *temp.get()))
536             throw XMLSecurityException("FilesystemCredentialResolver given mismatched key/certificate, check for consistency.");
537     }
538
539     // We (unfortunately) need to duplicate all the objects and put them in one set of arrays
540     // in order to create the credential wrapper.
541     FilesystemCredential* credential=nullptr;
542     auto_ptr<XSECCryptoKey> xseckey(m_key.key ? m_key.key->clone() : nullptr);
543     vector<XSECCryptoX509*> xseccerts;
544     vector<XSECCryptoX509CRL*> xseccrls;
545     try {
546         for (vector<ManagedCert>::iterator i = m_certs.begin(); i != m_certs.end(); ++i) {
547             for (vector<XSECCryptoX509*>::const_iterator y = i->certs.begin(); y != i->certs.end(); ++y)
548                 xseccerts.push_back(new OpenSSLCryptoX509(static_cast<OpenSSLCryptoX509*>(*y)->getOpenSSLX509()));
549         }
550         for (vector<ManagedCRL>::iterator j = m_crls.begin(); j != m_crls.end(); ++j) {
551             for (vector<XSECCryptoX509CRL*>::const_iterator z = j->crls.begin(); z != j->crls.end(); ++z)
552                 xseccrls.push_back((*z)->clone());
553         }
554         credential = new FilesystemCredential(this, xseckey.get(), xseccerts, xseccrls);
555         xseckey.release();
556     }
557     catch (exception&) {
558         for_each(xseccerts.begin(), xseccerts.end(), xmltooling::cleanup<XSECCryptoX509>());
559         for_each(xseccrls.begin(), xseccrls.end(), xmltooling::cleanup<XSECCryptoX509CRL>());
560         throw;
561     }
562
563     // At this point the copies are owned by the credential.
564     try {
565         credential->initKeyInfo(m_keyinfomask);
566     }
567     catch (exception&) {
568         delete credential;
569         throw;
570     }
571
572     return credential;
573 }
574
575 Lockable* FilesystemCredentialResolver::lock()
576 {
577 #ifdef _DEBUG
578     NDC ndc("lock");
579 #endif
580     Category& log=Category::getInstance(XMLTOOLING_LOGCAT".CredentialResolver."FILESYSTEM_CREDENTIAL_RESOLVER);
581
582     m_lock->rdlock();
583
584     // Check each managed resource while holding a read lock for staleness.
585     // If it comes back false, the lock is left as is, and the resource was stable.
586     // If it comes back true, the lock was elevated to a write lock, and the resource
587     // needs to be reloaded, and the credential replaced.
588     // Once a stale check comes back true, further checks leave the lock alone.
589
590     bool writelock = false, updated = false;
591
592     if (m_key.stale(log, m_lock)) {
593         writelock = true;
594         try {
595             m_key.load(log, m_keypass.c_str());
596             updated = true;
597         }
598         catch (long& ex) {
599             if (ex == HTTPResponse::XMLTOOLING_HTTP_STATUS_NOTMODIFIED) {
600                 log.info("remote key (%s) unchanged from cached version", m_key.source.c_str());
601             }
602             else {
603                 // Shouldn't happen, we should only get codes intended to be gracefully handled.
604                 log.crit("maintaining existing key, remote fetch returned atypical status code (%d)", ex);
605             }
606         }
607         catch (exception& ex) {
608             log.crit("maintaining existing key: %s", ex.what());
609         }
610     }
611
612     for (vector<ManagedCert>::iterator i = m_certs.begin(); i != m_certs.end(); ++i) {
613         if (i->stale(log, writelock ? nullptr : m_lock)) {
614             writelock = true;
615             try {
616                 i->load(log, (i==m_certs.begin()) ? m_certpass.c_str() : nullptr);
617                 updated = true;
618             }
619             catch (long& ex) {
620                 if (ex == HTTPResponse::XMLTOOLING_HTTP_STATUS_NOTMODIFIED) {
621                     log.info("remote certificate(s) (%s) unchanged from cached version", i->source.c_str());
622                 }
623                 else {
624                     // Shouldn't happen, we should only get codes intended to be gracefully handled.
625                     log.crit("maintaining existing certificate(s), remote fetch returned atypical status code (%d)", ex);
626                 }
627             }
628             catch (exception& ex) {
629                 log.crit("maintaining existing certificate(s): %s", ex.what());
630             }
631         }
632     }
633
634     for (vector<ManagedCRL>::iterator j = m_crls.begin(); j != m_crls.end(); ++j) {
635         if (j->stale(log, writelock ? nullptr : m_lock)) {
636             writelock = true;
637             try {
638                 j->load(log);
639                 updated = true;
640             }
641             catch (long& ex) {
642                 if (ex == HTTPResponse::XMLTOOLING_HTTP_STATUS_NOTMODIFIED) {
643                     log.info("remote CRL(s) (%s) unchanged from cached version", j->source.c_str());
644                 }
645                 else {
646                     // Shouldn't happen, we should only get codes intended to be gracefully handled.
647                     log.crit("maintaining existing CRL(s), remote fetch returned atypical status code (%d)", ex);
648                 }
649             }
650             catch (exception& ex) {
651                 log.crit("maintaining existing CRL(s): %s", ex.what());
652             }
653         }
654     }
655
656     if (updated) {
657         try {
658             auto_ptr<Credential> credential(getCredential());
659             delete m_credential;
660             m_credential = credential.release();
661         }
662         catch (exception& ex) {
663             log.crit("maintaining existing credentials, error reloading: %s", ex.what());
664         }
665     }
666
667     if (writelock) {
668         m_lock->unlock();
669         m_lock->rdlock();
670     }
671     return this;
672 }
673
674 const Credential* FilesystemCredentialResolver::resolve(const CredentialCriteria* criteria) const
675 {
676     return (criteria ? (criteria->matches(*m_credential) ? m_credential : nullptr) : m_credential);
677 }
678
679 vector<const Credential*>::size_type FilesystemCredentialResolver::resolve(
680     vector<const Credential*>& results, const CredentialCriteria* criteria
681     ) const
682 {
683     if (!criteria || criteria->matches(*m_credential)) {
684         results.push_back(m_credential);
685         return 1;
686     }
687     return 0;
688 }
689
690 // OpenSSL password callback...
691 static int passwd_callback(char* buf, int len, int verify, void* passwd)
692 {
693     if(!verify)
694     {
695         if(passwd && len > strlen(reinterpret_cast<char*>(passwd)))
696         {
697             strcpy(buf,reinterpret_cast<char*>(passwd));
698             return strlen(buf);
699         }
700     }
701     return 0;
702 }
703
704 void FilesystemCredential::attach(SSL_CTX* ctx) const
705 {
706 #ifdef _DEBUG
707     NDC ndc("attach");
708 #endif
709
710     int ret=0;
711     const char* path = m_resolver->m_key.local ? m_resolver->m_key.source.c_str() : m_resolver->m_key.backing.c_str();
712     if (!path || !*path)
713         throw XMLSecurityException("No key available, unable to attach private key to SSL context.");
714
715     if (!m_resolver->m_keypass.empty()) {
716         SSL_CTX_set_default_passwd_cb(ctx, passwd_callback);
717         SSL_CTX_set_default_passwd_cb_userdata(ctx, const_cast<char*>(m_resolver->m_keypass.c_str()));
718     }
719
720     if (m_resolver->m_key.format == "PEM") {
721         ret=SSL_CTX_use_PrivateKey_file(ctx, path, SSL_FILETYPE_PEM);
722     }
723     else if (m_resolver->m_key.format == "DER") {
724         ret=SSL_CTX_use_RSAPrivateKey_file(ctx, path, SSL_FILETYPE_ASN1);
725     }
726     else if (m_resolver->m_key.format == "PKCS12") {
727         BIO* in=BIO_new(BIO_s_file_internal());
728         if (in && BIO_read_filename(in,path)>0) {
729             PKCS12* p12 = d2i_PKCS12_bio(in, nullptr);
730             if (p12) {
731                 EVP_PKEY* pkey=nullptr;
732                 X509* x=nullptr;
733                 PKCS12_parse(p12, const_cast<char*>(m_resolver->m_keypass.c_str()), &pkey, &x, nullptr);
734                 PKCS12_free(p12);
735                 if (x)
736                     X509_free(x);
737                 if (pkey) {
738                     ret=SSL_CTX_use_PrivateKey(ctx, pkey);
739                     EVP_PKEY_free(pkey);
740                 }
741             }
742         }
743         if (in)
744             BIO_free(in);
745     }
746
747     if (ret!=1) {
748         log_openssl();
749         throw XMLSecurityException("Unable to attach private key to SSL context.");
750     }
751
752     // Attach certs.
753     for (vector<XSECCryptoX509*>::const_iterator i=m_xseccerts.begin(); i!=m_xseccerts.end(); i++) {
754         if (i==m_xseccerts.begin()) {
755             if (SSL_CTX_use_certificate(ctx, static_cast<OpenSSLCryptoX509*>(*i)->getOpenSSLX509()) != 1) {
756                 log_openssl();
757                 throw XMLSecurityException("Unable to attach client certificate to SSL context.");
758             }
759         }
760         else {
761             // When we add certs, they don't get ref counted, so we need to duplicate them.
762             X509* dup = X509_dup(static_cast<OpenSSLCryptoX509*>(*i)->getOpenSSLX509());
763             if (SSL_CTX_add_extra_chain_cert(ctx, dup) != 1) {
764                 X509_free(dup);
765                 log_openssl();
766                 throw XMLSecurityException("Unable to attach CA certificate to SSL context.");
767             }
768         }
769     }
770 }