nsAuthGSSAPI.cpp (18543B)
1 /* vim:set ts=4 sw=2 sts=2 et cindent: */ 2 /* This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 // 7 // GSSAPI Authentication Support Module 8 // 9 // Described by IETF Internet draft: draft-brezak-kerberos-http-00.txt 10 // (formerly draft-brezak-spnego-http-04.txt) 11 // 12 // Also described here: 13 // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnsecure/html/http-sso-1.asp 14 // 15 // 16 17 #include "mozilla/IntegerPrintfMacros.h" 18 19 #include "nsCOMPtr.h" 20 #include "nsNativeCharsetUtils.h" 21 #include "mozilla/Preferences.h" 22 #include "mozilla/SharedLibrary.h" 23 #include "mozilla/glean/SecurityManagerSslMetrics.h" 24 25 #include "nsAuthGSSAPI.h" 26 27 #ifdef XP_MACOSX 28 # include <Kerberos/Kerberos.h> 29 #endif 30 31 #ifdef XP_MACOSX 32 typedef KLStatus (*KLCacheHasValidTickets_type)(KLPrincipal, KLKerberosVersion, 33 KLBoolean*, KLPrincipal*, 34 char**); 35 #endif 36 37 #if defined(HAVE_RES_NINIT) 38 # include <sys/types.h> 39 # include <netinet/in.h> 40 # include <arpa/nameser.h> 41 # include <resolv.h> 42 #endif 43 44 using namespace mozilla; 45 46 //----------------------------------------------------------------------------- 47 48 // We define GSS_C_NT_HOSTBASED_SERVICE explicitly since it may be referenced 49 // by by a different name depending on the implementation of gss but always 50 // has the same value 51 52 static gss_OID_desc gss_c_nt_hostbased_service = { 53 10, (void*)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x04"}; 54 55 static const char kNegotiateAuthGssLib[] = "network.negotiate-auth.gsslib"; 56 static const char kNegotiateAuthNativeImp[] = 57 "network.negotiate-auth.using-native-gsslib"; 58 59 static struct GSSFunction { 60 const char* str; 61 PRFuncPtr func; 62 } gssFuncs[] = {{"gss_display_status", nullptr}, 63 {"gss_init_sec_context", nullptr}, 64 {"gss_indicate_mechs", nullptr}, 65 {"gss_release_oid_set", nullptr}, 66 {"gss_delete_sec_context", nullptr}, 67 {"gss_import_name", nullptr}, 68 {"gss_release_buffer", nullptr}, 69 {"gss_release_name", nullptr}, 70 {"gss_wrap", nullptr}, 71 {"gss_unwrap", nullptr}}; 72 73 static bool gssNativeImp = true; 74 static PRLibrary* gssLibrary = nullptr; 75 76 #define gss_display_status_ptr ((gss_display_status_type) * gssFuncs[0].func) 77 #define gss_init_sec_context_ptr \ 78 ((gss_init_sec_context_type) * gssFuncs[1].func) 79 #define gss_indicate_mechs_ptr ((gss_indicate_mechs_type) * gssFuncs[2].func) 80 #define gss_release_oid_set_ptr ((gss_release_oid_set_type) * gssFuncs[3].func) 81 #define gss_delete_sec_context_ptr \ 82 ((gss_delete_sec_context_type) * gssFuncs[4].func) 83 #define gss_import_name_ptr ((gss_import_name_type) * gssFuncs[5].func) 84 #define gss_release_buffer_ptr ((gss_release_buffer_type) * gssFuncs[6].func) 85 #define gss_release_name_ptr ((gss_release_name_type) * gssFuncs[7].func) 86 #define gss_wrap_ptr ((gss_wrap_type) * gssFuncs[8].func) 87 #define gss_unwrap_ptr ((gss_unwrap_type) * gssFuncs[9].func) 88 89 #ifdef XP_MACOSX 90 static PRFuncPtr KLCacheHasValidTicketsPtr; 91 # define KLCacheHasValidTickets_ptr \ 92 ((KLCacheHasValidTickets_type) * KLCacheHasValidTicketsPtr) 93 #endif 94 95 static nsresult gssInit() { 96 #ifdef XP_WIN 97 nsAutoString libPathU; 98 Preferences::GetString(kNegotiateAuthGssLib, libPathU); 99 NS_ConvertUTF16toUTF8 libPath(libPathU); 100 #else 101 nsAutoCString libPath; 102 Preferences::GetCString(kNegotiateAuthGssLib, libPath); 103 #endif 104 gssNativeImp = Preferences::GetBool(kNegotiateAuthNativeImp); 105 106 PRLibrary* lib = nullptr; 107 108 if (!libPath.IsEmpty()) { 109 LOG(("Attempting to load user specified library [%s]\n", libPath.get())); 110 gssNativeImp = false; 111 #ifdef XP_WIN 112 lib = LoadLibraryWithFlags(libPathU.get()); 113 #else 114 lib = LoadLibraryWithFlags(libPath.get()); 115 #endif 116 } else { 117 #ifdef XP_WIN 118 # ifdef _WIN64 119 constexpr auto kLibName = u"gssapi64.dll"_ns; 120 # else 121 constexpr auto kLibName = u"gssapi32.dll"_ns; 122 # endif 123 124 lib = LoadLibraryWithFlags(kLibName.get()); 125 #elif defined(__OpenBSD__) 126 /* OpenBSD doesn't register inter-library dependencies in basesystem 127 * libs therefor we need to load all the libraries gssapi depends on, 128 * in the correct order and with LD_GLOBAL for GSSAPI auth to work 129 * fine. 130 */ 131 132 const char* const verLibNames[] = { 133 "libasn1.so", "libcrypto.so", "libroken.so", "libheimbase.so", 134 "libcom_err.so", "libkrb5.so", "libgssapi.so"}; 135 136 PRLibSpec libSpec; 137 for (size_t i = 0; i < std::size(verLibNames); ++i) { 138 libSpec.type = PR_LibSpec_Pathname; 139 libSpec.value.pathname = verLibNames[i]; 140 lib = PR_LoadLibraryWithFlags(libSpec, PR_LD_GLOBAL); 141 } 142 143 #else 144 145 const char* const libNames[] = {"gss", "gssapi_krb5", "gssapi"}; 146 147 const char* const verLibNames[] = { 148 "libgssapi_krb5.so.2", /* MIT - FC, Suse10, Debian */ 149 "libgssapi.so.4", /* Heimdal - Suse10, MDK */ 150 "libgssapi.so.1" /* Heimdal - Suse9, CITI - FC, MDK, Suse10*/ 151 }; 152 153 for (size_t i = 0; i < std::size(verLibNames) && !lib; ++i) { 154 lib = PR_LoadLibrary(verLibNames[i]); 155 156 /* The CITI libgssapi library calls exit() during 157 * initialization if it's not correctly configured. Try to 158 * ensure that we never use this library for our GSSAPI 159 * support, as its just a wrapper library, anyway. 160 * See Bugzilla #325433 161 */ 162 if (lib && PR_FindFunctionSymbol(lib, "internal_krb5_gss_initialize") && 163 PR_FindFunctionSymbol(lib, "gssd_pname_to_uid")) { 164 LOG(("CITI libgssapi found, which calls exit(). Skipping\n")); 165 PR_UnloadLibrary(lib); 166 lib = nullptr; 167 } 168 } 169 170 for (size_t i = 0; i < std::size(libNames) && !lib; ++i) { 171 char* libName = PR_GetLibraryName(nullptr, libNames[i]); 172 if (libName) { 173 lib = PR_LoadLibrary(libName); 174 PR_FreeLibraryName(libName); 175 176 if (lib && PR_FindFunctionSymbol(lib, "internal_krb5_gss_initialize") && 177 PR_FindFunctionSymbol(lib, "gssd_pname_to_uid")) { 178 LOG(("CITI libgssapi found, which calls exit(). Skipping\n")); 179 PR_UnloadLibrary(lib); 180 lib = nullptr; 181 } 182 } 183 } 184 #endif 185 } 186 187 if (!lib) { 188 LOG(("Fail to load gssapi library\n")); 189 return NS_ERROR_FAILURE; 190 } 191 192 LOG(("Attempting to load gss functions\n")); 193 194 for (auto& gssFunc : gssFuncs) { 195 gssFunc.func = PR_FindFunctionSymbol(lib, gssFunc.str); 196 if (!gssFunc.func) { 197 LOG(("Fail to load %s function from gssapi library\n", gssFunc.str)); 198 PR_UnloadLibrary(lib); 199 return NS_ERROR_FAILURE; 200 } 201 } 202 #ifdef XP_MACOSX 203 if (gssNativeImp && !(KLCacheHasValidTicketsPtr = PR_FindFunctionSymbol( 204 lib, "KLCacheHasValidTickets"))) { 205 LOG(("Fail to load KLCacheHasValidTickets function from gssapi library\n")); 206 PR_UnloadLibrary(lib); 207 return NS_ERROR_FAILURE; 208 } 209 #endif 210 211 gssLibrary = lib; 212 return NS_OK; 213 } 214 215 // Generate proper GSSAPI error messages from the major and 216 // minor status codes. 217 void LogGssError(OM_uint32 maj_stat, OM_uint32 min_stat, const char* prefix) { 218 if (!MOZ_LOG_TEST(gNegotiateLog, LogLevel::Debug)) { 219 return; 220 } 221 222 OM_uint32 new_stat; 223 OM_uint32 msg_ctx = 0; 224 gss_buffer_desc status1_string; 225 gss_buffer_desc status2_string; 226 OM_uint32 ret; 227 nsAutoCString errorStr; 228 errorStr.Assign(prefix); 229 230 if (!gssLibrary) return; 231 232 errorStr += ": "; 233 do { 234 ret = gss_display_status_ptr(&new_stat, maj_stat, GSS_C_GSS_CODE, 235 GSS_C_NULL_OID, &msg_ctx, &status1_string); 236 errorStr.Append((const char*)status1_string.value, status1_string.length); 237 gss_release_buffer_ptr(&new_stat, &status1_string); 238 239 errorStr += '\n'; 240 ret = gss_display_status_ptr(&new_stat, min_stat, GSS_C_MECH_CODE, 241 GSS_C_NULL_OID, &msg_ctx, &status2_string); 242 errorStr.Append((const char*)status2_string.value, status2_string.length); 243 errorStr += '\n'; 244 } while (!GSS_ERROR(ret) && msg_ctx != 0); 245 246 LOG(("%s\n", errorStr.get())); 247 } 248 249 //----------------------------------------------------------------------------- 250 251 nsAuthGSSAPI::nsAuthGSSAPI(pType package) : mServiceFlags(REQ_DEFAULT) { 252 OM_uint32 minstat; 253 OM_uint32 majstat; 254 gss_OID_set mech_set; 255 gss_OID item; 256 257 unsigned int i; 258 static gss_OID_desc gss_krb5_mech_oid_desc = { 259 9, (void*)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x02"}; 260 static gss_OID_desc gss_spnego_mech_oid_desc = { 261 6, (void*)"\x2b\x06\x01\x05\x05\x02"}; 262 263 LOG(("entering nsAuthGSSAPI::nsAuthGSSAPI()\n")); 264 265 if (!gssLibrary && NS_FAILED(gssInit())) return; 266 267 mCtx = GSS_C_NO_CONTEXT; 268 mMechOID = &gss_krb5_mech_oid_desc; 269 270 // if the type is kerberos we accept it as default 271 // and exit 272 273 if (package == PACKAGE_TYPE_KERBEROS) return; 274 275 // Now, look at the list of supported mechanisms, 276 // if SPNEGO is found, then use it. 277 // Otherwise, set the desired mechanism to 278 // GSS_C_NO_OID and let the system try to use 279 // the default mechanism. 280 // 281 // Using Kerberos directly (instead of negotiating 282 // with SPNEGO) may work in some cases depending 283 // on how smart the server side is. 284 285 majstat = gss_indicate_mechs_ptr(&minstat, &mech_set); 286 if (GSS_ERROR(majstat)) return; 287 288 if (mech_set) { 289 for (i = 0; i < mech_set->count; i++) { 290 item = &mech_set->elements[i]; 291 if (item->length == gss_spnego_mech_oid_desc.length && 292 !memcmp(item->elements, gss_spnego_mech_oid_desc.elements, 293 item->length)) { 294 // ok, we found it 295 mMechOID = &gss_spnego_mech_oid_desc; 296 break; 297 } 298 } 299 gss_release_oid_set_ptr(&minstat, &mech_set); 300 } 301 } 302 303 void nsAuthGSSAPI::Reset() { 304 if (gssLibrary && mCtx != GSS_C_NO_CONTEXT) { 305 OM_uint32 minor_status; 306 gss_delete_sec_context_ptr(&minor_status, &mCtx, GSS_C_NO_BUFFER); 307 } 308 mCtx = GSS_C_NO_CONTEXT; 309 mComplete = false; 310 mDelegationRequested = false; 311 mDelegationSupported = false; 312 } 313 314 /* static */ 315 void nsAuthGSSAPI::Shutdown() { 316 if (gssLibrary) { 317 PR_UnloadLibrary(gssLibrary); 318 gssLibrary = nullptr; 319 } 320 } 321 322 /* Limitations apply to this class's thread safety. See the header file */ 323 NS_IMPL_ISUPPORTS(nsAuthGSSAPI, nsIAuthModule) 324 325 NS_IMETHODIMP 326 nsAuthGSSAPI::Init(const nsACString& serviceName, uint32_t serviceFlags, 327 const nsAString& domain, const nsAString& username, 328 const nsAString& password) { 329 // we don't expect to be passed any user credentials 330 NS_ASSERTION(domain.IsEmpty() && username.IsEmpty() && password.IsEmpty(), 331 "unexpected credentials"); 332 333 // it's critial that the caller supply a service name to be used 334 NS_ENSURE_TRUE(!serviceName.IsEmpty(), NS_ERROR_INVALID_ARG); 335 336 LOG(("entering nsAuthGSSAPI::Init()\n")); 337 338 if (!gssLibrary) return NS_ERROR_NOT_INITIALIZED; 339 340 mServiceName = serviceName; 341 mServiceFlags = serviceFlags; 342 343 static bool sTelemetrySent = false; 344 if (!sTelemetrySent) { 345 mozilla::glean::security::ntlm_module_used.AccumulateSingleSample( 346 serviceFlags & nsIAuthModule::REQ_PROXY_AUTH 347 ? NTLM_MODULE_KERBEROS_PROXY 348 : NTLM_MODULE_KERBEROS_DIRECT); 349 sTelemetrySent = true; 350 } 351 352 return NS_OK; 353 } 354 355 NS_IMETHODIMP 356 nsAuthGSSAPI::GetNextToken(const void* inToken, uint32_t inTokenLen, 357 void** outToken, uint32_t* outTokenLen) { 358 OM_uint32 major_status, minor_status; 359 OM_uint32 req_flags = 0; 360 OM_uint32 ret_flags = 0; 361 gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER; 362 gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER; 363 gss_buffer_t in_token_ptr = GSS_C_NO_BUFFER; 364 gss_name_t server; 365 nsAutoCString userbuf; 366 nsresult rv; 367 368 LOG(("entering nsAuthGSSAPI::GetNextToken()\n")); 369 370 if (!gssLibrary) return NS_ERROR_NOT_INITIALIZED; 371 372 // If they've called us again after we're complete, reset to start afresh. 373 if (mComplete) Reset(); 374 375 // Two-phase delegation logic 376 // Phase 1: Try authentication without delegation first 377 // Phase 2: Only retry with delegation if server supports it (ret_flags) 378 bool delegationConfigured = (mServiceFlags & REQ_DELEGATE) != 0; 379 380 if (delegationConfigured) { 381 if (!mDelegationRequested) { 382 // First attempt: don't request delegation yet 383 LOG(("First auth attempt without delegation")); 384 mDelegationRequested = true; 385 } else if (mDelegationSupported) { 386 // Second attempt: server supports delegation, now request it 387 LOG(("Retrying auth with delegation - server supports it")); 388 req_flags |= GSS_C_DELEG_FLAG; 389 } 390 } 391 392 if (mServiceFlags & REQ_MUTUAL_AUTH) req_flags |= GSS_C_MUTUAL_FLAG; 393 394 input_token.value = (void*)mServiceName.get(); 395 input_token.length = mServiceName.Length() + 1; 396 397 #if defined(HAVE_RES_NINIT) 398 res_ninit(&_res); 399 #endif 400 major_status = gss_import_name_ptr(&minor_status, &input_token, 401 &gss_c_nt_hostbased_service, &server); 402 input_token.value = nullptr; 403 input_token.length = 0; 404 if (GSS_ERROR(major_status)) { 405 LogGssError(major_status, minor_status, "gss_import_name() failed"); 406 return NS_ERROR_FAILURE; 407 } 408 409 if (inToken) { 410 input_token.length = inTokenLen; 411 input_token.value = (void*)inToken; 412 in_token_ptr = &input_token; 413 } else if (mCtx != GSS_C_NO_CONTEXT) { 414 // If there is no input token, then we are starting a new 415 // authentication sequence. If we have already initialized our 416 // security context, then we're in trouble because it means that the 417 // first sequence failed. We need to bail or else we might end up in 418 // an infinite loop. 419 LOG(("Cannot restart authentication sequence!")); 420 return NS_ERROR_UNEXPECTED; 421 } 422 423 #if defined(XP_MACOSX) 424 // Suppress Kerberos prompts to get credentials. See bug 240643. 425 // We can only use Mac OS X specific kerb functions if we are using 426 // the native lib 427 KLBoolean found; 428 bool doingMailTask = mServiceName.Find("imap@") || 429 mServiceName.Find("pop@") || 430 mServiceName.Find("smtp@") || mServiceName.Find("ldap@"); 431 432 if (!doingMailTask && 433 (gssNativeImp && 434 (KLCacheHasValidTickets_ptr(nullptr, kerberosVersion_V5, &found, nullptr, 435 nullptr) != klNoErr || 436 !found))) { 437 major_status = GSS_S_FAILURE; 438 minor_status = 0; 439 } else 440 #endif /* XP_MACOSX */ 441 major_status = gss_init_sec_context_ptr( 442 &minor_status, GSS_C_NO_CREDENTIAL, &mCtx, server, mMechOID, req_flags, 443 GSS_C_INDEFINITE, GSS_C_NO_CHANNEL_BINDINGS, in_token_ptr, nullptr, 444 &output_token, &ret_flags, nullptr); 445 446 if (GSS_ERROR(major_status)) { 447 LogGssError(major_status, minor_status, "gss_init_sec_context() failed"); 448 Reset(); 449 rv = NS_ERROR_FAILURE; 450 goto end; 451 } 452 // Check if server supports delegation (OK-AS-DELEGATE equivalent) 453 if (delegationConfigured && !mDelegationSupported && 454 (ret_flags & GSS_C_DELEG_FLAG)) { 455 LOG(("Server supports delegation (GSS_C_DELEG_FLAG in ret_flags)")); 456 457 // If we completed without requesting delegation, but server supports it, 458 // we need to restart with delegation 459 if (major_status == GSS_S_COMPLETE && !(req_flags & GSS_C_DELEG_FLAG)) { 460 LOG(("Restarting authentication to request delegation")); 461 Reset(); 462 463 // These flags get cleared by Reset(). 464 // Set them again to make sure the next call sets GSS_C_DELEG_FLAG 465 mDelegationRequested = true; 466 mDelegationSupported = true; 467 468 gss_release_name_ptr(&minor_status, &server); 469 return GetNextToken(inToken, inTokenLen, outToken, outTokenLen); 470 } 471 } 472 473 if (major_status == GSS_S_COMPLETE) { 474 // Mark ourselves as being complete, so that if we're called again 475 // we know to start afresh. 476 mComplete = true; 477 } else if (major_status == GSS_S_CONTINUE_NEEDED) { 478 // 479 // The important thing is that we do NOT reset the 480 // context here because it will be needed on the 481 // next call. 482 // 483 } 484 485 *outTokenLen = output_token.length; 486 if (output_token.length != 0) { 487 *outToken = moz_xmemdup(output_token.value, output_token.length); 488 } else { 489 *outToken = nullptr; 490 } 491 492 gss_release_buffer_ptr(&minor_status, &output_token); 493 494 if (major_status == GSS_S_COMPLETE) { 495 rv = NS_SUCCESS_AUTH_FINISHED; 496 } else { 497 rv = NS_OK; 498 } 499 500 end: 501 gss_release_name_ptr(&minor_status, &server); 502 503 LOG((" leaving nsAuthGSSAPI::GetNextToken [rv=%" PRIx32 "]", 504 static_cast<uint32_t>(rv))); 505 return rv; 506 } 507 508 NS_IMETHODIMP 509 nsAuthGSSAPI::Unwrap(const void* inToken, uint32_t inTokenLen, void** outToken, 510 uint32_t* outTokenLen) { 511 OM_uint32 major_status, minor_status; 512 513 gss_buffer_desc input_token; 514 gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER; 515 516 input_token.value = (void*)inToken; 517 input_token.length = inTokenLen; 518 519 major_status = gss_unwrap_ptr(&minor_status, mCtx, &input_token, 520 &output_token, nullptr, nullptr); 521 if (GSS_ERROR(major_status)) { 522 LogGssError(major_status, minor_status, "gss_unwrap() failed"); 523 Reset(); 524 gss_release_buffer_ptr(&minor_status, &output_token); 525 return NS_ERROR_FAILURE; 526 } 527 528 *outTokenLen = output_token.length; 529 530 if (output_token.length) { 531 *outToken = moz_xmemdup(output_token.value, output_token.length); 532 } else { 533 *outToken = nullptr; 534 } 535 536 gss_release_buffer_ptr(&minor_status, &output_token); 537 538 return NS_OK; 539 } 540 541 NS_IMETHODIMP 542 nsAuthGSSAPI::Wrap(const void* inToken, uint32_t inTokenLen, bool confidential, 543 void** outToken, uint32_t* outTokenLen) { 544 OM_uint32 major_status, minor_status; 545 546 gss_buffer_desc input_token; 547 gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER; 548 549 input_token.value = (void*)inToken; 550 input_token.length = inTokenLen; 551 552 major_status = 553 gss_wrap_ptr(&minor_status, mCtx, confidential, GSS_C_QOP_DEFAULT, 554 &input_token, nullptr, &output_token); 555 556 if (GSS_ERROR(major_status)) { 557 LogGssError(major_status, minor_status, "gss_wrap() failed"); 558 Reset(); 559 gss_release_buffer_ptr(&minor_status, &output_token); 560 return NS_ERROR_FAILURE; 561 } 562 563 *outTokenLen = output_token.length; 564 565 /* it is not possible for output_token.length to be zero */ 566 *outToken = moz_xmemdup(output_token.value, output_token.length); 567 gss_release_buffer_ptr(&minor_status, &output_token); 568 569 return NS_OK; 570 }