1use super::*;
4
5use tor_cell::relaycell::{
7 hs::intro_payload::{IntroduceHandshakePayload, OnionKey},
8 msg::{Introduce2, Rendezvous1},
9};
10use tor_circmgr::{ServiceOnionServiceDataTunnel, build::onion_circparams_from_netparams};
11use tor_linkspec::verbatim::VerbatimLinkSpecCircTarget;
12use tor_proto::{
13 client::circuit::handshake::{
14 self,
15 hs_ntor::{self, HsNtorHkdfKeyGenerator},
16 },
17 stream::{IncomingStream, IncomingStreamRequestFilter},
18};
19use tor_protover::Protocols;
20
21#[derive(Debug, Clone, thiserror::Error)]
24#[allow(clippy::enum_variant_names)]
25#[non_exhaustive]
26pub enum IntroRequestError {
27 #[error("Network directory not available")]
30 NetdirUnavailable(#[source] tor_netdir::Error),
31
32 #[error("Received an unsupported type of onion key")]
34 UnsupportedOnionKey,
35
36 #[error("Couldn't decode rendezvous point")]
38 InvalidRendezvousPoint(#[source] tor_netdir::VerbatimCircTargetDecodeError),
39
40 #[error("Introduction handshake was invalid")]
43 InvalidHandshake(#[source] tor_proto::Error),
44
45 #[error("Could not parse INTRODUCE2 payload")]
47 InvalidPayload(#[source] tor_bytes::Error),
48
49 #[error("Invalid link specifiers in INTRODUCE2 payload")]
51 InvalidLinkSpecs(#[source] tor_linkspec::decode::ChanTargetDecodeError),
52
53 #[error("Client requested an invalid or unsupported subprotocol capability")]
56 UnsupportedCapability,
57
58 #[error("Could not obtain subcredentials")]
60 Subcredentials(#[source] crate::FatalError),
61}
62
63impl HasKind for IntroRequestError {
64 fn kind(&self) -> tor_error::ErrorKind {
65 use IntroRequestError as E;
66 use tor_error::ErrorKind as EK;
67 match self {
68 E::NetdirUnavailable(e) => e.kind(),
69 E::UnsupportedOnionKey => EK::RemoteProtocolViolation,
70 E::InvalidRendezvousPoint(_) => EK::RemoteProtocolViolation,
71 E::InvalidHandshake(e) => e.kind(),
72 E::InvalidPayload(_) => EK::RemoteProtocolViolation,
73 E::InvalidLinkSpecs(_) => EK::RemoteProtocolViolation,
74 E::UnsupportedCapability => EK::RemoteProtocolViolation,
75 E::Subcredentials(e) => e.kind(),
76 }
77 }
78}
79
80#[derive(Debug, Clone, thiserror::Error)]
83#[non_exhaustive]
84pub enum EstablishSessionError {
85 #[error("Network directory not available")]
88 NetdirUnavailable(#[source] tor_netdir::Error),
89 #[error("Could not establish circuit to rendezvous point")]
91 RendCirc(#[source] RetryError<tor_circmgr::Error>),
92 #[error("Could not add virtual hop to circuit")]
94 VirtualHop(#[source] tor_circmgr::Error),
95 #[error("Could not configure circuit to allow BEGIN messages")]
98 AcceptBegins(#[source] tor_circmgr::Error),
99 #[error("Could not send RENDEZVOUS1 message")]
101 SendRendezvous(#[source] tor_circmgr::Error),
102 #[error("Internal error")]
104 Bug(#[from] tor_error::Bug),
105}
106
107impl HasKind for EstablishSessionError {
108 fn kind(&self) -> tor_error::ErrorKind {
109 use EstablishSessionError as E;
110 match self {
111 E::NetdirUnavailable(e) => e.kind(),
112 EstablishSessionError::RendCirc(e) => {
113 tor_circmgr::Error::summarized_error_kind(e.sources())
114 }
115 EstablishSessionError::VirtualHop(e) => e.kind(),
116 EstablishSessionError::AcceptBegins(e) => e.kind(),
117 EstablishSessionError::SendRendezvous(e) => e.kind(),
118 EstablishSessionError::Bug(e) => e.kind(),
119 }
120 }
121}
122
123#[derive(educe::Educe)]
132#[educe(Debug)]
133pub(crate) struct IntroRequest {
134 req: Introduce2,
137
138 #[educe(Debug(ignore))]
141 key_gen: HsNtorHkdfKeyGenerator,
142
143 rend1_msg: Rendezvous1,
147
148 intro_payload: IntroduceHandshakePayload,
150
151 requested_protocols: Protocols,
153
154 rend_point: VerbatimLinkSpecCircTarget<OwnedCircTarget>,
156}
157
158pub(crate) struct OpenSession {
163 pub(crate) stream_requests: BoxStream<'static, IncomingStream>,
165
166 pub(crate) tunnel: ServiceOnionServiceDataTunnel,
171}
172
173#[async_trait]
178pub(crate) trait RendCircConnector: Send + Sync {
179 async fn get_or_launch_specific(
181 &self,
182 netdir: &tor_netdir::NetDir,
183 target: VerbatimLinkSpecCircTarget<OwnedCircTarget>,
184 ) -> tor_circmgr::Result<ServiceOnionServiceDataTunnel>;
185
186 fn now(&self) -> Instant;
190
191 fn wallclock(&self) -> SystemTime;
193}
194
195#[async_trait]
196impl<R: Runtime> RendCircConnector for HsCircPool<R> {
197 async fn get_or_launch_specific(
198 &self,
199 netdir: &tor_netdir::NetDir,
200 target: VerbatimLinkSpecCircTarget<OwnedCircTarget>,
201 ) -> tor_circmgr::Result<ServiceOnionServiceDataTunnel> {
202 HsCircPool::get_or_launch_svc_rend(self, netdir, target).await
203 }
204
205 fn now(&self) -> Instant {
206 HsCircPool::now(self)
207 }
208
209 fn wallclock(&self) -> SystemTime {
210 HsCircPool::wallclock(self)
211 }
212}
213
214#[derive(Clone, Debug)]
216pub(crate) struct RequestFilter {
217 pub(crate) max_concurrent_streams: usize,
224}
225impl IncomingStreamRequestFilter for RequestFilter {
226 fn disposition(
227 &mut self,
228 _ctx: &tor_proto::stream::IncomingStreamRequestContext<'_>,
229 circ: &tor_proto::circuit::CircHopSyncView<'_>,
230 ) -> tor_proto::Result<tor_proto::stream::IncomingStreamRequestDisposition> {
231 if circ.n_open_streams() >= self.max_concurrent_streams {
232 Ok(tor_proto::stream::IncomingStreamRequestDisposition::CloseCircuit)
235 } else {
236 Ok(tor_proto::stream::IncomingStreamRequestDisposition::Accept)
237 }
238 }
239}
240
241impl IntroRequest {
242 pub(crate) fn decrypt_from_introduce2(
244 req: Introduce2,
245 context: &RendRequestContext,
246 ) -> Result<Self, IntroRequestError> {
247 use IntroRequestError as E;
248 let mut rng = rand::rng();
249
250 let subcredentials = context
254 .compute_subcredentials()
255 .map_err(IntroRequestError::Subcredentials)?;
256
257 let (key_gen, rend1_body, msg_body) = hs_ntor::server_receive_intro(
258 &mut rng,
259 &context.kp_hss_ntor,
260 &context.kp_hs_ipt_sid,
261 &subcredentials[..],
262 req.encoded_header(),
263 req.encrypted_body(),
264 )
265 .map_err(E::InvalidHandshake)?;
266
267 let intro_payload: IntroduceHandshakePayload = {
268 let mut r = tor_bytes::Reader::from_slice(&msg_body);
269 r.extract().map_err(E::InvalidPayload)?
270 };
274 let requested_protocols = crate::caps::negotiated_capabilities(&intro_payload)?;
275
276 let netdir = context
279 .netdir_provider
280 .netdir(tor_netdir::Timeliness::Timely)
281 .map_err(E::NetdirUnavailable)?;
282 let ntor_onion_key = match intro_payload.onion_key() {
283 OnionKey::NtorOnionKey(ntor_key) => ntor_key,
284 _ => return Err(E::UnsupportedOnionKey),
285 };
286
287 let rend_point = netdir
288 .circ_target_from_verbatim_linkspecs(intro_payload.link_specifiers(), ntor_onion_key)
289 .map_err(E::InvalidRendezvousPoint)?;
290
291 let rend1_msg = Rendezvous1::new(*intro_payload.cookie(), rend1_body);
292
293 Ok(IntroRequest {
294 req,
295 key_gen,
296 rend1_msg,
297 intro_payload,
298 requested_protocols,
299 rend_point,
300 })
301 }
302
303 pub(crate) async fn establish_session(
309 self,
310 filter: RequestFilter,
311 hs_pool: Arc<dyn RendCircConnector>,
312 provider: Arc<dyn NetDirProvider>,
313 ) -> Result<OpenSession, EstablishSessionError> {
314 use EstablishSessionError as E;
315
316 let netdir = provider
319 .netdir(tor_netdir::Timeliness::Timely)
320 .map_err(E::NetdirUnavailable)?;
321
322 let max_n_attempts = netdir.params().hs_service_rendezvous_failures_max;
323 let mut tunnel = None;
324 let mut retry_err: RetryError<tor_circmgr::Error> =
325 RetryError::in_attempt_to("Establish a circuit to a rendezvous point");
326
327 for _attempt in 1..=max_n_attempts.into() {
329 match hs_pool
330 .get_or_launch_specific(&netdir, self.rend_point.clone())
331 .await
332 {
333 Ok(t) => {
334 tunnel = Some(t);
335 break;
336 }
337 Err(e) => {
338 retry_err.push_timed(e, hs_pool.now(), Some(hs_pool.wallclock()));
339 }
343 }
344 }
345 let tunnel = tunnel.ok_or_else(|| E::RendCirc(retry_err))?;
346
347 let params = onion_circparams_from_netparams(netdir.params())
352 .map_err(into_internal!("Unable to build CircParameters"))?;
353
354 drop(netdir);
356
357 let last_real_hop = tunnel
358 .last_hop()
359 .map_err(into_internal!("Circuit with no final hop"))?;
360
361 tunnel
363 .extend_virtual(
364 handshake::RelayProtocol::HsV3,
365 handshake::HandshakeRole::Responder,
366 self.key_gen,
367 params,
368 &self.requested_protocols,
369 )
370 .await
371 .map_err(E::VirtualHop)?;
372
373 let virtual_hop = tunnel
374 .last_hop()
375 .map_err(into_internal!("Circuit with no virtual hop"))?;
376
377 let stream_requests = tunnel
379 .allow_stream_requests(&[tor_cell::relaycell::RelayCmd::BEGIN], virtual_hop, filter)
380 .await
381 .map_err(E::AcceptBegins)?
382 .boxed();
383
384 tunnel
386 .send_raw_msg(self.rend1_msg.into(), last_real_hop)
387 .await
388 .map_err(E::SendRendezvous)?;
389
390 Ok(OpenSession {
391 stream_requests,
392 tunnel,
393 })
394 }
395
396 pub(crate) fn intro_payload(&self) -> &IntroduceHandshakePayload {
398 &self.intro_payload
399 }
400}