Bug 520607 - Remove use of "ntlm" auth module and replace with use of "sys-ntlm". r=wtc, cbiesinger. sr=bz.
authorJim Mathies <jmathies@mozilla.com>
Thu, 19 Nov 2009 16:12:43 -0600
changeset 35150 2752252785507b3b72e71418e5eb523550135be3
parent 35149 76d83d28411e3346cc7862617eb10d7742b4b1cb
child 35151 f20c59deb6ac677114178e003b1bf2108c85382e
push idunknown
push userunknown
push dateunknown
reviewerswtc, cbiesinger, bz
bugs520607
milestone1.9.3a1pre
Bug 520607 - Remove use of "ntlm" auth module and replace with use of "sys-ntlm". r=wtc, cbiesinger. sr=bz.
extensions/auth/nsAuthSSPI.cpp
extensions/auth/nsHttpNegotiateAuth.cpp
modules/libpref/src/init/all.js
netwerk/protocol/http/src/nsHttpNTLMAuth.cpp
security/manager/ssl/src/nsNTLMAuthModule.cpp
--- a/extensions/auth/nsAuthSSPI.cpp
+++ b/extensions/auth/nsAuthSSPI.cpp
@@ -15,16 +15,17 @@
  * The Original Code is the SSPI NegotiateAuth Module
  *
  * The Initial Developer of the Original Code is IBM Corporation.
  * Portions created by the Initial Developer are Copyright (C) 2004
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Darin Fisher <darin@meer.net>
+ *   Jim Mathies <jmathies@mozilla.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -223,22 +224,18 @@ NS_IMETHODIMP
 nsAuthSSPI::Init(const char *serviceName,
                  PRUint32    serviceFlags,
                  const PRUnichar *domain,
                  const PRUnichar *username,
                  const PRUnichar *password)
 {
     LOG(("  nsAuthSSPI::Init\n"));
 
-    // we don't expect to be passed any user credentials
-    NS_ASSERTION(!domain && !username && !password, "unexpected credentials");
-
-    // if we're configured for SPNEGO (Negotiate) or Kerberos, then it's critical 
-    // that the caller supply a service name to be used.
-    // For NTLM, the service principal name can no longer be null. (Bug 487872)
+    // The caller must supply a service name to be used. (For why we now require
+    // a service name for NTLM, see bug 487872.)
     NS_ENSURE_TRUE(serviceName && *serviceName, NS_ERROR_INVALID_ARG);
 
     nsresult rv;
 
     // XXX lazy initialization like this assumes that we are single threaded
     if (!sspi) {
         rv = InitSSPI();
         if (NS_FAILED(rv))
@@ -261,28 +258,51 @@ nsAuthSSPI::Init(const char *serviceName
         LOG(("%s package not found\n", package));
         return NS_ERROR_UNEXPECTED;
     }
     mMaxTokenLen = pinfo->cbMaxToken;
     (sspi->FreeContextBuffer)(pinfo);
 
     TimeStamp useBefore;
 
+    SEC_WINNT_AUTH_IDENTITY_W ai;
+    SEC_WINNT_AUTH_IDENTITY_W *pai = nsnull;
+    
+    // domain, username, and password will be null if nsHttpNTLMAuth's ChallengeReceived
+    // returns false for identityInvalid. Use default credentials in this case by passing
+    // null for pai.
+    if (username && password) {
+        if (domain) {
+            ai.Domain = const_cast<unsigned short*>(domain);
+            ai.DomainLength = wcslen(domain);
+        }
+        else {
+            ai.Domain = NULL;
+            ai.DomainLength = 0;
+        }
+        ai.User = const_cast<unsigned short*>(username);
+        ai.UserLength = wcslen(username);
+        ai.Password = const_cast<unsigned short*>(password);
+        ai.PasswordLength = wcslen(password);
+        ai.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE;
+        pai = &ai;
+    }
+
     rc = (sspi->AcquireCredentialsHandleW)(NULL,
                                            package,
                                            SECPKG_CRED_OUTBOUND,
                                            NULL,
-                                           NULL,
+                                           pai,
                                            NULL,
                                            NULL,
                                            &mCred,
                                            &useBefore);
     if (rc != SEC_E_OK)
         return NS_ERROR_UNEXPECTED;
-
+    LOG(("AcquireCredentialsHandle() succeeded.\n"));
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsAuthSSPI::GetNextToken(const void *inToken,
                          PRUint32    inTokenLen,
                          void      **outToken,
                          PRUint32   *outTokenLen)
@@ -346,16 +366,24 @@ nsAuthSSPI::GetNextToken(const void *inT
                                             SECURITY_NATIVE_DREP,
                                             inToken ? &ibd : NULL,
                                             0,
                                             &mCtxt,
                                             &obd,
                                             &ctxAttr,
                                             &ignored);
     if (rc == SEC_I_CONTINUE_NEEDED || rc == SEC_E_OK) {
+
+#ifdef PR_LOGGING
+        if (rc == SEC_E_OK)
+            LOG(("InitializeSecurityContext: succeeded.\n"));
+        else
+            LOG(("InitializeSecurityContext: continue.\n"));
+#endif
+
         if (!ob.cbBuffer) {
             nsMemory::Free(ob.pvBuffer);
             ob.pvBuffer = NULL;
         }
         *outToken = ob.pvBuffer;
         *outTokenLen = ob.cbBuffer;
 
         if (rc == SEC_E_OK)
--- a/extensions/auth/nsHttpNegotiateAuth.cpp
+++ b/extensions/auth/nsHttpNegotiateAuth.cpp
@@ -252,25 +252,25 @@ nsHttpNegotiateAuth::GenerateCredentials
     PRUint32 inTokenLen, outTokenLen;
 
     if (len > kNegotiateLen) {
         challenge += kNegotiateLen;
         while (*challenge == ' ')
             challenge++;
         len = strlen(challenge);
 
+        // strip off any padding (see bug 230351)
+        while (challenge[len - 1] == '=')
+            len--;
+
         inTokenLen = (len * 3)/4;
         inToken = malloc(inTokenLen);
         if (!inToken)
             return (NS_ERROR_OUT_OF_MEMORY);
 
-        // strip off any padding (see bug 230351)
-        while (challenge[len - 1] == '=')
-            len--;
-
         //
         // Decode the response that followed the "Negotiate" token
         //
         if (PL_Base64Decode(challenge, len, (char *) inToken) == NULL) {
             free(inToken);
             return(NS_ERROR_UNEXPECTED);
         }
     }
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -817,16 +817,25 @@ pref("network.negotiate-auth.using-nativ
 
 #ifdef XP_WIN
 
 // Default to using the SSPI intead of GSSAPI on windows 
 pref("network.auth.use-sspi", true);
 
 #endif
 
+// Controls which NTLM authentication implementation we default to. True forces
+// the use of our generic (internal) NTLM authentication implementation vs. any
+// native implementation provided by the os. This pref is for diagnosing issues
+// with native NTLM. (See bug 520607 for details.) Using generic NTLM authentication
+// can expose the user to reflection attack vulnerabilities. Do not change this
+// unless you know what you're doing!
+// This pref should be removed 6 months after the release of firefox 3.6. 
+pref("network.auth.force-generic-ntlm", false);
+
 // The following prefs are used to enable automatic use of the operating
 // system's NTLM implementation to silently authenticate the user with their
 // Window's domain logon.  The trusted-uris pref follows the format of the
 // trusted-uris pref for negotiate authentication.
 pref("network.automatic-ntlm-auth.allow-proxies", true);
 pref("network.automatic-ntlm-auth.trusted-uris", "");
 
 // This preference controls whether or not the LM hash will be included in
--- a/netwerk/protocol/http/src/nsHttpNTLMAuth.cpp
+++ b/netwerk/protocol/http/src/nsHttpNTLMAuth.cpp
@@ -16,16 +16,17 @@
  *
  * The Initial Developer of the Original Code is
  * Netscape Communications Corporation.
  * Portions created by the Initial Developer are Copyright (C) 2003
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Darin Fisher <darin@meer.net>
+ *   Jim Mathies <jmathies@mozilla.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -49,16 +50,17 @@
 #include "nsIPrefBranch.h"
 #include "nsIPrefService.h"
 #include "nsIServiceManager.h"
 #include "nsIHttpChannel.h"
 #include "nsIURI.h"
 
 static const char kAllowProxies[] = "network.automatic-ntlm-auth.allow-proxies";
 static const char kTrustedURIs[]  = "network.automatic-ntlm-auth.trusted-uris";
+static const char kForceGeneric[] = "network.auth.force-generic-ntlm";
 
 // XXX MatchesBaseURI and TestPref are duplicated in nsHttpNegotiateAuth.cpp,
 // but since that file lives in a separate library we cannot directly share it.
 // bug 236865 addresses this problem.
 
 static PRBool
 MatchesBaseURI(const nsCSubstring &matchScheme,
                const nsCSubstring &matchHost,
@@ -164,47 +166,58 @@ TestPref(nsIURI *uri, const char *pref)
             break;
         start = end + 1;
     }
     
     nsMemory::Free(hostList);
     return PR_FALSE;
 }
 
+// Check to see if we should use our generic (internal) NTLM auth module.
 static PRBool
-CanUseSysNTLM(nsIHttpChannel *channel, PRBool isProxyAuth)
+ForceGenericNTLM()
 {
-    // check prefs
+    nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+    if (!prefs)
+        return PR_FALSE;
+    PRBool flag = PR_FALSE;
+
+    if (NS_FAILED(prefs->GetBoolPref(kForceGeneric, &flag)))
+        flag = PR_FALSE;
 
+    LOG(("Force use of generic ntlm auth module: %d\n", flag));
+    return flag;
+}
+
+// Check to see if we should use default credentials for this host or proxy.
+static PRBool
+CanUseDefaultCredentials(nsIHttpChannel *channel, PRBool isProxyAuth)
+{
     nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
     if (!prefs)
         return PR_FALSE;
 
-    PRBool val;
     if (isProxyAuth) {
+        PRBool val;
         if (NS_FAILED(prefs->GetBoolPref(kAllowProxies, &val)))
             val = PR_FALSE;
-        LOG(("sys-ntlm allowed for proxy: %d\n", val));
+        LOG(("Default credentials allowed for proxy: %d\n", val));
         return val;
     }
-    else {
-        nsCOMPtr<nsIURI> uri;
-        channel->GetURI(getter_AddRefs(uri));
-        if (uri && TestPref(uri, kTrustedURIs)) {
-            LOG(("sys-ntlm allowed for host\n"));
-            return PR_TRUE;
-        }
-    }
 
-    return PR_FALSE;
+    nsCOMPtr<nsIURI> uri;
+    channel->GetURI(getter_AddRefs(uri));
+    PRBool isTrustedHost = (uri && TestPref(uri, kTrustedURIs));
+    LOG(("Default credentials allowed for host: %d\n", isTrustedHost));
+    return isTrustedHost;
 }
 
 // Dummy class for session state object.  This class doesn't hold any data.
 // Instead we use its existance as a flag.  See ChallengeReceived.
-class nsNTLMSessionState : public nsISupports 
+class nsNTLMSessionState : public nsISupports
 {
 public:
     NS_DECL_ISUPPORTS
 };
 NS_IMPL_ISUPPORTS0(nsNTLMSessionState)
 
 //-----------------------------------------------------------------------------
 
@@ -216,69 +229,92 @@ nsHttpNTLMAuth::ChallengeReceived(nsIHtt
                                   PRBool          isProxyAuth,
                                   nsISupports   **sessionState,
                                   nsISupports   **continuationState,
                                   PRBool         *identityInvalid)
 {
     LOG(("nsHttpNTLMAuth::ChallengeReceived [ss=%p cs=%p]\n",
          *sessionState, *continuationState));
 
-    // NOTE: we don't define any session state
+    // NOTE: we don't define any session state, but we do use the pointer.
 
     *identityInvalid = PR_FALSE;
-    // start new auth sequence if challenge is exactly "NTLM"
+
+    // Start a new auth sequence if the challenge is exactly "NTLM".
+    // If native NTLM auth apis are available and enabled through prefs,
+    // try to use them.
     if (PL_strcasecmp(challenge, "NTLM") == 0) {
         nsCOMPtr<nsISupports> module;
-        //
-        // our session state is non-null to indicate that we've flagged
-        // this auth domain as not accepting the system's default login.
-        //
-        PRBool trySysNTLM = (*sessionState == nsnull);
 
-        //
-        // we may have access to a built-in SSPI library,
-        // which could be used to authenticate the user without prompting.
-        // 
-        // if the continuationState is null, then we may want to try using
-        // the SSPI NTLM module.  however, we need to take care to only use
-        // that module when speaking to a trusted host.  because the SSPI
-        // may send a weak LMv1 hash of the user's password, we cannot just
-        // send it to any server.
-        //
-        if (trySysNTLM && !*continuationState && CanUseSysNTLM(channel, isProxyAuth)) {
-            module = do_CreateInstance(NS_AUTH_MODULE_CONTRACTID_PREFIX "sys-ntlm");
+        // Check to see if we should default to our generic NTLM auth module
+        // through UseGenericNTLM. (We use native auth by default if the
+        // system provides it.) If *sessionState is non-null, we failed to
+        // instantiate a native NTLM module the last time, so skip trying again.
+        PRBool forceGeneric = ForceGenericNTLM();
+        if (!forceGeneric && !*sessionState) {
+            // Check for approved default credentials hosts and proxies. If 
+            // *continuationState is non-null, the last authentication attempt
+            // failed so skip default credential use.
+            if (!*continuationState && CanUseDefaultCredentials(channel, isProxyAuth)) {
+                // Try logging in with the user's default credentials. If
+                // successful, |identityInvalid| is false, which will trigger
+                // a default credentials attempt once we return.
+                module = do_CreateInstance(NS_AUTH_MODULE_CONTRACTID_PREFIX "sys-ntlm");
+            }
+#ifdef XP_WIN
+            else {
+                // Try to use native NTLM and prompt the user for their domain,
+                // username, and password. (only supported by windows nsAuthSSPI module.)
+                // Note, for servers that use LMv1 a weak hash of the user's password
+                // will be sent. We rely on windows internal apis to decide whether
+                // we should support this older, less secure version of the protocol.
+                module = do_CreateInstance(NS_AUTH_MODULE_CONTRACTID_PREFIX "sys-ntlm");
+                *identityInvalid = PR_TRUE;
+            }
+#endif // XP_WIN
 #ifdef PR_LOGGING
             if (!module)
-                LOG(("failed to load sys-ntlm module\n"));
+                LOG(("Native sys-ntlm auth module not found.\n"));
 #endif
         }
 
-        // it's possible that there is no ntlm-sspi auth module...
+#ifdef XP_WIN
+        // On windows, never fall back unless the user has specifically requested so.
+        if (!forceGeneric && !module)
+            return NS_ERROR_UNEXPECTED;
+#endif
+
+        // If no native support was available. Fall back on our internal NTLM implementation.
         if (!module) {
             if (!*sessionState) {
-                // remember the fact that we cannot use the "sys-ntlm" module,
+                // Remember the fact that we cannot use the "sys-ntlm" module,
                 // so we don't ever bother trying again for this auth domain.
                 *sessionState = new nsNTLMSessionState();
                 if (!*sessionState)
                     return NS_ERROR_OUT_OF_MEMORY;
                 NS_ADDREF(*sessionState);
             }
 
+            // Use our internal NTLM implementation. Note, this is less secure,
+            // see bug 520607 for details.
+            LOG(("Trying to fall back on internal ntlm auth.\n"));
             module = do_CreateInstance(NS_AUTH_MODULE_CONTRACTID_PREFIX "ntlm");
 
-            // prompt user for domain, username, and password...
+            // Prompt user for domain, username, and password.
             *identityInvalid = PR_TRUE;
         }
 
-        // if this fails, then it means that we cannot do NTLM auth.
-        if (!module)
+        // If this fails, then it means that we cannot do NTLM auth.
+        if (!module) {
+            LOG(("No ntlm auth modules available.\n"));
             return NS_ERROR_UNEXPECTED;
+        }
 
-        // non-null continuation state implies that we failed to authenticate.
-        // blow away the old authentication state, and use the new one.
+        // A non-null continuation state implies that we failed to authenticate.
+        // Blow away the old authentication state, and use the new one.
         module.swap(*continuationState);
     }
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsHttpNTLMAuth::GenerateCredentials(nsIHttpChannel  *httpChannel,
                                     const char      *challenge,
@@ -327,26 +363,26 @@ nsHttpNTLMAuth::GenerateCredentials(nsIH
         // decode challenge; skip past "NTLM " to the start of the base64
         // encoded data.
         int len = strlen(challenge);
         if (len < 6)
             return NS_ERROR_UNEXPECTED; // bogus challenge
         challenge += 5;
         len -= 5;
 
+        // strip off any padding (see bug 230351)
+        while (challenge[len - 1] == '=')
+          len--;
+
         // decode into the input secbuffer
         inBufLen = (len * 3)/4;      // sufficient size (see plbase64.h)
         inBuf = nsMemory::Alloc(inBufLen);
         if (!inBuf)
             return NS_ERROR_OUT_OF_MEMORY;
 
-        // strip off any padding (see bug 230351)
-        while (challenge[len - 1] == '=')
-          len--;
-
         if (PL_Base64Decode(challenge, len, (char *) inBuf) == nsnull) {
             nsMemory::Free(inBuf);
             return NS_ERROR_UNEXPECTED; // improper base64 encoding
         }
     }
 
     rv = module->GetNextToken(inBuf, inBufLen, &outBuf, &outBufLen);
     if (NS_SUCCEEDED(rv)) {
--- a/security/manager/ssl/src/nsNTLMAuthModule.cpp
+++ b/security/manager/ssl/src/nsNTLMAuthModule.cpp
@@ -797,17 +797,16 @@ nsNTLMAuthModule::InitTest()
 
 NS_IMETHODIMP
 nsNTLMAuthModule::Init(const char      *serviceName,
                        PRUint32         serviceFlags,
                        const PRUnichar *domain,
                        const PRUnichar *username,
                        const PRUnichar *password)
 {
-  NS_ASSERTION(serviceName == nsnull, "unexpected service name");
   NS_ASSERTION(serviceFlags == nsIAuthModule::REQ_DEFAULT, "unexpected service flags");
 
   mDomain = domain;
   mUsername = username;
   mPassword = password;
   return NS_OK;
 }