From ffd86dea5e9a03396151c9c6e61bcdc3d12c1871 Mon Sep 17 00:00:00 2001 From: Jeremiah Mackey Date: Thu, 2 Apr 2026 15:40:04 +0000 Subject: [PATCH 01/11] Add tests for recent fixes --- tests/test_context.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/test_context.py b/tests/test_context.py index 9609d14..0aa804b 100644 --- a/tests/test_context.py +++ b/tests/test_context.py @@ -74,3 +74,17 @@ def test_load_verify_locations_with_cafile(ssl_context): def test_load_verify_locations_with_cadata(ssl_context): ssl_context.load_verify_locations(cadata=_CADATA) + + +def test_check_hostname_requires_cert_required(ssl_provider, ssl_context): + with pytest.raises(ValueError): + ssl_context.check_hostname = True + + ssl_context.verify_mode = ssl_provider.CERT_REQUIRED + ssl_context.check_hostname = True + assert ssl_context.check_hostname is True + + +def test_wrap_socket_server_side_mismatch(ssl_context, tcp_socket): + with pytest.raises(ValueError): + ssl_context.wrap_socket(tcp_socket, server_side=True) From dc49a5391f97f263ed49c1f08167f15bc590621c Mon Sep 17 00:00:00 2001 From: Jeremiah Mackey Date: Thu, 2 Apr 2026 15:44:07 +0000 Subject: [PATCH 02/11] Default DTLS version in client example --- examples/client.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/client.py b/examples/client.py index f6f1406..da4fac3 100755 --- a/examples/client.py +++ b/examples/client.py @@ -130,6 +130,8 @@ def main(): # DTLS connection over UDP if args.u: + if args.v > 2: + args.v = 1 bind_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0) context = wolfssl.SSLContext(get_DTLSmethod(args.v)) # SSL/TLS connection over TCP From a406dce6c37312ccbc5c05c2746fb8a82f960730 Mon Sep 17 00:00:00 2001 From: Jeremiah Mackey Date: Thu, 2 Apr 2026 15:47:36 +0000 Subject: [PATCH 03/11] Add DTLS handshake to recv_into --- wolfssl/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/wolfssl/__init__.py b/wolfssl/__init__.py index 7678fb5..2da54a5 100644 --- a/wolfssl/__init__.py +++ b/wolfssl/__init__.py @@ -676,6 +676,8 @@ def recv_into(self, buffer, nbytes=None, flags=0): self._check_closed("read") if self._context.protocol < PROTOCOL_DTLSv1: self._check_connected() + else: + self.do_handshake() if buffer is None: raise ValueError("buffer cannot be None") From 84bd6375a2ceccfd6f810b097f9d6d9bc7185fd2 Mon Sep 17 00:00:00 2001 From: Jeremiah Mackey Date: Thu, 2 Apr 2026 15:52:47 +0000 Subject: [PATCH 04/11] Copy DER buffer in get_der --- wolfssl/__init__.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/wolfssl/__init__.py b/wolfssl/__init__.py index 2da54a5..8c32306 100644 --- a/wolfssl/__init__.py +++ b/wolfssl/__init__.py @@ -143,9 +143,7 @@ def get_der(self): if derPtr == _ffi.NULL: return None - derBytes = _ffi.buffer(derPtr, outSz[0]) - - return derBytes + return _ffi.buffer(derPtr, outSz[0])[:] class SSLContext(object): """ From e436664616081d41610b844fe824e1132b26bdae Mon Sep 17 00:00:00 2001 From: Jeremiah Mackey Date: Thu, 2 Apr 2026 15:53:05 +0000 Subject: [PATCH 05/11] Skip close on DTLS loop socket --- examples/server.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/server.py b/examples/server.py index 28812e4..8041ad8 100755 --- a/examples/server.py +++ b/examples/server.py @@ -170,7 +170,8 @@ def main(): finally: if secure_socket: secure_socket.shutdown(socket.SHUT_RDWR) - secure_socket.close() + if not args.u: + secure_socket.close() if not args.i: break From 89667f9f7ada276c672c417001791f43481f1c21 Mon Sep 17 00:00:00 2001 From: Jeremiah Mackey Date: Thu, 2 Apr 2026 15:53:32 +0000 Subject: [PATCH 06/11] Add null checks to version/pending --- wolfssl/__init__.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/wolfssl/__init__.py b/wolfssl/__init__.py index 8c32306..89f53c3 100644 --- a/wolfssl/__init__.py +++ b/wolfssl/__init__.py @@ -495,6 +495,7 @@ def _release_native_object(self): self.native_object = _ffi.NULL def pending(self): + self._check_closed("pending") return _lib.wolfSSL_pending(self.native_object) @property @@ -894,7 +895,10 @@ def version(self): """ Returns the version of the protocol used in the connection. """ - return _ffi.string(_lib.wolfSSL_get_version(self.native_object)).decode("ascii") + self._check_closed("version") + return _ffi.string( + _lib.wolfSSL_get_version( + self.native_object)).decode("ascii") # The following functions expose functionality of the underlying # Socket object. These are also exposed through Python's ssl module From 760cb466a7fa86600d30abae6f10e61b399342ef Mon Sep 17 00:00:00 2001 From: Jeremiah Mackey Date: Thu, 2 Apr 2026 15:58:52 +0000 Subject: [PATCH 07/11] Shutdown and free SSL in unwrap --- wolfssl/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/wolfssl/__init__.py b/wolfssl/__init__.py index 89f53c3..8ede6ba 100644 --- a/wolfssl/__init__.py +++ b/wolfssl/__init__.py @@ -733,7 +733,8 @@ def unwrap(self): Returns the wrapped OS socket. """ if self.native_object != _ffi.NULL: - _lib.wolfSSL_set_fd(self.native_object, -1) + _lib.wolfSSL_shutdown(self.native_object) + self._release_native_object() sock = socket(family=self._sock.family, sock_type=self._sock.type, From c81c839a24b3111fc264c1228d1f6f468197d3ab Mon Sep 17 00:00:00 2001 From: Jeremiah Mackey Date: Thu, 2 Apr 2026 15:59:15 +0000 Subject: [PATCH 08/11] Free peer address on set_peer failure --- wolfssl/__init__.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/wolfssl/__init__.py b/wolfssl/__init__.py index 8ede6ba..8d79071 100644 --- a/wolfssl/__init__.py +++ b/wolfssl/__init__.py @@ -750,11 +750,16 @@ def add_peer(self, addr): peerAddr = _lib.wolfSSL_dtls_create_peer(addr[1],t2b(addr[0])) if peerAddr == _ffi.NULL: raise SSLError("Failed to create peer") - ret = _lib.wolfSSL_dtls_set_peer(self.native_object, peerAddr, - _SOCKADDR_SZ) - if ret != _SSL_SUCCESS: - raise SSLError("Unable to set dtls peer. E(%d)" % ret) - _lib.wolfSSL_dtls_free_peer(peerAddr) + try: + ret = _lib.wolfSSL_dtls_set_peer( + self.native_object, peerAddr, + _SOCKADDR_SZ) + if ret != _SSL_SUCCESS: + raise SSLError( + "Unable to set dtls peer." + " E(%d)" % ret) + finally: + _lib.wolfSSL_dtls_free_peer(peerAddr) def do_handshake(self, block=False): # pylint: disable=unused-argument """ From e1ede238e47090ba0fc99bfec3a157f10d97f081 Mon Sep 17 00:00:00 2001 From: Jeremiah Mackey Date: Thu, 2 Apr 2026 16:00:08 +0000 Subject: [PATCH 09/11] Fix wolfSSL_Init return type and check --- wolfssl/__init__.py | 4 +++- wolfssl/_build_ffi.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/wolfssl/__init__.py b/wolfssl/__init__.py index 8d79071..f477408 100644 --- a/wolfssl/__init__.py +++ b/wolfssl/__init__.py @@ -152,7 +152,9 @@ class SSLContext(object): """ def __init__(self, protocol, server_side=None): - _lib.wolfSSL_Init() + if _lib.wolfSSL_Init() != _SSL_SUCCESS: + raise RuntimeError( + "wolfSSL library initialization failed") method = _WolfSSLMethod(protocol, server_side) self.protocol = protocol diff --git a/wolfssl/_build_ffi.py b/wolfssl/_build_ffi.py index dbc624b..7a34dcc 100644 --- a/wolfssl/_build_ffi.py +++ b/wolfssl/_build_ffi.py @@ -474,7 +474,7 @@ def generate_libwolfssl(): /* * SSL/TLS Session functions */ - void wolfSSL_Init(); + int wolfSSL_Init(void); WOLFSSL* wolfSSL_new(WOLFSSL_CTX*); void wolfSSL_free(WOLFSSL*); From 2c4ba3c8d7b7430cb20a607cb898837641792584 Mon Sep 17 00:00:00 2001 From: Jeremiah Mackey Date: Thu, 2 Apr 2026 16:32:27 +0000 Subject: [PATCH 10/11] Fix low-severity issues --- examples/client.py | 4 ++- wolfssl/__init__.py | 63 ++++++++++++++++++++++++------------------- wolfssl/_build_ffi.py | 2 +- 3 files changed, 39 insertions(+), 30 deletions(-) diff --git a/examples/client.py b/examples/client.py index da4fac3..b83d4c6 100755 --- a/examples/client.py +++ b/examples/client.py @@ -153,6 +153,7 @@ def main(): if args.l: context.set_ciphers(args.l) + secure_socket = None try: secure_socket = context.wrap_socket(bind_socket) @@ -173,7 +174,8 @@ def main(): print() finally: - secure_socket.close() + if secure_socket: + secure_socket.close() if __name__ == '__main__': diff --git a/wolfssl/__init__.py b/wolfssl/__init__.py index f477408..311bb85 100644 --- a/wolfssl/__init__.py +++ b/wolfssl/__init__.py @@ -81,7 +81,9 @@ class WolfSSL(object): @classmethod def enable_debug(self): - _lib.wolfSSL_Debugging_ON() + if _lib.wolfSSL_Debugging_ON() != _SSL_SUCCESS: + raise RuntimeError( + "wolfSSL debugging not available") @classmethod def disable_debug(self): @@ -356,9 +358,10 @@ def load_verify_locations(self, cafile=None, capath=None, cadata=None): raise SSLError("Unable to load verify locations. E(%d)" % ret) if cadata is not None: + cadata_bytes = t2b(cadata) ret = _lib.wolfSSL_CTX_load_verify_buffer( - self.native_object, t2b(cadata), - len(cadata), _SSL_FILETYPE_PEM) + self.native_object, cadata_bytes, + len(cadata_bytes), _SSL_FILETYPE_PEM) if ret != _SSL_SUCCESS: raise SSLError("Unable to load verify locations. E(%d)" % ret) @@ -476,8 +479,11 @@ def __init__(self, sock=None, keyfile=None, certfile=None, ret = _lib.wolfSSL_check_domain_name(self.native_object, sni) if ret != _SSL_SUCCESS: - raise SSLError("Unable to set domain name check for " - "hostname verification") + self._release_native_object() + raise SSLError( + "Unable to set domain name " + "check for hostname " + "verification") if connected: try: @@ -606,13 +612,6 @@ def sendall(self, data, flags=0): while sent < length: ret = self.write(data[sent:]) - if (ret <= 0): - #expect to receive 0 when peer is reset or closed - err = _lib.wolfSSL_get_error(self.native_object, 0) - if err == _SSL_ERROR_WANT_WRITE: - raise SSLWantWriteError() - else: - raise SSLError("wolfSSL_write error (%d)" % err) sent += ret @@ -683,7 +682,7 @@ def recv_into(self, buffer, nbytes=None, flags=0): if buffer is None: raise ValueError("buffer cannot be None") - if nbytes is None: + if nbytes is None or nbytes == 0: nbytes = len(buffer) else: nbytes = min(len(buffer), nbytes) @@ -724,7 +723,9 @@ def recvmsg_into(self, *args, **kwargs): def shutdown(self, how): if self.native_object != _ffi.NULL: - _lib.wolfSSL_shutdown(self.native_object) + ret = _lib.wolfSSL_shutdown(self.native_object) + if ret == 0: + _lib.wolfSSL_shutdown(self.native_object) self._release_native_object() if self._context.protocol < PROTOCOL_DTLSv1: self._sock.shutdown(how) @@ -823,18 +824,16 @@ def _real_connect(self, addr, connect_ex): raise ValueError("attempt to connect already-connected SSLSocket!") err = 0 - ret = _SSL_SUCCESS - + if self._context.protocol >= PROTOCOL_DTLSv1: - self.add_peer(addr) + self.add_peer(addr) else: if connect_ex: err = self._sock.connect_ex(addr) else: - err = 0 self._sock.connect(addr) - if err == 0 and ret == _SSL_SUCCESS: + if err == 0: self._connected = True if self.do_handshake_on_connect: self.do_handshake() @@ -912,6 +911,9 @@ def version(self): # Socket object. These are also exposed through Python's ssl module # API and are provided here for compatibility. def close(self): + if self.native_object != _ffi.NULL: + _lib.wolfSSL_shutdown(self.native_object) + self._release_native_object() self._sock.close() def fileno(self): @@ -1041,12 +1043,17 @@ def callback(self): def _get_passwd(self, passwd, sz, rw, userdata): try: result = self._passwd_wrapper(sz, rw, userdata) - if not isinstance(result, bytes): - raise ValueError("Problem, expected String, not bytes") - if len(result) > sz: - raise ValueError("Problem with password returned being long") - for i in range(len(result)): - passwd[i] = result[i:i + 1] - return len(result) - except Exception as e: - raise ValueError("Problem getting password from callback") + except Exception: + raise ValueError( + "Problem getting password from callback") + if not isinstance(result, bytes): + raise ValueError( + "Password callback must return bytes," + " not str") + if len(result) > sz: + raise ValueError( + "Problem with password returned" + " being long") + for i in range(len(result)): + passwd[i] = result[i:i + 1] + return len(result) diff --git a/wolfssl/_build_ffi.py b/wolfssl/_build_ffi.py index 7a34dcc..f2df0bd 100644 --- a/wolfssl/_build_ffi.py +++ b/wolfssl/_build_ffi.py @@ -405,7 +405,7 @@ def generate_libwolfssl(): /* * Debugging */ - void wolfSSL_Debugging_ON(); + int wolfSSL_Debugging_ON(void); void wolfSSL_Debugging_OFF(); /* From 7a1c3b088599d46d7ff83853b7d502f349fde702 Mon Sep 17 00:00:00 2001 From: Jeremiah Mackey Date: Tue, 14 Apr 2026 16:23:50 +0000 Subject: [PATCH 11/11] Guard shutdowns and clean up code --- examples/client.py | 5 ++++- examples/server.py | 12 +++++++++--- tests/test_client.py | 41 ++++++++++++++++++++++++++++++++++++++++ tests/test_context.py | 18 ++++++++++++++++++ wolfssl/__init__.py | 44 ++++++++++++++++++++++++------------------- 5 files changed, 97 insertions(+), 23 deletions(-) diff --git a/examples/client.py b/examples/client.py index b83d4c6..d30f351 100755 --- a/examples/client.py +++ b/examples/client.py @@ -140,7 +140,10 @@ def main(): context = wolfssl.SSLContext(get_SSLmethod(args.v)) # enable debug, if native wolfSSL has been compiled with '--enable-debug' - wolfssl.WolfSSL.enable_debug() + try: + wolfssl.WolfSSL.enable_debug() + except RuntimeError: + pass context.load_cert_chain(args.c, args.k) diff --git a/examples/server.py b/examples/server.py index 8041ad8..2f060af 100755 --- a/examples/server.py +++ b/examples/server.py @@ -119,8 +119,8 @@ def main(): args = build_arg_parser().parse_args() # DTLS connection over UDP if args.u: - # Set DTLSv1.2 as default if unspecified - if args.v == 5: + # Set DTLSv1.2 as default if unspecified + if args.v > 2: args.v = 1 bind_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0) bind_socket.bind(("" if args.b else "localhost", args.p)) @@ -136,7 +136,10 @@ def main(): print("Server listening on port", bind_socket.getsockname()[1]) # enable debug, if native wolfSSL has been compiled with '--enable-debug' - wolfssl.WolfSSL.enable_debug() + try: + wolfssl.WolfSSL.enable_debug() + except RuntimeError: + pass context.load_cert_chain(args.c, args.k) @@ -170,6 +173,9 @@ def main(): finally: if secure_socket: secure_socket.shutdown(socket.SHUT_RDWR) + # Don't close for DTLS - secure_socket wraps the + # shared bind_socket which is needed for + # subsequent connections if not args.u: secure_socket.close() diff --git a/tests/test_client.py b/tests/test_client.py index 1f529c5..cf81363 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -93,6 +93,47 @@ def test_get_version(ssl_server, ssl_version, tcp_socket): secure_socket.read(1024) +def test_close_after_connected(ssl_server, tcp_socket): + ctx = wolfssl.SSLContext(wolfssl.PROTOCOL_TLSv1_2) + sock = ctx.wrap_socket(tcp_socket) + sock.connect(('127.0.0.1', ssl_server.port)) + sock.write(b'hello wolfssl') + sock.read(1024) + sock.close() + + +def test_recv_into_nbytes_zero(ssl_server, tcp_socket): + ctx = wolfssl.SSLContext(wolfssl.PROTOCOL_TLSv1_2) + sock = ctx.wrap_socket(tcp_socket) + sock.connect(('127.0.0.1', ssl_server.port)) + sock.write(b'hello wolfssl') + buf = bytearray(1024) + n = sock.recv_into(buf, 0) + assert n > 0 + sock.close() + + +def test_unwrap_returns_socket(ssl_server, tcp_socket): + import socket as _socket + ctx = wolfssl.SSLContext(wolfssl.PROTOCOL_TLSv1_2) + sock = ctx.wrap_socket(tcp_socket) + sock.connect(('127.0.0.1', ssl_server.port)) + sock.write(b'hello wolfssl') + sock.read(1024) + raw = sock.unwrap() + assert isinstance(raw, _socket.socket) + raw.close() + + +def test_sendall_large_buffer(ssl_server, tcp_socket): + ctx = wolfssl.SSLContext(wolfssl.PROTOCOL_TLSv1_2) + sock = ctx.wrap_socket(tcp_socket) + sock.connect(('127.0.0.1', ssl_server.port)) + sock.sendall(b'x' * 8192) + sock.read(1024) + sock.close() + + def test_client_cert_verification_failure(): """ Test that a connection fails when the server requires client certificates diff --git a/tests/test_context.py b/tests/test_context.py index 0aa804b..6a2f3af 100644 --- a/tests/test_context.py +++ b/tests/test_context.py @@ -88,3 +88,21 @@ def test_check_hostname_requires_cert_required(ssl_provider, ssl_context): def test_wrap_socket_server_side_mismatch(ssl_context, tcp_socket): with pytest.raises(ValueError): ssl_context.wrap_socket(tcp_socket, server_side=True) + + +def test_close_without_handshake(ssl_context, tcp_socket): + sock = ssl_context.wrap_socket(tcp_socket) + sock.close() + + +def test_close_releases_native_object(ssl_context, tcp_socket): + sock = ssl_context.wrap_socket(tcp_socket) + sock.close() + sock.close() + + +def test_operations_after_close_raise(ssl_context, tcp_socket): + sock = ssl_context.wrap_socket(tcp_socket) + sock.close() + with pytest.raises(ValueError): + sock.read() diff --git a/wolfssl/__init__.py b/wolfssl/__init__.py index 311bb85..7a739c3 100644 --- a/wolfssl/__init__.py +++ b/wolfssl/__init__.py @@ -612,7 +612,6 @@ def sendall(self, data, flags=0): while sent < length: ret = self.write(data[sent:]) - sent += ret return None @@ -736,11 +735,15 @@ def unwrap(self): Returns the wrapped OS socket. """ if self.native_object != _ffi.NULL: - _lib.wolfSSL_shutdown(self.native_object) + if self._connected: + # Single-step shutdown is intentional; any + # bidirectional close_notify exchange is the + # caller's responsibility on the raw socket. + _lib.wolfSSL_shutdown(self.native_object) self._release_native_object() sock = socket(family=self._sock.family, - sock_type=self._sock.type, + type=self._sock.type, proto=self._sock.proto, fileno=self._sock.fileno()) @@ -750,19 +753,19 @@ def unwrap(self): return sock def add_peer(self, addr): - peerAddr = _lib.wolfSSL_dtls_create_peer(addr[1],t2b(addr[0])) - if peerAddr == _ffi.NULL: - raise SSLError("Failed to create peer") - try: - ret = _lib.wolfSSL_dtls_set_peer( - self.native_object, peerAddr, - _SOCKADDR_SZ) - if ret != _SSL_SUCCESS: - raise SSLError( - "Unable to set dtls peer." - " E(%d)" % ret) - finally: - _lib.wolfSSL_dtls_free_peer(peerAddr) + peerAddr = _lib.wolfSSL_dtls_create_peer(addr[1], t2b(addr[0])) + if peerAddr == _ffi.NULL: + raise SSLError("Failed to create peer") + try: + ret = _lib.wolfSSL_dtls_set_peer( + self.native_object, peerAddr, + _SOCKADDR_SZ) + if ret != _SSL_SUCCESS: + raise SSLError( + "Unable to set dtls peer." + " E(%d)" % ret) + finally: + _lib.wolfSSL_dtls_free_peer(peerAddr) def do_handshake(self, block=False): # pylint: disable=unused-argument """ @@ -912,7 +915,11 @@ def version(self): # API and are provided here for compatibility. def close(self): if self.native_object != _ffi.NULL: - _lib.wolfSSL_shutdown(self.native_object) + if self._connected: + # Single-step shutdown is intentional here; the + # socket is about to be closed so a bidirectional + # close_notify exchange is not required. + _lib.wolfSSL_shutdown(self.native_object) self._release_native_object() self._sock.close() @@ -1048,8 +1055,7 @@ def _get_passwd(self, passwd, sz, rw, userdata): "Problem getting password from callback") if not isinstance(result, bytes): raise ValueError( - "Password callback must return bytes," - " not str") + "Password callback must return bytes") if len(result) > sz: raise ValueError( "Problem with password returned"