From 2f4e505aac062f1faaf8b1b2830af18580813fcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Wed, 6 Jul 2022 14:02:40 +0200 Subject: [PATCH] connect: add export_keys tauri function (#1443) * connect: tidy * connect: add export_keys tauri function * changelog: add note * connect: remove unused --- CHANGELOG.md | 1 + clients/socks5/src/client/mod.rs | 13 ++-- nym-connect/src-tauri/src/config/mod.rs | 11 ++- nym-connect/src-tauri/src/error.rs | 7 ++ nym-connect/src-tauri/src/main.rs | 3 +- .../src-tauri/src/operations/export.rs | 73 +++++++++++++++++++ nym-connect/src-tauri/src/operations/mod.rs | 1 + nym-connect/src-tauri/src/state.rs | 12 ++- nym-connect/src-tauri/src/tasks.rs | 8 +- 9 files changed, 114 insertions(+), 15 deletions(-) create mode 100644 nym-connect/src-tauri/src/operations/export.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 3fa7ce698b..10daf2ff48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https:// - socks5 client/websocket client: add `--force-register-gateway` flag, useful when rerunning init ([#1353]) - nym-connect: initial proof-of-concept of a UI around the socks5 client was added - nym-connect: add ability to select network requester and gateway ([#1427]). +- nym-connect: add ability to export gateway keys as JSON. - all: added network compilation target to `--help` (or `--version`) commands ([#1256]). - explorer-api: learned how to sum the delegations by owner in a new endpoint. - explorer-api: add apy values to `mix_nodes` endpoint diff --git a/clients/socks5/src/client/mod.rs b/clients/socks5/src/client/mod.rs index 4bbd3253b5..6669a99430 100644 --- a/clients/socks5/src/client/mod.rs +++ b/clients/socks5/src/client/mod.rs @@ -287,12 +287,15 @@ impl NymClient { pub async fn run_and_listen(&mut self, mut receiver: Socks5ControlMessageReceiver) { self.start().await; tokio::select! { - message = receiver.next() => match message { - Some(Socks5ControlMessage::Stop) => { - log::info!("Received: {:?}", message); - log::info!("Shutting down"); + message = receiver.next() => { + log::debug!("Received message: {:?}", message); + match message { + Some(Socks5ControlMessage::Stop) => { + log::info!("Shutting down"); + log::info!("Graceful shutdown of tasks not yet implemented, you might see (harmless) panics until then"); + } + None => log::debug!("None"), } - None => log::info!("none"), } } } diff --git a/nym-connect/src-tauri/src/config/mod.rs b/nym-connect/src-tauri/src/config/mod.rs index d1965349e5..5f4ce22e21 100644 --- a/nym-connect/src-tauri/src/config/mod.rs +++ b/nym-connect/src-tauri/src/config/mod.rs @@ -1,7 +1,6 @@ use std::path::PathBuf; use client_core::config::GatewayEndpoint; -use log::info; use std::sync::Arc; use tap::TapFallible; use tokio::sync::RwLock; @@ -15,7 +14,7 @@ use crate::{ state::State, }; -pub static SOCKS5_CONFIG_ID: &str = "nym-connect"; +static SOCKS5_CONFIG_ID: &str = "nym-connect"; const DEFAULT_ETH_ENDPOINT: &str = "https://rinkeby.infura.io/v3/00000000000000000000000000000000"; const DEFAULT_ETH_PRIVATE_KEY: &str = @@ -73,7 +72,7 @@ impl Config { } pub async fn init(service_provider: &str, chosen_gateway_id: &str) -> Result<()> { - info!("Initialising..."); + log::info!("Initialising..."); let service_provider = service_provider.to_owned(); let chosen_gateway_id = chosen_gateway_id.to_owned(); @@ -91,7 +90,7 @@ impl Config { .join() .map_err(|_| BackendError::InitializationPanic)??; - info!("Configuration saved 🚀"); + log::info!("Configuration saved 🚀"); Ok(()) } @@ -101,7 +100,7 @@ impl Config { } pub async fn init_socks5_config(provider_address: String, chosen_gateway_id: String) -> Result<()> { - log::info!("Initialising client..."); + log::trace!("Initialising client..."); // Append the gateway id to the name id that we store the config under let id = socks5_config_id_appended_with(&chosen_gateway_id)?; @@ -166,7 +165,7 @@ pub async fn init_socks5_config(provider_address: String, chosen_gateway_id: Str "Service provider port: {}", config.get_socks5().get_listening_port() ); - info!("Client configuration completed."); + log::info!("Client configuration completed."); client_core::init::show_address(config.get_base()); Ok(()) diff --git a/nym-connect/src-tauri/src/error.rs b/nym-connect/src-tauri/src/error.rs index ad129a7c5a..df37001274 100644 --- a/nym-connect/src-tauri/src/error.rs +++ b/nym-connect/src-tauri/src/error.rs @@ -24,6 +24,11 @@ pub enum BackendError { #[from] source: tauri::Error, }, + #[error("{source}")] + SerdeJsonError { + #[from] + source: serde_json::Error, + }, #[error("State error")] StateError, @@ -45,6 +50,8 @@ pub enum BackendError { CouldNotInitWithoutGateway, #[error("Could initialize without service provider set")] CouldNotInitWithoutServiceProvider, + #[error("Could not get file name")] + CouldNotGetFilename, } impl Serialize for BackendError { diff --git a/nym-connect/src-tauri/src/main.rs b/nym-connect/src-tauri/src/main.rs index e13b324dd4..795533287c 100644 --- a/nym-connect/src-tauri/src/main.rs +++ b/nym-connect/src-tauri/src/main.rs @@ -43,8 +43,9 @@ fn main() { crate::operations::connection::connect::set_service_provider, crate::operations::connection::connect::start_connecting, crate::operations::connection::disconnect::start_disconnecting, - crate::operations::window::hide_window, crate::operations::directory::get_services, + crate::operations::export::export_keys, + crate::operations::window::hide_window, ]) .menu(Menu::new().add_default_app_submenu_if_macos()) .system_tray(create_tray_menu()) diff --git a/nym-connect/src-tauri/src/operations/export.rs b/nym-connect/src-tauri/src/operations/export.rs new file mode 100644 index 0000000000..d4190c686a --- /dev/null +++ b/nym-connect/src-tauri/src/operations/export.rs @@ -0,0 +1,73 @@ +use std::{ffi::OsStr, fs, sync::Arc}; +use tokio::sync::RwLock; + +use crate::{ + error::{BackendError, Result}, + state::State, +}; + +/// Export the gateway keys as a JSON string blob +#[tauri::command] +pub async fn export_keys(state: tauri::State<'_, Arc>>) -> Result { + let config = { + let state = state.read().await; + state.load_socks5_config()? + }; + + // Get key paths + let ack_key_file = config.get_base().get_ack_key_file(); + let gateway_shared_key_file = config.get_base().get_gateway_shared_key_file(); + + let pub_id_key_file = config.get_base().get_public_identity_key_file(); + let priv_id_key_file = config.get_base().get_private_identity_key_file(); + + let pub_enc_key_file = config.get_base().get_public_encryption_key_file(); + let priv_enc_key_file = config.get_base().get_private_encryption_key_file(); + + // Read file contents + let ack_key = fs::read_to_string(ack_key_file.clone())?; + let gateway_shared_key = fs::read_to_string(gateway_shared_key_file.clone())?; + + let pub_id_key = fs::read_to_string(pub_id_key_file.clone())?; + let priv_id_key = fs::read_to_string(priv_id_key_file.clone())?; + + let pub_enc_key = fs::read_to_string(pub_enc_key_file.clone())?; + let priv_enc_key = fs::read_to_string(priv_enc_key_file.clone())?; + + let ack_key_file = ack_key_file + .file_name() + .map(OsStr::to_string_lossy) + .ok_or(BackendError::CouldNotGetFilename)?; + let gateway_shared_key_file = gateway_shared_key_file + .file_name() + .map(OsStr::to_string_lossy) + .ok_or(BackendError::CouldNotGetFilename)?; + let pub_id_key_file = pub_id_key_file + .file_name() + .map(OsStr::to_string_lossy) + .ok_or(BackendError::CouldNotGetFilename)?; + let priv_id_key_file = priv_id_key_file + .file_name() + .map(OsStr::to_string_lossy) + .ok_or(BackendError::CouldNotGetFilename)?; + let pub_enc_key_file = pub_enc_key_file + .file_name() + .map(OsStr::to_string_lossy) + .ok_or(BackendError::CouldNotGetFilename)?; + let priv_enc_key_file = priv_enc_key_file + .file_name() + .map(OsStr::to_string_lossy) + .ok_or(BackendError::CouldNotGetFilename)?; + + // Format and return as json + let json = serde_json::json!({ + ack_key_file: ack_key, + gateway_shared_key_file: gateway_shared_key, + pub_id_key_file: pub_id_key, + priv_id_key_file: priv_id_key, + pub_enc_key_file: pub_enc_key, + priv_enc_key_file: priv_enc_key, + }); + + Ok(serde_json::to_string_pretty(&json)?) +} diff --git a/nym-connect/src-tauri/src/operations/mod.rs b/nym-connect/src-tauri/src/operations/mod.rs index c4a21fb7c9..766762858e 100644 --- a/nym-connect/src-tauri/src/operations/mod.rs +++ b/nym-connect/src-tauri/src/operations/mod.rs @@ -1,3 +1,4 @@ pub mod connection; pub mod directory; +pub mod export; pub mod window; diff --git a/nym-connect/src-tauri/src/state.rs b/nym-connect/src-tauri/src/state.rs index 36913f5192..d9a14dc9d0 100644 --- a/nym-connect/src-tauri/src/state.rs +++ b/nym-connect/src-tauri/src/state.rs @@ -1,10 +1,13 @@ use std::time::Duration; +use ::config::NymConfig; use futures::SinkExt; use tap::TapFallible; use tauri::Manager; -use nym_socks5::client::{Socks5ControlMessage, Socks5ControlMessageSender}; +use nym_socks5::client::{ + config::Config as Socks5Config, Socks5ControlMessage, Socks5ControlMessageSender, +}; use crate::{ config::{self, socks5_config_id_appended_with}, @@ -81,6 +84,13 @@ impl State { .and_then(|gateway_id| socks5_config_id_appended_with(gateway_id)) } + pub fn load_socks5_config(&self) -> Result { + let id = self.get_config_id()?; + let config = Socks5Config::load_from_file(Some(&id)) + .tap_err(|_| log::warn!("Failed to load configuration file"))?; + Ok(config) + } + /// Start connecting by first creating a config file, followed by starting a thread running the /// SOCKS5 client. pub async fn start_connecting( diff --git a/nym-connect/src-tauri/src/tasks.rs b/nym-connect/src-tauri/src/tasks.rs index 214ac33e8a..8e44f1bb40 100644 --- a/nym-connect/src-tauri/src/tasks.rs +++ b/nym-connect/src-tauri/src/tasks.rs @@ -7,23 +7,25 @@ use tokio::sync::RwLock; use config::NymConfig; #[cfg(not(feature = "coconut"))] use nym_socks5::client::NymClient as Socks5NymClient; -use nym_socks5::client::Socks5ControlMessageSender; +use nym_socks5::client::{config::Config as Socks5Config, Socks5ControlMessageSender}; use crate::{error::Result, state::State}; pub type StatusReceiver = futures::channel::oneshot::Receiver; +/// Status messages sent by the SOCKS5 client task to the main tauri task. #[derive(Debug)] pub enum Socks5StatusMessage { /// The SOCKS5 task successfully stopped Stopped, } +/// The main SOCKS5 client task. It loads the configuration from file determined by the `id`. pub fn start_nym_socks5_client( id: &str, ) -> Result<(Socks5ControlMessageSender, StatusReceiver, GatewayEndpoint)> { log::info!("Loading config from file: {id}"); - let config = nym_socks5::client::config::Config::load_from_file(Some(id)) + let config = Socks5Config::load_from_file(Some(id)) .tap_err(|_| log::warn!("Failed to load configuration file"))?; let used_gateway = config.get_base().get_gateway_endpoint().clone(); @@ -54,6 +56,8 @@ pub fn start_nym_socks5_client( Ok((socks5_ctrl_tx, socks5_status_rx, used_gateway)) } +/// The disconnect listener listens to the channel setup between the socks5 proxy task and the main +/// tauri task. Primarily it listens for shutdown messages, and updates the state accordingly. pub fn start_disconnect_listener( state: Arc>, window: tauri::Window,