/dev/posts/

Introduction to TLS v1.2

Published:

Updated:

Some notes about how TLS v1.2 (Transport Layer Security) works. The goal explain what is going on in a network traffic dump, the role of the different TLS extensions, the impact of the different cipher suites on security, etc. It includes several diagrams and many references.

The post starts with a summarizing sequence diagram. The next section describes in details an example of a typical TLS connection. Then, some more details are provided. The next section explains how session resumption works. The following section describes some less used features: mutual authentication, alternative certificate formats and TLS-PSK.

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

Note: in TLS v1.3

Some things are quite different in TLS v1.3. This is going to be covered in the next episode.

Update 2023-05-25: Fix error about protocol stack diagrams where “EAP-TLS” was used instead of “EAP-TTLS”.

Update 2020-02-01: added some notes about DNS rebinding for HTTPS in appendix.

Table of content

Overview

TLS works on top of a reliable transport (usually TCP) and provides:

Examples: protocol stacks

TLS can for example be used to secure: HTTP (HTTP/1.x and HTTP/2), WebSocket, POP, IMAP, SMTP, FTP, LDAP, DNS (DoT, DNS-over-TLS), etc.

[ HTTP/1.x ] [ HTTP/2 ] [ IMAP ] [ SMTP ] [ DNS ] [ LDAP ]
[ TLS      ] [ TLS    ] [ TLS  ] [ TLS  ] [ TLS ] [ TLS  ]
[ TCP      ] [ TCP    ] [ TCP  ] [ TCP  ] [ TCP ] [ TCP  ]
[ IP       ] [ IP     ] [ IP   ] [ IP   ] [ IP  ] [ IP   ]
  HTTPS        HTTPS     IMAPS    SMTPS    DoT     LDAPS    etc.
Example protocols stacks

You will find examples of using of TLS for authentication in appendix.

Note: TLS in QUIC

Another common usage of TLS is in the QUIC handshake (used in particular for HTTP/3). However, QUIC mandates TLS v1.3 or greater so this will be covered in the next episode.

Note: DTLS

For datagram-oriented application, DTLS may be used instead. DTLS is quite similar to TLS but works on top of a datagram-oriented transport (usually UDP) and provides a datagram-oriented transport.

The major phases of the TLS connections are:

  1. The client and the server negotiates which version of the TLS protocol to use, which encryption scheme to use, which key exchange algorithm to use, etc. This is done is the ClientHello and ServerHello messages.
  2. The client and the server proceed with the negotiated key exchange algorithm, typically a Diffie-Hellman (DH) key exchange. The purpose of the key exchange is to establish some shared secret (the master secret) between the client and the server without revealing this secret to eavesdroppers. The server and optionally the client are authenticated as part of the key exchange algorithm.
  3. The master secret is used to derive some key material (for encryption and authentication).
  4. Then the client and the server confirm to each other that they have computed the same master secret and observed the same TLS handshake. The latter should prevent an attacker from successfully tampering with the TLS handshake (eg. forcing the usage of weaker options during the negotiation). This is done in the ChangeCipherSpec and Finished messages.
  5. At this point, the TLS handshake is over. The client and the server can exchange encrypted and authenticated application protocol data.

Note: TLS sublayers

[ Handshake | ChangecipherSpec | Alert | Application (eg. HTTP) ] (TLS subprotocols)
[ TLS Record Protocol: fragmentation                            ] (multiplexing and framing)
[ TLS Record Protocol: compression                              ] (if negotiated)
[ TLS Record Protocol: record protection                        ] (encryption and message auth)
[ Transport layer (eg. TCP)                                     ]
Layers in the TLS protocol

Summary

The following sequence diagram summarized a huge part of TLS v1.2. This might be somewhat overwhelming for now. The next sections should explain it all. Some bits (such as session resumption and TLS-PSK) are omitted for now but are discussed in following sections. A simplified version of this diagram presenting the typical case is presented in the next section: you might want to skip there at first reading.

TLS v1.2 sequence diagram

Note: notations

The sequence diagrams in this post indicates message encryption with “(enc)”: everything at the right of “(enc)” is encrypted (and authenticated) with keys derived from the master secret; everything before “(enc)” is not encrypted.

The grey-out sections can be considered as deprecated.

Summary of handshake messages
Message Sent by Role
ClientHello/ServerHello client/sever negotiation (TLS version, TLS extensions, cipher suites, application layer protocol, etc.), nonce exchange (anti-replay), session resumption (session ID)
Certificate both claim an identity and associate a static public key to it
ServerKeyExchange server ephemeral DH public key exchange and, in some cases, signature-based server authentication
CertificateRequest server propose/request TLS client authentication
ServerHelloDone server end of the server handshake
ClientKeyExchange client ephemeral DH public key exchange
CertificateVerify client signature-based client authentication
ChangeCipherSpec both enable encryption for this direction
Finished both key confirmation and handshake tampering prevention (eg. protect against TLS downgrade)
NewSessionTicket server provision ticket-based session resumption

Example

This section describes in details a typical (real) TLS connection. This is what used to happens when connecting to this web site using Firefox 78.12.0esr[1]. This example, uses the ECDHE_RSA key exchange algorithm:

Sequence diagram for a typical TLS v1.2 connection

The differents steps are explained in more details afterwards.

Note: what is encrypted in TLS v1.2?

Each peer starts using encryption after sending the ChangeCipherSpec message. Even when encryption is used, the ContentType of each TLSPlaintext record (chunk of data) is sent in cleartext. This field indicates which TLS subprotocol (handshake protocol, application protocol, cipher_spec_change) is transported in a given TLSPlaintext record.

Negotiation

The client and the server starts by exchanging hello messages (ClientHello and ServerHello). These messages are used for negotiating:

These messages include extensions which can be used to:

The client and the server exchange random values (nonces) as well in the hello messages which are used for deriving key material later on. Exchanging nonces ensures that each participant contributes some ephemeral value to the exchange (even if, for example, they are using a static Diffie-Hellman key pair): this can be used to prevent replay attacks.

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

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

CipherSuite

The cipher suite used is negotiated in the TLS hello messages. In TLS v1.2, a cipher suite is roughly a combination of:

Ciphersuites are named using a pattern of the form TLS_{key_exchange}_WITH_{encryption}_{hash}.

Example

In our example, Firefox advertised the following cipher suites compatible with TLS v1.2 or below[3]:

In our example, the cipher suite chosen by the server is TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:

  • ECDHE_RSA is the key exchange method i.e. Elliptic Curve Diffie-Hellman key agreement with ephemeral server key (ECDHE) with RSA signature;
  • AES_128_GCM is used for the encryption;
  • SHA256 is a secure hash algorithm used for key derivation.

The cipher suite chosen by the server may depend on the requested server name (as requested in the SNI extension). For example, the chosen cipher suite must be compatible with whatever certificate the server has for this server name. In our case, we are using ECDHE_RSA so the server will have to present a certificate containing a RSA public key with the digitalSignature key usage.

Extensions

Both ClientHello and ServerHello messages can include TLS extensions. However, the server can only include extensions which have been sent by the client: some extensions are only sent by the client in order to advertise that it has support for them.

Some important extensions are explained below.

enum {
    signature_algorithms(13), (65535)
} ExtensionType;

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

Example: extensions sent by the browser

In my example, the following extensions were sent by Firefox:

The server answered with:

Warning: privacy consideration

The TLS extensions are sent in cleartext[4]. This can include for example the server name (SNI) and which application protocol is used (ALPN).

