Fail the connection when a fatal TLS alert arrives after the handshake by choksi2212 · Pull Request #8490 · mitmproxy/mitmproxy · GitHub
Skip to content

Fail the connection when a fatal TLS alert arrives after the handshake - #8490

Open
choksi2212 wants to merge 3 commits into
mitmproxy:mainfrom
choksi2212:fix/tls13-fatal-alert-after-handshake
Open

choksi2212 wants to merge 3 commits into
mitmproxy:mainfrom
choksi2212:fix/tls13-fatal-alert-after-handshake

Conversation

@choksi2212

Copy link
Copy Markdown

Description

Fixes #8486.

A TLS 1.3 server that rejects the client certificate only after the handshake has completed sends a fatal alert that surfaces from SSL.Connection.recv() as SSL.Error. TLSLayer.receive_data logged that alert but never failed the connection, so the child layer (e.g. the HTTP layer waiting for a response) was left waiting on a connection that would never deliver a response — the client hangs. A TLS 1.2 peer that rejects the certificate during the handshake instead fails the connection promptly and yields a 502 Bad Gateway, so the TLS 1.3 behavior is an observable regression in failure handling.

With this change, TLSLayer.receive_data records the error on the connection (conn.error, the same convention the QUIC stream layer already uses) and dispatches ConnectionClosed to the child layer, so the HTTP layer synthesizes its 502 promptly. Http1Client now surfaces conn.error in the ResponseProtocolError it emits when the server connection dies with a recorded error, so the 502 names the actual TLS failure instead of the generic "server closed connection".

WantReadError (need more data) and ZeroReturnError (close_notify) handling is unchanged; the new close path only fires when OpenSSL has permanently failed the record layer.

Reproduction

Reporter's exact scenario, TLS 1.3 server requesting a client cert and rejecting it after the handshake:

  • Before: mitmdump logs TLS Error: [('SSL routines', '', 'tlsv13 alert certificate required')] and then nothing — the client hangs indefinitely.
  • After: the flow fails immediately with << OpenSSL Error([('SSL routines', '', 'tlsv13 alert certificate required')]) — same prompt-failure behavior as TLS 1.2, with the actual reason named.

Testing

  • test/mitmproxy/proxy/layers/test_tls.py::TestServerTLS::test_fatal_alert_after_handshake — drives a genuine server-sent fatal alert (undecryptable record → OpenSSL fatal alert) through ServerTLSLayer post-handshake and asserts the connection is failed (CloseConnection + ConnectionClosed to the child + CAN_READ cleared + conn.error recorded). Fails on main with a playbook mismatch: no CloseConnection is ever emitted.
  • test/mitmproxy/proxy/layers/http/test_http1.py::TestClient::test_server_closed_with_error — asserts Http1Client surfaces the recorded connection error in the ResponseProtocolError when the server connection dies. Fails on main, which emits the generic server closed connection message instead.
  • Full local suite: 2005 passed, 3 skipped (Windows sandbox; the two deselected test_memory_usage_* tests pass standalone on this machine and only hit a missing _curses module under the xdist run — Python curses is not available on Windows).
  • ruff check / ruff format clean on all changed files; mypy reports no errors for the changed modules.

choksi2212 and others added 3 commits October 18, 2026 09:42
TLS 1.3 servers may reject a client certificate only after the handshake
has already completed. The fatal alert then surfaces from recv() as
SSL.Error, which the TLS layer logged without failing the connection, so
the HTTP layer was left waiting on a connection that would never deliver
a response (a TLS 1.2 peer that rejects during the handshake yields a
prompt 502 instead).

We now record the error on the connection and dispatch ConnectionClosed
to the child layer, mirroring the QUIC stream layer's existing error
handling. The HTTP layer surfaces conn.error in the 502 it synthesizes,
so the response names the actual TLS failure instead of a generic
"server closed connection".

Fixes mitmproxy#8486

Signed-off-by: choksi2212 <choksi2212@users.noreply.github.com>
@choksi2212

Copy link
Copy Markdown
Author

This branch has not been deployed

No deployments
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.

Request hangs when a TLS 1.3 server rejects the client certificate after the handshake

1 participant