rend_handshake.rs
1 //! Implementation for the introduce-and-rendezvous handshake. 2 3 use super::*; 4 5 // These imports just here, because they have names unsuitable for importing widely. 6 use tor_cell::relaycell::{ 7 hs::intro_payload::{IntroduceHandshakePayload, OnionKey}, 8 msg::{Introduce2, Rendezvous1}, 9 }; 10 use tor_linkspec::{decode::Strictness, verbatim::VerbatimLinkSpecCircTarget}; 11 use tor_proto::{ 12 circuit::handshake::{ 13 self, 14 hs_ntor::{self, HsNtorHkdfKeyGenerator}, 15 }, 16 stream::{IncomingStream, IncomingStreamRequestFilter}, 17 }; 18 19 /// An error produced while trying to process an introduction request we have 20 /// received from a client via an introduction point. 21 #[derive(Debug, Clone, thiserror::Error)] 22 #[allow(clippy::enum_variant_names)] 23 #[non_exhaustive] 24 pub enum IntroRequestError { 25 /// The handshake (e.g. hs_ntor) in the Introduce2 message was invalid and 26 /// could not be completed. 27 #[error("Introduction handshake was invalid")] 28 InvalidHandshake(#[source] tor_proto::Error), 29 30 /// The decrypted payload of the Introduce2 message could not be parsed. 31 #[error("Could not parse INTRODUCE2 payload")] 32 InvalidPayload(#[source] tor_bytes::Error), 33 34 /// We weren't able to build a ChanTarget from the Introduce2 message. 35 #[error("Invalid link specifiers in INTRODUCE2 payload")] 36 InvalidLinkSpecs(#[source] tor_linkspec::decode::ChanTargetDecodeError), 37 38 /// We weren't able to obtain the subcredentials for decrypting the Introduce2 message. 39 #[error("Could not obtain subcredentials")] 40 Subcredentials(#[source] crate::FatalError), 41 } 42 43 impl HasKind for IntroRequestError { 44 fn kind(&self) -> tor_error::ErrorKind { 45 use tor_error::ErrorKind as EK; 46 use IntroRequestError as E; 47 match self { 48 E::InvalidHandshake(e) => e.kind(), 49 E::InvalidPayload(_) => EK::RemoteProtocolViolation, 50 E::InvalidLinkSpecs(_) => EK::RemoteProtocolViolation, 51 E::Subcredentials(e) => e.kind(), 52 } 53 } 54 } 55 56 /// An error produced while trying to connect to a rendezvous point and open a 57 /// session with a client. 58 #[derive(Debug, Clone, thiserror::Error)] 59 #[non_exhaustive] 60 pub enum EstablishSessionError { 61 /// We couldn't get a timely network directory in order to build our 62 /// chosen circuits. 63 #[error("Network directory not available")] 64 NetdirUnavailable(#[source] tor_netdir::Error), 65 /// Got an onion key with an unrecognized type (not ntor). 66 #[error("Received an unsupported type of onion key")] 67 UnsupportedOnionKey, 68 /// Unable to build a circuit to the rendezvous point. 69 #[error("Could not establish circuit to rendezvous point")] 70 RendCirc(#[source] RetryError<tor_circmgr::Error>), 71 /// Encountered a failure while trying to add a virtual hop to the circuit. 72 #[error("Could not add virtual hop to circuit")] 73 VirtualHop(#[source] tor_proto::Error), 74 /// We encountered an error while configuring the virtual hop to send us 75 /// BEGIN messages. 76 #[error("Could not configure circuit to allow BEGIN messages")] 77 AcceptBegins(#[source] tor_proto::Error), 78 /// We encountered an error while sending the rendezvous1 message. 79 #[error("Could not send RENDEZVOUS1 message")] 80 SendRendezvous(#[source] tor_proto::Error), 81 /// The client sent us a rendezvous point with an impossible set of identities. 82 /// 83 /// (For example, it gave us `(Ed1, Rsa1)`, but in the network directory `Ed1` is 84 /// associated with `Rsa2`.) 85 #[error("Impossible combination of identities for rendezvous point")] 86 ImpossibleIds(#[source] tor_netdir::RelayLookupError), 87 /// An internal error occurred. 88 #[error("Internal error")] 89 Bug(#[from] tor_error::Bug), 90 } 91 92 impl HasKind for EstablishSessionError { 93 fn kind(&self) -> tor_error::ErrorKind { 94 use tor_error::ErrorKind as EK; 95 use EstablishSessionError as E; 96 match self { 97 E::NetdirUnavailable(e) => e.kind(), 98 E::UnsupportedOnionKey => EK::RemoteProtocolViolation, 99 EstablishSessionError::RendCirc(e) => { 100 tor_circmgr::Error::summarized_error_kind(e.sources()) 101 } 102 EstablishSessionError::VirtualHop(e) => e.kind(), 103 EstablishSessionError::AcceptBegins(e) => e.kind(), 104 EstablishSessionError::SendRendezvous(e) => e.kind(), 105 EstablishSessionError::ImpossibleIds(_) => EK::RemoteProtocolViolation, 106 EstablishSessionError::Bug(e) => e.kind(), 107 } 108 } 109 } 110 111 /// A decrypted request from an onion service client which we can 112 /// choose to answer (or not). 113 /// 114 /// This corresponds to a processed INTRODUCE2 message. 115 /// 116 /// To accept this request, call its 117 /// [`establish_session`](IntroRequest::establish_session) method. 118 /// To reject this request, simply drop it. 119 #[derive(educe::Educe)] 120 #[educe(Debug)] 121 pub(crate) struct IntroRequest { 122 /// The introduce2 message itself. We keep this in case we want to look at 123 /// the outer header. 124 req: Introduce2, 125 126 /// The key generator we'll use to derive our shared keys with the client when 127 /// creating a virtual hop. 128 #[educe(Debug(ignore))] 129 key_gen: HsNtorHkdfKeyGenerator, 130 131 /// The RENDEZVOUS1 message we'll send to the rendezvous point. 132 /// 133 /// (The rendezvous point will in turn send this to the client as a RENDEZVOUS2.) 134 rend1_msg: Rendezvous1, 135 136 /// The decrypted and parsed body of the introduce2 message. 137 intro_payload: IntroduceHandshakePayload, 138 139 /// The (in progress) ChanTarget that we'll use to build a circuit target 140 /// for connecting to the rendezvous point. 141 chan_target: OwnedChanTargetBuilder, 142 } 143 144 /// An open session with a single client. 145 /// 146 /// (We consume this type and take ownership of its members later in 147 /// [`RendRequest::accept()`](crate::req::RendRequest::accept).) 148 pub(crate) struct OpenSession { 149 /// A stream of incoming BEGIN requests. 150 pub(crate) stream_requests: BoxStream<'static, IncomingStream>, 151 152 /// Our circuit with the client in question. 153 /// 154 /// See `RendRequest::accept()` for more information on the life cycle of 155 /// this circuit. 156 pub(crate) circuit: Arc<ClientCirc>, 157 } 158 159 /// Dyn-safe trait to represent a `HsCircPool`. 160 /// 161 /// We need this so that we can hold an `Arc<HsCircPool<R>>` in 162 /// `RendRequestContext` without needing to parameterize on R. 163 #[async_trait] 164 pub(crate) trait RendCircConnector: Send + Sync { 165 async fn get_or_launch_specific( 166 &self, 167 netdir: &tor_netdir::NetDir, 168 kind: HsCircKind, 169 target: VerbatimLinkSpecCircTarget<OwnedCircTarget>, 170 ) -> tor_circmgr::Result<Arc<ClientCirc>>; 171 } 172 173 #[async_trait] 174 impl<R: Runtime> RendCircConnector for HsCircPool<R> { 175 async fn get_or_launch_specific( 176 &self, 177 netdir: &tor_netdir::NetDir, 178 kind: HsCircKind, 179 target: VerbatimLinkSpecCircTarget<OwnedCircTarget>, 180 ) -> tor_circmgr::Result<Arc<ClientCirc>> { 181 HsCircPool::get_or_launch_specific(self, netdir, kind, target).await 182 } 183 } 184 185 /// Filter callback used to enforce early requirements on streams. 186 #[derive(Clone, Debug)] 187 pub(crate) struct RequestFilter { 188 /// Largest number of streams we will accept on a circuit at a time. 189 // 190 // TODO: Conceivably, this should instead be a 191 // watch::Receiver<Arc<OnionServiceConfig>>, so we can re-check the latest 192 // value of the setting every time. Instead, we currently only copy this 193 // setting when an intro request is accepted. 194 pub(crate) max_concurrent_streams: usize, 195 } 196 impl IncomingStreamRequestFilter for RequestFilter { 197 fn disposition( 198 &mut self, 199 _ctx: &tor_proto::stream::IncomingStreamRequestContext<'_>, 200 circ: &tor_proto::circuit::ClientCircSyncView<'_>, 201 ) -> tor_proto::Result<tor_proto::stream::IncomingStreamRequestDisposition> { 202 if circ.n_open_streams() >= self.max_concurrent_streams { 203 // TODO: We may want to have a way to send back an END message as 204 // well and not tear down the circuit. 205 Ok(tor_proto::stream::IncomingStreamRequestDisposition::CloseCircuit) 206 } else { 207 Ok(tor_proto::stream::IncomingStreamRequestDisposition::Accept) 208 } 209 } 210 } 211 212 impl IntroRequest { 213 /// Try to decrypt an incoming Introduce2 request, using the set of keys provided. 214 pub(crate) fn decrypt_from_introduce2( 215 req: Introduce2, 216 context: &RendRequestContext, 217 ) -> Result<Self, IntroRequestError> { 218 use IntroRequestError as E; 219 let mut rng = rand::thread_rng(); 220 221 // We need the subcredential for the *current time period* in order to do the hs_ntor 222 // handshake. But that can change over time. We will instead use KeyMgr::get_matching to 223 // find all current subcredentials. 224 let subcredentials = context 225 .compute_subcredentials() 226 .map_err(IntroRequestError::Subcredentials)?; 227 228 let (key_gen, rend1_body, msg_body) = hs_ntor::server_receive_intro( 229 &mut rng, 230 &context.kp_hss_ntor, 231 &context.kp_hs_ipt_sid, 232 &subcredentials[..], 233 req.encoded_header(), 234 req.encrypted_body(), 235 ) 236 .map_err(E::InvalidHandshake)?; 237 238 let intro_payload: IntroduceHandshakePayload = { 239 let mut r = tor_bytes::Reader::from_slice(&msg_body); 240 r.extract().map_err(E::InvalidPayload)? 241 // Note: we _do not_ call `should_be_exhausted` here, since we 242 // explicitly expect the payload of an introduce2 message to be 243 // padded to hide its size. 244 }; 245 246 // We build the OwnedChanTargetBuilder now, so that we can detect any 247 // problems here earlier. 248 let chan_target = OwnedChanTargetBuilder::from_encoded_linkspecs( 249 Strictness::Standard, 250 intro_payload.link_specifiers(), 251 ) 252 .map_err(E::InvalidLinkSpecs)?; 253 254 let rend1_msg = Rendezvous1::new(*intro_payload.cookie(), rend1_body); 255 256 Ok(IntroRequest { 257 req, 258 key_gen, 259 rend1_msg, 260 intro_payload, 261 chan_target, 262 }) 263 } 264 265 /// Try to accept this client's request. 266 /// 267 /// To do so, we open a circuit to the client's chosen rendezvous point, 268 /// send it a RENDEZVOUS1 message, and wait for incoming BEGIN messages from 269 /// the client. 270 pub(crate) async fn establish_session( 271 self, 272 filter: RequestFilter, 273 hs_pool: Arc<dyn RendCircConnector>, 274 provider: Arc<dyn NetDirProvider>, 275 ) -> Result<OpenSession, EstablishSessionError> { 276 use EstablishSessionError as E; 277 278 // Find a netdir. Note that we _won't_ try to wait or retry if the 279 // netdir isn't there: we probably can't answer this user's request. 280 let netdir = provider 281 .netdir(tor_netdir::Timeliness::Timely) 282 .map_err(E::NetdirUnavailable)?; 283 284 // Try to construct a CircTarget for rendezvous point based on the 285 // intro_payload. 286 let rend_point = { 287 // TODO: We might have checked for a recognized onion key type earlier. 288 let ntor_onion_key = match self.intro_payload.onion_key() { 289 OnionKey::NtorOnionKey(ntor_key) => ntor_key, 290 _ => return Err(E::UnsupportedOnionKey), 291 }; 292 let mut bld = OwnedCircTarget::builder(); 293 *bld.chan_target() = self.chan_target; 294 295 // TODO (#1223): This block is very similar to circtarget_from_pieces in 296 // relay_info.rs. 297 // Is there a clean way to refactor this? 298 let protocols = { 299 let chan_target = bld.chan_target().build().map_err(into_internal!( 300 "from_encoded_linkspecs gave an invalid output" 301 ))?; 302 match netdir 303 .by_ids_detailed(&chan_target) 304 .map_err(E::ImpossibleIds)? 305 { 306 Some(relay) => relay.protovers().clone(), 307 None => netdir.relay_protocol_status().required_protocols().clone(), 308 } 309 }; 310 bld.protocols(protocols); 311 bld.ntor_onion_key(*ntor_onion_key); 312 VerbatimLinkSpecCircTarget::new( 313 bld.build() 314 .map_err(into_internal!("Failed to construct a valid circtarget"))?, 315 self.intro_payload.link_specifiers().into(), 316 ) 317 }; 318 319 let max_n_attempts = netdir.params().hs_service_rendezvous_failures_max; 320 let mut circuit = None; 321 let mut retry_err: RetryError<tor_circmgr::Error> = 322 RetryError::in_attempt_to("Establish a circuit to a rendezvous point"); 323 324 // Open circuit to rendezvous point. 325 for _attempt in 1..=max_n_attempts.into() { 326 match hs_pool 327 .get_or_launch_specific(&netdir, HsCircKind::SvcRend, rend_point.clone()) 328 .await 329 { 330 Ok(circ) => { 331 circuit = Some(circ); 332 break; 333 } 334 Err(e) => { 335 retry_err.push(e); 336 // Note that we do not sleep on errors: if there is any 337 // error that will be solved by waiting, it would probably 338 // require waiting too long to satisfy the client. 339 } 340 } 341 } 342 let circuit = circuit.ok_or_else(|| E::RendCirc(retry_err))?; 343 344 // We'll need parameters to extend the virtual hop. 345 let params = circparameters_from_netparameters(netdir.params()); 346 347 // We won't need the netdir any longer; stop holding the reference. 348 drop(netdir); 349 350 let last_real_hop = circuit 351 .last_hop_num() 352 .map_err(into_internal!("Circuit with no final hop"))?; 353 354 // Add a virtual hop. 355 circuit 356 .extend_virtual( 357 handshake::RelayProtocol::HsV3, 358 handshake::HandshakeRole::Responder, 359 self.key_gen, 360 params, 361 ) 362 .await 363 .map_err(E::VirtualHop)?; 364 365 let virtual_hop = circuit 366 .last_hop_num() 367 .map_err(into_internal!("Circuit with no virtual hop"))?; 368 369 // Accept begins from that virtual hop 370 let stream_requests = circuit 371 .allow_stream_requests(&[tor_cell::relaycell::RelayCmd::BEGIN], virtual_hop, filter) 372 .await 373 .map_err(E::AcceptBegins)? 374 .boxed(); 375 376 // Send the RENDEZVOUS1 message. 377 circuit 378 .send_raw_msg(self.rend1_msg.into(), last_real_hop) 379 .await 380 .map_err(E::SendRendezvous)?; 381 382 Ok(OpenSession { 383 stream_requests, 384 circuit, 385 }) 386 } 387 }