Update copyright.
[shibboleth/cpp-xmltooling.git] / xmltooling / signature / impl / InlineKeyResolver.cpp
1 /*
2  *  Copyright 2001-2007 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  * InlineKeyResolver.cpp
19  * 
20  * Resolves key information directly from recognized KeyInfo structures.
21  */
22
23 #include "internal.h"
24 #include "signature/CachingKeyResolver.h"
25 #include "signature/KeyInfo.h"
26 #include "util/NDC.h"
27 #include "util/Threads.h"
28 #include "util/XMLConstants.h"
29 #include "validation/ValidatorSuite.h"
30
31 #include <log4cpp/Category.hh>
32 #include <xercesc/util/XMLUniDefs.hpp>
33 #include <xsec/dsig/DSIGKeyInfoX509.hpp>
34 #include <xsec/enc/XSECKeyInfoResolverDefault.hpp>
35 #include <xsec/enc/OpenSSL/OpenSSLCryptoX509.hpp>
36 #include <xsec/enc/OpenSSL/OpenSSLCryptoKeyRSA.hpp>
37 #include <xsec/enc/OpenSSL/OpenSSLCryptoKeyDSA.hpp>
38 #include <xsec/enc/XSECCryptoException.hpp>
39 #include <xsec/framework/XSECException.hpp>
40
41 using namespace xmlsignature;
42 using namespace xmltooling;
43 using namespace log4cpp;
44 using namespace std;
45
46 namespace xmlsignature {
47     class XMLTOOL_DLLLOCAL InlineKeyResolver : public CachingKeyResolver
48     {
49     public:
50         InlineKeyResolver(const DOMElement* e);
51         virtual ~InlineKeyResolver();
52
53         XSECCryptoKey* resolveKey(const KeyInfo* keyInfo) const;
54         XSECCryptoKey* resolveKey(DSIGKeyInfoList* keyInfo) const;
55         vector<XSECCryptoX509*>::size_type resolveCertificates(const KeyInfo* keyInfo, ResolvedCertificates& certs) const;
56         vector<XSECCryptoX509*>::size_type resolveCertificates(DSIGKeyInfoList* keyInfo, ResolvedCertificates& certs) const;
57         XSECCryptoX509CRL* resolveCRL(const KeyInfo* keyInfo) const;
58         XSECCryptoX509CRL* resolveCRL(DSIGKeyInfoList* keyInfo) const;
59         
60         void clearCache() {
61             if (m_lock)
62                 m_lock->wrlock();
63             m_cache.clear();
64             if (m_lock)
65                 m_lock->unlock();
66         }
67         
68     private:
69         struct XMLTOOL_DLLLOCAL CacheEntry {
70             CacheEntry() : m_key(NULL), m_crl(NULL) {}
71             ~CacheEntry() {
72                 delete m_key;
73                 for_each(m_certs.begin(),m_certs.end(),xmltooling::cleanup<XSECCryptoX509>());
74                 delete m_crl;
75             }
76             XSECCryptoKey* m_key;
77             vector<XSECCryptoX509*> m_certs;
78             XSECCryptoX509CRL* m_crl;
79         };
80
81         void _resolve(const KeyInfo* keyInfo, CacheEntry& entry) const;
82         XSECCryptoKey* _resolveKey(const KeyInfo* keyInfo) const;
83         vector<XSECCryptoX509*>::size_type _resolveCertificates(const KeyInfo* keyInfo, vector<XSECCryptoX509*>& certs) const;
84         XSECCryptoX509CRL* _resolveCRL(const KeyInfo* keyInfo) const;
85
86         RWLock* m_lock;
87         mutable map<const KeyInfo*,CacheEntry> m_cache;
88     };
89
90     KeyResolver* XMLTOOL_DLLLOCAL InlineKeyResolverFactory(const DOMElement* const & e)
91     {
92         return new InlineKeyResolver(e);
93     }
94 };
95
96 static const XMLCh cache[] = UNICODE_LITERAL_5(c,a,c,h,e);
97
98 InlineKeyResolver::InlineKeyResolver(const DOMElement* e) : m_lock(NULL)
99 {
100     const XMLCh* flag = e ? e->getAttributeNS(NULL,cache) : NULL;
101     if (flag && XMLString::equals(flag,xmlconstants::XML_TRUE) || XMLString::equals(flag,xmlconstants::XML_ONE))
102         m_lock=RWLock::create();
103 }
104
105 InlineKeyResolver::~InlineKeyResolver()
106 {
107     clearCache();
108     delete m_lock;
109 }
110
111 void InlineKeyResolver::_resolve(const KeyInfo* keyInfo, CacheEntry& entry) const
112 {
113     if (_resolveCertificates(keyInfo, entry.m_certs)>0)
114         entry.m_key = entry.m_certs.front()->clonePublicKey();
115     else
116         entry.m_key = _resolveKey(keyInfo);
117     entry.m_crl = _resolveCRL(keyInfo);
118 }
119
120 XSECCryptoKey* InlineKeyResolver::_resolveKey(const KeyInfo* keyInfo) const
121 {
122 #ifdef _DEBUG
123     NDC ndc("_resolveKey");
124 #endif
125     Category& log=Category::getInstance(XMLTOOLING_LOGCAT".KeyResolver");
126
127     if (!keyInfo)
128         return NULL;
129
130     // Check for ds:X509Data
131     const vector<X509Data*>& x509Datas=keyInfo->getX509Datas();
132     for (vector<X509Data*>::const_iterator j=x509Datas.begin(); j!=x509Datas.end(); ++j) {
133         try {
134             const vector<X509Certificate*> x509Certs=const_cast<const X509Data*>(*j)->getX509Certificates();
135             if (!x509Certs.empty()) {
136                 auto_ptr_char x(x509Certs.front()->getValue());
137                 if (!x.get()) {
138                     log.warn("skipping empty ds:X509Certificate");
139                 }
140                 else {
141                     log.debug("resolving ds:X509Certificate");
142                     auto_ptr<XSECCryptoX509> x509(XSECPlatformUtils::g_cryptoProvider->X509());
143                     x509->loadX509Base64Bin(x.get(), strlen(x.get()));
144                     return x509->clonePublicKey();
145                 }
146             }
147         }
148         catch(XSECException& e) {
149             auto_ptr_char temp(e.getMsg());
150             log.error("caught XML-Security exception loading certificate: %s", temp.get());
151         }
152         catch(XSECCryptoException& e) {
153             log.error("caught XML-Security exception loading certificate: %s", e.getMsg());
154         }
155     }
156
157     // Check for ds:KeyValue
158     const vector<KeyValue*>& keyValues = keyInfo->getKeyValues();
159     for (vector<KeyValue*>::const_iterator i=keyValues.begin(); i!=keyValues.end(); ++i) {
160         try {
161             SchemaValidators.validate(*i);    // see if it's a "valid" key
162             RSAKeyValue* rsakv = (*i)->getRSAKeyValue();
163             if (rsakv) {
164                 log.debug("resolving ds:RSAKeyValue");
165                 auto_ptr_char mod(rsakv->getModulus()->getValue());
166                 auto_ptr_char exp(rsakv->getExponent()->getValue());
167                 auto_ptr<XSECCryptoKeyRSA> rsa(XSECPlatformUtils::g_cryptoProvider->keyRSA());
168                 rsa->loadPublicModulusBase64BigNums(mod.get(), strlen(mod.get()));
169                 rsa->loadPublicExponentBase64BigNums(exp.get(), strlen(exp.get()));
170                 return rsa.release();
171             }
172             DSAKeyValue* dsakv = (*i)->getDSAKeyValue();
173             if (dsakv) {
174                 log.debug("resolving ds:DSAKeyValue");
175                 auto_ptr<XSECCryptoKeyDSA> dsa(XSECPlatformUtils::g_cryptoProvider->keyDSA());
176                 auto_ptr_char y(dsakv->getY()->getValue());
177                 dsa->loadYBase64BigNums(y.get(), strlen(y.get()));
178                 if (dsakv->getP()) {
179                     auto_ptr_char p(dsakv->getP()->getValue());
180                     dsa->loadPBase64BigNums(p.get(), strlen(p.get()));
181                 }
182                 if (dsakv->getQ()) {
183                     auto_ptr_char q(dsakv->getQ()->getValue());
184                     dsa->loadQBase64BigNums(q.get(), strlen(q.get()));
185                 }
186                 if (dsakv->getG()) {
187                     auto_ptr_char g(dsakv->getG()->getValue());
188                     dsa->loadGBase64BigNums(g.get(), strlen(g.get()));
189                 }
190                 return dsa.release();
191             }
192         }
193         catch (ValidationException& ex) {
194             log.warn("skipping invalid ds:KeyValue (%s)", ex.what());
195         }
196         catch(XSECException& e) {
197             auto_ptr_char temp(e.getMsg());
198             log.error("caught XML-Security exception loading key: %s", temp.get());
199         }
200         catch(XSECCryptoException& e) {
201             log.error("caught XML-Security exception loading key: %s", e.getMsg());
202         }
203     }
204
205     // Check for RetrievalMethod.
206     const XMLCh* fragID=NULL;
207     const XMLObject* treeRoot=NULL;
208     XSECCryptoKey* remote=NULL;
209     const vector<RetrievalMethod*> methods=keyInfo->getRetrievalMethods();
210     for (vector<RetrievalMethod*>::const_iterator m=methods.begin(); m!=methods.end(); ++m) {
211         if (!XMLString::equals((*m)->getType(),RetrievalMethod::TYPE_X509DATA) &&
212             !XMLString::equals((*m)->getType(),RetrievalMethod::TYPE_RSAKEYVALUE) &&
213             !XMLString::equals((*m)->getType(),RetrievalMethod::TYPE_DSAKEYVALUE))
214             continue;
215         fragID = (*m)->getURI();
216         if (!fragID || *fragID != chPound || !*(fragID+1)) {
217             log.warn("skipping ds:RetrievalMethod with an empty or non-local reference");
218             continue;
219         }
220         if (!treeRoot) {
221             treeRoot = keyInfo;
222             while (treeRoot->getParent())
223                 treeRoot = treeRoot->getParent();
224         }
225         keyInfo = dynamic_cast<const KeyInfo*>(XMLHelper::getXMLObjectById(*treeRoot, fragID+1));
226         if (!keyInfo) {
227             log.warn("skipping ds:RetrievalMethod, local reference did not resolve to a ds:KeyInfo");
228             continue;
229         }
230         remote = _resolveKey(keyInfo);
231         if (remote)
232             return remote;
233     }
234
235     log.warn("unable to resolve key");
236     return NULL;
237 }
238
239 vector<XSECCryptoX509*>::size_type InlineKeyResolver::_resolveCertificates(
240     const KeyInfo* keyInfo, vector<XSECCryptoX509*>& certs
241     ) const
242 {
243 #ifdef _DEBUG
244     NDC ndc("_resolveCertificates");
245 #endif
246     Category& log=Category::getInstance(XMLTOOLING_LOGCAT".KeyResolver");
247
248     if (!keyInfo)
249         return 0;
250
251     // Check for ds:X509Data
252     const vector<X509Data*>& x509Datas=keyInfo->getX509Datas();
253     for (vector<X509Data*>::const_iterator j=x509Datas.begin(); certs.empty() && j!=x509Datas.end(); ++j) {
254         const vector<X509Certificate*> x509Certs=const_cast<const X509Data*>(*j)->getX509Certificates();
255         for (vector<X509Certificate*>::const_iterator k=x509Certs.begin(); k!=x509Certs.end(); ++k) {
256             try {
257                 auto_ptr_char x((*k)->getValue());
258                 if (!x.get()) {
259                     log.warn("skipping empty ds:X509Certificate");
260                 }
261                 else {
262                     log.debug("resolving ds:X509Certificate");
263                     auto_ptr<XSECCryptoX509> x509(XSECPlatformUtils::g_cryptoProvider->X509());
264                     x509->loadX509Base64Bin(x.get(), strlen(x.get()));
265                     certs.push_back(x509.release());
266                 }
267             }
268             catch(XSECException& e) {
269                 auto_ptr_char temp(e.getMsg());
270                 log.error("caught XML-Security exception loading certificate: %s", temp.get());
271             }
272             catch(XSECCryptoException& e) {
273                 log.error("caught XML-Security exception loading certificate: %s", e.getMsg());
274             }
275         }
276     }
277     
278     if (certs.empty()) {
279         // Check for RetrievalMethod.
280         const XMLCh* fragID=NULL;
281         const XMLObject* treeRoot=NULL;
282         const vector<RetrievalMethod*> methods=keyInfo->getRetrievalMethods();
283         for (vector<RetrievalMethod*>::const_iterator m=methods.begin(); certs.empty() && m!=methods.end(); ++m) {
284             if (!XMLString::equals((*m)->getType(),RetrievalMethod::TYPE_X509DATA))
285                 continue;
286             fragID = (*m)->getURI();
287             if (!fragID || *fragID != chPound || !*(fragID+1)) {
288                 log.warn("skipping ds:RetrievalMethod with an empty or non-local reference");
289                 continue;
290             }
291             if (!treeRoot) {
292                 treeRoot = keyInfo;
293                 while (treeRoot->getParent())
294                     treeRoot = treeRoot->getParent();
295             }
296             keyInfo = dynamic_cast<const KeyInfo*>(XMLHelper::getXMLObjectById(*treeRoot, fragID+1));
297             if (!keyInfo) {
298                 log.warn("skipping ds:RetrievalMethod, local reference did not resolve to a ds:KeyInfo");
299                 continue;
300             }
301             _resolveCertificates(keyInfo, certs);
302         }
303     }
304     
305     if (log.isDebugEnabled()) {
306         log.debug("resolved %d certificate%s", certs.size(), certs.size()==1 ? "" : "s");
307     }
308     return certs.size();
309 }
310
311 XSECCryptoX509CRL* InlineKeyResolver::_resolveCRL(const KeyInfo* keyInfo) const
312 {
313 #ifdef _DEBUG
314     NDC ndc("_resolveCRL");
315 #endif
316     Category& log=Category::getInstance(XMLTOOLING_LOGCAT".KeyResolver");
317
318     if (!keyInfo)
319         return NULL;
320
321     // Check for ds:X509Data
322     const vector<X509Data*>& x509Datas=keyInfo->getX509Datas();
323     for (vector<X509Data*>::const_iterator j=x509Datas.begin(); j!=x509Datas.end(); ++j) {
324         const vector<X509CRL*> x509CRLs=const_cast<const X509Data*>(*j)->getX509CRLs();
325         for (vector<X509CRL*>::const_iterator k=x509CRLs.begin(); k!=x509CRLs.end(); ++k) {
326             try {
327                 auto_ptr_char x((*k)->getValue());
328                 if (!x.get()) {
329                     log.warn("skipping empty ds:X509CRL");
330                 }
331                 else {
332                     log.debug("resolving ds:X509CRL");
333                     auto_ptr<XSECCryptoX509CRL> crl(XMLToolingConfig::getConfig().X509CRL());
334                     crl->loadX509CRLBase64Bin(x.get(), strlen(x.get()));
335                     return crl.release();
336                 }
337             }
338             catch(XSECException& e) {
339                 auto_ptr_char temp(e.getMsg());
340                 log.error("caught XML-Security exception loading certificate: %s", temp.get());
341             }
342             catch(XSECCryptoException& e) {
343                 log.error("caught XML-Security exception loading certificate: %s", e.getMsg());
344             }
345         }
346     }
347
348     // Check for RetrievalMethod.
349     const XMLCh* fragID=NULL;
350     const XMLObject* treeRoot=NULL;
351     XSECCryptoX509CRL* remote=NULL;
352     const vector<RetrievalMethod*> methods=keyInfo->getRetrievalMethods();
353     for (vector<RetrievalMethod*>::const_iterator m=methods.begin(); m!=methods.end(); ++m) {
354         if (!XMLString::equals((*m)->getType(),RetrievalMethod::TYPE_X509DATA))
355             continue;
356         fragID = (*m)->getURI();
357         if (!fragID || *fragID != chPound || !*(fragID+1)) {
358             log.warn("skipping ds:RetrievalMethod with an empty or non-local reference");
359             continue;
360         }
361         if (!treeRoot) {
362             treeRoot = keyInfo;
363             while (treeRoot->getParent())
364                 treeRoot = treeRoot->getParent();
365         }
366         keyInfo = dynamic_cast<const KeyInfo*>(XMLHelper::getXMLObjectById(*treeRoot, fragID+1));
367         if (!keyInfo) {
368             log.warn("skipping ds:RetrievalMethod, local reference did not resolve to a ds:KeyInfo");
369             continue;
370         }
371         remote = _resolveCRL(keyInfo);
372         if (remote)
373             return remote;
374     }
375
376     return NULL;
377 }
378
379 XSECCryptoKey* InlineKeyResolver::resolveKey(const KeyInfo* keyInfo) const
380 {
381     // Caching?
382     if (m_lock) {
383         // Get read lock.
384         m_lock->rdlock();
385         map<const KeyInfo*,CacheEntry>::iterator i=m_cache.find(keyInfo);
386         if (i != m_cache.end()) {
387             // Found in cache, so just return the results.
388             SharedLock locker(m_lock,false);
389             return i->second.m_key ? i->second.m_key->clone() : NULL;
390         }
391         else {
392             // Elevate lock.
393             m_lock->unlock();
394             m_lock->wrlock();
395             SharedLock locker(m_lock,false);
396             // Recheck cache.
397             i=m_cache.find(keyInfo);
398             if (i == m_cache.end()) {
399                 i = m_cache.insert(make_pair(keyInfo,CacheEntry())).first;
400                 _resolve(i->first, i->second);
401             }
402             return i->second.m_key ? i->second.m_key->clone() : NULL;
403         }
404     }
405     return _resolveKey(keyInfo);
406 }
407
408 XSECCryptoX509CRL* InlineKeyResolver::resolveCRL(const KeyInfo* keyInfo) const
409 {
410     // Caching?
411     if (m_lock) {
412         // Get read lock.
413         m_lock->rdlock();
414         map<const KeyInfo*,CacheEntry>::iterator i=m_cache.find(keyInfo);
415         if (i != m_cache.end()) {
416             // Found in cache, so just return the results.
417             SharedLock locker(m_lock,false);
418             return i->second.m_crl ? i->second.m_crl->clone() : NULL;
419         }
420         else {
421             // Elevate lock.
422             m_lock->unlock();
423             m_lock->wrlock();
424             SharedLock locker(m_lock,false);
425             // Recheck cache.
426             i=m_cache.find(keyInfo);
427             if (i == m_cache.end()) {
428                 i = m_cache.insert(make_pair(keyInfo,CacheEntry())).first;
429                 _resolve(i->first, i->second);
430             }
431             return i->second.m_crl ? i->second.m_crl->clone() : NULL;
432         }
433     }
434     return _resolveCRL(keyInfo);
435 }
436
437 vector<XSECCryptoX509*>::size_type InlineKeyResolver::resolveCertificates(
438     const KeyInfo* keyInfo, ResolvedCertificates& certs
439     ) const
440 {
441     // Caching?
442     if (m_lock) {
443         // Get read lock.
444         m_lock->rdlock();
445         map<const KeyInfo*,CacheEntry>::iterator i=m_cache.find(keyInfo);
446         if (i != m_cache.end()) {
447             // Found in cache, so just return the results.
448             SharedLock locker(m_lock,false);
449             accessCertificates(certs).assign(i->second.m_certs.begin(), i->second.m_certs.end());
450             accessOwned(certs) = false;
451             return accessCertificates(certs).size();
452         }
453         else {
454             // Elevate lock.
455             m_lock->unlock();
456             m_lock->wrlock();
457             SharedLock locker(m_lock,false);
458             // Recheck cache.
459             i=m_cache.find(keyInfo);
460             if (i == m_cache.end()) {
461                 i = m_cache.insert(make_pair(keyInfo,CacheEntry())).first;
462                 _resolve(i->first, i->second);
463             }
464             accessCertificates(certs).assign(i->second.m_certs.begin(), i->second.m_certs.end());
465             accessOwned(certs) = false;
466             return accessCertificates(certs).size();
467         }
468     }
469     accessOwned(certs) = true;
470     return _resolveCertificates(keyInfo, accessCertificates(certs));
471 }
472
473 XSECCryptoKey* InlineKeyResolver::resolveKey(DSIGKeyInfoList* keyInfo) const
474 {
475 #ifdef _DEBUG
476     NDC ndc("resolveKey");
477 #endif
478
479     if (!keyInfo)
480         return NULL;
481
482     // Default resolver handles RSA/DSAKeyValue and X509Certificate elements.
483     try {
484         XSECKeyInfoResolverDefault def;
485         return def.resolveKey(keyInfo);
486     }
487     catch(XSECException& e) {
488         auto_ptr_char temp(e.getMsg());
489         Category::getInstance(XMLTOOLING_LOGCAT".KeyResolver").error("caught XML-Security exception loading certificate: %s", temp.get());
490     }
491     catch(XSECCryptoException& e) {
492         Category::getInstance(XMLTOOLING_LOGCAT".KeyResolver").error("caught XML-Security exception loading certificate: %s", e.getMsg());
493     }
494     return NULL;
495 }
496
497 vector<XSECCryptoX509*>::size_type InlineKeyResolver::resolveCertificates(
498     DSIGKeyInfoList* keyInfo, ResolvedCertificates& certs
499     ) const
500 {
501     accessCertificates(certs).clear();
502     accessOwned(certs) = false;
503
504     if (!keyInfo)
505         return 0;
506
507         DSIGKeyInfoList::size_type sz = keyInfo->getSize();
508     for (DSIGKeyInfoList::size_type i=0; accessCertificates(certs).empty() && i<sz; ++i) {
509         if (keyInfo->item(i)->getKeyInfoType()==DSIGKeyInfo::KEYINFO_X509) {
510             DSIGKeyInfoX509* x509 = static_cast<DSIGKeyInfoX509*>(keyInfo->item(i));
511             int count = x509->getCertificateListSize();
512             for (int j=0; j<count; ++j) {
513                 accessCertificates(certs).push_back(x509->getCertificateCryptoItem(j));
514             }
515         }
516     }
517     return accessCertificates(certs).size();
518 }
519
520 XSECCryptoX509CRL* InlineKeyResolver::resolveCRL(DSIGKeyInfoList* keyInfo) const
521 {
522 #ifdef _DEBUG
523     NDC ndc("resolveCRL");
524 #endif
525
526     if (!keyInfo)
527         return NULL;
528
529     DSIGKeyInfoList::size_type sz = keyInfo->getSize();
530     for (DSIGKeyInfoList::size_type i=0; i<sz; ++i) {
531         if (keyInfo->item(i)->getKeyInfoType()==DSIGKeyInfo::KEYINFO_X509) {
532             auto_ptr_char buf(static_cast<DSIGKeyInfoX509*>(keyInfo->item(i))->getX509CRL());
533             if (buf.get()) {
534                 try {
535                     auto_ptr<XSECCryptoX509CRL> crlobj(XMLToolingConfig::getConfig().X509CRL());
536                     crlobj->loadX509CRLBase64Bin(buf.get(), strlen(buf.get()));
537                     return crlobj.release();
538                 }
539                 catch(XSECException& e) {
540                     auto_ptr_char temp(e.getMsg());
541                     Category::getInstance(XMLTOOLING_LOGCAT".KeyResolver").error("caught XML-Security exception loading CRL: %s", temp.get());
542                 }
543                 catch(XSECCryptoException& e) {
544                     Category::getInstance(XMLTOOLING_LOGCAT".KeyResolver").error("caught XML-Security exception loading CRL: %s", e.getMsg());
545                 }
546             }
547         }
548     }
549     return NULL;
550 }