tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }