握手協議用於協商鏈接的安全參數。握手消息被提供給 TLS 記錄層,在記錄層它們被封裝到一個或多個 TLSPlaintext 或 TLSCiphertext 中,它們按照當前活動鏈接狀態進行處理和傳輸。html
enum {
client_hello(1),
server_hello(2),
new_session_ticket(4),
end_of_early_data(5),
encrypted_extensions(8),
certificate(11),
certificate_request(13),
certificate_verify(15),
finished(20),
key_update(24),
message_hash(254),
(255)
} HandshakeType;
struct {
HandshakeType msg_type; /* handshake type */
uint24 length; /* remaining bytes in message */
select (Handshake.msg_type) {
case client_hello: ClientHello;
case server_hello: ServerHello;
case end_of_early_data: EndOfEarlyData;
case encrypted_extensions: EncryptedExtensions;
case certificate_request: CertificateRequest;
case certificate: Certificate;
case certificate_verify: CertificateVerify;
case finished: Finished;
case new_session_ticket: NewSessionTicket;
case key_update: KeyUpdate;
};
} Handshake;
複製代碼
協議消息必須按照必定順序發送(順序見下文)。若是對端發現收到的握手消息順序不對,必須使用 「unexpected_message」 alert 消息來停止握手。git
另外 IANA 分配了新的握手消息類型,見第 11 章github
密鑰交換消息用於確保 Client 和 Server 的安全性和創建用於保護握手和數據的通訊密鑰的安全性。算法
在 TLS 協議中,密鑰協商的過程當中,Client 在 ClientHello 中能夠提供如下 4 種 options。數據庫
若是 Server 不選擇 PSK,那麼上面 4 個 option 中的前 3 個是正交的, Server 獨立的選擇一個加密套件,獨立的選擇一個 (EC)DHE 組,獨立的選擇一個用於創建鏈接的密鑰共享,獨立的選擇一個簽名算法/證書對用於給 Client 驗證 Server 。若是 Server 收到的 "supported_groups" 中沒有 Server 能支持的算法,那麼就必須返回 "handshake_failure" 或者 "insufficient_security" 的 alert 消息。緩存
若是 Server 選擇了 PSK,它必須從 Client 的 "psk_key_exchange_modes" 擴展消息中選擇一個密鑰創建模式。這個時候 PSK 和 (EC)DHE 是分開的。在 PSK 和 (EC)DHE 分開的基礎上,即便,"supported_groups" 中不存在 Server 和 Client 相同的算法,也不會終止握手。安全
若是 Server 選擇了 (EC)DHE 組,而且 Client 在 ClientHello 中沒有提供合適的 "key_share" 擴展, Server 必須用 HelloRetryRequest 消息做爲迴應。cookie
若是 Server 成功的選擇了參數,也就不須要 HelloRetryRequest 消息了。 Server 將發送 ServerHello 消息,它包含如下幾個參數:網絡
若是 Server 不能協商出可支持的參數集合,即在 Client 和 Server 各自支持的參數集合中沒有重疊,那麼 Server 必須發送 "handshake_failure" 或者 "insufficient_security" 消息來停止握手。session
當一個 Client 第一次鏈接一個 Server 時,它須要在發送第一條 TLS 消息的時候,發送 ClientHello 消息。當 Server 發送 HelloRetryRequest 消息的時候,Client 收到了之後也須要回應一條 ClientHello 消息。在這種狀況下,Client 必須發送相同的無修改的 ClientHello 消息,除非如下幾種狀況:
因爲 TLS 1.3 嚴禁重協商,若是 Server 已經完成了 TLS 1.3 的協商了,在將來某一時刻又收到了 ClientHello ,Server 不該該理會這條消息,必須當即斷開鏈接,併發送 "unexpected_message" alert 消息。
若是一個 Server 創建了一個 TLS 之前版本的 TLS 鏈接,並在重協商的時候收到了 TLS 1.3 的 ClientHello ,這個時候,Server 必須繼續保持以前的版本,嚴禁協商 TLS 1.3 。
ClientHello 消息的結構是
uint16 ProtocolVersion;
opaque Random[32];
uint8 CipherSuite[2]; /* Cryptographic suite selector */
struct {
ProtocolVersion legacy_version = 0x0303; /* TLS v1.2 */
Random random;
opaque legacy_session_id<0..32>;
CipherSuite cipher_suites<2..2^16-2>;
opaque legacy_compression_methods<1..2^8-1>;
Extension extensions<8..2^16-1>;
} ClientHello;
複製代碼
關於結構體的一些說明:
legacy_version:
在 TLS 之前的版本里,這個字段被用來版本協商和表示 Client 所能支持的 TLS 最高版本號。經驗代表,不少 Server 並無正確的實現版本協商,致使了 "version intolerance" —— Sever 拒絕了一些原本能夠支持的 ClientHello 消息,只由於這些消息的版本號高於 Server 能支持的版本號。在 TLS 1.3 中,Client 在 "supported_versions" 擴展中代表了它的版本。而且 legacy_version 字段必須設置成 0x0303,這是 TLS 1.2 的版本號。在 TLS 1.3 中的 ClientHello 消息中的 legacy_version 都設置成 0x0303,supported_versions 擴展設置成 0x0304。更加詳細的信息見附錄 D。
random:
由一個安全隨機數生成器產生的32字節隨機數。額外信息見附錄 C。
legacy_session_id:
TLS 1.3 版本以前的版本支持會話恢復的特性。在 TLS 1.3 的這個版本中,這一特性已經和預共享密鑰 PSK 合併了。若是 Client 有 TLS 1.3 版本以前的 Server 設置的緩存 Session ID,那麼這個字段要填上這個 ID 值。在兼容模式下,這個值必須是非空的,因此一個 Client 要是不能提供 TLS 1.3 版本以前的 Session 的話,就必須生成一個新的 32 字節的值。這個值不要求是隨機值,但必須是一個不可預測的值,防止實現上固定成了一個固定的值了。不然,這個字段必須被設置成一個長度爲 0 的向量。(例如,一個0字節長度域)
cipher_suites:
這個列表是 Client 所支持對稱加密選項的列表,特別是記錄保護算法(包括密鑰長度) 和 HKDF 一塊兒使用的 hash 算法。以 Client 的偏好降序排列。若是列表包含的密碼套件是 Server 不能識別的或者是不能支持的,或者是但願使用的,Server 必須忽略這些密碼套件,照常處理剩下來的密碼套件。若是 Client 嘗試創建 PSK 密鑰,則它應該至少包含一個與 PSK 相關的哈希加密套件。
legacy_compression_methods:
TLS 1.3 以前的 TLS 版本支持壓縮,在這個字段中發送支持的壓縮方法列表。對於每一個 ClientHello,該向量必須包含一個設置爲 0 的一個字節,它對應着 TLS 以前版本中的 null 壓縮方法。若是 TLS 1.3 中的 ClientHello 中這個字段包含有值,Server 必須當即發送 「illegal_parameter」 alert 消息停止握手。注意,TLS 1.3 Server 可能接收到 TLS 1.2 或者以前更老版本的 ClientHellos,其中包含了其餘壓縮方法。若是正在協商這些以前的版本,那麼必須遵循 TLS 以前版本的規定。
extensions:
Client 經過在擴展字段中發送數據,向 Server 請求擴展功能。「Extension」 遵循格式定義。在 TLS 1.3 中,使用肯定的擴展項是強制的。由於功能被移動到了擴展中以保持和以前 TLS 版本的 ClientHello 消息的兼容性。Server 必須忽略不能識別的 extensions。
全部版本的 TLS 都容許可選的帶上 compression_methods 這個擴展字段。TLS 1.3 ClientHello 消息一般包含擴展消息(至少包含 「supported_versions」,不然這條消息會被解讀成 TLS 1.2 的 ClientHello 消息)然而,TLS 1.3 Server 也有可能收到以前 TLS 版本發來的不帶擴展字段的 ClientHello 消息。擴展是否存在,能夠經過檢測 ClientHello 結尾的 compression_methods 字段內是否有字節來肯定。請注意,這種檢測可選數據的方法與具備可變長度字段的普通 TLS 方法不一樣,可是在擴展被定義以前,這種方法能夠用來作兼容。TLS 1.3 Server 須要首先執行此項檢查,而且僅當存在 「supported_versions」 擴展時才嘗試協商 TLS 1.3。若是協商的是 TLS 1.3 以前的版本,Server 必須作 2 項檢查:legacy_compression_methods 字段後面是否還有數據;有效的 extensions block 後沒有數據跟隨。若是上面這 2 項檢查都不經過,須要當即發送 "decode_error" alert 消息停止握手。
若是 Client 經過擴展請求額外功能,可是這個功能 Server 並不提供,則 Client 能夠停止握手。
發送 ClientHello 消息後,Client 等待 ServerHello 或者 HelloRetryRequest 消息。若是 early data 在使用中,Client 在等待下一條握手消息期間,能夠先發送 early Application Data。
若是 Server 和 Client 能夠在 ClientHello 消息中協商出一套雙方均可以接受的握手參數的話,那麼 Server 會發送 Server Hello 消息迴應 ClientHello 消息。
消息的結構體是:
struct {
ProtocolVersion legacy_version = 0x0303; /* TLS v1.2 */
Random random;
opaque legacy_session_id_echo<0..32>;
CipherSuite cipher_suite;
uint8 legacy_compression_method = 0;
Extension extensions<6..2^16-1>;
} ServerHello;
複製代碼
legacy_version:
在 TLS 1.3 以前的版本,這個字段被用來版本協商和標識創建鏈接時候雙方選擇的版本號。不幸的是,一些中間件在給這個字段賦予新值的時候可能會失敗。在 TLS 1.3 中,Server 用 "supported_versions" 擴展字段來標識它支持的版本,legacy_version 字段必須設置爲 0x0303(這個值表明的 TLS 1.2)。(有關向後兼容性的詳細信息,請參閱附錄D.)
random:
由安全隨機數生成器生成的隨機 32 字節。若是協商的是 TLS 1.1 或者 TLS 1.2 ,那麼最後 8 字節必須被重寫,其他的 24 字節必須是隨機的。這個結構由 Server 生成而且必須獨立於 ClientHello.random。
legacy_session_id_echo:
Client 的 legacy_session_id 字段的內容。請注意,即便 Server 決定再也不恢復 TLS 1.3 以前的會話,Client 的 legacy_session_id 字段緩存的是 TLS 1.3 以前的值,這個時候 legacy_session_id_echo 字段也會被 echoed。Client 收到的 legacy_session_id_echo 值和它在 ClientHello 中發送的值不匹配的時候,必須當即用 "illegal_parameter" alert 消息停止握手。
cipher_suite:
Server 從 ClientHello 中的 cipher_suites 列表中選擇的一個加密套件。Client 若是接收到並無提供的密碼套件,此時應該當即用 "illegal_parameter" alert 消息停止握手。
legacy_compression_method:
必須有 0 值的單一字節。
extensions:
擴展列表。ServerHello 中必須僅僅只能包括創建加密上下文和協商協議版本所需的擴展。全部 TLS 1.3 的 ServerHello 消息必須包含 "supported_versions" 擴展。當前的 ServerHello 消息還另外包含 "pre_shared_key" 擴展或者 "key_share" 擴展,或者兩個擴展都有(當使用 PSK 和 (EC)DHE 創建鏈接的時候)。其餘的擴展會在 EncryptedExtensions 消息中分別發送。
出於向後兼容中間件的緣由,HelloRetryRequest 消息和 ServerHello 消息採用相同的結構體,但須要隨機設置 HelloRetryRequest 的 SHA-256 特定值:
CF 21 AD 74 E5 9A 61 11 BE 1D 8C 02 1E 65 B8 91
C2 A2 11 16 7A BB 8C 5E 07 9E 09 E2 C8 A8 33 9C
複製代碼
當收到 server_hello 消息之後,實現必須首先檢查這個隨機值是否是和上面這個值匹配。若是和上面這個值是一致的,再繼續處理。
TLS 1.3 具備降級保護機制,這種機制是經過嵌入在 Server 的隨機值實現的。TLS 1.3 Server 協商 TLS 1.2 或者更老的版本,爲了響應 ClientHello ,ServerHello 消息中必須在最後 8 個字節中填入特定的隨機值。
若是協商的 TLS 1.2 ,TLS 1.3 Server 必須把 ServerHello 中的 Random 字段的最後 8 字節設置爲:
44 4F 57 4E 47 52 44 01
D O W N G R D
複製代碼
若是協商的 TLS 1.1 或者更老的版本,TLS 1.3 Server 和 TLS 1.2 Server 必須把 ServerHello 中的 Random 字段的最後 8 字節的值改成:
44 4F 57 4E 47 52 44 00
D O W N G R D
複製代碼
TLS 1.3 Client 接收到 TLS 1.2 或者 TLS 更老的版本的 ServerHello 消息之後,必需要檢查 ServerHello 中的 Random 字段的最後 8 字節不等於上面 2 個值纔對。TLS 1.2 的 Client 也須要檢查最後 8 個字節,若是協商的是 TLS 1.1 或者是更老的版本,那麼 Random 值也不該該等於上面第二個值。若是都沒有匹配上,那麼 Client 必須用 "illegal_parameter" alert 消息停止握手。這種機制提供了有限的保護措施,抵禦降級攻擊。經過 Finished exchange ,能超越保護機制的保護範圍:由於在 TLS 1.2 或更低的版本上,ServerKeyExchange 消息包含 2 個隨機值的簽名。只要使用了臨時的加密方式,攻擊者就不可能在不被發現的狀況下,修改隨機值。因此對於靜態的 RSA,是沒法提供降級攻擊的保護。
請注意,上面這些改動在 RFC5246 中說明的,實際上許多 TLS 1.2 的 Client 和 Server 都沒有按照上面的規定來實踐。
若是 Client 在從新協商 TLS 1.2 或者更老的版本的時候,協商過程當中收到了 TLS 1.3 的 ServerHello,這個時候 Client 必須當即發送 「protocol_version」 alert 停止握手。請注意,一旦 TLS 1.3 協商完成,就沒法再從新協商了,由於 TLS 1.3 嚴禁從新協商。
若是在 Client 發來的 ClientHello 消息中可以找到一組能夠相互支持的參數,可是 Client 又不能爲接下來的握手提供足夠的信息,這個時候 Server 就須要發送 HelloRetryRequest 消息來響應 ClientHello 消息。在上一節中,談到 HelloRetryRequest 和 ServerHello 消息是有相同的數據結構,legacy_version, legacy_session_id_echo, cipher_suite, legacy_compression_method 這些字段的含義也是同樣的。爲了討論的方便,下文中,咱們討論 HelloRetryRequest 消息都當作不一樣的消息來對待。
Server 的擴展集中必須包含 "supported_versions"。另外,它還須要包含最小的擴展集,能讓 Client 生成正確的 ClientHello 對。相比 ServerHello 而言,HelloRetryRequest 只能包含任何在第一次 ClientHello 中出現過的擴展,除了可選的 "cookie" 之外。
Client 接收到 HelloRetryRequest 消息之後,必需要先校驗 legacy_version, legacy_session_id_echo, cipher_suite, legacy_compression_method 這四個參數。先從 「supported_versions」 開始決定和 Server 創建鏈接的版本,而後再處理擴展。若是 HelloRetryRequest 不會致使 ClientHello 的任何更改,Client 必須用 「illegal_parameter」 alert 消息停止握手。若是 Client 在一個鏈接中收到了第 2 個 HelloRetryRequest 消息( ClientHello 自己就是響應 HelloRetryRequest 的),那麼必須用 「unexpected_message」 alert 消息停止握手。
不然,Client 必須處理 HelloRetryRequest 中全部的擴展,而且發送第二個更新的 ClientHello。在本規範中定義的 HelloRetryRequest 擴展名是:
Client 在接收到本身並無提供的密碼套件的時候必須當即停止握手。Server 必須確保在接收到合法而且更新過的 ClientHello 時,它們在協商相同的密碼套件(若是 Server 把選擇密碼套件做爲協商的第一步,那麼這一步會自動發送)。Client 收到 ServerHello 後必須檢查 ServerHello 中提供的密碼套件是否與 HelloRetryRequest 中的密碼套件相同,不然將以 「illegal_parameter」 alert 消息停止握手。
此外,Client 在其更新的 ClientHello 中,Client 不能提供任何與所選密碼套件之外的預共享密鑰(與哈希相關聯的)。這容許 Client 避免在第二個 ClientHello 中計算多個散列的部分哈希轉錄。
在 HelloRetryRequest 的 "support_versions" 擴展中的 selected_version 字段的值必須被保留在 ServerHello 中,若是這個值變了,Client 必須用 「illegal_parameter」 alert 消息停止握手。
許多 TLS 的消息都包含 tag-length-value 編碼的擴展數據結構:
struct {
ExtensionType extension_type;
opaque extension_data<0..2^16-1>;
} Extension;
enum {
server_name(0), /* RFC 6066 */
max_fragment_length(1), /* RFC 6066 */
status_request(5), /* RFC 6066 */
supported_groups(10), /* RFC 8422, 7919 */
signature_algorithms(13), /* RFC 8446 */
use_srtp(14), /* RFC 5764 */
heartbeat(15), /* RFC 6520 */
application_layer_protocol_negotiation(16), /* RFC 7301 */
signed_certificate_timestamp(18), /* RFC 6962 */
client_certificate_type(19), /* RFC 7250 */
server_certificate_type(20), /* RFC 7250 */
padding(21), /* RFC 7685 */
pre_shared_key(41), /* RFC 8446 */
early_data(42), /* RFC 8446 */
supported_versions(43), /* RFC 8446 */
cookie(44), /* RFC 8446 */
psk_key_exchange_modes(45), /* RFC 8446 */
certificate_authorities(47), /* RFC 8446 */
oid_filters(48), /* RFC 8446 */
post_handshake_auth(49), /* RFC 8446 */
signature_algorithms_cert(50), /* RFC 8446 */
key_share(51), /* RFC 8446 */
(65535)
} ExtensionType;
複製代碼
這裏:
全部的擴展類型由 IANA 維護,具體的見附錄。
擴展一般以請求/響應方式構建,雖然有些擴展只是一些標識,並不會有任何響應。Client 在 ClientHello 中發送其擴展請求,Server 在 ServerHello, EncryptedExtensions, HelloRetryRequest,和 Certificate 消息中發送對應的擴展響應。Server 在 CertificateRequest 消息中發送擴展請求,Client 可能迴應 Certificate 消息。Server 也有可能不請自來的在 NewSessionTicket 消息中直接發送擴展請求,Client 能夠不用直接響應這條消息。
若是遠端沒有發送相應的擴展請求,除了 HelloRetryRequest 消息中的 「cookie」 擴展之外,實現方不得發送擴展響應。在接收到這樣的擴展之後,端點必須用 "unsupported_extension" alert 消息停止握手。
下表給出了可能出現的消息的擴展名,使用如下表示法:CH (ClientHello), SH (ServerHello), EE (EncryptedExtensions), CT (Certificate), CR (CertificateRequest), NST (NewSessionTicket), 和 HRR (HelloRetryRequest) 。當實現方在接收到它能識別的消息,而且並無爲出現的消息作規定的話,它必須用 "illegal_parameter" alert 消息停止握手。
+--------------------------------------------------+-------------+
| Extension | TLS 1.3 |
+--------------------------------------------------+-------------+
| server_name [RFC6066] | CH, EE |
| | |
| max_fragment_length [RFC6066] | CH, EE |
| | |
| status_request [RFC6066] | CH, CR, CT |
| | |
| supported_groups [RFC7919] | CH, EE |
| | |
| signature_algorithms (RFC 8446) | CH, CR |
| | |
| use_srtp [RFC5764] | CH, EE |
| | |
| heartbeat [RFC6520] | CH, EE |
| | |
| application_layer_protocol_negotiation [RFC7301] | CH, EE |
| | |
| signed_certificate_timestamp [RFC6962] | CH, CR, CT |
| | |
| client_certificate_type [RFC7250] | CH, EE |
| | |
| server_certificate_type [RFC7250] | CH, EE |
| | |
| padding [RFC7685] | CH |
| | |
| key_share (RFC 8446) | CH, SH, HRR |
| | |
| pre_shared_key (RFC 8446) | CH, SH |
| | |
| psk_key_exchange_modes (RFC 8446) | CH |
| | |
| early_data (RFC 8446) | CH, EE, NST |
| | |
| cookie (RFC 8446) | CH, HRR |
| | |
| supported_versions (RFC 8446) | CH, SH, HRR |
| | |
| certificate_authorities (RFC 8446) | CH, CR |
| | |
| oid_filters (RFC 8446) | CR |
| | |
| post_handshake_auth (RFC 8446) | CH |
| | |
| signature_algorithms_cert (RFC 8446) | CH, CR |
+--------------------------------------------------+-------------+
複製代碼
當存在多種不一樣類型的擴展的時候,除了 "pre_shared_key" 必須是 ClientHello 的最後一個擴展,其餘的擴展間的順序能夠是任意的。("pre_shared_key" 能夠出如今 ServerHello 中擴展塊中的任何位置)。不能存在多個同一個類型的擴展。
在 TLS 1.3 中,與 TLS 1.2 不一樣,即便是恢復 PSK 模式,每次握手都須要協商擴展。然而,0-RTT 的參數是在前一次握手中協商的。若是參數不匹配,須要拒絕 0-RTT。
在 TLS 1.3 中新特性和老特性之間存在微妙的交互,這可能會使得總體安全性顯著降低。下面是設計新擴展的時候須要考慮的因素:
Server 不一樣意擴展的某些狀況是錯誤的(例如握手不能繼續),有些狀況只是簡單的不支持特定的功能。通常來講,前一種狀況應該用錯誤的 alert,後一種狀況應該用 Server 的擴展響應中的一個字段來處理。
擴展應儘量設計爲防止能經過人爲操縱握手信息,從而強制使用(或不使用)特定功能的攻擊。無論這個功能是否會引發安全問題,這個原則都必須遵照。一般,包含在 Finished 消息的哈希輸入中的擴展字段是不用擔憂的,可是在握手階段,擴展試圖改變了發送消息的含義,這種狀況須要特別當心。設計者和實現者應該意識到,在握手完成身份認證以前,攻擊者均可以修改消息,插入、刪除或者替換擴展。
struct {
select (Handshake.msg_type) {
case client_hello:
ProtocolVersion versions<2..254>;
case server_hello: /* and HelloRetryRequest */
ProtocolVersion selected_version;
};
} SupportedVersions;
複製代碼
「supported_versions」 對於 Client 來講,Client 用它來標明它所能支持的 TLS 版本,對於 Server 來講,Server 用它來標明正在使用的 TLS 版本。這個擴展包含一個按照優先順序排列的,能支持的版本列表。最優先支持的版本放在第一個。TLS 1.3 這個版本的規範是必須在發送 ClientHello 消息時候帶上這個擴展,擴展中包含全部準備協商的 TLS 版本。(對於這個規範來講,這意味着最低是 0x0304,可是若是要協商 TLS 的之前的版本,那麼這個擴展必需要帶上)
若是不存在 「supported_versions」 擴展,知足 TLS 1.3 而且也兼容 TLS 1.2 規範的 Server 須要協商 TLS 1.2 或者以前的版本,即便 ClientHello.legacy_version 是 0x0304 或者更高的版本。Server 在接收到 ClientHello 中的 legacy_version 的值是 0x0304 或者更高的版本的時候,Server 可能須要馬上停止握手。
若是 ClientHello 中存在 「supported_versions」 擴展,Server 禁止使用 ClientHello.legacy_version 的值做爲版本協商的值,只能使用 "supported_versions" 決定 Client 的偏好。Server 必須只選擇該擴展中存在的 TLS 版本,而且必需要忽略任何未知版本。注意,若是通訊的一方支持稀疏範圍,這種機制使得能夠在 TLS 1.2 以前的版本間進行協商。選擇支持 TLS 的之前版本的 TLS 1.3 的實現應支持 TLS 1.2。Server 應準備好接收包含此擴展名的 ClientHellos 消息,但不要在 viersions 列表中包含 0x0304。
Server 在協商 TLS 1.3 以前的版本,必需要設置 ServerHello.version,不能發送 "supported_versions" 擴展。Server 在協商 TLS 1.3 版本時候,必須發送 "supported_versions" 擴展做爲響應,而且擴展中要包含選擇的 TLS 1.3 版本號(0x0304)。還要設置 ServerHello.legacy_version 爲 0x0303(TLS 1.2)。Client 必須在處理 ServerHello 以前檢查此擴展(儘管須要先解析 ServerHello 以便讀取擴展名)。若是 "supported_versions" 擴展存在,Client 必須忽略 ServerHello.legacy_version 的值,只使用 "supported_versions" 中的值肯定選擇的版本。若是 ServerHello 中的 "supported_versions" 擴展包含了 Client 沒有提供的版本,或者是包含了 TLS 1.3 以前的版本(原本是協商 TLS 1.3 的,卻又包含了 TLS 1.3 以前的版本),Client 必須當即發送 "illegal_parameter" alert 消息停止握手。
struct {
opaque cookie<1..2^16-1>;
} Cookie;
複製代碼
Cookies 有 2 大主要目的:
容許 Server 強制 Client 展現網絡地址的可達性(所以提供了一個保護 Dos 的度量方法),這主要是面向無鏈接的傳輸(參考 RFC 6347 中的例子)
容許 Server 卸載狀態。從而容許 Server 在向 Client 發送 HelloRetryRequest 消息的時候,不存儲任何狀態。爲了實現這一點,能夠經過 Server 把 ClientHello 的哈希存儲在 HelloRetryRequest 的 cookie 中(用一些合適的完整性算法保護)。
當發送 HelloRetryRequest 消息時,Server 能夠向 Client 提供 「cookie」 擴展(這是常規中的一個例外,常規約定是:只能是可能被髮送的擴展才能夠出如今 ClientHello 中)。當發送新的 ClientHello 消息時,Client 必須將 HelloRetryRequest 中收到的擴展的內容複製到新 ClientHello 中的 「cookie」 擴展中。Client 不得在後續鏈接中使用首次 ClientHello 中的 Cookie。
當 Server 在無狀態運行的時候,在第一個和第二個 ClientHello 之間可能會收到不受保護的 change_cipher_spec 消息。因爲 Server 沒有存儲任何狀態,它會表現出像到達的第一條消息同樣。無狀態的 Server 必須忽略這些記錄。
TLS 1.3 提供了 2 種擴展來標明在數字簽名中可能用到的簽名算法。"signature_algorithms_cert" 擴展提供了證書裏面的簽名算法。"signature_algorithms" 擴展(TLS 1.2 中就有這個擴展了),提供了 CertificateVerify 消息中的簽名算法。證書中的密鑰必需要根據所用的簽名算法匹配合適的類型。對於 RSA 密鑰和 PSS 簽名,這是一個特殊問題,描述以下:若是沒有 "signature_algorithms_cert" 擴展,則 "signature_algorithms" 擴展一樣適用於證書中的簽名。Client 想要 Server 經過證書來認證本身,則必須發送 "signature_algorithms" 擴展。若是 Server 正在進行證書的認證,這個時候 Client 又沒有提供 "signature_algorithms"擴展,Server 必須 發送 "missing_extension" 消息停止握手。
加入 "signature_algorithms_cert" 擴展的意圖是爲了讓已經支持了證書的不一樣算法集的實現方,能明確的標識他們的能力。TLS 1.2 實現應該也應該處理這個擴展。在兩種狀況下具備相同策略的實現能夠省略 "signature_algorithms_cert" 擴展名。
這些擴展中的 "extension_data" 字段包含一個 SignatureSchemeList 值:
enum {
/* RSASSA-PKCS1-v1_5 algorithms */
rsa_pkcs1_sha256(0x0401),
rsa_pkcs1_sha384(0x0501),
rsa_pkcs1_sha512(0x0601),
/* ECDSA algorithms */
ecdsa_secp256r1_sha256(0x0403),
ecdsa_secp384r1_sha384(0x0503),
ecdsa_secp521r1_sha512(0x0603),
/* RSASSA-PSS algorithms with public key OID rsaEncryption */
rsa_pss_rsae_sha256(0x0804),
rsa_pss_rsae_sha384(0x0805),
rsa_pss_rsae_sha512(0x0806),
/* EdDSA algorithms */
ed25519(0x0807),
ed448(0x0808),
/* RSASSA-PSS algorithms with public key OID RSASSA-PSS */
rsa_pss_pss_sha256(0x0809),
rsa_pss_pss_sha384(0x080a),
rsa_pss_pss_sha512(0x080b),
/* Legacy algorithms */
rsa_pkcs1_sha1(0x0201),
ecdsa_sha1(0x0203),
/* Reserved Code Points */
private_use(0xFE00..0xFFFF),
(0xFFFF)
} SignatureScheme;
struct {
SignatureScheme supported_signature_algorithms<2..2^16-2>;
} SignatureSchemeList;
複製代碼
請注意:這個枚舉之因此名爲 "SignatureScheme",是由於在 TLS 1.2 中已經存在了 "SignatureAlgorithm" 類型,取而代之。在本篇文章中,咱們都使用術語 "簽名算法"。
每個列出的 SignatureScheme 的值是 Client 想要驗證的單一簽名算法。這些值按照優先級降序排列。請注意,簽名算法以任意長度的消息做爲輸入,而不是摘要做爲輸入。傳統上用於摘要的算法應該在 TLS 中定義,首先使用指定的哈希算法對輸入進行哈希計算,而後再進行常規處理。上面列出的代碼具備如下含義:
RSASSA-PKCS1-v1_5 algorithms:
表示使用 RSASSA-PKCS1-v1_5 RFC8017 和定義在 SHS 中對應的哈希算法的簽名算法。這些值僅指,出如今證書中又沒有被定義用於簽名 TLS 握手消息的簽名。這些值會出如今 "signature_algorithms" 和 "signature_algorithms_cert" 中,由於須要向後兼容 TLS 1.2 。
ECDSA algorithms:
表示簽名算法使用 ECDSA,對應的曲線在 ANSI X9.62 ECDSA 和 FIPS 186-4 DSS 中定義了,對應的哈希算法在 SHS 中定義了。簽名被表示爲 DER 編碼的 ECDSA-Sig-Value 結構。
RSASSA-PSS RSAE algorithms:
表示使用帶有掩碼生成函數 1 的 RSASSA-PSS 簽名算法。在掩碼生成函數中使用的摘要和被簽名的摘要都是在 SHS 中定義的相應的哈希算法。鹽的長度必須等於摘要算法輸出的長度。若是公鑰在 X.509 證書中,則必須使用 rsaEncryption OID RFC5280。
EdDSA algorithms:
表示使用定義在 RFC 8032 中的 EdDSA 算法或者其後續改進算法。請注意,這些相應算法是 "PureEdDSA" 算法,而不是 "prehash" 變種算法。
RSASSA-PSS PSS algorithms:
表示使用帶有掩碼生成函數 1 的 RSASSA-PSS RFC 8017 簽名算法。在掩碼生成函數中使用的摘要和被簽名的摘要都是在 SHS 中定義的相應的哈希算法。鹽的長度必須等於摘要算法的長度。若是公鑰在 X.509 證書中,則必須使用 RSASSA-PSS OID RFC5756。當它被用在證書籤名中,算法參數必須是 DER 編碼。若是存在相應的公鑰參數,則簽名中的參數必須與公鑰中的參數相同。
Legacy algorithms:
表示使用正在被廢棄中的算法,由於這些算法有已知的缺點。特別是 SHA-1 配合上文提到的 RSASSA-PKCS1-v1_5 和 ECDSA 算法一塊兒使用。這些值僅指,出如今證書中又沒有被定義用於簽名 TLS 握手消息的簽名。這些值會出如今 "signature_algorithms" 和 "signature_algorithms_cert" 中,由於須要向後兼容 TLS 1.2 。終端不該該協商這些算法,但容許這樣作只是爲了向後兼容。提供這些值的 Client 必須把他們列在最低優先級的位置上(在 SignatureSchemeList 中的全部其餘算法以後列出)。TLS 1.3 Server 毫不能提供 SHA-1 簽名證書,除非沒有它就沒法生成有效的證書鏈。
自簽名證書上的簽名或信任錨的證書不能經過校驗,由於它們開始了一個認證路徑(見 RFC 5280)。開始認證路徑的證書可使用 "signature_algorithms" 擴展中不建議支持的簽名算法。
請注意,TLS 1.2 中這個擴展的定義和 TLS 1.3 的定義不一樣。在協商 TLS 1.2 版本時,願意協商 TLS 1.2 的 TLS 1.3 實現必須符合 RFC5246 的要求,尤爲是:
TLS 1.2 ClientHellos 能夠忽略此擴展。
在 TLS 1.2 中,擴展包含 hash/signature pairs。這些 pairs 被編碼爲兩個八位字節,因此已經分配空間的 SignatureScheme 值與 TLS 1.2 的編碼對齊。 一些傳統的 pairs 保留未分配。這些算法已被 TLS 1.3 棄用。它們不得在任何實現中被提供或被協商。 特別是,不得使用 MD5 [SLOTH] 、SHA-224 和 DSA。
ECDSA 簽名方案和 TLS 1.2 的 hash/signature pairs 一致。然而,舊的語義並無限制簽名曲線。若是 TLS 1.2 被協商了,實現方必須準備接受在 "supported_groups" 擴展中使用任何曲線的簽名。
即便協商了 TLS 1.2,支持了 RSASSA-PSS(在TLS 1.3中是強制性的)的實現方也準備接受該方案的簽名。在TLS 1.2中,RSASSA-PSS 與 RSA 密碼套件一塊兒使用。
"certificate_authorities" 擴展用於表示終端支持的 CA, 而且接收的端點應該使用它來指導證書的選擇。
"certificate_authorities" 擴展的主體包含了一個 CertificateAuthoritiesExtension 結構:
opaque DistinguishedName<1..2^16-1>;
struct {
DistinguishedName authorities<3..2^16-1>;
} CertificateAuthoritiesExtension;
複製代碼
Client 可能會在 ClientHello 消息中發送 "certificate_authorities" 擴展,Server 可能會在 CertificateRequest 消息中發送 "certificate_authorities" 擴展。
"trusted_ca_keys" 擴展和 "certificate_authorities" 擴展有相同的目的,可是更加複雜。"trusted_ca_keys" 擴展不能在 TLS 1.3 中使用,可是它在 TLS 1.3 以前的版本中,可能出如今 Client 的 ClientHello 消息中。
"oid_filters" 擴展容許 Server 提供一組 OID/value 對,用來匹配 Client 的證書。若是 Server 想要發送這個擴展,有且僅有在 CertificateRequest 消息中才能發送。
struct {
opaque certificate_extension_oid<1..2^8-1>;
opaque certificate_extension_values<0..2^16-1>;
} OIDFilter;
struct {
OIDFilter filters<0..2^16-1>;
} OIDFilterExtension;
複製代碼
PKIX RFC 定義了各類證書擴展 OID 及其對應的值類型。根據類型,匹配的證書擴展值不必定是按位相等的。指望 TLS 實現將依靠它們的 PKI 庫,使用證書擴展 OID 來作證書的選擇。
本文檔定義了 RFC5280 中定義的兩個標準證書擴展的匹配規則:
當請求中聲明的全部 Key Usage 位也一樣在 Key Usage 證書擴展聲明瞭,那麼證書中的 Key Usage 擴展匹配了請求。
當請求中全部的密鑰 OIDs 在 Extended Key Usage 證書擴展中也存在,那麼證書中的 Extended Key Usage 匹配了請求。特殊的 anyExtendedKeyUsage OID 必定不能在請求中使用。
單獨的規範能夠爲其餘證書擴展的規則定義匹配規則。
"post_handshake_auth" 擴展用於代表 Client 願意握手後再認證。Server 不能向沒有提供此擴展的 Client 發送握手後再認證的 CertificateRequest 消息。Server 不能發送此擴展。
struct {} PostHandshakeAuth;
複製代碼
"post_handshake_auth" 擴展名中的 "extension_data" 字段爲零長度。
當 Client 發送 "supported_groups" 擴展的時候,這個擴展代表了 Client 支持的用於密鑰交換的命名組。按照優先級從高到低。
請注意:在 TLS 1.3 以前的版本中,這個擴展原來叫 "elliptic_curves",而且只包含橢圓曲線組。具體請參考 RFC8422 和 RFC7919。這個擴展一樣能夠用來協商 ECDSA 曲線。簽名算法如今獨立協商了。
這個擴展中的 "extension_data" 字段包含一個 "NamedGroupList" 值:
enum {
/* Elliptic Curve Groups (ECDHE) */
secp256r1(0x0017), secp384r1(0x0018), secp521r1(0x0019),
x25519(0x001D), x448(0x001E),
/* Finite Field Groups (DHE) */
ffdhe2048(0x0100), ffdhe3072(0x0101), ffdhe4096(0x0102),
ffdhe6144(0x0103), ffdhe8192(0x0104),
/* Reserved Code Points */
ffdhe_private_use(0x01FC..0x01FF),
ecdhe_private_use(0xFE00..0xFEFF),
(0xFFFF)
} NamedGroup;
struct {
NamedGroup named_group_list<2..2^16-1>;
} NamedGroupList;
複製代碼
Elliptic Curve Groups (ECDHE):
表示支持在 FIPS 186-4 [DSS] 或者 [RFC7748] 中定義的對應命名的曲線。0xFE00 到 0xFEFF 的值保留使用[RFC8126]。
Finite Field Groups (DHE):
表示支持相應的有限域組,相關定義能夠參考 [RFC7919]。0x01FC 到 0x01FF 的值保留使用。
named_group_list 中的項根據發送者的優先級排序(最好是優先選擇的)。
在 TLS 1.3 中,Server 容許向 Client 發送 "supported_groups" 擴展。Client 不能在成功完成握手以前,在 "supported_groups" 中找到的任何信息採起行動,但可使用從成功完成的握手中得到的信息來更改在後續鏈接中的 "key_share" 擴展中使用的組。若是 Server 中有一個組,它更想接受 "key_share" 擴展中的那些值,但仍然願意接受 ClientHello 消息,這時候它應該發送 "supported_groups" 來更新 Client 的偏好視圖。不管 Client 是否支持它,這個擴展名都應該包含 Server 支持的全部組。
"key_share" 擴展包含終端的加密參數。
Client 可能會發送空的 client_shares 向量,以額外的往返代價,向 Server 請求選擇的組。
struct {
NamedGroup group;
opaque key_exchange<1..2^16-1>;
} KeyShareEntry;
複製代碼
group:
要交換的密鑰的命名組。
key_exchange:
密鑰交換信息。這個字段的內容由特定的組和相應的定義肯定。有限域的 Diffie-Hellman 參數在下面會描述。橢圓曲線 Diffie-Hellman 參數也會下面會描述。
在 ClientHello 消息中,"key_share" 擴展中的 "extension_data" 包含 KeyShareClientHello 值:
struct {
KeyShareEntry client_shares<0..2^16-1>;
} KeyShareClientHello;
複製代碼
若是 Client 正在請求 HelloRetryRequest, 則這個向量能夠爲空。每一個 KeyShareEntry 值必須對應一個在 "supported_groups" 擴展中提供的組,而且出現的順序必須相同。然而,當優先級排名第一的組合是新的,而且不足以提供預生成 key shares 的時候,那麼值能夠是 "supported_groups" 擴展的非連續子集,而且能夠省略最優選的組,這種狀況是可能會出現的。
Client 能夠提供與其提供的 support groups 同樣多數量的 KeyShareEntry 的值。每一個值都表明了一組密鑰交換參數。例如,Client 可能會爲多個橢圓曲線或者多個 FFDHE 組提供 shares。每一個 KeyShareEntry 中的 key_exchange 值必須獨立生成。Client 不能爲相同的 group 提供多個 KeyShareEntry 值。Client 不能爲,沒有出如今 Client 的 "supported_group" 擴展中列出的 group 提供任何 KeyShareEntry 值。Server 會檢查這些規則,若是違反了規則,當即發送 "illegal_parameter" alert 消息停止握手。
在 HelloRetryRequest 消息中,"key_share" 擴展中的 "extension_data" 字段包含 KeyShareHelloRetryRequest 值。
struct {
NamedGroup selected_group;
} KeyShareHelloRetryRequest;
複製代碼
在 HelloRetryRequest 消息中收到此擴展後,Client 必需要驗證 2 點。第一點,selected_group 必須在原始的 ClientHello 中的 "supported_groups" 中出現過。第二點,selected_group 沒有在原始的 ClientHello 中的 "key_share" 中出現過。若是上面 2 點檢查都失敗了,那麼 Client 必須經過 "illegal_parameter" alert 消息來停止握手。不然,在發送新的 ClientHello 時,Client 必須將原始的 "key_share" 擴展替換爲僅包含觸發 HelloRetryRequest 的 selected_group 字段中指示的組,這個組中只包含新的 KeyShareEntry。
在 ServerHello 消息中,"key_share" 擴展中的 "extension_data" 字段包含 KeyShareServerHello 值。
struct {
KeyShareEntry server_share;
} KeyShareServerHello;
複製代碼
若是使用 (EC)DHE 密鑰創建連接,Server 在 ServerHello 中只提供了一個 KeyShareEntry。這個值必須與,Server 爲了協商密鑰交換在 Client 提供的 KeyShareEntry 值中選擇的值,在同一組中。Server 不能爲 Client 的 "supported_groups" 擴展中指定的任何 group 發送 KeyShareEntry 值。Server 也不能在使用 "psk_ke" PskKeyExchangeMode 時候發送 KeyShareEntry 值。若是使用 (EC)DHE 創建連接,Client 收到了包含在 "key_share" 擴展中的 HelloRetryRequest 消息,Client 必須驗證在 ServerHello 中選擇的 NameGroup 與 HelloRetryRequest 中是否相同。若是不相同,Client 必須當即發送 "illegal_parameter" alert 消息停止握手。
Client 和 Server 二者的 Diffie-Hellman [DH76] 參數都編碼在 KeyShareEntry 中的 KeyShare 數據結構中 opaque 類型的 key_exchange 字段中。opaque 類型的值包含指定 group 的 Diffie-Hellman 公鑰(Y = g^X mod p),是用大端整數編碼的。這個值大小爲 p 字節,若是字節不夠,須要在其左邊添加 0 。
請注意:對於給定的 Diffie-Hellman 組,填充會致使全部的公鑰具備相同的長度。
對端必需要相互驗證對方的公鑰,確保 1 < Y < p-1。此檢查確保遠程對端正常運行,也使得本地系統不會強制進入進入更小的 subgroup。
Client 和 Server 二者的 ECDHE 參數都編碼在 KeyShareEntry 中的 KeyShare 數據結構中 opaque 類型的 key_exchange 字段中。
對於 secp256r1,secp384r1 和 secp521r1,內容是如下結構體的序列化值:
struct {
uint8 legacy_form = 4;
opaque X[coordinate_length];
opaque Y[coordinate_length];
} UncompressedPointRepresentation;
複製代碼
X 和 Y 分別是網絡字節順序中 X 和 Y 值的二進制表示。因爲沒有內部長度標記,因此每一個數字佔用曲線參數隱含的 8 位字節數。對於 P-256,這意味着 X 和 Y 中的每個佔用 32 個八位字節,若是須要,則在左側填充零。對於 P-384,它們分別佔用 48 個八位字節,對於 P-521,它們各佔用 66 個八位字節。
對於曲線 secp256r1, secp384r1, 和 secp521r1,對端必須驗證對方的的公鑰 Q,以保證這個點是橢圓曲線上有效的點。合適的驗證方法定義在 [ECDSA] 中或者 [KEYAGREEMENT]。這個處理包括了 3 步。第一步:驗證 Q 不是無窮大的點 (O)。第二步,驗證 Q = (x, y) 中的兩個整數 x,y 有正確的間隔。第三步,驗證 (x, y) 是橢圓曲線方程的正確的解。對於這些曲線,實現方不須要再驗證正確的 subgroup 中的成員身份。
對於 X25519 和 X448 來講,公共值的內容是 [RFC7748] 中定義的相應函數的字節串輸入和輸出,X25519 的是 32 個字節, X448 的是 56 個字節。
請注意:TLS 1.3 以前的版本容許 point format 協商,TLS 1.3 移除了這個功能,以利於每一個曲線的單獨 point format。
爲了使用 PSK,Client 還必須發送一個 "psk_key_exchange_modes" 擴展。這個擴展語意是 Client 僅支持使用具備這些模式的 PSK。這就限制了在這個 ClientHello 中提供的 PSK 的使用,也限制了 Server 經過 NewSessionTicket 提供的 PSK 的使用。
若是 Client 提供了 "pre_shared_key" 擴展,那麼它必須也要提供 "psk_key_exchange_modes" 擴展。若是 Client 發送不帶 "psk_key_exchange_modes" 擴展名的 "pre_shared_key",Server 必須當即停止握手。Server 不能選擇一個 Client 沒有列出的密鑰交換模式。此擴展還限制了與 PSK 恢復使用的模式。Server 也不能發送與建議的 modes 不兼容的 NewSessionTicket。不過若是 Server 必定要這樣作,影響的只是 Client 在嘗試恢復會話的時候會失敗。
Server 不能發送 "psk_key_exchange_modes" 擴展:
enum { psk_ke(0), psk_dhe_ke(1), (255) } PskKeyExchangeMode;
struct {
PskKeyExchangeMode ke_modes<1..255>;
} PskKeyExchangeModes;
複製代碼
psk_ke:
僅 PSK 密鑰創建。在這種模式下,Server 不能提供 "key_share" 值。
psk_dhe_ke:
PSK 和 (EC)DHE 創建。在這種模式下,Client 和 Server 必須提供 "key_share" 值。
將來分配的任何值都必需要能保證傳輸的協議消息能夠明確的標識 Server 選擇的模式。目前 Server 選擇的值由 ServerHello 中存在的 "key_share" 表示。
當使用 PSK 而且 PSK 容許使用 early_data 的時候,Client 能夠在其第一個消息中發送應用數據。若是 Client 選擇這麼作,則必須發送 "pre_shared_key" 和 "early_data" 擴展。
Early Data Indication 擴展中的 "extension_data" 字段包含了一個 EarlyDataIndication 值。
struct {} Empty;
struct {
select (Handshake.msg_type) {
case new_session_ticket: uint32 max_early_data_size;
case client_hello: Empty;
case encrypted_extensions: Empty;
};
} EarlyDataIndication;
複製代碼
有關 max_early_data_size 字段的使用請看 New Session Ticket Message 章節。
0-RTT 數據(版本,對稱加密套件,應用層協議協商協議[RFC7301],等等)的參數與使用中的 PSK 參數相關。對於外部配置的 PSK,關聯值是由密鑰提供的。對於經過 NewSessionTicket 消息創建的 PSK,關聯值是在創建 PSK 鏈接時協商的值。PSK 用來加密 early data 必須是 Client 在 "pre_shared_key" 擴展中列出的第一個 PSK。
對於經過 NewSessionTicket 提供的 PSK,Server 必須驗證所選 PSK 標識中的 ticket age(從 PskIdentity.obfuscated_ticket_age 取 2^32 模中減去 ticket_age_add)距離 ticket 發出的時間是否有一個很小的公差。若是相差的時間不少,那麼 Server 應該繼續握手,可是要拒絕 0-RTT,而且還要假定這條 ClientHello 是新的,也不能採起任何其餘措施。
在第一次 flight 中發送的 0-RTT 消息與其餘 flight (握手和應用數據)中發送的相同類型的消息具備相同(加密)的內容類型,但受到不一樣密鑰的保護。若是 Server 已經接收了 early data,Client 在收到 Server 的 Finished 消息之後,Client 則會發送 EndOfEarlyData 消息表示密鑰更改。這條消息將會使用 0-RTT 的 traffic 密鑰進行加密。
Server 接收 "early_data" 擴展必須如下面三種方式之一操做:
忽略 "early_data" 擴展,並返回常規的 1-RTT 響應。Server 嘗試經過用握手中的流量密鑰(traffic key)解密收到的記錄,並忽略掉 early data。丟棄解密失敗的記錄(取決於配置的 max_early_data_size)。一旦一個記錄被解密成功,它將會被 Server 看作 Client 第二次 flight 的開始而且 Server 會把它當作普通的 1-RTT 來處理。
經過迴應 HelloRetryRequest 來請求 Client 發送另一個 ClientHello。Client 不能在這個 ClientHello 中包含 "early_data" 擴展。Server 經過跳過具備外部內容類型的 "application_data"(說明他們被加密了) 的全部記錄來忽略 early data(一樣取決於配置的 max_early_data_size)。
在 EncryptedExtensions 中返回本身的 "early_data" 擴展,代表它準備處理 early data。Server 不可能只接受 early data 消息中的一部分。即便 Server 發送了一條接收 early data 的消息,可是實際上 early data 可能在 Server 生成這條消息的時候已經在 flight 了。
爲了接受 early data,Server 必須已經接受了 PSK 密碼套件而且選擇了 Client 的 "pre_shared_key" 擴展中提供的第一個密鑰。此外,Server 還須要驗證如下的值和選擇的 PSK 關聯值同樣:
這些要求是使用相關 PSK 執行 1-RTT 握手所需的超集。對於外部創建的 PSK,關聯值是與密鑰一塊兒提供的值。對於經過 NewSessionTicket 消息創建的 PSK,關聯值是在鏈接中協商的值,在這期間 ticket 被創建了。
將來的擴展必須定義它們與 0-RTT 的交互。
若是任何檢查失敗了,Server 不得在響應中附帶擴展,而且必須使用上面列出的前兩種機制中的一個,丟棄全部 first-flight 數據(所以回落到 1-RTT 或者 2-RTT)。若是 Client 嘗試 0-RTT 握手但 Server 拒絕了它,則 Server 一般不會有 0-RTT 記錄保護密鑰,而必須使用試用解密(使用 1-RTT 握手密鑰或者經過在有 HelloRetryRequest 消息的狀況下查找明文 ClientHello)找到第一個非 0-RTT 消息。
若是 Server 選擇接受 early_data 擴展,那麼在處理 early data 記錄的時候,Server 必須遵照用相同的標準(指定的相同錯誤處理要求)來處理全部記錄。具體來講,若是 Server 沒法解密已經接受的 "early_data" 擴展中的記錄,則它必須發送 "bad_record_mac" alert 消息停止握手。
若是 Server 拒絕 "early_data" 擴展,則 Client 應用程序能夠選擇在握手完成後從新發送先前在 early data 中發送的應用數據。請注意,early data 的自動重傳可能會致使關於鏈接狀態的誤判。例如,當協商鏈接從用於 early data 的協議中選擇不一樣的 ALPN 協議時,應用程序可能須要構造不一樣的消息。一樣,若是 early data 假定包含有關鏈接狀態的任何內容,則在握手完成後可能會錯誤地發送這些內容。
TLS 的實現不該該自動從新發送 early data;應用程序能夠很好的決定什麼時候重傳。除非協商鏈接選擇相同的 ALPN 協議,不然 TLS 實現毫不能自動從新發送 early data。
"pre_shared_key" 擴展用來協商標識的,這個標識是與 PSK 密鑰相關聯的給定握手所使用的預共享密鑰的標識。
這個擴展中的 "extension_data" 字段包含一個 PreSharedKeyExtension 值:
struct {
opaque identity<1..2^16-1>;
uint32 obfuscated_ticket_age;
} PskIdentity;
opaque PskBinderEntry<32..255>;
struct {
PskIdentity identities<7..2^16-1>;
PskBinderEntry binders<33..2^16-1>;
} OfferedPsks;
struct {
select (Handshake.msg_type) {
case client_hello: OfferedPsks;
case server_hello: uint16 selected_identity;
};
} PreSharedKeyExtension;
複製代碼
identity:
key 的標籤。例如,一個 ticket 或者是一個外部創建的預共享密鑰的標籤。
obfuscated_ticket_age:
age of the key 的混淆版本。這一章節描述了經過 NewSessionTicket 消息創建,如何爲標識(identities)生成這個值。對於外部創建的標識(identities),應該使用 0 的 obfuscated_ticket_age,而且 Server 也必須忽略這個值。
identities:
Client 願意和 Server 協商的 identities 列表。若是和 "early_data" 一塊兒發送,第一個標識被用來標識 0-RTT 的。
binders:
一系列的 HMAC 值。和 identities 列表中的每個值都一一對應,而且順序一致。
selected_identity:
Server 選擇的標識,這個標識是以 Client 列表中標識表示爲基於 0 的索引。
每個 PSK 都和單個哈希算法相關聯。對於經過 ticket 創建的 PSK,當 ticket 在鏈接中被創建,這時候用的哈希算法是 KDF 哈希算法。對於外部創建的 PSK,當 PSK 創建的時候,哈希算法必須設置,若是沒有設置,默認算法是 SHA-256。Server 必須確保它選擇的是兼容的 PSK (若是有的話) 和密鑰套件。
在 TLS 1.3 以前的版本中,Server Name Identification (SNI) 的值旨在與會話相關聯。Server 被強制要求,與會話關聯的 SNI 值要和恢復握手中指定的 SNI 值相互匹配。然而事實上,實現方和他們使用的兩個提供的 SNI 值是不一致的,這樣就會致使 Client 須要執行一致性的要求。在 TLS 1.3 版本中,SNI 的值始終在恢復握手中被明確的指出,而且 Server 不須要將 SNI 值和 ticket 相關聯。不過 Client 須要將 SNI 和 PSK 一塊兒存儲,以知足 [4.6.1 章節] 的要求。
實現者請注意:會話恢復是 PSK 最主要的用途,實現 PSK/密鑰套件 匹配要求的最直接的方法是先協商密碼套件,而後再排除任何不兼容的 PSK。任何未知的 PSK (例如:不在 PSK 數據庫中,或者用未知的 key 進行編碼的)都必須忽略。若是找不到可接受的 PSK,若是可能,Server 應該執行 non-PSK 握手。若是向後兼容性很重要,Client 提供的,外部創建的 PSK 應該影響密碼套件的選擇。
在接受PSK密鑰創建以前,Server 必須先驗證相應的 binder 值(見 [4.2.11.2 節])。若是這個值不存在或者未驗證,則 Server 必須當即停止握手。Server 不該該嘗試去驗證多個 binder,而應該選擇單個 PSK 而且僅驗證對應於該 PSK 的 binder。見 Appendix E.6 和 [8.2 節] 描述了針對這個要求的安全性解釋。爲了接受 PSK 密鑰創建鏈接,Server 發送 "pre_shared_key" 擴展,標明它所選擇的 identity。
Client 必須驗證 Server 的 selected_identity 是否在 Client 提供的範圍以內。Server 選擇的加密套件標明瞭與 PSK 關聯的哈希算法,若是 ClientHello "psk_key_exchange_modes" 有須要,Server 還應該發送 "key_share" 擴展。若是這些值不一致,Client 必須當即用 "illegal_parameter" alert 消息停止握手。
若是 Server 提供了 "early_data" 擴展,Client 必須驗證 Server 的 selected_identity 是否爲 0。若是返回任何其餘值,Client 必須使用 "illegal_parameter" alert 消息停止握手。
"pre_shared_key" 擴展必須是 ClientHello 中的最後一個擴展(這有利於下面的描述的實現)。Server 必須檢查它是最後一個擴展,不然用 "illegal_parameter" alert 消息停止握手。
從 Client 的角度來看,ticket 的時間指的是,收到 NewSessionTicket 消息開始到當前時刻的這段時間。Client 決不能使用時間大於 ticket 本身標明的 "ticket_lifetime" 這個時間的 ticket。每一個 PskIdentity 中的 "obfuscated_ticket_age" 字段都必須包含 ticket 時間的混淆版本,混淆方法是用 ticket 時間(毫秒爲單位)加上 "ticket_age_add" 字段,最後對 2^32 取模。除非這個 ticket 被重用了,不然這個混淆就能夠防止一些相關聯鏈接的被動觀察者。注意,NewSessionTicket 消息中的 "ticket_lifetime" 字段是秒爲單位,可是 "obfuscated_ticket_age" 是毫秒爲單位。由於 ticke lifetime 限制爲一週,32 位就足夠去表示任何合理的時間,即便是以毫秒爲單位也能夠表示。
PSK binder 的值造成了 2 種綁定關係,一種是 PSK 和當前握手的綁定,另一種是 PSK 產生之後(若是是經過 NewSessionTicket 消息)的握手和當前握手的綁定。每個在 binder 列表中的條目都會根據有一部分 ClientHello 的哈希副本計算 HMAC,最終 HMAC 會包含 PreSharedKeyExtension.identities 字段。也就是說,HMAC 包含全部的 ClientHello,可是不包含 binder list 。若是存在正確長度的 binders,消息的長度字段(包括總長度,擴展塊的長度和 "pre_shared_key" 擴展的長度)都被設置。
PskBinderEntry 的計算方法和 Finished 消息同樣。可是 BaseKey 是派生的 binder_key,派生方式是經過提供的相應的 PSK 的密鑰派生出來的。
若是握手包括 HelloRetryRequest 消息,則初始的 ClientHello 和 HelloRetryRequest 隨着新的 ClientHello 一塊兒被包含在副本中。例如,若是 Client 發送 ClientHello,則其 binder 將經過如下方式計算:
Transcript-Hash(Truncate(ClientHello1))
複製代碼
Truncate() 函數的做用是把 ClientHello 中的 binders list 移除。
若是 Server 響應了 HelloRetryRequest,那麼 Client 會發送 ClientHello2,它的 binder 會經過如下方式計算:
Transcript-Hash(ClientHello1,
HelloRetryRequest,
Truncate(ClientHello2))
複製代碼
完整的 ClientHello1/ClientHello2 都會包含在其餘的握手哈希計算中。請注意,在第一次發送中,Truncate(ClientHello1)
是直接計算哈希的,可是在第二次發送中,ClientHello1 計算哈希,而且還會再注入一條 "message_hash" 消息。
Client 被容許流式的發送 0-RTT 數據,直到它收到 Server 的 Finished 消息。Client 收到 Finished 消息之後,須要在握手的末尾,發送 EndOfEarlyData 消息。爲了防止死鎖,當 Server 接收 "early_data" 消息的時候,Server 必須當即處理 Client 的 ClientHello 消息,而後當即迴應 ServerHello,而不是等待收到 Client 的 EndOfEarlyData 消息之後再發送 ServerHello。
Server 接下來的 2 條消息,EncryptedExtensions 和 CertificateRequest 消息,包含來自 Server 的消息,這個 Server 肯定了握手的其他部分。這些消息是加密的,經過從 server_handshake_traffic_secret 中派生的密鑰加密的。
在全部的握手中,Server 必須在 ServerHello 消息以後當即發送 EncryptedExtensions 消息。這是在從 server_handshake_traffic_secret 派生的密鑰下加密的第一條消息。
EncryptedExtensions 消息包含應該被保護的擴展。即,任何不須要創建加密上下文但不與各個證書相互關聯的擴展。Client 必須檢查 EncryptedExtensions 消息中是否存在任何禁止的擴展,若是有發現禁止的擴展,必須當即用 "illegal_parameter" alert 消息停止握手。
Structure of this message:
struct {
Extension extensions<0..2^16-1>;
} EncryptedExtensions;
複製代碼
使用證書進行身份驗證的 Server 能夠選擇性的向 Client 請求證書,這條請求消息(若是發送了)要跟在 EncryptedExtensions 消息後面。
消息的結構體:
struct {
opaque certificate_request_context<0..2^8-1>;
Extension extensions<2..2^16-1>;
} CertificateRequest;
複製代碼
certificate_request_context:
一個不透明的字符串,這個字符串用來標識證書請求,並在 Client 的 Certificate 消息中回顯。certificate_request_context 必須在本次鏈接中必須是惟一的(從而防止 Client 的 CertificateVerify 重放攻擊)。這個字段通常狀況下都是 0 長度,除非用於 [4.6.2] 中描述的握手後身份驗證交換。當請求握手後身份驗證之後,Server 應該發送不可預測的上下文給 Client (例如,用隨機數生成),這樣是爲了防止攻擊者破解。攻擊者能夠預先計算有效的 CertificateVerify 消息,從而獲取臨時的 Client 私鑰的權限。
extensions:
一組描述正在請求的證書須要的參數擴展集。"signature_algorithms" 擴展必須是特定的,若是其餘的擴展被這個消息所定義,那麼其餘擴展也可能可選的被包含進來。Client 必須忽略不能識別的擴展。
在 TLS 1.3 以前的版本中,CertificateRequest 消息攜帶了簽名算法列表和 Server 可接受的證書受權列表。在 TLS 1.3 中,簽名算法列表能夠經過 "signature_algorithms" 和可選的 "signature_algorithms_cert" 擴展來表示。然後者證書受權列表能夠經過發送 "certificate_authorities" 擴展來表示。
經過 PSK 進行驗證的 Server 不能在主握手中發送 CertificateRequest 消息,不過它們可能能夠在握手後身份驗證中發送 CertificateRequest 消息,前提是 Client 已經發送了 "post_handshake_auth" 擴展名。
Reference:
GitHub Repo:Halfrost-Field
Follow: halfrost · GitHub
Source: halfrost.com/TLS_1.3_Han…