Skip to content

fix: session-lifecycle hardening — path-switch re-handshake (#36) + cert revocation (#41)#95

Merged
vxfemboy merged 15 commits into
mainfrom
feat/session-lifecycle-hardening
Jul 23, 2026
Merged

fix: session-lifecycle hardening — path-switch re-handshake (#36) + cert revocation (#41)#95
vxfemboy merged 15 commits into
mainfrom
feat/session-lifecycle-hardening

Conversation

@vxfemboy

Copy link
Copy Markdown
Member

Closes #36 and #41. Two independent session-lifecycle fixes in bin/yipd, both unblocked by the merged 9a/#91 rekey machinery. No wire change; yip-crypto/yip-wire/handshake.rs untouched; both are no-ops with membership off / no handshake in flight (2a/2b/2c byte-identical).

#36 — path-switch re-initiation half-opens a session

A path switch (Punch→Relay, Punch C1→C2) drew a fresh Noise ephemeral, so a responder already Established over the old path only replayed a cached_resp keyed to the original ephemeral → the initiator never completed → silent bidirectional black hole with the remote falsely "up".

  • retarget_handshake preserves the in-flight ephemeral across a re-target (resend the existing init_pkt instead of begin_handshake minting fresh). started_ms is not reset (the 90 s give-up still bounds ephemeral reuse); last_sent_ms is reset (no redundant back-to-back Init); the relay branch clears endpoint, the direct branch re-stamps it (so handle_handshake_resp matches the reply). The two tick escalation arms call it.
  • Responder-side relay adoption: 9a follow-up: relay-path session rekey (relay-only sessions get no forward-secrecy rotation) #91's path-consistency gate would otherwise make a direct-established responder drop the initiator's relayed Init. So a direct-established peer receiving a relayed cold-start retransmit (init_eph == cached_resp_init_eph — proven the original peer, harmless replay) adopts the relay for its egress and replays cached_resp, closing the headline scenario. A relayed Init with a new ephemeral against a direct peer stays 9a follow-up: relay-path session rekey (relay-only sessions get no forward-secrecy rotation) #91 fail-closed.
  • Accepted tradeoff (rides with Handshake anti-replay (timestamp) + authenticated endpoint learning #34): an attacker replaying A's captured original Init over the relay forces a persistent direct→relay path downgrade (not a hijack — relay_wrap addresses the peer's registered node; current's keys unchanged) and degrades direct rekey. Documented in the spec.

#41 — cert revocation lag exceeds cert lifetime

A revoked (cert-expired, non-renewed) mesh member kept its Established session until process restart, because rekey and cold-start re-admission both re-used the session identity without re-checking the cert.

  • drop_session — tear down a peer's session (remove its current/next/previous conn_tags from by_tag, revert to Idle).
  • Rekey re-verify — a rekey Init must carry a currently-valid cert, else the live session is dropped.
  • Re-admission gate — a tabled peer re-establishing must present a valid cert (without this, drop_session + the sweep only flap, not revoke).
  • Periodic liveness sweep — throttled to once per rekey interval, drops any Established mesh peer whose cert has expired/been revoked — catching the revoked rekey loser that never sends an Init.
  • A shared is_root helper exempts always-admit roots from all cert gates (roots are trusted via the signed root set, not a member cert). No-op when membership is None.

Verification

Known follow-up

For symmetry with the new gates, the pre-existing initiator-completion cert checks (handle_handshake_resp / relayed_handshake_resp) could also gain the is_root exemption — latent only (roots present valid certs in practice), out of this branch's delta. Filed separately.

vxfemboy added 13 commits July 22, 2026 17:05
#36: preserve the in-flight Noise ephemeral across a path re-target (resend
the existing init_pkt instead of begin_handshake minting a fresh one) so a
responder already Established on the old path completes us via its cached_resp
— closes the path-switch half-open black hole without touching the anti-hijack
gate or needing #34.

#41: re-verify the peer's cert in a received rekey Init (drop the session on
failure) + a periodic cert-liveness sweep on Established mesh peers (roots
exempt) so a revoked/expired member's session drops within a rekey interval
rather than at process restart.

Both no-ops when membership is off / no handshake in flight; no wire change.
… + #41)

4 tasks, subagent-driven, TDD: (1) #36 retarget_handshake helper + wire the two
tick escalation arms (preserve the ephemeral, don't mint fresh); (2) #41 drop_session
+ re-verify cert on rekey Init; (3) #41 member_cert_valid sweep (roots exempt,
throttled); (4) netns money tests both drivers + CI.
…line punch->relay scenario)

