Commit dc33837c authored by Kershaw Chang's avatar Kershaw Chang
Browse files

Bug 1754744 - isolating TLS handshake code, r=necko-reviewers,dragana

parent 7f4ca379
Loading
Loading
Loading
Loading
+3 −2
Original line number Diff line number Diff line
@@ -139,8 +139,9 @@ void DnsAndConnectSocket::PrintDiagnostics(nsCString& log) {
void nsHttpConnection::PrintDiagnostics(nsCString& log) {
  log.AppendPrintf("    CanDirectlyActivate = %d\n", CanDirectlyActivate());

  log.AppendPrintf("    npncomplete = %d  setupSSLCalled = %d\n", mNPNComplete,
                   mSetupSSLCalled);
  log.AppendPrintf("    npncomplete = %d  setupSSLCalled = %d\n",
                   mTlsHandshaker->NPNComplete(),
                   mTlsHandshaker->SetupSSLCalled());

  log.AppendPrintf("    spdyVersion = %d  reportedSpdy = %d everspdy = %d\n",
                   static_cast<int32_t>(mUsingSpdyVersion), mReportedSpdy,
+332 −0
Original line number Diff line number Diff line
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=4 sw=2 sts=2 et cin: */
/* 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/. */

// HttpLog.h should generally be included first
#include "HttpLog.h"

#include "TlsHandshaker.h"
#include "nsHttpConnectionInfo.h"
#include "nsHttpConnection.h"
#include "nsHttpHandler.h"
#include "nsISSLSocketControl.h"
#include "mozilla/StaticPrefs_network.h"

#define TLS_EARLY_DATA_NOT_AVAILABLE 0
#define TLS_EARLY_DATA_AVAILABLE_BUT_NOT_USED 1
#define TLS_EARLY_DATA_AVAILABLE_AND_USED 2

namespace mozilla::net {

NS_IMPL_ISUPPORTS(TlsHandshaker, nsITlsHandshakeCallbackListener)

TlsHandshaker::TlsHandshaker(nsHttpConnectionInfo* aInfo,
                             nsHttpConnection* aOwner)
    : mConnInfo(aInfo), mOwner(aOwner) {
  LOG(("TlsHandshaker ctor %p", this));
}

TlsHandshaker::~TlsHandshaker() { LOG(("TlsHandshaker dtor %p", this)); }

NS_IMETHODIMP
TlsHandshaker::HandshakeDone() {
  LOG(("TlsHandshaker::HandshakeDone mOwner=%p", mOwner.get()));
  if (mOwner) {
    mTlsHandshakeComplitionPending = true;

    // HandshakeDone needs to be dispatched so that it is not called inside
    // nss locks.
    RefPtr<TlsHandshaker> self(this);
    NS_DispatchToCurrentThread(NS_NewRunnableFunction(
        "TlsHandshaker::HandshakeDoneInternal", [self{std::move(self)}]() {
          if (self->mTlsHandshakeComplitionPending && self->mOwner) {
            self->mOwner->HandshakeDoneInternal();
            self->mTlsHandshakeComplitionPending = false;
          }
        }));
  }
  return NS_OK;
}

void TlsHandshaker::SetupSSL(bool aInSpdyTunnel, bool aForcePlainText) {
  if (!mOwner) {
    return;
  }

  LOG1(("TlsHandshaker::SetupSSL %p caps=0x%X %s\n", mOwner.get(),
        mOwner->TransactionCaps(), mConnInfo->HashKey().get()));

  if (mSetupSSLCalled) {  // do only once
    return;
  }
  mSetupSSLCalled = true;

  if (mNPNComplete) {
    return;
  }

  // we flip this back to false if SetNPNList succeeds at the end
  // of this function
  mNPNComplete = true;

  if (!mConnInfo->FirstHopSSL() || aForcePlainText) {
    return;
  }

  // if we are connected to the proxy with TLS, start the TLS
  // flow immediately without waiting for a CONNECT sequence.
  DebugOnly<nsresult> rv{};
  if (aInSpdyTunnel) {
    rv = InitSSLParams(false, true);
  } else {
    bool usingHttpsProxy = mConnInfo->UsingHttpsProxy();
    rv = InitSSLParams(usingHttpsProxy, usingHttpsProxy);
  }
  MOZ_ASSERT(NS_SUCCEEDED(rv));
}

nsresult TlsHandshaker::InitSSLParams(bool connectingToProxy,
                                      bool proxyStartSSL) {
  LOG(("TlsHandshaker::InitSSLParams [mOwner=%p] connectingToProxy=%d\n",
       mOwner.get(), connectingToProxy));
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");

  if (!mOwner) {
    return NS_ERROR_ABORT;
  }

  nsresult rv;
  nsCOMPtr<nsISupports> securityInfo;
  mOwner->GetSecurityInfo(getter_AddRefs(securityInfo));
  if (!securityInfo) {
    return NS_ERROR_FAILURE;
  }

  nsCOMPtr<nsISSLSocketControl> ssl = do_QueryInterface(securityInfo, &rv);
  if (NS_FAILED(rv)) {
    return rv;
  }

  // If proxy is use or 0RTT is excluded for a origin, don't use early-data.
  if (mConnInfo->UsingProxy() || gHttpHandler->Is0RttTcpExcluded(mConnInfo)) {
    ssl->DisableEarlyData();
  }

  if (proxyStartSSL) {
    rv = ssl->ProxyStartSSL();
    if (NS_FAILED(rv)) {
      return rv;
    }
  }

  if (NS_SUCCEEDED(SetupNPNList(ssl, mOwner->TransactionCaps())) &&
      NS_SUCCEEDED(ssl->SetHandshakeCallbackListener(this))) {
    LOG(("InitSSLParams Setting up SPDY Negotiation OK mOwner=%p",
         mOwner.get()));
    mNPNComplete = false;
  }

  return NS_OK;
}

// The naming of NPN is historical - this function creates the basic
// offer list for both NPN and ALPN. ALPN validation callbacks are made
// now before the handshake is complete, and NPN validation callbacks
// are made during the handshake.
nsresult TlsHandshaker::SetupNPNList(nsISSLSocketControl* ssl, uint32_t caps) {
  nsTArray<nsCString> protocolArray;

  nsCString npnToken = mConnInfo->GetNPNToken();
  if (npnToken.IsEmpty()) {
    // The first protocol is used as the fallback if none of the
    // protocols supported overlap with the server's list.
    // When using ALPN the advertised preferences are protocolArray indicies
    // {1, .., N, 0} in decreasing order.
    // For NPN, In the case of overlap, matching priority is driven by
    // the order of the server's advertisement - with index 0 used when
    // there is no match.
    protocolArray.AppendElement("http/1.1"_ns);

    if (StaticPrefs::network_http_http2_enabled() &&
        !(caps & NS_HTTP_DISALLOW_SPDY)) {
      LOG(("nsHttpConnection::SetupSSL Allow SPDY NPN selection"));
      const SpdyInformation* info = gHttpHandler->SpdyInfo();
      if (info->ALPNCallbacks(ssl)) {
        protocolArray.AppendElement(info->VersionString);
      }
    }
  } else {
    LOG(("nsHttpConnection::SetupSSL limiting NPN selection to %s",
         npnToken.get()));
    protocolArray.AppendElement(npnToken);
  }

  nsresult rv = ssl->SetNPNList(protocolArray);
  LOG(("TlsHandshaker::SetupNPNList %p %" PRIx32 "\n", mOwner.get(),
       static_cast<uint32_t>(rv)));
  return rv;
}

// Checks if TLS handshake is needed and it is responsible to move it forward.
bool TlsHandshaker::EnsureNPNComplete() {
  if (!mOwner) {
    mNPNComplete = true;
    return true;
  }

  nsCOMPtr<nsISocketTransport> transport = mOwner->Transport();
  MOZ_ASSERT(transport);
  if (!transport) {
    // this cannot happen
    mNPNComplete = true;
    return true;
  }

  if (mNPNComplete) {
    return true;
  }

  if (mTlsHandshakeComplitionPending) {
    return false;
  }

  nsresult rv = NS_OK;
  nsCOMPtr<nsISupports> securityInfo;
  mOwner->GetSecurityInfo(getter_AddRefs(securityInfo));
  if (!securityInfo) {
    FinishNPNSetup(false, false);
    return true;
  }

  nsCOMPtr<nsISSLSocketControl> ssl = do_QueryInterface(securityInfo, &rv);
  if (NS_FAILED(rv)) {
    FinishNPNSetup(false, false);
    return true;
  }

  if (!m0RTTChecked) {
    // We reuse m0RTTChecked. We want to send this status only once.
    RefPtr<nsAHttpTransaction> transaction = mOwner->Transaction();
    nsCOMPtr<nsISocketTransport> transport = mOwner->Transport();
    if (transaction && transport) {
      transaction->OnTransportStatus(transport,
                                     NS_NET_STATUS_TLS_HANDSHAKE_STARTING, 0);
    }
  }

  LOG(("TlsHandshaker::EnsureNPNComplete [mOwner=%p] drive TLS handshake",
       mOwner.get()));
  rv = ssl->DriveHandshake();
  if (NS_FAILED(rv) && rv != NS_BASE_STREAM_WOULD_BLOCK) {
    FinishNPNSetup(false, true);
    return true;
  }

  Check0RttEnabled(ssl);
  return false;
}

void TlsHandshaker::EarlyDataDone() {
  if (mEarlyDataState == EarlyData::USED) {
    mEarlyDataState = EarlyData::DONE_USED;
  } else if (mEarlyDataState == EarlyData::CANNOT_BE_USED) {
    mEarlyDataState = EarlyData::DONE_CANNOT_BE_USED;
  } else if (mEarlyDataState == EarlyData::NOT_AVAILABLE) {
    mEarlyDataState = EarlyData::DONE_NOT_AVAILABLE;
  }
}

void TlsHandshaker::FinishNPNSetup(bool handshakeSucceeded,
                                   bool hasSecurityInfo) {
  LOG(("TlsHandshaker::FinishNPNSetup mOwner=%p", mOwner.get()));
  mNPNComplete = true;

  mOwner->PostProcessNPNSetup(handshakeSucceeded, hasSecurityInfo,
                              EarlyDataUsed());
  EarlyDataDone();
}

void TlsHandshaker::Check0RttEnabled(nsISSLSocketControl* ssl) {
  if (!mOwner) {
    return;
  }

  if (m0RTTChecked) {
    return;
  }

  m0RTTChecked = true;

  if (mConnInfo->UsingProxy()) {
    return;
  }

  // There is no ALPN info (yet!). We need to consider doing 0RTT. We
  // will do so if there is ALPN information from a previous session
  // (AlpnEarlySelection), we are using HTTP/1, and the request data can
  // be safely retried.
  if (NS_FAILED(ssl->GetAlpnEarlySelection(mEarlyNegotiatedALPN))) {
    LOG1(
        ("TlsHandshaker::Check0RttEnabled %p - "
         "early selected alpn not available",
         mOwner.get()));
  } else {
    LOG1(
        ("TlsHandshaker::Check0RttEnabled %p -"
         "early selected alpn: %s",
         mOwner.get(), mEarlyNegotiatedALPN.get()));
    const SpdyInformation* info = gHttpHandler->SpdyInfo();
    if (!mEarlyNegotiatedALPN.Equals(info->VersionString)) {
      // This is the HTTP/1 case.
      // Check if early-data is allowed for this transaction.
      RefPtr<nsAHttpTransaction> transaction = mOwner->Transaction();
      if (transaction && transaction->Do0RTT()) {
        LOG(
            ("TlsHandshaker::Check0RttEnabled [mOwner=%p] - We "
             "can do 0RTT (http/1)!",
             mOwner.get()));
        mEarlyDataState = EarlyData::USED;
      } else {
        mEarlyDataState = EarlyData::CANNOT_BE_USED;
        // Poll for read now. Polling for write will cause us to busy wait.
        // When the handshake is done the polling flags will be set correctly.
        Unused << mOwner->ResumeRecv();
      }
    } else {
      // We have h2, we can at least 0-RTT the preamble and opening
      // SETTINGS, etc, and maybe some of the first request
      LOG(
          ("TlsHandshaker::Check0RttEnabled [mOwner=%p] - Starting "
           "0RTT for h2!",
           mOwner.get()));
      mEarlyDataState = EarlyData::USED;
      mOwner->Start0RTTSpdy(info->Version);
    }
  }
}

void TlsHandshaker::EarlyDataTelemetry(int16_t tlsVersion,
                                       bool earlyDataAccepted,
                                       int64_t aContentBytesWritten0RTT) {
  // Send the 0RTT telemetry only for tls1.3
  if (tlsVersion > nsISSLSocketControl::TLS_VERSION_1_2) {
    Telemetry::Accumulate(Telemetry::TLS_EARLY_DATA_NEGOTIATED,
                          (mEarlyDataState == EarlyData::NOT_AVAILABLE)
                              ? TLS_EARLY_DATA_NOT_AVAILABLE
                              : ((mEarlyDataState == EarlyData::USED)
                                     ? TLS_EARLY_DATA_AVAILABLE_AND_USED
                                     : TLS_EARLY_DATA_AVAILABLE_BUT_NOT_USED));
    if (EarlyDataUsed()) {
      Telemetry::Accumulate(Telemetry::TLS_EARLY_DATA_ACCEPTED,
                            earlyDataAccepted);
    }
    if (earlyDataAccepted) {
      Telemetry::Accumulate(Telemetry::TLS_EARLY_DATA_BYTES_WRITTEN,
                            aContentBytesWritten0RTT);
    }
  }
}

}  // namespace mozilla::net
+94 −0
Original line number Diff line number Diff line
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */

#ifndef TlsHandshaker_h__
#define TlsHandshaker_h__

#include "nsITlsHandshakeListener.h"

class nsISocketTransport;
class nsISSLSocketControl;

namespace mozilla::net {

class nsHttpConnection;
class nsHttpConnectionInfo;

class TlsHandshaker : public nsITlsHandshakeCallbackListener {
 public:
  NS_DECL_THREADSAFE_ISUPPORTS
  NS_DECL_NSITLSHANDSHAKECALLBACKLISTENER

  TlsHandshaker(nsHttpConnectionInfo* aInfo, nsHttpConnection* aOwner);

  void SetupSSL(bool aInSpdyTunnel, bool aForcePlainText);
  [[nodiscard]] nsresult InitSSLParams(bool connectingToProxy,
                                       bool ProxyStartSSL);
  [[nodiscard]] nsresult SetupNPNList(nsISSLSocketControl* ssl, uint32_t caps);
  // Makes certain the SSL handshake is complete and NPN negotiation
  // has had a chance to happen
  [[nodiscard]] bool EnsureNPNComplete();
  void FinishNPNSetup(bool handshakeSucceeded, bool hasSecurityInfo);
  bool EarlyDataAvailable() const {
    return mEarlyDataState == EarlyData::USED ||
           mEarlyDataState == EarlyData::CANNOT_BE_USED;
  }
  bool EarlyDataWasAvailable() const {
    return mEarlyDataState != EarlyData::NOT_AVAILABLE &&
           mEarlyDataState != EarlyData::DONE_NOT_AVAILABLE;
  }
  bool EarlyDataUsed() const { return mEarlyDataState == EarlyData::USED; }
  bool EarlyDataCanNotBeUsed() const {
    return mEarlyDataState == EarlyData::CANNOT_BE_USED;
  }
  void EarlyDataDone();
  void EarlyDataTelemetry(int16_t tlsVersion, bool earlyDataAccepted,
                          int64_t aContentBytesWritten0RTT);

  bool NPNComplete() const { return mNPNComplete; }
  bool SetupSSLCalled() const { return mSetupSSLCalled; }
  bool TlsHandshakeComplitionPending() const {
    return mTlsHandshakeComplitionPending;
  }
  const nsCString& EarlyNegotiatedALPN() const { return mEarlyNegotiatedALPN; }
  void SetNPNComplete() { mNPNComplete = true; }
  void NotifyClose() {
    mTlsHandshakeComplitionPending = false;
    mOwner = nullptr;
  }

 private:
  virtual ~TlsHandshaker();

  void Check0RttEnabled(nsISSLSocketControl* ssl);

  // SPDY related
  bool mSetupSSLCalled{false};
  bool mNPNComplete{false};

  bool mTlsHandshakeComplitionPending{false};
  // Helper variable for 0RTT handshake;
  // Possible 0RTT has been checked.
  bool m0RTTChecked{false};
  // 0RTT data state.
  enum EarlyData {
    NOT_AVAILABLE,
    USED,
    CANNOT_BE_USED,
    DONE_NOT_AVAILABLE,
    DONE_USED,
    DONE_CANNOT_BE_USED,
  };
  EarlyData mEarlyDataState{EarlyData::NOT_AVAILABLE};
  nsCString mEarlyNegotiatedALPN;
  RefPtr<nsHttpConnectionInfo> mConnInfo;
  // nsHttpConnection and TlsHandshaker create a reference cycle. To break this
  // cycle, NotifyClose() needs to be called in nsHttpConnection::Close().
  RefPtr<nsHttpConnection> mOwner;
};

}  // namespace mozilla::net

#endif  // TlsHandshaker_h__
+1 −0
Original line number Diff line number Diff line
@@ -153,6 +153,7 @@ UNIFIED_SOURCES += [
    "SocketWrapper.cpp",
    "SpeculativeTransaction.cpp",
    "TLSFilterTransaction.cpp",
    "TlsHandshaker.cpp",
    "TRRServiceChannel.cpp",
]

+46 −312

File changed.

Preview size limit exceeded, changes collapsed.

Loading