TLS協議分析 (四) handshake協議概覽

轉自:http://chuansong.me/n/1268791652843

5. handshake 協議

handshake protocol重要而繁瑣。

TLS 1.3對握手做了大修改,下面先講TLS 1.2,講完再介紹一下分析TLS 1.3.

5.1.handshake的總體流程

handshake protocol用於產生給record protocol使用的SecurityParameters。
在handshake中:

  • 客戶端和服務器端協商TLS協議版本號和一個CipherSuite,

  • 認證對端的身份(可選,一般如https是客戶端認證服務器端的身份),

  • 並且使用密鑰協商算法生成共享的master secret。

步驟如下:

  • 交換Hello消息,協商出算法,交換random值,檢查session resumption.

  • 交換必要的密碼學參數,來允許client和server協商出premaster secret。

  • 交換證書和密碼學參數,讓client和server做認證,證明自己的身份。

  • 從premaster secret和交換的random值 ,生成出master secret。

  • 把SecerityParameters提供被record層。

  • 允許client和server確認對端得出了相同的SecurityParameters,並且握手過程的數據沒有被攻擊者篡改。

Handshake的結果是在雙方建立相同的Session,Session 包含下列字段:

  1. session identifier
    session id,用來唯一標識一個session,在session 恢復的時候,也要用到

  2. peer certificate
    對端的 X509v3 格式證書. 如果不需要認證對端的身份,就爲空。

  3. compression method
    壓縮算法,一般被禁用

  4. cipher spec
    CipherSuite,如上文介紹,包含: 用於生成key的pseudorandom function (PRF) , 塊加密算法例如AES, MAC算法 (例如 HMAC-SHA256). 還包括一個 mac_length字段,在後文的we握手協議介紹

  5. master secret
    48字節的,client和server共享密鑰。

  6. is resumable
    一個標誌位,用來標識當前session是否能被恢復。

以上字段,隨後被用於生成 record層的SecurityParameters,多個連接可以通過握手協議的session恢復功能來複用同一個session。

握手協議使用 非對稱加密/密鑰協商/數字簽名  3類算法,
因此要求讀者對這3類算法概念清晰,能準確區分。
在此澄清一下,:
非對稱的算法分爲3類:

  • 非對稱加密,有:RSAES-PKCS1-v1_5,RSAES-OAEP ,Rabin-Williams-OAEP, Rabin-Williams-PKCS1-v1_5等

  • 非對稱密鑰協商,有:DH,DHE,ECDH,ECDHE 等

  • 非對稱數字簽名:RSASSA-PKCS1-v1_5,RSASSA-PSS,ECDSA,DSA,ED25519 等

另外,非對稱加密算法,可以當作密鑰協商算法來用,所以 RSAES-PKCS1-v1_5,RSAES-OAEP 也可以當作密鑰協商算法來用


插播一段 RSA:

RSA的實際工程應用,要遵循PKCS#1 標準,見 https://www.ietf.org/rfc/rfc3447

其中的 RSAES-PKCS1-v1_5 和 RSASSA-PKCS1-v1_5 是使用RSA算法的兩種不同scheme(體制)。
RSAES表示 RSA Encryption schemes,即非對稱加密,
RSAES有:RSAES-OAEP,RSAES-PKCS1-v1_5兩種,其中RSAES-OAEP更新更安全

RSASSA表示 Signature schemes with appendix,即appendix模式(appendix和recovery的區別請參看密碼學教材)的非對稱數字簽名算法。
RSASSA有: RSASSA-PSS, RSASSA-PKCS1-v1_5 兩種, 其中RSASSA-PSS更新更安全

RSA還有一個缺陷,就是很容易被時間側通道攻擊,所以現在的RSA實現都要加 blinding ,後文有介紹。

可以看到,RSA是一種很特殊的算法,既可以當非對稱加密算法使用,又可以當非對稱數字簽名使用。這一點很有迷惑性,其實很多用RSA的人都分不清自己用的是RSA的哪種模式。

相比之下,ECC(橢圓曲線)這一塊的算法就很清晰,ECDSA只能用作數字簽名,ECDH只能用作密鑰交換。

分清楚 RSAES-PKCS1-v1_5 和 RSASSA-PKCS1-v1_5 有什麼用涅?

PKCS#1規範解釋:

A generally good cryptographic practice is to employ a given RSA
key    pair in only one scheme.  This avoids the risk that
vulnerability in    one scheme may compromise the security of the
other, and may be    essential to maintain provable security.

FIPS PUB 186-3  美國標準規定:

An RSA key pair used for digital signatures shall only be used for one
digital signature scheme (e.g., ANS X9.31, RSASSA-PKCS1 v1.5 or
RSASSA-PSS; see Sections 5.4 and 5.5). In addition, an RSA digital
signature key pair shall not be used for other purposes (e.g., key
establishment).

一對密鑰只做一個用途,要麼用作非對稱加解密,要麼用作簽名驗證,別混着用!
一對密鑰只做一個用途,要麼用作非對稱加解密,要麼用作簽名驗證,別混着用!
一對密鑰只做一個用途,要麼用作非對稱加解密,要麼用作簽名驗證,別混着用!

這個要求,決定了一個協議的 PFS(前向安全性),在斯諾登曝光NSA的“今日捕獲,明日破解”政策後,越發重要

https://news.ycombinator.com/item?id=5942534

http://news.netcraft.com/archives/2013/06/25/ssl-intercepted-today-decrypted-tomorrow.html

https://lwn.net/Articles/572926/

https://www.eff.org/deeplinks/2014/04/why-web-needs-perfect-forward-secrecy

http://www.wired.com/2013/10/lavabit_unsealed

PFS反映到密鑰協商過程中,就是:

  • 不要使用RSA做密鑰協商,一定只用RSA做數字簽名

  • 不要把ECDH的公鑰固定內置在客戶端做密鑰協商

後文可以看到這一原則在 TLS 1.3,  QUIC,Apple的iMessage等協議中一再貫徹。

非對稱RSA/ECC這個話題比較大了,後面有空再寫文章吧,讀者可以先看一下參考資料,裏面有清晰的介紹。

插播結束,繼續TLS。


由於設計的時候,就要考慮兼容性,而且實際歷史悠久,所以TLS協議90年代曾經使用的一些算法,現在已經被破解了,例如有的被發現漏洞(rc4),有的密鑰長度過短(例如曾經美帝有出口限制,限制RSA 在512比特以下,對稱加密密鑰限制40比特以下,後來2005年限制被取消),但是考慮到兼容,現在的TLS實現中,還是包含了這種已經被破解的老算法的代碼。這樣,如果攻擊者可以干擾握手過程,誘使client和server使用這種已經被破解的算法,就會威脅TLS協議的安全,這被稱爲“降級攻擊”。

爲了在握手協議解決降級攻擊的問題,TLS協議規定:client發送ClientHello消息,server必須回覆ServerHello消息,否則就是fatal error,當成連接失敗處理。ClientHello和ServerHello消息用於建立client和server之間的安全增強能力,ClientHello和ServerHello消息建立如下屬性:

  • Protocol Version

  • Session ID

  • Cipher Suite

  • Compression Method.

另外,產生並交換兩個random值 ClientHello.random 和 ServerHello.random

密鑰協商使用四條: server的Certificate,ServerKeyExchange,client的Certificate,ClientKeyExchange 。TLS規定以後如果要新增密鑰協商方法,可以訂製這4條消息的數據格式,並且指定這4條消息的使用方法。密鑰協商得出的共享密鑰必須足夠長當前定義的密鑰協商算法生成的密鑰長度必須大於46字節

在hello消息之後,server會把自己的證書在一條Certificate消息裏面發給客戶端(如果需要做服務器端認證的話,例如https)。 並且,如果需要的話,server會發送一條ServerKeyExchange消息,(例如如果服務器的證書只用做簽名,不用做密鑰交換,或者服務器沒有證書)。client對server的認證完成後,server可以要求client發送client的證書,如果這是協商出來的CipherSuite允許的。下一步,server會發送ServerHelloDone消息,表示握手的hello消息部分已經結束。然後server會等待一個client的響應。如果server已經發過了CertificateRequest消息,client必須發送Certificate消息。然後發送ClientKeyExchange消息,並且這條消息的內容取決於ClientHello和ServerHello消息協商的算法。如果client發送了有簽名能力的證書,就顯式發送一個經過數字簽名的CertificateVerify消息,來證明自己擁有證書私鑰。

然後,client發送一個ChangeCipherSpec消息,並且client拷貝待定的Cipher  Spec到當前的Cipher Spec。然後client立即用新算法+新key+新密鑰 發送Finished消息。收到後,server發送自己的ChangeCipherSpec消息,作爲響應,並且拷貝待定的Cipher Spec到當前的Cipher Spec。此時,握手就完成了,client和server可以開始交換應用層數據(如下圖所示)。應用層數據不得在握手完成前發送。

引用一個來自網絡的圖片:

Client                                               Server

ClientHello                  -------->
                                                ServerHello
                                               Certificate*
                                         ServerKeyExchange*
                                        CertificateRequest*
                             <--------      ServerHelloDone
Certificate*
ClientKeyExchange
CertificateVerify*
[ChangeCipherSpec]
Finished                     -------->
                                         [ChangeCipherSpec]
                             <--------             Finished
Application Data             <------->     Application Data

       Figure 1.  Message flow for a full handshake

* 表示可選的消息,或者根據上下文在某些情況下會發送的消息。Indicates optional or situation-dependent messages that are not
always sent.

注:爲了幫助解決管道阻塞的問題,ChangeCipherSpec是一個獨立的TLS protocol content type,並不是一個握手消息。

TLS的完整握手過程,要進行RSA/ECDH/ECDSA等非對稱計算,非對稱計算是很慢的。關於非對稱的性能:
例如在2015年的服務器cpu:  Intel(R) Xeon(R) CPU E3-1230 V2 @ 3.30GHz 上,
使用如下命令測試:

openssl speed rsa2048
openssl speed ecdsap256
openssl speed ecdhp256
openssl speed aes-128-cbc
openssl speed -evp aes-128-cbc

結果如下表:

算法 性能 性能
RSA-2048 私鑰運算 723.7 次/秒 公鑰運算 23505.8 次/秒
256 bit ecdsa (nistp256) 簽名   8628.4 次/秒 驗證  2217.0 次/秒
256 bit ecdh (nistp256) ECDH協商 2807.8 次/秒
aes-128-cbc 加密 121531.39 K/秒
aes-128-cbc 使用aesni硬件加速 加密 683682.13 K/秒

注:非對稱的單位是 次/秒,這是由於非對稱一般只用於處理一個block,
對稱的單位是 K/秒,因爲對稱一般用於處理大量數據流,所以單位和流量一樣。
可以給非對稱的 次/秒 乘以 block  size ,就可以和對稱做比較了。例如rsa-2048,723.72048/8/1024=185.2672 K/秒 ,
*RSA-2048 私鑰運算性能 是aes-128-cbc 的 1.5/1000。是aesni的 2.6/10000

如上,性能數據慘不忍睹, 簡直不能忍!!!

有鑑於此,TLS從設計之初,就採用了萬能手段—加cache,有2種cache手段:session id,和session ticket。把握手的結果直接cache起來,繞過握手運算。

當client和server決定恢復一個之前的session,或複用一個已有的session時(可以不用協商一個新的SecurityParameters),消息流程如下:

客戶端使用要被恢復的session,發送一個ClientHello,把Session ID包含在其中。server在自己的session cache中,查找客戶端發來的Session ID,如果找到,sever把找到的session 狀態恢復到當前連接,然後發送一個ServerHello,在ServerHello中把Session ID帶回去。然後,client和server都必須ChangeCipherSpec消息,並緊跟着發送Finished消息。這幾步完成後,client和server 開始交換應用層數據(如下圖所示)。如果server在session cache中沒有找到Session ID,那server就生成一個新的session ID在ServerHello裏給客戶端,並且client和server進行完整的握手。

流程圖如下:

Client                                                Server

ClientHello                   -------->
                                                 ServerHello
                                          [ChangeCipherSpec]
                              <--------             Finished
[ChangeCipherSpec]
Finished                      -------->
Application Data              <------->     Application Data

    Figure 2.  Message flow for an abbreviated handshake

5.2. handshake 協議外層結構

從消息格式來看,TLS Handshake Protocol 在 TLS Record Protocol 的上層. 這個協議用於協商一個session的安全參數。 Handshake 消息(例如ClientHello,ServerHello等) 被包裝進 TLSPlaintext結構裏面,傳入TLS record層,根據當前session 狀態做處理,然後傳輸。

