nsHttpNegotiateAuth.cpp (18803B)
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 // HTTP Negotiate 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 #include <stdlib.h> 17 18 #include "nsAuth.h" 19 #include "nsHttpNegotiateAuth.h" 20 21 #include "nsIHttpAuthenticableChannel.h" 22 #include "nsIAuthModule.h" 23 #include "nsIPrefBranch.h" 24 #include "nsIPrefService.h" 25 #include "nsIProxyInfo.h" 26 #include "nsIURI.h" 27 #include "nsCOMPtr.h" 28 #include "nsString.h" 29 #include "nsNetCID.h" 30 #include "nsProxyRelease.h" 31 #include "plbase64.h" 32 #include "mozilla/Base64.h" 33 #include "mozilla/Tokenizer.h" 34 #include "mozilla/Sprintf.h" 35 #include "nsIChannel.h" 36 #include "nsNetUtil.h" 37 #include "nsThreadUtils.h" 38 #include "nsIHttpAuthenticatorCallback.h" 39 #include "nsICancelable.h" 40 #include "mozilla/net/HttpAuthUtils.h" 41 #include "mozilla/ClearOnShutdown.h" 42 #include "mozilla/net/DNS.h" 43 #include "mozilla/StaticPrefs_browser.h" 44 45 using mozilla::Base64Decode; 46 47 //----------------------------------------------------------------------------- 48 49 static const char kNegotiate[] = "Negotiate"; 50 static const char kNegotiateAuthTrustedURIs[] = 51 "network.negotiate-auth.trusted-uris"; 52 static const char kNegotiateAuthDelegationURIs[] = 53 "network.negotiate-auth.delegation-uris"; 54 static const char kNegotiateAuthAllowProxies[] = 55 "network.negotiate-auth.allow-proxies"; 56 static const char kNegotiateAuthAllowNonFqdn[] = 57 "network.negotiate-auth.allow-non-fqdn"; 58 static const char kNegotiateAuthSSPI[] = "network.auth.use-sspi"; 59 static const char kSSOinPBmode[] = "network.auth.private-browsing-sso"; 60 61 mozilla::StaticRefPtr<nsHttpNegotiateAuth> nsHttpNegotiateAuth::gSingleton; 62 63 #define kNegotiateLen (sizeof(kNegotiate) - 1) 64 #define DEFAULT_THREAD_TIMEOUT_MS 30000 65 66 //----------------------------------------------------------------------------- 67 68 // Return false when the channel comes from a Private browsing window. 69 static bool TestNotInPBMode(nsIHttpAuthenticableChannel* authChannel, 70 bool proxyAuth) { 71 // Proxy should go all the time, it's not considered a privacy leak 72 // to send default credentials to a proxy. 73 if (proxyAuth) { 74 return true; 75 } 76 77 nsCOMPtr<nsIChannel> bareChannel = do_QueryInterface(authChannel); 78 MOZ_ASSERT(bareChannel); 79 80 if (!NS_UsePrivateBrowsing(bareChannel)) { 81 return true; 82 } 83 84 nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); 85 if (prefs) { 86 bool ssoInPb; 87 if (NS_SUCCEEDED(prefs->GetBoolPref(kSSOinPBmode, &ssoInPb)) && ssoInPb) { 88 return true; 89 } 90 91 // When the "Never remember history" option is set, all channels are 92 // set PB mode flag, but here we want to make an exception, users 93 // want their credentials go out. 94 if (mozilla::StaticPrefs::browser_privatebrowsing_autostart()) { 95 return true; 96 } 97 } 98 99 return false; 100 } 101 102 already_AddRefed<nsIHttpAuthenticator> nsHttpNegotiateAuth::GetOrCreate() { 103 nsCOMPtr<nsIHttpAuthenticator> authenticator; 104 if (gSingleton) { 105 authenticator = gSingleton; 106 } else { 107 gSingleton = new nsHttpNegotiateAuth(); 108 mozilla::ClearOnShutdown(&gSingleton); 109 authenticator = gSingleton; 110 } 111 112 return authenticator.forget(); 113 } 114 115 NS_IMETHODIMP 116 nsHttpNegotiateAuth::GetAuthFlags(uint32_t* flags) { 117 // 118 // Negotiate Auth creds should not be reused across multiple requests. 119 // Only perform the negotiation when it is explicitly requested by the 120 // server. Thus, do *NOT* use the "REUSABLE_CREDENTIALS" flag here. 121 // 122 // CONNECTION_BASED is specified instead of REQUEST_BASED since we need 123 // to complete a sequence of transactions with the server over the same 124 // connection. 125 // 126 *flags = CONNECTION_BASED | IDENTITY_IGNORED; 127 return NS_OK; 128 } 129 130 // 131 // Always set *identityInvalid == FALSE here. This 132 // will prevent the browser from popping up the authentication 133 // prompt window. Because GSSAPI does not have an API 134 // for fetching initial credentials (ex: A Kerberos TGT), 135 // there is no correct way to get the users credentials. 136 // 137 NS_IMETHODIMP 138 nsHttpNegotiateAuth::ChallengeReceived(nsIHttpAuthenticableChannel* authChannel, 139 const nsACString& challenge, 140 bool isProxyAuth, 141 nsISupports** sessionState, 142 nsISupports** continuationState, 143 bool* identityInvalid) { 144 nsIAuthModule* rawModule = (nsIAuthModule*)*continuationState; 145 146 *identityInvalid = false; 147 148 /* Always fail Negotiate auth for Tor Browser. We don't need it. */ 149 return NS_ERROR_ABORT; 150 151 if (rawModule) { 152 return NS_OK; 153 } 154 155 nsresult rv; 156 nsCOMPtr<nsIAuthModule> module; 157 158 nsCOMPtr<nsIURI> uri; 159 rv = authChannel->GetURI(getter_AddRefs(uri)); 160 if (NS_FAILED(rv)) return rv; 161 162 uint32_t req_flags = nsIAuthModule::REQ_DEFAULT; 163 nsAutoCString service; 164 165 if (isProxyAuth) { 166 if (!TestBoolPref(kNegotiateAuthAllowProxies)) { 167 LOG(("nsHttpNegotiateAuth::ChallengeReceived proxy auth blocked\n")); 168 return NS_ERROR_ABORT; 169 } 170 171 req_flags |= nsIAuthModule::REQ_PROXY_AUTH; 172 nsCOMPtr<nsIProxyInfo> proxyInfo; 173 authChannel->GetProxyInfo(getter_AddRefs(proxyInfo)); 174 NS_ENSURE_STATE(proxyInfo); 175 176 proxyInfo->GetHost(service); 177 } else { 178 bool allowed = 179 TestNotInPBMode(authChannel, isProxyAuth) && 180 (TestNonFqdn(uri) || mozilla::net::auth::URIMatchesPrefPattern( 181 uri, kNegotiateAuthTrustedURIs)); 182 if (!allowed) { 183 LOG(("nsHttpNegotiateAuth::ChallengeReceived URI blocked\n")); 184 return NS_ERROR_ABORT; 185 } 186 187 bool delegation = mozilla::net::auth::URIMatchesPrefPattern( 188 uri, kNegotiateAuthDelegationURIs); 189 if (delegation) { 190 LOG((" using REQ_DELEGATE\n")); 191 req_flags |= nsIAuthModule::REQ_DELEGATE; 192 } 193 194 rv = uri->GetAsciiHost(service); 195 if (NS_FAILED(rv)) return rv; 196 } 197 198 LOG((" service = %s\n", service.get())); 199 200 // 201 // The correct service name for IIS servers is "HTTP/f.q.d.n", so 202 // construct the proper service name for passing to "gss_import_name". 203 // 204 // TODO: Possibly make this a configurable service name for use 205 // with non-standard servers that use stuff like "khttp/f.q.d.n" 206 // instead. 207 // 208 service.InsertLiteral("HTTP@", 0); 209 210 const char* authType; 211 if (TestBoolPref(kNegotiateAuthSSPI)) { 212 LOG((" using negotiate-sspi\n")); 213 authType = "negotiate-sspi"; 214 } else { 215 LOG((" using negotiate-gss\n")); 216 authType = "negotiate-gss"; 217 } 218 219 MOZ_ALWAYS_TRUE(module = nsIAuthModule::CreateInstance(authType)); 220 221 rv = module->Init(service, req_flags, u""_ns, u""_ns, u""_ns); 222 223 if (NS_FAILED(rv)) { 224 return rv; 225 } 226 227 module.forget(continuationState); 228 return NS_OK; 229 } 230 231 NS_IMPL_ISUPPORTS(nsHttpNegotiateAuth, nsIHttpAuthenticator) 232 233 namespace { 234 235 // 236 // GetNextTokenCompleteEvent 237 // 238 // This event is fired on main thread when async call of 239 // nsHttpNegotiateAuth::GenerateCredentials is finished. During the Run() 240 // method the nsIHttpAuthenticatorCallback::OnCredsAvailable is called with 241 // obtained credentials, flags and NS_OK when successful, otherwise 242 // NS_ERROR_FAILURE is returned as a result of failed operation. 243 // 244 class GetNextTokenCompleteEvent final : public nsIRunnable, 245 public nsICancelable { 246 public: 247 NS_DECL_THREADSAFE_ISUPPORTS 248 249 explicit GetNextTokenCompleteEvent(nsIHttpAuthenticatorCallback* aCallback) 250 : mCallback(aCallback) {} 251 252 nsresult DispatchSuccess(const nsACString& aCreds, uint32_t aFlags, 253 already_AddRefed<nsISupports> aSessionState, 254 already_AddRefed<nsISupports> aContinuationState) { 255 // Called from worker thread 256 MOZ_ASSERT(!NS_IsMainThread()); 257 258 mCreds = aCreds; 259 mFlags = aFlags; 260 mResult = NS_OK; 261 mSessionState = aSessionState; 262 mContinuationState = aContinuationState; 263 return NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL); 264 } 265 266 nsresult DispatchError(already_AddRefed<nsISupports> aSessionState, 267 already_AddRefed<nsISupports> aContinuationState) { 268 // Called from worker thread 269 MOZ_ASSERT(!NS_IsMainThread()); 270 271 mResult = NS_ERROR_FAILURE; 272 mSessionState = aSessionState; 273 mContinuationState = aContinuationState; 274 return NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL); 275 } 276 277 NS_IMETHODIMP Run() override { 278 // Runs on main thread 279 MOZ_ASSERT(NS_IsMainThread()); 280 281 if (!mCancelled) { 282 nsCOMPtr<nsIHttpAuthenticatorCallback> callback; 283 callback.swap(mCallback); 284 callback->OnCredsGenerated(mCreds, mFlags, mResult, mSessionState, 285 mContinuationState); 286 } 287 return NS_OK; 288 } 289 290 NS_IMETHODIMP Cancel(nsresult aReason) override { 291 // Supposed to be called from main thread 292 MOZ_ASSERT(NS_IsMainThread()); 293 294 mCancelled = true; 295 nsCOMPtr<nsIHttpAuthenticatorCallback> callback = std::move(mCallback); 296 if (callback) { 297 callback->OnCredsGenerated(mCreds, mFlags, aReason, nullptr, nullptr); 298 } 299 return NS_OK; 300 } 301 302 private: 303 virtual ~GetNextTokenCompleteEvent() = default; 304 305 nsCOMPtr<nsIHttpAuthenticatorCallback> mCallback; 306 nsCString mCreds; 307 uint32_t mFlags = 0; 308 nsresult mResult = NS_OK; 309 bool mCancelled = false; 310 nsCOMPtr<nsISupports> mSessionState; 311 nsCOMPtr<nsISupports> mContinuationState; 312 }; 313 314 inline nsISupports* ToSupports(GetNextTokenCompleteEvent* aEvent) { 315 return static_cast<nsIRunnable*>(aEvent); 316 } 317 318 NS_IMPL_ISUPPORTS(GetNextTokenCompleteEvent, nsIRunnable, nsICancelable) 319 320 // 321 // GetNextTokenRunnable 322 // 323 // This runnable is created by GenerateCredentialsAsync and it runs 324 // on the background thread pool and calls GenerateCredentials. 325 // 326 class GetNextTokenRunnable final : public mozilla::Runnable { 327 ~GetNextTokenRunnable() override = default; 328 329 public: 330 GetNextTokenRunnable( 331 nsMainThreadPtrHandle<nsIHttpAuthenticableChannel>& authChannel, 332 const nsACString& challenge, bool isProxyAuth, const nsAString& domain, 333 const nsAString& username, const nsAString& password, 334 nsISupports* sessionState, nsISupports* continuationState, 335 nsMainThreadPtrHandle<GetNextTokenCompleteEvent>& aCompleteEvent) 336 : mozilla::Runnable("GetNextTokenRunnable"), 337 mAuthChannel(authChannel), 338 mChallenge(challenge), 339 mIsProxyAuth(isProxyAuth), 340 mDomain(domain), 341 mUsername(username), 342 mPassword(password), 343 mSessionState(sessionState), 344 mContinuationState(continuationState), 345 mCompleteEvent(aCompleteEvent) {} 346 347 NS_IMETHODIMP Run() override { 348 // Runs on worker thread 349 MOZ_ASSERT(!NS_IsMainThread()); 350 351 nsCString creds; 352 uint32_t flags; 353 nsresult rv = ObtainCredentialsAndFlags(creds, &flags); 354 355 // Passing session and continuation state this way to not touch 356 // referencing of the object that may not be thread safe. 357 // Not having a thread safe referencing doesn't mean the object 358 // cannot be used on multiple threads (one example is nsAuthSSPI.) 359 // This ensures state objects will be destroyed on the main thread 360 // when not changed by GenerateCredentials. 361 if (NS_FAILED(rv)) { 362 return mCompleteEvent->DispatchError(mSessionState.forget(), 363 mContinuationState.forget()); 364 } 365 366 return mCompleteEvent->DispatchSuccess(creds, flags, mSessionState.forget(), 367 mContinuationState.forget()); 368 } 369 370 NS_IMETHODIMP ObtainCredentialsAndFlags(nsCString& aCreds, uint32_t* aFlags) { 371 nsresult rv; 372 373 // Use negotiate service to call GenerateCredentials outside of main thread 374 nsCOMPtr<nsIHttpAuthenticator> authenticator = new nsHttpNegotiateAuth(); 375 376 nsISupports* sessionState = mSessionState; 377 nsISupports* continuationState = mContinuationState; 378 // The continuationState is for the sake of completeness propagated 379 // to the caller (despite it is not changed in any GenerateCredentials 380 // implementation). 381 // 382 // The only implementation that use sessionState is the 383 // nsHttpDigestAuth::GenerateCredentials. Since there's no reason 384 // to implement nsHttpDigestAuth::GenerateCredentialsAsync 385 // because digest auth does not block the main thread, we won't 386 // propagate changes to sessionState to the caller because of 387 // the change is too complicated on the caller side. 388 // 389 // Should any of the session or continuation states change inside 390 // this method, they must be threadsafe. 391 rv = authenticator->GenerateCredentials( 392 mAuthChannel, mChallenge, mIsProxyAuth, mDomain, mUsername, mPassword, 393 &sessionState, &continuationState, aFlags, aCreds); 394 if (mSessionState != sessionState) { 395 mSessionState = sessionState; 396 } 397 if (mContinuationState != continuationState) { 398 mContinuationState = continuationState; 399 } 400 return rv; 401 } 402 403 private: 404 nsMainThreadPtrHandle<nsIHttpAuthenticableChannel> mAuthChannel; 405 nsCString mChallenge; 406 bool mIsProxyAuth; 407 nsString mDomain; 408 nsString mUsername; 409 nsString mPassword; 410 nsCOMPtr<nsISupports> mSessionState; 411 nsCOMPtr<nsISupports> mContinuationState; 412 nsMainThreadPtrHandle<GetNextTokenCompleteEvent> mCompleteEvent; 413 }; 414 415 } // anonymous namespace 416 417 NS_IMETHODIMP 418 nsHttpNegotiateAuth::GenerateCredentialsAsync( 419 nsIHttpAuthenticableChannel* authChannel, 420 nsIHttpAuthenticatorCallback* aCallback, const nsACString& challenge, 421 bool isProxyAuth, const nsAString& domain, const nsAString& username, 422 const nsAString& password, nsISupports* sessionState, 423 nsISupports* continuationState, nsICancelable** aCancelable) { 424 NS_ENSURE_ARG(aCallback); 425 NS_ENSURE_ARG_POINTER(aCancelable); 426 427 nsMainThreadPtrHandle<nsIHttpAuthenticableChannel> handle( 428 new nsMainThreadPtrHolder<nsIHttpAuthenticableChannel>( 429 "nsIHttpAuthenticableChannel", authChannel, false)); 430 nsMainThreadPtrHandle<GetNextTokenCompleteEvent> cancelEvent( 431 new nsMainThreadPtrHolder<GetNextTokenCompleteEvent>( 432 "GetNextTokenCompleteEvent", new GetNextTokenCompleteEvent(aCallback), 433 false)); 434 nsCOMPtr<nsIRunnable> getNextTokenRunnable = new GetNextTokenRunnable( 435 handle, challenge, isProxyAuth, domain, username, password, sessionState, 436 continuationState, cancelEvent); 437 438 nsresult rv = NS_DispatchBackgroundTask( 439 getNextTokenRunnable, nsIEventTarget::DISPATCH_EVENT_MAY_BLOCK); 440 NS_ENSURE_SUCCESS(rv, rv); 441 442 RefPtr<GetNextTokenCompleteEvent> cancelable(cancelEvent.get()); 443 cancelable.forget(aCancelable); 444 return NS_OK; 445 } 446 447 // 448 // GenerateCredentials 449 // 450 // This routine is responsible for creating the correct authentication 451 // blob to pass to the server that requested "Negotiate" authentication. 452 // 453 NS_IMETHODIMP 454 nsHttpNegotiateAuth::GenerateCredentials( 455 nsIHttpAuthenticableChannel* authChannel, const nsACString& aChallenge, 456 bool isProxyAuth, const nsAString& domain, const nsAString& username, 457 const nsAString& password, nsISupports** sessionState, 458 nsISupports** continuationState, uint32_t* flags, nsACString& creds) { 459 // ChallengeReceived must have been called previously. 460 nsIAuthModule* module = (nsIAuthModule*)*continuationState; 461 NS_ENSURE_TRUE(module, NS_ERROR_NOT_INITIALIZED); 462 463 *flags = USING_INTERNAL_IDENTITY; 464 465 LOG(("nsHttpNegotiateAuth::GenerateCredentials() [challenge=%s]\n", 466 aChallenge.BeginReading())); 467 468 #ifdef DEBUG 469 bool isGssapiAuth = StringBeginsWith(aChallenge, "Negotiate"_ns, 470 nsCaseInsensitiveCStringComparator); 471 NS_ASSERTION(isGssapiAuth, "Unexpected challenge"); 472 #endif 473 474 // 475 // If the "Negotiate:" header had some data associated with it, 476 // that data should be used as the input to this call. This may 477 // be a continuation of an earlier call because GSSAPI authentication 478 // often takes multiple round-trips to complete depending on the 479 // context flags given. We want to use MUTUAL_AUTHENTICATION which 480 // generally *does* require multiple round-trips. Don't assume 481 // auth can be completed in just 1 call. 482 // 483 484 nsAutoCString inToken; 485 if (aChallenge.Length() > kNegotiateLen) { 486 nsDependentCSubstring challenge(aChallenge, kNegotiateLen); 487 uint32_t startPos = 0; 488 while (startPos < challenge.Length() && challenge[startPos] == ' ') { 489 startPos++; 490 } 491 if (startPos == challenge.Length()) { 492 return NS_ERROR_UNEXPECTED; 493 } 494 495 // strip off any padding (see bug 230351) 496 uint32_t len = challenge.Length(); 497 while (len > startPos && challenge[len - 1] == '=') { 498 len--; 499 } 500 501 // 502 // Decode the response that followed the "Negotiate" token 503 // 504 (void)Base64Decode( 505 nsDependentCSubstring(challenge, startPos, len - startPos), inToken); 506 } 507 508 void* outToken = nullptr; 509 uint32_t outTokenLen = 0; 510 nsresult rv = module->GetNextToken(inToken.get(), inToken.Length(), &outToken, 511 &outTokenLen); 512 if (NS_FAILED(rv)) { 513 if (outToken) { 514 // Technically if the call fails we shouln't have allocated, but 515 // Coverity doesn't know that. 516 free(outToken); 517 } 518 return rv; 519 } 520 521 if (outTokenLen == 0) { 522 LOG((" No output token to send, exiting")); 523 return NS_ERROR_FAILURE; 524 } 525 526 // 527 // base64 encode the output token. 528 // 529 nsAutoCString encodedToken; 530 rv = mozilla::Base64Encode( 531 nsDependentCSubstring((char*)outToken, outTokenLen), encodedToken); 532 free(outToken); 533 if (NS_FAILED(rv)) { 534 return rv; 535 } 536 537 LOG((" Sending a token of length %d\n", outTokenLen)); 538 539 creds = nsPrintfCString("%s %s", kNegotiate, encodedToken.get()); 540 return rv; 541 } 542 543 bool nsHttpNegotiateAuth::TestBoolPref(const char* pref) { 544 nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); 545 if (!prefs) return false; 546 547 bool val; 548 nsresult rv = prefs->GetBoolPref(pref, &val); 549 if (NS_FAILED(rv)) return false; 550 551 return val; 552 } 553 554 bool nsHttpNegotiateAuth::TestNonFqdn(nsIURI* uri) { 555 nsAutoCString host; 556 557 if (!TestBoolPref(kNegotiateAuthAllowNonFqdn)) { 558 return false; 559 } 560 561 if (NS_FAILED(uri->GetAsciiHost(host))) { 562 return false; 563 } 564 565 // return true if host does not contain a dot and is not an ip address 566 return !host.IsEmpty() && !host.Contains('.') && 567 !mozilla::net::HostIsIPLiteral(host); 568 }