/dev/posts/

Introduction to TLS v1.3

Published:

Updated:

Some notes about how TLS v1.3 works. This is a follow-up of the previous episode about TLS v1.2. As before, the goal is to have a high-level overview about how the protocol works, what is the role of the different messages and be able to understand (and debug) a network traffic dump.

You may want to check as well “The New Illustrated TLS Connection” for examples of the messages on the wire.

TLS v1.3 supports 3 types of key exchange methods.

The DHE (ephemeral Diffie-Hellman) key exchange method is explained in the first section. This methods is generally used when no session resumption is used. It relies on an ephemeral Diffie-Hellman key exchange (for establishing a shared secret), digital signatures (for authentication) and certificates (for conveying the static private keys used for authentication).

The two other types of handshake (PSK psk_ke and PSK-with-DHE psk_dhe_ke) are explained in the second section. They rely on an already established secret between the client and the server, the pre-shared-secret (PSK). This PSK may have been established in a previous TLS v1.3 handshake between the client and the server (session resumption) or it may have been provisionned through another channel (out-of-band).

The third section describes some additional features.

Update 2023-05-30: reorganized the section about PSK.

Table of content

DHE handshake

The DHE handshake is based on a (server-)authenticated ephemeral Diffie-Hellman key exchange:

  1. The handshake starts with an ephemeral Diffie-Hellman (DHE) key exchange (as part of the ClientHello and ServerHello messages) which is used to generate cryptographic material (such as encryption keys and MAC keys). The same messages are used as well to negotiate some parameters (TLS version, encryption algorithm, etc.).
  2. The server authenticates itself using a digital signature of the TLS handshake.
  3. The client optionally authenticates itself using a digital signature of the TLS handshake.
  4. The client and the server can now exchange protected application data.
High level overview of TLS v1.3 (without PSK)

Because the Diffie-Hellman key exchange is done directly using the two first messages, a shared secret is established directly after these messages and all the subsequent messages are encrypted.

Sequence diagram for TLS v1.3 (without PSK)

Note: notations

  • “(h. enc)” denotes encryption with keys derived from the handshake secret;
  • “(enc)” denotes encryption with keys derived from the master secret.

Note: 1RTT

By comparing this diagram with the one for TLS v1.2, we can see that the client can send application data sooner in TLS v1.3: after 2 round trips (2RTT) in TLS v1.2; after one round trip (1RTT) in TLS v1.3 (for the happy path).

Note: ChangeCipherSpec and backward compatibility

For better retrocompatibility with existing middleboxes, the client and the server may send ChangeCipherSpec messages. These messages are ignored in TLS v1.3. These messages are not indicated in the sequence diagrams of this post.

On OpenSSL, this feature is enabled by default but may be disabled by clearing the SSL_OP_ENABLE_MIDDLEBOX_COMPAT option.

Key exchange

The client and the server starts by exchanging hello message (ClientHello and ServerHello) which features:

struct {
    ProtocolVersion legacy_version = 0x0303;    /* TLS v1.2 */
    Random random;
    opaque legacy_session_id<0..32>;
    CipherSuite cipher_suites<2..2^16-2>;
    opaque legacy_compression_methods<1..2^8-1>;
    Extension extensions<8..2^16-1>;
} ClientHello;

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;

Note: backward compatibility

Some of the fields in these messages are not really used anymore and are present for compatibility with previous versions of TLS:

  • For example, the TLS version used to be negotiated directly in the hello messages (ClientHello.legacy_version and ServerHello.legacy_version) but is now negotiated in the supported_versions extension. In TLS v1.3, the legacy_version is set to TLS v1.2 in order to avoid breaking middleboxes.
  • The legacy_session_id was used for session resumption in previous versions of the protocol. In TLS v1.3, the client may used a random value here and the server always echos the value in legacy_session_id_echo.

Note: extensions mechanism

In TLS v1.2, extensions were present in the ClientHello and ServerHello messages (in cleartext).

In TLS v1.3, the TLS extensions may be present in different messages. Many extensions are not sent in the ClientHello/ServerHello messages but are sent later on in the TLS handshake (and are thus encrypted).

Extensions mechanisms per message in TLS v1.3
Request message Response message Description
ClientHello ServerHello Extensions necessary for the key exchange (eg. key_share)
ClientHello EncryptedExtensions Other extensions
ClientHello HelloRetryRequest List of acceptable groups (key_share extension)
ClientHello Certificate Informations associated with the certificate
CertificateRequest Certificate Informations associated with the certificate
NewSessionTicket none

Many extensions are still sent in the ClientHello (in cleartext) however. This includes in particular, the server name (SNI) and application protocol (ALPN) extensions. The Encrypted Client Hello (ECH) extension has been proposed to provide confidentiality to the ClientHello message.

Diffie-Hellman Key Exchange