如下:

enum {
    hello_request(0), client_hello(1), server_hello(2),
    certificate(11), server_key_exchange (12),
    certificate_request(13), server_hello_done(14),
    certificate_verify(15), client_key_exchange(16),
    finished(20), (255)
} HandshakeType;

struct {    HandshakeType msg_type;  /* handshake type */    uint24 length;          /* bytes in message */    select (HandshakeType) {        
     case hello_request:       HelloRequest;
     case client_hello:        ClientHello;
     case server_hello:        ServerHello;      case certificate:         Certificate;              case server_key_exchange: ServerKeyExchange;      case certificate_request: CertificateRequest;      case server_hello_done:   ServerHelloDone;      case certificate_verify:  CertificateVerify;      case client_key_exchange: ClientKeyExchange;      case finished:            Finished;      case session_ticket:      NewSessionTicket; /* NEW */    } body; } Handshake;

TLS協議規定,handshake 協議的消息必須按照規定的順序發,收到不按順序來的消息,當成fatal error處理。也就是說,TLS協議可以當成狀態機來建模編碼。

下面按照消息發送必須遵循的順序,逐個解釋每一條握手消息。

handshake協議的外層字段,見這個抓包:

5.3. handshake  — ClientHello,ServerHello,HelloRequest

Hello消息有3個:ClientHello, ServerHello,HellloRequest
逐個說明:

5.3.1 Client Hello

當客戶端第一次連接到服務器時,第一條message必須發送ClientHello。
另外,rfc裏規定,如果客戶端和服務器支持重協商,在客戶端收到服務器發來的HelloRequest後,也可以回一條ClientHello,在一條已經建立的連接上開始重協商。(重協商是個很少用到的特性。)

消息結構:

struct {
    uint32 gmt_unix_time;
    opaque random_bytes[28];
} Random;

opaque SessionID<0..32>;

uint8 CipherSuite[2];

enum { null(0), (255) } CompressionMethod;

struct {    ProtocolVersion client_version;    Random random;    SessionID session_id;    CipherSuite cipher_suites<2..2^16-2>;    CompressionMethod compression_methods<1..2^8-1>;    select (extensions_present) {        
      case false:
           struct {};        
      case true:            Extension extensions<0..2^16-1>;    }; } ClientHello;

Random 其中:
     gmt_unix_time 是 unix epoch時間戳。
     random_bytes 是 28字節的,用密碼學安全隨機數生成器 生成出來的隨機數。


密碼學安全的隨機數生成,這是個很大的話題,也是一個大陷阱,目前最好的做法就是用 /dev/urandom,或者openssl庫的 RAND_bytes()

歷史上,恰好就在SSL的random_bytes這個字段,NetScape瀏覽器早期版本被爆出過隨機數生成器漏洞。
被爆菊的隨機數生成器使用 pid + 時間戳 來初始化一個seed,並用MD5(seed)得出結果。
http://www.cs.berkeley.edu/~daw/papers/ddj-netscape.html
建議讀者檢查一下自己的隨機數生成器。


client_version
:      客戶端支持的最高版本號。

random
:     客戶端生成的random。

ClientHello.session_id 唯一標識一個session,用來做session cache。如果爲空,表示不做複用,要求服務器生成新的session。
session_id的來源有:

  1. 之前的協商好的連接的session_id

  2. 當前連接的session_id

  3. 當前也在使用中的另一條連接的session_id

其中第三種允許不做重新握手,就同時建立多條獨立的安全連接。這些獨立的連接可能順序創建,也可以同時創建。一個SessionID當握手協商的Finished消息完成後,就合法可用了。存活直到太舊被移除,或者session 關聯的某個連接發生fatal error。SessionID的內容由服務器端生成。

注:由於SessionID的傳輸是不加密,不做MAC保護的,服務器不允許把私密信息發在裏面,不能允許僞造的SessionID在服務器造成安全問題。(握手過程中的數據,整體是受Finished消息的保護的)

