122aff3ff3c1efea63ead2121039d0858fe2460b
[shibboleth/cpp-xmltooling.git] / xmltooling / security / 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 "security/BasicX509Credential.h"
25 #include "security/KeyInfoCredentialContext.h"
26 #include "security/KeyInfoResolver.h"
27 #include "signature/KeyInfo.h"
28 #include "signature/Signature.h"
29 #include "util/NDC.h"
30 #include "util/Threads.h"
31 #include "util/XMLConstants.h"
32 #include "validation/ValidatorSuite.h"
33
34 #include <log4cpp/Category.hh>
35 #include <xercesc/util/XMLUniDefs.hpp>
36 #include <xsec/dsig/DSIGKeyInfoX509.hpp>
37 #include <xsec/enc/XSECKeyInfoResolverDefault.hpp>
38 #include <xsec/enc/OpenSSL/OpenSSLCryptoX509.hpp>
39 #include <xsec/enc/OpenSSL/OpenSSLCryptoKeyRSA.hpp>
40 #include <xsec/enc/OpenSSL/OpenSSLCryptoKeyDSA.hpp>
41 #include <xsec/enc/XSECCryptoException.hpp>
42 #include <xsec/framework/XSECException.hpp>
43
44 using namespace xmlsignature;
45 using namespace xmltooling;
46 using namespace log4cpp;
47 using namespace std;
48
49 namespace xmltooling {
50
51     class XMLTOOL_DLLLOCAL InlineCredential : public BasicX509Credential
52     {
53     public:
54         InlineCredential(const KeyInfo* keyInfo=NULL) : BasicX509Credential(keyInfo!=NULL), m_credctx(new KeyInfoCredentialContext(keyInfo)) {
55         }
56         InlineCredential(DSIGKeyInfoList* keyInfo) : BasicX509Credential(false), m_credctx(new KeyInfoCredentialContext(keyInfo)) {
57         }
58         InlineCredential(KeyInfoCredentialContext* context) : BasicX509Credential(context->getKeyInfo()!=NULL), m_credctx(NULL) {
59         }
60         virtual ~InlineCredential() {
61             delete m_credctx;
62         }
63
64         XSECCryptoKey* getPrivateKey() const {
65             return NULL;
66         }
67
68         const KeyInfo* getKeyInfo(bool compact=false) const {
69             return m_credctx->getKeyInfo();
70         }
71         
72         const CredentialContext* getCredentialContext() const {
73             return m_credctx;
74         }
75
76         void setCredentialContext(KeyInfoCredentialContext* context) {
77             m_credctx = context;
78         }
79
80         void resolve(const KeyInfo* keyInfo, int types=0);
81         void resolve(DSIGKeyInfoList* keyInfo, int types=0);
82
83     private:
84         bool resolveCerts(const KeyInfo* keyInfo);
85         bool resolveKey(const KeyInfo* keyInfo);
86         bool resolveCRL(const KeyInfo* keyInfo);
87
88         KeyInfoCredentialContext* m_credctx;
89     };
90
91     class XMLTOOL_DLLLOCAL InlineKeyResolver : public KeyInfoResolver
92     {
93     public:
94         InlineKeyResolver() {}
95         virtual ~InlineKeyResolver() {}
96
97         Credential* resolve(const KeyInfo* keyInfo, int types=0) const {
98             if (!keyInfo)
99                 return NULL;
100             if (types == 0)
101                 types = Credential::RESOLVE_KEYS|X509Credential::RESOLVE_CERTS|X509Credential::RESOLVE_CRLS;
102             auto_ptr<InlineCredential> credential(new InlineCredential(keyInfo));
103             credential->resolve(keyInfo, types);
104             return credential.release();
105         }
106         Credential* resolve(DSIGKeyInfoList* keyInfo, int types=0) const {
107             if (!keyInfo)
108                 return NULL;
109             if (types == 0)
110                 types = Credential::RESOLVE_KEYS|X509Credential::RESOLVE_CERTS|X509Credential::RESOLVE_CRLS;
111             auto_ptr<InlineCredential> credential(new InlineCredential(keyInfo));
112             credential->resolve(keyInfo, types);
113             return credential.release();
114         }
115         Credential* resolve(KeyInfoCredentialContext* context, int types=0) const {
116             if (!context)
117                 return NULL;
118             if (types == 0)
119                 types = Credential::RESOLVE_KEYS|X509Credential::RESOLVE_CERTS|X509Credential::RESOLVE_CRLS;
120             auto_ptr<InlineCredential> credential(new InlineCredential(context));
121             if (context->getKeyInfo())
122                 credential->resolve(context->getKeyInfo(), types);
123             else if (context->getNativeKeyInfo())
124                 credential->resolve(context->getNativeKeyInfo(), types);
125             credential->setCredentialContext(context);
126             return credential.release();
127         }
128     };
129
130     KeyInfoResolver* XMLTOOL_DLLLOCAL InlineKeyInfoResolverFactory(const DOMElement* const & e)
131     {
132         return new InlineKeyResolver();
133     }
134 };
135
136 void InlineCredential::resolve(const KeyInfo* keyInfo, int types)
137 {
138 #ifdef _DEBUG
139     NDC ndc("resolve");
140 #endif
141
142     if (types & X509Credential::RESOLVE_CERTS)
143         resolveCerts(keyInfo);
144     
145     if (types & Credential::RESOLVE_KEYS) {
146         if (types & X509Credential::RESOLVE_CERTS) {
147             // If we have a cert, just use it.
148             if (!m_xseccerts.empty())
149                 m_key = m_xseccerts.front()->clonePublicKey();
150         }
151         // Otherwise try directly for a key and then go for certs if none is found.
152         else if (!resolveKey(keyInfo) && resolveCerts(keyInfo)) {
153             m_key = m_xseccerts.front()->clonePublicKey();
154         }
155     }
156
157     if (types & X509Credential::RESOLVE_CRLS)
158         resolveCRL(keyInfo);
159
160     keyInfo->extractNames(m_keyNames);
161 }
162
163 bool InlineCredential::resolveKey(const KeyInfo* keyInfo)
164 {
165     Category& log = Category::getInstance(XMLTOOLING_LOGCAT".KeyInfoResolver");
166
167     // Check for ds:KeyValue
168     const vector<KeyValue*>& keyValues = keyInfo->getKeyValues();
169     for (vector<KeyValue*>::const_iterator i=keyValues.begin(); i!=keyValues.end(); ++i) {
170         try {
171             SchemaValidators.validate(*i);    // see if it's a "valid" key
172             RSAKeyValue* rsakv = (*i)->getRSAKeyValue();
173             if (rsakv) {
174                 log.debug("resolving ds:RSAKeyValue");
175                 auto_ptr_char mod(rsakv->getModulus()->getValue());
176                 auto_ptr_char exp(rsakv->getExponent()->getValue());
177                 auto_ptr<XSECCryptoKeyRSA> rsa(XSECPlatformUtils::g_cryptoProvider->keyRSA());
178                 rsa->loadPublicModulusBase64BigNums(mod.get(), strlen(mod.get()));
179                 rsa->loadPublicExponentBase64BigNums(exp.get(), strlen(exp.get()));
180                 m_key = rsa.release();
181                 return true;
182             }
183             DSAKeyValue* dsakv = (*i)->getDSAKeyValue();
184             if (dsakv) {
185                 log.debug("resolving ds:DSAKeyValue");
186                 auto_ptr<XSECCryptoKeyDSA> dsa(XSECPlatformUtils::g_cryptoProvider->keyDSA());
187                 auto_ptr_char y(dsakv->getY()->getValue());
188                 dsa->loadYBase64BigNums(y.get(), strlen(y.get()));
189                 if (dsakv->getP()) {
190                     auto_ptr_char p(dsakv->getP()->getValue());
191                     dsa->loadPBase64BigNums(p.get(), strlen(p.get()));
192                 }
193                 if (dsakv->getQ()) {
194                     auto_ptr_char q(dsakv->getQ()->getValue());
195                     dsa->loadQBase64BigNums(q.get(), strlen(q.get()));
196                 }
197                 if (dsakv->getG()) {
198                     auto_ptr_char g(dsakv->getG()->getValue());
199                     dsa->loadGBase64BigNums(g.get(), strlen(g.get()));
200                 }
201                 m_key = dsa.release();
202                 return true;
203             }
204         }
205         catch (ValidationException& ex) {
206             log.warn("skipping invalid ds:KeyValue (%s)", ex.what());
207         }
208         catch(XSECException& e) {
209             auto_ptr_char temp(e.getMsg());
210             log.error("caught XML-Security exception loading key: %s", temp.get());
211         }
212         catch(XSECCryptoException& e) {
213             log.error("caught XML-Security exception loading key: %s", e.getMsg());
214         }
215     }
216
217     // Check for RetrievalMethod.
218     const XMLCh* fragID=NULL;
219     const XMLObject* treeRoot=NULL;
220     const vector<RetrievalMethod*>& methods=keyInfo->getRetrievalMethods();
221     for (vector<RetrievalMethod*>::const_iterator m=methods.begin(); m!=methods.end(); ++m) {
222         if (!XMLString::equals((*m)->getType(),RetrievalMethod::TYPE_RSAKEYVALUE) &&
223             !XMLString::equals((*m)->getType(),RetrievalMethod::TYPE_DSAKEYVALUE))
224             continue;
225         fragID = (*m)->getURI();
226         if (!fragID || *fragID != chPound || !*(fragID+1)) {
227             log.warn("skipping ds:RetrievalMethod with an empty or non-local reference");
228             continue;
229         }
230         if (!treeRoot) {
231             treeRoot = keyInfo;
232             while (treeRoot->getParent())
233                 treeRoot = treeRoot->getParent();
234         }
235         keyInfo = dynamic_cast<const KeyInfo*>(XMLHelper::getXMLObjectById(*treeRoot, fragID+1));
236         if (!keyInfo) {
237             log.warn("skipping ds:RetrievalMethod, local reference did not resolve to a ds:KeyInfo");
238             continue;
239         }
240         if (resolveKey(keyInfo))
241             return true;
242     }
243     return false;
244 }
245
246 bool InlineCredential::resolveCerts(const KeyInfo* keyInfo)
247 {
248     Category& log = Category::getInstance(XMLTOOLING_LOGCAT".KeyInfoResolver");
249
250     // Check for ds:X509Data
251     const vector<X509Data*>& x509Datas=keyInfo->getX509Datas();
252     for (vector<X509Data*>::const_iterator j=x509Datas.begin(); m_xseccerts.empty() && j!=x509Datas.end(); ++j) {
253         const vector<X509Certificate*> x509Certs=const_cast<const X509Data*>(*j)->getX509Certificates();
254         for (vector<X509Certificate*>::const_iterator k=x509Certs.begin(); k!=x509Certs.end(); ++k) {
255             try {
256                 auto_ptr_char x((*k)->getValue());
257                 if (!x.get()) {
258                     log.warn("skipping empty ds:X509Certificate");
259                 }
260                 else {
261                     log.debug("resolving ds:X509Certificate");
262                     auto_ptr<XSECCryptoX509> x509(XSECPlatformUtils::g_cryptoProvider->X509());
263                     x509->loadX509Base64Bin(x.get(), strlen(x.get()));
264                     m_xseccerts.push_back(x509.release());
265                 }
266             }
267             catch(XSECException& e) {
268                 auto_ptr_char temp(e.getMsg());
269                 log.error("caught XML-Security exception loading certificate: %s", temp.get());
270             }
271             catch(XSECCryptoException& e) {
272                 log.error("caught XML-Security exception loading certificate: %s", e.getMsg());
273             }
274         }
275     }
276
277     if (m_xseccerts.empty()) {
278         // Check for RetrievalMethod.
279         const XMLCh* fragID=NULL;
280         const XMLObject* treeRoot=NULL;
281         const vector<RetrievalMethod*> methods=keyInfo->getRetrievalMethods();
282         for (vector<RetrievalMethod*>::const_iterator m=methods.begin(); m!=methods.end(); ++m) {
283             if (!XMLString::equals((*m)->getType(),RetrievalMethod::TYPE_X509DATA))
284                 continue;
285             fragID = (*m)->getURI();
286             if (!fragID || *fragID != chPound || !*(fragID+1)) {
287                 log.warn("skipping ds:RetrievalMethod with an empty or non-local reference");
288                 continue;
289             }
290             if (!treeRoot) {
291                 treeRoot = keyInfo;
292                 while (treeRoot->getParent())
293                     treeRoot = treeRoot->getParent();
294             }
295             keyInfo = dynamic_cast<const KeyInfo*>(XMLHelper::getXMLObjectById(*treeRoot, fragID+1));
296             if (!keyInfo) {
297                 log.warn("skipping ds:RetrievalMethod, local reference did not resolve to a ds:KeyInfo");
298                 continue;
299             }
300             if (resolveCerts(keyInfo))
301                 return true;
302         }
303         return false;
304     }
305     
306     log.debug("resolved %d certificate(s)", m_xseccerts.size());
307     return !m_xseccerts.empty();
308 }
309
310 bool InlineCredential::resolveCRL(const KeyInfo* keyInfo)
311 {
312     Category& log = Category::getInstance(XMLTOOLING_LOGCAT".KeyInfoResolver");
313
314     // Check for ds:X509Data
315     const vector<X509Data*>& x509Datas=keyInfo->getX509Datas();
316     for (vector<X509Data*>::const_iterator j=x509Datas.begin(); j!=x509Datas.end(); ++j) {
317         const vector<X509CRL*> x509CRLs=const_cast<const X509Data*>(*j)->getX509CRLs();
318         for (vector<X509CRL*>::const_iterator k=x509CRLs.begin(); k!=x509CRLs.end(); ++k) {
319             try {
320                 auto_ptr_char x((*k)->getValue());
321                 if (!x.get()) {
322                     log.warn("skipping empty ds:X509CRL");
323                 }
324                 else {
325                     log.debug("resolving ds:X509CRL");
326                     auto_ptr<XSECCryptoX509CRL> crl(XMLToolingConfig::getConfig().X509CRL());
327                     crl->loadX509CRLBase64Bin(x.get(), strlen(x.get()));
328                     m_crl = crl.release();
329                     return true;
330                 }
331             }
332             catch(XSECException& e) {
333                 auto_ptr_char temp(e.getMsg());
334                 log.error("caught XML-Security exception loading certificate: %s", temp.get());
335             }
336             catch(XSECCryptoException& e) {
337                 log.error("caught XML-Security exception loading certificate: %s", e.getMsg());
338             }
339         }
340     }
341
342     // Check for RetrievalMethod.
343     const XMLCh* fragID=NULL;
344     const XMLObject* treeRoot=NULL;
345     const vector<RetrievalMethod*> methods=keyInfo->getRetrievalMethods();
346     for (vector<RetrievalMethod*>::const_iterator m=methods.begin(); m!=methods.end(); ++m) {
347         if (!XMLString::equals((*m)->getType(),RetrievalMethod::TYPE_X509DATA))
348             continue;
349         fragID = (*m)->getURI();
350         if (!fragID || *fragID != chPound || !*(fragID+1)) {
351             log.warn("skipping ds:RetrievalMethod with an empty or non-local reference");
352             continue;
353         }
354         if (!treeRoot) {
355             treeRoot = keyInfo;
356             while (treeRoot->getParent())
357                 treeRoot = treeRoot->getParent();
358         }
359         keyInfo = dynamic_cast<const KeyInfo*>(XMLHelper::getXMLObjectById(*treeRoot, fragID+1));
360         if (!keyInfo) {
361             log.warn("skipping ds:RetrievalMethod, local reference did not resolve to a ds:KeyInfo");
362             continue;
363         }
364         if (resolveCRL(keyInfo))
365             return true;
366     }
367
368     return false;
369 }
370
371 void InlineCredential::resolve(DSIGKeyInfoList* keyInfo, int types)
372 {
373 #ifdef _DEBUG
374     NDC ndc("resolve");
375 #endif
376
377     if (types & Credential::RESOLVE_KEYS) {
378         // Default resolver handles RSA/DSAKeyValue and X509Certificate elements.
379         try {
380             XSECKeyInfoResolverDefault def;
381             m_key = def.resolveKey(keyInfo);
382         }
383         catch(XSECException& e) {
384             auto_ptr_char temp(e.getMsg());
385             Category::getInstance(XMLTOOLING_LOGCAT".KeyResolver").error("caught XML-Security exception loading certificate: %s", temp.get());
386         }
387         catch(XSECCryptoException& e) {
388             Category::getInstance(XMLTOOLING_LOGCAT".KeyResolver").error("caught XML-Security exception loading certificate: %s", e.getMsg());
389         }
390     }
391
392         DSIGKeyInfoList::size_type sz = keyInfo->getSize();
393
394     if (types & X509Credential::RESOLVE_CERTS) {
395         for (DSIGKeyInfoList::size_type i=0; i<sz; ++i) {
396             if (keyInfo->item(i)->getKeyInfoType()==DSIGKeyInfo::KEYINFO_X509) {
397                 DSIGKeyInfoX509* x509 = static_cast<DSIGKeyInfoX509*>(keyInfo->item(i));
398                 int count = x509->getCertificateListSize();
399                 if (count) {
400                     for (int j=0; j<count; ++j)
401                         m_xseccerts.push_back(x509->getCertificateCryptoItem(j));
402                     break;
403                 }
404             }
405         }
406     }
407
408     if (types & X509Credential::RESOLVE_CRLS) {
409         for (DSIGKeyInfoList::size_type i=0; i<sz; ++i) {
410             if (keyInfo->item(i)->getKeyInfoType()==DSIGKeyInfo::KEYINFO_X509) {
411                 auto_ptr_char buf(static_cast<DSIGKeyInfoX509*>(keyInfo->item(i))->getX509CRL());
412                 if (buf.get()) {
413                     try {
414                         auto_ptr<XSECCryptoX509CRL> crlobj(XMLToolingConfig::getConfig().X509CRL());
415                         crlobj->loadX509CRLBase64Bin(buf.get(), strlen(buf.get()));
416                         m_crl = crlobj.release();
417                         break;
418                     }
419                     catch(XSECException& e) {
420                         auto_ptr_char temp(e.getMsg());
421                         Category::getInstance(XMLTOOLING_LOGCAT".KeyResolver").error("caught XML-Security exception loading CRL: %s", temp.get());
422                     }
423                     catch(XSECCryptoException& e) {
424                         Category::getInstance(XMLTOOLING_LOGCAT".KeyResolver").error("caught XML-Security exception loading CRL: %s", e.getMsg());
425                     }
426                 }
427             }
428         }
429     }
430
431     Signature::extractNames(keyInfo, m_keyNames);
432 }