summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeremy Lainé <jeremy.laine@m4x.org>2023-02-13 13:45:33 +0100
committerGitHub <noreply@github.com>2023-02-13 13:45:33 +0100
commit3517764ed5517a6d76078eb7e6696f1c0e153ad1 (patch)
treeb42f36a3110569c607be1373cc2b89023c19e272
parent669969ea7afa8a5f5f35a784a50acef9bea3952a (diff)
downloadpyopenssl-3517764ed5517a6d76078eb7e6696f1c0e153ad1.tar.gz
Add support for DTLS timeouts (#1180)
Add support for DTLS timeouts When performing a DTLS handshake, the DTLS state machine may need to be updated based on the passage of time, for instance in response to packet loss. OpenSSL supports this by means of the `DTLSv1_get_timeout` and `DTLSv1_handle_timeout` methods, both of which are included in cryptography's bindings. This change adds Python wrappers for these methods in the `Connection` class.
-rw-r--r--CHANGELOG.rst3
-rw-r--r--src/OpenSSL/SSL.py31
-rw-r--r--tests/test_ssl.py46
3 files changed, 76 insertions, 4 deletions
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 67a6ae3..7842545 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -16,6 +16,9 @@ Deprecations:
Changes:
^^^^^^^^
+- Add ``OpenSSL.SSL.Connection.DTLSv1_get_timeout`` and ``OpenSSL.SSL.Connection.DTLSv1_handle_timeout``
+ to support DTLS timeouts `#1180 <https://github.com/pyca/pyopenssl/pull/1180>`_.
+
23.0.0 (2023-01-01)
-------------------
diff --git a/src/OpenSSL/SSL.py b/src/OpenSSL/SSL.py
index c1fb0f5..efbf790 100644
--- a/src/OpenSSL/SSL.py
+++ b/src/OpenSSL/SSL.py
@@ -2159,6 +2159,37 @@ class Connection:
if result < 0:
self._raise_ssl_error(self._ssl, result)
+ def DTLSv1_get_timeout(self):
+ """
+ Determine when the DTLS SSL object next needs to perform internal
+ processing due to the passage of time.
+
+ When the returned number of seconds have passed, the
+ :meth:`DTLSv1_handle_timeout` method needs to be called.
+
+ :return: The time left in seconds before the next timeout or `None`
+ if no timeout is currently active.
+ """
+ ptv_sec = _ffi.new("time_t *")
+ ptv_usec = _ffi.new("long *")
+ if _lib.Cryptography_DTLSv1_get_timeout(self._ssl, ptv_sec, ptv_usec):
+ return ptv_sec[0] + (ptv_usec[0] / 1000000)
+ else:
+ return None
+
+ def DTLSv1_handle_timeout(self):
+ """
+ Handles any timeout events which have become pending on a DTLS SSL
+ object.
+
+ :return: `True` if there was a pending timeout, `False` otherwise.
+ """
+ result = _lib.DTLSv1_handle_timeout(self._ssl)
+ if result < 0:
+ self._raise_ssl_error(self._ssl, result)
+ else:
+ return bool(result)
+
def bio_shutdown(self):
"""
If the Connection was created with a memory BIO, this method can be
diff --git a/tests/test_ssl.py b/tests/test_ssl.py
index bfc5ca8..e6c0cdc 100644
--- a/tests/test_ssl.py
+++ b/tests/test_ssl.py
@@ -9,6 +9,7 @@ import datetime
import gc
import select
import sys
+import time
import uuid
from errno import (
EAFNOSUPPORT,
@@ -4369,10 +4370,11 @@ class TestDTLS:
# new versions of OpenSSL, this is unnecessary, but harmless, because the
# DTLS state machine treats it like a network hiccup that duplicated a
# packet, which DTLS is robust against.
- def test_it_works_at_all(self):
- # arbitrary number larger than any conceivable handshake volley
- LARGE_BUFFER = 65536
+ # Arbitrary number larger than any conceivable handshake volley.
+ LARGE_BUFFER = 65536
+
+ def test_it_works_at_all(self):
s_ctx = Context(DTLS_METHOD)
def generate_cookie(ssl):
@@ -4403,7 +4405,7 @@ class TestDTLS:
def pump_membio(label, source, sink):
try:
- chunk = source.bio_read(LARGE_BUFFER)
+ chunk = source.bio_read(self.LARGE_BUFFER)
except WantReadError:
return False
# I'm not sure this check is needed, but I'm not sure it's *not*
@@ -4483,3 +4485,39 @@ class TestDTLS:
assert 0 < c.get_cleartext_mtu() < 500
except NotImplementedError: # OpenSSL 1.1.0 and earlier
pass
+
+ def test_timeout(self, monkeypatch):
+ c_ctx = Context(DTLS_METHOD)
+ c = Connection(c_ctx)
+
+ # No timeout before the handshake starts.
+ assert c.DTLSv1_get_timeout() is None
+ assert c.DTLSv1_handle_timeout() is False
+
+ # Start handshake and check there is data to send.
+ c.set_connect_state()
+ try:
+ c.do_handshake()
+ except SSL.WantReadError:
+ pass
+ assert c.bio_read(self.LARGE_BUFFER)
+
+ # There should now be an active timeout.
+ seconds = c.DTLSv1_get_timeout()
+ assert seconds is not None
+
+ # Handle the timeout and check there is data to send.
+ time.sleep(seconds)
+ assert c.DTLSv1_handle_timeout() is True
+ assert c.bio_read(self.LARGE_BUFFER)
+
+ # After the maximum number of allowed timeouts is reached,
+ # DTLSv1_handle_timeout will return -1.
+ #
+ # Testing this directly is prohibitively time consuming as the timeout
+ # duration is doubled on each retry, so the best we can do is to mock
+ # this condition.
+ monkeypatch.setattr(_lib, "DTLSv1_handle_timeout", lambda x: -1)
+
+ with pytest.raises(Error):
+ c.DTLSv1_handle_timeout()