tor_hsservice/
lib.rs

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