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 <log4cpp/Category.hh>
64 #include <xercesc/framework/URLInputSource.hpp>
65 #include <xercesc/util/regx/RegularExpression.hpp>
66 #include <xsec/enc/OpenSSL/OpenSSLCryptoX509.hpp>
68 using namespace shibboleth;
70 using namespace log4cpp;
73 class shibboleth::XMLCredentialsImpl
76 XMLCredentialsImpl(const char* pathname);
77 ~XMLCredentialsImpl();
84 bool attach(SSL_CTX* ctx);
86 enum format_t { X509DATA, DER, PEM };
88 format_t m_certtype, m_keytype;
89 vector<X509*> m_certs;
90 string m_certfile, m_keyfile;
91 vector<pair<const XMLCh*,bool> > m_relying;
94 vector<KeyUse*> m_keyuses;
95 typedef multimap<pair<const XMLCh*,bool>,KeyUse*> BindingMap;
96 BindingMap m_bindings;
101 XMLCredentialsImpl::KeyUse::~KeyUse()
103 for (vector<X509*>::iterator i=m_certs.begin(); i!=m_certs.end(); i++)
107 bool XMLCredentialsImpl::KeyUse::attach(SSL_CTX* ctx)
112 if (SSL_CTX_use_certificate_chain_file(ctx, m_certfile.c_str()) != 1)
115 throw TrustException("XMLCredentials::KeyUse::attach() unable to set PEM certificate chain");
120 if (SSL_CTX_use_certificate_file(ctx, m_certfile.c_str(), SSL_FILETYPE_ASN1) != 1)
123 throw TrustException("XMLCredentials::KeyUse::attach() unable to set DER certificate");
128 for (vector<X509*>::reverse_iterator i=m_certs.rbegin(); i!=m_certs.rend(); i++)
130 if (i==m_certs.rbegin())
132 if (SSL_CTX_use_certificate(ctx, *i) != 1)
135 throw TrustException("XMLCredentials::KeyUse::attach() unable to set certificate from X509Data");
140 // When we add extra certs, they don't get ref counted, so we need to duplicate them.
141 X509* dup = X509_dup(*i);
142 if (SSL_CTX_add_extra_chain_cert(ctx, dup) != 0)
146 throw TrustException("XMLCredentials::KeyUse::attach() unable to add CA certificate from X509Data");
155 if (SSL_CTX_use_PrivateKey_file(ctx, m_keyfile.c_str(), SSL_FILETYPE_PEM) != 1)
158 throw TrustException("XMLCredentials::KeyUse::attach() unable to set PEM private key");
163 if (SSL_CTX_use_PrivateKey_file(ctx, m_keyfile.c_str(), SSL_FILETYPE_ASN1) != 1)
166 throw TrustException("XMLCredentials::KeyUse::attach() unable to set PEM private key");
170 if (!SSL_CTX_check_private_key(ctx))
173 throw TrustException("XMLCredentials::KeyUse::attach found mismatch between the private key and certificate");
179 XMLCredentialsImpl::XMLCredentialsImpl(const char* pathname) : m_doc(NULL)
181 NDC ndc("XMLCredentialsImpl");
182 Category& log=Category::getInstance(SHIB_LOGCAT".XMLCredentialsImpl");
187 static XMLCh base[]={chLatin_f, chLatin_i, chLatin_l, chLatin_e, chColon, chForwardSlash, chForwardSlash, chForwardSlash, chNull};
188 URLInputSource src(base,pathname);
189 Wrapper4InputSource dsrc(&src,false);
192 log.infoStream() << "Loaded and parsed creds file (" << pathname << ")" << CategoryStream::ENDLINE;
194 DOMElement* e = m_doc->getDocumentElement();
195 if (XMLString::compareString(XML::SHIB_NS,e->getNamespaceURI()) ||
196 XMLString::compareString(SHIB_L(Credentials),e->getLocalName()))
198 log.error("Construction requires a valid creds file: (shib:Credentials as root element)");
199 throw MetadataException("Construction requires a valid creds file: (shib:Credentials as root element)");
202 // Loop over the KeyUse elements.
203 DOMNodeList* nlist=e->getElementsByTagNameNS(XML::SHIB_NS,SHIB_L(KeyUse));
204 for (int i=0; nlist && i<nlist->getLength(); i++)
206 auto_ptr<KeyUse> ku(new KeyUse());
208 bool key=false,cert=false;
210 // Grab all the RetrievalMethods for external material.
211 DOMNodeList* extlist=static_cast<DOMElement*>(nlist->item(i))->getElementsByTagNameNS(
212 saml::XML::XMLSIG_NS,SHIB_L(RetrievalMethod)
214 for (int j=0; (!key || !cert) && extlist && j<extlist->getLength(); j++)
216 DOMElement* method=static_cast<DOMElement*>(extlist->item(j));
217 const XMLCh* rmtype=method->getAttributeNS(NULL,SHIB_L(Type));
218 auto_ptr<char> uri(XMLString::transcode(method->getAttributeNS(NULL,SHIB_L(URI))));
220 // Is the URI locally accessible as a relative URL?
222 struct _stat stat_buf;
223 if (_stat(uri.get(), &stat_buf) != 0)
225 struct stat stat_buf;
226 if (stat(uri.get(), &stat_buf) != 0)
229 log.warn("Credential referenced by ds:RetrievalMethod can't be opened");
233 if (!XMLString::compareString(rmtype,shibboleth::Constants::XMLSIG_RETMETHOD_RAWX509))
236 log.warn("Found another certificate credential (DER), replacing the original with it");
237 ku->m_certfile=uri.get();
238 ku->m_certtype=KeyUse::DER;
241 else if (!XMLString::compareString(rmtype,shibboleth::Constants::SHIB_RETMETHOD_PEMX509))
244 log.warn("Found another certificate credential (PEM), replacing the original with it");
245 ku->m_certfile=uri.get();
246 ku->m_certtype=KeyUse::PEM;
249 else if (!XMLString::compareString(rmtype,shibboleth::Constants::SHIB_RETMETHOD_PEMRSA))
252 log.warn("Found another private key credential (PEM/RSA), replacing the original with it");
253 ku->m_keyfile=uri.get();
254 ku->m_keytype=KeyUse::PEM;
257 else if (!XMLString::compareString(rmtype,shibboleth::Constants::SHIB_RETMETHOD_DERRSA))
260 log.warn("Found another private key credential (DER/RSA), replacing the original with it");
261 ku->m_keyfile=uri.get();
262 ku->m_keytype=KeyUse::DER;
269 // Is there an X509Data?
270 DOMNodeList* x509data=static_cast<DOMElement*>(nlist->item(i))->getElementsByTagNameNS(
271 saml::XML::XMLSIG_NS,L(X509Data)
273 if (x509data && x509data->getLength())
275 if (x509data->getLength()>1)
276 log.warn("Found multiple certificate chains, using the first");
278 // Grab up any X509Certificate elements, and flatten into one list.
279 DOMNodeList* certlist=static_cast<DOMElement*>(x509data->item(0))->getElementsByTagNameNS(
280 saml::XML::XMLSIG_NS,L(X509Certificate)
282 for (int k=0; certlist && k<certlist->getLength(); k++)
284 auto_ptr<char> blob(XMLString::transcode(certlist->item(k)->getFirstChild()->getNodeValue()));
285 X509* x=B64_to_X509(blob.get());
287 ku->m_certs.push_back(x);
289 log.warn("Unable to parse ds:X509Certificate element, can't include in chain");
292 if (ku->m_certs.size()>0)
294 ku->m_certtype=KeyUse::X509DATA;
298 log.warn("Found no inline certificates in the ds:X509Data element, ignoring it");
304 log.error("Found no acceptable certificate in shib:KeyUse element, ignoring it");
310 log.error("Found no acceptable private/secret key in shib:KeyUse element, ignoring it");
314 // Pull in the relying parties.
315 DOMNodeList* parties=static_cast<DOMElement*>(nlist->item(i))->getElementsByTagNameNS(XML::SHIB_NS,SHIB_L(RelyingParty));
317 while (parties && m<parties->getLength())
319 const XMLCh* name=parties->item(m)->getFirstChild()->getNodeValue();
322 static const XMLCh one[]={ chDigit_1, chNull };
323 static const XMLCh tru[]={ chLatin_t, chLatin_r, chLatin_u, chLatin_e, chNull };
325 static_cast<DOMElement*>(parties->item(m))->getAttributeNS(NULL,SHIB_L(regexp));
326 bool flag=(!XMLString::compareString(regexp,one) || !XMLString::compareString(regexp,tru));
327 ku->m_relying.push_back(pair<const XMLCh*,bool>(name,flag));
331 // If no RelyingParties, this is a catch-all binding.
333 ku->m_relying.push_back(pair<const XMLCh*,bool>(NULL,false));
335 // Now map the subjects to the credentials.
336 DOMNodeList* subs=static_cast<DOMElement*>(nlist->item(i))->getElementsByTagNameNS(XML::SHIB_NS,L(Subject));
338 while (subs && l<subs->getLength())
340 const XMLCh* name=subs->item(l)->getFirstChild()->getNodeValue();
343 static const XMLCh one[]={ chDigit_1, chNull };
344 static const XMLCh tru[]={ chLatin_t, chLatin_r, chLatin_u, chLatin_e, chNull };
346 static_cast<DOMElement*>(subs->item(l))->getAttributeNS(NULL,SHIB_L(regexp));
347 bool flag=(!XMLString::compareString(regexp,one) || !XMLString::compareString(regexp,tru));
348 m_bindings.insert(BindingMap::value_type(pair<const XMLCh*,bool>(name,flag),ku.get()));
352 // If no Subjects, this is a catch-all binding.
354 m_bindings.insert(BindingMap::value_type(pair<const XMLCh*,bool>(NULL,false),ku.get()));
356 m_keyuses.push_back(ku.release());
359 catch (SAMLException& e)
361 log.errorStream() << "XML error while parsing creds configuration: " << e.what() << CategoryStream::ENDLINE;
362 for (vector<KeyUse*>::iterator i=m_keyuses.begin(); i!=m_keyuses.end(); i++)
370 log.error("Unexpected error while parsing creds configuration");
371 for (vector<KeyUse*>::iterator i=m_keyuses.begin(); i!=m_keyuses.end(); i++)
379 XMLCredentialsImpl::~XMLCredentialsImpl()
381 for (vector<KeyUse*>::iterator i=m_keyuses.begin(); i!=m_keyuses.end(); i++)
387 XMLCredentials::XMLCredentials(const char* pathname) : m_filestamp(0), m_source(pathname), m_impl(NULL)
390 struct _stat stat_buf;
391 if (_stat(pathname, &stat_buf) == 0)
393 struct stat stat_buf;
394 if (stat(pathname, &stat_buf) == 0)
396 m_filestamp=stat_buf.st_mtime;
397 m_impl=new XMLCredentialsImpl(pathname);
398 m_lock=RWLock::create();
401 XMLCredentials::~XMLCredentials()
407 void XMLCredentials::lock()
411 // Check if we need to refresh.
413 struct _stat stat_buf;
414 if (_stat(m_source.c_str(), &stat_buf) == 0)
416 struct stat stat_buf;
417 if (stat(m_source.c_str(), &stat_buf) == 0)
420 if (m_filestamp>0 && m_filestamp<stat_buf.st_mtime)
422 // Elevate lock and recheck.
425 if (m_filestamp>0 && m_filestamp<stat_buf.st_mtime)
429 XMLCredentialsImpl* new_mapper=new XMLCredentialsImpl(m_source.c_str());
432 m_filestamp=stat_buf.st_mtime;
435 catch(SAMLException& e)
438 saml::NDC ndc("lock");
439 Category::getInstance(SHIB_LOGCAT".XMLCredentials").error("failed to reload credentials metadata, sticking with what we have: %s", e.what());
444 saml::NDC ndc("lock");
445 Category::getInstance(SHIB_LOGCAT".XMLCredentials").error("caught an unknown exception, sticking with what we have");
457 void XMLCredentials::unlock()
463 bool XMLCredentials::attach(const XMLCh* subject, const ISite* relyingParty, SSL_CTX* ctx) const
467 // Use the matching bindings.
468 for (XMLCredentialsImpl::BindingMap::const_iterator i=m_impl->m_bindings.begin(); i!=m_impl->m_bindings.end(); i++)
472 if (i->first.first==NULL) // catch-all entry
476 else if (i->first.second) // regexp
480 RegularExpression re(i->first.first);
481 if (re.matches(subject))
484 catch (XMLException& ex)
486 auto_ptr<char> tmp(XMLString::transcode(ex.getMessage()));
487 Category& log=Category::getInstance(SHIB_LOGCAT".XMLCredentials");
488 log.errorStream() << "caught exception while parsing regular expression: " << tmp.get()
489 << CategoryStream::ENDLINE;
492 else if (!XMLString::compareString(subject,i->first.first))
499 // See if the relying party applies...
501 for (vector<pair<const XMLCh*,bool> >::const_iterator j=i->second->m_relying.begin(); j!=i->second->m_relying.end(); j++)
503 if (j->first==NULL) // catch-all entry
507 else if (j->second) // regexp
511 RegularExpression re(j->first);
512 if (re.matches(relyingParty->getName()))
516 Iterator<const XMLCh*> groups=relyingParty->getGroups();
517 while (!match && groups.hasNext())
518 if (re.matches(groups.next()))
522 catch (XMLException& ex)
524 auto_ptr<char> tmp(XMLString::transcode(ex.getMessage()));
525 Category& log=Category::getInstance(SHIB_LOGCAT".XMLCredentials");
526 log.errorStream() << "caught exception while parsing regular expression: " << tmp.get()
527 << CategoryStream::ENDLINE;
530 else if (!XMLString::compareString(relyingParty->getName(),j->first))
536 Iterator<const XMLCh*> groups=relyingParty->getGroups();
537 while (!match && groups.hasNext())
538 if (!XMLString::compareString(groups.next(),j->first))
546 // We have the credentials to use...
547 return i->second->attach(ctx);