From 667e1fb156aa1dfad68388f95082d87807898a3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Cig=C3=A1nek?= Date: Mon, 7 Dec 2020 16:27:36 +0100 Subject: [PATCH] fix: do not require resource proof for relocated node + test --- src/routing/approved.rs | 71 +++++++++++++++++++---------- src/routing/tests/mod.rs | 96 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 144 insertions(+), 23 deletions(-) diff --git a/src/routing/approved.rs b/src/routing/approved.rs index 870e610fc8..7b9ebc99c2 100644 --- a/src/routing/approved.rs +++ b/src/routing/approved.rs @@ -1028,39 +1028,64 @@ impl Approved { (MIN_AGE + 1, None, None) }; - if let Some(ResourceProofResponse { - solution, - data, - nonce, - nonce_signature, - }) = join_request.resource_proof_response - { - let serialized = bincode::serialize(&(*peer.name(), nonce))?; - if self - .node - .keypair - .public - .verify(&serialized, &nonce_signature) - .is_ok() - && self.resource_proof.validate_all(&nonce, &data, solution) - { - return self.vote(Vote::Online { - member_info: MemberInfo::joined(peer.with_age(age)), - previous_name, - their_knowledge, - }); + // Require resource proof only if joining as a new node. + if previous_name.is_none() { + if let Some(response) = join_request.resource_proof_response { + if !self.validate_resource_proof_response(peer.name(), response) { + debug!( + "Ignoring JoinRequest from {} - invalid resource proof response", + pub_id + ); + return Ok(vec![]); + } + } else { + return Ok(vec![self.send_resource_proof_challenge(&peer)?]); } } + self.vote(Vote::Online { + member_info: MemberInfo::joined(peer.with_age(age)), + previous_name, + their_knowledge, + }) + } + + fn validate_resource_proof_response( + &self, + peer_name: &XorName, + response: ResourceProofResponse, + ) -> bool { + let serialized = if let Ok(serialized) = bincode::serialize(&(peer_name, &response.nonce)) { + serialized + } else { + return false; + }; + + if self + .node + .keypair + .public + .verify(&serialized, &response.nonce_signature) + .is_err() + { + return false; + } + + self.resource_proof + .validate_all(&response.nonce, &response.data, response.solution) + } + + fn send_resource_proof_challenge(&self, peer: &Peer) -> Result { let nonce: [u8; 32] = rand::random(); - let serialized = bincode::serialize(&(*peer.name(), nonce))?; + let serialized = bincode::serialize(&(peer.name(), &nonce))?; let response = Variant::ResourceChallenge { data_size: RESOURCE_PROOF_DATA_SIZE, difficulty: RESOURCE_PROOF_DIFFICULTY, nonce, nonce_signature: crypto::sign(&serialized, &self.node.keypair), }; - Ok(vec![self.send_direct_message(peer.addr(), response)?]) + + self.send_direct_message(peer.addr(), response) } fn handle_dkg_start( diff --git a/src/routing/tests/mod.rs b/src/routing/tests/mod.rs index 3a7982b77d..1cbb83ee0a 100644 --- a/src/routing/tests/mod.rs +++ b/src/routing/tests/mod.rs @@ -24,6 +24,8 @@ use crate::{ peer::Peer, relocation, relocation::RelocateDetails, + relocation::RelocatePayload, + relocation::SignedRelocateDetails, section::{ test_utils::*, EldersInfo, MemberInfo, PeerState, Section, SectionKeyShare, SectionProofChain, MIN_AGE, @@ -186,6 +188,100 @@ async fn receive_join_request_with_resource_proof_response() -> Result<()> { Ok(()) } +#[tokio::test] +async fn receive_join_request_from_relocated_node() -> Result<()> { + let (elders_info, mut nodes) = create_elders_info(); + + let sk_set = SecretKeySet::random(); + let pk_set = sk_set.public_keys(); + let section_key = pk_set.public_key(); + + let (section, section_key_share) = create_section(&sk_set, &elders_info)?; + let node = nodes.remove(0); + let state = Approved::new( + node, + section, + Some(section_key_share), + mpsc::unbounded_channel().0, + ); + let stage = Stage::new(state, create_comm()?); + + let relocated_node_old_keypair = crypto::gen_keypair(); + let relocated_node_old_name = crypto::name(&relocated_node_old_keypair.public); + let relocated_node = Node::new(crypto::gen_keypair(), gen_addr()).with_age(MIN_AGE + 2); + + let relocate_details = RelocateDetails { + pub_id: relocated_node_old_name, + destination: rand::random(), + destination_key: section_key, + age: relocated_node.age, + }; + + let relocate_message = PlainMessage { + src: Prefix::default(), + dst: DstLocation::Node(relocated_node_old_name), + dst_key: section_key, + variant: Variant::Relocate(relocate_details), + }; + let signature = sk_set + .secret_key() + .sign(&bincode::serialize(&relocate_message.as_signable())?); + let proof_chain = SectionProofChain::new(section_key); + let relocate_message = Message::section_src(relocate_message, signature, proof_chain)?; + let relocate_details = SignedRelocateDetails::new(relocate_message)?; + let relocate_payload = RelocatePayload::new( + relocate_details, + &relocated_node.name(), + &relocated_node_old_keypair, + )?; + + let join_request = Message::single_src( + &relocated_node, + DstLocation::Direct, + Variant::JoinRequest(Box::new(JoinRequest { + section_key, + relocate_payload: Some(relocate_payload), + resource_proof_response: None, + })), + None, + None, + )?; + + let commands = stage + .handle_command(Command::HandleMessage { + sender: Some(relocated_node.addr), + message: join_request, + }) + .await?; + + let mut online_voted = false; + + for command in commands { + let vote = match command { + Command::HandleVote { vote, .. } => vote, + _ => continue, + }; + + if let Vote::Online { + member_info, + previous_name, + their_knowledge, + } = vote + { + assert_eq!(member_info.peer, relocated_node.peer()); + assert_eq!(member_info.state, PeerState::Joined); + assert_eq!(previous_name, Some(relocated_node_old_name)); + assert_eq!(their_knowledge, Some(section_key)); + + online_voted = true; + } + } + + assert!(online_voted); + + Ok(()) +} + #[tokio::test] async fn accumulate_votes() -> Result<()> { let (elders_info, mut nodes) = create_elders_info();