Skip to main content

tor_hsservice/
req.rs

1//! Request objects used to implement onion services.
2//!
3//! These requests are yielded on a stream, and the calling code needs to decide
4//! whether to permit or reject them.
5
6use crate::internal_prelude::*;
7
8use tor_cell::relaycell::msg::{Connected, End, Introduce2};
9use tor_circmgr::ServiceOnionServiceDataTunnel;
10use tor_hscrypto::Subcredential;
11use tor_keymgr::ArtiPath;
12use tor_proto::client::stream::{IncomingStream, IncomingStreamRequest};
13
14/// Request to complete an introduction/rendezvous handshake.
15///
16/// A request of this kind indicates that a client has asked permission to
17/// connect to an onion service through an introduction point.  The caller needs
18/// to decide whether or not to complete the handshake.
19///
20/// Protocol details: More specifically, we create one of these whenever we get a well-formed
21/// `INTRODUCE2` message.  Based on this, the caller decides whether to send a
22/// `RENDEZVOUS1` message.
23#[derive(Educe)]
24#[educe(Debug)]
25pub struct RendRequest {
26    /// The introduction point that sent this request.
27    ipt_lid: IptLocalId,
28
29    /// The message as received from the remote introduction point.
30    raw: Introduce2,
31
32    /// Reference to the keys we'll need to decrypt and handshake with this request.
33    #[educe(Debug(ignore))]
34    context: Arc<RendRequestContext>,
35
36    /// The introduce2 message that we've decrypted and processed.
37    ///
38    /// We do not compute this immediately upon receiving the Introduce2 cell,
39    /// since there is a bit of cryptography involved and we don't want to add
40    /// any extra latency to the message handler.
41    ///
42    /// TODO: This also contains `raw`, which is maybe not so great; it would be
43    /// neat to implement more efficiently.
44    //
45    // TODO MSRV TBD: Replace with std OnceCell (#1996)
46    expanded: once_cell::unsync::OnceCell<rend_handshake::IntroRequest>,
47}
48
49/// A request from a client to open a new stream to an onion service.
50///
51/// We can only receive these _after_ we have already permitted the client to
52/// connect via a [`RendRequest`].
53///
54/// Protocol details: More specifically, we create one of these whenever we get a well-formed
55/// `BEGIN` message.  Based on this, the caller decides whether to send a
56/// `CONNECTED` message.
57#[derive(Debug)]
58pub struct StreamRequest {
59    /// The object that will be used to send data to and from the client.
60    stream: IncomingStream,
61
62    /// The circuit that made this request.
63    on_tunnel: Arc<ServiceOnionServiceDataTunnel>,
64}
65
66/// Keys and objects needed to answer a RendRequest.
67pub(crate) struct RendRequestContext {
68    /// The nickname of the service receiving the request.
69    pub(crate) nickname: HsNickname,
70
71    /// The key manager, used for looking up subcredentials.
72    pub(crate) keymgr: Arc<KeyMgr>,
73
74    /// Key we'll use to decrypt the rendezvous request.
75    pub(crate) kp_hss_ntor: Arc<HsSvcNtorKeypair>,
76
77    /// We use this key to identify our session with this introduction point,
78    /// and prevent replays across sessions.
79    pub(crate) kp_hs_ipt_sid: HsIntroPtSessionIdKey,
80
81    /// Configuration for a filter for this service.
82    pub(crate) filter: rend_handshake::RequestFilter,
83
84    /// Provider we'll use to find a directory so that we can build a rendezvous
85    /// circuit.
86    pub(crate) netdir_provider: Arc<dyn tor_netdir::NetDirProvider>,
87
88    /// Circuit pool we'll use to build a rendezvous circuit.
89    pub(crate) circ_pool: Arc<dyn RendCircConnector + Send + Sync>,
90}
91
92impl RendRequestContext {
93    /// Obtain the all current `Subcredential`s of `nickname`
94    /// from the `K_hs_blind_id` read from the keystore.
95    pub(crate) fn compute_subcredentials(&self) -> Result<Vec<Subcredential>, FatalError> {
96        let hsid_key_spec = HsIdPublicKeySpecifier::new(self.nickname.clone());
97
98        let hsid = self
99            .keymgr
100            .get::<HsIdKey>(&hsid_key_spec)?
101            .ok_or_else(|| FatalError::MissingHsIdKeypair(self.nickname.clone()))?;
102
103        let pattern = BlindIdKeypairSpecifierPattern {
104            nickname: Some(self.nickname.clone()),
105            period: None,
106        }
107        .arti_pattern()?;
108
109        let blind_id_kps: Vec<(HsBlindIdKeypair, TimePeriod)> = self
110            .keymgr
111            .list_matching(&pattern)?
112            .iter()
113            .map(|entry| -> Result<Option<_>, FatalError> {
114                let path = entry
115                    .key_path()
116                    .arti()
117                    .ok_or_else(|| internal!("CTorPath matched arti pattern?!"))?;
118                let matches = path
119                    .matches(&pattern)
120                    .ok_or_else(|| internal!("path matched but no longer does?!"))?;
121                let period = Self::parse_time_period(path, &matches)?;
122                // Try to retrieve the key.
123                self.keymgr
124                    .get_entry::<HsBlindIdKeypair>(entry)
125                    .map_err(FatalError::Keystore)
126                    // If the key is not found, it means it has been garbage collected between the time
127                    // we queried the keymgr for the list of keys matching the pattern and now.
128                    // This is OK, because we only need the "current" keys
129                    .map(|maybe_key| maybe_key.map(|key| (key, period)))
130            })
131            .flatten_ok()
132            .collect::<Result<Vec<_>, FatalError>>()?;
133
134        Ok(blind_id_kps
135            .iter()
136            .map(|(blind_id_key, period)| hsid.compute_subcredential(&blind_id_key.into(), *period))
137            .collect())
138    }
139
140    /// Try to parse the `captures` of `path` as a [`TimePeriod`].
141    fn parse_time_period(
142        path: &ArtiPath,
143        captures: &[ArtiPathRange],
144    ) -> Result<TimePeriod, tor_keymgr::Error> {
145        use tor_keymgr::{ArtiPathError, KeyPathError, KeystoreCorruptionError as KCE};
146
147        let [denotator] = captures else {
148            return Err(internal!(
149                "invalid number of denotator captures: expected 1, found {}",
150                captures.len()
151            )
152            .into());
153        };
154
155        let Some(denotator) = path.substring(denotator) else {
156            return Err(internal!("captured substring out of range?!").into());
157        };
158
159        let tp = (|| {
160            let slug = Slug::new(denotator.to_string())
161                .map_err(|e| ArtiPathError::InvalidArtiPath(e.into()))?;
162
163            TimePeriod::from_slug(&slug).map_err(|error| {
164                ArtiPathError::InvalidKeyPathComponentValue {
165                    key: "time_period".to_owned(),
166                    value: slug.clone(),
167                    error,
168                }
169            })
170        })()
171        .map_err(|err| {
172            KCE::KeyPath(KeyPathError::Arti {
173                path: path.clone(),
174                err,
175            })
176        })?;
177
178        Ok(tp)
179    }
180}
181
182impl RendRequest {
183    /// Construct a new RendRequest from its parts.
184    pub(crate) fn new(
185        ipt_lid: IptLocalId,
186        msg: Introduce2,
187        context: Arc<RendRequestContext>,
188    ) -> Self {
189        Self {
190            ipt_lid,
191            raw: msg,
192            context,
193            expanded: Default::default(),
194        }
195    }
196
197    /// Try to return a reference to the intro_request, creating it if it did
198    /// not previously exist.
199    pub(crate) fn intro_request(
200        &self,
201    ) -> Result<&rend_handshake::IntroRequest, rend_handshake::IntroRequestError> {
202        self.expanded.get_or_try_init(|| {
203            rend_handshake::IntroRequest::decrypt_from_introduce2(self.raw.clone(), &self.context)
204        })
205    }
206
207    /// Mark this request as accepted, and try to connect to the client's
208    /// provided rendezvous point.
209    pub async fn accept(
210        mut self,
211    ) -> Result<impl Stream<Item = StreamRequest> + Unpin, ClientError> {
212        // Make sure the request is there.
213        self.intro_request().map_err(ClientError::BadIntroduce)?;
214        // Take ownership of the request.
215        let intro_request = self
216            .expanded
217            .take()
218            .expect("intro_request succeeded but did not fill 'expanded'.");
219        let rend_handshake::OpenSession {
220            stream_requests,
221            tunnel,
222        } = intro_request
223            .establish_session(
224                self.context.filter.clone(),
225                self.context.circ_pool.clone(),
226                self.context.netdir_provider.clone(),
227            )
228            .await
229            .map_err(ClientError::EstablishSession)?;
230
231        let tunnel = Arc::new(tunnel);
232
233        // Note that we move circuit (which is an Arc<ClientCirc>) into this
234        // closure, which lives for as long as the stream of StreamRequest, and
235        // for as long as each individual StreamRequest.  This is how we keep
236        // the rendezvous circuit alive, and ensure that it gets closed when
237        // the Stream we return is dropped.
238        Ok(stream_requests.map(move |stream| StreamRequest {
239            stream,
240            on_tunnel: tunnel.clone(),
241        }))
242    }
243
244    /// Reject this request.  (The client will receive no notification.)
245    #[allow(clippy::unused_async)] // we might need this to be async at some point
246    pub async fn reject(self) -> Result<(), Bug> {
247        // nothing to do.
248        Ok(())
249    }
250
251    // TODO: also add various accessors, as needed.
252}
253
254impl StreamRequest {
255    /// Return the message that was used to request this stream.
256    ///
257    /// NOTE: for consistency with other onion service implementations, you
258    /// should typically only accept `BEGIN` messages, and only check the port
259    /// in those messages. If you behave differently, your implementation will
260    /// be distinguishable.
261    pub fn request(&self) -> &IncomingStreamRequest {
262        self.stream.request()
263    }
264
265    /// Accept this request and send the client a `CONNECTED` message.
266    pub async fn accept(self, connected_message: Connected) -> Result<DataStream, ClientError> {
267        self.stream
268            .accept_data(connected_message)
269            .await
270            .map_err(ClientError::AcceptStream)
271    }
272
273    /// Reject this request, and send the client an `END` message.
274    ///
275    /// NOTE: If you need to be consistent with other onion service
276    /// implementations, you should typically only send back `End` messages with
277    /// the `DONE` reason. If you send back any other rejection, your
278    /// implementation will be distinguishable.
279    pub async fn reject(self, end_message: End) -> Result<(), ClientError> {
280        self.stream
281            .reject(end_message)
282            .await
283            .map_err(ClientError::RejectStream)
284    }
285
286    /// Reject this request and close the rendezvous circuit entirely,
287    /// along with all other streams attached to the circuit.
288    pub fn shutdown_circuit(self) -> Result<(), Bug> {
289        self.on_tunnel.terminate();
290        Ok(())
291    }
292
293    // TODO various accessors, including for circuit.
294}