google quic tls握手原理(一)

前言

  • 本文意在分析google quic項(xiàng)目中tls握手引擎的工作原理
  • 認(rèn)識(shí)client helloserver hello是在哪里生成的,同時(shí)了解quic的握手基本流程
  • 同時(shí)通過(guò)本文學(xué)習(xí)來(lái)進(jìn)一步了解TlsHandshaker模塊和其他模塊之間的關(guān)系

認(rèn)識(shí)TlsConnection模塊

TlsConnection模塊核心定義

class QUIC_EXPORT_PRIVATE TlsConnection {
 public:
  // A TlsConnection::Delegate implements the methods that are set as callbacks
  // of TlsConnection.
  class QUIC_EXPORT_PRIVATE Delegate {
   public:
    virtual ~Delegate() {}

   protected:
    // Certificate management functions:

    // Verifies the peer's certificate chain. It may use
    // SSL_get0_peer_certificates to get the cert chain. This method returns
    // ssl_verify_ok if the cert is valid, ssl_verify_invalid if it is invalid,
    // or ssl_verify_retry if verification is happening asynchronously.
    virtual enum ssl_verify_result_t VerifyCert(uint8_t* out_alert) = 0;

    // QUIC-TLS interface functions:

    // SetWriteSecret provides the encryption secret used to encrypt messages at
    // encryption level |level|. The secret provided here is the one from the
    // TLS 1.3 key schedule (RFC 8446 section 7.1), in particular the handshake
    // traffic secrets and application traffic secrets. The provided write
    // secret must be used with the provided cipher suite |cipher|.
    virtual void SetWriteSecret(EncryptionLevel level, const SSL_CIPHER* cipher,
                                absl::Span<const uint8_t> write_secret) = 0;

    // SetReadSecret is similar to SetWriteSecret, except that it is used for
    // decrypting messages. SetReadSecret at a particular level is always called
    // after SetWriteSecret for that level, except for ENCRYPTION_ZERO_RTT,
    // where the EncryptionLevel for SetWriteSecret is
    // ENCRYPTION_FORWARD_SECURE.
    virtual bool SetReadSecret(EncryptionLevel level, const SSL_CIPHER* cipher,
                               absl::Span<const uint8_t> read_secret) = 0;

    // WriteMessage is called when there is |data| from the TLS stack ready for
    // the QUIC stack to write in a crypto frame. The data must be transmitted
    // at encryption level |level|.
    virtual void WriteMessage(EncryptionLevel level,
                              absl::string_view data) = 0;

    // FlushFlight is called to signal that the current flight of messages have
    // all been written (via calls to WriteMessage) and can be flushed to the
    // underlying transport.
    virtual void FlushFlight() = 0;

    // SendAlert causes this TlsConnection to close the QUIC connection with an
    // error code corersponding to the TLS alert description |desc| sent at
    // level |level|.
    virtual void SendAlert(EncryptionLevel level, uint8_t desc) = 0;

    // Informational callback from BoringSSL. This callback is disabled by
    // default, but can be enabled by TlsConnection::EnableInfoCallback.
    //
    // See |SSL_CTX_set_info_callback| for the meaning of |type| and |value|.
    virtual void InfoCallback(int type, int value) = 0;

    // Message callback from BoringSSL, for debugging purposes. See
    // |SSL_CTX_set_msg_callback| for how to interpret |version|,
    // |content_type|, and |data|.
    virtual void MessageCallback(bool is_write, int version, int content_type,
                                 absl::string_view data) = 0;

    friend class TlsConnection;
  };
  ...
  SSL* ssl() const { return ssl_.get(); }      
      
 protected:
  // TlsConnection does not take ownership of |ssl_ctx| or |delegate|; they must
  // outlive the TlsConnection object.
  TlsConnection(SSL_CTX* ssl_ctx, Delegate* delegate, QuicSSLConfig ssl_config);
  ....
  // From a given SSL* |ssl|, returns a pointer to the TlsConnection that it
  // belongs to. This helper method allows the callbacks set in BoringSSL to be
  // dispatched to the correct TlsConnection from the SSL* passed into the
  // callback.
  static TlsConnection* ConnectionFromSsl(const SSL* ssl);
  ...
 private:
  ...
  Delegate* delegate_;
  bssl::UniquePtr<SSL> ssl_;
  QuicSSLConfig ssl_config_;
};
  • 以上為TlsConnection模塊的核心定義,核心成員是Delegate机打、SSL赏寇、QuicSSLConfig
  • 其中SSL為boring ssl引擎核心休蟹、QuicSSLConfig為加密引擎相關(guān)配置袭蝗,用于初始化SSL引擎使用
  • Delegate為一個(gè)ssl 委托接口類淮蜈,在該項(xiàng)目中哪工,ssl并沒(méi)有真實(shí)綁定socket 連接奥此,而是通過(guò)注冊(cè)回調(diào)的方式使用ssl引擎,也就是通過(guò)回調(diào)的方式給上次提供秘鑰套件等功能
  • 同時(shí)通過(guò)成員函數(shù)SSL ssl()*將ssl引擎提供給到其他模塊使用
  • 通過(guò)TlsConnection模塊的構(gòu)造函數(shù)可以看出,實(shí)例化一個(gè)TlsConnection模塊需喲傳入SSL_CTX、Delegate以及QuicSSLConfig
  • TlsConnection模塊本身也提供了創(chuàng)建SSL_CTX的靜態(tài)函數(shù)雁比,所有的SSL指針創(chuàng)建都需要傳入一個(gè)SSL_CTX
  • 以下為TlsConnection構(gòu)造函數(shù)
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());
  }
  if (ssl_config_.disable_ticket_support.has_value()) {
    if (*ssl_config_.disable_ticket_support) {
      SSL_set_options(ssl(), SSL_OP_NO_TICKET);
    }
  }
}
  • 首先創(chuàng)建SSL實(shí)例稚虎,并通過(guò)Api SSL_set_ex_data()TlsConnection和當(dāng)前創(chuàng)建的SSL實(shí)例進(jìn)行關(guān)聯(lián)
  • 其次是通過(guò)QuicSSLConfig對(duì)當(dāng)前創(chuàng)建的SSL實(shí)例進(jìn)行配置
  • 那么怎么通過(guò)SSL實(shí)例拿到當(dāng)前的TlsConnection指針呢?
TlsConnection* TlsConnection::ConnectionFromSsl(const SSL* ssl) {
  return reinterpret_cast<TlsConnection*>(SSL_get_ex_data(
      ssl, SslIndexSingleton::GetInstance()->ssl_ex_data_index_connection()));
}
  • TlsConnection模塊通過(guò)靜態(tài)成員函數(shù)ConnectionFromSsl(...)拿到自己的指針實(shí)例

SSL_CTX創(chuàng)建

  • 上面提到了所有的SSL實(shí)例都需要SSL_CTX偎捎,那么它是在什么時(shí)候創(chuàng)建的蠢终,以及在google quic中是怎么使用的序攘?
class QUIC_EXPORT_PRIVATE TlsConnection {
 ....
 protected:
  // Creates an SSL_CTX and configures it with the options that are appropriate
  // for both client and server. The caller is responsible for ownership of the
  // newly created struct.
  static bssl::UniquePtr<SSL_CTX> CreateSslCtx();

 private:
  // TlsConnection implements SSL_QUIC_METHOD, which provides the interface
  // between BoringSSL's TLS stack and a QUIC implementation.
  static const SSL_QUIC_METHOD kSslQuicMethod;
};
const SSL_QUIC_METHOD TlsConnection::kSslQuicMethod{
    TlsConnection::SetReadSecretCallback, // int (*set_read_secret)->用于設(shè)置用于解密傳入數(shù)據(jù)的加密密鑰
    TlsConnection::SetWriteSecretCallback,// int (*set_write_secret)->用于設(shè)置加密傳入數(shù)據(jù)的加密密鑰
    TlsConnection::WriteMessageCallback,  // int (*add_handshake_data)->將TLS握手期間產(chǎn)生的數(shù)據(jù)添加到SSL連接的緩沖區(qū)中
    TlsConnection::FlushFlightCallback,   // int (*flush_flight)
    TlsConnection::SendAlertCallback};    // int (*send_alert)->握手出錯(cuò)的時(shí)候回調(diào)
// static
void TlsConnection::MessageCallback(int is_write, int version, int content_type,
                                    const void* buf, size_t len, SSL* ssl,
                                    void*) {
  ConnectionFromSsl(ssl)->delegate_->MessageCallback(
      is_write != 0, version, content_type,
      absl::string_view(static_cast<const char*>(buf), len));
}
// static
bssl::UniquePtr<SSL_CTX> TlsConnection::CreateSslCtx() {
  CRYPTO_library_init();
  bssl::UniquePtr<SSL_CTX> ssl_ctx(SSL_CTX_new(TLS_with_buffers_method()));
  SSL_CTX_set_min_proto_version(ssl_ctx.get(), TLS1_3_VERSION);
  SSL_CTX_set_max_proto_version(ssl_ctx.get(), TLS1_3_VERSION);
  SSL_CTX_set_quic_method(ssl_ctx.get(), &kSslQuicMethod);
  SSL_CTX_set_msg_callback(ssl_ctx.get(), &MessageCallback);
  return ssl_ctx;
}
  • SslCtx的創(chuàng)建和初始化使用SSL_CTX_set_quic_method從外部實(shí)現(xiàn)的方式,并未真正綁定真實(shí)的socket io
  • 通過(guò)自定義實(shí)現(xiàn)SSL_QUIC_METHOD函數(shù)結(jié)構(gòu)指針對(duì)外提供加解密的秘鑰套件
  • 在各函數(shù)回調(diào)的時(shí)候通過(guò)ConnectionFromSsl(ssl)拿到對(duì)應(yīng)的TlsConnection實(shí)例寻拂,然后再通過(guò)其成員Delegate程奠,在Delegate的實(shí)現(xiàn)中進(jìn)行處理

TlsConnection子類介紹

