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::verbatim::VerbatimLinkSpecCircTarget;
12
use tor_proto::{
13
    client::circuit::handshake::{
14
        self,
15
        hs_ntor::{self, HsNtorHkdfKeyGenerator},
16
    },
17
    stream::{IncomingStream, IncomingStreamRequestFilter},
18
};
19
use tor_protover::Protocols;
20

            
21
/// An error produced while trying to process an introduction request we have
22
/// received from a client via an introduction point.
23
#[derive(Debug, Clone, thiserror::Error)]
24
#[allow(clippy::enum_variant_names)]
25
#[non_exhaustive]
26
pub enum IntroRequestError {
27
    /// We couldn't get a timely network directory in
28
    /// chosen circuits.
29
    #[error("Network directory not available")]
30
    NetdirUnavailable(#[source] tor_netdir::Error),
31

            
32
    /// Got an onion key with an unrecognized type (not ntor).
33
    #[error("Received an unsupported type of onion key")]
34
    UnsupportedOnionKey,
35

            
36
    /// The rendezvous point in the Introduce2 message was invalid and couldn't be used.
37
    #[error("Couldn't decode rendezvous point")]
38
    InvalidRendezvousPoint(#[source] tor_netdir::VerbatimCircTargetDecodeError),
39

            
40
    /// The handshake (e.g. hs_ntor) in the Introduce2 message was invalid and
41
    /// could not be completed.
42
    #[error("Introduction handshake was invalid")]
43
    InvalidHandshake(#[source] tor_proto::Error),
44

            
45
    /// The decrypted payload of the Introduce2 message could not be parsed.
46
    #[error("Could not parse INTRODUCE2 payload")]
47
    InvalidPayload(#[source] tor_bytes::Error),
48

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

            
53
    /// The client requested a capability or combination of capabilities
54
    /// that we don't support, or don't support negotiating.
55
    #[error("Client requested an invalid or unsupported subprotocol capability")]
56
    UnsupportedCapability,
57

            
58
    /// We weren't able to obtain the subcredentials for decrypting the Introduce2 message.
59
    #[error("Could not obtain subcredentials")]
60
    Subcredentials(#[source] crate::FatalError),
61
}
62

            
63
impl 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
/// An error produced while trying to connect to a rendezvous point and open a
81
/// session with a client.
82
#[derive(Debug, Clone, thiserror::Error)]
83
#[non_exhaustive]
84
pub enum EstablishSessionError {
85
    /// We couldn't get a timely network directory in order to build our
86
    /// chosen circuits.
87
    #[error("Network directory not available")]
88
    NetdirUnavailable(#[source] tor_netdir::Error),
89
    /// Unable to build a circuit to the rendezvous point.
90
    #[error("Could not establish circuit to rendezvous point")]
91
    RendCirc(#[source] RetryError<tor_circmgr::Error>),
92
    /// Encountered a failure while trying to add a virtual hop to the circuit.
93
    #[error("Could not add virtual hop to circuit")]
94
    VirtualHop(#[source] tor_circmgr::Error),
95
    /// We encountered an error while configuring the virtual hop to send us
96
    /// BEGIN messages.
97
    #[error("Could not configure circuit to allow BEGIN messages")]
98
    AcceptBegins(#[source] tor_circmgr::Error),
99
    /// We encountered an error while sending the rendezvous1 message.
100
    #[error("Could not send RENDEZVOUS1 message")]
101
    SendRendezvous(#[source] tor_circmgr::Error),
102
    /// An internal error occurred.
103
    #[error("Internal error")]
104
    Bug(#[from] tor_error::Bug),
105
}
106

            
107
impl 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
/// A decrypted request from an onion service client which we can
124
/// choose to answer (or not).
125
///
126
/// This corresponds to a processed INTRODUCE2 message.
127
///
128
/// To accept this request, call its
129
/// [`establish_session`](IntroRequest::establish_session) method.
130
/// To reject this request, simply drop it.
131
#[derive(educe::Educe)]
132
#[educe(Debug)]
133
pub(crate) struct IntroRequest {
134
    /// The introduce2 message itself. We keep this in case we want to look at
135
    /// the outer header.
136
    req: Introduce2,
137

            
138
    /// The key generator we'll use to derive our shared keys with the client when
139
    /// creating a virtual hop.
140
    #[educe(Debug(ignore))]
141
    key_gen: HsNtorHkdfKeyGenerator,
142

            
143
    /// The RENDEZVOUS1 message we'll send to the rendezvous point.
144
    ///
145
    /// (The rendezvous point will in turn send this to the client as a RENDEZVOUS2.)
146
    rend1_msg: Rendezvous1,
147

            
148
    /// The decrypted and parsed body of the introduce2 message.
149
    intro_payload: IntroduceHandshakePayload,
150

            
151
    /// A set of capabilities requested by the client that we are willing to support.
152
    requested_protocols: Protocols,
153

            
154
    /// The circuit target for the rendezvous point.
155
    rend_point: VerbatimLinkSpecCircTarget<OwnedCircTarget>,
156
}
157

            
158
/// An open session with a single client.
159
///
160
/// (We consume this type and take ownership of its members later in
161
/// [`RendRequest::accept()`](crate::req::RendRequest::accept).)
162
pub(crate) struct OpenSession {
163
    /// A stream of incoming BEGIN requests.
164
    pub(crate) stream_requests: BoxStream<'static, IncomingStream>,
165

            
166
    /// Our circuit with the client in question.
167
    ///
168
    /// See `RendRequest::accept()` for more information on the life cycle of
169
    /// this circuit.
170
    pub(crate) tunnel: ServiceOnionServiceDataTunnel,
171
}
172

            
173
/// Dyn-safe trait to represent a `HsCircPool`.
174
///
175
/// We need this so that we can hold an `Arc<HsCircPool<R>>` in
176
/// `RendRequestContext` without needing to parameterize on R.
177
#[async_trait]
178
pub(crate) trait RendCircConnector: Send + Sync {
179
    /// Launch or return an existing circuit to the specified target.
180
    async fn get_or_launch_specific(
181
        &self,
182
        netdir: &tor_netdir::NetDir,
183
        target: VerbatimLinkSpecCircTarget<OwnedCircTarget>,
184
    ) -> tor_circmgr::Result<ServiceOnionServiceDataTunnel>;
185

            
186
    /// Return the current time instant from the runtime.
187
    ///
188
    /// This provides mockable time for use in error tracking.
189
    fn now(&self) -> Instant;
190

            
191
    /// Return the current wall-clock time from the runtime.
192
    fn wallclock(&self) -> SystemTime;
193
}
194

            
195
#[async_trait]
196
impl<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
/// Filter callback used to enforce early requirements on streams.
215
#[derive(Clone, Debug)]
216
pub(crate) struct RequestFilter {
217
    /// Largest number of streams we will accept on a circuit at a time.
218
    //
219
    // TODO: Conceivably, this should instead be a
220
    // watch::Receiver<Arc<OnionServiceConfig>>, so we can re-check the latest
221
    // value of the setting every time.  Instead, we currently only copy this
222
    // setting when an intro request is accepted.
223
    pub(crate) max_concurrent_streams: usize,
224
}
225
impl 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
            // TODO: We may want to have a way to send back an END message as
233
            // well and not tear down the circuit.
234
            Ok(tor_proto::stream::IncomingStreamRequestDisposition::CloseCircuit)
235
        } else {
236
            Ok(tor_proto::stream::IncomingStreamRequestDisposition::Accept)
237
        }
238
    }
239
}
240

            
241
impl IntroRequest {
242
    /// Try to decrypt an incoming Introduce2 request, using the set of keys provided.
243
    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
        // We need the subcredential for the *current time period* in order to do the hs_ntor
251
        // handshake. But that can change over time.  We will instead use KeyMgr::get_matching to
252
        // find all current subcredentials.
253
        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
            // Note: we _do not_ call `should_be_exhausted` here, since we
271
            // explicitly expect the payload of an introduce2 message to be
272
            // padded to hide its size.
273
        };
274
        let requested_protocols = crate::caps::negotiated_capabilities(&intro_payload)?;
275

            
276
        // We build the rend_point now, so that we can detect any
277
        // problems as early as possible.
278
        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
    /// Try to accept this client's request.
304
    ///
305
    /// To do so, we open a circuit to the client's chosen rendezvous point,
306
    /// send it a RENDEZVOUS1 message, and wait for incoming BEGIN messages from
307
    /// the client.
308
    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
        // Find a netdir.  Note that we _won't_ try to wait or retry if the
317
        // netdir isn't there: we probably can't answer this user's request.
318
        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
        // Open circuit to rendezvous point.
328
        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
                    // Note that we do not sleep on errors: if there is any
340
                    // error that will be solved by waiting, it would probably
341
                    // require waiting too long to satisfy the client.
342
                }
343
            }
344
        }
345
        let tunnel = tunnel.ok_or_else(|| E::RendCirc(retry_err))?;
346

            
347
        // We'll need parameters to extend the virtual hop.
348
        //
349
        // (TODO #2594: If we stored our cc_sendme_inc along with our intro points, this would be the
350
        // place to look at it.)
351
        let params = onion_circparams_from_netparams(netdir.params())
352
            .map_err(into_internal!("Unable to build CircParameters"))?;
353

            
354
        // We won't need the netdir any longer; stop holding the reference.
355
        drop(netdir);
356

            
357
        let last_real_hop = tunnel
358
            .last_hop()
359
            .map_err(into_internal!("Circuit with no final hop"))?;
360

            
361
        // Add a virtual hop.
362
        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
        // Accept begins from that virtual hop
378
        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
        // Send the RENDEZVOUS1 message.
385
        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
    /// Get the [`IntroduceHandshakePayload`] associated with this [`IntroRequest`].
397
    pub(crate) fn intro_payload(&self) -> &IntroduceHandshakePayload {
398
        &self.intro_payload
399
    }
400
}