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 client::stream::{IncomingStream, IncomingStreamRequestFilter},
18};
19
20#[derive(Debug, Clone, thiserror::Error)]
23#[allow(clippy::enum_variant_names)]
24#[non_exhaustive]
25pub enum IntroRequestError {
26 #[error("Network directory not available")]
29 NetdirUnavailable(#[source] tor_netdir::Error),
30
31 #[error("Received an unsupported type of onion key")]
33 UnsupportedOnionKey,
34
35 #[error("Couldn't decode rendezvous point")]
37 InvalidRendezvousPoint(#[source] tor_netdir::VerbatimCircTargetDecodeError),
38
39 #[error("Introduction handshake was invalid")]
42 InvalidHandshake(#[source] tor_proto::Error),
43
44 #[error("Could not parse INTRODUCE2 payload")]
46 InvalidPayload(#[source] tor_bytes::Error),
47
48 #[error("Invalid link specifiers in INTRODUCE2 payload")]
50 InvalidLinkSpecs(#[source] tor_linkspec::decode::ChanTargetDecodeError),
51
52 #[error("Could not obtain subcredentials")]
54 Subcredentials(#[source] crate::FatalError),
55}
56
57impl HasKind for IntroRequestError {
58 fn kind(&self) -> tor_error::ErrorKind {
59 use IntroRequestError as E;
60 use tor_error::ErrorKind as EK;
61 match self {
62 E::NetdirUnavailable(e) => e.kind(),
63 E::UnsupportedOnionKey => EK::RemoteProtocolViolation,
64 E::InvalidRendezvousPoint(_) => EK::RemoteProtocolViolation,
65 E::InvalidHandshake(e) => e.kind(),
66 E::InvalidPayload(_) => EK::RemoteProtocolViolation,
67 E::InvalidLinkSpecs(_) => EK::RemoteProtocolViolation,
68 E::Subcredentials(e) => e.kind(),
69 }
70 }
71}
72
73#[derive(Debug, Clone, thiserror::Error)]
76#[non_exhaustive]
77pub enum EstablishSessionError {
78 #[error("Network directory not available")]
81 NetdirUnavailable(#[source] tor_netdir::Error),
82 #[error("Could not establish circuit to rendezvous point")]
84 RendCirc(#[source] RetryError<tor_circmgr::Error>),
85 #[error("Could not add virtual hop to circuit")]
87 VirtualHop(#[source] tor_circmgr::Error),
88 #[error("Could not configure circuit to allow BEGIN messages")]
91 AcceptBegins(#[source] tor_circmgr::Error),
92 #[error("Could not send RENDEZVOUS1 message")]
94 SendRendezvous(#[source] tor_circmgr::Error),
95 #[error("Internal error")]
97 Bug(#[from] tor_error::Bug),
98}
99
100impl HasKind for EstablishSessionError {
101 fn kind(&self) -> tor_error::ErrorKind {
102 use EstablishSessionError as E;
103 match self {
104 E::NetdirUnavailable(e) => e.kind(),
105 EstablishSessionError::RendCirc(e) => {
106 tor_circmgr::Error::summarized_error_kind(e.sources())
107 }
108 EstablishSessionError::VirtualHop(e) => e.kind(),
109 EstablishSessionError::AcceptBegins(e) => e.kind(),
110 EstablishSessionError::SendRendezvous(e) => e.kind(),
111 EstablishSessionError::Bug(e) => e.kind(),
112 }
113 }
114}
115
116#[derive(educe::Educe)]
125#[educe(Debug)]
126pub(crate) struct IntroRequest {
127 req: Introduce2,
130
131 #[educe(Debug(ignore))]
134 key_gen: HsNtorHkdfKeyGenerator,
135
136 rend1_msg: Rendezvous1,
140
141 intro_payload: IntroduceHandshakePayload,
143
144 rend_point: VerbatimLinkSpecCircTarget<OwnedCircTarget>,
146}
147
148pub(crate) struct OpenSession {
153 pub(crate) stream_requests: BoxStream<'static, IncomingStream>,
155
156 pub(crate) tunnel: ServiceOnionServiceDataTunnel,
161}
162
163#[async_trait]
168pub(crate) trait RendCircConnector: Send + Sync {
169 async fn get_or_launch_specific(
171 &self,
172 netdir: &tor_netdir::NetDir,
173 target: VerbatimLinkSpecCircTarget<OwnedCircTarget>,
174 ) -> tor_circmgr::Result<ServiceOnionServiceDataTunnel>;
175
176 fn now(&self) -> Instant;
180
181 fn wallclock(&self) -> SystemTime;
183}
184
185#[async_trait]
186impl<R: Runtime> RendCircConnector for HsCircPool<R> {
187 async fn get_or_launch_specific(
188 &self,
189 netdir: &tor_netdir::NetDir,
190 target: VerbatimLinkSpecCircTarget<OwnedCircTarget>,
191 ) -> tor_circmgr::Result<ServiceOnionServiceDataTunnel> {
192 HsCircPool::get_or_launch_svc_rend(self, netdir, target).await
193 }
194
195 fn now(&self) -> Instant {
196 HsCircPool::now(self)
197 }
198
199 fn wallclock(&self) -> SystemTime {
200 HsCircPool::wallclock(self)
201 }
202}
203
204#[derive(Clone, Debug)]
206pub(crate) struct RequestFilter {
207 pub(crate) max_concurrent_streams: usize,
214}
215impl IncomingStreamRequestFilter for RequestFilter {
216 fn disposition(
217 &mut self,
218 _ctx: &tor_proto::client::stream::IncomingStreamRequestContext<'_>,
219 circ: &tor_proto::circuit::CircHopSyncView<'_>,
220 ) -> tor_proto::Result<tor_proto::client::stream::IncomingStreamRequestDisposition> {
221 if circ.n_open_streams() >= self.max_concurrent_streams {
222 Ok(tor_proto::client::stream::IncomingStreamRequestDisposition::CloseCircuit)
225 } else {
226 Ok(tor_proto::client::stream::IncomingStreamRequestDisposition::Accept)
227 }
228 }
229}
230
231impl IntroRequest {
232 pub(crate) fn decrypt_from_introduce2(
234 req: Introduce2,
235 context: &RendRequestContext,
236 ) -> Result<Self, IntroRequestError> {
237 use IntroRequestError as E;
238 let mut rng = rand::rng();
239
240 let subcredentials = context
244 .compute_subcredentials()
245 .map_err(IntroRequestError::Subcredentials)?;
246
247 let (key_gen, rend1_body, msg_body) = hs_ntor::server_receive_intro(
248 &mut rng,
249 &context.kp_hss_ntor,
250 &context.kp_hs_ipt_sid,
251 &subcredentials[..],
252 req.encoded_header(),
253 req.encrypted_body(),
254 )
255 .map_err(E::InvalidHandshake)?;
256
257 let intro_payload: IntroduceHandshakePayload = {
258 let mut r = tor_bytes::Reader::from_slice(&msg_body);
259 r.extract().map_err(E::InvalidPayload)?
260 };
264
265 let netdir = context
268 .netdir_provider
269 .netdir(tor_netdir::Timeliness::Timely)
270 .map_err(E::NetdirUnavailable)?;
271 let ntor_onion_key = match intro_payload.onion_key() {
272 OnionKey::NtorOnionKey(ntor_key) => ntor_key,
273 _ => return Err(E::UnsupportedOnionKey),
274 };
275 let rend_point = netdir
276 .circ_target_from_verbatim_linkspecs(intro_payload.link_specifiers(), ntor_onion_key)
277 .map_err(E::InvalidRendezvousPoint)?;
278
279 let rend1_msg = Rendezvous1::new(*intro_payload.cookie(), rend1_body);
280
281 Ok(IntroRequest {
282 req,
283 key_gen,
284 rend1_msg,
285 intro_payload,
286 rend_point,
287 })
288 }
289
290 pub(crate) async fn establish_session(
296 self,
297 filter: RequestFilter,
298 hs_pool: Arc<dyn RendCircConnector>,
299 provider: Arc<dyn NetDirProvider>,
300 ) -> Result<OpenSession, EstablishSessionError> {
301 use EstablishSessionError as E;
302
303 let netdir = provider
306 .netdir(tor_netdir::Timeliness::Timely)
307 .map_err(E::NetdirUnavailable)?;
308
309 let max_n_attempts = netdir.params().hs_service_rendezvous_failures_max;
310 let mut tunnel = None;
311 let mut retry_err: RetryError<tor_circmgr::Error> =
312 RetryError::in_attempt_to("Establish a circuit to a rendezvous point");
313
314 for _attempt in 1..=max_n_attempts.into() {
316 match hs_pool
317 .get_or_launch_specific(&netdir, self.rend_point.clone())
318 .await
319 {
320 Ok(t) => {
321 tunnel = Some(t);
322 break;
323 }
324 Err(e) => {
325 retry_err.push_timed(e, hs_pool.now(), Some(hs_pool.wallclock()));
326 }
330 }
331 }
332 let tunnel = tunnel.ok_or_else(|| E::RendCirc(retry_err))?;
333
334 let params = onion_circparams_from_netparams(netdir.params())
336 .map_err(into_internal!("Unable to build CircParameters"))?;
337
338 let protocols = netdir.client_protocol_status().required_protocols().clone();
340
341 drop(netdir);
343
344 let last_real_hop = tunnel
345 .last_hop()
346 .map_err(into_internal!("Circuit with no final hop"))?;
347
348 tunnel
350 .extend_virtual(
351 handshake::RelayProtocol::HsV3,
352 handshake::HandshakeRole::Responder,
353 self.key_gen,
354 params,
355 &protocols,
356 )
357 .await
358 .map_err(E::VirtualHop)?;
359
360 let virtual_hop = tunnel
361 .last_hop()
362 .map_err(into_internal!("Circuit with no virtual hop"))?;
363
364 let stream_requests = tunnel
366 .allow_stream_requests(&[tor_cell::relaycell::RelayCmd::BEGIN], virtual_hop, filter)
367 .await
368 .map_err(E::AcceptBegins)?
369 .boxed();
370
371 tunnel
373 .send_raw_msg(self.rend1_msg.into(), last_real_hop)
374 .await
375 .map_err(E::SendRendezvous)?;
376
377 Ok(OpenSession {
378 stream_requests,
379 tunnel,
380 })
381 }
382
383 pub(crate) fn intro_payload(&self) -> &IntroduceHandshakePayload {
385 &self.intro_payload
386 }
387}