An ephemeral Diffie-Hellman key exchange is done in the ClientHello and ServerHello messages. The client and the server both include a key_share extension which contains an ephemeral Diffie-Hellman public key. This Diffie-Hellman key exchange is used to establish several shared secrets.

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),
    /* ... */
    (0xFFFF)
} NamedGroup;

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

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

struct {
    KeyShareEntry server_share;
} KeyShareServerHello;

Note: proposing several groups

The client can propose several ephemeral DH public keys (from different groups) in its key_share extension. In this case, the server chooses one of the groups proposed by the client (if it supports any) and generates an ephemeral Diffie-Hellman key pair in this group.

Example: proposed ephemeral DH public keys

In my case, Firefox included (EC) DH public keys for the x25519 and secp256r1 groups.

Note: retry request in case of incompatible group

If none of the DH groups proposed by the client is acceptable by the server, the server sends a HelloRetryRequest message which includes an acceptable group to use in its key_share extension.

struct {
    NamedGroup selected_group;
} KeyShareHelloRetryRequest;

The list of DH groups supported by the client has been advertised by the client in the supported_groups extension of the ClientHello: the group chosen by the server is picked in this list. The client may then send a new ClientHello with an ephemeral DH public key in this group.

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

TLS version negotiation

The supported_versions extension is used to negotiate the TLS version.

struct {
    select (Handshake.msg_type) {
        case client_hello:
            ProtocolVersion versions<2..254>;
        case server_hello: /* and HelloRetryRequest */
            ProtocolVersion selected_version;
    };
} SupportedVersions;

Note: backward compatibility

In TLS v1.2 and below, the ClientHello.client_version and ServerHello.server_version fields were used for negotiating the TLS versions. In TLS v1.3, these fields are set to TLS v1.2 in order to look like TLS v1.2 and avoid breaking middle boxes.

Note: TLS version downgrade protection

TLS v1.3 provides an addition protection against protocol downgrades: if TLS v1.2 or below is negotiated, the end server_random field should end with DNWGRD\x01 or DNWGRD\x00. This can be used by the client to detect a TLS version downgrade attack: the client should reject the connection in this case.

Ciphersuite negotiation

The cipher suite is negotiated in the ClientHello.cipher_suites and ServerHello.cipher_suite fields:

In TLS v1.3, the cipher suites are named using the TLS_{encryption}_{hash} pattern (eg. TLS_CHACHA20_POLY1305_SHA256). Each cipher suite is defined by the combination of:

Note: difference with TLS v1.2

In TLS v1.2, the key exchange algorithm (ECDHE_RSA) is part of cipher suite (eg. TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256) as well. In TLS v1.3, the key exchange algorithm is negotiated separately from the cipher suite (eg. TLS_CHACHA20_POLY1305_SHA256).

As a consequence, the cipher suites for TLS v1.3 use different cipher suites from the ones for TLS v1.2 and below (but they share the same ID space). A client willing to negotiate either TLS v1.2 and TLS v1.3 should propose cipher suites for both versions in the ClientHello.

Example: negotiated cipher suites

In my case, Firefox proposed using the TLS_CHACHA20_POLY1305_SHA256 and TLS_AES_128_GCM_SHA256 cipher suites (as well as TLS v1.2 cipher suites in case TLS v1.2 was negotiated). The TLS_AES_128_GCM_SHA256 cipher suite was chosen by the server.

Signature algorithms negotiation

The signature_algorithms extension is used in the ClientHello to announce which signature schemes are accepted by the client for server authentication (in the CertificateVerify message).

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;

If the set of signature schemes supported in certificates is different from the ones supported for CertificateVerify, the signature_algorithms_cert is used to announce the signature schemes supported in certificates.

Example: signature algorithms

In my example, Firefox announced support for: ecdsa_secp256r1_sha256, ecdsa_secp384r1_sha384, ecdsa_secp521r1_sha512, rsa_pss_rsae_sha256, rsa_pss_rsae_sha384, rsa_pss_rsae_sha512, rsa_pkcs1_sha256, rsa_pkcs1_sha384, rsa_pkcs1_sha512, ecdsa_sha1, rsa_pkcs1_sha1.

Note: new signature algorithms in TLS v1.3

New signature schemes have been introduced by TLS v1.3:

Note: difference between rsa_pss_rsae_* and rsa_pss_pss_*

In X.509 certificates, three different keys types (SubjectPublicKeyInfo.algorithm) are defined for RSA keys.

