RFC 9846

The Transport Layer Security (TLS) Protocol Version 1.3

๐Ÿ“… published July 2026, from ietf

Title Block ๐ŸŽ€

(part 1 of 34)

The Transport Layer Security (TLS) Protocol Version 1.3

  • RFC number: 9846 ๐Ÿฆ‹
  • Organization: Internet Engineering Task Force (IETF)
  • Author: E. Rescorla
  • Stream: Independent
  • Category: Standards Track
  • Date: July 2026
  • Obsoletes: RFCs 5077, 5246, 6961, 7627, 8422, and 8446
  • Updates: RFCs 5705 and 6066
  • ISSN: 2070-1721

Abstract โœจ

Okay so this doc is the spec for TLS 1.3 ๐Ÿ”. TLS is what lets client and server apps talk over the Internet in a way that's built to stop eavesdropping, tampering, and message forgery. It's giving security ๐Ÿ’…

This doc obsoletes RFC 8446, which specified TLS 1.3. It also obsoletes RFC 5246 (TLS 1.2) and RFCs 5077, 6961, 7627, and 8422, which all pertain to TLS 1.2 or earlier. It updates RFCs 5705 and 6066. On top of that, it adds new requirements for TLS 1.2 implementations ๐Ÿงš

Status of This Memo ๐Ÿ“œ

This is an Internet Standards Track document ๐ŸŒˆ

This doc is a product of the Internet Engineering Task Force (IETF). It represents the consensus of the IETF community. It went through public review and got approved for publication by the Internet Engineering Steering Group (IESG). If you want more info on Internet Standards, check out Section 2 of RFC 7841.

Info about the current status of this doc, any errata, and how to send feedback is at https://www.rfc-editor.org/info/rfc9846 โœจ

Copyright (c) 2026 IETF Trust and the persons identified as the document authors. All rights reserved.

This doc is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) as they stand on the date this doc was published. Please read these carefully, because they spell out your rights and restrictions for this doc. Code Components pulled out of this doc must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions, and they come without warranty, as described in the Revised BSD License.

This doc may contain material from IETF Documents or IETF Contributions published or made publicly available before November 10, 2008. The person(s) controlling the copyright in some of that material may not have granted the IETF Trust the right to allow modifications outside the IETF Standards Process. Without an adequate license from those copyright holders, this doc may not be modified outside the IETF Standards Process, and derivative works of it may not be created outside the IETF Standards Process, except to format it for publication as an RFC or to translate it into languages other than English ๐Ÿซถ

Table of Contents ๐Ÿ“œ

   1.  Introduction
     1.1.  Conventions and Terminology
     1.2.  Relationship to RFC 8446
     1.3.  Major Differences from TLS 1.2
     1.4.  Updates Affecting TLS 1.2
   2.  Protocol Overview
     2.1.  Incorrect DHE Share
     2.2.  Resumption and Pre-Shared Key (PSK)
     2.3.  0-RTT Data
   3.  Presentation Language
     3.1.  Basic Block Size
     3.2.  Miscellaneous
     3.3.  Numbers
     3.4.  Vectors
     3.5.  Enumerateds
     3.6.  Constructed Types
     3.7.  Constants
     3.8.  Variants
   4.  Handshake Protocol
     4.1.  The Transcript Hash
     4.2.  Key Exchange Messages
       4.2.1.  Cryptographic Negotiation
       4.2.2.  Client Hello
       4.2.3.  Server Hello
       4.2.4.  Hello Retry Request
     4.3.  Extensions
       4.3.1.  Supported Versions
       4.3.2.  Cookie
       4.3.3.  Signature Algorithms
       4.3.4.  Certificate Authorities
       4.3.5.  OID Filters
       4.3.6.  Post-Handshake Certificate-Based Client Authentication
       4.3.7.  Supported Groups
       4.3.8.  Key Share
       4.3.9.  Pre-Shared Key Exchange Modes
       4.3.10. Early Data Indication
       4.3.11. Pre-Shared Key Extension
     4.4.  Server Parameters
       4.4.1.  Encrypted Extensions
       4.4.2.  Certificate Request
     4.5.  Authentication Messages
       4.5.1.  Certificate
       4.5.2.  Certificate Verify
       4.5.3.  Finished
     4.6.  End of Early Data
     4.7.  Post-Handshake Messages
       4.7.1.  New Session Ticket Message
       4.7.2.  Post-Handshake Authentication
       4.7.3.  Key and Initialization Vector Update
   5.  Record Protocol
     5.1.  Record Layer
     5.2.  Record Payload Protection
     5.3.  Per-Record Nonce
     5.4.  Record Padding
     5.5.  Limits on Key Usage
   6.  Alert Protocol
     6.1.  Closure Alerts
     6.2.  Error Alerts
   7.  Cryptographic Computations
     7.1.  Key Schedule
     7.2.  Updating Traffic Secrets
     7.3.  Traffic Key Calculation
     7.4.  Asymmetric Shared Secret Calculation
       7.4.1.  Finite Field Diffie-Hellman
       7.4.2.  Elliptic Curve Diffie-Hellman
     7.5.  Exporters
   8.  0-RTT and Anti-Replay
     8.1.  Single-Use Tickets
     8.2.  Client Hello Recording
     8.3.  Freshness Checks
   9.  Compliance Requirements
     9.1.  Mandatory-to-Implement Cipher Suites
     9.2.  Mandatory-to-Implement Extensions
     9.3.  Protocol Invariants
   10. Security Considerations
   11. IANA Considerations
     11.1.  Changes for this RFC
   12. References
     12.1.  Normative References
     12.2.  Informative References
   Appendix A.  State Machine
     A.1.  Client
     A.2.  Server
   Appendix B.  Protocol Data Structures and Constant Values
     B.1.  Record Layer
     B.2.  Alert Messages
     B.3.  Handshake Protocol
       B.3.1.  Key Exchange Messages
       B.3.2.  Server Parameters Messages
       B.3.3.  Authentication Messages
       B.3.4.  Ticket Establishment
       B.3.5.  Updating Keys
     B.4.  Cipher Suites
   Appendix C.  Implementation Notes
     C.1.  Random Number Generation and Seeding
     C.2.  Certificates and Authentication
     C.3.  Implementation Pitfalls
     C.4.  Client and Server Tracking Prevention
     C.5.  Unauthenticated Operation
   Appendix D.  Updates to TLS 1.2
   Appendix E.  Backward Compatibility
     E.1.  Negotiating with an Older Server
     E.2.  Negotiating with an Older Client
     E.3.  0-RTT Backward Compatibility
     E.4.  Middlebox Compatibility Mode
     E.5.  Security Restrictions Related to Backward Compatibility
   Appendix F.  Overview of Security Properties
     F.1.  Handshake
       F.1.1.  Key Derivation and HKDF
       F.1.2.  Certificate-Based Client Authentication
       F.1.3.  0-RTT
       F.1.4.  Exporter Independence
       F.1.5.  Post-Compromise Security
       F.1.6.  External References
     F.2.  Record Layer
       F.2.1.  External References
     F.3.  Traffic Analysis
     F.4.  Side Channel Attacks
     F.5.  Replay Attacks on 0-RTT
       F.5.1.  Replay and Exporters
     F.6.  PSK Identity Exposure
     F.7.  Sharing PSKs Across Protocol Versions
     F.8.  External PSKs and Rerouting
     F.9.  Misbinding When Using Self-Signed Certificates or Raw
            Public Keys
     F.10. Attacks on Static RSA
   Contributors
   Author's Address

1. Introduction ๐ŸŒธ

Okay so the primary goal of TLS is to give two communicating peers a secure channel ๐Ÿ”. The only thing it needs from the transport underneath is a reliable, in-order data stream. Specifically, the secure channel should give you these vibes:

  • Authentication: The server side of the channel is always authenticated, and the client side is optionally authenticated. Authentication can happen with asymmetric cryptography (e.g., RSA [RSA], the Elliptic Curve Digital Signature Algorithm (ECDSA) [DSS], or the Edwards-Curve Digital Signature Algorithm (EdDSA) [RFC8032]) or with a symmetric pre-shared key (PSK).
  • Confidentiality: Data sent over the channel after it's established is only visible to the endpoints. TLS doesn't hide how long the data it transmits is, though endpoints can pad TLS records to hide lengths and level up their protection against traffic analysis techniques ๐Ÿ•ต๏ธโ€โ™€๏ธ
  • Integrity: Data sent over the channel after it's established can't be modified by attackers without getting caught ๐Ÿšซ

These properties should hold even when an attacker has complete control of the network, as described in [RFC3552]. Check out Appendix F for a more complete rundown of the security properties, it's giving detail ๐Ÿง

TLS is built out of two main pieces ๐Ÿงฉ:

  • A handshake protocol (Section 4) that authenticates the communicating parties, negotiates cryptographic algorithms and parameters, and sets up shared keying material. The handshake is designed to resist tampering. An active attacker should not be able to force the peers to negotiate different parameters than they would if the connection weren't under attack.
  • A record protocol (Section 5) that uses the parameters from the handshake to protect traffic between the peers. It chops traffic up into a series of records, and each record is independently protected using the traffic keys โœจ

TLS doesn't care which application protocol sits on top of it, so higher-level protocols can layer on transparently ๐Ÿฆ‹ The TLS standard does not say how protocols add security with TLS. How to start TLS handshaking and how to interpret the authentication certificates you get back are left to the judgment of the designers and implementors of the protocols running on top. Application protocols using TLS MUST specify how TLS works with their application protocol, including how and when handshaking occurs, and how to do identity verification. [RFC9525] has useful guidance on integrating TLS with application protocols ๐Ÿ“š

This doc defines TLS version 1.3. TLS 1.3 isn't directly compatible with previous versions, but every version of TLS has a versioning mechanism that lets clients and servers agree on a common version if both peers support one ๐Ÿค

This doc supersedes and obsoletes older versions of TLS, including version 1.2 [RFC5246], the extended master secret extension [RFC7627], and the elliptic curve definitions for TLS 1.2 [RFC8422]. It also obsoletes the TLS ticket mechanism defined in [RFC5077] and replaces it with the mechanism in Section 2.2 ๐ŸŽ€ Because TLS 1.3 changes the way keys are derived, it updates [RFC5705] as described in Section 7.5. Exporters no longer tell apart having no context and having an empty context. TLS 1.3 also changes how Online Certificate Status Protocol (OCSP) messages are carried, so it updates [RFC6066] and obsoletes [RFC6961] as described in Section 4.5.1.1 ๐Ÿ’œ

Part 2 is next, bestie ๐Ÿซถ

1.1. Conventions and Terminology ๐ŸŽ€

Okay bestie, first things first: the key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" only mean their BCP 14 [RFC2119] [RFC8174] thing when they show up in ALL CAPS like that. Lowercase "must" is just regular English, no cap ๐Ÿ’…

Here are the terms the doc uses:

  • client: The endpoint that starts the TLS connection. ๐Ÿš€
  • connection: A transport-layer connection between two endpoints. ๐Ÿ”—
  • endpoint: Either the client or the server of the connection. ๐Ÿ’œ
  • handshake: An opening negotiation between client and server that sets up the parameters for everything they do together inside TLS. ๐Ÿค
  • peer: An endpoint. When you're talking about one particular endpoint, "peer" means the other one, the one that isn't the main subject. ๐Ÿ‘ฏ
  • receiver: An endpoint that is receiving records. ๐Ÿ“ฅ
  • sender: An endpoint that is sending records. ๐Ÿ“ค
  • server: The endpoint that did not start the TLS connection. ๐Ÿ–ฅ๏ธ

1.2. Relationship to RFC 8446

So TLS 1.3 was first specified in [RFC8446]. This document is a minor update to TLS 1.3 that keeps the same version number and stays backward compatible ๐Ÿฆ‹ It tightens some requirements, rewrites the bits that people found confusing, and adds a bunch of editorial polish. It also drops the word "master" for secrets and uses "main" instead, or just shorter names when no term is needed at all. Here's what changed, specifically:

  • Forbid the reuse of KeyShare values between connections. ๐Ÿšซ
  • Forbid negotiating TLS 1.0 and 1.1, because they're now deprecated by [RFC8996]. ๐Ÿ‘‹
  • Remove ambiguity around which hash is used with PreSharedKeys and HelloRetryRequest. ๐Ÿงน
  • Require that clients ignore NewSessionTicket if they don't support resumption. ๐ŸŽŸ๏ธ
  • Upgrade the requirement to initiate key update before exceeding key usage limits to MUST, and clarify the scope of key usage limits. ๐Ÿ”‘
  • Limit the number of permitted KeyUpdate messages. ๐Ÿ”ข
  • Restore text defining the level of close_notify to warning. โš ๏ธ
  • Clarify behavior around user_canceled: close_notify must be sent, and user_canceled should be ignored. ๐Ÿ’ฌ
  • Add a general_error generic alert. ๐Ÿšจ
  • Correct the lower bound on CertificateRequest.extensions to be 0 bytes. This was a syntax error, since it's possible to send no extensions, which results in length 0. ๐Ÿ™ˆ
  • Correct the lower bound on ClientHello.extensions. This was a calculation error. ๐Ÿงฎ
  • Correct the upper bound on NewSessionTicket.extensions. This was also a calculation error. ๐Ÿงฎ
  • Use more generic language for asymmetric key exchange, which used to be exclusively (EC)DHE, to reflect the use of KEM-based key exchange. ๐ŸŒˆ
  • Clarify the interaction of the transcript hash with future specifications. ๐Ÿ“œ
  • Remove text requiring RSA PSS, matching [RFC9963]. โœ‚๏ธ

Plus, there have been some improvements to the security considerations, especially around privacy. ๐Ÿ”โœจ

1.3. Major Differences from TLS 1.2

Here's the tea on the major functional differences between TLS 1.2 and TLS 1.3. It's not an exhaustive list, and there are lots of smaller differences too ๐Ÿ’…

  • The list of supported symmetric encryption algorithms has been pruned of everything considered legacy. What's left is all Authenticated Encryption with Associated Data (AEAD) algorithms. The cipher suite concept was also changed so that authentication and key exchange are separate from the record protection algorithm (including secret key length), plus a hash that's used with both the key derivation function and the handshake message authentication code (MAC). ๐Ÿง
  • A zero round-trip time (0-RTT) mode was added, which saves a round trip at connection setup for some application data, at the cost of certain security properties. ๐ŸŽ๏ธ
  • Static RSA and Diffie-Hellman cipher suites are gone. Now every public-key based key exchange mechanism provides forward secrecy. ๐Ÿ™Œ
  • All handshake messages after the ServerHello are now encrypted. The brand-new EncryptedExtensions message lets extensions that used to be sent in the clear in the ServerHello get confidentiality protection too. ๐Ÿ”’
  • The key derivation function got redesigned. The new design is easier for cryptographers to analyze thanks to its improved key separation properties. HMAC-based Extract-and-Expand Key Derivation Function (HKDF) is used as the underlying primitive. ๐Ÿง 
  • The handshake state machine got a big restructure to be more consistent and to drop superfluous messages such as ChangeCipherSpec (except when needed for middlebox compatibility). ๐Ÿงน
  • Elliptic curve algorithms are now in the base specification, and new signature algorithms, such as EdDSA, are included. TLS 1.3 also removed point format negotiation in favor of a single point format for each curve. ๐Ÿ“
  • Other cryptographic upgrades include changing the RSA padding to the RSA Probabilistic Signature Scheme (RSASSA-PSS), and removing compression, the Digital Signature Algorithm (DSA), and custom Ephemeral Diffie-Hellman (DHE) groups. ๐Ÿ”ฅ
  • The TLS 1.2 version negotiation mechanism has been deprecated in favor of a version list in an extension. This also improves compatibility with existing servers that implemented version negotiation incorrectly. ๐Ÿคญ
  • Session resumption, with or without server-side state, plus the PSK-based cipher suites from earlier TLS versions, have all been replaced by one single new PSK exchange. ๐Ÿช„
  • References have been updated to point to the newer versions of RFCs where appropriate (for example, RFC 5280 instead of RFC 3280). ๐Ÿ“š

1.4. Updates Affecting TLS 1.2

This document defines several changes that optionally affect implementations of TLS 1.2, including ones that don't also support TLS 1.3 ๐Ÿซถ

  • A version downgrade protection mechanism is described in Section 4.2.3. ๐Ÿ›ก๏ธ
  • RSASSA-PSS signature schemes are defined in Section 4.3.3. โœ๏ธ
  • The supported_versions ClientHello extension can be used to negotiate the TLS version to use, which takes priority over the legacy_version field of the ClientHello. ๐ŸŽฏ
  • The signature_algorithms_cert extension lets a client say which signature algorithms it can validate in X.509 certificates. ๐Ÿชช
  • The term "master" as applied to secrets has been removed, and the extended_master_secret extension [RFC7627] has been renamed to extended_main_secret. ๐ŸŒธ

Also, this document clarifies some compliance requirements for earlier versions of TLS, so check out Section 9.3 for those ๐Ÿง

2. Protocol Overview โœจ

The cryptographic parameters used by the secure channel are produced by the TLS handshake protocol ๐Ÿ” This sub-protocol is what the client and server use the first time they talk to each other. The handshake lets peers negotiate a protocol version, select cryptographic algorithms, authenticate each other (with client authentication being optional), and establish shared secret keying material. Once the handshake is done, the peers use those keys to protect the application-layer traffic. ๐Ÿ’–

A failure of the handshake or any other protocol error triggers the termination of the connection, optionally preceded by an alert message (Section 6). ๐Ÿšซ

TLS supports three basic key exchange modes ๐Ÿ”‘

  • asymmetric key exchange
  • PSK
  • combined PSK and asymmetric key exchange

In this document, asymmetric key exchange includes Diffie-Hellman over either finite fields or elliptic curves. Other extensions define the use of key-encapsulation mechanisms (KEMs). ๐ŸŒˆ

Figure 1 below shows the basic full TLS handshake ๐Ÿ‘€

       Client                                         Server

Key  ^ ClientHello
Exch | + key_share*
     | + signature_algorithms*
     | + psk_key_exchange_modes*
     v + pre_shared_key*       -------->
                                                  ServerHello  ^ Key
                                                 + key_share*  | Exch
                                            + pre_shared_key*  v
                                        {EncryptedExtensions}  ^  Server
                                        {CertificateRequest*}  v  Params
                                               {Certificate*}  ^
                                         {CertificateVerify*}  | Auth
                                                   {Finished}  v
                               <--------  [Application Data*]
     ^ {Certificate*}

The handshake has three phases, and the diagram above labels them. Let's go, bestie ๐Ÿฆ‹

Auth | {CertificateVerify*}
     v {Finished}              -------->
       [Application Data]      <------->   [Application Data]

              +  Indicates noteworthy extensions sent in the
                 previously noted message.

              *  Indicates optional or situation-dependent
                 messages/extensions that are not always sent.

              {} Indicates messages protected using keys
                 derived from a [sender]_handshake_traffic_secret.

              [] Indicates messages protected using keys
                 derived from [sender]_application_traffic_secret_N.

Figure 1: Message Flow for Full TLS Handshake โœจ

The handshake can be thought of as having three phases (indicated in the diagram above) ๐ŸŽ€

  • Key Exchange: Establish shared keying material and select the cryptographic parameters. Everything after this phase is encrypted ๐Ÿ”
  • Server Parameters: Establish other handshake parameters, like whether the client is authenticated, application-layer protocol support, etc. ๐Ÿ’…
  • Authentication: Authenticate the server (and, optionally, the client) and provide key confirmation and handshake integrity ๐Ÿซถ

Okay so in the Key Exchange phase, the client sends the ClientHello (Section 4.2.2) message ๐Ÿ“จ It carries a random nonce (ClientHello.random), its offered protocol versions, a list of symmetric cipher/hash pairs, either a list of asymmetric key exchange shares (in the "key_share" (Section 4.3.8) extension), a list of pre-shared key labels (in the "pre_shared_key" (Section 4.3.11) extension), or both, and potentially additional extensions. Additional fields and/or messages may also be present for middlebox compatibility ๐Ÿงš

The server processes the ClientHello and figures out the right cryptographic parameters for the connection. Then it responds with its own ServerHello (Section 4.2.3), which says which connection parameters got negotiated ๐Ÿ“ The ClientHello and ServerHello together determine the shared keys. If asymmetric key establishment is in use, the ServerHello has a "key_share" extension with the server's ephemeral asymmetric key exchange share, and the server's share MUST be in the same group as one of the client's shares ๐Ÿ‘‘ If PSK key establishment is in use, the ServerHello has a "pre_shared_key" extension showing which of the client's offered PSKs got picked. Note that implementations can use asymmetric key exchange and PSK together, in which case both extensions get supplied ๐Ÿ’œ

Then the server sends two messages to set up the Server Parameters ๐ŸŒธ

  • EncryptedExtensions: Responses to ClientHello extensions that aren't required to determine the cryptographic parameters, other than the ones specific to individual certificates. [Section 4.4.1]
  • CertificateRequest: If certificate-based client authentication is desired, the desired parameters for that certificate. This message is omitted if client authentication is not desired. [Section 4.4.2]

Finally, the client and server swap Authentication messages. TLS uses the same set of messages every single time certificate-based authentication is needed. (PSK-based authentication happens as a side effect of key exchange, iykyk ๐Ÿ™ˆ) Specifically:

  • Certificate: The certificate of the endpoint and any per-certificate extensions. The server omits this message if it isn't authenticating with a certificate, and the client omits it if the server didn't send CertificateRequest (which means the client shouldn't authenticate with a certificate). Note that if raw public keys [RFC7250] or the cached information extension [RFC7924] are in use, this message won't contain a certificate, but some other value corresponding to the server's long-term key instead. [Section 4.5.1]
  • CertificateVerify: A signature over the entire handshake using the private key that matches the public key in the Certificate message. This message is omitted if the endpoint isn't authenticating via a certificate. [Section 4.5.2]
  • Finished: A MAC (Message Authentication Code) over the entire handshake. This message provides key confirmation for the shared secrets established in the handshake, binds the endpoint's identity to the exchanged keys, and in PSK mode also authenticates the handshake. [Section 4.5.3] ๐Ÿ”’

Once the server's messages come in, the client responds with its Authentication messages, namely Certificate and CertificateVerify (if requested), and Finished ๐Ÿซง

At this point the handshake is complete, and the client and server derive the keying material the record layer needs to exchange application-layer data protected through authenticated encryption. โœจ Application Data MUST NOT be sent prior to sending the Finished message, except as specified in Section 2.3. Note that while the server may send Application Data before receiving the client's Authentication messages, any data sent at that point is, of course, going to an unauthenticated peer ๐Ÿ‘€


2.1. Incorrect DHE Share ๐ŸŒˆ

If the client hasn't provided a sufficient "key_share" extension (for example, it only includes DHE or ECDHE groups the server finds unacceptable or doesn't support), the server fixes the mismatch with a HelloRetryRequest, and the client has to restart the handshake with an appropriate "key_share" extension, as shown in Figure 2. If no common cryptographic parameters can be negotiated, the server MUST abort the handshake with an appropriate alert ๐Ÿšจ

     Client                                               Server

     ClientHello
     + key_share             -------->
                                               HelloRetryRequest
                             <--------               + key_share
     ClientHello
     + key_share             -------->
                                                     ServerHello
                                                     + key_share
                                           {EncryptedExtensions}
                                           {CertificateRequest*}
                                                  {Certificate*}
                                            {CertificateVerify*}
                                                      {Finished}
                             <--------       [Application Data*]
     {Certificate*}
     {CertificateVerify*}
     {Finished}              -------->
     [Application Data]      <------->        [Application Data]

Figure 2: Message Flow for a Full Handshake with Mismatched Parameters ๐Ÿ’–

Note: The handshake transcript includes the initial ClientHello/HelloRetryRequest exchange. It is not reset with the new ClientHello. ๐Ÿ“

TLS also allows several optimized variants of the basic handshake, as described in the following sections โญ


2.2. Resumption and Pre-Shared Key (PSK) ๐Ÿฆ‹

Even though TLS PSKs can be established externally, PSKs can also be established in a previous connection and then used to set up a new connection ("session resumption" or "resuming" with a PSK) ๐Ÿ” Once a handshake has completed, the server can send the client a PSK identity that corresponds to a unique key derived from the initial handshake (see Section 4.7.1). The client can then use that PSK identity in future handshakes to negotiate the use of the associated PSK. If the server accepts the PSK, the security context of the new connection is cryptographically tied to the original connection, and the key derived from the initial handshake bootstraps the cryptographic state instead of a full handshake. In TLS 1.2 and below, this functionality was provided by "session IDs" and "session tickets" [RFC5077]. Both mechanisms are obsoleted in TLS 1.3 ๐Ÿชฆ

PSKs can be used with asymmetric key exchange to provide forward secrecy in combination with shared keys, or they can be used alone, at the cost of losing forward secrecy for the application data ๐Ÿซฃ

Figure 3 shows a pair of handshakes: the first one establishes a PSK, and the second one uses it ๐Ÿ‘ฏ

     Client                                               Server

Initial Handshake:
     ClientHello
     + key_share               -------->
                                                     ServerHello
                                                     + key_share
                                           {EncryptedExtensions}
                                           {CertificateRequest*}
                                                  {Certificate*}
                                            {CertificateVerify*}
                                                      {Finished}
                               <--------     [Application Data*]
     {Certificate*}
     {CertificateVerify*}
     {Finished}                -------->
                               <--------      [NewSessionTicket]
     [Application Data]        <------->      [Application Data]


Subsequent Handshake:
     ClientHello
     + key_share*
     + psk_key_exchange_modes
     + pre_shared_key          -------->
                                                     ServerHello
                                                + pre_shared_key
                                                    + key_share*
                                           {EncryptedExtensions}
                                                      {Finished}
                               <--------     [Application Data*]
     {Finished}                -------->
     [Application Data]        <------->      [Application Data]

Figure 3: Message Flow for Resumption and PSK ๐Ÿ“Š

As the server is authenticating via a PSK, it doesn't send a Certificate or a CertificateVerify message. When a client offers resumption via a PSK, it SHOULD also supply a "key_share" extension to the server, so the server can decline resumption and fall back to a full handshake if needed ๐Ÿ”„ The server responds with a "pre_shared_key" extension to negotiate the use of PSK key establishment. It can (as shown here) also respond with a "key_share" extension to do asymmetric key establishment, which gives you forward secrecy ๐Ÿ”

When PSKs are provisioned externally, the PSK identity and the KDF hash algorithm to be used with the PSK MUST also be provisioned ๐Ÿ“ฆ

Note: When using an externally provisioned pre-shared secret, a critical thing is using sufficient entropy during key generation, as discussed in [RFC4086]. Deriving a shared secret from a password or other low-entropy source is not secure. A low-entropy secret, or password, is subject to dictionary attacks based on the PSK binder. The specified PSK authentication is not a strong password-based authenticated key exchange, even when used with asymmetric key establishment. Specifically, it does not prevent an attacker that can observe the handshake from performing a brute-force attack on the password/pre-shared key. Please don't use passwords as PSKs, bestie ๐Ÿ’€

2.3. 0-RTT Data ๐Ÿš€

okay so bestie, when a client and server already share a PSK (either handed over out-of-band or from an earlier handshake), TLS 1.3 lets the client send data in its very first flight. This is called "early data" ๐Ÿงš The client uses that PSK to authenticate the server AND to encrypt the early data. iconic, right?

Look at Figure 4 below. The 0-RTT data is basically just tucked into the 1-RTT handshake's first flight ๐Ÿ’– Everything after that uses the same messages you'd see in a 1-RTT handshake with PSK resumption.

      Client                                               Server

      ClientHello
      + early_data
      + key_share*
      + psk_key_exchange_modes
      + pre_shared_key
      (Application Data*)     -------->
                                                      ServerHello
                                                 + pre_shared_key
                                                     + key_share*
                                            {EncryptedExtensions}
                                                    + early_data*
                                                       {Finished}
                              <--------       [Application Data*]
      (EndOfEarlyData*)
      {Finished}              -------->
      [Application Data]      <------->        [Application Data]

            +  Indicates noteworthy extensions sent in the
               previously noted message.

            *  Indicates optional or situation-dependent
               messages/extensions that are not always sent.

            () Indicates messages protected using keys
               derived from a client_early_traffic_secret.

            {} Indicates messages protected using keys
               derived from a [sender]_handshake_traffic_secret.

            [] Indicates messages protected using keys
               derived from [sender]_application_traffic_secret_N.

             Figure 4: Message Flow for a 0-RTT Handshake
IMPORTANT NOTE ๐Ÿšจ The security properties for 0-RTT data are weaker than those for other kinds of TLS data. Specifically:
  1. The protocol does not provide any forward secrecy guarantees for this data. The server's behavior determines what forward secrecy guarantees, if any, apply (see Section 8.1). This behavior is not communicated to the client as part of the protocol. Therefore, absent out-of-band knowledge of the server's behavior, the client should assume that this data is not forward secret. ๐Ÿ™ˆ
  2. There are no guarantees of non-replay between connections. Protection against replay for ordinary TLS 1.3 1-RTT data is provided via the server's Random value, but 0-RTT data does not depend on the ServerHello and therefore has weaker guarantees. This is especially relevant if the data is authenticated either with TLS client authentication or inside the application protocol. The same warnings apply to any use of the early_exporter_secret. ๐Ÿ’€

0-RTT data cannot be duplicated within a connection (i.e., the server will not process the same data twice for the same connection), and an attacker will not be able to make 0-RTT data appear to be 1-RTT data (because it is protected with different keys). Appendix F.5 contains a description of potential attacks, and Section 8 describes mechanisms which the server can use to limit the impact of replay. ๐Ÿฆ‹


3. Presentation Language ๐ŸŽ€

okay now we're getting into the nitty-gritty ๐Ÿค“ This document deals with how data gets formatted in an external representation. The presentation syntax we use is very basic and kinda casually defined, but it's all you need.

In the definitions below, optional components of this syntax are denoted by enclosing them in "[[ ]]" (double brackets). โœจ

3.1. Basic Block Size

The representation of all data items is explicitly specified. The basic data block size is one byte (i.e., 8 bits). Multiple-byte data items are concatenations of bytes, from left to right, from top to bottom. From the byte stream, a multi-byte item (a numeric in the following example) is formed (using C notation) by:

   value = (byte[0] << 8*(n-1)) | (byte[1] << 8*(n-2)) |
           ... | byte[n-1];

This byte ordering for multi-byte values is the commonplace network byte order or big-endian format. ๐ŸŒธ

3.2. Miscellaneous

Comments begin with "/" and end with "/". ๐Ÿ’œ

Single-byte entities containing uninterpreted data are of type opaque. ๐Ÿซถ

A type alias T' for an existing type T is defined by:

   T T';

3.3. Numbers

The basic numeric data type is an unsigned byte (uint8). All larger numeric data types are constructed from a fixed-length series of bytes concatenated as described in Section 3.1 and are also unsigned. The following numeric types are predefined.

   uint8 uint16[2];
   uint8 uint24[3];
   uint8 uint32[4];
   uint8 uint64[8];

All values, here and elsewhere in the specification, are transmitted in network byte (big-endian) order; the uint32 represented by the hex bytes 01 02 03 04 is equivalent to the decimal value 16909060. ๐Ÿ”ฅ

3.4. Vectors

A vector (single-dimensioned array) is a stream of homogeneous data elements. For presentation purposes, this specification refers to vectors as lists. The size of the vector may be specified at documentation time or left unspecified until runtime. In either case, the length declares the number of bytes, not the number of elements, in the vector. The syntax for specifying a new type, T', that is a fixed-length vector of type T is

   T T'[n];

Here, T' occupies n bytes in the data stream, where n is a multiple of the size of T. The length of the vector is not included in the encoded stream. ๐Ÿ‘‘

In the following example, Datum is defined to be three consecutive bytes that the protocol does not interpret, while Data is three consecutive Datum, consuming a total of nine bytes.

   opaque Datum[3];      /* three uninterpreted bytes */
   Datum Data[9];        /* three consecutive 3-byte vectors */

Variable-length vectors are defined by specifying a subrange of legal lengths, inclusively, using the notation <floor..ceiling>. When these are encoded, the actual length precedes the vector's contents in the byte stream. The length will be in the form of a number consuming as many bytes as required to hold the vector's specified maximum (ceiling) length. A variable-length vector with an actual length field of zero is referred to as an empty vector.

   T T'<floor..ceiling>;

In the following example, "mandatory" is a vector that must contain between 300 and 400 bytes of type opaque. It can never be empty. The actual length field consumes two bytes, a uint16, which is sufficient to represent the value 400 (see Section 3.3). Similarly, "longer" can represent up to 800 bytes of data, or 400 uint16 elements, and it may be empty. Its encoding will include a two-byte actual length field prepended to the vector. The length of an encoded vector must be an exact multiple of the length of a single element (e.g., a 17-byte vector of uint16 would be illegal). ๐Ÿงš

   opaque mandatory<300..400>;
         /* length field is two bytes, cannot be empty */
   uint16 longer<0..800>;
         /* zero to 400 16-bit unsigned integers */

3.5. Enumerateds

An additional sparse data type, called "enum" or "enumerated", is available. Each definition is a different type. Only enumerateds of the same type may be assigned or compared. Every element of an enumerated must be assigned a value, as demonstrated in the following example. Since the elements of the enumerated are not ordered, they can be assigned any unique value, in any order. ๐Ÿฆ‹

   enum { e1(v1), e2(v2), ... , en(vn) [[, (n)]] } Te;

Future extensions or additions to the protocol may define new values. Implementations need to be able to parse and ignore unknown values unless the definition of the field states otherwise. ๐ŸŒˆ

An enumerated occupies as much space in the byte stream as would its maximal defined ordinal value. The following definition would cause one byte to be used to carry fields of type Color.

   enum { red(3), blue(5), white(7) } Color;

One may optionally specify a value without its associated tag to force the width definition without defining a superfluous element. ๐Ÿ’…

In the following example, Taste will consume two bytes in the data stream but can only assume the values 1, 2, or 4 in the current version of the protocol.

   enum { sweet(1), sour(2), bitter(4), (32000) } Taste;

The names of the elements of an enumeration are scoped within the defined type. In the first example, a fully qualified reference to the second element of the enumeration would be Color.blue. Such qualification is not required if the target of the assignment is well specified.

   Color color = Color.blue;     /* overspecified, legal */
   Color color = blue;           /* correct, type implicit */

