Update copyright.
[shibboleth/sp.git] / shib-target / ShibHTTPHook.cpp
1 /*
2  *  Copyright 2001-2007 Internet2
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 /* ShibHTTPHook.cpp - Shibboleth hook for SAML Binding with SSL callback
18
19    Scott Cantor
20    2/13/05
21    
22    $History:$
23 */
24
25 #include "internal.h"
26
27 #include <shibsp/SPConfig.h>
28 #include <xmltooling/security/OpenSSLTrustEngine.h>
29 #include <xmltooling/signature/OpenSSLCredentialResolver.h>
30
31 #include <saml/version.h>
32 #include <openssl/ssl.h>
33 #include <openssl/x509_vfy.h>
34
35 using namespace shibsp;
36 using namespace shibtarget;
37 using namespace shibboleth;
38 using namespace saml;
39 using namespace xmltooling;
40 using namespace log4cpp;
41 using namespace std;
42 using xmlsignature::OpenSSLCredentialResolver;
43
44 /*
45  * Our verifier callback is a front-end for invoking each trust plugin until
46  * success, or we run out of plugins.
47  */
48 static int verify_callback(X509_STORE_CTX* x509_ctx, void* arg)
49 {
50     Category::getInstance("OpenSSL").debug("invoking default X509 verify callback");
51 #if (OPENSSL_VERSION_NUMBER >= 0x00907000L)
52     ShibHTTPHook::ShibHTTPHookCallContext* ctx = reinterpret_cast<ShibHTTPHook::ShibHTTPHookCallContext*>(arg);
53 #else
54     // Yes, this sucks. I'd use TLS, but there's no really obvious spot to put the thread key
55     // and global variables suck too. We can't access the X509_STORE_CTX depth directly because
56     // OpenSSL only copies it into the context if it's >=0, and the unsigned pointer may be
57     // negative in the SSL structure's int member.
58     SSL* ssl = reinterpret_cast<SSL*>(X509_STORE_CTX_get_ex_data(x509_ctx,SSL_get_ex_data_X509_STORE_CTX_idx()));
59     ShibHTTPHook::ShibHTTPHookCallContext* ctx =
60         reinterpret_cast<ShibHTTPHook::ShibHTTPHookCallContext*>(SSL_get_verify_depth(ssl));
61 #endif
62
63     const OpenSSLTrustEngine* t = dynamic_cast<const OpenSSLTrustEngine*>(ctx->getHook()->getTrustEngine());
64     if (!t || !t->validate(x509_ctx->cert,x509_ctx->untrusted,*(ctx->getRoleDescriptor()),false)) { // bypass name check (handled for us)
65         x509_ctx->error=X509_V_ERR_APPLICATION_VERIFICATION;     // generic error, check log for plugin specifics
66         return 0;
67     }
68     
69     // Signal success. Hopefully it doesn't matter what's actually in the structure now.
70     return 1;
71 }
72
73 /*
74  * OpenSAML callback is invoked during SSL context setup, before the handshake.
75  * We use it to attach credentials and our own certificate verifier callback above.
76  */
77 static bool ssl_ctx_callback(void* ssl_ctx, void* userptr)
78 {
79 #ifdef _DEBUG
80     xmltooling::NDC("ssl_ctx_callback");
81 #endif
82     Category& log=Category::getInstance(SHIBT_LOGCAT".ShibHTTPHook");
83     
84     try {
85         log.debug("OpenSAML invoked SSL context callback");
86         ShibHTTPHook::ShibHTTPHookCallContext* ctx = reinterpret_cast<ShibHTTPHook::ShibHTTPHookCallContext*>(userptr);
87         const PropertySet* credUse=ctx->getCredentialUse();
88         pair<bool,const char*> TLS=credUse ? credUse->getString("TLS") : pair<bool,const char*>(false,NULL);
89         if (TLS.first) {
90             OpenSSLCredentialResolver* cr=dynamic_cast<OpenSSLCredentialResolver*>(SPConfig::getConfig().getServiceProvider()->getCredentialResolver(TLS.second));
91             if (cr) {
92                 xmltooling::Locker locker(cr);
93                 cr->attach(reinterpret_cast<SSL_CTX*>(ssl_ctx));
94             }
95             else
96                 log.error("unable to attach credentials to request using (%s), leaving anonymous",TLS.second);
97         }
98         else
99             log.warn("no TLS credentials supplied, leaving anonymous");
100         
101         SSL_CTX_set_verify(reinterpret_cast<SSL_CTX*>(ssl_ctx),SSL_VERIFY_PEER,NULL);
102 #if (OPENSSL_VERSION_NUMBER >= 0x00907000L)
103         // With 0.9.7, we can pass a callback argument directly.
104         SSL_CTX_set_cert_verify_callback(reinterpret_cast<SSL_CTX*>(ssl_ctx),verify_callback,userptr);
105 #else
106         // With 0.9.6, there's no argument, so we're going to use a really embarrassing hack and
107         // stuff the argument in the depth property where it will get copied to the context object
108         // that's handed to the callback.
109         SSL_CTX_set_cert_verify_callback(
110             reinterpret_cast<SSL_CTX*>(ssl_ctx),
111             reinterpret_cast<int (*)()>(verify_callback),
112             NULL
113             );
114         SSL_CTX_set_verify_depth(reinterpret_cast<SSL_CTX*>(ssl_ctx),reinterpret_cast<int>(userptr));
115
116 #endif
117     }
118     catch (SAMLException& e) {
119         log.error(string("caught a SAML exception while attaching credentials to request: ") + e.what());
120         return false;
121     }
122 #ifndef _DEBUG
123     catch (...) {
124         log.error("caught an unknown exception while attaching credentials to request");
125         return false;
126     }
127 #endif
128     
129     return true;
130 }
131
132 bool ShibHTTPHook::outgoing(HTTPClient* conn, void* globalCtx, void* callCtx)
133 {
134     // Sanity check...
135     if (globalCtx != this)
136         return false;
137
138     // Clear authn status.
139     reinterpret_cast<ShibHTTPHookCallContext*>(callCtx)->m_authenticated=false;
140          
141     // The callCtx is our nested context class. Copy in the parent pointer.
142     reinterpret_cast<ShibHTTPHookCallContext*>(callCtx)->m_hook=this;
143     
144     // The hook function is called before connecting to the HTTP server. This
145     // gives us a chance to attach our own SSL callback, and set a version header.
146     if (!conn->setSSLCallback(ssl_ctx_callback,callCtx))
147         return false;
148     
149     if (!conn->setRequestHeader("Shibboleth", PACKAGE_VERSION))
150         return false;
151     if (!conn->setRequestHeader("Xerces-C", XERCES_FULLVERSIONDOT))
152         return false;
153     if (!conn->setRequestHeader("XML-Security-C", XSEC_VERSION))
154         return false;
155     if (!conn->setRequestHeader("OpenSAML-C", OPENSAML_FULLVERSIONDOT))
156         return false;
157
158     // Check for HTTP authentication...
159     const PropertySet* credUse=reinterpret_cast<ShibHTTPHookCallContext*>(callCtx)->getCredentialUse();
160     pair<bool,const char*> authType=credUse ? credUse->getString("authType") : pair<bool,const char*>(false,NULL);
161     if (authType.first) {
162 #ifdef _DEBUG
163         saml::NDC("outgoing");
164 #endif
165         Category& log=Category::getInstance(SHIBT_LOGCAT".ShibHTTPHook");
166         HTTPClient::auth_t type=HTTPClient::auth_none;
167         pair<bool,const char*> username=credUse->getString("authUsername");
168         pair<bool,const char*> password=credUse->getString("authPassword");
169         if (!username.first || !password.first) {
170             log.error("HTTP authType (%s) specified but authUsername or authPassword was missing", authType.second);
171             return false;
172         }
173         else if (!strcmp(authType.second,"basic"))
174             type = HTTPClient::auth_basic;
175         else if (!strcmp(authType.second,"digest"))
176             type = HTTPClient::auth_digest;
177         else if (!strcmp(authType.second,"ntlm"))
178             type = HTTPClient::auth_ntlm;
179         else if (!strcmp(authType.second,"gss"))
180             type = HTTPClient::auth_gss;
181         else {
182             log.error("Unknown authType (%s) specified in CredentialUse element", authType.second);
183             return false;
184         }
185         log.debug("configured for HTTP authentication (method=%s, username=%s)", authType.second, username.second);
186         return conn->setAuth(type,username.second,password.second);
187     }
188
189     // The best we can do is assume authentication succeeds because when libcurl reuses
190     // SSL and HTTP connections, no callback is made. Since we always authenticate SSL connections,
191     // the caller should check that the protocol is https.
192     reinterpret_cast<ShibHTTPHookCallContext*>(callCtx)->setAuthenticated();
193     return true;
194 }