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