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#![cfg_attr(
51 not(all(feature = "full", feature = "experimental")),
52 allow(unused, unreachable_pub)
53)]
54
55#[macro_use] mod time_store;
57
58mod internal_prelude;
59
60mod anon_level;
61pub mod config;
62mod err;
63mod helpers;
64mod ipt_establish;
65mod ipt_lid;
66mod ipt_mgr;
67mod ipt_set;
68mod keys;
69mod pow;
70mod publish;
71mod rend_handshake;
72mod replay;
73mod req;
74pub mod status;
75mod timeout_track;
76
77#[doc(hidden)]
87pub mod timeout_track_for_doctests_unstable_no_semver_guarantees {
88 pub use crate::timeout_track::*;
89}
90#[doc(hidden)]
91pub mod time_store_for_doctests_unstable_no_semver_guarantees {
92 pub use crate::time_store::*;
93}
94
95use std::pin::Pin;
96
97use internal_prelude::*;
98
99pub use anon_level::Anonymity;
102pub use config::OnionServiceConfig;
103pub use err::{ClientError, EstablishSessionError, FatalError, IntroRequestError, StartupError};
104pub use ipt_mgr::IptError;
105use keys::HsTimePeriodKeySpecifier;
106pub use keys::{
107 BlindIdKeypairSpecifier, BlindIdPublicKeySpecifier, DescSigningKeypairSpecifier,
108 HsIdKeypairSpecifier, HsIdPublicKeySpecifier,
109};
110use pow::{NewPowManager, PowManager};
111pub use publish::UploadError as DescUploadError;
112pub use req::{RendRequest, StreamRequest};
113pub use tor_hscrypto::pk::HsId;
114use tor_keymgr::KeystoreEntry;
115pub use tor_persist::hsnickname::{HsNickname, InvalidNickname};
116
117pub use helpers::handle_rend_requests;
118
119#[cfg(feature = "onion-service-cli-extra")]
120use tor_netdir::NetDir;
121
122pub(crate) type LinkSpecs = Vec<tor_linkspec::EncodedLinkSpec>;
126
127pub(crate) type NtorPublicKey = curve25519::PublicKey;
132
133#[must_use = "a hidden service object will terminate the service when dropped"]
141pub struct RunningOnionService {
142 inner: Mutex<SvcInner>,
144 nickname: HsNickname,
146 keymgr: Arc<KeyMgr>,
148}
149
150struct SvcInner {
152 config_tx: postage::watch::Sender<Arc<OnionServiceConfig>>,
154
155 _shutdown_tx: postage::broadcast::Sender<void::Void>,
157
158 status_tx: StatusSender,
161
162 #[allow(clippy::type_complexity)]
164 unlaunched: Option<(
165 Pin<Box<dyn Stream<Item = RendRequest> + Send + Sync>>,
166 Box<dyn Launchable + Send + Sync>,
167 )>,
168}
169
170struct ForLaunch<R: Runtime> {
172 publisher: Publisher<R, publish::Real<R>>,
178
179 ipt_mgr: IptManager<R, crate::ipt_mgr::Real<R>>,
185
186 ipt_mgr_view: IptsManagerView,
190
191 pow_manager: Arc<PowManager<R>>,
193}
194
195trait Launchable: Send + Sync {
198 fn launch(self: Box<Self>) -> Result<(), StartupError>;
200}
201
202impl<R: Runtime> Launchable for ForLaunch<R> {
203 fn launch(self: Box<Self>) -> Result<(), StartupError> {
204 self.ipt_mgr.launch_background_tasks(self.ipt_mgr_view)?;
205 self.publisher.launch()?;
206 self.pow_manager.launch()?;
207
208 Ok(())
209 }
210}
211
212#[derive(PartialEq)]
216#[must_use]
217pub(crate) enum ShutdownStatus {
218 Continue,
220 Terminate,
222}
223
224impl From<oneshot::Canceled> for ShutdownStatus {
225 fn from(_: oneshot::Canceled) -> ShutdownStatus {
226 ShutdownStatus::Terminate
227 }
228}
229
230#[derive(Builder)]
239#[builder(build_fn(private, name = "build_unvalidated", error = "FatalError"))]
240pub struct OnionService {
241 config: OnionServiceConfig,
243 keymgr: Arc<KeyMgr>,
245 state_dir: StateDirectory,
247}
248
249impl OnionService {
250 pub fn builder() -> OnionServiceBuilder {
252 OnionServiceBuilder::default()
253 }
254
255 pub fn launch<R>(
267 self,
268 runtime: R,
269 netdir_provider: Arc<dyn NetDirProvider>,
270 circ_pool: Arc<HsCircPool<R>>,
271 path_resolver: Arc<tor_config_path::CfgPathResolver>,
272 ) -> Result<Option<(Arc<RunningOnionService>, impl Stream<Item = RendRequest>)>, StartupError>
273 where
274 R: Runtime,
275 {
276 let OnionService {
277 config,
278 keymgr,
279 state_dir,
280 } = self;
281
282 let nickname = config.nickname.clone();
283
284 let offline_hsid = false;
288
289 let selector = KeystoreSelector::Primary;
291 maybe_generate_hsid(&keymgr, &config.nickname, offline_hsid, selector)?;
292
293 if !config.enabled() {
294 return Ok(None);
295 }
296
297 if config.restricted_discovery.enabled {
298 info!(
299 nickname=%nickname,
300 "Launching onion service in restricted discovery mode"
301 );
302 } else {
303 info!(
304 nickname=%nickname,
305 "Launching onion service"
306 );
307 }
308
309 let state_handle = state_dir
310 .acquire_instance(&config.nickname)
311 .map_err(StartupError::StateDirectoryInaccessible)?;
312
313 let iptpub_storage_handle = state_handle
316 .storage_handle("iptpub")
317 .map_err(StartupError::StateDirectoryInaccessible)?;
318
319 let status_tx = StatusSender::new(OnionServiceStatus::new_shutdown());
320 let (config_tx, config_rx) = postage::watch::channel_with(Arc::new(config));
321
322 let pow_manager_storage_handle = state_handle
323 .storage_handle("pow_manager")
324 .map_err(StartupError::StateDirectoryInaccessible)?;
325 let pow_nonce_dir = state_handle
326 .raw_subdir("pow_nonces")
327 .map_err(StartupError::StateDirectoryInaccessible)?;
328 let NewPowManager {
329 pow_manager,
330 rend_req_tx,
331 rend_req_rx,
332 publisher_update_rx,
333 } = PowManager::new(
334 runtime.clone(),
335 nickname.clone(),
336 pow_nonce_dir,
337 keymgr.clone(),
338 pow_manager_storage_handle,
339 netdir_provider.clone(),
340 status_tx.clone().into(),
341 config_rx.clone(),
342 )?;
343
344 let (shutdown_tx, shutdown_rx) = broadcast::channel(0);
345
346 let (ipt_mgr_view, publisher_view) =
347 crate::ipt_set::ipts_channel(&runtime, iptpub_storage_handle)?;
348
349 let ipt_mgr = IptManager::new(
350 runtime.clone(),
351 netdir_provider.clone(),
352 nickname.clone(),
353 config_rx.clone(),
354 rend_req_tx,
355 shutdown_rx.clone(),
356 &state_handle,
357 crate::ipt_mgr::Real {
358 circ_pool: circ_pool.clone(),
359 },
360 keymgr.clone(),
361 status_tx.clone().into(),
362 )?;
363
364 let publisher: Publisher<R, publish::Real<R>> = Publisher::new(
365 runtime,
366 nickname.clone(),
367 netdir_provider,
368 circ_pool,
369 publisher_view,
370 config_rx,
371 status_tx.clone().into(),
372 Arc::clone(&keymgr),
373 path_resolver,
374 pow_manager.clone(),
375 publisher_update_rx,
376 );
377
378 let svc = Arc::new(RunningOnionService {
379 nickname,
380 keymgr,
381 inner: Mutex::new(SvcInner {
382 config_tx,
383 _shutdown_tx: shutdown_tx,
384 status_tx,
385 unlaunched: Some((
386 rend_req_rx,
387 Box::new(ForLaunch {
388 publisher,
389 ipt_mgr,
390 ipt_mgr_view,
391 pow_manager,
392 }),
393 )),
394 }),
395 });
396
397 let stream = svc.launch()?;
398 Ok(Some((svc, stream)))
399 }
400
401 pub fn onion_address(&self) -> Option<HsId> {
409 onion_address(&self.keymgr, &self.config.nickname)
410 }
411
412 #[deprecated = "Use the new onion_address method instead"]
416 pub fn onion_name(&self) -> Option<HsId> {
417 self.onion_address()
418 }
419
420 pub fn generate_identity_key(&self, selector: KeystoreSelector) -> Result<HsId, StartupError> {
439 let offline_hsid = false;
443
444 maybe_generate_hsid(&self.keymgr, &self.config.nickname, offline_hsid, selector)
445 }
446
447 #[cfg(feature = "onion-service-cli-extra")]
455 pub fn list_expired_keys(&self, netdir: &NetDir) -> tor_keymgr::Result<Vec<KeystoreEntry>> {
456 list_expired_keys_for_service(
457 &netdir.hs_all_time_periods(),
458 self.config.nickname(),
459 &self.keymgr,
460 )
461 }
462}
463
464impl OnionServiceBuilder {
465 pub fn build(&self) -> Result<OnionService, StartupError> {
467 let svc = self.build_unvalidated()?;
468 Ok(svc)
469 }
470}
471
472impl RunningOnionService {
473 pub fn reconfigure(
479 &self,
480 new_config: OnionServiceConfig,
481 how: Reconfigure,
482 ) -> Result<(), ReconfigureError> {
483 let mut inner = self.inner.lock().expect("lock poisoned");
484 inner.config_tx.try_maybe_send(|cur_config| {
485 let new_config = cur_config.for_transition_to(new_config, how)?;
486 Ok(match how {
487 tor_config::Reconfigure::CheckAllOrNothing => Arc::clone(cur_config),
489 _ => Arc::new(new_config),
491 })
492 })
493
494 }
498
499 pub fn status(&self) -> OnionServiceStatus {
508 self.inner.lock().expect("poisoned lock").status_tx.get()
509 }
510
511 pub fn status_events(&self) -> OnionServiceStatusStream {
514 self.inner
515 .lock()
516 .expect("poisoned lock")
517 .status_tx
518 .subscribe()
519 }
520
521 fn launch(self: &Arc<Self>) -> Result<impl Stream<Item = RendRequest> + use<>, StartupError> {
527 let (rend_req_rx, launch) = {
528 let mut inner = self.inner.lock().expect("poisoned lock");
529 inner
530 .unlaunched
531 .take()
532 .ok_or(StartupError::AlreadyLaunched)?
533 };
534
535 match launch.launch() {
536 Ok(()) => {}
537 Err(e) => {
538 return Err(e);
539 }
540 }
541
542 Ok(rend_req_rx)
548 }
549
550 pub fn onion_address(&self) -> Option<HsId> {
570 onion_address(&self.keymgr, &self.nickname)
571 }
572
573 #[deprecated = "Use the new onion_address method instead"]
577 pub fn onion_name(&self) -> Option<HsId> {
578 self.onion_address()
579 }
580}
581
582fn maybe_generate_hsid(
586 keymgr: &Arc<KeyMgr>,
587 nickname: &HsNickname,
588 offline_hsid: bool,
589 selector: KeystoreSelector,
590) -> Result<HsId, StartupError> {
591 if offline_hsid {
592 unimplemented!("offline hsid mode");
593 }
594
595 let hsid_spec = HsIdPublicKeySpecifier::new(nickname.clone());
596
597 let kp = keymgr
598 .get::<HsIdKey>(&hsid_spec)
599 .map_err(|cause| StartupError::Keystore {
600 action: "read",
601 cause,
602 })?;
603
604 let mut rng = tor_llcrypto::rng::CautiousRng;
605 let (hsid, generated) = match kp {
606 Some(kp) => (kp.id(), false),
607 None => {
608 let hsid_spec = HsIdKeypairSpecifier::new(nickname.clone());
612 let kp = keymgr
613 .generate::<HsIdKeypair>(&hsid_spec, selector, &mut rng, false )
614 .map_err(|cause| StartupError::Keystore {
615 action: "generate",
616 cause,
617 })?;
618
619 (HsIdKey::from(&kp).id(), true)
620 }
621 };
622
623 if generated {
624 info!(
625 "Generated a new identity for service {nickname}: {}",
626 hsid.display_redacted()
627 );
628 } else {
629 info!(
632 "Using existing identity for service {nickname}: {}",
633 hsid.display_redacted()
634 );
635 }
636
637 Ok(hsid)
638}
639
640fn onion_address(keymgr: &KeyMgr, nickname: &HsNickname) -> Option<HsId> {
652 let hsid_spec = HsIdPublicKeySpecifier::new(nickname.clone());
653
654 keymgr
655 .get::<HsIdKey>(&hsid_spec)
656 .ok()?
657 .map(|hsid| hsid.id())
658}
659
660pub fn supported_hsservice_protocols() -> tor_protover::Protocols {
663 use tor_protover::named::*;
664 [
667 HSINTRO_V3,
669 HSINTRO_RATELIM,
670 HSREND_V3,
671 HSDIR_V3,
672 ]
673 .into_iter()
674 .collect()
675}
676
677fn list_expired_keys_for_service<'a>(
681 relevant_periods: &[HsDirParams],
682 nickname: &HsNickname,
683 keymgr: &'a KeyMgr,
684) -> tor_keymgr::Result<Vec<KeystoreEntry<'a>>> {
685 let arti_pat = tor_keymgr::KeyPathPattern::Arti(format!("hss/{}/*", nickname));
686 let possibly_relevant_keys = keymgr.list_matching(&arti_pat)?;
687 let mut expired_keys = Vec::new();
688
689 for entry in possibly_relevant_keys {
690 let key_path = entry.key_path();
691 let mut append_if_expired = |spec: &dyn HsTimePeriodKeySpecifier| {
692 if spec.nickname() != nickname {
693 return Err(internal!(
694 "keymgr gave us key {spec:?} that doesn't match our pattern {arti_pat:?}"
695 )
696 .into());
697 }
698 let is_expired = relevant_periods
699 .iter()
700 .all(|p| &p.time_period() != spec.period());
701
702 if is_expired {
703 expired_keys.push(entry.clone());
704 }
705
706 tor_keymgr::Result::Ok(())
707 };
708
709 macro_rules! append_if_expired {
710 ($K:ty) => {{
711 if let Ok(spec) = <$K>::try_from(key_path) {
712 append_if_expired(&spec)?;
713 }
714 }};
715 }
716
717 append_if_expired!(BlindIdPublicKeySpecifier);
718 append_if_expired!(BlindIdKeypairSpecifier);
719 append_if_expired!(DescSigningKeypairSpecifier);
720 }
721
722 Ok(expired_keys)
723}
724
725#[cfg(test)]
726pub(crate) mod test {
727 #![allow(clippy::bool_assert_comparison)]
729 #![allow(clippy::clone_on_copy)]
730 #![allow(clippy::dbg_macro)]
731 #![allow(clippy::mixed_attributes_style)]
732 #![allow(clippy::print_stderr)]
733 #![allow(clippy::print_stdout)]
734 #![allow(clippy::single_char_pattern)]
735 #![allow(clippy::unwrap_used)]
736 #![allow(clippy::unchecked_time_subtraction)]
737 #![allow(clippy::useless_vec)]
738 #![allow(clippy::needless_pass_by_value)]
739 use super::*;
741
742 use std::fmt::Display;
743 use std::path::Path;
744
745 use fs_mistrust::Mistrust;
746 use test_temp_dir::{TestTempDir, TestTempDirGuard, test_temp_dir};
747
748 use tor_basic_utils::test_rng::testing_rng;
749 use tor_keymgr::{ArtiNativeKeystore, KeyMgrBuilder};
750 use tor_llcrypto::pk::ed25519;
751 use tor_persist::state_dir::InstanceStateHandle;
752
753 use crate::config::OnionServiceConfigBuilder;
754 use crate::ipt_set::IptSetStorageHandle;
755 use crate::{HsIdKeypairSpecifier, HsIdPublicKeySpecifier};
756
757 const TEST_SVC_NICKNAME: &str = "test-svc";
759
760 #[test]
761 fn protocols() {
762 let pr = supported_hsservice_protocols();
763 let expected = "HSIntro=4-5 HSRend=2 HSDir=2".parse().unwrap();
764 assert_eq!(pr, expected);
765 }
766
767 pub(crate) fn create_keymgr(temp_dir: &TestTempDir) -> TestTempDirGuard<Arc<KeyMgr>> {
769 temp_dir.subdir_used_by("keystore", |keystore_dir| {
770 let keystore = ArtiNativeKeystore::from_path_and_mistrust(
771 keystore_dir,
772 &Mistrust::new_dangerously_trust_everyone(),
773 )
774 .unwrap();
775
776 Arc::new(
777 KeyMgrBuilder::default()
778 .primary_store(Box::new(keystore))
779 .build()
780 .unwrap(),
781 )
782 })
783 }
784
785 #[allow(clippy::let_and_return)] pub(crate) fn mk_state_instance(dir: &Path, nick: impl Display) -> InstanceStateHandle {
787 let nick = HsNickname::new(nick.to_string()).unwrap();
788 let mistrust = fs_mistrust::Mistrust::new_dangerously_trust_everyone();
789 let state_dir = StateDirectory::new(dir, &mistrust).unwrap();
790 let instance = state_dir.acquire_instance(&nick).unwrap();
791 instance
792 }
793
794 pub(crate) fn create_storage_handles(
795 dir: &Path,
796 ) -> (
797 tor_persist::state_dir::InstanceStateHandle,
798 IptSetStorageHandle,
799 ) {
800 let nick = HsNickname::try_from("allium".to_owned()).unwrap();
801 create_storage_handles_from_state_dir(dir, &nick)
802 }
803
804 pub(crate) fn create_storage_handles_from_state_dir(
805 state_dir: &Path,
806 nick: &HsNickname,
807 ) -> (
808 tor_persist::state_dir::InstanceStateHandle,
809 IptSetStorageHandle,
810 ) {
811 let instance = mk_state_instance(state_dir, nick);
812 let iptpub_state_handle = instance.storage_handle("iptpub").unwrap();
813 (instance, iptpub_state_handle)
814 }
815
816 macro_rules! maybe_generate_hsid {
817 ($keymgr:expr, $offline_hsid:expr) => {{
818 let nickname = HsNickname::try_from(TEST_SVC_NICKNAME.to_string()).unwrap();
819 let hsid_spec = HsIdKeypairSpecifier::new(nickname.clone());
820 let pub_hsid_spec = HsIdPublicKeySpecifier::new(nickname.clone());
821
822 assert!($keymgr.get::<HsIdKey>(&pub_hsid_spec).unwrap().is_none());
823 assert!($keymgr.get::<HsIdKeypair>(&hsid_spec).unwrap().is_none());
824
825 maybe_generate_hsid(&$keymgr, &nickname, $offline_hsid, Default::default()).unwrap();
826 }};
827 }
828
829 fn create_hsid() -> (HsIdKeypair, HsIdKey) {
831 let mut rng = testing_rng();
832 let keypair = ed25519::Keypair::generate(&mut rng);
833
834 let id_pub = HsIdKey::from(keypair.verifying_key());
835 let id_keypair = HsIdKeypair::from(ed25519::ExpandedKeypair::from(&keypair));
836
837 (id_keypair, id_pub)
838 }
839
840 #[test]
841 fn generate_hsid() {
842 let temp_dir = test_temp_dir!();
843 let keymgr = create_keymgr(&temp_dir);
844
845 let nickname = HsNickname::try_from(TEST_SVC_NICKNAME.to_string()).unwrap();
846 let hsid_spec = HsIdKeypairSpecifier::new(nickname.clone());
847
848 assert!(keymgr.get::<HsIdKeypair>(&hsid_spec).unwrap().is_none());
849 maybe_generate_hsid!(keymgr, false );
850 assert!(keymgr.get::<HsIdKeypair>(&hsid_spec).unwrap().is_some());
851 }
852
853 #[test]
854 fn hsid_keypair_already_exists() {
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 (existing_hsid_keypair, existing_hsid_public) = create_hsid();
862 let existing_keypair: ed25519::ExpandedKeypair = existing_hsid_keypair.into();
863 let existing_hsid_keypair = HsIdKeypair::from(existing_keypair);
864
865 keymgr
866 .insert(
867 existing_hsid_keypair,
868 &hsid_spec,
869 KeystoreSelector::Primary,
870 true,
871 )
872 .unwrap();
873
874 maybe_generate_hsid(
875 &keymgr,
876 &nickname,
877 false, Default::default(),
879 )
880 .unwrap();
881
882 let keypair = keymgr.get::<HsIdKeypair>(&hsid_spec).unwrap().unwrap();
883 let pk: HsIdKey = (&keypair).into();
884
885 assert_eq!(pk.as_ref(), existing_hsid_public.as_ref());
886 }
887
888 #[test]
889 #[ignore] fn generate_hsid_offline_hsid() {
891 let temp_dir = test_temp_dir!();
892 let keymgr = create_keymgr(&temp_dir);
893
894 let nickname = HsNickname::try_from(TEST_SVC_NICKNAME.to_string()).unwrap();
895 let hsid_spec = HsIdKeypairSpecifier::new(nickname.clone());
896 let pub_hsid_spec = HsIdPublicKeySpecifier::new(nickname.clone());
897
898 maybe_generate_hsid!(keymgr, true );
899
900 assert!(keymgr.get::<HsIdKey>(&pub_hsid_spec).unwrap().is_none());
901 assert!(keymgr.get::<HsIdKeypair>(&hsid_spec).unwrap().is_none());
902 }
903
904 #[test]
905 #[ignore] fn generate_hsid_corrupt_keystore() {
907 let temp_dir = test_temp_dir!();
908 let nickname = HsNickname::try_from(TEST_SVC_NICKNAME.to_string()).unwrap();
909 let hsid_spec = HsIdKeypairSpecifier::new(nickname.clone());
910 let pub_hsid_spec = HsIdPublicKeySpecifier::new(nickname.clone());
911
912 let keymgr = create_keymgr(&temp_dir);
913
914 let (hsid_keypair, _hsid_public) = create_hsid();
915 let (_hsid_keypair, hsid_public) = create_hsid();
916
917 keymgr
918 .insert(hsid_keypair, &hsid_spec, KeystoreSelector::Primary, true)
919 .unwrap();
920
921 keymgr
923 .insert(hsid_public, &pub_hsid_spec, KeystoreSelector::Primary, true)
924 .unwrap();
925
926 assert!(
927 maybe_generate_hsid(
928 &keymgr,
929 &nickname,
930 false, Default::default()
932 )
933 .is_err()
934 );
935 }
936
937 #[test]
938 fn onion_address() {
939 let temp_dir = test_temp_dir!();
940 let nickname = HsNickname::try_from(TEST_SVC_NICKNAME.to_string()).unwrap();
941 let hsid_spec = HsIdKeypairSpecifier::new(nickname.clone());
942 let keymgr = create_keymgr(&temp_dir);
943
944 let (hsid_keypair, hsid_public) = create_hsid();
945
946 keymgr
948 .insert(hsid_keypair, &hsid_spec, KeystoreSelector::Primary, true)
949 .unwrap();
950
951 let config = OnionServiceConfigBuilder::default()
952 .nickname(nickname)
953 .build()
954 .unwrap();
955
956 let state_dir = StateDirectory::new(
957 temp_dir.as_path_untracked(),
958 &fs_mistrust::Mistrust::new_dangerously_trust_everyone(),
959 )
960 .unwrap();
961
962 let service = OnionService::builder()
963 .config(config)
964 .keymgr(Arc::clone(&*keymgr))
965 .state_dir(state_dir)
966 .build()
967 .unwrap();
968
969 let hsid = HsId::from(hsid_public);
970 assert_eq!(service.onion_address().unwrap(), hsid);
971
972 drop(temp_dir); }
974}