1#![cfg_attr(docsrs, feature(doc_cfg))]
2#![doc = include_str!("../README.md")]
3#![allow(renamed_and_removed_lints)] #![allow(unknown_lints)] #![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)] #![allow(clippy::uninlined_format_args)]
40#![allow(clippy::significant_drop_in_scrutinee)] #![allow(clippy::result_large_err)] #![allow(clippy::needless_raw_string_hashes)] #![allow(clippy::needless_lifetimes)] #![allow(mismatched_lifetime_syntaxes)] #![allow(clippy::collapsible_if)] #![deny(clippy::unused_async)]
47#![deny(clippy::string_slice)] #![cfg_attr(
52 not(all(feature = "full", feature = "experimental")),
53 allow(unused, unreachable_pub)
54)]
55
56#[macro_use] mod 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#[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
100pub 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
123pub(crate) type LinkSpecs = Vec<tor_linkspec::EncodedLinkSpec>;
127
128pub(crate) type NtorPublicKey = curve25519::PublicKey;
133
134#[must_use = "a hidden service object will terminate the service when dropped"]
142pub struct RunningOnionService {
143 inner: Mutex<SvcInner>,
145 nickname: HsNickname,
147 keymgr: Arc<KeyMgr>,
149}
150
151struct SvcInner {
153 config_tx: postage::watch::Sender<Arc<OnionServiceConfig>>,
155
156 _shutdown_tx: postage::broadcast::Sender<void::Void>,
158
159 status_tx: StatusSender,
162
163 #[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
171struct ForLaunch<R: Runtime> {
173 publisher: Publisher<R, publish::Real<R>>,
179
180 ipt_mgr: IptManager<R, crate::ipt_mgr::Real<R>>,
186
187 ipt_mgr_view: IptsManagerView,
191
192 pow_manager: Arc<PowManager<R>>,
194}
195
196trait Launchable: Send + Sync {
199 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#[derive(PartialEq)]
217#[must_use]
218pub(crate) enum ShutdownStatus {
219 Continue,
221 Terminate,
223}
224
225impl From<oneshot::Canceled> for ShutdownStatus {
226 fn from(_: oneshot::Canceled) -> ShutdownStatus {
227 ShutdownStatus::Terminate
228 }
229}
230
231#[derive(Builder)]
240#[builder(build_fn(private, name = "build_unvalidated", error = "FatalError"))]
241pub struct OnionService {
242 config: OnionServiceConfig,
244 keymgr: Arc<KeyMgr>,
246 state_dir: StateDirectory,
248}
249
250impl OnionService {
251 pub fn builder() -> OnionServiceBuilder {
253 OnionServiceBuilder::default()
254 }
255
256 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 let offline_hsid = false;
289
290 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 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 pub fn onion_address(&self) -> Option<HsId> {
410 onion_address(&self.keymgr, &self.config.nickname)
411 }
412
413 #[deprecated = "Use the new onion_address method instead"]
417 pub fn onion_name(&self) -> Option<HsId> {
418 self.onion_address()
419 }
420
421 pub fn generate_identity_key(&self, selector: KeystoreSelector) -> Result<HsId, StartupError> {
440 let offline_hsid = false;
444
445 maybe_generate_hsid(&self.keymgr, &self.config.nickname, offline_hsid, selector)
446 }
447
448 #[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 pub fn build(&self) -> Result<OnionService, StartupError> {
468 let svc = self.build_unvalidated()?;
469 Ok(svc)
470 }
471}
472
473impl RunningOnionService {
474 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 tor_config::Reconfigure::CheckAllOrNothing => Arc::clone(cur_config),
490 _ => Arc::new(new_config),
492 })
493 })
494
495 }
499
500 pub fn status(&self) -> OnionServiceStatus {
509 self.inner.lock().expect("poisoned lock").status_tx.get()
510 }
511
512 pub fn status_events(&self) -> OnionServiceStatusStream {
515 self.inner
516 .lock()
517 .expect("poisoned lock")
518 .status_tx
519 .subscribe()
520 }
521
522 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 Ok(rend_req_rx)
549 }
550
551 pub fn onion_address(&self) -> Option<HsId> {
571 onion_address(&self.keymgr, &self.nickname)
572 }
573
574 #[deprecated = "Use the new onion_address method instead"]
578 pub fn onion_name(&self) -> Option<HsId> {
579 self.onion_address()
580 }
581}
582
583fn 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 let hsid_spec = HsIdKeypairSpecifier::new(nickname.clone());
613 let kp = keymgr
614 .generate::<HsIdKeypair>(&hsid_spec, selector, &mut rng, false )
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 info!(
633 "Using existing identity for service {nickname}: {}",
634 hsid.display_redacted()
635 );
636 }
637
638 Ok(hsid)
639}
640
641fn 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
661pub fn supported_hsservice_protocols() -> tor_protover::Protocols {
664 use tor_protover::named::*;
665 [
668 HSINTRO_V3,
670 HSINTRO_RATELIM,
671 HSREND_V3,
672 HSDIR_V3,
673 ]
674 .into_iter()
675 .collect()
676}
677
678fn 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 #![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)] 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 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 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)] 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 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 );
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 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, 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] 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 );
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] 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 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, 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 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); }
976}