Skip to main content

tor_hsservice/
lib.rs

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