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
    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
    /// We couldn't get a timely network directory in
27
    /// chosen circuits.
28
    #[error("Network directory not available")]
29
    NetdirUnavailable(#[source] tor_netdir::Error),
30

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

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

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

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

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

            
52
    /// We weren't able to obtain the subcredentials for decrypting the Introduce2 message.
53
    #[error("Could not obtain subcredentials")]
54
    Subcredentials(#[source] crate::FatalError),
55
}
56

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

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

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

            
136
    /// The RENDEZVOUS1 message we'll send to the rendezvous point.
137
    ///
138
    /// (The rendezvous point will in turn send this to the client as a RENDEZVOUS2.)
139
    rend1_msg: Rendezvous1,
140

            
141
    /// The decrypted and parsed body of the introduce2 message.
142
    intro_payload: IntroduceHandshakePayload,
143

            
144
    /// The circuit target for the rendezvous point.
145
    rend_point: VerbatimLinkSpecCircTarget<OwnedCircTarget>,
146
}
147

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

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

            
163
/// Dyn-safe trait to represent a `HsCircPool`.
164
///
165
/// We need this so that we can hold an `Arc<HsCircPool<R>>` in
166
/// `RendRequestContext` without needing to parameterize on R.
167
#[async_trait]
168
pub(crate) trait RendCircConnector: Send + Sync {
169
    /// Launch or return an existing circuit to the specified target.
170
    async fn get_or_launch_specific(
171
        &self,
172
        netdir: &tor_netdir::NetDir,
173
        target: VerbatimLinkSpecCircTarget<OwnedCircTarget>,
174
    ) -> tor_circmgr::Result<ServiceOnionServiceDataTunnel>;
175

            
176
    /// Return the current time instant from the runtime.
177
    ///
178
    /// This provides mockable time for use in error tracking.
179
    fn now(&self) -> std::time::Instant;
180

            
181
    /// Return the current wall-clock time from the runtime.
182
    fn wallclock(&self) -> std::time::SystemTime;
183
}
184

            
185
#[async_trait]
186
impl<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) -> std::time::Instant {
196
        HsCircPool::now(self)
197
    }
198

            
199
    fn wallclock(&self) -> std::time::SystemTime {
200
        HsCircPool::wallclock(self)
201
    }
202
}
203

            
204
/// Filter callback used to enforce early requirements on streams.
205
#[derive(Clone, Debug)]
206
pub(crate) struct RequestFilter {
207
    /// Largest number of streams we will accept on a circuit at a time.
208
    //
209
    // TODO: Conceivably, this should instead be a
210
    // watch::Receiver<Arc<OnionServiceConfig>>, so we can re-check the latest
211
    // value of the setting every time.  Instead, we currently only copy this
212
    // setting when an intro request is accepted.
213
    pub(crate) max_concurrent_streams: usize,
214
}
215
impl 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
            // TODO: We may want to have a way to send back an END message as
223
            // well and not tear down the circuit.
224
            Ok(tor_proto::client::stream::IncomingStreamRequestDisposition::CloseCircuit)
225
        } else {
226
            Ok(tor_proto::client::stream::IncomingStreamRequestDisposition::Accept)
227
        }
228
    }
229
}
230

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

            
265
        // We build the rend_point now, so that we can detect any
266
        // problems as early as possible.
267
        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
    /// Try to accept this client's request.
291
    ///
292
    /// To do so, we open a circuit to the client's chosen rendezvous point,
293
    /// send it a RENDEZVOUS1 message, and wait for incoming BEGIN messages from
294
    /// the client.
295
    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
        // Find a netdir.  Note that we _won't_ try to wait or retry if the
304
        // netdir isn't there: we probably can't answer this user's request.
305
        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
        // Open circuit to rendezvous point.
315
        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
                    // Note that we do not sleep on errors: if there is any
327
                    // error that will be solved by waiting, it would probably
328
                    // require waiting too long to satisfy the client.
329
                }
330
            }
331
        }
332
        let tunnel = tunnel.ok_or_else(|| E::RendCirc(retry_err))?;
333

            
334
        // We'll need parameters to extend the virtual hop.
335
        let params = onion_circparams_from_netparams(netdir.params())
336
            .map_err(into_internal!("Unable to build CircParameters"))?;
337

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

            
341
        // We won't need the netdir any longer; stop holding the reference.
342
        drop(netdir);
343

            
344
        let last_real_hop = tunnel
345
            .last_hop()
346
            .map_err(into_internal!("Circuit with no final hop"))?;
347

            
348
        // Add a virtual hop.
349
        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
        // Accept begins from that virtual hop
365
        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
        // Send the RENDEZVOUS1 message.
372
        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
    /// Get the [`IntroduceHandshakePayload`] associated with this [`IntroRequest`].
384
    pub(crate) fn intro_payload(&self) -> &IntroduceHandshakePayload {
385
        &self.intro_payload
386
    }
387
}