diff --git a/api/src/handlers.rs b/api/src/handlers.rs index 854c8f1d8..33fdd7d61 100644 --- a/api/src/handlers.rs +++ b/api/src/handlers.rs @@ -797,6 +797,7 @@ pub fn start_rest_apis( tx_pool: Weak>, peers: Weak, api_secret: Option, + tls_config: Option, ) -> bool { let mut apis = ApiServer::new(); let mut router = build_router(chain, tx_pool, peers).expect("unable to build API router"); @@ -810,7 +811,7 @@ pub fn start_rest_apis( info!(LOGGER, "Starting HTTP API server at {}.", addr); let socket_addr: SocketAddr = addr.parse().expect("unable to parse socket address"); - apis.start(socket_addr, router).is_ok() + apis.start(socket_addr, router, tls_config).is_ok() } pub fn build_router( diff --git a/api/src/rest.rs b/api/src/rest.rs index 38cdd9d93..9d440a4ef 100644 --- a/api/src/rest.rs +++ b/api/src/rest.rs @@ -18,7 +18,7 @@ //! To use it, just have your service(s) implement the ApiEndpoint trait and //! register them on a ApiServer. -use failure::{Backtrace, Context, Fail}; +use failure::{Backtrace, Context, Fail, ResultExt}; use futures::sync::oneshot; use futures::Stream; use hyper::rt::Future; @@ -27,6 +27,8 @@ use hyper::{rt, Body, Request, Server}; use native_tls::{Identity, TlsAcceptor}; use router::{Handler, HandlerObj, ResponseFuture, Router}; use std::fmt::{self, Display}; +use std::fs::File; +use std::io::Read; use std::net::SocketAddr; use std::{io, thread}; use tokio::net::TcpListener; @@ -95,6 +97,22 @@ pub struct TLSConfig { pub pass: String, } +impl TLSConfig { + pub fn new(pass: String, file: String) -> Result { + let mut f = File::open(&file).context(ErrorKind::Internal(format!( + "can't open TLS certifiacte file {}", + file + )))?; + let mut pkcs_bytes = Vec::new(); + f.read_to_end(&mut pkcs_bytes) + .context(ErrorKind::Internal(format!( + "can't read TLS certifiacte file {}", + file + )))?; + Ok(TLSConfig { pkcs_bytes, pass }) + } +} + /// HTTP server allowing the registration of ApiEndpoint implementations. pub struct ApiServer { shutdown_sender: Option>, @@ -109,11 +127,25 @@ impl ApiServer { } } - /// Starts the ApiServer at the provided address. + /// Starts ApiServer at the provided address. + /// TODO support stop operation pub fn start( &mut self, addr: SocketAddr, router: Router, + conf: Option, + ) -> Result, Error> { + match conf { + Some(conf) => self.start_tls(addr, router, conf), + None => self.start_no_tls(addr, router), + } + } + + /// Starts the ApiServer at the provided address. + fn start_no_tls( + &mut self, + addr: SocketAddr, + router: Router, ) -> Result, Error> { if self.shutdown_sender.is_some() { return Err(ErrorKind::Internal( @@ -137,7 +169,7 @@ impl ApiServer { /// Starts the TLS ApiServer at the provided address. /// TODO support stop operation - pub fn start_tls( + fn start_tls( &mut self, addr: SocketAddr, router: Router, diff --git a/api/tests/rest.rs b/api/tests/rest.rs index ccc8579bb..c17495198 100644 --- a/api/tests/rest.rs +++ b/api/tests/rest.rs @@ -69,7 +69,7 @@ fn test_start_api() { router.add_middleware(counter.clone()); let server_addr = "127.0.0.1:14434"; let addr: SocketAddr = server_addr.parse().expect("unable to parse server address"); - assert!(server.start(addr, router).is_ok()); + assert!(server.start(addr, router, None).is_ok()); let url = format!("http://{}/v1/", server_addr); let index = api::client::get::>(url.as_str(), None).unwrap(); assert_eq!(index.len(), 2); @@ -94,7 +94,7 @@ fn test_start_api_tls() { let router = build_router(); let server_addr = "127.0.0.1:14444"; let addr: SocketAddr = server_addr.parse().expect("unable to parse server address"); - assert!(server.start_tls(addr, router, tls_conf).is_ok()); + assert!(server.start(addr, router, Some(tls_conf)).is_ok()); let url = format!("https://{}/v1/", server_addr); let index = api::client::get::>(url.as_str(), None).unwrap(); assert_eq!(index.len(), 2); diff --git a/config/src/comments.rs b/config/src/comments.rs index 2158d98da..a5f31cbaf 100644 --- a/config/src/comments.rs +++ b/config/src/comments.rs @@ -310,6 +310,14 @@ fn comments() -> HashMap { "api_listen_port".to_string(), " #port for wallet listener + +#path of TLS certificate file (PKCS#12 format is supported) +#self-signed certificates are not supported, use https://github.com/FiloSottile/mkcert +#to test on localhost +#tls_certificate_file = \"\" +#password of TLS certificate file (PKCS#12 format is supported) +#tls_certificate_pass = \"\" + ".to_string(), ); diff --git a/servers/src/grin/server.rs b/servers/src/grin/server.rs index cc62de1ca..c08c20ce9 100644 --- a/servers/src/grin/server.rs +++ b/servers/src/grin/server.rs @@ -263,6 +263,7 @@ impl Server { Arc::downgrade(&tx_pool), Arc::downgrade(&p2p_server.peers), api_secret, + None, ); info!( diff --git a/servers/tests/framework/mod.rs b/servers/tests/framework/mod.rs index f49040f30..7c3253cc9 100644 --- a/servers/tests/framework/mod.rs +++ b/servers/tests/framework/mod.rs @@ -280,6 +280,7 @@ impl LocalServerContainer { wallet::controller::foreign_listener( Box::new(wallet), &self.wallet_config.api_listen_addr(), + None, ).unwrap_or_else(|e| { panic!( "Error creating wallet listener: {:?} Config: {:?}", diff --git a/servers/tests/simulnet.rs b/servers/tests/simulnet.rs index 03f2130ae..a800c81df 100644 --- a/servers/tests/simulnet.rs +++ b/servers/tests/simulnet.rs @@ -426,7 +426,7 @@ fn replicate_tx_fluff_failure() { let client1 = HTTPWalletClient::new("http://127.0.0.1:23003", None); let wallet1 = create_wallet("target/tmp/tx_fluff/wallet1", client1.clone()); let wallet1_handle = thread::spawn(move || { - controller::foreign_listener(wallet1, "127.0.0.1:33000") + controller::foreign_listener(wallet1, "127.0.0.1:33000", None) .unwrap_or_else(|e| panic!("Error creating wallet1 listener: {:?}", e,)); }); @@ -434,7 +434,7 @@ fn replicate_tx_fluff_failure() { let client2 = HTTPWalletClient::new("http://127.0.0.1:23001", None); let wallet2 = create_wallet("target/tmp/tx_fluff/wallet2", client2.clone()); let wallet2_handle = thread::spawn(move || { - controller::foreign_listener(wallet2, "127.0.0.1:33001") + controller::foreign_listener(wallet2, "127.0.0.1:33001", None) .unwrap_or_else(|e| panic!("Error creating wallet2 listener: {:?}", e,)); }); diff --git a/src/bin/cmd/wallet.rs b/src/bin/cmd/wallet.rs index 437706299..ac34ad327 100644 --- a/src/bin/cmd/wallet.rs +++ b/src/bin/cmd/wallet.rs @@ -24,6 +24,7 @@ use std::time::Duration; use clap::ArgMatches; +use api::TLSConfig; use config::GlobalWalletConfig; use core::{core, global}; use grin_wallet::{self, controller, display, libwallet}; @@ -134,12 +135,24 @@ pub fn wallet_command(wallet_args: &ArgMatches, config: GlobalWalletConfig) { let wallet = instantiate_wallet(wallet_config.clone(), passphrase, node_api_secret.clone()); let api_secret = get_first_line(wallet_config.api_secret_path.clone()); + let tls_conf = match wallet_config.tls_certificate_file.clone() { + None => None, + Some(file) => Some( + TLSConfig::new( + wallet_config + .tls_certificate_pass + .clone() + .expect("tls_certificate_pass must be set"), + file, + ).expect("failed to configure TLS"), + ), + }; match wallet_args.subcommand() { ("listen", Some(listen_args)) => { if let Some(port) = listen_args.value_of("port") { wallet_config.api_listen_port = port.parse().unwrap(); } - controller::foreign_listener(wallet, &wallet_config.api_listen_addr()) + controller::foreign_listener(wallet, &wallet_config.api_listen_addr(), tls_conf) .unwrap_or_else(|e| { panic!( "Error creating wallet listener: {:?} Config: {:?}", @@ -148,26 +161,24 @@ pub fn wallet_command(wallet_args: &ArgMatches, config: GlobalWalletConfig) { }); } ("owner_api", Some(_api_args)) => { - controller::owner_listener(wallet, "127.0.0.1:13420", api_secret).unwrap_or_else( - |e| { + controller::owner_listener(wallet, "127.0.0.1:13420", api_secret, tls_conf) + .unwrap_or_else(|e| { panic!( "Error creating wallet api listener: {:?} Config: {:?}", e, wallet_config ) - }, - ); + }); } ("web", Some(_api_args)) => { // start owner listener and run static file server start_webwallet_server(); - controller::owner_listener(wallet, "127.0.0.1:13420", api_secret).unwrap_or_else( - |e| { + controller::owner_listener(wallet, "127.0.0.1:13420", api_secret, tls_conf) + .unwrap_or_else(|e| { panic!( "Error creating wallet api listener: {:?} Config: {:?}", e, wallet_config ) - }, - ); + }); } _ => {} }; @@ -209,7 +220,7 @@ pub fn wallet_command(wallet_args: &ArgMatches, config: GlobalWalletConfig) { let fluff = send_args.is_present("fluff"); let max_outputs = 500; if method == "http" { - if dest.starts_with("http://") { + if dest.starts_with("http://") || dest.starts_with("https://") { let result = api.issue_send_tx( amount, minimum_confirmations, @@ -258,7 +269,7 @@ pub fn wallet_command(wallet_args: &ArgMatches, config: GlobalWalletConfig) { } else { error!( LOGGER, - "HTTP Destination should start with http://: {}", dest + "HTTP Destination should start with http://: or https://: {}", dest ); panic!(); } diff --git a/wallet/src/libwallet/controller.rs b/wallet/src/libwallet/controller.rs index 080aad4cc..e3013f398 100644 --- a/wallet/src/libwallet/controller.rs +++ b/wallet/src/libwallet/controller.rs @@ -15,7 +15,7 @@ //! Controller for wallet.. instantiates and handles listeners (or single-run //! invocations) as needed. //! Still experimental -use api::{ApiServer, BasicAuthMiddleware, Handler, ResponseFuture, Router}; +use api::{ApiServer, BasicAuthMiddleware, Handler, ResponseFuture, Router, TLSConfig}; use core::core::Transaction; use failure::ResultExt; use futures::future::{err, ok}; @@ -70,6 +70,7 @@ pub fn owner_listener( wallet: Box, addr: &str, api_secret: Option, + tls_config: Option, ) -> Result<(), Error> where T: WalletBackend + Send + Sync + 'static, @@ -95,11 +96,11 @@ where let mut apis = ApiServer::new(); info!(LOGGER, "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) - .context(ErrorKind::GenericError( - "API thread failed to start".to_string(), - ))?; + let api_thread = + apis.start(socket_addr, router, tls_config) + .context(ErrorKind::GenericError( + "API thread failed to start".to_string(), + ))?; api_thread .join() .map_err(|e| ErrorKind::GenericError(format!("API thread panicked :{:?}", e)).into()) @@ -107,7 +108,11 @@ where /// Listener version, providing same API but listening for requests on a /// port and wrapping the calls -pub fn foreign_listener(wallet: Box, addr: &str) -> Result<(), Error> +pub fn foreign_listener( + wallet: Box, + addr: &str, + tls_config: Option, +) -> Result<(), Error> where T: WalletBackend + Send + Sync + 'static, C: WalletClient + 'static, @@ -123,11 +128,11 @@ where let mut apis = ApiServer::new(); info!(LOGGER, "Starting HTTP Foreign API server at {}.", addr); let socket_addr: SocketAddr = addr.parse().expect("unable to parse socket address"); - let api_thread = apis - .start(socket_addr, router) - .context(ErrorKind::GenericError( - "API thread failed to start".to_string(), - ))?; + let api_thread = + apis.start(socket_addr, router, tls_config) + .context(ErrorKind::GenericError( + "API thread failed to start".to_string(), + ))?; api_thread .join() diff --git a/wallet/src/types.rs b/wallet/src/types.rs index 6d55be07e..8bbc97119 100644 --- a/wallet/src/types.rs +++ b/wallet/src/types.rs @@ -48,6 +48,10 @@ pub struct WalletConfig { pub check_node_api_http_addr: String, // The directory in which wallet files are stored pub data_file_dir: String, + /// TLS ceritificate file + pub tls_certificate_file: Option, + /// TLS ceritificate password + pub tls_certificate_pass: Option, } impl Default for WalletConfig { @@ -60,6 +64,8 @@ impl Default for WalletConfig { node_api_secret_path: Some(".api_secret".to_string()), check_node_api_http_addr: "http://127.0.0.1:13413".to_string(), data_file_dir: ".".to_string(), + tls_certificate_file: None, + tls_certificate_pass: None, } } }