extensions/auth/nsHttpNegotiateAuth.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

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

mercurial