TBSCertificate  ::=  SEQUENCE  {
    version         [0]  EXPLICIT Version DEFAULT v1,
    serialNumber         CertificateSerialNumber,
    signature            AlgorithmIdentifier,
    issuer               Name,
    validity             Validity,
    subject              Name,
    subjectPublicKeyInfo SubjectPublicKeyInfo,
    issuerUniqueID  [1]  IMPLICIT UniqueIdentifier OPTIONAL,
                         -- If present, version MUST be v2 or v3
    subjectUniqueID [2]  IMPLICIT UniqueIdentifier OPTIONAL,
                         -- If present, version MUST be v2 or v3
    extensions      [3]  EXPLICIT Extensions OPTIONAL
                         -- If present, version MUST be v3
    }

SubjectPublicKeyInfo  ::=  SEQUENCE  {
    algorithm            AlgorithmIdentifier,
    subjectPublicKey     BIT STRING  }

The rsaEncryption type has been used to identify RSA public keys, independently of their usage:

rsaEncryption  OBJECT IDENTIFIER  ::=  { pkcs-1 1 }

RSAPublicKey  ::=  SEQUENCE  {
    modulus            INTEGER,    -- n
    publicExponent     INTEGER  }  -- e

This type can be used both for RSA encryption (eg. with the RSA transport key exchage method in TLS v1.2) and for RSA signature (eg. with the ECDHE_RSA transport in TLS v1.2). The key usage X.509 extension could be used to restrict a given certificate to be used either for encryption or for signature. Moreover, there are two algorithms for RSA signatures (RSASSA-PKCS1-v1_5 and RSASSA-PSS) and there is no way to constraint a rsaEncryption key to one signature type or the other.

The RSASSA-PSS key type in X.509 certificates can be used if the certificate is intended to be used for RSASSA-PSS signatures only. In this case, the certificate may contain additional informations in the AlgorithmIdentifier.parameters.

id-RSASSA-PSS  OBJECT IDENTIFIER  ::=  { pkcs-1 10 }

RSASSA-PSS-params  ::=  SEQUENCE  {
    hashAlgorithm      [0] HashAlgorithm DEFAULT
                            sha1Identifier,
    maskGenAlgorithm   [1] MaskGenAlgorithm DEFAULT
                            mgf1SHA1Identifier,
    saltLength         [2] INTEGER DEFAULT 20,
    trailerField       [3] INTEGER DEFAULT 1  }

TLS v1.3 defines different signature schemes depending on the public key algorithm:

  • rsa_pss_rsae_* is for RSA-PSS signatures with a rsaEncryption (RSAE) key in the certificate;
  • rsa_pss_pss_* is for RSA-PSS signatures with a RSASSA-PSS key in the certificate.

Note: compatibility with TLS v1.2 SignatureAndHashAlgorithm

In TLS v1.2, a signature method was the combination of a signature method and a hash algorithm:

struct {
    HashAlgorithm hash;
    SignatureAlgorithm signature;
} SignatureAndHashAlgorithm;

These two 1-byte values have been merged into a a single 2-byte SignatureScheme values in TLS v1.3.

Note: difference with TLS v1.2 (ECDSA)

When using ECDSA in TLS v1.2, a suported signature algorithm was the combination of the ECDSA method and a hash function (eg. ecdsa sha256). The elliptic_curves extension was used both to announce which (elliptic curve) groups could be used for the ECDSA signature and which (elliptic curve) groups could be used for ECDH key exchanges.

In TLS v1.3, each ECDSA signature scheme is tied to a given elliptic curve (eg. ecdsa_secp256r1_sha256). The elliptic_curves has been renamed to supported_groups extension: it now only advertises the DH groups (both FFDH and ECDH) supported for the DHE key exchange (not for the digital signatures).

SNI

The client can use the Server Name Indication (SNI) extension to announce the server name it intends to connect to. For example, the server can use this information to select which certificate chain to send, whether to propose client authentication, etc.

struct {
    NameType name_type;
    select (name_type) {
        case host_name: HostName;
    } name;
} ServerName;

enum {
    host_name(0), (255)
} NameType;

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

struct {
    ServerName server_name_list<1..2^16-1>
} ServerNameList;

The server indicates that it actually used the content of the server_name by including an empty server_name extension in in the EncryptedExtensions messages.

ALPN

The peers can use the Application-Layer Protocol Negotiation (ALPN) extension to negotiate which application protocol will be transported by the TLS connection.

Note: ALPN and QUIC

When using QUIC, the application protocol negotiated using ALPN is not transported on top of TLS. Instead, the application protocol is transported directly on top QUIC.

    [ HTTP/1.1       ]   [ HTTP/2 ]   [ HTTP/3 | TLS ]
    [ TLS            ]   [ TLS    ]   [ QUIC         ]
    [ TCP            ]   [ TCP    ]   [ UDP          ]
    [ IP             ]   [ IP     ]   [ IP           ]
     HTTPS with           HTTPS with   HTTPS with
      HTTP/1.1            HTTP/2       HTTP/3
     (ALPN=http/1.1)     (ALPN=h2)     (ALPN=h3)
    
Comparing protocol stack for HTTPS with HTTP/1, HTTP/2 and HTTP/3

