如何讓chrome支持ESNI

By 霏艺Faye at 2020-03-28

同學們先自己去看下火狐的源碼,理解下火狐的ESNI代碼實現!

我昨天剛下好,300多MB的壓縮包!你們慢慢解壓縮吧。。。

代碼太深了,估計很多人不知道ESNI的代碼實現在哪裏。我解釋下,代碼在

security/nss/lib/ssl/tls13esni.c

函數是SSLExp_EnableESNI和tls13_ClientSetupESNI,整個代碼實現其實不難,當然,前提是你和我一樣無聊看完了openssl的代碼

重點講解,我放到https://2049bbs.xyz/t/3750 這裏了

ESNI, chrome, 支持


言歸正傳!

我們講解Chrome了,chrome的TLS實現叫做BoringSSL,fork自著名的openSSL。

開始講如何boringssl的代碼。

翻開課本,boringssl\ssl\handshake_client.cc這個代碼。開始閲讀ssl_client_handshake

對!ESNI是在客戶端發起握手的時候發送的!所以從握手開始讀起。

找到do_start_connect這個函數!很多人覺得看著這個代碼,很嚇人。別怕,其實關鍵信息就i是ssl_write_client_hello這麽一行罷了。其他的都不重要~

ssl_write_client_hello這個函數,什麽都不做,純粹拼報文而已!二進制級別拼寫,類似給一個struct賦值。

我們就看ssl_add_clienthello_tlsext這個函數!因爲ESNI或者SNI都是放在ext字段。

