google quic tls握手原理(二)

前言

  • 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里面的内容
    004.png
  • 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)


    004_1.png

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(&params)) {
    return false;
  }
  // 内存序列化
  std::vector<uint8_t> param_bytes;
  return SerializeTransportParameters(params, &param_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)

    004_2.png

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握手流程的影响

参考文献

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容