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