Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork32.1k
Description
Documentation
SSLSocket.shared_ciphers()
does not documentNone
is returned on session reuse
Summary
The fix of#96931 resulted in a change inSSLSocket.shared_ciphers()
.
If the session is reused,SSLSocket.shared_ciphers()
returnsNone
Proposal: update documentation ofSSLSocket.shared_ciphers()
to noteNone
is returned on session reuse.
Background & Motivation
As an example, here is a sampleserver.py
andclient.py
scripts to show the behavior change in Python 3.11.2 and 3.11.3:
# server.pyimportsocketimportsslimportplatformcontext=ssl.create_default_context(ssl.Purpose.CLIENT_AUTH,cafile="ca.pem")context.load_cert_chain(certfile="server.pem")port=12345bindsocket=socket.socket()bindsocket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)bindsocket.bind(("localhost",port))bindsocket.listen(5)print("Python version: {}".format(platform.python_version()))print("server listening on port {}".format(port))whileTrue:newsocket,fromaddr=bindsocket.accept()connstream:ssl.SSLContext.sslsocket_class=context.wrap_socket(newsocket,server_side=True,do_handshake_on_connect=True)print("server got connection on address: {}".format(fromaddr))print("server shared ciphers: {}".format(connstream.shared_ciphers()))print("server session reused? {}".format(connstream.session_reused))data=connstream.recv(1024)whiledata:print("server got data {}".format(data))data=connstream.recv(1024)print("server finished with client")connstream.close()
# client.pyimportsocketimportsslport=12345"""Use TLS 1.2 so session ticket is sent.https://docs.python.org/3/library/ssl.html#ssl-session describes:> Session tickets are no longer sent as part of the initial handshake and are handled differently. SSLSocket.session and SSLSession are not compatible with TLS 1.3."""context=ssl.SSLContext(protocol=ssl.PROTOCOL_TLSv1_2)context.load_verify_locations(cafile="ca.pem")conn:ssl.SSLSocket=context.wrap_socket(socket.socket(socket.AF_INET),server_hostname="localhost")conn.connect(("localhost",port))conn.write(b"foo")assertnotconn.session_reusedsession=conn.sessionconn.close()# Connect again and reuse the session.conn=context.wrap_socket(socket.socket(socket.AF_INET),server_hostname="localhost",session=session)conn.connect(("localhost",port))conn.write(b"foo")assertconn.session_reusedconn.close()
Here is the output ofserver.py
on Python 3.11.2:
Python version: 3.11.2server listening on port 12345server got connection on address: ('127.0.0.1', 64310)server shared ciphers: [('TLS_AES_256_GCM_SHA384', 'TLSv1.3', 256), ('TLS_CHACHA20_POLY1305_SHA256', 'TLSv1.3', 256), ('TLS_AES_128_GCM_SHA256', 'TLSv1.3', 128), ('ECDHE-ECDSA-AES256-GCM-SHA384', 'TLSv1.2', 256), ('ECDHE-RSA-AES256-GCM-SHA384', 'TLSv1.2', 256), ('ECDHE-ECDSA-AES128-GCM-SHA256', 'TLSv1.2', 128), ('ECDHE-RSA-AES128-GCM-SHA256', 'TLSv1.2', 128), ('ECDHE-ECDSA-CHACHA20-POLY1305', 'TLSv1.2', 256), ('ECDHE-RSA-CHACHA20-POLY1305', 'TLSv1.2', 256), ('ECDHE-ECDSA-AES256-SHA384', 'TLSv1.2', 256), ('ECDHE-RSA-AES256-SHA384', 'TLSv1.2', 256), ('ECDHE-ECDSA-AES128-SHA256', 'TLSv1.2', 128), ('ECDHE-RSA-AES128-SHA256', 'TLSv1.2', 128), ('DHE-RSA-AES256-GCM-SHA384', 'TLSv1.2', 256), ('DHE-RSA-AES128-GCM-SHA256', 'TLSv1.2', 128), ('DHE-RSA-AES256-SHA256', 'TLSv1.2', 256), ('DHE-RSA-AES128-SHA256', 'TLSv1.2', 128)]server session reused? Falseserver got data b'foo'server finished with clientserver got connection on address: ('127.0.0.1', 64311)server shared ciphers: [('TLS_AES_256_GCM_SHA384', 'TLSv1.3', 256), ('TLS_CHACHA20_POLY1305_SHA256', 'TLSv1.3', 256), ('TLS_AES_128_GCM_SHA256', 'TLSv1.3', 128), ('ECDHE-ECDSA-AES256-GCM-SHA384', 'TLSv1.2', 256), ('ECDHE-RSA-AES256-GCM-SHA384', 'TLSv1.2', 256), ('ECDHE-ECDSA-AES128-GCM-SHA256', 'TLSv1.2', 128), ('ECDHE-RSA-AES128-GCM-SHA256', 'TLSv1.2', 128), ('ECDHE-ECDSA-CHACHA20-POLY1305', 'TLSv1.2', 256), ('ECDHE-RSA-CHACHA20-POLY1305', 'TLSv1.2', 256), ('ECDHE-ECDSA-AES256-SHA384', 'TLSv1.2', 256), ('ECDHE-RSA-AES256-SHA384', 'TLSv1.2', 256), ('ECDHE-ECDSA-AES128-SHA256', 'TLSv1.2', 128), ('ECDHE-RSA-AES128-SHA256', 'TLSv1.2', 128), ('DHE-RSA-AES256-GCM-SHA384', 'TLSv1.2', 256), ('DHE-RSA-AES128-GCM-SHA256', 'TLSv1.2', 128), ('DHE-RSA-AES256-SHA256', 'TLSv1.2', 256), ('DHE-RSA-AES128-SHA256', 'TLSv1.2', 128)]server session reused? Trueserver got data b'foo'server finished with client
Here is the output ofserver.py
on Python 3.11.3:
Python version: 3.11.3server listening on port 12345server got connection on address: ('127.0.0.1', 64316)server shared ciphers: [('ECDHE-ECDSA-AES256-GCM-SHA384', 'TLSv1.2', 256), ('ECDHE-RSA-AES256-GCM-SHA384', 'TLSv1.2', 256), ('ECDHE-ECDSA-AES128-GCM-SHA256', 'TLSv1.2', 128), ('ECDHE-RSA-AES128-GCM-SHA256', 'TLSv1.2', 128), ('ECDHE-ECDSA-CHACHA20-POLY1305', 'TLSv1.2', 256), ('ECDHE-RSA-CHACHA20-POLY1305', 'TLSv1.2', 256), ('ECDHE-ECDSA-AES256-SHA384', 'TLSv1.2', 256), ('ECDHE-RSA-AES256-SHA384', 'TLSv1.2', 256), ('ECDHE-ECDSA-AES128-SHA256', 'TLSv1.2', 128), ('ECDHE-RSA-AES128-SHA256', 'TLSv1.2', 128), ('DHE-RSA-AES256-GCM-SHA384', 'TLSv1.2', 256), ('DHE-RSA-AES128-GCM-SHA256', 'TLSv1.2', 128), ('DHE-RSA-AES256-SHA256', 'TLSv1.2', 256), ('DHE-RSA-AES128-SHA256', 'TLSv1.2', 128)]server session reused? Falseserver got data b'foo'server finished with clientserver got connection on address: ('127.0.0.1', 64317)server shared ciphers: Noneserver session reused? Trueserver got data b'foo'server finished with client
In 3.11.3, after the session is reused, the return ofshared_ciphers()
isNone
. The scripts and test certificates arelocated here.
Alternatives
openssl/openssl#4295 suggest alternative API to use in OpenSSL:
Ah, the shared ciphers would only be computed when negotiation is performed (i.e., not resumption), yes. Hopefully you can update to a supported version of OpenSSL and pick up the needed funcitonality.
An alternative implementation could be to useSSL_CTX_set_client_hello_cb
to obtain the list of ciphers sent in the ClientHello. Store the list of ciphers for retrieval inshared_ciphers()
.#110902 implements this change but is (at present) left as draft. Storing the ciphers requires additional memory per socket and may not provide much value to users. Instead,#106345 proposes a documentation only change to inform users.
Known Impact
TheNone
return resulted in a bug report in PyKMIP. The call toshared_ciphers
was not checking for theNone
return value:OpenKMIP/PyKMIP#700