ClientHello.cipher_suites字段,包含了客戶端支持的CipherSuite的列表,按照客戶端希望的優先級排序,每個CipherSuite有2個字節,每個CipherSuite由:一個密鑰交換算法,一個大量數據加密算法(需要制定key length參數),一個MAC算法,一個PRF 構成。服務器會從客戶端發過來的列表中選擇一個;如果沒有可以接受的選擇,就返回一個 handshake failure 的 alert,並關閉連接。如果列表包含服務器不認識,不支持,或者禁用的CipherSuite,服務器必須忽略。
如果SessionID不爲空,則cipher_suites裏面起碼要包含客戶端cache的session裏面的那個CipherSuite

compression_methods,類似地,ClientHello裏面包含壓縮算法的列表,按照客戶端優先級排序。當然,如前介紹,服務器一般禁用TLS的壓縮。

compression_methods 後面可以跟一組擴展(extensions), extensions都是可選的,比較有用的擴展如: SNI, session ticket,ALPN,OCSP 等,後文介紹。

客戶端發送了ClientHello後,服務器端必須回覆ServerHello消息,回覆其他消息都會導致 fatal error 關閉連接。

5.3.2  Server Hello

當收到客戶端發來的ClientHello後,正常處理完後,服務器必須回覆ServerHello。

消息結構:

struct {
    ProtocolVersion server_version;
    Random random;
    SessionID session_id;
    CipherSuite cipher_suite;
    CompressionMethod compression_method;
    select (extensions_present) {
       case false:
           struct {};
       case true:            Extension extensions<0..2^16-1>;    }; } ServerHello;

server_version
:   服務器選擇 ClientHello.client_version 和 服務器支持的版本號 中的最小的。

random
:      服務器生成的random,必須確保和客戶端生成的random沒有關聯。

session_id
:      服務器爲本連接分配的SessionID。如果ClientHello.session_id不爲空,服務器會在自己的本地做查找。

  • 如果找到了匹配,並且服務器決定複用找到的session建立連接,服務器應該把ClientHello.session_id同樣的 session id填入ServerHello.session_id,這表示恢復了一個session,並且雙方會立即發送Finished消息。

  • 否則,回覆一個和ClientHello.random_id不同的Serverhello.session_id,來標識新session。服務器可以回覆一個空的session_id,來告訴客戶端這個session不要cache,不能恢復。
    如果一個session 被恢復了,那必須恢復成之前協商的session裏面的 CipherSuite。要注意的是,並不要求服務器一定要恢復session,   服務器可以不做恢復。

在實踐中,session cache在服務器端要求key-value形式的存儲,如果tls服務器不止一臺的話,就有一個存儲怎麼共享的問題,要麼存儲同步到所有TLS服務器的內存裏,要麼專門搞服務來支持存儲,並使用rpc訪問,
無論如何,都是很麻煩的事情,相比之下,後文要介紹的session ticket就簡單多了,所以一般優先使用session ticket。

cipher_suite
:      服務器選定的一個CipherSuite。如果是恢復的session,那就是session裏的CipherSuite。

compression_method
:      跟上面類似。

extensions
:   擴展列表。要注意的是,ServerHello.extensions 必須是 ClientHello.extensions的子集。

5.3.3  Hello Extensions

The extension 的格式是:

struct {
    ExtensionType extension_type;
    opaque extension_data<0..2^16-1>;
} Extension;
enum {    signature_algorithms(13), (65535) } ExtensionType;

其中:

  • “extension_type” 標識是哪一個擴展類型。

  • “extension_data” 一坨二進制的buffer,擴展的數據體,各個擴展自己做解析。

extension_type 只能出現一次,ExtensionType之間不指定順序。

extensions 可能在新連接創建時被髮送,也可能在要求session恢復的時候被髮送。所以各個extension都需要規定自己再完整握手和session恢復情況下的行爲。
這些情況比較瑣碎而微妙,具體案例要具體分析。

5.3.4  Hello Request

服務器任何時候都可以發送 HelloRequest 消息。

HelloRequest的意思是,客戶端應該開始協商過程。客戶端應該在方便的時候發送ClientHello。服務器不應該在客戶端剛創建好連接後,就發送HelloRequest,此時應該讓客戶端發送ClientHello。

客戶端收到這個消息後,可以直接忽略這條消息。
服務器發現客戶端沒有響應HelloRequest後,可以發送fatal error alert。

消息結構:

struct { } HelloRequest;

HelloRequest不包含在握手消息的hash計算範圍內。

發佈了2 篇原創文章 · 獲贊 7 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章