The names assigned to enumerateds do not need to be unique. The numerical value can describe a range over which the same name applies. The value includes the minimum and maximum inclusive values in that range, separated by two period characters. This is principally useful for reserving regions of the space.

   enum { sad(0), meh(1..254), happy(255) } Mood;

3.6. Constructed Types

Structure types may be constructed from primitive types for convenience. Each specification declares a new, unique type. The syntax used for definitions is much like that of C. ๐Ÿ“

   struct {
       T1 f1;
       T2 f2;
       ...
       Tn fn;
   } T;

Fixed- and variable-length list (vector) fields are allowed using the standard list syntax. Structures V1 and V2 in the variants example (Section 3.8) demonstrate this.

The fields within a structure may be qualified using the type's name, with a syntax much like that available for enumerateds. For example, T.f2 refers to the second field of the previous declaration. โœจ

3.7. Constants

Fields and variables may be assigned a fixed value using "=", as in:

   struct {
       T1 f1 = 8;  /* T.f1 must always be 8 */
       T2 f2;
   } T;

3.8. Variants

Defined structures may have variants based on some knowledge that is available within the environment. The selector must be an enumerated type that defines the possible variants the structure defines. Each arm of the select (below) specifies the type of that variant's field and an optional field label. The mechanism by which the variant is selected at runtime is not prescribed by the presentation language. ๐ŸŒธ

   struct {
       T1 f1;
       T2 f2;
       ....
       Tn fn;
       select (E) {
           case e1: Te1 [[fe1]];
           case e2: Te2 [[fe2]];
           ....
           case en: Ten [[fen]];
       };
   } Tv;

For example:

   enum { apple(0), orange(1) } VariantTag;

   struct {
       uint16 number;
       opaque string<0..10>; /* variable length */
   } V1;

   struct {
       uint32 number;
       opaque string[10];    /* fixed length */
   } V2;

   struct {
       VariantTag type;
       select (VariantRecord.type) {
           case apple:  V1;
           case orange: V2;
       };
   } VariantRecord;

4. Handshake Protocol ๐Ÿคโœจ

Okay so the handshake protocol is where the two sides figure out their security settings for a connection ๐Ÿ”๐Ÿ’– Handshake messages get handed to the TLS record layer, which wraps them inside one or more TLSPlaintext or TLSCiphertext structures. Those get processed and sent based on whatever the current active connection state says ๐ŸŽ€

   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;

Protocol messages MUST be sent in the order defined in Section 4.1 and shown in the diagrams in Section 2, unless a TLS extension changes that ๐Ÿ“‹ A peer that gets a handshake message in an unexpected order MUST abort the handshake with an "unexpected_message" alert ๐Ÿšจ

New handshake message types get assigned by IANA, as described in Section 11 ๐ŸŒธ


4.1. The Transcript Hash

Lots of the crypto math in TLS leans on a transcript hash ๐Ÿง It's computed by hashing together each included handshake message, including the handshake message header with its type and length fields, but not including the record layer headers. Basically:

   Transcript-Hash(M1, M2, ... Mn) = Hash(M1 || M2 || ... || Mn)

Now here's the twist ๐Ÿ™ˆ When the server answers a ClientHello with a HelloRetryRequest, the value of ClientHello1 gets swapped out for a special synthetic handshake message. Its handshake type is "message_hash" (254), and it contains Hash(ClientHello1). It looks like this:

  Transcript-Hash(ClientHello1, HelloRetryRequest, ... Mn) =
      Hash(message_hash ||        /* Handshake type */
           00 00 Hash.length  ||  /* Handshake message length (bytes) */
           Hash(ClientHello1) ||  /* Hash of ClientHello1 */
           HelloRetryRequest  || ... || Mn)

The reason for this is so the server can do a stateless HelloRetryRequest ๐Ÿงš It only has to stash the hash of ClientHello1 in the cookie, instead of exporting the entire intermediate hash state (see Section 4.3.2) ๐Ÿ’…

When a range of messages is given with "...", the transcript hash is taken from the sequence of handshake messages that were sent or received during the main handshake, starting and ending at the specified messages. In this document, it's taken from the following sequence of handshake messages (some of which might be omitted): ClientHello, HelloRetryRequest, ClientHello, ServerHello, EncryptedExtensions, server CertificateRequest, server Certificate, server CertificateVerify, server Finished, EndOfEarlyData, client Certificate, client CertificateVerify, and client Finished. TLS extensions may add or remove messages, in which case the transcript hash reflects the modified sequence โœจ

In general, implementations can keep a running transcript hash value based on the negotiated hash, once the hash has been picked in the ServerHello ๐Ÿฆ‹

Note, though, that subsequent post-handshake authentications don't include each other. They only cover the messages through the end of the main handshake ๐Ÿ‘‘


4.2. Key Exchange Messages

The key exchange messages figure out the security capabilities of the client and the server, and they set up shared secrets. That includes the traffic keys that protect the rest of the handshake and the data ๐Ÿ”‘๐Ÿ’œ

4.2.1. Cryptographic Negotiation

In TLS, the crypto negotiation happens when the client offers four sets of options in its ClientHello ๐Ÿ’Œ

  • A list of cipher suites, which indicates the AEAD algorithm and HKDF hash pairs the client supports.
  • A "supported_groups" (Section 4.3.7) extension, which indicates the key exchange groups the client supports, and a "key_share" (Section 4.3.8) extension, which contains key exchange shares for some or all of those groups. The term "groups" is historical, dating from the original version of TLS 1.3, which did not support non-DHE key key establishment algorithms).
  • A "signature_algorithms" (Section 4.3.3) extension, which indicates the signature algorithms the client can accept. A "signature_algorithms_cert" extension (Section 4.3.3) may also be added to indicate certificate-specific signature algorithms.
  • A "pre_shared_key" (Section 4.3.11) extension, which contains a list of symmetric key identities known to the client, and a "psk_key_exchange_modes" (Section 4.3.9) extension, which indicates the key exchange modes that may be used with PSKs.

If the server doesn't select a PSK, then the first three of these options are totally orthogonal ๐Ÿงฉ The server independently picks a cipher suite, an asymmetric key exchange group and key share for key establishment, and a signature algorithm/certificate pair to authenticate itself to the client. If there's no overlap between the received "supported_groups" and the groups the server supports, then the server MUST abort the handshake with a "handshake_failure" or an "insufficient_security" alert ๐Ÿ’€

If the server selects a PSK, then it MUST also select a key establishment mode from the list in the client's "psk_key_exchange_modes" extension (at present, PSK alone or PSK with asymmetric key exchange). Note that if the PSK can be used without asymmetric key exchange, then non-overlap in the "supported_groups" parameters doesn't have to be fatal, unlike the non-PSK case in the previous paragraph ๐ŸŒˆ

If the server selects an asymmetric key exchange group, and the client didn't offer a compatible "key_share" extension in the initial ClientHello, the server MUST respond with a HelloRetryRequest (Section 4.2.4) message ๐Ÿ”

If the server successfully picks parameters and doesn't need a HelloRetryRequest, it indicates the selected parameters in the ServerHello as follows:

  • If PSK is being used, then the server sends a "pre_shared_key" extension indicating the selected key.
  • When asymmetric key exchange is in use, the server also provides a "key_share" extension. If PSK isn't being used, then asymmetric key exchange and certificate-based authentication are always used.
  • When authenticating via a certificate, the server sends the Certificate (Section 4.5.1) and CertificateVerify (Section 4.5.2) messages. In TLS 1.3 as defined by this document, either a PSK or a certificate is always used, but not both. Future documents may define how to use them together ๐Ÿซถ

If the server can't negotiate a supported set of parameters (meaning there's no overlap between the client and server parameters), it MUST abort the handshake with either a "handshake_failure" or "insufficient_security" fatal alert (see Section 6) ๐Ÿ˜ญ

4.2.2. Client Hello โœจ

Okay bestie, so when a client first connects to a server, it is REQUIRED to send the ClientHello as its very first TLS message ๐Ÿ’– The client also sends a ClientHello again when the server replies to the first one with a HelloRetryRequest ๐Ÿฆ‹ In that case the client MUST send the same ClientHello with no changes, except for the following:

  • If a "key_share" extension was supplied in the HelloRetryRequest, the client replaces its list of shares with a list containing a single KeyShareEntry from the indicated group ๐ŸŽ€
  • The client removes the "early_data" extension (Section 4.3.10) if one was present. Early data is not permitted after a HelloRetryRequest ๐Ÿ™ˆ
  • The client includes a "cookie" extension if one was provided in the HelloRetryRequest ๐Ÿ“
  • If a "pre_shared_key" extension is present, the client updates it by recomputing the "obfuscated_ticket_age" and binder values and (optionally) removing any PSKs that are incompatible with the server's indicated cipher suite ๐Ÿงš
  • Optionally, the client adds, removes, or changes the length of the "padding" extension [RFC7685] ๐ŸŒธ
  • Other modifications that may be allowed by an extension defined in the future and present in the HelloRetryRequest are also fine ๐ŸŒˆ

Because TLS 1.3 forbids renegotiation, if a server has negotiated TLS 1.3 and receives a ClientHello at any other time, it MUST terminate the connection with an "unexpected_message" alert ๐Ÿ’€

If a server set up a TLS connection with an earlier version of TLS and then gets a TLS 1.3 ClientHello in a renegotiation, it MUST retain the previous protocol version. In particular, it MUST NOT negotiate TLS 1.3 ๐Ÿšซ no sneaking upgrades, periodt ๐Ÿ’…

Here's the structure of this message:

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<7..2^16-1>;
} ClientHello;

legacy_version: Back in the older versions of TLS, this field handled version negotiation and held the highest version number the client supported. Experience has shown that lots of servers don't implement version negotiation properly, which leads to "version intolerance," where the server rejects an otherwise fine ClientHello just because the version number is higher than it knows ๐Ÿ˜ญ So in TLS 1.3, the client states its version preferences in the "supported_versions" extension (Section 4.3.1), and the legacy_version field MUST be set to 0x0303, which is the version number for TLS 1.2 โœจ A TLS 1.3 ClientHello is identified by having a legacy_version of 0x0303 and a supported_versions extension present with 0x0304 as the highest version listed in it. (See Appendix E for details about backward compatibility.) A server that receives a legacy_version value not equal to 0x0303 MUST abort the handshake with a "protocol_version" alert ๐Ÿ‘‘

random: 32 bytes generated by a secure random number generator. See Appendix C for additional information ๐Ÿ”ฅ

legacy_session_id: Versions of TLS before TLS 1.3 had a "session resumption" feature, which got merged with pre-shared keys in this version (see Section 2.2) ๐Ÿซถ A client that has a cached session ID set by a pre-TLS 1.3 server SHOULD set this field to that value. In compatibility mode (see Appendix E.4), this field MUST be non-empty, so a client that isn't offering a pre-TLS 1.3 session MUST generate a new 32-byte value. This value need not be random, but it SHOULD be unpredictable so implementations don't fixate on one specific value (also known as ossification) ๐Ÿฆ‹ Otherwise, it MUST be set as a zero-length list (i.e., a zero-valued single byte length field) ๐Ÿซ 

cipher_suites: A list of the symmetric cipher options the client supports, specifically the record protection algorithm (including secret key length) and a hash to be used with HKDF, in descending order of client preference. Values are defined in Appendix B.4 ๐ŸŒˆ If the list contains cipher suites that the server doesn't recognize, support, or wish to use, the server MUST ignore those cipher suites and process the remaining ones as usual. If the client is attempting a PSK key establishment, it SHOULD advertise at least one cipher suite that indicates a Hash associated with the PSK ๐ŸŽ€

legacy_compression_methods: Versions of TLS before 1.3 supported compression, and the list of supported compression methods was sent in this field. For every TLS 1.3 ClientHello, this list MUST contain exactly one byte, set to zero, which corresponds to the "null" compression method in prior versions of TLS. If a TLS 1.3 ClientHello arrives with any other value in this field, the server MUST abort the handshake with an "illegal_parameter" alert ๐Ÿšจ Note that TLS 1.3 servers might receive TLS 1.2 or earlier ClientHellos that contain other compression methods, and (if negotiating such a prior version) MUST follow the procedures for the appropriate prior version of TLS ๐Ÿงš

extensions: Clients ask servers for extended functionality by sending data in the extensions field. The actual "Extension" format is defined in Section 4.3. In TLS 1.3, certain extensions are mandatory, because functionality moved into extensions to keep ClientHello compatibility with previous versions of TLS ๐Ÿ’– Servers MUST ignore unrecognized extensions ๐Ÿ™ˆ

All versions of TLS let an extensions field optionally follow the compression_methods field. TLS 1.3 ClientHello messages always contain extensions (at minimum "supported_versions", otherwise they'd be interpreted as TLS 1.2 ClientHello messages) ๐ŸŒธ However, TLS 1.3 servers might receive ClientHello messages without an extensions field from prior versions of TLS. You can detect the presence of extensions by checking whether there are bytes following the compression_methods field at the end of the ClientHello. This method of detecting optional data is different from the normal TLS approach of using a variable-length field, but it's used for compatibility with TLS from before extensions were defined ๐Ÿ•ฐ๏ธ TLS 1.3 servers will need to do this check first and only attempt to negotiate TLS 1.3 if the "supported_versions" extension is present. If a server is negotiating a version of TLS prior to 1.3, it MUST check that the message either contains no data after legacy_compression_methods or contains a valid extensions block with no data following. If not, it MUST abort the handshake with a "decode_error" alert ๐Ÿ˜ค

If a client requests extra functionality using extensions and the server doesn't supply it, the client MAY abort the handshake ๐Ÿคทโ€โ™€๏ธ

After sending the ClientHello message, the client waits for a ServerHello or HelloRetryRequest message. If early data is in use, the client may transmit early Application Data (Section 2.3) while it waits for the next handshake message ๐Ÿฆ‹

4.2.3. Server Hello โœจ

Okay bestie, the server sends this message back in response to a ClientHello ๐ŸŽ€. It only does that if it can agree on a set of handshake parameters that works with what the client offered ๐Ÿ’–

Structure of this message:

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: In older versions of TLS, this field was used for version negotiation and held the selected version number for the connection. Unfortunately, some middleboxes break when they see new values ๐Ÿ’€ In TLS 1.3, the server shows its version with the "supported_versions" extension (Section 4.3.1), and the legacy_version field MUST be set to 0x0303, which is the TLS 1.2 version number. (See Appendix E for the backward compatibility details ๐Ÿฆ‹.) A client that gets a TLS 1.3 Server Hello with a legacy_version value other than 0x0303 MUST abort the handshake with a "protocol_version" alert.
  • random: 32 bytes made by a secure random number generator. See Appendix C for more info โœจ The last 8 bytes MUST be overwritten as described below if TLS 1.2 or TLS 1.1 is being negotiated, but the remaining bytes MUST be random. The server generates this structure and it MUST be generated independently of the ClientHello.random ๐ŸŒธ
  • legacy_session_id_echo: The contents of the client's legacy_session_id field. This field gets echoed even if the client's value matched a cached pre-TLS 1.3 session that the server decided not to resume ๐Ÿ™ˆ A client that gets a legacy_session_id_echo that doesn't match what it sent in the ClientHello MUST abort the handshake with an "illegal_parameter" alert.
  • cipher_suite: The single cipher suite the server picked from the ClientHello.cipher_suites list ๐Ÿ”ฅ A client that receives a cipher suite it never offered MUST abort the handshake with an "illegal_parameter" alert.
  • legacy_compression_method: A single byte that MUST have the value 0. If a TLS 1.3 ServerHello comes in with any other value here, the client MUST abort the handshake with an "illegal_parameter" alert ๐Ÿ™…โ€โ™€๏ธ
  • extensions: A list of extensions. The ServerHello MUST only include extensions that are required to set up the cryptographic context and negotiate the protocol version. All TLS 1.3 ServerHello messages MUST contain the "supported_versions" extension. Current ServerHello messages also contain either the "pre_shared_key" extension or the "key_share" extension, or both (when using a PSK with asymmetric key establishment) ๐Ÿ‘‘ Other extensions (see Section 4.3) get sent separately in the EncryptedExtensions message.

For backward compatibility with middleboxes (see Appendix E.4), the HelloRetryRequest message uses the same structure as the ServerHello, but with Random set to a special value: the SHA-256 of "HelloRetryRequest" ๐Ÿงš

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

Upon receiving a message with type server_hello, implementations MUST first check the Random value. If it matches this value, they process the message as described in Section 4.2.4 โœจ

TLS 1.3 has a downgrade protection mechanism built into the server's random value ๐Ÿ›ก๏ธ TLS 1.3 servers that negotiate TLS 1.2 or lower in response to a ClientHello MUST set the last 8 bytes of their Random value in a special way in their ServerHello ๐Ÿ’…

If negotiating TLS 1.2, TLS 1.3 servers MUST set the last 8 bytes of their Random value to these bytes:

44 4F 57 4E 47 52 44 01

[RFC8996] and Appendix E.5 forbid negotiating TLS versions below 1.2. However, server implementations that don't follow that guidance MUST set the last 8 bytes of their ServerHello.random value to these bytes:

44 4F 57 4E 47 52 44 00

TLS 1.3 clients that receive a ServerHello indicating TLS 1.2 or lower MUST check that the last 8 bytes are not equal to either of these values. TLS 1.2 clients SHOULD also check that the last 8 bytes are not equal to the second value if the ServerHello indicates TLS 1.1 or lower. If a match is found, the client MUST abort the handshake with an "illegal_parameter" alert ๐Ÿšจ This mechanism gives limited downgrade protection on top of what the Finished exchange already provides. In TLS 1.2 and below, the ServerKeyExchange message includes a signature over both random values, so an active attacker can't change the random values without being detected, as long as ephemeral ciphers are used. It does not provide downgrade protection when static RSA is used ๐Ÿ˜ญ

Note: This is a change from [RFC5246], so in practice many TLS 1.2 clients and servers won't behave as described above ๐Ÿ™ˆ

A legacy TLS client that does renegotiation with TLS 1.2 or earlier, and that receives a TLS 1.3 ServerHello during renegotiation, MUST abort the handshake with a "protocol_version" alert. Renegotiation isn't possible once TLS 1.3 has been negotiated ๐Ÿฆ‹


4.2.4. Hello Retry Request ๐Ÿ”

The server sends this message in response to a ClientHello when it can find an acceptable set of parameters, but the ClientHello doesn't have enough info to continue the handshake. As Section 4.2.3 explains, the HelloRetryRequest has the same format as a ServerHello message, and the legacy_version, legacy_session_id_echo, cipher_suite, and legacy_compression_method fields mean the same thing in both โœจ For convenience, though, this document talks about "HelloRetryRequest" as if it were its own distinct message ๐Ÿ’œ

The server's extensions MUST contain "supported_versions". It SHOULD also contain the minimal set of extensions the client needs to generate a correct ClientHello pair. A HelloRetryRequest MUST NOT contain any extensions that the client didn't offer first in its ClientHello, except optionally the "cookie" extension (see Section 4.3.2) ๐Ÿ“

When the client gets a HelloRetryRequest, it MUST check the legacy_version, legacy_session_id_echo, cipher_suite, and legacy_compression_method as specified in Section 4.2.3. Then it processes the extensions, starting with working out the version from "supported_versions". Clients MUST abort the handshake with an "illegal_parameter" alert if the HelloRetryRequest wouldn't result in any change to the ClientHello. If a client receives a second HelloRetryRequest on the same connection (meaning the ClientHello was itself a response to a HelloRetryRequest), it MUST abort the handshake with an "unexpected_message" alert ๐Ÿ˜ค

Otherwise, the client MUST process all the extensions in the HelloRetryRequest and send a second, updated ClientHello. The HelloRetryRequest extensions defined in this specification are:

  • supported_versions (see Section 4.3.1)
  • cookie (see Section 4.3.2)
  • key_share (see Section 4.3.8)

A client that receives a cipher suite it never offered MUST abort the handshake. Servers MUST make sure they negotiate the same cipher suite when they receive a conformant updated ClientHello. (If the server picks the cipher suite as the first step of the negotiation, this happens automatically ๐Ÿ™Œ) When clients receive the ServerHello, they MUST check that the cipher suite in it matches the one in the HelloRetryRequest. Otherwise, they abort the handshake with an "illegal_parameter" alert ๐Ÿšซ

Also, in its updated ClientHello, the client SHOULD NOT offer any pre-shared keys tied to a hash other than the one used by the selected cipher suite. This lets the client skip computing partial hash transcripts for multiple hashes in the second ClientHello ๐Ÿง โœจ

The value of selected_version in the HelloRetryRequest "supported_versions" extension MUST be kept in the ServerHello. If that value changes, a client MUST abort the handshake with an "illegal_parameter" alert ๐Ÿ’–

4.3. Extensions โœจ

okay bestie, extensions are literally everywhere in TLS ๐ŸŽ€ A bunch of messages carry these tag-length-value encoded extension structures, so you need to know this one ๐Ÿ’…

    struct {
        ExtensionType extension_type;
        opaque extension_data<0..2^16-1>;
    } Extension;

    enum {
        server_name(0),                             /* RFC 6066, 9261 */
        status_request(5),                          /* RFC 6066, 9846 */
        supported_groups(10),                       /* RFC 7919, 9846 */
        signature_algorithms(13),                   /* RFC 9846 */
        use_srtp(14),                               /* RFC 5764 */
        heartbeat(15),                              /* RFC 6520 */
        application_layer_protocol_negotiation(16), /* RFC 7301 */
        client_certificate_type(19),                /* RFC 7250 */
        server_certificate_type(20),                /* RFC 7250 */
        padding(21),                                /* RFC 7685 */
        compress_certificate(27),                   /* RFC 8879 */
        record_size_limit(28),                      /* RFC 8449 */
        delegated_credential(34),                   /* RFC 9345 */
        supported_ekt_ciphers(39),                  /* RFC 8870 */
        pre_shared_key(41),                         /* RFC 9846 */
        early_data(42),                             /* RFC 9846 */
        supported_versions(43),                     /* RFC 9846 */
        cookie(44),                                 /* RFC 9846 */
        psk_key_exchange_modes(45),                 /* RFC 9846 */
        certificate_authorities(47),                /* RFC 9846 */
        oid_filters(48),                            /* RFC 9846 */
        post_handshake_auth(49),                    /* RFC 9846 */
        signature_algorithms_cert(50),              /* RFC 9846 */
        key_share(51),                              /* RFC 9846 */
        transparency_info(52),                      /* RFC 9162 */
        external_id_hash(55),                       /* RFC 8844 */
        external_session_id(56),                    /* RFC 8844 */
        quic_transport_parameters(57),              /* RFC 9001 */
        ticket_request(58),                         /* RFC 9149 */
        ech_outer_extensions(64768),                /* RFC 9849 */
        encrypted_client_hello(65037),              /* RFC 9849 */
        (65535)
    } ExtensionType;

Note: This list includes only extensions marked "Recommended" and allowed for TLS 1.3 at the time of this writing. ๐ŸŒธ

Here's the breakdown ๐Ÿงš

  • extension_type identifies the particular extension type.
  • extension_data contains information specific to the particular extension type.

The contents of the extension_data field are usually defined by an extension-specific structure written in the TLS presentation language. Unless otherwise specified, trailing data is forbidden. That means senders MUST NOT include data after the structure in the extension_data field. When processing an extension, receivers MUST abort the handshake with a decode_error alert if there is data left over after parsing the structure. This does not apply if the receiver does not implement or is configured to ignore an extension. ๐Ÿ™ˆ

The list of extension types is maintained by IANA as described in Section 11. ๐Ÿ“‹

Extensions are generally structured as request/response pairs, though some extensions are just requests with no response at all (aka indications) ๐Ÿฆ‹ The client sends its extension requests in the ClientHello message, and the server sends its extension responses in the ServerHello, EncryptedExtensions, HelloRetryRequest, and Certificate messages. The server sends extension requests in the CertificateRequest message, which a client MAY respond to with a Certificate message. The server MAY also send unsolicited extensions in the NewSessionTicket, though the client does not respond directly to these. โœจ

Implementations MUST NOT send extension responses (aka in the ServerHello, EncryptedExtensions, HelloRetryRequest, and Certificate messages) if the remote endpoint did not send the corresponding extension requests. The one exception is the "cookie" extension in the HelloRetryRequest. If an endpoint receives such an extension, it MUST abort the handshake with an "unsupported_extension" alert. ๐Ÿšซ

The table below shows which messages a given extension may appear in. The notation is: CH (ClientHello), SH (ServerHello), EE (EncryptedExtensions), CT (Certificate), CR (CertificateRequest), NST (NewSessionTicket), and HRR (HelloRetryRequest). If an implementation receives an extension it recognizes but that isn't specified for the message it's in, it MUST abort the handshake with an "illegal_parameter" alert. ๐Ÿ˜ญ

    +==================================================+=============+
    | Extension                                        |     TLS 1.3 |
    +==================================================+=============+
    | server_name [RFC6066] [RFC9261]                  |  CH, EE, CR |
    +--------------------------------------------------+-------------+
    | status_request [RFC6066] [RFC9846]               |  CH, CR, CT |
    +--------------------------------------------------+-------------+
    | supported_groups [RFC7919] [RFC9846]             |      CH, EE |
    +--------------------------------------------------+-------------+
    | signature_algorithms [RFC9846]                   |      CH, CR |
    +--------------------------------------------------+-------------+
    | use_srtp [RFC5764]                               |      CH, EE |
    +--------------------------------------------------+-------------+
    | heartbeat [RFC6520]                              |      CH, EE |
    +--------------------------------------------------+-------------+
    | application_layer_protocol_negotiation [RFC7301] |      CH, EE |
    +--------------------------------------------------+-------------+
    | client_certificate_type [RFC7250]                |      CH, EE |
    +--------------------------------------------------+-------------+
    | server_certificate_type [RFC7250]                |      CH, EE |
    +--------------------------------------------------+-------------+
    | padding [RFC7685]                                |          CH |
    +--------------------------------------------------+-------------+
    | compress_certificate [RFC8879]                   |      CH, CR |
    +--------------------------------------------------+-------------+
    | record_size_limit [RFC8449]                      |      CH, EE |
    +--------------------------------------------------+-------------+
    | delegated_credential [RFC9345]                   |  CH, CR, CT |
    +--------------------------------------------------+-------------+
    | supported_ekt_ciphers [RFC8870]                  |      CH, EE |
    +--------------------------------------------------+-------------+
    | pre_shared_key [RFC9846]                         |      CH, SH |
    +--------------------------------------------------+-------------+
    | early_data [RFC9846]                             | CH, EE, NST |
    +--------------------------------------------------+-------------+
    | supported_versions [RFC9846]                     | CH, SH, HRR |
    +--------------------------------------------------+-------------+
    | cookie [RFC9846]                                 |     CH, HRR |
    +--------------------------------------------------+-------------+
    | psk_key_exchange_modes [RFC9846]                 |          CH |
    +--------------------------------------------------+-------------+
    | certificate_authorities [RFC9846]                |      CH, CR |
    +--------------------------------------------------+-------------+
    | oid_filters [RFC9846]                            |          CR |
    +--------------------------------------------------+-------------+
    | post_handshake_auth [RFC9846]                    |          CH |
    +--------------------------------------------------+-------------+
    | signature_algorithms_cert [RFC9846]              |      CH, CR |
    +--------------------------------------------------+-------------+
    | key_share [RFC9846]                              | CH, SH, HRR |
    +--------------------------------------------------+-------------+
    | transparency_info [RFC9162]                      |  CH, CR, CT |
    +--------------------------------------------------+-------------+
    | external_id_hash [RFC8844]                       |      CH, EE |
    +--------------------------------------------------+-------------+
    | external_session_id [RFC8844]                    |      CH, EE |
    +--------------------------------------------------+-------------+
    | quic_transport_parameters [RFC9001]              |      CH, EE |
    +--------------------------------------------------+-------------+
    | ticket_request [RFC9149]                         |      CH, EE |
    +--------------------------------------------------+-------------+
    | ech_outer_extensions [RFC9849]                   |          CH |
    +--------------------------------------------------+-------------+
    | encrypted_client_hello [RFC9849]                 | CH, HRR, EE |
    +--------------------------------------------------+-------------+

                         Table 1: TLS Extensions

Note: This table includes only extensions marked "Recommended" and allowed for TLS 1.3 at the time of this writing. ๐Ÿ“

When multiple extensions of different types are present, the extensions MAY appear in any order, with one exception: pre_shared_key (Section 4.3.11) MUST be the last extension in the ClientHello, though it can appear anywhere in the ServerHello extensions block. There MUST NOT be more than one extension of the same type in a given extension block. ๐ŸŽ€

In TLS 1.3, unlike TLS 1.2, extensions are negotiated for each handshake, even in resumption-PSK mode. However, 0-RTT parameters are the ones negotiated in the previous handshake, so mismatches may require rejecting 0-RTT (see Section 4.3.10). ๐Ÿ”„

โš ๏ธ There are subtle (and not so subtle) interactions that can happen between new features and existing features, and they may cause a big drop in overall security. Keep these in mind when designing new extensions ๐Ÿ’€
  • Some cases where a server does not agree to an extension are error conditions (e.g., the handshake cannot continue), and some are simply refusals to support particular features. In general, error alerts should be used for the former and a field in the server extension response for the latter.
  • Extensions should, as far as possible, be designed to prevent any attack that forces use (or non-use) of a particular feature by manipulating handshake messages. This principle should be followed regardless of whether the feature is believed to cause a security problem. Often, the fact that the extension fields are included in the inputs to the Finished message hashes will be sufficient, but extreme care is needed when the extension changes the meaning of messages sent in the handshake phase. Designers and implementors should be aware that until the handshake has been authenticated, active attackers can modify messages and insert, remove, or replace extensions. ๐Ÿ‘‘

4.3.1. Supported Versions โœจ

Okay bestie, this is the extension where you tell everyone which TLS versions you're down to talk ๐Ÿ’… and where the server says which one it picked ๐Ÿซถ. Here's the struct:

struct {
    select (Handshake.msg_type) {
        case client_hello:
             ProtocolVersion versions<2..254>;

        case server_hello: /* and HelloRetryRequest */
             ProtocolVersion selected_version;
    };
} SupportedVersions;

The "supported_versions" extension is how the client shows which versions of TLS it supports, and how the server shows which version it's actually using ๐Ÿฆ‹. The list is in preference order, so the most-liked version goes first ๐Ÿ‘‘.

Implementations of this specification MUST send this extension in the ClientHello, and it has to include every version of TLS they're ready to negotiate ๐Ÿ’–. For this spec, that means at minimum 0x0304. If older versions of TLS are allowed to be negotiated, they MUST be in the list too โœจ.

If this extension is not in the ClientHello, servers that follow this spec and also support TLS 1.2 MUST negotiate TLS 1.2 or earlier, as specified in [RFC5246], even if ClientHello.legacy_version is 0x0304 or later ๐Ÿ™ˆ. Servers MAY abort the handshake when they get a ClientHello with legacy_version 0x0304 or later ๐Ÿ’€.

If this extension is in the ClientHello, servers MUST NOT use the ClientHello.legacy_version value for version negotiation. They MUST only use the "supported_versions" extension to figure out what the client prefers ๐ŸŽ€. Servers MUST only select a version of TLS that's in that extension, and MUST ignore any unknown versions in it ๐ŸŒธ. Lowkey wild detail: this means you can negotiate a version older than TLS 1.2 if both sides support a sparse range ๐Ÿงš. Implementations of TLS 1.3 that choose to support older versions of TLS SHOULD support TLS 1.2 ๐Ÿ“. Servers MUST be ready to receive ClientHellos that include this extension but don't include 0x0304 in the list of versions ๐Ÿ˜ญ.

A server that negotiates a version of TLS before TLS 1.3 MUST set ServerHello.version and MUST NOT send the "supported_versions" extension ๐ŸŒˆ. A server that negotiates TLS 1.3 MUST respond with a "supported_versions" extension that contains the selected version value (0x0304) ๐Ÿ”ฅ. It MUST also set the ServerHello.legacy_version field to 0x0303 (TLS 1.2) ๐Ÿ’œ.

Clients have to do something specific here. After checking ServerHello.random to see whether the server message is a ServerHello or a HelloRetryRequest, clients MUST check for this extension before processing the rest of the ServerHello ๐Ÿ‘€. That means the client has to parse the ServerHello to find the extension. If the extension is present, clients MUST ignore ServerHello.legacy_version and MUST only use the "supported_versions" extension to determine the selected version โญ. If the "supported_versions" extension in the ServerHello contains a version the client didn't offer, or contains a version prior to TLS 1.3, the client MUST abort the handshake with an "illegal_parameter" alert ๐Ÿšจ.


struct {
    opaque cookie<1..2^16-1>;
} Cookie;

Cookies have two main jobs โœจ:

  • Allowing the server to force the client to prove it can be reached at its apparent network address. This gives you a bit of DoS protection ๐Ÿ›ก๏ธ. It's mostly useful for non-connection-oriented transports (see [RFC9147] for an example of this) ๐Ÿฆ‹.
  • Allowing the server to offload state onto the client, so it can send a HelloRetryRequest without storing any state ๐Ÿง . The server does this by storing a hash of the ClientHello in the HelloRetryRequest cookie, protected with some suitable integrity protection algorithm ๐Ÿ”.

When sending a HelloRetryRequest, the server MAY provide a "cookie" extension to the client. This is an exception to the usual rule that only extensions from the ClientHello can be sent ๐Ÿ™Œ. When sending the new ClientHello, the client MUST copy the contents of the extension it received in the HelloRetryRequest into a "cookie" extension in the new ClientHello ๐Ÿ’…. Clients MUST NOT use cookies in their initial ClientHello in subsequent connections ๐Ÿšซ.

When a server is operating statelessly, it may receive an unprotected record of type change_cipher_spec between the first and second ClientHello (see Section 5). Since the server isn't storing any state, that record will look like it's the first message it got ๐Ÿ˜ตโ€๐Ÿ’ซ. Servers operating statelessly MUST ignore these records ๐Ÿ™ˆ.

4.3.3. Signature Algorithms โœจ

Okay bestie, TLS 1.3 gives us two extensions for telling the other side which signature algorithms we're cool with ๐Ÿ’…. The signature_algorithms_cert extension covers signatures in certificates, and the signature_algorithms extension (which first showed up in TLS 1.2) covers signatures in CertificateVerify messages ๐Ÿ”. The keys in certificates MUST also be the right type for the signature algorithms they're used with ๐Ÿ™ˆ. This is a particular issue for RSA keys and PSS signatures, which we'll get into below ๐Ÿ’€. If there's no signature_algorithms_cert extension, then signature_algorithms also applies to signatures in certificates โœจ. Clients that want the server to prove itself with a certificate MUST send the signature_algorithms extension ๐Ÿซถ. And if a server is authenticating with a certificate and the client didn't send signature_algorithms, the server MUST abort the handshake with a missing_extension alert (see Section 9.2) ๐Ÿ˜ญ.

The signature_algorithms_cert extension got added so that implementations with different sets of algorithms for certificates and for TLS itself could clearly signal what they can do ๐ŸŒˆ. TLS 1.2 implementations SHOULD also process this extension. Implementations with the same policy in both cases MAY omit the signature_algorithms_cert extension ๐Ÿฆ‹.

The extension_data field of these extensions holds a SignatureSchemeList value ๐ŸŽ€:

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;

Quick note ๐Ÿค“: this enum is called "SignatureScheme" because TLS 1.2 already had a "SignatureAlgorithm" type, and this one replaces it. We just say "signature algorithm" everywhere in the text, so don't stress ๐Ÿ’–.

Each SignatureScheme value lists one signature algorithm that the client is willing to verify โœ…. The values go in descending order of preference ๐Ÿ“ˆ. Also, a signature algorithm takes an arbitrary-length message as input, NOT a digest ๐Ÿ™…โ€โ™€๏ธ. So algorithms that normally act on a digest should be defined in TLS to hash the input with a specified hash algorithm first, and then carry on as usual. Here's what the code point groups mean ๐Ÿ‘‡:

  • RSASSA-PKCS1-v1_5 algorithms: Means a signature algorithm using RSASSA-PKCS1-v1_5 [RFC8017] with the matching hash algorithm from [SHS]. These values only refer to signatures that appear in certificates (see Section 4.5.1.2), and they're not defined for signed TLS handshake messages ๐Ÿ™ƒ. They MAY appear in "signature_algorithms" and "signature_algorithms_cert" for backward compatibility with TLS 1.2 ๐Ÿซ .
  • ECDSA algorithms: Means a signature algorithm using ECDSA [DSS], the matching curve from NIST SP 800-186 [ECDP], and the matching hash algorithm from [SHS] ๐ŸŒธ. The signature is a DER-encoded [X690] ECDSA-Sig-Value structure, as defined in [RFC4492] ๐Ÿงพ.
  • RSASSA-PSS RSAE algorithms: Means a signature algorithm using RSASSA-PSS with an MGF1 mask generation function, as defined in [RFC8017] ๐ŸŽญ. The digest used in MGF1 and the digest being signed are both the matching hash algorithm from [SHS]. The length of the Salt MUST be equal to the length of the output of the digest algorithm ๐Ÿง‚. If the public key lives in an X.509 certificate, it MUST use the rsaEncryption OID [RFC3279] ๐Ÿ”‘.
  • EdDSA algorithms: Means a signature algorithm using EdDSA as defined in [RFC8032] or its successors ๐Ÿฆ‹. These correspond to the "PureEdDSA" algorithms, NOT the "prehash" variants ๐Ÿ™…โ€โ™€๏ธ.
  • RSASSA-PSS PSS algorithms: Means a signature algorithm using RSASSA-PSS with an MGF1 mask generation function, as defined in [RFC8017] ๐ŸŽ€. The digest used in MGF1 and the digest being signed are both the matching hash algorithm from [SHS]. The length of the Salt MUST be equal to the length of the digest algorithm ๐Ÿง‚. If the public key lives in an X.509 certificate, it MUST use the RSASSA-PSS OID [RFC5756] ๐Ÿ”‘. When it's used in certificate signatures, the algorithm parameters MUST be DER encoded ๐Ÿ“ฆ. And if the corresponding public key's parameters are present, the parameters in the signature MUST be identical to those in the public key ๐Ÿ‘ฏโ€โ™€๏ธ.
  • Legacy algorithms: These are being deprecated because they use algorithms with known weaknesses, specifically SHA-1, which is used here with either (1) RSA using RSASSA-PKCS1-v1_5 or (2) ECDSA ๐Ÿฅ€. They only refer to signatures in certificates (see Section 4.5.1.2), and they're not defined for signed TLS handshake messages ๐Ÿšซ. They MAY appear in "signature_algorithms" and "signature_algorithms_cert" for backward compatibility with TLS 1.2 ๐Ÿซค. Endpoints SHOULD NOT negotiate these algorithms, but they're allowed to do so solely for backward compatibility ๐Ÿ˜ฌ. Clients offering these values MUST list them as the lowest priority, after all other algorithms in SignatureSchemeList ๐Ÿ“‰. TLS 1.3 servers MUST NOT offer a SHA-1 signed certificate unless no valid certificate chain can be produced without it (see Section 4.5.1.2) ๐Ÿ™…โ€โ™€๏ธ.

The signatures on certificates that are self-signed, or on certificates that are trust anchors, don't get validated, since they start a certification path (see [RFC5280], Section 3.2) ๐Ÿ‘‘. A certificate that starts a certification path MAY use a signature algorithm that isn't advertised in the "signature_algorithms" and "signature_algorithms_cert" extensions โœจ.

Heads up ๐Ÿšจ: TLS 1.2 defines this extension differently! TLS 1.3 implementations that are willing to negotiate TLS 1.2 MUST behave according to the requirements of [RFC5246] when negotiating that version. Here's the tea โ˜•:
  • TLS 1.2 ClientHellos MAY omit this extension ๐ŸŒท.
  • In TLS 1.2, the extension contained hash/signature pairs. Those pairs are encoded in two octets, so SignatureScheme values were allocated to line up with TLS 1.2's encoding ๐Ÿงฉ. Some legacy pairs are left unallocated ๐Ÿซฅ. These algorithms are deprecated as of TLS 1.3. They MUST NOT be offered or negotiated by any implementation ๐Ÿ’€. In particular, MD5 [SLOTH], SHA-224, and DSA MUST NOT be used ๐Ÿ™ˆ.
  • ECDSA signature schemes line up with TLS 1.2's ECDSA hash/signature pairs. However, the old semantics didn't constrain the signing curve ๐Ÿคทโ€โ™€๏ธ. If TLS 1.2 is negotiated, implementations MUST be prepared to accept a signature that uses any curve they advertised in the "supported_groups" extension ๐Ÿซถ.
  • Implementations that advertise support for RSASSA-PSS (which is mandatory in TLS 1.3) MUST be prepared to accept a signature using that scheme even when TLS 1.2 is negotiated ๐Ÿ’œ. In TLS 1.2, RSASSA-PSS is used with RSA cipher suites ๐ŸŽŸ๏ธ.

4.3.4. Certificate Authorities ๐Ÿงš

The "certificate_authorities" extension tells the other side which certificate authorities (CAs) an endpoint supports. The receiving endpoint SHOULD use that info to guide certificate selection ๐Ÿ—บ๏ธ.

The body of the "certificate_authorities" extension is a CertificateAuthoritiesExtension structure ๐Ÿงพ:

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

struct {
    DistinguishedName authorities<3..2^16-1>;
} CertificateAuthoritiesExtension;
  • authorities: A list of distinguished names [X501] for acceptable certificate authorities, in DER-encoded [X690] format ๐Ÿ“œ. These distinguished names describe a desired distinguished name for a trust anchor or subordinate CA. So this message can be used to describe known trust anchors and a desired authorization space ๐ŸŒˆ.

The client MAY send the "certificate_authorities" extension in the ClientHello message ๐Ÿ’Œ. The server MAY send it in the CertificateRequest message ๐Ÿ“ฌ.

The "trusted_ca_keys" extension [RFC6066] does a similar job, but it's way more complicated ๐Ÿ˜ตโ€๐Ÿ’ซ. It's not used in TLS 1.3, although it may appear in ClientHello messages from clients that are offering prior versions of TLS ๐Ÿ•ฐ๏ธ.

4.3.5. OID Filters ๐ŸŽ€

okay so the oid_filters extension is how a server says "hey, I'd really like the client's certificate to match these OID/value pairs, pls and thx" ๐Ÿ’… and this one is ONLY allowed to show up in the CertificateRequest message, so don't you dare put it anywhere else ๐Ÿ™ˆ

   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;

the filters list is basically the guest list ๐ŸŽŸ๏ธ it's a bunch of certificate extension OIDs [RFC5280] with their allowed values, all DER-encoded [X690] (yes, the encoding has a name, it's giving formal ๐Ÿง). some extension OIDs can hold more than one value, like Extended Key Usage, so keep that in mind โœจ

