blob: b7aab6c1a0acad3e012eb6292f9523b6b4e61a23 [file] [log] [blame]
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "net/socket/tls_stream_attempt.h"
#include <memory>
#include <optional>
#include <string_view>
#include "base/memory/scoped_refptr.h"
#include "base/trace_event/trace_event.h"
#include "base/values.h"
#include "net/base/completion_once_callback.h"
#include "net/base/features.h"
#include "net/base/host_port_pair.h"
#include "net/base/net_errors.h"
#include "net/socket/client_socket_factory.h"
#include "net/socket/tcp_stream_attempt.h"
#include "net/ssl/ssl_cert_request_info.h"
namespace net {
// static
std::string_view TlsStreamAttempt::StateToString(State state) {
switch (state) {
case State::kNone:
return "None";
case State::kTcpAttempt:
return "TcpAttempt";
case State::kTcpAttemptComplete:
return "TcpAttemptComplete";
case State::kTlsAttempt:
return "TlsAttempt";
case State::kTlsAttemptComplete:
return "TlsAttemptComplete";
}
}
TlsStreamAttempt::TlsStreamAttempt(const StreamAttemptParams* params,
IPEndPoint ip_endpoint,
perfetto::Track track,
HostPortPair host_port_pair,
Delegate* delegate)
: StreamAttempt(params,
ip_endpoint,
track,
NetLogSourceType::TLS_STREAM_ATTEMPT,
NetLogEventType::TLS_STREAM_ATTEMPT_ALIVE),
host_port_pair_(std::move(host_port_pair)),
delegate_(delegate) {}
TlsStreamAttempt::~TlsStreamAttempt() {
MaybeRecordTlsHandshakeEnd(ERR_ABORTED);
}
LoadState TlsStreamAttempt::GetLoadState() const {
switch (next_state_) {
case State::kNone:
return LOAD_STATE_IDLE;
case State::kTcpAttempt:
case State::kTcpAttemptComplete:
CHECK(nested_attempt_);
return nested_attempt_->GetLoadState();
case State::kTlsAttempt:
case State::kTlsAttemptComplete:
return LOAD_STATE_SSL_HANDSHAKE;
}
}
base::Value::Dict TlsStreamAttempt::GetInfoAsValue() const {
base::Value::Dict dict;
dict.Set("next_state", StateToString(next_state_));
dict.Set("tcp_handshake_completed", tcp_handshake_completed_);
dict.Set("tls_handshake_started", tls_handshake_started_);
dict.Set("has_ssl_config", ssl_config_.has_value());
if (nested_attempt_) {
dict.Set("nested_attempt", nested_attempt_->GetInfoAsValue());
}
return dict;
}
scoped_refptr<SSLCertRequestInfo> TlsStreamAttempt::GetCertRequestInfo() {
return ssl_cert_request_info_;
}
int TlsStreamAttempt::StartInternal() {
CHECK_EQ(next_state_, State::kNone);
next_state_ = State::kTcpAttempt;
return DoLoop(OK);
}
base::Value::Dict TlsStreamAttempt::GetNetLogStartParams() {
base::Value::Dict dict;
dict.Set("host_port", host_port_pair_.ToString());
return dict;
}
void TlsStreamAttempt::OnIOComplete(int rv) {
CHECK_NE(rv, ERR_IO_PENDING);
rv = DoLoop(rv);
if (rv != ERR_IO_PENDING) {
NotifyOfCompletion(rv);
}
}
int TlsStreamAttempt::DoLoop(int rv) {
CHECK_NE(next_state_, State::kNone);
do {
State state = next_state_;
next_state_ = State::kNone;
switch (state) {
case State::kNone:
NOTREACHED() << "Invalid state";
case State::kTcpAttempt:
rv = DoTcpAttempt();
break;
case State::kTcpAttemptComplete:
rv = DoTcpAttemptComplete(rv);
break;
case State::kTlsAttempt:
rv = DoTlsAttempt(rv);
break;
case State::kTlsAttemptComplete:
rv = DoTlsAttemptComplete(rv);
break;
}
} while (next_state_ != State::kNone && rv != ERR_IO_PENDING);
return rv;
}
int TlsStreamAttempt::DoTcpAttempt() {
next_state_ = State::kTcpAttemptComplete;
nested_attempt_ = std::make_unique<TcpStreamAttempt>(&params(), ip_endpoint(),
track(), &net_log());
return nested_attempt_->Start(
base::BindOnce(&TlsStreamAttempt::OnIOComplete, base::Unretained(this)));
}
int TlsStreamAttempt::DoTcpAttemptComplete(int rv) {
const LoadTimingInfo::ConnectTiming& nested_timing =
nested_attempt_->connect_timing();
mutable_connect_timing().connect_start = nested_timing.connect_start;
tcp_handshake_completed_ = true;
delegate_->OnTcpHandshakeComplete();
if (rv != OK) {
return rv;
}
net_log().BeginEvent(NetLogEventType::TLS_STREAM_ATTEMPT_WAIT_FOR_SSL_CONFIG);
next_state_ = State::kTlsAttempt;
if (ssl_config_.has_value()) {
// We restarted for ECH retry and already have a SSLConfig with retry
// configs.
return OK;
}
int ssl_config_ready_result = delegate_->WaitForSSLConfigReady(base::BindOnce(
&TlsStreamAttempt::OnIOComplete, weak_ptr_factory_.GetWeakPtr()));
if (ssl_config_ready_result == ERR_IO_PENDING) {
TRACE_EVENT_INSTANT("net.stream", "WaitForSSLConfig", track());
}
return ssl_config_ready_result;
}
int TlsStreamAttempt::DoTlsAttempt(int rv) {
CHECK_EQ(rv, OK);
net_log().EndEvent(NetLogEventType::TLS_STREAM_ATTEMPT_WAIT_FOR_SSL_CONFIG);
next_state_ = State::kTlsAttemptComplete;
std::unique_ptr<StreamSocket> nested_socket =
nested_attempt_->ReleaseStreamSocket();
if (!ssl_config_) {
auto get_config_result = delegate_->GetSSLConfig();
if (get_config_result.has_value()) {
ssl_config_ = *get_config_result;
// For metrics, we want to know whether the server advertised Trust Anchor
// IDs in DNS (i.e., whether the client could use Trust Anchor IDs with
// this server, regardless of whether the feature was enabled). But we
// don't want to actually configure the Trust Anchor IDs on the connection
// if the feature flag isn't enabled.
trust_anchor_ids_from_dns_ = !ssl_config_->trust_anchor_ids.empty();
if (!base::FeatureList::IsEnabled(features::kTLSTrustAnchorIDs)) {
ssl_config_->trust_anchor_ids.clear();
}
} else {
CHECK_EQ(get_config_result.error(), GetSSLConfigError::kAbort);
return ERR_ABORTED;
}
}
nested_attempt_.reset();
tls_handshake_started_ = true;
mutable_connect_timing().ssl_start = base::TimeTicks::Now();
tls_handshake_timeout_timer_.Start(
FROM_HERE, kTlsHandshakeTimeout,
base::BindOnce(&TlsStreamAttempt::OnTlsHandshakeTimeout,
base::Unretained(this)));
ssl_socket_ = params().client_socket_factory->CreateSSLClientSocket(
params().ssl_client_context, std::move(nested_socket), host_port_pair_,
*ssl_config_);
TRACE_EVENT_BEGIN("net.stream", "TlsConnect", track());
net_log().BeginEvent(NetLogEventType::TLS_STREAM_ATTEMPT_CONNECT);
return ssl_socket_->Connect(
base::BindOnce(&TlsStreamAttempt::OnIOComplete, base::Unretained(this)));
}
int TlsStreamAttempt::DoTlsAttemptComplete(int rv) {
MaybeRecordTlsHandshakeEnd(rv);
net_log().EndEventWithNetErrorCode(
NetLogEventType::TLS_STREAM_ATTEMPT_CONNECT, rv);
mutable_connect_timing().ssl_end = base::TimeTicks::Now();
tls_handshake_timeout_timer_.Stop();
const bool ech_enabled = params().ssl_client_context->config().ech_enabled;
if (!ech_retry_configs_ && rv == ERR_ECH_NOT_NEGOTIATED && ech_enabled) {
CHECK(ssl_socket_);
// We used ECH, and the server could not decrypt the ClientHello. However,
// it was able to handshake with the public name and send authenticated
// retry configs. If this is not the first time around, retry the connection
// with the new ECHConfigList, or with ECH disabled (empty retry configs),
// as directed.
//
// See
// https://www.ietf.org/archive/id/draft-ietf-tls-esni-22.html#section-6.1.6
ech_retry_configs_ = ssl_socket_->GetECHRetryConfigs();
ssl_config_->ech_config_list = *ech_retry_configs_;
// TODO(crbug.com/346835898): Add a NetLog to record ECH retry configs.
ResetStateForRestart();
next_state_ = State::kTcpAttempt;
return OK;
}
// If we got a certificate error and the server advertised some Trust Anchor
// IDs in the handshake that we trust, then retry the connection, using the
// fresh Trust Anchor IDs from the server. We only want to retry once; if we
// have we already retried, so we skip all of this and treat the connection
// error as usual.
//
// TODO(https://crbug.com/399937371): clarify and test the interactions of ECH
// retry and TAI retry.
if (IsCertificateError(rv) && !retried_for_trust_anchor_ids_ &&
base::FeatureList::IsEnabled(features::kTLSTrustAnchorIDs)) {
CHECK(ssl_socket_);
std::vector<std::vector<uint8_t>> server_trust_anchor_ids =
ssl_socket_->GetServerTrustAnchorIDsForRetry();
// https://tlswg.org/tls-trust-anchor-ids/draft-ietf-tls-trust-anchor-ids.html#name-retry-mechanism:
// If the EncryptedExtensions had no trust_anchor extension, or no match was
// found, the client returns the error to the application.
if (!server_trust_anchor_ids.empty()) {
std::vector<uint8_t> trust_anchor_ids_for_retry =
SSLConfig::SelectTrustAnchorIDs(
server_trust_anchor_ids,
params().ssl_client_context->config().trust_anchor_ids);
if (!trust_anchor_ids_for_retry.empty()) {
retried_for_trust_anchor_ids_ = true;
ssl_config_->trust_anchor_ids = trust_anchor_ids_for_retry;
ResetStateForRestart();
next_state_ = State::kTcpAttempt;
return OK;
}
}
}
const bool is_ech_capable =
ssl_config_ && !ssl_config_->ech_config_list.empty();
SSLClientSocket::RecordSSLConnectResult(
ssl_socket_.get(), rv, is_ech_capable, ech_enabled, ech_retry_configs_,
trust_anchor_ids_from_dns_, retried_for_trust_anchor_ids_,
connect_timing());
if (rv == OK || IsCertificateError(rv)) {
CHECK(ssl_socket_);
SetStreamSocket(std::move(ssl_socket_));
} else if (rv == ERR_SSL_CLIENT_AUTH_CERT_NEEDED) {
CHECK(ssl_socket_);
ssl_cert_request_info_ = base::MakeRefCounted<SSLCertRequestInfo>();
ssl_socket_->GetSSLCertRequestInfo(ssl_cert_request_info_.get());
}
return rv;
}
void TlsStreamAttempt::OnTlsHandshakeTimeout() {
// TODO(bashi): The error code should be ERR_CONNECTION_TIMED_OUT but use
// ERR_TIMED_OUT for consistency with ConnectJobs.
OnIOComplete(ERR_TIMED_OUT);
}
void TlsStreamAttempt::MaybeRecordTlsHandshakeEnd(int rv) {
if (!tls_handshake_started_ || !tls_handshake_timeout_timer_.IsRunning()) {
return;
}
TRACE_EVENT_END("net.stream", track(), "result", rv);
}
void TlsStreamAttempt::ResetStateForRestart() {
tcp_handshake_completed_ = false;
tls_handshake_started_ = false;
ssl_socket_.reset();
ssl_cert_request_info_.reset();
}
} // namespace net