1 /* vim:set ts=4 sw=2 sts=2 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"
18 #include "mozilla/IntegerPrintfMacros.h"
21 #include "nsNativeCharsetUtils.h"
22 #include "mozilla/Preferences.h"
23 #include "mozilla/SharedLibrary.h"
24 #include "mozilla/Telemetry.h"
26 #include "nsAuthGSSAPI.h"
29 # include <Kerberos/Kerberos.h>
33 typedef KLStatus (*KLCacheHasValidTickets_type
)(KLPrincipal
, KLKerberosVersion
,
34 KLBoolean
*, KLPrincipal
*,
38 #if defined(HAVE_RES_NINIT)
39 # include <sys/types.h>
40 # include <netinet/in.h>
41 # include <arpa/nameser.h>
45 using namespace mozilla
;
47 //-----------------------------------------------------------------------------
49 // We define GSS_C_NT_HOSTBASED_SERVICE explicitly since it may be referenced
50 // by by a different name depending on the implementation of gss but always
53 static gss_OID_desc gss_c_nt_hostbased_service
= {
54 10, (void*)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x04"};
56 static const char kNegotiateAuthGssLib
[] = "network.negotiate-auth.gsslib";
57 static const char kNegotiateAuthNativeImp
[] =
58 "network.negotiate-auth.using-native-gsslib";
60 static struct GSSFunction
{
63 } gssFuncs
[] = {{"gss_display_status", nullptr},
64 {"gss_init_sec_context", nullptr},
65 {"gss_indicate_mechs", nullptr},
66 {"gss_release_oid_set", nullptr},
67 {"gss_delete_sec_context", nullptr},
68 {"gss_import_name", nullptr},
69 {"gss_release_buffer", nullptr},
70 {"gss_release_name", nullptr},
71 {"gss_wrap", nullptr},
72 {"gss_unwrap", nullptr}};
74 static bool gssNativeImp
= true;
75 static PRLibrary
* gssLibrary
= nullptr;
77 #define gss_display_status_ptr ((gss_display_status_type) * gssFuncs[0].func)
78 #define gss_init_sec_context_ptr \
79 ((gss_init_sec_context_type) * gssFuncs[1].func)
80 #define gss_indicate_mechs_ptr ((gss_indicate_mechs_type) * gssFuncs[2].func)
81 #define gss_release_oid_set_ptr ((gss_release_oid_set_type) * gssFuncs[3].func)
82 #define gss_delete_sec_context_ptr \
83 ((gss_delete_sec_context_type) * gssFuncs[4].func)
84 #define gss_import_name_ptr ((gss_import_name_type) * gssFuncs[5].func)
85 #define gss_release_buffer_ptr ((gss_release_buffer_type) * gssFuncs[6].func)
86 #define gss_release_name_ptr ((gss_release_name_type) * gssFuncs[7].func)
87 #define gss_wrap_ptr ((gss_wrap_type) * gssFuncs[8].func)
88 #define gss_unwrap_ptr ((gss_unwrap_type) * gssFuncs[9].func)
91 static PRFuncPtr KLCacheHasValidTicketsPtr
;
92 # define KLCacheHasValidTickets_ptr \
93 ((KLCacheHasValidTickets_type) * KLCacheHasValidTicketsPtr)
96 static nsresult
gssInit() {
98 nsAutoString libPathU
;
99 Preferences::GetString(kNegotiateAuthGssLib
, libPathU
);
100 NS_ConvertUTF16toUTF8
libPath(libPathU
);
102 nsAutoCString libPath
;
103 Preferences::GetCString(kNegotiateAuthGssLib
, libPath
);
105 gssNativeImp
= Preferences::GetBool(kNegotiateAuthNativeImp
);
107 PRLibrary
* lib
= nullptr;
109 if (!libPath
.IsEmpty()) {
110 LOG(("Attempting to load user specified library [%s]\n", libPath
.get()));
111 gssNativeImp
= false;
113 lib
= LoadLibraryWithFlags(libPathU
.get());
115 lib
= LoadLibraryWithFlags(libPath
.get());
120 constexpr auto kLibName
= u
"gssapi64.dll"_ns
;
122 constexpr auto kLibName
= u
"gssapi32.dll"_ns
;
125 lib
= LoadLibraryWithFlags(kLibName
.get());
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
133 const char* const verLibNames
[] = {
134 "libasn1.so", "libcrypto.so", "libroken.so", "libheimbase.so",
135 "libcom_err.so", "libkrb5.so", "libgssapi.so"};
138 for (size_t i
= 0; i
< ArrayLength(verLibNames
); ++i
) {
139 libSpec
.type
= PR_LibSpec_Pathname
;
140 libSpec
.value
.pathname
= verLibNames
[i
];
141 lib
= PR_LoadLibraryWithFlags(libSpec
, PR_LD_GLOBAL
);
146 const char* const libNames
[] = {"gss", "gssapi_krb5", "gssapi"};
148 const char* const verLibNames
[] = {
149 "libgssapi_krb5.so.2", /* MIT - FC, Suse10, Debian */
150 "libgssapi.so.4", /* Heimdal - Suse10, MDK */
151 "libgssapi.so.1" /* Heimdal - Suse9, CITI - FC, MDK, Suse10*/
154 for (size_t i
= 0; i
< ArrayLength(verLibNames
) && !lib
; ++i
) {
155 lib
= PR_LoadLibrary(verLibNames
[i
]);
157 /* The CITI libgssapi library calls exit() during
158 * initialization if it's not correctly configured. Try to
159 * ensure that we never use this library for our GSSAPI
160 * support, as its just a wrapper library, anyway.
161 * See Bugzilla #325433
163 if (lib
&& PR_FindFunctionSymbol(lib
, "internal_krb5_gss_initialize") &&
164 PR_FindFunctionSymbol(lib
, "gssd_pname_to_uid")) {
165 LOG(("CITI libgssapi found, which calls exit(). Skipping\n"));
166 PR_UnloadLibrary(lib
);
171 for (size_t i
= 0; i
< ArrayLength(libNames
) && !lib
; ++i
) {
172 char* libName
= PR_GetLibraryName(nullptr, libNames
[i
]);
174 lib
= PR_LoadLibrary(libName
);
175 PR_FreeLibraryName(libName
);
177 if (lib
&& PR_FindFunctionSymbol(lib
, "internal_krb5_gss_initialize") &&
178 PR_FindFunctionSymbol(lib
, "gssd_pname_to_uid")) {
179 LOG(("CITI libgssapi found, which calls exit(). Skipping\n"));
180 PR_UnloadLibrary(lib
);
189 LOG(("Fail to load gssapi library\n"));
190 return NS_ERROR_FAILURE
;
193 LOG(("Attempting to load gss functions\n"));
195 for (auto& gssFunc
: gssFuncs
) {
196 gssFunc
.func
= PR_FindFunctionSymbol(lib
, gssFunc
.str
);
198 LOG(("Fail to load %s function from gssapi library\n", gssFunc
.str
));
199 PR_UnloadLibrary(lib
);
200 return NS_ERROR_FAILURE
;
204 if (gssNativeImp
&& !(KLCacheHasValidTicketsPtr
= PR_FindFunctionSymbol(
205 lib
, "KLCacheHasValidTickets"))) {
206 LOG(("Fail to load KLCacheHasValidTickets function from gssapi library\n"));
207 PR_UnloadLibrary(lib
);
208 return NS_ERROR_FAILURE
;
216 // Generate proper GSSAPI error messages from the major and
217 // minor status codes.
218 void LogGssError(OM_uint32 maj_stat
, OM_uint32 min_stat
, const char* prefix
) {
219 if (!MOZ_LOG_TEST(gNegotiateLog
, LogLevel::Debug
)) {
224 OM_uint32 msg_ctx
= 0;
225 gss_buffer_desc status1_string
;
226 gss_buffer_desc status2_string
;
228 nsAutoCString errorStr
;
229 errorStr
.Assign(prefix
);
231 if (!gssLibrary
) return;
235 ret
= gss_display_status_ptr(&new_stat
, maj_stat
, GSS_C_GSS_CODE
,
236 GSS_C_NULL_OID
, &msg_ctx
, &status1_string
);
237 errorStr
.Append((const char*)status1_string
.value
, status1_string
.length
);
238 gss_release_buffer_ptr(&new_stat
, &status1_string
);
241 ret
= gss_display_status_ptr(&new_stat
, min_stat
, GSS_C_MECH_CODE
,
242 GSS_C_NULL_OID
, &msg_ctx
, &status2_string
);
243 errorStr
.Append((const char*)status2_string
.value
, status2_string
.length
);
245 } while (!GSS_ERROR(ret
) && msg_ctx
!= 0);
247 LOG(("%s\n", errorStr
.get()));
250 //-----------------------------------------------------------------------------
252 nsAuthGSSAPI::nsAuthGSSAPI(pType package
) : mServiceFlags(REQ_DEFAULT
) {
255 gss_OID_set mech_set
;
259 static gss_OID_desc gss_krb5_mech_oid_desc
= {
260 9, (void*)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x02"};
261 static gss_OID_desc gss_spnego_mech_oid_desc
= {
262 6, (void*)"\x2b\x06\x01\x05\x05\x02"};
264 LOG(("entering nsAuthGSSAPI::nsAuthGSSAPI()\n"));
268 if (!gssLibrary
&& NS_FAILED(gssInit())) return;
270 mCtx
= GSS_C_NO_CONTEXT
;
271 mMechOID
= &gss_krb5_mech_oid_desc
;
273 // if the type is kerberos we accept it as default
276 if (package
== PACKAGE_TYPE_KERBEROS
) return;
278 // Now, look at the list of supported mechanisms,
279 // if SPNEGO is found, then use it.
280 // Otherwise, set the desired mechanism to
281 // GSS_C_NO_OID and let the system try to use
282 // the default mechanism.
284 // Using Kerberos directly (instead of negotiating
285 // with SPNEGO) may work in some cases depending
286 // on how smart the server side is.
288 majstat
= gss_indicate_mechs_ptr(&minstat
, &mech_set
);
289 if (GSS_ERROR(majstat
)) return;
292 for (i
= 0; i
< mech_set
->count
; i
++) {
293 item
= &mech_set
->elements
[i
];
294 if (item
->length
== gss_spnego_mech_oid_desc
.length
&&
295 !memcmp(item
->elements
, gss_spnego_mech_oid_desc
.elements
,
298 mMechOID
= &gss_spnego_mech_oid_desc
;
302 gss_release_oid_set_ptr(&minstat
, &mech_set
);
306 void nsAuthGSSAPI::Reset() {
307 if (gssLibrary
&& mCtx
!= GSS_C_NO_CONTEXT
) {
308 OM_uint32 minor_status
;
309 gss_delete_sec_context_ptr(&minor_status
, &mCtx
, GSS_C_NO_BUFFER
);
311 mCtx
= GSS_C_NO_CONTEXT
;
316 void nsAuthGSSAPI::Shutdown() {
318 PR_UnloadLibrary(gssLibrary
);
319 gssLibrary
= nullptr;
323 /* Limitations apply to this class's thread safety. See the header file */
324 NS_IMPL_ISUPPORTS(nsAuthGSSAPI
, nsIAuthModule
)
327 nsAuthGSSAPI::Init(const nsACString
& serviceName
, uint32_t serviceFlags
,
328 const nsAString
& domain
, const nsAString
& username
,
329 const nsAString
& password
) {
330 // we don't expect to be passed any user credentials
331 NS_ASSERTION(domain
.IsEmpty() && username
.IsEmpty() && password
.IsEmpty(),
332 "unexpected credentials");
334 // it's critial that the caller supply a service name to be used
335 NS_ENSURE_TRUE(!serviceName
.IsEmpty(), NS_ERROR_INVALID_ARG
);
337 LOG(("entering nsAuthGSSAPI::Init()\n"));
339 if (!gssLibrary
) return NS_ERROR_NOT_INITIALIZED
;
341 mServiceName
= serviceName
;
342 mServiceFlags
= serviceFlags
;
344 static bool sTelemetrySent
= false;
345 if (!sTelemetrySent
) {
346 mozilla::Telemetry::Accumulate(mozilla::Telemetry::NTLM_MODULE_USED_2
,
347 serviceFlags
& nsIAuthModule::REQ_PROXY_AUTH
348 ? NTLM_MODULE_KERBEROS_PROXY
349 : NTLM_MODULE_KERBEROS_DIRECT
);
350 sTelemetrySent
= true;
357 nsAuthGSSAPI::GetNextToken(const void* inToken
, uint32_t inTokenLen
,
358 void** outToken
, uint32_t* outTokenLen
) {
359 OM_uint32 major_status
, minor_status
;
360 OM_uint32 req_flags
= 0;
361 gss_buffer_desc input_token
= GSS_C_EMPTY_BUFFER
;
362 gss_buffer_desc output_token
= GSS_C_EMPTY_BUFFER
;
363 gss_buffer_t in_token_ptr
= GSS_C_NO_BUFFER
;
365 nsAutoCString userbuf
;
368 LOG(("entering nsAuthGSSAPI::GetNextToken()\n"));
370 if (!gssLibrary
) return NS_ERROR_NOT_INITIALIZED
;
372 // If they've called us again after we're complete, reset to start afresh.
373 if (mComplete
) Reset();
375 if (mServiceFlags
& REQ_DELEGATE
) req_flags
|= GSS_C_DELEG_FLAG
;
377 if (mServiceFlags
& REQ_MUTUAL_AUTH
) req_flags
|= GSS_C_MUTUAL_FLAG
;
379 input_token
.value
= (void*)mServiceName
.get();
380 input_token
.length
= mServiceName
.Length() + 1;
382 #if defined(HAVE_RES_NINIT)
385 major_status
= gss_import_name_ptr(&minor_status
, &input_token
,
386 &gss_c_nt_hostbased_service
, &server
);
387 input_token
.value
= nullptr;
388 input_token
.length
= 0;
389 if (GSS_ERROR(major_status
)) {
390 LogGssError(major_status
, minor_status
, "gss_import_name() failed");
391 return NS_ERROR_FAILURE
;
395 input_token
.length
= inTokenLen
;
396 input_token
.value
= (void*)inToken
;
397 in_token_ptr
= &input_token
;
398 } else if (mCtx
!= GSS_C_NO_CONTEXT
) {
399 // If there is no input token, then we are starting a new
400 // authentication sequence. If we have already initialized our
401 // security context, then we're in trouble because it means that the
402 // first sequence failed. We need to bail or else we might end up in
404 LOG(("Cannot restart authentication sequence!"));
405 return NS_ERROR_UNEXPECTED
;
408 #if defined(XP_MACOSX)
409 // Suppress Kerberos prompts to get credentials. See bug 240643.
410 // We can only use Mac OS X specific kerb functions if we are using
413 bool doingMailTask
= mServiceName
.Find("imap@") ||
414 mServiceName
.Find("pop@") ||
415 mServiceName
.Find("smtp@") || mServiceName
.Find("ldap@");
417 if (!doingMailTask
&&
419 (KLCacheHasValidTickets_ptr(nullptr, kerberosVersion_V5
, &found
, nullptr,
420 nullptr) != klNoErr
||
422 major_status
= GSS_S_FAILURE
;
425 #endif /* XP_MACOSX */
426 major_status
= gss_init_sec_context_ptr(
427 &minor_status
, GSS_C_NO_CREDENTIAL
, &mCtx
, server
, mMechOID
, req_flags
,
428 GSS_C_INDEFINITE
, GSS_C_NO_CHANNEL_BINDINGS
, in_token_ptr
, nullptr,
429 &output_token
, nullptr, nullptr);
431 if (GSS_ERROR(major_status
)) {
432 LogGssError(major_status
, minor_status
, "gss_init_sec_context() failed");
434 rv
= NS_ERROR_FAILURE
;
437 if (major_status
== GSS_S_COMPLETE
) {
438 // Mark ourselves as being complete, so that if we're called again
439 // we know to start afresh.
441 } else if (major_status
== GSS_S_CONTINUE_NEEDED
) {
443 // The important thing is that we do NOT reset the
444 // context here because it will be needed on the
449 *outTokenLen
= output_token
.length
;
450 if (output_token
.length
!= 0) {
451 *outToken
= moz_xmemdup(output_token
.value
, output_token
.length
);
456 gss_release_buffer_ptr(&minor_status
, &output_token
);
458 if (major_status
== GSS_S_COMPLETE
) {
459 rv
= NS_SUCCESS_AUTH_FINISHED
;
465 gss_release_name_ptr(&minor_status
, &server
);
467 LOG((" leaving nsAuthGSSAPI::GetNextToken [rv=%" PRIx32
"]",
468 static_cast<uint32_t>(rv
)));
473 nsAuthGSSAPI::Unwrap(const void* inToken
, uint32_t inTokenLen
, void** outToken
,
474 uint32_t* outTokenLen
) {
475 OM_uint32 major_status
, minor_status
;
477 gss_buffer_desc input_token
;
478 gss_buffer_desc output_token
= GSS_C_EMPTY_BUFFER
;
480 input_token
.value
= (void*)inToken
;
481 input_token
.length
= inTokenLen
;
483 major_status
= gss_unwrap_ptr(&minor_status
, mCtx
, &input_token
,
484 &output_token
, nullptr, nullptr);
485 if (GSS_ERROR(major_status
)) {
486 LogGssError(major_status
, minor_status
, "gss_unwrap() failed");
488 gss_release_buffer_ptr(&minor_status
, &output_token
);
489 return NS_ERROR_FAILURE
;
492 *outTokenLen
= output_token
.length
;
494 if (output_token
.length
) {
495 *outToken
= moz_xmemdup(output_token
.value
, output_token
.length
);
500 gss_release_buffer_ptr(&minor_status
, &output_token
);
506 nsAuthGSSAPI::Wrap(const void* inToken
, uint32_t inTokenLen
, bool confidential
,
507 void** outToken
, uint32_t* outTokenLen
) {
508 OM_uint32 major_status
, minor_status
;
510 gss_buffer_desc input_token
;
511 gss_buffer_desc output_token
= GSS_C_EMPTY_BUFFER
;
513 input_token
.value
= (void*)inToken
;
514 input_token
.length
= inTokenLen
;
517 gss_wrap_ptr(&minor_status
, mCtx
, confidential
, GSS_C_QOP_DEFAULT
,
518 &input_token
, nullptr, &output_token
);
520 if (GSS_ERROR(major_status
)) {
521 LogGssError(major_status
, minor_status
, "gss_wrap() failed");
523 gss_release_buffer_ptr(&minor_status
, &output_token
);
524 return NS_ERROR_FAILURE
;
527 *outTokenLen
= output_token
.length
;
529 /* it is not possible for output_token.length to be zero */
530 *outToken
= moz_xmemdup(output_token
.value
, output_token
.length
);
531 gss_release_buffer_ptr(&minor_status
, &output_token
);