The QUIC key materials are derived from the TLS traffic secrets.

opaque ProtocolName<1..2^8-1>;

struct {
    ProtocolName protocol_name_list<2..2^16-1>
} ProtocolNameList;

Example

In my example, Firefox announced support for HTTP/2 (h2) and HTTP/1.1 (http/1.1). The server chose to use HTTP/1.1.

Enabling encryption

After the DH key exchange, the client and the server have a DH shared secret and can start exchanging protected messages. All subsequence messages are encrypted and data-authenticated (AEAD) using encryption keys derived from this DH shared secret and the message transcript.

However, the participants have not been authenticated yet at this point.

Note: difference with TLS v1.2

In TLS v1.2, most of the handshake was send in cleartext. Only the Finished message was using encryption. In TLS v1.3, the encryption happens much sooner.

Server Parameters

Encrypted extensions

At this point, the server sends the EncryptedExtensions message. This message contains most server TLS extensions which were not necessary for the key exchange (some extensions are included in the Certificate message).

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

Certificate request

If the server supports client authentication, the server sends a CertificateRequest message.

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

This message contains several extensions. Important extensions are:

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

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

Note: certificate_request_context

The certificate_request_context is only used for post-handshake authentication.

Server Authentication

The server can now authenticate itself by sending:

  1. a Certificate message, with the server certificate chain;
  2. a CertificateVerify message, containing a signature of the server (signing) private key;
  3. a Finished message, containing a MAC of the TLS handshake.

The server may start sending application data after its Finished message. If the server supports client authentication, it might be willing to wait for the client authentication before sending confidential data.

Note: more robust use of digital signatures

In TLS v1.2, the digital signature used for authenticating the server (ServerKeyExchange.signed_params) for FFDHE and ECDHE cipher suites only covers a restricted subset of the handshake (cf. Logjam attack): the server and client random/nonces, the chosen (FF/EC)DH group and the server ephemeral (FF/EC)DH public key.

In TLS v1.3, this digital signature (in the CertificateVerify message) covers all the previous handshake messages (hash of the TLS handshake transcript).

Certificate

The Certificate message (typically) contains a chain of X.509 certificates.

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;

Extensions:

The certificate chain is a proof of the association of the public key (contained in the leaf certificate) with the server. After receiving this message, the client can validate the certificate chain. If the validation succeeds, the client can trust that the public key found in the server certificate actually belongs to the server and can be relied on for authenticating the server.

CertificateVerify

At this point, the server is not yet authenticated. In order to authenticate itself, the server sends a signature of (the hash of the current transcript of) the TLS handshake in the CertificateVerify message. This proves that the server the client it is talking to is in possession of the private key and therefore (in combination with the certificate chain validation) that the client is talking to the genuine server.

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

Note: comparison with TLS v1.2

In TLS v1.2, the signature of server authentication (for signature-based key exchange algorithms) would only cover (in the ServerKeyExchange message) a restricted set of values: server and client random value and the server Diffie-Hellman parameters. The logjam attack was possible because of this defect.

In TLS v1.3, the signature used for server authentication covers the complete TLS handshake at this point (a hash of the TLS transcript).

Finished

The server then sends a Finished message which contains an HMAC of the handshake messages with a secret key derived from the key exchange. This authenticates both the keys (key confirmation) and the current TLS handshake (protection against tampering the TLS handshake).

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

Client Authentication

The client authentication process is similar to the server authentication process.

If the client authentication was requested, the client sends a Certificate message with its certificate chain (or a raw public key if negotiated with client_certificate_type) and a CertificateVerify with a signature.

If the client does not wish (or cannot) authenticate, it sends a Certificate message with an empty certificate chain and no CertificateVerify message. The server may either accept unauthenticated connections and reject the connection with the certificate_required fatal alert.

Whether client authentication was requested or not, the client then sends a Finished message.

Note: difference with TLS v1.2

In TLS v1.2, the client certificate chain was sent in cleartext: this could reveal some important information (eg. client identity contained in the client certificate subject) to passive attackers.

In TLS v1.3, the client certificate is sent encrypted. Moreover, it is sent after server authentication. Therefore, the client knows it is communicating with the correct server when it sends its certificate chain.

Application Data

The client and the server can now exchange protected (encrypted and protected) messages over the TLS connection.

When one side does not need to send data over the TLS connection anymore, it sends a warning close_notify alert to signal end-of-data.

Alerts

If some error happens at any time (eg. in the TLS handshake), the peer sends an Alert message with some other alert code.

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

enum {
    close_notify(0),
    unexpected_message(10),
    ...
} AlertDescription;

struct {
    AlertLevel level;
    AlertDescription description;
} Alert;

The close_notify alert is sent by one peer when it does no have any mesasge to send on the TLS connection.

