Line data Source code
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 : // GSSAPI 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 :
17 : #include "mozilla/ArrayUtils.h"
18 : #include "mozilla/IntegerPrintfMacros.h"
19 :
20 : #include "prlink.h"
21 : #include "nsCOMPtr.h"
22 : #include "nsIPrefService.h"
23 : #include "nsIPrefBranch.h"
24 : #include "nsIServiceManager.h"
25 : #include "nsNativeCharsetUtils.h"
26 : #include "mozilla/Telemetry.h"
27 :
28 : #include "nsAuthGSSAPI.h"
29 :
30 : #ifdef XP_MACOSX
31 : #include <Kerberos/Kerberos.h>
32 : #endif
33 :
34 : #ifdef XP_MACOSX
35 : typedef KLStatus (*KLCacheHasValidTickets_type)(
36 : KLPrincipal,
37 : KLKerberosVersion,
38 : KLBoolean *,
39 : KLPrincipal *,
40 : char **);
41 : #endif
42 :
43 : #if defined(HAVE_RES_NINIT)
44 : #include <sys/types.h>
45 : #include <netinet/in.h>
46 : #include <arpa/nameser.h>
47 : #include <resolv.h>
48 : #endif
49 :
50 : using namespace mozilla;
51 :
52 : //-----------------------------------------------------------------------------
53 :
54 : // We define GSS_C_NT_HOSTBASED_SERVICE explicitly since it may be referenced
55 : // by by a different name depending on the implementation of gss but always
56 : // has the same value
57 :
58 : static gss_OID_desc gss_c_nt_hostbased_service =
59 : { 10, (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x04" };
60 :
61 : static const char kNegotiateAuthGssLib[] =
62 : "network.negotiate-auth.gsslib";
63 : static const char kNegotiateAuthNativeImp[] =
64 : "network.negotiate-auth.using-native-gsslib";
65 :
66 : static struct GSSFunction {
67 : const char *str;
68 : PRFuncPtr func;
69 : } gssFuncs[] = {
70 : { "gss_display_status", nullptr },
71 : { "gss_init_sec_context", nullptr },
72 : { "gss_indicate_mechs", nullptr },
73 : { "gss_release_oid_set", nullptr },
74 : { "gss_delete_sec_context", nullptr },
75 : { "gss_import_name", nullptr },
76 : { "gss_release_buffer", nullptr },
77 : { "gss_release_name", nullptr },
78 : { "gss_wrap", nullptr },
79 : { "gss_unwrap", nullptr }
80 : };
81 :
82 : static bool gssNativeImp = true;
83 : static PRLibrary* gssLibrary = nullptr;
84 :
85 : #define gss_display_status_ptr ((gss_display_status_type)*gssFuncs[0].func)
86 : #define gss_init_sec_context_ptr ((gss_init_sec_context_type)*gssFuncs[1].func)
87 : #define gss_indicate_mechs_ptr ((gss_indicate_mechs_type)*gssFuncs[2].func)
88 : #define gss_release_oid_set_ptr ((gss_release_oid_set_type)*gssFuncs[3].func)
89 : #define gss_delete_sec_context_ptr ((gss_delete_sec_context_type)*gssFuncs[4].func)
90 : #define gss_import_name_ptr ((gss_import_name_type)*gssFuncs[5].func)
91 : #define gss_release_buffer_ptr ((gss_release_buffer_type)*gssFuncs[6].func)
92 : #define gss_release_name_ptr ((gss_release_name_type)*gssFuncs[7].func)
93 : #define gss_wrap_ptr ((gss_wrap_type)*gssFuncs[8].func)
94 : #define gss_unwrap_ptr ((gss_unwrap_type)*gssFuncs[9].func)
95 :
96 : #ifdef XP_MACOSX
97 : static PRFuncPtr KLCacheHasValidTicketsPtr;
98 : #define KLCacheHasValidTickets_ptr \
99 : ((KLCacheHasValidTickets_type)*KLCacheHasValidTicketsPtr)
100 : #endif
101 :
102 : static nsresult
103 0 : gssInit()
104 : {
105 0 : nsXPIDLCString libPath;
106 0 : nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
107 0 : if (prefs) {
108 0 : prefs->GetCharPref(kNegotiateAuthGssLib, getter_Copies(libPath));
109 0 : prefs->GetBoolPref(kNegotiateAuthNativeImp, &gssNativeImp);
110 : }
111 :
112 0 : PRLibrary *lib = nullptr;
113 :
114 0 : if (!libPath.IsEmpty()) {
115 0 : LOG(("Attempting to load user specified library [%s]\n", libPath.get()));
116 0 : gssNativeImp = false;
117 0 : lib = PR_LoadLibrary(libPath.get());
118 : }
119 : else {
120 : #ifdef XP_WIN
121 : char *libName = PR_GetLibraryName(nullptr, "gssapi32");
122 : if (libName) {
123 : lib = PR_LoadLibrary("gssapi32");
124 : PR_FreeLibraryName(libName);
125 : }
126 : #elif defined(__OpenBSD__)
127 : /* OpenBSD doesn't register inter-library dependencies in basesystem
128 : * libs therefor we need to load all the libraries gssapi depends on,
129 : * in the correct order and with LD_GLOBAL for GSSAPI auth to work
130 : * fine.
131 : */
132 :
133 : const char *const verLibNames[] = {
134 : "libasn1.so",
135 : "libcrypto.so",
136 : "libroken.so",
137 : "libheimbase.so",
138 : "libcom_err.so",
139 : "libkrb5.so",
140 : "libgssapi.so"
141 : };
142 :
143 : PRLibSpec libSpec;
144 : for (size_t i = 0; i < ArrayLength(verLibNames); ++i) {
145 : libSpec.type = PR_LibSpec_Pathname;
146 : libSpec.value.pathname = verLibNames[i];
147 : lib = PR_LoadLibraryWithFlags(libSpec, PR_LD_GLOBAL);
148 : }
149 :
150 : #else
151 :
152 : const char *const libNames[] = {
153 : "gss",
154 : "gssapi_krb5",
155 : "gssapi"
156 0 : };
157 :
158 : const char *const verLibNames[] = {
159 : "libgssapi_krb5.so.2", /* MIT - FC, Suse10, Debian */
160 : "libgssapi.so.4", /* Heimdal - Suse10, MDK */
161 : "libgssapi.so.1" /* Heimdal - Suse9, CITI - FC, MDK, Suse10*/
162 0 : };
163 :
164 0 : for (size_t i = 0; i < ArrayLength(verLibNames) && !lib; ++i) {
165 0 : lib = PR_LoadLibrary(verLibNames[i]);
166 :
167 : /* The CITI libgssapi library calls exit() during
168 : * initialization if it's not correctly configured. Try to
169 : * ensure that we never use this library for our GSSAPI
170 : * support, as its just a wrapper library, anyway.
171 : * See Bugzilla #325433
172 : */
173 0 : if (lib &&
174 0 : PR_FindFunctionSymbol(lib,
175 0 : "internal_krb5_gss_initialize") &&
176 0 : PR_FindFunctionSymbol(lib, "gssd_pname_to_uid")) {
177 0 : LOG(("CITI libgssapi found, which calls exit(). Skipping\n"));
178 0 : PR_UnloadLibrary(lib);
179 0 : lib = nullptr;
180 : }
181 : }
182 :
183 0 : for (size_t i = 0; i < ArrayLength(libNames) && !lib; ++i) {
184 0 : char *libName = PR_GetLibraryName(nullptr, libNames[i]);
185 0 : if (libName) {
186 0 : lib = PR_LoadLibrary(libName);
187 0 : PR_FreeLibraryName(libName);
188 :
189 0 : if (lib &&
190 0 : PR_FindFunctionSymbol(lib,
191 0 : "internal_krb5_gss_initialize") &&
192 0 : PR_FindFunctionSymbol(lib, "gssd_pname_to_uid")) {
193 0 : LOG(("CITI libgssapi found, which calls exit(). Skipping\n"));
194 0 : PR_UnloadLibrary(lib);
195 0 : lib = nullptr;
196 : }
197 : }
198 : }
199 : #endif
200 : }
201 :
202 0 : if (!lib) {
203 0 : LOG(("Fail to load gssapi library\n"));
204 0 : return NS_ERROR_FAILURE;
205 : }
206 :
207 0 : LOG(("Attempting to load gss functions\n"));
208 :
209 0 : for (size_t i = 0; i < ArrayLength(gssFuncs); ++i) {
210 0 : gssFuncs[i].func = PR_FindFunctionSymbol(lib, gssFuncs[i].str);
211 0 : if (!gssFuncs[i].func) {
212 0 : LOG(("Fail to load %s function from gssapi library\n", gssFuncs[i].str));
213 0 : PR_UnloadLibrary(lib);
214 0 : return NS_ERROR_FAILURE;
215 : }
216 : }
217 : #ifdef XP_MACOSX
218 : if (gssNativeImp &&
219 : !(KLCacheHasValidTicketsPtr =
220 : PR_FindFunctionSymbol(lib, "KLCacheHasValidTickets"))) {
221 : LOG(("Fail to load KLCacheHasValidTickets function from gssapi library\n"));
222 : PR_UnloadLibrary(lib);
223 : return NS_ERROR_FAILURE;
224 : }
225 : #endif
226 :
227 0 : gssLibrary = lib;
228 0 : return NS_OK;
229 : }
230 :
231 : // Generate proper GSSAPI error messages from the major and
232 : // minor status codes.
233 : void
234 0 : LogGssError(OM_uint32 maj_stat, OM_uint32 min_stat, const char *prefix)
235 : {
236 0 : if (!MOZ_LOG_TEST(gNegotiateLog, LogLevel::Debug)) {
237 0 : return;
238 : }
239 :
240 : OM_uint32 new_stat;
241 0 : OM_uint32 msg_ctx = 0;
242 : gss_buffer_desc status1_string;
243 : gss_buffer_desc status2_string;
244 : OM_uint32 ret;
245 0 : nsAutoCString errorStr;
246 0 : errorStr.Assign(prefix);
247 :
248 0 : if (!gssLibrary)
249 0 : return;
250 :
251 0 : errorStr += ": ";
252 0 : do {
253 0 : ret = gss_display_status_ptr(&new_stat,
254 : maj_stat,
255 : GSS_C_GSS_CODE,
256 : GSS_C_NULL_OID,
257 : &msg_ctx,
258 0 : &status1_string);
259 0 : errorStr.Append((const char *) status1_string.value, status1_string.length);
260 0 : gss_release_buffer_ptr(&new_stat, &status1_string);
261 :
262 0 : errorStr += '\n';
263 0 : ret = gss_display_status_ptr(&new_stat,
264 : min_stat,
265 : GSS_C_MECH_CODE,
266 : GSS_C_NULL_OID,
267 : &msg_ctx,
268 0 : &status2_string);
269 0 : errorStr.Append((const char *) status2_string.value, status2_string.length);
270 0 : errorStr += '\n';
271 0 : } while (!GSS_ERROR(ret) && msg_ctx != 0);
272 :
273 0 : LOG(("%s\n", errorStr.get()));
274 : }
275 :
276 : //-----------------------------------------------------------------------------
277 :
278 0 : nsAuthGSSAPI::nsAuthGSSAPI(pType package)
279 0 : : mServiceFlags(REQ_DEFAULT)
280 : {
281 : OM_uint32 minstat;
282 : OM_uint32 majstat;
283 : gss_OID_set mech_set;
284 : gss_OID item;
285 :
286 : unsigned int i;
287 : static gss_OID_desc gss_krb5_mech_oid_desc =
288 : { 9, (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" };
289 : static gss_OID_desc gss_spnego_mech_oid_desc =
290 : { 6, (void *) "\x2b\x06\x01\x05\x05\x02" };
291 :
292 0 : LOG(("entering nsAuthGSSAPI::nsAuthGSSAPI()\n"));
293 :
294 0 : mComplete = false;
295 :
296 0 : if (!gssLibrary && NS_FAILED(gssInit()))
297 0 : return;
298 :
299 0 : mCtx = GSS_C_NO_CONTEXT;
300 0 : mMechOID = &gss_krb5_mech_oid_desc;
301 :
302 : // if the type is kerberos we accept it as default
303 : // and exit
304 :
305 0 : if (package == PACKAGE_TYPE_KERBEROS)
306 0 : return;
307 :
308 : // Now, look at the list of supported mechanisms,
309 : // if SPNEGO is found, then use it.
310 : // Otherwise, set the desired mechanism to
311 : // GSS_C_NO_OID and let the system try to use
312 : // the default mechanism.
313 : //
314 : // Using Kerberos directly (instead of negotiating
315 : // with SPNEGO) may work in some cases depending
316 : // on how smart the server side is.
317 :
318 0 : majstat = gss_indicate_mechs_ptr(&minstat, &mech_set);
319 0 : if (GSS_ERROR(majstat))
320 0 : return;
321 :
322 0 : if (mech_set) {
323 0 : for (i=0; i<mech_set->count; i++) {
324 0 : item = &mech_set->elements[i];
325 0 : if (item->length == gss_spnego_mech_oid_desc.length &&
326 0 : !memcmp(item->elements, gss_spnego_mech_oid_desc.elements,
327 0 : item->length)) {
328 : // ok, we found it
329 0 : mMechOID = &gss_spnego_mech_oid_desc;
330 0 : break;
331 : }
332 : }
333 0 : gss_release_oid_set_ptr(&minstat, &mech_set);
334 : }
335 : }
336 :
337 : void
338 0 : nsAuthGSSAPI::Reset()
339 : {
340 0 : if (gssLibrary && mCtx != GSS_C_NO_CONTEXT) {
341 : OM_uint32 minor_status;
342 0 : gss_delete_sec_context_ptr(&minor_status, &mCtx, GSS_C_NO_BUFFER);
343 : }
344 0 : mCtx = GSS_C_NO_CONTEXT;
345 0 : mComplete = false;
346 0 : }
347 :
348 : /* static */ void
349 0 : nsAuthGSSAPI::Shutdown()
350 : {
351 0 : if (gssLibrary) {
352 0 : PR_UnloadLibrary(gssLibrary);
353 0 : gssLibrary = nullptr;
354 : }
355 0 : }
356 :
357 : /* Limitations apply to this class's thread safety. See the header file */
358 0 : NS_IMPL_ISUPPORTS(nsAuthGSSAPI, nsIAuthModule)
359 :
360 : NS_IMETHODIMP
361 0 : nsAuthGSSAPI::Init(const char *serviceName,
362 : uint32_t serviceFlags,
363 : const char16_t *domain,
364 : const char16_t *username,
365 : const char16_t *password)
366 : {
367 : // we don't expect to be passed any user credentials
368 0 : NS_ASSERTION(!domain && !username && !password, "unexpected credentials");
369 :
370 : // it's critial that the caller supply a service name to be used
371 0 : NS_ENSURE_TRUE(serviceName && *serviceName, NS_ERROR_INVALID_ARG);
372 :
373 0 : LOG(("entering nsAuthGSSAPI::Init()\n"));
374 :
375 0 : if (!gssLibrary)
376 0 : return NS_ERROR_NOT_INITIALIZED;
377 :
378 0 : mServiceName = serviceName;
379 0 : mServiceFlags = serviceFlags;
380 :
381 : static bool sTelemetrySent = false;
382 0 : if (!sTelemetrySent) {
383 0 : mozilla::Telemetry::Accumulate(
384 : mozilla::Telemetry::NTLM_MODULE_USED_2,
385 0 : serviceFlags & nsIAuthModule::REQ_PROXY_AUTH
386 : ? NTLM_MODULE_KERBEROS_PROXY
387 0 : : NTLM_MODULE_KERBEROS_DIRECT);
388 0 : sTelemetrySent = true;
389 : }
390 :
391 0 : return NS_OK;
392 : }
393 :
394 : NS_IMETHODIMP
395 0 : nsAuthGSSAPI::GetNextToken(const void *inToken,
396 : uint32_t inTokenLen,
397 : void **outToken,
398 : uint32_t *outTokenLen)
399 : {
400 : OM_uint32 major_status, minor_status;
401 0 : OM_uint32 req_flags = 0;
402 0 : gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
403 0 : gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
404 0 : gss_buffer_t in_token_ptr = GSS_C_NO_BUFFER;
405 : gss_name_t server;
406 0 : nsAutoCString userbuf;
407 : nsresult rv;
408 :
409 0 : LOG(("entering nsAuthGSSAPI::GetNextToken()\n"));
410 :
411 0 : if (!gssLibrary)
412 0 : return NS_ERROR_NOT_INITIALIZED;
413 :
414 : // If they've called us again after we're complete, reset to start afresh.
415 0 : if (mComplete)
416 0 : Reset();
417 :
418 0 : if (mServiceFlags & REQ_DELEGATE)
419 0 : req_flags |= GSS_C_DELEG_FLAG;
420 :
421 0 : if (mServiceFlags & REQ_MUTUAL_AUTH)
422 0 : req_flags |= GSS_C_MUTUAL_FLAG;
423 :
424 0 : input_token.value = (void *)mServiceName.get();
425 0 : input_token.length = mServiceName.Length() + 1;
426 :
427 : #if defined(HAVE_RES_NINIT)
428 0 : res_ninit(&_res);
429 : #endif
430 0 : major_status = gss_import_name_ptr(&minor_status,
431 : &input_token,
432 : &gss_c_nt_hostbased_service,
433 0 : &server);
434 0 : input_token.value = nullptr;
435 0 : input_token.length = 0;
436 0 : if (GSS_ERROR(major_status)) {
437 0 : LogGssError(major_status, minor_status, "gss_import_name() failed");
438 0 : return NS_ERROR_FAILURE;
439 : }
440 :
441 0 : if (inToken) {
442 0 : input_token.length = inTokenLen;
443 0 : input_token.value = (void *) inToken;
444 0 : in_token_ptr = &input_token;
445 : }
446 0 : else if (mCtx != GSS_C_NO_CONTEXT) {
447 : // If there is no input token, then we are starting a new
448 : // authentication sequence. If we have already initialized our
449 : // security context, then we're in trouble because it means that the
450 : // first sequence failed. We need to bail or else we might end up in
451 : // an infinite loop.
452 0 : LOG(("Cannot restart authentication sequence!"));
453 0 : return NS_ERROR_UNEXPECTED;
454 : }
455 :
456 : #if defined(XP_MACOSX)
457 : // Suppress Kerberos prompts to get credentials. See bug 240643.
458 : // We can only use Mac OS X specific kerb functions if we are using
459 : // the native lib
460 : KLBoolean found;
461 : bool doingMailTask = mServiceName.Find("imap@") ||
462 : mServiceName.Find("pop@") ||
463 : mServiceName.Find("smtp@") ||
464 : mServiceName.Find("ldap@");
465 :
466 : if (!doingMailTask && (gssNativeImp &&
467 : (KLCacheHasValidTickets_ptr(nullptr, kerberosVersion_V5, &found, nullptr, nullptr) != klNoErr || !found)))
468 : {
469 : major_status = GSS_S_FAILURE;
470 : minor_status = 0;
471 : }
472 : else
473 : #endif /* XP_MACOSX */
474 0 : major_status = gss_init_sec_context_ptr(&minor_status,
475 : GSS_C_NO_CREDENTIAL,
476 : &mCtx,
477 : server,
478 : mMechOID,
479 : req_flags,
480 : GSS_C_INDEFINITE,
481 : GSS_C_NO_CHANNEL_BINDINGS,
482 : in_token_ptr,
483 : nullptr,
484 : &output_token,
485 : nullptr,
486 0 : nullptr);
487 :
488 0 : if (GSS_ERROR(major_status)) {
489 0 : LogGssError(major_status, minor_status, "gss_init_sec_context() failed");
490 0 : Reset();
491 0 : rv = NS_ERROR_FAILURE;
492 0 : goto end;
493 : }
494 0 : if (major_status == GSS_S_COMPLETE) {
495 : // Mark ourselves as being complete, so that if we're called again
496 : // we know to start afresh.
497 0 : mComplete = true;
498 : }
499 : else if (major_status == GSS_S_CONTINUE_NEEDED) {
500 : //
501 : // The important thing is that we do NOT reset the
502 : // context here because it will be needed on the
503 : // next call.
504 : //
505 : }
506 :
507 0 : *outTokenLen = output_token.length;
508 0 : if (output_token.length != 0)
509 0 : *outToken = nsMemory::Clone(output_token.value, output_token.length);
510 : else
511 0 : *outToken = nullptr;
512 :
513 0 : gss_release_buffer_ptr(&minor_status, &output_token);
514 :
515 0 : if (major_status == GSS_S_COMPLETE)
516 0 : rv = NS_SUCCESS_AUTH_FINISHED;
517 : else
518 0 : rv = NS_OK;
519 :
520 : end:
521 0 : gss_release_name_ptr(&minor_status, &server);
522 :
523 0 : LOG((" leaving nsAuthGSSAPI::GetNextToken [rv=%" PRIx32 "]",
524 : static_cast<uint32_t>(rv)));
525 0 : return rv;
526 : }
527 :
528 : NS_IMETHODIMP
529 0 : nsAuthGSSAPI::Unwrap(const void *inToken,
530 : uint32_t inTokenLen,
531 : void **outToken,
532 : uint32_t *outTokenLen)
533 : {
534 : OM_uint32 major_status, minor_status;
535 :
536 : gss_buffer_desc input_token;
537 0 : gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
538 :
539 0 : input_token.value = (void *) inToken;
540 0 : input_token.length = inTokenLen;
541 :
542 0 : major_status = gss_unwrap_ptr(&minor_status,
543 : mCtx,
544 : &input_token,
545 : &output_token,
546 : nullptr,
547 0 : nullptr);
548 0 : if (GSS_ERROR(major_status)) {
549 0 : LogGssError(major_status, minor_status, "gss_unwrap() failed");
550 0 : Reset();
551 0 : gss_release_buffer_ptr(&minor_status, &output_token);
552 0 : return NS_ERROR_FAILURE;
553 : }
554 :
555 0 : *outTokenLen = output_token.length;
556 :
557 0 : if (output_token.length)
558 0 : *outToken = nsMemory::Clone(output_token.value, output_token.length);
559 : else
560 0 : *outToken = nullptr;
561 :
562 0 : gss_release_buffer_ptr(&minor_status, &output_token);
563 :
564 0 : return NS_OK;
565 : }
566 :
567 : NS_IMETHODIMP
568 0 : nsAuthGSSAPI::Wrap(const void *inToken,
569 : uint32_t inTokenLen,
570 : bool confidential,
571 : void **outToken,
572 : uint32_t *outTokenLen)
573 : {
574 : OM_uint32 major_status, minor_status;
575 :
576 : gss_buffer_desc input_token;
577 0 : gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
578 :
579 0 : input_token.value = (void *) inToken;
580 0 : input_token.length = inTokenLen;
581 :
582 0 : major_status = gss_wrap_ptr(&minor_status,
583 : mCtx,
584 : confidential,
585 : GSS_C_QOP_DEFAULT,
586 : &input_token,
587 : nullptr,
588 0 : &output_token);
589 :
590 0 : if (GSS_ERROR(major_status)) {
591 0 : LogGssError(major_status, minor_status, "gss_wrap() failed");
592 0 : Reset();
593 0 : gss_release_buffer_ptr(&minor_status, &output_token);
594 0 : return NS_ERROR_FAILURE;
595 : }
596 :
597 0 : *outTokenLen = output_token.length;
598 :
599 : /* it is not possible for output_token.length to be zero */
600 0 : *outToken = nsMemory::Clone(output_token.value, output_token.length);
601 0 : gss_release_buffer_ptr(&minor_status, &output_token);
602 :
603 0 : return NS_OK;
604 : }
605 :
|