TLS 1.3 Handshake Protocol (上)

握手協議用於協商鏈接的安全參數。握手消息被提供給 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

一. Key Exchange Messages

密鑰交換消息用於確保 Client 和 Server 的安全性和創建用於保護握手和數據的通訊密鑰的安全性。算法

1. Cryptographic Negotiation

在 TLS 協議中,密鑰協商的過程當中,Client 在 ClientHello 中能夠提供如下 4 種 options。數據庫

  • Client 支持的加密套件列表。密碼套件裏面中能體現出 Client 支持的 AEAD 算法或者 HKDF 哈希對。
  • 「supported_groups」 的擴展 和 "key_share" 擴展。「supported_groups」 這個擴展代表了 Client 支持的 (EC)DHE groups,"key_share" 擴展代表了 Client 是否包含了一些或者所有的(EC)DHE共享。
  • "signature_algorithms" 簽名算法和 "signature_algorithms_cert" 簽名證書算法的擴展。"signature_algorithms" 這個擴展展現了 Client 能夠支持了簽名算法有哪些。"signature_algorithms_cert" 這個擴展展現了具體證書的簽名算法。
  • "pre_shared_key" 預共享密鑰和 "psk_key_exchange_modes" 擴展。預共享密鑰擴展包含了 Client 能夠識別的對稱密鑰標識。"psk_key_exchange_modes" 擴展代表了可能能夠和 psk 一塊兒使用的密鑰交換模式。

若是 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 消息,它包含如下幾個參數:網絡

  • 若是正在使用 PSK, Server 將發送 "pre_shared_key" 擴展,裏面包含了選擇的密鑰。
  • 若是沒有使用 PSK,選擇的 (EC)DHE, Server 將會提供一個 "key_share" 擴展。一般,若是 PSK 沒有使用,就會使用 (EC)DHE 和基於證書的認證。
  • 當經過證書進行認證的時候, Server 會發送 Certificate 和 CertificateVerify 消息。在 TLS 1.3 的官方規定中,PSK 和 證書一般被用到,可是不是一塊兒使用,將來的文檔可能會定義如何同時使用它們。

若是 Server 不能協商出可支持的參數集合,即在 Client 和 Server 各自支持的參數集合中沒有重疊,那麼 Server 必須發送 "handshake_failure" 或者 "insufficient_security" 消息來停止握手。session

2. Client Hello

當一個 Client 第一次鏈接一個 Server 時,它須要在發送第一條 TLS 消息的時候,發送 ClientHello 消息。當 Server 發送 HelloRetryRequest 消息的時候,Client 收到了之後也須要回應一條 ClientHello 消息。在這種狀況下,Client 必須發送相同的無修改的 ClientHello 消息,除非如下幾種狀況:

  • 若是 HelloRetryRequest 消息中包含了 "key_share" 擴展,則將共享列表用包含了單個來自代表的組中的 KeyShareEntry 代替。
  • 若是存在 「early_data」 擴展則將其移除。 「early_data」 不容許出如今 HelloRetryRequest 以後。
  • 若是 HelloRetryRequest 中包含了 cookie 擴展,則須要包含一個。
  • 若是從新計算了 "obfuscated_ticket_age" 和綁定值,同時(可選地)刪除了任何不兼容 Server 展現的密碼族的 PSK,則更新 "pre_shared_key" 擴展。
  • 選擇性地增長,刪除或更改 」padding」 擴展RFC 7685的長度。
  • 可能被容許的一些其餘的修改。例如將來指定的一些擴展定義和 HelloRetryRequest 。

因爲 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。

3. Server Hello

若是 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 嚴禁從新協商

4. Hello Retry Request

若是在 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 擴展名是:

  • supported_versions
  • cookie
  • key_share

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 消息停止握手。

二. Extensions

許多 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;
複製代碼

這裏:

  • "extension_type" 標識特定的擴展狀態。
  • "extension_data" 包含特定於該特定擴展類型的信息。

全部的擴展類型由 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 消息的哈希輸入中的擴展字段是不用擔憂的,可是在握手階段,擴展試圖改變了發送消息的含義,這種狀況須要特別當心。設計者和實現者應該意識到,在握手完成身份認證以前,攻擊者均可以修改消息,插入、刪除或者替換擴展。

1. Supported Versions

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 消息停止握手。

2. Cookie

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 必須忽略這些記錄。

3. Signature Algorithms

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 密碼套件一塊兒使用。

4. Certificate Authorities

"certificate_authorities" 擴展用於表示終端支持的 CA, 而且接收的端點應該使用它來指導證書的選擇。

"certificate_authorities" 擴展的主體包含了一個 CertificateAuthoritiesExtension 結構:

opaque DistinguishedName<1..2^16-1>;

      struct {
          DistinguishedName authorities<3..2^16-1>;
      } CertificateAuthoritiesExtension;
複製代碼
  • authorities:
    可接受證書頒發機構的一個可分辨名字 X501 的列表 ,這個列表是以 DER X690 編碼格式表示的。這些可分辨的名稱爲,信任錨或從屬的 CA 指定所需的可分辨的名稱。所以,可使用此消息描述已知的信任錨以及所需的受權空間。