PSK-based handshakes

The two PSK-based handshakes do not rely on digital signatures and certificates at all. Instead, they assume that a secret value, the pre shared secret (PSK), is already shared between the client and the server. This PSK is used to establish cryptographic material (such as encryption and MAC keys).

The lack of digital signature and digital certificates can be useful for low-performance environments such as IoT applications.

In contrast to TLS PSK is TLS v1.2 which was an extension of the core protocol, PSK support is part of the core TLS v1.3 and is used for session resumption.

Sequence diagram for TLS v1.2 with PSK

Note: notations

  • “(early enc)” denotes encryption with keys derived from the early secret;
  • “(h. enc)” denotes encryption with keys derived from the handshake secret;
  • “(enc)” denotes encryption with keys derived from the master secret.

Two PSK-based key exchange can be used:

Note: origin of the PSK

The PSK may either have been established by a previous TLS handshake (internal PSK), or by through another mean (external PSK):

  • The first case is used for session resumption in TLS v1.3[1]. In this case, the PSK has been established between the client and the server at the end of a previous TLS handshake (through the NewSessionTicket message).
  • Alternatively, the PSK may be shared through another mean (out of band). This may provide mutual authentication, assuming the PSK is only shared between these two peers.

In addition the PSK importer mechanism has been defined as a safer mechanism to derive (several) (imported) PSKs from an external PSK.

Type of PSK PSK identity PSK
External PSK opaque, arbitrary arbitrary
Imported PSK ImportedIdentity derived from the external PSK, ImportedIdentity, protocol version, KDF and some optional context
Internal PSK (session resumption) Chosen by the server and sent in NewSessionTicket.ticket derived from the resumption_master_secret and NewSessionTicket.ticket_nonce
struct {
    opaque external_identity<1...2^16-1>;
    opaque context<0..2^16-1>;
    uint16 target_protocol;
    uint16 target_kdf;
} ImportedIdentity;

The context field is an optional application-specific data. See Appendix A of RFC9258 for an example of context.

Warning: PSK identity exposure

The proposed PSK identities are sent in cleartext (unless ECH is used) and the accepted PSK identity index (if any) is sent in cleartext. This might be a privacy issue if external PSK identites are not opaque or are reused:

  • you might want to avoid including sensitive information such as personal identifiable information in the PSK identity (for example in the ImportedIdentity.context field);
  • even if the PSK identity is opaque, it could be used to track the client if it is reused.

This is not a problem for PSK identities used for session resumption because they are opaque (can be made opaque) and are not expected to be reused.

Moreover, an attacker may verify if a given PSK identity is valid.

Warning: restrictions when using PSKs

You should be make sure to only use a single PSK:

  • for a single client;
  • for a single server;
  • for a single KDF (for example if the PSK is tied to HKDF_SHA512 KDF, you should not be able / try to use it with the TLS_AES_128_GCM_SHA256 ciphersuite because this ciphersuite uses the HKDF_SHA256 KDF).

i.e.:

  • The client and the server should not swap roles unless their role is validated for example using SNI.
  • The PSKs should not be shared between more than two peers (group membership).

This should be automatically taken care of when using internal PSKs but this might not hold when designing a system using external PSKs.

Rerouting/impersonation vulnerabilities may be introduced if these conditions are not verified. You should use PSK importer mechanism (imported PSKs) instead of directly using external PSKs in this case. This mechanism can be used to may be used to derive multiple PSKs from a an external PSK:

For some guidance on using an external PSK, see Guidance for External PSK Usage in TLS.

Vulnerability: rerouting attacks (eg. Selfie) when using external PSK for group membership

If you want to secure the communications in a group of nodes, you might be tempted to use a single PSK for the whole group and have each node behave both as a TLS client and a TLS server. However, this setup is vulnerable to rerouting attacks (such as the Selfie attack). An active attacker can redirect a pending TLS connection to another node of the group (including the client itself). This is because, if the PSK is shared within a group, the authentication provided by the PSK key exchange it can only authenticate the group as a whole.

Note that this vulnerability is present in TLS-PSK v1.2 as well.

Mitigations:

  • This can be partially mitigated by using SNI to make sure the client is connecting to the correct server.
  • Another mitigation is to include (and validate) the client and server names at the application protocol layer.

However, the best solution is to avoid direcly using a group PSK:

Unless other accommodations are made to mitigate the risks of PSKs known to a group, each PSK MUST be restricted in its use to at most two logical nodes: one logical node in a TLS client role and one logical node in a TLS server role.

In other words, a given PSK should be associated with one client and one server and should not swap roles for the same PSK.

A solution is to use the PSK importer mechanism to derive different imported PSKs for the different roles from the main group PSK. This is achieved by including the node identities in the context parameter of the ImportedIdentity.

When possible, you might find it preferable to use public-key based authentication with one keypair per node.

Warning: sharing the same PSK between different versions of the protocol

The specification recommends against sharing the same PSK between TLS v1.2 and TLS v1.3 because of the lack of analysis in this regard.

OpenSSL acknowledges this but still provides some features (SSL_CTX_set_psk_server_callback()) which allows for sharing the same PSK for TLS v1.2 and TLS v1.3 by default.

PSK Key Exchange

When the client is willing to use one of the two PSK-based key exchange methods, it proposes one or several PSK identities[2] to use through the pre_shared_key extension of the ClientHello message. In addition, it uses the psk_key_exchange_modes extension to announce which PSK mode it supports (psk_ke and/or psk_dhe_ke).

If the server accepts one of the proposed PSK identities, the chosen identity (actually the index of the identity in the list of proposed PSK identities) is indicated in the pre_shared_key extension of the ServerHello.

The server never sends the psk_key_exchange_modes extension. The presence of a DHE public key (psk_key_exchange_modes extensions of the ServerHello) indicates that psk_dhe_ke is used. Otherwise, psk_ke is used.

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;

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

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

Warning: lack of forward secrecy for psk_ke

The psk_ke key exchange mode does not provide forward secrecy with respect to the PSK.

The psk_dhe_ke key exchange mode provides forward secrecy by incorporating an ephemeral Diffie-Hellman key exchange.

Note: PSK binders

The client pre_shared_key extensions includes a PSK binder per proposed identity which serves as a proof that the client possess the PSK associated with the PSK identity.

Early data (0-RTT)

When using a PSK (either with psk_ke or psk_dhe_ke), the client and the server already have a shared secret. The client, may use this shared secret to send protected application directly after the ClientHello message. This is called early data or 0RTT (because it does not require any round trip with the server).

In this case, the early_data extension is sent by the client.

If the client, proposes several PSK identities, only the first one is used to protect early data. The server may only accept the early data if it accepts the first PSK identity.

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 {} EndOfEarlyData;

The early application data is protected using key material derived from the PSK of the first proposed PSK identity. The encryption algorithm used for early data is the one which was associated with the PSK. For session resumption, this is the encryption algorithm used for the parent TLS connection.

Warning: security implication of using early data

There is no forward secrecy of the early/0RTT application data with respect to the PSK (because the Diffie-Hellman key exchange has not been done yet). Moreover, early data might be vulnerable to replay attacks (because the server did not have any occasion to contribute any value to the TLS handshake yet).

For these reasons, support for early data is usually not enabled by default:

  • in OpenSSL, dedicated functions are used for writing or reading early data;
  • in HTTPS, early data must not be used by default for unsafe (POST, etc.) method but may be used for safe methods (GET, etc.).

PSK for session resumption

The server may send one or several NewSessionTicket messages in order to provision internals PSKs to the client which may be used for session resumption.

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

The ticket value is an opaque value used as psk_identity. Each ticket is expected to be used only once (by the client). For this reason, the server may choose to send multiple tickets to the client in the same TLS connection.

The PSK associated with the ticket is derived from the resumption_master_secret and the ticket_nonce:

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

The following extension can be used in NewSessionTicket:

Note: comparison with TLS v1.2

In TLS v1.2, the session master secret was shared between the original TLS connection and the resumed session. As a consequence, if the session state was compromised at some point it could be used to decrypt the original TLS connection. This is no longer the case in TLS v1.3: the internal PSK used for session resumption is derived from the master secret of the original TLS connection.

Moreover, TLS v1.2 could not provide forward secrecy of the content of the resumed TLS connection data with respect to the master secret: if the master secret was compromised, it could be used to derypt the content of the resumed TLS connection after the fact. In TLS v1.3, the psk_dhe_ke mode provides forward forward secrecy of the resumed TLS connection with respect to the session resumption PSK.

Note: content of the ticket

The ticket is opaque for the client. It's content is an implementation detail of the server. It may be:

  • a reference to a database/map containing the session state;
  • a encrypt-the-MAC message containing the session state.

Extra features

Post handshake client authentication

If the client supports it, the server may request a client authentication after the TLS handshake. This feature may be useful:

Example: smartcard-based authentication

The second feature might for example be used for smartcard-based authentication: a TLS connection is still usable by the client even when the smartcard has been unplugged (bcause the private key is only used at duration the authentication). By requesting a post handshake authentication, the server could make sure that the client still has access to the smartcard.

Post handshake authentication in TLS v1.3
struct {
    opaque certificate_request_context<0..2^8-1>;
    Extension extensions<2..2^16-1>;
} CertificateRequest;

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

In a post handshake authentication, a non-empty CertificateRequest.certificate_request_context field is passed by the server. This value is repeated by the client in the Certificate.certificate_request_context field which can be used to match requests/responses. Moreover, the CertificateVerify signature covers the certificate request context which ensures the freshness of the signature.

