// Copyright 2021 The Grin Developers // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //! Controller for wallet.. instantiates and handles listeners (or single-run //! invocations) as needed. use crate::api::{self, ApiServer, BasicAuthMiddleware, ResponseFuture, Router, TLSConfig}; use crate::config::TorConfig; use crate::keychain::Keychain; use crate::libwallet::{ address, Error, ErrorKind, NodeClient, NodeVersionInfo, Slate, SlatepackAddress, WalletInst, WalletLCProvider, GRIN_BLOCK_HEADER_VERSION, }; use crate::util::secp::key::SecretKey; use crate::util::{from_hex, static_secp_instance, to_base64, Mutex}; use failure::ResultExt; use grin_wallet_api::JsonId; use grin_wallet_config::types::{TorBridgeConfig, TorProxyConfig}; use grin_wallet_util::OnionV3Address; use hyper::body; use hyper::header::HeaderValue; use hyper::{Body, Request, Response, StatusCode}; use serde::{Deserialize, Serialize}; use serde_json; use std::collections::HashMap; use std::convert::TryFrom; use std::net::SocketAddr; use std::sync::Arc; use crate::impls::tor::config as tor_config; use crate::impls::tor::process as tor_process; use crate::impls::tor::{bridge as tor_bridge, proxy as tor_proxy}; use crate::apiwallet::{ EncryptedRequest, EncryptedResponse, EncryptionErrorResponse, Foreign, ForeignCheckMiddlewareFn, ForeignRpc, Owner, OwnerRpc, }; use easy_jsonrpc_mw; use easy_jsonrpc_mw::{Handler, MaybeReply}; lazy_static! { pub static ref GRIN_OWNER_BASIC_REALM: HeaderValue = HeaderValue::from_str("Basic realm=GrinOwnerAPI").unwrap(); } fn check_middleware( name: ForeignCheckMiddlewareFn, node_version_info: Option, slate: Option<&Slate>, ) -> Result<(), Error> { match name { // allow coinbases to be built regardless ForeignCheckMiddlewareFn::BuildCoinbase => Ok(()), _ => { let mut bhv = 3; if let Some(n) = node_version_info { bhv = n.block_header_version; } if let Some(s) = slate { if bhv > 4 && s.version_info.block_header_version < GRIN_BLOCK_HEADER_VERSION { Err(ErrorKind::Compatibility( "Incoming Slate is not compatible with this wallet. \ Please upgrade the node or use a different one." .into(), ))?; } } Ok(()) } } } /// initiate the tor listener fn init_tor_listener( wallet: Arc + 'static>>>, keychain_mask: Arc>>, addr: &str, bridge: TorBridgeConfig, tor_proxy: TorProxyConfig, ) -> Result<(tor_process::TorProcess, SlatepackAddress), Error> where L: WalletLCProvider<'static, C, K> + 'static, C: NodeClient + 'static, K: Keychain + 'static, { let mut process = tor_process::TorProcess::new(); let mask = keychain_mask.lock(); // eventually want to read a list of service config keys let mut w_lock = wallet.lock(); let lc = w_lock.lc_provider()?; let w_inst = lc.wallet_inst()?; let k = w_inst.keychain((&mask).as_ref())?; let parent_key_id = w_inst.parent_key_id(); let tor_dir = format!("{}/tor/listener", lc.get_top_level_directory()?); let sec_key = address::address_from_derivation_path(&k, &parent_key_id, 0) .map_err(|e| ErrorKind::TorConfig(format!("{:?}", e).into()))?; let onion_address = OnionV3Address::from_private(&sec_key.0) .map_err(|e| ErrorKind::TorConfig(format!("{:?}", e).into()))?; let sp_address = SlatepackAddress::try_from(onion_address.clone())?; let mut hm_tor_bridge: HashMap = HashMap::new(); let mut tor_timeout = 20; if bridge.bridge_line.is_some() { tor_timeout = 40; let bridge_config = tor_bridge::TorBridge::try_from(bridge) .map_err(|e| ErrorKind::TorConfig(format!("{}", e).into()))?; hm_tor_bridge = bridge_config .to_hashmap() .map_err(|e| ErrorKind::TorConfig(format!("{}", e).into()))?; } let mut hm_tor_poxy: HashMap = HashMap::new(); if tor_proxy.transport.is_some() || tor_proxy.allowed_port.is_some() { let proxy_config = tor_proxy::TorProxy::try_from(tor_proxy) .map_err(|e| ErrorKind::TorConfig(format!("{}", e).into()))?; hm_tor_poxy = proxy_config .to_hashmap() .map_err(|e| ErrorKind::TorConfig(format!("{}", e.kind()).into()))?; } warn!( "Starting Tor Hidden Service for API listener at address {}, binding to {}", onion_address, addr ); tor_config::output_tor_listener_config( &tor_dir, addr, &vec![sec_key], hm_tor_bridge, hm_tor_poxy, ) .map_err(|e| ErrorKind::TorConfig(format!("{:?}", e).into()))?; // Start TOR process process .torrc_path(&format!("{}/torrc", tor_dir)) .working_dir(&tor_dir) .timeout(tor_timeout) .completion_percent(100) .launch() .map_err(|e| ErrorKind::TorProcess(format!("{:?}", e).into()))?; Ok((process, sp_address)) } /// Instantiate wallet Owner API for a single-use (command line) call /// Return a function containing a loaded API context to call pub fn owner_single_use( wallet: Option>>>>, keychain_mask: Option<&SecretKey>, api_context: Option<&mut Owner>, f: F, ) -> Result<(), Error> where L: WalletLCProvider<'static, C, K> + 'static, F: FnOnce(&mut Owner, Option<&SecretKey>) -> Result<(), Error>, C: NodeClient + 'static, K: Keychain + 'static, { match api_context { Some(c) => f(c, keychain_mask)?, None => { let wallet = match wallet { Some(w) => w, None => { return Err(ErrorKind::GenericError(format!( "Instantiated wallet or Owner API context must be provided" )) .into()) } }; f(&mut Owner::new(wallet, None), keychain_mask)? } } Ok(()) } /// Instantiate wallet Foreign API for a single-use (command line) call /// Return a function containing a loaded API context to call pub fn foreign_single_use<'a, L, F, C, K>( wallet: Arc>>>, keychain_mask: Option, f: F, ) -> Result<(), Error> where L: WalletLCProvider<'a, C, K>, F: FnOnce(&mut Foreign<'a, L, C, K>) -> Result<(), Error>, C: NodeClient + 'a, K: Keychain + 'a, { f(&mut Foreign::new( wallet, keychain_mask, Some(check_middleware), false, ))?; Ok(()) } /// Listener version, providing same API but listening for requests on a /// port and wrapping the calls /// Note keychain mask is only provided here in case the foreign listener is also being used /// in the same wallet instance pub fn owner_listener( wallet: Arc + 'static>>>, keychain_mask: Arc>>, addr: &str, api_secret: Option, tls_config: Option, owner_api_include_foreign: Option, tor_config: Option, test_mode: bool, ) -> Result<(), Error> where L: WalletLCProvider<'static, C, K> + 'static, C: NodeClient + 'static, K: Keychain + 'static, { let mut router = Router::new(); if api_secret.is_some() { let api_basic_auth = "Basic ".to_string() + &to_base64(&("grin:".to_string() + &api_secret.unwrap())); let basic_auth_middleware = Arc::new(BasicAuthMiddleware::new( api_basic_auth, &GRIN_OWNER_BASIC_REALM, Some("/v2/foreign".into()), )); router.add_middleware(basic_auth_middleware); } let mut running_foreign = false; if owner_api_include_foreign.unwrap_or(false) { running_foreign = true; } let api_handler_v3 = OwnerAPIHandlerV3::new( wallet.clone(), keychain_mask.clone(), tor_config.clone(), running_foreign, ); router .add_route("/v3/owner", Arc::new(api_handler_v3)) .map_err(|_| ErrorKind::GenericError("Router failed to add route".to_string()))?; // If so configured, add the foreign API to the same port if running_foreign { warn!("Starting HTTP Foreign API on Owner server at {}.", addr); let foreign_api_handler_v2 = ForeignAPIHandlerV2::new(wallet, keychain_mask, test_mode, Mutex::new(tor_config)); router .add_route("/v2/foreign", Arc::new(foreign_api_handler_v2)) .map_err(|_| ErrorKind::GenericError("Router failed to add route".to_string()))?; } let mut apis = ApiServer::new(); warn!("Starting HTTP Owner API server at {}.", addr); let socket_addr: SocketAddr = addr.parse().expect("unable to parse socket address"); let api_thread = apis.start(socket_addr, router, tls_config) .context(ErrorKind::GenericError( "API thread failed to start".to_string(), ))?; warn!("HTTP Owner listener started."); api_thread .join() .map_err(|e| ErrorKind::GenericError(format!("API thread panicked :{:?}", e)).into()) } /// Listener version, providing same API but listening for requests on a /// port and wrapping the calls pub fn foreign_listener( wallet: Arc + 'static>>>, keychain_mask: Arc>>, addr: &str, tls_config: Option, use_tor: bool, test_mode: bool, tor_config: Option, ) -> Result<(), Error> where L: WalletLCProvider<'static, C, K> + 'static, C: NodeClient + 'static, K: Keychain + 'static, { // Check if wallet has been opened first { let mut w_lock = wallet.lock(); let lc = w_lock.lc_provider()?; let _ = lc.wallet_inst()?; } let (tor_bridge, tor_proxy) = match tor_config.clone() { Some(s) => (s.bridge, s.proxy), None => (TorBridgeConfig::default(), TorProxyConfig::default()), }; // need to keep in scope while the main listener is running let (_tor_process, address) = match use_tor { true => { match init_tor_listener( wallet.clone(), keychain_mask.clone(), addr, tor_bridge, tor_proxy, ) { Ok((tp, addr)) => (Some(tp), Some(addr)), Err(e) => { warn!("Unable to start TOR listener; Check that TOR executable is installed and on your path"); error!("Tor Error: {}", e); warn!("Listener will be available via HTTP only"); (None, None) } } } false => (None, None), }; let api_handler_v2 = ForeignAPIHandlerV2::new(wallet, keychain_mask, test_mode, Mutex::new(tor_config)); let mut router = Router::new(); router .add_route("/v2/foreign", Arc::new(api_handler_v2)) .map_err(|_| ErrorKind::GenericError("Router failed to add route".to_string()))?; let mut apis = ApiServer::new(); warn!("Starting HTTP Foreign listener API server at {}.", addr); let socket_addr: SocketAddr = addr.parse().expect("unable to parse socket address"); let api_thread = apis.start(socket_addr, router, tls_config) .context(ErrorKind::GenericError( "API thread failed to start".to_string(), ))?; warn!("HTTP Foreign listener started."); if let Some(a) = address { warn!("Slatepack Address is: {}", a); } api_thread .join() .map_err(|e| ErrorKind::GenericError(format!("API thread panicked :{:?}", e)).into()) } /// V3 API Handler/Wrapper for owner functions, which include a secure /// mode + lifecycle functions pub struct OwnerAPIHandlerV3 where L: WalletLCProvider<'static, C, K> + 'static, C: NodeClient + 'static, K: Keychain + 'static, { /// Wallet instance pub wallet: Arc + 'static>>>, /// Handle to Owner API owner_api: Arc>, /// ECDH shared key pub shared_key: Arc>>, /// Keychain mask (to change if also running the foreign API) pub keychain_mask: Arc>>, /// Whether we're running the foreign API on the same port, and therefore /// have to store the mask in-process pub running_foreign: bool, } pub struct OwnerV3Helpers; impl OwnerV3Helpers { /// Checks whether a request is to init the secure API pub fn is_init_secure_api(val: &serde_json::Value) -> bool { matches!(val["method"].as_str(), Some("init_secure_api")) } /// Checks whether a request is to open the wallet pub fn is_open_wallet(val: &serde_json::Value) -> bool { matches!(val["method"].as_str(), Some("open_wallet")) } /// Checks whether a request is an encrypted request pub fn is_encrypted_request(val: &serde_json::Value) -> bool { matches!(val["method"].as_str(), Some("encrypted_request_v3")) } /// whether encryption is enabled pub fn encryption_enabled(key: Arc>>) -> bool { let share_key_ref = key.lock(); share_key_ref.is_some() } /// If incoming is an encrypted request, check there is a shared key, /// Otherwise return an error value pub fn check_encryption_started( key: Arc>>, ) -> Result<(), serde_json::Value> { match OwnerV3Helpers::encryption_enabled(key) { true => Ok(()), false => Err(EncryptionErrorResponse::new( 1, -32001, "Encryption must be enabled. Please call 'init_secure_api` first", ) .as_json_value()), } } /// Update the statically held owner API shared key pub fn update_owner_api_shared_key( key: Arc>>, val: &serde_json::Value, new_key: Option, ) { if let Some(_) = val["result"]["Ok"].as_str() { let mut share_key_ref = key.lock(); *share_key_ref = new_key; } } /// Update the shared mask, in case of foreign API being run pub fn update_mask(mask: Arc>>, val: &serde_json::Value) { if let Some(key) = val["result"]["Ok"].as_str() { let key_bytes = match from_hex(key) { Ok(k) => k, Err(_) => return, }; let secp_inst = static_secp_instance(); let secp = secp_inst.lock(); let sk = match SecretKey::from_slice(&secp, &key_bytes) { Ok(s) => s, Err(_) => return, }; let mut shared_mask_ref = mask.lock(); *shared_mask_ref = Some(sk); } } /// Decrypt an encrypted request pub fn decrypt_request( key: Arc>>, req: &serde_json::Value, ) -> Result<(JsonId, serde_json::Value), serde_json::Value> { let share_key_ref = key.lock(); let shared_key = share_key_ref.as_ref().unwrap(); let enc_req: EncryptedRequest = serde_json::from_value(req.clone()).map_err(|e| { EncryptionErrorResponse::new( 1, -32002, &format!("Encrypted request format error: {}", e), ) .as_json_value() })?; let id = enc_req.id.clone(); let res = enc_req.decrypt(&shared_key).map_err(|e| { EncryptionErrorResponse::new(1, -32002, &format!("Decryption error: {}", e.kind())) .as_json_value() })?; Ok((id, res)) } /// Encrypt a response pub fn encrypt_response( key: Arc>>, id: &JsonId, res: &serde_json::Value, ) -> Result { let share_key_ref = key.lock(); let shared_key = share_key_ref.as_ref().unwrap(); let enc_res = EncryptedResponse::from_json(id, res, &shared_key).map_err(|e| { EncryptionErrorResponse::new(1, -32003, &format!("EncryptionError: {}", e.kind())) .as_json_value() })?; let res = enc_res.as_json_value().map_err(|e| { EncryptionErrorResponse::new( 1, -32002, &format!("Encrypted response format error: {}", e), ) .as_json_value() })?; Ok(res) } /// convert an internal error (if exists) as proper JSON-RPC pub fn check_error_response(val: &serde_json::Value) -> (bool, serde_json::Value) { // check for string first. This ensures that error messages // that are just strings aren't given weird formatting let err_string = if val["result"]["Err"].is_object() { let mut retval; let hashed: Result, serde_json::Error> = serde_json::from_value(val["result"]["Err"].clone()); retval = match hashed { Err(e) => { debug!("Can't cast value to Hashmap {}", e); None } Ok(h) => { let mut r = "".to_owned(); for (k, v) in h.iter() { r = format!("{}: {}", k, v); } Some(r) } }; // Otherwise, see if error message is a map that needs // to be stringified (and accept weird formatting) if retval.is_none() { let hashed: Result, serde_json::Error> = serde_json::from_value(val["result"]["Err"].clone()); retval = match hashed { Err(e) => { debug!("Can't cast value to Hashmap {}", e); None } Ok(h) => { let mut r = "".to_owned(); for (k, v) in h.iter() { r = format!("{}: {}", k, v); } Some(r) } } } retval } else if val["result"]["Err"].is_string() { let parsed = serde_json::from_value::(val["result"]["Err"].clone()); match parsed { Ok(p) => Some(p), Err(_) => None, } } else { None }; match err_string { Some(s) => { return ( true, serde_json::json!({ "jsonrpc": "2.0", "id": val["id"], "error": { "message": s, "code": -32099 } }), ) } None => (false, val.clone()), } } } impl OwnerAPIHandlerV3 where L: WalletLCProvider<'static, C, K>, C: NodeClient + 'static, K: Keychain + 'static, { /// Create a new owner API handler for GET methods pub fn new( wallet: Arc + 'static>>>, keychain_mask: Arc>>, tor_config: Option, running_foreign: bool, ) -> OwnerAPIHandlerV3 { let owner_api = Owner::new(wallet.clone(), None); owner_api.set_tor_config(tor_config); let owner_api = Arc::new(owner_api); OwnerAPIHandlerV3 { wallet, owner_api, shared_key: Arc::new(Mutex::new(None)), keychain_mask: keychain_mask, running_foreign, } } async fn call_api( req: Request, key: Arc>>, mask: Arc>>, running_foreign: bool, api: Arc>, ) -> Result { let mut val: serde_json::Value = parse_body(req).await?; let mut is_init_secure_api = OwnerV3Helpers::is_init_secure_api(&val); let mut was_encrypted = false; let mut encrypted_req_id = JsonId::StrId(String::from("")); if !is_init_secure_api { if let Err(v) = OwnerV3Helpers::check_encryption_started(key.clone()) { return Ok(v); } let res = OwnerV3Helpers::decrypt_request(key.clone(), &val); match res { Err(e) => return Ok(e), Ok(v) => { encrypted_req_id = v.0.clone(); val = v.1; } } was_encrypted = true; } // check again, in case it was an encrypted call to init_secure_api is_init_secure_api = OwnerV3Helpers::is_init_secure_api(&val); // also need to intercept open/close wallet requests let is_open_wallet = OwnerV3Helpers::is_open_wallet(&val); match ::handle_request(&*api, val) { MaybeReply::Reply(mut r) => { let (_was_error, unencrypted_intercept) = OwnerV3Helpers::check_error_response(&r.clone()); if is_open_wallet && running_foreign { OwnerV3Helpers::update_mask(mask, &r.clone()); } if was_encrypted { let res = OwnerV3Helpers::encrypt_response( key.clone(), &encrypted_req_id, &unencrypted_intercept, ); r = match res { Ok(v) => v, Err(v) => return Ok(v), } } // intercept init_secure_api response (after encryption, // in case it was an encrypted call to 'init_api_secure') if is_init_secure_api { OwnerV3Helpers::update_owner_api_shared_key( key.clone(), &unencrypted_intercept, api.shared_key.lock().clone(), ); } Ok(r) } MaybeReply::DontReply => { // Since it's http, we need to return something. We return [] because jsonrpc // clients will parse it as an empty batch response. Ok(serde_json::json!([])) } } } async fn handle_post_request( req: Request, key: Arc>>, mask: Arc>>, running_foreign: bool, api: Arc>, ) -> Result, Error> { let res = Self::call_api(req, key, mask, running_foreign, api).await?; Ok(json_response_pretty(&res)) } } impl api::Handler for OwnerAPIHandlerV3 where L: WalletLCProvider<'static, C, K> + 'static, C: NodeClient + 'static, K: Keychain + 'static, { fn post(&self, req: Request) -> ResponseFuture { let key = self.shared_key.clone(); let mask = self.keychain_mask.clone(); let running_foreign = self.running_foreign; let api = self.owner_api.clone(); Box::pin(async move { match Self::handle_post_request(req, key, mask, running_foreign, api).await { Ok(r) => Ok(r), Err(e) => { error!("Request Error: {:?}", e); Ok(create_error_response(e)) } } }) } fn options(&self, _req: Request) -> ResponseFuture { Box::pin(async { Ok(create_ok_response("{}")) }) } } /// V2 API Handler/Wrapper for foreign functions pub struct ForeignAPIHandlerV2 where L: WalletLCProvider<'static, C, K> + 'static, C: NodeClient + 'static, K: Keychain + 'static, { /// Wallet instance pub wallet: Arc + 'static>>>, /// Keychain mask pub keychain_mask: Arc>>, /// run in doctest mode pub test_mode: bool, /// tor config pub tor_config: Mutex>, } impl ForeignAPIHandlerV2 where L: WalletLCProvider<'static, C, K> + 'static, C: NodeClient + 'static, K: Keychain + 'static, { /// Create a new foreign API handler for GET methods pub fn new( wallet: Arc + 'static>>>, keychain_mask: Arc>>, test_mode: bool, tor_config: Mutex>, ) -> ForeignAPIHandlerV2 { ForeignAPIHandlerV2 { wallet, keychain_mask, test_mode, tor_config, } } async fn call_api( req: Request, api: Foreign<'static, L, C, K>, ) -> Result { let val: serde_json::Value = parse_body(req).await?; match ::handle_request(&api, val) { MaybeReply::Reply(r) => Ok(r), MaybeReply::DontReply => { // Since it's http, we need to return something. We return [] because jsonrpc // clients will parse it as an empty batch response. Ok(serde_json::json!([])) } } } async fn handle_post_request( req: Request, mask: Option, wallet: Arc + 'static>>>, test_mode: bool, tor_config: Option, ) -> Result, Error> { let api = Foreign::new(wallet, mask, Some(check_middleware), test_mode); api.set_tor_config(tor_config); let res = Self::call_api(req, api).await?; Ok(json_response_pretty(&res)) } } impl api::Handler for ForeignAPIHandlerV2 where L: WalletLCProvider<'static, C, K> + 'static, C: NodeClient + 'static, K: Keychain + 'static, { fn post(&self, req: Request) -> ResponseFuture { let mask = self.keychain_mask.lock().clone(); let wallet = self.wallet.clone(); let test_mode = self.test_mode; let tor_config = self.tor_config.lock().clone(); Box::pin(async move { match Self::handle_post_request(req, mask, wallet, test_mode, tor_config).await { Ok(v) => Ok(v), Err(e) => { error!("Request Error: {:?}", e); Ok(create_error_response(e)) } } }) } fn options(&self, _req: Request) -> ResponseFuture { Box::pin(async { Ok(create_ok_response("{}")) }) } } // Utility to serialize a struct into JSON and produce a sensible Response // out of it. fn _json_response(s: &T) -> Response where T: Serialize, { match serde_json::to_string(s) { Ok(json) => response(StatusCode::OK, json), Err(_) => response(StatusCode::INTERNAL_SERVER_ERROR, ""), } } // pretty-printed version of above fn json_response_pretty(s: &T) -> Response where T: Serialize, { match serde_json::to_string_pretty(s) { Ok(json) => response(StatusCode::OK, json), Err(_) => response(StatusCode::INTERNAL_SERVER_ERROR, ""), } } fn create_error_response(e: Error) -> Response { Response::builder() .status(StatusCode::INTERNAL_SERVER_ERROR) .header("access-control-allow-origin", "*") .header( "access-control-allow-headers", "Content-Type, Authorization", ) .body(format!("{}", e).into()) .unwrap() } fn create_ok_response(json: &str) -> Response { Response::builder() .status(StatusCode::OK) .header("access-control-allow-origin", "*") .header( "access-control-allow-headers", "Content-Type, Authorization", ) .header(hyper::header::CONTENT_TYPE, "application/json") .body(json.to_string().into()) .unwrap() } /// Build a new hyper Response with the status code and body provided. /// /// Whenever the status code is `StatusCode::OK` the text parameter should be /// valid JSON as the content type header will be set to `application/json' fn response>(status: StatusCode, text: T) -> Response { let mut builder = Response::builder() .status(status) .header("access-control-allow-origin", "*") .header( "access-control-allow-headers", "Content-Type, Authorization", ); if status == StatusCode::OK { builder = builder.header(hyper::header::CONTENT_TYPE, "application/json"); } builder.body(text.into()).unwrap() } async fn parse_body(req: Request) -> Result where for<'de> T: Deserialize<'de> + Send + 'static, { let body = body::to_bytes(req.into_body()) .await .map_err(|_| ErrorKind::GenericError("Failed to read request".to_string()))?; serde_json::from_reader(&body[..]) .map_err(|e| ErrorKind::GenericError(format!("Invalid request body: {}", e)).into()) }