Skip to content

fix: prevent inbound QUIC connections without peer certifate verification#1346

Open
sumanjeet0012 wants to merge 1 commit into
libp2p:mainfrom
sumanjeet0012:quic-fix
Open

fix: prevent inbound QUIC connections without peer certifate verification#1346
sumanjeet0012 wants to merge 1 commit into
libp2p:mainfrom
sumanjeet0012:quic-fix

Conversation

@sumanjeet0012

@sumanjeet0012 sumanjeet0012 commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

What was wrong?

Fixes #1345

Inbound QUIC connections were being accepted and promoted to the application callback even when the remote client sent no peer certificate. This was an authentication bypass on the official QUIC listener path, allowing unauthenticated connections to appear legitimate.

The root cause involved a combination of aioquic default behavior and libp2p security requirements:

  1. In py-libp2p, verify_mode is intentionally set to ssl.CERT_NONE so that aioquic does not validate self-signed libp2p certificates against the system CA store. We perform manual libp2p peer identity verification inside connection.py instead.
  2. Because verify_mode is CERT_NONE, aioquic's TLS context (quic_conn.tls) defaults to _request_client_certificate = False.
  3. The server was previously attempting to request a client certificate by setting quic_conn.tls._request_client_certificate = True after calling receive_datagram on the initial packet.
  4. However, receive_datagram parses the initial datagram and synchronously processes the TLS ClientHello, generating and sending the ServerHello. Since our flag was not set yet, the ServerHello omitted the CertificateRequest block.
  5. Consequently, clients never sent a certificate, and connection.py silently returned None when _peer_certificate was missing, failing open and invoking the user handler with an unauthenticated connection.

What changed?

libp2p/transport/quic/connection.py

  • _verify_peer_identity_with_security now explicitly raises QUICPeerVerificationError when no peer certificate is present on an inbound (server-side) connection.
  • Outbound connections retain the lenient path (return None) since the client may not always have the remote cert immediately.
  • Added a fallback in get_peer_certificate to extract the certificate directly from the TLS context for early access before background tasks have flagged _handshake_completed.

libp2p/transport/quic/listener.py

  • Added a ServerQuicConnection subclass of aioquic's QuicConnection to cleanly hook into TLS initialization.
  • Why a subclass? aioquic instantiates the TLS context internally inside a private _initialize() method on the very first datagram, right before it generates the ServerHello. We override _initialize to immediately flip self.tls._request_client_certificate = True as soon as the context is created. This ensures the ServerHello accurately includes the CertificateRequest block.
  • Doing this inline without a subclass proved highly brittle because manually forcing _initialize from the outside breaks aioquic's internal "first packet" state machine handling for internal variables like _version and _network_paths.
  • Added a belt-and-suspenders guard in _promote_pending_connection: inbound connections with no peer certificate are explicitly rejected and closed without invoking the user handler. connections_rejected is correctly incremented.

To-Do

  • Clean up commit history
  • Add or update documentation related to these changes
  • Add entry to the release notes

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.

QUIC: Inbound callback exposed before peer identity verification (authentication bypass)

1 participant