extensions/auth/nsHttpNegotiateAuth.cpp

changeset 0
6474c204b198
     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 +}

mercurial