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/. */
7 // HTTP Negotiate Authentication Support Module
9 // Described by IETF Internet draft: draft-brezak-kerberos-http-00.txt
10 // (formerly draft-brezak-spnego-http-04.txt)
12 // Also described here:
13 // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnsecure/html/http-sso-1.asp
19 #include "nsHttpNegotiateAuth.h"
21 #include "nsIHttpAuthenticableChannel.h"
22 #include "nsIAuthModule.h"
23 #include "nsIPrefBranch.h"
24 #include "nsIPrefService.h"
25 #include "nsIProxyInfo.h"
30 #include "nsProxyRelease.h"
32 #include "mozilla/Base64.h"
33 #include "mozilla/Tokenizer.h"
34 #include "mozilla/UniquePtr.h"
35 #include "mozilla/Sprintf.h"
36 #include "nsIChannel.h"
37 #include "nsNetUtil.h"
38 #include "nsThreadUtils.h"
39 #include "nsIHttpAuthenticatorCallback.h"
40 #include "nsICancelable.h"
41 #include "mozilla/net/HttpAuthUtils.h"
42 #include "mozilla/ClearOnShutdown.h"
43 #include "mozilla/net/DNS.h"
44 #include "mozilla/StaticPrefs_browser.h"
46 using mozilla::Base64Decode
;
48 //-----------------------------------------------------------------------------
50 static const char kNegotiate
[] = "Negotiate";
51 static const char kNegotiateAuthTrustedURIs
[] =
52 "network.negotiate-auth.trusted-uris";
53 static const char kNegotiateAuthDelegationURIs
[] =
54 "network.negotiate-auth.delegation-uris";
55 static const char kNegotiateAuthAllowProxies
[] =
56 "network.negotiate-auth.allow-proxies";
57 static const char kNegotiateAuthAllowNonFqdn
[] =
58 "network.negotiate-auth.allow-non-fqdn";
59 static const char kNegotiateAuthSSPI
[] = "network.auth.use-sspi";
60 static const char kSSOinPBmode
[] = "network.auth.private-browsing-sso";
62 mozilla::StaticRefPtr
<nsHttpNegotiateAuth
> nsHttpNegotiateAuth::gSingleton
;
64 #define kNegotiateLen (sizeof(kNegotiate) - 1)
65 #define DEFAULT_THREAD_TIMEOUT_MS 30000
67 //-----------------------------------------------------------------------------
69 // Return false when the channel comes from a Private browsing window.
70 static bool TestNotInPBMode(nsIHttpAuthenticableChannel
* authChannel
,
72 // Proxy should go all the time, it's not considered a privacy leak
73 // to send default credentials to a proxy.
78 nsCOMPtr
<nsIChannel
> bareChannel
= do_QueryInterface(authChannel
);
79 MOZ_ASSERT(bareChannel
);
81 if (!NS_UsePrivateBrowsing(bareChannel
)) {
85 nsCOMPtr
<nsIPrefBranch
> prefs
= do_GetService(NS_PREFSERVICE_CONTRACTID
);
88 if (NS_SUCCEEDED(prefs
->GetBoolPref(kSSOinPBmode
, &ssoInPb
)) && ssoInPb
) {
92 // When the "Never remember history" option is set, all channels are
93 // set PB mode flag, but here we want to make an exception, users
94 // want their credentials go out.
95 if (mozilla::StaticPrefs::browser_privatebrowsing_autostart()) {
103 already_AddRefed
<nsIHttpAuthenticator
> nsHttpNegotiateAuth::GetOrCreate() {
104 nsCOMPtr
<nsIHttpAuthenticator
> authenticator
;
106 authenticator
= gSingleton
;
108 gSingleton
= new nsHttpNegotiateAuth();
109 mozilla::ClearOnShutdown(&gSingleton
);
110 authenticator
= gSingleton
;
113 return authenticator
.forget();
117 nsHttpNegotiateAuth::GetAuthFlags(uint32_t* flags
) {
119 // Negotiate Auth creds should not be reused across multiple requests.
120 // Only perform the negotiation when it is explicitly requested by the
121 // server. Thus, do *NOT* use the "REUSABLE_CREDENTIALS" flag here.
123 // CONNECTION_BASED is specified instead of REQUEST_BASED since we need
124 // to complete a sequence of transactions with the server over the same
127 *flags
= CONNECTION_BASED
| IDENTITY_IGNORED
;
132 // Always set *identityInvalid == FALSE here. This
133 // will prevent the browser from popping up the authentication
134 // prompt window. Because GSSAPI does not have an API
135 // for fetching initial credentials (ex: A Kerberos TGT),
136 // there is no correct way to get the users credentials.
139 nsHttpNegotiateAuth::ChallengeReceived(nsIHttpAuthenticableChannel
* authChannel
,
140 const nsACString
& challenge
,
142 nsISupports
** sessionState
,
143 nsISupports
** continuationState
,
144 bool* identityInvalid
) {
145 nsIAuthModule
* rawModule
= (nsIAuthModule
*)*continuationState
;
147 *identityInvalid
= false;
153 nsCOMPtr
<nsIAuthModule
> module
;
155 nsCOMPtr
<nsIURI
> uri
;
156 rv
= authChannel
->GetURI(getter_AddRefs(uri
));
157 if (NS_FAILED(rv
)) return rv
;
159 uint32_t req_flags
= nsIAuthModule::REQ_DEFAULT
;
160 nsAutoCString service
;
163 if (!TestBoolPref(kNegotiateAuthAllowProxies
)) {
164 LOG(("nsHttpNegotiateAuth::ChallengeReceived proxy auth blocked\n"));
165 return NS_ERROR_ABORT
;
168 req_flags
|= nsIAuthModule::REQ_PROXY_AUTH
;
169 nsCOMPtr
<nsIProxyInfo
> proxyInfo
;
170 authChannel
->GetProxyInfo(getter_AddRefs(proxyInfo
));
171 NS_ENSURE_STATE(proxyInfo
);
173 proxyInfo
->GetHost(service
);
176 TestNotInPBMode(authChannel
, isProxyAuth
) &&
177 (TestNonFqdn(uri
) || mozilla::net::auth::URIMatchesPrefPattern(
178 uri
, kNegotiateAuthTrustedURIs
));
180 LOG(("nsHttpNegotiateAuth::ChallengeReceived URI blocked\n"));
181 return NS_ERROR_ABORT
;
184 bool delegation
= mozilla::net::auth::URIMatchesPrefPattern(
185 uri
, kNegotiateAuthDelegationURIs
);
187 LOG((" using REQ_DELEGATE\n"));
188 req_flags
|= nsIAuthModule::REQ_DELEGATE
;
191 rv
= uri
->GetAsciiHost(service
);
192 if (NS_FAILED(rv
)) return rv
;
195 LOG((" service = %s\n", service
.get()));
198 // The correct service name for IIS servers is "HTTP/f.q.d.n", so
199 // construct the proper service name for passing to "gss_import_name".
201 // TODO: Possibly make this a configurable service name for use
202 // with non-standard servers that use stuff like "khttp/f.q.d.n"
205 service
.InsertLiteral("HTTP@", 0);
207 const char* authType
;
208 if (TestBoolPref(kNegotiateAuthSSPI
)) {
209 LOG((" using negotiate-sspi\n"));
210 authType
= "negotiate-sspi";
212 LOG((" using negotiate-gss\n"));
213 authType
= "negotiate-gss";
216 MOZ_ALWAYS_TRUE(module
= nsIAuthModule::CreateInstance(authType
));
218 rv
= module
->Init(service
, req_flags
, u
""_ns
, u
""_ns
, u
""_ns
);
224 module
.forget(continuationState
);
228 NS_IMPL_ISUPPORTS(nsHttpNegotiateAuth
, nsIHttpAuthenticator
)
233 // GetNextTokenCompleteEvent
235 // This event is fired on main thread when async call of
236 // nsHttpNegotiateAuth::GenerateCredentials is finished. During the Run()
237 // method the nsIHttpAuthenticatorCallback::OnCredsAvailable is called with
238 // obtained credentials, flags and NS_OK when successful, otherwise
239 // NS_ERROR_FAILURE is returned as a result of failed operation.
241 class GetNextTokenCompleteEvent final
: public nsIRunnable
,
242 public nsICancelable
{
244 NS_DECL_THREADSAFE_ISUPPORTS
246 explicit GetNextTokenCompleteEvent(nsIHttpAuthenticatorCallback
* aCallback
)
247 : mCallback(aCallback
) {}
249 nsresult
DispatchSuccess(const nsACString
& aCreds
, uint32_t aFlags
,
250 already_AddRefed
<nsISupports
> aSessionState
,
251 already_AddRefed
<nsISupports
> aContinuationState
) {
252 // Called from worker thread
253 MOZ_ASSERT(!NS_IsMainThread());
258 mSessionState
= aSessionState
;
259 mContinuationState
= aContinuationState
;
260 return NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL
);
263 nsresult
DispatchError(already_AddRefed
<nsISupports
> aSessionState
,
264 already_AddRefed
<nsISupports
> aContinuationState
) {
265 // Called from worker thread
266 MOZ_ASSERT(!NS_IsMainThread());
268 mResult
= NS_ERROR_FAILURE
;
269 mSessionState
= aSessionState
;
270 mContinuationState
= aContinuationState
;
271 return NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL
);
274 NS_IMETHODIMP
Run() override
{
275 // Runs on main thread
276 MOZ_ASSERT(NS_IsMainThread());
279 nsCOMPtr
<nsIHttpAuthenticatorCallback
> callback
;
280 callback
.swap(mCallback
);
281 callback
->OnCredsGenerated(mCreds
, mFlags
, mResult
, mSessionState
,
287 NS_IMETHODIMP
Cancel(nsresult aReason
) override
{
288 // Supposed to be called from main thread
289 MOZ_ASSERT(NS_IsMainThread());
292 nsCOMPtr
<nsIHttpAuthenticatorCallback
> callback
= std::move(mCallback
);
294 callback
->OnCredsGenerated(mCreds
, mFlags
, aReason
, nullptr, nullptr);
300 virtual ~GetNextTokenCompleteEvent() = default;
302 nsCOMPtr
<nsIHttpAuthenticatorCallback
> mCallback
;
305 nsresult mResult
= NS_OK
;
306 bool mCancelled
= false;
307 nsCOMPtr
<nsISupports
> mSessionState
;
308 nsCOMPtr
<nsISupports
> mContinuationState
;
311 inline nsISupports
* ToSupports(GetNextTokenCompleteEvent
* aEvent
) {
312 return static_cast<nsIRunnable
*>(aEvent
);
315 NS_IMPL_ISUPPORTS(GetNextTokenCompleteEvent
, nsIRunnable
, nsICancelable
)
318 // GetNextTokenRunnable
320 // This runnable is created by GenerateCredentialsAsync and it runs
321 // on the background thread pool and calls GenerateCredentials.
323 class GetNextTokenRunnable final
: public mozilla::Runnable
{
324 ~GetNextTokenRunnable() override
= default;
327 GetNextTokenRunnable(
328 nsMainThreadPtrHandle
<nsIHttpAuthenticableChannel
>& authChannel
,
329 const nsACString
& challenge
, bool isProxyAuth
, const nsAString
& domain
,
330 const nsAString
& username
, const nsAString
& password
,
331 nsISupports
* sessionState
, nsISupports
* continuationState
,
332 nsMainThreadPtrHandle
<GetNextTokenCompleteEvent
>& aCompleteEvent
)
333 : mozilla::Runnable("GetNextTokenRunnable"),
334 mAuthChannel(authChannel
),
335 mChallenge(challenge
),
336 mIsProxyAuth(isProxyAuth
),
340 mSessionState(sessionState
),
341 mContinuationState(continuationState
),
342 mCompleteEvent(aCompleteEvent
) {}
344 NS_IMETHODIMP
Run() override
{
345 // Runs on worker thread
346 MOZ_ASSERT(!NS_IsMainThread());
350 nsresult rv
= ObtainCredentialsAndFlags(creds
, &flags
);
352 // Passing session and continuation state this way to not touch
353 // referencing of the object that may not be thread safe.
354 // Not having a thread safe referencing doesn't mean the object
355 // cannot be used on multiple threads (one example is nsAuthSSPI.)
356 // This ensures state objects will be destroyed on the main thread
357 // when not changed by GenerateCredentials.
359 return mCompleteEvent
->DispatchError(mSessionState
.forget(),
360 mContinuationState
.forget());
363 return mCompleteEvent
->DispatchSuccess(creds
, flags
, mSessionState
.forget(),
364 mContinuationState
.forget());
367 NS_IMETHODIMP
ObtainCredentialsAndFlags(nsCString
& aCreds
, uint32_t* aFlags
) {
370 // Use negotiate service to call GenerateCredentials outside of main thread
371 nsCOMPtr
<nsIHttpAuthenticator
> authenticator
= new nsHttpNegotiateAuth();
373 nsISupports
* sessionState
= mSessionState
;
374 nsISupports
* continuationState
= mContinuationState
;
375 // The continuationState is for the sake of completeness propagated
376 // to the caller (despite it is not changed in any GenerateCredentials
379 // The only implementation that use sessionState is the
380 // nsHttpDigestAuth::GenerateCredentials. Since there's no reason
381 // to implement nsHttpDigestAuth::GenerateCredentialsAsync
382 // because digest auth does not block the main thread, we won't
383 // propagate changes to sessionState to the caller because of
384 // the change is too complicated on the caller side.
386 // Should any of the session or continuation states change inside
387 // this method, they must be threadsafe.
388 rv
= authenticator
->GenerateCredentials(
389 mAuthChannel
, mChallenge
, mIsProxyAuth
, mDomain
, mUsername
, mPassword
,
390 &sessionState
, &continuationState
, aFlags
, aCreds
);
391 if (mSessionState
!= sessionState
) {
392 mSessionState
= sessionState
;
394 if (mContinuationState
!= continuationState
) {
395 mContinuationState
= continuationState
;
401 nsMainThreadPtrHandle
<nsIHttpAuthenticableChannel
> mAuthChannel
;
402 nsCString mChallenge
;
407 nsCOMPtr
<nsISupports
> mSessionState
;
408 nsCOMPtr
<nsISupports
> mContinuationState
;
409 nsMainThreadPtrHandle
<GetNextTokenCompleteEvent
> mCompleteEvent
;
412 } // anonymous namespace
415 nsHttpNegotiateAuth::GenerateCredentialsAsync(
416 nsIHttpAuthenticableChannel
* authChannel
,
417 nsIHttpAuthenticatorCallback
* aCallback
, const nsACString
& challenge
,
418 bool isProxyAuth
, const nsAString
& domain
, const nsAString
& username
,
419 const nsAString
& password
, nsISupports
* sessionState
,
420 nsISupports
* continuationState
, nsICancelable
** aCancelable
) {
421 NS_ENSURE_ARG(aCallback
);
422 NS_ENSURE_ARG_POINTER(aCancelable
);
424 nsMainThreadPtrHandle
<nsIHttpAuthenticableChannel
> handle(
425 new nsMainThreadPtrHolder
<nsIHttpAuthenticableChannel
>(
426 "nsIHttpAuthenticableChannel", authChannel
, false));
427 nsMainThreadPtrHandle
<GetNextTokenCompleteEvent
> cancelEvent(
428 new nsMainThreadPtrHolder
<GetNextTokenCompleteEvent
>(
429 "GetNextTokenCompleteEvent", new GetNextTokenCompleteEvent(aCallback
),
431 nsCOMPtr
<nsIRunnable
> getNextTokenRunnable
= new GetNextTokenRunnable(
432 handle
, challenge
, isProxyAuth
, domain
, username
, password
, sessionState
,
433 continuationState
, cancelEvent
);
435 nsresult rv
= NS_DispatchBackgroundTask(
436 getNextTokenRunnable
, nsIEventTarget::DISPATCH_EVENT_MAY_BLOCK
);
437 NS_ENSURE_SUCCESS(rv
, rv
);
439 RefPtr
<GetNextTokenCompleteEvent
> cancelable(cancelEvent
.get());
440 cancelable
.forget(aCancelable
);
445 // GenerateCredentials
447 // This routine is responsible for creating the correct authentication
448 // blob to pass to the server that requested "Negotiate" authentication.
451 nsHttpNegotiateAuth::GenerateCredentials(
452 nsIHttpAuthenticableChannel
* authChannel
, const nsACString
& aChallenge
,
453 bool isProxyAuth
, const nsAString
& domain
, const nsAString
& username
,
454 const nsAString
& password
, nsISupports
** sessionState
,
455 nsISupports
** continuationState
, uint32_t* flags
, nsACString
& creds
) {
456 // ChallengeReceived must have been called previously.
457 nsIAuthModule
* module
= (nsIAuthModule
*)*continuationState
;
458 NS_ENSURE_TRUE(module
, NS_ERROR_NOT_INITIALIZED
);
460 *flags
= USING_INTERNAL_IDENTITY
;
462 LOG(("nsHttpNegotiateAuth::GenerateCredentials() [challenge=%s]\n",
463 aChallenge
.BeginReading()));
466 bool isGssapiAuth
= StringBeginsWith(aChallenge
, "Negotiate"_ns
,
467 nsCaseInsensitiveCStringComparator
);
468 NS_ASSERTION(isGssapiAuth
, "Unexpected challenge");
472 // If the "Negotiate:" header had some data associated with it,
473 // that data should be used as the input to this call. This may
474 // be a continuation of an earlier call because GSSAPI authentication
475 // often takes multiple round-trips to complete depending on the
476 // context flags given. We want to use MUTUAL_AUTHENTICATION which
477 // generally *does* require multiple round-trips. Don't assume
478 // auth can be completed in just 1 call.
481 nsAutoCString inToken
;
482 if (aChallenge
.Length() > kNegotiateLen
) {
483 nsDependentCSubstring
challenge(aChallenge
, kNegotiateLen
);
484 uint32_t startPos
= 0;
485 while (startPos
< challenge
.Length() && challenge
[startPos
] == ' ') {
488 if (startPos
== challenge
.Length()) {
489 return NS_ERROR_UNEXPECTED
;
492 // strip off any padding (see bug 230351)
493 uint32_t len
= challenge
.Length();
494 while (len
> startPos
&& challenge
[len
- 1] == '=') {
499 // Decode the response that followed the "Negotiate" token
502 nsDependentCSubstring(challenge
, startPos
, len
- startPos
), inToken
);
505 void* outToken
= nullptr;
506 uint32_t outTokenLen
= 0;
507 nsresult rv
= module
->GetNextToken(inToken
.get(), inToken
.Length(), &outToken
,
511 // Technically if the call fails we shouln't have allocated, but
512 // Coverity doesn't know that.
518 if (outTokenLen
== 0) {
519 LOG((" No output token to send, exiting"));
520 return NS_ERROR_FAILURE
;
524 // base64 encode the output token.
526 nsAutoCString encodedToken
;
527 rv
= mozilla::Base64Encode(
528 nsDependentCSubstring((char*)outToken
, outTokenLen
), encodedToken
);
534 LOG((" Sending a token of length %d\n", outTokenLen
));
536 creds
= nsPrintfCString("%s %s", kNegotiate
, encodedToken
.get());
540 bool nsHttpNegotiateAuth::TestBoolPref(const char* pref
) {
541 nsCOMPtr
<nsIPrefBranch
> prefs
= do_GetService(NS_PREFSERVICE_CONTRACTID
);
542 if (!prefs
) return false;
545 nsresult rv
= prefs
->GetBoolPref(pref
, &val
);
546 if (NS_FAILED(rv
)) return false;
551 bool nsHttpNegotiateAuth::TestNonFqdn(nsIURI
* uri
) {
554 if (!TestBoolPref(kNegotiateAuthAllowNonFqdn
)) {
558 if (NS_FAILED(uri
->GetAsciiHost(host
))) {
562 // return true if host does not contain a dot and is not an ip address
563 return !host
.IsEmpty() && !host
.Contains('.') &&
564 !mozilla::net::HostIsIPLiteral(host
);