here's the tea. if the server sent a non-empty filters list, the client certificate in the response MUST contain every specified extension OID that the client actually recognizes ๐Ÿ’€. for each recognized OID, ALL of the specified values MUST be present in the certificate, but the cert MAY also have extra values, which is totally fine, we don't judge ๐ŸŒธ

BUT the client MUST ignore and skip any OID it doesn't recognize. no drama, just keep it moving ๐Ÿฆ‹ and if the client skipped some required OIDs and the cert it sent doesn't satisfy the request, the server MAY, at its discretion, either keep the connection going WITHOUT client auth, or abort the handshake with an "unsupported_certificate" alert. its call, not ours ๐Ÿ‘‘

one more ironclad rule: any given OID MUST NOT appear more than once in the filters list. one OID, one spot, periodt ๐Ÿ’œ

PKIX RFCs define a whole bunch of certificate extension OIDs and their value types, and matching isn't always bitwise-equal, depending on the type ๐ŸŒˆ so it's expected that TLS implementations will lean on their PKI libraries to do the certificate selection using these OIDs. let the experts cook ๐Ÿ‘ฉโ€๐Ÿณ

this document defines matching rules for two standard extensions from [RFC5280] ๐Ÿ“

  • the Key Usage extension in a certificate matches the request when ALL the key usage bits asserted in the request are also asserted in the Key Usage certificate extension. all of them, no skipping โœจ
  • the Extended Key Usage extension matches when ALL the key purpose OIDs in the request are also found in the Extended Key Usage certificate extension. and the special anyExtendedKeyUsage OID MUST NOT be used in the request ๐Ÿ™…โ€โ™€๏ธ it's a no-go, bestie

separate specifications may define matching rules for other certificate extensions, so this isn't the final word on every extension ๐Ÿงš


4.3.6. Post-Handshake Certificate-Based Client Authentication ๐Ÿ”

the post_handshake_auth extension is how a client says it's willing to do post-handshake authentication (Section 4.7.2) ๐Ÿซถ and the rules are strict: servers MUST NOT send a post-handshake CertificateRequest to any client that didn't offer this extension. and servers MUST NOT send this extension themselves ๐Ÿ˜ญ it's a client-only vibe.

   struct {} PostHandshakeAuth;

the "extension_data" field of post_handshake_auth is zero length. nothing in there, just the vibe โœจ


4.3.7. Supported Groups ๐ŸŒธ

when the client sends the supported_groups extension, it's basically its wishlist of named groups it can do key exchange with, ordered from most preferred to least preferred ๐Ÿ’…

Note: before TLS 1.3 this extension was called elliptic_curves and it only held elliptic curve groups. see [RFC8422] and [RFC7919] ๐Ÿ“œ it also used to negotiate ECDSA curves, but signature algorithms are negotiated separately now (see Section 4.3.3), so it's a whole glow-up ๐Ÿฆ‹

the "extension_data" field holds a "NamedGroupList" value:

   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;

the elliptic curve groups (ECDHE) are the named curves from NIST SP 800-186 [ECDP] or [RFC7748] โœจ values 0xFE00 through 0xFEFF are reserved for Private Use [RFC8126] ๐Ÿ”’

the finite field groups (DHE) are defined in [RFC7919], and values 0x01FC through 0x01FF are reserved for Private Use ๐Ÿงš

the items in named_group_list are ordered by the sender's preferences, most preferred first ๐Ÿ‘‘ and the named_group_list MUST NOT contain any duplicate entries ๐Ÿ™…โ€โ™€๏ธ a recipient MAY abort the connection with a fatal "illegal_parameter" alert if it spots a duplicate ๐Ÿ’€

as of TLS 1.3, servers are allowed to send supported_groups to the client too ๐ŸŒˆ but clients MUST NOT act on anything in supported_groups before the handshake has successfully completed ๐Ÿ˜ค. they MAY use what they learned from a successfully completed handshake to change the groups in their key_share extension for future connections ๐Ÿฆ‹

if the server prefers a group that isn't in the key_share extension but is still willing to accept the ClientHello, it SHOULD send supported_groups to update the client's view of its preferences ๐Ÿซถ and that extension SHOULD contain ALL the groups the server supports, whether or not the client currently supports them ๐ŸŒธ


4.3.8. Key Share ๐Ÿ”‘

the key_share extension carries the endpoint's cryptographic parameters ๐Ÿ’–

clients MAY send an empty client_shares list to ask the server to pick the group, but it costs an extra round trip (see Section 4.2.4) ๐Ÿข

   struct {
       NamedGroup group;
       opaque key_exchange<1..2^16-1>;
   } KeyShareEntry;
  • group: the named group for the key being exchanged ๐ŸŽ€
  • key_exchange: the key exchange info. what's in here depends on the group and its definition. finite field Diffie-Hellman [DH76] parameters are in Section 4.3.8.1, and elliptic curve Diffie-Hellman parameters are in Section 4.3.8.2 โœจ

in the ClientHello, the "extension_data" field holds a "KeyShareClientHello" value:

   struct {
       KeyShareEntry client_shares<0..2^16-1>;
   } KeyShareClientHello;
  • client_shares: a list of offered KeyShareEntry values in descending order of client preference ๐Ÿ’…

this list MAY be empty if the client is requesting a HelloRetryRequest ๐Ÿงš each KeyShareEntry value MUST correspond to a group offered in the supported_groups extension, and MUST appear in the same order ๐Ÿ“‹. however, the values MAY be a non-contiguous subset of supported_groups and MAY skip the most preferred groups ๐Ÿ™ˆ that can happen when the top groups are brand new and not supported widely enough to make pregenerating shares worth it.

so, the omission of a share for group A and the inclusion of one for group B does NOT mean the client prefers B to A ๐Ÿซ  picking a group from KeyShareEntry might land you on a less preferred group than what client and server both support, though you do save the HelloRetryRequest round trip โœˆ๏ธ. servers that want to respect the client's preferences SHOULD first pick a group based on supported_groups, and THEN send either a ServerHello or a HelloRetryRequest depending on the contents of KeyshareClientHello ๐Ÿ‘‘

clients can offer as many KeyShareEntry values as the number of groups they support, each one a single set of key exchange parameters ๐ŸŒˆ for example, a client might offer shares for several elliptic curves or multiple FFDHE groups. the key_exchange values for each KeyShareEntry MUST be generated independently ๐Ÿ’œ. clients MUST NOT offer multiple KeyShareEntry values for the same group ๐Ÿ™…โ€โ™€๏ธ and clients MUST NOT offer any KeyShareEntry values for groups NOT listed in supported_groups ๐Ÿ’€. servers MAY check for these violations and abort the handshake with an "illegal_parameter" alert if one happens ๐Ÿšจ

clients and servers MUST NOT reuse a key share for multiple connections ๐Ÿ”ฅ. BUT, because [RFC8446] allowed reuse, receiving implementations MUST permit reuse by the peer so you don't get interop drama ๐Ÿซถ

in a HelloRetryRequest, the "extension_data" field holds a KeyShareHelloRetryRequest value:

   struct {
       NamedGroup selected_group;
   } KeyShareHelloRetryRequest;
  • selected_group: the mutually supported group the server wants to negotiate, and it's asking for a retried ClientHello/KeyShare for it ๐ŸŽฏ

when the client gets this extension in a HelloRetryRequest, it MUST verify that (1) the selected_group corresponds to a group that was provided in supported_groups in the original ClientHello, AND (2) the selected_group does NOT correspond to a group that was already provided in the key_share extension in the original ClientHello ๐Ÿ•ต๏ธโ€โ™€๏ธ. if either check fails, the client MUST abort the handshake with an "illegal_parameter" alert ๐Ÿ’€. otherwise, when sending the new ClientHello, the client MUST replace the original key_share extension with one containing ONLY a new KeyShareEntry for the group in selected_group from the triggering HelloRetryRequest โœจ

in a ServerHello, the "extension_data" field holds a KeyShareServerHello value:

   struct {
       KeyShareEntry server_share;
   } KeyShareServerHello;
  • server_share: a single KeyShareEntry value in the same group as one of the client's shares ๐Ÿฆ‹

if using asymmetric key establishment, servers offer EXACTLY one KeyShareEntry in the ServerHello ๐Ÿ’–. this value MUST be in the same group as the KeyShareEntry the client offered that the server selected for the negotiated key exchange ๐ŸŽ€. servers MUST NOT send a KeyShareEntry for any group not indicated in the client's supported_groups extension, and MUST NOT send a KeyShareEntry when using the "psk_ke" PskKeyExchangeMode ๐Ÿ™…โ€โ™€๏ธ. if using asymmetric key establishment AND the client got a HelloRetryRequest containing a key_share extension, the client MUST verify that the selected NamedGroup in the ServerHello is the same as the one in the HelloRetryRequest ๐Ÿ”. if that check fails, the client MUST abort the handshake with an "illegal_parameter" alert ๐Ÿ’€


4.3.8.1. Diffie-Hellman Parameters ๐Ÿงฎ

Diffie-Hellman [DH76] parameters for both clients and servers are encoded in the opaque key_exchange field of a KeyShareEntry inside a KeyShare structure โœจ the opaque value holds the Diffie-Hellman public value (Y = g^X mod p) for the specified group (see [RFC7919] for group definitions), encoded as a big-endian integer and padded on the left with zeros to the size of p in bytes ๐ŸŒธ

Note: for a given Diffie-Hellman group, the padding means ALL public keys end up the same length. neat, right? ๐Ÿค“

peers MUST validate each other's public key Y by making sure that 1 < Y < p-1 ๐Ÿ”. this check makes sure the remote peer is behaving and isn't trying to force the local system into a small subgroup ๐Ÿ›ก๏ธ bestie, that's a whole safety net ๐Ÿ’œ

4.3.8.2. ECDHE Parameters ๐Ÿฆ‹

okay so ECDHE parameters for both clients and servers live in the opaque key_exchange field of a KeyShareEntry, which is itself inside a KeyShare structure. nested containers, bestie ๐Ÿ“ฆโœจ

For secp256r1, secp384r1, and secp521r1, the contents are the serialized value of this struct ๐Ÿ’…:

struct {
    uint8 legacy_form = 4;
    opaque X[coordinate_length];
    opaque Y[coordinate_length];
} UncompressedPointRepresentation;

X and Y are the binary forms of the x and y values, in network byte order ๐ŸŒธ. There are no internal length markers, so each number just takes up however many octets the curve parameters say it should. For P-256, X and Y are each 32 octets, left-padded with zeros if needed. For P-384 they're 48 octets each, and for P-521 they're 66 octets each ๐Ÿงš.

For the curves secp256r1, secp384r1, and secp521r1, peers MUST validate each other's public value Q by checking that the point is actually a valid point on the elliptic curve ๐Ÿ”ฅ. The validation procedures are in Appendix D.1 of [ECDP], and there's an alternative in Section 5.6.2.3 of [KEYAGREEMENT]. the check has three steps: (1) make sure Q is not the point at infinity (O), (2) make sure that for Q = (x, y), both integers x and y are in the correct interval, and (3) make sure (x, y) is a correct solution to the elliptic curve equation. for these curves, implementors don't need to verify membership in the correct subgroup ๐Ÿซถ.

For X25519 and X448, the public value is the K_A or K_B value described in Section 6 of [RFC7748]. that's 32 bytes for X25519 and 56 bytes for X448 โœจ.

Note: Versions of TLS prior to 1.3 allowed point format negotiation. TLS 1.3 removes that feature and uses a single point format for each curve, so no more haggling ๐Ÿ™ˆ.

4.3.9. Pre-Shared Key Exchange Modes ๐Ÿ”

okay, so to use PSKs, clients MUST also send a "psk_key_exchange_modes" extension ๐Ÿ’–. the semantics are that the client only supports using PSKs with these modes. that restricts both the PSKs offered in this ClientHello and any the server might hand out via NewSessionTicket. main character restriction energy ๐Ÿ‘‘.

A client MUST provide a "psk_key_exchange_modes" extension if it offers a "pre_shared_key" extension. if clients offer "pre_shared_key" without a "psk_key_exchange_modes" extension, servers MUST abort the handshake ๐Ÿ’€. Servers MUST NOT select a key exchange mode that the client didn't list. this extension also restricts the modes used for PSK resumption. Servers SHOULD NOT send NewSessionTicket with tickets that aren't compatible with the advertised modes. but if a server does, the only consequence is that the client's resumption attempts fail ๐Ÿ™ˆ.

The server MUST NOT send a "psk_key_exchange_modes" extension ๐Ÿšซ.

enum { psk_ke(0), psk_dhe_ke(1), (255) } PskKeyExchangeMode;
struct {
    PskKeyExchangeMode ke_modes<1..255>;
} PskKeyExchangeModes;
  • psk_ke: PSK-only key establishment ๐Ÿ“. In this mode, the server MUST NOT supply a "key_share" value.
  • psk_dhe_ke: PSK with asymmetric key establishment ๐ŸŒˆ. (The "dhe" label is historical, from the original version of TLS 1.3, which only supported DHE and not other non-DHE key establishment algorithms.) In this mode, the client and server MUST supply "key_share" values as described in Section 4.3.8.

Any future values that get allocated must make sure the transmitted protocol messages unambiguously identify which mode the server selected. right now that's done by the presence of the "key_share" in the ServerHello ๐Ÿงšโ€โ™€๏ธ.

4.3.10. Early Data Indication ๐Ÿš€

okay this one's so fun โœจ. when a PSK is used and early data is allowed for that PSK (see for instance Appendix B.3.4), the client can send Application Data in its first flight of messages ๐Ÿฆ‹. if the client opts in, it MUST supply both the "pre_shared_key" and "early_data" extensions ๐ŸŽ€.

The "extension_data" field of this extension contains an "EarlyDataIndication" value:

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;

see Section 4.7.1 for details on how the max_early_data_size field gets used ๐Ÿ“–.

The parameters for the 0-RTT data (version, symmetric cipher suite, Application-Layer Protocol Negotiation (ALPN) [RFC7301] protocol, and so on) are whatever is associated with the PSK in use ๐Ÿ”‘. for externally provisioned PSKs, those are the values provisioned along with the key. for PSKs established through a NewSessionTicket message, they're the values negotiated in the connection that established the PSK. the PSK used to encrypt the early data MUST be the first PSK listed in the client's "pre_shared_key" extension ๐Ÿซถ.

For PSKs provisioned via NewSessionTicket, a server MUST validate that the ticket age for the selected PSK identity is within a small tolerance of the time since the ticket was issued (see Section 8) โฐ. the ticket age is computed by subtracting ticket_age_add from PskIdentity.obfuscated_ticket_age, modulo 2^32. if it isn't within that tolerance, the server SHOULD proceed with the handshake but reject 0-RTT, and SHOULD NOT take any other action that assumes this ClientHello is fresh ๐Ÿ™…โ€โ™€๏ธ.

0-RTT messages sent in the first flight have the same (encrypted) content types as messages of the same type sent in other flights (handshake and application_data), but they're protected under different keys ๐Ÿ”. after the server's Finished message is received, if the server has accepted early data, an EndOfEarlyData message gets sent to signal the key change. that message is encrypted with the 0-RTT traffic keys ๐ŸŒธ.

