extensions/auth/nsHttpNegotiateAuth.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* vim:set ts=4 sw=4 sts=4 et cindent: */
michael@0 2 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 3 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 5
michael@0 6 //
michael@0 7 // HTTP Negotiate Authentication Support Module
michael@0 8 //
michael@0 9 // Described by IETF Internet draft: draft-brezak-kerberos-http-00.txt
michael@0 10 // (formerly draft-brezak-spnego-http-04.txt)
michael@0 11 //
michael@0 12 // Also described here:
michael@0 13 // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnsecure/html/http-sso-1.asp
michael@0 14 //
michael@0 15
michael@0 16 #include <string.h>
michael@0 17 #include <stdlib.h>
michael@0 18
michael@0 19 #include "nsAuth.h"
michael@0 20 #include "nsHttpNegotiateAuth.h"
michael@0 21
michael@0 22 #include "nsIHttpAuthenticableChannel.h"
michael@0 23 #include "nsIProxiedChannel.h"
michael@0 24 #include "nsIAuthModule.h"
michael@0 25 #include "nsIServiceManager.h"
michael@0 26 #include "nsIPrefService.h"
michael@0 27 #include "nsIPrefBranch.h"
michael@0 28 #include "nsIProxyInfo.h"
michael@0 29 #include "nsIURI.h"
michael@0 30 #include "nsCOMPtr.h"
michael@0 31 #include "nsString.h"
michael@0 32 #include "nsNetCID.h"
michael@0 33 #include "plbase64.h"
michael@0 34 #include "plstr.h"
michael@0 35 #include "prprf.h"
michael@0 36 #include "prlog.h"
michael@0 37 #include "prmem.h"
michael@0 38 #include "prnetdb.h"
michael@0 39 #include "mozilla/Likely.h"
michael@0 40
michael@0 41 //-----------------------------------------------------------------------------
michael@0 42
michael@0 43 static const char kNegotiate[] = "Negotiate";
michael@0 44 static const char kNegotiateAuthTrustedURIs[] = "network.negotiate-auth.trusted-uris";
michael@0 45 static const char kNegotiateAuthDelegationURIs[] = "network.negotiate-auth.delegation-uris";
michael@0 46 static const char kNegotiateAuthAllowProxies[] = "network.negotiate-auth.allow-proxies";
michael@0 47 static const char kNegotiateAuthAllowNonFqdn[] = "network.negotiate-auth.allow-non-fqdn";
michael@0 48 static const char kNegotiateAuthSSPI[] = "network.auth.use-sspi";
michael@0 49
michael@0 50 #define kNegotiateLen (sizeof(kNegotiate)-1)
michael@0 51
michael@0 52 //-----------------------------------------------------------------------------
michael@0 53
michael@0 54 NS_IMETHODIMP
michael@0 55 nsHttpNegotiateAuth::GetAuthFlags(uint32_t *flags)
michael@0 56 {
michael@0 57 //
michael@0 58 // Negotiate Auth creds should not be reused across multiple requests.
michael@0 59 // Only perform the negotiation when it is explicitly requested by the
michael@0 60 // server. Thus, do *NOT* use the "REUSABLE_CREDENTIALS" flag here.
michael@0 61 //
michael@0 62 // CONNECTION_BASED is specified instead of REQUEST_BASED since we need
michael@0 63 // to complete a sequence of transactions with the server over the same
michael@0 64 // connection.
michael@0 65 //
michael@0 66 *flags = CONNECTION_BASED | IDENTITY_IGNORED;
michael@0 67 return NS_OK;
michael@0 68 }
michael@0 69
michael@0 70 //
michael@0 71 // Always set *identityInvalid == FALSE here. This
michael@0 72 // will prevent the browser from popping up the authentication
michael@0 73 // prompt window. Because GSSAPI does not have an API
michael@0 74 // for fetching initial credentials (ex: A Kerberos TGT),
michael@0 75 // there is no correct way to get the users credentials.
michael@0 76 //
michael@0 77 NS_IMETHODIMP
michael@0 78 nsHttpNegotiateAuth::ChallengeReceived(nsIHttpAuthenticableChannel *authChannel,
michael@0 79 const char *challenge,
michael@0 80 bool isProxyAuth,
michael@0 81 nsISupports **sessionState,
michael@0 82 nsISupports **continuationState,
michael@0 83 bool *identityInvalid)
michael@0 84 {
michael@0 85 nsIAuthModule *module = (nsIAuthModule *) *continuationState;
michael@0 86
michael@0 87 *identityInvalid = false;
michael@0 88
michael@0 89 /* Always fail Negotiate auth for Tor Browser. We don't need it. */
michael@0 90 return NS_ERROR_ABORT;
michael@0 91
michael@0 92 if (module)
michael@0 93 return NS_OK;
michael@0 94
michael@0 95 nsresult rv;
michael@0 96
michael@0 97 nsCOMPtr<nsIURI> uri;
michael@0 98 rv = authChannel->GetURI(getter_AddRefs(uri));
michael@0 99 if (NS_FAILED(rv))
michael@0 100 return rv;
michael@0 101
michael@0 102 uint32_t req_flags = nsIAuthModule::REQ_DEFAULT;
michael@0 103 nsAutoCString service;
michael@0 104
michael@0 105 if (isProxyAuth) {
michael@0 106 if (!TestBoolPref(kNegotiateAuthAllowProxies)) {
michael@0 107 LOG(("nsHttpNegotiateAuth::ChallengeReceived proxy auth blocked\n"));
michael@0 108 return NS_ERROR_ABORT;
michael@0 109 }
michael@0 110
michael@0 111 req_flags |= nsIAuthModule::REQ_PROXY_AUTH;
michael@0 112 nsCOMPtr<nsIProxyInfo> proxyInfo;
michael@0 113 authChannel->GetProxyInfo(getter_AddRefs(proxyInfo));
michael@0 114 NS_ENSURE_STATE(proxyInfo);
michael@0 115
michael@0 116 proxyInfo->GetHost(service);
michael@0 117 }
michael@0 118 else {
michael@0 119 bool allowed = TestNonFqdn(uri) ||
michael@0 120 TestPref(uri, kNegotiateAuthTrustedURIs);
michael@0 121 if (!allowed) {
michael@0 122 LOG(("nsHttpNegotiateAuth::ChallengeReceived URI blocked\n"));
michael@0 123 return NS_ERROR_ABORT;
michael@0 124 }
michael@0 125
michael@0 126 bool delegation = TestPref(uri, kNegotiateAuthDelegationURIs);
michael@0 127 if (delegation) {
michael@0 128 LOG((" using REQ_DELEGATE\n"));
michael@0 129 req_flags |= nsIAuthModule::REQ_DELEGATE;
michael@0 130 }
michael@0 131
michael@0 132 rv = uri->GetAsciiHost(service);
michael@0 133 if (NS_FAILED(rv))
michael@0 134 return rv;
michael@0 135 }
michael@0 136
michael@0 137 LOG((" service = %s\n", service.get()));
michael@0 138
michael@0 139 //
michael@0 140 // The correct service name for IIS servers is "HTTP/f.q.d.n", so
michael@0 141 // construct the proper service name for passing to "gss_import_name".
michael@0 142 //
michael@0 143 // TODO: Possibly make this a configurable service name for use
michael@0 144 // with non-standard servers that use stuff like "khttp/f.q.d.n"
michael@0 145 // instead.
michael@0 146 //
michael@0 147 service.Insert("HTTP@", 0);
michael@0 148
michael@0 149 const char *contractID;
michael@0 150 if (TestBoolPref(kNegotiateAuthSSPI)) {
michael@0 151 LOG((" using negotiate-sspi\n"));
michael@0 152 contractID = NS_AUTH_MODULE_CONTRACTID_PREFIX "negotiate-sspi";
michael@0 153 }
michael@0 154 else {
michael@0 155 LOG((" using negotiate-gss\n"));
michael@0 156 contractID = NS_AUTH_MODULE_CONTRACTID_PREFIX "negotiate-gss";
michael@0 157 }
michael@0 158
michael@0 159 rv = CallCreateInstance(contractID, &module);
michael@0 160
michael@0 161 if (NS_FAILED(rv)) {
michael@0 162 LOG((" Failed to load Negotiate Module \n"));
michael@0 163 return rv;
michael@0 164 }
michael@0 165
michael@0 166 rv = module->Init(service.get(), req_flags, nullptr, nullptr, nullptr);
michael@0 167
michael@0 168 if (NS_FAILED(rv)) {
michael@0 169 NS_RELEASE(module);
michael@0 170 return rv;
michael@0 171 }
michael@0 172
michael@0 173 *continuationState = module;
michael@0 174 return NS_OK;
michael@0 175 }
michael@0 176
michael@0 177 NS_IMPL_ISUPPORTS(nsHttpNegotiateAuth, nsIHttpAuthenticator)
michael@0 178
michael@0 179 //
michael@0 180 // GenerateCredentials
michael@0 181 //
michael@0 182 // This routine is responsible for creating the correct authentication
michael@0 183 // blob to pass to the server that requested "Negotiate" authentication.
michael@0 184 //
michael@0 185 NS_IMETHODIMP
michael@0 186 nsHttpNegotiateAuth::GenerateCredentials(nsIHttpAuthenticableChannel *authChannel,
michael@0 187 const char *challenge,
michael@0 188 bool isProxyAuth,
michael@0 189 const char16_t *domain,
michael@0 190 const char16_t *username,
michael@0 191 const char16_t *password,
michael@0 192 nsISupports **sessionState,
michael@0 193 nsISupports **continuationState,
michael@0 194 uint32_t *flags,
michael@0 195 char **creds)
michael@0 196 {
michael@0 197 // ChallengeReceived must have been called previously.
michael@0 198 nsIAuthModule *module = (nsIAuthModule *) *continuationState;
michael@0 199 NS_ENSURE_TRUE(module, NS_ERROR_NOT_INITIALIZED);
michael@0 200
michael@0 201 *flags = USING_INTERNAL_IDENTITY;
michael@0 202
michael@0 203 LOG(("nsHttpNegotiateAuth::GenerateCredentials() [challenge=%s]\n", challenge));
michael@0 204
michael@0 205 NS_ASSERTION(creds, "null param");
michael@0 206
michael@0 207 #ifdef DEBUG
michael@0 208 bool isGssapiAuth =
michael@0 209 !PL_strncasecmp(challenge, kNegotiate, kNegotiateLen);
michael@0 210 NS_ASSERTION(isGssapiAuth, "Unexpected challenge");
michael@0 211 #endif
michael@0 212
michael@0 213 //
michael@0 214 // If the "Negotiate:" header had some data associated with it,
michael@0 215 // that data should be used as the input to this call. This may
michael@0 216 // be a continuation of an earlier call because GSSAPI authentication
michael@0 217 // often takes multiple round-trips to complete depending on the
michael@0 218 // context flags given. We want to use MUTUAL_AUTHENTICATION which
michael@0 219 // generally *does* require multiple round-trips. Don't assume
michael@0 220 // auth can be completed in just 1 call.
michael@0 221 //
michael@0 222 unsigned int len = strlen(challenge);
michael@0 223
michael@0 224 void *inToken, *outToken;
michael@0 225 uint32_t inTokenLen, outTokenLen;
michael@0 226
michael@0 227 if (len > kNegotiateLen) {
michael@0 228 challenge += kNegotiateLen;
michael@0 229 while (*challenge == ' ')
michael@0 230 challenge++;
michael@0 231 len = strlen(challenge);
michael@0 232
michael@0 233 // strip off any padding (see bug 230351)
michael@0 234 while (challenge[len - 1] == '=')
michael@0 235 len--;
michael@0 236
michael@0 237 inTokenLen = (len * 3)/4;
michael@0 238 inToken = malloc(inTokenLen);
michael@0 239 if (!inToken)
michael@0 240 return (NS_ERROR_OUT_OF_MEMORY);
michael@0 241
michael@0 242 //
michael@0 243 // Decode the response that followed the "Negotiate" token
michael@0 244 //
michael@0 245 if (PL_Base64Decode(challenge, len, (char *) inToken) == nullptr) {
michael@0 246 free(inToken);
michael@0 247 return(NS_ERROR_UNEXPECTED);
michael@0 248 }
michael@0 249 }
michael@0 250 else {
michael@0 251 //
michael@0 252 // Initializing, don't use an input token.
michael@0 253 //
michael@0 254 inToken = nullptr;
michael@0 255 inTokenLen = 0;
michael@0 256 }
michael@0 257
michael@0 258 nsresult rv = module->GetNextToken(inToken, inTokenLen, &outToken, &outTokenLen);
michael@0 259
michael@0 260 free(inToken);
michael@0 261
michael@0 262 if (NS_FAILED(rv))
michael@0 263 return rv;
michael@0 264
michael@0 265 if (outTokenLen == 0) {
michael@0 266 LOG((" No output token to send, exiting"));
michael@0 267 return NS_ERROR_FAILURE;
michael@0 268 }
michael@0 269
michael@0 270 //
michael@0 271 // base64 encode the output token.
michael@0 272 //
michael@0 273 char *encoded_token = PL_Base64Encode((char *)outToken, outTokenLen, nullptr);
michael@0 274
michael@0 275 nsMemory::Free(outToken);
michael@0 276
michael@0 277 if (!encoded_token)
michael@0 278 return NS_ERROR_OUT_OF_MEMORY;
michael@0 279
michael@0 280 LOG((" Sending a token of length %d\n", outTokenLen));
michael@0 281
michael@0 282 // allocate a buffer sizeof("Negotiate" + " " + b64output_token + "\0")
michael@0 283 *creds = (char *) nsMemory::Alloc(kNegotiateLen + 1 + strlen(encoded_token) + 1);
michael@0 284 if (MOZ_UNLIKELY(!*creds))
michael@0 285 rv = NS_ERROR_OUT_OF_MEMORY;
michael@0 286 else
michael@0 287 sprintf(*creds, "%s %s", kNegotiate, encoded_token);
michael@0 288
michael@0 289 PR_Free(encoded_token);
michael@0 290 return rv;
michael@0 291 }
michael@0 292
michael@0 293 bool
michael@0 294 nsHttpNegotiateAuth::TestBoolPref(const char *pref)
michael@0 295 {
michael@0 296 nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
michael@0 297 if (!prefs)
michael@0 298 return false;
michael@0 299
michael@0 300 bool val;
michael@0 301 nsresult rv = prefs->GetBoolPref(pref, &val);
michael@0 302 if (NS_FAILED(rv))
michael@0 303 return false;
michael@0 304
michael@0 305 return val;
michael@0 306 }
michael@0 307
michael@0 308 bool
michael@0 309 nsHttpNegotiateAuth::TestNonFqdn(nsIURI *uri)
michael@0 310 {
michael@0 311 nsAutoCString host;
michael@0 312 PRNetAddr addr;
michael@0 313
michael@0 314 if (!TestBoolPref(kNegotiateAuthAllowNonFqdn))
michael@0 315 return false;
michael@0 316
michael@0 317 if (NS_FAILED(uri->GetAsciiHost(host)))
michael@0 318 return false;
michael@0 319
michael@0 320 // return true if host does not contain a dot and is not an ip address
michael@0 321 return !host.IsEmpty() && host.FindChar('.') == kNotFound &&
michael@0 322 PR_StringToNetAddr(host.BeginReading(), &addr) != PR_SUCCESS;
michael@0 323 }
michael@0 324
michael@0 325 bool
michael@0 326 nsHttpNegotiateAuth::TestPref(nsIURI *uri, const char *pref)
michael@0 327 {
michael@0 328 nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
michael@0 329 if (!prefs)
michael@0 330 return false;
michael@0 331
michael@0 332 nsAutoCString scheme, host;
michael@0 333 int32_t port;
michael@0 334
michael@0 335 if (NS_FAILED(uri->GetScheme(scheme)))
michael@0 336 return false;
michael@0 337 if (NS_FAILED(uri->GetAsciiHost(host)))
michael@0 338 return false;
michael@0 339 if (NS_FAILED(uri->GetPort(&port)))
michael@0 340 return false;
michael@0 341
michael@0 342 char *hostList;
michael@0 343 if (NS_FAILED(prefs->GetCharPref(pref, &hostList)) || !hostList)
michael@0 344 return false;
michael@0 345
michael@0 346 // pseudo-BNF
michael@0 347 // ----------
michael@0 348 //
michael@0 349 // url-list base-url ( base-url "," LWS )*
michael@0 350 // base-url ( scheme-part | host-part | scheme-part host-part )
michael@0 351 // scheme-part scheme "://"
michael@0 352 // host-part host [":" port]
michael@0 353 //
michael@0 354 // for example:
michael@0 355 // "https://, http://office.foo.com"
michael@0 356 //
michael@0 357
michael@0 358 char *start = hostList, *end;
michael@0 359 for (;;) {
michael@0 360 // skip past any whitespace
michael@0 361 while (*start == ' ' || *start == '\t')
michael@0 362 ++start;
michael@0 363 end = strchr(start, ',');
michael@0 364 if (!end)
michael@0 365 end = start + strlen(start);
michael@0 366 if (start == end)
michael@0 367 break;
michael@0 368 if (MatchesBaseURI(scheme, host, port, start, end))
michael@0 369 return true;
michael@0 370 if (*end == '\0')
michael@0 371 break;
michael@0 372 start = end + 1;
michael@0 373 }
michael@0 374
michael@0 375 nsMemory::Free(hostList);
michael@0 376 return false;
michael@0 377 }
michael@0 378
michael@0 379 bool
michael@0 380 nsHttpNegotiateAuth::MatchesBaseURI(const nsCSubstring &matchScheme,
michael@0 381 const nsCSubstring &matchHost,
michael@0 382 int32_t matchPort,
michael@0 383 const char *baseStart,
michael@0 384 const char *baseEnd)
michael@0 385 {
michael@0 386 // check if scheme://host:port matches baseURI
michael@0 387
michael@0 388 // parse the base URI
michael@0 389 const char *hostStart, *schemeEnd = strstr(baseStart, "://");
michael@0 390 if (schemeEnd) {
michael@0 391 // the given scheme must match the parsed scheme exactly
michael@0 392 if (!matchScheme.Equals(Substring(baseStart, schemeEnd)))
michael@0 393 return false;
michael@0 394 hostStart = schemeEnd + 3;
michael@0 395 }
michael@0 396 else
michael@0 397 hostStart = baseStart;
michael@0 398
michael@0 399 // XXX this does not work for IPv6-literals
michael@0 400 const char *hostEnd = strchr(hostStart, ':');
michael@0 401 if (hostEnd && hostEnd < baseEnd) {
michael@0 402 // the given port must match the parsed port exactly
michael@0 403 int port = atoi(hostEnd + 1);
michael@0 404 if (matchPort != (int32_t) port)
michael@0 405 return false;
michael@0 406 }
michael@0 407 else
michael@0 408 hostEnd = baseEnd;
michael@0 409
michael@0 410
michael@0 411 // if we didn't parse out a host, then assume we got a match.
michael@0 412 if (hostStart == hostEnd)
michael@0 413 return true;
michael@0 414
michael@0 415 uint32_t hostLen = hostEnd - hostStart;
michael@0 416
michael@0 417 // matchHost must either equal host or be a subdomain of host
michael@0 418 if (matchHost.Length() < hostLen)
michael@0 419 return false;
michael@0 420
michael@0 421 const char *end = matchHost.EndReading();
michael@0 422 if (PL_strncasecmp(end - hostLen, hostStart, hostLen) == 0) {
michael@0 423 // if matchHost ends with host from the base URI, then make sure it is
michael@0 424 // either an exact match, or prefixed with a dot. we don't want
michael@0 425 // "foobar.com" to match "bar.com"
michael@0 426 if (matchHost.Length() == hostLen ||
michael@0 427 *(end - hostLen) == '.' ||
michael@0 428 *(end - hostLen - 1) == '.')
michael@0 429 return true;
michael@0 430 }
michael@0 431
michael@0 432 return false;
michael@0 433 }

mercurial