Skip to content

Commit a8705dc

Browse files
committed
Initial implementation.
1 parent abf91d7 commit a8705dc

File tree

3 files changed

+135
-9
lines changed

3 files changed

+135
-9
lines changed

CHANGELOG.rst

+6
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ Changelog
44
Versions are year-based with a strict backward-compatibility policy.
55
The third digit is only for regressions.
66

7+
8+
- Added ``OpenSSL.SSL.Connection.session_reused()`` to query whether the
9+
current session was reused during the last handshake.
10+
[`#1275 <https://github.com/pyca/pyopenssl/issues/1275>`_]
11+
12+
713
23.3.0 (2023-10-25)
814
-------------------
915

src/OpenSSL/SSL.py

+19
Original file line numberDiff line numberDiff line change
@@ -2674,6 +2674,25 @@ def set_session(self, session):
26742674
result = _lib.SSL_set_session(self._ssl, session._session)
26752675
_openssl_assert(result == 1)
26762676

2677+
def session_reused(self):
2678+
"""
2679+
Query, whether a reused session was negotiated during the handshake.
2680+
2681+
During the negotiation, a client can propose to reuse a session.
2682+
The server then looks up the session in its cache.
2683+
If both client and server agree on the session,
2684+
it will be reused and a flag is being set that can be queried by the
2685+
application.
2686+
2687+
Retruns `0` when a new session was negotiated.
2688+
Returns `1` when a the session was reused.
2689+
2690+
:returns: int
2691+
2692+
.. versionadded:: NEXT
2693+
"""
2694+
return _lib.SSL_session_reused(self._ssl)
2695+
26772696
def _get_finished_message(self, function):
26782697
"""
26792698
Helper to implement :meth:`get_finished` and

tests/test_ssl.py

+110-9
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,9 @@ def socket_pair():
236236

237237

238238
def handshake(client, server):
239+
"""
240+
Wait until the TLS handshake is done on both client and server side.
241+
"""
239242
conns = [client, server]
240243
while conns:
241244
for conn in conns:
@@ -2755,43 +2758,141 @@ def test_set_session_wrong_args(self):
27552758
with pytest.raises(TypeError):
27562759
connection.set_session(object())
27572760

2758-
def test_client_set_session(self):
2761+
def test_session_reused(self):
2762+
"""
2763+
`Connection.session_reused`, returns 0 for new connections..
2764+
"""
2765+
ctx = Context(TLSv1_2_METHOD)
2766+
connection = Connection(ctx, None)
2767+
2768+
assert connection.session_reused() == 0
2769+
2770+
def test_client_set_session_tls1_2(self):
27592771
"""
27602772
`Connection.set_session`, when used prior to a connection being
27612773
established, accepts a `Session` instance and causes an attempt to
27622774
re-use the session it represents when the SSL handshake is performed.
2775+
2776+
`Connection.session_reused` is used to query the reuse status.
27632777
"""
27642778
key = load_privatekey(FILETYPE_PEM, server_key_pem)
27652779
cert = load_certificate(FILETYPE_PEM, server_cert_pem)
2766-
ctx = Context(TLSv1_2_METHOD)
2767-
ctx.use_privatekey(key)
2768-
ctx.use_certificate(cert)
2769-
ctx.set_session_id(b"unity-test")
2780+
server_ctx = Context(TLSv1_2_METHOD)
2781+
server_ctx.use_privatekey(key)
2782+
server_ctx.use_certificate(cert)
2783+
# !!!!
2784+
# I have no idea why it works when server-side cache is disabled.
2785+
# I guess that this might be because server and client are in the
2786+
# same process.
2787+
server_ctx.set_session_cache_mode(SSL.SESS_CACHE_OFF)
2788+
server_ctx.set_session_id(b"unity-test")
2789+
server_ctx.set_min_proto_version(TLS1_2_VERSION)
2790+
# Session is reused even when client cache is disabled.
2791+
client_ctx = Context(TLSv1_2_METHOD)
2792+
client_ctx.set_session_cache_mode(SSL.SESS_CACHE_OFF)
2793+
client_ctx.set_min_proto_version(TLS1_2_VERSION)
2794+
originalSession = None
27702795

27712796
def makeServer(socket):
2772-
server = Connection(ctx, socket)
2797+
server = Connection(server_ctx, socket)
27732798
server.set_accept_state()
27742799
return server
27752800

2776-
originalServer, originalClient = loopback(server_factory=makeServer)
2801+
def makeClient(socket):
2802+
client = Connection(client_ctx, socket)
2803+
client.set_connect_state()
2804+
if originalSession is not None:
2805+
client.set_session(originalSession)
2806+
return client
2807+
2808+
originalServer, originalClient = loopback(
2809+
server_factory=makeServer, client_factory=makeClient)
27772810
originalSession = originalClient.get_session()
27782811

2812+
assert originalServer.session_reused() == 0
2813+
assert originalClient.session_reused() == 0
2814+
2815+
resumedServer, resumedClient = loopback(
2816+
server_factory=makeServer, client_factory=makeClient
2817+
)
2818+
2819+
# The session on the original connections are not reused.
2820+
assert originalServer.session_reused() == 0
2821+
assert originalClient.session_reused() == 0
2822+
2823+
# The sessions on the new connections are reused.
2824+
assert resumedServer.session_reused() == 1
2825+
assert resumedClient.session_reused() == 1
2826+
2827+
# This is a proxy: in general, we have no access to any unique
2828+
# identifier for the session (new enough versions of OpenSSL expose
2829+
# a hash which could be usable, but "new enough" is very, very new).
2830+
# Instead, exploit the fact that the master key is re-used if the
2831+
# session is re-used. As long as the master key for the two
2832+
# connections is the same, the session was re-used!
2833+
assert originalServer.master_key() == resumedServer.master_key()
2834+
assert originalClient.master_key() == resumedClient.master_key()
2835+
2836+
def test_client_set_session_tls1_3(self):
2837+
"""
2838+
Test run for `Connection.set_session` and `Connection.session_reused`
2839+
when TLS 1.3 is used.
2840+
"""
2841+
key = load_privatekey(FILETYPE_PEM, server_key_pem)
2842+
cert = load_certificate(FILETYPE_PEM, server_cert_pem)
2843+
server_ctx = Context(TLS_METHOD)
2844+
server_ctx.use_privatekey(key)
2845+
server_ctx.use_certificate(cert)
2846+
2847+
# Session is reused even when server cache is disabled.
2848+
server_ctx.set_session_cache_mode(SESS_CACHE_SERVER)
2849+
server_ctx.set_session_id(b"unity-test")
2850+
server_ctx.set_min_proto_version(TLS1_3_VERSION)
2851+
server_ctx.set_options(OP_NO_TICKET)
2852+
2853+
client_ctx = Context(TLS_METHOD)
2854+
client_ctx.set_options(OP_NO_TICKET)
2855+
originalSession = None
2856+
2857+
def makeServer(socket):
2858+
server = Connection(server_ctx, socket)
2859+
server.set_accept_state()
2860+
return server
2861+
27792862
def makeClient(socket):
2780-
client = loopback_client_factory(socket)
2781-
client.set_session(originalSession)
2863+
client = Connection(client_ctx, socket)
2864+
client.set_connect_state()
2865+
if originalSession is not None:
2866+
client.set_session(originalSession)
27822867
return client
27832868

2869+
originalServer, originalClient = loopback(
2870+
server_factory=makeServer, client_factory=makeClient)
2871+
originalSession = originalClient.get_session()
2872+
2873+
assert originalServer.session_reused() == 0
2874+
assert originalClient.session_reused() == 0
2875+
27842876
resumedServer, resumedClient = loopback(
27852877
server_factory=makeServer, client_factory=makeClient
27862878
)
27872879

2880+
# The session on the original connections are not reused.
2881+
assert originalServer.session_reused() == 0
2882+
assert originalClient.session_reused() == 0
2883+
2884+
# The sessions on the new connections are reused.
2885+
assert resumedServer.session_reused() == 1
2886+
assert resumedClient.session_reused() == 1
2887+
27882888
# This is a proxy: in general, we have no access to any unique
27892889
# identifier for the session (new enough versions of OpenSSL expose
27902890
# a hash which could be usable, but "new enough" is very, very new).
27912891
# Instead, exploit the fact that the master key is re-used if the
27922892
# session is re-used. As long as the master key for the two
27932893
# connections is the same, the session was re-used!
27942894
assert originalServer.master_key() == resumedServer.master_key()
2895+
assert originalClient.master_key() == resumedClient.master_key()
27952896

27962897
def test_set_session_wrong_method(self):
27972898
"""

0 commit comments

Comments
 (0)