Skip to content

Commit 74a340d

Browse files
authored
Merge pull request #66 from JeremiahM37/fenrir-fixes-2
Fenrir fixes
2 parents 3f2cd6e + 7a1c3b0 commit 74a340d

6 files changed

Lines changed: 163 additions & 51 deletions

File tree

examples/client.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,8 @@ def main():
130130

131131
# DTLS connection over UDP
132132
if args.u:
133+
if args.v > 2:
134+
args.v = 1
133135
bind_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0)
134136
context = wolfssl.SSLContext(get_DTLSmethod(args.v))
135137
# SSL/TLS connection over TCP
@@ -138,7 +140,10 @@ def main():
138140
context = wolfssl.SSLContext(get_SSLmethod(args.v))
139141

140142
# enable debug, if native wolfSSL has been compiled with '--enable-debug'
141-
wolfssl.WolfSSL.enable_debug()
143+
try:
144+
wolfssl.WolfSSL.enable_debug()
145+
except RuntimeError:
146+
pass
142147

143148
context.load_cert_chain(args.c, args.k)
144149

@@ -151,6 +156,7 @@ def main():
151156
if args.l:
152157
context.set_ciphers(args.l)
153158

159+
secure_socket = None
154160
try:
155161
secure_socket = context.wrap_socket(bind_socket)
156162

@@ -171,7 +177,8 @@ def main():
171177
print()
172178

173179
finally:
174-
secure_socket.close()
180+
if secure_socket:
181+
secure_socket.close()
175182

176183

177184
if __name__ == '__main__':

examples/server.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,8 @@ def main():
119119
args = build_arg_parser().parse_args()
120120
# DTLS connection over UDP
121121
if args.u:
122-
# Set DTLSv1.2 as default if unspecified
123-
if args.v == 5:
122+
# Set DTLSv1.2 as default if unspecified
123+
if args.v > 2:
124124
args.v = 1
125125
bind_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0)
126126
bind_socket.bind(("" if args.b else "localhost", args.p))
@@ -136,7 +136,10 @@ def main():
136136
print("Server listening on port", bind_socket.getsockname()[1])
137137

138138
# enable debug, if native wolfSSL has been compiled with '--enable-debug'
139-
wolfssl.WolfSSL.enable_debug()
139+
try:
140+
wolfssl.WolfSSL.enable_debug()
141+
except RuntimeError:
142+
pass
140143

141144
context.load_cert_chain(args.c, args.k)
142145

@@ -170,7 +173,11 @@ def main():
170173
finally:
171174
if secure_socket:
172175
secure_socket.shutdown(socket.SHUT_RDWR)
173-
secure_socket.close()
176+
# Don't close for DTLS - secure_socket wraps the
177+
# shared bind_socket which is needed for
178+
# subsequent connections
179+
if not args.u:
180+
secure_socket.close()
174181

175182
if not args.i:
176183
break

tests/test_client.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,47 @@ def test_get_version(ssl_server, ssl_version, tcp_socket):
9393
secure_socket.read(1024)
9494

9595

96+
def test_close_after_connected(ssl_server, tcp_socket):
97+
ctx = wolfssl.SSLContext(wolfssl.PROTOCOL_TLSv1_2)
98+
sock = ctx.wrap_socket(tcp_socket)
99+
sock.connect(('127.0.0.1', ssl_server.port))
100+
sock.write(b'hello wolfssl')
101+
sock.read(1024)
102+
sock.close()
103+
104+
105+
def test_recv_into_nbytes_zero(ssl_server, tcp_socket):
106+
ctx = wolfssl.SSLContext(wolfssl.PROTOCOL_TLSv1_2)
107+
sock = ctx.wrap_socket(tcp_socket)
108+
sock.connect(('127.0.0.1', ssl_server.port))
109+
sock.write(b'hello wolfssl')
110+
buf = bytearray(1024)
111+
n = sock.recv_into(buf, 0)
112+
assert n > 0
113+
sock.close()
114+
115+
116+
def test_unwrap_returns_socket(ssl_server, tcp_socket):
117+
import socket as _socket
118+
ctx = wolfssl.SSLContext(wolfssl.PROTOCOL_TLSv1_2)
119+
sock = ctx.wrap_socket(tcp_socket)
120+
sock.connect(('127.0.0.1', ssl_server.port))
121+
sock.write(b'hello wolfssl')
122+
sock.read(1024)
123+
raw = sock.unwrap()
124+
assert isinstance(raw, _socket.socket)
125+
raw.close()
126+
127+
128+
def test_sendall_large_buffer(ssl_server, tcp_socket):
129+
ctx = wolfssl.SSLContext(wolfssl.PROTOCOL_TLSv1_2)
130+
sock = ctx.wrap_socket(tcp_socket)
131+
sock.connect(('127.0.0.1', ssl_server.port))
132+
sock.sendall(b'x' * 8192)
133+
sock.read(1024)
134+
sock.close()
135+
136+
96137
def test_client_cert_verification_failure():
97138
"""
98139
Test that a connection fails when the server requires client certificates

tests/test_context.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,3 +74,35 @@ def test_load_verify_locations_with_cafile(ssl_context):
7474

7575
def test_load_verify_locations_with_cadata(ssl_context):
7676
ssl_context.load_verify_locations(cadata=_CADATA)
77+
78+
79+
def test_check_hostname_requires_cert_required(ssl_provider, ssl_context):
80+
with pytest.raises(ValueError):
81+
ssl_context.check_hostname = True
82+
83+
ssl_context.verify_mode = ssl_provider.CERT_REQUIRED
84+
ssl_context.check_hostname = True
85+
assert ssl_context.check_hostname is True
86+
87+
88+
def test_wrap_socket_server_side_mismatch(ssl_context, tcp_socket):
89+
with pytest.raises(ValueError):
90+
ssl_context.wrap_socket(tcp_socket, server_side=True)
91+
92+
93+
def test_close_without_handshake(ssl_context, tcp_socket):
94+
sock = ssl_context.wrap_socket(tcp_socket)
95+
sock.close()
96+
97+
98+
def test_close_releases_native_object(ssl_context, tcp_socket):
99+
sock = ssl_context.wrap_socket(tcp_socket)
100+
sock.close()
101+
sock.close()
102+
103+
104+
def test_operations_after_close_raise(ssl_context, tcp_socket):
105+
sock = ssl_context.wrap_socket(tcp_socket)
106+
sock.close()
107+
with pytest.raises(ValueError):
108+
sock.read()

wolfssl/__init__.py

Lines changed: 68 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,9 @@ class WolfSSL(object):
8181

8282
@classmethod
8383
def enable_debug(self):
84-
_lib.wolfSSL_Debugging_ON()
84+
if _lib.wolfSSL_Debugging_ON() != _SSL_SUCCESS:
85+
raise RuntimeError(
86+
"wolfSSL debugging not available")
8587

8688
@classmethod
8789
def disable_debug(self):
@@ -143,9 +145,7 @@ def get_der(self):
143145
if derPtr == _ffi.NULL:
144146
return None
145147

146-
derBytes = _ffi.buffer(derPtr, outSz[0])
147-
148-
return derBytes
148+
return _ffi.buffer(derPtr, outSz[0])[:]
149149

150150
class SSLContext(object):
151151
"""
@@ -154,7 +154,9 @@ class SSLContext(object):
154154
"""
155155

156156
def __init__(self, protocol, server_side=None):
157-
_lib.wolfSSL_Init()
157+
if _lib.wolfSSL_Init() != _SSL_SUCCESS:
158+
raise RuntimeError(
159+
"wolfSSL library initialization failed")
158160
method = _WolfSSLMethod(protocol, server_side)
159161

160162
self.protocol = protocol
@@ -356,9 +358,10 @@ def load_verify_locations(self, cafile=None, capath=None, cadata=None):
356358
raise SSLError("Unable to load verify locations. E(%d)" % ret)
357359

358360
if cadata is not None:
361+
cadata_bytes = t2b(cadata)
359362
ret = _lib.wolfSSL_CTX_load_verify_buffer(
360-
self.native_object, t2b(cadata),
361-
len(cadata), _SSL_FILETYPE_PEM)
363+
self.native_object, cadata_bytes,
364+
len(cadata_bytes), _SSL_FILETYPE_PEM)
362365

363366
if ret != _SSL_SUCCESS:
364367
raise SSLError("Unable to load verify locations. E(%d)" % ret)
@@ -476,8 +479,11 @@ def __init__(self, sock=None, keyfile=None, certfile=None,
476479
ret = _lib.wolfSSL_check_domain_name(self.native_object,
477480
sni)
478481
if ret != _SSL_SUCCESS:
479-
raise SSLError("Unable to set domain name check for "
480-
"hostname verification")
482+
self._release_native_object()
483+
raise SSLError(
484+
"Unable to set domain name "
485+
"check for hostname "
486+
"verification")
481487

482488
if connected:
483489
try:
@@ -497,6 +503,7 @@ def _release_native_object(self):
497503
self.native_object = _ffi.NULL
498504

499505
def pending(self):
506+
self._check_closed("pending")
500507
return _lib.wolfSSL_pending(self.native_object)
501508

502509
@property
@@ -605,14 +612,6 @@ def sendall(self, data, flags=0):
605612

606613
while sent < length:
607614
ret = self.write(data[sent:])
608-
if (ret <= 0):
609-
#expect to receive 0 when peer is reset or closed
610-
err = _lib.wolfSSL_get_error(self.native_object, 0)
611-
if err == _SSL_ERROR_WANT_WRITE:
612-
raise SSLWantWriteError()
613-
else:
614-
raise SSLError("wolfSSL_write error (%d)" % err)
615-
616615
sent += ret
617616

618617
return None
@@ -676,11 +675,13 @@ def recv_into(self, buffer, nbytes=None, flags=0):
676675
self._check_closed("read")
677676
if self._context.protocol < PROTOCOL_DTLSv1:
678677
self._check_connected()
678+
else:
679+
self.do_handshake()
679680

680681
if buffer is None:
681682
raise ValueError("buffer cannot be None")
682683

683-
if nbytes is None:
684+
if nbytes is None or nbytes == 0:
684685
nbytes = len(buffer)
685686
else:
686687
nbytes = min(len(buffer), nbytes)
@@ -721,7 +722,9 @@ def recvmsg_into(self, *args, **kwargs):
721722

722723
def shutdown(self, how):
723724
if self.native_object != _ffi.NULL:
724-
_lib.wolfSSL_shutdown(self.native_object)
725+
ret = _lib.wolfSSL_shutdown(self.native_object)
726+
if ret == 0:
727+
_lib.wolfSSL_shutdown(self.native_object)
725728
self._release_native_object()
726729
if self._context.protocol < PROTOCOL_DTLSv1:
727730
self._sock.shutdown(how)
@@ -732,10 +735,15 @@ def unwrap(self):
732735
Returns the wrapped OS socket.
733736
"""
734737
if self.native_object != _ffi.NULL:
735-
_lib.wolfSSL_set_fd(self.native_object, -1)
738+
if self._connected:
739+
# Single-step shutdown is intentional; any
740+
# bidirectional close_notify exchange is the
741+
# caller's responsibility on the raw socket.
742+
_lib.wolfSSL_shutdown(self.native_object)
743+
self._release_native_object()
736744

