fix(relay): wire issuer_verifying_key to relay's own signing key (bug #123 D3) - #23
fix(relay): wire issuer_verifying_key to relay's own signing key (bug #123 D3)#23David Mireles (louzt) wants to merge 2 commits into
Conversation
Adds §Layer 0: SnapPipe-gated QUIC to OPERATIONAL-DEPLOYMENT.md. Cross-links to the 5-tier gist case study and to src/relay/listener.rs. Documents the ordered operations: 5-tier fallback → SnapPipe relay accept loop → server_handshake on stream 0 → streams 1..N forwarded. No code changes.
… trust store lookup The QUIC listener previously passed a hardcoded zeroed ed25519 key as both the issuer_verifying_key and expected_subject for SnapPipe ticket validation. That zeroed key is a well-known public key (all bytes zero), so any ticket signed by anyone could pass subject validation. The intent was to validate that the ticket was issued by THIS relay, but the implementation was broken. This fix: - Adds Relay::signing_key() accessor exposing the relay's long-term signing key (the same key used by issue_ticket) - Adds RelayConfig::with_signing_key() builder for production pinning - Defaults RelayConfig::new() to a fresh random key (preserves existing test ergonomics) - Wires listener::handle_connection to use relay.signing_key() for both issuer_verifying_key and expected_subject Two regression tests: - relay_exposes_signing_key_and_with_signing_key_overrides: verifies the getter and builder - relay_signing_key_is_not_the_dummy_zeroed_key: locks in the negative invariant so the zeroed-key bug cannot regress silently Validation: - 60 unit tests + 4 integration_trust_sync + 1 quic_e2e + 1 relay_listener_e2e pass - cargo clippy --lib -- -D warnings clean - cargo clippy --tests (selected) -- -D warnings clean Refs: bug #123 (D3), follow-up to PR #32 (C-001)
📝 WalkthroughWalkthroughThe relay now owns an explicit signing key for ticket validation and identity derivation, tracks active connection sessions, and uses that key during SnapPipe handshakes. Deployment documentation describes the SnapPipe-gated QUIC listener and fallback flow. ChangesRelay identity and lifecycle
Estimated code review effort: 3 (Moderate) | ~20 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/relay/mod.rs (1)
420-475: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd a test for
active_sessionscounter balance.The new
active_sessionstracking is exercised by existing tests indirectly (they callhandle_connection), but no test asserts the counter returns to zero after completion or is non-zero during a connection. A simple post-connection assertion would verify the increment/decrement pair is balanced.🧪 Suggested test for active_sessions balance
#[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn active_sessions_returns_to_zero_after_connection() { let trust = Arc::new(TrustStore::new()); let limiter = Arc::new(RateLimiter::new(100)); let config = RelayConfig::new("127.0.0.1:0".parse().unwrap(), trust, limiter); let relay = Relay::new(config); assert_eq!(relay.active_sessions(), 0, "should start at zero"); let peer = node(); let incoming = MemoryStream::with_payload(b"ping".to_vec()); let outgoing = MemoryStream::default(); let _log = relay .handle_connection(peer, incoming, outgoing, 1_000.0, || 1_000.0) .await .unwrap(); assert_eq!(relay.active_sessions(), 0, "should return to zero after completion"); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/relay/mod.rs` around lines 420 - 475, Add an async regression test near the existing relay tests, using handle_connection to process a MemoryStream connection and asserting active_sessions() is zero before and after completion. Follow the suggested setup with RelayConfig, node(), MemoryStream, and appropriate tokio test attributes to verify the increment/decrement balance.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@src/relay/mod.rs`:
- Around line 420-475: Add an async regression test near the existing relay
tests, using handle_connection to process a MemoryStream connection and
asserting active_sessions() is zero before and after completion. Follow the
suggested setup with RelayConfig, node(), MemoryStream, and appropriate tokio
test attributes to verify the increment/decrement balance.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 65e9dba0-1e19-4c21-a666-09a9c3e9602d
📒 Files selected for processing (3)
docs/OPERATIONAL-DEPLOYMENT.mdsrc/relay/listener.rssrc/relay/mod.rs
Bug #123 (D3) — issuer_verifying_key wired to relay key (was zeroed dummy)
Summary
Replaces the hardcoded zeroed ed25519 key in
listener::handle_connectionwiththe relay's actual long-term signing key. Closes a critical auth gap exposed
during the PR #32 follow-up review.
Root cause
src/relay/listener.rs:138-145previously constructed the issuerverifying-key and expected-subject for
server_handshakefrom a hardcodedzeroed
SigningKey::from_bytes(&[0u8; 32]). That zeroed key is a well-knownpublic key — its verifying-key is the bytes
00..00(32 of them). Any ticketwhose
subjectfield equals the all-zero ed25519 verifying-key (which istrivial to forge or set via
with_zeroed_subject()) would pass subjectvalidation against this server.
The intent was to validate that the ticket was issued by this relay.
The implementation accepted any ticket whose subject was the all-zero
verifying-key, defeating the entire trust-on-first-use model.
Fix
RelayConfignow owns asigning_key: SigningKey.Relay::signing_key()exposes it;RelayConfig::with_signing_key()allows production callers (e.g.
main.rs) to pin a deterministic key.RelayConfig::new()defaults togenerate_signing_key()— fresh key perprocess, preserving existing test ergonomics (no shared state across
processes in unit tests).
listener::handle_connectionnow passesrelay.signing_key().verifying_key()as both the
issuer_verifying_keyandexpected_subjecttoserver_handshake.Tests added
Two regression tests in
src/relay/mod.rs:relay_exposes_signing_key_and_with_signing_key_overrides— verifies theaccessor and the builder.
relay_signing_key_is_not_the_dummy_zeroed_key— locks the negativeinvariant: the relay's signing key MUST NOT be the all-zero key. This
test would fail loudly if anyone reintroduces the dummy_key pattern.
Validation
cargo build --lib— cleancargo test --lib— 60 passedcargo test --test integration_trust_sync --test quic_e2e --test relay_listener_e2e— 6 passed (4+1+1)cargo clippy --lib -- -D warnings— cleancargo clippy --tests -- -D warnings— clean (selected tests; quic_rebind_observer_e2e excluded — belongs to a separate branch)Out of scope (carried-over gaps, not part of this PR)
listener::handle_connectiondoes not yet use the trust store to look upthe subject's verifying-key for downstream authorization (it currently uses
the relay's own key as both issuer and subject). This is correct for the
self-ticket model but does not yet implement a trust-store-based issuer
resolution. Tracked as bug #124 (D4) — separate PR.
conn.close(0u8.into(), ...)after handshake failure uses error code 0.Should be
APPLICATION_ERROR(e.g.0x01). Tracked as bug #125.References
~/.claude/plans/shiny-petting-puppy.mdSummary by CodeRabbit
New Features
Bug Fixes
Documentation