SNI extension

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 mutual 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;

ALPN extension

The peers can use the Application-Layer Protocol Negotiation (ALPN) to negotiate which application protocol will be tansported by the TLS connection[5]. In this example, the client announces support for HTTP/2 (h2) and HTTP/1.1 (http/1.1) and the server chooses to use HTTP/1.1.

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

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

Signature algorithms extension

The client may use the signature algorithms extension to indicate which signature methods are supported by the client:

enum {
    none(0), md5(1), sha1(2), sha224(3), sha256(4), sha384(5),
    sha512(6), (255)
} HashAlgorithm;

enum { anonymous(0), rsa(1), dsa(2), ecdsa(3), (255) }
SignatureAlgorithm;

struct {
    HashAlgorithm hash;
    SignatureAlgorithm signature;
} SignatureAndHashAlgorithm;

SignatureAndHashAlgorithm supported_signature_algorithms<2..2^16-2>;

Note: signature algorithms for certificates

The signature_algorithms_cert extension may be used to negotiate different algorithms for certificate signatures than the one used for Diffie-Hellman exchange signatures. This extensions has been introduced in TLS v1.3 but may be used in TLS v1.2.

Note: digital signatures

Digital signatures is usually done in two steps:

  1. the payload is hashed using a secure hash function (eg. SHA-256);
  2. the resulting hash is signed using a signature algorithms.

These are indicated by the hash and signature fields of SignatureAndHashAlgorithm respectively.

Example: signature and hashes

In my example, Firefox was sending support for: ecdsa_secp256r1_sha256, ecdsa_secp384r1_sha384, ecdsa_secp512r1_sha512, rss_pss_rsae_sha256, rss_pss_rsae_sha384, rss_pss_rsae_sha521, rss_pkcs1_sha256, rss_pkcs1_sha383, rss_pkcs1_sha512, ecdsa_sha1, and rsa_pkcs1_sha1.

Supported groups

The supported_groups extension (formerly elliptic_curves) is used by the client to advertise which Diffie-Hellman groups it handles:

This extension is not sent by the server: the server advertises the chosen group in the ServerKeyExchange message.

Note: the supported_groups extensions used to be elliptic_curves

Before RFC7919, this extension was called elliptic_curves and only listed ECDH groups. The server was expected to be allowed to use arbitrary FFDH groups (defined by a prime number p).

The server may actually not support the supported_groups semantic and use a arbitrary FFDH group.

Server certificate chain

At this point, the server sends a certificate chain in the Certificate message. This certificate chain is used to associate a static public key to its identity (its domain name)[6]. This public key will then be used in the following steps to authenticate the TLS handshake.

By default and in most cases, X.509 certificates are used. The usage of other types of certificates can be negotiated using the client_certificate_type and server_certificate_type TLS extensions.

opaque ASN.1Cert<1..2^24-1>;

struct {
    ASN.1Cert certificate_list<0..2^24-1>;
} Certificate;

Note: Structure of the certificate chain

The first certificate is the leaf certificate i.e. the certificate of the server itself (it contains the server public key).

The other certificates are Certificate Authority (CA) certificates:

  • these CA certificates need not be from leaf to root order;
  • they are usually intermediate certificates only as it is not necessary to include the root certificate (but this is allowed).

At this point, the client validates the certificate chain (see appendix). 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.

The type of leaf certificate (type of public key, key usage) usable depends on which key exchange algorithm is used which type of key and which certificate may be used. For example, for ECDHE_RSA, the certificate must contain a RSA public key and the key usage extension (if present) must include digitalSignature.

Key exchange

At this point, the client and the server proceed with the negotiated key exchange. Several key exchange algorithms are available in TLS v1.2. In any case, the role of this step is to:

In our example, the ECDHE_RSA key exchange algorithm is used. This is an ephemeral Elliptic Curve Diffie-Hellamn key exchange (ECDHE) with RSA signature for server authentication:

  1. the server generates a new (ephemeral) ECDH key pair (different for each TLS session)[7];
  2. the server signs this public key (alongside with the random nonces) using its RSA private key in order to authenticate the key exchange;
  3. the server sends the ephemeral key and the signature to the client (ServerKeyExchange message);
  4. the client can now check the digital signature by using the server public key in order to ensure that the ECDH public key is sent by the server;
  5. the client generates an ephemeral ECDH key pair as well and sends the ephemeral public key to the server in the ClientKeyExchange message.
struct {
    select (KeyExchangeAlgorithm) {
        case ecdhe_rsa:
            ServerECDHParams    params;
            digitally-signed struct {
                opaque client_random[32];
                opaque server_random[32];
                ServerDHParams params;
            } signed_params;
    };
} ServerKeyExchange;

struct {
    select (KeyExchangeAlgorithm) {
        case ecdhe_rsa:
            ClientECDiffieHellmanPublic;
    } exchange_keys;
} ClientKeyExchange;

struct {
    ECParameters    curve_params;
    ECPoint         public;
} ServerECDHParams;

struct {
    ECPoint ecdh_Yc;
} ClientECDiffieHellmanPublic;

At this point, both the client and the server (see the details of the key derivation in appendix):

  1. compute a (the same) Diffie-Hellman shared secret (the pre master secret) using its own Diffie-Hellman private key and the other peer Diffie-Hellman public key;
  2. derive a shared master secret from this pre master secret;
  3. derive key material (such as encryption keys) from the master secret.

A passive attacker only has access to the Diffie-Hellman public keys. Therefore, it cannot compute the pre master secret, the master secret and the key material. See the previous episode for explanations about the DH key exchange.

An active attacker cannot contribute an ephemeral public key on behalf of the server because of the digital signature.

Warning: weakness of the signature in the ServerKeyExchange message

The signature in the ServerKeyExchange does not contain the cipher suite (and other negotiated parameters). An attacker could attempt to use the signature generated by the server in a different cipher suite than the one it was generated for.

This defect is fixed in TLS v1.3. In TLS v1.3, the digital signature in the CertificateVerify message contains a signature of the all the handshake messages so far: this includes the negotiated cipher suite.

Vulnerability: Logjam attack

This weakness in the TLS v1.2 protocol was exploited in the Logjam attack. In this attack, the client is negotiating a DHE key exchange but a meddler-in-the-middle (MITM) negotiates a DHE export[8] key exchange with the server instead:

  1. the server generates a ServerKeyExchange which contains a DH public key on a weak (export) DH group;
  2. the attacker forwards this message to the client;
  3. as the signature in the messages does not cover the cipher suite, the signature is acceptable by the client and the client may continue the handshake using the weak DH group.

The Finished messages are supposed to prevent this type of manipulation by ensuring that both the client and the server have the same view of the handshake. However, if the attacker is able to break either of the DH public keys on this weak DH group fast enough, he can compute the premaster secret in time in order to spoof the Finished message.

Sequence diagram illustrating the logjam attack

The client can protect against this attack by checking that the DH group chosen by the server is big enough.

Enabling encryption

At this point, the client peer sends:

  1. a ChangeCipherSpec message which indicates that the negotiated encryption method (in our case AES_128_GCM) will be used for all of its subsequence messages;
  2. a Finished message which confirms that both peers have the same master secret (key confirmation) and observed the same the same handshake (protection against downgrade attacks).

Then the server sends a ChangeCipherSpec message and a Finished message as well.

struct {
    enum { change_cipher_spec(1), (255) } type;
} ChangeCipherSpec;