737745
sock = socket(family=self._sock.family,
738-
sock_type=self._sock.type,
746+
type=self._sock.type,
739747
proto=self._sock.proto,
740748
fileno=self._sock.fileno())
741749

@@ -745,14 +753,19 @@ def unwrap(self):
745753
return sock
746754

747755
def add_peer(self, addr):
748-
peerAddr = _lib.wolfSSL_dtls_create_peer(addr[1],t2b(addr[0]))
749-
if peerAddr == _ffi.NULL:
750-
raise SSLError("Failed to create peer")
751-
ret = _lib.wolfSSL_dtls_set_peer(self.native_object, peerAddr,
752-
_SOCKADDR_SZ)
756+
peerAddr = _lib.wolfSSL_dtls_create_peer(addr[1], t2b(addr[0]))
757+
if peerAddr == _ffi.NULL:
758+
raise SSLError("Failed to create peer")
759+
try:
760+
ret = _lib.wolfSSL_dtls_set_peer(
761+
self.native_object, peerAddr,
762+
_SOCKADDR_SZ)
753763
if ret != _SSL_SUCCESS:
754-
raise SSLError("Unable to set dtls peer. E(%d)" % ret)
755-
_lib.wolfSSL_dtls_free_peer(peerAddr)
764+
raise SSLError(
765+
"Unable to set dtls peer."
766+
" E(%d)" % ret)
767+
finally:
768+
_lib.wolfSSL_dtls_free_peer(peerAddr)
756769