Ephemeral preservation (Task 1) fixes A's side but #91's path-consistency gate
blocks B's: a direct-established responder drops A's relayed cold-start Init. Add
one adoption case — a relayed cold-start RETRANSMIT (init_eph == cached_resp_init_eph)
against a relay==false peer adopts relay for egress + replays cached_resp over the
relay. New-ephemeral relayed Inits vs a direct peer stay #91 fail-closed. Accepted
tradeoff: an attacker replaying a captured Init forces a direct->relay path
downgrade (data still reaches the real peer; rides with #34).
…reset, spec tradeoff precision

Review of the complete #36 fix (APPROVED, 2 Important + 2 Minor, non-blocking):
- Important: update Peer.relay field doc for the #36 Established-adoption exception.
- Important: spec tradeoff prose now states the forced downgrade is persistent for
  the session AND degrades direct rekey (rides with #34).
- Minor: retarget_handshake resets last_sent_ms=now so no immediate redundant
  retransmit (anti-DPI timing); test asserts it.
- Minor: spec retarget behavior now documents the endpoint clear/re-stamp asymmetry.
…eers + accurate recovery bound

Task 3 review Minor: member_cert_valid is directory-keyed (populated only by
gossip ingest_record, not admit_member), so a validly cold-start-admitted peer
whose Record hasn't gossiped in can be swept. Bounded/recoverable (re-admission
re-verifies the presented cert), but recovery is ~rekey_interval/2 via the
responder's accept_rekey_init age gate, not instant.
…cert revocation

yip-ca sign-cert gains --secs N (sub-day validity) for minting short-lived certs
in the revocation test. Two netns money tests (both drivers, CI-wired):
- run-netns-pathswitch-rehandshake.sh (#36): A escalates punch->relay while B is
  direct-established; asserts A converges over the relay carrying the SAME
  ephemeral (DISTINCT_INIT_EPHEMERALS=1, no cold-start churn) + relay-forwarded>0.
- run-netns-cert-revocation.sh (#41): A's short-lived cert expires; asserts B
  drops A's session within a bounded window AND A cannot re-establish (re-admission
  gate refuses the expired cert).

All four runs (2 tests x poll+uring) pass under sudo.
…certs too; roots exempted by caller)

Final-review Minor: the doc had an orphaned handle_handshake_init block + a
'responder's msg2' framing that this branch's new #41 call sites (initiator
msg1 cert, is_root-exempt) made inaccurate.
vxfemboy added 2 commits July 22, 2026 22:56
…(CI fix)

CI netns-tunnel-test failed: the #41 test (a) waited out the daemon's 300s
CLOCK_SKEW grace (>5min) and (b) raced mesh discovery with a single establish
ping that didn't converge on the slower CI runner.

- membership.rs: add YIP_CERT_SKEW_SECS env override (default 300, like
  YIP_REKEY_INTERVAL_MS) so a cert can expire in seconds for the test; prod
  leaves it unset. Cached via OnceLock; 236 unit tests green at the default.
- run-netns-cert-revocation.sh: set YIP_CERT_SKEW_SECS=3 (whole run ~90s vs
  ~382s) and POLL the establish ping until discovery converges within the cert
  window (fixes the single-ping race).
- integration.yml: run #41 POLL-ONLY, matching its fork source run-netns-
  discovery.sh — gossip discovery convergence is flaky under io_uring and
  #41's cert-revocation logic is driver-agnostic. #36 stays both-driver.

Verified locally: #41 poll passes ~85s; #36 both drivers still pass.
@vxfemboy
vxfemboy merged commit 6bdbcdd into main Jul 23, 2026
9 checks passed
@vxfemboy
vxfemboy deleted the feat/session-lifecycle-hardening branch July 23, 2026 03:39
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.

2b: path-switch re-initiation can half-open a session (needs rekey/anti-replay)

1 participant