001.png
  • TlsConnection主要有兩大派生子類,TlsClientConnection用于客戶端祭钉、TlsServerConnection用于服務(wù)端
  • 服務(wù)端和客戶端對(duì)SSL_CTX的初始化會(huì)有些區(qū)別瞄沙,同時(shí)對(duì)SSL實(shí)例的設(shè)置也有些差異
// static
bssl::UniquePtr<SSL_CTX> TlsClientConnection::CreateSslCtx(
    bool enable_early_data) {
  bssl::UniquePtr<SSL_CTX> ssl_ctx = TlsConnection::CreateSslCtx();
  // Configure certificate verification.
  // 默認(rèn)驗(yàn)證服務(wù)端證書,通過(guò)VerifyCallback來(lái)驗(yàn)證證書的有效性
  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.
  // 禁用緩存慌核,僅為客戶端會(huì)話啟用緩存
  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允許應(yīng)用程序設(shè)置自定義驗(yàn)證函數(shù)距境,以在SSL / TLS握手過(guò)程中驗(yàn)證對(duì)等證書。該函數(shù)允許應(yīng)用程序執(zhí)行超出OpenSSL執(zhí)行的標(biāo)準(zhǔn)證書驗(yàn)證之外的其他檢查垮卓。自定義驗(yàn)證函數(shù)接受對(duì)等方提供的X509證書的指針垫桂,并返回一個(gè)值,指示證書是否有效扒接。如果證書無(wú)效伪货,則SSL / TLS握手將失敗,并且連接將終止钾怔。
  • SSL_CTX_set_reverify_on_resume用于設(shè)置 SSL/TLS 會(huì)話恢復(fù)時(shí)是否重新驗(yàn)證對(duì)端證書。SSL/TLS 會(huì)話恢復(fù)是一種優(yōu)化方式蒙挑,可以在 SSL/TLS 握手時(shí)重用之前的會(huì)話參數(shù)宗侦,避免重新執(zhí)行完整的 SSL/TLS 握手。但是忆蚀,由于會(huì)話恢復(fù)時(shí)不重新驗(yàn)證對(duì)端證書矾利,存在一定的安全風(fēng)險(xiǎn)。因此馋袜,SSL_CTX_set_reverify_on_resume 函數(shù)可以用于控制 SSL/TLS 會(huì)話恢復(fù)時(shí)是否重新驗(yàn)證對(duì)端證書
  • SSL_CTX_set_session_cache_mode用于設(shè)置給定SSL上下文的會(huì)話緩存模式男旗。會(huì)話緩存模式?jīng)Q定如何存儲(chǔ)和檢索SSL會(huì)話信息以便在隨后的連接中重用。有幾種可用的模式欣鳖,包括禁用緩存察皇,僅為客戶端會(huì)話啟用緩存,僅為服務(wù)器會(huì)話啟用緩存以及同時(shí)為客戶端和服務(wù)器會(huì)話啟用緩存泽台。適當(dāng)?shù)哪J饺Q于應(yīng)用程序的具體用例和安全要求
  • SSL_CTX_set_session_cache_mode用于設(shè)置給定SSL上下文的會(huì)話緩存模式什荣。會(huì)話緩存模式確定SSL會(huì)話信息如何存儲(chǔ)和檢索以便在后續(xù)連接中重用。有幾種可用的模式怀酷,包括禁用緩存稻爬,僅為客戶端會(huì)話啟用緩存,僅為服務(wù)器會(huì)話啟用緩存蜕依,以及同時(shí)為客戶端和服務(wù)器會(huì)話啟用緩存桅锄。適當(dāng)?shù)哪J饺Q于應(yīng)用程序的特定用例和安全要求琉雳。
  • SSL_CTX_sess_set_new_cb用于設(shè)置在創(chuàng)建新的SSL會(huì)話時(shí)調(diào)用的回調(diào)函數(shù)。當(dāng)SSL會(huì)話被創(chuàng)建時(shí)友瘤,SSL會(huì)調(diào)用這個(gè)回調(diào)函數(shù)咐吼,并將新會(huì)話的SSL_SESSION結(jié)構(gòu)體指針作為參數(shù)傳遞給它。應(yīng)用程序可以使用這個(gè)回調(diào)函數(shù)來(lái)執(zhí)行與新SSL會(huì)話相關(guān)的自定義操作商佑,例如將會(huì)話信息存儲(chǔ)在數(shù)據(jù)庫(kù)或磁盤上以供將來(lái)重用锯茄。如果未設(shè)置此回調(diào)函數(shù),則新SSL會(huì)話將不會(huì)被自動(dòng)緩存茶没,而是每次都會(huì)創(chuàng)建新的會(huì)話
  • SSL_CTX_set_early_data_enabled用于啟用或禁用TLS 1.3中的0-RTT數(shù)據(jù)功能肌幽。0-RTT數(shù)據(jù)允許客戶端在完成握手之前發(fā)送加密數(shù)據(jù),從而提高了應(yīng)用程序的性能抓半。啟用此功能時(shí)喂急,客戶端可以在第一次連接時(shí)發(fā)送一些數(shù)據(jù),而無(wú)需等待服務(wù)器確認(rèn)握手已完成笛求。但是需要注意廊移,0-RTT數(shù)據(jù)的安全性可能受到威脅,因?yàn)樗赡軙?huì)被中間人攻擊者攔截和篡改探入。因此狡孔,應(yīng)該謹(jǐn)慎使用0-RTT數(shù)據(jù)功能,并且只在對(duì)應(yīng)用程序的性能提升有明確需求且安全性風(fēng)險(xiǎn)可接受的情況下啟用它蜂嗽。