Client 可能會在 ClientHello 消息中發送 "certificate_authorities" 擴展,Server 可能會在 CertificateRequest 消息中發送 "certificate_authorities" 擴展。

"trusted_ca_keys" 擴展和 "certificate_authorities" 擴展有相同的目的,可是更加複雜。"trusted_ca_keys" 擴展不能在 TLS 1.3 中使用,可是它在 TLS 1.3 以前的版本中,可能出如今 Client 的 ClientHello 消息中。

5. OID Filters

"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;
複製代碼
  • filters:
    一個有容許值的證書擴展 OID RFC 5280 列表,以 DER 編碼 X690 格式表示。一些證書擴展 OID 容許多個值(例如,Extended Key Usage)。若是 Server 包含非空的 filters 列表,則響應中包含的 Client 證書必須包含 Client 識別的全部指定的擴展 OID。對於 Client 識別的每一個擴展 OID,全部指定的值必須存在於 Client 證書中(可是證書也能夠具備其餘值)。然而,Client 必須忽略並跳過任何沒法識別的證書擴展 OID。若是 Client 忽略了一些所需的證書擴展 OID 並提供了不知足請求的證書。Server 能夠自行決定是繼續與沒有身份認證的 Client 保持鏈接,仍是用 "unsupported_certificate" alert 消息停止握手。任何給定的 OID 都不能在 filters 列表中出現屢次。

PKIX RFC 定義了各類證書擴展 OID 及其對應的值類型。根據類型,匹配的證書擴展值不必定是按位相等的。指望 TLS 實現將依靠它們的 PKI 庫,使用證書擴展 OID 來作證書的選擇。

本文檔定義了 RFC5280 中定義的兩個標準證書擴展的匹配規則:

  • 當請求中聲明的全部 Key Usage 位也一樣在 Key Usage 證書擴展聲明瞭,那麼證書中的 Key Usage 擴展匹配了請求。

  • 當請求中全部的密鑰 OIDs 在 Extended Key Usage 證書擴展中也存在,那麼證書中的 Extended Key Usage 匹配了請求。特殊的 anyExtendedKeyUsage OID 必定不能在請求中使用。

單獨的規範能夠爲其餘證書擴展的規則定義匹配規則。

6. Post-Handshake Client Authentication

"post_handshake_auth" 擴展用於代表 Client 願意握手後再認證。Server 不能向沒有提供此擴展的 Client 發送握手後再認證的 CertificateRequest 消息。Server 不能發送此擴展。

struct {} PostHandshakeAuth;
複製代碼

"post_handshake_auth" 擴展名中的 "extension_data" 字段爲零長度。

7. Supported Groups

當 Client 發送 "supported_groups" 擴展的時候,這個擴展代表了 Client 支持的用於密鑰交換的命名組。按照優先級從高到低。

請注意:在 TLS 1.3 以前的版本中,這個擴展原來叫 "elliptic_curves",而且只包含橢圓曲線組。具體請參考 RFC8422RFC7919。這個擴展一樣能夠用來協商 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 支持的全部組。

8. Key Share

"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_shares:
    按照 Client 偏好降序順序提供的 KeyShareEntry 值列表。

若是 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;
複製代碼
  • selected_group:
    Server 打算協商的相互支持而且正在請求重試 ClientHello / KeyShare 的 group。

在 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;
複製代碼
  • server_share:
    與 Client 共享的位於同一組的單個 KeyShareEntry 值。

若是使用 (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 消息停止握手。

(1) Diffie-Hellman Parameters

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。

(2) ECDHE Parameters

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

9. Pre-Shared Key Exchange Modes

爲了使用 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" 表示。

10. Early Data Indication

當使用 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 關聯值同樣:

  • TLS 版本號
  • 選擇的密碼套件
  • 選擇的 ALPN 協議,若是選擇了的話

這些要求是使用相關 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。

11. Pre-Shared Key Extension

"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 消息停止握手。

(1) Ticket Age

從 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 位就足夠去表示任何合理的時間,即便是以毫秒爲單位也能夠表示。

(2) PSK Binder

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" 消息。

(3) Processing Order

Client 被容許流式的發送 0-RTT 數據,直到它收到 Server 的 Finished 消息。Client 收到 Finished 消息之後,須要在握手的末尾,發送 EndOfEarlyData 消息。爲了防止死鎖,當 Server 接收 "early_data" 消息的時候,Server 必須當即處理 Client 的 ClientHello 消息,而後當即迴應 ServerHello,而不是等待收到 Client 的 EndOfEarlyData 消息之後再發送 ServerHello。

三. Server Parameters

Server 接下來的 2 條消息,EncryptedExtensions 和 CertificateRequest 消息,包含來自 Server 的消息,這個 Server 肯定了握手的其他部分。這些消息是加密的,經過從 server_handshake_traffic_secret 中派生的密鑰加密的。

1. Encrypted Extensions

在全部的握手中,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;
複製代碼
  • extensions:
    擴展列表。

2. Certificate Request

使用證書進行身份驗證的 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:

RFC 8446

GitHub Repo:Halfrost-Field

Follow: halfrost · GitHub

Source: halfrost.com/TLS_1.3_Han…

相關文章
相關標籤/搜索