1 // Copyright 2024 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "net/socket/tls_stream_attempt.h"
6
7 #include <memory>
8 #include <optional>
9 #include <string_view>
10
11 #include "base/memory/scoped_refptr.h"
12 #include "base/values.h"
13 #include "net/base/completion_once_callback.h"
14 #include "net/base/host_port_pair.h"
15 #include "net/base/net_errors.h"
16 #include "net/socket/client_socket_factory.h"
17 #include "net/socket/tcp_stream_attempt.h"
18 #include "net/ssl/ssl_cert_request_info.h"
19
20 namespace net {
21
22 // static
StateToString(State state)23 std::string_view TlsStreamAttempt::StateToString(State state) {
24 switch (state) {
25 case State::kNone:
26 return "None";
27 case State::kTcpAttempt:
28 return "TcpAttempt";
29 case State::kTcpAttemptComplete:
30 return "TcpAttemptComplete";
31 case State::kTlsAttempt:
32 return "TlsAttempt";
33 case State::kTlsAttemptComplete:
34 return "TlsAttemptComplete";
35 }
36 }
37
TlsStreamAttempt(const StreamAttemptParams * params,IPEndPoint ip_endpoint,HostPortPair host_port_pair,SSLConfigProvider * ssl_config_provider)38 TlsStreamAttempt::TlsStreamAttempt(const StreamAttemptParams* params,
39 IPEndPoint ip_endpoint,
40 HostPortPair host_port_pair,
41 SSLConfigProvider* ssl_config_provider)
42 : StreamAttempt(params,
43 ip_endpoint,
44 NetLogSourceType::TLS_STREAM_ATTEMPT,
45 NetLogEventType::TLS_STREAM_ATTEMPT_ALIVE),
46 host_port_pair_(std::move(host_port_pair)),
47 ssl_config_provider_(ssl_config_provider) {}
48
49 TlsStreamAttempt::~TlsStreamAttempt() = default;
50
GetLoadState() const51 LoadState TlsStreamAttempt::GetLoadState() const {
52 switch (next_state_) {
53 case State::kNone:
54 return LOAD_STATE_IDLE;
55 case State::kTcpAttempt:
56 case State::kTcpAttemptComplete:
57 CHECK(nested_attempt_);
58 return nested_attempt_->GetLoadState();
59 case State::kTlsAttempt:
60 case State::kTlsAttemptComplete:
61 return LOAD_STATE_SSL_HANDSHAKE;
62 }
63 }
64
GetInfoAsValue() const65 base::Value::Dict TlsStreamAttempt::GetInfoAsValue() const {
66 base::Value::Dict dict;
67 dict.Set("next_state", StateToString(next_state_));
68 dict.Set("tcp_handshake_completed", tcp_handshake_completed_);
69 dict.Set("tls_handshake_started", tls_handshake_started_);
70 dict.Set("has_ssl_config", ssl_config_.has_value());
71 if (nested_attempt_) {
72 dict.Set("nested_attempt", nested_attempt_->GetInfoAsValue());
73 }
74 return dict;
75 }
76
GetCertRequestInfo()77 scoped_refptr<SSLCertRequestInfo> TlsStreamAttempt::GetCertRequestInfo() {
78 return ssl_cert_request_info_;
79 }
80
SetTcpHandshakeCompletionCallback(CompletionOnceCallback callback)81 void TlsStreamAttempt::SetTcpHandshakeCompletionCallback(
82 CompletionOnceCallback callback) {
83 CHECK(!tls_handshake_started_);
84 CHECK(!tcp_handshake_completion_callback_);
85 if (next_state_ <= State::kTcpAttemptComplete) {
86 tcp_handshake_completion_callback_ = std::move(callback);
87 }
88 }
89
StartInternal()90 int TlsStreamAttempt::StartInternal() {
91 CHECK_EQ(next_state_, State::kNone);
92 next_state_ = State::kTcpAttempt;
93 return DoLoop(OK);
94 }
95
GetNetLogStartParams()96 base::Value::Dict TlsStreamAttempt::GetNetLogStartParams() {
97 base::Value::Dict dict;
98 dict.Set("host_port", host_port_pair_.ToString());
99 return dict;
100 }
101
OnIOComplete(int rv)102 void TlsStreamAttempt::OnIOComplete(int rv) {
103 CHECK_NE(rv, ERR_IO_PENDING);
104 rv = DoLoop(rv);
105 if (rv != ERR_IO_PENDING) {
106 NotifyOfCompletion(rv);
107 }
108 }
109
DoLoop(int rv)110 int TlsStreamAttempt::DoLoop(int rv) {
111 CHECK_NE(next_state_, State::kNone);
112
113 do {
114 State state = next_state_;
115 next_state_ = State::kNone;
116 switch (state) {
117 case State::kNone:
118 NOTREACHED() << "Invalid state";
119 case State::kTcpAttempt:
120 rv = DoTcpAttempt();
121 break;
122 case State::kTcpAttemptComplete:
123 rv = DoTcpAttemptComplete(rv);
124 break;
125 case State::kTlsAttempt:
126 rv = DoTlsAttempt(rv);
127 break;
128 case State::kTlsAttemptComplete:
129 rv = DoTlsAttemptComplete(rv);
130 break;
131 }
132 } while (next_state_ != State::kNone && rv != ERR_IO_PENDING);
133
134 return rv;
135 }
136
DoTcpAttempt()137 int TlsStreamAttempt::DoTcpAttempt() {
138 next_state_ = State::kTcpAttemptComplete;
139 nested_attempt_ =
140 std::make_unique<TcpStreamAttempt>(¶ms(), ip_endpoint(), &net_log());
141 return nested_attempt_->Start(
142 base::BindOnce(&TlsStreamAttempt::OnIOComplete, base::Unretained(this)));
143 }
144
DoTcpAttemptComplete(int rv)145 int TlsStreamAttempt::DoTcpAttemptComplete(int rv) {
146 const LoadTimingInfo::ConnectTiming& nested_timing =
147 nested_attempt_->connect_timing();
148 mutable_connect_timing().connect_start = nested_timing.connect_start;
149
150 tcp_handshake_completed_ = true;
151 if (tcp_handshake_completion_callback_) {
152 std::move(tcp_handshake_completion_callback_).Run(rv);
153 }
154
155 if (rv != OK) {
156 return rv;
157 }
158
159 net_log().BeginEvent(NetLogEventType::TLS_STREAM_ATTEMPT_WAIT_FOR_SSL_CONFIG);
160
161 next_state_ = State::kTlsAttempt;
162
163 if (ssl_config_.has_value()) {
164 // We restarted for ECH retry and already have a SSLConfig with retry
165 // configs.
166 return OK;
167 }
168
169 return ssl_config_provider_->WaitForSSLConfigReady(
170 base::BindOnce(&TlsStreamAttempt::OnIOComplete, base::Unretained(this)));
171 }
172
DoTlsAttempt(int rv)173 int TlsStreamAttempt::DoTlsAttempt(int rv) {
174 CHECK_EQ(rv, OK);
175
176 net_log().EndEvent(NetLogEventType::TLS_STREAM_ATTEMPT_WAIT_FOR_SSL_CONFIG);
177
178 next_state_ = State::kTlsAttemptComplete;
179
180 std::unique_ptr<StreamSocket> nested_socket =
181 nested_attempt_->ReleaseStreamSocket();
182 if (!ssl_config_) {
183 CHECK(ssl_config_provider_);
184 auto get_config_result = ssl_config_provider_->GetSSLConfig();
185 // Clear `ssl_config_provider_` to avoid dangling pointer.
186 // TODO(bashi): Try not to clear the pointer. It seems that
187 // `ssl_config_provider_` should always outlive `this`.
188 ssl_config_provider_ = nullptr;
189
190 if (get_config_result.has_value()) {
191 ssl_config_ = *get_config_result;
192 } else {
193 CHECK_EQ(get_config_result.error(), GetSSLConfigError::kAbort);
194 return ERR_ABORTED;
195 }
196 }
197
198 nested_attempt_.reset();
199
200 tls_handshake_started_ = true;
201 mutable_connect_timing().ssl_start = base::TimeTicks::Now();
202 tls_handshake_timeout_timer_.Start(
203 FROM_HERE, kTlsHandshakeTimeout,
204 base::BindOnce(&TlsStreamAttempt::OnTlsHandshakeTimeout,
205 base::Unretained(this)));
206
207 ssl_socket_ = params().client_socket_factory->CreateSSLClientSocket(
208 params().ssl_client_context, std::move(nested_socket), host_port_pair_,
209 *ssl_config_);
210
211 net_log().BeginEvent(NetLogEventType::TLS_STREAM_ATTEMPT_CONNECT);
212
213 return ssl_socket_->Connect(
214 base::BindOnce(&TlsStreamAttempt::OnIOComplete, base::Unretained(this)));
215 }
216
DoTlsAttemptComplete(int rv)217 int TlsStreamAttempt::DoTlsAttemptComplete(int rv) {
218 net_log().EndEventWithNetErrorCode(
219 NetLogEventType::TLS_STREAM_ATTEMPT_CONNECT, rv);
220
221 mutable_connect_timing().ssl_end = base::TimeTicks::Now();
222 tls_handshake_timeout_timer_.Stop();
223
224 const bool ech_enabled = params().ssl_client_context->config().ech_enabled;
225
226 if (!ech_retry_configs_ && rv == ERR_ECH_NOT_NEGOTIATED && ech_enabled) {
227 CHECK(ssl_socket_);
228 // We used ECH, and the server could not decrypt the ClientHello. However,
229 // it was able to handshake with the public name and send authenticated
230 // retry configs. If this is not the first time around, retry the connection
231 // with the new ECHConfigList, or with ECH disabled (empty retry configs),
232 // as directed.
233 //
234 // See
235 // https://www.ietf.org/archive/id/draft-ietf-tls-esni-22.html#section-6.1.6
236 ech_retry_configs_ = ssl_socket_->GetECHRetryConfigs();
237 ssl_config_->ech_config_list = *ech_retry_configs_;
238
239 // TODO(crbug.com/346835898): Add a NetLog to record ECH retry configs.
240
241 // Reset states.
242 tcp_handshake_completed_ = false;
243 tls_handshake_started_ = false;
244 ssl_socket_.reset();
245 ssl_cert_request_info_.reset();
246
247 next_state_ = State::kTcpAttempt;
248 return OK;
249 }
250
251 const bool is_ech_capable =
252 ssl_config_ && !ssl_config_->ech_config_list.empty();
253 SSLClientSocket::RecordSSLConnectResult(ssl_socket_.get(), rv, is_ech_capable,
254 ech_enabled, ech_retry_configs_,
255 connect_timing());
256
257 if (rv == OK || IsCertificateError(rv)) {
258 CHECK(ssl_socket_);
259 SetStreamSocket(std::move(ssl_socket_));
260 } else if (rv == ERR_SSL_CLIENT_AUTH_CERT_NEEDED) {
261 CHECK(ssl_socket_);
262 ssl_cert_request_info_ = base::MakeRefCounted<SSLCertRequestInfo>();
263 ssl_socket_->GetSSLCertRequestInfo(ssl_cert_request_info_.get());
264 }
265
266 return rv;
267 }
268
OnTlsHandshakeTimeout()269 void TlsStreamAttempt::OnTlsHandshakeTimeout() {
270 // TODO(bashi): The error code should be ERR_CONNECTION_TIMED_OUT but use
271 // ERR_TIMED_OUT for consistency with ConnectJobs.
272 OnIOComplete(ERR_TIMED_OUT);
273 }
274
275 } // namespace net
276