get_peer_x509() checked only whether the session was NULL and then built
a WolfSSLX509, whose __init__ called wolfSSL_get_peer_certificate() and
raised SSLError on NULL. On a valid connection where the peer presented
no certificate (e.g. a server not requesting a client cert), this raised
instead of returning None as the stdlib ssl getpeercert() contract
requires. Fetch the certificate in get_peer_x509(), return None when it
is NULL, and have WolfSSLX509 wrap the already-obtained pointer.
write() converted data with t2b(), which str()-encodes anything that is
not already bytes. Valid bytes-like inputs such as bytearray and
memoryview were transmitted as their Python repr ("bytearray(b'...')",
"<memory at ...>") instead of their contents, corrupting the stream.
Convert via the buffer protocol (bytes(memoryview(data))) and raise
TypeError for objects that are not bytes-like, matching the stdlib ssl
module.
For DTLS, write()/read()/recv_into() called do_handshake() on every
call. do_handshake() runs wolfSSL_accept/connect, which on a
non-blocking socket can raise SSLWantReadError and abort an I/O long
after the handshake finished, and made DTLS write-side behaviour
inconsistent with TCP. Track completion with a _handshake_complete
flag set on a successful do_handshake(), and only drive the handshake
from I/O methods while that flag is False.
recv_into() shares read()'s error-mapping pattern and inherited the
same omission: wolfSSL_read returning WOLFSSL_ERROR_WANT_WRITE (during
a renegotiation needing a write) was reported as a generic SSLError
instead of SSLWantWriteError, breaking non-blocking callers that
distinguish readiness directions. Add the WANT_WRITE branch.
wolfSSL_read can return WOLFSSL_ERROR_WANT_WRITE when the SSL layer
must flush a handshake record (e.g. renegotiation) before returning
data. read() only handled WANT_READ, raising a generic SSLError
otherwise, which stops non-blocking callers from select()-ing on
writability. Add a WANT_WRITE branch raising SSLWantWriteError.
wolfSSL_write can return WOLFSSL_ERROR_WANT_READ (e.g. during a
renegotiation that must read a record before progressing; secure
renegotiation is enabled by default). write() only handled WANT_WRITE,
so WANT_READ fell through to a generic SSLError and non-blocking
callers tore the session down. Add a WANT_READ branch raising
SSLWantReadError, matching do_handshake().
Because we access the fields of WOLFSSL_ALERT_HISTORY and WOLFSSL_ALERT directly
in do_handshake, these structs can't be opaque, or an exception will be raised
by FFI.