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