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::{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 slug = Slug::new(denotator.to_string()).map_err(|e| {
160 KCE::KeyPath(KeyPathError::InvalidArtiPath {
161 path: path.clone(),
162 error: e.into(),
163 })
164 })?;
165 let tp = TimePeriod::from_slug(&slug).map_err(|error| {
166 KCE::KeyPath(KeyPathError::InvalidKeyPathComponentValue {
167 key: "time_period".to_owned(),
168 path: path.clone(),
169 value: slug,
170 error,
171 })
172 })?;
173
174 Ok(tp)
175 }
176}
177
178impl RendRequest {
179 /// Construct a new RendRequest from its parts.
180 pub(crate) fn new(
181 ipt_lid: IptLocalId,
182 msg: Introduce2,
183 context: Arc<RendRequestContext>,
184 ) -> Self {
185 Self {
186 ipt_lid,
187 raw: msg,
188 context,
189 expanded: Default::default(),
190 }
191 }
192
193 /// Try to return a reference to the intro_request, creating it if it did
194 /// not previously exist.
195 pub(crate) fn intro_request(
196 &self,
197 ) -> Result<&rend_handshake::IntroRequest, rend_handshake::IntroRequestError> {
198 self.expanded.get_or_try_init(|| {
199 rend_handshake::IntroRequest::decrypt_from_introduce2(self.raw.clone(), &self.context)
200 })
201 }
202
203 /// Mark this request as accepted, and try to connect to the client's
204 /// provided rendezvous point.
205 pub async fn accept(
206 mut self,
207 ) -> Result<impl Stream<Item = StreamRequest> + Unpin, ClientError> {
208 // Make sure the request is there.
209 self.intro_request().map_err(ClientError::BadIntroduce)?;
210 // Take ownership of the request.
211 let intro_request = self
212 .expanded
213 .take()
214 .expect("intro_request succeeded but did not fill 'expanded'.");
215 let rend_handshake::OpenSession {
216 stream_requests,
217 tunnel,
218 } = intro_request
219 .establish_session(
220 self.context.filter.clone(),
221 self.context.circ_pool.clone(),
222 self.context.netdir_provider.clone(),
223 )
224 .await
225 .map_err(ClientError::EstablishSession)?;
226
227 let tunnel = Arc::new(tunnel);
228
229 // Note that we move circuit (which is an Arc<ClientCirc>) into this
230 // closure, which lives for as long as the stream of StreamRequest, and
231 // for as long as each individual StreamRequest. This is how we keep
232 // the rendezvous circuit alive, and ensure that it gets closed when
233 // the Stream we return is dropped.
234 Ok(stream_requests.map(move |stream| StreamRequest {
235 stream,
236 on_tunnel: tunnel.clone(),
237 }))
238 }
239
240 /// Reject this request. (The client will receive no notification.)
241 pub async fn reject(self) -> Result<(), Bug> {
242 // nothing to do.
243 Ok(())
244 }
245
246 // TODO: also add various accessors, as needed.
247}
248
249impl StreamRequest {
250 /// Return the message that was used to request this stream.
251 ///
252 /// NOTE: for consistency with other onion service implementations, you
253 /// should typically only accept `BEGIN` messages, and only check the port
254 /// in those messages. If you behave differently, your implementation will
255 /// be distinguishable.
256 pub fn request(&self) -> &IncomingStreamRequest {
257 self.stream.request()
258 }
259
260 /// Accept this request and send the client a `CONNECTED` message.
261 pub async fn accept(self, connected_message: Connected) -> Result<DataStream, ClientError> {
262 self.stream
263 .accept_data(connected_message)
264 .await
265 .map_err(ClientError::AcceptStream)
266 }
267
268 /// Reject this request, and send the client an `END` message.
269 ///
270 /// NOTE: If you need to be consistent with other onion service
271 /// implementations, you should typically only send back `End` messages with
272 /// the `DONE` reason. If you send back any other rejection, your
273 /// implementation will be distinguishable.
274 pub async fn reject(self, end_message: End) -> Result<(), ClientError> {
275 self.stream
276 .reject(end_message)
277 .await
278 .map_err(ClientError::RejectStream)
279 }
280
281 /// Reject this request and close the rendezvous circuit entirely,
282 /// along with all other streams attached to the circuit.
283 pub fn shutdown_circuit(self) -> Result<(), Bug> {
284 self.on_tunnel.terminate();
285 Ok(())
286 }
287
288 // TODO various accessors, including for circuit.
289}