After a post handshake authentication, the server may send NewSessionTicket messages which may be used by the client for session resumption tied to the new client identity.

Warning: post-handshake authentication after an external PSK handshake

Post-handshake authentication is apparently/arguably allowed by the protocol after a PSK handshake. However, using a post-handshake authentication after a psk_ke key exchange with an external PSK can open up impersonation attacks.

As far as I understand, this attack could be prevented using SNI.

Alternative certificates

The type of certificate used for server authentication is negotiated using the server_certificate_type extension (in the ClientHello and EncryptedExtensions messages). TLS v1.3 only supports X.509 certificate chains and raw public key.

The type of certificate used for client authentication is negotiated using the client_certificate_type extension (in the ClientHello and EncryptedExtensions messages).

See the previous post for motivations for using raw public keys.

Certificate Compression

The compress_certificate extension may be used to negotiated the compression of the certificate chains (either client or server).

enum {
    zlib(1),
    brotli(2),
    zstd(3),
    (65535)
} CertificateCompressionAlgorithm;

struct {
    CertificateCompressionAlgorithm algorithms<2..2^8-2>;
} CertificateCompressionAlgorithms;

If the one peer has announced support for certificate compression, the other peer may send a CompressedCertificate message.

struct {
    CertificateCompressionAlgorithm algorithm;
    uint24 uncompressed_length;
    opaque compressed_certificate_message<1..2^24-1>;
} CompressedCertificate;

FAQ

How are used the random values in the hello message?

By including unique random nonces in the transcript, each participant can make sure that the handshake transcript is unique. This protects against replay attacks by ensuring the freshness of several authentication fields such as:

Moreover, the participants ensures that the derived cryptographic materials are unique to this TLS connections.

This is especially important when using the PSK (without DHE) key exchange mode: in the two other key exhange modes, each participant already contributes an ephemeral value in the TLS handshake (its ephemeral Diffie-Hellman public key[3]).

Moreover, in TLS v1.3 the server_random is used for downgrade detection.

How is used the hash function negotiated as part of the cipher suite?

The hash function negotiated as part of the cipher suite is used:

Appendix, key hierarchy

The key hierachy is made of three layers:

  1. Early Secret (ES);
  2. Handshake Secret (HS);
  3. Master Secret (MS).

See as well this nice diagram and this other nice diagram.

Early Secret

The early secret is intended to be used before getting any answer from the server. At this point, the Diffie-Hellman key exchange has not been done yet.

The early secret is derived from the PSK (if any). This is used to derive:

All of these features are only available with the PSK key exchange algorithms.

Moreover, because the server did not contribute any input to the early secret, they are vulnerable to replay attacks. Some mitigations may be implemented.

Handshake Secret

The handshake secret is derived from the early secret and Diffie-Hellman exchange (if any). It is used to derive keys used for protecting the messages of the TLS handshake:

Master Secret

The master secret is derived from the handshake secret. It is used to derive keys for protecting post-handshake communications (i.e. both post-handshake application data and post-handshake messages such as KeyUpdate and NewSessionTicket):

Each peer can update its traffic secret (and traffic key material) by sending a KeyUpdate message at any time after the handshake.

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

struct {
    KeyUpdateRequest request_update;
} KeyUpdate;

The new traffic secret is computed as:

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

Encryption material derivation from traffic secrets

Each traffic secret is used to derive key material for authenticated encryption:

Key exporters

As in TLS v1.2, the key exporter mechanism can be used to derive additional secrets/keys at the end of the TLS handshake to be used in other protocols/applications.

TLS v1.3 additionnaly supports early key exporter which can be used to export secrets directly after the ClientHello.

Warning: replay attacks when using early exporters

Secrets derived from the early key exporter may be vulnerable to replay attacks because the server did not contribute any data to the TLS handshake.

Appendix, changes from TLS v1.2

Summary

TLS v1.2 TLS v1.3
Ciphersuite key exchange algorithm + cipher + hash function cipher + hash function
Ciphersuite example TLS_ECDHE_RSA_WITH_­CHACHA20_POLY1305_SHA256 TLS_­CHACHA20_POLY1305_SHA256
RSA key exchange yes no
Static DH key exchange yes no
Anonymous DHE key exchange no no
DHE key exchange yes yes
PSK-based key exchanges extension built in
FFDH groups arbitrary, explicitly sent named groups only
Client authentication using static DH keypair yes no
Client authentication using signature yes yes
Server authentication using signature covers a limited amount of data (cf. Logjam) covers all the previous handshake messages
Key derivation using TLS-PRF using HKDF
Order of authentication client then server server then client
Finished.verify_data using TLS-PRF using an HMAC
RTT 2-RTT often 1-RTT, 2-RTT in the worst case
0-RTT data / early data no possible (optional) in PSK key exchange methods (including session resumption)
Session resumption Session identifiers or session tickets based on the PSK key exchange mechanism
Session resumption and forward secrecy no forward secrecy wrt. persisted master secret forward secrecy available (psk_dhe_ke only)
Client certificate privacy possible through TLS renegotiation builtin
SNI and ALPN privacy no extensions (encrypted ClientHello)
Post handshake authentication possible through TLS renegotiation dedicated support
Rekeying possible through TLS renegotiation dedicated support

