Add XML-based client SSL config.
[shibboleth/cpp-sp.git] / shib / XMLTrust.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 /* XMLTrust.h - a trust 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 <openssl/err.h>
64 #include <openssl/x509_vfy.h>
65
66 #include <log4cpp/Category.hh>
67 #include <xercesc/framework/URLInputSource.hpp>
68 #include <xercesc/util/regx/RegularExpression.hpp>
69 #include <xsec/enc/OpenSSL/OpenSSLCryptoX509.hpp>
70
71 using namespace shibboleth;
72 using namespace saml;
73 using namespace log4cpp;
74 using namespace std;
75
76 int verify_callback(int ok, X509_STORE_CTX* store)
77 {
78     if (!ok)
79         Category::getInstance("OpenSSL").error(X509_verify_cert_error_string(store->error));
80     return ok;
81 }
82
83 class shibboleth::XMLTrustImpl
84 {
85 public:
86     XMLTrustImpl(const char* pathname);
87     ~XMLTrustImpl();
88     
89     struct KeyAuthority
90     {
91         KeyAuthority() : m_store(NULL), m_depth(0), m_type(authority) {}
92         ~KeyAuthority();
93         X509_STORE* getX509Store(bool cached=true);
94
95         vector<XSECCryptoX509*> m_certs;
96         vector<X509*> m_nativecerts;
97         X509_STORE* m_store;
98         unsigned short m_depth;
99         enum {authority, entity} m_type;
100     };
101     
102     vector<KeyAuthority*> m_keyauths;
103     typedef map<pair<const XMLCh*,bool>,KeyAuthority*> BindingMap;
104     BindingMap m_bindings;
105     
106     DOMDocument* m_doc;
107 };
108
109 X509_STORE* XMLTrustImpl::KeyAuthority::getX509Store(bool cached)
110 {
111     // We cache them once they're built...
112     if (m_store && cached)
113         return m_store;
114     
115     NDC ndc("getX509Store");
116     Category& log=Category::getInstance(SHIB_LOGCAT".XMLTrust");
117
118     // Do we need to convert to native cert format?
119     if (m_nativecerts.empty())
120     {
121         for (vector<XSECCryptoX509*>::const_iterator i=m_certs.begin(); i!=m_certs.end(); i++)
122         {
123             X509* x509=B64_to_X509((*i)->getDEREncodingSB().rawCharBuffer());
124             if (!x509)
125             {
126                 log.warn("failed to parse X509 buffer: %s", (*i)->getDEREncodingSB().rawCharBuffer());
127                 continue;
128             }
129             m_nativecerts.push_back(x509);
130         }
131     }
132     
133     // Load the cert vector into a store.
134     X509_STORE* store=NULL;
135     if (!(store=X509_STORE_new()))
136     {
137         log_openssl();
138         return NULL;
139     }
140     
141     X509_STORE_set_verify_cb_func(store,verify_callback);
142
143     for (vector<X509*>::iterator j=m_nativecerts.begin(); j!=m_nativecerts.end(); j++)
144     {
145         if (!X509_STORE_add_cert(store,X509_dup(*j)))
146         {
147             log_openssl();
148             log.warn("failed to add cert: %s", (*j)->name);
149             continue;
150         }
151     }
152
153     if (cached)
154         m_store=store;
155     return store;
156 }
157
158 XMLTrustImpl::KeyAuthority::~KeyAuthority()
159 {
160     if (m_store)
161         X509_STORE_free(m_store);
162     for (vector<X509*>::iterator i=m_nativecerts.begin(); i!=m_nativecerts.end(); i++)
163         X509_free(*i);
164     for (vector<XSECCryptoX509*>::iterator j=m_certs.begin(); j!=m_certs.end(); j++)
165         delete (*j);
166 }
167
168 XMLTrustImpl::XMLTrustImpl(const char* pathname) : m_doc(NULL)
169 {
170     NDC ndc("XMLTrustImpl");
171     Category& log=Category::getInstance(SHIB_LOGCAT".XMLTrustImpl");
172
173     saml::XML::Parser p;
174     try
175     {
176         static XMLCh base[]={chLatin_f, chLatin_i, chLatin_l, chLatin_e, chColon, chForwardSlash, chForwardSlash, chForwardSlash, chNull};
177         URLInputSource src(base,pathname);
178         Wrapper4InputSource dsrc(&src,false);
179         m_doc=p.parse(dsrc);
180
181         log.infoStream() << "Loaded and parsed trust file (" << pathname << ")" << CategoryStream::ENDLINE;
182
183         DOMElement* e = m_doc->getDocumentElement();
184         if (XMLString::compareString(XML::SHIB_NS,e->getNamespaceURI()) ||
185             XMLString::compareString(SHIB_L(Trust),e->getLocalName()))
186         {
187             log.error("Construction requires a valid trust file: (shib:Trust as root element)");
188             throw MetadataException("Construction requires a valid trust file: (shib:Trust as root element)");
189         }
190
191         // Loop over the KeyAuthority elements.
192         DOMNodeList* nlist=e->getElementsByTagNameNS(XML::SHIB_NS,SHIB_L(KeyAuthority));
193         for (int i=0; nlist && i<nlist->getLength(); i++)
194         {
195             KeyAuthority* ka=new KeyAuthority();
196             m_keyauths.push_back(ka);
197             
198             const XMLCh* depth=static_cast<DOMElement*>(nlist->item(i))->getAttributeNS(NULL,SHIB_L(VerifyDepth));
199             if (depth && *depth)
200                 ka->m_depth=XMLString::parseInt(depth);
201             
202             const XMLCh* type=static_cast<DOMElement*>(nlist->item(i))->getAttributeNS(NULL,SHIB_L(Type));
203             if (type && *type==chLatin_e)
204                 ka->m_type=KeyAuthority::entity;
205
206             // Very rudimentary, grab up all the X509Certificate elements, and flatten into one list.
207             DOMNodeList* certlist=static_cast<DOMElement*>(nlist->item(i))->getElementsByTagNameNS(
208                 saml::XML::XMLSIG_NS,L(X509Certificate)
209                 );
210             for (int j=0; certlist && j<certlist->getLength(); j++)
211             {
212                 auto_ptr<char> blob(XMLString::transcode(certlist->item(j)->getFirstChild()->getNodeValue()));
213                 XSECCryptoX509* cert=new OpenSSLCryptoX509();
214                 cert->loadX509Base64Bin(blob.get(),strlen(blob.get()));
215                 ka->m_certs.push_back(cert);
216             }
217             
218             // Now map the subjects to the list of certs.
219             DOMNodeList* subs=static_cast<DOMElement*>(nlist->item(i))->getElementsByTagNameNS(XML::SHIB_NS,L(Subject));
220             int k=0;
221             while (subs && k<subs->getLength())
222             {
223                 const XMLCh* name=subs->item(k)->getFirstChild()->getNodeValue();
224                 if (name && *name)
225                 {
226                     static const XMLCh one[]={ chDigit_1, chNull };
227                     static const XMLCh tru[]={ chLatin_t, chLatin_r, chLatin_u, chLatin_e, chNull };
228                     const XMLCh* regexp=
229                         static_cast<DOMElement*>(subs->item(k))->getAttributeNS(NULL,SHIB_L(regexp));
230                     bool flag=(!XMLString::compareString(regexp,one) || !XMLString::compareString(regexp,tru));
231                     m_bindings[pair<const XMLCh*,bool>(name,flag)]=ka;
232                 }
233                 k++;
234             }
235             // If no Subjects, this is a catch-all binding.
236             if (k==0)
237                 m_bindings[pair<const XMLCh*,bool>(NULL,false)]=ka;
238         }
239     }
240     catch (SAMLException& e)
241     {
242         log.errorStream() << "XML error while parsing trust configuration: " << e.what() << CategoryStream::ENDLINE;
243         for (vector<KeyAuthority*>::iterator i=m_keyauths.begin(); i!=m_keyauths.end(); i++)
244             delete (*i);
245         if (m_doc)
246             m_doc->release();
247         throw;
248     }
249     catch (...)
250     {
251         log.error("Unexpected error while parsing trust configuration");
252         for (vector<KeyAuthority*>::iterator i=m_keyauths.begin(); i!=m_keyauths.end(); i++)
253             delete (*i);
254         if (m_doc)
255             m_doc->release();
256         throw;
257     }
258 }
259
260 XMLTrustImpl::~XMLTrustImpl()
261 {
262     for (vector<KeyAuthority*>::iterator i=m_keyauths.begin(); i!=m_keyauths.end(); i++)
263         delete (*i);
264     if (m_doc)
265         m_doc->release();
266 }
267
268 XMLTrust::XMLTrust(const char* pathname) : m_filestamp(0), m_source(pathname), m_impl(NULL)
269 {
270 #ifdef WIN32
271     struct _stat stat_buf;
272     if (_stat(pathname, &stat_buf) == 0)
273 #else
274     struct stat stat_buf;
275     if (stat(pathname, &stat_buf) == 0)
276 #endif
277         m_filestamp=stat_buf.st_mtime;
278     m_impl=new XMLTrustImpl(pathname);
279     m_lock=RWLock::create();
280 }
281
282 XMLTrust::~XMLTrust()
283 {
284     delete m_lock;
285     delete m_impl;
286 }
287
288 void XMLTrust::lock()
289 {
290     m_lock->rdlock();
291
292     // Check if we need to refresh.
293 #ifdef WIN32
294     struct _stat stat_buf;
295     if (_stat(m_source.c_str(), &stat_buf) == 0)
296 #else
297     struct stat stat_buf;
298     if (stat(m_source.c_str(), &stat_buf) == 0)
299 #endif
300     {
301         if (m_filestamp>0 && m_filestamp<stat_buf.st_mtime)
302         {
303             // Elevate lock and recheck.
304             m_lock->unlock();
305             m_lock->wrlock();
306             if (m_filestamp>0 && m_filestamp<stat_buf.st_mtime)
307             {
308                 try
309                 {
310                     XMLTrustImpl* new_mapper=new XMLTrustImpl(m_source.c_str());
311                     delete m_impl;
312                     m_impl=new_mapper;
313                     m_filestamp=stat_buf.st_mtime;
314                     m_lock->unlock();
315                 }
316                 catch(SAMLException& e)
317                 {
318                     m_lock->unlock();
319                     saml::NDC ndc("lock");
320                     Category::getInstance(SHIB_LOGCAT".XMLTrust").error("failed to reload trust metadata, sticking with what we have: %s", e.what());
321                 }
322 /*                catch(...)
323                 {
324                     m_lock->unlock();
325                     saml::NDC ndc("lock");
326                     Category::getInstance(SHIB_LOGCAT".XMLTrust").error("caught an unknown exception, sticking with what we have");
327                 } */
328             }
329             else
330             {
331                 m_lock->unlock();
332             }
333             m_lock->rdlock();
334         }
335     }
336 }
337
338 void XMLTrust::unlock()
339 {
340     m_lock->unlock();
341 }
342
343 Iterator<XSECCryptoX509*> XMLTrust::getCertificates(const XMLCh* subject) const
344 {
345     // Find the first matching entity binding.
346     for (XMLTrustImpl::BindingMap::const_iterator i=m_impl->m_bindings.begin(); i!=m_impl->m_bindings.end(); i++)
347     {
348         if (i->second->m_type!=XMLTrustImpl::KeyAuthority::entity)
349             continue;
350             
351         if (i->first.first==NULL)   // catch-all entry
352         {
353             return i->second->m_certs;
354         }
355         else if (i->first.second)   // regexp
356         {
357             try
358             {
359                 RegularExpression re(i->first.first);
360                 if (re.matches(subject))
361                     return i->second->m_certs;
362             }
363             catch (XMLException& ex)
364             {
365                 auto_ptr<char> tmp(XMLString::transcode(ex.getMessage()));
366                 NDC ndc("getCertificates");
367                 Category& log=Category::getInstance(SHIB_LOGCAT".XMLTrust");
368                 log.errorStream() << "caught exception while parsing regular expression: " << tmp.get()
369                     << CategoryStream::ENDLINE;
370             }
371         }
372         else if (!XMLString::compareString(subject,i->first.first))     // exact match
373         {
374             return i->second->m_certs;
375         }
376     }
377     return EMPTY(XSECCryptoX509*);
378 }
379
380 bool XMLTrust::attach(const ISite* site, SSL_CTX* ctx) const
381 {
382     NDC ndc("attach");
383
384     // Use the matching bindings.
385     for (XMLTrustImpl::BindingMap::const_iterator i=m_impl->m_bindings.begin(); i!=m_impl->m_bindings.end(); i++)
386     {
387         if (i->second->m_type!=XMLTrustImpl::KeyAuthority::authority)
388             continue;
389
390         bool match=false;
391         if (i->first.first==NULL)   // catch-all entry
392         {
393             match=true;
394         }
395         else if (i->first.second)   // regexp
396         {
397             try
398             {
399                 RegularExpression re(i->first.first);
400                 if (re.matches(site->getName()))
401                     match=true;
402                 else
403                 {
404                     Iterator<const XMLCh*> groups=site->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".XMLTrust");
414                 log.errorStream() << "caught exception while parsing regular expression: " << tmp.get()
415                     << CategoryStream::ENDLINE;
416             }
417         }
418         else if (!XMLString::compareString(site->getName(),i->first.first))     // exact match
419         {
420             match=true;
421         }
422         else
423         {
424             Iterator<const XMLCh*> groups=site->getGroups();
425             while (!match && groups.hasNext())
426                 if (!XMLString::compareString(i->first.first,groups.next()))
427                     match=true;
428         }
429
430         // If we have a match, use the associated keyauth and load it into the context's store.
431         if (match)
432         {
433             X509_STORE* store=i->second->getX509Store(false);
434             if (store)
435             {
436                 SSL_CTX_set_cert_store(ctx,store);
437                 SSL_CTX_set_verify_depth(ctx,i->second->m_depth);
438             }
439             return true;
440         }
441     }
442
443     return false;
444 }
445
446 bool XMLTrust::validate(const ISite* site, Iterator<XSECCryptoX509*> certs) const
447 {
448     vector<const XMLCh*> temp;
449     while (certs.hasNext())
450         temp.push_back(certs.next()->getDEREncodingSB().sbStrToXMLCh());
451     return validate(site,temp);
452 }
453
454 bool XMLTrust::validate(const ISite* site, Iterator<const XMLCh*> certs) const
455 {
456     NDC ndc("validate");
457
458     STACK_OF(X509)* chain=sk_X509_new_null();
459     while (certs.hasNext())
460     {
461         auto_ptr<char> temp(XMLString::transcode(certs.next()));
462         X509* x=B64_to_X509(temp.get());
463         if (!x)
464         {
465             sk_X509_pop_free(chain,X509_free);
466             return false;
467         }
468         sk_X509_push(chain,x);
469     }
470
471     // Use the matching bindings.
472     for (XMLTrustImpl::BindingMap::const_iterator i=m_impl->m_bindings.begin(); i!=m_impl->m_bindings.end(); i++)
473     {
474         if (i->second->m_type!=XMLTrustImpl::KeyAuthority::authority)
475             continue;
476
477         bool match=false;
478         if (i->first.first==NULL)   // catch-all entry
479         {
480             match=true;
481         }
482         else if (i->first.second)   // regexp
483         {
484             try
485             {
486                 RegularExpression re(i->first.first);
487                 if (re.matches(site->getName()))
488                     match=true;
489                 else
490                 {
491                     Iterator<const XMLCh*> groups=site->getGroups();
492                     while (!match && groups.hasNext())
493                         if (re.matches(groups.next()))
494                             match=true;
495                 }
496             }
497             catch (XMLException& ex)
498             {
499                 auto_ptr<char> tmp(XMLString::transcode(ex.getMessage()));
500                 Category& log=Category::getInstance(SHIB_LOGCAT".XMLTrust");
501                 log.errorStream() << "caught exception while parsing regular expression: " << tmp.get()
502                     << CategoryStream::ENDLINE;
503             }
504         }
505         else if (!XMLString::compareString(site->getName(),i->first.first))     // exact match
506         {
507             match=true;
508         }
509         else
510         {
511             Iterator<const XMLCh*> groups=site->getGroups();
512             while (!match && groups.hasNext())
513                 if (!XMLString::compareString(i->first.first,groups.next()))
514                     match=true;
515         }
516
517         // If we have a match, use the associated keyauth and do a verify against the store.
518         if (match)
519         {
520             X509_STORE* store=i->second->getX509Store();
521             if (store)
522             {
523                 X509_STORE_CTX* ctx=X509_STORE_CTX_new();
524                 if (!ctx)
525                 {
526                     log_openssl();
527                     return false;
528                 }
529
530 #if (OPENSSL_VERSION_NUMBER > 0x009070000L)
531                 if (X509_STORE_CTX_init(ctx,store,sk_X509_value(chain,0),chain)!=1)
532                 {
533                     log_openssl();
534                     sk_X509_pop_free(chain,X509_free);
535                     return false;
536                 }
537 #else
538                 X509_STORE_CTX_init(ctx,store,sk_X509_value(chain,0),chain);
539 #endif
540                 if (i->second->m_depth)
541                     X509_STORE_CTX_set_depth(ctx,i->second->m_depth);
542                 if (X509_verify_cert(ctx)==1)
543                 {
544                     sk_X509_pop_free(chain,X509_free);
545                     X509_STORE_CTX_free(ctx);
546                     return true;
547                 }
548                 X509_STORE_CTX_free(ctx);
549             }
550         }
551     }
552
553     return false;
554 }