A server that receives an "early_data" extension MUST behave in one of three ways ๐Ÿ‘‘:

  • Ignore the extension and return a regular 1-RTT response. The server then skips past the early data by trying to deprotect received records with the handshake traffic key, discarding any records that fail deprotection (up to the configured max_early_data_size). once a record deprotects successfully, it's treated as the start of the client's second flight, and the server carries on like an ordinary 1-RTT handshake ๐Ÿฆ‹.
  • Request that the client send another ClientHello by responding with a HelloRetryRequest. A client MUST NOT include the "early_data" extension in its followup ClientHello. The server then ignores the early data by skipping all records with an external content type of "application_data" (which means they're encrypted), up to the configured max_early_data_size ๐Ÿ™ˆ.
  • Return its own "early_data" extension in EncryptedExtensions, saying it intends to process the early data. the server can't accept just a subset of the early data messages ๐Ÿ’…. even though it's sending a message that accepts early data, the early data itself may already be in flight by the time the server generates that message ๐Ÿƒโ€โ™€๏ธ.

To accept early data, the server MUST have selected the first key offered in the client's "pre_shared_key" extension ๐ŸŽฏ. on top of that, it MUST verify that these values match the ones associated with the selected PSK:

  • The selected TLS version number
  • The selected cipher suite
  • The selected ALPN [RFC7301] protocol, if any

these requirements are a superset of what's needed to do a 1-RTT handshake with that PSK ๐ŸŒˆ.

Future extensions MUST define how they interact with 0-RTT ๐Ÿงš.

If any of these checks fail, the server MUST NOT respond with the extension, and it must discard all the first-flight data using one of the first two mechanisms listed above (falling back to 1-RTT or 2-RTT) ๐Ÿ’”. if the client attempts a 0-RTT handshake and the server rejects it, the server generally won't have the 0-RTT record protection keys. so it has to use trial decryption (either with the 1-RTT handshake keys, or by looking for a cleartext ClientHello in the case of a HelloRetryRequest) to find the first non-0-RTT message ๐Ÿ”.

If the server chooses to accept the "early_data" extension, then it MUST comply with the same error-handling requirements specified for all records when processing early data records. specifically, if the server fails to decrypt a 0-RTT record after accepting the "early_data" extension, it MUST terminate the connection with a "bad_record_mac" alert, as per Section 5.2 ๐Ÿšจ.

If the server rejects the "early_data" extension, the client application MAY opt to retransmit the Application Data it previously sent as early data once the handshake is complete โœจ. but heads up: automatic retransmission of early data could lead to wrong assumptions about the connection's status. for example, if the negotiated connection picks a different ALPN protocol than the one used for the early data, the application might need to build different messages ๐Ÿ™ƒ. likewise, if the early data assumed anything about the connection state, it might be sent in error after the handshake finishes ๐Ÿ˜ญ.

A TLS implementation SHOULD NOT automatically resend early data, because applications are in a much better position to decide when retransmission makes sense ๐Ÿซถ. a TLS implementation MUST NOT automatically resend early data unless the negotiated connection selects the same ALPN protocol ๐Ÿ’….

4.3.11. Pre-Shared Key Extension โœจ

okay so the pre_shared_key extension is how the two sides agree on which pre-shared key they're gonna use for a handshake, bestie ๐Ÿ”‘๐Ÿฆ‹ it's the PSK key establishment vibe, and it's giving main character energy ๐Ÿ’…

The "extension_data" field holds a "PreSharedKeyExtension" value, and it looks like this:

   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;

Here's what each piece does ๐ŸŽ€

  • identity: a label for a key ๐Ÿท๏ธ like a ticket (see Appendix B.3.4) or a label for a pre-shared key that was set up externally.
  • obfuscated_ticket_age: a scrambled version of the key's age ๐Ÿ™ˆ Section 4.3.11.1 explains how to make this one for identities from the NewSessionTicket message. For externally established identities, an obfuscated_ticket_age of 0 SHOULD be used, and servers MUST ignore the value. ๐Ÿ’€
  • identities: the list of identities the client is up for negotiating with the server ๐ŸŒธ If it's sent alongside the "early_data" extension (see Section 4.3.10), the first identity is the one used for 0-RTT data โœจ
  • binders: a bunch of HMAC values, one for each entry in identities, in the same order, computed the way described below ๐Ÿ”
  • selected_identity: the server's pick, written as a (0-based) index into the client's "OfferedPsks.identities" list ๐ŸŽฏ

Every PSK is tied to a single Hash algorithm ๐Ÿง For PSKs from the ticket mechanism (Section 4.7.1), that's the KDF Hash algorithm on the connection where the ticket was made. For externally established PSKs, the Hash algorithm MUST be set when the PSK is established, or default to SHA-256 if no such algorithm is defined. The server MUST ensure that it selects a compatible PSK (if any) and cipher suite. ๐Ÿ‘‘

Okay this part is serious, bestie: the server has to get the PSK and cipher suite matching right, no skipping it ๐Ÿ’€

Now the SNI drama ๐ŸŽญ In TLS versions prior to TLS 1.3, the Server Name Indication (SNI) value was meant to be tied to the session (Section 3 of [RFC6066]), and the server was required to enforce that the SNI associated with the session matches the one in the resumption handshake. But implementations didn't agree on which of two supplied SNI values to use, so clients ended up de facto enforcing the consistency requirement ๐Ÿ˜ญ In TLS 1.3, the SNI value is always explicitly specified in the resumption handshake, so the server doesn't need to link an SNI value to the ticket at all ๐Ÿฆ‹ Clients, however, SHOULD store the SNI with the PSK to fulfill the requirements of Section 4.7.1. ๐Ÿซถ


Implementor's note ๐Ÿ“โœจ When session resumption is the main use case for PSKs, the simplest way to handle the PSK/cipher suite matching requirements is to negotiate the cipher suite first, then exclude any incompatible PSKs ๐Ÿงฉ Any unknown PSKs (like ones not in the PSK database, or encrypted with an unknown key) SHOULD simply be ignored ๐Ÿ™ˆ If no acceptable PSKs are found, the server SHOULD perform a non-PSK handshake if possible. And if backward compatibility matters to you, client-provided, externally established PSKs SHOULD influence cipher suite selection. ๐ŸŒˆ


Before accepting PSK key establishment, the server MUST validate the corresponding binder value (see Section 4.3.11.2 below) ๐Ÿ” If that value is missing or doesn't validate, the server MUST abort the handshake ๐Ÿšซ Servers SHOULD NOT attempt to validate multiple binders. Instead, they SHOULD select a single PSK and validate only the binder for that PSK ๐ŸŽ€ See Section 8.2 and Appendix F.6 for the security rationale behind this requirement ๐Ÿ” To accept PSK key establishment, the server sends a "pre_shared_key" extension indicating the selected identity ๐Ÿ’–

Clients MUST verify that the server's selected_identity is within the range the client supplied, that the server picked a cipher suite indicating a Hash associated with the PSK, and that a server "key_share" extension is present if the ClientHello "psk_key_exchange_modes" extension requires it. ๐Ÿง If these values aren't consistent, the client MUST abort the handshake with an "illegal_parameter" alert ๐Ÿšจ

If the server supplies an "early_data" extension, the client MUST verify that the server's selected_identity is 0. If any other value comes back, the client MUST abort the handshake with an "illegal_parameter" alert ๐Ÿ™…โ€โ™€๏ธ

The "pre_shared_key" extension MUST be the last extension in the ClientHello (this makes implementation easier, as described below) ๐Ÿงƒ Servers MUST check that it's the last extension, and otherwise fail the handshake with an "illegal_parameter" alert โญ


4.3.11.1. Ticket Age โฐ

The client's view of a ticket's age is the time since it received the NewSessionTicket message. Clients MUST NOT attempt to use tickets whose ages are greater than the "ticket_lifetime" value that came with the ticket ๐Ÿšซ The "obfuscated_ticket_age" field of each PskIdentity holds a scrambled version of the ticket age, made by taking the age in milliseconds and adding the "ticket_age_add" value that came with the ticket (see Section 4.7.1), modulo 2^32 ๐ŸŒ€ This addition stops passive observers from linking connections together, unless tickets or key shares get reused ๐Ÿ•ต๏ธโ€โ™€๏ธ Note that the "ticket_lifetime" field in NewSessionTicket is in seconds, but "obfuscated_ticket_age" is in milliseconds, so watch those units ๐Ÿ“ Because ticket lifetimes are capped at a week, 32 bits is plenty to represent any plausible age, even in milliseconds ๐Ÿงฎ


4.3.11.2. PSK Binder ๐Ÿ”

The PSK binder forms a binding between a PSK and the current handshake, and also between the handshake where the PSK was generated (if it came from a NewSessionTicket message) and the current handshake ๐Ÿ’œ Each entry in the binders list is computed as an HMAC over a transcript hash (see Section 4.1) that covers a partial ClientHello, up to and including the PreSharedKeyExtension.identities field ๐Ÿงฉ So it includes the whole ClientHello but not the binders list itself. The length fields for the message (the overall length, the length of the extensions block, and the length of the "pre_shared_key" extension) are all set as if binders of the correct lengths were there ๐Ÿ“

The PskBinderEntry is computed the same way as the Finished message (Section 4.5.3), except the BaseKey is the binder_key derived through the key schedule from the PSK being offered (see Section 7.1) ๐Ÿ”‘

If the handshake includes a HelloRetryRequest, the initial ClientHello and the HelloRetryRequest go into the transcript along with the new ClientHello ๐Ÿ” For instance, if the client sends ClientHello1, its binder is computed over:

   Transcript-Hash(Truncate(ClientHello1))

Where Truncate() removes the binders list from the ClientHello. Note that this hash is computed using the hash associated with the PSK, since the client doesn't know which cipher suite the server will pick ๐Ÿคทโ€โ™€๏ธ

If the server responds with a HelloRetryRequest and the client then sends ClientHello2, its binder is computed over:

   Transcript-Hash(ClientHello1,
                   HelloRetryRequest,
                   Truncate(ClientHello2))

The full ClientHello1/ClientHello2 gets included in all the other handshake hash computations ๐ŸŒˆ Note that in the first flight, Truncate(ClientHello1) is hashed directly, but in the second flight, ClientHello1 is hashed and then reinjected as a "message_hash" message, as described in Section 4.1 ๐ŸŽ€ Note that the "message_hash" is hashed with the negotiated function, which may or may not match the hash associated with the PSK. This is consistent with how the transcript is calculated for the rest of the handshake โœจ


4.3.11.3. Processing Order ๐Ÿƒโ€โ™€๏ธ

Clients are allowed to "stream" 0-RTT data until they receive the server's Finished, and only then send the EndOfEarlyData message, followed by the rest of the handshake ๐ŸŒŠ To avoid deadlocks, when accepting "early_data", servers MUST process the client's ClientHello and then immediately send their flight of messages, instead of waiting for the client's EndOfEarlyData message before sending their ServerHello ๐Ÿ’ซ


4.4. Server Parameters ๐ŸŽ

The next two messages from the server, EncryptedExtensions and CertificateRequest, carry the info from the server that determines the rest of the handshake ๐Ÿ“ฆ These messages are encrypted with keys derived from the server_handshake_traffic_secret ๐Ÿ”’

4.4.1. Encrypted Extensions ๐Ÿ›ก๏ธ

In all handshakes, the server MUST send the EncryptedExtensions message immediately after the ServerHello message ๐Ÿ‘‘ This is the first message encrypted under keys derived from the server_handshake_traffic_secret ๐Ÿ”

The EncryptedExtensions message holds extensions that can be protected, meaning any that aren't needed to set up the cryptographic context but also aren't tied to individual certificates ๐Ÿง The client MUST check EncryptedExtensions for forbidden extensions, and if it finds any, it MUST abort the handshake with an "illegal_parameter" alert ๐Ÿšจ

Structure of this message:

   struct {
       Extension extensions<0..2^16-1>;
   } EncryptedExtensions;

extensions: a list of extensions. For more info, check the table in Section 4.3. ๐Ÿ“‹

4.4.2. Certificate Request โœจ

okay so bestie, this is where the server gets a little nosy ๐Ÿ‘€ A server that's proving who it is with a certificate MAY optionally ask the client for a certificate too ๐ŸŽ€ If it does send this message, it MUST come right after EncryptedExtensions, no skipping ahead ๐Ÿ’…

Here's the shape of it ๐Ÿฆ‹

struct {
    opaque certificate_request_context<0..2^8-1>;
    Extension extensions<0..2^16-1>;
} CertificateRequest;

The certificate_request_context is basically an ID tag for this particular request ๐Ÿท๏ธ It's an opaque string, and the client echoes it back in its own Certificate message. This value MUST be unique within the scope of this connection, which is what stops someone from replaying old client CertificateVerify messages ๐Ÿ™…โ€โ™€๏ธ It SHALL be zero length unless it's being used for the post-handshake authentication stuff in Section 4.7.2. And when the server is asking for post-handshake auth, it SHOULD make that context unpredictable to the client (randomly generating it works great). The reason is sneaky: an attacker who has temporary access to the client's private key could otherwise pre-compute valid CertificateVerify messages in advance ๐Ÿ˜ญ Not me being paranoid, that's just good security hygiene fr fr ๐Ÿ”

Then there's extensions, which is a list describing the parameters of the certificate being requested ๐Ÿงš The "signature_algorithms" extension MUST be specified, and other extensions MAY optionally be included if they're defined for this message. Clients MUST ignore any extensions they don't recognize ๐Ÿซถ Those are the rules, no cap.

Now, quick history lesson, because it's kinda iconic ๐ŸŒˆ In older TLS versions, the CertificateRequest carried a list of signature algorithms and certificate authorities the server would accept. In TLS 1.3, the signature algorithms part moved into the "signature_algorithms" and optionally "signature_algorithms_cert" extensions. The certificate authorities part is now handled by the "certificate_authorities" extension (check out Section 4.3.4 for that one) ๐Ÿ’œ

Next up, resumption PSKs and external PSKs, which have their own rules ๐Ÿ™ˆ Servers that are authenticating with a resumption PSK MUST NOT send the CertificateRequest in the main handshake. They MAY still send it during post-handshake authentication (Section 4.7.2), but only if the client has sent the "post_handshake_auth" extension (Section 4.3.6). For external PSKs, unless some other specification says otherwise, servers MUST NOT send CertificateRequest in the main handshake or ask for post-handshake authentication either. There's an extension in [RFC8773] that lets this happen, but it hasn't gotten as much analysis as this spec, so tread carefully bestie โš ๏ธ


4.5. Authentication Messages ๐Ÿ”‘

As we talked about in Section 2, TLS mostly uses the same few messages for authentication, key confirmation, and handshake integrity: Certificate, CertificateVerify, and Finished ๐Ÿ’– (The PSK binders do key confirmation too, just in a similar way โœจ) These three always show up as the last messages in their handshake flight. Certificate and CertificateVerify only get sent in certain situations, which we'll break down below. Finished is always sent as part of the Authentication Block ๐ŸŽ€ All of these are encrypted under keys derived from the [sender]_handshake_traffic_secret, except during post-handshake authentication ๐Ÿ”

Every authentication computation takes the same three inputs ๐Ÿง

  • The certificate and signing key to be used.
  • A Handshake Context, which is the list of messages included in the transcript hash.
  • A Base Key, used to compute a MAC key.

Based on those inputs, the messages carry the following ๐Ÿ’…

Certificate: the certificate to be used for authentication, plus any supporting certificates in the chain. Heads up, certificate-based client authentication is NOT available in PSK handshake flows (including 0-RTT) ๐Ÿ™…โ€โ™€๏ธ

CertificateVerify: a signature over the value Transcript-Hash(Handshake Context, Certificate) ๐Ÿ–Š๏ธ

Finished: a MAC over the value Transcript-Hash(Handshake Context, Certificate, CertificateVerify), using a MAC key derived from the Base Key ๐Ÿ”’

Here's the table for which Handshake Context and MAC Base Key go with each scenario ๐ŸŒธ

+==============+====================+===============================+
|Mode          |Handshake Context   |Base Key                       |
+==============+====================+===============================+
|Server        |ClientHello ...     |server_handshake_traffic_secret|
|              |later of            |                               |
|              |EncryptedExtensions/|                               |
|              |CertificateRequest  |                               |
+--------------+--------------------+-------------------------------+
|Client        |ClientHello ...     |client_handshake_traffic_secret|
|              |later of server     |                               |
|              |Finished/           |                               |
|              |EndOfEarlyData      |                               |
+--------------+--------------------+-------------------------------+
|Post-Handshake|ClientHello ...     |[sender]_application_traffic_  |
|              |client Finished +   |secret_N                       |
|              |CertificateRequest  |                               |
+--------------+--------------------+-------------------------------+

Table 2: Authentication Inputs ๐Ÿ“‹


4.5.1. Certificate ๐ŸŽ€

okay this message is how each endpoint hands over its certificate chain to the other side ๐Ÿฆ‹

The server MUST send a Certificate message whenever the agreed-upon key exchange method uses certificates for authentication. This covers all the key exchange methods defined in this document except PSK ๐Ÿ’œ

The client MUST send a Certificate message if and only if the server has asked for certificate-based client authentication with a CertificateRequest message (Section 4.4.2) ๐Ÿ™ˆ If the server asks but the client has no suitable certificate, the client MUST still send a Certificate message, just one with no certificates in it (so "certificate_list" has length 0). A Finished message MUST be sent no matter whether the Certificate message is empty or not ๐Ÿ”

Structure of this message ๐Ÿงš

enum {
    X509(0),
    RawPublicKey(2),
    (255)
} CertificateType;

struct {
    select (certificate_type) {
        case RawPublicKey:
          /* From RFC 7250 ASN.1_subjectPublicKeyInfo */
          opaque ASN1_subjectPublicKeyInfo<1..2^24-1>;

        case X509:
          opaque cert_data<1..2^24-1>;
    };
    Extension extensions<0..2^16-1>;
} CertificateEntry;

struct {
    opaque certificate_request_context<0..2^8-1>;
    CertificateEntry certificate_list<0..2^24-1>;
} Certificate;

certificate_request_context: if this message is replying to a CertificateRequest, it's the value from that request. Otherwise (for server authentication), this field SHALL be zero length ๐Ÿซง

certificate_list: a list (chain) of CertificateEntry structures, each holding one certificate and a list of extensions ๐ŸŒˆ

extensions: a list of extension values for the CertificateEntry. The "Extension" format is defined in Section 4.3 โœจ Right now the valid extensions for server certificates are the OCSP Status extension [RFC6066] and the SignedCertificateTimestamp extension [RFC6962], and future extensions might show up here too ๐Ÿ‘‘ Extensions in the server's Certificate MUST correspond to ones from the ClientHello. Extensions in the client's Certificate MUST correspond to extensions in the server's CertificateRequest ๐Ÿ’… If an extension applies to the whole chain, it SHOULD be included in the first CertificateEntry ๐Ÿ“

If the corresponding certificate type extension ("server_certificate_type" or "client_certificate_type") was not negotiated in EncryptedExtensions, or the X.509 certificate type was negotiated, then each CertificateEntry holds a DER-encoded X.509 certificate ๐ŸŽ€ The sender's certificate MUST be first in the list. Each certificate after that SHOULD directly certify the one right before it. Trust anchors have to be distributed independently, so a certificate that specifies a trust anchor MAY be left out of the chain, as long as supported peers are known to already have it ๐Ÿฆ‹

Note: before TLS 1.3, certificate_list ordering had to follow the rule that each certificate certifies the one right before it, but some implementations were a bit flexible about it ๐Ÿ™ƒ Servers sometimes send both a current and a deprecated intermediate for transition reasons, and some are just configured wrong, yet those chains can still validate properly ๐Ÿ’€ For maximum compatibility, all implementations SHOULD be ready to handle possibly extra certificates and arbitrary orderings from any TLS version, EXCEPT the end-entity certificate, which MUST come first ๐Ÿ‘‘

If the RawPublicKey certificate type was negotiated, then certificate_list MUST contain no more than one CertificateEntry, and it has to hold an ASN1_subjectPublicKeyInfo value as defined in [RFC7250], Section 3 ๐ŸŒธ

The OpenPGP certificate type [RFC6091] MUST NOT be used with TLS 1.3 ๐Ÿšซ

The server's certificate_list MUST always be non-empty. A client will send an empty certificate_list if it doesn't have a suitable certificate to answer the server's authentication request ๐Ÿซถ


4.5.1.1. OCSP Status and SCT Extensions ๐Ÿ”

[RFC6066] and [RFC6961] both define extensions that let the server send OCSP responses to the client ๐Ÿงš In TLS 1.2 and below, the server replies with an empty extension to say "yep, negotiated," and the OCSP info travels in a CertificateStatus message. In TLS 1.3, the server's OCSP info rides along in an extension inside the CertificateEntry for the matching certificate โœจ Specifically, the body of the server's "status_request" extension MUST be a CertificateStatus structure as defined in [RFC6066], which is interpreted as defined in [RFC6960] ๐Ÿ’–

Note: the status_request_v2 extension [RFC6961] is deprecated ๐Ÿ’€ TLS 1.3 servers MUST NOT act on its presence or on any information in it when processing ClientHello messages. In particular, they MUST NOT send the status_request_v2 extension in EncryptedExtensions, CertificateRequest, or Certificate messages. TLS 1.3 servers MUST be able to process ClientHello messages that include it, though, because clients might still send it when they want to use it in earlier protocol versions ๐Ÿฅน

A server MAY ask the client to present an OCSP response with its certificate by sending an empty "status_request" extension in its CertificateRequest message. If the client decides to send an OCSP response, the body of its "status_request" extension MUST be a CertificateStatus structure as defined in [RFC6066] ๐ŸŒˆ

Similarly, [RFC6962] gives a way for a server to send a Signed Certificate Timestamp (SCT) as an extension in the ServerHello in TLS 1.2 and below. In TLS 1.3, the server's SCT info is carried in an extension in the CertificateEntry instead ๐Ÿฆ‹

4.5.1.2. Certificate Selection

okay so this is where we check which certs are allowed to show up at the party ๐Ÿ’… here are the rules for certificates sent by the client or the server:

  • The certificate type MUST be X.509v3 [RFC5280], unless explicitly negotiated otherwise (e.g., [RFC7250]). ๐ŸŽ€
  • The end-entity certificate MUST allow the key to be used for signing with a signature scheme indicated in the peer's "signature_algorithms" extension (see Section 4.3.3). That is, the digitalSignature bit MUST be set if the Key Usage extension is present, and the public key (with associated restrictions) MUST be compatible with some supported signature scheme. ๐Ÿ”‘
  • If the peer sent a "certificate_authorities" extension, at least one of the certificates in the certificate chain SHOULD be issued by one of the listed CAs. ๐Ÿ›๏ธ

The following rule additionally applies to certificates sent by the client:

  • If the CertificateRequest message contained a non-empty "oid_filters" extension, the end-entity certificate MUST match the extension OIDs that are recognized by the client, as described in Section 4.3.5. ๐Ÿงš

The following rule additionally applies to certificates sent by the server:

  • The "server_name" [RFC6066] extension is used to guide certificate selection. As servers MAY require the presence of the "server_name" extension, clients SHOULD send this extension when the server is identified by name. ๐ŸŒธ

All certificates provided by the sender MUST be signed by a signature algorithm advertised by the peer, if it is able to provide such a chain (see Section 4.3.3). Certificates that are self-signed or certificates that are expected to be trust anchors are not validated as part of the chain and therefore MAY be signed with any algorithm. ๐Ÿฆ‹

If the sender is the server, and the server cannot produce a certificate chain that is signed only via the indicated supported algorithms, then it SHOULD continue the handshake by sending a certificate chain of its choice that may include algorithms that are not known to be supported by the client. This fallback chain MUST NOT use the deprecated SHA-1 hash, unless the client specifically advertises that it is willing to accept SHA-1. ๐Ÿ™ˆ

If the sender is the client, the client MAY use a fallback chain as above or continue the handshake anonymously. ๐Ÿซถ

If the receiver cannot construct an acceptable chain using the provided certificates and decides to abort the handshake, then it MUST abort the handshake with an appropriate certificate-related alert (by default, "unsupported_certificate"; see Section 6.2 for more information). ๐Ÿ’€

If the sender has multiple certificates, it chooses one of them based on the above-mentioned criteria (in addition to other criteria, such as transport-layer endpoint, local configuration, and preferences). โœจ


4.5.1.3. Receiving a Certificate Message

In general, detailed certificate validation procedures are out of scope for TLS (see [RFC5280]). This section provides TLS-specific requirements. ๐Ÿ“œ

If the server supplies an empty Certificate message, the client MUST abort the handshake with a "decode_error" alert. ๐Ÿ˜ญ

If the client does not send any certificates (i.e., it sends an empty Certificate message), the server MAY at its discretion either continue the handshake without client authentication, or abort the handshake with a "certificate_required" alert. Also, if some aspect of the certificate chain was unacceptable (e.g., it was not signed by a known, trusted CA), the server MAY at its discretion either continue the handshake (considering the client unauthenticated) or abort the handshake. ๐Ÿ“

Any endpoint receiving any certificate which it would need to validate using any signature algorithm using an MD5 hash MUST abort the handshake with a "bad_certificate" alert. SHA-1 is deprecated and it is RECOMMENDED that any endpoint receiving any certificate which it would need to validate using any signature algorithm using a SHA-1 hash abort the handshake with a "bad_certificate" alert. For clarity, this means that endpoints can accept these algorithms for certificates that are self-signed or are trust anchors. ๐Ÿšซ

All endpoints are RECOMMENDED to transition to SHA-256 or better as soon as possible to maintain interoperability with implementations currently in the process of phasing out SHA-1 support. ๐ŸŒˆ

Note that a certificate containing a key for one signature algorithm MAY be signed using a different signature algorithm (for instance, an RSA key signed with an ECDSA key). ๐Ÿงฉ


4.5.2. Certificate Verify

This message is used to provide explicit proof that an endpoint possesses the private key corresponding to its certificate. The CertificateVerify message also provides integrity for the handshake up to this point. Servers MUST send this message when authenticating via a certificate. Clients MUST send this message whenever authenticating via a certificate (i.e., when the Certificate message is non-empty). When sent, this message MUST appear immediately after the Certificate message and immediately prior to the Finished message. ๐Ÿ”

Structure of this message:

struct {
    SignatureScheme algorithm;
    opaque signature<0..2^16-1>;
} CertificateVerify;

The algorithm field specifies the signature algorithm used (see Section 4.3.3 for the definition of this type). The signature is a digital signature using that algorithm. The content that is covered under the signature is the hash output as described in Section 4.1, namely:

Transcript-Hash(Handshake Context, Certificate)

The digital signature is then computed over the concatenation of:

  • A string that consists of octet 32 (0x20) repeated 64 times
  • The context string (defined below)
  • A single 0 byte which serves as the separator
  • The content to be signed

This structure is intended to prevent an attack on previous versions of TLS in which the ServerKeyExchange format meant that attackers could obtain a signature of a message with a chosen 32-byte prefix (ClientHello.random). The initial 64-byte pad clears that prefix along with the server-controlled ServerHello.random. ๐Ÿ›ก๏ธ

The context string for a server signature is "TLS 1.3, server CertificateVerify". The context string for a client signature is "TLS 1.3, client CertificateVerify". It is used to provide separation between signatures made in different contexts, helping against potential cross-protocol attacks. ๐Ÿชž

For example, if the transcript hash was 32 bytes of 01 (this length would make sense for SHA-256), the content covered by the digital signature for a server CertificateVerify would be:

2020202020202020202020202020202020202020202020202020202020202020
2020202020202020202020202020202020202020202020202020202020202020
544c5320312e332c207365727665722043657274696669636174655665726966
79
00
0101010101010101010101010101010101010101010101010101010101010101

On the sender side, the process for computing the signature field of the CertificateVerify message takes as input:

  • The content covered by the digital signature
  • The private signing key corresponding to the certificate sent in the previous message

If the CertificateVerify message is sent by a server, the signature algorithm MUST be one offered in the client's "signature_algorithms" extension unless no valid certificate chain can be produced without unsupported algorithms (see Section 4.3.3). ๐ŸŽฏ

If sent by a client, the signature algorithm used in the signature MUST be one of those present in the supported_signature_algorithms field of the "signature_algorithms" extension in the CertificateRequest message. ๐ŸŽ€

In addition, the signature algorithm MUST be compatible with the key in the sender's end-entity certificate. The SHA-1 algorithm MUST NOT be used in any signatures of CertificateVerify messages. All SHA-1 signature algorithms in this specification are defined solely for use in legacy certificates and are not valid for CertificateVerify signatures. ๐Ÿšท

The receiver of a CertificateVerify message MUST verify the signature field. The verification process takes as input:

  • The content covered by the digital signature
  • The public key contained in the end-entity certificate found in the associated Certificate message
  • The digital signature received in the signature field of the CertificateVerify message

If the verification fails, the receiver MUST terminate the handshake with a "decrypt_error" alert. ๐Ÿฅ€


4.5.3. Finished

The Finished message is the final message in the Authentication Block. It is essential for providing authentication of the handshake and of the computed keys. ๐Ÿ”

Recipients of Finished messages MUST verify that the contents are correct and if incorrect MUST terminate the connection with a "decrypt_error" alert. ๐Ÿšจ

Once a side has sent its Finished message and has received and validated the Finished message from its peer, it may begin to send and receive Application Data over the connection. There are two settings in which it is permitted to send data prior to receiving the peer's Finished:

  1. Clients sending 0-RTT data as described in Section 4.3.10.
  2. Servers MAY send data after sending their first flight, but because the handshake is not yet complete, they have no assurance of either the peer's identity or its liveness (i.e., the ClientHello might have been replayed). ๐Ÿ‘€

The key used to compute the Finished message is computed from the Base Key defined in Section 4.5 using HKDF (see Section 7.1). Specifically:

finished_key =
    HKDF-Expand-Label(BaseKey, "finished", "", Hash.length)

Structure of this message:

struct {
    opaque verify_data[Hash.length];
} Finished;

The verify_data value is computed as follows:

verify_data =
    HMAC(finished_key,
         Transcript-Hash(Handshake Context,
                         Certificate*, CertificateVerify*))

The asterisk (*) marks the parts that are only included if present. โœจ

HMAC [RFC2104] uses the Hash algorithm for the handshake. As noted above, the HMAC input can generally be implemented by a running hash, i.e., just the handshake hash at this point. ๐Ÿง

In previous versions of TLS, the verify_data was always 12 octets long. In TLS 1.3, it is the size of the HMAC output for the Hash used for the handshake. ๐Ÿ“

Note: Alerts and any other non-handshake record types are not handshake messages and are not included in the hash computations. ๐Ÿ™ƒ

Any records following a Finished message MUST be encrypted under the appropriate application traffic key as described in Section 7.2. In particular, this includes any alerts sent by the server in response to client Certificate and CertificateVerify messages. ๐Ÿ”’


4.6. End of Early Data

struct {} EndOfEarlyData;

If the server sent an "early_data" extension in EncryptedExtensions, the client MUST send an EndOfEarlyData message after receiving the server Finished. If the server does not send an "early_data" extension in EncryptedExtensions, then the client MUST NOT send an EndOfEarlyData message. This message indicates that all 0-RTT application_data messages, if any, have been transmitted and that the following records are protected under handshake traffic keys. Servers MUST NOT send this message, and clients receiving it MUST terminate the connection with an "unexpected_message" alert. This message is encrypted under keys derived from the client_early_traffic_secret. ๐ŸŒ™

4.7. Post-Handshake Messages ๐Ÿ’Œ

TLS also lets other messages get sent after the main handshake is done โœจ These use a handshake content type and get encrypted under the right application traffic key ๐Ÿ”

4.7.1. New Session Ticket Message ๐ŸŽซ

If the client's hello had a suitable psk_key_exchange_modes extension, the server MAY send a NewSessionTicket message at any time after it has received the client Finished message ๐Ÿ’– The NewSessionTicket message creates a unique link between the ticket value and a secret PSK that's derived from the resumption secret (see Section 7) ๐Ÿฆ‹

The client MAY use this PSK for future handshakes by putting the ticket value in the pre_shared_key extension in its ClientHello (Section 4.3.11). Clients that get a NewSessionTicket message but don't support resumption MUST silently ignore it ๐Ÿ™ˆ Resumption MAY happen while the original connection is still open ๐ŸŒธ Servers MAY send multiple tickets on a single connection, either right after each other or after specific events (see Appendix C.4) ๐Ÿงš For example, the server might send a new ticket after post-handshake authentication, so the ticket carries the extra client auth state too ๐Ÿซถ Multiple tickets are really useful for clients, for things like:

  • Opening multiple parallel HTTP connections.
  • Doing connection racing across interfaces and address families with (for example) Happy Eyeballs [RFC8305] or related techniques ๐ŸŽ๏ธ

Any ticket MUST only be resumed with a cipher suite that has the same KDF hash algorithm as the one used to set up the original connection ๐Ÿ’…

Clients MUST only resume if the new SNI value is valid for the server certificate presented in the original session ๐Ÿ”ฅ They SHOULD only resume if the SNI value matches the one from the original session. This one's a performance thing, okay? Normally there's no reason to expect that different servers covered by a single certificate would accept each other's tickets, so trying to resume in that case would waste a single-use ticket ๐Ÿ˜ญ If some indication is provided (externally or by any other means), clients MAY resume with a different SNI value ๐Ÿ‘‘

On resumption, if an implementation is reporting an SNI value to the calling application, it MUST use the value sent in the resumption ClientHello, not the one from the previous session ๐Ÿ’œ Note that if a server implementation declines all PSK identities with different SNI values, these two values are always the same โœจ

Note: Although the resumption secret depends on the client's second flight, a server that doesn't request certificate-based client authentication MAY compute the rest of the transcript independently and then send a NewSessionTicket immediately upon sending its Finished, instead of waiting for the client Finished ๐ŸŒˆ This might be a good fit when the client is expected to open multiple TLS connections in parallel and would benefit from the lower overhead of a resumption handshake, for example ๐Ÿ“

Here's the struct, bestie ๐Ÿ‘‡

struct {
    uint32 ticket_lifetime;
    uint32 ticket_age_add;
    opaque ticket_nonce<0..255>;
    opaque ticket<1..2^16-1>;
    Extension extensions<0..2^16-1>;
} NewSessionTicket;
  • ticket_lifetime: The lifetime in seconds, as a 32-bit unsigned integer in network byte order, counted from the time of ticket issuance. Servers MUST NOT use any value greater than 604800 seconds (7 days) โณ A value of zero means the ticket should be discarded immediately. Clients MUST NOT use tickets for longer than 7 days after issuance, regardless of the ticket_lifetime, and MAY delete tickets earlier based on local policy. A server MAY treat a ticket as valid for a shorter period than what's stated in the ticket_lifetime ๐Ÿ•ฐ๏ธ
  • ticket_age_add: A securely generated, random 32-bit value that obscures the age of the ticket the client includes in the pre_shared_key extension ๐Ÿ•ต๏ธโ€โ™€๏ธ The client-side ticket age gets added to this value modulo 2^32, and the result is what the client actually transmits. The server MUST generate a fresh value for each ticket it sends ๐ŸŽ€
  • ticket_nonce: A per-ticket value that's unique across all tickets issued on this connection ๐Ÿ”‘
  • ticket: The value of the ticket to be used as the PSK identity. The ticket itself is an opaque label. It MAY be either a database lookup key or a self-encrypted and self-authenticated value ๐Ÿฆ‹
  • extensions: A list of extension values for the ticket. The "Extension" format is defined in Section 4.3. Clients MUST ignore unrecognized extensions ๐Ÿ™ˆ

The only extension defined for NewSessionTicket right now is early_data, which says the ticket can be used to send 0-RTT data (Section 4.3.10) ๐Ÿš€ It has this value:

  • max_early_data_size: The maximum amount of 0-RTT data the client is allowed to send when using this ticket, in bytes. Only the Application Data payload counts (that's the plaintext, but not the padding or the inner content type byte) ๐Ÿ“ A server that receives more than max_early_data_size bytes of 0-RTT data SHOULD terminate the connection with an "unexpected_message" alert ๐Ÿšจ Also, servers that reject early data because they lack cryptographic material won't be able to tell padding apart from content, so clients SHOULD NOT depend on being able to send big piles of padding in early data records ๐Ÿ™ƒ

The PSK tied to the ticket is computed like this:

HKDF-Expand-Label(resumption_secret,
                  "resumption", ticket_nonce, Hash.length)

Since the ticket_nonce value is different for each NewSessionTicket message, a different PSK gets derived for each ticket ๐Ÿ’•

Okay, real talk: in principle you could keep issuing new tickets that indefinitely extend the lifetime of the keying material from an original non-PSK handshake (which was most likely tied to the peer's certificate) ๐Ÿ˜ณ It is RECOMMENDED that implementations put limits on the total lifetime of that keying material. Those limits should take into account the lifetime of the peer's certificate, the likelihood of an intervening revocation, and the time since the peer's online CertificateVerify signature ๐Ÿง

4.7.2. Post-Handshake Authentication ๐Ÿชช

When the client has sent the post_handshake_auth extension (see Section 4.3.6), a server MAY request certificate-based client authentication at any time after the handshake has finished, by sending a CertificateRequest message ๐Ÿ“จ The client MUST respond with the right Authentication messages (see Section 4.5) ๐Ÿ’… If the client chooses to authenticate, it MUST send Certificate, CertificateVerify, and Finished. If it declines, it MUST send a Certificate message with no certificates in it, followed by Finished ๐Ÿฅบ All of the client's messages for a given response MUST show up consecutively on the wire, with no messages of other types or from other responses in between ๐Ÿงต

A client that gets a post-handshake CertificateRequest message without having sent the post_handshake_auth extension MUST send a fatal "unexpected_message" alert ๐Ÿ›‘

Note: Because certificate-based client authentication could involve prompting the user, servers MUST be prepared for some delay, including receiving an arbitrary number of other messages between sending the CertificateRequest and getting a response ๐Ÿ• Also, clients that receive multiple CertificateRequests in quick succession MAY respond to them in a different order than they came in (the certificate_request_context value lets the server sort out which response is which) ๐Ÿงฉ

4.7.3. Key and Initialization Vector Update ๐Ÿ”„

The KeyUpdate handshake message is used to say that the sender is updating its sending cryptographic keys ๐Ÿ—๏ธ Either peer can send this message after it has sent a Finished message. Implementations that receive a KeyUpdate message before receiving a Finished message MUST terminate the connection with an "unexpected_message" alert ๐Ÿšซ After sending a KeyUpdate message, the sender SHALL send all of its traffic using the next generation of keys, computed as described in Section 7.2 โœจ When a receiver gets a KeyUpdate, it MUST update its receiving keys ๐Ÿ’ซ

enum {
    update_not_requested(0), update_requested(1), (255)
} KeyUpdateRequest;

struct {
    KeyUpdateRequest request_update;
} KeyUpdate;
  • request_update: Says whether the recipient of the KeyUpdate should respond with its own KeyUpdate. If an implementation receives any other value, it MUST terminate the connection with an "illegal_parameter" alert ๐Ÿ™…โ€โ™€๏ธ

If the request_update field is set to "update_requested", then the receiver MUST send its own KeyUpdate with request_update set to "update_not_requested" before sending its next Application Data record ๐ŸŽ This lets either side force an update on the whole connection, but it means an implementation that gets multiple KeyUpdates while it's silent will respond with just a single update ๐Ÿฆ„ Until it gets a subsequent KeyUpdate from the peer, the sender MUST NOT send another KeyUpdate with request_update set to "update_requested" ๐Ÿ›‘

Implementations might receive an arbitrary number of messages between sending a KeyUpdate with request_update set to "update_requested" and getting the peer's KeyUpdate, including unrelated KeyUpdates, because those messages may already be in flight ๐Ÿ›ซ Still, since send and receive keys come from independent traffic secrets, keeping the receive traffic secret doesn't threaten the forward secrecy of data sent before the sender changed keys ๐Ÿ”

If implementations independently send their own KeyUpdates with request_update set to "update_requested" and they cross in flight, then each side will also send a response, so each side ends up incrementing by two generations ๐Ÿ’ฅ

Both sender and receiver MUST encrypt their KeyUpdate messages with the old keys ๐Ÿ”‘ Also, both sides MUST enforce that a KeyUpdate with the old key is received before accepting any messages encrypted with the new key. If they don't, that may allow message truncation attacks ๐Ÿ˜ฑ

With a 128-bit key like in AES-128, rekeying 2^64 times has a high probability of key reuse within a given connection ๐Ÿฅถ Even if the key repeats, the IV is also generated independently, so the chance of a joint key/IV collision is much lower ๐Ÿ™Œ For an extra margin of security, sending implementations MUST NOT allow the epoch (and so the number of key updates) to go past 2^48-1. To leave room for changing this value later (for example, for ciphers with keys longer than 128 bits), receiving implementations MUST NOT enforce this rule ๐ŸŒŸ If a sending implementation receives a KeyUpdate with request_update set to "update_requested", it MUST NOT send its own KeyUpdate if that would push it past these limits, and it SHOULD instead ignore the "update_requested" flag ๐Ÿซฃ This might eventually mean the connection has to be terminated once the limits in Section 5.5 are hit ๐Ÿ’€

5. Record Protocol โœจ

Okay bestie, the record layer is where the magic happens ๐Ÿช„. It takes the messages you want to send, chops the data into manageable blocks, protects each record, and sends the result off. On the receiving end, incoming data gets verified, decrypted, and reassembled, then handed up to the higher-level clients ๐Ÿ’–.

Records are typed, which is what lets multiple higher-level protocols multiplex over the same record layer ๐Ÿฆ‹. This document specifies four content types: handshake, application_data, alert, and change_cipher_spec. The change_cipher_spec record only exists for compatibility purposes (see Appendix E.4) ๐ŸŽ€.

An implementation may receive an unencrypted record of type change_cipher_spec consisting of the single byte value 0x01 at any time after the first ClientHello message has been sent or received and before the peer's Finished message has been received. When that happens, it MUST simply drop it without further processing ๐Ÿ™ˆ. This record can show up at a point in the handshake where the implementation is expecting protected records, so you have to detect that condition before you try to deprotect the record ๐Ÿ‘€. An implementation that receives any other change_cipher_spec value, or that receives a protected change_cipher_spec record, MUST abort the handshake with an "unexpected_message" alert ๐Ÿ’€. If an implementation detects a change_cipher_spec record received before the first ClientHello message or after the peer's Finished message, it MUST be treated as an unexpected record type. Stateless servers might not be able to tell these cases apart from the allowed ones, though ๐Ÿงš.

Implementations MUST NOT send record types that aren't defined in this document unless some extension negotiates them ๐Ÿšซ. If a TLS implementation receives an unexpected record type, it MUST terminate the connection with an "unexpected_message" alert ๐Ÿ˜ญ. New record content type values get assigned by IANA in the TLS ContentType registry, as described in Section 11 ๐Ÿ“‹.

5.1. Record Layer

The record layer chops information blocks into TLSPlaintext records, carrying data in chunks of 2^14 bytes or less ๐Ÿ“. How message boundaries get handled depends on the underlying ContentType. Any future content types MUST specify appropriate rules. These rules are stricter than what TLS 1.2 enforced, so pay attention ๐Ÿ‘‘.

Handshake messages MAY be coalesced into a single TLSPlaintext record or fragmented across several records, provided that:

  • Handshake messages MUST NOT be interleaved with other record types. So if a handshake message gets split over two or more records, there MUST NOT be any other records between them.
  • Handshake messages MUST NOT span key changes. Implementations MUST verify that all messages immediately preceding a key change line up with a record boundary. If they don't, they MUST terminate the connection with an "unexpected_message" alert. Because the ClientHello, EndOfEarlyData, ServerHello, Finished, and KeyUpdate messages can immediately precede a key change, implementations MUST send these messages lined up with a record boundary ๐ŸŽฏ.

Implementations MUST NOT send zero-length fragments of Handshake types, even if those fragments contain padding ๐Ÿ™….

Alert messages (Section 6) MUST NOT be fragmented across records, and multiple alert messages MUST NOT be coalesced into a single TLSPlaintext record. In other words, a record with an Alert type MUST contain exactly one message ๐Ÿšจ.

Application Data messages contain data that's opaque to TLS ๐Ÿ”’. They're always protected. Zero-length fragments of Application Data (that's TLSInnerPlaintext records of type application_data with zero-length content) MAY be sent, since they're potentially useful as a traffic analysis countermeasure ๐Ÿ•ต๏ธ. Application Data fragments MAY be split across multiple records or coalesced into a single record ๐Ÿฐ.

      enum {
          invalid(0),
          change_cipher_spec(20),
          alert(21),
          handshake(22),
          application_data(23),
          (255)
      } ContentType;

      struct {
          ContentType type;
          ProtocolVersion legacy_record_version;
          uint16 length;
          opaque fragment[TLSPlaintext.length];
      } TLSPlaintext;

type: the higher-level protocol used to process the enclosed fragment ๐Ÿงฉ.

legacy_record_version: MUST be set to 0x0303 for all records generated by a TLS 1.3 implementation other than an initial ClientHello (i.e., one not generated after a HelloRetryRequest). It MAY also be 0x0301 for compatibility purposes. This field is deprecated and MUST be ignored for all purposes. Previous versions of TLS would use other values in this field under some circumstances ๐Ÿฅ€.

length: the length (in bytes) of the following TLSPlaintext.fragment. The length MUST NOT exceed 2^14 bytes. An endpoint that receives a record that exceeds this length MUST terminate the connection with a "record_overflow" alert ๐Ÿ“.

fragment: the data being transmitted. This value is transparent, and it's treated as an independent block to be dealt with by the higher-level protocol named in the type field ๐ŸŽ.

This document describes TLS 1.3, which uses the version 0x0304. That version value is historical, deriving from the use of 0x0301 for TLS 1.0 and 0x0300 for SSL 3.0. To maximize backward compatibility, a record containing an initial ClientHello SHOULD have version 0x0301 (reflecting TLS 1.0), and a record containing a second ClientHello or a ServerHello MUST have version 0x0303 (reflecting TLS 1.2). When negotiating prior versions of TLS, endpoints follow the procedure and requirements in Appendix E ๐Ÿ’….

When record protection hasn't been engaged yet, TLSPlaintext structures get written straight onto the wire. Once record protection has started, TLSPlaintext records are protected and sent the way the next section describes. Note that Application Data records MUST NOT be written to the wire unprotected (see Section 2 for details) ๐Ÿ›ก๏ธ.

5.2. Record Payload Protection

The record protection functions translate a TLSPlaintext structure into a TLSCiphertext structure, and the deprotection functions reverse that process. In TLS 1.3, unlike previous versions of TLS, all ciphers are modeled as "Authenticated Encryption with Associated Data" (AEAD) [RFC5116] ๐Ÿ”. AEAD functions give you one unified encryption-and-authentication operation that turns plaintext into authenticated ciphertext and back again ๐Ÿ’œ. Each encrypted record is a plaintext header followed by an encrypted body, and that body contains a type and optional padding ๐ŸŽ€.

      struct {
          opaque content[TLSPlaintext.length];
          ContentType type;
          uint8 zeros[length_of_padding];
      } TLSInnerPlaintext;

      struct {
          ContentType opaque_type = application_data; /* 23 */
          ProtocolVersion legacy_record_version = 0x0303; /* TLS v1.2 */
          uint16 length;
          opaque encrypted_record[TLSCiphertext.length];
      } TLSCiphertext;

content: the TLSPlaintext.fragment value, containing the byte encoding of a handshake or an alert message, or the raw bytes of the application's data to send ๐Ÿ“ฆ.

type: the TLSPlaintext.type value, containing the content type of the record ๐Ÿท๏ธ.

zeros: an arbitrary-length run of zero-valued bytes may appear in the cleartext after the type field. This gives senders an opportunity to pad any TLS record by a chosen amount, as long as the total stays within record size limits. See Section 5.4 for more details ๐Ÿซง.

opaque_type: the outer opaque_type field of a TLSCiphertext record is always set to the value 23 (application_data). This is for outward compatibility with middleboxes that are used to parsing previous versions of TLS. The actual content type of the record is found in TLSInnerPlaintext.type after decryption ๐Ÿ•ถ๏ธ.

legacy_record_version: the legacy_record_version field is always 0x0303. TLS 1.3 TLSCiphertexts aren't generated until after TLS 1.3 has been negotiated, so there are no historical compatibility concerns where other values might be received. Note that the handshake protocol, including the ClientHello and ServerHello messages, authenticates the protocol version, so this value is redundant ๐Ÿ™ƒ.

length: the length (in bytes) of the following TLSCiphertext.encrypted_record. It's the sum of the lengths of the content and the padding, plus one for the inner content type, plus any expansion added by the AEAD algorithm. The length MUST NOT exceed 2^14 + 256 bytes. An endpoint that receives a record that exceeds this length MUST terminate the connection with a "record_overflow" alert ๐Ÿšง.

encrypted_record: the AEAD-encrypted form of the serialized TLSInnerPlaintext structure ๐Ÿ”’.

AEAD algorithms take as input a single key, a nonce, a plaintext, and "additional data" to be included in the authentication check, as described in Section 2.1 of [RFC5116]. The key is either the client_write_key or the server_write_key. The nonce is derived from the sequence number and the client_write_iv or server_write_iv (see Section 5.3), and the additional data input is the record header. Here's how that breaks down:

      additional_data = TLSCiphertext.opaque_type ||
                        TLSCiphertext.legacy_record_version ||
                        TLSCiphertext.length

The plaintext input to the AEAD algorithm is the encoded TLSInnerPlaintext structure. Derivation of traffic keys is defined in Section 7.3 ๐Ÿ”‘.

The AEAD output is the ciphertext output from the AEAD encryption operation. The length of the plaintext is greater than the corresponding TLSPlaintext.length, because it includes TLSInnerPlaintext.type and any padding supplied by the sender. The length of the AEAD output will generally be larger than the plaintext, but by an amount that varies with the AEAD algorithm. Since the ciphers might incorporate padding, the amount of overhead could vary with different lengths of plaintext. Symbolically, it looks like this:

      AEADEncrypted =
          AEAD-Encrypt(write_key, nonce, additional_data, plaintext)

The encrypted_record field of TLSCiphertext is set to AEADEncrypted ๐ŸŽ.

To decrypt and verify, the cipher takes as input the key, nonce, additional data, and the AEADEncrypted value. The output is either the plaintext or an error indicating that the decryption failed. There's no separate integrity check. Symbolically, that looks like this:

 plaintext of encrypted_record =
     AEAD-Decrypt(peer_write_key, nonce, additional_data, AEADEncrypted)

If the decryption fails, the receiver MUST terminate the connection with a "bad_record_mac" alert ๐Ÿ’€.

An AEAD algorithm used in TLS 1.3 MUST NOT produce an expansion greater than 255 octets. An endpoint that receives a record from its peer with TLSCiphertext.length larger than 2^14 + 256 octets MUST terminate the connection with a "record_overflow" alert. This limit comes from the maximum TLSInnerPlaintext length of 2^14 octets, plus 1 octet for ContentType, plus the maximum AEAD expansion of 255 octets ๐Ÿงฎ.

5.3. Per-Record Nonce ๐ŸŽ€

okay so every record gets its own nonce, and it's built from a 64-bit sequence number โœจ There are two of these counters, one for reading and one for writing, and they're totally separate. Each one goes up by one after every record ๐Ÿ’… They both reset to zero at the start of a connection and any time the key changes, so the very first record sent under a traffic key MUST use sequence number 0 ๐Ÿงš

Because the sequence number is 64 bits, it should not wrap ๐Ÿ™ˆ If an implementation would have to wrap it, it MUST either rekey (Section 4.7.3) or terminate the connection. no cap, that's the only way out ๐Ÿ’€

Every AEAD algorithm tells you a range of nonce lengths, from N_MIN bytes up to N_MAX bytes of input [RFC5116]. The TLS per-record nonce length (iv_length) is the bigger of 8 bytes and N_MIN for that AEAD algorithm (see [RFC5116], Section 4). An AEAD algorithm where N_MAX is less than 8 bytes MUST NOT be used with TLS. Not me being picky, it's a hard rule ๐Ÿซถ Here's how the nonce for the AEAD construction gets built:

  1. The 64-bit record sequence number is encoded in network byte order and padded on the left with zeros to iv_length.
  2. The padded sequence number is XORed with either the static client_write_iv or server_write_iv (depending on the role).

The result (which is iv_length long) is the per-record nonce. It's giving math-but-make-it-pretty ๐ŸŒˆ

Note: this is a different construction from TLS 1.2, which used a partially explicit nonce. So if you're coming from 1.2, don't carry that brain habit over ๐Ÿ™ƒ

5.4. Record Padding ๐Ÿ“

okay, so encrypted TLS records can be padded to make the TLSCiphertext bigger. This lets the sender hide how big the traffic is from anyone watching ๐Ÿ‘€

When an implementation generates a TLSCiphertext record, it MAY choose to pad. An unpadded record is just one with a padding length of zero. Padding is a string of zero-valued bytes added to the ContentType field before encryption. Implementations MUST set all the padding octets to zeros before encrypting. Non-negotiable, bestie ๐Ÿ˜ค

Application Data records are allowed to have a zero-length TLSInnerPlaintext.content if the sender wants. This is lowkey useful for plausible-looking cover traffic, especially when whether anything is happening might be sensitive ๐Ÿฆ‹ But Handshake and Alert records MUST NOT be sent with a zero-length TLSInnerPlaintext.content. If one shows up anyway, the receiver MUST terminate the connection with an "unexpected_message" alert. Yikes ๐Ÿ’€

Here's how the padding gets checked: the record protection mechanism verifies it automatically. After a TLSCiphertext.encrypted_record decrypts successfully, the receiver scans from the end toward the beginning until it hits a non-zero octet. That non-zero octet is the content type of the message. iykyk, this is genius because it lets you pad any encrypted record by any size (anywhere from zero up to the TLS record size limits) without inventing new content types ๐Ÿง  Enforcing all-zero padding also makes padding errors easy to spot ๐Ÿ”

Implementations MUST limit their scanning to the cleartext returned from the AEAD decryption. If a receiver can't find a non-zero octet in that cleartext, it MUST terminate the connection with an "unexpected_message" alert. ๐Ÿ˜ญ

Padding doesn't change the overall record size limits: the full encoded TLSInnerPlaintext MUST NOT exceed 2^14 + 1 octets. If the maximum fragment length gets reduced (for example by the record_size_limit extension from [RFC8449]), then the reduced limit covers the full plaintext, including the content type and padding ๐Ÿ’œ

Picking a padding policy (when to pad and how much) is a complicated topic and is out of scope for this spec ๐Ÿ™…โ€โ™€๏ธ If the application-layer protocol on top of TLS has its own padding, it may be better to pad Application Data records up there in the application layer. Padding for encrypted Handshake or Alert records still has to be handled at the TLS layer, though ๐Ÿซ  Later documents might define padding selection algorithms, or a padding policy request mechanism through TLS extensions or some other means โœจ

5.5. Limits on Key Usage ๐Ÿ”

there are cryptographic limits on how much plaintext can be safely encrypted under a given set of keys. [AEAD-LIMITS] analyzes these limits, assuming the underlying primitive (AES or ChaCha20) has no weaknesses ๐Ÿง Implementations MUST either close the connection or do a key update as described in Section 4.7.3 before reaching these limits. Also, a KeyUpdate isn't possible for early data, so implementations MUST NOT exceed the limits when sending early data. Receiving implementations SHOULD NOT enforce these limits, since future analyses may update the values ๐ŸŒธ

For AES-GCM, up to 2^24.5 full-size records (about 24 million) may be encrypted under a given set of keys while keeping a safety margin of about 2^-57 for Authenticated Encryption (AE) security ๐Ÿ”ฅ For ChaCha20/Poly1305, the record sequence number would wrap before the safety limit is reached, so that's a whole different situation ๐Ÿ˜ฌ

  1. Alert Protocol ๐Ÿšจ

TLS has an Alert content type for closure info and errors. Like other messages, alerts are encrypted under the current connection state ๐Ÿ”’

Alert messages carry a description of the alert, plus a legacy field that held the severity level in earlier TLS versions. Alerts split into two classes: closure alerts and error alerts. In TLS 1.3, severity is implicit in the alert type being sent, so the "level" field can safely be ignored ๐Ÿ’โ€โ™€๏ธ The "close_notify" alert means one direction of the connection is closing in an orderly way. When one is received, the TLS implementation SHOULD indicate end-of-data to the application ๐Ÿ“

Error alerts mean the connection is aborting (see Section 6.2). When an error alert comes in, the TLS implementation SHOULD indicate an error to the application, and MUST NOT allow any further data to be sent or received on the connection. Servers and clients MUST forget the secret values and keys established in failed connections, except for PSKs tied to session tickets, which SHOULD be discarded if possible ๐Ÿ—‘๏ธ

All the alerts listed in Section 6.2 MUST be sent with AlertLevel=fatal, and MUST be treated as error alerts when received, no matter what AlertLevel is in the message. Unknown Alert types MUST be treated as error alerts too ๐Ÿ™Œ

Note: TLS defines two generic alerts (see Section 6) for when a message fails to parse. Peers that receive a message which can't be parsed according to the syntax (for example, a length that runs past the message boundary or an out-of-range length) MUST terminate the connection with a "decode_error" alert. Peers that receive a message which is syntactically correct but semantically invalid (for example, a DHE share of p - 1, or an invalid enum) MUST terminate the connection with an "illegal_parameter" alert. ๐Ÿ’€
enum { warning(1), fatal(2), (255) } AlertLevel;

enum {
    close_notify(0),
    unexpected_message(10),
    bad_record_mac(20),
    record_overflow(22),
    handshake_failure(40),
    bad_certificate(42),
    unsupported_certificate(43),
    certificate_revoked(44),
    certificate_expired(45),
    certificate_unknown(46),
    illegal_parameter(47),
    unknown_ca(48),
    access_denied(49),
    decode_error(50),
    decrypt_error(51),
    protocol_version(70),
    insufficient_security(71),
    internal_error(80),
    inappropriate_fallback(86),
    user_canceled(90),
    missing_extension(109),
    unsupported_extension(110),
    unrecognized_name(112),
    bad_certificate_status_response(113),
    unknown_psk_identity(115),
    certificate_required(116),
    general_error(117),
    no_application_protocol(120),
    (255)
} AlertDescription;

struct {
    AlertLevel level;
    AlertDescription description;
} Alert;

6.1. Closure Alerts ๐Ÿšช

The client and the server have to both know the connection is ending, or else you get a truncation attack ๐Ÿ˜ฑ

close_notify: This alert tells the recipient that the sender won't send any more messages on this connection. Any data received after a closure alert has arrived MUST be ignored. This alert MUST be sent with AlertLevel=warning. ๐Ÿ‘‹

user_canceled: This alert tells the recipient that the sender is canceling the handshake for a reason that's unrelated to a protocol failure. If a user cancels something after the handshake is already complete, just closing the connection by sending a "close_notify" is more appropriate. This alert MUST be followed by a "close_notify". It generally has AlertLevel=warning. Receiving implementations SHOULD keep reading data from the peer until a "close_notify" comes in, though they MAY log or otherwise record them ๐Ÿ“

Either party MAY start closing its write side of the connection by sending a "close_notify" alert. If a transport-level close shows up before a "close_notify", the receiver can't know that all the data sent has actually arrived ๐Ÿคทโ€โ™€๏ธ

Each party MUST send a "close_notify" alert before closing its write side of the connection, unless it has already sent some error alert. This doesn't affect its read side of the connection at all ๐Ÿ’… Note that this is a change from TLS versions before 1.3, where implementations were required to react to a "close_notify" by discarding pending writes and sending their own immediate "close_notify". That older requirement could cause truncation on the read side. Both parties don't need to wait for a "close_notify" before closing their read side, though waiting would open the door to truncation ๐Ÿซฃ

Application protocols MAY choose to flush their send buffers and immediately send a "close_notify" upon receiving one, but this lets an attacker influence the data the peer receives, by delaying the "close_notify" or by delaying the transport-level delivery of the application's packets. These issues can be handled at the application layer, for example by ignoring packets received after sending the "close_notify" ๐Ÿ›ก๏ธ

If the application protocol using TLS allows any data to be carried over the underlying transport after the TLS connection is closed, the TLS implementation MUST receive a "close_notify" alert before indicating end-of-data to the application layer. Nothing in this standard should be read as dictating how a TLS usage profile manages its data transport, including when connections are opened or closed ๐ŸŒˆ

Note: It is assumed that closing the write side of a connection reliably delivers pending data before destroying the transport. ๐Ÿซถ

6.2. Error Alerts ๐Ÿšจ

okay so error handling in TLS is literally the simplest thing ever ๐Ÿ’…. When something goes wrong, the party that spots it sends a message to its peer. Once a fatal alert is sent or received, both parties MUST immediately close the connection ๐Ÿ”’.

Whenever an implementation hits a fatal error, it SHOULD send an appropriate fatal alert, and it MUST close the connection without sending or receiving any more data ๐Ÿ™…โ€โ™€๏ธ. Across this spec, when you see "terminate the connection" or "abort the handshake" with no specific alert named, the implementation SHOULD send the alert described below. But "terminate the connection with an X alert" or "abort the handshake with an X alert" means the implementation MUST send alert X if it sends any alert at all ๐ŸŽฏ. Every alert in this section, plus every unknown alert, is universally considered fatal as of TLS 1.3 (see Section 6) ๐Ÿ’€. And the implementation SHOULD provide a way to log alerts being sent and received ๐Ÿ“. Future you will thank present you, trust ๐Ÿซถ.

Here are the error alerts that are defined ๐Ÿ‘‡

  • unexpected_message ๐Ÿ™ˆ: an inappropriate message showed up, like the wrong handshake message or Application Data arriving too early. You should never see this between proper implementations.
  • bad_record_mac ๐Ÿ›ก๏ธ: sent when a record can't be deprotected. AEAD algorithms mash decryption and verification together, and to dodge side-channel attacks, this one alert covers every deprotection failure. Between proper implementations, it should only show up if messages got corrupted in the network.
  • record_overflow ๐Ÿ“ฆ: a TLSCiphertext record arrived longer than 2^14 + 256 bytes, or a record decrypted into a TLSPlaintext record with more than 2^14 bytes (or some other negotiated limit). Same deal: only from network corruption between proper implementations.
  • handshake_failure ๐Ÿค: the sender couldn't agree on an acceptable set of security parameters with the options it had.
  • bad_certificate ๐Ÿ“œ: a certificate was corrupt, had signatures that didn't verify, and so on.
  • unsupported_certificate ๐Ÿ™…: the certificate was of a type that isn't supported.
  • certificate_revoked ๐Ÿšซ: the signer revoked the certificate.
  • certificate_expired โฐ: the certificate has expired or isn't currently valid.
  • certificate_unknown ๐Ÿคท: some other, unspecified issue came up while processing the certificate, making it unacceptable.
  • illegal_parameter ๐Ÿงฉ: a field in the handshake was wrong or clashed with other fields. This is for stuff that follows the formal syntax but is otherwise incorrect.
  • unknown_ca ๐Ÿ›๏ธ: a valid certificate chain or partial chain arrived, but the CA certificate couldn't be found or matched to a known trust anchor, so it got rejected.
  • access_denied ๐Ÿšช: a valid certificate or PSK arrived, but when access control kicked in, the sender decided not to keep negotiating.
  • decode_error ๐Ÿง : a message couldn't be decoded because a field was out of range or the message length was wrong. This is for messages that don't conform to the formal protocol syntax. Between proper implementations, it should only show up if the network corrupted things.
  • decrypt_error ๐Ÿ”: a handshake cryptographic operation failed (not the record layer). That includes being unable to verify a signature, validate a Finished message, or validate a PSK binder.
  • protocol_version ๐Ÿ”ข: the peer tried to negotiate a protocol version that's recognized but not supported (see Appendix E).
  • insufficient_security ๐Ÿ’ช: sent instead of handshake_failure when negotiation failed specifically because the server needs parameters more secure than the client supports.
  • internal_error ๐Ÿ”ฅ: an internal error unrelated to the peer or protocol correctness, like a memory allocation failure, makes it impossible to continue.
  • inappropriate_fallback ๐Ÿ”: a server sends this in response to an invalid connection retry attempt from a client (see [RFC7507]).
  • missing_extension ๐Ÿงฉ: endpoints that receive a handshake message without an extension that's mandatory to send for the offered TLS version or other negotiated parameters send this.
  • unsupported_extension ๐ŸŽ: endpoints that receive any handshake message containing an extension in a ServerHello, HelloRetryRequest, EncryptedExtensions, or Certificate that wasn't first offered in the corresponding ClientHello or CertificateRequest send this.
  • unrecognized_name ๐Ÿท๏ธ: servers send this when there's no server matching the name the client gave via the "server_name" extension (see [RFC6066]).
  • bad_certificate_status_response ๐Ÿ“‹: clients send this when the server provides an invalid or unacceptable OCSP response via the "status_request" extension (see [RFC6066]).
  • unknown_psk_identity ๐Ÿซฃ: servers send this when PSK key establishment is wanted but the client didn't provide an acceptable PSK identity. Sending this alert is OPTIONAL. Servers MAY instead send a decrypt_error alert to just signal an invalid PSK identity.
  • certificate_required ๐Ÿชช: servers send this when they want a client certificate and the client didn't provide one.
  • general_error ๐Ÿ™Š: sent when either no more specific error is available or the sender wants to hide the specific error code. Implementations SHOULD use more specific errors when they're available โœจ.
  • no_application_protocol ๐Ÿ—ฃ๏ธ: servers send this when the client's "application_layer_protocol_negotiation" extension only advertises protocols the server doesn't support (see [RFC7301]).

New Alert values are assigned by IANA as described in Section 11 ๐Ÿ“‹โœจ.


7. Cryptographic Computations ๐Ÿ”

okay so the TLS handshake sets up one or more input secrets, and those get combined to make the actual working keying material ๐Ÿ”‘. The key derivation process pulls in both the input secrets and the handshake transcript. Here's the iconic part: because the transcript includes the random values from the Hello messages, every handshake ends up with different traffic secrets, even when the input secrets are the same. Like when the same PSK gets reused across multiple connections ๐Ÿ”๐Ÿ’œ.

7.1. Key Schedule

Okay bestie, the key derivation process is where the magic happens ๐Ÿ”ฎโœจ It uses HKDF-Extract and HKDF-Expand, as defined for HKDF [RFC5869], plus a couple of helper functions we're about to meet ๐Ÿ’…

HKDF-Expand-Label(Secret, Label, Context, Length) =
    HKDF-Expand(Secret, HkdfLabel, Length)

Here's how HkdfLabel is built ๐Ÿง

struct {
    uint16 length = Length;
    opaque label<7..255> = "tls13 " + Label;
    opaque context<0..255> = Context;
} HkdfLabel;
Derive-Secret(Secret, Label, Messages) =
    HKDF-Expand-Label(Secret, Label,
                      Transcript-Hash(Messages), Hash.length)

The Hash function used by Transcript-Hash and HKDF is the cipher suite hash algorithm ๐ŸŒธ Hash.length is its output length in bytes. Messages is the concatenation of the indicated handshake messages, including the handshake message type and length fields, but not including record layer headers. Sometimes a zero-length Context (written as "") gets passed to HKDF-Expand-Label, so don't be surprised when you see that ๐Ÿ™ˆ Also, the labels in this document are all ASCII strings and they do not include a trailing NUL byte.

Any extensions to TLS that use "HKDF-Expand-Label" use the HkdfLabel definition tied to the version of TLS they're used with ๐Ÿฆ‹ With this specification, that means HkdfLabel exactly as defined above. With DTLS [RFC9147], that means the version defined in [RFC9147], Section 5.9.

Note: With common hash functions, any label longer than 12 characters needs an extra iteration of the hash function to compute. The labels in this specification were all picked to fit inside that limit ๐Ÿซถ

Keys are derived from two input secrets using HKDF-Extract and Derive-Secret ๐Ÿ”‘ The general pattern for adding a new secret is to run HKDF-Extract with the Salt set to the current secret state and the Input Keying Material (IKM) set to the new secret you're adding. In this version of TLS 1.3, the two input secrets are:

  • PSK (a pre-shared key established externally, or derived from the resumption_secret value from a previous connection)
  • Asymmetric shared secret resulting from the asymmetric key establishment (see Section 7.4)

This gives you the key schedule shown in the diagram below (Figure 5) ๐ŸŒˆ Here's how to read it:

  • HKDF-Extract takes its Salt argument from the top and its IKM argument from the left, sends its output to the bottom, and has the name of the output on the right.
  • Derive-Secret's Secret argument is shown by the incoming arrow. For instance, the Early Secret is the Secret for generating the client_early_traffic_secret.
  • "0" means a string of Hash.length bytes set to zero.
Note: The key derivation labels use the string "master" even though the values are referred to as "main" secrets. This mismatch is a result of renaming the values while keeping compatibility ๐Ÿ’€
Note: This doesn't show all the leaf keys, like the separate AEAD and IV keys. It shows the first set of secrets derived from the handshake ๐Ÿงš
                 0
                 |
                 v
     PSK -->  HKDF-Extract = Early Secret
                 |
                 +-----> Derive-Secret(.,
                 |                     "ext binder" |
                 |                     "res binder",
                 |                     "")
                 |              = binder_key
                 |
                 +-----> Derive-Secret(., "c e traffic",
                 |                     ClientHello)
                 |              = client_early_traffic_secret
                 |
                 +-----> Derive-Secret(., "e exp master",
                 |                     ClientHello)
                 |              = early_exporter_secret
                 v
            Derive-Secret(., "derived", "")
                 |
  asymmetric     v
   shared --> HKDF-Extract = Handshake Secret
   secret        |
                 +-----> Derive-Secret(., "c hs traffic",
                 |                     ClientHello...ServerHello)
                 |              = client_handshake_traffic_secret
                 |
                 +-----> Derive-Secret(., "s hs traffic",
                 |                     ClientHello...ServerHello)
                 |              = server_handshake_traffic_secret
                 v
            Derive-Secret(., "derived", "")
                 |
                 v
        0 --> HKDF-Extract = Main Secret
                 |
                 +-----> Derive-Secret(., "c ap traffic",
                 |                     ClientHello...server Finished)
                 |              = client_application_traffic_secret_0
                 |
                 +-----> Derive-Secret(., "s ap traffic",
                 |                     ClientHello...server Finished)
                 |              = server_application_traffic_secret_0
                 |
                 +-----> Derive-Secret(., "exp master",
                 |                     ClientHello...server Finished)
                 |              = exporter_secret
                 |
                 +-----> Derive-Secret(., "res master",
                                       ClientHello...client Finished)
                              = resumption_secret
Figure 5: Main TLS 1.3 Key Schedule

The general vibe here is that the secrets down the left side of the diagram are just raw entropy with no context ๐ŸŒ™ The secrets down the right side include Handshake Context, so they can be used to derive working keys without extra context. Different calls to Derive-Secret can take different Messages arguments, even with the same secret โœจ In a 0-RTT exchange, Derive-Secret is called with four distinct transcripts. In a 1-RTT-only exchange, it's called with three distinct transcripts.

If a given secret isn't available, the 0-value (a string of Hash.length bytes set to zeros) gets used ๐Ÿซง Note that this does not mean skipping rounds. So if PSK isn't in use, the Early Secret will still be HKDF-Extract(0, 0). For the computation of the binder_key, the label is "ext binder" for external PSKs (the ones provisioned outside of TLS) and "res binder" for resumption PSKs (the ones provisioned as the resumption secret of a previous handshake). Those different labels stop one type of PSK from being swapped in for the other, which is very iconic of them ๐Ÿ‘‘

There are multiple potential Early Secret values, depending on which PSK the server ends up selecting ๐Ÿ’œ The client will need to compute one for each potential PSK. If no PSK gets selected, it then needs to compute the Early Secret for the zero PSK.

Once all the values derived from a given secret have been computed, that secret SHOULD be erased ๐Ÿงน

7.2. Updating Traffic Secrets

Once the handshake is done, either side can update its sending traffic keys using the KeyUpdate handshake message defined in Section 4.7.3 ๐Ÿ”„ The next generation of traffic keys is computed by generating client_/server_application_traffic_secret_N+1 from client_/server_application_traffic_secret_N, as described in this section, and then re-deriving the traffic keys as described in Section 7.3.

The next-generation application_traffic_secret is computed as:

application_traffic_secret_N+1 =
    HKDF-Expand-Label(application_traffic_secret_N,
                      "traffic upd", "", Hash.length)

Once client_/server_application_traffic_secret_N+1 and its associated traffic keys have been computed, implementations SHOULD delete client_/server_application_traffic_secret_N and its associated traffic keys ๐Ÿ—‘๏ธโœจ

7.3. Traffic Key Calculation

The traffic keying material is generated from these three input values ๐ŸŽ€

  • A secret value
  • A purpose value indicating the specific value being generated
  • The length of the key being generated

The traffic keying material is generated from an input traffic secret value using:

[sender]_write_key = HKDF-Expand-Label(Secret, "key", "", key_length)
[sender]_write_iv  = HKDF-Expand-Label(Secret, "iv", "", iv_length)

[sender] denotes the sending side ๐Ÿ“ค The Secret value for each category of data is shown in the table below.

   +====================+=======================================+
   | Data Type          | Secret                                |
   +====================+=======================================+
   | 0-RTT Application  | client_early_traffic_secret           |
   | and EndOfEarlyData |                                       |
   +--------------------+---------------------------------------+
   | Initial Handshake  | [sender]_handshake_traffic_secret     |
   +--------------------+---------------------------------------+
   | Post-Handshake and | [sender]_application_traffic_secret_N |
   | Application Data   |                                       |
   +--------------------+---------------------------------------+
Table 3: Secrets for Traffic Keys

Alerts are sent with the then-current sending key (or as plaintext if no such key has been established) ๐Ÿšจ All the traffic keying material gets recomputed whenever the underlying Secret changes. For example, that happens when you switch from the handshake keys to the Application Data keys, or when there's a key update ๐Ÿ”

7.4. Asymmetric Shared Secret Calculation

7.4.1. Finite Field Diffie-Hellman

For finite field groups, a conventional Diffie-Hellman [KEYAGREEMENT] computation is performed ๐Ÿงฎ The negotiated key (Z) gets converted to a byte string by encoding it in big-endian form and left-padding it with zeros up to the size of the prime. That byte string is then used as the shared secret in the key schedule, as specified above ๐Ÿ’–

Note that this construction differs from previous versions of TLS, which removed leading zeros ๐Ÿ™ƒ

7.4.2. Elliptic Curve Diffie-Hellman ๐Ÿ’Ž

okay so for secp256r1, secp384r1, and secp521r1 ๐Ÿฆ‹, the ECDH stuff (key generation AND the shared secret math) gets done following Sections 5.6.1.2 and 5.7.1.2 of [KEYAGREEMENT], using the Elliptic Curve Cryptography Cofactor Diffie-Hellman Primitive. The shared secret Z is the x-coordinate of the ECDH shared secret point, written out as an octet string. ๐Ÿงš

Here's the ngl super important bit ๐Ÿ‘€: the octet string Z from the Field-Element-to-Byte String Conversion in Appendix C.2 of [KEYAGREEMENT] has a constant length for any given field. So leading zeros in it MUST NOT be truncated ๐Ÿ™…โ€โ™€๏ธ. And for the public-key validation rules, go check Section 4.3.8.2 ๐Ÿ”.

For X25519 and X448, the ECDH math looks like this ๐ŸŒธ:

  • The public key you put into the KeyShareEntry.key_exchange structure is what you get from running the ECDH scalar multiplication function on your secret key (that goes into the scalar input, at the right length) and the standard public basepoint (that goes into the u-coordinate point input).
  • The ECDH shared secret is what you get from running the ECDH scalar multiplication function on your secret key (into the scalar input) and the peer's public key (into the u-coordinate point input). The output is used raw, with no processing. no extra steps, just vibes ๐Ÿ’….

For these curves, implementations SHOULD use the approach specified in [RFC7748] to calculate the Diffie-Hellman shared secret ๐Ÿ’œ. Implementations MUST check whether the computed Diffie-Hellman shared secret is the all-zero value and abort if so, as described in Section 6 of [RFC7748] ๐Ÿšซ. If implementors use an alternative implementation of these elliptic curves, they SHOULD perform the additional checks specified in Section 7 of [RFC7748] ๐Ÿ›ก๏ธ.


7.5. Exporters ๐ŸŽ€

[RFC5705] defines keying material exporters for TLS in terms of the TLS pseudorandom function (PRF). This document swaps the PRF out for HKDF, so a brand new construction is needed. The exporter interface itself stays the same, which is giving continuity ๐Ÿซถ.

The exporter value is computed as:

TLS-Exporter(label, context_value, key_length) =
    HKDF-Expand-Label(Derive-Secret(Secret, label, ""),
                      "exporter", Hash(context_value), key_length)

Where Secret is either the early_exporter_secret or the exporter_secret. Implementations MUST use the exporter_secret unless explicitly specified by the application ๐Ÿ”‘. The early_exporter_secret is defined for settings where an exporter is needed for 0-RTT data โฉ. A separate interface for the early exporter is RECOMMENDED, because this stops the exporter user from accidentally grabbing an early exporter when they wanted a regular one, or the other way around ๐Ÿ™ˆ.

If no context is provided, the context_value is zero length. So providing no context computes the same value as providing an empty context. This is a change from earlier versions of TLS, where an empty context gave a different output than an absent context ๐Ÿ”„. As of this document's publication, no allocated exporter label is used both with and without a context. Future specifications MUST NOT define a use of exporters that permits both an empty context and no context with the same label ๐Ÿšท. New uses of exporters SHOULD provide a context in all exporter computations, though the value could be empty ๐Ÿ“.

Requirements for the format of exporter labels are defined in Section 4 of [RFC5705] ๐Ÿ“‹.


8. 0-RTT and Anti-Replay ๐Ÿ•ต๏ธโ€โ™€๏ธ

As noted in Section 2.3 and Appendix F.5, TLS does not provide inherent replay protections for 0-RTT data. There are two potential threats to be concerned with ๐Ÿ˜ฌ:

  • Network attackers who mount a replay attack by simply duplicating a flight of 0-RTT data.
  • Network attackers who take advantage of client retry behavior to arrange for the server to receive multiple copies of an application message. This threat already exists to some extent, because clients that value robustness respond to network errors by attempting to retry requests. But 0-RTT adds an additional dimension if clients automatically retransmit rejected 0-RTT data after the completion of a handshake, since an attacker may be able to cause the data to be processed as 0-RTT data on connection 1 and as 1-RTT data on connection 2 ๐Ÿ’€.

The first class of attack can be prevented by sharing state to guarantee that the 0-RTT data is accepted at most once. Servers SHOULD provide that level of replay safety by implementing one of the methods described in this section, or by equivalent means ๐Ÿ”’. It is understood, however, that due to operational concerns, not all deployments will maintain state at that level. Therefore, in normal operation, clients will not know which, if any, of these mechanisms servers actually implement, and hence they MUST only send early data which they deem safe to be replayed ๐Ÿ™.

On top of the direct effects of replays, there is a whole class of attacks where even operations normally considered idempotent could be exploited by a large number of replays. Think timing attacks, resource limit exhaustion, and others, as described in Appendix F.5 ๐Ÿ˜ฑ. Those can be mitigated by ensuring that every 0-RTT payload can be replayed only a limited number of times. The server MUST ensure that any instance of it (be it a machine, a thread, or any other entity within the relevant serving infrastructure) would accept 0-RTT for the same 0-RTT handshake at most once. This limits the number of replays to the number of server instances in the deployment ๐Ÿงฎ. Such a guarantee can be accomplished by locally recording data from recently received ClientHellos and rejecting repeats, or by any other method that provides the same or a stronger guarantee โœจ. The "at most once per server instance" guarantee is a minimum requirement, and servers SHOULD limit 0-RTT replays further when feasible ๐Ÿ“‰.

The second class of attack cannot be prevented at the TLS layer, so it MUST be dealt with by any application (see, for instance, [RFC8470], which provides guidance on the use of HTTP with 0-RTT data) ๐Ÿ“ฃ. Note that any application whose clients implement any kind of retry behavior already needs to implement some sort of anti-replay defense. That includes properly handling duplicate application layer requests which arrive on distinct TLS connections ๐Ÿ›ก๏ธ.

8.1. Single-Use Tickets ๐ŸŽŸ๏ธ

The simplest form of anti-replay defense is for the server to only allow each session ticket to be used once 1๏ธโƒฃ. For instance, the server can maintain a database of all outstanding valid tickets, deleting each ticket from the database as it is used. If an unknown ticket is provided, the server would then fall back to a full handshake ๐Ÿค.

If the tickets are not self-contained but are database keys, and the corresponding PSKs are deleted upon use, then connections established using PSKs enjoy not only anti-replay protection, but also forward secrecy once all copies of the PSK from the database entry have been deleted ๐ŸŒˆ. This mechanism also improves security for PSK usage when PSK is used without an asymmetric key exchange.

Because this mechanism requires sharing the session database between server nodes in environments with multiple distributed servers, it may be hard to achieve high rates of successful PSK 0-RTT connections compared to self-encrypted tickets ๐ŸŒ. Unlike session databases, session tickets can successfully do PSK-based session establishment even without consistent storage. Though when 0-RTT is allowed, they still require consistent storage for anti-replay of 0-RTT data, as detailed in the following section ๐Ÿง .

8.2. Client Hello Recording ๐Ÿ“ผ

An alternative form of anti-replay is to record a unique value derived from the ClientHello (generally either the random value or the PSK binder) and reject duplicates. Recording all ClientHellos causes state to grow without bound. So instead, a server can record ClientHellos within a given time window and use the "obfuscated_ticket_age" to ensure that tickets aren't reused outside that window โณ.

To implement this, when a ClientHello is received, the server first verifies the PSK binder as described in Section 4.3.11. It then computes the expected_arrival_time as described in the next section, and rejects 0-RTT if it is outside the recording window, falling back to the 1-RTT handshake ๐Ÿ”.

If the expected_arrival_time is in the window, then the server checks to see if it has recorded a matching ClientHello. If one is found, it either aborts the handshake with an "illegal_parameter" alert, or accepts the PSK but rejects 0-RTT ๐Ÿšจ. If no matching ClientHello is found, then it accepts 0-RTT and then stores the ClientHello for as long as the expected_arrival_time is inside the window. Servers MAY also implement data stores with false positives, such as Bloom filters, in which case they MUST respond to apparent replay by rejecting 0-RTT, but MUST NOT abort the handshake ๐ŸŒธ.

The server MUST derive the storage key only from validated sections of the ClientHello. If the ClientHello contains multiple PSK identities, then an attacker can create multiple ClientHellos with different binder values for the less-preferred identity, on the assumption that the server will not verify it (as recommended by Section 4.3.11). I.e., if the client sends PSKs A and B but the server prefers A, then the attacker can change the binder for B without affecting the binder for A. If the binder for B is part of the storage key, then this ClientHello will not appear as a duplicate. That causes the ClientHello to be accepted, and may cause side effects such as replay cache pollution, although any 0-RTT data will not be decryptable because it will use different keys ๐Ÿฅด. If the validated binder or the ClientHello.random is used as the storage key, then this attack is not possible ๐Ÿ™Œ.

Because this mechanism does not require storing all outstanding tickets, it may be easier to implement in distributed systems with high rates of resumption and 0-RTT. The cost is potentially weaker anti-replay defense, because of the difficulty of reliably storing and retrieving the received ClientHello messages. In many such systems, it is impractical to have globally consistent storage of all the received ClientHellos. In this case, the best anti-replay protection is provided by having a single storage zone be authoritative for a given ticket, and refusing 0-RTT for that ticket in any other zone ๐Ÿ—บ๏ธ. This approach prevents simple replay by the attacker, because only one zone will accept 0-RTT data. A weaker design is to implement separate storage for each zone but allow 0-RTT in any zone. This approach limits the number of replays to once per zone. Application message duplication of course remains possible with either design ๐Ÿ“ฌ.

When implementations are freshly started, they SHOULD reject 0-RTT as long as any portion of their recording window overlaps the startup time ๐Ÿš€. Otherwise, they run the risk of accepting replays which were originally sent during that period ๐Ÿ˜ต.

Note: If the client's clock is running much faster than the server's, then a ClientHello may be received that is outside the window in the future. In that case, it might be accepted for 1-RTT, causing a client retry, and then acceptable later for 0-RTT. This is another variant of the second form of attack described in Section 8 ๐Ÿ•ฐ๏ธ.

8.3. Freshness Checks

okay so the ClientHello literally tells the server when the client sent it ๐Ÿ•ฐ๏ธ, which means the server can efficiently check whether it was sent recently and only accept 0-RTT for a fresh one. Otherwise it falls back to a 1-RTT handshake. This is needed for the ClientHello storage mechanism in Section 8.2, because otherwise the server would have to store an unlimited number of ClientHellos ๐Ÿ’€. It's also a lush optimization for self-contained single-use tickets, since it lets the server reject ClientHellos that can't be used for 0-RTT anyway โœจ.

To make this work, a server needs to store the time it generated the session ticket, offset by an estimate of the round-trip time between client and server. Basically it looks like this:

adjusted_creation_time = creation_time + estimated_RTT

This value can be encoded in the ticket, so the server doesn't need to keep state for every outstanding ticket ๐Ÿงš. The server figures out the client's view of the ticket's age by subtracting the ticket's "ticket_age_add" value from the "obfuscated_ticket_age" parameter in the client's "pre_shared_key" extension. Then it can work out the expected_arrival_time of the ClientHello:

expected_arrival_time = adjusted_creation_time + clients_ticket_age

When a new ClientHello shows up, the expected_arrival_time gets compared against the server's current wall clock time. If they differ by more than a certain amount, 0-RTT gets rejected, though the 1-RTT handshake can still be allowed to finish ๐Ÿ’–.

There are a few potential sources of error that might cause mismatches between the expected_arrival_time and the measured time ๐Ÿ™ˆ. Variations in client and server clock rates are likely to be minimal, though the absolute times could be off by large values. Network propagation delays are the most likely cause of a mismatch in legit elapsed time. Both the NewSessionTicket and ClientHello messages might get retransmitted and delayed, which TCP might hide. For clients on the Internet, this implies windows on the order of ten seconds to cover clock errors and measurement variations. Other deployment scenarios may have different needs ๐ŸŒˆ. Clock skew distributions aren't symmetric, so the optimal tradeoff may involve an asymmetric range of permissible mismatch values.

Freshness checking alone is not sufficient to prevent replays ๐Ÿ˜ฑ. It doesn't detect them during the error window, which (depending on bandwidth and system capacity) could include billions of replays in real-world settings. And this freshness check only happens when the ClientHello is received, not when later early Application Data records arrive. After early data is accepted, records may keep streaming to the server over a much longer period.

9. Compliance Requirements

9.1. Mandatory-to-Implement Cipher Suites

In the absence of an application profile standard specifying otherwise:

  • A TLS-compliant application MUST implement the TLS_AES_128_GCM_SHA256 [GCM] cipher suite and SHOULD implement the TLS_AES_256_GCM_SHA384 [GCM] and TLS_CHACHA20_POLY1305_SHA256 [RFC8439] cipher suites (see Appendix B.4).
  • A TLS-compliant application MUST support digital signatures with rsa_pkcs1_sha256 (for certificates), rsa_pss_rsae_sha256 (for CertificateVerify and certificates), and ecdsa_secp256r1_sha256. A TLS-compliant application MUST support key exchange with secp256r1 (NIST P-256) and SHOULD support key exchange with X25519 [RFC7748] ๐Ÿ”ฅ.

9.2. Mandatory-to-Implement Extensions

In the absence of an application profile standard specifying otherwise, a TLS-compliant application MUST implement the following TLS extensions ๐Ÿ‘‘:

  • Supported Versions ("supported_versions"; Section 4.3.1)
  • Cookie ("cookie"; Section 4.3.2)
  • Signature Algorithms ("signature_algorithms"; Section 4.3.3)
  • Signature Algorithms Certificate ("signature_algorithms_cert"; Section 4.3.3)
  • Supported Groups ("supported_groups"; Section 4.3.7)
  • Key Share ("key_share"; Section 4.3.8)
  • Server Name Indication ("server_name"; Section 3 of [RFC6066])

All implementations MUST send and use these extensions when offering applicable features ๐Ÿซถ:

  • "supported_versions" is REQUIRED for all ClientHello, ServerHello, and HelloRetryRequest messages.
  • "signature_algorithms" is REQUIRED for certificate authentication.
  • "supported_groups" is REQUIRED for ClientHello messages using DHE or ECDHE key exchange.
  • "key_share" is REQUIRED for DHE or ECDHE key exchange.
  • "pre_shared_key" is REQUIRED for PSK key agreement.
  • "psk_key_exchange_modes" is REQUIRED for PSK key agreement.

A client is considered to be attempting to negotiate using this specification if the ClientHello contains a "supported_versions" extension with 0x0304 contained in its body. Such a ClientHello message MUST meet the following requirements ๐Ÿ’…:

  • If not containing a "pre_shared_key" extension, it MUST contain both a "signature_algorithms" extension and a "supported_groups" extension.
  • If containing a "supported_groups" extension, it MUST also contain a "key_share" extension, and vice versa. An empty KeyShare.client_shares list is permitted.

Servers receiving a ClientHello which does not conform to these requirements MUST abort the handshake with a "missing_extension" alert ๐Ÿ™…โ€โ™€๏ธ.

Additionally, all implementations MUST support the use of the "server_name" extension with applications capable of using it. Servers MAY require clients to send a valid "server_name" extension. Servers requiring this extension SHOULD respond to a ClientHello lacking a "server_name" extension by terminating the connection with a "missing_extension" alert ๐Ÿ“.

9.3. Protocol Invariants

This section describes invariants that TLS endpoints and middleboxes MUST follow. It also applies to earlier versions of TLS ๐Ÿง .

TLS is designed to be securely and compatibly extensible โœจ. Newer clients or servers, when communicating with newer peers, should negotiate the most preferred common parameters. The TLS handshake provides downgrade protection: middleboxes passing traffic between a newer client and newer server without terminating TLS should be unable to influence the handshake (see Appendix F.1). At the same time, deployments update at different rates, so a newer client or server MAY continue to support older parameters, which would let it interoperate with older endpoints ๐Ÿฆ‹.

For this to work, implementations MUST correctly handle extensible fields:

  • A client sending a ClientHello MUST support all non-GREASE [RFC8701] parameters advertised in it. Otherwise, the server may fail to interoperate by selecting one of those parameters.
  • A server receiving a ClientHello MUST correctly ignore all unrecognized cipher suites, extensions, and other parameters. Otherwise, it may fail to interoperate with newer clients. In TLS 1.3, a client receiving a CertificateRequest or NewSessionTicket MUST also ignore all unrecognized extensions.
  • A middlebox which terminates a TLS connection MUST behave as a compliant TLS server (to the original client), including having a certificate which the client is willing to accept, and also as a compliant TLS client (to the original server), including verifying the original server's certificate. In particular, it MUST generate its own ClientHello containing only parameters it understands, and it MUST generate a fresh ServerHello random value, rather than forwarding the endpoint's value.

Note that TLS's protocol requirements and security analysis only apply to the two connections separately. Safely deploying a TLS terminator requires additional security considerations which are beyond the scope of this document ๐ŸŽ€.

  • A middlebox which forwards ClientHello parameters it does not understand MUST NOT process any messages beyond that ClientHello. It MUST forward all subsequent traffic unmodified. Otherwise, it may fail to interoperate with newer clients and servers.

Forwarded ClientHellos may contain advertisements for features not supported by the middlebox, so the response may include future TLS additions the middlebox does not recognize. These additions MAY change any message beyond the ClientHello arbitrarily. In particular, the values sent in the ServerHello might change, the ServerHello format might change, and the TLSCiphertext format might change ๐ŸŒธ.

The design of TLS 1.3 was constrained by widely deployed non-compliant TLS middleboxes (see Appendix E.4); however, it does not relax the invariants. Those middleboxes continue to be non-compliant. Bestie, the middleboxes are the problem here ๐Ÿ’€.

10. Security Considerations

Security issues are discussed throughout this memo, especially in Appendix C, Appendix E, and Appendix F ๐Ÿ”๐Ÿ˜ญ. Read those appendices like your life depends on it, because the security of the whole protocol rides on them โญ.

11. IANA Considerations ๐ŸŽ€

okay bestie, this is the IANA part, aka the registry paperwork ๐Ÿ“‹๐Ÿ’…. This document uses several registries that were originally created in [RFC4346] and updated in [RFC8446] and [RFC8447]. The changes between [RFC8446], [RFC8447], and this document are described in Section 11.1. IANA has replaced references to these RFCs with references to this document. The registries and their allocation policies are below:

  • TLS Cipher Suites registry: Values with the first byte in the range 0-254 (decimal) are assigned via Specification Required [RFC8126]. Values with the first byte 255 (decimal) are reserved for Private Use [RFC8126].
    • IANA has added the cipher suites listed in Appendix B.4 to the registry. The Value and Description columns are taken from the table. The DTLS-OK and Recommended columns are both marked as Y for these cipher suites. ๐Ÿ”ฅ
  • TLS ContentType registry: Future values are allocated via Standards Action [RFC8126]. ๐ŸŒธ
  • TLS Alerts registry: Future values are allocated via Standards Action [RFC8126]. IANA has populated this registry with the values from Appendix B.2. The DTLS-OK column is marked as Y for all such values. Values marked as _RESERVED have comments describing their previous usage. ๐Ÿ’€
  • TLS HandshakeType registry: Future values are allocated via Standards Action [RFC8126]. IANA has updated this registry to rename item 4 from NewSessionTicket to new_session_ticket and populated this registry with the values from Appendix B.3. The DTLS-OK column is marked as Y for all such values. Values marked _RESERVED have comments describing their previous or temporary usage. โœจ

This document also uses the TLS ExtensionType Values registry originally created in [RFC4366]. IANA has updated it to reference this document. Changes to the registry follow:

  • IANA has updated the registration policy as follows:
    • Values with the first byte in the range 0-254 (decimal) are assigned via Specification Required [RFC8126]. Values with the first byte 255 (decimal) are reserved for Private Use [RFC8126]. ๐Ÿฆ‹
  • IANA has updated this registry to include the key_share, pre_shared_key, psk_key_exchange_modes, early_data, cookie, supported_versions, certificate_authorities, oid_filters, post_handshake_auth, and signature_algorithms_cert extensions with the values defined in this document and the Recommended value of Y. ๐ŸŽ€
  • IANA has updated this registry to include a TLS 1.3 column which lists the messages in which the extension may appear. This column has been initially populated from the table in Section 4.3, with any extension not listed there marked as - to indicate that it is not used by TLS 1.3. ๐Ÿงš

This document updates two entries in the TLS Certificate Types registry originally created in [RFC6091] and updated in [RFC8447]. IANA has updated the entry for value 1 to have the name OpenPGP_RESERVED, Recommended value N, and comment Used in TLS versions prior to 1.3. IANA has updated the entry for value 0 to have the name X509, Recommended value Y, and comment Was X.509 before TLS 1.3. ๐Ÿ“

This document updates an entry in the TLS Certificate Status Types registry originally created in [RFC6961]. IANA has updated the entry for value 2 to have the name ocsp_multi_RESERVED and comment Used in TLS versions prior to 1.3. ๐Ÿ™ˆ

This document updates two entries in the TLS Supported Groups registry (created under a different name by [RFC4492]; now maintained by [RFC8422] and updated by [RFC7919] and [RFC8447]). The entries for values 29 and 30 (x25519 and x448) have been updated to also refer to this document. ๐ŸŒˆ

In addition, this document defines two new registries that are maintained by IANA:

  • TLS SignatureScheme registry: Values with the first byte in the range 0-253 (decimal) are assigned via Specification Required [RFC8126]. Values with the first byte 254 or 255 (decimal) are reserved for Private Use [RFC8126]. Values with the first byte in the range 0-6 or with the second byte in the range 0-3 that are not currently allocated are reserved for backward compatibility. This registry has a Recommended column. The registry has been initially populated with the values described in Section 4.3.3. The following values are marked as Recommended: ecdsa_secp256r1_sha256, ecdsa_secp384r1_sha384, rsa_pss_rsae_sha256, rsa_pss_rsae_sha384, rsa_pss_rsae_sha512, rsa_pss_pss_sha256, rsa_pss_pss_sha384, rsa_pss_pss_sha512, and ed25519. ๐Ÿ”ฅ
  • TLS PskKeyExchangeMode registry: Values in the range 0-253 (decimal) are assigned via Specification Required [RFC8126]. The values 254 and 255 (decimal) are reserved for Private Use [RFC8126]. This registry has a Recommended column. The registry has been initially populated with psk_ke (0) and psk_dhe_ke (1). Both are marked as Recommended. ๐Ÿ‘‘

The TLS ExtensionType Values, TLS Cipher Suites, TLS Supported Groups, TLS Exporter Labels, TLS HashAlgorithm, TLS Certificate Types, TLSPskKeyExchangeMode, and TLS SignatureScheme registries have a Recommended column. See Section 3 of [RFC9847] for additional information about Recommended column values and settings. ๐Ÿ’œ


11.1. Changes for this RFC

okay this is the changelog bit, so you can see exactly what IANA touched ๐Ÿ“โœจ

IANA has updated all references to [RFC8446] in the IANA registries with references to this document.

IANA has updated the Recommended column procedures to match [RFC9847] for the TLS SignatureScheme and PskKeyExchangeMode registries.

IANA has renamed the extended_master_secret value in the TLS ExtensionType Values registry to extended_main_secret.

IANA has created a value for the general_error alert in the TLS Alerts registry with the value given in Section 6. ๐Ÿšจ

IANA has added references to this document for the status_request and supported_groups entries in the TLS ExtensionType Values registry.

IANA has updated the TLS ExtensionType value in the TLS 1.3 column for cached_info to CH, EE. ๐Ÿซถ

12. References ๐Ÿ“š

12.1. Normative References ๐Ÿ“šโœจ

okay bestie, this is the must-read pile ๐Ÿซถ these are the docs the spec leans on, so you'll need every single one to make sense of it ๐Ÿ’…


12.2. Informative References ๐ŸŒธ

these are the extra-credit reads ๐Ÿงš totally worth a look if you want the deep lore behind the security stuff ๐Ÿ’œ

  • [AEAD-LIMITS] Luykx, A. and K. Paterson, "Limits on Authenticated Encryption Use in TLS", August 2017, <https://eprint.iacr.org/2024/051>.
  • [BBFGKZ16] Bhargavan, K., Brzuska, C., Fournet, C., Green, M., Kohlweiss, M., and S. Zanella-Beguelin, "Downgrade Resilience in Key-Exchange Protocols", IEEE, 2016 IEEE Symposium on Security and Privacy (SP) pp. 506-525, DOI 10.1109/sp.2016.37, May 2016, <https://doi.org/10.1109/sp.2016.37>.
  • [BBK17] Bhargavan, K., Blanchet, B., and N. Kobeissi, "Verified Models and Reference Implementations for the TLS 1.3 Standard Candidate", IEEE, 2017 IEEE Symposium on Security and Privacy (SP) pp. 483-502, DOI 10.1109/sp.2017.26, May 2017, <https://doi.org/10.1109/sp.2017.26>.
  • [BDFKPPRSZZ16] Bhargavan, K., Delignat-Lavaud, A., Fournet, C., Kohlweiss, M., Pan, J., Protzenko, J., Rastogi, A., Swamy, N., Zanella-Beguelin, S., and J. Zinzindohoue, "Implementing and Proving the TLS 1.3 Record Layer", Proceedings of IEEE Symposium on Security and Privacy (San Jose) 2017, December 2016, <https://eprint.iacr.org/2016/1178>.
  • [Ben17a] Benjamin, D., "Presentation before the TLS WG at IETF 100", IETF 100 Proceedings, November 2017, <https://datatracker.ietf.org/meeting/100/materials/slides-100-tls-sessa-tls13/>.
  • [Ben17b] Benjamin, D., "Additional TLS 1.3 results from Chrome", message to the TLS mailing list, 18 December 2017, <https://mailarchive.ietf.org/arch/msg/tls/i9blmvG2BEPf1s1OJkenHknRw9c/>.
  • [Blei98] Bleichenbacher, D., "Chosen Ciphertext Attacks against Protocols Based on RSA Encryption Standard PKCS #1", Proceedings of CRYPTO '98, 1998, <https://link.springer.com/chapter/10.1007/bfb0055716>.
  • [BMMRT15] Badertscher, C., Matt, C., Maurer, U., Rogaway, P., and B. Tackmann, "Augmented Secure Channels and the Goal of the TLS 1.3 Record Layer", ProvSec 2015, September 2015, <https://eprint.iacr.org/2015/394>.
  • [BT16] Bellare, M. and B. Tackmann, "The Multi-User Security of Authenticated Encryption: AES-GCM in TLS 1.3", Proceedings of CRYPTO 2016, July 2016, <https://eprint.iacr.org/2016/564>.
  • [CCG16] Cohn-Gordon, K., Cremers, C., and L. Garratt, "On Post-compromise Security", IEEE, 2016 IEEE 29th Computer Security Foundations Symposium (CSF) pp. 164-178, DOI 10.1109/csf.2016.19, June 2016, <https://doi.org/10.1109/csf.2016.19>.
  • [CHECKOWAY] Checkoway, S., Maskiewicz, J., Garman, C., Fried, J., Cohney, S., Green, M., Heninger, N., Weinmann, R., Rescorla, E., and H. Shacham, "A Systematic Analysis of the Juniper Dual EC Incident", ACM, Proceedings of the 2016 ACM SIGSAC Conference on Computer and Communications Security pp. 468-479, DOI 10.1145/2976749.2978395, October 2016, <https://doi.org/10.1145/2976749.2978395>.
  • [CHHSV17] Cremers, C., Horvat, M., Hoyland, J., van der Merwe, T., and S. Scott, "Awkward Handshake: Possible mismatch of client/server view on client authentication in post-handshake mode in Revision 18", message to the TLS mailing list, 10 February 2017, <https://mailarchive.ietf.org/arch/msg/tls/crdSCgiW-94z2joulYJtuA52E9E/>.
  • [CHSV16] Cremers, C., Horvat, M., Scott, S., and T. van der Merwe, "Automated Analysis and Verification of TLS 1.3: 0-RTT, Resumption and Delayed Authentication", IEEE, 2016 IEEE Symposium on Security and Privacy (SP) pp. 470-485, DOI 10.1109/sp.2016.35, May 2016, <https://doi.org/10.1109/sp.2016.35>.

[CK01] Canetti, R. and H. Krawczyk, "Analysis of Key-Exchange Protocols and Their Use for Building Secure Channels", Springer Berlin Heidelberg, Lecture Notes in Computer Science pp. 453-474, DOI 10.1007/3-540-44987-6_28, ISBN ["9783540420705", "9783540449874"], 2001, <https://doi.org/10.1007/3-540-44987-6_28>.

[CLINIC] Miller, B., Huang, L., Joseph, A., and J. Tygar, "I Know Why You Went to the Clinic: Risks and Realization of HTTPS Traffic Analysis", Springer International Publishing, Lecture Notes in Computer Science pp. 143-163, DOI 10.1007/978-3-319-08506-7_8, ISBN ["9783319085050", "9783319085067"], 2014, <https://doi.org/10.1007/978-3-319-08506-7_8>.

[DFGS15] Dowling, B., Fischlin, M., Guenther, F., and D. Stebila, "A Cryptographic Analysis of the TLS 1.3 draft-10 Full and Pre-shared Key Handshake Protocol", Proceedings of ACM CCS 2015, October 2015, <https://eprint.iacr.org/2015/914>.

[DFGS16] Dowling, B., Fischlin, M., Guenther, F., and D. Stebila, "A Cryptographic Analysis of the TLS 1.3 draft-10 Full and Pre-shared Key Handshake Protocol", TRON 2016, February 2016, <https://eprint.iacr.org/2016/081>.

[DH76] Diffie, W. and M. Hellman, "New directions in cryptography", Institute of Electrical and Electronics Engineers (IEEE), IEEE Transactions on Information Theory vol. 22, no. 6, pp. 644-654, DOI 10.1109/tit.1976.1055638, November 1976, <https://doi.org/10.1109/tit.1976.1055638>.

[DOW92] Diffie, W., Van Oorschot, P., and M. Wiener, "Authentication and authenticated key exchanges", Springer Science and Business Media LLC, Designs, Codes and Cryptography vol. 2, no. 2, pp. 107-125, DOI 10.1007/bf00124891, June 1992, <https://doi.org/10.1007/bf00124891>.

[DSA-1571-1] Weimer, F., "[SECURITY] [DSA 1571-1] New openssl packages fix predictable random number generator", message to the debian-security-announce mailing list, May 2008, <https://www.debian.org/security/2008/dsa-1571>.

[DSS] "Digital Signature Standard (DSS)", National Institute of Standards and Technology (U.S.), DOI 10.6028/nist.fips.186-5, February 2023, <https://doi.org/10.6028/nist.fips.186-5>.

[ECDP] Chen, L., Moody, D., Regenscheid, A., Robinson, A., and K. Randall, "Recommendations for Discrete Logarithm-based Cryptography:: Elliptic Curve Domain Parameters", National Institute of Standards and Technology, DOI 10.6028/nist.sp.800-186, February 2023, <https://doi.org/10.6028/nist.sp.800-186>.

[FETCH] WHATWG, "Fetch", WHATWG Living Standard, <https://fetch.spec.whatwg.org/>. Commit snapshot: https://fetch.spec.whatwg.org/commit-snapshots/4775fcb48042c8411df497c0b7cf167b4240004f/

[FG17] Fischlin, M. and F. Guenther, "Replay Attacks on Zero Round-Trip Time: The Case of the TLS 1.3 Handshake Candidates", Proceedings of Euro S&P 2017, April 2017, <https://eprint.iacr.org/2017/082>.

[FGSW16] Fischlin, M., Guenther, F., Schmidt, B., and B. Warinschi, "Key Confirmation in Key Exchange: A Formal Treatment and Implications for TLS 1.3", Proceedings of IEEE Symposium on Security and Privacy (Oakland) 2016, DOI 10.1109/SP.2016.34, 2016, <http://ieeexplore.ieee.org/document/7546517/>.

[FW15] Weimer, F., "Factoring RSA Keys With TLS Perfect Forward Secrecy", Red Hat Blog, 2 September 2015, <https://www.redhat.com/en/blog/factoring-rsa-keys-tls-perfect-forward-secrecy>.

[HCJC16] Husรกk, M., ฤŒermรกk, M., Jirsรญk, T., and P. ฤŒeleda, "HTTPS traffic analysis and client identification using passive SSL/TLS fingerprinting", Springer Science and Business Media LLC, EURASIP Journal on Information Security vol. 2016, no. 1, DOI 10.1186/s13635-016-0030-7, February 2016, <https://doi.org/10.1186/s13635-016-0030-7>.

[HGFS15] Hlauschek, C., Gruber, M., Fankhauser, F., and C. Schanes, "Prying Open Pandora's Box: KCI Attacks against TLS", Proceedings of USENIX Workshop on Offensive Technologies, 2015, <https://www.usenix.org/conference/woot15/workshop-program/presentation/hlauschek>.

[JSS15] Jager, T., Schwenk, J., and J. Somorovsky, "On the Security of TLS 1.3 and QUIC Against Weaknesses in PKCS#1 v1.5 Encryption", ACM, Proceedings of the 22nd ACM SIGSAC Conference on Computer and Communications Security pp. 1185-1196, DOI 10.1145/2810103.2813657, October 2015, <https://doi.org/10.1145/2810103.2813657>.

[Kraw10] Krawczyk, H., "Cryptographic Extraction and Key Derivation: The HKDF Scheme", Proceedings of CRYPTO 2010, 2010, <https://eprint.iacr.org/2010/264>.

[Kraw16] Krawczyk, H., "A Unilateral-to-Mutual Authentication Compiler for Key Exchange (with Applications to Client Authentication in TLS 1.3", Proceedings of ACM CCS 2016, October 2016, <https://eprint.iacr.org/2016/711>.

[KW16] Krawczyk, H. and H. Wee, "The OPTLS Protocol and TLS 1.3", Proceedings of Euro S&P 2016, March 2016, <https://eprint.iacr.org/2015/978>.

[LXZFH16] Li, X., Xu, J., Zhang, Z., Feng, D., and H. Hu, "Multiple Handshakes Security of TLS 1.3 Candidates", IEEE, 2016 IEEE Symposium on Security and Privacy (SP) pp. 486-505, DOI 10.1109/sp.2016.36, May 2016, <https://doi.org/10.1109/sp.2016.36>.

[Mac17] MacCarthaigh, C., "Security Review of TLS1.3 0-RTT", May 2017, <https://github.com/tlswg/tls13-spec/issues/1001>.

[MM24] Moustafa, M., Sethi, M., and T. Aura, "Misbinding Raw Public Keys to Identities in TLS", arxiv:2411.09770, 29 September 2025, <https://arxiv.org/pdf/2411.09770>.

[PS18] Patton, C. and T. Shrimpton, "Partially specified channels: The TLS 1.3 record layer without elision", 2018, <https://eprint.iacr.org/2018/634>.

[PSK-FINISHED] Cremers, C., Horvat, M., van der Merwe, T., and S. Scott, "Revision 10: possible attack if client authentication is allowed during PSK", message to the TLS mailing list, 31 October 2015, <https://mailarchive.ietf.org/arch/msg/tls/TugB5ddJu3nYg7chcyeIyUqWSbA/>.

[REKEY] Abdalla, M. and M. Bellare, "Increasing the Lifetime of a Key: A Comparative Analysis of the Security of Re-keying Techniques", Springer Berlin Heidelberg, Lecture Notes in Computer Science pp. 546-559, DOI 10.1007/3-540-44448-3_42, ISBN ["9783540414049", "9783540444480"], 2000, <https://doi.org/10.1007/3-540-44448-3_42>.

[Res17a] Rescorla, E., "Preliminary data on Firefox TLS 1.3 Middlebox experiment", message to the TLS mailing list, 5 December 2017, <https://mailarchive.ietf.org/arch/msg/tls/RBp0X-OWNuWXugFJRV7c_hIU0dI/>.

[Res17b] Rescorla, E., "More compatibility measurement results", message to the TLS mailing list, 22 December 2017, <https://mailarchive.ietf.org/arch/msg/tls/6pGGT-wm5vSkacMFPEPvFMEnj-M/>.

[RFC2246] Dierks, T. and C. Allen, "The TLS Protocol Version 1.0", RFC 2246, DOI 10.17487/RFC2246, January 1999, <https://www.rfc-editor.org/info/rfc2246>.

[RFC3552] Rescorla, E. and B. Korver, "Guidelines for Writing RFC Text on Security Considerations", BCP 72, RFC 3552, DOI 10.17487/RFC3552, July 2003, <https://www.rfc-editor.org/info/rfc3552>.

[RFC4086] Eastlake 3rd, D., Schiller, J., and S. Crocker, "Randomness Requirements for Security", BCP 106, RFC 4086, DOI 10.17487/RFC4086, June 2005, <https://www.rfc-editor.org/info/rfc4086>.

[RFC4346] Dierks, T. and E. Rescorla, "The Transport Layer Security (TLS) Protocol Version 1.1", RFC 4346, DOI 10.17487/RFC4346, April 2006, <https://www.rfc-editor.org/info/rfc4346>.

[RFC4366] Blake-Wilson, S., Nystrom, M., Hopwood, D., Mikkelsen, J., and T. Wright, "Transport Layer Security (TLS) Extensions", RFC 4366, DOI 10.17487/RFC4366, April 2006, <https://www.rfc-editor.org/info/rfc4366>.

[RFC4492] Blake-Wilson, S., Bolyard, N., Gupta, V., Hawk, C., and B. Moeller, "Elliptic Curve Cryptography (ECC) Cipher Suites for Transport Layer Security (TLS)", RFC 4492, DOI 10.17487/RFC4492, May 2006, <https://www.rfc-editor.org/info/rfc4492>.

[RFC5077] Salowey, J., Zhou, H., Eronen, P., and H. Tschofenig, "Transport Layer Security (TLS) Session Resumption without Server-Side State", RFC 5077, DOI 10.17487/RFC5077, January 2008, <https://www.rfc-editor.org/info/rfc5077>.

[RFC5246] Dierks, T. and E. Rescorla, "The Transport Layer Security (TLS) Protocol Version 1.2", RFC 5246, DOI 10.17487/RFC5246, August 2008, <https://www.rfc-editor.org/info/rfc5246>.

[RFC5763] Fischl, J., Tschofenig, H., and E. Rescorla, "Framework for Establishing a Secure Real-time Transport Protocol (SRTP) Security Context Using Datagram Transport Layer Security (DTLS)", RFC 5763, DOI 10.17487/RFC5763, May 2010, <https://www.rfc-editor.org/info/rfc5763>.

[RFC5764] McGrew, D. and E. Rescorla, "Datagram Transport Layer Security (DTLS) Extension to Establish Keys for the Secure Real-time Transport Protocol (SRTP)", RFC 5764, DOI 10.17487/RFC5764, May 2010, <https://www.rfc-editor.org/info/rfc5764>.

[RFC5929] Altman, J., Williams, N., and L. Zhu, "Channel Bindings for TLS", RFC 5929, DOI 10.17487/RFC5929, July 2010, <https://www.rfc-editor.org/info/rfc5929>.

[RFC6091] Mavrogiannopoulos, N. and D. Gillmor, "Using OpenPGP Keys for Transport Layer Security (TLS) Authentication", RFC 6091, DOI 10.17487/RFC6091, February 2011, <https://www.rfc-editor.org/info/rfc6091>.

[RFC6101] Freier, A., Karlton, P., and P. Kocher, "The Secure Sockets Layer (SSL) Protocol Version 3.0", RFC 6101, DOI 10.17487/RFC6101, August 2011, <https://www.rfc-editor.org/info/rfc6101>.

[RFC6176] Turner, S. and T. Polk, "Prohibiting Secure Sockets Layer (SSL) Version 2.0", RFC 6176, DOI 10.17487/RFC6176, March 2011, <https://www.rfc-editor.org/info/rfc6176>.

[RFC6520] Seggelmann, R., Tuexen, M., and M. Williams, "Transport Layer Security (TLS) and Datagram Transport Layer Security (DTLS) Heartbeat Extension", RFC 6520, DOI 10.17487/RFC6520, February 2012, <https://www.rfc-editor.org/info/rfc6520>.

Okay bestie, the receipts are coming in ๐Ÿ“šโœจ Here are the informative references, all the way down to X501 ๐Ÿ’…


Appendix A. State Machine ๐Ÿ—บ๏ธโœจ

This appendix is the cheat sheet for every legal state transition in the client and server handshakes ๐Ÿ’œ State names are in all capitals, like START. They have no formal meaning, they're just there so you can follow along more easily. Actions that only happen in certain circumstances are shown in []. And the notation K_{send,recv} = foo just means "set the send/recv key to the given key" ๐Ÿ”‘

A.1. Client

                              START <----+
               Send ClientHello |        | Recv HelloRetryRequest
          [K_send = early data] |        |
                                v        |
           +->               WAIT_SH ----+
           |                    | Recv ServerHello
           |                    | K_recv = handshake
       Can |                    V
      send |                 WAIT_EE
     early |                    | Recv EncryptedExtensions
      data |          +---------+-------+
           |    Using |                 | Using certificate
           |      PSK |                 v
           |          |            WAIT_CERT_CR
           |          |        Recv |       | Recv CertificateRequest
           |          | Certificate |       v
           |          |             |    WAIT_CERT
           |          |             |       | Recv Certificate
           |          |             v       v
           |          |              WAIT_CV
           |          |                   | Recv CertificateVerify
           |          +-> WAIT_FINISHED <-+
           |                  | Recv Finished
           +->                | [Send EndOfEarlyData]
                              | K_send = handshake
                              | [Send Certificate [+ CertificateVerify]]
    Can send                  | Send Finished
    app data   -->            | K_send = K_recv = application
    after here                v
                          CONNECTED

Heads up, this one's a sneaky bit ๐Ÿ‘€ Even with the transitions drawn above, clients can send alerts that come from messages after ServerHello, either in the clear or using the early data keys. If clients need to send alerts like that, they SHOULD first rekey to the handshake keys if possible. ๐Ÿซถ

A.2. Server ๐Ÿงš

Okay bestie, the server's side of the state machine is giving main character energy ๐Ÿ’… Here's the whole flow from START to CONNECTED โœจ

                              START <-----+
               Recv ClientHello |         | Send HelloRetryRequest
                                v         |
                             RECVD_CH ----+
                                | Select parameters
                                v
                             NEGOTIATED
                                | Send ServerHello
                                | K_send = handshake
                                | Send EncryptedExtensions
                                | [Send CertificateRequest]
 Can send                       | [Send Certificate + CertificateVerify]
 app data                       | Send Finished
 after   -->                    | K_send = application
 here                  +--------+--------+
              No 0-RTT |                 | 0-RTT
                       |                 |
   K_recv = handshake  |                 | K_recv = early data
 [Skip decrypt errors] |    +------> WAIT_EOED --+
                       |    |       Recv |       | Recv EndOfEarlyData
                       |    | early data |       | K_recv = handshake
                       |    +------------+       |
                       |                         |
                       +-> WAIT_FLIGHT2 <--------+
                                |
                       +--------+--------+
               No auth |                 | Cert-based client auth
                       |                 |
                       |                 v
                       |             WAIT_CERT
                       |        Recv |       | Recv Certificate
                       |       empty |       v
                       | Certificate |    WAIT_CV
                       |             |       | Recv
                       |             v       | CertificateVerify
                       +-> WAIT_FINISHED <---+
                                | Recv Finished
                                | K_recv = application
                                v
                            CONNECTED

Appendix B. Protocol Data Structures and Constant Values ๐ŸŽ€

Okay so this appendix is where the normative protocol types and the constant values live ๐Ÿซถ Anything marked _RESERVED was used in earlier versions of TLS, and it's only listed here so nothing's missing ๐Ÿ‘€ TLS 1.3 implementations MUST NOT send them ๐Ÿ™… but they might receive them from older TLS implementations ๐Ÿฆ‹

B.1. Record Layer ๐Ÿ“ฆ

      enum {
          invalid(0),
          change_cipher_spec(20),
          alert(21),
          handshake(22),
          application_data(23),
          (255)
      } ContentType;

      struct {
          ContentType type;
          ProtocolVersion legacy_record_version;
          uint16 length;
          opaque fragment[TLSPlaintext.length];
      } TLSPlaintext;

      struct {
          opaque content[TLSPlaintext.length];
          ContentType type;
          uint8 zeros[length_of_padding];
      } TLSInnerPlaintext;

      struct {
          ContentType opaque_type = application_data; /* 23 */
          ProtocolVersion legacy_record_version = 0x0303; /* TLS v1.2 */
          uint16 length;
          opaque encrypted_record[TLSCiphertext.length];
      } TLSCiphertext;

B.2. Alert Messages ๐Ÿšจ

      enum { warning(1), fatal(2), (255) } AlertLevel;

      enum {
          close_notify(0),
          unexpected_message(10),
          bad_record_mac(20),
          decryption_failed_RESERVED(21),
          record_overflow(22),
          decompression_failure_RESERVED(30),
          handshake_failure(40),
          no_certificate_RESERVED(41),
          bad_certificate(42),
          unsupported_certificate(43),
          certificate_revoked(44),
          certificate_expired(45),
          certificate_unknown(46),
          illegal_parameter(47),
          unknown_ca(48),
          access_denied(49),
          decode_error(50),
          decrypt_error(51),
          export_restriction_RESERVED(60),
          protocol_version(70),
          insufficient_security(71),
          internal_error(80),
          inappropriate_fallback(86),
          user_canceled(90),
          no_renegotiation_RESERVED(100),
          missing_extension(109),
          unsupported_extension(110),
          certificate_unobtainable_RESERVED(111),
          unrecognized_name(112),
          bad_certificate_status_response(113),
          bad_certificate_hash_value_RESERVED(114),
          unknown_psk_identity(115),
          certificate_required(116),
          general_error(117),
          no_application_protocol(120),
          (255)
      } AlertDescription;

      struct {
          AlertLevel level;
          AlertDescription description;
      } Alert;

B.3. Handshake Protocol ๐Ÿค

      enum {
          hello_request_RESERVED(0),
          client_hello(1),
          server_hello(2),
          hello_verify_request_RESERVED(3),
          new_session_ticket(4),
          end_of_early_data(5),
          hello_retry_request_RESERVED(6),
          encrypted_extensions(8),
          certificate(11),
          server_key_exchange_RESERVED(12),
          certificate_request(13),
          server_hello_done_RESERVED(14),
          certificate_verify(15),
          client_key_exchange_RESERVED(16),
          finished(20),
          certificate_url_RESERVED(21),
          certificate_status_RESERVED(22),
          supplemental_data_RESERVED(23),
          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;

B.3.1. Key Exchange Messages ๐Ÿ”‘

    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<7..2^16-1>;
    } 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;

    struct {
        ExtensionType extension_type;
        opaque extension_data<0..2^16-1>;
    } Extension;

    enum {
        server_name(0),                             /* RFC 6066, 9261 */
        status_request(5),                          /* RFC 6066, 9846 */
        supported_groups(10),                       /* RFC 7919, 9846 */
        signature_algorithms(13),                   /* RFC 9846 */
        use_srtp(14),                               /* RFC 5764 */
        heartbeat(15),                              /* RFC 6520 */
        application_layer_protocol_negotiation(16), /* RFC 7301 */
        client_certificate_type(19),                /* RFC 7250 */
        server_certificate_type(20),                /* RFC 7250 */
        padding(21),                                /* RFC 7685 */
        compress_certificate(27),                   /* RFC 8879 */
        record_size_limit(28),                      /* RFC 8449 */
        delegated_credential(34),                   /* RFC 9345 */
        supported_ekt_ciphers(39),                  /* RFC 8870 */
        pre_shared_key(41),                         /* RFC 9846 */
        early_data(42),                             /* RFC 9846 */
        supported_versions(43),                     /* RFC 9846 */
        cookie(44),                                 /* RFC 9846 */
        psk_key_exchange_modes(45),                 /* RFC 9846 */
        certificate_authorities(47),                /* RFC 9846 */
        oid_filters(48),                            /* RFC 9846 */
        post_handshake_auth(49),                    /* RFC 9846 */
        signature_algorithms_cert(50),              /* RFC 9846 */
        key_share(51),                              /* RFC 9846 */
        transparency_info(52),                      /* RFC 9162 */
        external_id_hash(55),                       /* RFC 8844 */
        external_session_id(56),                    /* RFC 8844 */
        quic_transport_parameters(57),              /* RFC 9001 */
        ticket_request(58),                         /* RFC 9149 */
        ech_outer_extensions(64768),                /* RFC 9849 */
        encrypted_client_hello(65037),              /* RFC 9849 */
        (65535)
    } ExtensionType;

    struct {
        NamedGroup group;
        opaque key_exchange<1..2^16-1>;
    } KeyShareEntry;

    struct {
        KeyShareEntry client_shares<0..2^16-1>;
    } KeyShareClientHello;

    struct {
        NamedGroup selected_group;
    } KeyShareHelloRetryRequest;

    struct {
        KeyShareEntry server_share;
    } KeyShareServerHello;

    struct {
        uint8 legacy_form = 4;
        opaque X[coordinate_length];
        opaque Y[coordinate_length];
    } UncompressedPointRepresentation;

    enum { psk_ke(0), psk_dhe_ke(1), (255) } PskKeyExchangeMode;

    struct {
        PskKeyExchangeMode ke_modes<1..255>;
    } PskKeyExchangeModes;

    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;

    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;

B.3.1.1. Version Extension ๐Ÿ’–

      struct {
          select (Handshake.msg_type) {
              case client_hello:
                   ProtocolVersion versions<2..254>;

              case server_hello: /* and HelloRetryRequest */
                   ProtocolVersion selected_version;
          };
      } SupportedVersions;
      struct {
          opaque cookie<1..2^16-1>;
      } Cookie;

B.3.1.3. Signature Algorithm Extension โœจ

okay so this is the list of every signature scheme a sender can pick, and bestie, the code points are the whole point ๐Ÿ’… Each one is a 2-byte value, and the grouping tells you the family it belongs to ๐Ÿฆ‹

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 */
    obsolete_RESERVED(0x0000..0x0200),
    dsa_sha1_RESERVED(0x0202),
    obsolete_RESERVED(0x0204..0x0400),
    dsa_sha256_RESERVED(0x0402),
    obsolete_RESERVED(0x0404..0x0500),
    dsa_sha384_RESERVED(0x0502),
    obsolete_RESERVED(0x0504..0x0600),
    dsa_sha512_RESERVED(0x0602),
    obsolete_RESERVED(0x0604..0x06FF),
    private_use(0xFE00..0xFFFF),
    (0xFFFF)
} SignatureScheme;

struct {
    SignatureScheme supported_signature_algorithms<2..2^16-2>;
} SignatureSchemeList;

B.3.1.4. Supported Groups Extension ๐ŸŒธ

this one lists the named groups, split into the elliptic curve ones (ECDHE) and the finite field ones (DHE) ๐Ÿงš

enum {
    unallocated_RESERVED(0x0000),

    /* Elliptic Curve Groups (ECDHE) */
    obsolete_RESERVED(0x0001..0x0016),
    secp256r1(0x0017), secp384r1(0x0018), secp521r1(0x0019),
    obsolete_RESERVED(0x001A..0x001C),
    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),
    obsolete_RESERVED(0xFF01..0xFF02),
    (0xFFFF)
} NamedGroup;

struct {
    NamedGroup named_group_list<2..2^16-1>;
} NamedGroupList;

Now the rules, and this part is serious ๐Ÿ˜ญ Values sitting in the "obsolete_RESERVED" ranges came from earlier TLS versions, and TLS 1.3 implementations MUST NOT offer or negotiate them ๐Ÿšซ The obsolete curves have known or theoretical weaknesses, or barely got used (sometimes only because a server was misconfigured). They're not considered fit for general use anymore, so you should treat them as potentially unsafe โš ๏ธ The curves listed here are enough to interoperate with every TLS implementation that's deployed today and configured properly ๐Ÿ’œ


B.3.2. Server Parameters Messages ๐Ÿ‘‘

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

struct {
    DistinguishedName authorities<3..2^16-1>;
} CertificateAuthoritiesExtension;

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;

struct {} PostHandshakeAuth;

struct {
    Extension extensions<0..2^16-1>;
} EncryptedExtensions;

struct {
    opaque certificate_request_context<0..2^8-1>;
    Extension extensions<0..2^16-1>;
} CertificateRequest;

B.3.3. Authentication Messages ๐Ÿ”

enum {
    X509(0),
    OpenPGP_RESERVED(1),
    RawPublicKey(2),
    (255)
} CertificateType;

struct {
    select (certificate_type) {
        case RawPublicKey:
          /* From RFC 7250 ASN.1_subjectPublicKeyInfo */
          opaque ASN1_subjectPublicKeyInfo<1..2^24-1>;

        case X509:
          opaque cert_data<1..2^24-1>;
    };
    Extension extensions<0..2^16-1>;
} CertificateEntry;

struct {
    opaque certificate_request_context<0..2^8-1>;
    CertificateEntry certificate_list<0..2^24-1>;
} Certificate;

struct {
    SignatureScheme algorithm;
    opaque signature<0..2^16-1>;
} CertificateVerify;

struct {
    opaque verify_data[Hash.length];
} Finished;

B.3.4. Ticket Establishment ๐ŸŽŸ๏ธ

struct {
    uint32 ticket_lifetime;
    uint32 ticket_age_add;
    opaque ticket_nonce<0..255>;
    opaque ticket<1..2^16-1>;
    Extension extensions<0..2^16-1>;
} NewSessionTicket;

B.3.5. Updating Keys ๐Ÿ”„

struct {} EndOfEarlyData;

enum {
    update_not_requested(0), update_requested(1), (255)
} KeyUpdateRequest;

struct {
    KeyUpdateRequest request_update;
} KeyUpdate;

B.4. Cipher Suites ๐Ÿ“

A cipher suite is the pairing of an AEAD algorithm and a hash algorithm, the second one used with HKDF. The names follow this pattern, and it's giving very organised energy:

CipherSuite TLS_AEAD_HASH = VALUE;
+===========+=======================================================+
| Component | Contents                                              |
+===========+=======================================================+
| TLS       | The string "TLS"                                      |
+-----------+-------------------------------------------------------+
| AEAD      | The AEAD algorithm used for record protection         |
+-----------+-------------------------------------------------------+
| HASH      | The hash algorithm used with HKDF and                 |
|           | Transcript-Hash                                       |
+-----------+-------------------------------------------------------+
| VALUE     | The two byte ID assigned for this cipher              |
|           | suite                                                 |
+-----------+-------------------------------------------------------+

                 Table 4: Cipher Suite Name Structure

This spec defines the following cipher suites for TLS 1.3 ๐Ÿฆ‹

+==============================+=============+
| Description                  | Value       |
+==============================+=============+
| TLS_AES_128_GCM_SHA256       | {0x13,0x01} |
+------------------------------+-------------+
| TLS_AES_256_GCM_SHA384       | {0x13,0x02} |
+------------------------------+-------------+
| TLS_CHACHA20_POLY1305_SHA256 | {0x13,0x03} |
+------------------------------+-------------+
| TLS_AES_128_CCM_SHA256       | {0x13,0x04} |
+------------------------------+-------------+
| TLS_AES_128_CCM_8_SHA256     | {0x13,0x05} |
+------------------------------+-------------+

                     Table 5: Cipher Suite List

The AEAD algorithms AEAD_AES_128_GCM, AEAD_AES_256_GCM and AEAD_AES_128_CCM are defined in [RFC5116]. AEAD_CHACHA20_POLY1305 is defined in [RFC8439]. AEAD_AES_128_CCM_8 is defined in [RFC6655]. The matching hash algorithms are defined in [SHS].

Here's the plot twist ๐Ÿ™ˆ TLS 1.3 shares the same cipher suite space as older TLS versions, but its cipher suites are defined differently. They only specify the symmetric ciphers, and they can't be used with TLS 1.2. Same deal the other way: TLS 1.2 and lower suites can't be used with TLS 1.3 โœจ

New cipher suite values get assigned by IANA, as described in Section 11 ๐Ÿ“‹

Appendix C. Implementation Notes ๐Ÿง

okay the TLS protocol literally cannot stop every common security mistake, so this appendix is full of recommendations to help implementers ๐Ÿซถ [RFC8448] has test vectors for TLS 1.3 handshakes, which is so useful ๐ŸŒˆ

C.1. Random Number Generation and Seeding ๐ŸŽฒ

TLS needs a cryptographically secure pseudorandom number generator (CSPRNG). Most operating systems give you a fast, properly secure one, or you can grab one from a cryptographic library ๐Ÿ’– It is RECOMMENDED to use an existing CSPRNG implementation rather than crafting your own, and honestly, many good crypto libraries are already out there under friendly licence terms ๐Ÿ™Œ If none of those work out, [RFC4086] has guidance on generating random values ๐Ÿ“š

TLS uses random values in two places: (1) in public protocol fields, like the public Random values in the ClientHello and ServerHello, and (2) to generate keying material. When the CSPRNG works properly, this isn't a security problem, because you can't feasibly work out the CSPRNG state from its output. But if the CSPRNG is broken, an attacker might use the public output to recover the internal state and then predict the keying material, as documented in [CHECKOWAY] and [DSA-1571-1] ๐Ÿ˜ฑ

Implementations can add extra protection against this kind of attack by using separate CSPRNGs to generate public and private values ๐Ÿ›ก๏ธ

[RFC8937] describes a way for security protocol implementations to boost their (pseudo)random number generators using a long-term private key and a deterministic signature function. This helps when the random number generator is broken or has been tampered with ๐Ÿ”ฅ

C.2. Certificates and Authentication ๐Ÿ”

Implementations are responsible for verifying the integrity of certificates, and they should generally support certificate revocation messages (see Section 4.5.1.1) ๐Ÿ’… Unless an application profile says otherwise, certificates should always be verified to make sure a trusted certificate authority (CA) actually signed them. Choosing and adding trust anchors should be done very carefully, and users should be able to view information about the certificate and the trust anchor ๐Ÿ‘€ Applications SHOULD also enforce minimum and maximum key sizes. For example, certification paths with keys or signatures weaker than 2048-bit RSA or 224-bit ECDSA are not appropriate for secure applications ๐Ÿšซ

โš ๏ธ It's common in some protocols to use the same certificate in both client and server modes. This setup hasn't been studied extensively, so it's the responsibility of the higher-level protocol to make sure there's no ambiguity about the higher-level semantics in this case. Be careful, bestie ๐Ÿ’€

C.3. Implementation Pitfalls ๐Ÿ›

okay so implementation experience has shown that some parts of the older TLS specs are lowkey confusing, and they've caused interop and security problems ๐Ÿ˜ญ. A lot of that got cleared up in this doc, but this appendix is a short list of the most important things implementors need to give extra attention ๐Ÿ’….

TLS protocol issues:

  • Do you correctly handle handshake messages that are fragmented across multiple TLS records (see Section 5.1)? Do you handle corner cases like a ClientHello that is split into several small fragments? Do you fragment handshake messages that exceed the maximum fragment size? The Certificate and CertificateRequest handshake messages can be big enough to need fragmentation, so be careful with those ๐Ÿงฉ. Certificate compression as defined in [RFC8879] can be used to reduce the risk of fragmentation.
  • Do you ignore the TLS record layer version number in all unencrypted TLS records (see Appendix E)?
  • Have you made sure that all support for SSL, RC4, EXPORT ciphers, and MD5 (via the "signature_algorithms" extension) is completely removed from every configuration that supports TLS 1.3 or later, and that attempts to use these obsolete capabilities fail correctly (see Appendix E)? no more zombie ciphers, bestie ๐ŸงŸ
  • Do you handle TLS extensions in ClientHellos correctly, including unknown extensions?
  • When the server has requested a client certificate but no suitable certificate is available, do you correctly send an empty Certificate message instead of omitting the whole message (see Section 4.5.1)? ๐Ÿซถ
  • When processing the plaintext fragment produced by AEAD-Decrypt and scanning from the end for the ContentType, do you avoid scanning past the start of the cleartext in the event that the peer has sent a malformed plaintext of all zeros? ๐Ÿ™ˆ
  • Do you properly ignore unrecognized cipher suites (Section 4.2.2), hello extensions (Section 4.3), named groups (Section 4.3.7), key shares (Section 4.3.8), supported versions (Section 4.3.1), and signature algorithms (Section 4.3.3) in the ClientHello?
  • As a server, do you send a HelloRetryRequest to clients that support a compatible asymmetric key exchange group but do not predict it in the "key_share" extension? As a client, do you correctly handle a HelloRetryRequest from the server? ๐Ÿ”

Cryptographic details:

  • What countermeasures do you use to prevent timing attacks [TIMING]? โฑ๏ธ
  • When using Diffie-Hellman key exchange, do you correctly preserve leading zero bytes in the negotiated key (see Section 7.4.1)?
  • Does your TLS client check that the Diffie-Hellman parameters sent by the server are acceptable (see Section 4.3.8.1)?
  • Do you use a strong and, most importantly, properly seeded random number generator (see Appendix C.1) when generating Diffie-Hellman private values, the ECDSA "k" parameter, and other security-critical values? It is RECOMMENDED that implementations implement "deterministic ECDSA" as specified in [RFC6979]. Note that purely deterministic Elliptic Curve Cryptography (ECC) signatures, such as deterministic ECDSA and EdDSA, may be vulnerable to certain side-channel and fault injection attacks in easily accessible Internet of Things (IoT) devices. ๐Ÿ” this one is giving serious "read the fine print" energy.
  • Do you zero-pad Diffie-Hellman public key values and shared secrets to the group size (see Section 4.3.8.1 and Section 7.4.1)?
  • Do you verify signatures after making them, to protect against RSA-CRT key leaks [FW15]? ๐Ÿ›ก๏ธ

C.4. Client and Server Tracking Prevention ๐Ÿ‘€

Clients SHOULD NOT reuse a ticket for multiple connections. Reusing a ticket lets passive observers correlate different connections, and nobody wants that ๐Ÿ™…โ€โ™€๏ธ. Servers that issue tickets SHOULD offer at least as many tickets as the number of connections a client might use. For example, a web browser using HTTP/1.1 [RFC9112] might open six connections to a server. Servers SHOULD issue new tickets with every connection, so clients can always grab a fresh ticket when they open a new connection โœจ.

Offering a ticket to a server also lets the server correlate different connections, and this is possible even without ticket reuse ๐Ÿ˜ฌ. Client applications SHOULD NOT offer tickets across connections that are meant to be uncorrelated. For example, [FETCH] defines network partition keys to separate cache lookups in web browsers ๐Ÿ“.

It is RECOMMENDED that the labels for external identities be selected so that they don't give extra information about the identity of the user. For instance, if the label includes an email address, that trivially identifies the user to a passive attacker, unlike the client's Certificate, which is encrypted. There are a few ways to avoid this risk: (1) using random identity labels, (2) pre-encrypting the identity under a key known to the server, or (3) using Encrypted Client Hello [RFC9849]. ๐ŸŽ€

If an external PSK identity is used for multiple connections, it will generally be possible for an external observer to track clients and/or servers across connections. Encrypted Client Hello [RFC9849] can mitigate this risk, and so can mechanisms outside of TLS that rotate or encrypt the PSK identity ๐Ÿ”„.

C.5. Unauthenticated Operation ๐Ÿ•ต๏ธโ€โ™€๏ธ

Previous versions of TLS offered explicitly unauthenticated cipher suites based on anonymous Diffie-Hellman. These modes have been deprecated in TLS 1.3. However, it's still possible to negotiate parameters that don't provide verifiable server authentication, by several methods, including:

  • Raw public keys [RFC7250].
  • Using a public key contained in a certificate but without validation of the certificate chain or any of its contents.

Either technique used alone is vulnerable to man-in-the-middle attacks, so it's unsafe for general use ๐Ÿšจ. It is possible to bind such connections to an external authentication mechanism, though, via out-of-band validation of the server's public key, trust on first use, or a mechanism such as channel bindings (though the channel bindings described in [RFC5929] are not defined for TLS 1.3). If no such mechanism is used, then the connection has no protection against an active man-in-the-middle attack. Applications MUST NOT use TLS in such a way absent explicit configuration or a specific application profile. ๐Ÿ’€ please don't.


Appendix D. Updates to TLS 1.2 ๐Ÿ“

To line up with the names used in this document, the following terms from [RFC5246] get renamed:

  • The master secret, computed in Section 8.1 of [RFC5246], is renamed to the main secret. It is referred to as main_secret in formulas and structures, instead of master_secret. However, the label parameter to the PRF function is left unchanged for compatibility.
  • The premaster secret is renamed to the preliminary secret. It is referred to as preliminary_secret in formulas and structures, instead of pre_master_secret.
  • The PreMasterSecret and EncryptedPreMasterSecret structures, defined in Section 7.4.7.1 of [RFC5246], are renamed to PreliminarySecret and EncryptedPreliminarySecret, respectively.

Correspondingly, the extension defined in [RFC7627] is renamed to the "Extended Main Secret" extension. The extension code point is renamed to "extended_main_secret". The label parameter to the PRF function in Section 4 of [RFC7627] is left unchanged for compatibility ๐Ÿฆ‹.

Appendix E. Backward Compatibility ๐ŸŒˆ

The TLS protocol has a built-in mechanism for version negotiation between endpoints that might support different versions of TLS ๐Ÿค.

TLS 1.x and SSL 3.0 use compatible ClientHello messages. Servers can also handle clients trying to use future versions of TLS, as long as the ClientHello format stays compatible and there's at least one protocol version supported by both the client and the server.

Earlier versions of TLS used the record layer version number (TLSPlaintext.legacy_record_version and TLSCiphertext.legacy_record_version) for various purposes. As of TLS 1.3, this field is deprecated. The value of TLSPlaintext.legacy_record_version MUST be ignored by all implementations. The value of TLSCiphertext.legacy_record_version is included in the additional data for deprotection but MAY otherwise be ignored, or MAY be validated to match the fixed constant value. Version negotiation is performed using only the handshake versions (ClientHello.legacy_version and ServerHello.legacy_version, as well as the ClientHello, HelloRetryRequest, and ServerHello "supported_versions" extensions). To maximize interoperability with older endpoints, implementations that negotiate the use of TLS 1.0-1.2 SHOULD set the record layer version number to the negotiated version for the ServerHello and all records after it ๐Ÿ’–.

For maximum compatibility with previously non-standard behavior and misconfigured deployments, all implementations SHOULD support validation of certification paths based on the expectations in this document, even when handling handshakes from prior TLS versions (see Section 4.5.1.2).

TLS 1.2 and prior supported an "Extended Main Secret" [RFC7627] extension, which digested large parts of the handshake transcript into the secret and derived keys. Note that this extension was renamed in Appendix D. Because TLS 1.3 always hashes in the transcript up to the server Finished, implementations that support both TLS 1.3 and earlier versions SHOULD indicate the use of the Extended Main Secret extension in their APIs whenever TLS 1.3 is used ๐Ÿง .

E.1. Negotiating with an Older Server ๐Ÿค™

A TLS 1.3 client that wants to negotiate with servers that don't support TLS 1.3 will send a normal TLS 1.3 ClientHello containing 0x0303 (TLS 1.2) in ClientHello.legacy_version, but with the correct version(s) in the "supported_versions" extension. If the server doesn't support TLS 1.3, it will respond with a ServerHello containing an older version number. If the client agrees to use this version, the negotiation proceeds as appropriate for the negotiated protocol. A client using a ticket for resumption SHOULD initiate the connection using the version that was previously negotiated ๐Ÿ”.

Note that 0-RTT data is not compatible with older servers and SHOULD NOT be sent unless the client knows the server supports TLS 1.3. See Appendix E.3. โš ๏ธ

If the version chosen by the server is not supported by the client (or is not acceptable), the client MUST abort the handshake with a "protocol_version" alert.

Some legacy server implementations are known to not implement the TLS specification properly, and they might abort connections when they run into TLS extensions or versions they don't recognize ๐Ÿฅฒ. Interoperability with buggy servers is a complicated topic that's beyond the scope of this document. Multiple connection attempts may be required to negotiate a backward-compatible connection. However, this practice is vulnerable to downgrade attacks and is NOT RECOMMENDED. ๐Ÿšซ

E.2. Negotiating with an Older Client โœจ

okay so a TLS server can also get a ClientHello whose version number is lower than the highest version it supports ๐Ÿ“ฑ bestie, that's the "still on my old phone" energy ๐Ÿ™ˆ

If the supported_versions extension is there, the server MUST negotiate using that extension, the way Section 4.3.1 describes ๐Ÿ’… If supported_versions is missing, the server MUST negotiate the minimum of ClientHello.legacy_version and TLS 1.2 ๐ŸŽ€ For example, if the server supports TLS 1.0, 1.1, and 1.2 and legacy_version is TLS 1.0, the server will proceed with a TLS 1.0 ServerHello ๐Ÿฆ‹ If supported_versions is absent and the server only supports versions greater than ClientHello.legacy_version, the server MUST abort the handshake with a protocol_version alert ๐Ÿ’€

โš ๏ธ the protocol_version alert is the server's way of saying "sorry bestie, we don't overlap" ๐Ÿ˜ญ

Earlier versions of TLS didn't clearly specify the record layer version number value in every case (TLSPlaintext.legacy_record_version) ๐ŸŒธ Servers will receive all kinds of TLS 1.x versions in this field, but its value MUST always be ignored ๐Ÿซถ

E.3. 0-RTT Backward Compatibility ๐Ÿงš

0-RTT data is not compatible with older servers, bestie ๐Ÿ˜ญ An older server responds to the ClientHello with an older ServerHello, but it won't correctly skip the 0-RTT data and will fail to complete the handshake. This can cause issues when a client tries to use 0-RTT, especially against multi-server deployments. For example, a deployment could roll out TLS 1.3 gradually, with some servers on TLS 1.3 and some still on TLS 1.2, or a TLS 1.3 deployment could be downgraded to TLS 1.2 ๐Ÿ™ˆ

A client that attempts to send 0-RTT data MUST fail a connection if it receives a ServerHello with TLS 1.2 or older ๐Ÿ’” It can then retry the connection with 0-RTT disabled. To avoid a downgrade attack, the client SHOULD NOT disable TLS 1.3, only 0-RTT ๐Ÿ”ฅ

To avoid this error condition, multi-server deployments SHOULD ensure a uniform and stable deployment of TLS 1.3 without 0-RTT prior to enabling 0-RTT โœจ

E.4. Middlebox Compatibility Mode ๐ŸŒˆ

Field measurements [Ben17a] [Ben17b] [Res17a] [Res17b] have found that a significant number of middleboxes misbehave when a TLS client/server pair negotiates TLS 1.3 ๐Ÿ˜ฉ So implementations can boost their odds of getting through those middleboxes by making the TLS 1.3 handshake look more like a TLS 1.2 handshake:

  • The client always provides a non-empty session ID in the ClientHello, as described in the legacy_session_id section of Section 4.2.2. ๐Ÿ“
  • If not offering early data, the client sends a dummy change_cipher_spec record (see the third paragraph of Section 5) immediately before its second flight. This may either be before its second ClientHello or before its encrypted handshake flight. If offering early data, the record is placed immediately after the first ClientHello. ๐Ÿง
  • The server sends a dummy change_cipher_spec record immediately after its first handshake message. This may either be after a ServerHello or a HelloRetryRequest. ๐Ÿ’œ

When you put all of this together, the TLS 1.3 handshake starts to resemble TLS 1.2 session resumption, which improves the chance of connecting successfully through middleboxes ๐Ÿฆ„ This "compatibility mode" is only partially negotiated: the client can opt to provide a session ID or not, and the server has to echo it. Either side can send change_cipher_spec at any time during the handshake, as they must be ignored by the peer, but if the client sends a non-empty session ID, the server MUST send the change_cipher_spec as described in this appendix ๐ŸŽ€

okay this is the serious section, bestie, so let's lock in ๐Ÿ”’

Implementations negotiating the use of older versions of TLS SHOULD prefer forward secret and AEAD cipher suites, when available โœจ

The security of RC4 cipher suites is considered insufficient for the reasons cited in [RFC7465]. Implementations MUST NOT offer or negotiate RC4 cipher suites for any version of TLS for any reason ๐Ÿšซ

Old versions of TLS allowed very low strength ciphers. Ciphers with a strength less than 112 bits MUST NOT be offered or negotiated for any version of TLS for any reason ๐Ÿšซ

The security of SSL 2.0 [SSL2], SSL 3.0 [RFC6101], TLS 1.0 [RFC2246], and TLS 1.1 [RFC4346] is considered insufficient for the reasons enumerated in [RFC6176], [RFC7568], and [RFC8996], and they MUST NOT be negotiated for any reason ๐Ÿ’€

Implementations MUST NOT send an SSL version 2.0 compatible CLIENT-HELLO. Implementations MUST NOT negotiate TLS 1.3 or later using an SSL version 2.0 compatible CLIENT-HELLO. Implementations are NOT RECOMMENDED to accept an SSL version 2.0 compatible CLIENT-HELLO to negotiate older versions of TLS ๐Ÿ™…

Implementations MUST use 0x0303 for the ClientHello.legacy_version and ServerHello.legacy_version. Implementations receiving Hello messages with other version numbers MUST abort the handshake as described in Section 4.2.2 and Section 4.2.3 ๐Ÿ›‘

Implementations MUST NOT send any records with a version less than 0x0300. Implementations SHOULD NOT accept any records with a version less than 0x0300 (but may inadvertently do so if the record version number is ignored completely) ๐Ÿ™ˆ

Implementations MUST NOT use the Truncated HMAC extension, defined in Section 7 of [RFC6066], as it is not applicable to AEAD algorithms and has been shown to be insecure in some scenarios ๐Ÿ’” bestie, skip it, no cap.

Appendix F. Overview of Security Properties ๐Ÿง 

A complete security analysis of TLS is outside the scope of this document. In this appendix, we provide an informal description of the desired properties, as well as references to more detailed work in the research literature that gives more formal definitions ๐Ÿ“š

We cover properties of the handshake separately from those of the record layer ๐Ÿฆ‹

F.1. Handshake ๐Ÿค

Okay so the TLS handshake is an Authenticated Key Exchange (AKE) protocol ๐Ÿ” that's built to give you both one-way authenticated (server-only) AND mutually authenticated (client and server) vibes. When it finishes, each side walks away holding its own view of:

  • A set of "session keys" (the various secrets derived from the main secret) from which a set of working keys can be derived. Heads up: when early data is in use, secrets get derived from the early secret too. These are lowkey weaker than the ones from the main secret, more on that below โœจ
  • A set of cryptographic parameters (algorithms, etc.) ๐ŸŽ€
  • The identities of the communicating parties ๐Ÿซถ

We assume the attacker is an active network attacker, meaning they have complete control over the network the two parties use to talk [RFC3552] ๐Ÿ˜ˆ. Even then, the handshake should still deliver the properties below. They aren't necessarily independent of each other. They just reflect what protocol consumers actually need ๐Ÿ’…

  • Establishing the same session keys: The handshake needs to output the same set of session keys on both sides, provided it completes successfully on each endpoint (see [CK01], Definition 1, part 1). ๐Ÿ”‘
  • Secrecy of the session keys: The shared session keys should be known only to the communicating parties and not to the attacker (see [CK01]; Definition 1, part 2). Note that in a unilaterally authenticated connection, the attacker can establish its own session keys with the server, but those session keys are distinct from the ones the client established. ๐Ÿ™ˆ
  • Peer Authentication: The client's view of the peer identity should reflect the server's identity. If the client is authenticated, the server's view of the peer identity should match the client's identity. ๐Ÿชช
  • Uniqueness of the session keys: Any two distinct handshakes should produce distinct, unrelated session keys. Individual session keys produced by a handshake should also be distinct and independent. ๐Ÿฆ‹
  • Downgrade Protection: The cryptographic parameters should be the same on both sides, and they should be the same as if the peers had been talking without any attack going on (see [BBFGKZ16]; Definitions 8 and 9). ๐Ÿ›ก๏ธ
  • Forward secret with respect to long-term keys: If the long-term keying material (here that's the signature keys in certificate-based authentication modes, or the external/resumption PSK in PSK with asymmetric key exchange modes) gets compromised after the handshake is complete, the security of the session key is NOT compromised (see [DOW92]). That's only true as long as the session key itself, and all material that could be used to recreate it, has been erased ๐Ÿงน. Specifically, private keys for key shares, shared secrets, and keys derived in the TLS key schedule also need to be erased, except for binder_key, resumption_secret, and PSKs derived from the resumption_secret. The forward secrecy property is not satisfied when PSK is used in the psk_ke PskKeyExchangeMode. ๐Ÿšซ Failing to erase keys or secrets that were meant to be ephemeral or connection-specific basically creates extra long-term keys that now need protecting. If those long-term keys leak (even after the handshake is done), the connection's traffic can lose its protection. ๐Ÿ˜ญ
  • Key Compromise Impersonation (KCI) resistance: In a mutually authenticated connection with certificates, compromising one actor's long-term secret should not break that actor's authentication of their peer in the given connection (see [HGFS15]). For example, if a client's signature key is compromised, it should not be possible to impersonate arbitrary servers to that client in later handshakes. ๐Ÿ‘€
  • Protection of endpoint identities: The server's identity (certificate) should be protected against passive attackers. The client's identity (certificate) should be protected against both passive and active attackers. This property does not hold for cipher suites without confidentiality. This specification doesn't define any such cipher suites, but other documents may do so. ๐ŸŽญ

Informally, the signature-based modes of TLS 1.3 set up a unique, secret, shared key through an asymmetric key exchange. That key is authenticated by the server's signature over the handshake transcript, and it's also tied to the server's identity by a MAC. If the client is authenticated by a certificate, it signs over the handshake transcript too, and provides a MAC tied to both identities ๐Ÿงฉ. [SIGMA] covers the design and analysis of this type of key exchange protocol. If fresh asymmetric keys are used for each connection, the output keys are forward secret ๐ŸŒธ.

The external PSK and resumption PSK bootstrap from a long-term shared secret into a unique, per-connection set of short-term session keys ๐Ÿ”. That secret may have been established in a previous handshake. If PSK with asymmetric key establishment is used, these session keys are forward secret too. The resumption PSK is designed so that the resumption secret computed by connection N, which connection N+1 needs, is separate from the traffic keys used by connection N. That gives you forward secrecy between connections ๐ŸŒˆ. Also, if multiple tickets get established on the same connection, they're associated with different keys. So compromising the PSK for one ticket doesn't compromise connections that used the PSKs for other tickets. This property is most interesting when tickets are stored in a database (so they can be deleted), rather than when they're self-encrypted ๐Ÿ—‘๏ธ.

Forward secrecy limits the effect of key leakage in one direction: compromising a key at time T2 doesn't compromise some key at time T1 where T1 < T2 โช. Protection in the other direction (compromise at time T1 doesn't compromise keys at time T2) can be achieved by rerunning an asymmetric key establishment ๐Ÿ”„. If a long-term authentication key has been compromised, a full handshake with asymmetric key establishment protects against passive attackers. If the resumption_secret has been compromised, a resumption handshake with asymmetric key establishment protects against passive attackers, and a full handshake with asymmetric key establishment protects against active attackers. If a traffic secret has been compromised, any handshake with asymmetric key establishment protects against active attackers. Using the terms in [RFC7624], forward secrecy without rerunning asymmetric key establishment doesn't stop an attacker from doing static key exfiltration ๐Ÿ˜ฑ. After key exfiltration of application_traffic_secret_N, an attacker can, e.g., passively eavesdrop on all future data sent on the connection, including data encrypted with application_traffic_secret_N+1, application_traffic_secret_N+2, and so on. Frequently rerunning asymmetric key establishment forces an attacker to do dynamic key exfiltration (or content exfiltration) instead, which is way harder ๐Ÿ’ช.

The PSK binder value forms a binding between a PSK and the current handshake. It also binds the session where the PSK was established (if it was established via a NewSessionTicket message) to the current session ๐Ÿ”—. This binding transitively includes the original handshake transcript, because that transcript is digested into the values that produce the resumption secret. This requires that both the KDF used to produce the resumption secret and the MAC used to compute the binder be collision resistant. See Appendix F.1.1 for more on this. Note: The binder does not cover the binder values from other PSKs, though they are included in the Finished MAC ๐Ÿ“.

Note: This specification does not currently permit the server to send a CertificateRequest message in non-certificate-based handshakes (e.g., PSK) ๐Ÿ™…. If this restriction were ever relaxed, the client's signature would not cover the server's certificate directly. However, if the PSK was established through a NewSessionTicket, the client's signature would transitively cover the server's certificate through the PSK binder. [PSK-FINISHED] describes a concrete attack on constructions that don't bind to the server's certificate (see also [Kraw16]) ๐Ÿšจ. It is unsafe to use certificate-based client authentication when the client might share the same PSK/key-id pair with two different endpoints. Unless some other specification says otherwise, implementations MUST NOT combine external PSKs with certificate-based authentication of either the client or the server. [RFC8773] provides an extension to permit this, but it hasn't received the same level of analysis as this specification ๐Ÿง.

If an exporter is used, it produces values that are unique and secret, because they're generated from a unique session key ๐Ÿ—๏ธ. Exporters computed with different labels and contexts are computationally independent, so it isn't feasible to compute one from another, or to compute the session secret from the exported value. Note: Exporters can produce arbitrary-length values. If exporters are to be used as channel bindings, the exported value MUST be large enough to provide collision resistance ๐Ÿ’œ. The exporters provided in TLS 1.3 are derived from the same Handshake Contexts as the early traffic keys and the application traffic keys, respectively, so they have similar security properties. Note that they don't include the client's certificate. Future applications that want to bind to the client's certificate may need to define a new exporter that includes the full handshake transcript ๐Ÿ“œ.

For all handshake modes, the Finished MAC (and, where present, the signature) prevents downgrade attacks ๐Ÿ™Œ. Also, the use of certain bytes in the random nonces, as described in Section 4.2.3, allows detection of downgrade to previous TLS versions ๐Ÿ•ต๏ธโ€โ™€๏ธ. See [BBFGKZ16] for more details on TLS 1.3 and downgrade.

As soon as the client and the server have exchanged enough information to establish shared keys, the rest of the handshake is encrypted ๐Ÿ”’. That protects against passive attackers, even if the computed shared key isn't authenticated yet. Because the server authenticates before the client, the client can make sure that if it authenticates to the server, it only reveals its identity to an authenticated server ๐Ÿฅน. Note that implementations must use the provided record-padding mechanism during the handshake to avoid leaking identity information through lengths ๐Ÿ“. The client's proposed PSK identities aren't encrypted, and neither is the one the server selects ๐Ÿ‘€.

F.1.1. Key Derivation and HKDF ๐Ÿ”‘

okay so key derivation in TLS 1.3 runs on HKDF, as defined in [RFC5869], with its two parts, HKDF-Extract and HKDF-Expand ๐Ÿ’–. The full reasoning behind the HKDF construction is in [Kraw10], and the reasoning for how TLS 1.3 uses it is in [KW16] โœจ. Throughout this doc, every HKDF-Extract gets followed by one or more HKDF-Expand calls. This ordering should always be followed, including in future revisions of this doc. Bestie, one SHOULD NOT feed an HKDF-Extract output into another HKDF-Extract without an HKDF-Expand in between ๐Ÿ™…โ€โ™€๏ธ. Multiple HKDF-Expand calls on some of the same inputs are allowed, as long as they're told apart by the key and/or the labels ๐Ÿท๏ธ.

Also, HKDF-Expand is basically a pseudorandom function (PRF) with inputs and outputs of variable length ๐Ÿงฉ. For some uses, like generating exporters and the resumption_secret, the HKDF-Expand application has to be collision resistant. That means it should be infeasible to find two different inputs to HKDF-Expand that output the same value ๐Ÿšซ. This needs the underlying hash function to be collision resistant, and the HKDF-Expand output to be at least 256 bits (or as much as needed for the hash function to prevent finding collisions) ๐Ÿ”.

F.1.2. Certificate-Based Client Authentication ๐Ÿชช

A client that has sent certificate-based authentication data to a server, either during the handshake or in post-handshake authentication, can't be sure whether the server afterwards considers it authenticated ๐Ÿ˜ญ. If the client needs to know whether the server considers the connection unilaterally or mutually authenticated, the application layer has to provision that. See [CHHSV17] for details ๐Ÿ“. Plus, the analysis of post-handshake authentication in [Kraw16] shows that the client identified by the certificate sent in the post-handshake phase possesses the traffic key. So that party is either the client that did the original handshake, or someone the original client delegated the traffic key to (assuming the traffic key hasn't been compromised) ๐Ÿ”‘โœจ.

F.1.3. 0-RTT ๐Ÿš€

The 0-RTT mode generally gives security properties similar to 1-RTT data, with two exceptions. First, the 0-RTT encryption keys don't provide full forward secrecy. Second, the server can't guarantee uniqueness of the handshake (non-replayability) without keeping potentially undue amounts of state ๐Ÿ™ˆ. See Section 8 for mechanisms that limit exposure to replay ๐Ÿ›ก๏ธ.

F.1.4. Exporter Independence ๐Ÿงช

exporter_secret and early_exporter_secret are derived to be independent of the traffic keys, so they don't threaten the security of traffic encrypted with those keys ๐Ÿ’…. However, because these secrets can be used to compute any exporter value, they SHOULD be erased as soon as possible ๐Ÿงน. If the total set of exporter labels is known, implementations SHOULD pre-compute the inner Derive-Secret stage of the exporter computation for all those labels. Then they erase the [early_]exporter_secret, followed by each inner value as soon as it's known it won't be needed again โœจ.

F.1.5. Post-Compromise Security ๐Ÿ’”

TLS doesn't provide security for handshakes that take place after the peer's long-term secret (signature key or external PSK) is compromised. So it doesn't provide post-compromise security [CCG16], which is sometimes called backwards or future secrecy ๐Ÿฅ€. This is different from KCI resistance, which describes the security guarantees a party has after its own long-term secret has been compromised ๐Ÿง .

F.1.6. External References ๐Ÿ“š

The reader should check these references for analysis of the TLS handshake: [DFGS15], [CHSV16], [DFGS16], [KW16], [Kraw16], [FGSW16], [LXZFH16], [FG17], and [BBK17] ๐Ÿค“.

F.2. Record Layer ๐Ÿ“ฆ

The record layer depends on the handshake producing strong traffic secrets, which can be used to derive bidirectional encryption keys and nonces ๐Ÿ”. Assuming that's true, and the keys are used for no more data than indicated in Section 5.5, then the record layer should provide these guarantees:

  • Confidentiality: An attacker should not be able to determine the plaintext contents of a given record. ๐Ÿ™Š
  • Integrity: An attacker should not be able to craft a new record that is different from an existing record and will be accepted by the receiver. ๐Ÿšซ
  • Order protection/non-replayability: An attacker should not be able to cause the receiver to accept a record it has already accepted, or cause the receiver to accept record N+1 without having first processed record N. ๐Ÿ”
  • Length concealment: Given a record with a given external length, the attacker should not be able to determine how much of the record is content versus padding. ๐Ÿซฅ
  • Forward secrecy after key change: If the traffic key update mechanism described in Section 4.7.3 has been used and the previous generation key is deleted, an attacker who compromises the endpoint should not be able to decrypt traffic encrypted with the old key. ๐ŸงŠ

Informally, TLS 1.3 provides these properties by AEAD-protecting the plaintext with a strong key ๐Ÿ›ก๏ธ. AEAD encryption [RFC5116] provides confidentiality and integrity for the data. Non-replayability comes from using a separate nonce for each record, derived from the record sequence number (Section 5.3), with the sequence number maintained independently at both sides. So records delivered out of order result in AEAD deprotection failures ๐Ÿ™ƒ. To prevent mass cryptanalysis when the same plaintext is repeatedly encrypted by different users under the same key (as is commonly the case for HTTP) ๐Ÿ“, the nonce is formed by mixing the sequence number with a secret per-connection initialization vector derived along with the traffic keys. See [BT16] for analysis of this construction ๐Ÿงฎ.

The rekeying technique in TLS 1.3 (see Section 7.2) follows the construction of the serial generator discussed in [REKEY]. That shows rekeying can allow keys to be used for a larger number of encryptions than without rekeying ๐Ÿ“ˆ. This relies on the security of HKDF-Expand-Label as a pseudorandom function (PRF). Also, as long as that function is truly one-way, it's not possible to compute traffic keys from before a key change (forward secrecy) ๐Ÿ’œ.

TLS doesn't provide security for data communicated on a connection after a traffic secret of that connection is compromised. That means TLS doesn't provide post-compromise security/future secrecy/backward secrecy with respect to the traffic secret. Indeed, an attacker who learns a traffic secret can compute all future traffic secrets on that connection ๐Ÿ˜ฑ. Systems that want such guarantees need to do a fresh handshake and establish a new connection with an asymmetric key exchange ๐Ÿ”„.

F.2.1. External References ๐Ÿ“š

The reader should check these references for analysis of the TLS record layer: [BMMRT15], [BT16], [BDFKPPRSZZ16], [BBK17], and [PS18] ๐Ÿง.

F.3. Traffic Analysis ๐Ÿ‘€

TLS is susceptible to a bunch of traffic analysis attacks based on watching the length and timing of encrypted packets [CLINIC] [HCJC16] ๐Ÿ•ต๏ธโ€โ™€๏ธ. This is especially easy when there's a small set of possible messages to tell apart, like a video server hosting a fixed corpus of content. But it still leaks usable info even in more complicated scenarios ๐ŸŽฌ.

TLS doesn't provide any specific defenses against this kind of attack, BUT it does include a padding mechanism for applications ๐Ÿงธ. The plaintext protected by the AEAD function consists of content plus variable-length padding. That lets the application produce arbitrary-length encrypted records, as well as padding-only cover traffic to hide the difference between periods of transmission and periods of silence ๐Ÿซฃ. Because the padding is encrypted alongside the actual content, an attacker can't directly determine the length of the padding. But they may be able to measure it indirectly through timing channels exposed during record processing, like seeing how long it takes to process a record, or trickling in records to see which ones elicit a response from the server โฑ๏ธ. In general, it's not known how to remove all of these channels, because even a constant-time padding removal function will likely feed the content into data-dependent functions ๐Ÿ˜ต. At minimum, a fully constant-time server or client would require close cooperation with the application-layer protocol implementation, including making that higher-level protocol constant time ๐Ÿง‘โ€๐Ÿ’ป.

Note: Robust traffic analysis defenses will likely lead to inferior performance due to delays in transmitting packets and increased traffic volume. ๐Ÿข

F.4. Side Channel Attacks ๐Ÿซฃ

In general, TLS doesn't have specific defenses against side-channel attacks (attacks on the communications via secondary channels, like timing), leaving those to the implementation of the relevant cryptographic primitives ๐Ÿ”ง. However, certain features of TLS are designed to make it easier to write side-channel resistant code ๐Ÿ’ช:

  • Unlike previous versions of TLS, which used a composite MAC-then-encrypt structure, TLS 1.3 only uses AEAD algorithms. That lets implementations use self-contained constant-time implementations of those primitives โœจ.
  • TLS uses a uniform "bad_record_mac" alert for all decryption errors, which is meant to stop an attacker from gaining piecewise insight into portions of the message ๐Ÿšจ. Additional resistance is provided by terminating the connection on such errors. A new connection will have different cryptographic material, preventing attacks against the cryptographic primitives that require multiple trials ๐Ÿ”.

Information leakage through side channels can occur at layers above TLS, in application protocols and the applications that use them ๐Ÿ“ฑ. Resistance to side-channel attacks depends on applications and application protocols separately ensuring that confidential information isn't inadvertently leaked ๐Ÿ”’.

F.5. Replay Attacks on 0-RTT โœจ

Okay bestie, let's talk about replay attacks on 0-RTT data ๐Ÿ˜ญ๐Ÿ”ฅ. Replayable 0-RTT data is lowkey a whole threat to TLS-using apps ๐Ÿ’€, unless the app is specifically built to be safe under replay. Minimally that means idempotent ๐Ÿซถ, but in plenty of cases it needs even stronger stuff too, like constant-time responses. The attacks it opens up are:

  • Duplicating actions with side effects (think buying something or moving money ๐Ÿ’ธ), so the site or the user gets hurt because it happened twice ๐Ÿ™ˆ.
  • Storing and replaying 0-RTT messages to reorder them compared to other messages. Like, moving a delete to after a create? Chaotic ๐Ÿ’€.
  • Amplifying existing information leaks caused by side effects like caching. An attacker could replay a 0-RTT message to a cache node that hasn't cached some resource they care about, then check on a separate connection whether that resource has shown up in the cache ๐Ÿ‘€. They can repeat this with different cache nodes as many times as the message is replayable.

If data can be replayed a bunch of times, even more attacks become possible, like making repeated measurements of how fast crypto operations are ๐Ÿง, or overloading rate-limiting systems ๐Ÿ˜ค. For a deeper dive on all this, check out [Mac17] ๐Ÿ“š.

Ultimately, servers are responsible for protecting themselves against 0-RTT data replay ๐Ÿ›ก๏ธ. The mechanisms in Section 8 are meant to prevent replay at the TLS layer, but they don't fully protect against receiving multiple copies of client data ๐Ÿ˜ฌ. When the server has no info about the client (maybe it's in a different cluster that doesn't share state, or the ticket got deleted as described in Section 8.1), TLS 1.3 falls back to the 1-RTT handshake ๐Ÿ”. If the application-layer protocol retransmits data in that situation, an attacker can cause duplication by sending the ClientHello to both the original cluster (which processes the data right away) and another cluster that falls back to 1-RTT and processes the data when the application replays it ๐ŸŽญ. How big this attack can get depends on how willing the client is to retry transactions, so it only allows a limited amount of duplication, with each copy showing up as a brand new connection at the server ๐Ÿฆ‹.

If implemented correctly, the mechanisms in Section 8.1 and Section 8.2 stop a replayed ClientHello and its 0-RTT data from being accepted multiple times by any cluster with consistent state โœจ. For servers that limit 0-RTT to one cluster per ticket, a given ClientHello and its 0-RTT data will only be accepted once ๐Ÿ’–. But if state isn't completely consistent, an attacker might get multiple copies accepted during the replication window ๐Ÿ˜ฑ. Since clients don't know the exact details of server behavior, they MUST NOT send early data that isn't safe to have replayed and that they wouldn't be willing to retry across multiple 1-RTT connections ๐Ÿ™…โ€โ™€๏ธ.

Application protocols MUST NOT use 0-RTT data without a profile that defines its use ๐Ÿ“‹. That profile needs to say which messages or interactions are safe to send with 0-RTT, and how to handle the situation when the server rejects 0-RTT and falls back to 1-RTT ๐Ÿงš.

Also, to avoid accidental misuse, TLS implementations MUST NOT enable 0-RTT (sending or accepting) unless the application specifically asks for it ๐Ÿ™‹โ€โ™€๏ธ, and they MUST NOT automatically resend 0-RTT data if the server rejects it, unless the application tells them to ๐Ÿ™…. Server-side apps might want special processing for 0-RTT data for some kinds of traffic (like aborting the connection, asking for the data to be resent at the application layer, or delaying processing until the handshake completes) ๐ŸŽ€. So that apps can do this, TLS implementations MUST provide a way for the application to tell whether the handshake has completed ๐Ÿ’….

F.5.1. Replay and Exporters ๐Ÿฆ‹

Replays of the ClientHello produce the same early exporter, so apps that use these exporters need extra care ๐Ÿ‘€. Especially if the exporters are used as an authentication channel binding (for example, by signing the exporter output). In that case, an attacker who compromises the PSK can transplant authenticators between connections without compromising the authentication key ๐Ÿ˜ฑ.

Also, the early exporter SHOULD NOT be used to generate server-to-client encryption keys, because that would mean reusing those keys ๐Ÿ”‘. This mirrors how the early application traffic keys are only used in the client-to-server direction ๐Ÿ’œ.

F.6. PSK Identity Exposure ๐Ÿ•ต๏ธโ€โ™€๏ธ

Because implementations respond to an invalid PSK binder by aborting the handshake, an attacker might be able to check whether a given PSK identity is valid ๐Ÿ‘€. Specifically, if a server accepts both external-PSK and certificate-based handshakes, a valid PSK identity makes the handshake fail, while an invalid identity just gets skipped and results in a successful certificate handshake ๐Ÿซฃ. Servers that only support PSK handshakes can resist this by treating "no valid PSK identity" and "an identity with an invalid binder" exactly the same way ๐Ÿ›ก๏ธ.

F.7. Sharing PSKs Across Protocol Versions ๐ŸŽ€

TLS 1.3 is super conservative with PSKs and binds them to a specific KDF ๐Ÿ”’. TLS 1.2, on the other hand, lets PSKs be used with any hash function and the TLS 1.2 PRF ๐Ÿคทโ€โ™€๏ธ. So any PSK used with both TLS 1.2 and TLS 1.3 MUST be used with only one hash in TLS 1.3, which is less than ideal if users want to set up a single PSK ๐Ÿ˜ฉ. The constructions in TLS 1.2 and TLS 1.3 are different, though they're both based on HMAC ๐Ÿงฉ. There's no known way the same PSK could produce related output in both versions, but only limited analysis has been done ๐Ÿค”. Implementations can stay safe from cross-protocol related output by not reusing PSKs between TLS 1.3 and TLS 1.2 โœจ.

F.8. External PSKs and Rerouting ๐ŸŒธ

External PSKs in TLS are designed to be known to exactly one client and one server ๐Ÿ‘ฏ. But as noted in [RFC9257], some use cases share PSKs between more than two entities ๐Ÿ™ˆ. In those setups, beyond the expected weakness that a compromised group member can impersonate any other member, a malicious non-member can reroute handshakes between honest group members to connect them in unintended ways [Selfie] ๐Ÿ˜ˆ. [RFC9257] has recommendations for external PSK usage, including external PSK importers as defined in [RFC9258], which prevent that kind of malicious rerouting of messages ๐Ÿ™Œ.

F.9. Misbinding When Using Self-Signed Certificates or Raw Public Keys ๐Ÿฆ‹

When TLS 1.3 is used with self-signed certificates that have no useful identities (as in DTLS-SRTP [RFC5763]), or with raw public keys [RFC7250] for peer authentication, it may be vulnerable to misbinding attacks [MM24] ๐Ÿ˜ฌ. You can mitigate this by using the "external_id_hash" extension [RFC8844] or, if only the server is being authenticated, by having the server verify that the "server_name" extension matches its expected identity ๐Ÿ’….

F.10. Attacks on Static RSA ๐Ÿ’€

Although TLS 1.3 doesn't use RSA key transport and so isn't directly susceptible to Bleichenbacher-type attacks [Blei98], if TLS 1.3 servers also support static RSA in the context of previous versions of TLS, it may be possible to impersonate the server for TLS 1.3 connections [JSS15] ๐Ÿ˜ฑ. TLS 1.3 implementations can prevent this by disabling support for static RSA across all versions of TLS ๐Ÿ›‘. In principle, implementations might also separate certificates with different keyUsage bits for static RSA decryption and RSA signature, but this relies on clients refusing to accept signatures from keys in certificates that don't have the digitalSignature bit set, and many clients don't enforce that restriction ๐Ÿ™ƒโœจ.

Contributors โœจ

okay bestie, this is the whole squad that helped make this happen ๐Ÿซถ๐Ÿ’– names, affiliations, and emails are all right here, exactly as they were written, so you can find anyone you want to thank ๐Ÿฆ‹

  • Martin Abadi
    • University of California, Santa Cruz
    • abadi@cs.ucsc.edu
  • Christopher Allen
    • (co-editor of TLS 1.0)
    • Alacrity Ventures
    • ChristopherA@AlacrityManagement.com
  • Nimrod Aviram
    • Tel Aviv University
    • nimrod.aviram@gmail.com
  • Richard Barnes
    • Cisco
    • rlb@ipv.sx
  • Steven M. Bellovin
    • Columbia University
    • smb@cs.columbia.edu
  • David Benjamin
    • Google
    • davidben@google.com
  • Benjamin Beurdouche
    • INRIA & Microsoft Research
    • benjamin.beurdouche@ens.fr
  • Karthikeyan Bhargavan
    • (editor of RFC 7627)
    • INRIA
    • karthikeyan.bhargavan@inria.fr
  • Simon Blake-Wilson
    • (co-author of RFC 4492)
    • BCI
    • sblakewilson@bcisse.com
  • Nelson Bolyard
    • (co-author of RFC 4492)
    • Sun Microsystems, Inc.
    • nelson@bolyard.com
  • Ran Canetti
    • IBM
    • canetti@watson.ibm.com
  • Matt Caswell
    • OpenSSL
    • matt@openssl.org
  • Stephen Checkoway
    • University of Illinois at Chicago
    • sfc@uic.edu
  • Pete Chown
    • Skygate Technology Ltd
    • pc@skygate.co.uk
  • Katriel Cohn-Gordon
    • University of Oxford
    • me@katriel.co.uk
  • Cas Cremers
    • University of Oxford
    • cas.cremers@cs.ox.ac.uk
  • Antoine Delignat-Lavaud
    • (co-author of RFC 7627)
    • INRIA
    • antdl@microsoft.com
  • Tim Dierks
    • (co-author of TLS 1.0, co-editor of TLS 1.1 and 1.2)
    • Independent
    • tim@dierks.org
  • Roelof DuToit
    • Symantec Corporation
    • roelof_dutoit@symantec.com
  • Taher Elgamal
    • Securify
    • taher@securify.com
  • Pasi Eronen
    • Nokia
    • pasi.eronen@nokia.com
  • Cedric Fournet
    • Microsoft
    • fournet@microsoft.com
  • Anil Gangolli
    • anil@busybuddha.org
  • David M. Garrett
    • dave@nulldereference.com
  • Illya Gerasymchuk
    • Independent
    • illya@iluxonchik.me
  • Alessandro Ghedini
    • Cloudflare Inc.
    • alessandro@cloudflare.com
  • Daniel Kahn Gillmor
    • ACLU
    • dkg@fifthhorseman.net
  • Matthew Green
    • Johns Hopkins University
    • mgreen@cs.jhu.edu
  • Jens Guballa
    • ETAS
    • jens.guballa@etas.com
  • Felix Guenther
    • TU Darmstadt
    • mail@felixguenther.info
  • Vipul Gupta
    • (co-author of RFC 4492)
    • Sun Microsystems Laboratories
    • vipul.gupta@sun.com
  • Chris Hawk
    • (co-author of RFC 4492)
    • Corriente Networks LLC
    • chris@corriente.net
  • Kipp Hickman
  • Alfred Hoenes
  • David Hopwood
    • Independent Consultant
    • david.hopwood@blueyonder.co.uk
  • Marko Horvat
    • MPI-SWS
    • mhorvat@mpi-sws.org
  • Jonathan Hoyland
    • Royal Holloway, University of London
    • jonathan.hoyland@gmail.com
  • Subodh Iyengar
    • Facebook
    • subodh@fb.com
  • Benjamin Kaduk
    • Akamai Technologies
    • kaduk@mit.edu
  • Hubert Kario
    • Red Hat Inc.
    • hkario@redhat.com
  • Phil Karlton
    • (co-author of SSL 3.0)
  • Leon Klingele
    • Independent
    • mail@leonklingele.de
  • Paul Kocher
    • (co-author of SSL 3.0)
    • Cryptography Research
    • paul@cryptography.com
  • Hugo Krawczyk
    • IBM
    • hugokraw@us.ibm.com
  • Adam Langley
    • (co-author of RFC 7627)
    • Google
    • agl@google.com
  • Olivier Levillain
    • ANSSI
    • olivier.levillain@ssi.gouv.fr
  • Xiaoyin Liu
    • University of North Carolina at Chapel Hill
    • xiaoyin.l@outlook.com
  • Ilari Liusvaara
    • Independent
    • ilariliusvaara@welho.com
  • Atul Luykx
    • K.U. Leuven
    • atul.luykx@kuleuven.be
  • Colm MacCarthaigh
    • Amazon Web Services
    • colm@allcosts.net
  • Carl Mehner
    • USAA
    • carl.mehner@usaa.com
  • Jan Mikkelsen
    • Transactionware
    • janm@transactionware.com
  • Bodo Moeller
    • (co-author of RFC 4492)
    • Google
    • bodo@acm.org
  • Kyle Nekritz
    • Facebook
    • knekritz@fb.com
  • Erik Nygren
    • Akamai Technologies
    • erik+ietf@nygren.org
  • Magnus Nystrom
    • Microsoft
    • mnystrom@microsoft.com
  • Kazuho Oku
    • DeNA Co., Ltd.
    • kazuhooku@gmail.com
  • Kenny Paterson
    • Royal Holloway, University of London
    • kenny.paterson@rhul.ac.uk
  • Christopher Patton
    • University of Florida
    • cjpatton@ufl.edu
  • Alfredo Pironti
    • (co-author of RFC 7627)
    • INRIA
    • alfredo.pironti@inria.fr
  • Andrei Popov
    • Microsoft
    • andrei.popov@microsoft.com
  • John PreuรŸ Mattsson
    • Ericsson
    • john.mattsson@ericsson.com
  • Marsh Ray
    • (co-author of RFC 7627)
    • Microsoft
    • maray@microsoft.com
  • Robert Relyea
    • Netscape Communications
    • relyea@netscape.com
  • Kyle Rose
    • Akamai Technologies
    • krose@krose.org
  • Jim Roskind
    • Amazon
    • jroskind@amazon.com
  • Michael Sabin
  • Joe Salowey
    • Tableau Software
    • joe@salowey.net
  • Rich Salz
    • Akamai
    • rsalz@akamai.com
  • David Schinazi
    • Apple Inc.
    • dschinazi@apple.com
  • Sam Scott
    • Royal Holloway, University of London
    • me@samjs.co.uk
  • Mohit Sethi
    • Aalto University
    • mohit@iki.fi
  • Thomas Shrimpton
    • University of Florida
    • teshrim@ufl.edu
  • Dan Simon
    • Microsoft, Inc.
    • dansimon@microsoft.com
  • Brian Smith
    • Independent
    • brian@briansmith.org
  • Ben Smyth
    • Ampersand
    • www.bensmyth.com
  • Brian Sniffen
    • Akamai Technologies
    • ietf@bts.evenmere.org
  • Nick Sullivan
    • Cloudflare Inc.
    • nick@cloudflare.com
  • Bjoern Tackmann
    • University of California, San Diego
    • btackmann@eng.ucsd.edu
  • Tim Taubert
    • Mozilla
    • ttaubert@mozilla.com
  • Martin Thomson
    • Mozilla
    • mt@mozilla.com
  • Hannes Tschofenig
    • Arm Limited
    • Hannes.Tschofenig@arm.com
  • Sean Turner
    • sn3rd
    • sean@sn3rd.com
  • Steven Valdez
    • Google
    • svaldez@google.com
  • Filippo Valsorda
    • Cloudflare Inc.
    • filippo@cloudflare.com
  • Thyla van der Merwe
    • Royal Holloway, University of London
    • tjvdmerwe@gmail.com
  • Victor Vasiliev
    • Google
    • vasilvv@google.com
  • Loganaden Velvindron
    • cyberstorm.mu
    • logan@cyberstorm.mu
  • Hoeteck Wee
    • Ecole Normale Superieure, Paris
    • hoeteck@alum.mit.edu
  • Tom Weinstein
  • David Wong
    • NCC Group
    • david.wong@nccgroup.trust
  • Christopher A. Wood
    • Apple Inc.
    • cawood@apple.com
  • Tim Wright
    • Vodafone
    • timothy.wright@vodafone.com
  • Peter Wu
    • Independent
    • peter@lekensteyn.nl
  • Kazu Yamamoto
    • Internet Initiative Japan Inc.
    • kazu@iij.ad.jp

Author's Address ๐Ÿ’Œ

  • Eric Rescorla
  • Independent
  • Email: ekr@rtfm.com
๐Ÿ“ฆ where this page comes from