Skip to content

Release 1.3.1#484

Merged
lucassaldanha merged 5 commits into
libp2p:masterfrom
lucassaldanha:1.3.1
Jun 4, 2026
Merged

Release 1.3.1#484
lucassaldanha merged 5 commits into
libp2p:masterfrom
lucassaldanha:1.3.1

Conversation

@lucassaldanha

Copy link
Copy Markdown
Collaborator

No description provided.

lucassaldanha and others added 5 commits June 4, 2026 21:35
* fix(pubsub): verify message from matches signing key and prevent seen-cache poisoning

Closes a strict-sign origin spoofing vulnerability in the pubsub stack. A peer
with any valid signing key could publish a signed message whose `from` named a
different peer; receivers attributed the message to the impersonated peer and
the forged `from || seqno` poisoned the seen-cache, suppressing the legitimate
follow-up message from the real author.

Three changes:

* PubsubCrypto.pubsubValidate now rejects messages missing/empty `from`, `key`
  or `signature`, returns false (never throws) on un-parseable key/from, and
  requires `PeerId.fromPubKey(msg.key) == PeerId(msg.from)` in addition to the
  existing signature check.

* PubsubApiImpl.PublisherImpl.publishExt rejects any caller-supplied `from`
  that does not match the publisher's signing key, throwing
  IllegalArgumentException. Null `from` continues to default to the signing
  key's PeerId as before; unsigned publishers (no privKey) are unchanged.

* AbstractRouter no longer leaves a seenMessages entry behind for a message
  that fails validation. On both sync and async validation failure the entry
  inserted speculatively at the start of `onInbound` is evicted, so a later
  legitimate message with the same id is still processed and delivered.

Adds PubsubStrictSignSpoofTest covering forged-from rejection, missing-field
rejection, publishExt mismatch rejection, the matching-from happy path, and an
end-to-end regression test that the seen-cache is no longer poisoned by a
rejected forgery.
* fix(quic): use per-connection SSL session for server cert extraction

Replace shared Libp2pTrustManager.remoteCert field reads with
per-connection sslEngine().session.peerCertificates to eliminate
race condition under concurrent inbound connections. Add
concurrentInboundConnections test to verify correctness.

* fix(quic): support PeerId-less dial and extract identity from TLS cert

Replace trustManager.remoteCert!! NPE-prone access in dial() with
sslEngine().session.peerCertificates. When no PeerId is provided in
the multiaddr, extract remotePeerId and remotePubKey from the TLS cert
post-handshake. Add dialWithoutPeerIdExtractsPeerIdFromCert test.

* feat(quic): introduce QuicConfig with corrected flow control defaults

Add QuicConfig data class with sensible defaults (15MB connection data,
10MB per stream, 256 bidirectional streams, 30s idle timeout). Wire
config into both client and server codec builders, replacing hardcoded
1MB/64-streams values. Update factory methods to accept optional config.

* feat(quic): add deterministic stateless reset token derived from identity key

Derive a 16-byte stateless reset token from SHA-256(localKey.raw() +
"libp2p-quic-stateless-reset"), truncated to 16 bytes. Register with
both client and server QUIC codec builders so peers can cleanly close
connections if our node restarts.

* fix(quic): disable unidirectional streams and connection migration for spec compliance

Set initialMaxStreamsUnidirectional(0) since libp2p QUIC exclusively uses
bidirectional streams. Disable activeMigration(false) until address-change
handling is implemented. Add comment about spin bit being quiche's default.

* docs(quic): document that private networks (PSK) are not supported

Add KDoc to QuicTransport class explaining that QUIC v1 uses mandatory
TLS 1.3 which cannot be replaced with a pre-shared key scheme, so PSK
private networks are incompatible with this transport.

* feat(quic): track listener channels and attempt port reuse for outbound connections

Track active listener DatagramChannels by address family in
listenerChannelsByFamily map. When dialing, attempt to bind the client
UDP socket to the same port as the active listener (SO_REUSEADDR) for
consistent NAT mappings. Falls back to ephemeral port if binding fails.
TODO: add SO_REUSEPORT for Linux/Epoll.

* feat(quic): add hole punching foundation with UDP probes and inbound connection routing

Add pendingHolePunches map to route inbound QUIC connections to waiting
dialAsListener callers. dialAsListener sends UDP probe datagrams at 50ms
intervals from the active listener socket to open NAT mappings, then waits
up to 5 seconds for the remote peer to connect back. Add holePunchTimeout
test to verify the timeout path.

* fix(quic): replace Java 12 exceptionallyCompose with JDK 11 compatible handle+thenCompose

* fix(quic): set CONNECTION attribute at channel-init time on client side

Previously, ConnectionOverNetty and QuicMuxerSession were created in the
thenApply callback after the QUIC connection future completed. This meant
InboundStreamHandler.initChannel() could be called on an inbound stream
before ch.parent().attr(CONNECTION) was set, yielding null and causing a
NullPointerException (surfacing as NoSuchElementException via Optional.get()
in the stream pipeline).

Mirror the server-side pattern: attach a .handler() on QuicChannel.newBootstrap()
that creates ConnectionOverNetty and QuicMuxerSession at channel-init time.
ConnectionOverNetty.init() sets ch.attr(CONNECTION) immediately, so any
inbound stream handler can always find a non-null CONNECTION on its parent.

* refactor(quic): replace Boolean map key with AddressFamily enum

@StefanBratanov StefanBratanov left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM

@lucassaldanha lucassaldanha merged commit 31f8bcc into libp2p:master Jun 4, 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.

3 participants