nsHttpNTLMAuth.cpp (13983B)
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 "prnetdb.h" 15 16 //----------------------------------------------------------------------------- 17 18 #include "nsIPrefBranch.h" 19 #include "nsIHttpAuthenticableChannel.h" 20 #include "nsIURI.h" 21 #ifdef XP_WIN 22 # include "nsIChannel.h" 23 # include "nsIX509Cert.h" 24 # include "nsITransportSecurityInfo.h" 25 #endif 26 #include "mozilla/Base64.h" 27 #include "mozilla/CheckedInt.h" 28 #include "mozilla/Maybe.h" 29 #include "mozilla/Tokenizer.h" 30 #include "nsCRT.h" 31 #include "nsNetUtil.h" 32 #include "nsIChannel.h" 33 #include "nsUnicharUtils.h" 34 #include "mozilla/net/HttpAuthUtils.h" 35 #include "mozilla/ClearOnShutdown.h" 36 #include "mozilla/net/DNS.h" 37 #include "mozilla/StaticPrefs_browser.h" 38 39 namespace mozilla { 40 namespace net { 41 42 static const char kAllowProxies[] = "network.automatic-ntlm-auth.allow-proxies"; 43 static const char kAllowNonFqdn[] = 44 "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 StaticRefPtr<nsHttpNTLMAuth> nsHttpNTLMAuth::gSingleton; 50 51 static bool IsNonFqdn(nsIURI* uri) { 52 nsAutoCString host; 53 if (NS_FAILED(uri->GetAsciiHost(host))) { 54 return false; 55 } 56 57 // return true if host does not contain a dot and is not an ip address 58 return !host.IsEmpty() && !host.Contains('.') && !HostIsIPLiteral(host); 59 } 60 61 // Check to see if we should use our generic (internal) NTLM auth module. 62 static bool ForceGenericNTLM() { 63 nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); 64 if (!prefs) return false; 65 bool flag = false; 66 67 if (NS_FAILED(prefs->GetBoolPref(kForceGeneric, &flag))) flag = false; 68 69 LOG(("Force use of generic ntlm auth module: %d\n", flag)); 70 return flag; 71 } 72 73 // Check to see if we should use default credentials for this host or proxy. 74 static bool CanUseDefaultCredentials(nsIHttpAuthenticableChannel* channel, 75 bool isProxyAuth) { 76 nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); 77 if (!prefs) { 78 return false; 79 } 80 81 // Proxy should go all the time, it's not considered a privacy leak 82 // to send default credentials to a proxy. 83 if (isProxyAuth) { 84 bool val; 85 if (NS_FAILED(prefs->GetBoolPref(kAllowProxies, &val))) val = false; 86 LOG(("Default credentials allowed for proxy: %d\n", val)); 87 return val; 88 } 89 90 // Prevent using default credentials for authentication when we are in the 91 // private browsing mode (but not in "never remember history" mode) and when 92 // not explicitely allowed. Otherwise, it would cause a privacy data leak. 93 nsCOMPtr<nsIChannel> bareChannel = do_QueryInterface(channel); 94 MOZ_ASSERT(bareChannel); 95 96 if (NS_UsePrivateBrowsing(bareChannel)) { 97 bool ssoInPb; 98 if (NS_SUCCEEDED(prefs->GetBoolPref(kSSOinPBmode, &ssoInPb)) && ssoInPb) { 99 return true; 100 } 101 102 if (!StaticPrefs::browser_privatebrowsing_autostart()) { 103 return false; 104 } 105 } 106 107 nsCOMPtr<nsIURI> uri; 108 (void)channel->GetURI(getter_AddRefs(uri)); 109 110 bool allowNonFqdn; 111 if (NS_FAILED(prefs->GetBoolPref(kAllowNonFqdn, &allowNonFqdn))) { 112 allowNonFqdn = false; 113 } 114 if (allowNonFqdn && uri && IsNonFqdn(uri)) { 115 LOG(("Host is non-fqdn, default credentials are allowed\n")); 116 return true; 117 } 118 119 bool isTrustedHost = (uri && auth::URIMatchesPrefPattern(uri, kTrustedURIs)); 120 LOG(("Default credentials allowed for host: %d\n", isTrustedHost)); 121 return isTrustedHost; 122 } 123 124 // Dummy class for session state object. This class doesn't hold any data. 125 // Instead we use its existence as a flag. See ChallengeReceived. 126 class nsNTLMSessionState final : public nsISupports { 127 ~nsNTLMSessionState() = default; 128 129 public: 130 NS_DECL_ISUPPORTS 131 }; 132 NS_IMPL_ISUPPORTS0(nsNTLMSessionState) 133 134 //----------------------------------------------------------------------------- 135 136 already_AddRefed<nsIHttpAuthenticator> nsHttpNTLMAuth::GetOrCreate() { 137 nsCOMPtr<nsIHttpAuthenticator> authenticator; 138 if (gSingleton) { 139 authenticator = gSingleton; 140 } else { 141 gSingleton = new nsHttpNTLMAuth(); 142 ClearOnShutdown(&gSingleton); 143 authenticator = gSingleton; 144 } 145 146 return authenticator.forget(); 147 } 148 149 NS_IMPL_ISUPPORTS(nsHttpNTLMAuth, nsIHttpAuthenticator) 150 151 NS_IMETHODIMP 152 nsHttpNTLMAuth::ChallengeReceived(nsIHttpAuthenticableChannel* channel, 153 const nsACString& challenge, bool isProxyAuth, 154 nsISupports** sessionState, 155 nsISupports** continuationState, 156 bool* identityInvalid) { 157 LOG(("nsHttpNTLMAuth::ChallengeReceived [ss=%p cs=%p]\n", *sessionState, 158 *continuationState)); 159 160 // Use the native NTLM if available 161 mUseNative = true; 162 163 // NOTE: we don't define any session state, but we do use the pointer. 164 165 *identityInvalid = false; 166 167 /* Always fail Negotiate auth for Tor Browser. We don't need it. */ 168 return NS_ERROR_ABORT; 169 170 // Start a new auth sequence if the challenge is exactly "NTLM". 171 // If native NTLM auth apis are available and enabled through prefs, 172 // try to use them. 173 if (challenge.Equals("NTLM"_ns, nsCaseInsensitiveCStringComparator)) { 174 nsCOMPtr<nsIAuthModule> module; 175 176 #ifdef MOZ_AUTH_EXTENSION 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 #endif 235 236 // If this fails, then it means that we cannot do NTLM auth. 237 if (!module) { 238 LOG(("No ntlm auth modules available.\n")); 239 return NS_ERROR_UNEXPECTED; 240 } 241 242 // A non-null continuation state implies that we failed to authenticate. 243 // Blow away the old authentication state, and use the new one. 244 module.forget(continuationState); 245 } 246 return NS_OK; 247 } 248 249 NS_IMETHODIMP 250 nsHttpNTLMAuth::GenerateCredentialsAsync( 251 nsIHttpAuthenticableChannel* authChannel, 252 nsIHttpAuthenticatorCallback* aCallback, const nsACString& challenge, 253 bool isProxyAuth, const nsAString& domain, const nsAString& username, 254 const nsAString& password, nsISupports* sessionState, 255 nsISupports* continuationState, nsICancelable** aCancellable) { 256 return NS_ERROR_NOT_IMPLEMENTED; 257 } 258 259 NS_IMETHODIMP 260 nsHttpNTLMAuth::GenerateCredentials( 261 nsIHttpAuthenticableChannel* authChannel, const nsACString& aChallenge, 262 bool isProxyAuth, const nsAString& domain, const nsAString& user, 263 const nsAString& pass, nsISupports** sessionState, 264 nsISupports** continuationState, uint32_t* aFlags, nsACString& creds) 265 266 { 267 LOG(("nsHttpNTLMAuth::GenerateCredentials\n")); 268 269 creds.Truncate(); 270 *aFlags = 0; 271 272 // if user or password is empty, ChallengeReceived returned 273 // identityInvalid = false, that means we are using default user 274 // credentials; see nsAuthSSPI::Init method for explanation of this 275 // condition 276 if (user.IsEmpty() || pass.IsEmpty()) *aFlags = USING_INTERNAL_IDENTITY; 277 278 nsresult rv; 279 nsCOMPtr<nsIAuthModule> module = do_QueryInterface(*continuationState, &rv); 280 NS_ENSURE_SUCCESS(rv, rv); 281 282 void *inBuf, *outBuf; 283 uint32_t inBufLen, outBufLen; 284 Maybe<nsTArray<uint8_t>> certArray; 285 286 // initial challenge 287 if (aChallenge.Equals("NTLM"_ns, nsCaseInsensitiveCStringComparator)) { 288 // NTLM service name format is 'HTTP@host' for both http and https 289 nsCOMPtr<nsIURI> uri; 290 rv = authChannel->GetURI(getter_AddRefs(uri)); 291 if (NS_FAILED(rv)) return rv; 292 nsAutoCString serviceName, host; 293 rv = uri->GetAsciiHost(host); 294 if (NS_FAILED(rv)) return rv; 295 serviceName.AppendLiteral("HTTP@"); 296 serviceName.Append(host); 297 // initialize auth module 298 uint32_t reqFlags = nsIAuthModule::REQ_DEFAULT; 299 if (isProxyAuth) reqFlags |= nsIAuthModule::REQ_PROXY_AUTH; 300 301 rv = module->Init(serviceName, reqFlags, domain, user, pass); 302 if (NS_FAILED(rv)) return rv; 303 304 inBufLen = 0; 305 inBuf = nullptr; 306 // This update enables updated Windows machines (Win7 or patched previous 307 // versions) and Linux machines running Samba (updated for Channel 308 // Binding), to perform Channel Binding when authenticating using NTLMv2 309 // and an outer secure channel. 310 // 311 // Currently only implemented for Windows, linux support will be landing in 312 // a separate patch, update this #ifdef accordingly then. 313 // Extended protection update is just for Linux and Windows machines. 314 #if defined(XP_WIN) /* || defined (LINUX) */ 315 // We should retrieve the server certificate and compute the CBT, 316 // but only when we are using the native NTLM implementation and 317 // not the internal one. 318 // It is a valid case not having the security info object. This 319 // occures when we connect an https site through an ntlm proxy. 320 // After the ssl tunnel has been created, we get here the second 321 // time and now generate the CBT from now valid security info. 322 nsCOMPtr<nsIChannel> channel = do_QueryInterface(authChannel, &rv); 323 if (NS_FAILED(rv)) return rv; 324 325 nsCOMPtr<nsITransportSecurityInfo> securityInfo; 326 rv = channel->GetSecurityInfo(getter_AddRefs(securityInfo)); 327 if (NS_FAILED(rv)) return rv; 328 329 if (mUseNative && securityInfo) { 330 nsCOMPtr<nsIX509Cert> cert; 331 rv = securityInfo->GetServerCert(getter_AddRefs(cert)); 332 if (NS_FAILED(rv)) return rv; 333 334 if (cert) { 335 certArray.emplace(); 336 rv = cert->GetRawDER(*certArray); 337 if (NS_FAILED(rv)) { 338 return rv; 339 } 340 341 // If there is a server certificate, we pass it along the 342 // first time we call GetNextToken(). 343 inBufLen = certArray->Length(); 344 inBuf = certArray->Elements(); 345 } 346 } 347 #endif 348 } else { 349 // decode challenge; skip past "NTLM " to the start of the base64 350 // encoded data. 351 if (aChallenge.Length() < 6) { 352 return NS_ERROR_UNEXPECTED; // bogus challenge 353 } 354 355 // strip off any padding (see bug 230351) 356 nsDependentCSubstring challenge(aChallenge, 5); 357 uint32_t len = challenge.Length(); 358 while (len > 0 && challenge[len - 1] == '=') { 359 len--; 360 } 361 362 // decode into the input secbuffer 363 rv = Base64Decode(challenge.BeginReading(), 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 nsAutoCString encoded; 380 (void)Base64Encode(nsDependentCSubstring((char*)outBuf, outBufLen), 381 encoded); 382 creds = nsPrintfCString("NTLM %s", encoded.get()); 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 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