1 /* vim:set ts=4 sw=2 sts=2 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/. */
5 
6 // HttpLog.h should generally be included first
7 #include "HttpLog.h"
8 
9 #include "nsHttpNTLMAuth.h"
10 #include "nsIAuthModule.h"
11 #include "nsCOMPtr.h"
12 #include "nsServiceManagerUtils.h"
13 #include "plbase64.h"
14 #include "plstr.h"
15 #include "prnetdb.h"
16 
17 //-----------------------------------------------------------------------------
18 
19 #include "nsIPrefBranch.h"
20 #include "nsIHttpAuthenticableChannel.h"
21 #include "nsIURI.h"
22 #ifdef XP_WIN
23 #  include "nsIChannel.h"
24 #  include "nsIX509Cert.h"
25 #  include "nsITransportSecurityInfo.h"
26 #endif
27 #include "mozilla/Attributes.h"
28 #include "mozilla/Base64.h"
29 #include "mozilla/CheckedInt.h"
30 #include "mozilla/Maybe.h"
31 #include "mozilla/Tokenizer.h"
32 #include "mozilla/UniquePtr.h"
33 #include "mozilla/Unused.h"
34 #include "nsNetUtil.h"
35 #include "nsIChannel.h"
36 #include "nsUnicharUtils.h"
37 #include "mozilla/net/HttpAuthUtils.h"
38 #include "mozilla/ClearOnShutdown.h"
39 
40 namespace mozilla {
41 namespace net {
42 
43 static const char kAllowProxies[] = "network.automatic-ntlm-auth.allow-proxies";
44 static const char kAllowNonFqdn[] =
45     "network.automatic-ntlm-auth.allow-non-fqdn";
46 static const char kTrustedURIs[] = "network.automatic-ntlm-auth.trusted-uris";
47 static const char kForceGeneric[] = "network.auth.force-generic-ntlm";
48 static const char kSSOinPBmode[] = "network.auth.private-browsing-sso";
49 
50 StaticRefPtr<nsHttpNTLMAuth> nsHttpNTLMAuth::gSingleton;
51 
IsNonFqdn(nsIURI * uri)52 static bool IsNonFqdn(nsIURI* uri) {
53   nsAutoCString host;
54   PRNetAddr addr;
55 
56   if (NS_FAILED(uri->GetAsciiHost(host))) return false;
57 
58   // return true if host does not contain a dot and is not an ip address
59   return !host.IsEmpty() && !host.Contains('.') &&
60          PR_StringToNetAddr(host.BeginReading(), &addr) != PR_SUCCESS;
61 }
62 
63 // Check to see if we should use our generic (internal) NTLM auth module.
ForceGenericNTLM()64 static bool ForceGenericNTLM() {
65   nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
66   if (!prefs) return false;
67   bool flag = false;
68 
69   if (NS_FAILED(prefs->GetBoolPref(kForceGeneric, &flag))) flag = false;
70 
71   LOG(("Force use of generic ntlm auth module: %d\n", flag));
72   return flag;
73 }
74 
75 // Check to see if we should use default credentials for this host or proxy.
CanUseDefaultCredentials(nsIHttpAuthenticableChannel * channel,bool isProxyAuth)76 static bool CanUseDefaultCredentials(nsIHttpAuthenticableChannel* channel,
77                                      bool isProxyAuth) {
78   nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
79   if (!prefs) {
80     return false;
81   }
82 
83   // Proxy should go all the time, it's not considered a privacy leak
84   // to send default credentials to a proxy.
85   if (isProxyAuth) {
86     bool val;
87     if (NS_FAILED(prefs->GetBoolPref(kAllowProxies, &val))) val = false;
88     LOG(("Default credentials allowed for proxy: %d\n", val));
89     return val;
90   }
91 
92   // Prevent using default credentials for authentication when we are in the
93   // private browsing mode (but not in "never remember history" mode) and when
94   // not explicitely allowed.  Otherwise, it would cause a privacy data leak.
95   nsCOMPtr<nsIChannel> bareChannel = do_QueryInterface(channel);
96   MOZ_ASSERT(bareChannel);
97 
98   if (NS_UsePrivateBrowsing(bareChannel)) {
99     bool ssoInPb;
100     if (NS_SUCCEEDED(prefs->GetBoolPref(kSSOinPBmode, &ssoInPb)) && ssoInPb) {
101       return true;
102     }
103 
104     bool dontRememberHistory;
105     if (NS_SUCCEEDED(prefs->GetBoolPref("browser.privatebrowsing.autostart",
106                                         &dontRememberHistory)) &&
107         !dontRememberHistory) {
108       return false;
109     }
110   }
111 
112   nsCOMPtr<nsIURI> uri;
113   Unused << channel->GetURI(getter_AddRefs(uri));
114 
115   bool allowNonFqdn;
116   if (NS_FAILED(prefs->GetBoolPref(kAllowNonFqdn, &allowNonFqdn)))
117     allowNonFqdn = false;
118   if (allowNonFqdn && uri && IsNonFqdn(uri)) {
119     LOG(("Host is non-fqdn, default credentials are allowed\n"));
120     return true;
121   }
122 
123   bool isTrustedHost = (uri && auth::URIMatchesPrefPattern(uri, kTrustedURIs));
124   LOG(("Default credentials allowed for host: %d\n", isTrustedHost));
125   return isTrustedHost;
126 }
127 
128 // Dummy class for session state object.  This class doesn't hold any data.
129 // Instead we use its existence as a flag.  See ChallengeReceived.
130 class nsNTLMSessionState final : public nsISupports {
131   ~nsNTLMSessionState() = default;
132 
133  public:
134   NS_DECL_ISUPPORTS
135 };
NS_IMPL_ISUPPORTS0(nsNTLMSessionState)136 NS_IMPL_ISUPPORTS0(nsNTLMSessionState)
137 
138 //-----------------------------------------------------------------------------
139 
140 already_AddRefed<nsIHttpAuthenticator> nsHttpNTLMAuth::GetOrCreate() {
141   nsCOMPtr<nsIHttpAuthenticator> authenticator;
142   if (gSingleton) {
143     authenticator = gSingleton;
144   } else {
145     gSingleton = new nsHttpNTLMAuth();
146     ClearOnShutdown(&gSingleton);
147     authenticator = gSingleton;
148   }
149 
150   return authenticator.forget();
151 }
152 
NS_IMPL_ISUPPORTS(nsHttpNTLMAuth,nsIHttpAuthenticator)153 NS_IMPL_ISUPPORTS(nsHttpNTLMAuth, nsIHttpAuthenticator)
154 
155 NS_IMETHODIMP
156 nsHttpNTLMAuth::ChallengeReceived(nsIHttpAuthenticableChannel* channel,
157                                   const char* challenge, bool isProxyAuth,
158                                   nsISupports** sessionState,
159                                   nsISupports** continuationState,
160                                   bool* identityInvalid) {
161   LOG(("nsHttpNTLMAuth::ChallengeReceived [ss=%p cs=%p]\n", *sessionState,
162        *continuationState));
163 
164   // Use the native NTLM if available
165   mUseNative = true;
166 
167   // NOTE: we don't define any session state, but we do use the pointer.
168 
169   *identityInvalid = false;
170 
171   // Start a new auth sequence if the challenge is exactly "NTLM".
172   // If native NTLM auth apis are available and enabled through prefs,
173   // try to use them.
174   if (PL_strcasecmp(challenge, "NTLM") == 0) {
175     nsCOMPtr<nsIAuthModule> module;
176 
177     // Check to see if we should default to our generic NTLM auth module
178     // through UseGenericNTLM. (We use native auth by default if the
179     // system provides it.) If *sessionState is non-null, we failed to
180     // instantiate a native NTLM module the last time, so skip trying again.
181     bool forceGeneric = ForceGenericNTLM();
182     if (!forceGeneric && !*sessionState) {
183       // Check for approved default credentials hosts and proxies. If
184       // *continuationState is non-null, the last authentication attempt
185       // failed so skip default credential use.
186       if (!*continuationState &&
187           CanUseDefaultCredentials(channel, isProxyAuth)) {
188         // Try logging in with the user's default credentials. If
189         // successful, |identityInvalid| is false, which will trigger
190         // a default credentials attempt once we return.
191         module = nsIAuthModule::CreateInstance("sys-ntlm");
192       }
193 #ifdef XP_WIN
194       else {
195         // Try to use native NTLM and prompt the user for their domain,
196         // username, and password. (only supported by windows nsAuthSSPI
197         // module.) Note, for servers that use LMv1 a weak hash of the user's
198         // password will be sent. We rely on windows internal apis to decide
199         // whether we should support this older, less secure version of the
200         // protocol.
201         module = nsIAuthModule::CreateInstance("sys-ntlm");
202         *identityInvalid = true;
203       }
204 #endif  // XP_WIN
205       if (!module) LOG(("Native sys-ntlm auth module not found.\n"));
206     }
207 
208 #ifdef XP_WIN
209     // On windows, never fall back unless the user has specifically requested
210     // so.
211     if (!forceGeneric && !module) return NS_ERROR_UNEXPECTED;
212 #endif
213 
214     // If no native support was available. Fall back on our internal NTLM
215     // implementation.
216     if (!module) {
217       if (!*sessionState) {
218         // Remember the fact that we cannot use the "sys-ntlm" module,
219         // so we don't ever bother trying again for this auth domain.
220         RefPtr<nsNTLMSessionState> state = new nsNTLMSessionState();
221         state.forget(sessionState);
222       }
223 
224       // Use our internal NTLM implementation. Note, this is less secure,
225       // see bug 520607 for details.
226       LOG(("Trying to fall back on internal ntlm auth.\n"));
227       module = nsIAuthModule::CreateInstance("ntlm");
228 
229       mUseNative = false;
230 
231       // Prompt user for domain, username, and password.
232       *identityInvalid = true;
233     }
234 
235     // If this fails, then it means that we cannot do NTLM auth.
236     if (!module) {
237       LOG(("No ntlm auth modules available.\n"));
238       return NS_ERROR_UNEXPECTED;
239     }
240 
241     // A non-null continuation state implies that we failed to authenticate.
242     // Blow away the old authentication state, and use the new one.
243     module.forget(continuationState);
244   }
245   return NS_OK;
246 }
247 
248 NS_IMETHODIMP
GenerateCredentialsAsync(nsIHttpAuthenticableChannel * authChannel,nsIHttpAuthenticatorCallback * aCallback,const char * challenge,bool isProxyAuth,const char16_t * domain,const char16_t * username,const char16_t * password,nsISupports * sessionState,nsISupports * continuationState,nsICancelable ** aCancellable)249 nsHttpNTLMAuth::GenerateCredentialsAsync(
250     nsIHttpAuthenticableChannel* authChannel,
251     nsIHttpAuthenticatorCallback* aCallback, const char* challenge,
252     bool isProxyAuth, const char16_t* domain, const char16_t* username,
253     const char16_t* password, nsISupports* sessionState,
254     nsISupports* continuationState, nsICancelable** aCancellable) {
255   return NS_ERROR_NOT_IMPLEMENTED;
256 }
257 
258 NS_IMETHODIMP
GenerateCredentials(nsIHttpAuthenticableChannel * authChannel,const char * challenge,bool isProxyAuth,const char16_t * domain,const char16_t * user,const char16_t * pass,nsISupports ** sessionState,nsISupports ** continuationState,uint32_t * aFlags,char ** creds)259 nsHttpNTLMAuth::GenerateCredentials(nsIHttpAuthenticableChannel* authChannel,
260                                     const char* challenge, bool isProxyAuth,
261                                     const char16_t* domain,
262                                     const char16_t* user, const char16_t* pass,
263                                     nsISupports** sessionState,
264                                     nsISupports** continuationState,
265                                     uint32_t* aFlags, char** creds)
266 
267 {
268   LOG(("nsHttpNTLMAuth::GenerateCredentials\n"));
269 
270   *creds = nullptr;
271   *aFlags = 0;
272 
273   // if user or password is empty, ChallengeReceived returned
274   // identityInvalid = false, that means we are using default user
275   // credentials; see  nsAuthSSPI::Init method for explanation of this
276   // condition
277   if (!user || !pass) *aFlags = USING_INTERNAL_IDENTITY;
278 
279   nsresult rv;
280   nsCOMPtr<nsIAuthModule> module = do_QueryInterface(*continuationState, &rv);
281   NS_ENSURE_SUCCESS(rv, rv);
282 
283   void *inBuf, *outBuf;
284   uint32_t inBufLen, outBufLen;
285   Maybe<nsTArray<uint8_t>> certArray;
286 
287   // initial challenge
288   if (PL_strcasecmp(challenge, "NTLM") == 0) {
289     // NTLM service name format is 'HTTP@host' for both http and https
290     nsCOMPtr<nsIURI> uri;
291     rv = authChannel->GetURI(getter_AddRefs(uri));
292     if (NS_FAILED(rv)) return rv;
293     nsAutoCString serviceName, host;
294     rv = uri->GetAsciiHost(host);
295     if (NS_FAILED(rv)) return rv;
296     serviceName.AppendLiteral("HTTP@");
297     serviceName.Append(host);
298     // initialize auth module
299     uint32_t reqFlags = nsIAuthModule::REQ_DEFAULT;
300     if (isProxyAuth) reqFlags |= nsIAuthModule::REQ_PROXY_AUTH;
301 
302     rv = module->Init(serviceName.get(), reqFlags, domain, user, pass);
303     if (NS_FAILED(rv)) return rv;
304 
305 // This update enables updated Windows machines (Win7 or patched previous
306 // versions) and Linux machines running Samba (updated for Channel
307 // Binding), to perform Channel Binding when authenticating using NTLMv2
308 // and an outer secure channel.
309 //
310 // Currently only implemented for Windows, linux support will be landing in
311 // a separate patch, update this #ifdef accordingly then.
312 #if defined(XP_WIN) /* || defined (LINUX) */
313     // We should retrieve the server certificate and compute the CBT,
314     // but only when we are using the native NTLM implementation and
315     // not the internal one.
316     // It is a valid case not having the security info object.  This
317     // occures when we connect an https site through an ntlm proxy.
318     // After the ssl tunnel has been created, we get here the second
319     // time and now generate the CBT from now valid security info.
320     nsCOMPtr<nsIChannel> channel = do_QueryInterface(authChannel, &rv);
321     if (NS_FAILED(rv)) return rv;
322 
323     nsCOMPtr<nsISupports> security;
324     rv = channel->GetSecurityInfo(getter_AddRefs(security));
325     if (NS_FAILED(rv)) return rv;
326 
327     nsCOMPtr<nsITransportSecurityInfo> secInfo = do_QueryInterface(security);
328 
329     if (mUseNative && secInfo) {
330       nsCOMPtr<nsIX509Cert> cert;
331       rv = secInfo->GetServerCert(getter_AddRefs(cert));
332       if (NS_FAILED(rv)) return rv;
333 
334       certArray.emplace();
335       rv = cert->GetRawDER(*certArray);
336       if (NS_FAILED(rv)) return rv;
337 
338       // If there is a server certificate, we pass it along the
339       // first time we call GetNextToken().
340       inBufLen = certArray->Length();
341       inBuf = certArray->Elements();
342     } else {
343       // If there is no server certificate, we don't pass anything.
344       inBufLen = 0;
345       inBuf = nullptr;
346     }
347 #else  // Extended protection update is just for Linux and Windows machines.
348     inBufLen = 0;
349     inBuf = nullptr;
350 #endif
351   } else {
352     // decode challenge; skip past "NTLM " to the start of the base64
353     // encoded data.
354     int len = strlen(challenge);
355     if (len < 6) return NS_ERROR_UNEXPECTED;  // bogus challenge
356     challenge += 5;
357     len -= 5;
358 
359     // strip off any padding (see bug 230351)
360     while (len && challenge[len - 1] == '=') len--;
361 
362     // decode into the input secbuffer
363     rv = Base64Decode(challenge, len, (char**)&inBuf, &inBufLen);
364     if (NS_FAILED(rv)) {
365       return rv;
366     }
367   }
368 
369   rv = module->GetNextToken(inBuf, inBufLen, &outBuf, &outBufLen);
370   if (NS_SUCCEEDED(rv)) {
371     // base64 encode data in output buffer and prepend "NTLM "
372     CheckedUint32 credsLen = ((CheckedUint32(outBufLen) + 2) / 3) * 4;
373     credsLen += 5;  // "NTLM "
374     credsLen += 1;  // null terminate
375 
376     if (!credsLen.isValid()) {
377       rv = NS_ERROR_FAILURE;
378     } else {
379       *creds = (char*)moz_xmalloc(credsLen.value());
380       memcpy(*creds, "NTLM ", 5);
381       PL_Base64Encode((char*)outBuf, outBufLen, *creds + 5);
382       (*creds)[credsLen.value() - 1] = '\0';  // null terminate
383     }
384 
385     // OK, we are done with |outBuf|
386     free(outBuf);
387   }
388 
389   // inBuf needs to be freed if it's not pointing into certArray
390   if (inBuf && !certArray) {
391     free(inBuf);
392   }
393 
394   return rv;
395 }
396 
397 NS_IMETHODIMP
GetAuthFlags(uint32_t * flags)398 nsHttpNTLMAuth::GetAuthFlags(uint32_t* flags) {
399   *flags = CONNECTION_BASED | IDENTITY_INCLUDES_DOMAIN | IDENTITY_ENCRYPTED;
400   return NS_OK;
401 }
402 
403 }  // namespace net
404 }  // namespace mozilla
405