|
19 | 19 | // along with XRootD. If not, see <http://www.gnu.org/licenses/>. |
20 | 20 | //------------------------------------------------------------------------------ |
21 | 21 |
|
| 22 | +#include <cctype> |
| 23 | +#include <cstdio> |
| 24 | +#include <cstring> |
| 25 | +#include <map> |
| 26 | +#include <string> |
| 27 | +#include <vector> |
| 28 | + |
| 29 | +#include <openssl/evp.h> |
| 30 | + |
22 | 31 | #include "XrdHttpProtocol.hh" |
23 | 32 | #include "XrdHttpTrace.hh" |
24 | 33 | #include "XrdHttpSecXtractor.hh" |
| 34 | +#include "XrdSec/XrdSecLoadSecurity.hh" |
| 35 | +#include "XrdSec/XrdSecInterface.hh" |
| 36 | +#include "XrdOuc/XrdOucErrInfo.hh" |
25 | 37 | #include "Xrd/XrdLink.hh" |
26 | 38 | #include "XrdCrypto/XrdCryptoX509Chain.hh" |
27 | 39 | #include "XrdCrypto/XrdCryptosslAux.hh" |
@@ -49,6 +61,81 @@ const char *TraceID = "Security"; |
49 | 61 |
|
50 | 62 | using namespace XrdHttpProtoInfo; |
51 | 63 |
|
| 64 | +namespace |
| 65 | +{ |
| 66 | + |
| 67 | +std::string bearerTokenKey(const char *tok, int tlen) |
| 68 | +{ |
| 69 | + unsigned char md[EVP_MAX_MD_SIZE]; |
| 70 | + unsigned int mdLen = 0; |
| 71 | + if (!tok || tlen <= 0 |
| 72 | + || !EVP_Digest(tok, static_cast<size_t>(tlen), md, &mdLen, EVP_sha256(), nullptr)) |
| 73 | + return {}; |
| 74 | + char hex[EVP_MAX_MD_SIZE * 2 + 1]; |
| 75 | + for (unsigned int i = 0; i < mdLen; ++i) |
| 76 | + snprintf(hex + i * 2, sizeof(hex) - i * 2, "%02x", md[i]); |
| 77 | + return std::string(hex, mdLen * 2); |
| 78 | +} |
| 79 | + |
| 80 | +bool hasPrefix(const char *s, const char *end, const char *pfx) |
| 81 | +{ |
| 82 | + while (*pfx && s < end && *s == *pfx) {++s; ++pfx;} |
| 83 | + return !*pfx; |
| 84 | +} |
| 85 | + |
| 86 | +const char *stripBearerToken(const char *bTok, int &sz) |
| 87 | +{ |
| 88 | + const char *sTok = bTok; |
| 89 | + sz = 0; |
| 90 | + if (!sTok) return nullptr; |
| 91 | + |
| 92 | + const char *endPtr = sTok + strlen(sTok); |
| 93 | + while (sTok < endPtr && isspace(static_cast<unsigned char>(*sTok))) ++sTok; |
| 94 | + if (sTok >= endPtr) return nullptr; |
| 95 | + |
| 96 | + if ((endPtr - sTok) >= 9 && hasPrefix(sTok, endPtr, "Bearer%20")) sTok += 9; |
| 97 | + else if ((endPtr - sTok) >= 7 && hasPrefix(sTok, endPtr, "Bearer ")) sTok += 7; |
| 98 | + |
| 99 | + while (sTok < endPtr && isspace(static_cast<unsigned char>(*sTok))) ++sTok; |
| 100 | + if (sTok >= endPtr) return nullptr; |
| 101 | + |
| 102 | + while (endPtr > sTok && isspace(static_cast<unsigned char>(*(endPtr - 1)))) --endPtr; |
| 103 | + sz = static_cast<int>(endPtr - sTok); |
| 104 | + return (sz > 0 ? sTok : nullptr); |
| 105 | +} |
| 106 | + |
| 107 | +void copyEntityAttrs(XrdSecEntity &dst, const XrdSecEntity &src) |
| 108 | +{ |
| 109 | + for (const auto &key : src.eaAPI->Keys()) |
| 110 | + {std::string val; |
| 111 | + if (src.eaAPI->Get(key, val)) dst.eaAPI->Add(key, val, true); |
| 112 | + } |
| 113 | +} |
| 114 | + |
| 115 | +bool extractBearerToken(const XrdHttpReq &req, std::string &token) |
| 116 | +{ |
| 117 | + const std::string &cgi = req.hdr2cgistr; |
| 118 | + const char *key = "authz="; |
| 119 | + size_t pos = cgi.find(key); |
| 120 | + if (pos != std::string::npos) |
| 121 | + {size_t start = pos + strlen(key); |
| 122 | + size_t end = cgi.find('&', start); |
| 123 | + token = (end == std::string::npos ? cgi.substr(start) |
| 124 | + : cgi.substr(start, end - start)); |
| 125 | + return !token.empty(); |
| 126 | + } |
| 127 | + |
| 128 | + for (const auto &hdr : req.allheaders) |
| 129 | + {if (!strcasecmp(hdr.first.c_str(), "authorization")) |
| 130 | + {token = hdr.second; |
| 131 | + return !token.empty(); |
| 132 | + } |
| 133 | + } |
| 134 | + return false; |
| 135 | +} |
| 136 | + |
| 137 | +} // namespace |
| 138 | + |
52 | 139 | /******************************************************************************/ |
53 | 140 | /* I n i t S e c u r i t y */ |
54 | 141 | /******************************************************************************/ |
@@ -81,11 +168,125 @@ bool XrdHttpProtocol::InitSecurity() { |
81 | 168 | secxtractor->Init(sslctx, XrdHttpTrace.What); |
82 | 169 | } |
83 | 170 |
|
| 171 | +// Load the security framework when HTTP bearer OIDC is enabled. |
| 172 | +// |
| 173 | + if (oidcHttpMode && !CIA) |
| 174 | + {if (!oidcConfigFN) |
| 175 | + {eDest.Say("Error: http.oidc requires a configuration file path"); |
| 176 | + return false; |
| 177 | + } |
| 178 | + if (!(CIA = XrdSecLoadSecService(&eDest, oidcConfigFN))) |
| 179 | + {eDest.Say("Error loading security framework for http.oidc"); |
| 180 | + return false; |
| 181 | + } |
| 182 | + } |
| 183 | + |
84 | 184 | // All done |
85 | 185 | // |
86 | 186 | return true; |
87 | 187 | } |
88 | 188 |
|
| 189 | +/******************************************************************************/ |
| 190 | +/* H a n d l e O I D C A u t h e n t i c a t i o n */ |
| 191 | +/******************************************************************************/ |
| 192 | + |
| 193 | +int |
| 194 | +XrdHttpProtocol::HandleOidcAuthentication() |
| 195 | +{ |
| 196 | +#undef TRACELINK |
| 197 | +#define TRACELINK Link |
| 198 | + |
| 199 | + if (!oidcHttpMode || !CIA) return 0; |
| 200 | + |
| 201 | + // Client-certificate identity is fixed for the TLS connection. |
| 202 | + if (SecEntity.name && strncmp(SecEntity.prot, "oidc", 4) != 0) return 0; |
| 203 | + |
| 204 | + std::string bearer; |
| 205 | + if (!extractBearerToken(CurrentReq, bearer)) |
| 206 | + {if (oidcHttpMode == 2 && oidcBearerTokKey.empty()) |
| 207 | + {TRACEI(REQ, " OIDC bearer token required but not provided."); |
| 208 | + SendSimpleResp(401, nullptr, nullptr, "Authentication required", 0, false); |
| 209 | + return 1; |
| 210 | + } |
| 211 | + return 0; |
| 212 | + } |
| 213 | + |
| 214 | + int tlen = 0; |
| 215 | + const char *tok = stripBearerToken(bearer.c_str(), tlen); |
| 216 | + if (!tok || tlen <= 0) |
| 217 | + {TRACEI(REQ, " OIDC bearer token malformed."); |
| 218 | + SendSimpleResp(401, nullptr, nullptr, "Authentication failed", 0, false); |
| 219 | + return 1; |
| 220 | + } |
| 221 | + |
| 222 | + const std::string tokKey = bearerTokenKey(tok, tlen); |
| 223 | + if (tokKey.empty()) |
| 224 | + {TRACEI(REQ, " OIDC bearer token fingerprint failed."); |
| 225 | + SendSimpleResp(500, nullptr, nullptr, "Authentication failed", 0, false); |
| 226 | + return 1; |
| 227 | + } |
| 228 | + |
| 229 | + if (!oidcBearerTokKey.empty() && tokKey == oidcBearerTokKey) return 0; |
| 230 | + |
| 231 | + const int bsz = 5 + tlen + 1; |
| 232 | + std::vector<char> credBuf(static_cast<size_t>(bsz)); |
| 233 | + strcpy(credBuf.data(), "oidc"); |
| 234 | + memcpy(credBuf.data() + 5, tok, static_cast<size_t>(tlen)); |
| 235 | + credBuf[static_cast<size_t>(5 + tlen)] = '\0'; |
| 236 | + |
| 237 | + XrdSecCredentials cred; |
| 238 | + cred.buffer = credBuf.data(); |
| 239 | + cred.size = bsz; |
| 240 | + |
| 241 | + XrdOucErrInfo eMsg; |
| 242 | + XrdSecProtocol *authProt = CIA->getProtocol(Link->Host(), *(Link->AddrInfo()), |
| 243 | + &cred, eMsg); |
| 244 | + if (!authProt) |
| 245 | + {int ec = 0; |
| 246 | + const char *et = eMsg.getErrText(ec); |
| 247 | + TRACEI(REQ, " OIDC protocol unavailable: " << (et && *et ? et : "unknown")); |
| 248 | + SendSimpleResp(401, nullptr, nullptr, "Authentication failed", 0, false); |
| 249 | + return 1; |
| 250 | + } |
| 251 | + |
| 252 | + XrdSecParameters *parm = nullptr; |
| 253 | + const int rc = authProt->Authenticate(&cred, &parm, &eMsg); |
| 254 | + if (parm) delete parm; |
| 255 | + |
| 256 | + if (rc != 0 || !CIA->PostProcess(authProt->Entity, eMsg)) |
| 257 | + {int ec = 0; |
| 258 | + const char *et = eMsg.getErrText(ec); |
| 259 | + TRACEI(REQ, " OIDC token validation failed: " << (et && *et ? et : "unknown")); |
| 260 | + authProt->Delete(); |
| 261 | + SendSimpleResp(401, nullptr, nullptr, "Authentication failed", 0, false); |
| 262 | + return 1; |
| 263 | + } |
| 264 | + |
| 265 | + if (!oidcBearerTokKey.empty() || Bridge) |
| 266 | + {if (Bridge && !Bridge->Disc()) |
| 267 | + {TRACEI(REQ, " OIDC token changed but bridge is busy."); |
| 268 | + authProt->Delete(); |
| 269 | + SendSimpleResp(503, nullptr, nullptr, "Authentication busy", 0, false); |
| 270 | + return 1; |
| 271 | + } |
| 272 | + Bridge = nullptr; |
| 273 | + DoingLogin = false; |
| 274 | + DoneSetInfo = false; |
| 275 | + if (!oidcBearerTokKey.empty()) |
| 276 | + TRACEI(REQ, " OIDC bearer token changed; re-authenticating."); |
| 277 | + } |
| 278 | + |
| 279 | + if (SecEntity.name) free(SecEntity.name); |
| 280 | + SecEntity.name = authProt->Entity.name ? strdup(authProt->Entity.name) : nullptr; |
| 281 | + strncpy(SecEntity.prot, authProt->Entity.prot, sizeof(SecEntity.prot)); |
| 282 | + copyEntityAttrs(SecEntity, authProt->Entity); |
| 283 | + authProt->Delete(); |
| 284 | + |
| 285 | + oidcBearerTokKey = tokKey; |
| 286 | + TRACEI(REQ, " OIDC authenticated as: " << SecEntity.name); |
| 287 | + return 0; |
| 288 | +} |
| 289 | + |
89 | 290 | /******************************************************************************/ |
90 | 291 | /* H a n d l e A u t h e n t i c a t i o n */ |
91 | 292 | /******************************************************************************/ |
|
0 commit comments