Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(identify): validate public key from remote peer #5707

Merged
merged 8 commits into from
Dec 13, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ libp2p-dcutr = { version = "0.12.1", path = "protocols/dcutr" }
libp2p-dns = { version = "0.42.0", path = "transports/dns" }
libp2p-floodsub = { version = "0.45.0", path = "protocols/floodsub" }
libp2p-gossipsub = { version = "0.48.0", path = "protocols/gossipsub" }
libp2p-identify = { version = "0.46.0", path = "protocols/identify" }
libp2p-identify = { version = "0.46.1", path = "protocols/identify" }
libp2p-identity = { version = "0.2.10" }
libp2p-kad = { version = "0.47.0", path = "protocols/kad" }
libp2p-mdns = { version = "0.46.0", path = "protocols/mdns" }
Expand Down
4 changes: 4 additions & 0 deletions protocols/identify/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.46.1
- Discard `Info`s received from remote peers that contain a public key that doesn't match their peer ID.
See [PR 5707](https://github.com/libp2p/rust-libp2p/pull/5707).

## 0.46.0

- Make `identify::Config` fields private and add getter functions.
Expand Down
2 changes: 1 addition & 1 deletion protocols/identify/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name = "libp2p-identify"
edition = "2021"
rust-version = { workspace = true }
description = "Nodes identification protocol for libp2p"
version = "0.46.0"
version = "0.46.1"
authors = ["Parity Technologies <admin@parity.io>"]
license = "MIT"
repository = "https://github.com/libp2p/rust-libp2p"
Expand Down
29 changes: 19 additions & 10 deletions protocols/identify/src/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -242,10 +242,18 @@ impl Handler {
}
}

fn handle_incoming_info(&mut self, info: &Info) {
/// If the public key matches the remote peer, handles the given `info` and returns `true`.
fn handle_incoming_info(&mut self, info: &Info) -> bool {
let derived_peer_id = info.public_key.to_peer_id();
if self.remote_peer_id != derived_peer_id {
tracing::warn!(%self.remote_peer_id, ?info.public_key, %derived_peer_id, "Discarding received identify message as public key does not match remote peer ID");
return false;
}

self.remote_info.replace(info.clone());

self.update_supported_protocols_for_remote(info);
true
}

fn update_supported_protocols_for_remote(&mut self, remote_info: &Info) {
Expand Down Expand Up @@ -346,11 +354,11 @@ impl ConnectionHandler for Handler {

match self.active_streams.poll_unpin(cx) {
Poll::Ready(Ok(Ok(Success::ReceivedIdentify(remote_info)))) => {
self.handle_incoming_info(&remote_info);

return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour(Event::Identified(
remote_info,
)));
if self.handle_incoming_info(&remote_info) {
return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour(
Event::Identified(remote_info),
));
}
}
jxs marked this conversation as resolved.
Show resolved Hide resolved
Poll::Ready(Ok(Ok(Success::SentIdentifyPush(info)))) => {
return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour(
Expand All @@ -365,11 +373,12 @@ impl ConnectionHandler for Handler {
Poll::Ready(Ok(Ok(Success::ReceivedIdentifyPush(remote_push_info)))) => {
if let Some(mut info) = self.remote_info.clone() {
info.merge(remote_push_info);
self.handle_incoming_info(&info);

return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour(
Event::Identified(info),
));
if self.handle_incoming_info(&info) {
return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour(
Event::Identified(info),
));
}
};
}
Poll::Ready(Ok(Err(e))) => {
Expand Down
2 changes: 1 addition & 1 deletion protocols/identify/src/protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ pub const PUSH_PROTOCOL_NAME: StreamProtocol = StreamProtocol::new("/ipfs/id/pus
/// Identify information of a peer sent in protocol messages.
#[derive(Debug, Clone)]
pub struct Info {
/// The public key of the local peer.
/// The public key of the peer.
pub public_key: PublicKey,
/// Application-specific version of the protocol family used by the peer,
/// e.g. `ipfs/1.0.0` or `polkadot/1.0.0`.
Expand Down
43 changes: 43 additions & 0 deletions protocols/identify/tests/smoke.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use std::{

use futures::StreamExt;
use libp2p_identify as identify;
use libp2p_identity::Keypair;
use libp2p_swarm::{Swarm, SwarmEvent};
use libp2p_swarm_test::SwarmExt;
use tracing_subscriber::EnvFilter;
Expand Down Expand Up @@ -440,3 +441,45 @@ async fn configured_interval_starts_after_first_identify() {

assert!(time_to_first_identify < identify_interval)
}

#[async_std::test]
async fn reject_mismatched_public_key() {
let _ = tracing_subscriber::fmt()
.with_env_filter(EnvFilter::from_default_env())
.try_init();
jxs marked this conversation as resolved.
Show resolved Hide resolved

let mut honest_swarm = Swarm::new_ephemeral(|identity| {
identify::Behaviour::new(
identify::Config::new("a".to_string(), identity.public())
.with_interval(Duration::from_secs(1)),
)
});
let mut spoofing_swarm = Swarm::new_ephemeral(|_unused_identity| {
let arbitrary_public_key = Keypair::generate_ed25519().public();
identify::Behaviour::new(
identify::Config::new("a".to_string(), arbitrary_public_key)
.with_interval(Duration::from_secs(1)),
)
});

honest_swarm.listen().with_memory_addr_external().await;
spoofing_swarm.connect(&mut honest_swarm).await;

spoofing_swarm
.wait(|event| {
matches!(event, SwarmEvent::Behaviour(identify::Event::Sent { .. })).then_some(())
})
.await;

let honest_swarm_events = futures::stream::poll_fn(|cx| honest_swarm.poll_next_unpin(cx))
.take(4)
.collect::<Vec<_>>()
.await;

assert!(
!honest_swarm_events
.iter()
.any(|e| matches!(e, SwarmEvent::Behaviour(identify::Event::Received { .. }))),
"should emit no received events as received public key won't match remote peer",
);
}