Bug 1909986 - For sidebar revamp, only show chatbot entrypoints (context menu, shortc...
[gecko.git] / netwerk / protocol / http / TlsHandshaker.cpp
blob83229eabe77ceb4f0fd4f528b8caa46b7ef56292
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/. */
7 // HttpLog.h should generally be included first
8 #include "HttpLog.h"
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/GleanMetrics.h"
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
22 namespace mozilla::net {
24 NS_IMPL_ISUPPORTS(TlsHandshaker, nsITlsHandshakeCallbackListener)
26 TlsHandshaker::TlsHandshaker(nsHttpConnectionInfo* aInfo,
27 nsHttpConnection* aOwner)
28 : mConnInfo(aInfo), mOwner(aOwner) {
29 LOG(("TlsHandshaker ctor %p", this));
32 TlsHandshaker::~TlsHandshaker() { LOG(("TlsHandshaker dtor %p", this)); }
34 NS_IMETHODIMP
35 TlsHandshaker::CertVerificationDone() {
36 LOG(("TlsHandshaker::CertVerificationDone mOwner=%p", mOwner.get()));
37 if (mOwner) {
38 Unused << mOwner->ResumeSend();
40 return NS_OK;
43 NS_IMETHODIMP
44 TlsHandshaker::ClientAuthCertificateSelected() {
45 LOG(("TlsHandshaker::ClientAuthCertificateSelected mOwner=%p", mOwner.get()));
46 if (mOwner) {
47 Unused << mOwner->ResumeSend();
49 return NS_OK;
52 NS_IMETHODIMP
53 TlsHandshaker::HandshakeDone() {
54 LOG(("TlsHandshaker::HandshakeDone mOwner=%p", mOwner.get()));
55 if (mOwner) {
56 mTlsHandshakeComplitionPending = true;
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;
67 }));
69 return NS_OK;
72 void TlsHandshaker::SetupSSL(bool aInSpdyTunnel, bool aForcePlainText) {
73 if (!mOwner) {
74 return;
77 LOG1(("TlsHandshaker::SetupSSL %p caps=0x%X %s\n", mOwner.get(),
78 mOwner->TransactionCaps(), mConnInfo->HashKey().get()));
80 if (mSetupSSLCalled) { // do only once
81 return;
83 mSetupSSLCalled = true;
85 if (mNPNComplete) {
86 return;
89 // we flip this back to false if SetNPNList succeeds at the end
90 // of this function
91 mNPNComplete = true;
93 if (!mConnInfo->FirstHopSSL() || aForcePlainText) {
94 return;
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);
106 MOZ_ASSERT(NS_SUCCEEDED(rv));
109 nsresult TlsHandshaker::InitSSLParams(bool connectingToProxy,
110 bool proxyStartSSL) {
111 LOG(("TlsHandshaker::InitSSLParams [mOwner=%p] connectingToProxy=%d\n",
112 mOwner.get(), connectingToProxy));
113 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
115 if (!mOwner) {
116 return NS_ERROR_ABORT;
119 nsCOMPtr<nsITLSSocketControl> ssl;
120 mOwner->GetTLSSocketControl(getter_AddRefs(ssl));
121 if (!ssl) {
122 return NS_ERROR_FAILURE;
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();
130 if (proxyStartSSL) {
131 nsresult rv = ssl->ProxyStartSSL();
132 if (NS_FAILED(rv)) {
133 return rv;
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 mNPNComplete = false;
145 return NS_OK;
148 // The naming of NPN is historical - this function creates the basic
149 // offer list for both NPN and ALPN. ALPN validation callbacks are made
150 // now before the handshake is complete, and NPN validation callbacks
151 // are made during the handshake.
152 nsresult TlsHandshaker::SetupNPNList(nsITLSSocketControl* ssl, uint32_t caps,
153 bool connectingToProxy) {
154 nsTArray<nsCString> protocolArray;
156 // The first protocol is used as the fallback if none of the
157 // protocols supported overlap with the server's list.
158 // When using ALPN the advertised preferences are protocolArray indicies
159 // {1, .., N, 0} in decreasing order.
160 // For NPN, In the case of overlap, matching priority is driven by
161 // the order of the server's advertisement - with index 0 used when
162 // there is no match.
163 protocolArray.AppendElement("http/1.1"_ns);
165 if (StaticPrefs::network_http_http2_enabled() &&
166 (connectingToProxy || !(caps & NS_HTTP_DISALLOW_SPDY)) &&
167 !(connectingToProxy && (caps & NS_HTTP_DISALLOW_HTTP2_PROXY))) {
168 LOG(("nsHttpConnection::SetupSSL Allow SPDY NPN selection"));
169 const SpdyInformation* info = gHttpHandler->SpdyInfo();
170 if (info->ALPNCallbacks(ssl)) {
171 protocolArray.AppendElement(info->VersionString);
173 } else {
174 LOG(("nsHttpConnection::SetupSSL Disallow SPDY NPN selection"));
177 nsresult rv = ssl->SetNPNList(protocolArray);
178 LOG(("TlsHandshaker::SetupNPNList %p %" PRIx32 "\n", mOwner.get(),
179 static_cast<uint32_t>(rv)));
180 return rv;
183 // Checks if TLS handshake is needed and it is responsible to move it forward.
184 bool TlsHandshaker::EnsureNPNComplete() {
185 if (!mOwner) {
186 mNPNComplete = true;
187 return true;
190 nsCOMPtr<nsISocketTransport> transport = mOwner->Transport();
191 MOZ_ASSERT(transport);
192 if (!transport) {
193 // this cannot happen
194 mNPNComplete = true;
195 return true;
198 if (mNPNComplete) {
199 return true;
202 if (mTlsHandshakeComplitionPending) {
203 return false;
206 nsCOMPtr<nsITLSSocketControl> ssl;
207 mOwner->GetTLSSocketControl(getter_AddRefs(ssl));
208 if (!ssl) {
209 FinishNPNSetup(false, false);
210 return true;
213 if (!m0RTTChecked) {
214 // We reuse m0RTTChecked. We want to send this status only once.
215 RefPtr<nsAHttpTransaction> transaction = mOwner->Transaction();
216 nsCOMPtr<nsISocketTransport> transport = mOwner->Transport();
217 if (transaction && transport) {
218 transaction->OnTransportStatus(transport,
219 NS_NET_STATUS_TLS_HANDSHAKE_STARTING, 0);
223 LOG(("TlsHandshaker::EnsureNPNComplete [mOwner=%p] drive TLS handshake",
224 mOwner.get()));
225 nsresult rv = ssl->DriveHandshake();
226 if (NS_FAILED(rv) && rv != NS_BASE_STREAM_WOULD_BLOCK) {
227 FinishNPNSetup(false, true);
228 return true;
231 Check0RttEnabled(ssl);
232 return false;
235 void TlsHandshaker::EarlyDataDone() {
236 if (mEarlyDataState == EarlyData::USED) {
237 mEarlyDataState = EarlyData::DONE_USED;
238 } else if (mEarlyDataState == EarlyData::CANNOT_BE_USED) {
239 mEarlyDataState = EarlyData::DONE_CANNOT_BE_USED;
240 } else if (mEarlyDataState == EarlyData::NOT_AVAILABLE) {
241 mEarlyDataState = EarlyData::DONE_NOT_AVAILABLE;
245 void TlsHandshaker::FinishNPNSetup(bool handshakeSucceeded,
246 bool hasSecurityInfo) {
247 LOG(("TlsHandshaker::FinishNPNSetup mOwner=%p", mOwner.get()));
248 mNPNComplete = true;
250 mOwner->PostProcessNPNSetup(handshakeSucceeded, hasSecurityInfo,
251 EarlyDataUsed());
252 EarlyDataDone();
255 void TlsHandshaker::Check0RttEnabled(nsITLSSocketControl* ssl) {
256 if (!mOwner) {
257 return;
260 if (m0RTTChecked) {
261 return;
264 m0RTTChecked = true;
266 if (mConnInfo->UsingProxy()) {
267 return;
270 // There is no ALPN info (yet!). We need to consider doing 0RTT. We
271 // will do so if there is ALPN information from a previous session
272 // (AlpnEarlySelection), we are using HTTP/1, and the request data can
273 // be safely retried.
274 if (NS_FAILED(ssl->GetAlpnEarlySelection(mEarlyNegotiatedALPN))) {
275 LOG1(
276 ("TlsHandshaker::Check0RttEnabled %p - "
277 "early selected alpn not available",
278 mOwner.get()));
279 } else {
280 mOwner->ChangeConnectionState(ConnectionState::ZERORTT);
281 LOG1(
282 ("TlsHandshaker::Check0RttEnabled %p -"
283 "early selected alpn: %s",
284 mOwner.get(), mEarlyNegotiatedALPN.get()));
285 const SpdyInformation* info = gHttpHandler->SpdyInfo();
286 if (!mEarlyNegotiatedALPN.Equals(info->VersionString)) {
287 // This is the HTTP/1 case.
288 // Check if early-data is allowed for this transaction.
289 RefPtr<nsAHttpTransaction> transaction = mOwner->Transaction();
290 if (transaction && transaction->Do0RTT()) {
291 LOG(
292 ("TlsHandshaker::Check0RttEnabled [mOwner=%p] - We "
293 "can do 0RTT (http/1)!",
294 mOwner.get()));
295 mEarlyDataState = EarlyData::USED;
296 } else {
297 mEarlyDataState = EarlyData::CANNOT_BE_USED;
298 // Poll for read now. Polling for write will cause us to busy wait.
299 // When the handshake is done the polling flags will be set correctly.
300 Unused << mOwner->ResumeRecv();
302 } else {
303 // We have h2, we can at least 0-RTT the preamble and opening
304 // SETTINGS, etc, and maybe some of the first request
305 LOG(
306 ("TlsHandshaker::Check0RttEnabled [mOwner=%p] - Starting "
307 "0RTT for h2!",
308 mOwner.get()));
309 mEarlyDataState = EarlyData::USED;
310 mOwner->Start0RTTSpdy(info->Version);
315 #ifndef ANDROID
316 void TlsHandshaker::EarlyDataTelemetry(int16_t tlsVersion,
317 bool earlyDataAccepted,
318 int64_t aContentBytesWritten0RTT) {
319 // Send the 0RTT telemetry only for tls1.3
320 if (tlsVersion > nsITLSSocketControl::TLS_VERSION_1_2) {
321 if (mEarlyDataState == EarlyData::NOT_AVAILABLE) { // not possible
322 Telemetry::Accumulate(Telemetry::TLS_EARLY_DATA_NEGOTIATED,
323 TLS_EARLY_DATA_NOT_AVAILABLE);
324 mozilla::glean::network::tls_early_data_negotiated.Get("not_available"_ns)
325 .Add(1);
326 } else if (mEarlyDataState == EarlyData::USED) { // possible and used
327 Telemetry::Accumulate(Telemetry::TLS_EARLY_DATA_NEGOTIATED,
328 TLS_EARLY_DATA_AVAILABLE_AND_USED);
329 mozilla::glean::network::tls_early_data_negotiated
330 .Get("available_and_used"_ns)
331 .Add(1);
332 } else { // possible but not used
333 Telemetry::Accumulate(Telemetry::TLS_EARLY_DATA_NEGOTIATED,
334 TLS_EARLY_DATA_AVAILABLE_BUT_NOT_USED);
335 mozilla::glean::network::tls_early_data_negotiated
336 .Get("available_but_not_used"_ns)
337 .Add(1);
340 // TLS early data was used and it was accepted/rejected by the remote host.
341 if (EarlyDataUsed()) {
342 Telemetry::Accumulate(Telemetry::TLS_EARLY_DATA_ACCEPTED,
343 earlyDataAccepted);
344 mozilla::glean::network::tls_early_data_accepted
345 .Get(earlyDataAccepted ? "accepted"_ns : "not_accepted"_ns)
346 .Add(1);
349 // Amount of bytes sent using TLS early data at the start of a TLS
350 // connection for a given channel.
351 if (earlyDataAccepted) {
352 mozilla::glean::network::tls_early_data_bytes_written
353 .AccumulateSingleSample(aContentBytesWritten0RTT);
357 #endif
359 } // namespace mozilla::net