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::{decode::Strictness, 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("Introduction handshake was invalid")]
29    InvalidHandshake(#[source] tor_proto::Error),
30
31    #[error("Could not parse INTRODUCE2 payload")]
33    InvalidPayload(#[source] tor_bytes::Error),
34
35    #[error("Invalid link specifiers in INTRODUCE2 payload")]
37    InvalidLinkSpecs(#[source] tor_linkspec::decode::ChanTargetDecodeError),
38
39    #[error("Could not obtain subcredentials")]
41    Subcredentials(#[source] crate::FatalError),
42}
43
44impl HasKind for IntroRequestError {
45    fn kind(&self) -> tor_error::ErrorKind {
46        use IntroRequestError as E;
47        use tor_error::ErrorKind as EK;
48        match self {
49            E::InvalidHandshake(e) => e.kind(),
50            E::InvalidPayload(_) => EK::RemoteProtocolViolation,
51            E::InvalidLinkSpecs(_) => EK::RemoteProtocolViolation,
52            E::Subcredentials(e) => e.kind(),
53        }
54    }
55}
56
57#[derive(Debug, Clone, thiserror::Error)]
60#[non_exhaustive]
61pub enum EstablishSessionError {
62    #[error("Network directory not available")]
65    NetdirUnavailable(#[source] tor_netdir::Error),
66    #[error("Received an unsupported type of onion key")]
68    UnsupportedOnionKey,
69    #[error("Could not establish circuit to rendezvous point")]
71    RendCirc(#[source] RetryError<tor_circmgr::Error>),
72    #[error("Could not add virtual hop to circuit")]
74    VirtualHop(#[source] tor_circmgr::Error),
75    #[error("Could not configure circuit to allow BEGIN messages")]
78    AcceptBegins(#[source] tor_circmgr::Error),
79    #[error("Could not send RENDEZVOUS1 message")]
81    SendRendezvous(#[source] tor_circmgr::Error),
82    #[error("Impossible combination of identities for rendezvous point")]
87    ImpossibleIds(#[source] tor_netdir::RelayLookupError),
88    #[error("Internal error")]
90    Bug(#[from] tor_error::Bug),
91}
92
93impl HasKind for EstablishSessionError {
94    fn kind(&self) -> tor_error::ErrorKind {
95        use EstablishSessionError as E;
96        use tor_error::ErrorKind as EK;
97        match self {
98            E::NetdirUnavailable(e) => e.kind(),
99            E::UnsupportedOnionKey => EK::RemoteProtocolViolation,
100            EstablishSessionError::RendCirc(e) => {
101                tor_circmgr::Error::summarized_error_kind(e.sources())
102            }
103            EstablishSessionError::VirtualHop(e) => e.kind(),
104            EstablishSessionError::AcceptBegins(e) => e.kind(),
105            EstablishSessionError::SendRendezvous(e) => e.kind(),
106            EstablishSessionError::ImpossibleIds(_) => EK::RemoteProtocolViolation,
107            EstablishSessionError::Bug(e) => e.kind(),
108        }
109    }
110}
111
112#[derive(educe::Educe)]
121#[educe(Debug)]
122pub(crate) struct IntroRequest {
123    req: Introduce2,
126
127    #[educe(Debug(ignore))]
130    key_gen: HsNtorHkdfKeyGenerator,
131
132    rend1_msg: Rendezvous1,
136
137    intro_payload: IntroduceHandshakePayload,
139
140    chan_target: OwnedChanTargetBuilder,
143}
144
145pub(crate) struct OpenSession {
150    pub(crate) stream_requests: BoxStream<'static, IncomingStream>,
152
153    pub(crate) tunnel: ServiceOnionServiceDataTunnel,
158}
159
160#[async_trait]
165pub(crate) trait RendCircConnector: Send + Sync {
166    async fn get_or_launch_specific(
167        &self,
168        netdir: &tor_netdir::NetDir,
169        target: VerbatimLinkSpecCircTarget<OwnedCircTarget>,
170    ) -> tor_circmgr::Result<ServiceOnionServiceDataTunnel>;
171}
172
173#[async_trait]
174impl<R: Runtime> RendCircConnector for HsCircPool<R> {
175    async fn get_or_launch_specific(
176        &self,
177        netdir: &tor_netdir::NetDir,
178        target: VerbatimLinkSpecCircTarget<OwnedCircTarget>,
179    ) -> tor_circmgr::Result<ServiceOnionServiceDataTunnel> {
180        HsCircPool::get_or_launch_svc_rend(self, netdir, target).await
181    }
182}
183
184#[derive(Clone, Debug)]
186pub(crate) struct RequestFilter {
187    pub(crate) max_concurrent_streams: usize,
194}
195impl IncomingStreamRequestFilter for RequestFilter {
196    fn disposition(
197        &mut self,
198        _ctx: &tor_proto::client::stream::IncomingStreamRequestContext<'_>,
199        circ: &tor_proto::circuit::CircSyncView<'_>,
200    ) -> tor_proto::Result<tor_proto::client::stream::IncomingStreamRequestDisposition> {
201        if circ.n_open_streams() >= self.max_concurrent_streams {
202            Ok(tor_proto::client::stream::IncomingStreamRequestDisposition::CloseCircuit)
205        } else {
206            Ok(tor_proto::client::stream::IncomingStreamRequestDisposition::Accept)
207        }
208    }
209}
210
211impl IntroRequest {
212    pub(crate) fn decrypt_from_introduce2(
214        req: Introduce2,
215        context: &RendRequestContext,
216    ) -> Result<Self, IntroRequestError> {
217        use IntroRequestError as E;
218        let mut rng = rand::rng();
219
220        let subcredentials = context
224            .compute_subcredentials()
225            .map_err(IntroRequestError::Subcredentials)?;
226
227        let (key_gen, rend1_body, msg_body) = hs_ntor::server_receive_intro(
228            &mut rng,
229            &context.kp_hss_ntor,
230            &context.kp_hs_ipt_sid,
231            &subcredentials[..],
232            req.encoded_header(),
233            req.encrypted_body(),
234        )
235        .map_err(E::InvalidHandshake)?;
236
237        let intro_payload: IntroduceHandshakePayload = {
238            let mut r = tor_bytes::Reader::from_slice(&msg_body);
239            r.extract().map_err(E::InvalidPayload)?
240            };
244
245        let chan_target = OwnedChanTargetBuilder::from_encoded_linkspecs(
248            Strictness::Standard,
249            intro_payload.link_specifiers(),
250        )
251        .map_err(E::InvalidLinkSpecs)?;
252
253        let rend1_msg = Rendezvous1::new(*intro_payload.cookie(), rend1_body);
254
255        Ok(IntroRequest {
256            req,
257            key_gen,
258            rend1_msg,
259            intro_payload,
260            chan_target,
261        })
262    }
263
264    pub(crate) async fn establish_session(
270        self,
271        filter: RequestFilter,
272        hs_pool: Arc<dyn RendCircConnector>,
273        provider: Arc<dyn NetDirProvider>,
274    ) -> Result<OpenSession, EstablishSessionError> {
275        use EstablishSessionError as E;
276
277        let netdir = provider
280            .netdir(tor_netdir::Timeliness::Timely)
281            .map_err(E::NetdirUnavailable)?;
282
283        let rend_point = {
286            let ntor_onion_key = match self.intro_payload.onion_key() {
288                OnionKey::NtorOnionKey(ntor_key) => ntor_key,
289                _ => return Err(E::UnsupportedOnionKey),
290            };
291            let mut bld = OwnedCircTarget::builder();
292            *bld.chan_target() = self.chan_target;
293
294            let protocols = {
298                let chan_target = bld.chan_target().build().map_err(into_internal!(
299                    "from_encoded_linkspecs gave an invalid output"
300                ))?;
301                match netdir
302                    .by_ids_detailed(&chan_target)
303                    .map_err(E::ImpossibleIds)?
304                {
305                    Some(relay) => relay.protovers().clone(),
306                    None => netdir.relay_protocol_status().required_protocols().clone(),
307                }
308            };
309            bld.protocols(protocols);
310            bld.ntor_onion_key(*ntor_onion_key);
311            VerbatimLinkSpecCircTarget::new(
312                bld.build()
313                    .map_err(into_internal!("Failed to construct a valid circtarget"))?,
314                self.intro_payload.link_specifiers().into(),
315            )
316        };
317
318        let max_n_attempts = netdir.params().hs_service_rendezvous_failures_max;
319        let mut tunnel = None;
320        let mut retry_err: RetryError<tor_circmgr::Error> =
321            RetryError::in_attempt_to("Establish a circuit to a rendezvous point");
322
323        for _attempt in 1..=max_n_attempts.into() {
325            match hs_pool
326                .get_or_launch_specific(&netdir, rend_point.clone())
327                .await
328            {
329                Ok(t) => {
330                    tunnel = Some(t);
331                    break;
332                }
333                Err(e) => {
334                    retry_err.push(e);
335                    }
339            }
340        }
341        let tunnel = tunnel.ok_or_else(|| E::RendCirc(retry_err))?;
342
343        let params = onion_circparams_from_netparams(netdir.params())
345            .map_err(into_internal!("Unable to build CircParameters"))?;
346
347        let protocols = netdir.client_protocol_status().required_protocols().clone();
349
350        drop(netdir);
352
353        let last_real_hop = tunnel
354            .last_hop()
355            .map_err(into_internal!("Circuit with no final hop"))?;
356
357        tunnel
359            .extend_virtual(
360                handshake::RelayProtocol::HsV3,
361                handshake::HandshakeRole::Responder,
362                self.key_gen,
363                params,
364                &protocols,
365            )
366            .await
367            .map_err(E::VirtualHop)?;
368
369        let virtual_hop = tunnel
370            .last_hop()
371            .map_err(into_internal!("Circuit with no virtual hop"))?;
372
373        let stream_requests = tunnel
375            .allow_stream_requests(&[tor_cell::relaycell::RelayCmd::BEGIN], virtual_hop, filter)
376            .await
377            .map_err(E::AcceptBegins)?
378            .boxed();
379
380        tunnel
382            .send_raw_msg(self.rend1_msg.into(), last_real_hop)
383            .await
384            .map_err(E::SendRendezvous)?;
385
386        Ok(OpenSession {
387            stream_requests,
388            tunnel,
389        })
390    }
391
392    pub(crate) fn intro_payload(&self) -> &IntroduceHandshakePayload {
394        &self.intro_payload
395    }
396}