From 28c31fe4249c191f681af3b4e7dfa0c80d47cae3 Mon Sep 17 00:00:00 2001 From: John Smith Date: Mon, 14 Nov 2022 13:09:33 -0500 Subject: [PATCH] fix private routing 1.0 --- veilid-core/proto/veilid.capnp | 8 +- .../src/routing_table/route_spec_store.rs | 6 +- .../coders/private_safety_route.rs | 37 +++-- veilid-core/src/rpc_processor/destination.rs | 4 +- veilid-core/src/rpc_processor/rpc_route.rs | 139 +++++++++--------- veilid-core/src/veilid_api/privacy.rs | 59 +++++--- 6 files changed, 143 insertions(+), 110 deletions(-) diff --git a/veilid-core/proto/veilid.capnp b/veilid-core/proto/veilid.capnp index 1ce4c11a..05bd4de7 100644 --- a/veilid-core/proto/veilid.capnp +++ b/veilid-core/proto/veilid.capnp @@ -126,14 +126,18 @@ struct RouteHop @0xf8f672d75cce0c3b { nodeId @0 :NodeID; # node id only for established routes peerInfo @1 :PeerInfo; # full peer info for this hop to establish the route } - nextHop @2 :RouteHopData; # Optional: if the private route is a stub, it contains no route hop data, just the target node for the routed operation. + nextHop @2 :RouteHopData; # optional: If this the end of a private route, this field will not exist # if this is a safety route routehop, this field is not optional and must exist } struct PrivateRoute @0x8a83fccb0851e776 { publicKey @0 :RoutePublicKey; # private route public key (unique per private route) hopCount @1 :UInt8; # Count of hops left in the private route (for timeout calculation purposes only) - firstHop @2 :RouteHop; # Optional: first hop in the private route, if empty, this is the last hop and payload should be decrypted and processed. + hops :union { + firstHop @2 :RouteHop; # first hop of a private route is unencrypted (hopcount > 0) + data @3 :RouteHopData; # private route has more hops (hopcount > 0 && hopcount < total_hopcount) + empty @4 :Void; # private route has ended (hopcount = 0) + } } struct SafetyRoute @0xf554734d07cb5d59 { diff --git a/veilid-core/src/routing_table/route_spec_store.rs b/veilid-core/src/routing_table/route_spec_store.rs index 17fe558b..8d1c0cf3 100644 --- a/veilid-core/src/routing_table/route_spec_store.rs +++ b/veilid-core/src/routing_table/route_spec_store.rs @@ -854,8 +854,8 @@ impl RouteSpecStore { if pr_hopcount > max_route_hop_count { bail!("private route hop count too long"); } - let Some(pr_first_hop) = &private_route.first_hop else { - bail!("compiled private route should have first_hop"); + let PrivateRouteHops::FirstHop(pr_first_hop) = &private_route.hops else { + bail!("compiled private route should have first hop"); }; // See if we are using a safety route, if not, short circuit this operation @@ -1179,7 +1179,7 @@ impl RouteSpecStore { let private_route = PrivateRoute { public_key: key.clone(), hop_count: hop_count.try_into().unwrap(), - first_hop: Some(route_hop), + hops: PrivateRouteHops::FirstHop(route_hop), }; Ok(private_route) } diff --git a/veilid-core/src/rpc_processor/coders/private_safety_route.rs b/veilid-core/src/rpc_processor/coders/private_safety_route.rs index 76a280f5..1edbb9fd 100644 --- a/veilid-core/src/rpc_processor/coders/private_safety_route.rs +++ b/veilid-core/src/rpc_processor/coders/private_safety_route.rs @@ -106,11 +106,20 @@ pub fn encode_private_route( &mut builder.reborrow().init_public_key(), )?; builder.set_hop_count(private_route.hop_count); - if let Some(rh) = &private_route.first_hop { - let mut rh_builder = builder.reborrow().init_first_hop(); - encode_route_hop(rh, &mut rh_builder)?; + let mut h_builder = builder.reborrow().init_hops(); + match &private_route.hops { + PrivateRouteHops::FirstHop(first_hop) => { + let mut rh_builder = h_builder.init_first_hop(); + encode_route_hop(first_hop, &mut rh_builder)?; + } + PrivateRouteHops::Data(data) => { + let mut rhd_builder = h_builder.init_data(); + encode_route_hop_data(data, &mut rhd_builder)?; + } + PrivateRouteHops::Empty => { + h_builder.set_empty(()); + } }; - Ok(()) } @@ -121,19 +130,23 @@ pub fn decode_private_route( "invalid public key in private route", ))?); let hop_count = reader.get_hop_count(); - let first_hop = if reader.has_first_hop() { - let rh_reader = reader - .get_first_hop() - .map_err(RPCError::map_protocol("invalid first hop in private route"))?; - Some(decode_route_hop(&rh_reader)?) - } else { - None + + let hops = match reader.get_hops().which().map_err(RPCError::protocol)? { + veilid_capnp::private_route::hops::Which::FirstHop(rh_reader) => { + let rh_reader = rh_reader.map_err(RPCError::protocol)?; + PrivateRouteHops::FirstHop(decode_route_hop(&rh_reader)?) + } + veilid_capnp::private_route::hops::Which::Data(rhd_reader) => { + let rhd_reader = rhd_reader.map_err(RPCError::protocol)?; + PrivateRouteHops::Data(decode_route_hop_data(&rhd_reader)?) + } + veilid_capnp::private_route::hops::Which::Empty(_) => PrivateRouteHops::Empty, }; Ok(PrivateRoute { public_key, hop_count, - first_hop, + hops, }) } diff --git a/veilid-core/src/rpc_processor/destination.rs b/veilid-core/src/rpc_processor/destination.rs index 33647085..d7a10d0b 100644 --- a/veilid-core/src/rpc_processor/destination.rs +++ b/veilid-core/src/rpc_processor/destination.rs @@ -205,8 +205,8 @@ impl RPCProcessor { private_route, safety_selection, } => { - let Some(pr_first_hop) = &private_route.first_hop else { - return Err(RPCError::internal("destination private route must have first_hop")); + let PrivateRouteHops::FirstHop(pr_first_hop) = &private_route.hops else { + return Err(RPCError::internal("destination private route must have first hop")); }; match safety_selection { diff --git a/veilid-core/src/rpc_processor/rpc_route.rs b/veilid-core/src/rpc_processor/rpc_route.rs index 9f771708..3cffa743 100644 --- a/veilid-core/src/rpc_processor/rpc_route.rs +++ b/veilid-core/src/rpc_processor/rpc_route.rs @@ -72,26 +72,20 @@ impl RPCProcessor { #[instrument(level = "trace", skip_all, err)] async fn process_route_private_route_hop( &self, - mut route: RPCOperationRoute, - mut next_private_route: PrivateRoute, + mut routed_operation: RoutedOperation, + next_route_node: RouteNode, + safety_route_public_key: DHTKey, + next_private_route: PrivateRoute, ) -> Result<(), RPCError> { // Make sure hop count makes sense - if route.safety_route.hop_count != 0 { - return Err(RPCError::protocol( - "Safety hop count should be zero if switched to private route", - )); - } if next_private_route.hop_count as usize > self.unlocked_inner.max_route_hop_count { return Err(RPCError::protocol( "Private route hop count too high to process", )); } - // Get private route first hop (this is validated to not be None before calling this function) - let first_hop = next_private_route.first_hop.as_ref().unwrap(); - // Get next hop node ref - let next_hop_nr = match &first_hop.node { + let next_hop_nr = match &next_route_node { RouteNode::NodeId(id) => { // self.routing_table @@ -116,27 +110,24 @@ impl RPCProcessor { } }?; - if first_hop.next_hop.is_some() { + if !matches!(next_private_route.hops, PrivateRouteHops::Empty) { // Sign the operation if this is not our last hop // as the last hop is already signed by the envelope let node_id = self.routing_table.node_id(); let node_id_secret = self.routing_table.node_id_secret(); - let sig = sign(&node_id, &node_id_secret, &route.operation.data) + let sig = sign(&node_id, &node_id_secret, &routed_operation.data) .map_err(RPCError::internal)?; - route.operation.signatures.push(sig); - } else { - // If this is our last hop, then we drop the 'first_hop' from private route - // XXX ? next_private_route.first_hop = None; + routed_operation.signatures.push(sig); } // Pass along the route let next_hop_route = RPCOperationRoute { safety_route: SafetyRoute { - public_key: route.safety_route.public_key, + public_key: safety_route_public_key, hop_count: 0, hops: SafetyRouteHops::Private(next_private_route), }, - operation: route.operation, + operation: routed_operation, }; let next_hop_route_stmt = RPCStatement::new(RPCStatementDetail::Route(next_hop_route)); @@ -342,19 +333,25 @@ impl RPCProcessor { }; // Get the next hop node ref - if private_route.first_hop.is_some() { - // Switching to private route from safety route - self.process_route_private_route_hop(route, private_route) - .await?; - } else { - // Private route is empty, process routed operation - self.process_routed_operation( - detail, - route.operation, - &route.safety_route, - &private_route, - )?; - } + let PrivateRouteHops::FirstHop(pr_first_hop) = private_route.hops else { + return Err(RPCError::protocol("switching from safety route to private route requires first hop")); + }; + + // Switching to private route from safety route + self.process_route_private_route_hop( + route.operation, + pr_first_hop.node, + route.safety_route.public_key, + PrivateRoute { + public_key: private_route.public_key, + hop_count: private_route.hop_count - 1, + hops: pr_first_hop + .next_hop + .map(|rhd| PrivateRouteHops::Data(rhd)) + .unwrap_or(PrivateRouteHops::Empty), + }, + ) + .await?; } else if blob_tag == 0 { // RouteHop let route_hop = { @@ -373,18 +370,24 @@ impl RPCProcessor { // No safety route left, now doing private route SafetyRouteHops::Private(ref private_route) => { // See if we have a hop, if not, we are at the end of the private route - if let Some(first_hop) = &private_route.first_hop { - // See if we have next hop data - let opt_next_first_hop = if let Some(next_hop) = &first_hop.next_hop { + match &private_route.hops { + PrivateRouteHops::FirstHop(_) => { + return Err(RPCError::protocol("should not have first hop here")); + } + PrivateRouteHops::Data(route_hop_data) => { // Decrypt the blob with DEC(nonce, DH(the PR's public key, this hop's secret) let node_id_secret = self.routing_table.node_id_secret(); let dh_secret = self .crypto .cached_dh(&private_route.public_key, &node_id_secret) .map_err(RPCError::protocol)?; - let dec_blob_data = - Crypto::decrypt_aead(&next_hop.blob, &next_hop.nonce, &dh_secret, None) - .map_err(RPCError::protocol)?; + let dec_blob_data = Crypto::decrypt_aead( + &route_hop_data.blob, + &route_hop_data.nonce, + &dh_secret, + None, + ) + .map_err(RPCError::protocol)?; let dec_blob_reader = RPCMessageData::new(dec_blob_data).get_reader()?; // Decode next RouteHop @@ -394,40 +397,42 @@ impl RPCProcessor { .map_err(RPCError::protocol)?; decode_route_hop(&rh_reader)? }; - Some(route_hop) - } else { - // If the first hop has no RouteHopData, then this is a stub private route - // and we should just pass the operation to its final destination with - // an empty safety and private route - None - }; - // Ensure hop count > 0 - if private_route.hop_count == 0 { - return Err(RPCError::protocol("route should not be at the end")); - } + // Ensure hop count > 0 + if private_route.hop_count == 0 { + return Err(RPCError::protocol("route should not be at the end")); + } - // Make next PrivateRoute and pass it on - let next_private_route = PrivateRoute { - public_key: private_route.public_key, - hop_count: private_route.hop_count - 1, - first_hop: opt_next_first_hop, - }; - self.process_route_private_route_hop(route, next_private_route) + // Make next PrivateRoute and pass it on + self.process_route_private_route_hop( + route.operation, + route_hop.node, + route.safety_route.public_key, + PrivateRoute { + public_key: private_route.public_key, + hop_count: private_route.hop_count - 1, + hops: route_hop + .next_hop + .map(|rhd| PrivateRouteHops::Data(rhd)) + .unwrap_or(PrivateRouteHops::Empty), + }, + ) .await?; - } else { - // Ensure hop count == 0 - if private_route.hop_count != 0 { - return Err(RPCError::protocol("route should be at the end")); } + PrivateRouteHops::Empty => { + // Ensure hop count == 0 + if private_route.hop_count != 0 { + return Err(RPCError::protocol("route should be at the end")); + } - // No hops left, time to process the routed operation - self.process_routed_operation( - detail, - route.operation, - &route.safety_route, - private_route, - )?; + // No hops left, time to process the routed operation + self.process_routed_operation( + detail, + route.operation, + &route.safety_route, + private_route, + )?; + } } } } diff --git a/veilid-core/src/veilid_api/privacy.rs b/veilid-core/src/veilid_api/privacy.rs index a3c04603..f3cac31c 100644 --- a/veilid-core/src/veilid_api/privacy.rs +++ b/veilid-core/src/veilid_api/privacy.rs @@ -3,15 +3,21 @@ use super::*; //////////////////////////////////////////////////////////////////////////////////////////////////// // Compiled Privacy Objects +/// An encrypted private/safety route hop #[derive(Clone, Debug)] pub struct RouteHopData { + /// The nonce used in the encryption ENC(Xn,DH(PKn,SKapr)) pub nonce: Nonce, + /// The encrypted blob pub blob: Vec, } +/// How to find a route node #[derive(Clone, Debug)] pub enum RouteNode { + /// Route node is optimized, no contact method information as this node id has been seen before NodeId(NodeId), + /// Route node with full contact method information to ensure the peer is reachable PeerInfo(PeerInfo), } impl fmt::Display for RouteNode { @@ -27,17 +33,33 @@ impl fmt::Display for RouteNode { } } +/// An unencrypted private/safety route hop #[derive(Clone, Debug)] pub struct RouteHop { + /// The location of the hop pub node: RouteNode, + /// The encrypted blob to pass to the next hop as its data (None for stubs) pub next_hop: Option, } +/// The kind of hops a private route can have +#[derive(Clone, Debug)] +pub enum PrivateRouteHops { + /// The first hop of a private route, unencrypted, route_hops == total hop count + FirstHop(RouteHop), + /// Private route internal node. Has > 0 private route hops left but < total hop count + Data(RouteHopData), + /// Private route has ended (hop count = 0) + Empty, +} + +/// A private route for receiver privacy #[derive(Clone, Debug)] pub struct PrivateRoute { + /// The public key used for the entire route pub public_key: DHTKey, pub hop_count: u8, - pub first_hop: Option, + pub hops: PrivateRouteHops, } impl PrivateRoute { @@ -46,7 +68,7 @@ impl PrivateRoute { Self { public_key, hop_count: 0, - first_hop: None, + hops: PrivateRouteHops::Empty, } } /// Stub route is the form used when no privacy is required, but you need to specify the destination for a safety route @@ -54,29 +76,12 @@ impl PrivateRoute { Self { public_key, hop_count: 1, - first_hop: Some(RouteHop { + hops: PrivateRouteHops::FirstHop(RouteHop { node, next_hop: None, }), } } - /// Switch from full node info to simple node id - /// Only simplified single hop, primarily useful for stubs - /// Published routes with >= 1 hops should be in NodeId form already, as they have - /// already been connectivity-verified by the time the route is published - pub fn simplify(self) -> Self { - Self { - public_key: self.public_key, - hop_count: self.hop_count, - first_hop: self.first_hop.map(|h| RouteHop { - node: match h.node { - RouteNode::NodeId(ni) => RouteNode::NodeId(ni), - RouteNode::PeerInfo(pi) => RouteNode::NodeId(pi.node_id), - }, - next_hop: h.next_hop, - }), - } - } } impl fmt::Display for PrivateRoute { @@ -86,10 +91,16 @@ impl fmt::Display for PrivateRoute { "PR({:?}+{}{})", self.public_key, self.hop_count, - if let Some(first_hop) = &self.first_hop { - format!("->{}", first_hop.node) - } else { - "".to_owned() + match &self.hops { + PrivateRouteHops::FirstHop(fh) => { + format!("->{}", fh.node) + } + PrivateRouteHops::Data(_) => { + "->?".to_owned() + } + PrivateRouteHops::Empty => { + "".to_owned() + } } ) }