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