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.h - a trust implementation that uses an XML file
60 #include <sys/types.h>
63 #include <openssl/err.h>
64 #include <openssl/x509_vfy.h>
66 #include <log4cpp/Category.hh>
67 #include <xercesc/framework/URLInputSource.hpp>
68 #include <xercesc/util/regx/RegularExpression.hpp>
69 #include <xsec/enc/OpenSSL/OpenSSLCryptoX509.hpp>
71 using namespace shibboleth;
73 using namespace log4cpp;
76 int verify_callback(int ok, X509_STORE_CTX* store)
79 Category::getInstance("OpenSSL").error(X509_verify_cert_error_string(store->error));
83 class shibboleth::XMLTrustImpl
86 XMLTrustImpl(const char* pathname);
91 KeyAuthority() : m_store(NULL), m_depth(0), m_type(authority) {}
93 X509_STORE* getX509Store(bool cached=true);
95 vector<XSECCryptoX509*> m_certs;
96 vector<X509*> m_nativecerts;
98 unsigned short m_depth;
99 enum {authority, entity} m_type;
102 vector<KeyAuthority*> m_keyauths;
103 typedef map<pair<const XMLCh*,bool>,KeyAuthority*> BindingMap;
104 BindingMap m_bindings;
109 X509_STORE* XMLTrustImpl::KeyAuthority::getX509Store(bool cached)
111 // We cache them once they're built...
112 if (m_store && cached)
115 NDC ndc("getX509Store");
116 Category& log=Category::getInstance(SHIB_LOGCAT".XMLTrust");
118 // Do we need to convert to native cert format?
119 if (m_nativecerts.empty())
121 for (vector<XSECCryptoX509*>::const_iterator i=m_certs.begin(); i!=m_certs.end(); i++)
123 X509* x509=B64_to_X509((*i)->getDEREncodingSB().rawCharBuffer());
126 log.warn("failed to parse X509 buffer: %s", (*i)->getDEREncodingSB().rawCharBuffer());
129 m_nativecerts.push_back(x509);
133 // Load the cert vector into a store.
134 X509_STORE* store=NULL;
135 if (!(store=X509_STORE_new()))
141 X509_STORE_set_verify_cb_func(store,verify_callback);
143 for (vector<X509*>::iterator j=m_nativecerts.begin(); j!=m_nativecerts.end(); j++)
145 if (!X509_STORE_add_cert(store,X509_dup(*j)))
148 log.warn("failed to add cert: %s", (*j)->name);
158 XMLTrustImpl::KeyAuthority::~KeyAuthority()
161 X509_STORE_free(m_store);
162 for (vector<X509*>::iterator i=m_nativecerts.begin(); i!=m_nativecerts.end(); i++)
164 for (vector<XSECCryptoX509*>::iterator j=m_certs.begin(); j!=m_certs.end(); j++)
168 XMLTrustImpl::XMLTrustImpl(const char* pathname) : m_doc(NULL)
170 NDC ndc("XMLTrustImpl");
171 Category& log=Category::getInstance(SHIB_LOGCAT".XMLTrustImpl");
176 static XMLCh base[]={chLatin_f, chLatin_i, chLatin_l, chLatin_e, chColon, chForwardSlash, chForwardSlash, chForwardSlash, chNull};
177 URLInputSource src(base,pathname);
178 Wrapper4InputSource dsrc(&src,false);
181 log.infoStream() << "Loaded and parsed trust file (" << pathname << ")" << CategoryStream::ENDLINE;
183 DOMElement* e = m_doc->getDocumentElement();
184 if (XMLString::compareString(XML::SHIB_NS,e->getNamespaceURI()) ||
185 XMLString::compareString(SHIB_L(Trust),e->getLocalName()))
187 log.error("Construction requires a valid trust file: (shib:Trust as root element)");
188 throw MetadataException("Construction requires a valid trust file: (shib:Trust as root element)");
191 // Loop over the KeyAuthority elements.
192 DOMNodeList* nlist=e->getElementsByTagNameNS(XML::SHIB_NS,SHIB_L(KeyAuthority));
193 for (int i=0; nlist && i<nlist->getLength(); i++)
195 KeyAuthority* ka=new KeyAuthority();
196 m_keyauths.push_back(ka);
198 const XMLCh* depth=static_cast<DOMElement*>(nlist->item(i))->getAttributeNS(NULL,SHIB_L(VerifyDepth));
200 ka->m_depth=XMLString::parseInt(depth);
202 const XMLCh* type=static_cast<DOMElement*>(nlist->item(i))->getAttributeNS(NULL,SHIB_L(Type));
203 if (type && *type==chLatin_e)
204 ka->m_type=KeyAuthority::entity;
206 // Very rudimentary, grab up all the X509Certificate elements, and flatten into one list.
207 DOMNodeList* certlist=static_cast<DOMElement*>(nlist->item(i))->getElementsByTagNameNS(
208 saml::XML::XMLSIG_NS,L(X509Certificate)
210 for (int j=0; certlist && j<certlist->getLength(); j++)
212 auto_ptr<char> blob(XMLString::transcode(certlist->item(j)->getFirstChild()->getNodeValue()));
213 XSECCryptoX509* cert=new OpenSSLCryptoX509();
214 cert->loadX509Base64Bin(blob.get(),strlen(blob.get()));
215 ka->m_certs.push_back(cert);
218 // Now map the subjects to the list of certs.
219 DOMNodeList* subs=static_cast<DOMElement*>(nlist->item(i))->getElementsByTagNameNS(XML::SHIB_NS,L(Subject));
221 while (subs && k<subs->getLength())
223 const XMLCh* name=subs->item(k)->getFirstChild()->getNodeValue();
226 static const XMLCh one[]={ chDigit_1, chNull };
227 static const XMLCh tru[]={ chLatin_t, chLatin_r, chLatin_u, chLatin_e, chNull };
229 static_cast<DOMElement*>(subs->item(k))->getAttributeNS(NULL,SHIB_L(regexp));
230 bool flag=(!XMLString::compareString(regexp,one) || !XMLString::compareString(regexp,tru));
231 m_bindings[pair<const XMLCh*,bool>(name,flag)]=ka;
235 // If no Subjects, this is a catch-all binding.
237 m_bindings[pair<const XMLCh*,bool>(NULL,false)]=ka;
240 catch (SAMLException& e)
242 log.errorStream() << "XML error while parsing trust configuration: " << e.what() << CategoryStream::ENDLINE;
243 for (vector<KeyAuthority*>::iterator i=m_keyauths.begin(); i!=m_keyauths.end(); i++)
251 log.error("Unexpected error while parsing trust configuration");
252 for (vector<KeyAuthority*>::iterator i=m_keyauths.begin(); i!=m_keyauths.end(); i++)
260 XMLTrustImpl::~XMLTrustImpl()
262 for (vector<KeyAuthority*>::iterator i=m_keyauths.begin(); i!=m_keyauths.end(); i++)
268 XMLTrust::XMLTrust(const char* pathname) : m_filestamp(0), m_source(pathname), m_impl(NULL)
271 struct _stat stat_buf;
272 if (_stat(pathname, &stat_buf) == 0)
274 struct stat stat_buf;
275 if (stat(pathname, &stat_buf) == 0)
277 m_filestamp=stat_buf.st_mtime;
278 m_impl=new XMLTrustImpl(pathname);
279 m_lock=RWLock::create();
282 XMLTrust::~XMLTrust()
288 void XMLTrust::lock()
292 // Check if we need to refresh.
294 struct _stat stat_buf;
295 if (_stat(m_source.c_str(), &stat_buf) == 0)
297 struct stat stat_buf;
298 if (stat(m_source.c_str(), &stat_buf) == 0)
301 if (m_filestamp>0 && m_filestamp<stat_buf.st_mtime)
303 // Elevate lock and recheck.
306 if (m_filestamp>0 && m_filestamp<stat_buf.st_mtime)
310 XMLTrustImpl* new_mapper=new XMLTrustImpl(m_source.c_str());
313 m_filestamp=stat_buf.st_mtime;
316 catch(SAMLException& e)
319 saml::NDC ndc("lock");
320 Category::getInstance(SHIB_LOGCAT".XMLTrust").error("failed to reload trust metadata, sticking with what we have: %s", e.what());
325 saml::NDC ndc("lock");
326 Category::getInstance(SHIB_LOGCAT".XMLTrust").error("caught an unknown exception, sticking with what we have");
338 void XMLTrust::unlock()
343 Iterator<XSECCryptoX509*> XMLTrust::getCertificates(const XMLCh* subject) const
345 // Find the first matching entity binding.
346 for (XMLTrustImpl::BindingMap::const_iterator i=m_impl->m_bindings.begin(); i!=m_impl->m_bindings.end(); i++)
348 if (i->second->m_type!=XMLTrustImpl::KeyAuthority::entity)
351 if (i->first.first==NULL) // catch-all entry
353 return i->second->m_certs;
355 else if (i->first.second) // regexp
359 RegularExpression re(i->first.first);
360 if (re.matches(subject))
361 return i->second->m_certs;
363 catch (XMLException& ex)
365 auto_ptr<char> tmp(XMLString::transcode(ex.getMessage()));
366 NDC ndc("getCertificates");
367 Category& log=Category::getInstance(SHIB_LOGCAT".XMLTrust");
368 log.errorStream() << "caught exception while parsing regular expression: " << tmp.get()
369 << CategoryStream::ENDLINE;
372 else if (!XMLString::compareString(subject,i->first.first)) // exact match
374 return i->second->m_certs;
377 return EMPTY(XSECCryptoX509*);
380 bool XMLTrust::attach(const ISite* site, SSL_CTX* ctx) const
384 // Use the matching bindings.
385 for (XMLTrustImpl::BindingMap::const_iterator i=m_impl->m_bindings.begin(); i!=m_impl->m_bindings.end(); i++)
387 if (i->second->m_type!=XMLTrustImpl::KeyAuthority::authority)
391 if (i->first.first==NULL) // catch-all entry
395 else if (i->first.second) // regexp
399 RegularExpression re(i->first.first);
400 if (re.matches(site->getName()))
404 Iterator<const XMLCh*> groups=site->getGroups();
405 while (!match && groups.hasNext())
406 if (re.matches(groups.next()))
410 catch (XMLException& ex)
412 auto_ptr<char> tmp(XMLString::transcode(ex.getMessage()));
413 Category& log=Category::getInstance(SHIB_LOGCAT".XMLTrust");
414 log.errorStream() << "caught exception while parsing regular expression: " << tmp.get()
415 << CategoryStream::ENDLINE;
418 else if (!XMLString::compareString(site->getName(),i->first.first)) // exact match
424 Iterator<const XMLCh*> groups=site->getGroups();
425 while (!match && groups.hasNext())
426 if (!XMLString::compareString(i->first.first,groups.next()))
430 // If we have a match, use the associated keyauth and load it into the context's store.
433 X509_STORE* store=i->second->getX509Store(false);
436 SSL_CTX_set_cert_store(ctx,store);
437 SSL_CTX_set_verify_depth(ctx,i->second->m_depth);
446 bool XMLTrust::validate(const ISite* site, Iterator<XSECCryptoX509*> certs) const
448 vector<const XMLCh*> temp;
449 while (certs.hasNext())
450 temp.push_back(certs.next()->getDEREncodingSB().sbStrToXMLCh());
451 return validate(site,temp);
454 bool XMLTrust::validate(const ISite* site, Iterator<const XMLCh*> certs) const
458 STACK_OF(X509)* chain=sk_X509_new_null();
459 while (certs.hasNext())
461 auto_ptr<char> temp(XMLString::transcode(certs.next()));
462 X509* x=B64_to_X509(temp.get());
465 sk_X509_pop_free(chain,X509_free);
468 sk_X509_push(chain,x);
471 // Use the matching bindings.
472 for (XMLTrustImpl::BindingMap::const_iterator i=m_impl->m_bindings.begin(); i!=m_impl->m_bindings.end(); i++)
474 if (i->second->m_type!=XMLTrustImpl::KeyAuthority::authority)
478 if (i->first.first==NULL) // catch-all entry
482 else if (i->first.second) // regexp
486 RegularExpression re(i->first.first);
487 if (re.matches(site->getName()))
491 Iterator<const XMLCh*> groups=site->getGroups();
492 while (!match && groups.hasNext())
493 if (re.matches(groups.next()))
497 catch (XMLException& ex)
499 auto_ptr<char> tmp(XMLString::transcode(ex.getMessage()));
500 Category& log=Category::getInstance(SHIB_LOGCAT".XMLTrust");
501 log.errorStream() << "caught exception while parsing regular expression: " << tmp.get()
502 << CategoryStream::ENDLINE;
505 else if (!XMLString::compareString(site->getName(),i->first.first)) // exact match
511 Iterator<const XMLCh*> groups=site->getGroups();
512 while (!match && groups.hasNext())
513 if (!XMLString::compareString(i->first.first,groups.next()))
517 // If we have a match, use the associated keyauth and do a verify against the store.
520 X509_STORE* store=i->second->getX509Store();
523 X509_STORE_CTX* ctx=X509_STORE_CTX_new();
530 #if (OPENSSL_VERSION_NUMBER > 0x009070000L)
531 if (X509_STORE_CTX_init(ctx,store,sk_X509_value(chain,0),chain)!=1)
534 sk_X509_pop_free(chain,X509_free);
538 X509_STORE_CTX_init(ctx,store,sk_X509_value(chain,0),chain);
540 if (i->second->m_depth)
541 X509_STORE_CTX_set_depth(ctx,i->second->m_depth);
542 if (X509_verify_cert(ctx)==1)
544 sk_X509_pop_free(chain,X509_free);
545 X509_STORE_CTX_free(ctx);
548 X509_STORE_CTX_free(ctx);