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