Skip to main content

tor_hsservice/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2#![doc = include_str!("../README.md")]
3// @@ begin lint list maintained by maint/add_warning @@
4#![allow(renamed_and_removed_lints)] // @@REMOVE_WHEN(ci_arti_stable)
5#![allow(unknown_lints)] // @@REMOVE_WHEN(ci_arti_nightly)
6#![warn(missing_docs)]
7#![warn(noop_method_call)]
8#![warn(unreachable_pub)]
9#![warn(clippy::all)]
10#![deny(clippy::await_holding_lock)]
11#![deny(clippy::cargo_common_metadata)]
12#![deny(clippy::cast_lossless)]
13#![deny(clippy::checked_conversions)]
14#![allow(clippy::cognitive_complexity)] // See arti#2556
15#![deny(clippy::debug_assert_with_mut_call)]
16#![deny(clippy::exhaustive_enums)]
17#![deny(clippy::exhaustive_structs)]
18#![deny(clippy::expl_impl_clone_on_copy)]
19#![deny(clippy::fallible_impl_from)]
20#![deny(clippy::implicit_clone)]
21#![deny(clippy::large_stack_arrays)]
22#![warn(clippy::manual_ok_or)]
23#![deny(clippy::missing_docs_in_private_items)]
24#![warn(clippy::needless_borrow)]
25#![warn(clippy::needless_pass_by_value)]
26#![warn(clippy::option_option)]
27#![deny(clippy::print_stderr)]
28#![deny(clippy::print_stdout)]
29#![warn(clippy::rc_buffer)]
30#![deny(clippy::ref_option_ref)]
31#![warn(clippy::semicolon_if_nothing_returned)]
32#![warn(clippy::trait_duplication_in_bounds)]
33#![deny(clippy::unchecked_time_subtraction)]
34#![deny(clippy::unnecessary_wraps)]
35#![warn(clippy::unseparated_literal_suffix)]
36#![deny(clippy::unwrap_used)]
37#![deny(clippy::mod_module_files)]
38#![allow(clippy::let_unit_value)] // This can reasonably be done for explicitness
39#![allow(clippy::uninlined_format_args)]
40#![allow(clippy::significant_drop_in_scrutinee)] // arti/-/merge_requests/588/#note_2812945
41#![allow(clippy::result_large_err)] // temporary workaround for arti#587
42#![allow(clippy::needless_raw_string_hashes)] // complained-about code is fine, often best
43#![allow(clippy::needless_lifetimes)] // See arti#1765
44#![allow(mismatched_lifetime_syntaxes)] // temporary workaround for arti#2060
45#![allow(clippy::collapsible_if)] // See arti#2342
46#![deny(clippy::unused_async)]
47#![deny(clippy::string_slice)] // See arti#2571
48//! <!-- @@ end lint list maintained by maint/add_warning @@ -->
49
50// TODO beta clippy bug, rust-clippy/issues/17525
51#![allow(clippy::redundant_field_names)]
52// TODO #1645 (either remove this, or decide to have it everywhere)
53#![cfg_attr(
54    not(all(feature = "full", feature = "experimental")),
55    allow(unused, unreachable_pub)
56)]
57
58#[macro_use] // SerdeStringOrTransparent
59mod time_store;
60
61mod internal_prelude;
62
63mod anon_level;
64mod caps;
65pub mod config;
66mod err;
67mod helpers;
68mod ipt_establish;
69mod ipt_lid;
70mod ipt_mgr;
71mod ipt_set;
72mod keys;
73mod pow;
74mod publish;
75mod rend_handshake;
76mod replay;
77mod req;
78pub mod status;
79mod timeout_track;
80
81// rustdoc doctests can't use crate-public APIs, so are broken if provided for private items.
82// So we export the whole module again under this name.
83// Supports the Example in timeout_track.rs's module-level docs.
84//
85// Any out-of-crate user needs to write this ludicrous name in their code,
86// so we don't need to put any warnings in the docs for the individual items.)
87//
88// (`#[doc(hidden)] pub mod timeout_track;` would work for the test but it would
89// completely suppress the actual documentation, which is not what we want.)
90#[doc(hidden)]
91pub mod timeout_track_for_doctests_unstable_no_semver_guarantees {
92    pub use crate::timeout_track::*;
93}
94#[doc(hidden)]
95pub mod time_store_for_doctests_unstable_no_semver_guarantees {
96    pub use crate::time_store::*;
97}
98
99use std::pin::Pin;
100
101use internal_prelude::*;
102
103// ---------- public exports ----------
104
105pub use anon_level::Anonymity;
106pub use config::OnionServiceConfig;
107pub use err::{ClientError, EstablishSessionError, FatalError, IntroRequestError, StartupError};
108pub use ipt_mgr::IptError;
109use keys::HsTimePeriodKeySpecifier;
110pub use keys::{
111    BlindIdKeypairSpecifier, BlindIdPublicKeySpecifier, DescSigningKeypairSpecifier,
112    HsIdKeypairSpecifier, HsIdPublicKeySpecifier,
113};
114use pow::{NewPowManager, PowManager};
115pub use publish::UploadError as DescUploadError;
116pub use req::{RendRequest, StreamRequest};
117pub use tor_hscrypto::pk::HsId;
118use tor_keymgr::KeystoreEntry;
119pub use tor_persist::hsnickname::{HsNickname, InvalidNickname};
120
121pub use helpers::handle_rend_requests;
122
123#[cfg(feature = "onion-service-cli-extra")]
124use tor_netdir::NetDir;
125
126//---------- top-level service implementation (types and methods) ----------
127
128/// Convenience alias for link specifiers of an intro point
129pub(crate) type LinkSpecs = Vec<tor_linkspec::EncodedLinkSpec>;
130
131/// Convenient type alias for an ntor public key
132// TODO (#1022) maybe this should be
133// `tor_proto::crypto::handshake::ntor::NtorPublicKey`,
134// or a unified OnionKey type.
135pub(crate) type NtorPublicKey = curve25519::PublicKey;
136
137/// A handle to a running instance of an onion service.
138//
139/// To construct a `RunningOnionService`, use [`OnionServiceBuilder`]
140/// to build an [`OnionService`], and then call its
141/// [``.launch()``](OnionService::launch) method.
142//
143// (APIs should return Arc<OnionService>)
144#[must_use = "a hidden service object will terminate the service when dropped"]
145pub struct RunningOnionService {
146    /// The mutable implementation details of this onion service.
147    inner: Mutex<SvcInner>,
148    /// The nickname of this service.
149    nickname: HsNickname,
150    /// The key manager, used for accessing the underlying key stores.
151    keymgr: Arc<KeyMgr>,
152}
153
154/// Implementation details for an onion service.
155struct SvcInner {
156    /// Configuration information about this service.
157    config_tx: postage::watch::Sender<Arc<OnionServiceConfig>>,
158
159    /// A oneshot that will be dropped when this object is dropped.
160    _shutdown_tx: postage::broadcast::Sender<void::Void>,
161
162    /// Postage sender, used to tell subscribers about changes in the status of
163    /// this onion service.
164    status_tx: StatusSender,
165
166    /// Handles that we'll take ownership of when launching the service.
167    #[allow(clippy::type_complexity)]
168    unlaunched: Option<(
169        Pin<Box<dyn Stream<Item = RendRequest> + Send + Sync>>,
170        Box<dyn Launchable + Send + Sync>,
171    )>,
172}
173
174/// Objects and handles needed to launch an onion service.
175struct ForLaunch<R: Runtime> {
176    /// An unlaunched handle for the HsDesc publisher.
177    ///
178    /// This publisher is responsible for determining when we need to upload a
179    /// new set of HsDescs, building them, and publishing them at the correct
180    /// HsDirs.
181    publisher: Publisher<R, publish::Real<R>>,
182
183    /// Our handler for the introduction point manager.
184    ///
185    /// This manager is responsible for selecting introduction points,
186    /// maintaining our connections to them, and telling the publisher which ones
187    /// are publicly available.
188    ipt_mgr: IptManager<R, crate::ipt_mgr::Real<R>>,
189
190    /// A handle used by the ipt manager to send Ipts to the publisher.
191    ///
192    ///
193    ipt_mgr_view: IptsManagerView,
194
195    /// Proof-of-work manager.
196    pow_manager: Arc<PowManager<R>>,
197}
198
199/// Private trait used to type-erase `ForLaunch<R>`, so that we don't need to
200/// parameterize OnionService on `<R>`.
201trait Launchable: Send + Sync {
202    /// Launch
203    fn launch(self: Box<Self>) -> Result<(), StartupError>;
204}
205
206impl<R: Runtime> Launchable for ForLaunch<R> {
207    fn launch(self: Box<Self>) -> Result<(), StartupError> {
208        self.ipt_mgr.launch_background_tasks(self.ipt_mgr_view)?;
209        self.publisher.launch()?;
210        self.pow_manager.launch()?;
211
212        Ok(())
213    }
214}
215
216/// Return value from one call to the main loop iteration
217///
218/// Used by the publisher reactor and by the [`IptManager`].
219#[derive(PartialEq)]
220#[must_use]
221pub(crate) enum ShutdownStatus {
222    /// We should continue to operate this component
223    Continue,
224    /// We should shut down: the service, or maybe the whole process, is shutting down
225    Terminate,
226}
227
228impl From<oneshot::Canceled> for ShutdownStatus {
229    fn from(_: oneshot::Canceled) -> ShutdownStatus {
230        ShutdownStatus::Terminate
231    }
232}
233
234/// A handle to an instance of an onion service.
235///
236/// To construct an `OnionService`, use [`OnionServiceBuilder`].
237/// It will not start handling requests until you call its
238/// [``.launch()``](OnionService::launch) method.
239///
240/// Note: the identity key (HsId) of the service is not generated until
241/// [``.launch()``](OnionService::launch) is called.
242#[derive(Builder)]
243#[builder(build_fn(private, name = "build_unvalidated", error = "FatalError"))]
244pub struct OnionService {
245    /// The current configuration.
246    config: OnionServiceConfig,
247    /// The key manager, used for accessing the underlying key stores.
248    keymgr: Arc<KeyMgr>,
249    /// The location on disk where the persistent data is stored.
250    state_dir: StateDirectory,
251}
252
253impl OnionService {
254    /// Create an [`OnionServiceBuilder`].
255    pub fn builder() -> OnionServiceBuilder {
256        OnionServiceBuilder::default()
257    }
258
259    /// Tell this onion service to begin running, and return a
260    /// [`RunningOnionService`] and its stream of rendezvous requests.
261    ///
262    /// Returns `Ok(None)` if the service specified is disabled in the config.
263    ///
264    /// You can turn the resulting stream into a stream of [`StreamRequest`]
265    /// using the [`handle_rend_requests`] helper function.
266    ///
267    /// Once the `RunningOnionService` is dropped, the onion service will stop
268    /// publishing, and stop accepting new introduction requests.  Existing
269    /// streams and rendezvous circuits will remain open.
270    pub fn launch<R>(
271        self,
272        runtime: R,
273        netdir_provider: Arc<dyn NetDirProvider>,
274        circ_pool: Arc<HsCircPool<R>>,
275        path_resolver: Arc<tor_config_path::CfgPathResolver>,
276    ) -> Result<Option<(Arc<RunningOnionService>, impl Stream<Item = RendRequest>)>, StartupError>
277    where
278        R: Runtime,
279    {
280        let OnionService {
281            config,
282            keymgr,
283            state_dir,
284        } = self;
285
286        let nickname = config.nickname.clone();
287
288        // TODO (#1194): add a config option for specifying whether to expect the KS_hsid to be stored
289        // offline
290        //let offline_hsid = config.offline_hsid;
291        let offline_hsid = false;
292
293        // TODO (#1106): make this configurable
294        let selector = KeystoreSelector::Primary;
295        maybe_generate_hsid(&keymgr, &config.nickname, offline_hsid, selector)?;
296
297        if !config.enabled() {
298            return Ok(None);
299        }
300
301        if config.restricted_discovery.enabled {
302            info!(
303                nickname=%nickname,
304                "Launching onion service in restricted discovery mode"
305            );
306        } else {
307            info!(
308                nickname=%nickname,
309                "Launching onion service"
310            );
311        }
312
313        let state_handle = state_dir
314            .acquire_instance(&config.nickname)
315            .map_err(StartupError::StateDirectoryInaccessible)?;
316
317        // We pass the "cooked" handle, with the storage key embedded, to ipt_set,
318        // since the ipt_set code doesn't otherwise have access to the HS nickname.
319        let iptpub_storage_handle = state_handle
320            .storage_handle("iptpub")
321            .map_err(StartupError::StateDirectoryInaccessible)?;
322
323        let status_tx = StatusSender::new(OnionServiceStatus::new_shutdown());
324        let (config_tx, config_rx) = postage::watch::channel_with(Arc::new(config));
325
326        let pow_manager_storage_handle = state_handle
327            .storage_handle("pow_manager")
328            .map_err(StartupError::StateDirectoryInaccessible)?;
329        let pow_nonce_dir = state_handle
330            .raw_subdir("pow_nonces")
331            .map_err(StartupError::StateDirectoryInaccessible)?;
332        let NewPowManager {
333            pow_manager,
334            rend_req_tx,
335            rend_req_rx,
336            publisher_update_rx,
337        } = PowManager::new(
338            runtime.clone(),
339            nickname.clone(),
340            pow_nonce_dir,
341            keymgr.clone(),
342            pow_manager_storage_handle,
343            netdir_provider.clone(),
344            status_tx.clone().into(),
345            config_rx.clone(),
346        )?;
347
348        let (shutdown_tx, shutdown_rx) = broadcast::channel(0);
349
350        let (ipt_mgr_view, publisher_view) =
351            crate::ipt_set::ipts_channel(&runtime, iptpub_storage_handle)?;
352
353        let ipt_mgr = IptManager::new(
354            runtime.clone(),
355            netdir_provider.clone(),
356            nickname.clone(),
357            config_rx.clone(),
358            rend_req_tx,
359            shutdown_rx.clone(),
360            &state_handle,
361            crate::ipt_mgr::Real {
362                circ_pool: circ_pool.clone(),
363            },
364            keymgr.clone(),
365            status_tx.clone().into(),
366        )?;
367
368        let publisher: Publisher<R, publish::Real<R>> = Publisher::new(
369            runtime,
370            nickname.clone(),
371            netdir_provider,
372            circ_pool,
373            publisher_view,
374            config_rx,
375            status_tx.clone().into(),
376            Arc::clone(&keymgr),
377            path_resolver,
378            pow_manager.clone(),
379            publisher_update_rx,
380        );
381
382        let svc = Arc::new(RunningOnionService {
383            nickname,
384            keymgr,
385            inner: Mutex::new(SvcInner {
386                config_tx,
387                _shutdown_tx: shutdown_tx,
388                status_tx,
389                unlaunched: Some((
390                    rend_req_rx,
391                    Box::new(ForLaunch {
392                        publisher,
393                        ipt_mgr,
394                        ipt_mgr_view,
395                        pow_manager,
396                    }),
397                )),
398            }),
399        });
400
401        let stream = svc.launch()?;
402        Ok(Some((svc, stream)))
403    }
404
405    /// Return the onion address of this service.
406    ///
407    /// Clients must know the service's onion address in order to discover or
408    /// connect to it.
409    ///
410    /// Returns `None` if the HsId of the service could not be found in any of the configured
411    /// keystores.
412    pub fn onion_address(&self) -> Option<HsId> {
413        onion_address(&self.keymgr, &self.config.nickname)
414    }
415
416    /// Return the onion address of this service.
417    ///
418    /// See [`onion_address`](Self::onion_address)
419    #[deprecated = "Use the new onion_address method instead"]
420    pub fn onion_name(&self) -> Option<HsId> {
421        self.onion_address()
422    }
423
424    /// Generate an identity key (KP_hs_id) for this service.
425    ///
426    /// If the keystore specified by `selector` contains an entry for the identity key
427    /// of this service, it will be returned. Otherwise, a new key will be generated.
428    ///
429    /// Most users do not need to call this function: on [`launch`](`OnionService::launch`),
430    /// the service will automatically generate its identity key if needed.
431    /// You should only use this function if you need to know the KP_hs_id of the service
432    /// before launching it.
433    ///
434    /// The `selector` argument is used for choosing the keystore in which to generate the keypair.
435    /// While most users will want to write to the [`Primary`](KeystoreSelector::Primary), if you
436    /// have configured this `TorClient` with a non-default keystore and wish to generate the
437    /// keypair in it, you can do so by calling this function with a [KeystoreSelector::Id]
438    /// specifying the keystore ID of your keystore.
439    ///
440    // Note: the selector argument exists for future-proofing reasons. We don't currently support
441    // configuring custom or non-default keystores (see #1106).
442    pub fn generate_identity_key(&self, selector: KeystoreSelector) -> Result<HsId, StartupError> {
443        // TODO (#1194): add a config option for specifying whether to expect the KS_hsid to be stored
444        // offline
445        //let offline_hsid = config.offline_hsid;
446        let offline_hsid = false;
447
448        maybe_generate_hsid(&self.keymgr, &self.config.nickname, offline_hsid, selector)
449    }
450
451    /// List the no-longer-relevant keys of this service.
452    ///
453    /// Returns the [`KeystoreEntry`]s associated with time periods that are not
454    /// "relevant" according to the specified [`NetDir`],
455    /// (i.e. the keys associated with time periods
456    /// the service is not publishing descriptors for).
457    // TODO: unittest
458    #[cfg(feature = "onion-service-cli-extra")]
459    pub fn list_expired_keys(&self, netdir: &NetDir) -> tor_keymgr::Result<Vec<KeystoreEntry>> {
460        list_expired_keys_for_service(
461            &netdir.hs_all_time_periods(),
462            self.config.nickname(),
463            &self.keymgr,
464        )
465    }
466}
467
468impl OnionServiceBuilder {
469    /// Build the [`OnionService`]
470    pub fn build(&self) -> Result<OnionService, StartupError> {
471        let svc = self.build_unvalidated()?;
472        Ok(svc)
473    }
474}
475
476impl RunningOnionService {
477    /// Change the configuration of this onion service.
478    ///
479    /// (Not everything can be changed here. At the very least we'll need to say
480    /// that the identity of a service is fixed. We might want to make the
481    /// storage  backing this, and the anonymity status, unchangeable.)
482    pub fn reconfigure(
483        &self,
484        new_config: OnionServiceConfig,
485        how: Reconfigure,
486    ) -> Result<(), ReconfigureError> {
487        let mut inner = self.inner.lock().expect("lock poisoned");
488        inner.config_tx.try_maybe_send(|cur_config| {
489            let new_config = cur_config.for_transition_to(new_config, how)?;
490            Ok(match how {
491                // We're only checking, so return the current configuration.
492                tor_config::Reconfigure::CheckAllOrNothing => Arc::clone(cur_config),
493                // We're replacing the configuration, and we didn't get an error.
494                _ => Arc::new(new_config),
495            })
496        })
497
498        // TODO (#1153, #1209): We need to make sure that the various tasks listening on
499        // config_rx actually enforce the configuration, not only on new
500        // connections, but existing ones.
501    }
502
503    /*
504    /// Tell this onion service about some new short-term keys it can use.
505    pub fn add_keys(&self, keys: ()) -> Result<(), Bug> {
506        todo!() // TODO #1194
507    }
508    */
509
510    /// Return the current status of this onion service.
511    pub fn status(&self) -> OnionServiceStatus {
512        self.inner.lock().expect("poisoned lock").status_tx.get()
513    }
514
515    /// Return a stream of events that will receive notifications of changes in
516    /// this onion service's status.
517    pub fn status_events(&self) -> OnionServiceStatusStream {
518        self.inner
519            .lock()
520            .expect("poisoned lock")
521            .status_tx
522            .subscribe()
523    }
524
525    /// Tell this onion service to begin running, and return a
526    /// stream of rendezvous requests on the service.
527    ///
528    /// You can turn the resulting stream into a stream of [`StreamRequest`]
529    /// using the [`handle_rend_requests`] helper function.
530    fn launch(self: &Arc<Self>) -> Result<impl Stream<Item = RendRequest> + use<>, StartupError> {
531        let (rend_req_rx, launch) = {
532            let mut inner = self.inner.lock().expect("poisoned lock");
533            inner
534                .unlaunched
535                .take()
536                .ok_or(StartupError::AlreadyLaunched)?
537        };
538
539        match launch.launch() {
540            Ok(()) => {}
541            Err(e) => {
542                return Err(e);
543            }
544        }
545
546        // This needs to launch at least the following tasks:
547        //
548        // TODO (#1194) If we decide to use separate disk-based key
549        // provisioning, we need a task to monitor our keys directory.
550
551        Ok(rend_req_rx)
552    }
553
554    /*
555    /// Tell this onion service to stop running.
556    ///
557    /// It can be restarted with launch().
558    ///
559    /// You can also shut down an onion service completely by dropping the last
560    /// Clone of it.
561    pub fn pause(&self) {
562        todo!() // TODO (#1231)
563    }
564    */
565
566    /// Return the onion address of this service.
567    ///
568    /// Clients must know the service's onion address in order to discover or
569    /// connect to it.
570    ///
571    /// Returns `None` if the HsId of the service could not be found in any of the configured
572    /// keystores.
573    pub fn onion_address(&self) -> Option<HsId> {
574        onion_address(&self.keymgr, &self.nickname)
575    }
576
577    /// Return the onion address of this service.
578    ///
579    /// See [`onion_address`](Self::onion_address)
580    #[deprecated = "Use the new onion_address method instead"]
581    pub fn onion_name(&self) -> Option<HsId> {
582        self.onion_address()
583    }
584}
585
586/// Generate the identity key of the service, unless it already exists or `offline_hsid` is `true`.
587//
588// TODO (#1194): we don't support offline_hsid yet.
589fn maybe_generate_hsid(
590    keymgr: &Arc<KeyMgr>,
591    nickname: &HsNickname,
592    offline_hsid: bool,
593    selector: KeystoreSelector,
594) -> Result<HsId, StartupError> {
595    if offline_hsid {
596        unimplemented!("offline hsid mode");
597    }
598
599    let hsid_spec = HsIdPublicKeySpecifier::new(nickname.clone());
600
601    let kp = keymgr
602        .get::<HsIdKey>(&hsid_spec)
603        .map_err(|cause| StartupError::Keystore {
604            action: "read",
605            cause,
606        })?;
607
608    let mut rng = tor_llcrypto::rng::CautiousRng;
609    let (hsid, generated) = match kp {
610        Some(kp) => (kp.id(), false),
611        None => {
612            // Note: there is a race here. If the HsId is generated through some other means
613            // (e.g. via the CLI) at some point between the time we looked up the keypair and
614            // now, we will return an error.
615            let hsid_spec = HsIdKeypairSpecifier::new(nickname.clone());
616            let kp = keymgr
617                .generate::<HsIdKeypair>(&hsid_spec, selector, &mut rng, false /* overwrite */)
618                .map_err(|cause| StartupError::Keystore {
619                    action: "generate",
620                    cause,
621                })?;
622
623            (HsIdKey::from(&kp).id(), true)
624        }
625    };
626
627    if generated {
628        info!(
629            "Generated a new identity for service {nickname}: {}",
630            hsid.display_redacted()
631        );
632    } else {
633        // TODO: We may want to downgrade this to trace once we have a CLI
634        // for extracting it.
635        info!(
636            "Using existing identity for service {nickname}: {}",
637            hsid.display_redacted()
638        );
639    }
640
641    Ok(hsid)
642}
643
644/// Return the onion address of this service.
645///
646/// Clients must know the service's onion address in order to discover or
647/// connect to it.
648///
649/// Returns `None` if the HsId of the service could not be found in any of the configured
650/// keystores.
651//
652// TODO: instead of duplicating RunningOnionService::onion_address, maybe we should make this a
653// method on an ArtiHss type, and make both OnionService and RunningOnionService deref to
654// ArtiHss.
655fn onion_address(keymgr: &KeyMgr, nickname: &HsNickname) -> Option<HsId> {
656    let hsid_spec = HsIdPublicKeySpecifier::new(nickname.clone());
657
658    keymgr
659        .get::<HsIdKey>(&hsid_spec)
660        .ok()?
661        .map(|hsid| hsid.id())
662}
663
664/// Return a list of the protocols[supported](tor_protover::doc_supported)
665/// by this crate, running as a hidden service.
666pub fn supported_hsservice_protocols() -> tor_protover::Protocols {
667    use tor_protover::named::*;
668    // WARNING: REMOVING ELEMENTS FROM THIS LIST CAN BE DANGEROUS!
669    // SEE [`tor_protover::doc_changing`]
670    [
671        //
672        HSINTRO_V3,
673        HSINTRO_RATELIM,
674        HSREND_V3,
675        HSDIR_V3,
676    ]
677    .into_iter()
678    .collect()
679}
680
681/// Returns all the keys (as [`KeystoreEntry`]) of the service
682/// identified by `nickname` that are expired according to the
683/// provided [`HsDirParams`].
684fn list_expired_keys_for_service<'a>(
685    relevant_periods: &[HsDirParams],
686    nickname: &HsNickname,
687    keymgr: &'a KeyMgr,
688) -> tor_keymgr::Result<Vec<KeystoreEntry<'a>>> {
689    let arti_pat = tor_keymgr::KeyPathPattern::Arti(format!("hss/{}/*", nickname));
690    let possibly_relevant_keys = keymgr.list_matching(&arti_pat)?;
691    let mut expired_keys = Vec::new();
692
693    for entry in possibly_relevant_keys {
694        let key_path = entry.key_path();
695        let mut append_if_expired = |spec: &dyn HsTimePeriodKeySpecifier| {
696            if spec.nickname() != nickname {
697                return Err(internal!(
698                    "keymgr gave us key {spec:?} that doesn't match our pattern {arti_pat:?}"
699                )
700                .into());
701            }
702            let is_expired = relevant_periods
703                .iter()
704                .all(|p| &p.time_period() != spec.period());
705
706            if is_expired {
707                expired_keys.push(entry.clone());
708            }
709
710            tor_keymgr::Result::Ok(())
711        };
712
713        macro_rules! append_if_expired {
714            ($K:ty) => {{
715                if let Ok(spec) = <$K>::try_from(key_path) {
716                    append_if_expired(&spec)?;
717                }
718            }};
719        }
720
721        append_if_expired!(BlindIdPublicKeySpecifier);
722        append_if_expired!(BlindIdKeypairSpecifier);
723        append_if_expired!(DescSigningKeypairSpecifier);
724    }
725
726    Ok(expired_keys)
727}
728
729#[cfg(test)]
730pub(crate) mod test {
731    // @@ begin test lint list maintained by maint/add_warning @@
732    #![allow(clippy::bool_assert_comparison)]
733    #![allow(clippy::clone_on_copy)]
734    #![allow(clippy::dbg_macro)]
735    #![allow(clippy::mixed_attributes_style)]
736    #![allow(clippy::print_stderr)]
737    #![allow(clippy::print_stdout)]
738    #![allow(clippy::single_char_pattern)]
739    #![allow(clippy::unwrap_used)]
740    #![allow(clippy::unchecked_time_subtraction)]
741    #![allow(clippy::useless_vec)]
742    #![allow(clippy::needless_pass_by_value)]
743    #![allow(clippy::string_slice)] // See arti#2571
744    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
745    use super::*;
746
747    use std::fmt::Display;
748    use std::path::Path;
749
750    use fs_mistrust::Mistrust;
751    use test_temp_dir::{TestTempDir, TestTempDirGuard, test_temp_dir};
752
753    use tor_basic_utils::test_rng::testing_rng;
754    use tor_keymgr::{ArtiNativeKeystore, KeyMgrBuilder};
755    use tor_llcrypto::pk::ed25519;
756    use tor_persist::state_dir::InstanceStateHandle;
757
758    use crate::config::OnionServiceConfigBuilder;
759    use crate::ipt_set::IptSetStorageHandle;
760    use crate::{HsIdKeypairSpecifier, HsIdPublicKeySpecifier};
761
762    /// The nickname of the test service.
763    const TEST_SVC_NICKNAME: &str = "test-svc";
764
765    #[test]
766    fn protocols() {
767        let pr = supported_hsservice_protocols();
768        let expected = "HSIntro=4-5 HSRend=2 HSDir=2".parse().unwrap();
769        assert_eq!(pr, expected);
770    }
771
772    /// Make a fresh `KeyMgr` (containing no keys) using files in `temp_dir`
773    pub(crate) fn create_keymgr(temp_dir: &TestTempDir) -> TestTempDirGuard<Arc<KeyMgr>> {
774        temp_dir.subdir_used_by("keystore", |keystore_dir| {
775            let keystore = ArtiNativeKeystore::from_path_and_mistrust(
776                keystore_dir,
777                &Mistrust::new_dangerously_trust_everyone(),
778            )
779            .unwrap();
780
781            Arc::new(
782                KeyMgrBuilder::default()
783                    .primary_store(Box::new(keystore))
784                    .build()
785                    .unwrap(),
786            )
787        })
788    }
789
790    #[allow(clippy::let_and_return)] // clearer and more regular
791    pub(crate) fn mk_state_instance(dir: &Path, nick: impl Display) -> InstanceStateHandle {
792        let nick = HsNickname::new(nick.to_string()).unwrap();
793        let mistrust = fs_mistrust::Mistrust::new_dangerously_trust_everyone();
794        let state_dir = StateDirectory::new(dir, &mistrust).unwrap();
795        let instance = state_dir.acquire_instance(&nick).unwrap();
796        instance
797    }
798
799    pub(crate) fn create_storage_handles(
800        dir: &Path,
801    ) -> (
802        tor_persist::state_dir::InstanceStateHandle,
803        IptSetStorageHandle,
804    ) {
805        let nick = HsNickname::try_from("allium".to_owned()).unwrap();
806        create_storage_handles_from_state_dir(dir, &nick)
807    }
808
809    pub(crate) fn create_storage_handles_from_state_dir(
810        state_dir: &Path,
811        nick: &HsNickname,
812    ) -> (
813        tor_persist::state_dir::InstanceStateHandle,
814        IptSetStorageHandle,
815    ) {
816        let instance = mk_state_instance(state_dir, nick);
817        let iptpub_state_handle = instance.storage_handle("iptpub").unwrap();
818        (instance, iptpub_state_handle)
819    }
820
821    macro_rules! maybe_generate_hsid {
822        ($keymgr:expr, $offline_hsid:expr) => {{
823            let nickname = HsNickname::try_from(TEST_SVC_NICKNAME.to_string()).unwrap();
824            let hsid_spec = HsIdKeypairSpecifier::new(nickname.clone());
825            let pub_hsid_spec = HsIdPublicKeySpecifier::new(nickname.clone());
826
827            assert!($keymgr.get::<HsIdKey>(&pub_hsid_spec).unwrap().is_none());
828            assert!($keymgr.get::<HsIdKeypair>(&hsid_spec).unwrap().is_none());
829
830            maybe_generate_hsid(&$keymgr, &nickname, $offline_hsid, Default::default()).unwrap();
831        }};
832    }
833
834    /// Create a test hsid keypair.
835    fn create_hsid() -> (HsIdKeypair, HsIdKey) {
836        let mut rng = testing_rng();
837        let keypair = ed25519::Keypair::generate(&mut rng);
838
839        let id_pub = HsIdKey::from(keypair.verifying_key());
840        let id_keypair = HsIdKeypair::from(ed25519::ExpandedKeypair::from(&keypair));
841
842        (id_keypair, id_pub)
843    }
844
845    #[test]
846    fn generate_hsid() {
847        let temp_dir = test_temp_dir!();
848        let keymgr = create_keymgr(&temp_dir);
849
850        let nickname = HsNickname::try_from(TEST_SVC_NICKNAME.to_string()).unwrap();
851        let hsid_spec = HsIdKeypairSpecifier::new(nickname.clone());
852
853        assert!(keymgr.get::<HsIdKeypair>(&hsid_spec).unwrap().is_none());
854        maybe_generate_hsid!(keymgr, false /* offline_hsid */);
855        assert!(keymgr.get::<HsIdKeypair>(&hsid_spec).unwrap().is_some());
856    }
857
858    #[test]
859    fn hsid_keypair_already_exists() {
860        let temp_dir = test_temp_dir!();
861        let nickname = HsNickname::try_from(TEST_SVC_NICKNAME.to_string()).unwrap();
862        let hsid_spec = HsIdKeypairSpecifier::new(nickname.clone());
863        let keymgr = create_keymgr(&temp_dir);
864
865        // Insert the preexisting hsid keypair.
866        let (existing_hsid_keypair, existing_hsid_public) = create_hsid();
867        let existing_keypair: ed25519::ExpandedKeypair = existing_hsid_keypair.into();
868        let existing_hsid_keypair = HsIdKeypair::from(existing_keypair);
869
870        keymgr
871            .insert(
872                existing_hsid_keypair,
873                &hsid_spec,
874                KeystoreSelector::Primary,
875                true,
876            )
877            .unwrap();
878
879        maybe_generate_hsid(
880            &keymgr,
881            &nickname,
882            false, /* offline_hsid */
883            Default::default(),
884        )
885        .unwrap();
886
887        let keypair = keymgr.get::<HsIdKeypair>(&hsid_spec).unwrap().unwrap();
888        let pk: HsIdKey = (&keypair).into();
889
890        assert_eq!(pk.as_ref(), existing_hsid_public.as_ref());
891    }
892
893    #[test]
894    #[ignore] // TODO (#1194): Revisit when we add support for offline hsid mode
895    fn generate_hsid_offline_hsid() {
896        let temp_dir = test_temp_dir!();
897        let keymgr = create_keymgr(&temp_dir);
898
899        let nickname = HsNickname::try_from(TEST_SVC_NICKNAME.to_string()).unwrap();
900        let hsid_spec = HsIdKeypairSpecifier::new(nickname.clone());
901        let pub_hsid_spec = HsIdPublicKeySpecifier::new(nickname.clone());
902
903        maybe_generate_hsid!(keymgr, true /* offline_hsid */);
904
905        assert!(keymgr.get::<HsIdKey>(&pub_hsid_spec).unwrap().is_none());
906        assert!(keymgr.get::<HsIdKeypair>(&hsid_spec).unwrap().is_none());
907    }
908
909    #[test]
910    #[ignore] // TODO (#1194): Revisit when we add support for offline hsid mode
911    fn generate_hsid_corrupt_keystore() {
912        let temp_dir = test_temp_dir!();
913        let nickname = HsNickname::try_from(TEST_SVC_NICKNAME.to_string()).unwrap();
914        let hsid_spec = HsIdKeypairSpecifier::new(nickname.clone());
915        let pub_hsid_spec = HsIdPublicKeySpecifier::new(nickname.clone());
916
917        let keymgr = create_keymgr(&temp_dir);
918
919        let (hsid_keypair, _hsid_public) = create_hsid();
920        let (_hsid_keypair, hsid_public) = create_hsid();
921
922        keymgr
923            .insert(hsid_keypair, &hsid_spec, KeystoreSelector::Primary, true)
924            .unwrap();
925
926        // Insert a mismatched public key
927        keymgr
928            .insert(hsid_public, &pub_hsid_spec, KeystoreSelector::Primary, true)
929            .unwrap();
930
931        assert!(
932            maybe_generate_hsid(
933                &keymgr,
934                &nickname,
935                false, /* offline_hsid */
936                Default::default()
937            )
938            .is_err()
939        );
940    }
941
942    #[test]
943    fn onion_address() {
944        let temp_dir = test_temp_dir!();
945        let nickname = HsNickname::try_from(TEST_SVC_NICKNAME.to_string()).unwrap();
946        let hsid_spec = HsIdKeypairSpecifier::new(nickname.clone());
947        let keymgr = create_keymgr(&temp_dir);
948
949        let (hsid_keypair, hsid_public) = create_hsid();
950
951        // Insert the hsid into the keystore
952        keymgr
953            .insert(hsid_keypair, &hsid_spec, KeystoreSelector::Primary, true)
954            .unwrap();
955
956        let config = OnionServiceConfigBuilder::default()
957            .nickname(nickname)
958            .build()
959            .unwrap();
960
961        let state_dir = StateDirectory::new(
962            temp_dir.as_path_untracked(),
963            &fs_mistrust::Mistrust::new_dangerously_trust_everyone(),
964        )
965        .unwrap();
966
967        let service = OnionService::builder()
968            .config(config)
969            .keymgr(Arc::clone(&*keymgr))
970            .state_dir(state_dir)
971            .build()
972            .unwrap();
973
974        let hsid = HsId::from(hsid_public);
975        assert_eq!(service.onion_address().unwrap(), hsid);
976
977        drop(temp_dir); // prove that this is still live
978    }
979}