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