1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/extensions/auth/nsHttpNegotiateAuth.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,433 @@ 1.4 +/* vim:set ts=4 sw=4 sts=4 et cindent: */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +// 1.10 +// HTTP Negotiate Authentication Support Module 1.11 +// 1.12 +// Described by IETF Internet draft: draft-brezak-kerberos-http-00.txt 1.13 +// (formerly draft-brezak-spnego-http-04.txt) 1.14 +// 1.15 +// Also described here: 1.16 +// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnsecure/html/http-sso-1.asp 1.17 +// 1.18 + 1.19 +#include <string.h> 1.20 +#include <stdlib.h> 1.21 + 1.22 +#include "nsAuth.h" 1.23 +#include "nsHttpNegotiateAuth.h" 1.24 + 1.25 +#include "nsIHttpAuthenticableChannel.h" 1.26 +#include "nsIProxiedChannel.h" 1.27 +#include "nsIAuthModule.h" 1.28 +#include "nsIServiceManager.h" 1.29 +#include "nsIPrefService.h" 1.30 +#include "nsIPrefBranch.h" 1.31 +#include "nsIProxyInfo.h" 1.32 +#include "nsIURI.h" 1.33 +#include "nsCOMPtr.h" 1.34 +#include "nsString.h" 1.35 +#include "nsNetCID.h" 1.36 +#include "plbase64.h" 1.37 +#include "plstr.h" 1.38 +#include "prprf.h" 1.39 +#include "prlog.h" 1.40 +#include "prmem.h" 1.41 +#include "prnetdb.h" 1.42 +#include "mozilla/Likely.h" 1.43 + 1.44 +//----------------------------------------------------------------------------- 1.45 + 1.46 +static const char kNegotiate[] = "Negotiate"; 1.47 +static const char kNegotiateAuthTrustedURIs[] = "network.negotiate-auth.trusted-uris"; 1.48 +static const char kNegotiateAuthDelegationURIs[] = "network.negotiate-auth.delegation-uris"; 1.49 +static const char kNegotiateAuthAllowProxies[] = "network.negotiate-auth.allow-proxies"; 1.50 +static const char kNegotiateAuthAllowNonFqdn[] = "network.negotiate-auth.allow-non-fqdn"; 1.51 +static const char kNegotiateAuthSSPI[] = "network.auth.use-sspi"; 1.52 + 1.53 +#define kNegotiateLen (sizeof(kNegotiate)-1) 1.54 + 1.55 +//----------------------------------------------------------------------------- 1.56 + 1.57 +NS_IMETHODIMP 1.58 +nsHttpNegotiateAuth::GetAuthFlags(uint32_t *flags) 1.59 +{ 1.60 + // 1.61 + // Negotiate Auth creds should not be reused across multiple requests. 1.62 + // Only perform the negotiation when it is explicitly requested by the 1.63 + // server. Thus, do *NOT* use the "REUSABLE_CREDENTIALS" flag here. 1.64 + // 1.65 + // CONNECTION_BASED is specified instead of REQUEST_BASED since we need 1.66 + // to complete a sequence of transactions with the server over the same 1.67 + // connection. 1.68 + // 1.69 + *flags = CONNECTION_BASED | IDENTITY_IGNORED; 1.70 + return NS_OK; 1.71 +} 1.72 + 1.73 +// 1.74 +// Always set *identityInvalid == FALSE here. This 1.75 +// will prevent the browser from popping up the authentication 1.76 +// prompt window. Because GSSAPI does not have an API 1.77 +// for fetching initial credentials (ex: A Kerberos TGT), 1.78 +// there is no correct way to get the users credentials. 1.79 +// 1.80 +NS_IMETHODIMP 1.81 +nsHttpNegotiateAuth::ChallengeReceived(nsIHttpAuthenticableChannel *authChannel, 1.82 + const char *challenge, 1.83 + bool isProxyAuth, 1.84 + nsISupports **sessionState, 1.85 + nsISupports **continuationState, 1.86 + bool *identityInvalid) 1.87 +{ 1.88 + nsIAuthModule *module = (nsIAuthModule *) *continuationState; 1.89 + 1.90 + *identityInvalid = false; 1.91 + 1.92 + /* Always fail Negotiate auth for Tor Browser. We don't need it. */ 1.93 + return NS_ERROR_ABORT; 1.94 + 1.95 + if (module) 1.96 + return NS_OK; 1.97 + 1.98 + nsresult rv; 1.99 + 1.100 + nsCOMPtr<nsIURI> uri; 1.101 + rv = authChannel->GetURI(getter_AddRefs(uri)); 1.102 + if (NS_FAILED(rv)) 1.103 + return rv; 1.104 + 1.105 + uint32_t req_flags = nsIAuthModule::REQ_DEFAULT; 1.106 + nsAutoCString service; 1.107 + 1.108 + if (isProxyAuth) { 1.109 + if (!TestBoolPref(kNegotiateAuthAllowProxies)) { 1.110 + LOG(("nsHttpNegotiateAuth::ChallengeReceived proxy auth blocked\n")); 1.111 + return NS_ERROR_ABORT; 1.112 + } 1.113 + 1.114 + req_flags |= nsIAuthModule::REQ_PROXY_AUTH; 1.115 + nsCOMPtr<nsIProxyInfo> proxyInfo; 1.116 + authChannel->GetProxyInfo(getter_AddRefs(proxyInfo)); 1.117 + NS_ENSURE_STATE(proxyInfo); 1.118 + 1.119 + proxyInfo->GetHost(service); 1.120 + } 1.121 + else { 1.122 + bool allowed = TestNonFqdn(uri) || 1.123 + TestPref(uri, kNegotiateAuthTrustedURIs); 1.124 + if (!allowed) { 1.125 + LOG(("nsHttpNegotiateAuth::ChallengeReceived URI blocked\n")); 1.126 + return NS_ERROR_ABORT; 1.127 + } 1.128 + 1.129 + bool delegation = TestPref(uri, kNegotiateAuthDelegationURIs); 1.130 + if (delegation) { 1.131 + LOG((" using REQ_DELEGATE\n")); 1.132 + req_flags |= nsIAuthModule::REQ_DELEGATE; 1.133 + } 1.134 + 1.135 + rv = uri->GetAsciiHost(service); 1.136 + if (NS_FAILED(rv)) 1.137 + return rv; 1.138 + } 1.139 + 1.140 + LOG((" service = %s\n", service.get())); 1.141 + 1.142 + // 1.143 + // The correct service name for IIS servers is "HTTP/f.q.d.n", so 1.144 + // construct the proper service name for passing to "gss_import_name". 1.145 + // 1.146 + // TODO: Possibly make this a configurable service name for use 1.147 + // with non-standard servers that use stuff like "khttp/f.q.d.n" 1.148 + // instead. 1.149 + // 1.150 + service.Insert("HTTP@", 0); 1.151 + 1.152 + const char *contractID; 1.153 + if (TestBoolPref(kNegotiateAuthSSPI)) { 1.154 + LOG((" using negotiate-sspi\n")); 1.155 + contractID = NS_AUTH_MODULE_CONTRACTID_PREFIX "negotiate-sspi"; 1.156 + } 1.157 + else { 1.158 + LOG((" using negotiate-gss\n")); 1.159 + contractID = NS_AUTH_MODULE_CONTRACTID_PREFIX "negotiate-gss"; 1.160 + } 1.161 + 1.162 + rv = CallCreateInstance(contractID, &module); 1.163 + 1.164 + if (NS_FAILED(rv)) { 1.165 + LOG((" Failed to load Negotiate Module \n")); 1.166 + return rv; 1.167 + } 1.168 + 1.169 + rv = module->Init(service.get(), req_flags, nullptr, nullptr, nullptr); 1.170 + 1.171 + if (NS_FAILED(rv)) { 1.172 + NS_RELEASE(module); 1.173 + return rv; 1.174 + } 1.175 + 1.176 + *continuationState = module; 1.177 + return NS_OK; 1.178 +} 1.179 + 1.180 +NS_IMPL_ISUPPORTS(nsHttpNegotiateAuth, nsIHttpAuthenticator) 1.181 + 1.182 +// 1.183 +// GenerateCredentials 1.184 +// 1.185 +// This routine is responsible for creating the correct authentication 1.186 +// blob to pass to the server that requested "Negotiate" authentication. 1.187 +// 1.188 +NS_IMETHODIMP 1.189 +nsHttpNegotiateAuth::GenerateCredentials(nsIHttpAuthenticableChannel *authChannel, 1.190 + const char *challenge, 1.191 + bool isProxyAuth, 1.192 + const char16_t *domain, 1.193 + const char16_t *username, 1.194 + const char16_t *password, 1.195 + nsISupports **sessionState, 1.196 + nsISupports **continuationState, 1.197 + uint32_t *flags, 1.198 + char **creds) 1.199 +{ 1.200 + // ChallengeReceived must have been called previously. 1.201 + nsIAuthModule *module = (nsIAuthModule *) *continuationState; 1.202 + NS_ENSURE_TRUE(module, NS_ERROR_NOT_INITIALIZED); 1.203 + 1.204 + *flags = USING_INTERNAL_IDENTITY; 1.205 + 1.206 + LOG(("nsHttpNegotiateAuth::GenerateCredentials() [challenge=%s]\n", challenge)); 1.207 + 1.208 + NS_ASSERTION(creds, "null param"); 1.209 + 1.210 +#ifdef DEBUG 1.211 + bool isGssapiAuth = 1.212 + !PL_strncasecmp(challenge, kNegotiate, kNegotiateLen); 1.213 + NS_ASSERTION(isGssapiAuth, "Unexpected challenge"); 1.214 +#endif 1.215 + 1.216 + // 1.217 + // If the "Negotiate:" header had some data associated with it, 1.218 + // that data should be used as the input to this call. This may 1.219 + // be a continuation of an earlier call because GSSAPI authentication 1.220 + // often takes multiple round-trips to complete depending on the 1.221 + // context flags given. We want to use MUTUAL_AUTHENTICATION which 1.222 + // generally *does* require multiple round-trips. Don't assume 1.223 + // auth can be completed in just 1 call. 1.224 + // 1.225 + unsigned int len = strlen(challenge); 1.226 + 1.227 + void *inToken, *outToken; 1.228 + uint32_t inTokenLen, outTokenLen; 1.229 + 1.230 + if (len > kNegotiateLen) { 1.231 + challenge += kNegotiateLen; 1.232 + while (*challenge == ' ') 1.233 + challenge++; 1.234 + len = strlen(challenge); 1.235 + 1.236 + // strip off any padding (see bug 230351) 1.237 + while (challenge[len - 1] == '=') 1.238 + len--; 1.239 + 1.240 + inTokenLen = (len * 3)/4; 1.241 + inToken = malloc(inTokenLen); 1.242 + if (!inToken) 1.243 + return (NS_ERROR_OUT_OF_MEMORY); 1.244 + 1.245 + // 1.246 + // Decode the response that followed the "Negotiate" token 1.247 + // 1.248 + if (PL_Base64Decode(challenge, len, (char *) inToken) == nullptr) { 1.249 + free(inToken); 1.250 + return(NS_ERROR_UNEXPECTED); 1.251 + } 1.252 + } 1.253 + else { 1.254 + // 1.255 + // Initializing, don't use an input token. 1.256 + // 1.257 + inToken = nullptr; 1.258 + inTokenLen = 0; 1.259 + } 1.260 + 1.261 + nsresult rv = module->GetNextToken(inToken, inTokenLen, &outToken, &outTokenLen); 1.262 + 1.263 + free(inToken); 1.264 + 1.265 + if (NS_FAILED(rv)) 1.266 + return rv; 1.267 + 1.268 + if (outTokenLen == 0) { 1.269 + LOG((" No output token to send, exiting")); 1.270 + return NS_ERROR_FAILURE; 1.271 + } 1.272 + 1.273 + // 1.274 + // base64 encode the output token. 1.275 + // 1.276 + char *encoded_token = PL_Base64Encode((char *)outToken, outTokenLen, nullptr); 1.277 + 1.278 + nsMemory::Free(outToken); 1.279 + 1.280 + if (!encoded_token) 1.281 + return NS_ERROR_OUT_OF_MEMORY; 1.282 + 1.283 + LOG((" Sending a token of length %d\n", outTokenLen)); 1.284 + 1.285 + // allocate a buffer sizeof("Negotiate" + " " + b64output_token + "\0") 1.286 + *creds = (char *) nsMemory::Alloc(kNegotiateLen + 1 + strlen(encoded_token) + 1); 1.287 + if (MOZ_UNLIKELY(!*creds)) 1.288 + rv = NS_ERROR_OUT_OF_MEMORY; 1.289 + else 1.290 + sprintf(*creds, "%s %s", kNegotiate, encoded_token); 1.291 + 1.292 + PR_Free(encoded_token); 1.293 + return rv; 1.294 +} 1.295 + 1.296 +bool 1.297 +nsHttpNegotiateAuth::TestBoolPref(const char *pref) 1.298 +{ 1.299 + nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); 1.300 + if (!prefs) 1.301 + return false; 1.302 + 1.303 + bool val; 1.304 + nsresult rv = prefs->GetBoolPref(pref, &val); 1.305 + if (NS_FAILED(rv)) 1.306 + return false; 1.307 + 1.308 + return val; 1.309 +} 1.310 + 1.311 +bool 1.312 +nsHttpNegotiateAuth::TestNonFqdn(nsIURI *uri) 1.313 +{ 1.314 + nsAutoCString host; 1.315 + PRNetAddr addr; 1.316 + 1.317 + if (!TestBoolPref(kNegotiateAuthAllowNonFqdn)) 1.318 + return false; 1.319 + 1.320 + if (NS_FAILED(uri->GetAsciiHost(host))) 1.321 + return false; 1.322 + 1.323 + // return true if host does not contain a dot and is not an ip address 1.324 + return !host.IsEmpty() && host.FindChar('.') == kNotFound && 1.325 + PR_StringToNetAddr(host.BeginReading(), &addr) != PR_SUCCESS; 1.326 +} 1.327 + 1.328 +bool 1.329 +nsHttpNegotiateAuth::TestPref(nsIURI *uri, const char *pref) 1.330 +{ 1.331 + nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); 1.332 + if (!prefs) 1.333 + return false; 1.334 + 1.335 + nsAutoCString scheme, host; 1.336 + int32_t port; 1.337 + 1.338 + if (NS_FAILED(uri->GetScheme(scheme))) 1.339 + return false; 1.340 + if (NS_FAILED(uri->GetAsciiHost(host))) 1.341 + return false; 1.342 + if (NS_FAILED(uri->GetPort(&port))) 1.343 + return false; 1.344 + 1.345 + char *hostList; 1.346 + if (NS_FAILED(prefs->GetCharPref(pref, &hostList)) || !hostList) 1.347 + return false; 1.348 + 1.349 + // pseudo-BNF 1.350 + // ---------- 1.351 + // 1.352 + // url-list base-url ( base-url "," LWS )* 1.353 + // base-url ( scheme-part | host-part | scheme-part host-part ) 1.354 + // scheme-part scheme "://" 1.355 + // host-part host [":" port] 1.356 + // 1.357 + // for example: 1.358 + // "https://, http://office.foo.com" 1.359 + // 1.360 + 1.361 + char *start = hostList, *end; 1.362 + for (;;) { 1.363 + // skip past any whitespace 1.364 + while (*start == ' ' || *start == '\t') 1.365 + ++start; 1.366 + end = strchr(start, ','); 1.367 + if (!end) 1.368 + end = start + strlen(start); 1.369 + if (start == end) 1.370 + break; 1.371 + if (MatchesBaseURI(scheme, host, port, start, end)) 1.372 + return true; 1.373 + if (*end == '\0') 1.374 + break; 1.375 + start = end + 1; 1.376 + } 1.377 + 1.378 + nsMemory::Free(hostList); 1.379 + return false; 1.380 +} 1.381 + 1.382 +bool 1.383 +nsHttpNegotiateAuth::MatchesBaseURI(const nsCSubstring &matchScheme, 1.384 + const nsCSubstring &matchHost, 1.385 + int32_t matchPort, 1.386 + const char *baseStart, 1.387 + const char *baseEnd) 1.388 +{ 1.389 + // check if scheme://host:port matches baseURI 1.390 + 1.391 + // parse the base URI 1.392 + const char *hostStart, *schemeEnd = strstr(baseStart, "://"); 1.393 + if (schemeEnd) { 1.394 + // the given scheme must match the parsed scheme exactly 1.395 + if (!matchScheme.Equals(Substring(baseStart, schemeEnd))) 1.396 + return false; 1.397 + hostStart = schemeEnd + 3; 1.398 + } 1.399 + else 1.400 + hostStart = baseStart; 1.401 + 1.402 + // XXX this does not work for IPv6-literals 1.403 + const char *hostEnd = strchr(hostStart, ':'); 1.404 + if (hostEnd && hostEnd < baseEnd) { 1.405 + // the given port must match the parsed port exactly 1.406 + int port = atoi(hostEnd + 1); 1.407 + if (matchPort != (int32_t) port) 1.408 + return false; 1.409 + } 1.410 + else 1.411 + hostEnd = baseEnd; 1.412 + 1.413 + 1.414 + // if we didn't parse out a host, then assume we got a match. 1.415 + if (hostStart == hostEnd) 1.416 + return true; 1.417 + 1.418 + uint32_t hostLen = hostEnd - hostStart; 1.419 + 1.420 + // matchHost must either equal host or be a subdomain of host 1.421 + if (matchHost.Length() < hostLen) 1.422 + return false; 1.423 + 1.424 + const char *end = matchHost.EndReading(); 1.425 + if (PL_strncasecmp(end - hostLen, hostStart, hostLen) == 0) { 1.426 + // if matchHost ends with host from the base URI, then make sure it is 1.427 + // either an exact match, or prefixed with a dot. we don't want 1.428 + // "foobar.com" to match "bar.com" 1.429 + if (matchHost.Length() == hostLen || 1.430 + *(end - hostLen) == '.' || 1.431 + *(end - hostLen - 1) == '.') 1.432 + return true; 1.433 + } 1.434 + 1.435 + return false; 1.436 +}