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