// static
bssl::UniquePtr<SSL_CTX> TlsServerConnection::CreateSslCtx(
    ProofSource* proof_source) {
  bssl::UniquePtr<SSL_CTX> ssl_ctx = TlsConnection::CreateSslCtx();

  // Server does not request/verify client certs by default. Individual server
  // connections may call SSL_set_custom_verify on their SSL object to request
  // client certs.

  SSL_CTX_set_tlsext_servername_callback(ssl_ctx.get(),
                                         &TlsExtServernameCallback);
  SSL_CTX_set_alpn_select_cb(ssl_ctx.get(), &SelectAlpnCallback, nullptr);
  // We don't actually need the TicketCrypter here, but we need to know
  // whether it's set.
  if (proof_source->GetTicketCrypter()) {
    QUIC_CODE_COUNT(quic_session_tickets_enabled);
    SSL_CTX_set_ticket_aead_method(ssl_ctx.get(),
                                   &TlsServerConnection::kSessionTicketMethod);
  } else {
    QUIC_CODE_COUNT(quic_session_tickets_disabled);
  }

  SSL_CTX_set_early_data_enabled(ssl_ctx.get(), 1);

  SSL_CTX_set_select_certificate_cb(
      ssl_ctx.get(), &TlsServerConnection::EarlySelectCertCallback);
  SSL_CTX_set_options(ssl_ctx.get(), SSL_OP_CIPHER_SERVER_PREFERENCE);

  // Allow ProofSource to change SSL_CTX settings.
  proof_source->OnNewSslCtx(ssl_ctx.get());

  return ssl_ctx;
}
  • SSL_OP_CIPHER_SERVER_PREFERENCE用于設(shè)置SSL/TLS握手期間的密碼套件協(xié)商順序苗膝。啟用此選項(xiàng)后,服務(wù)器將優(yōu)先選擇自己支持的密碼套件中在客戶端支持的密碼套件中排名靠前的那個(gè)植旧。這樣可以增加服務(wù)器端選擇更安全的密碼套件的可能性辱揭,并減少使用較弱密碼套件的風(fēng)險(xiǎn)。需要注意的是病附,啟用此選項(xiàng)可能會(huì)導(dǎo)致某些客戶端無(wú)法連接到服務(wù)器问窃,因?yàn)樗鼈兛赡軆H支持不在服務(wù)器的優(yōu)先列表中的密碼套件。

  • SSL_CTX_set_alpn_select_cb用于設(shè)置用于應(yīng)用程序自定義協(xié)議協(xié)商邏輯的回調(diào)函數(shù)完沪。在TLS握手期間域庇,客戶端和服務(wù)器需要協(xié)商選擇一個(gè)共同支持的應(yīng)用層協(xié)議,這個(gè)協(xié)議可以是HTTP/1.1丽焊、HTTP/2较剃、WebSocket等等。如果應(yīng)用程序需要使用自定義的協(xié)議技健,可以通過(guò)實(shí)現(xiàn)一個(gè)回調(diào)函數(shù)并使用SSL_CTX_set_alpn_select_cb函數(shù)將其注冊(cè)到SSL上下文中写穴,來(lái)進(jìn)行自定義的協(xié)議協(xié)商〈萍回調(diào)函數(shù)將在客戶端和服務(wù)器進(jìn)行協(xié)議協(xié)商時(shí)被調(diào)用啊送,它需要輸入一個(gè)支持的協(xié)議列表偿短,以及它們的長(zhǎng)度,然后輸出選擇的協(xié)議馋没。需要注意的是昔逗,回調(diào)函數(shù)必須是線程安全的,因?yàn)樗赡軙?huì)在多個(gè)線程中同時(shí)被調(diào)用篷朵。

  • SSL_CTX_set_tlsext_servername_callback用于設(shè)置當(dāng)客戶端發(fā)送Server Name Indication (SNI)擴(kuò)展時(shí)的回調(diào)函數(shù)勾怒。 SNI擴(kuò)展允許客戶端在建立TLS連接時(shí)指定想要連接的服務(wù)器名稱。如果服務(wù)器支持SNI擴(kuò)展声旺,它可以根據(jù)客戶端發(fā)送的SNI擴(kuò)展來(lái)選擇相應(yīng)的證書和密鑰笔链,從而使得一個(gè)服務(wù)器可以為多個(gè)域名提供服務(wù)∪回調(diào)函數(shù)需要輸入SSL連接鉴扫、SNI擴(kuò)展的類型和值,并返回0或1澈缺,以指示是否成功選擇了相應(yīng)的證書和密鑰坪创。如果回調(diào)函數(shù)返回0,則表示沒(méi)有選擇證書和密鑰姐赡,TLS握手將失敗莱预。

  • SSL_CTX_set_select_certificate_cb用于設(shè)置一個(gè)回調(diào)函數(shù),以在TLS握手期間選擇服務(wù)器端證書雏吭∷回調(diào)函數(shù)將在服務(wù)器端收到客戶端的ClientHello消息后被調(diào)用,它需要從SSL連接中獲取客戶端發(fā)送的信息杖们,然后選擇合適的證書來(lái)進(jìn)行TLS握手。需要注意的是肩狂,回調(diào)函數(shù)必須是線程安全的摘完,因?yàn)樗赡軙?huì)在多個(gè)線程中同時(shí)被調(diào)用。

  • SSL_CTX_set_ticket_aead_method用于設(shè)置用于處理TLS會(huì)話票據(jù)加密的加密算法傻谁。TLS會(huì)話票據(jù)是一種機(jī)制孝治,用于在客戶端和服務(wù)器之間共享TLS會(huì)話狀態(tài),從而提高HTTPS連接的性能和安全性审磁。在TLS 1.3中谈飒,會(huì)話票據(jù)被加密并使用AEAD算法進(jìn)行保護(hù)。SSL_CTX_set_ticket_aead_method函數(shù)可以用于設(shè)置用于加密和解密會(huì)話票據(jù)的AEAD算法态蒂,以及相關(guān)的加密密鑰和解密密鑰

