|
1 /* vim:set ts=4 sw=4 sts=4 et cindent: */ |
|
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 // |
|
7 // HTTP Negotiate Authentication Support Module |
|
8 // |
|
9 // Described by IETF Internet draft: draft-brezak-kerberos-http-00.txt |
|
10 // (formerly draft-brezak-spnego-http-04.txt) |
|
11 // |
|
12 // Also described here: |
|
13 // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnsecure/html/http-sso-1.asp |
|
14 // |
|
15 |
|
16 #include <string.h> |
|
17 #include <stdlib.h> |
|
18 |
|
19 #include "nsAuth.h" |
|
20 #include "nsHttpNegotiateAuth.h" |
|
21 |
|
22 #include "nsIHttpAuthenticableChannel.h" |
|
23 #include "nsIProxiedChannel.h" |
|
24 #include "nsIAuthModule.h" |
|
25 #include "nsIServiceManager.h" |
|
26 #include "nsIPrefService.h" |
|
27 #include "nsIPrefBranch.h" |
|
28 #include "nsIProxyInfo.h" |
|
29 #include "nsIURI.h" |
|
30 #include "nsCOMPtr.h" |
|
31 #include "nsString.h" |
|
32 #include "nsNetCID.h" |
|
33 #include "plbase64.h" |
|
34 #include "plstr.h" |
|
35 #include "prprf.h" |
|
36 #include "prlog.h" |
|
37 #include "prmem.h" |
|
38 #include "prnetdb.h" |
|
39 #include "mozilla/Likely.h" |
|
40 |
|
41 //----------------------------------------------------------------------------- |
|
42 |
|
43 static const char kNegotiate[] = "Negotiate"; |
|
44 static const char kNegotiateAuthTrustedURIs[] = "network.negotiate-auth.trusted-uris"; |
|
45 static const char kNegotiateAuthDelegationURIs[] = "network.negotiate-auth.delegation-uris"; |
|
46 static const char kNegotiateAuthAllowProxies[] = "network.negotiate-auth.allow-proxies"; |
|
47 static const char kNegotiateAuthAllowNonFqdn[] = "network.negotiate-auth.allow-non-fqdn"; |
|
48 static const char kNegotiateAuthSSPI[] = "network.auth.use-sspi"; |
|
49 |
|
50 #define kNegotiateLen (sizeof(kNegotiate)-1) |
|
51 |
|
52 //----------------------------------------------------------------------------- |
|
53 |
|
54 NS_IMETHODIMP |
|
55 nsHttpNegotiateAuth::GetAuthFlags(uint32_t *flags) |
|
56 { |
|
57 // |
|
58 // Negotiate Auth creds should not be reused across multiple requests. |
|
59 // Only perform the negotiation when it is explicitly requested by the |
|
60 // server. Thus, do *NOT* use the "REUSABLE_CREDENTIALS" flag here. |
|
61 // |
|
62 // CONNECTION_BASED is specified instead of REQUEST_BASED since we need |
|
63 // to complete a sequence of transactions with the server over the same |
|
64 // connection. |
|
65 // |
|
66 *flags = CONNECTION_BASED | IDENTITY_IGNORED; |
|
67 return NS_OK; |
|
68 } |
|
69 |
|
70 // |
|
71 // Always set *identityInvalid == FALSE here. This |
|
72 // will prevent the browser from popping up the authentication |
|
73 // prompt window. Because GSSAPI does not have an API |
|
74 // for fetching initial credentials (ex: A Kerberos TGT), |
|
75 // there is no correct way to get the users credentials. |
|
76 // |
|
77 NS_IMETHODIMP |
|
78 nsHttpNegotiateAuth::ChallengeReceived(nsIHttpAuthenticableChannel *authChannel, |
|
79 const char *challenge, |
|
80 bool isProxyAuth, |
|
81 nsISupports **sessionState, |
|
82 nsISupports **continuationState, |
|
83 bool *identityInvalid) |
|
84 { |
|
85 nsIAuthModule *module = (nsIAuthModule *) *continuationState; |
|
86 |
|
87 *identityInvalid = false; |
|
88 |
|
89 /* Always fail Negotiate auth for Tor Browser. We don't need it. */ |
|
90 return NS_ERROR_ABORT; |
|
91 |
|
92 if (module) |
|
93 return NS_OK; |
|
94 |
|
95 nsresult rv; |
|
96 |
|
97 nsCOMPtr<nsIURI> uri; |
|
98 rv = authChannel->GetURI(getter_AddRefs(uri)); |
|
99 if (NS_FAILED(rv)) |
|
100 return rv; |
|
101 |
|
102 uint32_t req_flags = nsIAuthModule::REQ_DEFAULT; |
|
103 nsAutoCString service; |
|
104 |
|
105 if (isProxyAuth) { |
|
106 if (!TestBoolPref(kNegotiateAuthAllowProxies)) { |
|
107 LOG(("nsHttpNegotiateAuth::ChallengeReceived proxy auth blocked\n")); |
|
108 return NS_ERROR_ABORT; |
|
109 } |
|
110 |
|
111 req_flags |= nsIAuthModule::REQ_PROXY_AUTH; |
|
112 nsCOMPtr<nsIProxyInfo> proxyInfo; |
|
113 authChannel->GetProxyInfo(getter_AddRefs(proxyInfo)); |
|
114 NS_ENSURE_STATE(proxyInfo); |
|
115 |
|
116 proxyInfo->GetHost(service); |
|
117 } |
|
118 else { |
|
119 bool allowed = TestNonFqdn(uri) || |
|
120 TestPref(uri, kNegotiateAuthTrustedURIs); |
|
121 if (!allowed) { |
|
122 LOG(("nsHttpNegotiateAuth::ChallengeReceived URI blocked\n")); |
|
123 return NS_ERROR_ABORT; |
|
124 } |
|
125 |
|
126 bool delegation = TestPref(uri, kNegotiateAuthDelegationURIs); |
|
127 if (delegation) { |
|
128 LOG((" using REQ_DELEGATE\n")); |
|
129 req_flags |= nsIAuthModule::REQ_DELEGATE; |
|
130 } |
|
131 |
|
132 rv = uri->GetAsciiHost(service); |
|
133 if (NS_FAILED(rv)) |
|
134 return rv; |
|
135 } |
|
136 |
|
137 LOG((" service = %s\n", service.get())); |
|
138 |
|
139 // |
|
140 // The correct service name for IIS servers is "HTTP/f.q.d.n", so |
|
141 // construct the proper service name for passing to "gss_import_name". |
|
142 // |
|
143 // TODO: Possibly make this a configurable service name for use |
|
144 // with non-standard servers that use stuff like "khttp/f.q.d.n" |
|
145 // instead. |
|
146 // |
|
147 service.Insert("HTTP@", 0); |
|
148 |
|
149 const char *contractID; |
|
150 if (TestBoolPref(kNegotiateAuthSSPI)) { |
|
151 LOG((" using negotiate-sspi\n")); |
|
152 contractID = NS_AUTH_MODULE_CONTRACTID_PREFIX "negotiate-sspi"; |
|
153 } |
|
154 else { |
|
155 LOG((" using negotiate-gss\n")); |
|
156 contractID = NS_AUTH_MODULE_CONTRACTID_PREFIX "negotiate-gss"; |
|
157 } |
|
158 |
|
159 rv = CallCreateInstance(contractID, &module); |
|
160 |
|
161 if (NS_FAILED(rv)) { |
|
162 LOG((" Failed to load Negotiate Module \n")); |
|
163 return rv; |
|
164 } |
|
165 |
|
166 rv = module->Init(service.get(), req_flags, nullptr, nullptr, nullptr); |
|
167 |
|
168 if (NS_FAILED(rv)) { |
|
169 NS_RELEASE(module); |
|
170 return rv; |
|
171 } |
|
172 |
|
173 *continuationState = module; |
|
174 return NS_OK; |
|
175 } |
|
176 |
|
177 NS_IMPL_ISUPPORTS(nsHttpNegotiateAuth, nsIHttpAuthenticator) |
|
178 |
|
179 // |
|
180 // GenerateCredentials |
|
181 // |
|
182 // This routine is responsible for creating the correct authentication |
|
183 // blob to pass to the server that requested "Negotiate" authentication. |
|
184 // |
|
185 NS_IMETHODIMP |
|
186 nsHttpNegotiateAuth::GenerateCredentials(nsIHttpAuthenticableChannel *authChannel, |
|
187 const char *challenge, |
|
188 bool isProxyAuth, |
|
189 const char16_t *domain, |
|
190 const char16_t *username, |
|
191 const char16_t *password, |
|
192 nsISupports **sessionState, |
|
193 nsISupports **continuationState, |
|
194 uint32_t *flags, |
|
195 char **creds) |
|
196 { |
|
197 // ChallengeReceived must have been called previously. |
|
198 nsIAuthModule *module = (nsIAuthModule *) *continuationState; |
|
199 NS_ENSURE_TRUE(module, NS_ERROR_NOT_INITIALIZED); |
|
200 |
|
201 *flags = USING_INTERNAL_IDENTITY; |
|
202 |
|
203 LOG(("nsHttpNegotiateAuth::GenerateCredentials() [challenge=%s]\n", challenge)); |
|
204 |
|
205 NS_ASSERTION(creds, "null param"); |
|
206 |
|
207 #ifdef DEBUG |
|
208 bool isGssapiAuth = |
|
209 !PL_strncasecmp(challenge, kNegotiate, kNegotiateLen); |
|
210 NS_ASSERTION(isGssapiAuth, "Unexpected challenge"); |
|
211 #endif |
|
212 |
|
213 // |
|
214 // If the "Negotiate:" header had some data associated with it, |
|
215 // that data should be used as the input to this call. This may |
|
216 // be a continuation of an earlier call because GSSAPI authentication |
|
217 // often takes multiple round-trips to complete depending on the |
|
218 // context flags given. We want to use MUTUAL_AUTHENTICATION which |
|
219 // generally *does* require multiple round-trips. Don't assume |
|
220 // auth can be completed in just 1 call. |
|
221 // |
|
222 unsigned int len = strlen(challenge); |
|
223 |
|
224 void *inToken, *outToken; |
|
225 uint32_t inTokenLen, outTokenLen; |
|
226 |
|
227 if (len > kNegotiateLen) { |
|
228 challenge += kNegotiateLen; |
|
229 while (*challenge == ' ') |
|
230 challenge++; |
|
231 len = strlen(challenge); |
|
232 |
|
233 // strip off any padding (see bug 230351) |
|
234 while (challenge[len - 1] == '=') |
|
235 len--; |
|
236 |
|
237 inTokenLen = (len * 3)/4; |
|
238 inToken = malloc(inTokenLen); |
|
239 if (!inToken) |
|
240 return (NS_ERROR_OUT_OF_MEMORY); |
|
241 |
|
242 // |
|
243 // Decode the response that followed the "Negotiate" token |
|
244 // |
|
245 if (PL_Base64Decode(challenge, len, (char *) inToken) == nullptr) { |
|
246 free(inToken); |
|
247 return(NS_ERROR_UNEXPECTED); |
|
248 } |
|
249 } |
|
250 else { |
|
251 // |
|
252 // Initializing, don't use an input token. |
|
253 // |
|
254 inToken = nullptr; |
|
255 inTokenLen = 0; |
|
256 } |
|
257 |
|
258 nsresult rv = module->GetNextToken(inToken, inTokenLen, &outToken, &outTokenLen); |
|
259 |
|
260 free(inToken); |
|
261 |
|
262 if (NS_FAILED(rv)) |
|
263 return rv; |
|
264 |
|
265 if (outTokenLen == 0) { |
|
266 LOG((" No output token to send, exiting")); |
|
267 return NS_ERROR_FAILURE; |
|
268 } |
|
269 |
|
270 // |
|
271 // base64 encode the output token. |
|
272 // |
|
273 char *encoded_token = PL_Base64Encode((char *)outToken, outTokenLen, nullptr); |
|
274 |
|
275 nsMemory::Free(outToken); |
|
276 |
|
277 if (!encoded_token) |
|
278 return NS_ERROR_OUT_OF_MEMORY; |
|
279 |
|
280 LOG((" Sending a token of length %d\n", outTokenLen)); |
|
281 |
|
282 // allocate a buffer sizeof("Negotiate" + " " + b64output_token + "\0") |
|
283 *creds = (char *) nsMemory::Alloc(kNegotiateLen + 1 + strlen(encoded_token) + 1); |
|
284 if (MOZ_UNLIKELY(!*creds)) |
|
285 rv = NS_ERROR_OUT_OF_MEMORY; |
|
286 else |
|
287 sprintf(*creds, "%s %s", kNegotiate, encoded_token); |
|
288 |
|
289 PR_Free(encoded_token); |
|
290 return rv; |
|
291 } |
|
292 |
|
293 bool |
|
294 nsHttpNegotiateAuth::TestBoolPref(const char *pref) |
|
295 { |
|
296 nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); |
|
297 if (!prefs) |
|
298 return false; |
|
299 |
|
300 bool val; |
|
301 nsresult rv = prefs->GetBoolPref(pref, &val); |
|
302 if (NS_FAILED(rv)) |
|
303 return false; |
|
304 |
|
305 return val; |
|
306 } |
|
307 |
|
308 bool |
|
309 nsHttpNegotiateAuth::TestNonFqdn(nsIURI *uri) |
|
310 { |
|
311 nsAutoCString host; |
|
312 PRNetAddr addr; |
|
313 |
|
314 if (!TestBoolPref(kNegotiateAuthAllowNonFqdn)) |
|
315 return false; |
|
316 |
|
317 if (NS_FAILED(uri->GetAsciiHost(host))) |
|
318 return false; |
|
319 |
|
320 // return true if host does not contain a dot and is not an ip address |
|
321 return !host.IsEmpty() && host.FindChar('.') == kNotFound && |
|
322 PR_StringToNetAddr(host.BeginReading(), &addr) != PR_SUCCESS; |
|
323 } |
|
324 |
|
325 bool |
|
326 nsHttpNegotiateAuth::TestPref(nsIURI *uri, const char *pref) |
|
327 { |
|
328 nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); |
|
329 if (!prefs) |
|
330 return false; |
|
331 |
|
332 nsAutoCString scheme, host; |
|
333 int32_t port; |
|
334 |
|
335 if (NS_FAILED(uri->GetScheme(scheme))) |
|
336 return false; |
|
337 if (NS_FAILED(uri->GetAsciiHost(host))) |
|
338 return false; |
|
339 if (NS_FAILED(uri->GetPort(&port))) |
|
340 return false; |
|
341 |
|
342 char *hostList; |
|
343 if (NS_FAILED(prefs->GetCharPref(pref, &hostList)) || !hostList) |
|
344 return false; |
|
345 |
|
346 // pseudo-BNF |
|
347 // ---------- |
|
348 // |
|
349 // url-list base-url ( base-url "," LWS )* |
|
350 // base-url ( scheme-part | host-part | scheme-part host-part ) |
|
351 // scheme-part scheme "://" |
|
352 // host-part host [":" port] |
|
353 // |
|
354 // for example: |
|
355 // "https://, http://office.foo.com" |
|
356 // |
|
357 |
|
358 char *start = hostList, *end; |
|
359 for (;;) { |
|
360 // skip past any whitespace |
|
361 while (*start == ' ' || *start == '\t') |
|
362 ++start; |
|
363 end = strchr(start, ','); |
|
364 if (!end) |
|
365 end = start + strlen(start); |
|
366 if (start == end) |
|
367 break; |
|
368 if (MatchesBaseURI(scheme, host, port, start, end)) |
|
369 return true; |
|
370 if (*end == '\0') |
|
371 break; |
|
372 start = end + 1; |
|
373 } |
|
374 |
|
375 nsMemory::Free(hostList); |
|
376 return false; |
|
377 } |
|
378 |
|
379 bool |
|
380 nsHttpNegotiateAuth::MatchesBaseURI(const nsCSubstring &matchScheme, |
|
381 const nsCSubstring &matchHost, |
|
382 int32_t matchPort, |
|
383 const char *baseStart, |
|
384 const char *baseEnd) |
|
385 { |
|
386 // check if scheme://host:port matches baseURI |
|
387 |
|
388 // parse the base URI |
|
389 const char *hostStart, *schemeEnd = strstr(baseStart, "://"); |
|
390 if (schemeEnd) { |
|
391 // the given scheme must match the parsed scheme exactly |
|
392 if (!matchScheme.Equals(Substring(baseStart, schemeEnd))) |
|
393 return false; |
|
394 hostStart = schemeEnd + 3; |
|
395 } |
|
396 else |
|
397 hostStart = baseStart; |
|
398 |
|
399 // XXX this does not work for IPv6-literals |
|
400 const char *hostEnd = strchr(hostStart, ':'); |
|
401 if (hostEnd && hostEnd < baseEnd) { |
|
402 // the given port must match the parsed port exactly |
|
403 int port = atoi(hostEnd + 1); |
|
404 if (matchPort != (int32_t) port) |
|
405 return false; |
|
406 } |
|
407 else |
|
408 hostEnd = baseEnd; |
|
409 |
|
410 |
|
411 // if we didn't parse out a host, then assume we got a match. |
|
412 if (hostStart == hostEnd) |
|
413 return true; |
|
414 |
|
415 uint32_t hostLen = hostEnd - hostStart; |
|
416 |
|
417 // matchHost must either equal host or be a subdomain of host |
|
418 if (matchHost.Length() < hostLen) |
|
419 return false; |
|
420 |
|
421 const char *end = matchHost.EndReading(); |
|
422 if (PL_strncasecmp(end - hostLen, hostStart, hostLen) == 0) { |
|
423 // if matchHost ends with host from the base URI, then make sure it is |
|
424 // either an exact match, or prefixed with a dot. we don't want |
|
425 // "foobar.com" to match "bar.com" |
|
426 if (matchHost.Length() == hostLen || |
|
427 *(end - hostLen) == '.' || |
|
428 *(end - hostLen - 1) == '.') |
|
429 return true; |
|
430 } |
|
431 |
|
432 return false; |
|
433 } |