Better handling for remote-user duplicate header.
[shibboleth/cpp-sp.git] / shib-target / ShibHTTPHook.cpp
1 /*
2  *  Copyright 2001-2005 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 <saml/version.h>
28 #include <openssl/ssl.h>
29 #include <openssl/x509_vfy.h>
30
31 using namespace shibtarget::logging;
32 using namespace shibtarget;
33 using namespace shibboleth;
34 using namespace saml;
35 using namespace std;
36
37 /*
38  * Our verifier callback is a front-end for invoking each trust plugin until
39  * success, or we run out of plugins.
40  */
41 static int verify_callback(X509_STORE_CTX* x509_ctx, void* arg)
42 {
43     Category& log = Category::getInstance("OpenSSL");
44     log.debug("invoking default X509 verify callback");
45     
46 #if (OPENSSL_VERSION_NUMBER >= 0x00907000L)
47     ShibHTTPHook::ShibHTTPHookCallContext* ctx = reinterpret_cast<ShibHTTPHook::ShibHTTPHookCallContext*>(arg);
48 #else
49     // Yes, this sucks. I'd use TLS, but there's no really obvious spot to put the thread key
50     // and global variables suck too. We can't access the X509_STORE_CTX depth directly because
51     // OpenSSL only copies it into the context if it's >=0, and the unsigned pointer may be
52     // negative in the SSL structure's int member.
53     SSL* ssl = reinterpret_cast<SSL*>(X509_STORE_CTX_get_ex_data(x509_ctx,SSL_get_ex_data_X509_STORE_CTX_idx()));
54     ShibHTTPHook::ShibHTTPHookCallContext* ctx =
55         reinterpret_cast<ShibHTTPHook::ShibHTTPHookCallContext*>(SSL_get_verify_depth(ssl));
56 #endif
57
58     // Instead of using the supplied verifier, we let the plugins do whatever they want to do
59     // with the untrusted certificates we find in the object. We can save a bit of memory by
60     // just building a vector that points at them inside the supplied structure.
61     vector<void*> chain;
62     for (int i=0; i<sk_X509_num(x509_ctx->untrusted); i++)
63         chain.push_back(sk_X509_value(x509_ctx->untrusted,i));
64     
65     Trust t(ctx->getHook()->getTrustProviders());
66     if (!t.validate(x509_ctx->cert,chain,ctx->getRoleDescriptor(),false)) { // bypass name check (handled for us)
67         x509_ctx->error=X509_V_ERR_APPLICATION_VERIFICATION;     // generic error, check log for plugin specifics
68         return 0;
69     }
70     
71     log.info("verified server's TLS key/certificate");
72     
73     // Signal success. Hopefully it doesn't matter what's actually in the structure now.
74     return 1;
75 }
76
77 /*
78  * OpenSAML callback is invoked during SSL context setup, before the handshake.
79  * We use it to attach credentials and our own certificate verifier callback above.
80  */
81 static bool ssl_ctx_callback(void* ssl_ctx, void* userptr)
82 {
83 #ifdef _DEBUG
84     saml::NDC("ssl_ctx_callback");
85 #endif
86     Category& log=Category::getInstance(SHIBT_LOGCAT".ShibHTTPHook");
87     
88     try {
89         log.debug("OpenSAML invoked SSL context callback");
90         ShibHTTPHook::ShibHTTPHookCallContext* ctx = reinterpret_cast<ShibHTTPHook::ShibHTTPHookCallContext*>(userptr);
91         const IPropertySet* credUse=ctx->getCredentialUse();
92         pair<bool,const char*> TLS=credUse ? credUse->getString("TLS") : pair<bool,const char*>(false,NULL);
93         if (TLS.first) {
94             Credentials c(ctx->getHook()->getCredentialProviders());
95             const ICredResolver* cr=c.lookup(TLS.second);
96             if (cr)
97                 cr->attach(ssl_ctx);
98             else
99                 log.error("unable to attach credentials to request using (%s), leaving anonymous",TLS.second);
100         }
101         else
102             log.warn("no TLS credentials supplied, leaving anonymous");
103         
104         SSL_CTX_set_verify(reinterpret_cast<SSL_CTX*>(ssl_ctx),SSL_VERIFY_PEER,NULL);
105 #if (OPENSSL_VERSION_NUMBER >= 0x00907000L)
106         // With 0.9.7, we can pass a callback argument directly.
107         SSL_CTX_set_cert_verify_callback(reinterpret_cast<SSL_CTX*>(ssl_ctx),verify_callback,userptr);
108 #else
109         // With 0.9.6, there's no argument, so we're going to use a really embarrassing hack and
110         // stuff the argument in the depth property where it will get copied to the context object
111         // that's handed to the callback.
112         SSL_CTX_set_cert_verify_callback(
113             reinterpret_cast<SSL_CTX*>(ssl_ctx),
114             reinterpret_cast<int (*)()>(verify_callback),
115             NULL
116             );
117         SSL_CTX_set_verify_depth(reinterpret_cast<SSL_CTX*>(ssl_ctx),reinterpret_cast<int>(userptr));
118
119 #endif
120     }
121     catch (SAMLException& e) {
122         log.error(string("caught a SAML exception while attaching credentials to request: ") + e.what());
123         return false;
124     }
125 #ifndef _DEBUG
126     catch (...) {
127         log.error("caught an unknown exception while attaching credentials to request");
128         return false;
129     }
130 #endif
131     
132     return true;
133 }
134
135 bool ShibHTTPHook::outgoing(HTTPClient* conn, void* globalCtx, void* callCtx)
136 {
137     // Sanity check...
138     if (globalCtx != this)
139         return false;
140
141     // Clear authn status.
142     reinterpret_cast<ShibHTTPHookCallContext*>(callCtx)->m_authenticated=false;
143          
144     // The callCtx is our nested context class. Copy in the parent pointer.
145     reinterpret_cast<ShibHTTPHookCallContext*>(callCtx)->m_hook=this;
146     
147     // The hook function is called before connecting to the HTTP server. This
148     // gives us a chance to attach our own SSL callback, and set a version header.
149     if (!conn->setSSLCallback(ssl_ctx_callback,callCtx))
150         return false;
151     
152     if (!conn->setRequestHeader("User-Agent", PACKAGE_NAME))
153         return false;
154     if (!conn->setRequestHeader(PACKAGE_NAME, PACKAGE_VERSION))
155         return false;
156     if (!conn->setRequestHeader("Xerces-C", XERCES_FULLVERSIONDOT))
157         return false;
158     if (!conn->setRequestHeader("XML-Security-C", XSEC_VERSION))
159         return false;
160     if (!conn->setRequestHeader("OpenSAML-C", OPENSAML_FULLVERSIONDOT))
161         return false;
162
163     // Check for HTTP authentication...
164     const IPropertySet* credUse=reinterpret_cast<ShibHTTPHookCallContext*>(callCtx)->getCredentialUse();
165     pair<bool,const char*> authType=credUse ? credUse->getString("authType") : pair<bool,const char*>(false,NULL);
166     if (authType.first) {
167 #ifdef _DEBUG
168         saml::NDC("outgoing");
169 #endif
170         Category& log=Category::getInstance(SHIBT_LOGCAT".ShibHTTPHook");
171         HTTPClient::auth_t type=HTTPClient::auth_none;
172         pair<bool,const char*> username=credUse->getString("authUsername");
173         pair<bool,const char*> password=credUse->getString("authPassword");
174         if (!username.first || !password.first) {
175             log.error("HTTP authType (%s) specified but authUsername or authPassword was missing", authType.second);
176             return false;
177         }
178         else if (!strcmp(authType.second,"basic"))
179             type = HTTPClient::auth_basic;
180         else if (!strcmp(authType.second,"digest"))
181             type = HTTPClient::auth_digest;
182         else if (!strcmp(authType.second,"ntlm"))
183             type = HTTPClient::auth_ntlm;
184         else if (!strcmp(authType.second,"gss"))
185             type = HTTPClient::auth_gss;
186         else {
187             log.error("Unknown authType (%s) specified in CredentialUse element", authType.second);
188             return false;
189         }
190         log.debug("configured for HTTP authentication (method=%s, username=%s)", authType.second, username.second);
191         return conn->setAuth(type,username.second,password.second);
192     }
193
194     // The best we can do is assume authentication succeeds because when libcurl reuses
195     // SSL and HTTP connections, no callback is made. Since we always authenticate SSL connections,
196     // the caller should check that the protocol is https.
197     reinterpret_cast<ShibHTTPHookCallContext*>(callCtx)->setAuthenticated();
198     return true;
199 }