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 /* XMLCredentials.cpp - a credentials 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 namespace shibboleth {
75 class XMLCredentialsImpl
78 XMLCredentialsImpl(const char* pathname);
79 ~XMLCredentialsImpl();
81 typedef map<string,ICredResolver*> resolvermap_t;
82 resolvermap_t m_resolverMap;
86 KeyUse(resolvermap_t& resolverMap, const XMLCh* keyref, const XMLCh* certref=NULL);
89 ICredResolver* m_cert;
90 vector<pair<const XMLCh*,bool> > m_relying;
93 vector<KeyUse*> m_keyuses;
94 typedef multimap<pair<const XMLCh*,bool>,KeyUse*> BindingMap;
95 BindingMap m_bindings;
100 class XMLCredentials : public ICredentials
103 XMLCredentials(const char* pathname);
104 ~XMLCredentials() { delete m_lock; delete m_impl; }
105 bool attach(const XMLCh* subject, const ISite* relyingParty, SSL_CTX* ctx) const;
109 void unlock() { m_lock->unlock(); }
110 std::string m_source;
113 XMLCredentialsImpl* m_impl;
118 extern "C" ICredentials* XMLCredentialsFactory(const char* source)
120 return new XMLCredentials(source);
123 XMLCredentialsImpl::KeyUse::KeyUse(resolvermap_t& resolverMap, const XMLCh* keyref, const XMLCh* certref) : m_key(NULL), m_cert(NULL)
125 auto_ptr<char> temp(XMLString::transcode(keyref));
126 resolvermap_t::iterator i=resolverMap.find(temp.get());
127 if (i==resolverMap.end())
128 throw MetadataException(string("XMLCredentialsImpl::KeyUse::KeyUse() unable to find valid key reference (") + temp.get() + ")");
131 if (certref && *certref)
133 auto_ptr<char> temp2(XMLString::transcode(certref));
134 i=resolverMap.find(temp2.get());
135 if (i==resolverMap.end())
136 throw MetadataException(string("XMLCredentialsImpl::KeyUse::KeyUse() unable to find valid certificate reference (") + temp2.get() + ")");
141 XMLCredentialsImpl::XMLCredentialsImpl(const char* pathname) : m_doc(NULL)
143 NDC ndc("XMLCredentialsImpl");
144 Category& log=Category::getInstance(SHIB_LOGCAT".XMLCredentialsImpl");
149 static XMLCh base[]={chLatin_f, chLatin_i, chLatin_l, chLatin_e, chColon, chForwardSlash, chForwardSlash, chForwardSlash, chNull};
150 URLInputSource src(base,pathname);
151 Wrapper4InputSource dsrc(&src,false);
154 log.infoStream() << "Loaded and parsed creds file (" << pathname << ")" << CategoryStream::ENDLINE;
156 DOMElement* e = m_doc->getDocumentElement();
157 if (XMLString::compareString(XML::SHIB_NS,e->getNamespaceURI()) ||
158 XMLString::compareString(SHIB_L(Credentials),e->getLocalName()))
160 log.error("Construction requires a valid creds file: (shib:Credentials as root element)");
161 throw MetadataException("Construction requires a valid creds file: (shib:Credentials as root element)");
164 // Process everything up to the first shib:KeyUse as a resolver.
165 DOMElement* child=saml::XML::getFirstChildElement(e);
166 while (!saml::XML::isElementNamed(child,XML::SHIB_NS,SHIB_L(KeyUse)))
168 CredResolverFactory* factory=NULL;
169 auto_ptr<char> id(XMLString::transcode(child->getAttributeNS(NULL,SHIB_L(Id))));
171 if (saml::XML::isElementNamed(child,XML::SHIB_NS,SHIB_L(FileCredResolver)))
172 factory=ShibConfig::getConfig().getCredResolverFactory("edu.internet2.middleware.shibboleth.creds.provider.FileCredResolver");
173 else if (saml::XML::isElementNamed(child,saml::XML::XMLSIG_NS,L(KeyInfo)))
174 factory=ShibConfig::getConfig().getCredResolverFactory("edu.internet2.middleware.shibboleth.creds.provider.KeyInfoResolver");
175 else if (saml::XML::isElementNamed(child,XML::SHIB_NS,SHIB_L(CustomCredResolver)))
177 auto_ptr<char> c(XMLString::transcode(child->getAttributeNS(NULL,SHIB_L(Class))));
178 factory=ShibConfig::getConfig().getCredResolverFactory(c.get());
185 ICredResolver* cr=(*factory)(child);
186 m_resolverMap[id.get()]=cr;
188 catch (SAMLException& e)
190 log.error("failed to instantiate credential resolver (%s): %s", id.get(), e.what());
194 child=saml::XML::getNextSiblingElement(child);
197 // Now loop over the KeyUse elements.
198 while (child && saml::XML::isElementNamed(child,XML::SHIB_NS,SHIB_L(KeyUse)))
200 KeyUse* ku = new KeyUse(
202 child->getAttributeNS(NULL,SHIB_L(KeyRef)),
203 child->getAttributeNS(NULL,SHIB_L(CertificateRef))
205 m_keyuses.push_back(ku);
207 // Pull in the relying parties.
208 DOMNodeList* parties=child->getElementsByTagNameNS(XML::SHIB_NS,SHIB_L(RelyingParty));
210 while (parties && m<parties->getLength())
212 const XMLCh* name=parties->item(m)->getFirstChild()->getNodeValue();
215 static const XMLCh one[]={ chDigit_1, chNull };
216 static const XMLCh tru[]={ chLatin_t, chLatin_r, chLatin_u, chLatin_e, chNull };
218 static_cast<DOMElement*>(parties->item(m))->getAttributeNS(NULL,SHIB_L(regexp));
219 bool flag=(!XMLString::compareString(regexp,one) || !XMLString::compareString(regexp,tru));
220 ku->m_relying.push_back(pair<const XMLCh*,bool>(name,flag));
224 // If no RelyingParties, this is a catch-all binding.
226 ku->m_relying.push_back(pair<const XMLCh*,bool>(NULL,false));
228 // Now map the subjects to the credentials.
229 DOMNodeList* subs=child->getElementsByTagNameNS(XML::SHIB_NS,L(Subject));
231 while (subs && l<subs->getLength())
233 const XMLCh* name=subs->item(l)->getFirstChild()->getNodeValue();
236 static const XMLCh one[]={ chDigit_1, chNull };
237 static const XMLCh tru[]={ chLatin_t, chLatin_r, chLatin_u, chLatin_e, chNull };
239 static_cast<DOMElement*>(subs->item(l))->getAttributeNS(NULL,SHIB_L(regexp));
240 bool flag=(!XMLString::compareString(regexp,one) || !XMLString::compareString(regexp,tru));
241 m_bindings.insert(BindingMap::value_type(pair<const XMLCh*,bool>(name,flag),ku));
245 // If no Subjects, this is a catch-all binding.
247 m_bindings.insert(BindingMap::value_type(pair<const XMLCh*,bool>(NULL,false),ku));
249 child=saml::XML::getNextSiblingElement(child);
252 catch (SAMLException& e)
254 log.errorStream() << "XML error while parsing creds configuration: " << e.what() << CategoryStream::ENDLINE;
255 for (vector<KeyUse*>::iterator i=m_keyuses.begin(); i!=m_keyuses.end(); i++)
257 for (resolvermap_t::iterator j=m_resolverMap.begin(); j!=m_resolverMap.end(); j++)
265 log.error("Unexpected error while parsing creds configuration");
266 for (vector<KeyUse*>::iterator i=m_keyuses.begin(); i!=m_keyuses.end(); i++)
268 for (resolvermap_t::iterator j=m_resolverMap.begin(); j!=m_resolverMap.end(); j++)
276 XMLCredentialsImpl::~XMLCredentialsImpl()
278 for (vector<KeyUse*>::iterator i=m_keyuses.begin(); i!=m_keyuses.end(); i++)
280 for (resolvermap_t::iterator j=m_resolverMap.begin(); j!=m_resolverMap.end(); j++)
286 XMLCredentials::XMLCredentials(const char* pathname) : m_filestamp(0), m_source(pathname), m_impl(NULL)
289 struct _stat stat_buf;
290 if (_stat(pathname, &stat_buf) == 0)
292 struct stat stat_buf;
293 if (stat(pathname, &stat_buf) == 0)
295 m_filestamp=stat_buf.st_mtime;
296 m_impl=new XMLCredentialsImpl(pathname);
297 m_lock=RWLock::create();
300 void XMLCredentials::lock()
304 // Check if we need to refresh.
306 struct _stat stat_buf;
307 if (_stat(m_source.c_str(), &stat_buf) == 0)
309 struct stat stat_buf;
310 if (stat(m_source.c_str(), &stat_buf) == 0)
313 if (m_filestamp>0 && m_filestamp<stat_buf.st_mtime)
315 // Elevate lock and recheck.
318 if (m_filestamp>0 && m_filestamp<stat_buf.st_mtime)
322 XMLCredentialsImpl* new_mapper=new XMLCredentialsImpl(m_source.c_str());
325 m_filestamp=stat_buf.st_mtime;
328 catch(SAMLException& e)
331 saml::NDC ndc("lock");
332 Category::getInstance(SHIB_LOGCAT".XMLCredentials").error("failed to reload credentials metadata, sticking with what we have: %s", e.what());
337 saml::NDC ndc("lock");
338 Category::getInstance(SHIB_LOGCAT".XMLCredentials").error("caught an unknown exception, sticking with what we have");
351 bool XMLCredentials::attach(const XMLCh* subject, const ISite* relyingParty, SSL_CTX* ctx) const
355 // Use the matching bindings.
356 for (XMLCredentialsImpl::BindingMap::const_iterator i=m_impl->m_bindings.begin(); i!=m_impl->m_bindings.end(); i++)
360 if (i->first.first==NULL) // catch-all entry
364 else if (i->first.second) // regexp
368 RegularExpression re(i->first.first);
369 if (re.matches(subject))
372 catch (XMLException& ex)
374 auto_ptr<char> tmp(XMLString::transcode(ex.getMessage()));
375 Category& log=Category::getInstance(SHIB_LOGCAT".XMLCredentials");
376 log.errorStream() << "caught exception while parsing regular expression: " << tmp.get()
377 << CategoryStream::ENDLINE;
380 else if (!XMLString::compareString(subject,i->first.first))
387 // See if the relying party applies...
389 for (vector<pair<const XMLCh*,bool> >::const_iterator j=i->second->m_relying.begin(); j!=i->second->m_relying.end(); j++)
391 if (j->first==NULL) // catch-all entry
395 else if (j->second) // regexp
399 RegularExpression re(j->first);
400 if (re.matches(relyingParty->getName()))
404 Iterator<const XMLCh*> groups=relyingParty->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".XMLCredentials");
414 log.errorStream() << "caught exception while parsing regular expression: " << tmp.get()
415 << CategoryStream::ENDLINE;
418 else if (!XMLString::compareString(relyingParty->getName(),j->first))
424 Iterator<const XMLCh*> groups=relyingParty->getGroups();
425 while (!match && groups.hasNext())
426 if (!XMLString::compareString(groups.next(),j->first))
436 i->second->m_key->resolveKey(ctx);
437 if (i->second->m_cert)
438 i->second->m_cert->resolveCert(ctx);
440 if (!SSL_CTX_check_private_key(ctx))
443 throw MetadataException("XMLCredentials::attach() found mismatch between the private key and certificate used");
448 catch (SAMLException& e)
450 Category& log=Category::getInstance(SHIB_LOGCAT".XMLCredentials");
451 log.error("caught a SAML exception while attaching credentials: %s", e.what());
455 Category& log=Category::getInstance(SHIB_LOGCAT".XMLCredentials");
456 log.error("caught an unknown exception while attaching credentials");