認(rèn)識(shí)TlsHandshaker

TlsHandshaker的派生關(guān)系

002.png
  • TlsHandshakerTlsConnection::DelegateCrytoMessageParser派生杭措,同時(shí)重寫兩個(gè)父類的接口API
  • 其中CrytoMessageParser提供如下方法:
bool TlsHandshaker::ProcessInput(absl::string_view input,
                                 EncryptionLevel level) {
  ....
  // TODO(nharper): Call SSL_quic_read_level(ssl()) and check whether the
  // encryption level BoringSSL expects matches the encryption level that we
  // just received input at. If they mismatch, should ProcessInput return true
  // or false? If data is for a future encryption level, it should be queued for
  // later?
  if (SSL_provide_quic_data(ssl(), TlsConnection::BoringEncryptionLevel(level),
                            reinterpret_cast<const uint8_t*>(input.data()),
                            input.size()) != 1) {
    // SSL_provide_quic_data can fail for 3 reasons:
    // - API misuse (calling it before SSL_set_custom_quic_method, which we
    //   call in the TlsHandshaker c'tor)
    // - Memory exhaustion when appending data to its buffer
    // - Data provided at the wrong encryption level
    //
    // Of these, the only sensible error to handle is data provided at the wrong
    // encryption level.
    //
    // Note: the error provided below has a good-sounding enum value, although
    // it doesn't match the description as it's a QUIC Crypto specific error.
    parser_error_ = QUIC_INVALID_CRYPTO_MESSAGE_TYPE;
    parser_error_detail_ = "TLS stack failed to receive data";
    return false;
  }
  AdvanceHandshake();
  return true;
}
  • SSL_provide_quic_data 用于向 TLS 連接提供 QUIC數(shù)據(jù)。當(dāng)使用 QUIC 作為 TLS 的傳輸層協(xié)議時(shí)钾恢,它將被用到手素,可以實(shí)現(xiàn)更快速和更有效的互聯(lián)網(wǎng)通信鸳址。該函數(shù)接受一個(gè)指向 SSL 對(duì)象的指針和一個(gè)包含 QUIC 數(shù)據(jù)的緩沖區(qū)的指針作為輸入,并返回一個(gè)整數(shù)值泉懦,指示操作的成功或失敗
  • 該函數(shù)的核心作用是將收到的對(duì)端的CryptoStream的信息稿黍,如Initial包和Handshake包的payload部分輸入到ssl引擎,如果輸入成功則回調(diào)用AdvanceHandshake()函數(shù)進(jìn)而調(diào)用SSL_do_handshake()進(jìn)行握手處理
  • TlsConnection::Delegate接口提供的API在上一節(jié)中有提到崩哩,主要有加解密引擎的設(shè)置函數(shù)巡球、握手信息回調(diào)函數(shù)、錯(cuò)誤處理函數(shù)等邓嘹,該模塊實(shí)現(xiàn)了這些接口并在對(duì)應(yīng)的函數(shù)中進(jìn)行了處理酣栈,如創(chuàng)建加解密引擎,轉(zhuǎn)發(fā)握手信息等,其實(shí)現(xiàn)如下:
