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