<input id="ohw05"></input>
  • <table id="ohw05"><menu id="ohw05"></menu></table>
  • <var id="ohw05"></var>
  • <code id="ohw05"><cite id="ohw05"></cite></code>
    <label id="ohw05"></label>
    <var id="ohw05"></var>
  • openssl客戶端編程:一個不起眼的函數導致的SSL會話失敗問題

    我們目前大部分使用的openssl庫還是基于TLS1.2協議的1.0.2版本系列,如果要支持更高的TLS1.3協議,就必須使用openssl的1.1.1版本或3.0版本。升級openssl庫有可能會導致SSL會話失敗,我在升級 wincurl 時,意外的收獲了一個函數。這個函數非常的不起眼,但具有的現實意義卻很大。
    大部分情況下如果你不調用該函數,并不影響SSL會話和通信,但有時會被某些服務器拒絕。一旦被拒絕,查找具體的原因將變得非常痛苦。這個函數的意義好比HTTP協議中HOST字段,它和NGINX反向代理的Server name有異曲同工之妙。了解這個函數的意義,可能會讓你今后在配置nginx反向代理時少走一些彎路。
    這個函數是 SSL_set_tlsext_host_name,在介紹這個函數之前,我們先快速看看TLS協議和openssl的發展。

     
    雖然TLS1.3的標準自2018年就已發布,但目前國內幾乎所有網站并沒有增加對TLS 1.3的支持,大部分主流網站使用的還是基于TLS1.2的版本。目前國內使用TLS1.3的好像只有知乎,其它諸如:新浪、網易、搜狐、騰訊、華為、京東、百度....都還是采用TLS1.2的標準。
     
    網站是否支持TLS1.3其實并不重要,重要的是瀏覽器是否支持TLS1.3協議。好在目前市場上所有主流瀏覽器都已升級到了TLS1.3。因此對于用戶而言,不管是支持TLS1.3的網站還是支持TLS1.2的網站,訪問起來都不是問題,或者說對終端用戶而言這種訪問是無感的。
     
    為何TLS3.0已經出現5年了,各大網絡平臺還是首選TLS1.2呢?估計還是出于對兼容和穩定的考慮。如果貿然升級到1.3導致大量客戶端無法訪問從而丟失用戶,是任何平臺所無法承受的。此外,支持TLS1.3的openssl版本也在不斷完善中,在其未達到穩定前,最好還是使用穩定的1.2版本。這大概就是目前幾乎所有平臺類網站還在使用TLS1.2的原因。
     
    此外,即使有的網站采用TLS1.3協議,也會繼續提供對1.2的訪問支持(向下兼容并保留所有TLS1.2的加密套件),除非該網站的運維人員強行使用TLS1.3的加密套件。但如果這樣做的話就會導致大量仍然使用1.2標準的客戶端程序無法繼續訪問該網站,這也意味著該運維人員離下崗再就業不遠了。
     
    這也是為什么我們即使不升級老版本的openssl庫,也依然可以訪問TLS1.3網站的原因。
     
    上面說的TLS1.2,1.3指的是TLS的協議,實現上述協議的是openssl庫。 其中支持TLS1.3的openssl庫目前有兩個版本系列:一個是1.1.1系列,一個是3.0系列。其中1.1.1更像是一個過渡版本,openssl團隊承諾會支撐該版本到2023年9月11日,也就是明年911事件22周年之際(確實是一個值得紀念的日子)將停止更新1.1.1版本。這就意味著大家還沒開始普及使用1.1.1,它就已經結束了。

    這個其實也不重要,因為3.0才是最終openssl的終極版本,直接跳過了2.0,從而體現了openssl的跨越式發展。 3.0版本是openssl團隊主推版本,也是一個長期維護的版本,該版本會維護到2026年9月7日。

    雖然主流web瀏覽器都已支持TLS1.3,但瀏覽器只是客戶端的一種,其它大部分客戶端使用的還是openssl 1.0.2系列版本,甚至是1.0.1系列版本,也就是我們經常看到的libeay32.dllssleay32.dll 庫,這些版本是不支持TLS1.3協議的。比如工具類的postman、curl,以及應用類QQ、Foxmail、微信以及各種下載客戶端程序,使用的可能還是老的openssl庫。openssl庫在1.1.1及以上版本已經將庫的導出名稱改為libcryptolibssl,不過這只是前綴名,如果是1.1.1系列,則后綴為-1.1.dll,如果是3.0系列,則后綴為-3.dll,我們通過openssl的庫名就能判斷出該應用程序是否支持TLS1.3。
     
    升級到TLS1.3可以從openssl的官網https://openssl.org)下載1.1.1或3.0版本的源碼進行編譯。編譯完畢后,我們只要替換openssl庫,并重新配置并編譯你的客戶端程序即可。
    使用openssl庫編寫客戶端程序的一般流程如下:先創建套接字,并連接到服務器,然后創建ssl,并綁定套接字,通過調動SSL_connect函數進行握手操作,成功后使用SSL_readSSL_write進行業務通信。
    代碼通常如下:

    	// 創建套接字,并連接到服務器
    	SOCKET s = socket(AF_INET, SOCK_STREAM, 0);
    	struct hostent* hst = ::gethostbyname(pszServer);
    	if(NULL == hst)
    		return FALSE;
    	unsigned long addr;
    	struct sockaddr_in sockAddr;
    	memcpy(&addr, hst->h_addr, hst->h_length);
    	sockAddr.sin_family = AF_INET;
    	sockAddr.sin_port = htons(nPort);
    	sockAddr.sin_addr.S_un.S_addr = addr; 
    	// 連接到服務器
    	if(SOCKET_ERROR != ::connect(s, (struct sockaddr *)&sockAddr, sizeof(struct sockaddr_in)))
    		return FALSE;
    	
    	// Openssl庫初始化
    	OpenSSL_add_ssl_algorithms();
    	SSL_load_error_strings(); 
    	SSLeay_add_ssl_algorithms();
    	ERR_load_BIO_strings();
    	ctx = SSL_CTX_new(SSLv23_client_method());
    	
    	// 創建ssl上下文,并綁定套接字
    	SSL* ssl = SSL_new (m_ctx);
    	SSL_set_fd(m_ssl, s);
    	// 開始ssl握手
    	int iRet = SSL_connect(m_ssl);
    	if(1 != iRet)
    		return FALSE;
    		
    	// 下面使用SSL_read或SSL_write進行通信
    	
    

    上述代碼中SSL_connect函數是最重要的,它內部實現了SSL的握手過程,openssl在內部采用狀態機的方式實現了整個握手,代碼還是相當的晦澀。幾乎所有的SSL通信失敗都是在握手階段產生的,比如加密套件不匹配,服務器證書沒有可靠的簽名等。

    openssl中有個s_client命令集合,該命令用于實現客戶端的通信,它的實現在s_client.c文件中,在這個龐大代碼中有一個不起眼的函數調用,這個函數是SSL_set_tlsext_host_name。該函數的作用是在客戶端在發送ClientHello消息時,將所訪問的主機(服務器)名稱寫入到server name的擴展字段中。

       if (!noservername && (servername != NULL || dane_tlsa_domain == NULL)) {
            if (servername == NULL) {
                if(host == NULL || is_dNS_name(host)) 
                    servername = (host == NULL) ? "localhost" : host;
            }
            if (servername != NULL && !SSL_set_tlsext_host_name(con, servername)) {
                BIO_printf(bio_err, "Unable to set TLS servername extension.\n");
                ERR_print_errors(bio_err);
                goto end;
            }
        }
    
    

    調用該函數后會在ClientHello的擴展字段中增加一個Server Name字段。如下圖所示:

    我們在客戶端編程時,通常不會調用該函數,因為會覺得該字段可有可無,既然已經連接到Host服務器上,為何還要將Host的名稱告訴給服務器呢?如果我們不調用SSL_set_tlsext_host_name 函數,難道SSL會話會失敗么?實際上即使不調用該函數,也是可以握手成功的,并不影響TLS的正常通信。

    但如果服務器強制校驗該字段時,就會導致握手失敗,這就好比HTTP協議中請求的HOST字段一樣。那么為什么有的服務器要強制校驗該字段呢?如果一臺服務器綁定不同的DSN名稱(也就是一個IP地址綁定多個域名),這個擴展字段的意義就出來了,服務器會根據不同的域名提供不同的證書。
     
    舉個例子,假設BAT都破產了,他們窮到共用一臺服務器的地步,也就是一個IP地址綁定了百度、阿里和騰訊三個公司域名,并且這三家公司都給域名申請了數字證書,此時擴展字段的HOST的作用就體現出來了,openssl服務器端會跟據不同的host名稱決定返回哪家公司的數字證書。這也是Ngnix反向代理的原理,根據不同域名進行不同訪問地址映射。

    寫到這里,本文也即將結束,也許我們調用了SSL_set_tlsext_host_name函數也無法知道它的最終目的是什么,這也是我寫這篇文章的原因之一吧。

    posted @ 2022-06-27 16:29  一只會鏟史的貓  閱讀(166)  評論(0編輯  收藏  舉報
    国产美女a做受大片观看