void TlsHandshaker::SetWriteSecret(EncryptionLevel level,
                                   const SSL_CIPHER* cipher,
                                   absl::Span<const uint8_t> write_secret) {
  std::unique_ptr<QuicEncrypter> encrypter =
      QuicEncrypter::CreateFromCipherSuite(SSL_CIPHER_get_id(cipher));
  const EVP_MD* prf = Prf(cipher);
  CryptoUtils::SetKeyAndIV(prf, write_secret,
                           handshaker_delegate_->parsed_version(),
                           encrypter.get());
  std::vector<uint8_t> header_protection_key =
      CryptoUtils::GenerateHeaderProtectionKey(
          prf, write_secret, handshaker_delegate_->parsed_version(),
          encrypter->GetKeySize());
  encrypter->SetHeaderProtectionKey(
      absl::string_view(reinterpret_cast<char*>(header_protection_key.data()),
                        header_protection_key.size()));
  if (level == ENCRYPTION_FORWARD_SECURE) {
    QUICHE_DCHECK(latest_write_secret_.empty());
    latest_write_secret_.assign(write_secret.begin(), write_secret.end());
    one_rtt_write_header_protection_key_ = header_protection_key;
  }
  handshaker_delegate_->OnNewEncryptionKeyAvailable(level,
                                                    std::move(encrypter));
}
  • 通過(guò)ssl提供的加密套件秘鑰創(chuàng)建QuicEncrypter加密模塊引擎吴超,并通過(guò)HandshakerDelegateInterface接口將其轉(zhuǎn)發(fā)到其他模塊
bool TlsHandshaker::SetReadSecret(EncryptionLevel level,
                                  const SSL_CIPHER* cipher,
                                  absl::Span<const uint8_t> read_secret) {
  std::unique_ptr<QuicDecrypter> decrypter =
      QuicDecrypter::CreateFromCipherSuite(SSL_CIPHER_get_id(cipher));
  const EVP_MD* prf = Prf(cipher);
  CryptoUtils::SetKeyAndIV(prf, read_secret,
                           handshaker_delegate_->parsed_version(),
                           decrypter.get());
  std::vector<uint8_t> header_protection_key =
      CryptoUtils::GenerateHeaderProtectionKey(
          prf, read_secret, handshaker_delegate_->parsed_version(),
          decrypter->GetKeySize());
  decrypter->SetHeaderProtectionKey(
      absl::string_view(reinterpret_cast<char*>(header_protection_key.data()),
                        header_protection_key.size()));
  if (level == ENCRYPTION_FORWARD_SECURE) {
    QUICHE_DCHECK(latest_read_secret_.empty());
    latest_read_secret_.assign(read_secret.begin(), read_secret.end());
    one_rtt_read_header_protection_key_ = header_protection_key;
  }
  return handshaker_delegate_->OnNewDecryptionKeyAvailable(
      level, std::move(decrypter),
      /*set_alternative_decrypter=*/false,
      /*latch_once_used=*/false);
}
  • 通過(guò)ssl提供的解密套件秘鑰創(chuàng)建QuicDecrypter解密模塊引擎钉嘹,并通過(guò)HandshakerDelegateInterface接口將其轉(zhuǎn)發(fā)到其他模塊
void TlsHandshaker::WriteMessage(EncryptionLevel level,
                                 absl::string_view data) {
  stream_->WriteCryptoData(level, data);
}
  • ssl生成握手信息,然后通過(guò)QuicCryptoStream進(jìn)行對(duì)應(yīng)的發(fā)送處理鲸阻,如initial包和handshake包的payload信息就是這個(gè)吧跋涣?

TlsHandshaker的核心成員變量

class QUIC_EXPORT_PRIVATE TlsHandshaker : public TlsConnection::Delegate,
                                          public CryptoMessageParser {
 public:
  // TlsHandshaker does not take ownership of any of its arguments; they must
  // outlive the TlsHandshaker.
  TlsHandshaker(QuicCryptoStream* stream, QuicSession* session); 
  ...
 private:
  ...
  QuicCryptoStream* stream_;
  HandshakerDelegateInterface* handshaker_delegate_;
};
TlsHandshaker::TlsHandshaker(QuicCryptoStream* stream, QuicSession* session)
    : stream_(stream), handshaker_delegate_(session) {}
  • 由此可看出QuicSessionHandshakerDelegateInterface接口的派生類,這樣TlsHandshakerQuicSession就關(guān)聯(lián)起來(lái)了
  • QuicCryptoStream模塊對(duì)ssl引擎生成的握手信息進(jìn)行組裝打包成QuicCryptoFrame,再經(jīng)由其他模塊發(fā)生到網(wǎng)絡(luò)

TlsHandshaker的子類介紹

