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/. */
7 // GSSAPI Authentication Support Module
9 // Described by IETF Internet draft: draft-brezak-kerberos-http-00.txt
10 // (formerly draft-brezak-spnego-http-04.txt)
12 // Also described here:
13 // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnsecure/html/http-sso-1.asp
17 #include "mozilla/ArrayUtils.h"
21 #include "nsIPrefService.h"
22 #include "nsIPrefBranch.h"
23 #include "nsIServiceManager.h"
24 #include "nsNativeCharsetUtils.h"
25 #include "mozilla/Telemetry.h"
27 #include "nsAuthGSSAPI.h"
30 #include <Kerberos/Kerberos.h>
34 typedef KLStatus (*KLCacheHasValidTickets_type
)(
42 #if defined(HAVE_RES_NINIT)
43 #include <sys/types.h>
44 #include <netinet/in.h>
45 #include <arpa/nameser.h>
49 using namespace mozilla
;
51 //-----------------------------------------------------------------------------
53 // We define GSS_C_NT_HOSTBASED_SERVICE explicitly since it may be referenced
54 // by by a different name depending on the implementation of gss but always
57 static gss_OID_desc gss_c_nt_hostbased_service
=
58 { 10, (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x04" };
60 static const char kNegotiateAuthGssLib
[] =
61 "network.negotiate-auth.gsslib";
62 static const char kNegotiateAuthNativeImp
[] =
63 "network.negotiate-auth.using-native-gsslib";
65 static struct GSSFunction
{
69 { "gss_display_status", nullptr },
70 { "gss_init_sec_context", nullptr },
71 { "gss_indicate_mechs", nullptr },
72 { "gss_release_oid_set", nullptr },
73 { "gss_delete_sec_context", nullptr },
74 { "gss_import_name", nullptr },
75 { "gss_release_buffer", nullptr },
76 { "gss_release_name", nullptr },
77 { "gss_wrap", nullptr },
78 { "gss_unwrap", nullptr }
81 static bool gssNativeImp
= true;
82 static PRLibrary
* gssLibrary
= nullptr;
84 #define gss_display_status_ptr ((gss_display_status_type)*gssFuncs[0].func)
85 #define gss_init_sec_context_ptr ((gss_init_sec_context_type)*gssFuncs[1].func)
86 #define gss_indicate_mechs_ptr ((gss_indicate_mechs_type)*gssFuncs[2].func)
87 #define gss_release_oid_set_ptr ((gss_release_oid_set_type)*gssFuncs[3].func)
88 #define gss_delete_sec_context_ptr ((gss_delete_sec_context_type)*gssFuncs[4].func)
89 #define gss_import_name_ptr ((gss_import_name_type)*gssFuncs[5].func)
90 #define gss_release_buffer_ptr ((gss_release_buffer_type)*gssFuncs[6].func)
91 #define gss_release_name_ptr ((gss_release_name_type)*gssFuncs[7].func)
92 #define gss_wrap_ptr ((gss_wrap_type)*gssFuncs[8].func)
93 #define gss_unwrap_ptr ((gss_unwrap_type)*gssFuncs[9].func)
96 static PRFuncPtr KLCacheHasValidTicketsPtr
;
97 #define KLCacheHasValidTickets_ptr \
98 ((KLCacheHasValidTickets_type)*KLCacheHasValidTicketsPtr)
104 nsXPIDLCString libPath
;
105 nsCOMPtr
<nsIPrefBranch
> prefs
= do_GetService(NS_PREFSERVICE_CONTRACTID
);
107 prefs
->GetCharPref(kNegotiateAuthGssLib
, getter_Copies(libPath
));
108 prefs
->GetBoolPref(kNegotiateAuthNativeImp
, &gssNativeImp
);
111 PRLibrary
*lib
= nullptr;
113 if (!libPath
.IsEmpty()) {
114 LOG(("Attempting to load user specified library [%s]\n", libPath
.get()));
115 gssNativeImp
= false;
116 lib
= PR_LoadLibrary(libPath
.get());
120 char *libName
= PR_GetLibraryName(nullptr, "gssapi32");
122 lib
= PR_LoadLibrary("gssapi32");
123 PR_FreeLibraryName(libName
);
125 #elif defined(__OpenBSD__)
126 /* OpenBSD doesn't register inter-library dependencies in basesystem
127 * libs therefor we need to load all the libraries gssapi depends on,
128 * in the correct order and with LD_GLOBAL for GSSAPI auth to work
132 const char *const verLibNames
[] = {
143 for (size_t i
= 0; i
< ArrayLength(verLibNames
); ++i
) {
144 libSpec
.type
= PR_LibSpec_Pathname
;
145 libSpec
.value
.pathname
= verLibNames
[i
];
146 lib
= PR_LoadLibraryWithFlags(libSpec
, PR_LD_GLOBAL
);
151 const char *const libNames
[] = {
157 const char *const verLibNames
[] = {
158 "libgssapi_krb5.so.2", /* MIT - FC, Suse10, Debian */
159 "libgssapi.so.4", /* Heimdal - Suse10, MDK */
160 "libgssapi.so.1" /* Heimdal - Suse9, CITI - FC, MDK, Suse10*/
163 for (size_t i
= 0; i
< ArrayLength(verLibNames
) && !lib
; ++i
) {
164 lib
= PR_LoadLibrary(verLibNames
[i
]);
166 /* The CITI libgssapi library calls exit() during
167 * initialization if it's not correctly configured. Try to
168 * ensure that we never use this library for our GSSAPI
169 * support, as its just a wrapper library, anyway.
170 * See Bugzilla #325433
173 PR_FindFunctionSymbol(lib
,
174 "internal_krb5_gss_initialize") &&
175 PR_FindFunctionSymbol(lib
, "gssd_pname_to_uid")) {
176 LOG(("CITI libgssapi found, which calls exit(). Skipping\n"));
177 PR_UnloadLibrary(lib
);
182 for (size_t i
= 0; i
< ArrayLength(libNames
) && !lib
; ++i
) {
183 char *libName
= PR_GetLibraryName(nullptr, libNames
[i
]);
185 lib
= PR_LoadLibrary(libName
);
186 PR_FreeLibraryName(libName
);
189 PR_FindFunctionSymbol(lib
,
190 "internal_krb5_gss_initialize") &&
191 PR_FindFunctionSymbol(lib
, "gssd_pname_to_uid")) {
192 LOG(("CITI libgssapi found, which calls exit(). Skipping\n"));
193 PR_UnloadLibrary(lib
);
202 LOG(("Fail to load gssapi library\n"));
203 return NS_ERROR_FAILURE
;
206 LOG(("Attempting to load gss functions\n"));
208 for (size_t i
= 0; i
< ArrayLength(gssFuncs
); ++i
) {
209 gssFuncs
[i
].func
= PR_FindFunctionSymbol(lib
, gssFuncs
[i
].str
);
210 if (!gssFuncs
[i
].func
) {
211 LOG(("Fail to load %s function from gssapi library\n", gssFuncs
[i
].str
));
212 PR_UnloadLibrary(lib
);
213 return NS_ERROR_FAILURE
;
218 !(KLCacheHasValidTicketsPtr
=
219 PR_FindFunctionSymbol(lib
, "KLCacheHasValidTickets"))) {
220 LOG(("Fail to load KLCacheHasValidTickets function from gssapi library\n"));
221 PR_UnloadLibrary(lib
);
222 return NS_ERROR_FAILURE
;
230 #if defined( PR_LOGGING )
232 // Generate proper GSSAPI error messages from the major and
233 // minor status codes.
235 LogGssError(OM_uint32 maj_stat
, OM_uint32 min_stat
, const char *prefix
)
238 OM_uint32 msg_ctx
= 0;
239 gss_buffer_desc status1_string
;
240 gss_buffer_desc status2_string
;
242 nsAutoCString errorStr
;
243 errorStr
.Assign(prefix
);
250 ret
= gss_display_status_ptr(&new_stat
,
256 errorStr
.Append((const char *) status1_string
.value
, status1_string
.length
);
257 gss_release_buffer_ptr(&new_stat
, &status1_string
);
260 ret
= gss_display_status_ptr(&new_stat
,
266 errorStr
.Append((const char *) status2_string
.value
, status2_string
.length
);
268 } while (!GSS_ERROR(ret
) && msg_ctx
!= 0);
270 LOG(("%s\n", errorStr
.get()));
273 #else /* PR_LOGGING */
275 #define LogGssError(x,y,z)
277 #endif /* PR_LOGGING */
279 //-----------------------------------------------------------------------------
281 nsAuthGSSAPI::nsAuthGSSAPI(pType package
)
282 : mServiceFlags(REQ_DEFAULT
)
286 gss_OID_set mech_set
;
290 static gss_OID_desc gss_krb5_mech_oid_desc
=
291 { 9, (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" };
292 static gss_OID_desc gss_spnego_mech_oid_desc
=
293 { 6, (void *) "\x2b\x06\x01\x05\x05\x02" };
295 LOG(("entering nsAuthGSSAPI::nsAuthGSSAPI()\n"));
299 if (!gssLibrary
&& NS_FAILED(gssInit()))
302 mCtx
= GSS_C_NO_CONTEXT
;
303 mMechOID
= &gss_krb5_mech_oid_desc
;
305 // if the type is kerberos we accept it as default
308 if (package
== PACKAGE_TYPE_KERBEROS
)
311 // Now, look at the list of supported mechanisms,
312 // if SPNEGO is found, then use it.
313 // Otherwise, set the desired mechanism to
314 // GSS_C_NO_OID and let the system try to use
315 // the default mechanism.
317 // Using Kerberos directly (instead of negotiating
318 // with SPNEGO) may work in some cases depending
319 // on how smart the server side is.
321 majstat
= gss_indicate_mechs_ptr(&minstat
, &mech_set
);
322 if (GSS_ERROR(majstat
))
326 for (i
=0; i
<mech_set
->count
; i
++) {
327 item
= &mech_set
->elements
[i
];
328 if (item
->length
== gss_spnego_mech_oid_desc
.length
&&
329 !memcmp(item
->elements
, gss_spnego_mech_oid_desc
.elements
,
332 mMechOID
= &gss_spnego_mech_oid_desc
;
336 gss_release_oid_set_ptr(&minstat
, &mech_set
);
341 nsAuthGSSAPI::Reset()
343 if (gssLibrary
&& mCtx
!= GSS_C_NO_CONTEXT
) {
344 OM_uint32 minor_status
;
345 gss_delete_sec_context_ptr(&minor_status
, &mCtx
, GSS_C_NO_BUFFER
);
347 mCtx
= GSS_C_NO_CONTEXT
;
352 nsAuthGSSAPI::Shutdown()
355 PR_UnloadLibrary(gssLibrary
);
356 gssLibrary
= nullptr;
360 /* Limitations apply to this class's thread safety. See the header file */
361 NS_IMPL_ISUPPORTS(nsAuthGSSAPI
, nsIAuthModule
)
364 nsAuthGSSAPI::Init(const char *serviceName
,
365 uint32_t serviceFlags
,
366 const char16_t
*domain
,
367 const char16_t
*username
,
368 const char16_t
*password
)
370 // we don't expect to be passed any user credentials
371 NS_ASSERTION(!domain
&& !username
&& !password
, "unexpected credentials");
373 // it's critial that the caller supply a service name to be used
374 NS_ENSURE_TRUE(serviceName
&& *serviceName
, NS_ERROR_INVALID_ARG
);
376 LOG(("entering nsAuthGSSAPI::Init()\n"));
379 return NS_ERROR_NOT_INITIALIZED
;
381 mServiceName
= serviceName
;
382 mServiceFlags
= serviceFlags
;
384 static bool sTelemetrySent
= false;
385 if (!sTelemetrySent
) {
386 mozilla::Telemetry::Accumulate(
387 mozilla::Telemetry::NTLM_MODULE_USED_2
,
388 serviceFlags
& nsIAuthModule::REQ_PROXY_AUTH
389 ? NTLM_MODULE_KERBEROS_PROXY
390 : NTLM_MODULE_KERBEROS_DIRECT
);
391 sTelemetrySent
= true;
398 nsAuthGSSAPI::GetNextToken(const void *inToken
,
401 uint32_t *outTokenLen
)
403 OM_uint32 major_status
, minor_status
;
404 OM_uint32 req_flags
= 0;
405 gss_buffer_desc input_token
= GSS_C_EMPTY_BUFFER
;
406 gss_buffer_desc output_token
= GSS_C_EMPTY_BUFFER
;
407 gss_buffer_t in_token_ptr
= GSS_C_NO_BUFFER
;
409 nsAutoCString userbuf
;
412 LOG(("entering nsAuthGSSAPI::GetNextToken()\n"));
415 return NS_ERROR_NOT_INITIALIZED
;
417 // If they've called us again after we're complete, reset to start afresh.
421 if (mServiceFlags
& REQ_DELEGATE
)
422 req_flags
|= GSS_C_DELEG_FLAG
;
424 if (mServiceFlags
& REQ_MUTUAL_AUTH
)
425 req_flags
|= GSS_C_MUTUAL_FLAG
;
427 input_token
.value
= (void *)mServiceName
.get();
428 input_token
.length
= mServiceName
.Length() + 1;
430 #if defined(HAVE_RES_NINIT)
433 major_status
= gss_import_name_ptr(&minor_status
,
435 &gss_c_nt_hostbased_service
,
437 input_token
.value
= nullptr;
438 input_token
.length
= 0;
439 if (GSS_ERROR(major_status
)) {
440 LogGssError(major_status
, minor_status
, "gss_import_name() failed");
441 return NS_ERROR_FAILURE
;
445 input_token
.length
= inTokenLen
;
446 input_token
.value
= (void *) inToken
;
447 in_token_ptr
= &input_token
;
449 else if (mCtx
!= GSS_C_NO_CONTEXT
) {
450 // If there is no input token, then we are starting a new
451 // authentication sequence. If we have already initialized our
452 // security context, then we're in trouble because it means that the
453 // first sequence failed. We need to bail or else we might end up in
455 LOG(("Cannot restart authentication sequence!"));
456 return NS_ERROR_UNEXPECTED
;
459 #if defined(XP_MACOSX)
460 // Suppress Kerberos prompts to get credentials. See bug 240643.
461 // We can only use Mac OS X specific kerb functions if we are using
464 bool doingMailTask
= mServiceName
.Find("imap@") ||
465 mServiceName
.Find("pop@") ||
466 mServiceName
.Find("smtp@") ||
467 mServiceName
.Find("ldap@");
469 if (!doingMailTask
&& (gssNativeImp
&&
470 (KLCacheHasValidTickets_ptr(nullptr, kerberosVersion_V5
, &found
, nullptr, nullptr) != klNoErr
|| !found
)))
472 major_status
= GSS_S_FAILURE
;
476 #endif /* XP_MACOSX */
477 major_status
= gss_init_sec_context_ptr(&minor_status
,
484 GSS_C_NO_CHANNEL_BINDINGS
,
491 if (GSS_ERROR(major_status
)) {
492 LogGssError(major_status
, minor_status
, "gss_init_sec_context() failed");
494 rv
= NS_ERROR_FAILURE
;
497 if (major_status
== GSS_S_COMPLETE
) {
498 // Mark ourselves as being complete, so that if we're called again
499 // we know to start afresh.
502 else if (major_status
== GSS_S_CONTINUE_NEEDED
) {
504 // The important thing is that we do NOT reset the
505 // context here because it will be needed on the
510 *outTokenLen
= output_token
.length
;
511 if (output_token
.length
!= 0)
512 *outToken
= nsMemory::Clone(output_token
.value
, output_token
.length
);
516 gss_release_buffer_ptr(&minor_status
, &output_token
);
518 if (major_status
== GSS_S_COMPLETE
)
519 rv
= NS_SUCCESS_AUTH_FINISHED
;
524 gss_release_name_ptr(&minor_status
, &server
);
526 LOG((" leaving nsAuthGSSAPI::GetNextToken [rv=%x]", rv
));
531 nsAuthGSSAPI::Unwrap(const void *inToken
,
534 uint32_t *outTokenLen
)
536 OM_uint32 major_status
, minor_status
;
538 gss_buffer_desc input_token
;
539 gss_buffer_desc output_token
= GSS_C_EMPTY_BUFFER
;
541 input_token
.value
= (void *) inToken
;
542 input_token
.length
= inTokenLen
;
544 major_status
= gss_unwrap_ptr(&minor_status
,
550 if (GSS_ERROR(major_status
)) {
551 LogGssError(major_status
, minor_status
, "gss_unwrap() failed");
553 gss_release_buffer_ptr(&minor_status
, &output_token
);
554 return NS_ERROR_FAILURE
;
557 *outTokenLen
= output_token
.length
;
559 if (output_token
.length
)
560 *outToken
= nsMemory::Clone(output_token
.value
, output_token
.length
);
564 gss_release_buffer_ptr(&minor_status
, &output_token
);
570 nsAuthGSSAPI::Wrap(const void *inToken
,
574 uint32_t *outTokenLen
)
576 OM_uint32 major_status
, minor_status
;
578 gss_buffer_desc input_token
;
579 gss_buffer_desc output_token
= GSS_C_EMPTY_BUFFER
;
581 input_token
.value
= (void *) inToken
;
582 input_token
.length
= inTokenLen
;
584 major_status
= gss_wrap_ptr(&minor_status
,
592 if (GSS_ERROR(major_status
)) {
593 LogGssError(major_status
, minor_status
, "gss_wrap() failed");
595 gss_release_buffer_ptr(&minor_status
, &output_token
);
596 return NS_ERROR_FAILURE
;
599 *outTokenLen
= output_token
.length
;
601 /* it is not possible for output_token.length to be zero */
602 *outToken
= nsMemory::Clone(output_token
.value
, output_token
.length
);
603 gss_release_buffer_ptr(&minor_status
, &output_token
);