Encryption happens sooner

The Diffie-Hellman key exchange is now done directly as part of the first two messages (ClientHello/ServerHello). As a consequence a shared secret is established directly after the two messages and encryption can be used directly after these messages:

Moreover, the type of TLS messages is now encrypted. In TLS v1.2, this was sent in cleartext. It was for example possible for an eavesdropper to know that an alert was sent (but not which alert was sent).

When session resumption is used, the client may send application data without any round trip (0RTT/early data) with some caveats.

Cleanup

A lot of vulnerable/problematic mechanisms were removed or modified.

Several key exchange algorithms have been removed:

Weak cipher suites have been removed[4].

TLS-level compression has been removed. It was usually disabled in practice because of the CRIME (Compression Ratio Info-leak Made Easy) vulnerability (CVE-2012-4929).

TLS renegotiation which has been associated with several vulnerabilities (CVE-2009-3555, 3SHAKE) has been replaced with a simpler rekeying mechanisms and dedicated support for post-handshake authentication).

The new session resumption mechanism has heen integrated with the pre-shared keys (PSK) key exchange mechanism. In TLS v1.2, the session mechanism was based on the idea of storing the master secret of the original TLS connections and reusing for subsequent TLS connections: this master secret could be used to decrypt both the original session and resumed ones. In TLS v1.3, the session PSK cannot be used to decrypt the original TLS connection. Moreover it cannot be used to decrypt the resumed TLS connection after the fact either when the psk_dhe_ke mode is used (forward secrecy).

A mechanism similar to the extended master secret extension (which has been introduced as a fix for the 3SHAKE vulnerability) is now integrated in the base protocol.

Server authentication is more robust than in TLS v1.2. In TLS v1.2, the signature used for server authentication only covers a subset of the fields of the TLS handshake. This problem was used in the Logjam attack.

References

RFCs:

Registries:

Papers:

About TLS interception:

Other:


  1. The two mechanisms available in TLS v1.2 for session resumption are not present in TLS v1.3. ↩︎

  2. A PSK identity is an identifier for a PSK. It is used by the server to identify which PSK to use (i.e. like a login). ↩︎

  3. The fact that the each peer contributes a random means that a peer could actually use a static DH keypair instead of an ephemeral one without completely compromising the security of the TLS handshake

    This has been proposed as a solution for implementing passive monitoring (i.e. decryption) of the TLS traffic in a data center (this is called “static (EC)DHE” in the draft 🤪). In this scheme, each server to monitor would be provisionned with its own static DH keypair which would be shared with the monitoring infrastructure. The monitoring infrastructure could compute the Diffie-Hellman secret of each TLS handshake and decrypt the TLS traffic in order to monitor it. Actually, in this scheme, the monitoring infrastructure could completely meddler-in-the-middle (MITM) the traffic (modify the data in transit).

    This scheme breaks the expectation of forward secrecy provided by the (supposedly-)ephemeral Diffie-Hellman key exchange: if the static Diffie-Hellman private key is compromised, all the communications established using it would be compromised (including the ones established in the past). For this reason, some would argue that this is a bad practice for the privacy of the users and that a client should reject a handshake if it detects that a DH key is reused in DHE handshakes.

    The same scheme is called eTLS (“Enterprise Transport Layer Security”) in ETSI TS 103 523-3 and suggested in NIST SP 800-37A preliminary draft. Another solution envisionned in the NIST SP 800-37A preliminary draft is for TLS server to submit the “symmetric key used to encrypt the connection” to some “key distribution function”.

    See section 7.4 of RFC9325 for other security issues associated with reusing a DH key pair. ↩︎

  4. RFC8998 defines two cipher suites based on the Chinese ShangMi algorithms (the SM4 block cipher and the SM3 hash function). These are expected to be used in China and are not endorsed by the IETF 🤔.

    RFC9367 defines four cipher suites based on Russian algorithms: the Kuznyechik or Magma block cipher in Multilinear Galois Mode (MGM) mode. and the GOST R 34.11-2012 hash function. These are expected to be used in Russia and are not endorsed by the IETF 🤔.

    RFC9150 defines two ciphersuites (TLS_SHA256_SHA256 and TLS_SHA384_SHA384) which do not provide confidentiality (no encryption) but only integrity and authentication. These are not endorsed by the IETF and should only be used in specific cases. ↩︎