/ crates / tor-hsservice / src / rend_handshake.rs
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  }