XRootD
XrdHttpSecurity.cc
Go to the documentation of this file.
1 //------------------------------------------------------------------------------
2 // This file is part of XrdHTTP: A pragmatic implementation of the
3 // HTTP/WebDAV protocol for the Xrootd framework
4 //
5 // Copyright (c) 2020 by European Organization for Nuclear Research (CERN)
6 // Author: Fabrizio Furano <furano@cern.ch>
7 //------------------------------------------------------------------------------
8 // XRootD is free software: you can redistribute it and/or modify
9 // it under the terms of the GNU Lesser General Public License as published by
10 // the Free Software Foundation, either version 3 of the License, or
11 // (at your option) any later version.
12 //
13 // XRootD is distributed in the hope that it will be useful,
14 // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 // GNU General Public License for more details.
17 //
18 // You should have received a copy of the GNU Lesser General Public License
19 // along with XRootD. If not, see <http://www.gnu.org/licenses/>.
20 //------------------------------------------------------------------------------
21 
22 #include "XrdHttpProtocol.hh"
23 #include "XrdHttpTrace.hh"
24 #include "XrdHttpSecXtractor.hh"
25 #include "Xrd/XrdLink.hh"
31 #include "XrdTls/XrdTlsContext.hh"
32 #include "XrdOuc/XrdOucGMap.hh"
33 
34 namespace XrdHttpProtoInfo
35 {
36  extern XrdTlsContext *xrdctx;
37 }
38 
39 XrdOucGMap *XrdHttpProtocol::servGMap = 0; // Grid mapping service
40 XrdCryptoFactory *XrdHttpProtocol::myCryptoFactory = 0;
41 
42 // Static definitions
43 #define TRACELINK lp
44 
45 namespace
46 {
47 const char *TraceID = "Security";
48 }
49 
50 using namespace XrdHttpProtoInfo;
51 
52 /******************************************************************************/
53 /* I n i t S e c u r i t y */
54 /******************************************************************************/
55 
56 bool XrdHttpProtocol::InitSecurity() {
57 #ifdef HAVE_XRDCRYPTO
58  // Borrow the initialization of XrdCryptossl, in order to share the
59  // OpenSSL threading bits
60  if (!(myCryptoFactory = XrdCryptoFactory::GetCryptoFactory("ssl"))) {
61  eDest.Say("Error instantiating crypto factory ssl", "");
62  return false;
63  }
64 #endif
65 
66 // If GRID map file was specified, load the plugin for it
67 //
68  if (gridmap) {
69  XrdOucString pars;
70  if (XrdHttpTrace.What & TRACE_DEBUG) pars += "dbg|";
71 
72  if (!(servGMap = XrdOucgetGMap(&eDest, gridmap, pars.c_str()))) {
73  eDest.Say("Error loading grid map file:", gridmap);
74  return false;
75  }
76  TRACE(ALL, "using grid map file: "<< gridmap);
77  }
78 
79 // If a secxtractor was specified, load that too.
80 //
81  if (secxtractor)
82  {SSL_CTX *sslctx = (SSL_CTX*)xrdctx->Context(); // Need to avoid this!
83  secxtractor->Init(sslctx, XrdHttpTrace.What);
84  }
85 
86 // All done
87 //
88  return true;
89 }
90 
91 /******************************************************************************/
92 /* H a n d l e A u t h e n t i c a t i o n */
93 /******************************************************************************/
94 
95 int
96 XrdHttpProtocol::HandleAuthentication(XrdLink* lp)
97 {
98  EPNAME("HandleAuthentication");
99  int rc_ssl = SSL_get_verify_result(ssl);
100 
101  if (rc_ssl) {
102  TRACEI(DEBUG, " SSL_get_verify_result returned :" << rc_ssl);
103  return 1;
104  }
105 
106  XrdTlsPeerCerts pc(SSL_get_peer_certificate(ssl),SSL_get_peer_cert_chain(ssl));
107  XrdCryptoX509Chain chain;
108 
109  if ((!pc.hasCert()) ||
110  (myCryptoFactory && !myCryptoFactory->X509ParseStack()(&pc, &chain))) {
111  TRACEI(DEBUG, "No certificate found in peer chain.");
112  chain.Cleanup();
113  return 0;
114  }
115 
116  // Extract the DN for the current connection that will be used later on when
117  // handling the gridmap file
118  const char * dn = chain.EECname();
119  const char * eechash = chain.EEChash();
120 
121  if (!dn || !eechash) {
122  // X509Chain doesn't assume it owns the underlying certs unless
123  // you explicitly invoke the Cleanup method
124  TRACEI(DEBUG, "Failed to extract DN information.");
125  chain.Cleanup();
126  return 1;
127  }
128 
129  if (SecEntity.moninfo) {
130  free(SecEntity.moninfo);
131  }
132 
133  SecEntity.moninfo = strdup(dn);
134  TRACEI(DEBUG, " Subject name is : '" << SecEntity.moninfo << "'; hash is " << eechash);
135  // X509Chain doesn't assume it owns the underlying certs unless
136  // you explicitly invoke the Cleanup method
137 
138  if (GetVOMSData(lp)) {
139  TRACEI(DEBUG, " No VOMS information for DN: " << SecEntity.moninfo);
140 
141  if (isRequiredXtractor) {
142  eDest.Emsg(epname, "Failed extracting required VOMS info for DN: ",
143  SecEntity.moninfo);
144  chain.Cleanup();
145  return 1;
146  }
147  }
148 
149  auto retval = HandleGridMap(lp, eechash);
150  chain.Cleanup();
151  return retval;
152 }
153 
154 
155 /******************************************************************************/
156 /* H a n d l e G r i d M a p */
157 /******************************************************************************/
158 
159 int
160 XrdHttpProtocol::HandleGridMap(XrdLink* lp, const char * eechash)
161 {
162  EPNAME("HandleGridMap");
163  char bufname[256];
164 
165  if (servGMap) {
166  int mape = servGMap->dn2user(SecEntity.moninfo, bufname, sizeof(bufname), 0);
167  if ( !mape && SecEntity.moninfo[0] ) {
168  TRACEI(DEBUG, " Mapping name: '" << SecEntity.moninfo << "' --> " << bufname);
169  if (SecEntity.name) free(SecEntity.name);
170  SecEntity.name = strdup(bufname);
171  SecEntity.eaAPI->Add("gridmap.name", "1", true);
172  }
173  else {
174  TRACEI(ALL, " Mapping name: " << SecEntity.moninfo << " Failed. err: " << mape);
175 
176  if (isRequiredGridmap) {
177  eDest.Emsg(epname, "Required gridmap mapping failed for DN:",
178  SecEntity.moninfo);
179  return 1;
180  }
181  }
182  }
183 
184  if (!SecEntity.name && !compatNameGeneration) {
185  TRACEI(DEBUG, " Will fallback name to subject hash: " << eechash);
186  SecEntity.name = strdup(eechash);
187  return 0;
188  }
189 
190  if (!SecEntity.name) {
191  // Here we have the user DN, and try to extract an useful user name from it
192  if (SecEntity.name) free(SecEntity.name);
193  SecEntity.name = 0;
194  // To set the name we pick the first CN of the certificate subject
195  // and hope that it makes some sense, it usually does
196  char *lnpos = strstr(SecEntity.moninfo, "/CN=");
197  char bufname2[9];
198 
199 
200  if (lnpos) {
201  lnpos += 4;
202  char *lnpos2 = index(lnpos, '/');
203  if (lnpos2) {
204  int l = ( lnpos2-lnpos < (int)sizeof(bufname) ? lnpos2-lnpos : (int)sizeof(bufname)-1 );
205  strncpy(bufname, lnpos, l);
206  bufname[l] = '\0';
207 
208  // Here we have the string in the buffer. Take the last 8 non-space characters
209  size_t j = 8;
210  strcpy(bufname2, "unknown-"); // note it's 8 chars + '\0' at the end
211  for (int i = (int)strlen(bufname)-1; i >= 0; i--) {
212  if (isalnum(bufname[i])) {
213  j--;
214  bufname2[j] = bufname[i];
215  if (j == 0) break;
216  }
217 
218  }
219 
220  SecEntity.name = strdup(bufname);
221  TRACEI(DEBUG, " Setting link name: '" << bufname2+j << "'");
222  lp->setID(bufname2+j, 0);
223  }
224  }
225  }
226 
227  // If we could not find anything good, take the last 8 non-space characters of the main subject
228  if (!SecEntity.name) {
229  size_t j = 8;
230  SecEntity.name = strdup("unknown-\0"); // note it's 9 chars
231  for (int i = (int)strlen(SecEntity.moninfo)-1; i >= 0; i--) {
232  if (isalnum(SecEntity.moninfo[i])) {
233  j--;
234  SecEntity.name[j] = SecEntity.moninfo[i];
235  if (j == 0) break;
236  }
237  }
238  }
239 
240  return 0;
241 }
242 
243 
244 /******************************************************************************/
245 /* G e t V O M S D a t a */
246 /******************************************************************************/
247 
248 int XrdHttpProtocol::GetVOMSData(XrdLink *lp)
249 {
250  TRACEI(DEBUG, " Extracting auth info.");
251 
252  // Invoke the Security exctractor plugin which will fill in the XrdSecEntity
253  // with VOMS info, if VOMS is installed. If we have no sec extractor then do
254  // nothing, just plain https will work.
255  if (secxtractor) {
256  // Note: this is kept for compatibility with XrdHttpVOMS which modified the
257  // SecEntity.name filed
258  char *savestr = 0;
259 
260  if (servGMap && SecEntity.name) {
261  savestr = strdup(SecEntity.name);
262  }
263 
264  int r = secxtractor->GetSecData(lp, SecEntity, ssl);
265 
266  if (servGMap && savestr) {
267  if (SecEntity.name) free(SecEntity.name);
268  SecEntity.name = savestr;
269  }
270 
271  if (r) {
272  TRACEI(ALL, " Certificate data extraction failed: " << SecEntity.moninfo
273  << " Failed. err: " << r);
274  }
275 
276  return r;
277  }
278 
279  return 0;
280 }
#define DEBUG(x)
Definition: XrdBwmTrace.hh:54
#define EPNAME(x)
Definition: XrdBwmTrace.hh:56
static XrdSysError eDest(0,"crypto_")
XrdSysTrace XrdHttpTrace("http")
A pragmatic implementation of the HTTP/DAV protocol for the Xrd framework.
Trace definitions.
XrdOucGMap * XrdOucgetGMap(XrdOucGMapArgs)
Definition: XrdOucGMap.cc:92
#define TRACE_DEBUG
Definition: XrdTrace.hh:36
#define TRACE(act, x)
Definition: XrdTrace.hh:63
#define TRACEI(act, x)
Definition: XrdTrace.hh:66
static XrdCryptoFactory * GetCryptoFactory(const char *factoryname)
void Cleanup(bool keepCA=0)
static XrdOucGMap * servGMap
The instance of the DN mapper. Created only when a valid path is given.
const char * c_str() const
int Emsg(const char *esfx, int ecode, const char *text1, const char *text2=0)
Definition: XrdSysError.cc:95
void Say(const char *text1, const char *text2=0, const char *txt3=0, const char *text4=0, const char *text5=0, const char *txt6=0)
Definition: XrdSysError.cc:141
void * Context()
XrdTlsContext * xrdctx