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