netwerk/protocol/http/nsHttpNTLMAuth.cpp

Thu, 15 Jan 2015 21:03:48 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 15 Jan 2015 21:03:48 +0100
branch
TOR_BUG_9701
changeset 11
deefc01c0e14
permissions
-rw-r--r--

Integrate friendly tips from Tor colleagues to make (or not) 4.5 alpha 3;
This includes removal of overloaded (but unused) methods, and addition of
a overlooked call to DataStruct::SetData(nsISupports, uint32_t, bool.)

     1 /* vim:set ts=4 sw=4 sts=4 et ci: */
     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 // HttpLog.h should generally be included first
     7 #include "HttpLog.h"
     9 #include "nsHttpNTLMAuth.h"
    10 #include "nsIAuthModule.h"
    11 #include "nsCOMPtr.h"
    12 #include "plbase64.h"
    13 #include "prnetdb.h"
    15 //-----------------------------------------------------------------------------
    17 #include "nsIPrefBranch.h"
    18 #include "nsIPrefService.h"
    19 #include "nsIHttpAuthenticableChannel.h"
    20 #include "nsIURI.h"
    21 #ifdef XP_WIN
    22 #include "nsIX509Cert.h"
    23 #include "nsISSLStatus.h"
    24 #include "nsISSLStatusProvider.h"
    25 #endif
    26 #include "mozilla/Attributes.h"
    27 #include "nsThreadUtils.h"
    29 namespace mozilla {
    30 namespace net {
    32 static const char kAllowProxies[] = "network.automatic-ntlm-auth.allow-proxies";
    33 static const char kAllowNonFqdn[] = "network.automatic-ntlm-auth.allow-non-fqdn";
    34 static const char kTrustedURIs[]  = "network.automatic-ntlm-auth.trusted-uris";
    35 static const char kForceGeneric[] = "network.auth.force-generic-ntlm";
    36 static const char kAllowGenericHTTP[] = "network.negotiate-auth.allow-insecure-ntlm-v1";
    37 static const char kAllowGenericHTTPS[] = "network.negotiate-auth.allow-insecure-ntlm-v1-https";
    39 // XXX MatchesBaseURI and TestPref are duplicated in nsHttpNegotiateAuth.cpp,
    40 // but since that file lives in a separate library we cannot directly share it.
    41 // bug 236865 addresses this problem.
    43 static bool
    44 MatchesBaseURI(const nsCSubstring &matchScheme,
    45                const nsCSubstring &matchHost,
    46                int32_t             matchPort,
    47                const char         *baseStart,
    48                const char         *baseEnd)
    49 {
    50     // check if scheme://host:port matches baseURI
    52     // parse the base URI
    53     const char *hostStart, *schemeEnd = strstr(baseStart, "://");
    54     if (schemeEnd) {
    55         // the given scheme must match the parsed scheme exactly
    56         if (!matchScheme.Equals(Substring(baseStart, schemeEnd)))
    57             return false;
    58         hostStart = schemeEnd + 3;
    59     }
    60     else
    61         hostStart = baseStart;
    63     // XXX this does not work for IPv6-literals
    64     const char *hostEnd = strchr(hostStart, ':');
    65     if (hostEnd && hostEnd < baseEnd) {
    66         // the given port must match the parsed port exactly
    67         int port = atoi(hostEnd + 1);
    68         if (matchPort != (int32_t) port)
    69             return false;
    70     }
    71     else
    72         hostEnd = baseEnd;
    75     // if we didn't parse out a host, then assume we got a match.
    76     if (hostStart == hostEnd)
    77         return true;
    79     uint32_t hostLen = hostEnd - hostStart;
    81     // matchHost must either equal host or be a subdomain of host
    82     if (matchHost.Length() < hostLen)
    83         return false;
    85     const char *end = matchHost.EndReading();
    86     if (PL_strncasecmp(end - hostLen, hostStart, hostLen) == 0) {
    87         // if matchHost ends with host from the base URI, then make sure it is
    88         // either an exact match, or prefixed with a dot.  we don't want
    89         // "foobar.com" to match "bar.com"
    90         if (matchHost.Length() == hostLen ||
    91             *(end - hostLen) == '.' ||
    92             *(end - hostLen - 1) == '.')
    93             return true;
    94     }
    96     return false;
    97 }
    99 static bool
   100 IsNonFqdn(nsIURI *uri)
   101 {
   102     nsAutoCString host;
   103     PRNetAddr addr;
   105     if (NS_FAILED(uri->GetAsciiHost(host)))
   106         return false;
   108     // return true if host does not contain a dot and is not an ip address
   109     return !host.IsEmpty() && host.FindChar('.') == kNotFound &&
   110            PR_StringToNetAddr(host.BeginReading(), &addr) != PR_SUCCESS;
   111 }
   113 static bool
   114 TestPref(nsIURI *uri, const char *pref)
   115 {
   116     nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
   117     if (!prefs)
   118         return false;
   120     nsAutoCString scheme, host;
   121     int32_t port;
   123     if (NS_FAILED(uri->GetScheme(scheme)))
   124         return false;
   125     if (NS_FAILED(uri->GetAsciiHost(host)))
   126         return false;
   127     if (NS_FAILED(uri->GetPort(&port)))
   128         return false;
   130     char *hostList;
   131     if (NS_FAILED(prefs->GetCharPref(pref, &hostList)) || !hostList)
   132         return false;
   134     // pseudo-BNF
   135     // ----------
   136     //
   137     // url-list       base-url ( base-url "," LWS )*
   138     // base-url       ( scheme-part | host-part | scheme-part host-part )
   139     // scheme-part    scheme "://"
   140     // host-part      host [":" port]
   141     //
   142     // for example:
   143     //   "https://, http://office.foo.com"
   144     //
   146     char *start = hostList, *end;
   147     for (;;) {
   148         // skip past any whitespace
   149         while (*start == ' ' || *start == '\t')
   150             ++start;
   151         end = strchr(start, ',');
   152         if (!end)
   153             end = start + strlen(start);
   154         if (start == end)
   155             break;
   156         if (MatchesBaseURI(scheme, host, port, start, end))
   157             return true;
   158         if (*end == '\0')
   159             break;
   160         start = end + 1;
   161     }
   163     nsMemory::Free(hostList);
   164     return false;
   165 }
   167 // Check to see if we should use our generic (internal) NTLM auth module.
   168 static bool
   169 ForceGenericNTLM()
   170 {
   171     nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
   172     if (!prefs)
   173         return false;
   174     bool flag = false;
   176     if (NS_FAILED(prefs->GetBoolPref(kForceGeneric, &flag)))
   177         flag = false;
   179     LOG(("Force use of generic ntlm auth module: %d\n", flag));
   180     return flag;
   181 }
   183 // Check to see if we should use our generic (internal) NTLM auth module.
   184 static bool
   185 AllowGenericNTLM()
   186 {
   187     MOZ_ASSERT(NS_IsMainThread());
   189     nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
   190     if (!prefs)
   191         return false;
   193     bool flag = false;
   194     if (NS_FAILED(prefs->GetBoolPref(kAllowGenericHTTP, &flag)))
   195         flag = false;
   197     LOG(("Allow use of generic ntlm auth module: %d\n", flag));
   198     return flag;
   199 }
   201 // Check to see if we should use our generic (internal) NTLM auth module.
   202 static bool
   203 AllowGenericNTLMforHTTPS(nsIHttpAuthenticableChannel *channel)
   204 {
   205     bool isSSL = false;
   206     channel->GetIsSSL(&isSSL);
   207     if (!isSSL)
   208         return false;
   210     MOZ_ASSERT(NS_IsMainThread());
   212     nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
   213     if (!prefs)
   214         return false;
   216     bool flag = false;
   217     if (NS_FAILED(prefs->GetBoolPref(kAllowGenericHTTPS, &flag)))
   218         flag = false;
   220     LOG(("Allow use of generic ntlm auth module for only https: %d\n", flag));
   221     return flag;
   222 }
   224 // Check to see if we should use default credentials for this host or proxy.
   225 static bool
   226 CanUseDefaultCredentials(nsIHttpAuthenticableChannel *channel,
   227                          bool isProxyAuth)
   228 {
   229     nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
   230     if (!prefs)
   231         return false;
   233     if (isProxyAuth) {
   234         bool val;
   235         if (NS_FAILED(prefs->GetBoolPref(kAllowProxies, &val)))
   236             val = false;
   237         LOG(("Default credentials allowed for proxy: %d\n", val));
   238         return val;
   239     }
   241     nsCOMPtr<nsIURI> uri;
   242     channel->GetURI(getter_AddRefs(uri));
   244     bool allowNonFqdn;
   245     if (NS_FAILED(prefs->GetBoolPref(kAllowNonFqdn, &allowNonFqdn)))
   246         allowNonFqdn = false;
   247     if (allowNonFqdn && uri && IsNonFqdn(uri)) {
   248         LOG(("Host is non-fqdn, default credentials are allowed\n"));
   249         return true;
   250     }
   252     bool isTrustedHost = (uri && TestPref(uri, kTrustedURIs));
   253     LOG(("Default credentials allowed for host: %d\n", isTrustedHost));
   254     return isTrustedHost;
   255 }
   257 // Dummy class for session state object.  This class doesn't hold any data.
   258 // Instead we use its existence as a flag.  See ChallengeReceived.
   259 class nsNTLMSessionState MOZ_FINAL : public nsISupports
   260 {
   261 public:
   262     NS_DECL_ISUPPORTS
   263 };
   264 NS_IMPL_ISUPPORTS0(nsNTLMSessionState)
   266 //-----------------------------------------------------------------------------
   268 NS_IMPL_ISUPPORTS(nsHttpNTLMAuth, nsIHttpAuthenticator)
   270 NS_IMETHODIMP
   271 nsHttpNTLMAuth::ChallengeReceived(nsIHttpAuthenticableChannel *channel,
   272                                   const char     *challenge,
   273                                   bool            isProxyAuth,
   274                                   nsISupports   **sessionState,
   275                                   nsISupports   **continuationState,
   276                                   bool           *identityInvalid)
   277 {
   278     LOG(("nsHttpNTLMAuth::ChallengeReceived [ss=%p cs=%p]\n",
   279          *sessionState, *continuationState));
   281     // Use the native NTLM if available
   282     mUseNative = true;
   284     // NOTE: we don't define any session state, but we do use the pointer.
   286     *identityInvalid = false;
   288     /* Always fail Negotiate auth for Tor Browser. We don't need it. */
   289     return NS_ERROR_ABORT;
   291     // Start a new auth sequence if the challenge is exactly "NTLM".
   292     // If native NTLM auth apis are available and enabled through prefs,
   293     // try to use them.
   294     if (PL_strcasecmp(challenge, "NTLM") == 0) {
   295         nsCOMPtr<nsISupports> module;
   297         // Check to see if we should default to our generic NTLM auth module
   298         // through UseGenericNTLM. (We use native auth by default if the
   299         // system provides it.) If *sessionState is non-null, we failed to
   300         // instantiate a native NTLM module the last time, so skip trying again.
   301         bool forceGeneric = ForceGenericNTLM();
   302         if (!forceGeneric && !*sessionState) {
   303             // Check for approved default credentials hosts and proxies. If
   304             // *continuationState is non-null, the last authentication attempt
   305             // failed so skip default credential use.
   306             if (!*continuationState && CanUseDefaultCredentials(channel, isProxyAuth)) {
   307                 // Try logging in with the user's default credentials. If
   308                 // successful, |identityInvalid| is false, which will trigger
   309                 // a default credentials attempt once we return.
   310                 module = do_CreateInstance(NS_AUTH_MODULE_CONTRACTID_PREFIX "sys-ntlm");
   311             }
   312 #ifdef XP_WIN
   313             else {
   314                 // Try to use native NTLM and prompt the user for their domain,
   315                 // username, and password. (only supported by windows nsAuthSSPI module.)
   316                 // Note, for servers that use LMv1 a weak hash of the user's password
   317                 // will be sent. We rely on windows internal apis to decide whether
   318                 // we should support this older, less secure version of the protocol.
   319                 module = do_CreateInstance(NS_AUTH_MODULE_CONTRACTID_PREFIX "sys-ntlm");
   320                 *identityInvalid = true;
   321             }
   322 #endif // XP_WIN
   323 #ifdef PR_LOGGING
   324             if (!module)
   325                 LOG(("Native sys-ntlm auth module not found.\n"));
   326 #endif
   327         }
   329 #ifdef XP_WIN
   330         // On windows, never fall back unless the user has specifically requested so.
   331         if (!forceGeneric && !module)
   332             return NS_ERROR_UNEXPECTED;
   333 #endif
   335         // If no native support was available. Fall back on our internal NTLM implementation.
   336         if (!module) {
   337             if (!*sessionState) {
   338                 // Remember the fact that we cannot use the "sys-ntlm" module,
   339                 // so we don't ever bother trying again for this auth domain.
   340                 *sessionState = new nsNTLMSessionState();
   341                 if (!*sessionState)
   342                     return NS_ERROR_OUT_OF_MEMORY;
   343                 NS_ADDREF(*sessionState);
   344             }
   346             // Use our internal NTLM implementation. Note, this is less secure,
   347             // see bug 520607 for details.
   349             // For now with default preference settings (i.e. allow-insecure-ntlm-v1-https = true
   350             // and allow-insecure-ntlm-v1 = false) we don't allow authentication to any proxy,
   351             // either http or https.  This will be fixed in a followup bug.
   352             if (AllowGenericNTLM() || (!isProxyAuth && AllowGenericNTLMforHTTPS(channel))) {
   353                 LOG(("Trying to fall back on internal ntlm auth.\n"));
   354                 module = do_CreateInstance(NS_AUTH_MODULE_CONTRACTID_PREFIX "ntlm");
   355             }
   357             mUseNative = false;
   359             // Prompt user for domain, username, and password.
   360             *identityInvalid = true;
   361         }
   363         // If this fails, then it means that we cannot do NTLM auth.
   364         if (!module) {
   365             LOG(("No ntlm auth modules available.\n"));
   366             return NS_ERROR_UNEXPECTED;
   367         }
   369         // A non-null continuation state implies that we failed to authenticate.
   370         // Blow away the old authentication state, and use the new one.
   371         module.swap(*continuationState);
   372     }
   373     return NS_OK;
   374 }
   376 NS_IMETHODIMP
   377 nsHttpNTLMAuth::GenerateCredentials(nsIHttpAuthenticableChannel *authChannel,
   378                                     const char      *challenge,
   379                                     bool             isProxyAuth,
   380                                     const char16_t *domain,
   381                                     const char16_t *user,
   382                                     const char16_t *pass,
   383                                     nsISupports    **sessionState,
   384                                     nsISupports    **continuationState,
   385                                     uint32_t       *aFlags,
   386                                     char           **creds)
   388 {
   389     LOG(("nsHttpNTLMAuth::GenerateCredentials\n"));
   391     *creds = nullptr;
   392     *aFlags = 0;
   394     // if user or password is empty, ChallengeReceived returned
   395     // identityInvalid = false, that means we are using default user
   396     // credentials; see  nsAuthSSPI::Init method for explanation of this
   397     // condition
   398     if (!user || !pass)
   399         *aFlags = USING_INTERNAL_IDENTITY;
   401     nsresult rv;
   402     nsCOMPtr<nsIAuthModule> module = do_QueryInterface(*continuationState, &rv);
   403     NS_ENSURE_SUCCESS(rv, rv);
   405     void *inBuf, *outBuf;
   406     uint32_t inBufLen, outBufLen;
   408     // initial challenge
   409     if (PL_strcasecmp(challenge, "NTLM") == 0) {
   410         // NTLM service name format is 'HTTP@host' for both http and https
   411         nsCOMPtr<nsIURI> uri;
   412         rv = authChannel->GetURI(getter_AddRefs(uri));
   413         if (NS_FAILED(rv))
   414             return rv;
   415         nsAutoCString serviceName, host;
   416         rv = uri->GetAsciiHost(host);
   417         if (NS_FAILED(rv))
   418             return rv;
   419         serviceName.AppendLiteral("HTTP@");
   420         serviceName.Append(host);
   421         // initialize auth module
   422         uint32_t reqFlags = nsIAuthModule::REQ_DEFAULT;
   423         if (isProxyAuth)
   424             reqFlags |= nsIAuthModule::REQ_PROXY_AUTH;
   426         rv = module->Init(serviceName.get(), reqFlags, domain, user, pass);
   427         if (NS_FAILED(rv))
   428             return rv;
   430 // This update enables updated Windows machines (Win7 or patched previous
   431 // versions) and Linux machines running Samba (updated for Channel
   432 // Binding), to perform Channel Binding when authenticating using NTLMv2
   433 // and an outer secure channel.
   434 //
   435 // Currently only implemented for Windows, linux support will be landing in
   436 // a separate patch, update this #ifdef accordingly then.
   437 #if defined (XP_WIN) /* || defined (LINUX) */
   438         // We should retrieve the server certificate and compute the CBT,
   439         // but only when we are using the native NTLM implementation and
   440         // not the internal one.
   441         // It is a valid case not having the security info object.  This
   442         // occures when we connect an https site through an ntlm proxy.
   443         // After the ssl tunnel has been created, we get here the second
   444         // time and now generate the CBT from now valid security info.
   445         nsCOMPtr<nsIChannel> channel = do_QueryInterface(authChannel, &rv);
   446         if (NS_FAILED(rv))
   447             return rv;
   449         nsCOMPtr<nsISupports> security;
   450         rv = channel->GetSecurityInfo(getter_AddRefs(security));
   451         if (NS_FAILED(rv))
   452             return rv;
   454         nsCOMPtr<nsISSLStatusProvider> statusProvider =
   455             do_QueryInterface(security);
   457         if (mUseNative && statusProvider) {
   458             nsCOMPtr<nsISSLStatus> status;
   459             rv = statusProvider->GetSSLStatus(getter_AddRefs(status));
   460             if (NS_FAILED(rv))
   461                 return rv;
   463             nsCOMPtr<nsIX509Cert> cert;
   464             rv = status->GetServerCert(getter_AddRefs(cert));
   465             if (NS_FAILED(rv))
   466                 return rv;
   468             uint32_t length;
   469             uint8_t* certArray;
   470             cert->GetRawDER(&length, &certArray);						
   472             // If there is a server certificate, we pass it along the
   473             // first time we call GetNextToken().
   474             inBufLen = length;
   475             inBuf = certArray;
   476         } else {
   477             // If there is no server certificate, we don't pass anything.
   478             inBufLen = 0;
   479             inBuf = nullptr;
   480         }
   481 #else // Extended protection update is just for Linux and Windows machines.
   482         inBufLen = 0;
   483         inBuf = nullptr;
   484 #endif
   485     }
   486     else {
   487         // decode challenge; skip past "NTLM " to the start of the base64
   488         // encoded data.
   489         int len = strlen(challenge);
   490         if (len < 6)
   491             return NS_ERROR_UNEXPECTED; // bogus challenge
   492         challenge += 5;
   493         len -= 5;
   495         // strip off any padding (see bug 230351)
   496         while (challenge[len - 1] == '=')
   497           len--;
   499         // decode into the input secbuffer
   500         inBufLen = (len * 3)/4;      // sufficient size (see plbase64.h)
   501         inBuf = nsMemory::Alloc(inBufLen);
   502         if (!inBuf)
   503             return NS_ERROR_OUT_OF_MEMORY;
   505         if (PL_Base64Decode(challenge, len, (char *) inBuf) == nullptr) {
   506             nsMemory::Free(inBuf);
   507             return NS_ERROR_UNEXPECTED; // improper base64 encoding
   508         }
   509     }
   511     rv = module->GetNextToken(inBuf, inBufLen, &outBuf, &outBufLen);
   512     if (NS_SUCCEEDED(rv)) {
   513         // base64 encode data in output buffer and prepend "NTLM "
   514         int credsLen = 5 + ((outBufLen + 2)/3)*4;
   515         *creds = (char *) nsMemory::Alloc(credsLen + 1);
   516         if (!*creds)
   517             rv = NS_ERROR_OUT_OF_MEMORY;
   518         else {
   519             memcpy(*creds, "NTLM ", 5);
   520             PL_Base64Encode((char *) outBuf, outBufLen, *creds + 5);
   521             (*creds)[credsLen] = '\0'; // null terminate
   522         }
   523         // OK, we are done with |outBuf|
   524         nsMemory::Free(outBuf);
   525     }
   527     if (inBuf)
   528         nsMemory::Free(inBuf);
   530     return rv;
   531 }
   533 NS_IMETHODIMP
   534 nsHttpNTLMAuth::GetAuthFlags(uint32_t *flags)
   535 {
   536     *flags = CONNECTION_BASED | IDENTITY_INCLUDES_DOMAIN | IDENTITY_ENCRYPTED;
   537     return NS_OK;
   538 }
   540 } // namespace mozilla::net
   541 } // namespace mozilla

mercurial