extensions/auth/nsAuthGSSAPI.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     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/. */
     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 //
    17 #include "mozilla/ArrayUtils.h"
    19 #include "prlink.h"
    20 #include "nsCOMPtr.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"
    29 #ifdef XP_MACOSX
    30 #include <Kerberos/Kerberos.h>
    31 #endif
    33 #ifdef XP_MACOSX
    34 typedef KLStatus (*KLCacheHasValidTickets_type)(
    35     KLPrincipal,
    36     KLKerberosVersion,
    37     KLBoolean *,
    38     KLPrincipal *,
    39     char **);
    40 #endif
    42 #if defined(HAVE_RES_NINIT)
    43 #include <sys/types.h>
    44 #include <netinet/in.h>
    45 #include <arpa/nameser.h>
    46 #include <resolv.h>
    47 #endif
    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
    55 // has the same value
    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 {
    66     const char *str;
    67     PRFuncPtr func;
    68 } gssFuncs[] = {
    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 }
    79 };
    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)
    95 #ifdef XP_MACOSX
    96 static PRFuncPtr KLCacheHasValidTicketsPtr;
    97 #define KLCacheHasValidTickets_ptr \
    98         ((KLCacheHasValidTickets_type)*KLCacheHasValidTicketsPtr)
    99 #endif
   101 static nsresult
   102 gssInit()
   103 {
   104     nsXPIDLCString libPath;
   105     nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
   106     if (prefs) {
   107         prefs->GetCharPref(kNegotiateAuthGssLib, getter_Copies(libPath)); 
   108         prefs->GetBoolPref(kNegotiateAuthNativeImp, &gssNativeImp); 
   109     }
   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());
   117     }
   118     else {
   119 #ifdef XP_WIN
   120         char *libName = PR_GetLibraryName(nullptr, "gssapi32");
   121         if (libName) {
   122             lib = PR_LoadLibrary("gssapi32");
   123             PR_FreeLibraryName(libName);
   124         }
   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
   129          * fine.
   130          */
   132         const char *const verLibNames[] = {
   133             "libasn1.so",
   134             "libcrypto.so",
   135             "libroken.so",
   136             "libheimbase.so",
   137             "libcom_err.so",
   138             "libkrb5.so",
   139             "libgssapi.so"
   140         };
   142         PRLibSpec libSpec;
   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);
   147         };
   149 #else
   151         const char *const libNames[] = {
   152             "gss",
   153             "gssapi_krb5",
   154             "gssapi"
   155         };
   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*/
   161         };
   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
   171              */
   172             if (lib &&
   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);
   178                 lib = nullptr;
   179             }
   180         }
   182         for (size_t i = 0; i < ArrayLength(libNames) && !lib; ++i) {
   183             char *libName = PR_GetLibraryName(nullptr, libNames[i]);
   184             if (libName) {
   185                 lib = PR_LoadLibrary(libName);
   186                 PR_FreeLibraryName(libName);
   188                 if (lib &&
   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);
   194                     lib = nullptr;
   195                 } 
   196             }
   197         }
   198 #endif
   199     }
   201     if (!lib) {
   202         LOG(("Fail to load gssapi library\n"));
   203         return NS_ERROR_FAILURE;
   204     }
   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;
   214         }
   215     }
   216 #ifdef XP_MACOSX
   217     if (gssNativeImp &&
   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;
   223     }
   224 #endif
   226     gssLibrary = lib;
   227     return NS_OK;
   228 }
   230 #if defined( PR_LOGGING )
   232 // Generate proper GSSAPI error messages from the major and
   233 // minor status codes.
   234 void
   235 LogGssError(OM_uint32 maj_stat, OM_uint32 min_stat, const char *prefix)
   236 {
   237     OM_uint32 new_stat;
   238     OM_uint32 msg_ctx = 0;
   239     gss_buffer_desc status1_string;
   240     gss_buffer_desc status2_string;
   241     OM_uint32 ret;
   242     nsAutoCString errorStr;
   243     errorStr.Assign(prefix);
   245     if (!gssLibrary)
   246         return;
   248     errorStr += ": ";
   249     do {
   250         ret = gss_display_status_ptr(&new_stat,
   251                                      maj_stat,
   252                                      GSS_C_GSS_CODE,
   253                                      GSS_C_NULL_OID,
   254                                      &msg_ctx,
   255                                      &status1_string);
   256         errorStr.Append((const char *) status1_string.value, status1_string.length);
   257         gss_release_buffer_ptr(&new_stat, &status1_string);
   259         errorStr += '\n';
   260         ret = gss_display_status_ptr(&new_stat,
   261                                      min_stat,
   262                                      GSS_C_MECH_CODE,
   263                                      GSS_C_NULL_OID,
   264                                      &msg_ctx,
   265                                      &status2_string);
   266         errorStr.Append((const char *) status2_string.value, status2_string.length);
   267         errorStr += '\n';
   268     } while (!GSS_ERROR(ret) && msg_ctx != 0);
   270     LOG(("%s\n", errorStr.get()));
   271 }
   273 #else /* PR_LOGGING */
   275 #define LogGssError(x,y,z)
   277 #endif /* PR_LOGGING */
   279 //-----------------------------------------------------------------------------
   281 nsAuthGSSAPI::nsAuthGSSAPI(pType package)
   282     : mServiceFlags(REQ_DEFAULT)
   283 {
   284     OM_uint32 minstat;
   285     OM_uint32 majstat;
   286     gss_OID_set mech_set;
   287     gss_OID item;
   289     unsigned int i;
   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"));
   297     mComplete = false;
   299     if (!gssLibrary && NS_FAILED(gssInit()))
   300         return;
   302     mCtx = GSS_C_NO_CONTEXT;
   303     mMechOID = &gss_krb5_mech_oid_desc;
   305     // if the type is kerberos we accept it as default
   306     // and exit 
   308     if (package == PACKAGE_TYPE_KERBEROS)
   309         return;
   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.
   316     //
   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))
   323         return;
   325     if (mech_set) {
   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,
   330                 item->length)) {
   331                 // ok, we found it
   332                 mMechOID = &gss_spnego_mech_oid_desc;
   333                 break;
   334             }
   335         }
   336         gss_release_oid_set_ptr(&minstat, &mech_set);
   337     }
   338 }
   340 void
   341 nsAuthGSSAPI::Reset()
   342 {
   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);
   346     }
   347     mCtx = GSS_C_NO_CONTEXT;
   348     mComplete = false;
   349 }
   351 /* static */ void
   352 nsAuthGSSAPI::Shutdown()
   353 {
   354     if (gssLibrary) {
   355         PR_UnloadLibrary(gssLibrary);
   356         gssLibrary = nullptr;
   357     }
   358 }
   360 /* Limitations apply to this class's thread safety. See the header file */
   361 NS_IMPL_ISUPPORTS(nsAuthGSSAPI, nsIAuthModule)
   363 NS_IMETHODIMP
   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)
   369 {
   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"));
   378     if (!gssLibrary)
   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;
   392     }
   394     return NS_OK;
   395 }
   397 NS_IMETHODIMP
   398 nsAuthGSSAPI::GetNextToken(const void *inToken,
   399                            uint32_t    inTokenLen,
   400                            void      **outToken,
   401                            uint32_t   *outTokenLen)
   402 {
   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;
   408     gss_name_t server;
   409     nsAutoCString userbuf;
   410     nsresult rv;
   412     LOG(("entering nsAuthGSSAPI::GetNextToken()\n"));
   414     if (!gssLibrary)
   415        return NS_ERROR_NOT_INITIALIZED;
   417     // If they've called us again after we're complete, reset to start afresh.
   418     if (mComplete)
   419         Reset();
   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)
   431     res_ninit(&_res);
   432 #endif
   433     major_status = gss_import_name_ptr(&minor_status,
   434                                    &input_token,
   435                                    &gss_c_nt_hostbased_service,
   436                                    &server);
   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;
   442     }
   444     if (inToken) {
   445         input_token.length = inTokenLen;
   446         input_token.value = (void *) inToken;
   447         in_token_ptr = &input_token;
   448     }
   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
   454         // an infinite loop.
   455         LOG(("Cannot restart authentication sequence!"));
   456         return NS_ERROR_UNEXPECTED; 
   457     }
   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 
   462     // the native lib
   463     KLBoolean found;    
   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)))
   471     {
   472         major_status = GSS_S_FAILURE;
   473         minor_status = 0;
   474     }
   475     else
   476 #endif /* XP_MACOSX */
   477     major_status = gss_init_sec_context_ptr(&minor_status,
   478                                             GSS_C_NO_CREDENTIAL,
   479                                             &mCtx,
   480                                             server,
   481                                             mMechOID,
   482                                             req_flags,
   483                                             GSS_C_INDEFINITE,
   484                                             GSS_C_NO_CHANNEL_BINDINGS,
   485                                             in_token_ptr,
   486                                             nullptr,
   487                                             &output_token,
   488                                             nullptr,
   489                                             nullptr);
   491     if (GSS_ERROR(major_status)) {
   492         LogGssError(major_status, minor_status, "gss_init_sec_context() failed");
   493         Reset();
   494         rv = NS_ERROR_FAILURE;
   495         goto end;
   496     }
   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.
   500         mComplete = true;
   501     }
   502     else if (major_status == GSS_S_CONTINUE_NEEDED) {
   503         //
   504         // The important thing is that we do NOT reset the
   505         // context here because it will be needed on the
   506         // next call.
   507         //
   508     } 
   510     *outTokenLen = output_token.length;
   511     if (output_token.length != 0)
   512         *outToken = nsMemory::Clone(output_token.value, output_token.length);
   513     else
   514         *outToken = nullptr;
   516     gss_release_buffer_ptr(&minor_status, &output_token);
   518     if (major_status == GSS_S_COMPLETE)
   519         rv = NS_SUCCESS_AUTH_FINISHED;
   520     else
   521         rv = NS_OK;
   523 end:
   524     gss_release_name_ptr(&minor_status, &server);
   526     LOG(("  leaving nsAuthGSSAPI::GetNextToken [rv=%x]", rv));
   527     return rv;
   528 }
   530 NS_IMETHODIMP
   531 nsAuthGSSAPI::Unwrap(const void *inToken,
   532                      uint32_t    inTokenLen,
   533                      void      **outToken,
   534                      uint32_t   *outTokenLen)
   535 {
   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,
   545                                   mCtx,
   546                                   &input_token,
   547                                   &output_token,
   548                                   nullptr,
   549                                   nullptr);
   550     if (GSS_ERROR(major_status)) {
   551         LogGssError(major_status, minor_status, "gss_unwrap() failed");
   552         Reset();
   553         gss_release_buffer_ptr(&minor_status, &output_token);
   554         return NS_ERROR_FAILURE;
   555     }
   557     *outTokenLen = output_token.length;
   559     if (output_token.length)
   560         *outToken = nsMemory::Clone(output_token.value, output_token.length);
   561     else
   562         *outToken = nullptr;
   564     gss_release_buffer_ptr(&minor_status, &output_token);
   566     return NS_OK;
   567 }
   569 NS_IMETHODIMP
   570 nsAuthGSSAPI::Wrap(const void *inToken,
   571                    uint32_t    inTokenLen,
   572                    bool        confidential,
   573                    void      **outToken,
   574                    uint32_t   *outTokenLen)
   575 {
   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,
   585                                 mCtx,
   586                                 confidential,
   587                                 GSS_C_QOP_DEFAULT,
   588                                 &input_token,
   589                                 nullptr,
   590                                 &output_token);
   592     if (GSS_ERROR(major_status)) {
   593         LogGssError(major_status, minor_status, "gss_wrap() failed");
   594         Reset();
   595         gss_release_buffer_ptr(&minor_status, &output_token);
   596         return NS_ERROR_FAILURE;
   597     }
   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);
   605     return NS_OK;
   606 }

mercurial