Skip to content

Completing the TLS 1.3 Handshake

Overview

In the first stage of the TLS 1.3 handshake, the client and server negotiate encryption parameters and exchange asymmetric key material by sending an unecrypted Hello message. After completing this exchange, each participant computes symmetric traffic encryption keys that are used when sending and receiving the remaining messages in the handshake. We'll move forward in this section with the goal of completing the handshake and computing a second set of symmetric traffic encryption keys to protect application layer data.

Additional Handshake Messages

After the server sends its Hello message and computes the handshake traffic encryption keys, it is required to send several additional handshake messages, including the following: Encrypted Extensions (8), Certificate (11), Certificate Verify (15), and Finished (20). Prior to sending the Finished message, a TLS 1.3 server may also send additional handshake messages, such as a Certificate Request. These handshake messages enable the server to send additional options through the TLS extension mechanism, authenticate its identity using a PKI-based certificate mechanism, request authentication of the client, and prove integrity of the handshake and its own traffic key computations.

After receiving and processing the remaining handshake messages from the server, the TLS client responds to additional requests such as a Certificate Request and updates the handshake message transcript. Next, the client computes a hash-based message authentication code (HMAC) over the message transcript. This value, referred to as the client's Verify Data is encapsulated in the handshake protocol's Finished message and sent to the server. The client's Finished message signals the end of the handshake to the server and enables it to cryptographically verify the integrity of the handshake and confirm that the client holds the correct encryption secrets.

Backwards Compatibility

Along with its remaining handshake messages, the server might also send a Change Cipher Spec. This message was used in previous TLS versions to signal a cipher configuration change, such as the transition from sending records in plaintext to sending them in ciphertext. This record type is only included in the TLS 1.3 for compatibility with middleboxes such as proxy servers, load balancers, and web application firewalls. TLS 1.3 clients must be able to receive a Change Cipher Spec message to ensure compatibility, but they must not take any further action after reading and removing it from the underlying data s

Implementation Tasks

Sticking to our goal of writing the least amount of functionality needed to send and receive encrypted application traffic, we can proceed without needing to fully parse the handshake messages mentioned above. However, since these messages are part of the handshake, their plaintext content does need to be included in the handshake transcript so that client can compute the required HMAC and application traffic keys.

Even in our toy implementation, we are likely to encounter a Change Cipher Spec message. This message is passed in an unencrypted record type that can be identified by the record header's type value (20). Since it is used only for compatibility, the Change Cipher Spec should be skipped without further processing. Further, the message is not part of the handshake protocol, and it is omitted from the handshake transcript.

The additional funcitionality required to complete this task is summarized here:

  1. Compute handshake traffic encryption keys using the get_handshake_cipher() method of TLSContext. This step is described within Generating TLS 1.3 Handshake Keys.
  2. Continue processing each TLS record sent by the server until you have seen its Finished message (handshake type 20). Encrypted records should be handled according to the process described in Working with Encrypted TLS Records.
    1. You will need to detect Change Cipher Spec messages (record type 20), but you should not do any further processing after removing the messages from the incoming socket buffer.
    2. Decrypt each Application Data message (record type 23), and determine the underlying message type by examining the 1-byte record footer that was appended to the original message data.
    3. Add the decrypted message body from each Handshake message (record type 22) to the handshake transcript using the update_transcript() method of TLSContext.
  3. Use the compute_verify_data() of TLSContext to compute the client-generated Verify Data based on the current handshake transcript. This method is called without arguments and returns a Python bytes object.
  4. Construct a handshake Finished message using the the computed Verify Data, and return it to the server as an encrypted record. This message should not be included in the handshake transcript.
  5. Finally, use the get_application_cipher() method of TLSContext to generate traffic keys for application data.
Back to top