summaryrefslogtreecommitdiffstats
path: root/src/swarm/peer.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/swarm/peer.rs')
-rw-r--r--src/swarm/peer.rs85
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.