From 1e7fa84a422ce77ee065a4430a9d49cd611a4347 Mon Sep 17 00:00:00 2001 From: moparisthebest Date: Tue, 17 Dec 2019 01:11:19 -0500 Subject: [PATCH] Added TLS --pinnedpubkey support --- README.md | 8 +++- appveyor.yml | 4 +- ci/script.sh | 8 ++++ src/bin/udp-test.rs | 89 +++++++++++++++++++------------------- src/bin/wireguard-proxy.rs | 14 ++++-- src/lib.rs | 4 +- src/notls.rs | 2 +- src/openssl.rs | 31 ++++++++++++- test.sh | 10 +++++ 9 files changed, 115 insertions(+), 55 deletions(-) diff --git a/README.md b/README.md index 1985461..c9ef51b 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,13 @@ usage: wireguard-proxy [options...] -uh, --udp-host UDP host to listen on, point wireguard client here, default: 127.0.0.1:51820 --tls use TLS when connecting to tcp-target - WARNING: currently verifies nothing! + WARNING: authenticates/verifies nothing + without --pinnedpubkey below!! + --pinnedpubkey Public key to verify peer against, + format is any number of base64 encoded + sha256 hashes preceded by "sha256//" + and separated by ";". Identical to curl's + --pinnedpubkey and CURLOPT_PINNEDPUBLICKEY --tls-hostname send this in SNI instead of host from --tcp-target, useful for avoiding DNS lookup on connect diff --git a/appveyor.yml b/appveyor.yml index ed229f9..c36306e 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -54,8 +54,10 @@ test_script: cargo run --target %TARGET% --release --features %CARGO_FEATURES% --bin udp-test && cargo run --target %TARGET% --release --features %CARGO_FEATURES% --bin udp-test -- -is ) + # todo: should run --pinnedpubkey test here where we expect failure, but unsure how to do that with windows, help? - if [%CARGO_FEATURES%]==[openssl_vendored] ( - cargo run --target %TARGET% --release --features %CARGO_FEATURES% --bin udp-test -- -is --tls-key ci/cert.key --tls-cert ci/cert.pem + cargo run --target %TARGET% --release --features %CARGO_FEATURES% --bin udp-test -- -is --tls-key ci/cert.key --tls-cert ci/cert.pem && + cargo run --target %TARGET% --release --features %CARGO_FEATURES% --bin udp-test -- -is --tls-key ci/cert.key --tls-cert ci/cert.pem --pinnedpubkey sha256//BEyQeSjwwUBLXXNuCILHRWyV1gLmY31CdMHNA4VH4dE= ) before_deploy: diff --git a/ci/script.sh b/ci/script.sh index 98437a4..95e1e1f 100644 --- a/ci/script.sh +++ b/ci/script.sh @@ -19,6 +19,14 @@ main() { if [ $CARGO_FEATURES != "default" ]; then # run TLS tests then too cross run --target $TARGET --release --features $CARGO_FEATURES --bin udp-test -- -is --tls-key ci/cert.key --tls-cert ci/cert.pem + + # now pubkey tests + + # one that should fail (wrong pinnedpubkey lowercase e at end instead of uppercase E) + cross run --target $TARGET --release --features $CARGO_FEATURES --bin udp-test -- -is --tls-key ci/cert.key --tls-cert ci/cert.pem --pinnedpubkey sha256//BEyQeSjwwUBLXXNuCILHRWyV1gLmY31CdMHNA4VH4de= && exit 1 || true + + # and one that should pass + cross run --target $TARGET --release --features $CARGO_FEATURES --bin udp-test -- -is --tls-key ci/cert.key --tls-cert ci/cert.pem --pinnedpubkey sha256//BEyQeSjwwUBLXXNuCILHRWyV1gLmY31CdMHNA4VH4dE= fi } diff --git a/src/bin/udp-test.rs b/src/bin/udp-test.rs index 3e503f7..f48dd7d 100644 --- a/src/bin/udp-test.rs +++ b/src/bin/udp-test.rs @@ -75,6 +75,7 @@ fn main() { let tls_key = args.get_option(&["-tk", "--tls-key"]); let tls_cert = args.get_option(&["-tc", "--tls-cert"]); + let pinnedpubkey = args.get_option(&["--pinnedpubkey"]); let tls = if tls_key.is_some() && tls_cert.is_some() { true @@ -98,10 +99,15 @@ fn main() { before terminating, default: {} TLS option for self tests only, otherwise self tests are plaintext only: - -tk, --tls-key TLS key to listen with, - requires --tls-cert also - -tc, --tls-cert TLS cert to listen with, - requires --tls-key also + -tk, --tls-key TLS key to listen with, + requires --tls-cert also + -tc, --tls-cert TLS cert to listen with, + requires --tls-key also + --pinnedpubkey Public key to verify peer against, + format is any number of base64 encoded + sha256 hashes preceded by "sha256//" + and separated by ";". Identical to curl's + --pinnedpubkey and CURLOPT_PINNEDPUBLICKEY "#, default_udp_host_target, default_udp_host_target, default_socket_timeout); return; } else if args.flag("-s") || args.flag("--self-test") { @@ -113,50 +119,37 @@ fn main() { let udp_test = args.get_str_idx(0, "udp-test"); let proxy = udp_test.clone().replace("udp-test", "wireguard-proxy"); - let mut proxyd = if tls { + let mut proxyd_args = vec!["-th", tcp_host, "-ut", host]; + + if tls { let tls_key = tls_key.unwrap(); let tls_cert = tls_cert.unwrap(); - println!("executing: {} -th '{}' -ut '{}' -tk '{}' -tc '{}'", proxy, tcp_host, host, tls_key, tls_cert); - Command::new(proxy.clone()) - .arg("-th") - .arg(tcp_host) - .arg("-ut") - .arg(host) - .arg("-tk") - .arg(tls_key) - .arg("-tc") - .arg(tls_cert) + proxyd_args.extend(["-tk", tls_key, "-tc", tls_cert].iter().cloned()); + } + + println!("executing: {} {}", proxy, proxyd_args.join(" ")); + let mut proxyd = Command::new(proxy.clone()) + .args(&proxyd_args) .spawn() - .expect("wireguard-proxy TLS server failed to launch") - } else { - println!("executing: {} -th '{}' -ut '{}'", proxy, tcp_host, host); - Command::new(proxy.clone()) - .arg("-th") - .arg(tcp_host) - .arg("-ut") - .arg(host) - .spawn() - .expect("wireguard-proxy server failed to launch") - }; + .expect("wireguard-proxy server failed to launch"); println!("waiting: {:?} for wireguard-proxy server to come up.....", sleep); thread::sleep(sleep); - let mut proxy = if tls { - println!("executing: {} -tt {} --tls", proxy, tcp_host); - Command::new(proxy) - .arg("-tt") - .arg(tcp_host) - .arg("--tls") + let mut proxy_args = vec!["-tt", tcp_host]; + + if tls { + proxy_args.push("--tls"); + if pinnedpubkey.is_some() { + proxy_args.push("--pinnedpubkey"); + proxy_args.push(pinnedpubkey.unwrap()); + } + } + + println!("executing: {} {}", proxy, proxy_args.join(" ")); + let mut proxy = Command::new(proxy) + .args(proxy_args) .spawn() - .expect("wireguard-proxy TLS client failed to launch") - } else { - println!("executing: {} -tt {}", proxy, tcp_host); - Command::new(proxy) - .arg("-tt") - .arg(tcp_host) - .spawn() - .expect("wireguard-proxy client failed to launch") - }; + .expect("wireguard-proxy TLS client failed to launch"); println!("waiting: {:?} for wireguard-proxy client to come up.....", sleep); thread::sleep(sleep); @@ -226,9 +219,17 @@ fn main() { ); if tls { - println!("executing: wireguard-proxy -tt {} --tls", tcp_host); - let hostname = tcp_host.split(":").next().expect("cannot extract hostname from tcp_host"); - thread::spawn(move || proxy_client.start_tls(hostname).expect("error running proxy_client")); + let hostname = tcp_host.split(":").next(); + match pinnedpubkey { + Some(pinnedpubkey) => + println!("executing: wireguard-proxy -tt {} --tls --pinnedpubkey {}", tcp_host, pinnedpubkey), + None => + println!("executing: wireguard-proxy -tt {} --tls", tcp_host), + } + // this is a little funky, is this the only way to do it? + let pinnedpubkey = pinnedpubkey.map(&str::to_owned); + // can use pinnedpubkey.as_deref() below when it's stabilized + thread::spawn(move || proxy_client.start_tls(hostname, pinnedpubkey.as_ref().map(String::as_str)).expect("error running proxy_client")); } else { println!("executing: wireguard-proxy -tt {}", tcp_host); thread::spawn(move || proxy_client.start().expect("error running proxy_client")); diff --git a/src/bin/wireguard-proxy.rs b/src/bin/wireguard-proxy.rs index 813e7cc..08e8e0d 100644 --- a/src/bin/wireguard-proxy.rs +++ b/src/bin/wireguard-proxy.rs @@ -24,7 +24,13 @@ fn main() { -uh, --udp-host UDP host to listen on, point wireguard client here, default: {} --tls use TLS when connecting to tcp-target - WARNING: currently verifies nothing! + WARNING: authenticates/verifies nothing + without --pinnedpubkey below!! + --pinnedpubkey Public key to verify peer against, + format is any number of base64 encoded + sha256 hashes preceded by "sha256//" + and separated by ";". Identical to curl's + --pinnedpubkey and CURLOPT_PINNEDPUBLICKEY --tls-hostname send this in SNI instead of host from --tcp-target, useful for avoiding DNS lookup on connect @@ -79,9 +85,9 @@ fn client(tcp_target: &str, socket_timeout: u64, args: Args) { ); if tls { - let hostname = args.get_option(&["--tls-hostname"]).or_else(|| tcp_target.split(":").next()) - .expect("--tls-hostname not set and cannot extract hostname from --tcp-target"); - proxy_client.start_tls(hostname).expect("error running tls proxy_client"); + let hostname = args.get_option(&["--tls-hostname"]).or_else(|| tcp_target.split(":").next()); + let pinnedpubkey = args.get_option(&["--pinnedpubkey"]); + proxy_client.start_tls(hostname, pinnedpubkey).expect("error running tls proxy_client"); } else { proxy_client.start().expect("error running proxy_client"); } diff --git a/src/lib.rs b/src/lib.rs index 362f911..30e3bbf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -188,10 +188,10 @@ impl ProxyClient { TcpUdpPipe::new(tcp_stream, udp_socket).shuffle_after_first_udp() } - pub fn start_tls(&self, hostname: &str) -> Result { + pub fn start_tls(&self, hostname: Option<&str>, pinnedpubkey: Option<&str>) -> Result { let tcp_stream = self.tcp_connect()?; - let tcp_stream = TlsStream::client(hostname, tcp_stream)?; + let tcp_stream = TlsStream::client(hostname, pinnedpubkey, tcp_stream)?; let udp_socket = self.udp_connect()?; diff --git a/src/notls.rs b/src/notls.rs index 76bec44..83aaa51 100644 --- a/src/notls.rs +++ b/src/notls.rs @@ -10,7 +10,7 @@ fn err() -> Error { pub struct TlsStream; impl TlsStream { - pub fn client(_host_name: &str, _tcp_stream: TcpStream) -> Result { + pub fn client(_hostname: Option<&str>, _pinnedpubkey: Option<&str>, _tcp_stream: TcpStream) -> Result { Err(err()) } } diff --git a/src/openssl.rs b/src/openssl.rs index 69a9d25..c8de116 100644 --- a/src/openssl.rs +++ b/src/openssl.rs @@ -25,11 +25,38 @@ impl TlsStream { sess: Arc::new(UnsafeCell::new(stream)) } } - pub fn client(host_name: &str, tcp_stream: TcpStream) -> Result { + pub fn client(hostname: Option<&str>, pinnedpubkey: Option<&str>, tcp_stream: TcpStream) -> Result { let mut connector = SslConnector::builder(SslMethod::tls())?.build().configure()?; + connector.set_use_server_name_indication(hostname.is_some()); connector.set_verify_hostname(false); connector.set_verify(SslVerifyMode::NONE); - let tcp_stream = connector.connect(host_name, tcp_stream)?; + if pinnedpubkey.is_some() { + let pinnedpubkey = pinnedpubkey.unwrap().to_owned(); + connector.set_verify_callback(SslVerifyMode::PEER, move|_preverify_ok, x509_store_ctx| { + //println!("preverify_ok: {}", preverify_ok); + let cert = x509_store_ctx.current_cert().expect("could not get TLS cert"); + let pubkey = cert.public_key().expect("could not get public key from TLS cert"); + let pubkey = pubkey.public_key_to_der().expect("could not get TLS public key bytes"); + //println!("pubkey.len(): {}", pubkey.len()); + + let mut sha256 = openssl::sha::Sha256::new(); + sha256.update(&pubkey); + let pubkey = sha256.finish(); + + let pubkey = ["sha256//", &openssl::base64::encode_block(&pubkey)].join(""); + println!("pubkey from cert: {}", pubkey); + + for key in pinnedpubkey.split(";") { + if key == pubkey { + println!("SUCCESS: pubkey match found!",); + return true; + } + } + println!("ERROR: pubkey match not found!"); + false + }); + } + let tcp_stream = connector.connect(hostname.unwrap_or(""), tcp_stream)?; Ok(TlsStream::new(tcp_stream)) } } diff --git a/test.sh b/test.sh index 76b48ff..0a6d5c6 100755 --- a/test.sh +++ b/test.sh @@ -60,4 +60,14 @@ run_tests || exit 1 # then TLS tests run_tests --tls --tls-key ci/cert.key --tls-cert ci/cert.pem || exit 1 +# now pubkey tests + +# these should fail +udp-test -s --tls-key ci/cert.key --tls-cert ci/cert.pem --pinnedpubkey sha256//BEyQeSjwwUBLXXNuCILHRWyV1gLmY31CdMHNA4VH4de= && exit 1 +udp-test -is --tls-key ci/cert.key --tls-cert ci/cert.pem --pinnedpubkey sha256//BEyQeSjwwUBLXXNuCILHRWyV1gLmY31CdMHNA4VH4de= && exit 1 + +# these should pass +udp-test -s --tls-key ci/cert.key --tls-cert ci/cert.pem --pinnedpubkey sha256//BEyQeSjwwUBLXXNuCILHRWyV1gLmY31CdMHNA4VH4dE= || exit 1 +udp-test -is --tls-key ci/cert.key --tls-cert ci/cert.pem --pinnedpubkey sha256//BEyQeSjwwUBLXXNuCILHRWyV1gLmY31CdMHNA4VH4dE= || exit 1 + exit 0