summaryrefslogtreecommitdiffstats
path: root/netwerk/protocol/http/TlsHandshaker.cpp
blob: 5288fb939d0f3d8d3870f399855361e95776bf49 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
/* -*- 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 "mozilla/StaticPrefs_network.h"
#include "nsHttpConnection.h"
#include "nsHttpConnectionInfo.h"
#include "nsHttpHandler.h"
#include "nsITLSSocketControl.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::CertVerificationDone() {
  LOG(("TlsHandshaker::CertVerificationDone mOwner=%p", mOwner.get()));
  if (mOwner) {
    Unused << mOwner->ResumeSend();
  }
  return NS_OK;
}

NS_IMETHODIMP
TlsHandshaker::ClientAuthCertificateSelected() {
  LOG(("TlsHandshaker::ClientAuthCertificateSelected mOwner=%p", mOwner.get()));
  if (mOwner) {
    Unused << mOwner->ResumeSend();
  }
  return NS_OK;
}

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;
  }

  nsCOMPtr<nsITLSSocketControl> ssl;
  mOwner->GetTLSSocketControl(getter_AddRefs(ssl));
  if (!ssl) {
    return NS_ERROR_FAILURE;
  }

  // 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) {
    nsresult rv = ssl->ProxyStartSSL();
    if (NS_FAILED(rv)) {
      return rv;
    }
  }

  if (NS_SUCCEEDED(
          SetupNPNList(ssl, mOwner->TransactionCaps(), connectingToProxy)) &&
      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(nsITLSSocketControl* ssl, uint32_t caps,
                                     bool connectingToProxy) {
  nsTArray<nsCString> protocolArray;

  // 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() &&
      (connectingToProxy || !(caps & NS_HTTP_DISALLOW_SPDY)) &&
      !(connectingToProxy && (caps & NS_HTTP_DISALLOW_HTTP2_PROXY))) {
    LOG(("nsHttpConnection::SetupSSL Allow SPDY NPN selection"));
    const SpdyInformation* info = gHttpHandler->SpdyInfo();
    if (info->ALPNCallbacks(ssl)) {
      protocolArray.AppendElement(info->VersionString);
    }
  } else {
    LOG(("nsHttpConnection::SetupSSL Disallow SPDY NPN selection"));
  }

  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;
  }

  nsCOMPtr<nsITLSSocketControl> ssl;
  mOwner->GetTLSSocketControl(getter_AddRefs(ssl));
  if (!ssl) {
    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()));
  nsresult 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(nsITLSSocketControl* 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 {
    mOwner->ChangeConnectionState(ConnectionState::ZERORTT);
    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 > nsITLSSocketControl::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