123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656 |
- /* vim:set ts=4 sw=4 sts=4 et cindent: */
- /* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
- //
- // Negotiate Authentication Support Module
- //
- // Described by IETF Internet draft: draft-brezak-kerberos-http-00.txt
- // (formerly draft-brezak-spnego-http-04.txt)
- //
- // Also described here:
- // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnsecure/html/http-sso-1.asp
- //
- #include "nsAuthSSPI.h"
- #include "nsIServiceManager.h"
- #include "nsIDNSService.h"
- #include "nsIDNSRecord.h"
- #include "nsNetCID.h"
- #include "nsCOMPtr.h"
- #include "nsICryptoHash.h"
- #include <windows.h>
- #define SEC_SUCCESS(Status) ((Status) >= 0)
- #ifndef KERB_WRAP_NO_ENCRYPT
- #define KERB_WRAP_NO_ENCRYPT 0x80000001
- #endif
- #ifndef SECBUFFER_PADDING
- #define SECBUFFER_PADDING 9
- #endif
- #ifndef SECBUFFER_STREAM
- #define SECBUFFER_STREAM 10
- #endif
- //-----------------------------------------------------------------------------
- static const wchar_t *const pTypeName [] = {
- L"Kerberos",
- L"Negotiate",
- L"NTLM"
- };
- #ifdef DEBUG
- #define CASE_(_x) case _x: return # _x;
- static const char *MapErrorCode(int rc)
- {
- switch (rc) {
- CASE_(SEC_E_OK)
- CASE_(SEC_I_CONTINUE_NEEDED)
- CASE_(SEC_I_COMPLETE_NEEDED)
- CASE_(SEC_I_COMPLETE_AND_CONTINUE)
- CASE_(SEC_E_INCOMPLETE_MESSAGE)
- CASE_(SEC_I_INCOMPLETE_CREDENTIALS)
- CASE_(SEC_E_INVALID_HANDLE)
- CASE_(SEC_E_TARGET_UNKNOWN)
- CASE_(SEC_E_LOGON_DENIED)
- CASE_(SEC_E_INTERNAL_ERROR)
- CASE_(SEC_E_NO_CREDENTIALS)
- CASE_(SEC_E_NO_AUTHENTICATING_AUTHORITY)
- CASE_(SEC_E_INSUFFICIENT_MEMORY)
- CASE_(SEC_E_INVALID_TOKEN)
- }
- return "<unknown>";
- }
- #else
- #define MapErrorCode(_rc) ""
- #endif
- //-----------------------------------------------------------------------------
- static PSecurityFunctionTableW sspi;
- static nsresult
- InitSSPI()
- {
- LOG((" InitSSPI\n"));
- sspi = InitSecurityInterfaceW();
- if (!sspi) {
- LOG(("InitSecurityInterfaceW failed"));
- return NS_ERROR_UNEXPECTED;
- }
- return NS_OK;
- }
- //-----------------------------------------------------------------------------
- static nsresult
- MakeSN(const char *principal, nsCString &result)
- {
- nsresult rv;
- nsAutoCString buf(principal);
- // The service name looks like "protocol@hostname", we need to map
- // this to a value that SSPI expects. To be consistent with IE, we
- // need to map '@' to '/' and canonicalize the hostname.
- int32_t index = buf.FindChar('@');
- if (index == kNotFound)
- return NS_ERROR_UNEXPECTED;
-
- nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID, &rv);
- if (NS_FAILED(rv))
- return rv;
- // This could be expensive if our DNS cache cannot satisfy the request.
- // However, we should have at least hit the OS resolver once prior to
- // reaching this code, so provided the OS resolver has this information
- // cached, we should not have to worry about blocking on this function call
- // for very long. NOTE: because we ask for the canonical hostname, we
- // might end up requiring extra network activity in cases where the OS
- // resolver might not have enough information to satisfy the request from
- // its cache. This is not an issue in versions of Windows up to WinXP.
- nsCOMPtr<nsIDNSRecord> record;
- rv = dns->Resolve(Substring(buf, index + 1),
- nsIDNSService::RESOLVE_CANONICAL_NAME,
- getter_AddRefs(record));
- if (NS_FAILED(rv))
- return rv;
- nsAutoCString cname;
- rv = record->GetCanonicalName(cname);
- if (NS_SUCCEEDED(rv)) {
- result = StringHead(buf, index) + NS_LITERAL_CSTRING("/") + cname;
- LOG(("Using SPN of [%s]\n", result.get()));
- }
- return rv;
- }
- //-----------------------------------------------------------------------------
- nsAuthSSPI::nsAuthSSPI(pType package)
- : mServiceFlags(REQ_DEFAULT)
- , mMaxTokenLen(0)
- , mPackage(package)
- , mCertDERData(nullptr)
- , mCertDERLength(0)
- {
- memset(&mCred, 0, sizeof(mCred));
- memset(&mCtxt, 0, sizeof(mCtxt));
- }
- nsAuthSSPI::~nsAuthSSPI()
- {
- Reset();
- if (mCred.dwLower || mCred.dwUpper) {
- #ifdef __MINGW32__
- (sspi->FreeCredentialsHandle)(&mCred);
- #else
- (sspi->FreeCredentialHandle)(&mCred);
- #endif
- memset(&mCred, 0, sizeof(mCred));
- }
- }
- void
- nsAuthSSPI::Reset()
- {
- mIsFirst = true;
- if (mCertDERData){
- free(mCertDERData);
- mCertDERData = nullptr;
- mCertDERLength = 0;
- }
- if (mCtxt.dwLower || mCtxt.dwUpper) {
- (sspi->DeleteSecurityContext)(&mCtxt);
- memset(&mCtxt, 0, sizeof(mCtxt));
- }
- }
- NS_IMPL_ISUPPORTS(nsAuthSSPI, nsIAuthModule)
- NS_IMETHODIMP
- nsAuthSSPI::Init(const char *serviceName,
- uint32_t serviceFlags,
- const char16_t *domain,
- const char16_t *username,
- const char16_t *password)
- {
- LOG((" nsAuthSSPI::Init\n"));
- mIsFirst = true;
- mCertDERLength = 0;
- mCertDERData = nullptr;
- // The caller must supply a service name to be used. (For why we now require
- // a service name for NTLM, see bug 487872.)
- NS_ENSURE_TRUE(serviceName && *serviceName, NS_ERROR_INVALID_ARG);
- nsresult rv;
- // XXX lazy initialization like this assumes that we are single threaded
- if (!sspi) {
- rv = InitSSPI();
- if (NS_FAILED(rv))
- return rv;
- }
- SEC_WCHAR *package;
- package = (SEC_WCHAR *) pTypeName[(int)mPackage];
- if (mPackage == PACKAGE_TYPE_NTLM) {
- // (bug 535193) For NTLM, just use the uri host, do not do canonical host lookups.
- // The incoming serviceName is in the format: "protocol@hostname", SSPI expects
- // "<service class>/<hostname>", so swap the '@' for a '/'.
- mServiceName.Assign(serviceName);
- int32_t index = mServiceName.FindChar('@');
- if (index == kNotFound)
- return NS_ERROR_UNEXPECTED;
- mServiceName.Replace(index, 1, '/');
- }
- else {
- // Kerberos requires the canonical host, MakeSN takes care of this through a
- // DNS lookup.
- rv = MakeSN(serviceName, mServiceName);
- if (NS_FAILED(rv))
- return rv;
- }
- mServiceFlags = serviceFlags;
- SECURITY_STATUS rc;
- PSecPkgInfoW pinfo;
- rc = (sspi->QuerySecurityPackageInfoW)(package, &pinfo);
- if (rc != SEC_E_OK) {
- LOG(("%s package not found\n", package));
- return NS_ERROR_UNEXPECTED;
- }
- mMaxTokenLen = pinfo->cbMaxToken;
- (sspi->FreeContextBuffer)(pinfo);
- MS_TimeStamp useBefore;
- SEC_WINNT_AUTH_IDENTITY_W ai;
- SEC_WINNT_AUTH_IDENTITY_W *pai = nullptr;
-
- // domain, username, and password will be null if nsHttpNTLMAuth's ChallengeReceived
- // returns false for identityInvalid. Use default credentials in this case by passing
- // null for pai.
- if (username && password) {
- // Keep a copy of these strings for the duration
- mUsername.Assign(username);
- mPassword.Assign(password);
- mDomain.Assign(domain);
- ai.Domain = reinterpret_cast<unsigned short*>(mDomain.BeginWriting());
- ai.DomainLength = mDomain.Length();
- ai.User = reinterpret_cast<unsigned short*>(mUsername.BeginWriting());
- ai.UserLength = mUsername.Length();
- ai.Password = reinterpret_cast<unsigned short*>(mPassword.BeginWriting());
- ai.PasswordLength = mPassword.Length();
- ai.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE;
- pai = &ai;
- }
- rc = (sspi->AcquireCredentialsHandleW)(nullptr,
- package,
- SECPKG_CRED_OUTBOUND,
- nullptr,
- pai,
- nullptr,
- nullptr,
- &mCred,
- &useBefore);
- if (rc != SEC_E_OK)
- return NS_ERROR_UNEXPECTED;
- LOG(("AcquireCredentialsHandle() succeeded.\n"));
- return NS_OK;
- }
- // The arguments inToken and inTokenLen are used to pass in the server
- // certificate (when available) in the first call of the function. The
- // second time these arguments hold an input token.
- NS_IMETHODIMP
- nsAuthSSPI::GetNextToken(const void *inToken,
- uint32_t inTokenLen,
- void **outToken,
- uint32_t *outTokenLen)
- {
- // String for end-point bindings.
- const char end_point[] = "tls-server-end-point:";
- const int end_point_length = sizeof(end_point) - 1;
- const int hash_size = 32; // Size of a SHA256 hash.
- const int cbt_size = hash_size + end_point_length;
-
- SECURITY_STATUS rc;
- MS_TimeStamp ignored;
- DWORD ctxAttr, ctxReq = 0;
- CtxtHandle *ctxIn;
- SecBufferDesc ibd, obd;
- // Optional second input buffer for the CBT (Channel Binding Token)
- SecBuffer ib[2], ob;
- // Pointer to the block of memory that stores the CBT
- char* sspi_cbt = nullptr;
- SEC_CHANNEL_BINDINGS pendpoint_binding;
- LOG(("entering nsAuthSSPI::GetNextToken()\n"));
- if (!mCred.dwLower && !mCred.dwUpper) {
- LOG(("nsAuthSSPI::GetNextToken(), not initialized. exiting."));
- return NS_ERROR_NOT_INITIALIZED;
- }
- if (mServiceFlags & REQ_DELEGATE)
- ctxReq |= ISC_REQ_DELEGATE;
- if (mServiceFlags & REQ_MUTUAL_AUTH)
- ctxReq |= ISC_REQ_MUTUAL_AUTH;
- if (inToken) {
- if (mIsFirst) {
- // First time if it comes with a token,
- // the token represents the server certificate.
- mIsFirst = false;
- mCertDERLength = inTokenLen;
- mCertDERData = moz_xmalloc(inTokenLen);
- if (!mCertDERData)
- return NS_ERROR_OUT_OF_MEMORY;
- memcpy(mCertDERData, inToken, inTokenLen);
- // We are starting a new authentication sequence.
- // If we have already initialized our
- // security context, then we're in trouble because it means that the
- // first sequence failed. We need to bail or else we might end up in
- // an infinite loop.
- if (mCtxt.dwLower || mCtxt.dwUpper) {
- LOG(("Cannot restart authentication sequence!"));
- return NS_ERROR_UNEXPECTED;
- }
- ctxIn = nullptr;
- // The certificate needs to be erased before being passed
- // to InitializeSecurityContextW().
- inToken = nullptr;
- inTokenLen = 0;
- } else {
- ibd.ulVersion = SECBUFFER_VERSION;
- ibd.cBuffers = 0;
- ibd.pBuffers = ib;
-
- // If we have stored a certificate, the Channel Binding Token
- // needs to be generated and sent in the first input buffer.
- if (mCertDERLength > 0) {
- // First we create a proper Endpoint Binding structure.
- pendpoint_binding.dwInitiatorAddrType = 0;
- pendpoint_binding.cbInitiatorLength = 0;
- pendpoint_binding.dwInitiatorOffset = 0;
- pendpoint_binding.dwAcceptorAddrType = 0;
- pendpoint_binding.cbAcceptorLength = 0;
- pendpoint_binding.dwAcceptorOffset = 0;
- pendpoint_binding.cbApplicationDataLength = cbt_size;
- pendpoint_binding.dwApplicationDataOffset =
- sizeof(SEC_CHANNEL_BINDINGS);
- // Then add it to the array of sec buffers accordingly.
- ib[ibd.cBuffers].BufferType = SECBUFFER_CHANNEL_BINDINGS;
- ib[ibd.cBuffers].cbBuffer =
- pendpoint_binding.cbApplicationDataLength
- + pendpoint_binding.dwApplicationDataOffset;
-
- sspi_cbt = (char *) moz_xmalloc(ib[ibd.cBuffers].cbBuffer);
- if (!sspi_cbt){
- return NS_ERROR_OUT_OF_MEMORY;
- }
- // Helper to write in the memory block that stores the CBT
- char* sspi_cbt_ptr = sspi_cbt;
-
- ib[ibd.cBuffers].pvBuffer = sspi_cbt;
- ibd.cBuffers++;
- memcpy(sspi_cbt_ptr, &pendpoint_binding,
- pendpoint_binding.dwApplicationDataOffset);
- sspi_cbt_ptr += pendpoint_binding.dwApplicationDataOffset;
- memcpy(sspi_cbt_ptr, end_point, end_point_length);
- sspi_cbt_ptr += end_point_length;
-
- // Start hashing. We are always doing SHA256, but depending
- // on the certificate, a different alogirthm might be needed.
- nsAutoCString hashString;
- nsresult rv;
- nsCOMPtr<nsICryptoHash> crypto;
- crypto = do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv);
- if (NS_SUCCEEDED(rv))
- rv = crypto->Init(nsICryptoHash::SHA256);
- if (NS_SUCCEEDED(rv))
- rv = crypto->Update((unsigned char*)mCertDERData, mCertDERLength);
- if (NS_SUCCEEDED(rv))
- rv = crypto->Finish(false, hashString);
- if (NS_FAILED(rv)) {
- free(mCertDERData);
- mCertDERData = nullptr;
- mCertDERLength = 0;
- free(sspi_cbt);
- return rv;
- }
-
- // Once the hash has been computed, we store it in memory right
- // after the Endpoint structure and the "tls-server-end-point:"
- // char array.
- memcpy(sspi_cbt_ptr, hashString.get(), hash_size);
-
- // Free memory used to store the server certificate
- free(mCertDERData);
- mCertDERData = nullptr;
- mCertDERLength = 0;
- } // End of CBT computation.
- // We always need this SECBUFFER.
- ib[ibd.cBuffers].BufferType = SECBUFFER_TOKEN;
- ib[ibd.cBuffers].cbBuffer = inTokenLen;
- ib[ibd.cBuffers].pvBuffer = (void *) inToken;
- ibd.cBuffers++;
- ctxIn = &mCtxt;
- }
- } else { // First time and without a token (no server certificate)
- // We are starting a new authentication sequence. If we have already
- // initialized our security context, then we're in trouble because it
- // means that the first sequence failed. We need to bail or else we
- // might end up in an infinite loop.
- if (mCtxt.dwLower || mCtxt.dwUpper || mCertDERData || mCertDERLength) {
- LOG(("Cannot restart authentication sequence!"));
- return NS_ERROR_UNEXPECTED;
- }
- ctxIn = nullptr;
- mIsFirst = false;
- }
- obd.ulVersion = SECBUFFER_VERSION;
- obd.cBuffers = 1;
- obd.pBuffers = &ob;
- ob.BufferType = SECBUFFER_TOKEN;
- ob.cbBuffer = mMaxTokenLen;
- ob.pvBuffer = moz_xmalloc(ob.cbBuffer);
- if (!ob.pvBuffer){
- if (sspi_cbt)
- free(sspi_cbt);
- return NS_ERROR_OUT_OF_MEMORY;
- }
- memset(ob.pvBuffer, 0, ob.cbBuffer);
- NS_ConvertUTF8toUTF16 wSN(mServiceName);
- SEC_WCHAR *sn = (SEC_WCHAR *) wSN.get();
- rc = (sspi->InitializeSecurityContextW)(&mCred,
- ctxIn,
- sn,
- ctxReq,
- 0,
- SECURITY_NATIVE_DREP,
- inToken ? &ibd : nullptr,
- 0,
- &mCtxt,
- &obd,
- &ctxAttr,
- &ignored);
- if (rc == SEC_I_CONTINUE_NEEDED || rc == SEC_E_OK) {
- if (rc == SEC_E_OK)
- LOG(("InitializeSecurityContext: succeeded.\n"));
- else
- LOG(("InitializeSecurityContext: continue.\n"));
- if (sspi_cbt)
- free(sspi_cbt);
-
- if (!ob.cbBuffer) {
- free(ob.pvBuffer);
- ob.pvBuffer = nullptr;
- }
- *outToken = ob.pvBuffer;
- *outTokenLen = ob.cbBuffer;
- if (rc == SEC_E_OK)
- return NS_SUCCESS_AUTH_FINISHED;
- return NS_OK;
- }
- LOG(("InitializeSecurityContext failed [rc=%d:%s]\n", rc, MapErrorCode(rc)));
- Reset();
- free(ob.pvBuffer);
- return NS_ERROR_FAILURE;
- }
- NS_IMETHODIMP
- nsAuthSSPI::Unwrap(const void *inToken,
- uint32_t inTokenLen,
- void **outToken,
- uint32_t *outTokenLen)
- {
- SECURITY_STATUS rc;
- SecBufferDesc ibd;
- SecBuffer ib[2];
- ibd.cBuffers = 2;
- ibd.pBuffers = ib;
- ibd.ulVersion = SECBUFFER_VERSION;
- // SSPI Buf
- ib[0].BufferType = SECBUFFER_STREAM;
- ib[0].cbBuffer = inTokenLen;
- ib[0].pvBuffer = moz_xmalloc(ib[0].cbBuffer);
- if (!ib[0].pvBuffer)
- return NS_ERROR_OUT_OF_MEMORY;
-
- memcpy(ib[0].pvBuffer, inToken, inTokenLen);
- // app data
- ib[1].BufferType = SECBUFFER_DATA;
- ib[1].cbBuffer = 0;
- ib[1].pvBuffer = nullptr;
- rc = (sspi->DecryptMessage)(
- &mCtxt,
- &ibd,
- 0, // no sequence numbers
- nullptr
- );
- if (SEC_SUCCESS(rc)) {
- // check if ib[1].pvBuffer is really just ib[0].pvBuffer, in which
- // case we can let the caller free it. Otherwise, we need to
- // clone it, and free the original
- if (ib[0].pvBuffer == ib[1].pvBuffer) {
- *outToken = ib[1].pvBuffer;
- }
- else {
- *outToken = nsMemory::Clone(ib[1].pvBuffer, ib[1].cbBuffer);
- free(ib[0].pvBuffer);
- if (!*outToken)
- return NS_ERROR_OUT_OF_MEMORY;
- }
- *outTokenLen = ib[1].cbBuffer;
- }
- else
- free(ib[0].pvBuffer);
- if (!SEC_SUCCESS(rc))
- return NS_ERROR_FAILURE;
- return NS_OK;
- }
- // utility class used to free memory on exit
- class secBuffers
- {
- public:
- SecBuffer ib[3];
- secBuffers() { memset(&ib, 0, sizeof(ib)); }
- ~secBuffers()
- {
- if (ib[0].pvBuffer)
- free(ib[0].pvBuffer);
- if (ib[1].pvBuffer)
- free(ib[1].pvBuffer);
- if (ib[2].pvBuffer)
- free(ib[2].pvBuffer);
- }
- };
- NS_IMETHODIMP
- nsAuthSSPI::Wrap(const void *inToken,
- uint32_t inTokenLen,
- bool confidential,
- void **outToken,
- uint32_t *outTokenLen)
- {
- SECURITY_STATUS rc;
- SecBufferDesc ibd;
- secBuffers bufs;
- SecPkgContext_Sizes sizes;
- rc = (sspi->QueryContextAttributesW)(
- &mCtxt,
- SECPKG_ATTR_SIZES,
- &sizes);
- if (!SEC_SUCCESS(rc))
- return NS_ERROR_FAILURE;
-
- ibd.cBuffers = 3;
- ibd.pBuffers = bufs.ib;
- ibd.ulVersion = SECBUFFER_VERSION;
-
- // SSPI
- bufs.ib[0].cbBuffer = sizes.cbSecurityTrailer;
- bufs.ib[0].BufferType = SECBUFFER_TOKEN;
- bufs.ib[0].pvBuffer = moz_xmalloc(sizes.cbSecurityTrailer);
- if (!bufs.ib[0].pvBuffer)
- return NS_ERROR_OUT_OF_MEMORY;
- // APP Data
- bufs.ib[1].BufferType = SECBUFFER_DATA;
- bufs.ib[1].pvBuffer = moz_xmalloc(inTokenLen);
- bufs.ib[1].cbBuffer = inTokenLen;
-
- if (!bufs.ib[1].pvBuffer)
- return NS_ERROR_OUT_OF_MEMORY;
- memcpy(bufs.ib[1].pvBuffer, inToken, inTokenLen);
- // SSPI
- bufs.ib[2].BufferType = SECBUFFER_PADDING;
- bufs.ib[2].cbBuffer = sizes.cbBlockSize;
- bufs.ib[2].pvBuffer = moz_xmalloc(bufs.ib[2].cbBuffer);
- if (!bufs.ib[2].pvBuffer)
- return NS_ERROR_OUT_OF_MEMORY;
- rc = (sspi->EncryptMessage)(&mCtxt,
- confidential ? 0 : KERB_WRAP_NO_ENCRYPT,
- &ibd, 0);
- if (SEC_SUCCESS(rc)) {
- int len = bufs.ib[0].cbBuffer + bufs.ib[1].cbBuffer + bufs.ib[2].cbBuffer;
- char *p = (char *) moz_xmalloc(len);
- if (!p)
- return NS_ERROR_OUT_OF_MEMORY;
-
- *outToken = (void *) p;
- *outTokenLen = len;
- memcpy(p, bufs.ib[0].pvBuffer, bufs.ib[0].cbBuffer);
- p += bufs.ib[0].cbBuffer;
- memcpy(p,bufs.ib[1].pvBuffer, bufs.ib[1].cbBuffer);
- p += bufs.ib[1].cbBuffer;
- memcpy(p,bufs.ib[2].pvBuffer, bufs.ib[2].cbBuffer);
-
- return NS_OK;
- }
- return NS_ERROR_FAILURE;
- }
|