tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

TlsHandshaker.cpp (12273B)


      1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim:set ts=4 sw=2 sts=2 et cin: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 // HttpLog.h should generally be included first
      8 #include "HttpLog.h"
      9 
     10 #include "TlsHandshaker.h"
     11 #include "mozilla/StaticPrefs_network.h"
     12 #include "nsHttpConnection.h"
     13 #include "nsHttpConnectionInfo.h"
     14 #include "nsHttpHandler.h"
     15 #include "nsITLSSocketControl.h"
     16 #include "mozilla/glean/NetwerkProtocolHttpMetrics.h"
     17 
     18 #define TLS_EARLY_DATA_NOT_AVAILABLE 0
     19 #define TLS_EARLY_DATA_AVAILABLE_BUT_NOT_USED 1
     20 #define TLS_EARLY_DATA_AVAILABLE_AND_USED 2
     21 
     22 namespace mozilla::net {
     23 
     24 NS_IMPL_ISUPPORTS(TlsHandshaker, nsITlsHandshakeCallbackListener)
     25 
     26 TlsHandshaker::TlsHandshaker(nsHttpConnectionInfo* aInfo,
     27                             nsHttpConnection* aOwner)
     28    : mConnInfo(aInfo), mOwner(aOwner) {
     29  LOG(("TlsHandshaker ctor %p", this));
     30 }
     31 
     32 TlsHandshaker::~TlsHandshaker() { LOG(("TlsHandshaker dtor %p", this)); }
     33 
     34 NS_IMETHODIMP
     35 TlsHandshaker::CertVerificationDone() {
     36  LOG(("TlsHandshaker::CertVerificationDone mOwner=%p", mOwner.get()));
     37  if (mOwner) {
     38    (void)mOwner->ResumeSend();
     39  }
     40  return NS_OK;
     41 }
     42 
     43 NS_IMETHODIMP
     44 TlsHandshaker::ClientAuthCertificateSelected() {
     45  LOG(("TlsHandshaker::ClientAuthCertificateSelected mOwner=%p", mOwner.get()));
     46  if (mOwner) {
     47    (void)mOwner->ResumeSend();
     48  }
     49  return NS_OK;
     50 }
     51 
     52 NS_IMETHODIMP
     53 TlsHandshaker::HandshakeDone() {
     54  LOG(("TlsHandshaker::HandshakeDone mOwner=%p", mOwner.get()));
     55  if (mOwner) {
     56    mTlsHandshakeComplitionPending = true;
     57 
     58    // HandshakeDone needs to be dispatched so that it is not called inside
     59    // nss locks.
     60    RefPtr<TlsHandshaker> self(this);
     61    NS_DispatchToCurrentThread(NS_NewRunnableFunction(
     62        "TlsHandshaker::HandshakeDoneInternal", [self{std::move(self)}]() {
     63          if (self->mTlsHandshakeComplitionPending && self->mOwner) {
     64            self->mOwner->HandshakeDoneInternal();
     65            self->mTlsHandshakeComplitionPending = false;
     66          }
     67        }));
     68  }
     69  return NS_OK;
     70 }
     71 
     72 void TlsHandshaker::SetupSSL(bool aInSpdyTunnel, bool aForcePlainText) {
     73  if (!mOwner) {
     74    return;
     75  }
     76 
     77  LOG1(("TlsHandshaker::SetupSSL %p caps=0x%X %s\n", mOwner.get(),
     78        mOwner->TransactionCaps(), mConnInfo->HashKey().get()));
     79 
     80  if (mSetupSSLCalled) {  // do only once
     81    return;
     82  }
     83  mSetupSSLCalled = true;
     84 
     85  if (mNPNComplete) {
     86    return;
     87  }
     88 
     89  // we flip this back to false if SetNPNList succeeds at the end
     90  // of this function
     91  mNPNComplete = true;
     92 
     93  if (!mConnInfo->FirstHopSSL() || aForcePlainText) {
     94    return;
     95  }
     96 
     97  // if we are connected to the proxy with TLS, start the TLS
     98  // flow immediately without waiting for a CONNECT sequence.
     99  DebugOnly<nsresult> rv{};
    100  if (aInSpdyTunnel) {
    101    rv = InitSSLParams(false, true);
    102  } else {
    103    bool usingHttpsProxy = mConnInfo->UsingHttpsProxy();
    104    rv = InitSSLParams(usingHttpsProxy, usingHttpsProxy);
    105  }
    106 }
    107 
    108 nsresult TlsHandshaker::InitSSLParams(bool connectingToProxy,
    109                                      bool proxyStartSSL) {
    110  LOG(("TlsHandshaker::InitSSLParams [mOwner=%p] connectingToProxy=%d\n",
    111       mOwner.get(), connectingToProxy));
    112  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
    113 
    114  if (!mOwner) {
    115    return NS_ERROR_ABORT;
    116  }
    117 
    118  nsCOMPtr<nsITLSSocketControl> ssl;
    119  mOwner->GetTLSSocketControl(getter_AddRefs(ssl));
    120  if (!ssl) {
    121    LOG(("Can't find tls socket control"));
    122    return NS_ERROR_FAILURE;
    123  }
    124 
    125  // If proxy is use or 0RTT is excluded for a origin, don't use early-data.
    126  if (mConnInfo->UsingProxy() || gHttpHandler->Is0RttTcpExcluded(mConnInfo)) {
    127    ssl->DisableEarlyData();
    128  }
    129 
    130  if (proxyStartSSL) {
    131    nsresult rv = ssl->ProxyStartSSL();
    132    if (NS_FAILED(rv)) {
    133      return rv;
    134    }
    135  }
    136 
    137  if (NS_SUCCEEDED(
    138          SetupNPNList(ssl, mOwner->TransactionCaps(), connectingToProxy)) &&
    139      NS_SUCCEEDED(ssl->SetHandshakeCallbackListener(this))) {
    140    LOG(("InitSSLParams Setting up SPDY Negotiation OK mOwner=%p",
    141         mOwner.get()));
    142    ReportSecureConnectionStart();
    143    mNPNComplete = false;
    144  }
    145 
    146  return NS_OK;
    147 }
    148 
    149 // The naming of NPN is historical - this function creates the basic
    150 // offer list for both NPN and ALPN. ALPN validation callbacks are made
    151 // now before the handshake is complete, and NPN validation callbacks
    152 // are made during the handshake.
    153 nsresult TlsHandshaker::SetupNPNList(nsITLSSocketControl* ssl, uint32_t caps,
    154                                     bool connectingToProxy) {
    155  nsTArray<nsCString> protocolArray;
    156 
    157  // The first protocol is used as the fallback if none of the
    158  // protocols supported overlap with the server's list.
    159  // When using ALPN the advertised preferences are protocolArray indicies
    160  // {1, .., N, 0} in decreasing order.
    161  // For NPN, In the case of overlap, matching priority is driven by
    162  // the order of the server's advertisement - with index 0 used when
    163  // there is no match.
    164  protocolArray.AppendElement("http/1.1"_ns);
    165 
    166  if (StaticPrefs::network_http_http2_enabled() &&
    167      (connectingToProxy || !(caps & NS_HTTP_DISALLOW_SPDY)) &&
    168      !(connectingToProxy && (caps & NS_HTTP_DISALLOW_HTTP2_PROXY))) {
    169    LOG(("nsHttpConnection::SetupSSL Allow SPDY NPN selection"));
    170    const SpdyInformation* info = gHttpHandler->SpdyInfo();
    171    if (info->ALPNCallbacks(ssl)) {
    172      protocolArray.AppendElement(info->VersionString);
    173    }
    174  } else {
    175    LOG(("nsHttpConnection::SetupSSL Disallow SPDY NPN selection"));
    176  }
    177 
    178  nsresult rv = ssl->SetNPNList(protocolArray);
    179  LOG(("TlsHandshaker::SetupNPNList %p %" PRIx32 "\n", mOwner.get(),
    180       static_cast<uint32_t>(rv)));
    181  return rv;
    182 }
    183 
    184 // Checks if TLS handshake is needed and it is responsible to move it forward.
    185 bool TlsHandshaker::EnsureNPNComplete() {
    186  if (!mOwner) {
    187    mNPNComplete = true;
    188    return true;
    189  }
    190 
    191  nsCOMPtr<nsISocketTransport> transport = mOwner->Transport();
    192  MOZ_ASSERT(transport);
    193  if (!transport) {
    194    // this cannot happen
    195    mNPNComplete = true;
    196    return true;
    197  }
    198 
    199  if (mNPNComplete) {
    200    return true;
    201  }
    202 
    203  if (mTlsHandshakeComplitionPending) {
    204    return false;
    205  }
    206 
    207  nsCOMPtr<nsITLSSocketControl> ssl;
    208  mOwner->GetTLSSocketControl(getter_AddRefs(ssl));
    209  if (!ssl) {
    210    FinishNPNSetup(false, false);
    211    return true;
    212  }
    213 
    214  LOG(("TlsHandshaker::EnsureNPNComplete [mOwner=%p] drive TLS handshake",
    215       mOwner.get()));
    216  ReportSecureConnectionStart();
    217  nsresult rv = ssl->DriveHandshake();
    218  if (NS_FAILED(rv) && rv != NS_BASE_STREAM_WOULD_BLOCK) {
    219    FinishNPNSetup(false, true);
    220    return true;
    221  }
    222 
    223  Check0RttEnabled(ssl);
    224  return false;
    225 }
    226 
    227 void TlsHandshaker::EarlyDataDone() {
    228  if (mEarlyDataState == EarlyData::USED) {
    229    mEarlyDataState = EarlyData::DONE_USED;
    230  } else if (mEarlyDataState == EarlyData::CANNOT_BE_USED) {
    231    mEarlyDataState = EarlyData::DONE_CANNOT_BE_USED;
    232  } else if (mEarlyDataState == EarlyData::NOT_AVAILABLE) {
    233    mEarlyDataState = EarlyData::DONE_NOT_AVAILABLE;
    234  }
    235 }
    236 
    237 void TlsHandshaker::FinishNPNSetup(bool handshakeSucceeded,
    238                                   bool hasSecurityInfo) {
    239  LOG(("TlsHandshaker::FinishNPNSetup mOwner=%p", mOwner.get()));
    240  mNPNComplete = true;
    241 
    242  mOwner->PostProcessNPNSetup(handshakeSucceeded, hasSecurityInfo,
    243                              EarlyDataUsed());
    244  EarlyDataDone();
    245 }
    246 
    247 void TlsHandshaker::Check0RttEnabled(nsITLSSocketControl* ssl) {
    248  if (!mOwner) {
    249    return;
    250  }
    251 
    252  if (m0RTTChecked) {
    253    return;
    254  }
    255 
    256  m0RTTChecked = true;
    257 
    258  if (mConnInfo->UsingProxy()) {
    259    return;
    260  }
    261 
    262  // There is no ALPN info (yet!). We need to consider doing 0RTT. We
    263  // will do so if there is ALPN information from a previous session
    264  // (AlpnEarlySelection), we are using HTTP/1, and the request data can
    265  // be safely retried.
    266  if (NS_FAILED(ssl->GetAlpnEarlySelection(mEarlyNegotiatedALPN))) {
    267    LOG1(
    268        ("TlsHandshaker::Check0RttEnabled %p - "
    269         "early selected alpn not available",
    270         mOwner.get()));
    271  } else {
    272    mOwner->ChangeConnectionState(ConnectionState::ZERORTT);
    273    LOG1(
    274        ("TlsHandshaker::Check0RttEnabled %p -"
    275         "early selected alpn: %s",
    276         mOwner.get(), mEarlyNegotiatedALPN.get()));
    277    const SpdyInformation* info = gHttpHandler->SpdyInfo();
    278    if (!mEarlyNegotiatedALPN.Equals(info->VersionString)) {
    279      // This is the HTTP/1 case.
    280      // Check if early-data is allowed for this transaction.
    281      RefPtr<nsAHttpTransaction> transaction = mOwner->Transaction();
    282      if (transaction && transaction->Do0RTT()) {
    283        LOG(
    284            ("TlsHandshaker::Check0RttEnabled [mOwner=%p] - We "
    285             "can do 0RTT (http/1)!",
    286             mOwner.get()));
    287        mEarlyDataState = EarlyData::USED;
    288      } else {
    289        mEarlyDataState = EarlyData::CANNOT_BE_USED;
    290        // Poll for read now. Polling for write will cause us to busy wait.
    291        // When the handshake is done the polling flags will be set correctly.
    292        (void)mOwner->ResumeRecv();
    293      }
    294    } else {
    295      // We have h2, we can at least 0-RTT the preamble and opening
    296      // SETTINGS, etc, and maybe some of the first request
    297      LOG(
    298          ("TlsHandshaker::Check0RttEnabled [mOwner=%p] - Starting "
    299           "0RTT for h2!",
    300           mOwner.get()));
    301      mEarlyDataState = EarlyData::USED;
    302      mOwner->Start0RTTSpdy(info->Version);
    303    }
    304  }
    305 }
    306 
    307 void TlsHandshaker::ReportSecureConnectionStart() {
    308  if (mSecureConnectionStartReported) {
    309    return;
    310  }
    311 
    312  RefPtr<nsAHttpTransaction> transaction = mOwner->Transaction();
    313  LOG(("ReportSecureConnectionStart transaction=%p", transaction.get()));
    314  if (!transaction || transaction->QueryNullTransaction()) {
    315    // When we don't have a transaction or have a NullTransaction, we need to
    316    // store `secureConnectionStart` in nsHttpConnection::mBootstrappedTimings.
    317    mOwner->SetEvent(NS_NET_STATUS_TLS_HANDSHAKE_STARTING);
    318    mSecureConnectionStartReported = true;
    319    return;
    320  }
    321 
    322  nsCOMPtr<nsISocketTransport> transport = mOwner->Transport();
    323  if (transport) {
    324    transaction->OnTransportStatus(transport,
    325                                   NS_NET_STATUS_TLS_HANDSHAKE_STARTING, 0);
    326    mSecureConnectionStartReported = true;
    327  }
    328 }
    329 
    330 #ifndef ANDROID
    331 void TlsHandshaker::EarlyDataTelemetry(int16_t tlsVersion,
    332                                       bool earlyDataAccepted,
    333                                       int64_t aContentBytesWritten0RTT) {
    334  // Send the 0RTT telemetry only for tls1.3
    335  if (tlsVersion > nsITLSSocketControl::TLS_VERSION_1_2) {
    336    if (mEarlyDataState == EarlyData::NOT_AVAILABLE) {  // not possible
    337      glean::http::tls_early_data_negotiated.AccumulateSingleSample(
    338          TLS_EARLY_DATA_NOT_AVAILABLE);
    339      mozilla::glean::network::tls_early_data_negotiated.Get("not_available"_ns)
    340          .Add(1);
    341    } else if (mEarlyDataState == EarlyData::USED) {  // possible and used
    342      glean::http::tls_early_data_negotiated.AccumulateSingleSample(
    343          TLS_EARLY_DATA_AVAILABLE_AND_USED);
    344      mozilla::glean::network::tls_early_data_negotiated
    345          .Get("available_and_used"_ns)
    346          .Add(1);
    347    } else {  // possible but not used
    348      glean::http::tls_early_data_negotiated.AccumulateSingleSample(
    349          TLS_EARLY_DATA_AVAILABLE_BUT_NOT_USED);
    350      mozilla::glean::network::tls_early_data_negotiated
    351          .Get("available_but_not_used"_ns)
    352          .Add(1);
    353    }
    354 
    355    // TLS early data was used and it was accepted/rejected by the remote host.
    356    if (EarlyDataUsed()) {
    357      glean::http::tls_early_data_accepted
    358          .EnumGet(static_cast<glean::http::TlsEarlyDataAcceptedLabel>(
    359              earlyDataAccepted))
    360          .Add();
    361      mozilla::glean::network::tls_early_data_accepted
    362          .Get(earlyDataAccepted ? "accepted"_ns : "not_accepted"_ns)
    363          .Add(1);
    364    }
    365 
    366    // Amount of bytes sent using TLS early data at the start of a TLS
    367    // connection for a given channel.
    368    if (earlyDataAccepted) {
    369      mozilla::glean::network::tls_early_data_bytes_written
    370          .AccumulateSingleSample(aContentBytesWritten0RTT);
    371    }
    372  }
    373 }
    374 #endif
    375 
    376 }  // namespace mozilla::net