Skip to content

Fenrir fixes (2026-06-23)#70

Merged
JacobBarthelmeh merged 14 commits into
wolfSSL:masterfrom
julek-wolfssl:fenrir/20260623
Jul 14, 2026
Merged

Fenrir fixes (2026-06-23)#70
JacobBarthelmeh merged 14 commits into
wolfSSL:masterfrom
julek-wolfssl:fenrir/20260623

Conversation

@julek-wolfssl

Copy link
Copy Markdown
Member

This branch collects a set of Fenrir-tracked fixes for the wolfSSL Python bindings.

Changes

  • Return None from getpeercert when peer has no certificate (F-5623) — match the stdlib ssl contract.
  • Send bytes-like data verbatim in SSLSocket.write (F-5622) — avoid mangling bytes-like inputs.
  • Enable hostname verification in client example (F-5621).
  • Drive DTLS handshake only until complete in I/O methods (F-4136).
  • Map WANT_WRITE from SSLSocket.recv_into() to SSLWantWriteError (F-3907).
  • Map WANT_WRITE from SSLSocket.read() to SSLWantWriteError (F-3906).
  • Map WANT_READ from SSLSocket.write() to SSLWantReadError (F-3905).
  • Fix DTLS server example consuming the ClientHello before handshake (F-3481).

Each fix ships with accompanying tests.

…-3481)

The DTLS branch called bind_socket.recvfrom(1) before creating the
context. On UDP that removes the entire first datagram (the client's
ClientHello) from the queue and discards everything past the first
byte, so wolfSSL_accept() had nothing to consume and the handshake
only recovered after the client's retransmit timer. The captured
from_addr was also reused for every iteration of the -i loop.

Replace it with a peek_peer_address() helper that uses MSG_PEEK to read
the source address without consuming the datagram, and move the peek
into the accept loop so the address is refreshed per connection.
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().
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.
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.
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.
Copilot AI review requested due to automatic review settings June 23, 2026 13:19
@julek-wolfssl julek-wolfssl self-assigned this Jun 23, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR bundles several Fenrir-tracked behavioral fixes to the wolfSSL Python bindings to better match CPython ssl semantics (peer cert handling, I/O error mapping, DTLS handshake behavior) and to harden the shipped client/server examples (hostname verification + DTLS UDP peeking), with accompanying regression tests.

Changes:

  • Adjust peer-certificate retrieval so getpeercert()/get_peer_x509() return None when the peer presented no certificate.
  • Improve SSLSocket I/O behavior: accept bytes-like inputs for write(), map WANT_READ/WANT_WRITE to the corresponding exceptions, and avoid re-driving DTLS handshakes after completion.
  • Update examples (client hostname verification, DTLS server peek) and add targeted pytest coverage for each fix.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
wolfssl/__init__.py Core binding behavior changes: peer cert retrieval, bytes-like write(), DTLS handshake completion tracking, WANT_* exception mapping.
examples/client.py Adds hostname-verification behavior (and opt-out flag) and passes server_hostname to wrap_socket().
examples/server.py Fixes DTLS UDP example to peek source address without consuming the ClientHello datagram.
tests/test_write_bytes.py Regression tests ensuring write() sends bytes-like contents verbatim and rejects non-bytes-like inputs.
tests/test_io_error_mapping.py Verifies WANT_READ/WANT_WRITE error-code mapping to SSLWantReadError/SSLWantWriteError.
tests/test_getpeercert.py Confirms getpeercert()/get_peer_x509() return None when the peer provides no certificate.
tests/test_dtls_server_example.py Ensures DTLS server peeking doesn’t consume the datagram needed for handshake.
tests/test_dtls_handshake_once.py Ensures DTLS handshake is driven only until completion (not on every I/O call).
tests/test_client_example.py Validates example client verification/hostname-check configuration behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread wolfssl/__init__.py Outdated
Comment thread examples/client.py
Comment thread examples/client.py

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.

Comment thread wolfssl/__init__.py Outdated
Comment thread examples/client.py
@julek-wolfssl julek-wolfssl removed their assignment Jun 24, 2026
The client example set CERT_REQUIRED and loaded CA roots but never set
check_hostname or passed server_hostname to wrap_socket, so wolfSSL
validated the chain to a trusted CA without binding the certificate to
the requested host. A peer presenting any CA-trusted certificate for a
different hostname would be accepted by anyone reusing this as a secure
client template. Make verification configure hostname checking by
default (via a new configure_verification helper) and add a -n flag to
opt out explicitly for IP literals or test certificates.
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.
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.

@dgarske dgarske left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skoll Code Review

Scan type: reviewOverall recommendation: COMMENT
Findings: 3 total — 3 posted, 0 skipped
3 finding(s) posted as inline comments (see file-level comments below)

Posted findings

  • [Medium] Client example default host is an IP literal but hostname verification is now on by defaultexamples/client.py:46-48,134-158,183-191
  • [Low] Non-ASCII character in test source literaltests/test_write_bytes.py:80-84
  • [Low] WolfSSLX509 type discrimination relies on exact cffi cname string matchwolfssl/__init__.py:99-111

Review generated by Skoll

Comment thread examples/client.py
Comment thread tests/test_write_bytes.py
Comment thread wolfssl/__init__.py
The bundled CRL expired 2024-11-11, so the client example's default
CRL load (enabled unless -C) fails. Take the current CRL from wolfSSL
v5.8.4-stable certs/crl/crl.pem: same Sawtooth CA and key, revokes
only serial 02, valid until 2028-08-09.

Note: the bundled ca/server/client certs expire 2026-09-08 and will
need a refresh of their own before then.
Review follow-up for F-5621. wolfSSL_check_domain_name() only matches
DNS names: on this path CheckForAltNames() is called with isIP=0, so
iPAddress SANs are always skipped (verified on v5.8.4-stable and
master). The default invocation (host 127.0.0.1) therefore failed the
handshake with DOMAIN_NAME_MISMATCH (-322) once hostname verification
was enabled by default.

Skip the hostname check for IP literal hosts and say so, keeping
CERT_REQUIRED verification. This also stops offering an IP literal in
SNI, which RFC 6066 forbids. Connecting by DNS name still enables the
hostname check.

Add unit tests for the IP literal paths and an end-to-end test that
runs server.py and client.py with default arguments.
Use the \u00e9 escape instead of a literal e-acute. The test still
exercises multi-byte UTF-8 encoding.
Compare interned cffi type objects instead of rendered type name
strings and raise TypeError for anything that is not a WOLFSSL* or
WOLFSSL_X509*.
socket.inet_pton() is missing on some supported platforms (Python 2.7
on Windows). getaddrinfo() with AI_NUMERICHOST parses without
resolving and is available everywhere. It also handles scoped IPv6
literals.
subprocess.run() is Python 3.5+. Use Popen with communicate() and a
threading.Timer watchdog in place of the communicate() timeout, which
is 3.3+.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.

Comment thread examples/client.py
@julek-wolfssl julek-wolfssl requested a review from dgarske July 13, 2026 20:11
@julek-wolfssl julek-wolfssl removed their assignment Jul 13, 2026
@JacobBarthelmeh JacobBarthelmeh merged commit 2e77f30 into wolfSSL:master Jul 14, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants