2 * The Shibboleth License, Version 1.
4 * University Corporation for Advanced Internet Development, Inc.
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions are met:
11 * Redistributions of source code must retain the above copyright notice, this
12 * list of conditions and the following disclaimer.
14 * Redistributions in binary form must reproduce the above copyright notice,
15 * this list of conditions and the following disclaimer in the documentation
16 * and/or other materials provided with the distribution, if any, must include
17 * the following acknowledgment: "This product includes software developed by
18 * the University Corporation for Advanced Internet Development
19 * <http://www.ucaid.edu>Internet2 Project. Alternately, this acknowledegement
20 * may appear in the software itself, if and wherever such third-party
21 * acknowledgments normally appear.
23 * Neither the name of Shibboleth nor the names of its contributors, nor
24 * Internet2, nor the University Corporation for Advanced Internet Development,
25 * Inc., nor UCAID may be used to endorse or promote products derived from this
26 * software without specific prior written permission. For written permission,
27 * please contact shibboleth@shibboleth.org
29 * Products derived from this software may not be called Shibboleth, Internet2,
30 * UCAID, or the University Corporation for Advanced Internet Development, nor
31 * may Shibboleth appear in their name, without prior written permission of the
32 * University Corporation for Advanced Internet Development.
35 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
36 * AND WITH ALL FAULTS. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
37 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
38 * PARTICULAR PURPOSE, AND NON-INFRINGEMENT ARE DISCLAIMED AND THE ENTIRE RISK
39 * OF SATISFACTORY QUALITY, PERFORMANCE, ACCURACY, AND EFFORT IS WITH LICENSEE.
40 * IN NO EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY
41 * CORPORATION FOR ADVANCED INTERNET DEVELOPMENT, INC. BE LIABLE FOR ANY DIRECT,
42 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
43 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
44 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
45 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
46 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
47 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
50 /* XMLTrust.cpp - a trust implementation that uses an XML file
60 #include <sys/types.h>
63 #include <openssl/err.h>
64 #include <openssl/x509v3.h>
65 #include <openssl/x509_vfy.h>
67 #include <log4cpp/Category.hh>
68 #include <xercesc/framework/URLInputSource.hpp>
69 #include <xercesc/util/regx/RegularExpression.hpp>
70 #include <xsec/enc/XSECCryptoException.hpp>
71 #include <xsec/enc/XSECKeyInfoResolverDefault.hpp>
73 using namespace shibboleth;
75 using namespace log4cpp;
79 class XMLTrustImpl : public ReloadableXMLFileImpl
82 XMLTrustImpl(const char* pathname) : ReloadableXMLFileImpl(pathname), m_wildcard(NULL) { init(); }
83 XMLTrustImpl(const DOMElement* e) : ReloadableXMLFileImpl(e), m_wildcard(NULL) { init(); }
89 KeyAuthority() : m_depth(1) {}
91 X509_STORE* getX509Store();
94 vector<const XMLCh*> m_subjects;
96 vector<X509*> m_certs;
97 vector<X509_CRL*> m_crls;
98 unsigned short m_depth;
101 vector<DSIGKeyInfoList*> m_keybinds;
102 vector<KeyAuthority*> m_keyauths;
103 KeyAuthority* m_wildcard;
105 typedef map<xstring,KeyAuthority*> AuthMap;
106 typedef map<xstring,DSIGKeyInfoList*> BindMap;
112 class XMLTrust : public ITrust, public ReloadableXMLFile
115 XMLTrust(const DOMElement* e);
118 bool validate(void* certEE, const Iterator<void*>& certChain, const IRoleDescriptor* role, bool checkName=true);
119 bool validate(const saml::SAMLSignedObject& token, const IRoleDescriptor* role);
122 virtual ReloadableXMLFileImpl* newImplementation(const char* pathname, bool first=true) const;
123 virtual ReloadableXMLFileImpl* newImplementation(const DOMElement* e, bool first=true) const;
125 vector<KeyInfoResolver*> m_resolvers;
130 IPlugIn* XMLTrustFactory(const DOMElement* e)
132 auto_ptr<XMLTrust> t(new XMLTrust(e));
133 t->getImplementation();
138 ReloadableXMLFileImpl* XMLTrust::newImplementation(const char* pathname, bool first) const
140 return new XMLTrustImpl(pathname);
143 ReloadableXMLFileImpl* XMLTrust::newImplementation(const DOMElement* e, bool first) const
145 return new XMLTrustImpl(e);
148 X509_STORE* XMLTrustImpl::KeyAuthority::getX509Store()
151 NDC ndc("getX509Store");
153 Category& log=Category::getInstance(XMLPROVIDERS_LOGCAT".Trust");
155 // Load the cert vector into a store.
156 X509_STORE* store=X509_STORE_new();
161 X509_STORE_set_flags(store,X509_V_FLAG_CRL_CHECK_ALL);
163 for (vector<X509*>::iterator j=m_certs.begin(); j!=m_certs.end(); j++) {
164 if (!X509_STORE_add_cert(store,*j)) {
166 log.warn("failed to add cert: %s", (*j)->name);
171 for (vector<X509_CRL*>::iterator k=m_crls.begin(); k!=m_crls.end(); k++) {
172 if (!X509_STORE_add_crl(store,*k)) {
174 log.warn("failed to add CRL");
182 XMLTrustImpl::KeyAuthority::~KeyAuthority()
184 for (vector<X509*>::iterator i=m_certs.begin(); i!=m_certs.end(); i++)
186 for (vector<X509_CRL*>::iterator j=m_crls.begin(); j!=m_crls.end(); j++)
190 class KeyInfoNodeFilter : public DOMNodeFilter
193 short acceptNode(const DOMNode* node) const
195 // Our filter just skips any trees not rooted by ds:KeyInfo.
196 if (node->getNodeType()==DOMNode::ELEMENT_NODE) {
197 if (saml::XML::isElementNamed(static_cast<const DOMElement*>(node),saml::XML::XMLSIG_NS,L(KeyInfo)))
198 return FILTER_ACCEPT;
200 return FILTER_REJECT;
204 void XMLTrustImpl::init()
207 saml::NDC ndc("init");
209 Category& log=Category::getInstance(XMLPROVIDERS_LOGCAT".Trust");
212 if (!saml::XML::isElementNamed(m_root,::XML::TRUST_NS,SHIB_L(Trust))) {
213 log.error("Construction requires a valid trust file: (trust:Trust as root element)");
214 throw TrustException("Construction requires a valid trust file: (trust:Trust as root element)");
217 // Loop over the KeyAuthority elements.
218 DOMNodeList* nlist=m_root->getElementsByTagNameNS(::XML::TRUST_NS,SHIB_L(KeyAuthority));
219 for (int i=0; nlist && i<nlist->getLength(); i++) {
220 auto_ptr<KeyAuthority> ka(new KeyAuthority());
222 const DOMElement* e=static_cast<DOMElement*>(nlist->item(i));
223 const XMLCh* depth=e->getAttributeNS(NULL,SHIB_L(VerifyDepth));
225 ka->m_depth=XMLString::parseInt(depth);
227 const DOMElement* k_child=saml::XML::getLastChildElement(e,saml::XML::XMLSIG_NS,L(KeyInfo));
229 log.error("ignoring KeyAuthority element with no ds:KeyInfo");
232 const DOMElement* badkeyname=saml::XML::getFirstChildElement(k_child,saml::XML::XMLSIG_NS,SHIB_L(KeyName));
234 log.error("ignoring KeyAuthority element with embedded ds:KeyName, these must appear only outside of ds:KeyInfo");
238 // Very rudimentary, grab up all the in-band X509Certificate elements, and flatten into one list.
239 DOMNodeList* certlist=k_child->getElementsByTagNameNS(saml::XML::XMLSIG_NS,L(X509Certificate));
240 for (int j=0; certlist && j<certlist->getLength(); j++) {
241 auto_ptr_char blob(certlist->item(j)->getFirstChild()->getNodeValue());
242 X509* x=B64_to_X509(blob.get());
244 ka->m_certs.push_back(x);
246 log.error("unable to create certificate from inline X509Certificate data");
249 // Now look for externally referenced objects.
250 certlist=k_child->getElementsByTagNameNS(saml::XML::XMLSIG_NS,SHIB_L(RetrievalMethod));
251 for (int k=0; certlist && k<certlist->getLength(); k++) {
252 DOMElement* cert=static_cast<DOMElement*>(certlist->item(k));
253 if (!XMLString::compareString(cert->getAttributeNS(NULL,SHIB_L(Type)),::XML::XMLSIG_RETMETHOD_RAWX509)) {
255 auto_ptr_char fname(cert->getAttributeNS(NULL,SHIB_L(URI)));
256 FILE* f=fopen(fname.get(),"r");
261 ka->m_certs.push_back(x);
267 log.error("unable to create certificate from externally referenced file");
271 // Very rudimentary, grab up all the in-band X509CRL elements, and flatten into one list.
272 certlist=k_child->getElementsByTagNameNS(saml::XML::XMLSIG_NS,SHIB_L(X509CRL));
273 for (int r=0; certlist && r<certlist->getLength(); r++) {
274 auto_ptr_char blob(certlist->item(r)->getFirstChild()->getNodeValue());
275 X509_CRL* x=B64_to_CRL(blob.get());
277 ka->m_crls.push_back(x);
279 log.warn("unable to create CRL from inline X509CRL data");
282 KeyAuthority* ka2=ka.release();
283 m_keyauths.push_back(ka2);
285 // Now map the ds:KeyName values to the list of certs.
287 DOMElement* sub=saml::XML::getFirstChildElement(e,saml::XML::XMLSIG_NS,SHIB_L(KeyName));
289 const XMLCh* name=sub->getFirstChild()->getNodeValue();
295 ka2->m_subjects.push_back(name);
298 sub=saml::XML::getNextSiblingElement(sub,saml::XML::XMLSIG_NS,SHIB_L(KeyName));
301 // If no Subjects, this is a catch-all binding.
304 log.warn("found a wildcard KeyAuthority element, make sure this is what you intend");
308 log.warn("found multiple wildcard KeyAuthority elements, ignoring all but the first");
312 // Now traverse the outer ds:KeyInfo elements. Supposedly this cast just works...
314 KeyInfoNodeFilter filter;
315 XSECKeyInfoResolverDefault resolver;
316 DOMTreeWalker* walker=
317 static_cast<DOMDocumentTraversal*>(m_doc)->createTreeWalker(const_cast<DOMElement*>(m_root),DOMNodeFilter::SHOW_ELEMENT,&filter,false);
318 DOMElement* kidom=static_cast<DOMElement*>(walker->firstChild());
321 DSIGKeyInfoList* KIL = new DSIGKeyInfoList(NULL);
322 // We let XMLSec hack through anything it can. This should evolve over time, or we can
323 // plug in our own KeyResolver later...
325 if (!KIL->loadListFromXML(kidom))
326 log.error("skipping ds:KeyInfo element (%d) containing unsupported children",count);
328 catch (XSECCryptoException& xe) {
329 log.error("unable to process ds:KeyInfo element (%d): %s",count,xe.getMsg());
332 // Dry run...can we resolve to a key?
333 XSECCryptoKey* key=resolver.resolveKey(KIL);
335 // So far so good, now look for the name binding(s).
338 for (size_t index=0; index<KIL->getSize(); index++) {
339 DSIGKeyInfo* info=KIL->item(index);
340 const XMLCh* name=info->getKeyName();
343 m_keybinds.push_back(KIL);
351 log.warn("skipping ds:KeyInfo binding (%d) that does not contain a usable key name",count);
356 log.warn("skipping ds:KeyInfo binding (%d) that does not resolve to a key",count);
359 kidom=static_cast<DOMElement*>(walker->nextSibling());
361 walker->release(); // This just cleans up aggressively, but there's no leak if we don't.
363 catch (SAMLException& e) {
364 log.errorStream() << "Error while parsing trust configuration: " << e.what() << CategoryStream::ENDLINE;
365 this->~XMLTrustImpl();
370 log.error("Unexpected error while parsing trust configuration");
371 this->~XMLTrustImpl();
377 XMLTrustImpl::~XMLTrustImpl()
379 for (vector<KeyAuthority*>::iterator i=m_keyauths.begin(); i!=m_keyauths.end(); i++)
381 for (vector<DSIGKeyInfoList*>::iterator j=m_keybinds.begin(); j!=m_keybinds.end(); j++)
385 XMLTrust::XMLTrust(const DOMElement* e) : ReloadableXMLFile(e), m_delegate(NULL)
387 static const XMLCh resolver[] =
388 { chLatin_K, chLatin_e, chLatin_y, chLatin_I, chLatin_n, chLatin_f, chLatin_o,
389 chLatin_R, chLatin_e, chLatin_s, chLatin_o, chLatin_l, chLatin_v, chLatin_e, chLatin_r, chNull
392 static const XMLCh _type[] =
393 { chLatin_t, chLatin_y, chLatin_p, chLatin_e, chNull };
395 Category& log=Category::getInstance(XMLPROVIDERS_LOGCAT".Trust");
397 // Find any KeyResolver plugins.
398 DOMElement* child=saml::XML::getFirstChildElement(e);
400 if (!XMLString::compareString(resolver,child->getLocalName()) && child->hasAttributeNS(NULL,_type)) {
402 auto_ptr_char temp(child->getAttributeNS(NULL,_type));
403 m_resolvers.push_back(KeyInfoResolver::getInstance(temp.get(),child));
405 catch (SAMLException& ex) {
406 log.error("caught SAML exception building KeyInfoResolver plugin: %s",ex.what());
410 log.error("caught unknown exception building KeyInfoResolver plugin");
414 child=saml::XML::getNextSiblingElement(child);
416 m_resolvers.push_back(KeyInfoResolver::getInstance(e));
419 IPlugIn* plugin=SAMLConfig::getConfig().getPlugMgr().newPlugin(
420 "edu.internet2.middleware.shibboleth.common.provider.ShibbolethTrust",e
422 m_delegate=dynamic_cast<ITrust*>(plugin);
425 log.error("plugin was not a trust provider");
426 throw UnsupportedExtensionException("Legacy trust provider requires Shibboleth trust provider in order to function.");
429 catch (SAMLException& ex) {
430 log.error("caught SAML exception building embedded trust provider: %s", ex.what());
435 XMLTrust::~XMLTrust()
438 for (vector<KeyInfoResolver*>::iterator i=m_resolvers.begin(); i!=m_resolvers.end(); i++)
442 extern "C" int error_callback(int ok, X509_STORE_CTX* ctx)
445 Category::getInstance("OpenSSL").error("path validation failure: %s", X509_verify_cert_error_string(ctx->error));
449 bool XMLTrust::validate(void* certEE, const Iterator<void*>& certChain, const IRoleDescriptor* role, bool checkName)
451 // The delegated trust plugin handles path validation with metadata extensions.
452 // We only take over if the legacy format has to kick in.
453 if (m_delegate->validate(certEE,certChain,role,checkName))
457 saml::NDC ndc("validate");
459 Category& log=Category::getInstance(XMLPROVIDERS_LOGCAT".Trust");
463 XMLTrustImpl* impl=dynamic_cast<XMLTrustImpl*>(getImplementation());
465 // Build a list of the names to match. We include any named KeyDescriptors, and the provider ID and its groups.
466 vector<const XMLCh*> names;
467 Iterator<const IKeyDescriptor*> kdlist=role->getKeyDescriptors();
468 while (kdlist.hasNext()) {
469 const IKeyDescriptor* kd=kdlist.next();
470 if (kd->getUse()==IKeyDescriptor::encryption)
472 DSIGKeyInfoList* kilist=kd->getKeyInfo();
473 for (size_t s=0; kilist && s<kilist->getSize(); s++) {
474 const XMLCh* n=kilist->item(s)->getKeyName();
479 names.push_back(role->getEntityDescriptor()->getId());
480 const IEntitiesDescriptor* group=role->getEntityDescriptor()->getEntitiesDescriptor();
482 names.push_back(group->getName());
483 group=group->getEntitiesDescriptor();
486 // Now check each name.
487 XMLTrustImpl::KeyAuthority* kauth=NULL;
488 for (vector<const XMLCh*>::const_iterator name=names.begin(); !kauth && name!=names.end(); name++) {
490 XMLTrustImpl::AuthMap::const_iterator c=impl->m_authMap.find(*name);
491 if (c!=impl->m_authMap.end()) {
493 if (log.isInfoEnabled()) {
494 auto_ptr_char temp(*name);
495 log.info("KeyAuthority match on %s",temp.get());
499 // Without a decent STL, we trade-off the transcoding by doing a linear search.
500 for (vector<XMLTrustImpl::KeyAuthority*>::const_iterator keyauths=impl->m_keyauths.begin(); !kauth && keyauths!=impl->m_keyauths.end(); keyauths++) {
501 for (vector<const XMLCh*>::const_iterator subs=(*keyauths)->m_subjects.begin(); !kauth && subs!=(*keyauths)->m_subjects.end(); subs++) {
502 if (!XMLString::compareString(*name,*subs)) {
504 if (log.isInfoEnabled()) {
505 auto_ptr_char temp(*name);
506 log.info("KeyAuthority match on %s",temp.get());
515 if (impl->m_wildcard) {
516 log.warn("applying wildcard KeyAuthority, use with caution!");
517 kauth=impl->m_wildcard;
521 log.warn("no KeyAuthority found to validate SSL connection, leaving it alone");
526 log.debug("performing certificate path validation...");
528 // If we have a match, use the associated keyauth.
529 X509_STORE* store=kauth->getX509Store();
531 STACK_OF(X509)* untrusted=sk_X509_new(NULL);
533 while (certChain.hasNext())
534 sk_X509_push(untrusted,(X509*)certChain.next());
536 // This contains the state of the validate operation.
539 // AFAICT, EE and untrusted are passed in but not owned by the ctx.
540 #if (OPENSSL_VERSION_NUMBER >= 0x00907000L)
541 if (X509_STORE_CTX_init(&ctx,store,(X509*)certEE,untrusted)!=1) {
543 log.error("unable to initialize X509_STORE_CTX");
544 X509_STORE_free(store);
545 sk_X509_free(untrusted);
550 X509_STORE_CTX_init(&ctx,store,certEE,untrusted);
552 X509_STORE_CTX_set_depth(&ctx,kauth->m_depth+1); // correct yet another OpenSSL/PXIX bug
553 X509_STORE_CTX_set_verify_cb(&ctx,error_callback);
555 int ret=X509_verify_cert(&ctx);
558 X509_STORE_CTX_cleanup(&ctx);
559 X509_STORE_free(store);
562 log.info("successfully validated certificate chain");
576 bool XMLTrust::validate(const saml::SAMLSignedObject& token, const IRoleDescriptor* role)
578 // The delegated trust plugin handles metadata keys and use of metadata extensions.
579 // If it fails to find an inline key in metadata, then it will branch off to the
580 // extended version and verify the token using the certificates inside it. At that
581 // point, control will pass to the other virtual function above and we can handle
582 // legacy KeyAuthority rules that way.
583 if (m_delegate->validate(token,role))
587 saml::NDC ndc("validate");
589 Category& log=Category::getInstance(XMLPROVIDERS_LOGCAT".Trust");
593 XMLTrustImpl* impl=dynamic_cast<XMLTrustImpl*>(getImplementation());
595 // If we actually make it this far, the only case we're handling directly
596 // is an inline key in the old trust file format. Build a list of key names
597 // which will be used to find matching rules.
598 vector<const XMLCh*> names;
600 // Build a list of acceptable names. Transcode the possible key "names" to UTF-8.
601 // For some simple cases, this should handle UTF-8 encoded DNs in certificates.
602 Iterator<const IKeyDescriptor*> kd_i=role->getKeyDescriptors();
603 while (kd_i.hasNext()) {
604 const IKeyDescriptor* kd=kd_i.next();
605 if (kd->getUse()!=IKeyDescriptor::signing)
607 DSIGKeyInfoList* KIL=kd->getKeyInfo();
610 for (size_t s=0; s<KIL->getSize(); s++) {
611 const XMLCh* n=KIL->item(s)->getKeyName();
616 names.push_back(role->getEntityDescriptor()->getId());
618 log.debug("checking for keys in trust file");
619 DSIGKeyInfoList* KIL=NULL;
620 for (vector<const XMLCh*>::const_iterator name=names.begin(); !KIL && name!=names.end(); name++) {
622 XMLTrustImpl::BindMap::const_iterator c=impl->m_bindMap.find(*name);
623 if (c!=impl->m_bindMap.end()) {
625 if (log.isInfoEnabled()) {
626 auto_ptr_char temp(*name);
627 log.info("KeyInfo match on %s",temp.get());
631 // Without a decent STL, we trade-off the transcoding by doing a linear search.
632 for (vector<DSIGKeyInfoList*>::const_iterator keybinds=impl->m_keybinds.begin(); !KIL && keybinds!=impl->m_keybinds.end(); keybinds++) {
633 for (size_t s=0; !KIL && s<(*keybinds)->getSize(); s++) {
634 if (!XMLString::compareString(*name,(*keybinds)->item(s)->getKeyName())) {
636 if (log.isInfoEnabled()) {
637 auto_ptr_char temp(*name);
638 log.info("KeyInfo match on %s",temp.get());
647 // Any inline KeyInfo should ostensibly resolve to a key we can try.
648 Iterator<KeyInfoResolver*> resolvers(m_resolvers);
649 while (resolvers.hasNext()) {
650 XSECCryptoKey* key=((XSECKeyInfoResolver*)*resolvers.next())->resolveKey(KIL);
652 log.debug("resolved key, trying it...");
656 log.info("token verified with KeyInfo, nothing more to verify");
659 catch (SAMLException& e) {
661 log.warn("verification with inline key failed: %s", e.what());
666 log.warn("KeyInfo in trust provider did not resolve to a key");