//! Request objects used to implement onion services. //! //! These requests are yielded on a stream, and the calling code needs to decide //! whether to permit or reject them. use crate::internal_prelude::*; use tor_cell::relaycell::msg::{Connected, End, Introduce2}; use tor_circmgr::ServiceOnionServiceDataTunnel; use tor_hscrypto::Subcredential; use tor_keymgr::ArtiPath; use tor_proto::stream::{IncomingStream, IncomingStreamRequest}; /// Request to complete an introduction/rendezvous handshake. /// /// A request of this kind indicates that a client has asked permission to /// connect to an onion service through an introduction point. The caller needs /// to decide whether or not to complete the handshake. /// /// Protocol details: More specifically, we create one of these whenever we get a well-formed /// `INTRODUCE2` message. Based on this, the caller decides whether to send a /// `RENDEZVOUS1` message. #[derive(Educe)] #[educe(Debug)] pub struct RendRequest { /// The introduction point that sent this request. ipt_lid: IptLocalId, /// The message as received from the remote introduction point. raw: Introduce2, /// Reference to the keys we'll need to decrypt and handshake with this request. #[educe(Debug(ignore))] context: Arc, /// The introduce2 message that we've decrypted and processed. /// /// We do not compute this immediately upon receiving the Introduce2 cell, /// since there is a bit of cryptography involved and we don't want to add /// any extra latency to the message handler. /// /// TODO: This also contains `raw`, which is maybe not so great; it would be /// neat to implement more efficiently. // // TODO MSRV TBD: Replace with std OnceCell (#1996) expanded: once_cell::unsync::OnceCell, } /// A request from a client to open a new stream to an onion service. /// /// We can only receive these _after_ we have already permitted the client to /// connect via a [`RendRequest`]. /// /// Protocol details: More specifically, we create one of these whenever we get a well-formed /// `BEGIN` message. Based on this, the caller decides whether to send a /// `CONNECTED` message. #[derive(Debug)] pub struct StreamRequest { /// The object that will be used to send data to and from the client. stream: IncomingStream, /// The circuit that made this request. on_tunnel: Arc, } /// Keys and objects needed to answer a RendRequest. pub(crate) struct RendRequestContext { /// The nickname of the service receiving the request. pub(crate) nickname: HsNickname, /// The key manager, used for looking up subcredentials. pub(crate) keymgr: Arc, /// Key we'll use to decrypt the rendezvous request. pub(crate) kp_hss_ntor: Arc, /// We use this key to identify our session with this introduction point, /// and prevent replays across sessions. pub(crate) kp_hs_ipt_sid: HsIntroPtSessionIdKey, /// Configuration for a filter for this service. pub(crate) filter: rend_handshake::RequestFilter, /// Provider we'll use to find a directory so that we can build a rendezvous /// circuit. pub(crate) netdir_provider: Arc, /// Circuit pool we'll use to build a rendezvous circuit. pub(crate) circ_pool: Arc, } impl RendRequestContext { /// Obtain the all current `Subcredential`s of `nickname` /// from the `K_hs_blind_id` read from the keystore. pub(crate) fn compute_subcredentials(&self) -> Result, FatalError> { let hsid_key_spec = HsIdPublicKeySpecifier::new(self.nickname.clone()); let hsid = self .keymgr .get::(&hsid_key_spec)? .ok_or_else(|| FatalError::MissingHsIdKeypair(self.nickname.clone()))?; let pattern = BlindIdKeypairSpecifierPattern { nickname: Some(self.nickname.clone()), period: None, } .arti_pattern()?; let blind_id_kps: Vec<(HsBlindIdKeypair, TimePeriod)> = self .keymgr .list_matching(&pattern)? .iter() .map(|entry| -> Result, FatalError> { let path = entry .key_path() .arti() .ok_or_else(|| internal!("CTorPath matched arti pattern?!"))?; let matches = path .matches(&pattern) .ok_or_else(|| internal!("path matched but no longer does?!"))?; let period = Self::parse_time_period(path, &matches)?; // Try to retrieve the key. self.keymgr .get_entry::(entry) .map_err(FatalError::Keystore) // If the key is not found, it means it has been garbage collected between the time // we queried the keymgr for the list of keys matching the pattern and now. // This is OK, because we only need the "current" keys .map(|maybe_key| maybe_key.map(|key| (key, period))) }) .flatten_ok() .collect::, FatalError>>()?; Ok(blind_id_kps .iter() .map(|(blind_id_key, period)| hsid.compute_subcredential(&blind_id_key.into(), *period)) .collect()) } /// Try to parse the `captures` of `path` as a [`TimePeriod`]. fn parse_time_period( path: &ArtiPath, captures: &[ArtiPathRange], ) -> Result { use tor_keymgr::{ArtiPathError, KeyPathError, KeystoreCorruptionError as KCE}; let [denotator] = captures else { return Err(internal!( "invalid number of denotator captures: expected 1, found {}", captures.len() ) .into()); }; let Some(denotator) = path.substring(denotator) else { return Err(internal!("captured substring out of range?!").into()); }; let tp = (|| { let slug = Slug::new(denotator.to_string()) .map_err(|e| ArtiPathError::InvalidArtiPath(e.into()))?; TimePeriod::from_slug(&slug).map_err(|error| { ArtiPathError::InvalidKeyPathComponentValue { key: "time_period".to_owned(), value: slug.clone(), error, } }) })() .map_err(|err| { KCE::KeyPath(KeyPathError::Arti { path: path.clone(), err, }) })?; Ok(tp) } } impl RendRequest { /// Construct a new RendRequest from its parts. pub(crate) fn new( ipt_lid: IptLocalId, msg: Introduce2, context: Arc, ) -> Self { Self { ipt_lid, raw: msg, context, expanded: Default::default(), } } /// Try to return a reference to the intro_request, creating it if it did /// not previously exist. pub(crate) fn intro_request( &self, ) -> Result<&rend_handshake::IntroRequest, rend_handshake::IntroRequestError> { self.expanded.get_or_try_init(|| { rend_handshake::IntroRequest::decrypt_from_introduce2(self.raw.clone(), &self.context) }) } /// Mark this request as accepted, and try to connect to the client's /// provided rendezvous point. pub async fn accept( mut self, ) -> Result + Unpin, ClientError> { // Make sure the request is there. self.intro_request().map_err(ClientError::BadIntroduce)?; // Take ownership of the request. let intro_request = self .expanded .take() .expect("intro_request succeeded but did not fill 'expanded'."); let rend_handshake::OpenSession { stream_requests, tunnel, } = intro_request .establish_session( self.context.filter.clone(), self.context.circ_pool.clone(), self.context.netdir_provider.clone(), ) .await .map_err(ClientError::EstablishSession)?; let tunnel = Arc::new(tunnel); // Note that we move circuit (which is an Arc) into this // closure, which lives for as long as the stream of StreamRequest, and // for as long as each individual StreamRequest. This is how we keep // the rendezvous circuit alive, and ensure that it gets closed when // the Stream we return is dropped. Ok(stream_requests.map(move |stream| StreamRequest { stream, on_tunnel: tunnel.clone(), })) } /// Reject this request. (The client will receive no notification.) #[allow(clippy::unused_async)] // we might need this to be async at some point pub async fn reject(self) -> Result<(), Bug> { // nothing to do. Ok(()) } // TODO: also add various accessors, as needed. } impl StreamRequest { /// Return the message that was used to request this stream. /// /// NOTE: for consistency with other onion service implementations, you /// should typically only accept `BEGIN` messages, and only check the port /// in those messages. If you behave differently, your implementation will /// be distinguishable. pub fn request(&self) -> &IncomingStreamRequest { self.stream.request() } /// Accept this request and send the client a `CONNECTED` message. pub async fn accept(self, connected_message: Connected) -> Result { self.stream .accept_data(connected_message) .await .map_err(ClientError::AcceptStream) } /// Reject this request, and send the client an `END` message. /// /// NOTE: If you need to be consistent with other onion service /// implementations, you should typically only send back `End` messages with /// the `DONE` reason. If you send back any other rejection, your /// implementation will be distinguishable. pub async fn reject(self, end_message: End) -> Result<(), ClientError> { self.stream .reject(end_message) .await .map_err(ClientError::RejectStream) } /// Reject this request and close the rendezvous circuit entirely, /// along with all other streams attached to the circuit. pub fn shutdown_circuit(self) -> Result<(), Bug> { self.on_tunnel.terminate(); Ok(()) } // TODO various accessors, including for circuit. }