TlsServerHandshaker
class QUIC_EXPORT_PRIVATE TlsServerHandshaker
    : public TlsHandshaker,
      public TlsServerConnection::Delegate,
      public ProofSourceHandleCallback,
      public QuicCryptoServerStreamBase {
      ......
 protected:          
  const TlsConnection* tls_connection() const override {
    return &tls_connection_;
  }      
          
 private:
  TlsServerConnection tls_connection_;
}
TlsServerHandshaker::TlsServerHandshaker(
    QuicSession* session, const QuicCryptoServerConfig* crypto_config)
    : TlsHandshaker(this, session),
      QuicCryptoServerStreamBase(session),
      proof_source_(crypto_config->proof_source()),
      pre_shared_key_(crypto_config->pre_shared_key()),
      crypto_negotiated_params_(new QuicCryptoNegotiatedParameters),
      tls_connection_(crypto_config->ssl_ctx(), this, session->GetSSLConfig()),
      crypto_config_(crypto_config) {

  // Configure the SSL to be a server.
  SSL_set_accept_state(ssl());

  // Make sure we use the right TLS extension codepoint.
  int use_legacy_extension = 0;
  if (session->version().UsesLegacyTlsExtension()) {
    use_legacy_extension = 1;
  }
  SSL_set_quic_use_legacy_codepoint(ssl(), use_legacy_extension);

#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
}
  • TlsServerHandshakerTlsHandshaker鸟悴、TlsServerConnection::Delegate陈辱、ProofSourceHandleCallback、QuicCryptoServerStreamBase派生

  • QuicCryptoServerStreamBaseQuicCryptoStream的子類细诸,由此可見在上面提及到的TlsHandshaker模塊中的核心成員變量QuicCryptoStream stream_針對(duì)服務(wù)端的實(shí)現(xiàn)其實(shí)就是TlsServerHandshaker*自身

  • 在上一節(jié)中提到的TlsServerConnection模塊其內(nèi)部操作沛贪,實(shí)際上都是基于其Delegate成員派發(fā)進(jìn)行處理,從而可知震贵,TlsServerConnection中的大部分回調(diào)操作都是在TlsServerHandshaker完成

  • ProofSourceHandleCallback是證書相關(guān)的API利赋,針對(duì)服務(wù)端也是在該模塊中進(jìn)行處理諸如證書簽名、證書選擇猩系、應(yīng)用參數(shù)設(shè)置等等很多都是在該模塊中具體實(shí)現(xiàn)

  • TlsServerHandshaker定義成員變量TlsServerConnection tls_connection_媚送,這樣TlsServerHandshakerTlsServerConnection就正式關(guān)聯(lián)起來(lái)了,在該模塊中可以對(duì)TlsServerConnection進(jìn)行相應(yīng)的操作

TlsClientHandshaker
// An implementation of QuicCryptoClientStream::HandshakerInterface which uses
// TLS 1.3 for the crypto handshake protocol.
class QUIC_EXPORT_PRIVATE TlsClientHandshaker
    : public TlsHandshaker,
      public QuicCryptoClientStream::HandshakerInterface,
      public TlsClientConnection::Delegate {
 public:
  // |crypto_config| must outlive 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);
  // From QuicCryptoClientStream::HandshakerInterface
  bool CryptoConnect() override;          
 protected:
  const TlsConnection* tls_connection() const override {
    return &tls_connection_;
  }
  ...
 private:
  ...
  QuicSession* session_;
  TlsClientConnection tls_connection_;
  // Used for session resumption. |session_cache_| is owned by the
  // QuicCryptoClientConfig passed into TlsClientHandshaker's constructor.
  SessionCache* session_cache_;
};
  • TlsClientHandshakerTlsHandshaker、QuicCryptoClientStream::HandshakerInterface寇甸、TlsClientConnection::Delegate派生
  • QuicCryptoClientStream::HandshakerInterface為更上層業(yè)務(wù)提供對(duì)應(yīng)的API塘偎,比如CryptoConnect(),這個(gè)是客戶端連接服務(wù)端的入口
  • TlsClientConnection::DelegateTlsConnection子類介紹中有提及主要提供InsertSession()API,在TlsClientConnection::CreateSslCtx函數(shù)中,通過(guò)調(diào)用SSL_CTX_sess_set_new_cb(ssl_ctx.get(), NewSessionCallback)設(shè)置了一個(gè)回調(diào)函數(shù)拿霉,該函數(shù)的核心作用是當(dāng)tls創(chuàng)建一個(gè)TLS session的時(shí)候觸發(fā)吟秩,而該回調(diào)函數(shù)的核心處理就是通過(guò)TlsClientConnection::Delegate接口的InsertSession()進(jìn)行轉(zhuǎn)發(fā),實(shí)現(xiàn)如下
void TlsClientHandshaker::InsertSession(bssl::UniquePtr<SSL_SESSION> session) {
  if (!received_transport_params_) {
    QUIC_BUG(quic_bug_10576_8) << "Transport parameters isn't received";
    return;
  }
  if (session_cache_ == nullptr) {
    QUIC_DVLOG(1) << "No session cache, not inserting a session";
    return;
  }
  if (has_application_state_ && !received_application_state_) {
    // Application state is not received yet. cache the sessions.
    if (cached_tls_sessions_[0] != nullptr) {
      cached_tls_sessions_[1] = std::move(cached_tls_sessions_[0]);
    }
    cached_tls_sessions_[0] = std::move(session);
    return;
  }
  session_cache_->Insert(server_id_, std::move(session),
                         *received_transport_params_,
                         received_application_state_.get());
}
  • SessionCache是在服務(wù)啟動(dòng)階段初始化的一個(gè)模塊,用于狀態(tài)恢復(fù)绽淘,在后續(xù)會(huì)有單獨(dú)文章分析,這里只做引出
  • 這里只要了解涵防,同ssl引擎收到服務(wù)端的握手信息,并成功創(chuàng)建SSL_SESSION后會(huì)觸發(fā)該函數(shù)