struct {
    opaque verify_data[verify_data_length];
} Finished;

In our case, we are using AES_128_GCM for encryption. Two encryption secret keys (one for each direction) are derived from the master secret. In addition, two initialization vectors are derived from the master secret as well (one for each direction). As this is an authenticated encryption with associated data (AEAD) algorithm, it already protects against tempering: there is not need for a separate MAC and there is no need to derived MAC keys from the master secret.

The Finished message includes a verify_data field which contains a a MAC[9] tag of all the previous handshake messages using the master secret:

verify_data
         PRF(master_secret, finished_label, Hash(handshake_messages))
            [0..verify_data_length-1];

The other peer verifies this value in order to ensure that the handshake has not been tampered with (eg. downgrading the TLS version, forcing usage of weaker cipher suites, disabling TLS extensions, etc.).

Application Data

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

When one side does not need to send data on the TLS connection anymore, it sends a Alert close_notify messsage to the other side with the warning level to signal end-of-data.

Details

Key exchange methods

The following key exchange algorithms support server authentication using public-key cryptography:

The following key exchange algorithms are anonymous (unauthenticated ephemeral Diffie-Hellman exchange). They do not authenticate the server:

Other key exchange algorithms are used for providing mutual authentication (i.e. both the server and the client are authenticated) using a shared-secret (instead of public-key cryptography or in addition of it):

struct {
    select (KeyExchangeAlgorithm) {
        case dh_anon:
            ServerDHParams params;
        case dhe_dss:
        case dhe_rsa:
            ServerDHParams params;
            digitally-signed struct {
                opaque client_random[32];
                opaque server_random[32];
                ServerDHParams params;
            } signed_params;
        case rsa:
        case dh_dss:
        case dh_rsa:
        case ecdh_dss:
        case ecdh_rsa:
            struct {} ;
            /* message is omitted for rsa, dh_dss, and dh_rsa */
        case ecdhe_dss:
        case ecdhe_rsa:
        case ecdh_anon:
            ServerECDHParams    params;
            digitally-signed struct {
                opaque client_random[32];
                opaque server_random[32];
                ServerDHParams params;
            } signed_params;
    };
} ServerKeyExchange;

struct {
    select (KeyExchangeAlgorithm) {
        case rsa:
            EncryptedPreMasterSecret;
        case dhe_dss:
        case dhe_rsa:
        case dh_dss:
        case dh_rsa:
        case dh_anon:
            ClientDiffieHellmanPublic;
        case ecdh_dss:
        case ecdh_rsa:
        case ecdhe_dss:
        case ecdhe_rsa:
        case ecdh_anon:
            ClientECDiffieHellmanPublic;
    } exchange_keys;
} ClientKeyExchange;
struct {
    public-key-encrypted PreMasterSecret pre_master_secret;
} EncryptedPreMasterSecret;

struct {
    opaque dh_p<1..2^16-1>;
    opaque dh_g<1..2^16-1>;
    opaque dh_Ys<1..2^16-1>;
} ServerDHParams;     /* Ephemeral DH parameters */

struct {
    select (PublicValueEncoding) {
        case implicit: struct { };
        case explicit: opaque dh_Yc<1..2^16-1>;
    } dh_public;
} ClientDiffieHellmanPublic;

struct {
    ECParameters    curve_params;
    ECPoint         public;
} ServerECDHParams;

struct {
    ECPoint ecdh_Yc;
} ClientECDiffieHellmanPublic;

Note: usage of the server certificate private key

After validating the certificate chain, the client need to ensure that server owns the corresponding private key in order to know it is actually communicating with the correct server.

For the RSA exchange method, the client encrypts the master secret with the server public key: if the handshake terminates correctly (server Finished message), the server has managed to decrypt the master secret and has the server certificate private key.

For (EC)DHE_* key exchanges, the server sends a signature (signed with the server certificate signing key) of the ephemeral DH server public key (and the random nonces) alongside the ephemeral DH server public key in the ServerKeyExchange message.

For (EC)DH_* key exchanges, the certificate contains the server static DH public key. The server uses the corresponding static private key in the DH key exchange.

Note: signing method in non-ephemeral key exchange cipher suites

In TLS v1.1, the digital signature algorithm indicated in key exchange algorithm name of non-ephemeral key exchange methods (eg. RSA in ECDH_RSA) used to indicate the algorithm used for the digital signature of the server certificate.

In TLS v1.2, the algorithm for the certificate signature is independent of the cipher suite: instead, the supported signature algorithms are indicated by the signature_algorithms extension. Therefore, the DH_DSS and DH_RSA cipher suites on the one hand and the ECDH_ECDSA and ECDH_RSA cipher suites on the other hand are equivalent in TLS v1.2.

Meaning of the different key exchanges
Key exchange TLS v1.1 TLS v1.2
DH_DSS Static FFDH with DSA signature Static FFDH with any signature
DH_RSA Static FFDH with RSA signature Static FFDH with any signature (same)
ECDH_ECDSA Static ECDH with ECDSA signature Static ECDH with any signature
ECDH_RSA Static ECDH with RSA signature Static ECDH with any signature (same)

Note: forward secrecy

Whe using static Diffie-Hellman key exchange (DH_DSS, DH_RSA, ECDH_RSA, ECDH_ECDSA), an attacker which manages to obtain the server static private key can compute the pre master secret of all past TLS communications based on that key and decrypt them. This is problematic: an attacker could records all the encrypted TLS communications with a given server in the hope of being able to exfiltrate the server private key in the future; using this private key it would then be able decrypt all the previous recorded communications.

Using an ephemeral Diffie-Hellman key exchange (ECDHE_* and DHE_*) fixes this problem. When using such a method, an attacker cannot use the static (signing) server private key to recover the pre master secrets of previous TLS sessions. This property is called forward secrecy with respect to the server signature private key.

The usage of methods which do not support forward secrecy is discouraged.

Note: anonymous key exchange

The DH_anon and ECDH_anon key exchange algorithms use non-authenticated (anonymous) ephemeral Diffie-Hellman key exchange. In this case, the server does not send certificates (and signatures). As these methods are not authenticated, they are vulnerable to active attacks. They only provide opportunistic encryption.

Payload protection

Message authentication is always used in addition to encryption. This prevents a man-in-the-middle from tampering with the protected data.

Some encryption schemes use authenticated encryption with additional data (AEAD): they already protect against tampering. This is the case for example for AES_128_GCM, AES_128_GCM and CHACHA20_POLY1305.

Other encryption schemes (eg. AES_256_CBC) do not protect against tempering. In this case, an HMAC is included with each encrypted TLS records in order to provide message authentication: TLS uses MAC-then-encrypt by default for this but the encrypt_then_mac extension can be used to negotiate the usage of encrypt-then-MAC instead. Encrypt-then-MAC is considered to be more robust (see “The Order of Encryption and Authentication for Protecting Communications”).

Note: TLS v1.3

TLS v1.3 only includes AEAD encryption schemes.

In all cases, the payload protection covers some additional authenticated data (AAD) in addition to the payload:

additional_data = seq_num + TLSCompressed.type +
                        TLSCompressed.version + TLSCompressed.length;

In particular, the sequence number (seq_num) is incremented for each TLS record: this prevents an attacker from replaying a TLS record.

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.

Session resumption

In order to speed up/optimize the connection establishment, TLS supports the resumption of an existing TLS session.

This can have two benefits:

