前言
- google quic tls握手原理(一)一文中我们简单了解到google quiche项目中客户端和服务端用于握手的核心模块,以及他们之间的关系。
- 本文基于上文,在上文的基础上深入学习TLS1.3客户端的握手流程,着重分析客户端握手过程中client hello的生成、配置以及握手触发机制等。
客户端Client Hello消息生成
- 通过对客户端
client hello
消息的生成梳理如下:
- 客户端握手client hello消息生成主要包括如上1~7步骤,然后通过调用
SSL_do_handshake()
函数生成Client Hello
消息,并触发ssl
引擎中的add_handshake_data(...)
函数指针将client hello消息带出到更上层发出。 - 其中上面的1/2/3步我们可以统称为客户端握手前准备,包括
ssl
引擎初始化、配置客户端支持的签名算法列表、以及设置证书链和私钥等信息,当然客户端可以不配置私钥等信息。 - 由于
add_handshake_data(...)
函数指针在TlsConnection
模块被实现,所以上述最终Client Hello
消息由TlsConnection
模块发出。
1~3、客户端握手前准备
- 本节开始介绍客户端握手前准备,主要包括上节中提到的第1~第3步
- 首先我们看看客户端握手
ssl
引擎的初始化工作,ssl
引擎的初始化主要聚集在TlsConnection和TlsClientConnection
模块
客户端SSL_CTX实例化
QuicCryptoClientConfig::QuicCryptoClientConfig(
std::unique_ptr<ProofVerifier> proof_verifier,
std::unique_ptr<SessionCache> session_cache)
: proof_verifier_(std::move(proof_verifier)),
session_cache_(std::move(session_cache)),
ssl_ctx_(TlsClientConnection::CreateSslCtx(
!GetQuicFlag(quic_disable_client_tls_zero_rtt))) {
....
}
- 客户端SSL_CTX的实例化发生在QuicCryptoClientConfig模块的构造过程当中,通过调用TlsClientConnection::CreateSslCtx静态方法来创建SSL_CTX。
bssl::UniquePtr<SSL_CTX> TlsClientConnection::CreateSslCtx(
bool enable_early_data) {
bssl::UniquePtr<SSL_CTX> ssl_ctx = TlsConnection::CreateSslCtx();
// Configure certificate verification.
SSL_CTX_set_custom_verify(ssl_ctx.get(), SSL_VERIFY_PEER, &VerifyCallback);
int reverify_on_resume_enabled = 1;
SSL_CTX_set_reverify_on_resume(ssl_ctx.get(), reverify_on_resume_enabled);
// Configure session caching.
// 设置仅客户缓存session
SSL_CTX_set_session_cache_mode(
ssl_ctx.get(), SSL_SESS_CACHE_CLIENT | SSL_SESS_CACHE_NO_INTERNAL);
// 收到服务端得握手信息后,并握手成功会触发新会话创建的回调
SSL_CTX_sess_set_new_cb(ssl_ctx.get(), NewSessionCallback);
// TODO(wub): Always enable early data on the SSL_CTX, but allow it to be
// overridden on the SSL object, via QuicSSLConfig.
SSL_CTX_set_early_data_enabled(ssl_ctx.get(), enable_early_data);
return ssl_ctx;
}
- 通过
SSL_CTX_set_custom_verify()
自定义证书验证回调函数为TlsConnection::VerifyCallback
,其中参数mod的常见值为SSL_VERIFY_NONE
(不验证证书)、SSL_VERIFY_PEER
(验证对等方证书),该回调会在收到服务端的握手报文后,并提取服务端证书信息后由ssl
引擎回调。 - 通过
SSL_CTX_set_reverify_on_resume()
设置是否在SSL会话恢复时重新验证证书链,该功能在SessionCache
的情况下使用。
客户端SSL实例化
-
ssl
实例化由TlsClientHandshaker
发起并由TlsConnection
的构造函数完成
TlsClientHandshaker::TlsClientHandshaker(
const QuicServerId& server_id, QuicCryptoStream* stream,
QuicSession* session, std::unique_ptr<ProofVerifyContext> verify_context,
QuicCryptoClientConfig* crypto_config,
QuicCryptoClientStream::ProofHandler* proof_handler,
bool has_application_state)
: TlsHandshaker(stream, session),
....,
tls_connection_(crypto_config->ssl_ctx(), this, session->GetSSLConfig()) {
if (crypto_config->tls_signature_algorithms().has_value()) {
SSL_set1_sigalgs_list(ssl(),
crypto_config->tls_signature_algorithms()->c_str());
}
if (crypto_config->proof_source() != nullptr) {
const ClientProofSource::CertAndKey* cert_and_key =
crypto_config->proof_source()->GetCertAndKey(server_id.host());
if (cert_and_key != nullptr) {
QUIC_DVLOG(1) << "Setting client cert and key for " << server_id.host();
tls_connection_.SetCertChain(cert_and_key->chain->ToCryptoBuffers().value,
cert_and_key->private_key.private_key());
}
}
#if BORINGSSL_API_VERSION >= 22
if (!crypto_config->preferred_groups().empty()) {
SSL_set1_group_ids(ssl(), crypto_config->preferred_groups().data(),
crypto_config->preferred_groups().size());
}
#endif // BORINGSSL_API_VERSION
}
TlsConnection::TlsConnection(SSL_CTX* ssl_ctx,
TlsConnection::Delegate* delegate,
QuicSSLConfig ssl_config)
: delegate_(delegate),
ssl_(SSL_new(ssl_ctx)),
ssl_config_(std::move(ssl_config)) {
SSL_set_ex_data(
ssl(), SslIndexSingleton::GetInstance()->ssl_ex_data_index_connection(),
this);
if (ssl_config_.early_data_enabled.has_value()) {
const int early_data_enabled = *ssl_config_.early_data_enabled ? 1 : 0;
SSL_set_early_data_enabled(ssl(), early_data_enabled);
}
if (ssl_config_.signing_algorithm_prefs.has_value()) {
SSL_set_signing_algorithm_prefs(
ssl(), ssl_config_.signing_algorithm_prefs->data(),
ssl_config_.signing_algorithm_prefs->size());
}
// 禁用TLS会话恢复中的票据机制。票据机制可以加速连接建立过程,但也带来了一些安全风险,例如中间人攻击。
// 因此,在一些安全性要求较高的场景下,禁用票据机制是有益的
if (ssl_config_.disable_ticket_support.has_value()) {
if (*ssl_config_.disable_ticket_support) {
SSL_set_options(ssl(), SSL_OP_NO_TICKET);
}
}
}
- 实例化
SSL
并对ssl
引擎做一些可选初始化工作 - (可选)使用
SSL_set_early_data_enabled()
开启握手前是否允许发送数据 - (可选)使用
SSL_set_signing_algorithm_prefs()
设置SSL连接中允许使用的签名算法列表,在调用SSL_set_signing_algorithm_prefs()
函数之后,SSL库会使用指定的签名算法列表来协商SSL连接中使用的签名算法。当客户端和服务器之间协商签名算法时,它们将优先选择在列表中出现的算法,并根据算法的优先级排序。 - (可选)使用
SSL_set1_sigalgs_list()
设置SSL连接中允许使用的签名算法列表,注意和上面的区别,一个是优先级一个是允许使用,SSL库会使用指定的签名算法列表来协商SSL连接中使用的签名算法。当客户端和服务器之间协商签名算法时,它们将优先选择在列表中出现的算法,并根据算法的优先级排序 - (可选)使用
SSL_set_chain_and_key()
为SSL连接设置证书链和私钥,如果指定了证书链,则SSL库会使用链中的证书来构建完整的证书链,以便验证对等方的身份 - (可选)使用
SSL_set1_group_ids
设置SSL连接中允许使用的椭圆曲线加密算法(ECC)的曲线ID列表,SSL库将使用指定的曲线ID列表来协商SSL连接中使用的曲线。当客户端和服务器之间协商曲线时,它们将优先选择在列表中出现的曲线,并根据曲线的优先级排序。
客户端握手入口函数分析
- 上一节中介绍了客户端
ssl
引擎的实例化和初始化工作,在客户端根据不同的可选配置,可以对支持的签名算法列表,优先签名算法列表等进行配置,同时也可以对证书链和私钥进行配置。 - 接下来看一下客户端握手的入口函数为后续各环节做铺垫
bool TlsClientHandshaker::CryptoConnect() {
// 这个版本似乎不支持psk格式的证书
if (!pre_shared_key_.empty()) {
// TODO(b/154162689) add PSK support to QUIC+TLS.
std::string error_details =
"QUIC client pre-shared keys not yet supported with TLS";
QUIC_BUG(quic_bug_10576_1) << error_details;
CloseConnection(QUIC_HANDSHAKE_FAILED, error_details);
return false;
}
....
// TODO(b/193650832) Add SetFromConfig to QUIC handshakers and remove reliance
// on session pointer.
#if BORINGSSL_API_VERSION >= 16
// Ask BoringSSL to randomize the order of TLS extensions.
SSL_set_permute_extensions(ssl(), true);
#endif // BORINGSSL_API_VERSION
// Set the SNI to send, if any.
SSL_set_connect_state(ssl());
....
if (!server_id_.host().empty() &&
(QuicHostnameUtils::IsValidSNI(server_id_.host()) ||
allow_invalid_sni_for_tests_) &&
SSL_set_tlsext_host_name(ssl(), server_id_.host().c_str()) != 1) {
return false;
}
// 4)设置应用扩展协议
if (!SetAlpn()) {
CloseConnection(QUIC_HANDSHAKE_FAILED, "Client failed to set ALPN");
return false;
}
// Set the Transport Parameters to send in the ClientHello
// 5)设置传输参数
if (!SetTransportParameters()) {
CloseConnection(QUIC_HANDSHAKE_FAILED,
"Client failed to set Transport Parameters");
return false;
}
// Set a session to resume, if there is one.
if (session_cache_) {
cached_state_ = session_cache_->Lookup(
server_id_, session()->GetClock()->WallNow(), SSL_get_SSL_CTX(ssl()));
}
if (cached_state_) {
SSL_set_session(ssl(), cached_state_->tls_session.get());
if (!cached_state_->token.empty()) {
session()->SetSourceAddressTokenToSend(cached_state_->token);
}
}
SSL_set_enable_ech_grease(ssl(),
tls_connection_.ssl_config().ech_grease_enabled);
if (!tls_connection_.ssl_config().ech_config_list.empty() &&
!SSL_set1_ech_config_list(
ssl(),
reinterpret_cast<const uint8_t*>(
tls_connection_.ssl_config().ech_config_list.data()),
tls_connection_.ssl_config().ech_config_list.size())) {
CloseConnection(QUIC_HANDSHAKE_FAILED,
"Client failed to set ECHConfigList");
return false;
}
// Start the handshake.
// 6) 开始握手
AdvanceHandshake();
return session()->connection()->connected();
}
- 本节结合图(1)进行说明,
TlsClientHandshaker::CryptoConnect()
为客户端握手的函数入口,QuicSession
模块通过调用该函数让客户端开始和服务端进行TLS1.3握手 - 通过
SSL_set_permute_extensions()
启用或禁用SSL连接中的扩展名随机重排功能,在SSL连接中,扩展名用于传递一些附加信息,例如SNI(Server Name Indication)和ALPN(Application-Layer Protocol Negotiation)等。扩展名随机重排是一种在客户端和服务器之间交换扩展名时的安全策略,可以防止中间人攻击和信息泄露。通过调用SSL_set_permute_extensions()
函数,可以启用或禁用扩展名随机重排功能。如果启用该功能,SSL库将在交换扩展名时随机重排扩展名的顺序,以增加攻击者的难度。如果禁用该功能,则扩展名的顺序将按照固定的顺序进行交换,可能会降低安全性。 - 通过
SSL_set_connect_state()
将SSL连接对象设置为客户端连接状态。 - 通过调用
SSL_set_tlsext_host_name()
函数,可以设置SSL连接对象的SNI扩展中的主机名。在客户端Hello消息中,将包含SNI扩展,并将该扩展设置为指定的主机名。需要注意的是,SSL_set_tlsext_host_name()
函数只适用于客户端连接状态,对于服务器连接状态无效。如果需要在服务器端设置SNI,可以通过配置服务器软件来实现。 - 4)调用
SetAlpn()
设置应用扩展协议(h3),并进行内存序列化,并通过SSL_add_application_settings()
函数将该序列化的内存信息写入到ssl
引擎。 - 5)调用
SetTransportParameters()
设置传输参数,并进行内存序列化,并通过SSL_set_quic_transport_params()
函数将该序列化的内存信息写入到ssl
引擎。 - 通过
SSL_set_enable_ech_grease()
启用或禁用TLS扩展中的ECH(Encrypted Client Hello)扩展中的GREASE机制。在TLS 1.3中,ECH扩展用于隐藏客户端的SNI和协议版本等信息,以增加隐私保护。然而,攻击者可能会利用此机制来进行攻击或识别客户端。为了缓解这种风险,TLS 1.3引入了GREASE机制,即向ECH扩展中添加一些无用的、随机的数据(GREASE值),以使攻击者难以识别ECH扩展的内容。 - 6)调用
AdvanceHandshake
函数进入握手状态,并等待服务端返回server hello
,该函数的核心是调用ssl引擎中的SSL_do_handshake()
函数 - 经过以上步骤
client hello
消息就已经成功生成,并通过ssl
处理最终回调TlsConnection::WriteMessageCallback()
函数并携带client hello
消息。 - 以下用一个抓包实例来认识
client hello
里面的内容
-
Client Hello
在扩展字段里标明了支持的 TLS 版本(Supported Version:TLS1.3)。值得注意的是 Version 字段必须要是 TLS1.2,这是因为 TLS1.2 已经在互联网上存在了 10 年。网络中大量的网络中间设备都十分老旧,这些网络设备会识别中间的 TLS 握手头部,所以 TLS1.3 的出现如果引入了未知的 TLS Version 必然会存在大量的握手失败。 -
Client Hello
中包含了非常重要的 key_share 扩展,客户端在发送之前,会自己根据 DHE 算法生成一个公私钥对。发送Client Hello
报文的时候会把这个公钥发过去,那么这个公钥就存在于 key_share 中,key_share 还包含了客户端所选择的曲线 X25519。总之,key_share 是客户端提前生成好的公钥信息。 -
Client Hello
里还包括了客户端支持的加密算法套件信息(Cipher Suites
)、椭圆曲线加密算法(supported_groups
)、服务名称(server_name
)、签名算法(signature_algorithms
)、psk_key秘钥交换模式、传输参数(quic_transport_parameters
)等等。
4、SetAlpn()设置应用扩展信息
- 本节详细介绍应用扩展信息的设置
bool TlsClientHandshaker::SetAlpn() {
std::vector<std::string> alpns = session()->GetAlpnsToOffer();
...
// SSL_set_alpn_protos expects a sequence of one-byte-length-prefixed
// strings.
uint8_t alpn[1024];
QuicDataWriter alpn_writer(sizeof(alpn), reinterpret_cast<char*>(alpn));
bool success = true;
for (const std::string& alpn_string : alpns) {
success = success && alpn_writer.WriteUInt8(alpn_string.size()) &&
alpn_writer.WriteStringPiece(alpn_string);
}
success =
success && (SSL_set_alpn_protos(ssl(), alpn, alpn_writer.length()) == 0);
.....
// Enable ALPS only for versions that use HTTP/3 frames.
for (const std::string& alpn_string : alpns) {
for (const ParsedQuicVersion& version : session()->supported_versions()) {
if (!version.UsesHttp3() || AlpnForVersion(version) != alpn_string) {
continue;
}
if (SSL_add_application_settings(
ssl(), reinterpret_cast<const uint8_t*>(alpn_string.data()),
alpn_string.size(), nullptr, /* settings_len = */ 0) != 1) {
return false;
}
break;
}
}
return true;
}
该函数通过应用会话层提供的协议支持,将其进行内存序列化,并通过
SSL_set_alpn_protos(..)和SSL_add_application_settings(...)
将其写入到对应的ssl
当中。-
SSL_set_alpn_protos()
用于设置支持的应用层协议(ALPN)的列表。在TLS握手协议中,客户端和服务器可以通过ALPN扩展协商所使用的应用层协议。该函数接受一个包含协议名称的字节数组和数组长度作为参数,可以设置多个协议名称,每个协议名称使用一个字节表示。例如,如果要将ALPN协议列表设置为HTTP/1.1和HTTP/2,则可以使用以下代码:const unsigned char protos[] = { 8, 'h', 't', 't', 'p', '/', '1', '.', '1', 2, 'h', '2' }; SSL_set_alpn_protos(ssl, protos, sizeof(protos));
-
SSL_add_application_settings()
是OpenSSL库中的一个函数,用于向SSL连接对象添加应用层协议设置。在TLS 1.3中,客户端和服务器可以通过应用层协议设置协商一些应用层协议相关的参数,例如HTTP/3中的流量控制和QPACK头部压缩设置。该函数接受一个包含设置数据的字节数组和数组长度作为参数。例如,如果要向连接对象添加HTTP/3相关的设置数据,则可以使用以下代码:const unsigned char settings[] = { 0x00, 0x0b, 0x00, 0x02, 0x7f, 0x00, 0x00, 0x01, 0x7f, 0x00, 0x00, 0x01 }; SSL_add_application_settings(ssl, settings, sizeof(settings));
-
Alpn字段对应下图(3)
5、SetTransportParameters()设置传输参数
bool TlsClientHandshaker::SetTransportParameters() {
TransportParameters params;
params.perspective = Perspective::IS_CLIENT;
params.legacy_version_information =
TransportParameters::LegacyVersionInformation();
params.legacy_version_information.value().version =
CreateQuicVersionLabel(session()->supported_versions().front());
params.version_information = TransportParameters::VersionInformation();
const QuicVersionLabel version = CreateQuicVersionLabel(session()->version());
params.version_information.value().chosen_version = version;
params.version_information.value().other_versions.push_back(version);
// 填充TransportParameters
if (!handshaker_delegate()->FillTransportParameters(¶ms)) {
return false;
}
// 内存序列化
std::vector<uint8_t> param_bytes;
return SerializeTransportParameters(params, ¶m_bytes) &&
SSL_set_quic_transport_params(ssl(), param_bytes.data(),
param_bytes.size()) == 1;
}
-
TransportParameters
是quic ietf握手阶段必须的参数,具体定义可阅读draft-ietf-quic-transport-29 - 通过
QuicConfig::FillTransportParameters()
对TransportParameters
进行填充,并通过SSL_set_quic_transport_params()
将参数写入到ssl
引擎 -
SSL_set_quic_transport_params()
用于向TLS连接对象添加QUIC传输参数,该函数接受一个包含参数数据的字节数组和数组长度作为参数。
bool QuicConfig::FillTransportParameters(TransportParameters* params) const {
if (original_destination_connection_id_to_send_.has_value()) {
params->original_destination_connection_id =
original_destination_connection_id_to_send_.value();
}
// 最大空闲超时(以毫秒为单位)。
params->max_idle_timeout_ms.set_value(
max_idle_timeout_to_send_.ToMilliseconds());
// Stateless reset token used in verifying stateless resets.
if (stateless_reset_token_.HasSendValue()) {
StatelessResetToken stateless_reset_token =
stateless_reset_token_.GetSendValue();
params->stateless_reset_token.assign(
reinterpret_cast<const char*>(&stateless_reset_token),
reinterpret_cast<const char*>(&stateless_reset_token) +
sizeof(stateless_reset_token));
}
// Limits the size of packets that the endpoint is willing to receive.
// This indicates that packets larger than this limit will be dropped.
params->max_udp_payload_size.set_value(GetMaxPacketSizeToSend());
// Indicates support for the DATAGRAM frame and the maximum frame size that
// the sender accepts. See draft-ietf-quic-datagram.
params->max_datagram_frame_size.set_value(GetMaxDatagramFrameSizeToSend());
// Contains the initial value for the maximum amount of data that can
// be sent on the connection.
params->initial_max_data.set_value(
GetInitialSessionFlowControlWindowToSend());
// The max stream data bidirectional transport parameters can be either local
// or remote. A stream is local iff it is initiated by the endpoint that sent
// the transport parameter (see the Transport Parameter Definitions section of
// draft-ietf-quic-transport). In this function we are sending transport
// parameters, so a local stream is one we initiated, which means an outgoing
// stream.
params->initial_max_stream_data_bidi_local.set_value(
GetInitialMaxStreamDataBytesOutgoingBidirectionalToSend());
// Initial flow control limit for peer-initiated bidirectional streams.
params->initial_max_stream_data_bidi_remote.set_value(
GetInitialMaxStreamDataBytesIncomingBidirectionalToSend());
// Initial flow control limit for unidirectional streams.
params->initial_max_stream_data_uni.set_value(
GetInitialMaxStreamDataBytesUnidirectionalToSend());
// Initial maximum number of bidirectional streams the peer may initiate.
params->initial_max_streams_bidi.set_value(
GetMaxBidirectionalStreamsToSend());
// Initial maximum number of unidirectional streams the peer may initiate.
params->initial_max_streams_uni.set_value(
GetMaxUnidirectionalStreamsToSend());
// Maximum amount of time in milliseconds by which the endpoint will
// delay sending acknowledgments.
params->max_ack_delay.set_value(GetMaxAckDelayToSendMs());
if (min_ack_delay_ms_.HasSendValue()) {
params->min_ack_delay_us.set_value(min_ack_delay_ms_.GetSendValue() *
kNumMicrosPerMilli);
}
// Exponent used to decode the ACK Delay field in ACK frames.
params->ack_delay_exponent.set_value(GetAckDelayExponentToSend());
// Indicates lack of support for connection migration.
params->disable_active_migration =
connection_migration_disabled_.HasSendValue() &&
connection_migration_disabled_.GetSendValue() != 0;
if (alternate_server_address_ipv6_.HasSendValue() ||
alternate_server_address_ipv4_.HasSendValue()) {
TransportParameters::PreferredAddress preferred_address;
if (alternate_server_address_ipv6_.HasSendValue()) {
preferred_address.ipv6_socket_address =
alternate_server_address_ipv6_.GetSendValue();
}
if (alternate_server_address_ipv4_.HasSendValue()) {
preferred_address.ipv4_socket_address =
alternate_server_address_ipv4_.GetSendValue();
}
if (preferred_address_connection_id_and_token_) {
preferred_address.connection_id =
preferred_address_connection_id_and_token_->first;
auto* begin = reinterpret_cast<const char*>(
&preferred_address_connection_id_and_token_->second);
auto* end =
begin + sizeof(preferred_address_connection_id_and_token_->second);
preferred_address.stateless_reset_token.assign(begin, end);
}
params->preferred_address =
std::make_unique<TransportParameters::PreferredAddress>(
preferred_address);
}
// The value that the endpoint included in the Source Connection ID field of
// the first Initial packet it sent.
if (active_connection_id_limit_.HasSendValue()) {
params->active_connection_id_limit.set_value(
active_connection_id_limit_.GetSendValue());
}
if (initial_source_connection_id_to_send_.has_value()) {
params->initial_source_connection_id =
initial_source_connection_id_to_send_.value();
}
if (retry_source_connection_id_to_send_.has_value()) {
params->retry_source_connection_id =
retry_source_connection_id_to_send_.value();
}
if (initial_round_trip_time_us_.HasSendValue()) {
params->initial_round_trip_time_us.set_value(
initial_round_trip_time_us_.GetSendValue());
}
if (connection_options_.HasSendValues() &&
!connection_options_.GetSendValues().empty()) {
params->google_connection_options = connection_options_.GetSendValues();
}
if (google_handshake_message_to_send_.has_value()) {
params->google_handshake_message = google_handshake_message_to_send_;
}
params->custom_parameters = custom_transport_parameters_to_send_;
return true;
}
该函数主要目的就是对
TransportParameters
进行填充,该结构里面定义的各种成员的语义,需要结合draft-ietf-quic-transport-29来查明,服务端解析该字段后会进行配置协商。同时通过该函数也可以看出,quic协议在初始化的时候通过
QuicConfig
设置很多个性化的初始参数,已达到业务需求。-
以上这些参数在抓包中的字段对应的是扩展
quic_transport_parameters
,如下图(4)
6、AdvanceHandshake()开始握手
void TlsHandshaker::AdvanceHandshake() {
....
QUIC_VLOG(1) << ENDPOINT << "Continuing handshake";
last_tls_alert_.reset();
int rv = SSL_do_handshake(ssl());
if (is_connection_closed()) {
return;
}
// If SSL_do_handshake return success(1) and we are in early data, it is
// possible that we have provided ServerHello to BoringSSL but it hasn't been
// processed. Retry SSL_do_handshake once will advance the handshake more in
// that case. If there are no unprocessed ServerHello, the retry will return a
// non-positive number.
if (rv == 1 && SSL_in_early_data(ssl())) {
OnEnterEarlyData();
rv = SSL_do_handshake(ssl());
if (is_connection_closed()) {
return;
}
.....
// The retry should either
// - Return <= 0 if the handshake is still pending, likely still in early
// data.
// - Return 1 if the handshake has _actually_ finished. i.e.
// SSL_in_early_data should be false.
//
// In either case, it should not both return 1 and stay in early data.
if (rv == 1 && SSL_in_early_data(ssl()) && !is_connection_closed()) {
QUIC_BUG(quic_handshaker_stay_in_early_data)
<< "The original and the retry of SSL_do_handshake both returned "
"success and in early data";
CloseConnection(QUIC_HANDSHAKE_FAILED,
"TLS handshake failed: Still in early data after retry");
return;
}
}
if (rv == 1) {
FinishHandshake();
return;
}
int ssl_error = SSL_get_error(ssl(), rv);
if (ssl_error == expected_ssl_error_) {
return;
}
}
- 该函数相对比较简单,调用
SSL_do_handshake()
函数驱动ssl
引擎进入握手状态,生成client hello
消息,经QuicSession、QuicConnection
处理并发送到网络后该函数会返回-1,SSL_get_error(ssl(), rv)
会返回2,表示需要更多的信息进行握手。 - 至此,客户端握手的第一个流程就完成了,接下来就是等待服务端回复
Initial+HandleShake
包,进行双向协商握手流程。
总结
- 通过本文深入学习google quiche项目中客户端握手
initial
阶段Client Hello
消息的生成的逻辑,同时通过本文学习掌握ssl
握手过程中基本API的使用和含义,便于后续代码分析。 - 通过本文学习,针对
quic
协议TLS1.3握手,需要处理哪些信息,从本文来看,握手前可以自定义证书验证函数、签名算法列表、秘钥交换算法列表、需要配置传输参数,应用扩展协议、服务端Host名称等等。 - 当服务端收到客户端的
InitIal
报文后,会生成Initial+Handshake
报文,并发送给客户端,下文将分析客户端怎么处理服务端下发的握手报文的。 - 同时本文结合
Client Hello
抓包文件,进一步认识和加深对TLS1.3握手流程的影响