|
19 | 19 | // along with XRootD. If not, see <http://www.gnu.org/licenses/>. |
20 | 20 | //------------------------------------------------------------------------------ |
21 | 21 |
|
| 22 | +#include <cstdio> |
| 23 | +#include <cstring> |
| 24 | +#include <map> |
| 25 | +#include <string> |
| 26 | + |
| 27 | +#include <openssl/evp.h> |
| 28 | + |
22 | 29 | #include "XrdHttpProtocol.hh" |
23 | 30 | #include "XrdHttpTrace.hh" |
24 | 31 | #include "XrdHttpSecXtractor.hh" |
| 32 | +#include "XrdOuc/XrdOucOIDC.hh" |
25 | 33 | #include "Xrd/XrdLink.hh" |
26 | 34 | #include "XrdCrypto/XrdCryptoX509Chain.hh" |
27 | 35 | #include "XrdCrypto/XrdCryptosslAux.hh" |
@@ -49,6 +57,46 @@ const char *TraceID = "Security"; |
49 | 57 |
|
50 | 58 | using namespace XrdHttpProtoInfo; |
51 | 59 |
|
| 60 | +namespace |
| 61 | +{ |
| 62 | + |
| 63 | +std::string bearerTokenKey(const char *tok, int tlen) |
| 64 | +{ |
| 65 | + unsigned char md[EVP_MAX_MD_SIZE]; |
| 66 | + unsigned int mdLen = 0; |
| 67 | + if (!tok || tlen <= 0 |
| 68 | + || !EVP_Digest(tok, static_cast<size_t>(tlen), md, &mdLen, EVP_sha256(), nullptr)) |
| 69 | + return {}; |
| 70 | + char hex[EVP_MAX_MD_SIZE * 2 + 1]; |
| 71 | + for (unsigned int i = 0; i < mdLen; ++i) |
| 72 | + snprintf(hex + i * 2, sizeof(hex) - i * 2, "%02x", md[i]); |
| 73 | + return std::string(hex, mdLen * 2); |
| 74 | +} |
| 75 | + |
| 76 | +bool extractBearerToken(const XrdHttpReq &req, std::string &token) |
| 77 | +{ |
| 78 | + const std::string &cgi = req.hdr2cgistr; |
| 79 | + const char *key = "authz="; |
| 80 | + size_t pos = cgi.find(key); |
| 81 | + if (pos != std::string::npos) |
| 82 | + {size_t start = pos + strlen(key); |
| 83 | + size_t end = cgi.find('&', start); |
| 84 | + token = (end == std::string::npos ? cgi.substr(start) |
| 85 | + : cgi.substr(start, end - start)); |
| 86 | + return !token.empty(); |
| 87 | + } |
| 88 | + |
| 89 | + for (const auto &hdr : req.allheaders) |
| 90 | + {if (!strcasecmp(hdr.first.c_str(), "authorization")) |
| 91 | + {token = hdr.second; |
| 92 | + return !token.empty(); |
| 93 | + } |
| 94 | + } |
| 95 | + return false; |
| 96 | +} |
| 97 | + |
| 98 | +} // namespace |
| 99 | + |
52 | 100 | /******************************************************************************/ |
53 | 101 | /* I n i t S e c u r i t y */ |
54 | 102 | /******************************************************************************/ |
@@ -86,6 +134,81 @@ bool XrdHttpProtocol::InitSecurity() { |
86 | 134 | return true; |
87 | 135 | } |
88 | 136 |
|
| 137 | +/******************************************************************************/ |
| 138 | +/* H a n d l e O I D C A u t h e n t i c a t i o n */ |
| 139 | +/******************************************************************************/ |
| 140 | + |
| 141 | +int |
| 142 | +XrdHttpProtocol::HandleOidcAuthentication() |
| 143 | +{ |
| 144 | +#undef TRACELINK |
| 145 | +#define TRACELINK Link |
| 146 | + |
| 147 | + if (!oidcHttpMode || !XrdOucOIDC::IsConfigured()) return 0; |
| 148 | + |
| 149 | + // Client-certificate identity is fixed for the TLS connection. |
| 150 | + if (SecEntity.name && strncmp(SecEntity.prot, "oidc", 4) != 0) return 0; |
| 151 | + |
| 152 | + std::string bearer; |
| 153 | + if (!extractBearerToken(CurrentReq, bearer)) |
| 154 | + {if (oidcHttpMode == 2 && oidcBearerTokKey.empty()) |
| 155 | + {TRACEI(REQ, " OIDC bearer token required but not provided."); |
| 156 | + SendSimpleResp(401, nullptr, nullptr, "Authentication required", 0, false); |
| 157 | + return 1; |
| 158 | + } |
| 159 | + return 0; |
| 160 | + } |
| 161 | + |
| 162 | + int tlen = 0; |
| 163 | + const char *tok = XrdOucOIDC::StripToken(bearer.c_str(), tlen); |
| 164 | + if (!tok || tlen <= 0) |
| 165 | + {TRACEI(REQ, " OIDC bearer token malformed."); |
| 166 | + SendSimpleResp(401, nullptr, nullptr, "Authentication failed", 0, false); |
| 167 | + return 1; |
| 168 | + } |
| 169 | + |
| 170 | + const std::string tokKey = bearerTokenKey(tok, tlen); |
| 171 | + if (tokKey.empty()) |
| 172 | + {TRACEI(REQ, " OIDC bearer token fingerprint failed."); |
| 173 | + SendSimpleResp(500, nullptr, nullptr, "Authentication failed", 0, false); |
| 174 | + return 1; |
| 175 | + } |
| 176 | + |
| 177 | + if (!oidcBearerTokKey.empty() && tokKey == oidcBearerTokKey) return 0; |
| 178 | + |
| 179 | + std::string tokStr(tok, static_cast<size_t>(tlen)); |
| 180 | + std::string identity, emsg; |
| 181 | + std::map<std::string, std::string> entityAttrs; |
| 182 | + if (!XrdOucOIDC::ValidateToken(tokStr.c_str(), identity, emsg, nullptr, |
| 183 | + &entityAttrs)) |
| 184 | + {TRACEI(REQ, " OIDC token validation failed: " << emsg); |
| 185 | + SendSimpleResp(401, nullptr, nullptr, "Authentication failed", 0, false); |
| 186 | + return 1; |
| 187 | + } |
| 188 | + |
| 189 | + if (!oidcBearerTokKey.empty() || Bridge) |
| 190 | + {if (Bridge && !Bridge->Disc()) |
| 191 | + {TRACEI(REQ, " OIDC token changed but bridge is busy."); |
| 192 | + SendSimpleResp(503, nullptr, nullptr, "Authentication busy", 0, false); |
| 193 | + return 1; |
| 194 | + } |
| 195 | + Bridge = nullptr; |
| 196 | + DoingLogin = false; |
| 197 | + DoneSetInfo = false; |
| 198 | + if (!oidcBearerTokKey.empty()) |
| 199 | + TRACEI(REQ, " OIDC bearer token changed; re-authenticating."); |
| 200 | + } |
| 201 | + |
| 202 | + if (SecEntity.name) free(SecEntity.name); |
| 203 | + SecEntity.name = strdup(identity.c_str()); |
| 204 | + strncpy(SecEntity.prot, "oidc", sizeof(SecEntity.prot)); |
| 205 | + for (const auto &attr : entityAttrs) |
| 206 | + SecEntity.eaAPI->Add(attr.first, attr.second, true); |
| 207 | + oidcBearerTokKey = tokKey; |
| 208 | + TRACEI(REQ, " OIDC authenticated as: " << SecEntity.name); |
| 209 | + return 0; |
| 210 | +} |
| 211 | + |
89 | 212 | /******************************************************************************/ |
90 | 213 | /* H a n d l e A u t h e n t i c a t i o n */ |
91 | 214 | /******************************************************************************/ |
|
0 commit comments