diff options
| author | tv <tv@krebsco.de> | 2026-10-22 20:37:34 +0200 |
|---|---|---|
| committer | tv <tv@krebsco.de> | 2026-10-22 20:37:34 +0200 |
| commit | d05e3b01af441ccf975152995701e9e29606bd11 (patch) | |
| tree | 2bd9e04cb89098c16c20021447ae19766044b34c /src/swarm/peer.rs | |
| parent | 0850433577942b8e65d3dacc060f2548b93d9c17 (diff) | |
introduce pre-shared swarm key
Diffstat (limited to 'src/swarm/peer.rs')
| -rw-r--r-- | src/swarm/peer.rs | 85 |
1 files changed, 85 insertions, 0 deletions
diff --git a/src/swarm/peer.rs b/src/swarm/peer.rs index 53a4731..d801b0f 100644 --- a/src/swarm/peer.rs +++ b/src/swarm/peer.rs @@ -237,6 +237,11 @@ async fn transmit_hello(state: &AppState, peer: &str, delay: Duration) -> Result peer, &ParsedMessage::Hello(transport::HelloRequest { epoch: state.local_epoch(), + auth: state + .swarm_key + .as_ref() + .map(|key| key.hello_tag(state.local_id(), peer)) + .unwrap_or_default(), }), ) .await; @@ -321,10 +326,16 @@ async fn send_hello_reply(state: &AppState, peer: &str) -> Result<()> { } } + let auth = state + .swarm_key + .as_ref() + .map(|key| key.hello_reply_tag(&swarm_id, state.local_id(), peer)) + .unwrap_or_default(); let reply = transport::HelloReply { swarm_id, peers, epoch: state.local_epoch(), + auth, }; let ready = state.is_ready().await; @@ -563,6 +574,76 @@ async fn observe_peer_epoch(state: &AppState, peer: &str, remote: Epoch) -> bool relation == EpochRelation::Same } +// --- swarm key ----------------------------------------------------------------- + +/// With a swarm key, only peers that proved it get past this point. A HELLO or +/// HELLO_REPLY must carry a valid tag, and it admits its sender; anything else is only +/// accepted from a peer admitted that way. Everything else is ignored without a reply, +/// so strangers learn nothing, not even whether we know the key. +async fn admit(state: &AppState, peer: &str, message: &ParsedMessage) -> bool { + let Some(key) = &state.swarm_key else { + return true; + }; + let local = state.local_id(); + + let proof = match message { + ParsedMessage::Hello(hello) => Some(key.verify_hello(&hello.auth, peer, local)), + ParsedMessage::HelloReply(reply) => { + Some(key.verify_hello_reply(&reply.auth, &reply.swarm_id, peer, local)) + } + _ => None, + }; + + match proof { + Some(true) => { + remember_peer(state, peer).await; + if let Some(peer_state) = state.get_peer_state(peer).await { + let mut peer_state = peer_state.lock().await; + if !peer_state.authenticated { + peer_state.authenticated = true; + debug!(peer = %peer, "Peer proved the swarm key"); + } + } + true + } + Some(false) => { + // A known peer with a wrong key (e.g. one that was given another key) loses + // its admission until it proves the right one, and with it its membership: + // we stop syncing our document to it, not just listening to it. + if let Some(peer_state) = state.get_peer_state(peer).await { + let mut peer_state = peer_state.lock().await; + if peer_state.authenticated { + warn!(peer = %peer, "Peer no longer proves the swarm key; dropping its membership"); + } + peer_state.authenticated = false; + peer_state.membership = PeerMembership::Unknown; + peer_state.hello_state = HelloState::NotSent; + peer_state.reset_sync(); + } + // The one place an operator can see why a node can't join: rate limited, + // since anyone can send us handshakes. + if let Some(suppressed) = key.note_rejection() { + info!( + peer = %peer, + suppressed, + "Rejected handshake without a valid swarm key proof" + ); + } + false + } + None => { + let admitted = match state.get_peer_state(peer).await { + Some(peer_state) => peer_state.lock().await.authenticated, + None => false, + }; + if !admitted { + trace!(peer = %peer, "Ignoring message from a peer that hasn't proven the swarm key"); + } + admitted + } + } +} + // --- inbound dispatch -------------------------------------------------------- pub(crate) async fn handle_message( @@ -575,6 +656,10 @@ pub(crate) async fn handle_message( return Ok(()); } + if !admit(state, &peer, &message).await { + return Ok(()); + } + remember_peer(state, &peer).await; // Any inbound message proves liveness. |