Two different mechanisms may be used for session resumption in TLS v1.2:

In both cases, the session state (cipher, MAC, master secret, etc.) established in a previous TLS connection are reused for the new TLS connection. As the random nonces are different in the new connection, the generated key material is different as well.

Note: TLS v1.3

Session resumption is very different in TLS v1.3.

Session identifiers

If the server supports classic session resumption,

  1. it returns a session identifier in the ServerHello message while initializing a full handshake;
  2. it stores the session state (cipher, MAC, master secret, etc.) associated with the session ID in a session cache;
  3. the client may choose to include the session state in a cache of its own.
opaque SessionID<0..32>;

Later, the client may try to resume the session in order to avoid the overhead associated with a full handshake:

  1. the client includes the session identifier in the ClientHello;
  2. if the server accepts the session, it sends back the session identifier in the ServerHello (otherwise, it uses a fresh session identifier);
  3. the client and server reuse the negotiated chosen session state;
  4. they derive new key material from the master secret and the new random nonces.
Sequence diagram for session resumption in TLS v1.2

Session ticket

A downside of the classic session resumption mechanism is that it requires a server-side state: the server needs to store the state of each sessions in some cache. The session ticket extension can be used to avoid the need for server-side cache. It is used instead of session identifiers.

When the session_ticket, is used the server may send an opaque session ticket in the NewSessionTicket message as the result of a TLS handshake. The client keeps this ticket and associates it with the master secret. This ticket can then sent by the client in a in a new TLS connection (in the session_ticket extensions of the ClientHello) in order to resume the connection.

struct {
    uint32 ticket_lifetime_hint;
    opaque ticket<0..2^16-1>;
} NewSessionTicket;

The ticket is design to contain the session state (cipher, MAC, master secret, etc.). Therefore, it needs to be be much larger than the session identifier. The session state are encrypted[11] and authenticated using some key material known by the server. When the server receives the ticket, it checks that it is genuine (and still valid), decrypts it and uses the session state in the ticket for this new connection.

The message NewSessionTicket message is sent before the ChangeCipherSpec and is therefore not encrypted. An attacker can obtain the session ticket but cannot use it to resume a connection on behalf of the client as it does not know the master secret.

Sequence diagram for session resumption with session tickets

Note: ticket content

As the ticket is opaque to the client, the server may use any format at its convenience but a recommended/suggested format is included in the RFC:

struct {
    opaque key_name[16];
    opaque iv[16];
    opaque encrypted_state<0..2^16-1>;
    opaque mac[32];
} ticket;

struct {
    ProtocolVersion protocol_version;
    CipherSuite cipher_suite;
    CompressionMethod compression_method;
    opaque master_secret[48];
    ClientIdentity client_identity;
    uint32 timestamp;
} StatePlaintext;

enum {
    anonymous(0),
    certificate_based(1),
    psk(2)
} ClientAuthenticationType;

struct {
    ClientAuthenticationType client_authentication_type;
    select (ClientAuthenticationType) {
        case anonymous: struct {};
        case certificate_based:
            ASN.1Cert certificate_list<0..2^24-1>;
        case psk:
            opaque psk_identity<0..2^16-1>;   /* from [RFC4279] */
    };
} ClientIdentity;

The RFC suggests using encrypt-then-MAC with AES-CBC-128 for encryption and HMAC-SHA-256 for MAC.

Note: ticket content with OpenSSL

For OpenSSL, the ticket content is by default an encrypt-then-MAC (using AES-256-CBC for encryption and HMAC-SHA-256 for MAC) of the following ASN.1 structure:

ASN1_SEQUENCE(SSL_SESSION_ASN1) = {
    ASN1_EMBED(SSL_SESSION_ASN1, version, UINT32),
    ASN1_EMBED(SSL_SESSION_ASN1, ssl_version, INT32),
    ASN1_SIMPLE(SSL_SESSION_ASN1, cipher, ASN1_OCTET_STRING),
    ASN1_SIMPLE(SSL_SESSION_ASN1, session_id, ASN1_OCTET_STRING),
    ASN1_SIMPLE(SSL_SESSION_ASN1, master_key, ASN1_OCTET_STRING),
    ASN1_IMP_OPT(SSL_SESSION_ASN1, key_arg, ASN1_OCTET_STRING, 0),
    ASN1_EXP_OPT_EMBED(SSL_SESSION_ASN1, time, ZINT64, 1),
    ASN1_EXP_OPT_EMBED(SSL_SESSION_ASN1, timeout, ZINT64, 2),
    ASN1_EXP_OPT(SSL_SESSION_ASN1, peer, X509, 3),
    ASN1_EXP_OPT(SSL_SESSION_ASN1, session_id_context, ASN1_OCTET_STRING, 4),
    ASN1_EXP_OPT_EMBED(SSL_SESSION_ASN1, verify_result, ZINT32, 5),
    ASN1_EXP_OPT(SSL_SESSION_ASN1, tlsext_hostname, ASN1_OCTET_STRING, 6),
#ifndef OPENSSL_NO_PSK
    ASN1_EXP_OPT(SSL_SESSION_ASN1, psk_identity_hint, ASN1_OCTET_STRING, 7),
    ASN1_EXP_OPT(SSL_SESSION_ASN1, psk_identity, ASN1_OCTET_STRING, 8),
#endif
    ASN1_EXP_OPT_EMBED(SSL_SESSION_ASN1, tlsext_tick_lifetime_hint, ZUINT64, 9),
    ASN1_EXP_OPT(SSL_SESSION_ASN1, tlsext_tick, ASN1_OCTET_STRING, 10),
    ASN1_EXP_OPT(SSL_SESSION_ASN1, comp_id, ASN1_OCTET_STRING, 11),
#ifndef OPENSSL_NO_SRP
    ASN1_EXP_OPT(SSL_SESSION_ASN1, srp_username, ASN1_OCTET_STRING, 12),
#endif
    ASN1_EXP_OPT_EMBED(SSL_SESSION_ASN1, flags, ZUINT64, 13),
    ASN1_EXP_OPT_EMBED(SSL_SESSION_ASN1, tlsext_tick_age_add, ZUINT32, 14),
    ASN1_EXP_OPT_EMBED(SSL_SESSION_ASN1, max_early_data, ZUINT32, 15),
    ASN1_EXP_OPT(SSL_SESSION_ASN1, alpn_selected, ASN1_OCTET_STRING, 16),
    ASN1_EXP_OPT_EMBED(SSL_SESSION_ASN1, tlsext_max_fragment_len_mode, ZUINT32, 17),
    ASN1_EXP_OPT(SSL_SESSION_ASN1, ticket_appdata, ASN1_OCTET_STRING, 18),
    ASN1_EXP_OPT_EMBED(SSL_SESSION_ASN1, kex_group, UINT32, 19),
    ASN1_EXP_OPT(SSL_SESSION_ASN1, peer_rpk, ASN1_OCTET_STRING, 20)
} static_ASN1_SEQUENCE_END(SSL_SESSION_ASN1)

Impact of session resumption on security

Vulnerability: SSRF attacks through session resumption

Both session resumption mechanisms can be abused to trigger some form of SSRF attacks on some protocols when used in combination with DNS rebinding. See When TLS Hacks You.

Warning: session resumption and forward secrecy

We have seen that TLS key exchanges based on ephemeral Diffie-Hellman provide forward secrecy: if the long-term/persistent keys (eg. signing private keys) are compromised, the security of the previously established sessions is not compromised. However, both session resumption mechanisms may compromise forward secrecy.

When using session identifiers for session resumption, both the client and the server need to keep the session master secret in a session cache for a possibly long duration (depending on the implementation): an attacker getting access to this session cache could decrypt all the (previous) connections based on the master secrets present in the cache. If the cache is stored on a persistent storage, the master secrets may still be present on disk after they have been evicted from the cache.

When using session tickets for session resumption, the tickets are sent in cleartext in both ClientHello and NewSessionTicket. The session ticket usually contains the master secret encrypted with an encryption key possessed by the server (and authenticated using a MAC):

  • If the encryption key is compromised, an attacker can decrypt the session tickets encrypted with that key, obtain the master secrets from the observed tickets and compromise all the associated TLS sessions. for sessions associated with session tickets, there is no forward secrecy with respect to the session ticket encryption key. Moreover, this could be used to hijack sessions based on these master secrets. This is especially problematic if the session ticket encryption keys are long-lived or persisted on disk.
  • If the MAC key is compromised, an attacker can forge session tickets. This could be used to impersonate arbitrary clients (when mTLS or PSK authentication is supported) by forging the StatePlaintext.client_identity (or a similar field such as the OpenSSL peer field).

For this reason, Mozilla recommends to disable session tickets. Another solution (depending on the implementation) might be to restart the server daily in order to purge the cache and renew the session ticket encryption key.

Extra features

Mutual TLS authentication

Usually, only the server is authenticated at the TLS level. However, TLS supports authenticating the client as well[12] using public-key cryptography. This is called mutual (or client) TLS authentication (mTLS).

When the server supports mutual authentication, it sends a CertificateRequest message. This message can include information about which certificates are accepted by the server: the certificate_authorities field can list the distinguished names (DN) of the certificate authorities (CA) (either root or intermediate) accepted by the server.

enum {
    rsa_sign(1), dss_sign(2), rsa_fixed_dh(3), dss_fixed_dh(4),
    rsa_ephemeral_dh_RESERVED(5), dss_ephemeral_dh_RESERVED(6),
    fortezza_dms_RESERVED(20),
    ecdsa_sign(64), rsa_fixed_ecdh(65), ecdsa_fixed_ecdh(66),
    (255)
} ClientCertificateType;

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

struct {
    ClientCertificateType certificate_types<1..2^8-1>;
    SignatureAndHashAlgorithm supported_signature_algorithms<2^16-1>;
    DistinguishedName certificate_authorities<0..2^16-1>;
} CertificateRequest;

The server advertizes which type(s) of client certificate it is willing to accept:

If the client chooses not to authenticate itself, it sends an empty Certificate message: the server may choose to either accept or reject (by terminating the TLS connection with a handshake_failure error) non-authenticated clients.

If the client chooses to authenticate itself, it first sends its certificate chain in a Certificate message:

struct {
    digitally-signed struct {
        opaque handshake_messages[handshake_messages_length];
    }
} CertificateVerify;

Warning: privacy consideration

The client identity (included in the certificate) is sent in cleartext. This is not ideal for privacy in many contexts. For example, for authenticating a user: the user name or email may be present in the user certificate and could be sent in cleartext. Some details are provided in appendix.

It is possible to prevent this by doing the client authentication as part of a TLS renegotiation.

Warning: forward secrecy

Using a static client DH key pair poses the same problem as using a static server DH key pair: if an attacker manages to get the static private DH key, he can then decrypt all the previous TLS sessions initiated using that static key (lack of forward secrecy).

Note: mid-session client authentication with HTTP

Web applications often used to rely on mid-session TLS client authentication: the server would only request TLS client authentication (using TLS renegotiation) when the client would request some restricted resources. However, mid-session client authentication is only supported with HTTP/1.x.

In HTTP/2, TLS renegotiation is only permitted in order to provide confidentiality protection for client credentials (the client certificate). TLS renegotiation must not be used to for mid-session client authentication with HTTP/2.

TLS renegotiation has been removed in TLS v1.3. In TLS v1.3, post-handshake client authentication could arguably be used instead of TLS renegotiation. However, its usage is forbidden both in HTTP/2 and in QUIC (which is used a transport for HTTP/3) because it does not play nicely with request multiplexing.

Moreover, TLS v1.3 post-authentication support is not widely support in browsers and is probably not going to be widely supported:

  • Firefox, has support for it but it is disabled by default (security.tls.enable_post_handshake_auth);
  • it is not implemented in Chromium.
Status of mid-session client authentication for HTTP
Application TLS Status of mid-session client authentication
HTTP/1.1 v1.2 supported (with TLS renegotiation)
HTTP/1.1 v1.3 theoretically supported (with TLS v1.3 post-handshake authentication) but not implemented in practice
HTTP/2 v1.2 not supported (TLS v1.2 renegotiation only allowed to provide confidentiality to the authentication)
HTTP/2 v1.3 not supported (TLS v1.3 post handshake authentication forbidden with HTTP/2)
HTTP/3 v1.3 not supported (TLS v1.3 post handshake authentication forbidden with QUIC, HTTP/3)

Warning: Key Compromise Impersonation

When the client uses static DH authentication (rsa_fixed_dh, dss_fixed_dh, rsa_fixed_ecdh and ecdsa_fixed_ecdh), with a TLS_(EC)DH_* key exchange, the actual key exchange is static-static DH which is vulnerable to Key Compromise Impersonation (KCI). If an attacker manages to get the static DH private key of one participant (Alice), he can not only impersonate this participant (Alice) but any other participant (eg. Bob) when talking to Alice. A practical vulnerability (CVE-2015-8960) is detailed in appendix.

Renegotiation

The client may start a new handshake in order to renegotiate the security parameters of a connection at any time after the initial handshake.

Why? Reasons for using TLS renegotiation may include:

How? In order to do this, the client sends a ClientHello message and proceeds with a new handshake. The client may start the TLS renegotiation unilaterally or at the request of the server (HelloRequest message). As the renegotiation handshake happens after the ChangecipherSpec, it is protected (encrypted) using the key material of the existing connection.

struct { } HelloRequest

Warning: security impact of TLS renegotiation

TLS renegotiation can be considered to be a dangerous feature. Several vulnerabilites are related to renegotiation such as CVE-2009-3555 (see the explanations in RFC5746) or 3SHAKE.

TLS renegotiation has been removed (and replaced with other mechanisms) in TLS v1.3. HTTP/2 explicitly forbids the usage of TLS renegotiation in many cases.

Secure renegotiation is a fix at the TLS protocol level for CVE-2009-3555. In introduces the renegotiation_info TLS extension. In the initial handshake, this extension is only used to negotiate the usage of secure renegotiation. In the renegotiation handshake, these extension contain:

Vulnerability: unsafe TLS renegotiations

TLS renegotiation without renegotiation_info is not safe (vulnerable to CVE-2009-3555. Unsafe (legacy) TLS renegotiation is disabled by default in current implementations. For example, with OpenSSL, the SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION option must be set in order to accept unsafe renegotiations.

Note: extended master secret

The extended_master_secret extension has been introduced to fix the 3SHAKE attack. This attacks combines renegotiation and session resumption in order to break client authentication.

Alternative certificates

By default, TLS uses X.509 certificates. Alternative public keys formats can be used instead of X.509 certificates using the client_certificate_type and server_certificate_type TLS extensions.

This can be used to use exchange raw public keys instead of X.509 certificates. In this case, the Certificate messages contain the raw public key of the peer. Raw public keys may for example, be used for IoT applications in order to avoid relying on a public key infrastructure (PKI). The devices may for example be identified with a hash of their public keys. Alternatively, a similar result could be achieved by exchanging self-signed X.509 certificates and only process the public keys.

opaque ASN.1Cert<1..2^24-1>;

struct {
    select(certificate_type){

        // certificate type defined in this document.
        case RawPublicKey:
            opaque ASN.1_subjectPublicKeyInfo<1..2^24-1>;

        // X.509 certificate defined in RFC 5246
        case X.509:
            ASN.1Cert certificate_list<0..2^24-1>;

        // Additional certificate type based on
        // "TLS Certificate Types" subregistry
    };
} Certificate;

TLS-PSK

TLS-PSK includes key exchange algorithms which provide mutual authentication based on a (per-client) shared secret, the pre shared key (PSK): public key cryptography is not used for server (or client) authentication at all (no certificate is used). This can be useful for low-performance environments such as IoT applications.

Sequence diagram for TLS v1.2 with PSK

PSK key exchange algorithms:

Warning: forward secrecy

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

The RSA_PSK key exchange algorithm does not provide forward secrecy either: an attacker which has both the PSK and the server RSA private key can decrypt previous communications.

The DHE_PSK and ECDHE_PSK key exchange algorithms use ephemeral Diffie-Hellman to provide forward secrecy with respect to the PSK.

Warning: other security considerations

See the next episode about TLS v1.3 for more security considerations about the usage of PSK in TLS most of which applies to TLS v1.2 as well.

Summary:

  • PSK identity exposure (sent in cleartext);
  • vulnerabilities when using external PSK for group membership (Selfie attack).

A psk_identity is included in the ClientKeyExchange. It has the same role as login, identifying the client that is trying to authenticate.

Warning: privacy consideration

The client sends its PSK identity in cleartext.

struct {
    select (KeyExchangeAlgorithm) {
        case psk:
            opaque psk_identity_hint<0..2^16-1>;
        case diffie_hellman_psk:
            opaque psk_identity_hint<0..2^16-1>;
            ServerDHParams params;
        case rsa_psk:
            opaque psk_identity_hint<0..2^16-1>;
        case ec_diffie_hellman_psk:
            opaque psk_identity_hint<0..2^16-1>;
            ServerECDHParams params;
    };
} ServerKeyExchange;

struct {
    select (KeyExchangeAlgorithm) {
        case psk:
            opaque psk_identity<0..2^16-1>;
        case diffie_hellman_psk:
            opaque psk_identity<0..2^16-1>;
            ClientDiffieHellmanPublic public;
        case rsa_psk:
            opaque psk_identity<0..2^16-1>;
            EncryptedPreMasterSecret;
        case ec_diffie_hellman_psk:
            opaque psk_identity<0..2^16-1>;
            ClientECDiffieHellmanPublic public;
    } exchange_keys;
} ClientKeyExchange;

Warning: low-entropy secret

TLS-PSK should be used with high entropy PSK. It should not be used with PSKs derived from passwords: see TLS-SRP and TLS-PWD for password-based mutual authentication at the TLS layer.

FAQ

How are used the random values in the hello message?

The random values in the hello messages are used for:

This prevents some forms of replay attacks by ensuring that both participants contribute some ephemeral value to the master secret. This is especially important when non non-DHE key exchange is used or for session resumption.

The random values are used as well in the digital signatures used for authentication (for example in in the DHE_* and ECDHE_* key exchange algorithms):

struct {
    select (KeyExchangeAlgorithm) {
        case dhe_dss:
        case dhe_rsa:
            ServerDHParams params;
            digitally-signed struct {
                opaque client_random[32];
                opaque server_random[32];
                ServerDHParams params;
            } signed_params;
        case ecdhe_dss:
        case ecdhe_rsa:
        case ecdh_anon:
            ServerECDHParams    params;
            digitally-signed struct {
                opaque client_random[32];
                opaque server_random[32];
                ServerDHParams params;
            } signed_params;
    };
} ServerKeyExchange;

By including a new random for each TLS connection, the client can be sure that the server ephemeral DH public key is not being replayed.

How is the hash function used in the cipher suite?

The hash function included in the cipher suite is used:

The TLS PRF is used:

Appendix, key hierarchy

Pre Master Secret

The pre master secret is obtained as a result of the key exchange algorithm. Depending on the key exchange algorithm it may:

Master Secret

The master secret is derived from pre master secret and the two nonces (client and server random). The master secret is reused in case of session resumption. It is computed as:

master_secret = PRF(pre_master_secret, "master secret",
                    ClientHello.random + ServerHello.random)
                    [0..47];

where the PRF depends on the cipher suite.

The inclusion of the server and client nonces prevents some form of replay attacks by making sure that both participants contribute some ephemeral value to the master secret.

When the extended_master_secret extension is used, the hash of all the previous handshake messages is used instead of only using the server and client random values. This binds the master secret with the full TLS handshake which fixes the triple handshake attack.

master_secret = PRF(pre_master_secret, "extended master secret",
                    session_hash)
                    [0..47];

When using session resumption, the same master secret is reused for all connections associated with the same TLS session.

Note: importance of the master secret

The master secret (or the pre master secret) is everything needed to decrypt all the connections belonging to a given session, hijack them, or create new connections from the session (if session resumption is enabled).

Key Material

The key material is computed as:

key_block = PRF(SecurityParameters.master_secret,
                      "key expansion",
                      SecurityParameters.server_random +
                      SecurityParameters.client_random);

This key material is decomposed in client and server MAC keys, encryption keys and initialization vectors (IV):

client_write_MAC_key[SecurityParameters.mac_key_length]
server_write_MAC_key[SecurityParameters.mac_key_length]
client_write_key[SecurityParameters.enc_key_length]
server_write_key[SecurityParameters.enc_key_length]
client_write_IV[SecurityParameters.fixed_iv_length]
server_write_IV[SecurityParameters.fixed_iv_length]

As the master secret is reused in case of session resumption, the inclusion of the server and client random values ensures that the key material changes is different from different TLS connections in the same TLS session: this prevents replay attacks through session resumption.

Keying Material Exporters

The Keying Material Exporters mechanism defines a way to generate key material as a result of the TLS connection to be used in other protocols.

This can either be:

PRF(SecurityParameters.master_secret, label,
               SecurityParameters.client_random +
               SecurityParameters.server_random
               )[length]
PRF(SecurityParameters.master_secret, label,
               SecurityParameters.client_random +
               SecurityParameters.server_random +
               context_value_length + context_value
               )[length]

where the label depends on the consuming application/protocol.

A IANA registry of existing labels can be used to find some usages of this feature.

Note: Key exporter usage in OpenVPN

OpenVPN uses this feature for generating key material from the TLS handshake. See the generate_key_expansion_tls_export() and key_state_export_keying_material() functions.

Note: similar mechanism in some EAP methods (eg. WPA-entreprise)

Some EAP methods such as EAP-TLS use a similar approach to export resulting key material, a master session key (MSK), which may be used by lower layer protocols. For example, in WPA-entreprise (aka WPA-EAP), the MSK exported by the EAP method is used to derive Wifi key material: these keys are used to encrypt and authenticate the Wifi frames.

This EAP-TLS method does not explicitly mention the key exporter mechanism as it predates the key exporter RFC.

Appendix, using TLS for authentication only

In some applications, no application protocol data is transported on top of the TLS connection. Instead, the TLS protocl is only used for the secure handshake and the authentication it provides. The secrets established in the TLS handshake may be used to generate cryptograpic material for another protocol (see the Keying Material Exporters) which does not sit on top of TLS. This approach is for example used in OpenVPN, WPA-EAP (WPA-entreprise) with EAP-TLS, and QUIC[14]).

                     [ TLS     | IP   ]
[ TLS | IP or Eth. ] [ EAP-TLS | SNAP ] [ TLS     ]      [ TLS | HTTP3 ]
[ OpenVPN          ] [ EAP     | LLC  ] [ EAP-TLS ]      [ QUIC        ]
[ TCP or UDP       ] [ EAPOL   | CCMP ] [ EAP     | IP ] [ UDP         ]
[ IP               ] [ Wifi           ] [ PPP          ] [ IP          ]
     OpenVPN              WPA2-EAP           PPP             HTTPS
                            with             with           (HTTP/3)
                           EAP-TLS          EAP-TLS

Some EAP methods (EAP-TTLS, PEAP and TEAP) use TLS to secure (“tunnel”) the traffic of another authentication method. As before, the secrets established in the TLS handshake may be used to derive exported key material for other protocols (eg. in WPA-EAP).

            [ ...      ]
            [ EAP      ] [ PAP      ] [ CHAP     ] [ MS-CHAP  ] [ ...  ] [ ...  ]
            [ AVP      ] [ AVP      ] [ AVP      ] [ AVP      ] [ EAP  ] [ EAP  ]
[ mTLS    ] [ TLS      ] [ TLS      ] [ TLS      ] [ TLS      ] [ TLS  ] [ TLS  ]
[ EAP-TLS ] [ EAP-TTLS ] [ EAP-TTLS ] [ EAP-TTLS ] [ EAP-TTLS ] [ PEAP ] [ TEAP ]
[ EAP     ] [ EAP      ] [ EAP      ] [ EAP      ] [ EAP      ] [ EAP  ] [ EAP  ]
[ ...     ] [ ...      ] [ ...      ] [ ...      ] [ ...      ] [ ...  ] [ ...  ]
  EAP-TLS       EAP           PAP          CHAP       MS-CHAP     EAP      EAP
                over          over         over        over       over     over
             EAP-TTLS       EAP-TTLS     EAP-TTLS     EAP-TLS     PEAP     TEAP

See the EAP-TTLS AVP Usage subregistry for list of authentication methods compatible with EAP-TTLS.

Appendix, cleartext client identity

In TLS v1.2, the client sends its certificate chain before the cipher change. As a consequence, the certificate chain is sent in cleartext (unless it is sent as part of the TLS renegotiation). For example, some VPN solutions (eg. OpenvPN) provide support for mTLS based client authentication. In this case, the identity of the client might be leaked in cleartext.

This can be seen in the following screenshot of Wireshark inspecting an OpenVPN handshake.

In a packet-disection of an OpenVPN traffic using WireShark,
              we can see that the client identity
              present in the client certificate is sent in cleartext.
              A text version is included below.
Client identity sent in cleartext in OpenVPN hanshake as seen from WireShark

Text version:

