667c8bf68f72200cff96c96d735ea55b92df90c6
[shibboleth/sp.git] / xmlproviders / XMLRevocation.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 /* XMLRevocation.cpp - a revocation implementation that uses an XML file
51
52    Scott Cantor
53    2/16/04
54
55    $History:$
56 */
57
58 #include "internal.h"
59
60 #include <sys/types.h>
61 #include <sys/stat.h>
62
63 #include <openssl/err.h>
64
65 #include <log4cpp/Category.hh>
66
67 using namespace shibboleth;
68 using namespace saml;
69 using namespace log4cpp;
70 using namespace std;
71
72 namespace {
73
74     class XMLRevocationImpl : public ReloadableXMLFileImpl
75     {
76     public:
77         XMLRevocationImpl(const char* pathname) : ReloadableXMLFileImpl(pathname), m_wildcard(NULL) { init(); }
78         XMLRevocationImpl(const DOMElement* e) : ReloadableXMLFileImpl(e), m_wildcard(NULL) { init(); }
79         void init();
80         ~XMLRevocationImpl();
81         
82         struct KeyAuthority
83         {
84             KeyAuthority() {}
85             ~KeyAuthority();
86
87 #ifndef HAVE_GOOD_STL
88             vector<const XMLCh*> m_subjects;
89 #endif
90             vector<void*> m_crls;
91         };
92         
93         vector<KeyAuthority*> m_keyauths;
94         KeyAuthority* m_wildcard;
95 #ifdef HAVE_GOOD_STL
96         typedef map<xstring,KeyAuthority*> AuthMap;
97         AuthMap m_map;
98 #endif
99     };
100
101     class XMLRevocation : public IRevocation, public ReloadableXMLFile
102     {
103     public:
104         XMLRevocation(const DOMElement* e) : ReloadableXMLFile(e) {}
105         ~XMLRevocation() {}
106
107         Iterator<void*> getRevocationLists(const IProvider* provider, const IProviderRole* role=NULL) const;
108
109     protected:
110         virtual ReloadableXMLFileImpl* newImplementation(const char* pathname) const;
111         virtual ReloadableXMLFileImpl* newImplementation(const DOMElement* e) const;
112     };
113
114 }
115
116 extern "C" IRevocation* XMLRevocationFactory(const DOMElement* e)
117 {
118     XMLRevocation* r=new XMLRevocation(e);
119     try {
120         r->getImplementation();
121     }
122     catch (...) {
123         delete r;
124         throw;
125     }
126     return r;
127 }
128
129
130 ReloadableXMLFileImpl* XMLRevocation::newImplementation(const char* pathname) const
131 {
132     return new XMLRevocationImpl(pathname);
133 }
134
135 ReloadableXMLFileImpl* XMLRevocation::newImplementation(const DOMElement* e) const
136 {
137     return new XMLRevocationImpl(e);
138 }
139
140 XMLRevocationImpl::KeyAuthority::~KeyAuthority()
141 {
142     for (vector<void*>::iterator i=m_crls.begin(); i!=m_crls.end(); i++)
143         X509_CRL_free(reinterpret_cast<X509_CRL*>(*i));
144 }
145
146 void XMLRevocationImpl::init()
147 {
148     NDC ndc("XMLRevocationImpl");
149     Category& log=Category::getInstance(XMLPROVIDERS_LOGCAT".XMLRevocationImpl");
150
151     try {
152         if (!saml::XML::isElementNamed(m_root,::XML::TRUST_NS,SHIB_L(Trust))) {
153             log.error("Construction requires a valid trust file: (trust:Trust as root element)");
154             throw TrustException("Construction requires a valid trust file: (trust:Trust as root element)");
155         }
156
157         // Loop over the KeyAuthority elements.
158         DOMNodeList* nlist=m_root->getElementsByTagNameNS(::XML::TRUST_NS,SHIB_L(KeyAuthority));
159         for (int i=0; nlist && i<nlist->getLength(); i++) {
160             auto_ptr<KeyAuthority> ka(new KeyAuthority());
161                         
162             // Very rudimentary, grab up all the in-band X509CRL elements, and flatten into one list.
163             DOMNodeList* crllist=static_cast<DOMElement*>(nlist->item(i))->getElementsByTagNameNS(
164                 saml::XML::XMLSIG_NS,SHIB_L(X509CRL)
165                 );
166             for (int j=0; crllist && j<crllist->getLength(); j++) {
167                 auto_ptr_char blob(crllist->item(j)->getFirstChild()->getNodeValue());
168                 X509_CRL* x=B64_to_CRL(blob.get());
169                 if (x)
170                     ka->m_crls.push_back(x);
171                 else
172                     log.warn("unable to create CRL from inline X509CRL data");
173             }
174             
175             // Now look for externally referenced objects.
176             crllist=static_cast<DOMElement*>(nlist->item(i))->getElementsByTagNameNS(
177                 saml::XML::XMLSIG_NS,SHIB_L(RetrievalMethod)
178                 );
179             for (int k=0; crllist && k<crllist->getLength(); k++) {
180                 DOMElement* crl=static_cast<DOMElement*>(crllist->item(k));
181                 if (!XMLString::compareString(crl->getAttributeNS(NULL,SHIB_L(Type)),::XML::XMLSIG_RETMETHOD_RAWX509CRL)) {
182                     // DER format
183                     auto_ptr_char fname(crl->getAttributeNS(NULL,SHIB_L(URI)));
184                     FILE* f=fopen(fname.get(),"r");
185                     if (f) {
186                         X509_CRL* x=NULL;
187                         d2i_X509_CRL_fp(f,&x);
188                         if (x) {
189                             ka->m_crls.push_back(x);
190                             continue;
191                         }
192                         else
193                             log_openssl();
194                     }
195                     log.warn("unable to create CRL from externally referenced X509CRL file");
196                 }
197                 else if (!XMLString::compareString(crl->getAttributeNS(NULL,SHIB_L(Type)),::XML::SHIB_RETMETHOD_PEMX509CRL)) {
198                     // PEM format
199                     int count=0;
200                     auto_ptr_char fname(crl->getAttributeNS(NULL,SHIB_L(URI)));
201                     FILE* f=fopen(fname.get(),"r");
202                     if (f) {
203                         X509_CRL* x=NULL;
204                         while (x=PEM_read_X509_CRL(f,NULL,NULL,NULL)) {
205                             ka->m_crls.push_back(x);
206                             count++;
207                         }
208                     }
209                     if (!count)
210                         log.warn("unable to create CRL from externally referenced X509CRL file");
211                 }
212             }
213
214             if (ka->m_crls.empty())
215                 continue;
216             m_keyauths.push_back(ka.get());
217             
218             // Now map the ds:KeyName values to the list of certs.
219             bool wildcard=true;
220             DOMElement* sub=saml::XML::getFirstChildElement(static_cast<DOMElement*>(nlist->item(i)),saml::XML::XMLSIG_NS,SHIB_L(KeyName));
221             while (sub) {
222                 const XMLCh* name=sub->getFirstChild()->getNodeValue();
223                 if (name && *name) {
224                     wildcard=false;
225 #ifdef HAVE_GOOD_STL
226                     m_map[name]=ka.get();
227 #else
228                     ka->m_subjects.push_back(name);
229 #endif
230                 }
231                 sub=saml::XML::getNextSiblingElement(sub,saml::XML::XMLSIG_NS,SHIB_L(KeyName));
232             }
233             
234             // If no Subjects, this is a catch-all binding.
235             if (wildcard) {
236                 if (!m_wildcard)
237                     m_wildcard=ka.get();
238                 else
239                     log.warn("found multiple wildcard KeyAuthority elements, ignoring all but the first");
240             }
241             ka.release();
242         }
243     }
244     catch (SAMLException& e) {
245         log.errorStream() << "Error while parsing revocation configuration: " << e.what() << CategoryStream::ENDLINE;
246         for (vector<KeyAuthority*>::iterator i=m_keyauths.begin(); i!=m_keyauths.end(); i++)
247             delete (*i);
248         throw;
249     }
250     catch (...) {
251         log.error("Unexpected error while parsing revocation configuration");
252         for (vector<KeyAuthority*>::iterator i=m_keyauths.begin(); i!=m_keyauths.end(); i++)
253             delete (*i);
254         throw;
255     }
256 }
257
258 XMLRevocationImpl::~XMLRevocationImpl()
259 {
260     for (vector<KeyAuthority*>::iterator i=m_keyauths.begin(); i!=m_keyauths.end(); i++)
261         delete (*i);
262 }
263
264 Iterator<void*> XMLRevocation::getRevocationLists(const IProvider* provider, const IProviderRole* role) const
265 {
266     saml::NDC ndc("getRevocationLists");
267     Category& log=Category::getInstance(XMLPROVIDERS_LOGCAT".XMLRevocation");
268     XMLRevocationImpl* impl=dynamic_cast<XMLRevocationImpl*>(getImplementation());
269
270     // Build a list of the names to match. We include any named KeyDescriptors, and the provider ID and its groups.
271     vector<const XMLCh*> names;
272     if (role) {
273         Iterator<const IKeyDescriptor*> kdlist=role->getKeyDescriptors();
274         while (kdlist.hasNext()) {
275             const IKeyDescriptor* kd=kdlist.next();
276             if (kd->getUse()!=IKeyDescriptor::signing)
277                 continue;
278             DSIGKeyInfoList* kilist=kd->getKeyInfo();
279             for (size_t s=0; kilist && s<kilist->getSize(); s++) {
280                 const XMLCh* n=kilist->item(s)->getKeyName();
281                 if (n)
282                     names.push_back(n);
283             }
284         }
285     }
286     names.push_back(provider->getId());
287     Iterator<const XMLCh*> groups=provider->getGroups();
288     while (groups.hasNext())
289         names.push_back(groups.next());
290
291     // Now check each name.
292     for (vector<const XMLCh*>::const_iterator name=names.begin(); name!=names.end(); name++) {
293         const XMLRevocationImpl::KeyAuthority* kauth=NULL;
294 #ifdef HAVE_GOOD_STL
295         XMLRevocationImpl::AuthMap::const_iterator c=impl->m_map.find(*name);
296         if (c!=impl->m_map.end()) {
297             kauth=c->second;
298             if (log.isDebugEnabled()) {
299                 auto_ptr_char temp(*name);
300                 log.debug("revocation list match on %s",temp.get());
301             }
302         }
303 #else
304         // Without a decent STL, we trade-off the transcoding by doing a linear search.
305         for (vector<XMLRevocationImpl::KeyAuthority*>::const_iterator keyauths=impl->m_keyauths.begin(); keyauths!=impl->m_keyauths.end(); keyauths++) {
306             for (vector<const XMLCh*>::const_iterator subs=keyauths->m_subjects.begin(); subs!=keyauths->m_subjects.end(); subs++) {
307                 if (!XMLString::compareString(*name,*subs)) {
308                     kauth=*keyauths;
309                     if (log.isDebugEnabled()) {
310                         auto_ptr_char temp(*name);
311                         log.debug("revocation list match on %s",temp.get());
312                     }
313                 }
314             }
315         }
316 #endif
317         if (kauth)
318             return kauth->m_crls;
319         else if (impl->m_wildcard)
320             return impl->m_wildcard->m_crls;
321     }
322     
323     log.debug("no matching revocation list");
324     return EMPTY(void*);
325 }