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