總結(jié):

  • 本文并未深入學(xué)習(xí)QUIC TLS1.3握手的深入流程收恢,只是從google quiche的代碼設(shè)計(jì)上簡(jiǎn)單了解和握手相關(guān)的一些模塊的核心成員和定義以及對(duì)應(yīng)的關(guān)系
  • TlsClientHandshaker為客戶端握手的基礎(chǔ)模塊武学,在其構(gòu)造函數(shù)中做了握手前準(zhǔn)備如設(shè)置SSL/TLS握手過(guò)程中支持的簽名算法列表祭往、為SSL/TLS連接設(shè)置證書鏈和私鑰、設(shè)置SSL/TLS握手過(guò)程中支持的密鑰交換算法組等
  • 同時(shí)TlsClientHandshaker可通過(guò)其提供的CryptoConnect()函數(shù)開始進(jìn)行握手火窒,在該函數(shù)中首先為設(shè)置傳輸參數(shù)硼补、設(shè)置應(yīng)用協(xié)議等,最終調(diào)用SSL_do_handshake() 函數(shù)觸發(fā)ssl引擎生成client hello信息并通過(guò)QuicCryptoStream模塊進(jìn)行處理最終轉(zhuǎn)發(fā)到網(wǎng)絡(luò)
  • TlsHandshaker模塊由CryptoMessageParser派生提供ProcessInput函數(shù)用于輸入client hello信息熏矿,作為服務(wù)端已骇,當(dāng)收到客戶端的client hello后,會(huì)通過(guò)QuicCryptoStream進(jìn)行處理票编,并提取出client hello信息褪储,在經(jīng)過(guò)TlsServerHandshaker、TlsHandshaker慧域、TlsServerConnection將該信息輸入到ssl引擎進(jìn)行處理鲤竹,從而觸發(fā)握手流程
  • QuicCryptoStream、TlsClientHandshaker昔榴、TlsHandshaker辛藻、TlsClientConnetion、TlsConnetion五大模塊組成了客戶端握手核心引擎
  • TlsServerHandshaker互订、TlsHandshaker吱肌、TlsServerConnetion、TlsConnetion四大模塊組成了服務(wù)端握手核心引擎
  • 后續(xù)基于本文會(huì)對(duì)TLS1.3 握手進(jìn)行詳細(xì)的分析
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
禁止轉(zhuǎn)載仰禽,如需轉(zhuǎn)載請(qǐng)通過(guò)簡(jiǎn)信或評(píng)論聯(lián)系作者氮墨。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市吐葵,隨后出現(xiàn)的幾起案子规揪,更是在濱河造成了極大的恐慌,老刑警劉巖温峭,帶你破解...
    沈念sama閱讀 206,602評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件粒褒,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡诚镰,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門祥款,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)清笨,“玉大人,你說(shuō)我怎么就攤上這事刃跛】侔” “怎么了?”我有些...
    開封第一講書人閱讀 152,878評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵桨昙,是天一觀的道長(zhǎng)检号。 經(jīng)常有香客問(wèn)我腌歉,道長(zhǎng),這世上最難降的妖魔是什么齐苛? 我笑而不...
    開封第一講書人閱讀 55,306評(píng)論 1 279
  • 正文 為了忘掉前任翘盖,我火速辦了婚禮,結(jié)果婚禮上凹蜂,老公的妹妹穿的比我還像新娘馍驯。我一直安慰自己,他們只是感情好玛痊,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,330評(píng)論 5 373
  • 文/花漫 我一把揭開白布汰瘫。 她就那樣靜靜地躺著,像睡著了一般擂煞。 火紅的嫁衣襯著肌膚如雪混弥。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,071評(píng)論 1 285
  • 那天对省,我揣著相機(jī)與錄音蝗拿,去河邊找鬼。 笑死官辽,一個(gè)胖子當(dāng)著我的面吹牛蛹磺,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播同仆,決...
    沈念sama閱讀 38,382評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼萤捆,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了俗批?” 一聲冷哼從身側(cè)響起俗或,我...
    開封第一講書人閱讀 37,006評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎岁忘,沒(méi)想到半個(gè)月后辛慰,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,512評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡干像,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,965評(píng)論 2 325
  • 正文 我和宋清朗相戀三年帅腌,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片麻汰。...
    茶點(diǎn)故事閱讀 38,094評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡速客,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出五鲫,到底是詐尸還是另有隱情溺职,我是刑警寧澤,帶...
    沈念sama閱讀 33,732評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站浪耘,受9級(jí)特大地震影響乱灵,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜七冲,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,283評(píng)論 3 307
  • 文/蒙蒙 一痛倚、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧癞埠,春花似錦状原、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至通铲,卻和暖如春毕莱,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背颅夺。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工朋截, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人吧黄。 一個(gè)月前我還...
    沈念sama閱讀 45,536評(píng)論 2 354
  • 正文 我出身青樓部服,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親拗慨。 傳聞我的和親對(duì)象是個(gè)殘疾皇子廓八,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,828評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容