Moved property set classes to new library.
[shibboleth/cpp-sp.git] / xmlproviders / XMLTrust.cpp
index 7c989d0..ef697da 100644 (file)
@@ -1,50 +1,17 @@
-/* 
- * The Shibboleth License, Version 1. 
- * Copyright (c) 2002 
- * University Corporation for Advanced Internet Development, Inc. 
- * All rights reserved
+/*
+ *  Copyright 2001-2005 Internet2
  * 
- * 
- * Redistribution and use in source and binary forms, with or without 
- * modification, are permitted provided that the following conditions are met:
- * 
- * Redistributions of source code must retain the above copyright notice, this 
- * list of conditions and the following disclaimer.
- * 
- * Redistributions in binary form must reproduce the above copyright notice, 
- * this list of conditions and the following disclaimer in the documentation 
- * and/or other materials provided with the distribution, if any, must include 
- * the following acknowledgment: "This product includes software developed by 
- * the University Corporation for Advanced Internet Development 
- * <http://www.ucaid.edu>Internet2 Project. Alternately, this acknowledegement 
- * may appear in the software itself, if and wherever such third-party 
- * acknowledgments normally appear.
- * 
- * Neither the name of Shibboleth nor the names of its contributors, nor 
- * Internet2, nor the University Corporation for Advanced Internet Development, 
- * Inc., nor UCAID may be used to endorse or promote products derived from this 
- * software without specific prior written permission. For written permission, 
- * please contact shibboleth@shibboleth.org
- * 
- * Products derived from this software may not be called Shibboleth, Internet2, 
- * UCAID, or the University Corporation for Advanced Internet Development, nor 
- * may Shibboleth appear in their name, without prior written permission of the 
- * University Corporation for Advanced Internet Development.
- * 
- * 
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
- * AND WITH ALL FAULTS. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 
- * PARTICULAR PURPOSE, AND NON-INFRINGEMENT ARE DISCLAIMED AND THE ENTIRE RISK 
- * OF SATISFACTORY QUALITY, PERFORMANCE, ACCURACY, AND EFFORT IS WITH LICENSEE. 
- * IN NO EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY 
- * CORPORATION FOR ADVANCED INTERNET DEVELOPMENT, INC. BE LIABLE FOR ANY DIRECT, 
- * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 
- * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
- * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 /* XMLTrust.cpp - a trust implementation that uses an XML file
@@ -57,6 +24,7 @@
 
 #include "internal.h"
 
+#include <algorithm>
 #include <sys/types.h>
 #include <sys/stat.h>
 
@@ -116,7 +84,7 @@ namespace {
         ~XMLTrust();
 
     bool validate(void* certEE, const Iterator<void*>& certChain, const IRoleDescriptor* role, bool checkName=true);
-    bool validate(const saml::SAMLSignedObject& token, const IRoleDescriptor* role);
+    bool validate(const saml::SAMLSignedObject& token, const IRoleDescriptor* role, ITrust* certValidator=NULL);
 
     protected:
         virtual ReloadableXMLFileImpl* newImplementation(const char* pathname, bool first=true) const;
@@ -158,8 +126,10 @@ X509_STORE* XMLTrustImpl::KeyAuthority::getX509Store()
         log_openssl();
         return NULL;
     }
+#if (OPENSSL_VERSION_NUMBER >= 0x00907000L)
     X509_STORE_set_flags(store,X509_V_FLAG_CRL_CHECK_ALL);
-    
+#endif
+
     for (vector<X509*>::iterator j=m_certs.begin(); j!=m_certs.end(); j++) {
         if (!X509_STORE_add_cert(store,*j)) {
             log_openssl();
@@ -181,10 +151,8 @@ X509_STORE* XMLTrustImpl::KeyAuthority::getX509Store()
 
 XMLTrustImpl::KeyAuthority::~KeyAuthority()
 {
-    for (vector<X509*>::iterator i=m_certs.begin(); i!=m_certs.end(); i++)
-        X509_free(*i);
-    for (vector<X509_CRL*>::iterator j=m_crls.begin(); j!=m_crls.end(); j++)
-        X509_CRL_free(*j);
+    for_each(m_certs.begin(),m_certs.end(),X509_free);
+    for_each(m_crls.begin(),m_crls.end(),X509_CRL_free);
 }
 
 class KeyInfoNodeFilter : public DOMNodeFilter
@@ -216,7 +184,7 @@ void XMLTrustImpl::init()
 
         // Loop over the KeyAuthority elements.
         DOMNodeList* nlist=m_root->getElementsByTagNameNS(::XML::TRUST_NS,SHIB_L(KeyAuthority));
-        for (int i=0; nlist && i<nlist->getLength(); i++) {
+        for (unsigned int i=0; nlist && i<nlist->getLength(); i++) {
             auto_ptr<KeyAuthority> ka(new KeyAuthority());
             
             const DOMElement* e=static_cast<DOMElement*>(nlist->item(i));
@@ -237,7 +205,7 @@ void XMLTrustImpl::init()
             
             // Very rudimentary, grab up all the in-band X509Certificate elements, and flatten into one list.
             DOMNodeList* certlist=k_child->getElementsByTagNameNS(saml::XML::XMLSIG_NS,L(X509Certificate));
-            for (int j=0; certlist && j<certlist->getLength(); j++) {
+            for (unsigned int j=0; certlist && j<certlist->getLength(); j++) {
                 auto_ptr_char blob(certlist->item(j)->getFirstChild()->getNodeValue());
                 X509* x=B64_to_X509(blob.get());
                 if (x)
@@ -248,7 +216,7 @@ void XMLTrustImpl::init()
 
             // Now look for externally referenced objects.
             certlist=k_child->getElementsByTagNameNS(saml::XML::XMLSIG_NS,SHIB_L(RetrievalMethod));
-            for (int k=0; certlist && k<certlist->getLength(); k++) {
+            for (unsigned int k=0; certlist && k<certlist->getLength(); k++) {
                 DOMElement* cert=static_cast<DOMElement*>(certlist->item(k));
                 if (!XMLString::compareString(cert->getAttributeNS(NULL,SHIB_L(Type)),::XML::XMLSIG_RETMETHOD_RAWX509)) {
                     // DER format
@@ -270,7 +238,7 @@ void XMLTrustImpl::init()
 
             // Very rudimentary, grab up all the in-band X509CRL elements, and flatten into one list.
             certlist=k_child->getElementsByTagNameNS(saml::XML::XMLSIG_NS,SHIB_L(X509CRL));
-            for (int r=0; certlist && r<certlist->getLength(); r++) {
+            for (unsigned int r=0; certlist && r<certlist->getLength(); r++) {
                 auto_ptr_char blob(certlist->item(r)->getFirstChild()->getNodeValue());
                 X509_CRL* x=B64_to_CRL(blob.get());
                 if (x)
@@ -376,10 +344,8 @@ void XMLTrustImpl::init()
 
 XMLTrustImpl::~XMLTrustImpl()
 {
-    for (vector<KeyAuthority*>::iterator i=m_keyauths.begin(); i!=m_keyauths.end(); i++)
-        delete (*i);
-    for (vector<DSIGKeyInfoList*>::iterator j=m_keybinds.begin(); j!=m_keybinds.end(); j++)
-        delete (*j);
+    for_each(m_keyauths.begin(),m_keyauths.end(),xmltooling::cleanup<KeyAuthority>());
+    for_each(m_keybinds.begin(),m_keybinds.end(),xmltooling::cleanup<DSIGKeyInfoList>());
 }
 
 XMLTrust::XMLTrust(const DOMElement* e) : ReloadableXMLFile(e), m_delegate(NULL)
@@ -435,11 +401,10 @@ XMLTrust::XMLTrust(const DOMElement* e) : ReloadableXMLFile(e), m_delegate(NULL)
 XMLTrust::~XMLTrust()
 {
     delete m_delegate;
-    for (vector<KeyInfoResolver*>::iterator i=m_resolvers.begin(); i!=m_resolvers.end(); i++)
-        delete *i;
+    for_each(m_resolvers.begin(),m_resolvers.end(),xmltooling::cleanup<KeyInfoResolver>());
 }
 
-extern "C" int error_callback(int ok, X509_STORE_CTX* ctx)
+static int error_callback(int ok, X509_STORE_CTX* ctx)
 {
     if (!ok)
         Category::getInstance("OpenSSL").error("path validation failure: %s", X509_verify_cert_error_string(ctx->error));
@@ -457,7 +422,132 @@ bool XMLTrust::validate(void* certEE, const Iterator<void*>& certChain, const IR
     saml::NDC ndc("validate");
 #endif
     Category& log=Category::getInstance(XMLPROVIDERS_LOGCAT".Trust");
-    
+
+    if (checkName) {
+        // Before we do the cryptogprahy, check that the EE certificate "name" matches
+        // one of the acceptable key "names" for the signer.
+        vector<string> keynames;
+        
+        // Build a list of acceptable names. Transcode the possible key "names" to UTF-8.
+        // For some simple cases, this should handle UTF-8 encoded DNs in certificates.
+        Iterator<const IKeyDescriptor*> kd_i=role->getKeyDescriptors();
+        while (kd_i.hasNext()) {
+            const IKeyDescriptor* kd=kd_i.next();
+            if (kd->getUse()!=IKeyDescriptor::signing)
+                continue;
+            DSIGKeyInfoList* KIL=kd->getKeyInfo();
+            if (!KIL)
+                continue;
+            for (size_t s=0; s<KIL->getSize(); s++) {
+                const XMLCh* n=KIL->item(s)->getKeyName();
+                if (n) {
+                    auto_ptr<char> kn(toUTF8(n));
+                    keynames.push_back(kn.get());
+                }
+            }
+        }
+        auto_ptr<char> kn(toUTF8(role->getEntityDescriptor()->getId()));
+        keynames.push_back(kn.get());
+        
+        char buf[256];
+        X509* x=(X509*)certEE;
+        X509_NAME* subject=X509_get_subject_name(x);
+        if (subject) {
+            // One way is a direct match to the subject DN.
+            // Seems that the way to do the compare is to write the X509_NAME into a BIO.
+            BIO* b = BIO_new(BIO_s_mem());
+            BIO* b2 = BIO_new(BIO_s_mem());
+            BIO_set_mem_eof_return(b, 0);
+            BIO_set_mem_eof_return(b2, 0);
+            // The flags give us LDAP order instead of X.500, with a comma separator.
+            int len=X509_NAME_print_ex(b,subject,0,XN_FLAG_RFC2253);
+            string subjectstr,subjectstr2;
+            BIO_flush(b);
+            while ((len = BIO_read(b, buf, 255)) > 0) {
+                buf[len] = '\0';
+                subjectstr+=buf;
+            }
+            log.infoStream() << "certificate subject: " << subjectstr << CategoryStream::ENDLINE;
+            // The flags give us LDAP order instead of X.500, with a comma plus space separator.
+            len=X509_NAME_print_ex(b2,subject,0,XN_FLAG_RFC2253 + XN_FLAG_SEP_CPLUS_SPC - XN_FLAG_SEP_COMMA_PLUS);
+            BIO_flush(b2);
+            while ((len = BIO_read(b2, buf, 255)) > 0) {
+                buf[len] = '\0';
+                subjectstr2+=buf;
+            }
+            
+            // Check each keyname.
+            for (vector<string>::const_iterator n=keynames.begin(); n!=keynames.end(); n++) {
+#ifdef HAVE_STRCASECMP
+                if (!strcasecmp(n->c_str(),subjectstr.c_str()) || !strcasecmp(n->c_str(),subjectstr2.c_str())) {
+#else
+                if (!_stricmp(n->c_str(),subjectstr.c_str()) || !_stricmp(n->c_str(),subjectstr2.c_str())) {
+#endif
+                    log.info("matched full subject DN to a key name (%s)", n->c_str());
+                    checkName=false;
+                    break;
+                }
+            }
+            BIO_free(b);
+            BIO_free(b2);
+
+            if (checkName) {
+                log.debug("unable to match DN, trying TLS subjectAltName match");
+                STACK_OF(GENERAL_NAME)* altnames=(STACK_OF(GENERAL_NAME)*)X509_get_ext_d2i(x, NID_subject_alt_name, NULL, NULL);
+                if (altnames) {
+                    int numalts = sk_GENERAL_NAME_num(altnames);
+                    for (int an=0; !checkName && an<numalts; an++) {
+                        const GENERAL_NAME* check = sk_GENERAL_NAME_value(altnames, an);
+                        if (check->type==GEN_DNS || check->type==GEN_URI) {
+                            const char* altptr = (char*)ASN1_STRING_data(check->d.ia5);
+                            const int altlen = ASN1_STRING_length(check->d.ia5);
+                            
+                            for (vector<string>::const_iterator n=keynames.begin(); n!=keynames.end(); n++) {
+#ifdef HAVE_STRCASECMP
+                                if (!strncasecmp(altptr,n->c_str(),altlen)) {
+#else
+                                if (!_strnicmp(altptr,n->c_str(),altlen)) {
+#endif
+                                    log.info("matched DNS/URI subjectAltName to a key name (%s)", n->c_str());
+                                    checkName=false;
+                                    break;
+                                }
+                            }
+                        }
+                    }
+                    GENERAL_NAMES_free(altnames);
+                }
+                
+                if (checkName) {
+                    log.debug("unable to match subjectAltName, trying TLS CN match");
+                    memset(buf,0,sizeof(buf));
+                    if (X509_NAME_get_text_by_NID(subject,NID_commonName,buf,255)>0) {
+                        for (vector<string>::const_iterator n=keynames.begin(); n!=keynames.end(); n++) {
+#ifdef HAVE_STRCASECMP
+                            if (!strcasecmp(buf,n->c_str())) {
+#else
+                            if (!_stricmp(buf,n->c_str())) {
+#endif
+                                log.info("matched subject CN to a key name (%s)", n->c_str());
+                                checkName=false;
+                                break;
+                            }
+                        }
+                    }
+                    else
+                        log.warn("no common name in certificate subject");
+                }
+            }
+        }
+        else
+            log.error("certificate has no subject?!");
+    }
+
+    if (checkName) {
+        log.error("cannot match certificate subject against acceptable key names based on KeyDescriptors");
+        return false;
+    }
+
     lock();
     try {
         XMLTrustImpl* impl=dynamic_cast<XMLTrustImpl*>(getImplementation());
@@ -479,7 +569,8 @@ bool XMLTrust::validate(void* certEE, const Iterator<void*>& certChain, const IR
         names.push_back(role->getEntityDescriptor()->getId());
         const IEntitiesDescriptor* group=role->getEntityDescriptor()->getEntitiesDescriptor();
         while (group) {
-            names.push_back(group->getName());
+            if (group->getName())
+                names.push_back(group->getName());
             group=group->getEntitiesDescriptor();
         }
     
@@ -528,7 +619,7 @@ bool XMLTrust::validate(void* certEE, const Iterator<void*>& certChain, const IR
         // If we have a match, use the associated keyauth.
         X509_STORE* store=kauth->getX509Store();
         if (store) {
-            STACK_OF(X509)* untrusted=sk_X509_new(NULL);
+            STACK_OF(X509)* untrusted=sk_X509_new_null();
             certChain.reset();
             while (certChain.hasNext())
                 sk_X509_push(untrusted,(X509*)certChain.next());
@@ -547,12 +638,24 @@ bool XMLTrust::validate(void* certEE, const Iterator<void*>& certChain, const IR
                 return false;
             }
 #else
-            X509_STORE_CTX_init(&ctx,store,certEE,untrusted);
+            X509_STORE_CTX_init(&ctx,store,(X509*)certEE,untrusted);
 #endif
-            X509_STORE_CTX_set_depth(&ctx,kauth->m_depth+1);    // correct yet another OpenSSL/PXIX bug
+            X509_STORE_CTX_set_depth(&ctx,100);    // handle depth below
             X509_STORE_CTX_set_verify_cb(&ctx,error_callback);
             
             int ret=X509_verify_cert(&ctx);
+            if (ret==1) {
+                // Now see if the depth was acceptable by counting the number of intermediates.
+                int depth=sk_X509_num(ctx.chain)-2;
+                if (kauth->m_depth < depth) {
+                    log.error(
+                        "certificate chain was too long (%d intermediates, only %d allowed)",
+                        (depth==-1) ? 0 : depth,
+                        kauth->m_depth
+                        );
+                    ret=0;
+                }
+            }
             
             // Clean up...
             X509_STORE_CTX_cleanup(&ctx);
@@ -573,14 +676,14 @@ bool XMLTrust::validate(void* certEE, const Iterator<void*>& certChain, const IR
     return false;
 }
 
-bool XMLTrust::validate(const saml::SAMLSignedObject& token, const IRoleDescriptor* role)
+bool XMLTrust::validate(const saml::SAMLSignedObject& token, const IRoleDescriptor* role, ITrust* certValidator)
 {
     // The delegated trust plugin handles metadata keys and use of metadata extensions.
     // If it fails to find an inline key in metadata, then it will branch off to the
     // extended version and verify the token using the certificates inside it. At that
     // point, control will pass to the other virtual function above and we can handle
     // legacy KeyAuthority rules that way.
-    if (m_delegate->validate(token,role))
+    if (m_delegate->validate(token,role,certValidator ? certValidator : this))
         return true;
 
 #ifdef _DEBUG
@@ -647,7 +750,7 @@ bool XMLTrust::validate(const saml::SAMLSignedObject& token, const IRoleDescript
             // Any inline KeyInfo should ostensibly resolve to a key we can try.
             Iterator<KeyInfoResolver*> resolvers(m_resolvers);
             while (resolvers.hasNext()) {
-                XSECCryptoKey* key=((XSECKeyInfoResolver*)resolvers.next())->resolveKey(KIL);
+                XSECCryptoKey* key=((XSECKeyInfoResolver*)*resolvers.next())->resolveKey(KIL);
                 if (key) {
                     log.debug("resolved key, trying it...");
                     try {