Redesigned target around URL->application mapping
[shibboleth/cpp-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 : public ReloadableXMLFileImpl
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
98     class XMLCredentials : public ICredentials, public ReloadableXMLFile
99     {
100     public:
101         XMLCredentials(const char* pathname) : ReloadableXMLFile(pathname) {}
102         ~XMLCredentials() {}
103         
104         bool attach(const XMLCh* subject, const ISite* relyingParty, SSL_CTX* ctx) const;
105
106     protected:
107         virtual ReloadableXMLFileImpl* newImplementation(const char* pathname) const;
108     };
109
110 }
111
112 extern "C" ICredentials* XMLCredentialsFactory(const char* source)
113 {
114     XMLCredentials* creds=new XMLCredentials(source);
115     try
116     {
117         creds->getImplementation();
118     }
119     catch (...)
120     {
121         delete creds;
122         throw;
123     }
124     return creds;    
125 }
126
127 ReloadableXMLFileImpl* XMLCredentials::newImplementation(const char* pathname) const
128 {
129     return new XMLCredentialsImpl(pathname);
130 }
131
132 XMLCredentialsImpl::KeyUse::KeyUse(resolvermap_t& resolverMap, const XMLCh* keyref, const XMLCh* certref) : m_key(NULL), m_cert(NULL)
133 {
134     auto_ptr<char> temp(XMLString::transcode(keyref));
135     resolvermap_t::iterator i=resolverMap.find(temp.get());
136     if (i==resolverMap.end())
137         throw MetadataException(string("XMLCredentialsImpl::KeyUse::KeyUse() unable to find valid key reference (") + temp.get() + ")");
138     m_key=i->second;
139     
140     if (certref && *certref)
141     {
142         auto_ptr<char> temp2(XMLString::transcode(certref));
143         i=resolverMap.find(temp2.get());
144         if (i==resolverMap.end())
145             throw MetadataException(string("XMLCredentialsImpl::KeyUse::KeyUse() unable to find valid certificate reference (") + temp2.get() + ")");
146         m_cert=i->second;
147     }
148 }
149
150 XMLCredentialsImpl::XMLCredentialsImpl(const char* pathname) : ReloadableXMLFileImpl(pathname)
151 {
152     NDC ndc("XMLCredentialsImpl");
153     Category& log=Category::getInstance(SHIB_LOGCAT".XMLCredentialsImpl");
154
155     try
156     {
157         DOMElement* e = m_doc->getDocumentElement();
158         if (XMLString::compareString(XML::SHIB_NS,e->getNamespaceURI()) ||
159             XMLString::compareString(SHIB_L(Credentials),e->getLocalName()))
160         {
161             log.error("Construction requires a valid creds file: (shib:Credentials as root element)");
162             throw MetadataException("Construction requires a valid creds file: (shib:Credentials as root element)");
163         }
164
165         // Process everything up to the first shib:KeyUse as a resolver.
166         DOMElement* child=saml::XML::getFirstChildElement(e);
167         while (!saml::XML::isElementNamed(child,XML::SHIB_NS,SHIB_L(KeyUse)))
168         {
169             string cr_type;
170             auto_ptr<char> id(XMLString::transcode(child->getAttributeNS(NULL,SHIB_L(Id))));
171             
172             if (saml::XML::isElementNamed(child,XML::SHIB_NS,SHIB_L(FileCredResolver)))
173                 cr_type="edu.internet2.middleware.shibboleth.creds.provider.FileCredResolver";
174             else if (saml::XML::isElementNamed(child,saml::XML::XMLSIG_NS,L(KeyInfo)))
175                 cr_type="edu.internet2.middleware.shibboleth.creds.provider.KeyInfoResolver";
176             else if (saml::XML::isElementNamed(child,XML::SHIB_NS,SHIB_L(CustomCredResolver)))
177             {
178                 auto_ptr_char c(child->getAttributeNS(NULL,SHIB_L(Class)));
179                 cr_type=c.get();
180             }
181             
182             if (!cr_type.empty())
183             {
184                 try
185                 {
186                     ICredResolver* cr=ShibConfig::getConfig().newCredResolver(cr_type.c_str(),child);
187                     m_resolverMap[id.get()]=cr;
188                 }
189                 catch (SAMLException& e)
190                 {
191                     log.error("failed to instantiate credential resolver (%s): %s", id.get(), e.what());
192                 }
193             }
194             
195             child=saml::XML::getNextSiblingElement(child);
196         }
197
198         // Now loop over the KeyUse elements.
199         while (child && saml::XML::isElementNamed(child,XML::SHIB_NS,SHIB_L(KeyUse)))
200         {
201             KeyUse* ku = new KeyUse(
202                 m_resolverMap,
203                 child->getAttributeNS(NULL,SHIB_L(KeyRef)),
204                 child->getAttributeNS(NULL,SHIB_L(CertificateRef))
205                 );
206             m_keyuses.push_back(ku);
207
208             // Pull in the relying parties.
209             DOMNodeList* parties=child->getElementsByTagNameNS(XML::SHIB_NS,SHIB_L(RelyingParty));
210             int m=0;
211             while (parties && m<parties->getLength())
212             {
213                 const XMLCh* name=parties->item(m)->getFirstChild()->getNodeValue();
214                 if (name && *name)
215                 {
216                     static const XMLCh one[]={ chDigit_1, chNull };
217                     static const XMLCh tru[]={ chLatin_t, chLatin_r, chLatin_u, chLatin_e, chNull };
218                     const XMLCh* regexp=
219                         static_cast<DOMElement*>(parties->item(m))->getAttributeNS(NULL,SHIB_L(regexp));
220                     bool flag=(!XMLString::compareString(regexp,one) || !XMLString::compareString(regexp,tru));
221                     ku->m_relying.push_back(pair<const XMLCh*,bool>(name,flag));
222                 }
223                 m++;
224             }
225             // If no RelyingParties, this is a catch-all binding.
226             if (m==0)
227                 ku->m_relying.push_back(pair<const XMLCh*,bool>(NULL,false));
228             
229             // Now map the subjects to the credentials.
230             DOMNodeList* subs=child->getElementsByTagNameNS(XML::SHIB_NS,L(Subject));
231             int l=0;
232             while (subs && l<subs->getLength())
233             {
234                 const XMLCh* name=subs->item(l)->getFirstChild()->getNodeValue();
235                 if (name && *name)
236                 {
237                     static const XMLCh one[]={ chDigit_1, chNull };
238                     static const XMLCh tru[]={ chLatin_t, chLatin_r, chLatin_u, chLatin_e, chNull };
239                     const XMLCh* regexp=
240                         static_cast<DOMElement*>(subs->item(l))->getAttributeNS(NULL,SHIB_L(regexp));
241                     bool flag=(!XMLString::compareString(regexp,one) || !XMLString::compareString(regexp,tru));
242                     m_bindings.insert(BindingMap::value_type(pair<const XMLCh*,bool>(name,flag),ku));
243                 }
244                 l++;
245             }
246             // If no Subjects, this is a catch-all binding.
247             if (l==0)
248                 m_bindings.insert(BindingMap::value_type(pair<const XMLCh*,bool>(NULL,false),ku));
249
250             child=saml::XML::getNextSiblingElement(child);
251         }
252     }
253     catch (SAMLException& e)
254     {
255         log.errorStream() << "Error while parsing creds configuration: " << e.what() << CategoryStream::ENDLINE;
256         for (vector<KeyUse*>::iterator i=m_keyuses.begin(); i!=m_keyuses.end(); i++)
257             delete (*i);
258         for (resolvermap_t::iterator j=m_resolverMap.begin(); j!=m_resolverMap.end(); j++)
259             delete j->second;
260         if (m_doc)
261             m_doc->release();
262         throw;
263     }
264 #ifndef _DEBUG
265     catch (...)
266     {
267         log.error("Unexpected error while parsing creds configuration");
268         for (vector<KeyUse*>::iterator i=m_keyuses.begin(); i!=m_keyuses.end(); i++)
269             delete (*i);
270         for (resolvermap_t::iterator j=m_resolverMap.begin(); j!=m_resolverMap.end(); j++)
271             delete j->second;
272         if (m_doc)
273             m_doc->release();
274         throw;
275     }
276 #endif
277 }
278
279 XMLCredentialsImpl::~XMLCredentialsImpl()
280 {
281     for (vector<KeyUse*>::iterator i=m_keyuses.begin(); i!=m_keyuses.end(); i++)
282         delete (*i);
283     for (resolvermap_t::iterator j=m_resolverMap.begin(); j!=m_resolverMap.end(); j++)
284         delete j->second;
285 }
286
287 bool XMLCredentials::attach(const XMLCh* subject, const ISite* relyingParty, SSL_CTX* ctx) const
288 {
289     NDC ndc("attach");
290
291     // Use the matching bindings.
292     XMLCredentialsImpl* impl=dynamic_cast<XMLCredentialsImpl*>(getImplementation());
293     for (XMLCredentialsImpl::BindingMap::const_iterator i=impl->m_bindings.begin(); i!=impl->m_bindings.end(); i++)
294     {
295         bool match=false;
296         
297         if (i->first.first==NULL)   // catch-all entry
298         {
299             match=true;
300         }
301         else if (i->first.second)   // regexp
302         {
303             try
304             {
305                 RegularExpression re(i->first.first);
306                 if (re.matches(subject))
307                     match=true;
308             }
309             catch (XMLException& ex)
310             {
311                 auto_ptr<char> tmp(XMLString::transcode(ex.getMessage()));
312                 Category& log=Category::getInstance(SHIB_LOGCAT".XMLCredentials");
313                 log.errorStream() << "caught exception while parsing regular expression: " << tmp.get()
314                     << CategoryStream::ENDLINE;
315             }
316         }
317         else if (!XMLString::compareString(subject,i->first.first))
318         {
319             match=true;
320         }
321         
322         if (match)
323         {
324             // See if the relying party applies...
325             match=false;
326             for (vector<pair<const XMLCh*,bool> >::const_iterator j=i->second->m_relying.begin(); j!=i->second->m_relying.end(); j++)
327             {
328                 if (j->first==NULL)     // catch-all entry
329                 {
330                     match=true;
331                 }
332                 else if (j->second)     // regexp
333                 {
334                     try
335                     {
336                         RegularExpression re(j->first);
337                         if (re.matches(relyingParty->getName()))
338                             match=true;
339                         else
340                         {
341                             Iterator<const XMLCh*> groups=relyingParty->getGroups();
342                             while (!match && groups.hasNext())
343                                 if (re.matches(groups.next()))
344                                     match=true;
345                         }
346                     }
347                     catch (XMLException& ex)
348                     {
349                         auto_ptr<char> tmp(XMLString::transcode(ex.getMessage()));
350                         Category& log=Category::getInstance(SHIB_LOGCAT".XMLCredentials");
351                         log.errorStream() << "caught exception while parsing regular expression: " << tmp.get()
352                             << CategoryStream::ENDLINE;
353                     }
354                 }
355                 else if (!XMLString::compareString(relyingParty->getName(),j->first))
356                 {
357                     match=true;
358                 }
359                 else
360                 {
361                     Iterator<const XMLCh*> groups=relyingParty->getGroups();
362                     while (!match && groups.hasNext())
363                         if (!XMLString::compareString(groups.next(),j->first))
364                             match=true;
365                 }
366             }
367         }
368         
369         if (match)
370         {
371             try
372             {
373                 i->second->m_key->resolveKey(ctx);
374                 if (i->second->m_cert)
375                     i->second->m_cert->resolveCert(ctx);
376
377                 if (!SSL_CTX_check_private_key(ctx))
378                 {
379                     log_openssl();
380                     throw MetadataException("XMLCredentials::attach() found mismatch between the private key and certificate used");
381                 }
382
383                 return true;
384             }
385             catch (SAMLException& e)
386             {
387                 Category& log=Category::getInstance(SHIB_LOGCAT".XMLCredentials");
388                 log.error("caught a SAML exception while attaching credentials: %s", e.what());
389             }
390             catch (...)
391             {
392                 Category& log=Category::getInstance(SHIB_LOGCAT".XMLCredentials");
393                 log.error("caught an unknown exception while attaching credentials");
394             }
395         }
396     }
397
398     return false;
399 }