diff --git a/src/XrdCrypto/XrdCryptoFactory.cc b/src/XrdCrypto/XrdCryptoFactory.cc index 03f4941f555..5c11e70a096 100644 --- a/src/XrdCrypto/XrdCryptoFactory.cc +++ b/src/XrdCrypto/XrdCryptoFactory.cc @@ -314,6 +314,16 @@ XrdCryptoX509ParseFile_t XrdCryptoFactory::X509ParseFile() return 0; } +//______________________________________________________________________________ +XrdCryptoX509ParseStack_t XrdCryptoFactory::X509ParseStack() +{ + // Return an instance of an implementation of a function + // to parse a stack supposed to contain for X509 certificates. + + ABSTRACTMETHOD("XrdCryptoFactory::X509ParseStack"); + return 0; +} + //______________________________________________________________________________ XrdCryptoX509ParseBucket_t XrdCryptoFactory::X509ParseBucket() { diff --git a/src/XrdCrypto/XrdCryptoFactory.hh b/src/XrdCrypto/XrdCryptoFactory.hh index 9ee1aabc267..b67464562b9 100644 --- a/src/XrdCrypto/XrdCryptoFactory.hh +++ b/src/XrdCrypto/XrdCryptoFactory.hh @@ -55,6 +55,7 @@ class XrdCryptoX509Chain; class XrdCryptogsiX509Chain; class XrdCryptoX509Crl; class XrdCryptoX509Req; +class XrdTlsPeerCerts; // // Prototypes for some Utility Functions @@ -79,6 +80,11 @@ typedef int (*XrdCryptoX509ChainToFile_t)(XrdCryptoX509Chain *, const char *); // certificates from file parsing typedef int (*XrdCryptoX509ParseFile_t)(const char *fname, XrdCryptoX509Chain *); + +// certificates from STACK_OF(X509*) +typedef int (*XrdCryptoX509ParseStack_t)(XrdTlsPeerCerts* pc, + XrdCryptoX509Chain *c); + // certificates from bucket parsing typedef int (*XrdCryptoX509ParseBucket_t)(XrdSutBucket *, XrdCryptoX509Chain *); @@ -173,6 +179,7 @@ public: virtual XrdCryptoX509VerifyCert_t X509VerifyCert(); virtual XrdCryptoX509VerifyChain_t X509VerifyChain(); virtual XrdCryptoX509ParseFile_t X509ParseFile(); + virtual XrdCryptoX509ParseStack_t X509ParseStack(); virtual XrdCryptoX509ParseBucket_t X509ParseBucket(); virtual XrdCryptoX509ExportChain_t X509ExportChain(); virtual XrdCryptoX509ChainToFile_t X509ChainToFile(); diff --git a/src/XrdCrypto/XrdCryptoX509Chain.cc b/src/XrdCrypto/XrdCryptoX509Chain.cc index 44bdf0b54e8..e840fc46dd7 100644 --- a/src/XrdCrypto/XrdCryptoX509Chain.cc +++ b/src/XrdCrypto/XrdCryptoX509Chain.cc @@ -308,6 +308,8 @@ void XrdCryptoX509Chain::PushBack(XrdCryptoX509 *c) end->SetNext(nc); end = nc; size++; + } else if (c) { + delete c; } // Search for the effective CA (the last one, in case of subCAs) diff --git a/src/XrdCrypto/XrdCryptosslAux.cc b/src/XrdCrypto/XrdCryptosslAux.cc index 992bd6bc8ac..81442c9efee 100644 --- a/src/XrdCrypto/XrdCryptosslAux.cc +++ b/src/XrdCrypto/XrdCryptosslAux.cc @@ -42,6 +42,7 @@ #include "XrdCrypto/XrdCryptosslRSA.hh" #include "XrdCrypto/XrdCryptosslX509.hh" #include "XrdCrypto/XrdCryptosslTrace.hh" +#include "XrdTls/XrdTlsPeerCerts.hh" #include // Error code from verification set by verify callback function @@ -376,6 +377,57 @@ int XrdCryptosslX509ChainToFile(XrdCryptoX509Chain *ch, const char *fn) return 0; } +//______________________________________________________________________________ +int XrdCryptosslX509ParseStack(XrdTlsPeerCerts* pc, XrdCryptoX509Chain *chain) +{ + EPNAME("X509ParseStack"); + int nci = 0; + // Make sure we got a chain where to add the certificates + if (!chain) { + DEBUG("chain undefined: can do nothing"); + return nci; + } + + if (pc->cert) { + XrdCryptoX509 *c = new XrdCryptosslX509(pc->cert); + + if (c) { + chain->PushBack(c); + nci ++; + } + } + + if (!pc->chain) { + return nci; + } + + for (int i=0; i < sk_X509_num(pc->chain); i++) { + X509 *cert = sk_X509_value(pc->chain, i); + XrdCryptoX509 *c = new XrdCryptosslX509(cert); + + if (c) { + // The SSL_get_peer_chain method does not increment the + // refcount; the XrdCryptoX509 object assumes it owns + // the X509* but also does not increment the refcount. + // Hence, we increment manually. +#if OPENSSL_VERSION_NUMBER < 0x010100000L + CRYPTO_add(&(cert->references), 1, CRYPTO_LOCK_X509); +#else + X509_up_ref(cert); +#endif + chain->PushBack(c); + } else { + X509_free(cert); + DEBUG("could not create certificate: memory exhausted?"); + chain->Reorder(); + return nci; + } + nci ++; + } + chain->Reorder(); + return nci; +} + //____________________________________________________________________________ int XrdCryptosslX509ParseFile(const char *fname, XrdCryptoX509Chain *chain) diff --git a/src/XrdCrypto/XrdCryptosslAux.hh b/src/XrdCrypto/XrdCryptosslAux.hh index b9d7daf8a7c..989d4c5db75 100644 --- a/src/XrdCrypto/XrdCryptosslAux.hh +++ b/src/XrdCrypto/XrdCryptosslAux.hh @@ -41,6 +41,9 @@ #define kSslKDFunDefLen 24 +//! Froward declaration +class XrdTlsPeerCerts; + // // Password-Based Key Derivation Function 2, specified in PKCS #5 // @@ -60,6 +63,8 @@ int XrdCryptosslX509ChainToFile(XrdCryptoX509Chain *c, const char *fn); int XrdCryptosslX509ParseFile(const char *fname, XrdCryptoX509Chain *c); // certificates from bucket parsing int XrdCryptosslX509ParseBucket(XrdSutBucket *b, XrdCryptoX509Chain *c); +// certificates from STACK_OF(X509*) +int XrdCryptosslX509ParseStack(XrdTlsPeerCerts* pc, XrdCryptoX509Chain *chain); // // Function to convert from ASN1 time format into UTC since Epoch (Jan 1, 1970) time_t XrdCryptosslASN1toUTC(const ASN1_TIME *tsn1); diff --git a/src/XrdCrypto/XrdCryptosslFactory.cc b/src/XrdCrypto/XrdCryptosslFactory.cc index f29c0266d65..ac9c12f4de7 100644 --- a/src/XrdCrypto/XrdCryptosslFactory.cc +++ b/src/XrdCrypto/XrdCryptosslFactory.cc @@ -429,6 +429,15 @@ XrdCryptoX509ParseFile_t XrdCryptosslFactory::X509ParseFile() return &XrdCryptosslX509ParseFile; } +//______________________________________________________________________________ +XrdCryptoX509ParseStack_t XrdCryptosslFactory::X509ParseStack() +{ + // Return an instance of an implementation of a function + // to parse a file supposed to contain for X509 certificates. + + return &XrdCryptosslX509ParseStack; +} + //______________________________________________________________________________ XrdCryptoX509ParseBucket_t XrdCryptosslFactory::X509ParseBucket() { diff --git a/src/XrdCrypto/XrdCryptosslFactory.hh b/src/XrdCrypto/XrdCryptosslFactory.hh index 6b63709fdd3..5d9cf6ea5b8 100644 --- a/src/XrdCrypto/XrdCryptosslFactory.hh +++ b/src/XrdCrypto/XrdCryptosslFactory.hh @@ -93,6 +93,7 @@ public: XrdCryptoX509VerifyCert_t X509VerifyCert(); XrdCryptoX509VerifyChain_t X509VerifyChain(); XrdCryptoX509ParseFile_t X509ParseFile(); + XrdCryptoX509ParseStack_t X509ParseStack(); XrdCryptoX509ParseBucket_t X509ParseBucket(); XrdCryptoX509ExportChain_t X509ExportChain(); XrdCryptoX509ChainToFile_t X509ChainToFile(); diff --git a/src/XrdHttp.cmake b/src/XrdHttp.cmake index 756b6b59c41..ef033df4145 100644 --- a/src/XrdHttp.cmake +++ b/src/XrdHttp.cmake @@ -24,6 +24,7 @@ if( BUILD_HTTP ) ${LIB_XRD_HTTP_UTILS} SHARED XrdHttp/XrdHttpProtocol.cc XrdHttp/XrdHttpProtocol.hh + XrdHttp/XrdHttpSecurity.cc XrdHttp/XrdHttpReq.cc XrdHttp/XrdHttpReq.hh XrdHttp/XrdHttpSecXtractor.hh XrdHttp/XrdHttpExtHandler.cc XrdHttp/XrdHttpExtHandler.hh @@ -40,6 +41,7 @@ if( BUILD_HTTP ) ${LIB_XRD_HTTP_UTILS} XrdServer XrdUtils + XrdCrypto ${CMAKE_DL_LIBS} pthread ${OPENSSL_LIBRARIES} diff --git a/src/XrdHttp/XrdHttpProtocol.cc b/src/XrdHttp/XrdHttpProtocol.cc index d7c3105f60a..ace201c12a6 100644 --- a/src/XrdHttp/XrdHttpProtocol.cc +++ b/src/XrdHttp/XrdHttpProtocol.cc @@ -32,7 +32,6 @@ #include "XrdSys/XrdSysE2T.hh" #include "XrdSys/XrdSysTimer.hh" #include "XrdOuc/XrdOucPinLoader.hh" - #include "XrdHttpTrace.hh" #include "XrdHttpProtocol.hh" @@ -56,7 +55,6 @@ #define XRHTTP_TK_GRACETIME 600 - /******************************************************************************/ /* G l o b a l s */ /******************************************************************************/ @@ -91,11 +89,11 @@ char *XrdHttpProtocol::sslcafile = 0; char *XrdHttpProtocol::secretkey = 0; char *XrdHttpProtocol::gridmap = 0; -XrdOucGMap *XrdHttpProtocol::servGMap = 0; // Grid mapping service - +bool XrdHttpProtocol::isRequiredGridmap = false; int XrdHttpProtocol::sslverifydepth = 9; BIO *XrdHttpProtocol::sslbio_err = 0; XrdHttpSecXtractor *XrdHttpProtocol::secxtractor = 0; +bool XrdHttpProtocol::isRequiredXtractor = false; struct XrdHttpProtocol::XrdHttpExtHandlerInfo XrdHttpProtocol::exthandler[MAX_XRDHTTPEXTHANDLERS]; int XrdHttpProtocol::exthandlercnt = 0; std::map< std::string, std::string > XrdHttpProtocol::hdr2cgimap; @@ -281,165 +279,9 @@ XrdProtocol *XrdHttpProtocol::Match(XrdLink *lp) { // Bind the protocol to the link and return the protocol // hp->Link = lp; - - return (XrdProtocol *) hp; } -/******************************************************************************/ -/* G e t V O M S D a t a */ -/******************************************************************************/ - -int XrdHttpProtocol::GetVOMSData(XrdLink *lp) { - TRACEI(DEBUG, " Extracting auth info."); - - X509 *peer_cert; - - // No external plugin, hence we fill our XrdSec with what we can do here - peer_cert = SSL_get_peer_certificate(ssl); - TRACEI(DEBUG, " SSL_get_peer_certificate returned :" << peer_cert); - ERR_print_errors(sslbio_err); - - if (peer_cert) { - char bufname[256]; - - // Add the original DN to the moninfo. Not sure if it makes sense to parametrize this or not. - if (SecEntity.moninfo) free(SecEntity.moninfo); - - // The original DN is not so univoque. In the case of a proxy this can be either - // the issuer name or the subject name - // Here we try to use the one that is known to the user mapping - - - - if (servGMap) { - int mape; - - SecEntity.moninfo = X509_NAME_oneline(X509_get_issuer_name(peer_cert), NULL, 0); - TRACEI(DEBUG, " Issuer name is : '" << SecEntity.moninfo << "'"); - - mape = servGMap->dn2user(SecEntity.moninfo, bufname, sizeof(bufname), 0); - if ( !mape && SecEntity.moninfo[0] ) { - TRACEI(DEBUG, " Mapping name: '" << SecEntity.moninfo << "' --> " << bufname); - if (SecEntity.name) free(SecEntity.name); - SecEntity.name = strdup(bufname); - } - else { - TRACEI(ALL, " Mapping name: '" << SecEntity.moninfo << "' Failed. err: " << mape); - - // Mapping the issuer name failed, let's try with the subject name - if (SecEntity.moninfo) free(SecEntity.moninfo); - SecEntity.moninfo = X509_NAME_oneline(X509_get_subject_name(peer_cert), NULL, 0); - TRACEI(DEBUG, " Subject name is : '" << SecEntity.moninfo << "'"); - - mape = servGMap->dn2user(SecEntity.moninfo, bufname, sizeof(bufname), 0); - if ( !mape ) { - TRACEI(DEBUG, " Mapping name: " << SecEntity.moninfo << " --> " << bufname); - if (SecEntity.name) free(SecEntity.name); - SecEntity.name = strdup(bufname); - } - else { - TRACEI(ALL, " Mapping name: " << SecEntity.moninfo << " Failed. err: " << mape); - } - } - - } - else { - - SecEntity.moninfo = X509_NAME_oneline(X509_get_subject_name(peer_cert), NULL, 0); - TRACEI(DEBUG, " Subject name is : '" << SecEntity.moninfo << "'"); - } - - if (!SecEntity.name) { - // Here we have the user DN, and try to extract an useful user name from it - if (SecEntity.name) free(SecEntity.name); - SecEntity.name = 0; - // To set the name we pick the first CN of the certificate subject - // and hope that it makes some sense, it usually does - char *lnpos = strstr(SecEntity.moninfo, "/CN="); - char bufname2[9]; - - - if (lnpos) { - lnpos += 4; - char *lnpos2 = index(lnpos, '/'); - if (lnpos2) { - int l = ( lnpos2-lnpos < (int)sizeof(bufname) ? lnpos2-lnpos : (int)sizeof(bufname)-1 ); - strncpy(bufname, lnpos, l); - bufname[l] = '\0'; - - // Here we have the string in the buffer. Take the last 8 non-space characters - size_t j = 8; - strcpy(bufname2, "unknown-\0"); // note it's 9 chars - for (int i = (int)strlen(bufname)-1; i >= 0; i--) { - if (isalnum(bufname[i])) { - j--; - bufname2[j] = bufname[i]; - if (j == 0) break; - } - - } - - SecEntity.name = strdup(bufname); - TRACEI(DEBUG, " Setting link name: '" << bufname2+j << "'"); - lp->setID(bufname2+j, 0); - } - } - } - - - // If we could not find anything good, take the last 8 non-space characters of the main subject - if (!SecEntity.name) { - size_t j = 8; - SecEntity.name = strdup("unknown-\0"); // note it's 9 chars - for (int i = (int)strlen(SecEntity.moninfo)-1; i >= 0; i--) { - if (isalnum(SecEntity.moninfo[i])) { - j--; - SecEntity.name[j] = SecEntity.moninfo[i]; - if (j == 0) break; - - } - } - } - - - - } - else return 0; // Don't fail if no cert - - if (peer_cert) X509_free(peer_cert); - - - - // Invoke our instance of the Security exctractor plugin - // This will fill the XrdSec thing with VOMS info, if VOMS is - // installed. If we have no sec extractor then do nothing, just plain https - // will work. - if (secxtractor) { - // We assume that if the sysadmin has assigned a gridmap file then he - // is interested in the mapped name, not the original one that would be - // overwritten inside the plugin - char *savestr = 0; - if (servGMap && SecEntity.name) - savestr = strdup(SecEntity.name); - - int r = secxtractor->GetSecData(lp, SecEntity, ssl); - - if (servGMap && savestr) { - if (SecEntity.name) - free(SecEntity.name); - SecEntity.name = savestr; - } - - - if (r) - TRACEI(ALL, " Certificate data extraction failed: " << SecEntity.moninfo << " Failed. err: " << r); - return r; - } - - return 0; -} - char *XrdHttpProtocol::GetClientIPStr() { char buf[256]; buf[0] = '\0'; @@ -684,25 +526,22 @@ int XrdHttpProtocol::Process(XrdLink *lp) // We ignore the argument here ssl = 0; return -1; } - BIO_set_nbio(sbio, 0); - res = SSL_get_verify_result(ssl); - TRACEI(DEBUG, " SSL_get_verify_result returned :" << res); + BIO_set_nbio(sbio, 0); ERR_print_errors(sslbio_err); strcpy(SecEntity.prot, "https"); - // Get the voms string and auth information - if (GetVOMSData(Link)) { + if (HandleAuthentication(Link)) { SSL_free(ssl); ssl = 0; return -1; } - - if (res != X509_V_OK) return -1; ssldone = true; - if (TRACING(TRACE_AUTH)) SecEntity.Display(eDest); + if (TRACING(TRACE_AUTH)) { + SecEntity.Display(eDest); + } } @@ -1773,36 +1612,7 @@ int XrdHttpProtocol::Configure(char *parms, XrdProtocol_Config * pi) { return 1; } -/******************************************************************************/ -/* I n i t S e c u r i t y */ -/******************************************************************************/ - -bool XrdHttpProtocol::InitSecurity() { - -// If GRID map file was specified, load the plugin for it -// - if (gridmap) { - XrdOucString pars; - if (XrdHttpTrace->What == TRACE_DEBUG) pars += "dbg|"; - - if (!(servGMap = XrdOucgetGMap(&eDest, gridmap, pars.c_str()))) { - eDest.Say("Error loading grid map file:", gridmap); - return false; - } - TRACE(ALL, "using grid map file: "<< gridmap); - } - -// If a secxtractor was specified, load that too. -// - if (secxtractor) - {SSL_CTX *sslctx = (SSL_CTX*)xrdctx->Context(); // Need to avoid this! - secxtractor->Init(sslctx, XrdHttpTrace->What); - } -// All done -// - return true; -} /******************************************************************************/ /* I n i t T L S */ @@ -2084,12 +1894,13 @@ int XrdHttpProtocol::xsslkey(XrdOucStream & Config) { /* Function: xgmap - Purpose: To parse the directive: gridmap + Purpose: To parse the directive: gridmap [required] - the path of the gridmap file to be used. Normally - it's /etc/grid-security/gridmap - No mapfile means no translation required - Pointing to a non existing mapfile is an error + required optional parameter which if present treats any grimap errors + as fatal. + the path of the gridmap file to be used. Normally it's + /etc/grid-security/gridmap. No mapfile means no translation + required. Pointing to a non existing mapfile is an error. Output: 0 upon success or !0 upon failure. */ @@ -2105,11 +1916,23 @@ int XrdHttpProtocol::xgmap(XrdOucStream & Config) { return 1; } + // Handle optional parameter "required" + // + if (!strncmp(val, "required", 8)) { + isRequiredGridmap = true; + val = Config.GetWord(); + + if (!val || !val[0]) { + eDest.Emsg("Config", "HTTP X509 gridmap file missing after [required] " + "parameter"); + return 1; + } + } + // Record the path // if (gridmap) free(gridmap); gridmap = strdup(val); - return 0; } @@ -2506,14 +2329,17 @@ int XrdHttpProtocol::xselfhttps2http(XrdOucStream & Config) { /* Function: xsecxtractor - Purpose: To parse the directive: secxtractor + Purpose: To parse the directive: secxtractor [required] - the path of the plugin to be loaded + required optional parameter which if present treats any secxtractor + errors as fatal. + the path of the plugin to be loaded + parameters passed to the secxtractor library Output: 0 upon success or !0 upon failure. */ -int XrdHttpProtocol::xsecxtractor(XrdOucStream & Config) { +int XrdHttpProtocol::xsecxtractor(XrdOucStream& Config) { char *val; // Get the path @@ -2523,6 +2349,19 @@ int XrdHttpProtocol::xsecxtractor(XrdOucStream & Config) { eDest.Emsg("Config", "No security extractor plugin specified."); return 1; } else { + // Handle optional parameter [required] + // + if (!strncmp(val, "required", 8)) { + isRequiredXtractor = true; + val = Config.GetWord(); + + if (!val || !val[0]) { + eDest.Emsg("Config", "No security extractor plugin after [required] " + "parameter"); + return 1; + } + } + char libName[4096]; strncpy(libName, val, sizeof(libName)); libName[sizeof(libName) - 1] = '\0'; diff --git a/src/XrdHttp/XrdHttpProtocol.hh b/src/XrdHttp/XrdHttpProtocol.hh index eb4e92fc99b..789d5c30d40 100644 --- a/src/XrdHttp/XrdHttpProtocol.hh +++ b/src/XrdHttp/XrdHttpProtocol.hh @@ -71,6 +71,7 @@ class XrdHttpSecXtractor; class XrdHttpExtHandler; struct XrdVersionInfo; class XrdOucGMap; +class XrdCryptoFactory; class XrdHttpProtocol : public XrdProtocol { @@ -151,10 +152,19 @@ private: /// Reset values, counters, in order to reutilize an object of this class void Reset(); + /// Handle authentication of the client + /// @return 0 if successful, otherwise error + int HandleAuthentication(XrdLink* lp); + /// After the SSL handshake, retrieve the VOMS info and the various stuff /// that is needed for autorization int GetVOMSData(XrdLink *lp); + // Handle gridmap file mapping if present + // + // @return 0 if successful, otherwise !0 + int HandleGridMap(XrdLink* lp); + /// Get up to blen bytes from the connection. Put them into mybuff. /// This primitive, for the way it is used, is not supposed to block int getDataOneShot(int blen, bool wait=false); @@ -186,6 +196,7 @@ private: static int xheader2cgi(XrdOucStream &Config); static int xhttpsmode(XrdOucStream &Config); + static bool isRequiredXtractor; // If true treat secxtractor errors as fatal static XrdHttpSecXtractor *secxtractor; // Loads the SecXtractor plugin, if available @@ -271,7 +282,7 @@ private: /// Flag to tell if the https handshake has finished, in the case of an https /// connection being established bool ssldone; - + static XrdCryptoFactory *myCryptoFactory; protected: @@ -337,6 +348,7 @@ protected: /// Gridmap file location. The same used by XrdSecGsi static char *gridmap;// [s] gridmap file [/etc/grid-security/gridmap] + static bool isRequiredGridmap; // If true treat gridmap errors as fatal /// The key used to calculate the url hashes static char *secretkey; diff --git a/src/XrdHttp/XrdHttpSecurity.cc b/src/XrdHttp/XrdHttpSecurity.cc new file mode 100644 index 00000000000..dd5ec0ddb31 --- /dev/null +++ b/src/XrdHttp/XrdHttpSecurity.cc @@ -0,0 +1,266 @@ +//------------------------------------------------------------------------------ +// This file is part of XrdHTTP: A pragmatic implementation of the +// HTTP/WebDAV protocol for the Xrootd framework +// +// Copyright (c) 2020 by European Organization for Nuclear Research (CERN) +// Author: Fabrizio Furano +//------------------------------------------------------------------------------ +// XRootD is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// XRootD is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with XRootD. If not, see . +//------------------------------------------------------------------------------ + +#include "XrdHttpProtocol.hh" +#include "XrdHttpTrace.hh" +#include "XrdHttpSecXtractor.hh" +#include "Xrd/XrdLink.hh" +#include "XrdCrypto/XrdCryptoX509Chain.hh" +#include "XrdCrypto/XrdCryptosslAux.hh" +#include "XrdCrypto/XrdCryptoFactory.hh" +#include "XrdTls/XrdTlsPeerCerts.hh" +#include "XrdTls/XrdTlsContext.hh" +#include "XrdOuc/XrdOucGMap.hh" + +namespace XrdHttpProtoInfo +{ + extern XrdTlsContext *xrdctx; +} + +XrdOucGMap *XrdHttpProtocol::servGMap = 0; // Grid mapping service +XrdCryptoFactory *XrdHttpProtocol::myCryptoFactory = 0; + +// Static definitions +#define TRACELINK lp + +using namespace XrdHttpProtoInfo; + +/******************************************************************************/ +/* I n i t S e c u r i t y */ +/******************************************************************************/ + +bool XrdHttpProtocol::InitSecurity() { +#ifdef HAVE_XRDCRYPTO + // Borrow the initialization of XrdCryptossl, in order to share the + // OpenSSL threading bits + if (!(myCryptoFactory = XrdCryptoFactory::GetCryptoFactory("ssl"))) { + eDest.Say("Error instantiating crypto factory ssl", ""); + return false; + } +#endif + +// If GRID map file was specified, load the plugin for it +// + if (gridmap) { + XrdOucString pars; + if (XrdHttpTrace->What == TRACE_DEBUG) pars += "dbg|"; + + if (!(servGMap = XrdOucgetGMap(&eDest, gridmap, pars.c_str()))) { + eDest.Say("Error loading grid map file:", gridmap); + return false; + } + TRACE(ALL, "using grid map file: "<< gridmap); + } + +// If a secxtractor was specified, load that too. +// + if (secxtractor) + {SSL_CTX *sslctx = (SSL_CTX*)xrdctx->Context(); // Need to avoid this! + secxtractor->Init(sslctx, XrdHttpTrace->What); + } + +// All done +// + return true; +} + +/******************************************************************************/ +/* H a n d l e A u t h e n t i c a t i o n */ +/******************************************************************************/ + +int +XrdHttpProtocol::HandleAuthentication(XrdLink* lp) +{ + EPNAME("HandleAuthentication"); + int rc_ssl = SSL_get_verify_result(ssl); + + if (rc_ssl) { + TRACEI(DEBUG, " SSL_get_verify_result returned :" << rc_ssl); + return 1; + } + + XrdTlsPeerCerts pc; + XrdCryptoX509Chain chain; + pc.cert = SSL_get_peer_certificate(ssl); + pc.chain = SSL_get_peer_cert_chain(ssl); + + if ((!pc.cert) || + (myCryptoFactory && !myCryptoFactory->X509ParseStack()(&pc, &chain))) { + TRACEI(DEBUG, "No certificate found in peer chain."); + chain.Cleanup(); + return 0; + } + + // Extract the DN for the current connection that will be used later on when + // handling the gridmap file + const char * dn = chain.EECname(); + + if (!dn) { + // X509Chain doesn't assume it owns the underlying certs unless + // you explicitly invoke the Cleanup method + TRACEI(DEBUG, "Failed to extract DN information."); + chain.Cleanup(); + return 1; + } + + if (SecEntity.moninfo) { + free(SecEntity.moninfo); + } + + SecEntity.moninfo = strdup(dn); + TRACEI(DEBUG, " Subject name is : '" << SecEntity.moninfo << "'"); + // X509Chain doesn't assume it owns the underlying certs unless + // you explicitly invoke the Cleanup method + chain.Cleanup(); + + if (GetVOMSData(lp)) { + TRACEI(DEBUG, " No VOMS information for DN: " << SecEntity.moninfo); + + if (isRequiredXtractor) { + eDest.Emsg(epname, "Failed extracting required VOMS info for DN: ", + SecEntity.moninfo); + return 1; + } + } + + return HandleGridMap(lp); +} + + +/******************************************************************************/ +/* H a n d l e G r i d M a p */ +/******************************************************************************/ + +int +XrdHttpProtocol::HandleGridMap(XrdLink* lp) +{ + EPNAME("HandleGridMap"); + char bufname[256]; + + if (servGMap) { + int mape = servGMap->dn2user(SecEntity.moninfo, bufname, sizeof(bufname), 0); + if ( !mape && SecEntity.moninfo[0] ) { + TRACEI(DEBUG, " Mapping name: '" << SecEntity.moninfo << "' --> " << bufname); + if (SecEntity.name) free(SecEntity.name); + SecEntity.name = strdup(bufname); + } + else { + TRACEI(ALL, " Mapping name: " << SecEntity.moninfo << " Failed. err: " << mape); + + if (isRequiredGridmap) { + eDest.Emsg(epname, "Required gridmap mapping failed for DN:", + SecEntity.moninfo); + return 1; + } + } + } + + if (!SecEntity.name) { + // Here we have the user DN, and try to extract an useful user name from it + if (SecEntity.name) free(SecEntity.name); + SecEntity.name = 0; + // To set the name we pick the first CN of the certificate subject + // and hope that it makes some sense, it usually does + char *lnpos = strstr(SecEntity.moninfo, "/CN="); + char bufname2[9]; + + + if (lnpos) { + lnpos += 4; + char *lnpos2 = index(lnpos, '/'); + if (lnpos2) { + int l = ( lnpos2-lnpos < (int)sizeof(bufname) ? lnpos2-lnpos : (int)sizeof(bufname)-1 ); + strncpy(bufname, lnpos, l); + bufname[l] = '\0'; + + // Here we have the string in the buffer. Take the last 8 non-space characters + size_t j = 8; + strcpy(bufname2, "unknown-\0"); // note it's 9 chars + for (int i = (int)strlen(bufname)-1; i >= 0; i--) { + if (isalnum(bufname[i])) { + j--; + bufname2[j] = bufname[i]; + if (j == 0) break; + } + + } + + SecEntity.name = strdup(bufname); + TRACEI(DEBUG, " Setting link name: '" << bufname2+j << "'"); + lp->setID(bufname2+j, 0); + } + } + } + + // If we could not find anything good, take the last 8 non-space characters of the main subject + if (!SecEntity.name) { + size_t j = 8; + SecEntity.name = strdup("unknown-\0"); // note it's 9 chars + for (int i = (int)strlen(SecEntity.moninfo)-1; i >= 0; i--) { + if (isalnum(SecEntity.moninfo[i])) { + j--; + SecEntity.name[j] = SecEntity.moninfo[i]; + if (j == 0) break; + } + } + } + + return 0; +} + + +/******************************************************************************/ +/* G e t V O M S D a t a */ +/******************************************************************************/ + +int XrdHttpProtocol::GetVOMSData(XrdLink *lp) +{ + TRACEI(DEBUG, " Extracting auth info."); + + // Invoke the Security exctractor plugin which will fill in the XrdSecEntity + // with VOMS info, if VOMS is installed. If we have no sec extractor then do + // nothing, just plain https will work. + if (secxtractor) { + // Note: this is kept for compatibility with XrdHttpVOMS which modified the + // SecEntity.name filed + char *savestr = 0; + + if (servGMap && SecEntity.name) { + savestr = strdup(SecEntity.name); + } + + int r = secxtractor->GetSecData(lp, SecEntity, ssl); + + if (servGMap && savestr) { + if (SecEntity.name) free(SecEntity.name); + SecEntity.name = savestr; + } + + if (r) { + TRACEI(ALL, " Certificate data extraction failed: " << SecEntity.moninfo + << " Failed. err: " << r); + } + + return r; + } + + return 0; +} diff --git a/src/XrdHttp/XrdHttpTrace.hh b/src/XrdHttp/XrdHttpTrace.hh index 8071c8f4c2f..124c4c1e3d6 100644 --- a/src/XrdHttp/XrdHttpTrace.hh +++ b/src/XrdHttp/XrdHttpTrace.hh @@ -80,6 +80,7 @@ extern const char *XrdHttpTraceID; XrdHttpTrace->End();} #define TRACING(x) XrdHttpTrace->What & x +#define EPNAME(x) static const char* epname = x; #else @@ -88,6 +89,7 @@ extern const char *XrdHttpTraceID; #define TRACEP(act,x) #define TRACES(act,x) #define TRACING(x) 0 +#define EPNAME(x) #endif #endif