Skip to content
Snippets Groups Projects
Commit dc33837c authored by Kershaw Chang's avatar Kershaw Chang
Browse files

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

parent 7f4ca379
No related branches found
No related tags found
No related merge requests found
......@@ -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,
......
/* -*- 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
/* -*- 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__
......@@ -153,6 +153,7 @@ UNIFIED_SOURCES += [
"SocketWrapper.cpp",
"SpeculativeTransaction.cpp",
"TLSFilterTransaction.cpp",
"TlsHandshaker.cpp",
"TRRServiceChannel.cpp",
]
......
This diff is collapsed.
......@@ -19,6 +19,7 @@
#include "ARefBase.h"
#include "TimingStruct.h"
#include "HttpTrafficAnalyzer.h"
#include "TlsHandshaker.h"
#include "nsIAsyncInputStream.h"
#include "nsIAsyncOutputStream.h"
......@@ -58,7 +59,6 @@ class nsHttpConnection final : public HttpConnectionBase,
public nsIOutputStreamCallback,
public nsITransportEventSink,
public nsIInterfaceRequestor,
public nsITlsHandshakeCallbackListener,
public NudgeTunnelCallback {
private:
virtual ~nsHttpConnection();
......@@ -73,7 +73,6 @@ class nsHttpConnection final : public HttpConnectionBase,
NS_DECL_NSIOUTPUTSTREAMCALLBACK
NS_DECL_NSITRANSPORTEVENTSINK
NS_DECL_NSIINTERFACEREQUESTOR
NS_DECL_NSITLSHANDSHAKECALLBACKLISTENER
NS_DECL_NUDGETUNNELCALLBACK
nsHttpConnection();
......@@ -123,6 +122,7 @@ class nsHttpConnection final : public HttpConnectionBase,
HttpVersion GetLastHttpResponseVersion() { return mLastHttpResponseVersion; }
friend class HttpConnectionForceIO;
friend class TlsHandshaker;
// When a persistent connection is in the connection manager idle
// connection pool, the nsHttpConnection still reads errors and hangups
......@@ -226,11 +226,6 @@ class nsHttpConnection final : public HttpConnectionBase,
kTCPKeepaliveLongLivedConfig
};
// called to cause the underlying socket to start speaking SSL
[[nodiscard]] nsresult InitSSLParams(bool connectingToProxy,
bool ProxyStartSSL);
[[nodiscard]] nsresult SetupNPNList(nsISSLSocketControl* ssl, uint32_t caps);
[[nodiscard]] nsresult OnTransactionDone(nsresult reason);
[[nodiscard]] nsresult OnSocketWritable();
[[nodiscard]] nsresult OnSocketReadable();
......@@ -238,12 +233,6 @@ class nsHttpConnection final : public HttpConnectionBase,
PRIntervalTime IdleTime();
bool IsAlive();
// Makes certain the SSL handshake is complete and NPN negotiation
// has had a chance to happen
[[nodiscard]] bool EnsureNPNComplete();
void SetupSSL();
// Start the Spdy transaction handler when NPN indicates spdy/*
void StartSpdy(nsISSLSocketControl* ssl, SpdyVersion spdyVersion);
// Like the above, but do the bare minimum to do 0RTT data, so we can back
......@@ -265,17 +254,19 @@ class nsHttpConnection final : public HttpConnectionBase,
[[nodiscard]] nsresult DisableTCPKeepalives();
bool CheckCanWrite0RTTData();
void Check0RttEnabled(nsISSLSocketControl* ssl);
void EarlyDataTelemetry(int16_t tlsVersion, bool earlyDataAccepted);
void FinishNPNSetup(bool handshakeSucceeded, bool hasSecurityInfo);
void PostProcessNPNSetup(bool handshakeSucceeded, bool hasSecurityInfo,
bool earlyDataUsed);
void Reset0RttForSpdy();
void HandshakeDoneInternal();
uint32_t TransactionCaps() const { return mTransactionCaps; }
private:
// mTransaction only points to the HTTP Transaction callbacks if the
// transaction is open, otherwise it is null.
RefPtr<nsAHttpTransaction> mTransaction;
RefPtr<TlsHandshaker> mTlsHandshaker;
nsCOMPtr<nsIAsyncInputStream> mSocketIn;
nsCOMPtr<nsIAsyncOutputStream> mSocketOut;
......@@ -330,10 +321,6 @@ class nsHttpConnection final : public HttpConnectionBase,
// on this persistent connection.
uint32_t mRemainingConnectionUses{0xffffffff};
// SPDY related
bool mNPNComplete{false};
bool mSetupSSLCalled{false};
// version level in use, 0 if unused
SpdyVersion mUsingSpdyVersion{SpdyVersion::NONE};
......@@ -364,34 +351,8 @@ class nsHttpConnection final : public HttpConnectionBase,
bool mForceSendPending{false};
nsCOMPtr<nsITimer> mForceSendTimer;
// 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};
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; }
void EarlyDataDone();
int64_t mContentBytesWritten0RTT{0};
nsCString mEarlyNegotiatedALPN;
bool mDid0RTTSpdy{false};
bool mTlsHandshakeComplitionPending{false};
nsresult mErrorBeforeConnect = NS_OK;
......@@ -406,8 +367,6 @@ class nsHttpConnection final : public HttpConnectionBase,
int64_t mTotalBytesWritten = 0; // does not include CONNECT tunnel
nsCOMPtr<nsIInputStream> mProxyConnectStream;
bool mClosed{false};
};
NS_DEFINE_STATIC_IID_ACCESSOR(nsHttpConnection, NS_HTTPCONNECTION_IID)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment