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 provisioned 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:
- The handshake starts with an ephemeral Diffie-Hellman (DHE) key exchange (as part of the
ClientHello
andServerHello
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.). - The server authenticates itself using a digital signature of the TLS handshake.
- The client optionally authenticates itself using a digital signature of the TLS handshake.
- The client and the server can now exchange protected application data.
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.
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:
- an ephemeral Diffie-Hellman key exchange;
- random nonces exchange (
random
fields); - features negotiation (TLS version, cipher suite, signature algorithms, application protocol, etc.);
- other informations (requested server name, etc.).
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
andServerHello.legacy_version
) but is now negotiated in thesupported_versions
extension. In TLS v1.3, thelegacy_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 inlegacy_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).
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:
ClientHello.cipher_suites
lists the cipher suites supported by the client;ServerHello.cipher_suite
indicates which cipheruite is chosen by the server.
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:
- an AEAD method (authenticated encryption with additional data);
- a secure hash algorithm.
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:
- EdDSA (Ed25519, Ed448), which has several advantages compared to ECDSA;
- RSASSA-PSS (RSA Signature Scheme with Appendix - Probabilistic Signature Scheme).
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 arsaEncryption
(RSAE) key in the certificate;rsa_pss_pss_*
is for RSA-PSS signatures with aRSASSA-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.
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:
signature_algorithms
(required), signature schemes accepted for the authentication (CertificateVerify
);signature_algorithms_cert
(optional), signature schemes accepted for certificate signatures (if different);certificate_authorities
(optional), certificate authorities accepted by the server.
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:
- a
Certificate
message, with the server certificate chain; - a
CertificateVerify
message, containing a signature of the server (signing) private key; - 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:
status_request
for OCSP status;signed_certificate_timestamp
for Certificate Transparency.
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.
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:
- The PSK-with-DHE handshake (
psk_dhe_ke
) includes an ephemeral Diffie-Hellman key exchange as well in order to provide forward secrecy (with respect to the PSK). - In contrast, the
psk_ke
handshake does not provide forward secrecy with respect to the PSK.
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 RFC 9258 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 theTLS_AES_128_GCM_SHA256
ciphersuite because this ciphersuite uses theHKDF_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 different KDFs;
- for different client/server combinations using
ImportedIdentity.context
(as explained below).
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 server accepts early data, it sends an
early_data
extension in theEncryptedExtensions
message. In this case, the client can send early data until it receives the serverFinished
message: when this happens, it must send anEndOfEarlyData
message marking the end of the early data. - If the server does not accept early data, the early data is ignored by the server and the early data must be repeated by the client after the handshake.
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
:
early_data
(EarlyDataIndication
), indicates that 0-RTT data may be accepted by the server.
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 decrypt 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:
- to only do client authentication when and if it is really needed (for example when the client tries to make restricted request);
- in order for the client to re-prove its ownership of the private key at some point.
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.
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:
- the
verify_data
of theFinished
messages; - the
signature
fields of theCertificateVerify
messages.
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:
- for key derivation (using HKDF);
- for computing several MACs (using HMAC);
- to hash the current TLS handshake (i.e. the transcript hash).
Appendix, key hierarchy
The key hierachy is made of three layers:
- Early Secret (ES);
- Handshake Secret (HS);
- 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:
binder_key
, used to compute an HMAC to prove knowledge of the PSK to the server;client_early_traffic_secret
, used to derive key material to encrypt the client early (0-RTT) traffic;early_exporter_master_secret
, see key exporters.
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:
client_handshake_traffic_secret
, used to derive key material to encrypt client-initiated handshake messages (after theClientHello
up to the clientFinished
);server_handshake_traffic_secret
, used to derive key material to encrypt server-initiated handshake messages (after theServerHello
up to the serverFinished
).
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
):
client_application_traffic_secret_0
, used to derive key material to encrypt for client post-handshake messages;server_application_traffic_secret_0
, used to derive key material to encrypt for server post-handshake messages;exporter_master_secret
, see key exporters;resumption_master_secret
, use to derive internal PSKs (for session resumption).
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:
- a (AEAD) encryption key;
- a initialisation vector (IV).
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:
- the rest of the TLS handshake can be encrypted (whereas the handshake was for the most part in cleartext in TLS v1.2);
- the server can start sending application data without an additional round trip;
- many extensions are now sent in subsequence messages and are encrypted (they were in the
ClientHello
/ServerHello
messages in TLS v1.2) - when client authentication is used, the client certificate (which contains the client identity) is sent encrypted (it was typically sent in cleartext in TLS v1.2) and after the server authentication (which means an active attacker cannot obtain it);
- the client may send application data after a single round trip (1RTT) whereas two round-trips were needed in TLS v1.2.
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:
- RSA transport and static Diffie-Hellman key exchange (which did not provide forward secrecy with respect to the static private key);
- anonymous DH key exchange (which did not authenticate the server).
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:
- RFC 8446, TLS v1.3
- RFC 8448, Example Handshake Traces for TLS 1.3
- RFC 8773, TLS 1.3 Extension for Certificate-Based Authentication with an External PSK
- RFC 5869, HMAC-based Extract-and-Expand Key Derivation Function (HKDF)
- RFC 9257, Guidance for External Pre-Shared Key (PSK) Usage in TLS
- RFC 9258, Importing External Pre-Shared Keys (PSKs) for TLS 1.3
- RFC 8998, ShangMi (SM) Cipher Suites for TLS 1.3
- RFC 9367, GOST Cipher Suites for TLS 1.3
- RFC 9150, TLS 1.3 Authentication and Integrity-Only Cipher Suites
- RFC 7525, Recommendations for Secure Use of TLS and DTLS
- RFC 9325, Recommendations for Secure Use of TLS and DTLS
- RFC 8879, TLS Certificate Compression
Registries:
Papers:
- A Cryptographic Analysis of the TLS 1.3 Handshake Protocol Candidates
- A Cryptographic Analysis of the TLS 1.3 Handshake Protocol
- The OPTLS Protocol and TLS 1.3
- Cryptographic Extraction and Key Derivation: The HKDF Scheme
- Selfie: reflections on TLS 1.3 with PSK
- Continuing to reflect on TLS 1.3 with external PSK
- On the Security of TLS 1.3 and QUIC Against Weaknesses in PKCS#1 v1.5 Encryption
TLS 1.3:
- Repository of the TLS v1.3 specification
- Overview of TLS v1.3
- TLS/DTLS 1.3 Profiles for the Internet of Things (draft)
- See this page fetch itself, byte by byte, over TLS
- Implementing a toy version of TLS 1.3
- The future of session resumption ~ Forward secure PSK key agreement in TLS 1.3
- TLS v1.3 on the OpenSSL Wiki
- TLS 1.3 Status ~ BearSSL
Encrypted Client Hello:
Forward secrecy:
- Botching Forward Secrecy ~ The sad state of server-side TLS Session Resumption implementations
- How to botch TLS forward secrecy
TLS interception:
- The Security Impact of HTTPS Interception
- ETS Isn't TLS and You Shouldn't Use It, by the Electronic Fontier Foundation
- TLS clients should reject static Diffie-Hellman
- Data Center use of Static Diffie-Hellman in TLS 1.3 (draft)
- ETSI TS 103 523-3, Enterprise Transport Security
- NIST Special Publication 1800-37A : Addressing Visibility Challenges with TLS 1.3 (preliminary draft)
- TLS 1.3 Option for Negotiation of Visibility in the Datacenter
- Responsibly Intercepting TLS and the Impact of TLS 1.3
Other:
The two mechanisms available in TLS v1.2 for session resumption are not present in TLS v1.3. ↩︎
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). ↩︎
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 handshakeThis 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 provisioned 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 man-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 RFC 9325 for other security issues associated with reusing a DH key pair. ↩︎
RFC 8998 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 🤔.
RFC 9367 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 🤔.
RFC 9150 defines two ciphersuites (
TLS_SHA256_SHA256
andTLS_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. ↩︎