翻開課本 boringssl\ssl\t1_lib.cc 找到 ssl_add_clienthello_tlsext

  for (size_t i = 0; i < kNumExtensions; i++) {
    const size_t len_before = CBB_len(&extensions);
    if (!kExtensions[i].add_clienthello(hs, &extensions)) {
      OPENSSL_PUT_ERROR(SSL, SSL_R_ERROR_ADDING_EXTENSION);
      ERR_add_error_dataf("extension %u", (unsigned)kExtensions[i].value);
      return false;
    }

意思是遍歷所有的ext,調用對應ext的add_clienthello函數,拼clienthello報文

// kExtensions contains all the supported extensions.
static const struct tls_extension kExtensions[] = {
  {
    TLSEXT_TYPE_server_name,
    NULL,
    ext_sni_add_clienthello,
    ext_sni_parse_serverhello,
    ext_sni_parse_clienthello,
    ext_sni_add_serverhello,
  },
  {
    TLSEXT_TYPE_extended_master_secret,
    NULL,
    ext_ems_add_clienthello,
    ext_ems_parse_serverhello,
    ext_ems_parse_clienthello,
    ext_ems_add_serverhello,
  },
  {
    TLSEXT_TYPE_renegotiate,
    NULL,
    ext_ri_add_clienthello,
    ext_ri_parse_serverhello,
    ext_ri_parse_clienthello,
    ext_ri_add_serverhello,
  },
  {
    TLSEXT_TYPE_supported_groups,
    NULL,
    ext_supported_groups_add_clienthello,
    ext_supported_groups_parse_serverhello,
    ext_supported_groups_parse_clienthello,
    dont_add_serverhello,
  },
  {
    TLSEXT_TYPE_ec_point_formats,
    NULL,
    ext_ec_point_add_clienthello,
    ext_ec_point_parse_serverhello,
    ext_ec_point_parse_clienthello,
    ext_ec_point_add_serverhello,
  },
  {
    TLSEXT_TYPE_session_ticket,
    NULL,
    ext_ticket_add_clienthello,
    ext_ticket_parse_serverhello,
    // Ticket extension client parsing is handled in ssl_session.c
    ignore_parse_clienthello,
    ext_ticket_add_serverhello,
  },
  {
    TLSEXT_TYPE_application_layer_protocol_negotiation,
    NULL,
    ext_alpn_add_clienthello,
    ext_alpn_parse_serverhello,
    // ALPN is negotiated late in |ssl_negotiate_alpn|.
    ignore_parse_clienthello,
    ext_alpn_add_serverhello,
  },
  {
    TLSEXT_TYPE_status_request,
    NULL,
    ext_ocsp_add_clienthello,
    ext_ocsp_parse_serverhello,
    ext_ocsp_parse_clienthello,
    ext_ocsp_add_serverhello,
  },
  {
    TLSEXT_TYPE_signature_algorithms,
    NULL,
    ext_sigalgs_add_clienthello,
    forbid_parse_serverhello,
    ext_sigalgs_parse_clienthello,
    dont_add_serverhello,
  },
  {
    TLSEXT_TYPE_next_proto_neg,
    NULL,
    ext_npn_add_clienthello,
    ext_npn_parse_serverhello,
    ext_npn_parse_clienthello,
    ext_npn_add_serverhello,
  },
  {
    TLSEXT_TYPE_certificate_timestamp,
    NULL,
    ext_sct_add_clienthello,
    ext_sct_parse_serverhello,
    ext_sct_parse_clienthello,
    ext_sct_add_serverhello,
  },
  {
    TLSEXT_TYPE_channel_id,
    ext_channel_id_init,
    ext_channel_id_add_clienthello,
    ext_channel_id_parse_serverhello,
    ext_channel_id_parse_clienthello,
    ext_channel_id_add_serverhello,
  },
  {
    TLSEXT_TYPE_srtp,
    ext_srtp_init,
    ext_srtp_add_clienthello,
    ext_srtp_parse_serverhello,
    ext_srtp_parse_clienthello,
    ext_srtp_add_serverhello,
  },
  {
    TLSEXT_TYPE_key_share,
    NULL,
    ext_key_share_add_clienthello,
    forbid_parse_serverhello,
    ignore_parse_clienthello,
    dont_add_serverhello,
  },
  {
    TLSEXT_TYPE_psk_key_exchange_modes,
    NULL,
    ext_psk_key_exchange_modes_add_clienthello,
    forbid_parse_serverhello,
    ext_psk_key_exchange_modes_parse_clienthello,
    dont_add_serverhello,
  },
  {
    TLSEXT_TYPE_early_data,
    NULL,
    ext_early_data_add_clienthello,
    ext_early_data_parse_serverhello,
    ext_early_data_parse_clienthello,
    ext_early_data_add_serverhello,
  },
  {
    TLSEXT_TYPE_supported_versions,
    NULL,
    ext_supported_versions_add_clienthello,
    forbid_parse_serverhello,
    ignore_parse_clienthello,
    dont_add_serverhello,
  },
  {
    TLSEXT_TYPE_cookie,
    NULL,
    ext_cookie_add_clienthello,
    forbid_parse_serverhello,
    ignore_parse_clienthello,
    dont_add_serverhello,
  },
  {
    TLSEXT_TYPE_quic_transport_parameters,
    NULL,
    ext_quic_transport_params_add_clienthello,
    ext_quic_transport_params_parse_serverhello,
    ext_quic_transport_params_parse_clienthello,
    ext_quic_transport_params_add_serverhello,
  },
  {
    TLSEXT_TYPE_token_binding,
    NULL,
    ext_token_binding_add_clienthello,
    ext_token_binding_parse_serverhello,
    ext_token_binding_parse_clienthello,
    ext_token_binding_add_serverhello,
  },
  {
    TLSEXT_TYPE_cert_compression,
    NULL,
    cert_compression_add_clienthello,
    cert_compression_parse_serverhello,
    cert_compression_parse_clienthello,
    cert_compression_add_serverhello,
  },
  {
    TLSEXT_TYPE_delegated_credential,
    NULL,
    ext_delegated_credential_add_clienthello,
    forbid_parse_serverhello,
    ext_delegated_credential_parse_clienthello,
    dont_add_serverhello,
  },
};

因爲我們要做的是SNI,所以看到了第一個ext,找到了它對應的add_clienthello是ext_sni_add_clienthello

霏艺Faye at 2020-03-28
1

繼續查看ext_sni_add_clienthello函數

發現CBB_add_bytes(&name, (const uint8_t *)ssl->hostname.get(),這行代碼,會對SNI賦值hostname

也就是說,爲什麽我們通過wireshark抓包,可以明文看到自己訪問什麽網站,就是這行代碼造成的!

GFW通過全網檢查ClientHello的SNI字段,來判斷你在訪問什麽網站!而且因爲是明文,所以GFW的工作很簡單,只要檢查你的TLS握手SNI字段,如果是敏感詞,就RST掉你的tcp連接。你就上不去了。品蔥就是這樣,所以上不去,需要開啓ESNI功能,就沒事了~

代碼修改:

1.增加一個ext,取名叫ESNI,參考SNI的實現,完成相關代碼

2.移植火狐的esni加密代碼到boringssl,完成第一步的esni報文拼接

3.編譯自己的boringssl庫,替換掉chrome自帶的boringssl

4.chrome有自己dll校驗機制,你手動替換有點問題,好像跑不起來,得網上找下教程,學習如何替換chrome的boringssl

別找我要代碼或者已經編譯好的dll !我要是寫好了,早提PR給chrome了~

關於爲什麽不自己寫代碼完成剛才我説的這麽多事情:

我翻墻的條件很糟糕,下載chrome的源碼,經常斷掉!失敗就得重新下載。至今沒有下載到chrome源碼。

另外編譯環境配置非常複雜,我沒有那個功夫去完成這麽費時間的工作。老了啊,想當年自己花了3個星期,搭了Gentoo Linux,現在搭個chrome的編譯環境都覺得浪費時間

霏艺Faye at 2020-03-28
2

大神你好,我一上来,就看你更新了好多文章啊!大神学习这些有什么方式方法么,我想当一个程序员!

张怀义 at 2020-03-28
3