Commit efcaf97c authored by Michael Kaply's avatar Michael Kaply Committed by dsmith@mozilla.com
Browse files

Bug 1179722 - Send SSPI channel bindings on the initial InitializeSecurityContext call. a=diannaS

The SEC_CHANNEL_BINDINGS buffer was only ever built on continuation calls. That
is sufficient for raw NTLM, whose channel binding travels in message 3, but
SPNEGO carries the AP-REQ in the very first token, so Kerberos needs the
bindings from the initial call on and never received them.

Move the token construction into a helper and attach it whenever a certificate
is available, building the input buffer descriptor up front so it can be passed
with no accompanying token. Keep the certificate for the lifetime of the
sequence instead of freeing it after first use, so every leg of a multi-leg
exchange carries the bindings.

The exemption for PACKAGE_TYPE_NTLM covers the sys-ntlm module only, which
keeps behaving exactly as it does today. NTLM negotiated inside SPNEGO runs
under PACKAGE_TYPE_NEGOTIATE and will now carry bindings from the first call:
that is the main behaviour change here and the one worth reviewing closely,
since raw NTLM is deliberately untouched.

On its own this commit changes nothing, because no caller supplies a
certificate on the Negotiate path until the next one.

Original Revision: https://phabricator.services.mozilla.com/D316807

