2 * Copyright 2001-2005 Internet2
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 /* ShibHTTPHook.cpp - Shibboleth hook for SAML Binding with SSL callback
27 #include <xmltooling/security/OpenSSLTrustEngine.h>
29 #include <saml/version.h>
30 #include <openssl/ssl.h>
31 #include <openssl/x509_vfy.h>
33 using namespace shibsp;
34 using namespace shibtarget;
35 using namespace shibboleth;
37 using namespace xmltooling;
38 using namespace log4cpp;
42 * Our verifier callback is a front-end for invoking each trust plugin until
43 * success, or we run out of plugins.
45 static int verify_callback(X509_STORE_CTX* x509_ctx, void* arg)
47 Category::getInstance("OpenSSL").debug("invoking default X509 verify callback");
48 #if (OPENSSL_VERSION_NUMBER >= 0x00907000L)
49 ShibHTTPHook::ShibHTTPHookCallContext* ctx = reinterpret_cast<ShibHTTPHook::ShibHTTPHookCallContext*>(arg);
51 // Yes, this sucks. I'd use TLS, but there's no really obvious spot to put the thread key
52 // and global variables suck too. We can't access the X509_STORE_CTX depth directly because
53 // OpenSSL only copies it into the context if it's >=0, and the unsigned pointer may be
54 // negative in the SSL structure's int member.
55 SSL* ssl = reinterpret_cast<SSL*>(X509_STORE_CTX_get_ex_data(x509_ctx,SSL_get_ex_data_X509_STORE_CTX_idx()));
56 ShibHTTPHook::ShibHTTPHookCallContext* ctx =
57 reinterpret_cast<ShibHTTPHook::ShibHTTPHookCallContext*>(SSL_get_verify_depth(ssl));
60 const OpenSSLTrustEngine* t = dynamic_cast<const OpenSSLTrustEngine*>(ctx->getHook()->getTrustEngine());
61 if (!t || !t->validate(x509_ctx->cert,x509_ctx->untrusted,*(ctx->getRoleDescriptor()),false)) { // bypass name check (handled for us)
62 x509_ctx->error=X509_V_ERR_APPLICATION_VERIFICATION; // generic error, check log for plugin specifics
66 // Signal success. Hopefully it doesn't matter what's actually in the structure now.
71 * OpenSAML callback is invoked during SSL context setup, before the handshake.
72 * We use it to attach credentials and our own certificate verifier callback above.
74 static bool ssl_ctx_callback(void* ssl_ctx, void* userptr)
77 saml::NDC("ssl_ctx_callback");
79 Category& log=Category::getInstance(SHIBT_LOGCAT".ShibHTTPHook");
82 log.debug("OpenSAML invoked SSL context callback");
83 ShibHTTPHook::ShibHTTPHookCallContext* ctx = reinterpret_cast<ShibHTTPHook::ShibHTTPHookCallContext*>(userptr);
84 const PropertySet* credUse=ctx->getCredentialUse();
85 pair<bool,const char*> TLS=credUse ? credUse->getString("TLS") : pair<bool,const char*>(false,NULL);
87 Credentials c(ctx->getHook()->getCredentialProviders());
88 const ICredResolver* cr=c.lookup(TLS.second);
92 log.error("unable to attach credentials to request using (%s), leaving anonymous",TLS.second);
95 log.warn("no TLS credentials supplied, leaving anonymous");
97 SSL_CTX_set_verify(reinterpret_cast<SSL_CTX*>(ssl_ctx),SSL_VERIFY_PEER,NULL);
98 #if (OPENSSL_VERSION_NUMBER >= 0x00907000L)
99 // With 0.9.7, we can pass a callback argument directly.
100 SSL_CTX_set_cert_verify_callback(reinterpret_cast<SSL_CTX*>(ssl_ctx),verify_callback,userptr);
102 // With 0.9.6, there's no argument, so we're going to use a really embarrassing hack and
103 // stuff the argument in the depth property where it will get copied to the context object
104 // that's handed to the callback.
105 SSL_CTX_set_cert_verify_callback(
106 reinterpret_cast<SSL_CTX*>(ssl_ctx),
107 reinterpret_cast<int (*)()>(verify_callback),
110 SSL_CTX_set_verify_depth(reinterpret_cast<SSL_CTX*>(ssl_ctx),reinterpret_cast<int>(userptr));
114 catch (SAMLException& e) {
115 log.error(string("caught a SAML exception while attaching credentials to request: ") + e.what());
120 log.error("caught an unknown exception while attaching credentials to request");
128 bool ShibHTTPHook::outgoing(HTTPClient* conn, void* globalCtx, void* callCtx)
131 if (globalCtx != this)
134 // Clear authn status.
135 reinterpret_cast<ShibHTTPHookCallContext*>(callCtx)->m_authenticated=false;
137 // The callCtx is our nested context class. Copy in the parent pointer.
138 reinterpret_cast<ShibHTTPHookCallContext*>(callCtx)->m_hook=this;
140 // The hook function is called before connecting to the HTTP server. This
141 // gives us a chance to attach our own SSL callback, and set a version header.
142 if (!conn->setSSLCallback(ssl_ctx_callback,callCtx))
145 if (!conn->setRequestHeader("Shibboleth", PACKAGE_VERSION))
147 if (!conn->setRequestHeader("Xerces-C", XERCES_FULLVERSIONDOT))
149 if (!conn->setRequestHeader("XML-Security-C", XSEC_VERSION))
151 if (!conn->setRequestHeader("OpenSAML-C", OPENSAML_FULLVERSIONDOT))
154 // Check for HTTP authentication...
155 const PropertySet* credUse=reinterpret_cast<ShibHTTPHookCallContext*>(callCtx)->getCredentialUse();
156 pair<bool,const char*> authType=credUse ? credUse->getString("authType") : pair<bool,const char*>(false,NULL);
157 if (authType.first) {
159 saml::NDC("outgoing");
161 Category& log=Category::getInstance(SHIBT_LOGCAT".ShibHTTPHook");
162 HTTPClient::auth_t type=HTTPClient::auth_none;
163 pair<bool,const char*> username=credUse->getString("authUsername");
164 pair<bool,const char*> password=credUse->getString("authPassword");
165 if (!username.first || !password.first) {
166 log.error("HTTP authType (%s) specified but authUsername or authPassword was missing", authType.second);
169 else if (!strcmp(authType.second,"basic"))
170 type = HTTPClient::auth_basic;
171 else if (!strcmp(authType.second,"digest"))
172 type = HTTPClient::auth_digest;
173 else if (!strcmp(authType.second,"ntlm"))
174 type = HTTPClient::auth_ntlm;
175 else if (!strcmp(authType.second,"gss"))
176 type = HTTPClient::auth_gss;
178 log.error("Unknown authType (%s) specified in CredentialUse element", authType.second);
181 log.debug("configured for HTTP authentication (method=%s, username=%s)", authType.second, username.second);
182 return conn->setAuth(type,username.second,password.second);
185 // The best we can do is assume authentication succeeds because when libcurl reuses
186 // SSL and HTTP connections, no callback is made. Since we always authenticate SSL connections,
187 // the caller should check that the protocol is https.
188 reinterpret_cast<ShibHTTPHookCallContext*>(callCtx)->setAuthenticated();