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_circmgr::{ServiceOnionServiceDataTunnel, build::onion_circparams_from_netparams};
11
use tor_linkspec::{decode::Strictness, verbatim::VerbatimLinkSpecCircTarget};
12
use tor_proto::{
13
    client::circuit::handshake::{
14
        self,
15
        hs_ntor::{self, HsNtorHkdfKeyGenerator},
16
    },
17
    client::stream::{IncomingStream, IncomingStreamRequestFilter},
18
};
19

            
20
/// An error produced while trying to process an introduction request we have
21
/// received from a client via an introduction point.
22
#[derive(Debug, Clone, thiserror::Error)]
23
#[allow(clippy::enum_variant_names)]
24
#[non_exhaustive]
25
pub enum IntroRequestError {
26
    /// The handshake (e.g. hs_ntor) in the Introduce2 message was invalid and
27
    /// could not be completed.
28
    #[error("Introduction handshake was invalid")]
29
    InvalidHandshake(#[source] tor_proto::Error),
30

            
31
    /// The decrypted payload of the Introduce2 message could not be parsed.
32
    #[error("Could not parse INTRODUCE2 payload")]
33
    InvalidPayload(#[source] tor_bytes::Error),
34

            
35
    /// We weren't able to build a ChanTarget from the Introduce2 message.
36
    #[error("Invalid link specifiers in INTRODUCE2 payload")]
37
    InvalidLinkSpecs(#[source] tor_linkspec::decode::ChanTargetDecodeError),
38

            
39
    /// We weren't able to obtain the subcredentials for decrypting the Introduce2 message.
40
    #[error("Could not obtain subcredentials")]
41
    Subcredentials(#[source] crate::FatalError),
42
}
43

            
44
impl 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
/// An error produced while trying to connect to a rendezvous point and open a
58
/// session with a client.
59
#[derive(Debug, Clone, thiserror::Error)]
60
#[non_exhaustive]
61
pub enum EstablishSessionError {
62
    /// We couldn't get a timely network directory in order to build our
63
    /// chosen circuits.
64
    #[error("Network directory not available")]
65
    NetdirUnavailable(#[source] tor_netdir::Error),
66
    /// Got an onion key with an unrecognized type (not ntor).
67
    #[error("Received an unsupported type of onion key")]
68
    UnsupportedOnionKey,
69
    /// Unable to build a circuit to the rendezvous point.
70
    #[error("Could not establish circuit to rendezvous point")]
71
    RendCirc(#[source] RetryError<tor_circmgr::Error>),
72
    /// Encountered a failure while trying to add a virtual hop to the circuit.
73
    #[error("Could not add virtual hop to circuit")]
74
    VirtualHop(#[source] tor_circmgr::Error),
75
    /// We encountered an error while configuring the virtual hop to send us
76
    /// BEGIN messages.
77
    #[error("Could not configure circuit to allow BEGIN messages")]
78
    AcceptBegins(#[source] tor_circmgr::Error),
79
    /// We encountered an error while sending the rendezvous1 message.
80
    #[error("Could not send RENDEZVOUS1 message")]
81
    SendRendezvous(#[source] tor_circmgr::Error),
82
    /// The client sent us a rendezvous point with an impossible set of identities.
83
    ///
84
    /// (For example, it gave us `(Ed1, Rsa1)`, but in the network directory `Ed1` is
85
    /// associated with `Rsa2`.)
86
    #[error("Impossible combination of identities for rendezvous point")]
87
    ImpossibleIds(#[source] tor_netdir::RelayLookupError),
88
    /// An internal error occurred.
89
    #[error("Internal error")]
90
    Bug(#[from] tor_error::Bug),
91
}
92

            
93
impl 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
/// A decrypted request from an onion service client which we can
113
/// choose to answer (or not).
114
///
115
/// This corresponds to a processed INTRODUCE2 message.
116
///
117
/// To accept this request, call its
118
/// [`establish_session`](IntroRequest::establish_session) method.
119
/// To reject this request, simply drop it.
120
#[derive(educe::Educe)]
121
#[educe(Debug)]
122
pub(crate) struct IntroRequest {
123
    /// The introduce2 message itself. We keep this in case we want to look at
124
    /// the outer header.
125
    req: Introduce2,
126

            
127
    /// The key generator we'll use to derive our shared keys with the client when
128
    /// creating a virtual hop.
129
    #[educe(Debug(ignore))]
130
    key_gen: HsNtorHkdfKeyGenerator,
131

            
132
    /// The RENDEZVOUS1 message we'll send to the rendezvous point.
133
    ///
134
    /// (The rendezvous point will in turn send this to the client as a RENDEZVOUS2.)
135
    rend1_msg: Rendezvous1,
136

            
137
    /// The decrypted and parsed body of the introduce2 message.
138
    intro_payload: IntroduceHandshakePayload,
139

            
140
    /// The (in progress) ChanTarget that we'll use to build a circuit target
141
    /// for connecting to the rendezvous point.
142
    chan_target: OwnedChanTargetBuilder,
143
}
144

            
145
/// An open session with a single client.
146
///
147
/// (We consume this type and take ownership of its members later in
148
/// [`RendRequest::accept()`](crate::req::RendRequest::accept).)
149
pub(crate) struct OpenSession {
150
    /// A stream of incoming BEGIN requests.
151
    pub(crate) stream_requests: BoxStream<'static, IncomingStream>,
152

            
153
    /// Our circuit with the client in question.
154
    ///
155
    /// See `RendRequest::accept()` for more information on the life cycle of
156
    /// this circuit.
157
    pub(crate) tunnel: ServiceOnionServiceDataTunnel,
158
}
159

            
160
/// Dyn-safe trait to represent a `HsCircPool`.
161
///
162
/// We need this so that we can hold an `Arc<HsCircPool<R>>` in
163
/// `RendRequestContext` without needing to parameterize on R.
164
#[async_trait]
165
pub(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]
174
impl<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
/// Filter callback used to enforce early requirements on streams.
185
#[derive(Clone, Debug)]
186
pub(crate) struct RequestFilter {
187
    /// Largest number of streams we will accept on a circuit at a time.
188
    //
189
    // TODO: Conceivably, this should instead be a
190
    // watch::Receiver<Arc<OnionServiceConfig>>, so we can re-check the latest
191
    // value of the setting every time.  Instead, we currently only copy this
192
    // setting when an intro request is accepted.
193
    pub(crate) max_concurrent_streams: usize,
194
}
195
impl IncomingStreamRequestFilter for RequestFilter {
196
    fn disposition(
197
        &mut self,
198
        _ctx: &tor_proto::client::stream::IncomingStreamRequestContext<'_>,
199
        circ: &tor_proto::client::circuit::ClientCircSyncView<'_>,
200
    ) -> tor_proto::Result<tor_proto::client::stream::IncomingStreamRequestDisposition> {
201
        if circ.n_open_streams() >= self.max_concurrent_streams {
202
            // TODO: We may want to have a way to send back an END message as
203
            // well and not tear down the circuit.
204
            Ok(tor_proto::client::stream::IncomingStreamRequestDisposition::CloseCircuit)
205
        } else {
206
            Ok(tor_proto::client::stream::IncomingStreamRequestDisposition::Accept)
207
        }
208
    }
209
}
210

            
211
impl IntroRequest {
212
    /// Try to decrypt an incoming Introduce2 request, using the set of keys provided.
213
    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
        // We need the subcredential for the *current time period* in order to do the hs_ntor
221
        // handshake. But that can change over time.  We will instead use KeyMgr::get_matching to
222
        // find all current subcredentials.
223
        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
            // Note: we _do not_ call `should_be_exhausted` here, since we
241
            // explicitly expect the payload of an introduce2 message to be
242
            // padded to hide its size.
243
        };
244

            
245
        // We build the OwnedChanTargetBuilder now, so that we can detect any
246
        // problems here earlier.
247
        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
    /// Try to accept this client's request.
265
    ///
266
    /// To do so, we open a circuit to the client's chosen rendezvous point,
267
    /// send it a RENDEZVOUS1 message, and wait for incoming BEGIN messages from
268
    /// the client.
269
    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
        // Find a netdir.  Note that we _won't_ try to wait or retry if the
278
        // netdir isn't there: we probably can't answer this user's request.
279
        let netdir = provider
280
            .netdir(tor_netdir::Timeliness::Timely)
281
            .map_err(E::NetdirUnavailable)?;
282

            
283
        // Try to construct a CircTarget for rendezvous point based on the
284
        // intro_payload.
285
        let rend_point = {
286
            // TODO: We might have checked for a recognized onion key type earlier.
287
            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
            // TODO (#1223): This block is very similar to circtarget_from_pieces in
295
            // relay_info.rs.
296
            // Is there a clean way to refactor this?
297
            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
        // Open circuit to rendezvous point.
324
        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
                    // Note that we do not sleep on errors: if there is any
336
                    // error that will be solved by waiting, it would probably
337
                    // require waiting too long to satisfy the client.
338
                }
339
            }
340
        }
341
        let tunnel = tunnel.ok_or_else(|| E::RendCirc(retry_err))?;
342

            
343
        // We'll need parameters to extend the virtual hop.
344
        let params = onion_circparams_from_netparams(netdir.params())
345
            .map_err(into_internal!("Unable to build CircParameters"))?;
346

            
347
        // TODO CC: We may be able to do better based on the client's handshake message.
348
        let protocols = netdir.client_protocol_status().required_protocols().clone();
349

            
350
        // We won't need the netdir any longer; stop holding the reference.
351
        drop(netdir);
352

            
353
        let last_real_hop = tunnel
354
            .last_hop()
355
            .map_err(into_internal!("Circuit with no final hop"))?;
356

            
357
        // Add a virtual hop.
358
        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
        // Accept begins from that virtual hop
374
        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
        // Send the RENDEZVOUS1 message.
381
        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
    /// Get the [`IntroduceHandshakePayload`] associated with this [`IntroRequest`].
393
    pub(crate) fn intro_payload(&self) -> &IntroduceHandshakePayload {
394
        &self.intro_payload
395
    }
396
}