pyca / pyopenssl

A Python wrapper around the OpenSSL library
https://pyopenssl.org/
Apache License 2.0
890 stars 419 forks source link

Issue with TLS 1.3 Session Resumption in PyOpenSSL #1380

Open Smuul opened 1 week ago

Smuul commented 1 week ago

Description:

I am encountering an issue when attempting to implement session resumption in TLS 1.3 using PyOpenSSL. I have already tried the implementation suggested in #1291.

Scenario:

Client

self.context = SSL.Context(config.secure_version)
self.context.set_session_cache_mode(SSL.SESS_CACHE_CLIENT)
if session is not None:
             self.conn.set_session(session)

try:
                self.conn.set_connect_state()
                self.conn.do_handshake()
                while self._running:
                    data = self.conn.recv(1)
                    if data:
                            self.session = self.conn.get_session()  
                            break         

Server

self.context = SSL.Context(config.secure_version)
self.context.set_session_cache_mode(SSL.SESS_CACHE_SERVER)
self.context.set_session_id(b'12345678123456781234567812345678')

Observations:

The session resumption works correctly with TLS 1.2: image

However, the same implementation fails for TLS 1.3. It always creates a new Session Ticket during the second connection attempt: image

Questions:

What am I doing wrong in my implementation? Is there anything I might be missing to achieve proper session resumption for TLS 1.3?

Any guidance would be greatly appreciated!

dennisn00 commented 6 days ago

In TLS1.3, a server may send a NewSessionTicket even on a resumed connection (for example to extend the lifetime of the original session ticket). I am not sure whether pyOpenSSL is doing that on default, but the existence of a NewSessionTicket message itself should not be proof that the session was not resumed. You could test manually whether certificates are sent by using set_verify() on the client side:

def test_verification(a, b, c, d, e):
    print("Certificate is verified")
    return True

self.context.set_verify(SSL.VERIFY_PEER, test_verification)

This should only print on the complete handshake (it will print multiple times though) and not during the resuming handshake.

Also just looking at the message sizes in your screenshot, it seems like the second ClientHello is a lot bigger, which might be the PSK. You can check in Wireshark whether the second ClientHello includes the pre_shared_key extension

Smuul commented 3 days ago

I finally managed to get the second ClientHello to use the pre_shared_key extension, and the value of the new session ticket issued during the initial handshake. Here’s how I addressed the problem:

Added Sleep Delays: I added some sleep intervals in my code where the client and server instances are created. It seems that allowing a little delay between the different stages helped in stabilizing the session ticket process. Without these sleeps, it appears that the client wasn't ready to properly use the received ticket for resumption.

In addition, i don't use Certificates in my implementation, as i want to use a PSK cipher. Thanks a lot for your help!