diff --git a/Cargo.toml b/Cargo.toml index d35a981..80151bd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ ring = "0.14.0" lazy_static = "1.2.0" tower-service = "0.2" tokio-rustls = "0.10.0-alpha.3" +rustls = { version = "0.15.2", features = ["dangerous_configuration"] } webpki = "0.19.1" webpki-roots = "0.16.0" tower-h2 = { git = "https://github.com/tower-rs/tower-h2" } diff --git a/src/commands.rs b/src/commands.rs index ed087a9..4c090a7 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -118,7 +118,7 @@ impl Command for InfoCommand { fn exec(&self, _args: &[&str], lightclient: &LightClient) -> String { lightclient.do_sync(true); - LightClient::do_info(lightclient.get_server_uri()) + lightclient.do_info() } } diff --git a/src/grpcconnector.rs b/src/grpcconnector.rs index 073cda4..8052ad1 100644 --- a/src/grpcconnector.rs +++ b/src/grpcconnector.rs @@ -24,11 +24,28 @@ use crate::grpc_client::{ChainSpec, BlockId, BlockRange, RawTransaction, TransparentAddressBlockFilter, TxFilter, Empty, LightdInfo}; use crate::grpc_client::client::CompactTxStreamer; +mod danger { + use rustls; + use webpki; + + pub struct NoCertificateVerification {} + + impl rustls::ServerCertVerifier for NoCertificateVerification { + fn verify_server_cert(&self, + _roots: &rustls::RootCertStore, + _presented_certs: &[rustls::Certificate], + _dns_name: webpki::DNSNameRef<'_>, + _ocsp: &[u8]) -> Result { + Ok(rustls::ServerCertVerified::assertion()) + } + } +} /// A Secure (https) grpc destination. struct Dst { - addr: SocketAddr, - host: String, + addr: SocketAddr, + host: String, + no_cert: bool, } impl tower_service::Service<()> for Dst { @@ -43,15 +60,24 @@ impl tower_service::Service<()> for Dst { fn call(&mut self, _: ()) -> Self::Future { let mut config = ClientConfig::new(); + config.alpn_protocols.push(b"h2".to_vec()); config.root_store.add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS); + + if self.no_cert { + config.dangerous() + .set_certificate_verifier(Arc::new(danger::NoCertificateVerification {})); + } let config = Arc::new(config); let tls_connector = TlsConnector::from(config); let addr_string_local = self.host.clone(); - let domain = webpki::DNSNameRef::try_from_ascii_str(&addr_string_local).unwrap(); + let domain = match webpki::DNSNameRef::try_from_ascii_str(&addr_string_local) { + Ok(d) => d, + Err(_) => webpki::DNSNameRef::try_from_ascii_str("localhost").unwrap() + }; let domain_local = domain.to_owned(); let stream = TcpStream::connect(&self.addr).and_then(move |sock| { @@ -92,7 +118,7 @@ impl tower_service::Service<()> for Dst { macro_rules! make_grpc_client { - ($protocol:expr, $host:expr, $port:expr) => {{ + ($protocol:expr, $host:expr, $port:expr, $nocert:expr) => {{ let uri: http::Uri = format!("{}://{}", $protocol, $host).parse().unwrap(); let addr = format!("{}:{}", $host, $port) @@ -102,7 +128,7 @@ macro_rules! make_grpc_client { .unwrap(); let h2_settings = Default::default(); - let mut make_client = tower_h2::client::Connect::new(Dst {addr, host: $host.to_string()}, h2_settings, DefaultExecutor::current()); + let mut make_client = tower_h2::client::Connect::new(Dst {addr, host: $host.to_string(), no_cert: $nocert}, h2_settings, DefaultExecutor::current()); make_client .make_service(()) @@ -126,8 +152,8 @@ macro_rules! make_grpc_client { // GRPC code // ============== -pub fn get_info(uri: http::Uri) -> Result { - let runner = make_grpc_client!(uri.scheme_str().unwrap(), uri.host().unwrap(), uri.port_part().unwrap()) +pub fn get_info(uri: http::Uri, no_cert: bool) -> Result { + let runner = make_grpc_client!(uri.scheme_str().unwrap(), uri.host().unwrap(), uri.port_part().unwrap(), no_cert) .and_then(move |mut client| { client.get_lightd_info(Request::new(Empty{})) .map_err(|e| { @@ -145,9 +171,9 @@ pub fn get_info(uri: http::Uri) -> Result { } -pub fn fetch_blocks(uri: &http::Uri, start_height: u64, end_height: u64, c: F) +pub fn fetch_blocks(uri: &http::Uri, start_height: u64, end_height: u64, no_cert: bool, c: F) where F : Fn(&[u8]) { - let runner = make_grpc_client!(uri.scheme_str().unwrap(), uri.host().unwrap(), uri.port_part().unwrap()) + let runner = make_grpc_client!(uri.scheme_str().unwrap(), uri.host().unwrap(), uri.port_part().unwrap(), no_cert) .and_then(move |mut client| { let bs = BlockId{ height: start_height, hash: vec!()}; let be = BlockId{ height: end_height, hash: vec!()}; @@ -183,9 +209,9 @@ pub fn fetch_blocks(uri: &http::Uri, start_heig } pub fn fetch_transparent_txids(uri: &http::Uri, address: String, - start_height: u64, end_height: u64,c: F) + start_height: u64, end_height: u64, no_cert: bool, c: F) where F : Fn(&[u8], u64) { - let runner = make_grpc_client!(uri.scheme_str().unwrap(), uri.host().unwrap(), uri.port_part().unwrap()) + let runner = make_grpc_client!(uri.scheme_str().unwrap(), uri.host().unwrap(), uri.port_part().unwrap(), no_cert) .and_then(move |mut client| { let start = Some(BlockId{ height: start_height, hash: vec!()}); let end = Some(BlockId{ height: end_height, hash: vec!()}); @@ -218,9 +244,9 @@ pub fn fetch_transparent_txids(uri: &http::Uri, }; } -pub fn fetch_full_tx(uri: &http::Uri, txid: TxId, c: F) +pub fn fetch_full_tx(uri: &http::Uri, txid: TxId, no_cert: bool, c: F) where F : Fn(&[u8]) { - let runner = make_grpc_client!(uri.scheme_str().unwrap(), uri.host().unwrap(), uri.port_part().unwrap()) + let runner = make_grpc_client!(uri.scheme_str().unwrap(), uri.host().unwrap(), uri.port_part().unwrap(), no_cert) .and_then(move |mut client| { let txfilter = TxFilter { block: None, index: 0, hash: txid.0.to_vec() }; client.get_transaction(Request::new(txfilter)) @@ -244,8 +270,8 @@ pub fn fetch_full_tx(uri: &http::Uri, txid: TxI }; } -pub fn broadcast_raw_tx(uri: &http::Uri, tx_bytes: Box<[u8]>) -> Result { - let runner = make_grpc_client!(uri.scheme_str().unwrap(), uri.host().unwrap(), uri.port_part().unwrap()) +pub fn broadcast_raw_tx(uri: &http::Uri, no_cert: bool, tx_bytes: Box<[u8]>) -> Result { + let runner = make_grpc_client!(uri.scheme_str().unwrap(), uri.host().unwrap(), uri.port_part().unwrap(), no_cert) .and_then(move |mut client| { client.send_transaction(Request::new(RawTransaction {data: tx_bytes.to_vec(), height: 0})) .map_err(|e| { @@ -265,9 +291,9 @@ pub fn broadcast_raw_tx(uri: &http::Uri, tx_bytes: Box<[u8]>) -> Result(uri: &http::Uri, mut c : F) +pub fn fetch_latest_block(uri: &http::Uri, no_cert: bool, mut c : F) where F : FnMut(BlockId) { - let runner = make_grpc_client!(uri.scheme_str().unwrap(), uri.host().unwrap(), uri.port_part().unwrap()) + let runner = make_grpc_client!(uri.scheme_str().unwrap(), uri.host().unwrap(), uri.port_part().unwrap(), no_cert) .and_then(|mut client| { client.get_latest_block(Request::new(ChainSpec {})) .map_err(|e| { format!("ERR = {:?}", e) }) diff --git a/src/lightclient.rs b/src/lightclient.rs index 0aa82c2..c432bfe 100644 --- a/src/lightclient.rs +++ b/src/lightclient.rs @@ -33,6 +33,7 @@ pub struct LightClientConfig { pub sapling_activation_height : u64, pub consensus_branch_id : String, pub anchor_offset : u32, + pub no_cert_verification : bool, } impl LightClientConfig { @@ -323,9 +324,8 @@ impl LightClient { self.config.server.clone() } - pub fn do_info(uri: http::Uri) -> String { - let r = get_info(uri); - match r { + pub fn do_info(&self) -> String { + match get_info(self.get_server_uri(), self.config.no_cert_verification) { Ok(i) => format!("{:?}", i)[11..].to_string(), Err(e) => e } @@ -562,7 +562,7 @@ impl LightClient { // This will hold the latest block fetched from the RPC let latest_block_height = Arc::new(AtomicU64::new(0)); let lbh = latest_block_height.clone(); - fetch_latest_block(&self.get_server_uri(), move |block: BlockId| { + fetch_latest_block(&self.get_server_uri(), self.config.no_cert_verification, move |block: BlockId| { lbh.store(block.height, Ordering::SeqCst); }); let latest_block = latest_block_height.load(Ordering::SeqCst); @@ -602,7 +602,7 @@ impl LightClient { let last_invalid_height = Arc::new(AtomicI32::new(0)); let last_invalid_height_inner = last_invalid_height.clone(); - fetch_blocks(&self.get_server_uri(), start_height, end_height, + fetch_blocks(&self.get_server_uri(), start_height, end_height, self.config.no_cert_verification, move |encoded_block: &[u8]| { // Process the block only if there were no previous errors if last_invalid_height_inner.load(Ordering::SeqCst) > 0 { @@ -652,7 +652,7 @@ impl LightClient { // TODO: Use for all t addresses let address = self.wallet.address_from_sk(&self.wallet.tkeys.read().unwrap()[0]); let wallet = self.wallet.clone(); - fetch_transparent_txids(&self.get_server_uri(), address, start_height, end_height, + fetch_transparent_txids(&self.get_server_uri(), address, start_height, end_height, self.config.no_cert_verification, move |tx_bytes: &[u8], height: u64 | { let tx = Transaction::read(tx_bytes).unwrap(); @@ -697,7 +697,7 @@ impl LightClient { info!("Fetching full Tx: {}", txid); responses.push(format!("Fetching full Tx: {}", txid)); - fetch_full_tx(&self.get_server_uri(), txid, move |tx_bytes: &[u8] | { + fetch_full_tx(&self.get_server_uri(), txid, self.config.no_cert_verification, move |tx_bytes: &[u8] | { let tx = Transaction::read(tx_bytes).unwrap(); light_wallet_clone.scan_full_tx(&tx, height); @@ -716,7 +716,7 @@ impl LightClient { ); match rawtx { - Ok(txbytes) => match broadcast_raw_tx(&self.get_server_uri(), txbytes) { + Ok(txbytes) => match broadcast_raw_tx(&self.get_server_uri(), self.config.no_cert_verification, txbytes) { Ok(k) => k, Err(e) => e, }, diff --git a/src/main.rs b/src/main.rs index 0e96dbb..b41de5f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -85,6 +85,10 @@ pub fn main() { .help("Lightwalletd server to connect to.") .takes_value(true) .default_value(lightclient::DEFAULT_SERVER)) + .arg(Arg::with_name("dangerous") + .long("dangerous") + .help("Disable server TLS certificate verification. Use this if you're running a local lightwalletd with a self-signed certificate. WARNING: This is dangerous, don't use it with a server that is not your own.") + .takes_value(false)) .arg(Arg::with_name("nosync") .help("By default, zecwallet-cli will sync the wallet at startup. Pass --nosync to prevent the automatic sync at startup.") .long("nosync") @@ -114,8 +118,10 @@ pub fn main() { return; } + let dangerous = matches.is_present("dangerous"); + // Do a getinfo first, before opening the wallet - let info = match grpcconnector::get_info(server.clone()) { + let info = match grpcconnector::get_info(server.clone(), dangerous) { Ok(ld) => ld, Err(e) => { eprintln!("Error:\n{}\nCouldn't get server info, quitting!", e); @@ -123,6 +129,7 @@ pub fn main() { } }; + // Create a Light Client Config let config = lightclient::LightClientConfig { server : server.clone(), @@ -130,6 +137,7 @@ pub fn main() { sapling_activation_height : info.sapling_activation_height, consensus_branch_id : info.consensus_branch_id, anchor_offset : ANCHOR_OFFSET, + no_cert_verification : dangerous, }; // Configure logging first.