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