1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289
//! 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_hscrypto::Subcredential;
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<RendRequestContext>,
/// 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.
expanded: once_cell::unsync::OnceCell<rend_handshake::IntroRequest>,
}
/// 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_circuit: Arc<ClientCirc>,
}
/// 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<KeyMgr>,
/// Key we'll use to decrypt the rendezvous request.
pub(crate) kp_hss_ntor: Arc<HsSvcNtorKeypair>,
/// 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<dyn tor_netdir::NetDirProvider>,
/// Circuit pool we'll use to build a rendezvous circuit.
pub(crate) circ_pool: Arc<dyn RendCircConnector + Send + Sync>,
}
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<Vec<Subcredential>, FatalError> {
let hsid_key_spec = HsIdKeypairSpecifier::new(self.nickname.clone());
// TODO (#1194): Revisit when we add support for offline hsid mode
let keypair = self
.keymgr
.get::<HsIdKeypair>(&hsid_key_spec)?
.ok_or_else(|| FatalError::MissingHsIdKeypair(self.nickname.clone()))?;
let hsid = HsIdKey::from(&keypair);
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<Option<_>, FatalError> {
let path = entry.key_path();
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::<HsBlindIdKeypair>(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::<Result<Vec<_>, 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: &KeyPath,
captures: &[KeyPathRange],
) -> Result<TimePeriod, tor_keymgr::Error> {
use tor_keymgr::{KeyPathError, KeystoreCorruptionError as KCE};
let path = match path {
KeyPath::Arti(path) => path,
KeyPath::CTor(_) => todo!(),
_ => todo!(),
};
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 slug = Slug::new(denotator.to_string()).map_err(|e| {
KCE::KeyPath(KeyPathError::InvalidArtiPath {
path: path.clone(),
error: e.into(),
})
})?;
let tp = TimePeriod::from_slug(&slug).map_err(|error| {
KCE::KeyPath(KeyPathError::InvalidKeyPathComponentValue {
key: "time_period".to_owned(),
path: path.clone(),
value: slug,
error,
})
})?;
Ok(tp)
}
}
impl RendRequest {
/// Construct a new RendRequest from its parts.
pub(crate) fn new(
ipt_lid: IptLocalId,
msg: Introduce2,
context: Arc<RendRequestContext>,
) -> 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.
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<impl Stream<Item = StreamRequest> + 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,
circuit,
} = intro_request
.establish_session(
self.context.filter.clone(),
self.context.circ_pool.clone(),
self.context.netdir_provider.clone(),
)
.await
.map_err(ClientError::EstablishSession)?;
// Note that we move circuit (which is an Arc<ClientCirc>) 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_circuit: circuit.clone(),
}))
}
/// Reject this request. (The client will receive no notification.)
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<DataStream, ClientError> {
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_circuit.terminate();
Ok(())
}
// TODO various accessors, including for circuit.
}