diff --git a/Cargo.lock b/Cargo.lock index d429cb6..9c567fd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -521,6 +521,7 @@ dependencies = [ "tor-guardmgr", "tor-hsclient", "tor-hscrypto", + "tor-hsservice", "tor-keymgr", "tor-linkspec", "tor-llcrypto", @@ -2966,6 +2967,27 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "fslock-arti-fork" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b21bd626aaab7b904b20bef6d9e06298914a0c8d9fb8b010483766b2e532791" +dependencies = [ + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "fslock-guard" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9267d03223dd8877b0a3f8341661d21b7ba6a18e90f60e92e550addd30bc32c7" +dependencies = [ + "fslock-arti-fork", + "thiserror", + "winapi 0.3.9", +] + [[package]] name = "fuchsia-cprng" version = "0.1.1" @@ -3394,7 +3416,9 @@ dependencies = [ "arti-client", "built", "chrono", + "curve25519-dalek 4.1.2", "dirs 5.0.1", + "ed25519-dalek 2.1.1", "eframe", "egui", "egui_extras", @@ -3425,12 +3449,18 @@ dependencies = [ "serde", "serde_derive", "serde_json", + "sha2 0.10.8", "sys-locale", "thiserror", "tokio 1.37.0", "tokio-util 0.7.10", "toml 0.8.12", "tor-config", + "tor-hscrypto", + "tor-hsrproxy", + "tor-hsservice", + "tor-keymgr", + "tor-llcrypto", "tor-rtcompat", "url", "wgpu", @@ -3872,6 +3902,18 @@ dependencies = [ "subtle", ] +[[package]] +name = "growable-bloom-filter" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c669fa03050eb3445343f215d62fc1ab831e8098bc9a55f26e9724faff11075c" +dependencies = [ + "serde", + "serde_bytes", + "serde_derive", + "xxhash-rust", +] + [[package]] name = "h2" version = "0.2.7" @@ -4636,6 +4678,16 @@ dependencies = [ "serde_json", ] +[[package]] +name = "k12" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4dc5fdb62af2f520116927304f15d25b3c2667b4817b90efdc045194c912c54" +dependencies = [ + "digest 0.10.7", + "sha3 0.10.8", +] + [[package]] name = "keccak" version = "0.1.5" @@ -6528,6 +6580,12 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8a99fddc9f0ba0a85884b8d14e3592853e787d581ca1816c91349b10e4eeab" +[[package]] +name = "rangemap" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60fcc7d6849342eff22c4350c8b9a989ee8ceabc4b481253e8946b9fe83d684" + [[package]] name = "rav1e" version = "0.7.1" @@ -7267,6 +7325,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_bytes" +version = "0.11.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b8497c313fd43ab992087548117643f6fcd935cbf36f176ffda0aacf9591734" +dependencies = [ + "serde", +] + [[package]] name = "serde_derive" version = "1.0.198" @@ -8397,6 +8464,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87bb9b74a5f5402768cee442147641d39ca2d0cba459f52fcca03cd8d978bd0d" dependencies = [ "caret", + "derive_builder_fork_arti", "derive_more", "digest 0.10.7", "thiserror", @@ -8724,6 +8792,7 @@ version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24b0c899ce91d6fe6461f646d1e3c8d421dd5c8b570c0799540c4d4a2de80013" dependencies = [ + "cipher 0.4.4", "data-encoding", "derive_more", "digest 0.10.7", @@ -8741,6 +8810,90 @@ dependencies = [ "tor-error", "tor-llcrypto", "tor-units", + "zeroize", +] + +[[package]] +name = "tor-hsrproxy" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e66cbbbff869500e673c775a056e0ead78152ebbfbe51ce442bd9833b873882" +dependencies = [ + "derive-adhoc 0.8.4", + "derive_builder_fork_arti", + "futures 0.3.30", + "rangemap", + "safelog", + "serde", + "serde_with", + "thiserror", + "tor-async-utils", + "tor-cell", + "tor-config", + "tor-error", + "tor-hsservice", + "tor-log-ratelim", + "tor-proto", + "tor-rtcompat", + "tracing", + "void", +] + +[[package]] +name = "tor-hsservice" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f81310c95fa1cd2d533dd1cf7ef43720308ac5bd0086801409491ebc1130c6e" +dependencies = [ + "amplify", + "async-trait", + "base64ct", + "derive-adhoc 0.8.4", + "derive_builder_fork_arti", + "derive_more", + "digest 0.10.7", + "educe", + "fs-mistrust", + "futures 0.3.30", + "growable-bloom-filter", + "hex", + "humantime 2.1.0", + "itertools 0.12.1", + "k12", + "once_cell", + "postage", + "rand 0.8.5", + "rand_core 0.6.4", + "retry-error", + "safelog", + "serde", + "serde_with", + "strum 0.26.2", + "thiserror", + "tor-async-utils", + "tor-basic-utils", + "tor-bytes", + "tor-cell", + "tor-cert", + "tor-circmgr", + "tor-config", + "tor-dirclient", + "tor-error", + "tor-hscrypto", + "tor-keymgr", + "tor-linkspec", + "tor-llcrypto", + "tor-log-ratelim", + "tor-netdir", + "tor-netdoc", + "tor-persist", + "tor-proto", + "tor-protover", + "tor-relay-selection", + "tor-rtcompat", + "tor-units", + "tracing", + "void", ] [[package]] @@ -8934,11 +9087,13 @@ version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7401ddb8c6a9ed71adeab421a20b786196409f02f389b415bb56041a5b0c80d4" dependencies = [ + "amplify", "derive-adhoc 0.8.4", "derive_more", "filetime", "fs-mistrust", "fslock", + "fslock-guard", "itertools 0.12.1", "paste", "sanitize-filename", @@ -10384,6 +10539,12 @@ version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "791978798f0597cfc70478424c2b4fdc2b7a8024aaff78497ef00f24ef674193" +[[package]] +name = "xxhash-rust" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "927da81e25be1e1a2901d59b81b37dd2efd1fc9c9345a55007f09bf5a2d3ee03" + [[package]] name = "xz2" version = "0.1.7" diff --git a/Cargo.toml b/Cargo.toml index 5f7ecaf..98875e5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,10 +56,18 @@ tokio = { version = "1.37.0", features = ["full"] } ## tor arti = { version = "1.2.0", features = ["experimental-api", "pt-client", "static"] } -arti-client = { version = "0.17.0", features = ["experimental-api", "pt-client", "static"] } +arti-client = { version = "0.17.0", features = ["experimental-api", "pt-client", "static", "onion-service-service"] } tor-rtcompat = { version = "0.17.0", features = ["static"] } tor-config = "0.17.0" fs-mistrust = "0.7.9" +tor-hsservice = "0.17.0" +tor-hsrproxy = "0.17.0" +tor-keymgr = "0.17.0" +ed25519-dalek = "2.1.1" +tor-llcrypto = "0.17.0" +tor-hscrypto = "0.17.0" +sha2 = "0.10.0" +curve25519-dalek = "4.1.2" ## stratum server tokio-util = { version = "0.7.8", features = ["codec"] } diff --git a/src/gui/views/network/connections.rs b/src/gui/views/network/connections.rs index 853b99f..b208879 100644 --- a/src/gui/views/network/connections.rs +++ b/src/gui/views/network/connections.rs @@ -12,8 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::time::Duration; use egui::{Align, Id, Layout, RichText, Rounding}; use url::Url; +use crate::tor::{TorServer, TorServerConfig}; use crate::AppConfig; use crate::gui::Colors; @@ -22,7 +24,6 @@ use crate::gui::platform::PlatformCallbacks; use crate::gui::views::{Modal, NodeSetup, View}; use crate::gui::views::types::{ModalContainer, ModalPosition, TextEditOptions}; use crate::node::{Node, NodeConfig}; -use crate::tor::{TorServer, TorServerConfig}; use crate::wallet::{ConnectionsConfig, ExternalConnection}; /// Network connections content. @@ -122,6 +123,12 @@ impl ConnectionsContent { }); } } + + // Redraw after delay if Tor server is running. + if TorServer::is_running() || TorServer::is_starting() || + TorServer::is_stopping() { + ui.ctx().request_repaint_after(Duration::from_millis(1000)); + } } /// Draw Tor connection item content. diff --git a/src/gui/views/network/content.rs b/src/gui/views/network/content.rs index 1e36261..b248b10 100644 --- a/src/gui/views/network/content.rs +++ b/src/gui/views/network/content.rs @@ -16,13 +16,12 @@ use egui::{Margin, RichText, ScrollArea, Stroke}; use crate::AppConfig; use crate::gui::Colors; -use crate::gui::icons::{BRIEFCASE, CARDHOLDER, DATABASE, DOTS_THREE_OUTLINE_VERTICAL, FACTORY, FADERS, GAUGE, PLUS_CIRCLE, POWER}; +use crate::gui::icons::{BRIEFCASE, DATABASE, DOTS_THREE_OUTLINE_VERTICAL, FACTORY, FADERS, GAUGE, PLUS_CIRCLE, POWER}; use crate::gui::platform::PlatformCallbacks; use crate::gui::views::{ConnectionsContent, NetworkMetrics, NetworkMining, NetworkNode, NetworkSettings, Root, TitlePanel, View}; use crate::gui::views::network::types::{NetworkTab, NetworkTabType}; use crate::gui::views::types::{TitleContentType, TitleType}; use crate::node::Node; -use crate::tor::TorServer; use crate::wallet::ExternalConnection; /// Network content. @@ -148,8 +147,7 @@ impl NetworkContent { }); // Redraw after delay if node is syncing to update stats. - if Node::is_running() || TorServer::is_running() || TorServer::is_starting() || - TorServer::is_stopping() { + if Node::is_running() { ui.ctx().request_repaint_after(Node::STATS_UPDATE_DELAY); } } @@ -219,7 +217,7 @@ impl NetworkContent { self.connections.show_add_ext_conn_modal(None, cb); }); } - }, |ui, frame| { + }, |ui, _| { if !Root::is_dual_panel_mode(ui) { View::title_button(ui, BRIEFCASE, || { Root::toggle_network_panel(); diff --git a/src/tor/config.rs b/src/tor/config.rs index 490d082..2e16538 100644 --- a/src/tor/config.rs +++ b/src/tor/config.rs @@ -33,12 +33,45 @@ impl Default for TorServerConfig { } impl TorServerConfig { - /// Application configuration file name. - pub const FILE_NAME: &'static str = "app.toml"; + /// Tor configuration file name. + pub const FILE_NAME: &'static str = "tor.toml"; + + /// Directory for config and Tor related files. + const DIR_NAME: &'static str = "tor"; + + /// Subdirectory name for Tor state. + const STATE_SUB_DIR: &'static str = "state"; + /// Subdirectory name for Tor cache. + const CACHE_SUB_DIR: &'static str = "cache"; + /// Subdirectory name for Tor keystore. + const KEYSTORE_DIR: &'static str = "keystore"; /// Save application configuration to the file. pub fn save(&self) { - Settings::write_to_file(self, Settings::get_config_path(Self::FILE_NAME, None)); + Settings::write_to_file(self, Settings::get_config_path(Self::FILE_NAME, + Some(Self::DIR_NAME.to_string()))); + } + + /// Get subdirectory path from dir name. + fn sub_dir_path(name: &str) -> String { + let mut base = Settings::get_base_path(Some(Self::DIR_NAME.to_string())); + base.push(name); + base.to_str().unwrap().to_string() + } + + /// Get Tor state directory path. + pub fn state_path() -> String { + Self::sub_dir_path(Self::STATE_SUB_DIR) + } + + /// Get Tor cache directory path. + pub fn cache_path() -> String { + Self::sub_dir_path(Self::CACHE_SUB_DIR) + } + + /// Get Tor keystore directory path. + pub fn keystore_path() -> String { + Self::sub_dir_path(Self::KEYSTORE_DIR) } /// Get SOCKS port value. diff --git a/src/tor/tor.rs b/src/tor/tor.rs index 928736b..947264c 100644 --- a/src/tor/tor.rs +++ b/src/tor/tor.rs @@ -12,22 +12,37 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::collections::HashMap; +use std::net::SocketAddr; use std::sync::{Arc, RwLock}; use std::sync::atomic::{AtomicBool, Ordering}; use std::thread; use std::time::Duration; use lazy_static::lazy_static; -use arti::socks::run_socks_proxy; -use arti_client::{TorClient, TorClientConfig}; -use arti_client::config::pt::{TransportConfigBuilder}; -use arti_client::config::{BridgeConfigBuilder, TorClientConfigBuilder, StorageConfigBuilder}; use futures::task::SpawnExt; use tokio::task::JoinHandle; -use anyhow::{Result}; +use anyhow::Result; use tokio::time::sleep; + +use arti::socks::run_socks_proxy; +use arti_client::{TorClient, TorClientConfig}; +use arti_client::config::pt::TransportConfigBuilder; +use arti_client::config::{BridgeConfigBuilder, TorClientConfigBuilder}; +use fs_mistrust::Mistrust; +use grin_util::secp::SecretKey; +use grin_wallet_util::OnionV3Address; +use ed25519_dalek::hazmat::ExpandedSecretKey; +use curve25519_dalek::digest::Digest; +use sha2::Sha512; use tor_config::{CfgPath, Listen}; -use tor_rtcompat::{BlockOn, Runtime}; -use tor_rtcompat::tokio::TokioNativeTlsRuntime; +use tor_rtcompat::{BlockOn, PreferredRuntime, Runtime}; +use tor_hsrproxy::OnionServiceReverseProxy; +use tor_hsrproxy::config::{Encapsulation, ProxyAction, ProxyPattern, ProxyRule, TargetAddr, ProxyConfigBuilder}; +use tor_hsservice::config::OnionServiceConfigBuilder; +use tor_hsservice::{HsIdKeypairSpecifier, HsIdPublicKeySpecifier, HsNickname}; +use tor_keymgr::{ArtiNativeKeystore, KeyMgrBuilder, KeystoreSelector}; +use tor_llcrypto::pk::ed25519::ExpandedKeypair; +use tor_hscrypto::pk::{HsIdKey, HsIdKeypair}; use crate::tor::TorServerConfig; @@ -36,18 +51,25 @@ lazy_static! { static ref TOR_SERVER_STATE: Arc = Arc::new(TorServer::default()); } -/// Tor SOCKS proxy server. +/// Tor server to use as SOCKS proxy for requests and to launch Onion services. pub struct TorServer { + /// Running Tor client. + client: Arc>>>, + /// Running Tor client configuration. + config: Arc>>, + /// Flag to check if server is running. running: AtomicBool, /// Flag to check if server is starting. starting: AtomicBool, /// Flag to check if server needs to stop. stopping: AtomicBool, + /// Flag to check if error happened. error: AtomicBool, - /// Tor client to use for proxy. - client: Arc>>> + + /// Mapping of running Onion services identifiers to proxy. + running_services: Arc>>> } impl Default for TorServer { @@ -58,6 +80,8 @@ impl Default for TorServer { stopping: AtomicBool::new(false), error: AtomicBool::new(false), client: Arc::new(RwLock::new(None)), + running_services: Arc::new(RwLock::new(HashMap::new())), + config: Arc::new(RwLock::new(None)), } } } @@ -109,13 +133,19 @@ impl TorServer { let _ = runtime.clone().block_on(Self::launch_socks_proxy(runtime, client)); } else { // Create Tor client config to connect. - let mut builder = TorClientConfig::builder(); + let mut builder = + TorClientConfigBuilder::from_directories(TorServerConfig::state_path(), + TorServerConfig::cache_path()); + builder.address_filter().allow_onion_addrs(true); // Setup Snowflake bridges. Self::setup_bridges(&mut builder); // Create Tor client from config. if let Ok(config) = builder.build() { + let mut w_config = TOR_SERVER_STATE.config.write().unwrap(); + *w_config = Some(config.clone()); + // Restart server on connection timeout. thread::spawn(|| { thread::sleep(Duration::from_millis(30000)); @@ -125,7 +155,7 @@ impl TorServer { } }); // Create Tor client. - let runtime = TokioNativeTlsRuntime::create().unwrap(); + let runtime = PreferredRuntime::current().unwrap(); match TorClient::with_runtime(runtime.clone()) .config(config) .bootstrap_behavior(arti_client::BootstrapBehavior::OnDemand) @@ -155,7 +185,7 @@ impl TorServer { }); } - /// Launch SOCKS proxy server. + /// Launch SOCKS proxy server to send connections. async fn launch_socks_proxy(runtime: R, tor_client: TorClient) -> Result<()> { let proxy_handle: JoinHandle> = tokio::spawn( run_socks_proxy( @@ -180,6 +210,125 @@ impl TorServer { } } + /// Check if Onion service is running. + pub fn is_service_running(id: &String) -> bool { + let r_services = TOR_SERVER_STATE.running_services.read().unwrap(); + r_services.contains_key(id) + } + + /// Stop running Onion service. + pub fn stop_service(id: &String) { + let mut w_services = TOR_SERVER_STATE.running_services.write().unwrap(); + if let Some(proxy) = w_services.remove(id) { + proxy.shutdown(); + } + } + + /// Run Onion service from listening local address, secret key and identifier. + pub fn run_service(addr: SocketAddr, key: SecretKey, id: &String) { + // Check if service is already running. + if Self::is_service_running(id) { + return; + } + + let hs_nickname = HsNickname::new(id.clone()).unwrap(); + let service_config = OnionServiceConfigBuilder::default() + .nickname(hs_nickname.clone()) + .build() + .unwrap(); + let r_client = TOR_SERVER_STATE.client.read().unwrap(); + let client = r_client.clone().unwrap(); + + // Add service key to keystore. + let r_config = TOR_SERVER_STATE.config.read().unwrap(); + let config = r_config.clone().unwrap(); + Self::add_service_key(config.fs_mistrust(), &key, &hs_nickname); + + // Launch Onion service. + let (_, request) = client.launch_onion_service(service_config).unwrap(); + + // Setup proxy to forward request from Tor address to local address. + let proxy_rule = ProxyRule::new( + ProxyPattern::one_port(80).unwrap(), + ProxyAction::Forward(Encapsulation::Simple, TargetAddr::Inet(addr)), + ); + let mut proxy_cfg_builder = ProxyConfigBuilder::default(); + proxy_cfg_builder.set_proxy_ports(vec![proxy_rule]); + let proxy = OnionServiceReverseProxy::new(proxy_cfg_builder.build().unwrap()); + + // Launch proxy at client runtime. + let proxy_service = proxy.clone(); + let runtime = client.runtime().clone(); + let nickname = hs_nickname.clone(); + client + .runtime() + .spawn(async move { + // Launch proxy for launched service. + match proxy_service.handle_requests(runtime, nickname.clone(), request).await { + Ok(()) => { + eprintln!("Onion service {} stopped.", nickname); + } + Err(e) => { + eprintln!("Onion service {} exited with an error: {}", nickname, e); + } + } + }).unwrap(); + + // Save running service. + let mut w_services = TOR_SERVER_STATE.running_services.write().unwrap(); + w_services.insert(id.clone(), proxy); + + let onion_addr = OnionV3Address::from_private(&key.0).unwrap(); + eprintln!("Onion service {} launched at {}", hs_nickname, onion_addr.to_ov3_str()); + } + + /// Add Onion service key to keystore. + fn add_service_key(mistrust: &Mistrust, key: &SecretKey, hs_nickname: &HsNickname) { + let mut client_config_builder = TorClientConfigBuilder::from_directories( + TorServerConfig::state_path(), + TorServerConfig::cache_path() + ); + client_config_builder + .address_filter() + .allow_onion_addrs(true); + let arti_store = + ArtiNativeKeystore::from_path_and_mistrust(TorServerConfig::keystore_path(), &mistrust) + .unwrap(); + + let key_manager = KeyMgrBuilder::default() + .default_store(Box::new(arti_store)) + .build() + .unwrap(); + + let expanded_sk = ExpandedSecretKey::from_bytes( + Sha512::default() + .chain_update(key) + .finalize() + .as_ref(), + ); + + let mut sk_bytes = [0_u8; 64]; + sk_bytes[0..32].copy_from_slice(&expanded_sk.scalar.to_bytes()); + sk_bytes[32..64].copy_from_slice(&expanded_sk.hash_prefix); + let expanded_kp = ExpandedKeypair::from_secret_key_bytes(sk_bytes).unwrap(); + + key_manager + .insert( + HsIdKey::from(expanded_kp.public().clone()), + &HsIdPublicKeySpecifier::new(hs_nickname.clone()), + KeystoreSelector::Default, + ) + .unwrap(); + + key_manager + .insert( + HsIdKeypair::from(expanded_kp), + &HsIdKeypairSpecifier::new(hs_nickname.clone()), + KeystoreSelector::Default, + ) + .unwrap(); + } + /// Setup Tor Snowflake bridges. fn setup_bridges(builder: &mut TorClientConfigBuilder) { // Add a single bridge to the list of bridges, from a bridge line.