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  // Borrow the initialization of XrdCryptossl, in order to share the
58  // OpenSSL threading bits
59  if (!(myCryptoFactory = XrdCryptoFactory::GetCryptoFactory("ssl"))) {
60  eDest.Say("Error instantiating crypto factory ssl", "");
61  return false;
62  }
63 
64 // If GRID map file was specified, load the plugin for it
65 //
66  if (gridmap) {
67  XrdOucString pars;
68  if (XrdHttpTrace.What & TRACE_DEBUG) pars += "dbg|";
69 
70  if (!(servGMap = XrdOucgetGMap(&eDest, gridmap, pars.c_str()))) {
71  eDest.Say("Error loading grid map file:", gridmap);
72  return false;
73  }
74  TRACE(ALL, "using grid map file: "<< gridmap);
75  }
76 
77 // If a secxtractor was specified, load that too.
78 //
79  if (secxtractor)
80  {SSL_CTX *sslctx = (SSL_CTX*)xrdctx->Context(); // Need to avoid this!
81  secxtractor->Init(sslctx, XrdHttpTrace.What);
82  }
83 
84 // All done
85 //
86  return true;
87 }
88 
89 /******************************************************************************/
90 /* H a n d l e A u t h e n t i c a t i o n */
91 /******************************************************************************/
92 
93 int
94 XrdHttpProtocol::HandleAuthentication(XrdLink* lp)
95 {
96  EPNAME("HandleAuthentication");
97  int rc_ssl = SSL_get_verify_result(ssl);
98 
99  if (rc_ssl) {
100  TRACEI(DEBUG, " SSL_get_verify_result returned :" << rc_ssl);
101  return 1;
102  }
103 
104  XrdTlsPeerCerts pc(SSL_get_peer_certificate(ssl),SSL_get_peer_cert_chain(ssl));
105  XrdCryptoX509Chain chain;
106 
107  if ((!pc.hasCert()) ||
108  (myCryptoFactory && !myCryptoFactory->X509ParseStack()(&pc, &chain))) {
109  TRACEI(DEBUG, "No certificate found in peer chain.");
110  chain.Cleanup();
111  return 0;
112  }
113 
114  // Extract the DN for the current connection that will be used later on when
115  // handling the gridmap file
116  const char * dn = chain.EECname();
117  const char * eechash = chain.EEChash();
118 
119  if (!dn || !eechash) {
120  // X509Chain doesn't assume it owns the underlying certs unless
121  // you explicitly invoke the Cleanup method
122  TRACEI(DEBUG, "Failed to extract DN information.");
123  chain.Cleanup();
124  return 1;
125  }
126 
127  if (SecEntity.moninfo) {
128  free(SecEntity.moninfo);
129  }
130 
131  SecEntity.moninfo = strdup(dn);
132  TRACEI(DEBUG, " Subject name is : '" << SecEntity.moninfo << "'; hash is " << eechash);
133  // X509Chain doesn't assume it owns the underlying certs unless
134  // you explicitly invoke the Cleanup method
135 
136  if (GetVOMSData(lp)) {
137  TRACEI(DEBUG, " No VOMS information for DN: " << SecEntity.moninfo);
138 
139  if (isRequiredXtractor) {
140  eDest.Emsg(epname, "Failed extracting required VOMS info for DN: ",
141  SecEntity.moninfo);
142  chain.Cleanup();
143  return 1;
144  }
145  }
146 
147  auto retval = HandleGridMap(lp, eechash);
148  chain.Cleanup();
149  return retval;
150 }
151 
152 
153 /******************************************************************************/
154 /* H a n d l e G r i d M a p */
155 /******************************************************************************/
156 
157 int
158 XrdHttpProtocol::HandleGridMap(XrdLink* lp, const char * eechash)
159 {
160  EPNAME("HandleGridMap");
161  char bufname[256];
162 
163  if (servGMap) {
164  int mape = servGMap->dn2user(SecEntity.moninfo, bufname, sizeof(bufname), 0);
165  if ( !mape && SecEntity.moninfo[0] ) {
166  TRACEI(DEBUG, " Mapping name: '" << SecEntity.moninfo << "' --> " << bufname);
167  if (SecEntity.name) free(SecEntity.name);
168  SecEntity.name = strdup(bufname);
169  SecEntity.eaAPI->Add("gridmap.name", "1", true);
170  }
171  else {
172  TRACEI(ALL, " Mapping name: " << SecEntity.moninfo << " Failed. err: " << mape);
173 
174  if (isRequiredGridmap) {
175  eDest.Emsg(epname, "Required gridmap mapping failed for DN:",
176  SecEntity.moninfo);
177  return 1;
178  }
179  }
180  }
181 
182  if (!SecEntity.name && !compatNameGeneration) {
183  TRACEI(DEBUG, " Will fallback name to subject hash: " << eechash);
184  SecEntity.name = strdup(eechash);
185  return 0;
186  }
187 
188  if (!SecEntity.name) {
189  // Here we have the user DN, and try to extract an useful user name from it
190  if (SecEntity.name) free(SecEntity.name);
191  SecEntity.name = 0;
192  // To set the name we pick the first CN of the certificate subject
193  // and hope that it makes some sense, it usually does
194  char *lnpos = strstr(SecEntity.moninfo, "/CN=");
195  char bufname2[9];
196 
197 
198  if (lnpos) {
199  lnpos += 4;
200  char *lnpos2 = index(lnpos, '/');
201  if (lnpos2) {
202  int l = ( lnpos2-lnpos < (int)sizeof(bufname) ? lnpos2-lnpos : (int)sizeof(bufname)-1 );
203  strncpy(bufname, lnpos, l);
204  bufname[l] = '\0';
205 
206  // Here we have the string in the buffer. Take the last 8 non-space characters
207  size_t j = 8;
208  strcpy(bufname2, "unknown-"); // note it's 8 chars + '\0' at the end
209  for (int i = (int)strlen(bufname)-1; i >= 0; i--) {
210  if (isalnum(bufname[i])) {
211  j--;
212  bufname2[j] = bufname[i];
213  if (j == 0) break;
214  }
215 
216  }
217 
218  SecEntity.name = strdup(bufname);
219  TRACEI(DEBUG, " Setting link name: '" << bufname2+j << "'");
220  lp->setID(bufname2+j, 0);
221  }
222  }
223  }
224 
225  // If we could not find anything good, take the last 8 non-space characters of the main subject
226  if (!SecEntity.name) {
227  size_t j = 8;
228  SecEntity.name = strdup("unknown-\0"); // note it's 9 chars
229  for (int i = (int)strlen(SecEntity.moninfo)-1; i >= 0; i--) {
230  if (isalnum(SecEntity.moninfo[i])) {
231  j--;
232  SecEntity.name[j] = SecEntity.moninfo[i];
233  if (j == 0) break;
234  }
235  }
236  }
237 
238  return 0;
239 }
240 
241 
242 /******************************************************************************/
243 /* G e t V O M S D a t a */
244 /******************************************************************************/
245 
246 int XrdHttpProtocol::GetVOMSData(XrdLink *lp)
247 {
248  TRACEI(DEBUG, " Extracting auth info.");
249 
250  // Invoke the Security exctractor plugin which will fill in the XrdSecEntity
251  // with VOMS info, if VOMS is installed. If we have no sec extractor then do
252  // nothing, just plain https will work.
253  if (secxtractor) {
254  // Note: this is kept for compatibility with XrdHttpVOMS which modified the
255  // SecEntity.name filed
256  char *savestr = 0;
257 
258  if (servGMap && SecEntity.name) {
259  savestr = strdup(SecEntity.name);
260  }
261 
262  int r = secxtractor->GetSecData(lp, SecEntity, ssl);
263 
264  if (servGMap && savestr) {
265  if (SecEntity.name) free(SecEntity.name);
266  SecEntity.name = savestr;
267  }
268 
269  if (r) {
270  TRACEI(ALL, " Certificate data extraction failed: " << SecEntity.moninfo
271  << " Failed. err: " << r);
272  }
273 
274  return r;
275  }
276 
277  return 0;
278 }
#define DEBUG(x)
Definition: XrdBwmTrace.hh:54
#define EPNAME(x)
Definition: XrdBwmTrace.hh:56
XrdSysError eDest(0, "HttpMon")
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:116
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:162
XrdTlsContext * xrdctx