diff --git a/Cargo.lock b/Cargo.lock index e80ca8a..3e3cbeb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2475,6 +2475,7 @@ dependencies = [ "bytes 0.5.6", "chacha20", "clap", + "dirs", "failure", "futures 0.3.17", "grin_api 5.2.0-alpha.1 (git+https://github.com/mimblewimble/grin)", diff --git a/Cargo.toml b/Cargo.toml index 3e12750..6b85996 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ byteorder = "1" bytes = "0.5.6" chacha20 = "0.8.1" clap = { version = "2.33", features = ["yaml"] } +dirs = "2.0" failure = "0.1.8" futures = "0.3" hmac = { version = "0.12.0", features = ["std"]} diff --git a/mwixnet.yml b/mwixnet.yml index 591dce2..a341dfd 100644 --- a/mwixnet.yml +++ b/mwixnet.yml @@ -13,11 +13,19 @@ args: short: n long: grin_node_url takes_value: true + - grin_node_secret_path: + help: Path to a file containing the secret for the GRIN node api + long: grin_node_secret_path + takes_value: true - wallet_owner_url: help: Api address of running wallet owner listener short: l long: wallet_owner_url takes_value: true + - wallet_owner_secret_path: + help: Path to a file containing the secret for the wallet owner api + long: wallet_owner_secret_path + takes_value: true - wallet_pass: help: The wallet's password long: wallet_pass diff --git a/src/config.rs b/src/config.rs index 16f7085..b9b2980 100644 --- a/src/config.rs +++ b/src/config.rs @@ -2,7 +2,7 @@ use crate::error::{self, Result}; use crate::secp::SecretKey; use core::num::NonZeroU32; -use grin_util::{ToHex, ZeroingString}; +use grin_util::{file, ToHex, ZeroingString}; use rand::{thread_rng, Rng}; use ring::{aead, pbkdf2}; use serde_derive::{Deserialize, Serialize}; @@ -11,6 +11,11 @@ use std::io::prelude::*; use std::net::SocketAddr; use std::path::PathBuf; +const GRIN_HOME: &str = ".grin"; +const CHAIN_NAME: &str = "main"; +const NODE_API_SECRET_FILE_NAME: &str = ".api_secret"; +const WALLET_OWNER_API_SECRET_FILE_NAME: &str = ".owner_api_secret"; + /// The decrypted server config to be passed around and used by the rest of the mwixnet code #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct ServerConfig { @@ -22,8 +27,22 @@ pub struct ServerConfig { pub addr: SocketAddr, /// foreign api address of the grin node pub grin_node_url: SocketAddr, + /// path to file containing api secret for the grin node + pub grin_node_secret_path: Option, /// owner api address of the grin wallet pub wallet_owner_url: SocketAddr, + /// path to file containing secret for the grin wallet's owner api + pub wallet_owner_secret_path: Option, +} + +impl ServerConfig { + pub fn node_api_secret(&self) -> Option { + file::get_first_line(self.grin_node_secret_path.clone()) + } + + pub fn wallet_owner_api_secret(&self) -> Option { + file::get_first_line(self.wallet_owner_secret_path.clone()) + } } /// Encrypted server key, for storing on disk and decrypting with a password. @@ -134,7 +153,9 @@ struct RawConfig { interval_s: u32, addr: SocketAddr, grin_node_url: SocketAddr, + grin_node_secret_path: Option, wallet_owner_url: SocketAddr, + wallet_owner_secret_path: Option, } /// Writes the server config to the config_path given, encrypting the server_key first. @@ -152,7 +173,9 @@ pub fn write_config( interval_s: server_config.interval_s, addr: server_config.addr, grin_node_url: server_config.grin_node_url, + grin_node_secret_path: server_config.grin_node_secret_path.clone(), wallet_owner_url: server_config.wallet_owner_url, + wallet_owner_secret_path: server_config.wallet_owner_secret_path.clone(), }; let encoded: String = toml::to_string(&raw_config).map_err(|e| { error::ErrorKind::SaveConfigError(format!("Error while encoding config as toml: {}", e)) @@ -189,10 +212,34 @@ pub fn load_config(config_path: &PathBuf, password: &ZeroingString) -> Result PathBuf { + let mut grin_path = match dirs::home_dir() { + Some(p) => p, + None => PathBuf::new(), + }; + grin_path.push(GRIN_HOME); + grin_path.push(CHAIN_NAME); + grin_path +} + +pub fn node_secret_path() -> PathBuf { + let mut path = get_grin_path(); + path.push(NODE_API_SECRET_FILE_NAME); + path +} + +pub fn wallet_owner_secret_path() -> PathBuf { + let mut path = get_grin_path(); + path.push(WALLET_OWNER_API_SECRET_FILE_NAME); + path +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/main.rs b/src/main.rs index b1f83cd..e880030 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,10 @@ use config::ServerConfig; -use error::{Error, ErrorKind}; use node::HttpGrinNode; use wallet::HttpWallet; use clap::App; use grin_util::{StopState, ZeroingString}; use rpassword; -use std::env; -use std::io::{self, Write}; use std::path::PathBuf; use std::sync::Arc; use tokio::runtime::Runtime; @@ -41,9 +38,9 @@ fn real_main() -> Result<(), Box> { let config_path = match args.value_of("config_file") { Some(path) => PathBuf::from(path), None => { - let mut current_dir = env::current_dir()?; - current_dir.push("mwixnet-config.toml"); - current_dir + let mut grin_path = config::get_grin_path(); + grin_path.push("mwixnet-config.toml"); + grin_path } }; @@ -52,7 +49,9 @@ fn real_main() -> Result<(), Box> { .map(|t| t.parse::().unwrap()); let bind_addr = args.value_of("bind_addr"); let grin_node_url = args.value_of("grin_node_url"); + let grin_node_secret_path = args.value_of("grin_node_secret_path"); let wallet_owner_url = args.value_of("wallet_owner_url"); + let wallet_owner_secret_path = args.value_of("wallet_owner_secret_path"); // Write a new config file if init-config command is supplied if let ("init-config", Some(_)) = args.subcommand() { @@ -68,7 +67,17 @@ fn real_main() -> Result<(), Box> { interval_s: round_time.unwrap_or(DEFAULT_INTERVAL), addr: bind_addr.unwrap_or("0.0.0.0:3000").parse()?, grin_node_url: grin_node_url.unwrap_or("127.0.0.1:3413").parse()?, + grin_node_secret_path: match grin_node_secret_path { + Some(p) => Some(p.to_owned()), + None => config::node_secret_path().to_str().map(|p| p.to_owned()), + }, wallet_owner_url: wallet_owner_url.unwrap_or("127.0.0.1:3420").parse()?, + wallet_owner_secret_path: match wallet_owner_secret_path { + Some(p) => Some(p.to_owned()), + None => config::wallet_owner_secret_path() + .to_str() + .map(|p| p.to_owned()), + }, }; let password = prompt_password_confirm(); @@ -93,31 +102,41 @@ fn real_main() -> Result<(), Box> { server_config.grin_node_url = grin_node_url.parse()?; } + // Override grin_node_secret_path, if supplied + if let Some(grin_node_secret_path) = grin_node_secret_path { + server_config.grin_node_secret_path = Some(grin_node_secret_path.to_owned()); + } + // Override wallet_owner_url, if supplied if let Some(wallet_owner_url) = wallet_owner_url { server_config.wallet_owner_url = wallet_owner_url.parse()?; } + // Override wallet_owner_secret_path, if supplied + if let Some(wallet_owner_secret_path) = wallet_owner_secret_path { + server_config.wallet_owner_secret_path = Some(wallet_owner_secret_path.to_owned()); + } + // Open wallet let wallet_pass = prompt_wallet_password(&args.value_of("wallet_pass")); - let wallet = HttpWallet::open_wallet(&server_config.wallet_owner_url, &wallet_pass)?; + let wallet = HttpWallet::open_wallet( + &server_config.wallet_owner_url, + &server_config.wallet_owner_api_secret(), + &wallet_pass, + )?; // Create GrinNode - let node = HttpGrinNode::new(&server_config.grin_node_url, &None); + let node = HttpGrinNode::new( + &server_config.grin_node_url, + &server_config.node_api_secret(), + ); let stop_state = Arc::new(StopState::new()); - let stop_state_clone = stop_state.clone(); let rt = Runtime::new()?; rt.spawn(async move { - let shutdown_signal = async move { - // Wait for the CTRL+C signal - tokio::signal::ctrl_c() - .await - .expect("failed to install CTRL+C signal handler"); - }; - futures::executor::block_on(shutdown_signal); + futures::executor::block_on(build_signals_fut()); stop_state_clone.stop(); }); @@ -130,6 +149,30 @@ fn real_main() -> Result<(), Box> { ) } +async fn build_signals_fut() { + if cfg!(unix) { + use tokio::signal::unix::{signal, SignalKind}; + + // Listen for SIGINT, SIGQUIT, and SIGTERM + let mut terminate_signal = + signal(SignalKind::terminate()).expect("failed to create terminate signal"); + let mut quit_signal = signal(SignalKind::quit()).expect("failed to create quit signal"); + let mut interrupt_signal = + signal(SignalKind::interrupt()).expect("failed to create interrupt signal"); + + futures::future::select_all(vec![ + Box::pin(terminate_signal.recv()), + Box::pin(quit_signal.recv()), + Box::pin(interrupt_signal.recv()), + ]) + .await; + } else { + tokio::signal::ctrl_c() + .await + .expect("failed to install CTRL+C signal handler"); + } +} + fn prompt_password() -> ZeroingString { ZeroingString::from(rpassword::prompt_password_stdout("Server password: ").unwrap()) } diff --git a/src/secp.rs b/src/secp.rs index 5aef196..67221c2 100644 --- a/src/secp.rs +++ b/src/secp.rs @@ -116,15 +116,14 @@ pub mod comsig_serde { } /// Creates a ComSignature from a hex string - pub fn deserialize<'de, D>(deserializer: D) -> std::result::Result + pub fn deserialize<'de, D>(deserializer: D) -> Result where D: serde::Deserializer<'de>, { use serde::de::Error; let bytes = String::deserialize(deserializer) .and_then(|string| grin_util::from_hex(&string).map_err(Error::custom))?; - let sig: ComSignature = - grin_core::ser::deserialize_default(&mut &bytes[..]).map_err(Error::custom)?; + let sig: ComSignature = ser::deserialize_default(&mut &bytes[..]).map_err(Error::custom)?; Ok(sig) } } diff --git a/src/server.rs b/src/server.rs index 8e14925..483cbf5 100644 --- a/src/server.rs +++ b/src/server.rs @@ -319,7 +319,9 @@ mod tests { interval_s: 1, addr: TcpListener::bind("127.0.0.1:0")?.local_addr()?, grin_node_url: "127.0.0.1:3413".parse()?, + grin_node_secret_path: None, wallet_owner_url: "127.0.0.1:3420".parse()?, + wallet_owner_secret_path: None, }; let threaded_rt = runtime::Runtime::new()?; diff --git a/src/wallet.rs b/src/wallet.rs index bcdb7db..93a0d59 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -85,6 +85,7 @@ pub fn assemble_tx( #[derive(Clone)] pub struct HttpWallet { wallet_owner_url: SocketAddr, + wallet_owner_secret: Option, shared_key: SecretKey, token: Token, } @@ -104,6 +105,7 @@ impl HttpWallet { /// Calls the 'open_wallet' using the RPC API. pub fn open_wallet( wallet_owner_url: &SocketAddr, + wallet_owner_secret: &Option, wallet_pass: &ZeroingString, ) -> Result { println!("Opening wallet at {}", wallet_owner_url); @@ -115,6 +117,7 @@ impl HttpWallet { }); let token: Token = HttpWallet::send_enc_request( &wallet_owner_url, + &wallet_owner_secret, "open_wallet", &open_wallet_params, &shared_key, @@ -123,6 +126,7 @@ impl HttpWallet { Ok(HttpWallet { wallet_owner_url: wallet_owner_url.clone(), + wallet_owner_secret: wallet_owner_secret.clone(), shared_key: shared_key.clone(), token: token.clone(), }) @@ -152,6 +156,7 @@ impl HttpWallet { fn send_enc_request( wallet_owner_url: &SocketAddr, + wallet_owner_secret: &Option, method: &str, params: &serde_json::Value, shared_key: &SecretKey, @@ -164,8 +169,11 @@ impl HttpWallet { "jsonrpc": "2.0", }); let enc_req = EncryptedRequest::from_json(&JsonId::IntId(1), &req, &shared_key)?; - let res = - client::post::(url.as_str(), None, &enc_req)?; + let res = client::post::( + url.as_str(), + wallet_owner_secret.clone(), + &enc_req, + )?; let decrypted = res.decrypt(&shared_key)?; let response: Response = serde_json::from_value(decrypted.clone())?; let parsed = serde_json::from_value(response.result.unwrap().get("Ok").unwrap().clone())?; @@ -205,6 +213,7 @@ impl Wallet for HttpWallet { }); let output: OutputWithBlind = HttpWallet::send_enc_request( &self.wallet_owner_url, + &self.wallet_owner_secret, "build_output", &req_json, &self.shared_key,