e9a0cd801ad57b94170f32b44b617390894d0b0c
[shibboleth/sp.git] / shib / XMLCredentials.cpp
1 /* 
2  * The Shibboleth License, Version 1. 
3  * Copyright (c) 2002 
4  * University Corporation for Advanced Internet Development, Inc. 
5  * All rights reserved
6  * 
7  * 
8  * Redistribution and use in source and binary forms, with or without 
9  * modification, are permitted provided that the following conditions are met:
10  * 
11  * Redistributions of source code must retain the above copyright notice, this 
12  * list of conditions and the following disclaimer.
13  * 
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.
22  * 
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
28  * 
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.
33  * 
34  * 
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.
48  */
49
50 /* XMLCredentials.cpp - a credentials implementation that uses an XML file
51
52    Scott Cantor
53    9/27/02
54
55    $History:$
56 */
57
58 #include "internal.h"
59
60 #include <sys/types.h>
61 #include <sys/stat.h>
62
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>
67
68 using namespace shibboleth;
69 using namespace saml;
70 using namespace log4cpp;
71 using namespace std;
72
73 namespace shibboleth {
74     
75     class XMLCredentialsImpl
76     {
77     public:
78         XMLCredentialsImpl(const char* pathname);
79         ~XMLCredentialsImpl();
80         
81         typedef map<string,ICredResolver*> resolvermap_t;
82         resolvermap_t m_resolverMap;
83
84         struct KeyUse
85         {
86             KeyUse(resolvermap_t& resolverMap, const XMLCh* keyref, const XMLCh* certref=NULL);
87             
88             ICredResolver* m_key;
89             ICredResolver* m_cert;
90             vector<pair<const XMLCh*,bool> > m_relying;
91         };
92         
93         vector<KeyUse*> m_keyuses;
94         typedef multimap<pair<const XMLCh*,bool>,KeyUse*> BindingMap;
95         BindingMap m_bindings;
96         
97         DOMDocument* m_doc;
98     };
99
100     class XMLCredentials : public ICredentials
101     {
102     public:
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;
106
107     private:
108         void lock();
109         void unlock() { m_lock->unlock(); }
110         std::string m_source;
111         time_t m_filestamp;
112         RWLock* m_lock;
113         XMLCredentialsImpl* m_impl;
114     };
115
116 }
117
118 extern "C" ICredentials* XMLCredentialsFactory(const char* source)
119 {
120     return new XMLCredentials(source);
121 }
122
123 XMLCredentialsImpl::KeyUse::KeyUse(resolvermap_t& resolverMap, const XMLCh* keyref, const XMLCh* certref) : m_key(NULL), m_cert(NULL)
124 {
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() + ")");
129     m_key=i->second;
130     
131     if (certref && *certref)
132     {
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() + ")");
137         m_cert=i->second;
138     }
139 }
140
141 XMLCredentialsImpl::XMLCredentialsImpl(const char* pathname) : m_doc(NULL)
142 {
143     NDC ndc("XMLCredentialsImpl");
144     Category& log=Category::getInstance(SHIB_LOGCAT".XMLCredentialsImpl");
145
146     saml::XML::Parser p;
147     try
148     {
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);
152         m_doc=p.parse(dsrc);
153
154         log.infoStream() << "Loaded and parsed creds file (" << pathname << ")" << CategoryStream::ENDLINE;
155
156         DOMElement* e = m_doc->getDocumentElement();
157         if (XMLString::compareString(XML::SHIB_NS,e->getNamespaceURI()) ||
158             XMLString::compareString(SHIB_L(Credentials),e->getLocalName()))
159         {
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)");
162         }
163
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)))
167         {
168             CredResolverFactory* factory=NULL;
169             auto_ptr<char> id(XMLString::transcode(child->getAttributeNS(NULL,SHIB_L(Id))));
170             
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)))
176             {
177                 auto_ptr<char> c(XMLString::transcode(child->getAttributeNS(NULL,SHIB_L(Class))));
178                 factory=ShibConfig::getConfig().getCredResolverFactory(c.get());
179             }
180             
181             if (factory)
182             {
183                 try
184                 {
185                     ICredResolver* cr=(*factory)(child);
186                     m_resolverMap[id.get()]=cr;
187                 }
188                 catch (SAMLException& e)
189                 {
190                     log.error("failed to instantiate credential resolver (%s): %s", id.get(), e.what());
191                 }
192             }
193             
194             child=saml::XML::getNextSiblingElement(child);
195         }
196
197         // Now loop over the KeyUse elements.
198         while (child && saml::XML::isElementNamed(child,XML::SHIB_NS,SHIB_L(KeyUse)))
199         {
200             KeyUse* ku = new KeyUse(
201                 m_resolverMap,
202                 child->getAttributeNS(NULL,SHIB_L(KeyRef)),
203                 child->getAttributeNS(NULL,SHIB_L(CertificateRef))
204                 );
205             m_keyuses.push_back(ku);
206
207             // Pull in the relying parties.
208             DOMNodeList* parties=child->getElementsByTagNameNS(XML::SHIB_NS,SHIB_L(RelyingParty));
209             int m=0;
210             while (parties && m<parties->getLength())
211             {
212                 const XMLCh* name=parties->item(m)->getFirstChild()->getNodeValue();
213                 if (name && *name)
214                 {
215                     static const XMLCh one[]={ chDigit_1, chNull };
216                     static const XMLCh tru[]={ chLatin_t, chLatin_r, chLatin_u, chLatin_e, chNull };
217                     const XMLCh* regexp=
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));
221                 }
222                 m++;
223             }
224             // If no RelyingParties, this is a catch-all binding.
225             if (m==0)
226                 ku->m_relying.push_back(pair<const XMLCh*,bool>(NULL,false));
227             
228             // Now map the subjects to the credentials.
229             DOMNodeList* subs=child->getElementsByTagNameNS(XML::SHIB_NS,L(Subject));
230             int l=0;
231             while (subs && l<subs->getLength())
232             {
233                 const XMLCh* name=subs->item(l)->getFirstChild()->getNodeValue();
234                 if (name && *name)
235                 {
236                     static const XMLCh one[]={ chDigit_1, chNull };
237                     static const XMLCh tru[]={ chLatin_t, chLatin_r, chLatin_u, chLatin_e, chNull };
238                     const XMLCh* regexp=
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));
242                 }
243                 l++;
244             }
245             // If no Subjects, this is a catch-all binding.
246             if (l==0)
247                 m_bindings.insert(BindingMap::value_type(pair<const XMLCh*,bool>(NULL,false),ku));
248
249             child=saml::XML::getNextSiblingElement(child);
250         }
251     }
252     catch (SAMLException& e)
253     {
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++)
256             delete (*i);
257         for (resolvermap_t::iterator j=m_resolverMap.begin(); j!=m_resolverMap.end(); j++)
258             delete j->second;
259         if (m_doc)
260             m_doc->release();
261         throw;
262     }
263     catch (...)
264     {
265         log.error("Unexpected error while parsing creds configuration");
266         for (vector<KeyUse*>::iterator i=m_keyuses.begin(); i!=m_keyuses.end(); i++)
267             delete (*i);
268         for (resolvermap_t::iterator j=m_resolverMap.begin(); j!=m_resolverMap.end(); j++)
269             delete j->second;
270         if (m_doc)
271             m_doc->release();
272         throw;
273     }
274 }
275
276 XMLCredentialsImpl::~XMLCredentialsImpl()
277 {
278     for (vector<KeyUse*>::iterator i=m_keyuses.begin(); i!=m_keyuses.end(); i++)
279         delete (*i);
280     for (resolvermap_t::iterator j=m_resolverMap.begin(); j!=m_resolverMap.end(); j++)
281         delete j->second;
282     if (m_doc)
283         m_doc->release();
284 }
285
286 XMLCredentials::XMLCredentials(const char* pathname) : m_filestamp(0), m_source(pathname), m_impl(NULL)
287 {
288 #ifdef WIN32
289     struct _stat stat_buf;
290     if (_stat(pathname, &stat_buf) == 0)
291 #else
292     struct stat stat_buf;
293     if (stat(pathname, &stat_buf) == 0)
294 #endif
295         m_filestamp=stat_buf.st_mtime;
296     m_impl=new XMLCredentialsImpl(pathname);
297     m_lock=RWLock::create();
298 }
299
300 void XMLCredentials::lock()
301 {
302     m_lock->rdlock();
303
304     // Check if we need to refresh.
305 #ifdef WIN32
306     struct _stat stat_buf;
307     if (_stat(m_source.c_str(), &stat_buf) == 0)
308 #else
309     struct stat stat_buf;
310     if (stat(m_source.c_str(), &stat_buf) == 0)
311 #endif
312     {
313         if (m_filestamp>0 && m_filestamp<stat_buf.st_mtime)
314         {
315             // Elevate lock and recheck.
316             m_lock->unlock();
317             m_lock->wrlock();
318             if (m_filestamp>0 && m_filestamp<stat_buf.st_mtime)
319             {
320                 try
321                 {
322                     XMLCredentialsImpl* new_mapper=new XMLCredentialsImpl(m_source.c_str());
323                     delete m_impl;
324                     m_impl=new_mapper;
325                     m_filestamp=stat_buf.st_mtime;
326                     m_lock->unlock();
327                 }
328                 catch(SAMLException& e)
329                 {
330                     m_lock->unlock();
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());
333                 }
334                 catch(...)
335                 {
336                     m_lock->unlock();
337                     saml::NDC ndc("lock");
338                     Category::getInstance(SHIB_LOGCAT".XMLCredentials").error("caught an unknown exception, sticking with what we have");
339                 }
340             }
341             else
342             {
343                 m_lock->unlock();
344             }
345             m_lock->rdlock();
346         }
347     }
348 }
349
350
351 bool XMLCredentials::attach(const XMLCh* subject, const ISite* relyingParty, SSL_CTX* ctx) const
352 {
353     NDC ndc("attach");
354
355     // Use the matching bindings.
356     for (XMLCredentialsImpl::BindingMap::const_iterator i=m_impl->m_bindings.begin(); i!=m_impl->m_bindings.end(); i++)
357     {
358         bool match=false;
359         
360         if (i->first.first==NULL)   // catch-all entry
361         {
362             match=true;
363         }
364         else if (i->first.second)   // regexp
365         {
366             try
367             {
368                 RegularExpression re(i->first.first);
369                 if (re.matches(subject))
370                     match=true;
371             }
372             catch (XMLException& ex)
373             {
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;
378             }
379         }
380         else if (!XMLString::compareString(subject,i->first.first))
381         {
382             match=true;
383         }
384         
385         if (match)
386         {
387             // See if the relying party applies...
388             match=false;
389             for (vector<pair<const XMLCh*,bool> >::const_iterator j=i->second->m_relying.begin(); j!=i->second->m_relying.end(); j++)
390             {
391                 if (j->first==NULL)     // catch-all entry
392                 {
393                     match=true;
394                 }
395                 else if (j->second)     // regexp
396                 {
397                     try
398                     {
399                         RegularExpression re(j->first);
400                         if (re.matches(relyingParty->getName()))
401                             match=true;
402                         else
403                         {
404                             Iterator<const XMLCh*> groups=relyingParty->getGroups();
405                             while (!match && groups.hasNext())
406                                 if (re.matches(groups.next()))
407                                     match=true;
408                         }
409                     }
410                     catch (XMLException& ex)
411                     {
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;
416                     }
417                 }
418                 else if (!XMLString::compareString(relyingParty->getName(),j->first))
419                 {
420                     match=true;
421                 }
422                 else
423                 {
424                     Iterator<const XMLCh*> groups=relyingParty->getGroups();
425                     while (!match && groups.hasNext())
426                         if (!XMLString::compareString(groups.next(),j->first))
427                             match=true;
428                 }
429             }
430         }
431         
432         if (match)
433         {
434             try
435             {
436                 i->second->m_key->resolveKey(ctx);
437                 if (i->second->m_cert)
438                     i->second->m_cert->resolveCert(ctx);
439
440                 if (!SSL_CTX_check_private_key(ctx))
441                 {
442                     log_openssl();
443                     throw MetadataException("XMLCredentials::attach() found mismatch between the private key and certificate used");
444                 }
445
446                 return true;
447             }
448             catch (SAMLException& e)
449             {
450                 Category& log=Category::getInstance(SHIB_LOGCAT".XMLCredentials");
451                 log.error("caught a SAML exception while attaching credentials: %s", e.what());
452             }
453             catch (...)
454             {
455                 Category& log=Category::getInstance(SHIB_LOGCAT".XMLCredentials");
456                 log.error("caught an unknown exception while attaching credentials");
457             }
458         }
459     }
460
461     return false;
462 }