diff --git a/Cargo.lock b/Cargo.lock index be7023115..d5f82b4d4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4164,6 +4164,7 @@ dependencies = [ "tokio", "tor-async-utils", "tor-basic-utils", + "tor-bytes", "tor-chanmgr", "tor-checkable", "tor-circmgr", diff --git a/crates/tor-hsclient/Cargo.toml b/crates/tor-hsclient/Cargo.toml index 8698bec2c..b097ad4dc 100644 --- a/crates/tor-hsclient/Cargo.toml +++ b/crates/tor-hsclient/Cargo.toml @@ -44,13 +44,14 @@ safelog = { path = "../safelog", version = "0.3.1" } slotmap = "1.0.6" strum = { version = "0.24", features = ["derive"] } thiserror = "1" +tor-bytes = { path = "../tor-bytes", version = "0.7.1" } tor-checkable = { path = "../tor-checkable", version = "0.5.1" } tor-circmgr = { version = "0.9.0", path = "../tor-circmgr", features = ["hs-client"] } tor-config = { path = "../tor-config", version = "0.9.1" } tor-dirclient = { path = "../tor-dirclient", version = "0.7.1", default-features = false, features = ["hs-client"] } tor-error = { path = "../tor-error", version = "0.5.1", features = ["experimental-api"] } # TODO HS tor-hscrypto = { version = "0.2.1", path = "../tor-hscrypto" } -tor-linkspec = { version = "0.8.0", path = "../tor-linkspec" } +tor-linkspec = { version = "0.8.0", path = "../tor-linkspec", features = ["decode", "verbatim"] } tor-llcrypto = { version = "0.5.1", path = "../tor-llcrypto" } tor-netdir = { version = "0.9.1", path = "../tor-netdir", features = ["hs-client"] } tor-netdoc = { path = "../tor-netdoc", version = "0.8.0", features = ["hs-client"] } diff --git a/crates/tor-hsclient/src/lib.rs b/crates/tor-hsclient/src/lib.rs index c12a3fff3..4b61a5ed5 100644 --- a/crates/tor-hsclient/src/lib.rs +++ b/crates/tor-hsclient/src/lib.rs @@ -42,6 +42,7 @@ mod connect; mod err; mod isol_map; mod keys; +mod relay_info; mod state; use std::future::Future; diff --git a/crates/tor-hsclient/src/relay_info.rs b/crates/tor-hsclient/src/relay_info.rs new file mode 100644 index 000000000..2a8937a4b --- /dev/null +++ b/crates/tor-hsclient/src/relay_info.rs @@ -0,0 +1,100 @@ +//! Translate relay information from the formats used in the onion service +//! protocol into `CircTarget`s that we can use for building circuits. +//! +//! (Later this will include support for INTRODUCE2 messages too.) + +#![allow(dead_code, unreachable_pub)] // TODO HS remove these once this API is exposed. + +use tor_error::into_internal; +use tor_linkspec::{ + decode::Strictness, verbatim::VerbatimLinkSpecCircTarget, CircTarget, EncodedLinkSpec, + OwnedChanTargetBuilder, OwnedCircTarget, +}; +use tor_llcrypto::pk::curve25519; +use tor_netdir::NetDir; +use tor_netdoc::doc::hsdesc::IntroPointDesc; + +/// Helper: create a [`CircTarget`] from its component parts as provided by +/// another party on the network. +/// +/// This function is used to build a `CircTarget` from an `IntroPointDesc` (for +/// extending to an introduction point). Later, it can also be used to build a +/// CircTarget from an `Introduce2` message (for extending to a rendezvous +/// point). +fn circtarget_from_pieces( + linkspecs: &[EncodedLinkSpec], + ntor_onion_key: &curve25519::PublicKey, + netdir: &NetDir, +) -> Result { + let mut bld = OwnedCircTarget::builder(); + // Decode the link specifiers and use them to find out what we can about + // this relay. + let linkspecs_decoded = linkspecs + .iter() + .map(|ls| ls.parse()) + .collect::, _>>()?; + *bld.chan_target() = + OwnedChanTargetBuilder::from_linkspecs(Strictness::Standard, &linkspecs_decoded[..])?; + // Look up the relay in the directory, to see: + // 1) if it is flatly impossible, + // 2) what subprotocols we should assume it implements. + let protocols = { + let chan_target = bld.chan_target().build().map_err(into_internal!( + "from_linkspecs gave us a non-working ChanTargetBuilder" + ))?; + match netdir.by_ids_detailed(&chan_target)? { + Some(relay) => relay.protovers().clone(), + None => netdir.relay_protocol_status().required_protocols().clone(), + } + }; + bld.protocols(protocols); + bld.ntor_onion_key(*ntor_onion_key); + let circ_target = bld.build().map_err(into_internal!( + "somehow we made an invalid CircTargetBuilder" + ))?; + Ok(VerbatimLinkSpecCircTarget::new( + circ_target, + linkspecs.to_vec(), + )) +} + +/// Construct a [`CircTarget`] from a provided [`IntroPointDesc`]. +/// +/// Onion service clients use this function to convert an `IntroPointDesc` in +/// the onion service descriptor into a form that they can use when building a +/// circuit to an introduction point. +/// +/// The `netdir` argument is used to fill in missing information about the +/// target relay, and to make sure that the target relay's identities are not +/// inconsistent with the rest of the network. +pub(crate) fn ipt_to_circtarget( + desc: &IntroPointDesc, + netdir: &NetDir, +) -> Result { + circtarget_from_pieces(desc.link_specifiers(), desc.ipt_ntor_key(), netdir) +} + +/// We were given unusable information about an introduction point or rendezvous +/// point. +#[derive(Clone, Debug, thiserror::Error)] +#[non_exhaustive] +pub enum InvalidTarget { + /// The provided link specifiers included some that, when we tried to parse + /// them, proved to be misformed. + #[error("Misformed channel target information provided")] + UnparseableChanTargetInfo(#[from] tor_bytes::Error), + + /// The provided link specifiers were inconsistent with one another, or missing + /// key information. + #[error("Invalid channel target information provided")] + InvalidChanTargetInfo(#[from] tor_linkspec::decode::ChanTargetDecodeError), + + /// The provided relay identities (in the link specifiers) described a relay + /// which, according to the network directory, cannot possibly exist. + #[error("Impossible combination of relay identities")] + ImpossibleRelayIds(#[from] tor_netdir::RelayLookupError), + + /// An internal error occurred. + #[error("{0}")] + Bug(#[from] tor_error::Bug), +} diff --git a/crates/tor-netdir/semver.md b/crates/tor-netdir/semver.md new file mode 100644 index 000000000..06a907b57 --- /dev/null +++ b/crates/tor-netdir/semver.md @@ -0,0 +1,2 @@ +ADDED: Accessor for relay protocol status. + diff --git a/crates/tor-netdir/src/lib.rs b/crates/tor-netdir/src/lib.rs index c3dc93eef..c9ec84c59 100644 --- a/crates/tor-netdir/src/lib.rs +++ b/crates/tor-netdir/src/lib.rs @@ -1081,6 +1081,25 @@ impl NetDir { pub fn params(&self) -> &NetParameters { &self.params } + + /// Return a [`ProtoStatus`](netstatus::ProtoStatus) that lists the + /// network's current requirements and recommendations for the list of + /// protocols that every relay must implement. + // + // TODO HS: I am not sure this is the right API; other alternatives would be: + // * To expose the _required_ relay protocol list instead (since that's all that + // onion service implementations need). + // * To expose the client protocol list as well (for symmetry). + // * To expose the MdConsensus instead (since that's more general, although + // it restricts the future evolution of this API). + // + // I think that this is a reasonably good compromise for now, but I'm going + // to put it behind the `hs-common` feature to give us time to consider more. + #[cfg(feature = "hs-common")] + pub fn relay_protocol_status(&self) -> &netstatus::ProtoStatus { + self.consensus.relay_protocol_status() + } + /// Return weighted the fraction of relays we can use. We only /// consider relays that match the predicate `usable`. We weight /// this bandwidth according to the provided `role`. diff --git a/crates/tor-netdoc/semver.md b/crates/tor-netdoc/semver.md new file mode 100644 index 000000000..755074792 --- /dev/null +++ b/crates/tor-netdoc/semver.md @@ -0,0 +1,2 @@ +ADDED: accessors for protocol status. + diff --git a/crates/tor-netdoc/src/doc/netstatus.rs b/crates/tor-netdoc/src/doc/netstatus.rs index 68d207559..0e61653fe 100644 --- a/crates/tor-netdoc/src/doc/netstatus.rs +++ b/crates/tor-netdoc/src/doc/netstatus.rs @@ -201,6 +201,8 @@ where } /// A list of subprotocol versions that implementors should/must provide. +/// +/// Each consensus has two of these: one for relays, and one for clients. #[allow(dead_code)] #[derive(Debug, Clone, Default)] pub struct ProtoStatus { @@ -679,6 +681,18 @@ impl Consensus { pub fn shared_rand_prev(&self) -> Option<&SharedRandStatus> { self.header.shared_rand_prev.as_ref() } + + /// Return a [`ProtoStatus`] that lists the network's current requirements and + /// recommendations for the list of protocols that every relay must implement. + pub fn relay_protocol_status(&self) -> &ProtoStatus { + &self.header.hdr.relay_protos + } + + /// Return a [`ProtoStatus`] that lists the network's current requirements and + /// recommendations for the list of protocols that every client must implement. + pub fn client_protocol_status(&self) -> &ProtoStatus { + &self.header.hdr.client_protos + } } decl_keyword! { @@ -895,6 +909,24 @@ impl ProtoStatus { required, }) } + + /// Return the protocols that are listed as "required" in this `ProtoStatus`. + /// + /// Implementations may assume that relays on the network implement all the + /// protocols in the relays' required-protocols list. Implementations should + /// refuse to start if they do not implement all the protocols on their own + /// (client or relay) required-protocols list. + pub fn required_protocols(&self) -> &Protocols { + &self.required + } + + /// Return the protocols that are listed as "recommended" in this `ProtoStatus`. + /// + /// Implementations should warn if they do not implement all the protocols + /// on their own (client or relay) recommended-protocols list. + pub fn recommended_protocols(&self) -> &Protocols { + &self.recommended + } } impl std::str::FromStr for NetParams