757770
def do_handshake(self, block=False): # pylint: disable=unused-argument
758771
"""
@@ -814,18 +827,16 @@ def _real_connect(self, addr, connect_ex):
814827
raise ValueError("attempt to connect already-connected SSLSocket!")
815828

816829
err = 0
817-
ret = _SSL_SUCCESS
818-
830+
819831
if self._context.protocol >= PROTOCOL_DTLSv1:
820-
self.add_peer(addr)
832+
self.add_peer(addr)
821833
else:
822834
if connect_ex:
823835
err = self._sock.connect_ex(addr)
824836
else:
825-
err = 0
826837
self._sock.connect(addr)
827838

828-
if err == 0 and ret == _SSL_SUCCESS:
839+
if err == 0:
829840
self._connected = True
830841
if self.do_handshake_on_connect:
831842
self.do_handshake()
@@ -894,12 +905,22 @@ def version(self):
894905
"""
895906
Returns the version of the protocol used in the connection.
896907
"""
897-
return _ffi.string(_lib.wolfSSL_get_version(self.native_object)).decode("ascii")
908+
self._check_closed("version")
909+
return _ffi.string(
910+
_lib.wolfSSL_get_version(
911+
self.native_object)).decode("ascii")
898912

899913
# The following functions expose functionality of the underlying
900914
# Socket object. These are also exposed through Python's ssl module
901915
# API and are provided here for compatibility.
902916
def close(self):
917+
if self.native_object != _ffi.NULL:
918+
if self._connected:
919+
# Single-step shutdown is intentional here; the
920+
# socket is about to be closed so a bidirectional
921+
# close_notify exchange is not required.
922+
_lib.wolfSSL_shutdown(self.native_object)
923+
self._release_native_object()
903924
self._sock.close()
904925

905926
def fileno(self):
@@ -1029,12 +1050,16 @@ def callback(self):
10291050
def _get_passwd(self, passwd, sz, rw, userdata):
10301051
try:
10311052
result = self._passwd_wrapper(sz, rw, userdata)
1032-
if not isinstance(result, bytes):
1033-
raise ValueError("Problem, expected String, not bytes")
1034-
if len(result) > sz:
1035-
raise ValueError("Problem with password returned being long")
1036-
for i in range(len(result)):
1037-
passwd[i] = result[i:i + 1]
1038-
return len(result)
1039-
except Exception as e:
1040-
raise ValueError("Problem getting password from callback")
1053+
except Exception:
1054+
raise ValueError(
1055+
"Problem getting password from callback")
1056+
if not isinstance(result, bytes):
1057+
raise ValueError(
1058+
"Password callback must return bytes")
1059+
if len(result) > sz:
1060+
raise ValueError(
1061+
"Problem with password returned"
1062+
" being long")
1063+
for i in range(len(result)):
1064+
passwd[i] = result[i:i + 1]
1065+
return len(result)

0 commit comments

Comments
 (0)