Frame 15: 1264 bytes on wire (10112 bits), 1264 bytes captured (10112 bits)
Ethernet II, Src: 00:00:00_00:00:00 (00:00:00:00:00:00), Dst: 00:00:00_00:00:00 (00:00:00:00:00:00)
Internet Protocol Version 4, Src: 127.0.0.1, Dst: 127.0.0.1
Transmission Control Protocol, Src Port: 55934, Dst Port: 1194, Seq: 259, Ack: 1596, Len: 1198
OpenVPN Protocol
Transport Layer Security
    TLSv1.2 Record Layer: Handshake Protocol: Certificate
        Content Type: Handshake (22)
        Version: TLS 1.2 (0x0303)
        Length: 999
        Handshake Protocol: Certificate
            Handshake Type: Certificate (11)
            Length: 995
            Certificates Length: 992
            Certificates (992 bytes)
                Certificate Length: 989
                Certificate: 308203d9308202c1a003020102021414e338472bac1eece342df5fc60e83779784a25830… (pkcs-9-at-emailAddress=john.doe@example.com,id-at-commonName=Jon Doe,id-at-organizationName=Internet Widgits Pty Ltd,id-at-stateOrProvinceName=Some-S
                    signedCertificate
                        version: v3 (2)
                        serialNumber: 0x14e338472bac1eece342df5fc60e83779784a258
                        signature (sha256WithRSAEncryption)
                        issuer: rdnSequence (0)
                        validity
                        subject: rdnSequence (0)
                            rdnSequence: 5 items (pkcs-9-at-emailAddress=john.doe@example.com,id-at-commonName=Jon Doe,id-at-organizationName=Internet Widgits Pty Ltd,id-at-stateOrProvinceName=Some-State,id-at-countryName=AU)
                                RDNSequence item: 1 item (id-at-countryName=AU)
                                RDNSequence item: 1 item (id-at-stateOrProvinceName=Some-State)
                                RDNSequence item: 1 item (id-at-organizationName=Internet Widgits Pty Ltd)
                                RDNSequence item: 1 item (id-at-commonName=Jon Doe)
                                RDNSequence item: 1 item (pkcs-9-at-emailAddress=john.doe@example.com)
                        subjectPublicKeyInfo
                        extensions: 3 items
                    algorithmIdentifier (sha256WithRSAEncryption)
                    Padding: 0
                    encrypted: 342782ae2f94bf0123ade8d88117423e9bfeaefe69f9f93fc9f6a2104d4595f7bc47f6a3…
    TLSv1.2 Record Layer: Handshake Protocol: Client Key Exchange

This is especially problematic if the OpenVPN tunnel is intended to protect your privacy. You probably should not include personally identifiable information in the client certificate if you are using TLS v1.2 or below.

Note: replication

The capture was generated with the following commands:

openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout client.key -out client.crt
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout client.key -out client.crt
openssl dhparam -out dh2048.pem 2048
sudo openvpn --mode server --port 1194 --local 127.0.0.1 --dev tun0 \
    --proto tcp-server --tls-server --ca client.crt --cert server.crt \
    --dh dh2048.pem --key server.key \
    --tls-version-min 1.2 --tls-version-max 1.2 --verify-client-cert require
sudo tcpdump "tcp port 1194" -w openvpn-tls12.pcap -i lo
sudo openvpn --remote 127.0.0.1 1194 tcp-client --dev tun1 --client \
    --ca server.crt --cert client.crt --key client.key \
    --tls-version-min 1.2 --tls-version-max 1.2
wireshark openvpn-tls12.pcap

The OpenVPN version used was:

OpenVPN 2.4.7 x86_64-pc-linux-gnu [SSL (OpenSSL)] [LZO] [LZ4] [EPOLL] [PKCS11] [MH/PKTINFO] [AEAD] built on Apr 28 2021
library versions: OpenSSL 1.1.1d  10 Sep 2019, LZO 2.10

Note: client identity in TLS v1.3

TLS v1.3 fixes this defect: the Certificate messages are sent encrypted (after an ephemeral DH key exchange) and after the server has been authenticated.

However, a meddler-in-the-middle (MITM) could attempt to downgrade the handshake in TLS v1.2 mode in order to make the client disclose its identity: this is because TLS downgrade can only be detected in the Finished message after the Certificate messages are sent in TLS v1.2. In order to protect from this downgrade attack, we can either:

  • disable support for TLS v1.2 and below in the client;
  • disable support for TLS v1.2 and below in the server (assuming the client only supports (EC)DHE_* key exchanges).

Note: possible mitigations for OpenVPN

For OpenVPN, this can be mitigated by:

  • forcing the client to use TLS v1.3;
  • using opaque identifiers for client certificates (not perfect as this identifier will still be transmitted in cleartext);
  • using the tls-crypt or the tls-crypt-v2 option.

The tls-crypt and tls-crypt-v2 options are OpenVPN specific features introduced in OpenPVN 2.5:

--tls-crypt keyfile

Encrypt and authenticate all control channel packets with the key from keyfile. (See --tls-auth for more background.)

Encrypting (and authenticating) control channel packets:

  • provides more privacy by hiding the certificate used for the TLS connection,
  • makes it harder to identify OpenVPN traffic as such,
  • provides "poor-man's" post-quantum security, against attackers who will never know the pre-shared key (i.e. no forward secrecy).

[…]

All peers use the same --tls-crypt pre-shared group key to authenticate and encrypt control channel messages. […]

[…]

For large setups or setups where clients are not trusted, consider using --tls-crypt-v2 instead. That uses per-client unique keys, and thereby improves the bounds to 'rotate a client key at least once per 8000 years'.

Appendix, KCI TLS vulnerability

We have seen that authentication based on static DH is vulnerable to Key Compromise Impersonation (KCI). The KCI TLS vulnerability (CVE-2015-8960) is a practical vulnerability of this. If an attacker tricks the user into installing a client certificate (and associated private key) chosen by the attacker into his web browser, the attacker can impersonate any web site which uses a compatible certificate (in the same DH group) even if this web site does not support static DH (and client authentication) at all.

The attack may go as follows:

  1. the attacker generates a ECDH key pair and generates an associated (possibly self-signed) TLS client certificate;
  2. the attacker tricks the user info installing a PKCS#12 file containing the chosen client certificate and private key to the user,
  3. the user browser tries to connect to a website (eg. https://example.com) which has a certificate with a EC public key which can be used for ECDH (either no key usage extension or keyAgreement in the key usage extension);
  4. the attacker intercepts comunications and TLS handshake starts between the client and the attacker;
  5. the attacker negotiates the usage of a cipher suite with static ECDH server key (ECDH_* key exchange);
  6. the attacker sends the certificate for https://example.com (containing the server ECDSA signing key);
  7. the client interprets this EC public key as an ECDH private key (key usage confusion);
  8. the attacker requests client authentication;
  9. the browser proposes using the client certificate for client authentication;
  10. the user accepts;
  11. key material is derived from a static-static ECDH key exchange;
  12. the attacker has the client static ECDH private key (because he has generated it) and the server static ECDH public key and can compute the pre master secret, master secret and key material.
Key Compromise Impersonation on TLS

The following conditions must be met for this attack (see the KCI TLS slides):

Mitigations:

Note: TLS v1.3

TLS v1.3 removed support for static DH.

Appendix, DNS-rebinding for HTTPS using session resumption and RSA transport

An interesting vulnerability, uses session resumption for DNS-rebinding attacks against certain HTTPS services.

TLS-based services can be protected against DNS-rebinding by enforcing the value of the TLS-level server name (SNI extension). Application-layer server name (eg. the Host HTTP header) can be enforced as well for the same effect.

Even if neither TLS-level server name nor application-layer server name is enforced, DNS-rebinding attacks are normally prevented by TLS: this is because, when the client tries to establish a TLS connection to the target.example server instead of attacker.example, the client expects a certificate chain for attacker.example but receives a certificate chain for target.example it should reject the TLS connection.

However, the attacker can manage to avoid server certificates by using session resumption. In order for this to work, the attacker has to:

  1. associate the master secret established in the client-attacker TLS session with a session in the target;
  2. forward the target session ID to the client.

This can easily be done using the RSA transport key exchange algorithm as explained in the following diagram.

Sequence diagram for DNS-rebinding attack on HTTPS using session resumption and RSA transport

The consequence of this attack is that the attacker can issue arbitrary requests to the target website from the client and access the responses. This can be useful for several reasons:

Prerequisites:

Several workarounds have been implemented in different implementations. This should be fixed by using the extended master secret extension if it is implemented as specified:

If the original session did not use the "extended_master_secret" extension but the new ClientHello contains the extension, then the server MUST NOT perform the abbreviated handshake.

This is fixed in TLS v1.3.

Appendix, certificate validation

Without going into details, the validation of the certificate chain may include:

  1. verification of the validity of each certificate (notBefore, notAfter);
  2. verification of the signature of each certificate in the chain up to a trusted root CA;
  3. verification that the server host name matches with one of the names in the server certificate (SubjectAltName extension);
  4. processing of other certificate extensions such as:
  5. enforcing certificate pinning, which may
  6. enforcing DANE/TLSA contraints;
  7. enforcing the presence of Signed Certificate Timestamps (SCTs) (which may be in the signed_certificate_timestamp TLS extension, in the SignedCertificateTimestampList X.509/OCSP extension) and validating them (for certificate transparency).

References

RFCs:

Registries:

Misc:


  1. While making this post I realized that my nginx was still configured to only support TLS v1.2. ↩︎

  2. As TLS compression is dangerous (see the CRIME vulnerability), only the null compression (no compression) is now usually enabled. Compression at the TLS level is forbidden in HTTP/2 and has been removed in TLS v1.3. ↩︎

  3. In TLS v1.3, the key agreement method is not part of the cipher suite anymore. In my example, Firefox advertised as well support for the following TLS v1.3 cipher suites: TLS_AES_128_GCM_SHA256, TLS_CHACHA20_POLY1305_SHA256 and TLS_AES_256_GCM_SHA384. ↩︎

  4. Unless client authentication is done in a TLS renegotiation (i.e. a TLS handshake after the initial TLS handsake). However, TLS renegotiation can be considered to be deprecated. ↩︎

  5. When used with QUIC, this is the application protocol used over QUIC, not over TLS. ↩︎

  6. The server domain name is indicated in the subject alternative names (SAN) X.509 extension. ↩︎

  7. In contrast, in static ECDH (such as with TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256), the server always uses the same ECDH key pair (which is included in its certificate). The downside of this approach is that it does not provide forward secrecy with respect to the static ECDH key. ↩︎

  8. Export cipher suites were cipher suites defined for TLS 1.0 and below and were designed to be weak in order to be legally exported from the USA. 😅 ↩︎

  9. The TLS pseudo random function (PRF) is used as a MAC. This is safe because PRFs can be used as MACs. ↩︎

  10. In contrast to ECDSA which is a signing algorithm only, RSA can be used either for public-key signature and or for public-key encryption. When used in DHE_RSA or ECDHE_RSA mode, the server certificate (containing the RSA public key) must include keyEncipherment in the key usage extension. When used in RSA, the server certificate (containing the RSA public key), must include digitalSignature in the key usage extension. ↩︎

  11. Note that TLS-level encryption is not enabled when the NewSessionTicket message is sent. In particular, it is (obviously) very important that the master secret is not disclosed to eavesdroppers. ↩︎

  12. Client authentication is often done at the application protocol layer instead. ↩︎

  13. When using TLS v1.2, SHA-256 is used instead of weaker hash algorithms for the PRF. ↩︎

  14. However, QUIC only supports TLS v1.3 or above. ↩︎