-
Notifications
You must be signed in to change notification settings - Fork 420
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
#1275 Add OpenSSL.SSL.Connection.session_reused API. #1276
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2674,6 +2674,25 @@ def set_session(self, session): | |
result = _lib.SSL_set_session(self._ssl, session._session) | ||
_openssl_assert(result == 1) | ||
|
||
def session_reused(self): | ||
""" | ||
Query, whether a reused session was negotiated during the handshake. | ||
|
||
During the negotiation, a client can propose to reuse a session. | ||
The server then looks up the session in its cache. | ||
If both client and server agree on the session, | ||
it will be reused and a flag is being set that can be queried by the | ||
application. | ||
|
||
Retruns `0` when a new session was negotiated. | ||
Returns `1` when a the session was reused. | ||
|
||
:returns: int | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I went with upstream API, but maybe is best to return a |
||
|
||
.. versionadded:: NEXT | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure in which version it will be released. |
||
""" | ||
return _lib.SSL_session_reused(self._ssl) | ||
|
||
def _get_finished_message(self, function): | ||
""" | ||
Helper to implement :meth:`get_finished` and | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -236,6 +236,9 @@ def socket_pair(): | |
|
||
|
||
def handshake(client, server): | ||
""" | ||
Wait until the TLS handshake is done on both client and server side. | ||
""" | ||
conns = [client, server] | ||
while conns: | ||
for conn in conns: | ||
|
@@ -2755,43 +2758,143 @@ def test_set_session_wrong_args(self): | |
with pytest.raises(TypeError): | ||
connection.set_session(object()) | ||
|
||
def test_client_set_session(self): | ||
def test_session_reused(self): | ||
""" | ||
`Connection.session_reused`, returns 0 for new connections.. | ||
""" | ||
ctx = Context(TLSv1_2_METHOD) | ||
connection = Connection(ctx, None) | ||
|
||
assert connection.session_reused() == 0 | ||
|
||
def test_client_set_session_tls1_2(self): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I went with both TLS 1.2 and 1.3 tests since the session handling is a big different. I will refactor the tests to share more code. |
||
""" | ||
`Connection.set_session`, when used prior to a connection being | ||
established, accepts a `Session` instance and causes an attempt to | ||
re-use the session it represents when the SSL handshake is performed. | ||
|
||
`Connection.session_reused` is used to query the reuse status. | ||
""" | ||
key = load_privatekey(FILETYPE_PEM, server_key_pem) | ||
cert = load_certificate(FILETYPE_PEM, server_cert_pem) | ||
ctx = Context(TLSv1_2_METHOD) | ||
ctx.use_privatekey(key) | ||
ctx.use_certificate(cert) | ||
ctx.set_session_id(b"unity-test") | ||
server_ctx = Context(TLSv1_2_METHOD) | ||
server_ctx.use_privatekey(key) | ||
server_ctx.use_certificate(cert) | ||
# !!!! | ||
# I have no idea why it works when server-side cache is disabled. | ||
# I guess that this might be because server and client are in the | ||
# same process. | ||
server_ctx.set_session_cache_mode(SSL.SESS_CACHE_OFF) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I went with explicit context for server and client. Somehow for this test for TLS 1.2, it works even when cache is off. For my end to end test, in which I use 2 separate processes, the server cache needs to be enabled for session reuse. |
||
server_ctx.set_session_id(b"unity-test") | ||
server_ctx.set_min_proto_version(TLS1_2_VERSION) | ||
# Session is reused even when client cache is disabled. | ||
client_ctx = Context(TLSv1_2_METHOD) | ||
client_ctx.set_session_cache_mode(SSL.SESS_CACHE_OFF) | ||
client_ctx.set_min_proto_version(TLS1_2_VERSION) | ||
originalSession = None | ||
|
||
def makeServer(socket): | ||
server = Connection(ctx, socket) | ||
server = Connection(server_ctx, socket) | ||
server.set_accept_state() | ||
return server | ||
|
||
originalServer, originalClient = loopback(server_factory=makeServer) | ||
def makeClient(socket): | ||
client = Connection(client_ctx, socket) | ||
client.set_connect_state() | ||
if originalSession is not None: | ||
client.set_session(originalSession) | ||
return client | ||
|
||
originalServer, originalClient = loopback( | ||
server_factory=makeServer, client_factory=makeClient | ||
) | ||
originalSession = originalClient.get_session() | ||
|
||
assert originalServer.session_reused() == 0 | ||
assert originalClient.session_reused() == 0 | ||
|
||
resumedServer, resumedClient = loopback( | ||
server_factory=makeServer, client_factory=makeClient | ||
) | ||
|
||
# The session on the original connections are not reused. | ||
assert originalServer.session_reused() == 0 | ||
assert originalClient.session_reused() == 0 | ||
|
||
# The sessions on the new connections are reused. | ||
assert resumedServer.session_reused() == 1 | ||
assert resumedClient.session_reused() == 1 | ||
|
||
# This is a proxy: in general, we have no access to any unique | ||
# identifier for the session (new enough versions of OpenSSL expose | ||
# a hash which could be usable, but "new enough" is very, very new). | ||
# Instead, exploit the fact that the master key is re-used if the | ||
# session is re-used. As long as the master key for the two | ||
# connections is the same, the session was re-used! | ||
assert originalServer.master_key() == resumedServer.master_key() | ||
assert originalClient.master_key() == resumedClient.master_key() | ||
|
||
def test_client_set_session_tls1_3(self): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have no idea why tls 1.3 fails in this test. I have it working in a separate manual proof of concept code, in which I have the server with pyOpenSSL and the client is curl. |
||
""" | ||
Test run for `Connection.set_session` and `Connection.session_reused` | ||
when TLS 1.3 is used. | ||
""" | ||
key = load_privatekey(FILETYPE_PEM, server_key_pem) | ||
cert = load_certificate(FILETYPE_PEM, server_cert_pem) | ||
server_ctx = Context(TLS_METHOD) | ||
server_ctx.use_privatekey(key) | ||
server_ctx.use_certificate(cert) | ||
|
||
# Session is reused even when server cache is disabled. | ||
server_ctx.set_session_cache_mode(SESS_CACHE_SERVER) | ||
server_ctx.set_session_id(b"unity-test") | ||
server_ctx.set_min_proto_version(TLS1_3_VERSION) | ||
server_ctx.set_options(OP_NO_TICKET) | ||
|
||
client_ctx = Context(TLS_METHOD) | ||
client_ctx.set_options(OP_NO_TICKET) | ||
originalSession = None | ||
|
||
def makeServer(socket): | ||
server = Connection(server_ctx, socket) | ||
server.set_accept_state() | ||
return server | ||
|
||
def makeClient(socket): | ||
client = loopback_client_factory(socket) | ||
client.set_session(originalSession) | ||
client = Connection(client_ctx, socket) | ||
client.set_connect_state() | ||
if originalSession is not None: | ||
client.set_session(originalSession) | ||
return client | ||
|
||
originalServer, originalClient = loopback( | ||
server_factory=makeServer, client_factory=makeClient | ||
) | ||
originalSession = originalClient.get_session() | ||
|
||
assert originalServer.session_reused() == 0 | ||
assert originalClient.session_reused() == 0 | ||
|
||
resumedServer, resumedClient = loopback( | ||
server_factory=makeServer, client_factory=makeClient | ||
) | ||
|
||
# The session on the original connections are not reused. | ||
assert originalServer.session_reused() == 0 | ||
assert originalClient.session_reused() == 0 | ||
|
||
# The sessions on the new connections are reused. | ||
assert resumedServer.session_reused() == 1 | ||
assert resumedClient.session_reused() == 1 | ||
|
||
# This is a proxy: in general, we have no access to any unique | ||
# identifier for the session (new enough versions of OpenSSL expose | ||
# a hash which could be usable, but "new enough" is very, very new). | ||
# Instead, exploit the fact that the master key is re-used if the | ||
# session is re-used. As long as the master key for the two | ||
# connections is the same, the session was re-used! | ||
assert originalServer.master_key() == resumedServer.master_key() | ||
assert originalClient.master_key() == resumedClient.master_key() | ||
|
||
def test_set_session_wrong_method(self): | ||
""" | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let me know if you want a link to a PR.
I have created the changelong before creating the PR... so at that time, I didn't had a PR ID.