Skip to content

Commit da78001

Browse files
committed
Initial implementation and tests.
1 parent abf91d7 commit da78001

File tree

4 files changed

+147
-9
lines changed

4 files changed

+147
-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

CONTRIBUTING.rst

+12
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,18 @@ First of all, thank you for your interest in contributing to pyOpenSSL!
55
This project has no company backing its development therefore we're dependent on help by the community.
66

77

8+
Development environment
9+
-----------------------
10+
11+
Create a ``virtualenv``, activate it and install the project in edit mode,
12+
and run the tests::
13+
14+
pip install -e .[test,docs]
15+
pytest tests/
16+
17+
18+
19+
820
Filing bug reports
921
------------------
1022

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)