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