//! Principal types for onion services. #![allow(dead_code, unused_variables)] // TODO hss remove. mod netdir; use std::sync::{Arc, Mutex}; use futures::channel::mpsc; use futures::Stream; use safelog::sensitive; use tor_async_utils::oneshot; use tor_async_utils::PostageWatchSenderExt as _; use tor_circmgr::hspool::HsCircPool; use tor_config::{Reconfigure, ReconfigureError}; use tor_error::Bug; use tor_hscrypto::pk::HsId; use tor_hscrypto::pk::HsIdKey; use tor_hscrypto::pk::HsIdKeypair; use tor_keymgr::KeyMgr; use tor_keymgr::KeystoreSelector; use tor_llcrypto::pk::curve25519; use tor_llcrypto::pk::ed25519; use tor_netdir::NetDirProvider; use tor_rtcompat::Runtime; use tracing::{info, trace, warn}; use crate::ipt_mgr::IptManager; use crate::ipt_set::IptsManagerView; use crate::keys::HsSvcHsIdKeyRole; use crate::svc::publish::Publisher; use crate::HsNickname; use crate::HsSvcKeySpecifier; use crate::OnionServiceConfig; use crate::OnionServiceStatus; use crate::RendRequest; use crate::StartupError; pub(crate) mod ipt_establish; pub(crate) mod publish; pub(crate) mod rend_handshake; /// Convenience alias for link specifiers of an intro point pub(crate) type LinkSpecs = Vec; /// Convenient type alias for an ntor public key // TODO HSS maybe this should be `tor_proto::crypto::handshake::ntor::NtorPublicKey`? type NtorPublicKey = curve25519::PublicKey; /// A handle to an instance of an onion service. // // TODO HSS: Write more. // // (APIs should return Arc) #[must_use = "a hidden service object will terminate the service when dropped"] pub struct OnionService { /// The mutable implementation details of this onion service. inner: Mutex, } /// Implementation details for an onion service. struct SvcInner { /// Configuration information about this service. config_tx: postage::watch::Sender>, /// A keymgr used to look up our keys and store new medium-term keys. // // TODO HSS: Do we actually need this in this structure? keymgr: Arc, /// A oneshot that will be dropped when this object is dropped. shutdown_tx: oneshot::Sender, /// Handles that we'll take ownership of when launching the service. /// /// (TODO HSS: Having to consume this may indicate a design problem.) unlaunched: Option<( mpsc::Receiver, Box, )>, } /// Objects and handles needed to launch an onion service. struct ForLaunch { /// An unlaunched handle for the HsDesc publisher. /// /// This publisher is responsible for determining when we need to upload a /// new set of HsDescs, building them, and publishing them at the correct /// HsDirs. publisher: Publisher>, /// Our handler for the introduction point manager. /// /// This manager is responsible for selecting introduction points, /// maintaining our connections to them, and telling the publisher which ones /// are publicly available. ipt_mgr: IptManager>, /// A handle used by the ipt manager to send Ipts to the publisher. /// /// ipt_mgr_view: IptsManagerView, } /// Private trait used to type-erase `ForLaunch`, so that we don't need to /// parameterize OnionService on ``. trait Launchable: Send + Sync { /// Launch fn launch(self: Box) -> Result<(), StartupError>; } impl Launchable for ForLaunch { fn launch(self: Box) -> Result<(), StartupError> { self.ipt_mgr.launch_background_tasks(self.ipt_mgr_view)?; self.publisher.launch()?; Ok(()) } } impl OnionService { /// Create (but do not launch) a new onion service. // // TODO HSS: How do we handle the case where somebody tries to launch two // onion services with the same nickname? They will conflict by trying to // use the same state and the same keys. Do we stop it here, or in // arti_client? pub fn new( runtime: R, config: OnionServiceConfig, netdir_provider: Arc, circ_pool: Arc>, keymgr: Arc, statemgr: S, ) -> Result, StartupError> where R: Runtime, S: tor_persist::StateMgr + Send + Sync + 'static, { let nickname = config.nickname.clone(); // TODO HSS: Maybe, adjust tor_persist::fs to handle subdirectories, and // use onion/{nickname}? let storage_key = format!("onion_svc_{nickname}"); // TODO HSS-IPT-PERSIST: Use this handle, and use a real struct type instead. let storage_handle: Arc> = statemgr.create_handle(storage_key); let (rend_req_tx, rend_req_rx) = mpsc::channel(32); let (shutdown_tx, shutdown_rx) = oneshot::channel(); let (config_tx, config_rx) = postage::watch::channel_with(Arc::new(config)); // TODO HSS: How do I give ipt_mgr_view to ipt_mgr? Does IptManager even take // one of these? let (ipt_mgr_view, publisher_view) = crate::ipt_set::ipts_channel(None); let ipt_mgr = IptManager::new( runtime.clone(), netdir_provider.clone(), nickname.clone(), config_rx.clone(), rend_req_tx, shutdown_rx, crate::ipt_mgr::Real { circ_pool: circ_pool.clone(), }, keymgr.clone(), )?; // TODO HSS: add a config option for specifying whether to expect the KS_hsid to be stored // offline //let offline_hsid = config.offline_hsid; let offline_hsid = false; maybe_generate_hsid(&keymgr, &nickname, offline_hsid)?; let publisher: Publisher> = Publisher::new( runtime, nickname, netdir_provider, circ_pool, publisher_view, config_rx, Arc::clone(&keymgr), ); // TODO HSS: we need to actually do something with: shutdown_tx, // rend_req_rx. The latter may need to be refactored to actually work // with svc::rend_handshake, if it doesn't already. Ok(Arc::new(OnionService { inner: Mutex::new(SvcInner { config_tx, shutdown_tx, keymgr, unlaunched: Some(( rend_req_rx, Box::new(ForLaunch { publisher, ipt_mgr, ipt_mgr_view, }), )), }), })) } /// Change the configuration of this onion service. /// /// (Not everything can be changed here. At the very least we'll need to say /// that the identity of a service is fixed. We might want to make the /// storage backing this, and the anonymity status, unchangeable.) pub fn reconfigure( &self, new_config: OnionServiceConfig, how: Reconfigure, ) -> Result<(), ReconfigureError> { let mut inner = self.inner.lock().expect("lock poisoned"); inner.config_tx.try_maybe_send(|cur_config| { let new_config = cur_config.for_transition_to(new_config, how)?; Ok(match how { // We're only checking, so return the current configuration. tor_config::Reconfigure::CheckAllOrNothing => Arc::clone(cur_config), // We're replacing the configuration, and we didn't get an error. _ => Arc::new(new_config), }) }) // TODO HSS: We need to make sure that the various tasks listening on // config_rx actually enforce the configuration, not only on new // connections, but existing ones. } /// Tell this onion service about some new short-term keys it can use. pub fn add_keys(&self, keys: ()) -> Result<(), Bug> { todo!() // TODO hss } /// Return the current status of this onion service. pub fn status(&self) -> OnionServiceStatus { todo!() // TODO hss } // TODO hss let's also have a function that gives you a stream of Status // changes? Or use a publish-based watcher? /// Tell this onion service to begin running, and return a /// stream of rendezvous requests on the service. /// /// You can turn the resulting stream into a stream of [`StreamRequest`](crate::StreamRequest) /// using the [`handle_rend_requests`](crate::handle_rend_requests) helper function pub fn launch(self: &Arc) -> Result, StartupError> { let (rend_req_rx, launch) = { let mut inner = self.inner.lock().expect("poisoned lock"); inner .unlaunched .take() .ok_or(StartupError::AlreadyLaunched)? }; launch.launch()?; // TODO HSS: This needs to launch at least the following tasks: // // - If we decide to use separate disk-based key provisioning, a task to // monitor our keys directory. // - If we own our identity key, a task to generate per-period sub-keys as // needed. Ok(rend_req_rx) } /// Tell this onion service to stop running. /// /// It can be restarted with launch(). /// /// You can also shut down an onion service completely by dropping the last /// Clone of it. pub fn stop(&self) { todo!() // TODO hss } } /// Generate the identity key of the service, unless it already exists or `offline_hsid` is `true`. fn maybe_generate_hsid( keymgr: &Arc, nickname: &HsNickname, offline_hsid: bool, ) -> Result<(), StartupError> { let hsid_spec = HsSvcKeySpecifier::new(nickname, HsSvcHsIdKeyRole::HsIdKeypair); let pub_hsid_spec = HsSvcKeySpecifier::new(nickname, HsSvcHsIdKeyRole::HsIdPublicKey); let has_hsid_kp = keymgr .get::(&hsid_spec) .map_err(|cause| StartupError::Keystore { action: "read", cause, })? .is_some(); let has_hsid_pub = keymgr .get::(&pub_hsid_spec) .map_err(|cause| StartupError::Keystore { action: "read", cause, })? .is_some(); // If KS_hs_id is missing (and not stored offline), generate a new keypair. // // TODO HSS: if the hsid is missing but the service key directory exists, should we remove // any preexisting keys from it? if !offline_hsid { if !has_hsid_kp && has_hsid_pub { // The hsid keypair is missing, but the hsid public key is not, so we can't // generate a fresh keypair. We also cannot proceed, because the hsid is not // supposed to be offline warn!("offline_hsid is false, but KS_hs_id missing!"); return Err(StartupError::KeystoreCorrupted); } // TODO HSS: make the selector configurable let keystore_sel = KeystoreSelector::Default; let mut rng = rand::thread_rng(); // NOTE: KeyMgr::generate will generate a new hsid keypair and corresponding public // key. if keymgr .generate_with_derived::( &hsid_spec, &pub_hsid_spec, keystore_sel, |sk| sk.public, &mut rng, false, /* overwrite */ ) .map_err(|cause| StartupError::Keystore { action: "generate key", cause, })? .is_some() { let kp = keymgr .get::(&hsid_spec) .map_err(|cause| StartupError::Keystore { action: "read", cause, })? .ok_or(StartupError::KeystoreCorrupted)?; let hsid: HsId = HsIdKey::from(&kp).into(); info!( "Generated a new identity for service {nickname}: {}", sensitive(hsid) ); } else { trace!("Using existing identity for service {nickname}"); } } Ok(()) } #[cfg(test)] mod test { // @@ begin test lint list maintained by maint/add_warning @@ #![allow(clippy::bool_assert_comparison)] #![allow(clippy::clone_on_copy)] #![allow(clippy::dbg_macro)] #![allow(clippy::print_stderr)] #![allow(clippy::print_stdout)] #![allow(clippy::single_char_pattern)] #![allow(clippy::unwrap_used)] #![allow(clippy::unchecked_duration_subtraction)] #![allow(clippy::useless_vec)] #![allow(clippy::needless_pass_by_value)] //! use super::*; use fs_mistrust::Mistrust; use tempfile::{tempdir, TempDir}; use tor_basic_utils::test_rng::testing_rng; use tor_keymgr::ArtiNativeKeystore; use tor_llcrypto::util::rand_compat::RngCompatExt; /// The nickname of the test service. const TEST_SVC_NICKNAME: &str = "test-svc"; fn create_keymgr(keystore_dir: &TempDir) -> Arc { let keystore = ArtiNativeKeystore::from_path_and_mistrust( keystore_dir, &Mistrust::new_dangerously_trust_everyone(), ) .unwrap(); Arc::new(KeyMgr::new(keystore, vec![])) } macro_rules! maybe_generate_hsid { ($keymgr:expr, $offline_hsid:expr) => {{ let nickname = HsNickname::try_from(TEST_SVC_NICKNAME.to_string()).unwrap(); let hsid_spec = HsSvcKeySpecifier::new(&nickname, HsSvcHsIdKeyRole::HsIdKeypair); let pub_hsid_spec = HsSvcKeySpecifier::new(&nickname, HsSvcHsIdKeyRole::HsIdPublicKey); assert!($keymgr.get::(&pub_hsid_spec).unwrap().is_none()); assert!($keymgr.get::(&hsid_spec).unwrap().is_none()); maybe_generate_hsid(&$keymgr, &nickname, $offline_hsid).unwrap(); }}; } /// Create a test hsid keypair. fn create_hsid() -> (HsIdKeypair, HsIdKey) { let mut rng = testing_rng().rng_compat(); let keypair = ed25519::Keypair::generate(&mut rng); let id_pub = HsIdKey::from(keypair.public); let id_keypair = HsIdKeypair::from(ed25519::ExpandedKeypair { secret: ed25519::ExpandedSecretKey::from(&keypair.secret), public: id_pub.clone().into(), }); (id_keypair, id_pub) } #[test] fn generate_hsid() { let keystore_dir = tempdir().unwrap(); let keymgr = create_keymgr(&keystore_dir); let nickname = HsNickname::try_from(TEST_SVC_NICKNAME.to_string()).unwrap(); let hsid_spec = HsSvcKeySpecifier::new(&nickname, HsSvcHsIdKeyRole::HsIdKeypair); let pub_hsid_spec = HsSvcKeySpecifier::new(&nickname, HsSvcHsIdKeyRole::HsIdPublicKey); maybe_generate_hsid!(keymgr, false /* offline_hsid */); let hsid_public = keymgr.get::(&pub_hsid_spec).unwrap().unwrap(); let hsid_keypair = keymgr.get::(&hsid_spec).unwrap().unwrap(); let keypair: ed25519::ExpandedKeypair = hsid_keypair.into(); assert_eq!(hsid_public.as_ref(), &keypair.public); } #[test] fn hsid_keypair_already_exists() { let keystore_dir = tempdir().unwrap(); let nickname = HsNickname::try_from(TEST_SVC_NICKNAME.to_string()).unwrap(); let hsid_spec = HsSvcKeySpecifier::new(&nickname, HsSvcHsIdKeyRole::HsIdKeypair); let pub_hsid_spec = HsSvcKeySpecifier::new(&nickname, HsSvcHsIdKeyRole::HsIdPublicKey); for hsid_pub_missing in [false, true] { let keymgr = create_keymgr(&keystore_dir); // Insert the preexisting hsid keypair. let (existing_hsid_keypair, existing_hsid_public) = create_hsid(); let existing_keypair: ed25519::ExpandedKeypair = existing_hsid_keypair.into(); // Expanded keypairs are not clone, so we have to extract the private key bytes here to use // them in an assertion that comes after the insert() let existing_keypair_secret = existing_keypair.secret.to_bytes(); let existing_hsid_keypair = HsIdKeypair::from(existing_keypair); keymgr .insert(existing_hsid_keypair, &hsid_spec, KeystoreSelector::Default) .unwrap(); // Maybe the public key already exists too (in which case maybe_generate_hsid // doesn't need to insert it into the keystore). if hsid_pub_missing { keymgr .insert( existing_hsid_public.clone(), &pub_hsid_spec, KeystoreSelector::Default, ) .unwrap(); } maybe_generate_hsid(&keymgr, &nickname, false /* offline_hsid */).unwrap(); let hsid_public = keymgr.get::(&pub_hsid_spec).unwrap().unwrap(); let hsid_keypair = keymgr.get::(&hsid_spec).unwrap().unwrap(); let keypair: ed25519::ExpandedKeypair = hsid_keypair.into(); // The keypair was not overwritten. The public key matches the existing keypair. assert_eq!(hsid_public.as_ref(), existing_hsid_public.as_ref()); assert_eq!(keypair.secret.to_bytes(), existing_keypair_secret); } } #[test] fn generate_hsid_offline_hsid() { let keystore_dir = tempdir().unwrap(); let keymgr = create_keymgr(&keystore_dir); let nickname = HsNickname::try_from(TEST_SVC_NICKNAME.to_string()).unwrap(); let hsid_spec = HsSvcKeySpecifier::new(&nickname, HsSvcHsIdKeyRole::HsIdKeypair); let pub_hsid_spec = HsSvcKeySpecifier::new(&nickname, HsSvcHsIdKeyRole::HsIdPublicKey); maybe_generate_hsid!(keymgr, true /* offline_hsid */); assert!(keymgr.get::(&pub_hsid_spec).unwrap().is_none()); assert!(keymgr.get::(&hsid_spec).unwrap().is_none()); } #[test] fn generate_hsid_missing_keypair() { let keystore_dir = tempdir().unwrap(); let nickname = HsNickname::try_from(TEST_SVC_NICKNAME.to_string()).unwrap(); let hsid_spec = HsSvcKeySpecifier::new(&nickname, HsSvcHsIdKeyRole::HsIdKeypair); let pub_hsid_spec = HsSvcKeySpecifier::new(&nickname, HsSvcHsIdKeyRole::HsIdPublicKey); let keymgr = create_keymgr(&keystore_dir); let (_hsid_keypair, hsid_public) = create_hsid(); keymgr .insert(hsid_public, &pub_hsid_spec, KeystoreSelector::Default) .unwrap(); // We're running with an online hsid, but the keypair is missing! The public part // of the key exists in the keystore, so we can't generate a new keypair. assert!(maybe_generate_hsid(&keymgr, &nickname, false /* offline_hsid */).is_err()); } #[test] fn generate_hsid_corrupt_keystore() { let keystore_dir = tempdir().unwrap(); let nickname = HsNickname::try_from(TEST_SVC_NICKNAME.to_string()).unwrap(); let hsid_spec = HsSvcKeySpecifier::new(&nickname, HsSvcHsIdKeyRole::HsIdKeypair); let pub_hsid_spec = HsSvcKeySpecifier::new(&nickname, HsSvcHsIdKeyRole::HsIdPublicKey); let keymgr = create_keymgr(&keystore_dir); let (hsid_keypair, _hsid_public) = create_hsid(); let (_hsid_keypair, hsid_public) = create_hsid(); keymgr .insert(hsid_keypair, &hsid_spec, KeystoreSelector::Default) .unwrap(); // Insert a mismatched public key keymgr .insert(hsid_public, &pub_hsid_spec, KeystoreSelector::Default) .unwrap(); assert!(maybe_generate_hsid(&keymgr, &nickname, false /* offline_hsid */).is_err()); } }