Differential Revision: https://phabricator.services.mozilla.com/D317836
parent 394a5057
Loading
Loading
Loading
Loading
+147 −144
Original line number Diff line number Diff line
@@ -184,8 +184,9 @@ nsAuthSSPI::Init(const nsACString& aServiceName, uint32_t aServiceFlags,
  LOG(("  nsAuthSSPI::Init\n"));

  mIsFirst = true;
  mCertDERLength = 0;
  free(mCertDERData);
  mCertDERData = nullptr;
  mCertDERLength = 0;

  // The caller must supply a service name to be used. (For why we now require
  // a service name for NTLM, see bug 487872.)
@@ -272,69 +273,12 @@ nsAuthSSPI::Init(const nsACString& aServiceName, uint32_t aServiceFlags,
  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) {
nsresult nsAuthSSPI::MakeChannelBindings(mozilla::UniqueFreePtr<char>& aBuffer,
                                         uint32_t& aBufferLength) {
  // String for end-point bindings.
  const char end_point[] = "tls-server-end-point:";
  const int end_point_length = sizeof(end_point) - 1;
  const uint32_t end_point_length = sizeof(end_point) - 1;

  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);
      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) {
  // Default to SHA256 for compatibility, but detect SHA384 and SHA512
  uint32_t hashAlgorithm = nsICryptoHash::SHA256;
  uint32_t hashSize = 32;  // SHA256 hash size
@@ -349,8 +293,8 @@ nsAuthSSPI::GetNextToken(const void* inToken, uint32_t inTokenLen,
    using namespace mozilla::pkix;
    Input certDER;

          mozilla::pkix::Result pkixResult = certDER.Init(
              static_cast<const uint8_t*>(mCertDERData), mCertDERLength);
    mozilla::pkix::Result pkixResult =
        certDER.Init(static_cast<const uint8_t*>(mCertDERData), mCertDERLength);
    if (pkixResult != Success) {
      return;
    }
@@ -391,33 +335,26 @@ nsAuthSSPI::GetNextToken(const void* inToken, uint32_t inTokenLen,
  }();

  // Create Endpoint Binding structure with correct size
        const int cbt_size = hashSize + end_point_length;
        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);
  SEC_CHANNEL_BINDINGS endpointBinding;
  endpointBinding.dwInitiatorAddrType = 0;
  endpointBinding.cbInitiatorLength = 0;
  endpointBinding.dwInitiatorOffset = 0;
  endpointBinding.dwAcceptorAddrType = 0;
  endpointBinding.cbAcceptorLength = 0;
  endpointBinding.dwAcceptorOffset = 0;
  endpointBinding.cbApplicationDataLength = hashSize + end_point_length;
  endpointBinding.dwApplicationDataOffset = sizeof(SEC_CHANNEL_BINDINGS);

  aBufferLength = endpointBinding.cbApplicationDataLength +
                  endpointBinding.dwApplicationDataOffset;
  aBuffer.reset(static_cast<char*>(moz_xmalloc(aBufferLength)));

  // Helper to write in the memory block that stores the CBT
        char* sspi_cbt_ptr = sspi_cbt;
  char* sspi_cbt_ptr = aBuffer.get();

        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, &endpointBinding,
         endpointBinding.dwApplicationDataOffset);
  sspi_cbt_ptr += endpointBinding.dwApplicationDataOffset;

  memcpy(sspi_cbt_ptr, end_point, end_point_length);
  sspi_cbt_ptr += end_point_length;
@@ -436,10 +373,6 @@ nsAuthSSPI::GetNextToken(const void* inToken, uint32_t inTokenLen,
  if (NS_SUCCEEDED(rv)) rv = crypto->Finish(false, hashString);

  if (NS_FAILED(rv)) {
          free(mCertDERData);
          mCertDERData = nullptr;
          mCertDERLength = 0;
          free(sspi_cbt);
    return rv;
  }

@@ -447,17 +380,63 @@ nsAuthSSPI::GetNextToken(const void* inToken, uint32_t inTokenLen,
  // structure and the "tls-server-end-point:" char array
  memcpy(sspi_cbt_ptr, hashString.get(), hashSize);

        // Free memory used to store the server certificate
        free(mCertDERData);
        mCertDERData = nullptr;
        mCertDERLength = 0;
      }  // End of CBT computation.
  return NS_OK;
}

      // We always need this SECBUFFER.
      ib[ibd.cBuffers].BufferType = SECBUFFER_TOKEN;
      ib[ibd.cBuffers].cbBuffer = inTokenLen;
      ib[ibd.cBuffers].pvBuffer = (void*)inToken;
      ibd.cBuffers++;
// 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) {
  SECURITY_STATUS rc;
  MS_TimeStamp ignored;

  DWORD ctxAttr, ctxReq = 0;
  CtxtHandle* ctxIn = nullptr;
  SecBufferDesc ibd, obd;
  // Optional second input buffer for the CBT (Channel Binding Token)
  SecBuffer ib[2], ob;
  // Block of memory that stores the CBT
  mozilla::UniqueFreePtr<char> sspi_cbt;

  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;

  ibd.ulVersion = SECBUFFER_VERSION;
  ibd.cBuffers = 0;
  ibd.pBuffers = ib;

  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);
      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;
      }
      // The certificate needs to be erased before being passed
      // to InitializeSecurityContextW().
      inToken = nullptr;
      inTokenLen = 0;
    } else {
      ctxIn = &mCtxt;
    }
  } else {  // First time and without a token (no server certificate)
@@ -469,10 +448,36 @@ nsAuthSSPI::GetNextToken(const void* inToken, uint32_t inTokenLen,
      LOG(("Cannot restart authentication sequence!"));
      return NS_ERROR_UNEXPECTED;
    }
    ctxIn = nullptr;
    mIsFirst = false;
  }

  // If we have stored a certificate, the Channel Binding Token needs to be
  // generated and sent in the first input buffer. Kerberos carries the AP-REQ
  // in the very first token, so for SPNEGO the bindings have to be attached
  // from the initial call on. Raw NTLM instead carries them in message 3,
  // generated on a continuation call, so that package keeps sending them only
  // once a context exists.
  bool haveContext = mCtxt.dwLower || mCtxt.dwUpper;
  if (mCertDERLength > 0 && (haveContext || mPackage != PACKAGE_TYPE_NTLM)) {
    uint32_t cbtLength = 0;
    nsresult rv = MakeChannelBindings(sspi_cbt, cbtLength);
    if (NS_FAILED(rv)) {
      return rv;
    }

    ib[ibd.cBuffers].BufferType = SECBUFFER_CHANNEL_BINDINGS;
    ib[ibd.cBuffers].cbBuffer = cbtLength;
    ib[ibd.cBuffers].pvBuffer = sspi_cbt.get();
    ibd.cBuffers++;
  }

  if (inToken) {
    ib[ibd.cBuffers].BufferType = SECBUFFER_TOKEN;
    ib[ibd.cBuffers].cbBuffer = inTokenLen;
    ib[ibd.cBuffers].pvBuffer = (void*)inToken;
    ibd.cBuffers++;
  }

  obd.ulVersion = SECBUFFER_VERSION;
  obd.cBuffers = 1;
  obd.pBuffers = &ob;
@@ -486,15 +491,13 @@ nsAuthSSPI::GetNextToken(const void* inToken, uint32_t inTokenLen,

  rc = (sspi->InitializeSecurityContextW)(
      &mCred, ctxIn, sn, ctxReq, 0, SECURITY_NATIVE_DREP,
      inToken ? &ibd : nullptr, 0, &mCtxt, &obd, &ctxAttr, &ignored);
      ibd.cBuffers ? &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;
+6 −0
Original line number Diff line number Diff line
@@ -9,6 +9,7 @@
#include "nsAuth.h"
#include "nsIAuthModule.h"
#include "nsString.h"
#include "mozilla/UniquePtrExtensions.h"

#include <windows.h>

@@ -43,6 +44,11 @@ class nsAuthSSPI final : public nsIAuthModule {
 private:
  nsresult MakeSN(const nsACString& principal, nsCString& result);

  // Builds the SEC_CHANNEL_BINDINGS blob describing the "tls-server-end-point"
  // binding for the server certificate stored in mCertDERData.
  nsresult MakeChannelBindings(mozilla::UniqueFreePtr<char>& aBuffer,
                               uint32_t& aBufferLength);

  CredHandle mCred;
  CtxtHandle mCtxt;
  nsCString mServiceName;