From 4d89c33882f6e0bb430efd45fcd4bcfadea34214 Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Mon, 23 Nov 2020 00:14:09 -0800 Subject: [PATCH] Cleaning up Authentication App --- Cargo.toml | 5 +- deps/rust-socks/src/lib.rs | 14 +- deps/rust-socks/src/v4.rs | 50 ++--- deps/rust-socks/src/v5.rs | 162 +++++---------- deps/rust-socks/src/writev.rs | 24 +-- rustfmt.toml | 66 ++++++ src/acns/mod.rs | 2 +- src/acns/tor/authentication.rs | 14 +- src/acns/tor/mod.rs | 93 +++++---- src/applications/authentication_app.rs | 265 +++++++++++++++++-------- src/applications/mod.rs | 1 - src/applications/transcript_app.rs | 44 ---- src/connections/mod.rs | 122 +++++++++++- src/connections/service.rs | 103 +++++----- src/connections/utils.rs | 65 +----- src/lib.rs | 3 +- src/primitives/identity.rs | 28 +-- src/primitives/mod.rs | 2 +- src/primitives/transcript.rs | 31 +-- tests/simple_setup.rs | 54 ++--- 20 files changed, 620 insertions(+), 528 deletions(-) create mode 100644 rustfmt.toml delete mode 100644 src/applications/transcript_app.rs diff --git a/Cargo.toml b/Cargo.toml index fc342c5..2146aaf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ sha3 = "0.9.1" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0.59" byteorder = "1.3.4" -socks = {path="./deps/rust-socks"} +socks = "0.3.3" integer-encoding = "2.1.1" -secretbox = "0.1.2" \ No newline at end of file +secretbox = "0.1.2" +subtle = "2.3.0" \ No newline at end of file diff --git a/deps/rust-socks/src/lib.rs b/deps/rust-socks/src/lib.rs index 89c9417..6c07649 100644 --- a/deps/rust-socks/src/lib.rs +++ b/deps/rust-socks/src/lib.rs @@ -1,5 +1,5 @@ //! SOCKS proxy clients -#![doc(html_root_url="https://docs.rs/socks/0.3.0")] +#![doc(html_root_url = "https://docs.rs/socks/0.3.0")] #![warn(missing_docs)] extern crate byteorder; @@ -15,8 +15,8 @@ use std::io; use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs}; use std::vec; -pub use v4::{Socks4Stream, Socks4Listener}; -pub use v5::{Socks5Stream, Socks5Listener, Socks5Datagram}; +pub use v4::{Socks4Listener, Socks4Stream}; +pub use v5::{Socks5Datagram, Socks5Listener, Socks5Stream}; mod v4; mod v5; @@ -140,16 +140,12 @@ impl<'a> ToTargetAddr for &'a str { let mut parts_iter = self.rsplitn(2, ':'); let port_str = match parts_iter.next() { Some(s) => s, - None => { - return Err(io::Error::new(io::ErrorKind::InvalidInput, "invalid socket address")) - } + None => return Err(io::Error::new(io::ErrorKind::InvalidInput, "invalid socket address")), }; let host = match parts_iter.next() { Some(s) => s, - None => { - return Err(io::Error::new(io::ErrorKind::InvalidInput, "invalid socket address")) - } + None => return Err(io::Error::new(io::ErrorKind::InvalidInput, "invalid socket address")), }; let port: u16 = match port_str.parse() { diff --git a/deps/rust-socks/src/v4.rs b/deps/rust-socks/src/v4.rs index 31b6529..0378367 100644 --- a/deps/rust-socks/src/v4.rs +++ b/deps/rust-socks/src/v4.rs @@ -1,8 +1,8 @@ -use byteorder::{ReadBytesExt, WriteBytesExt, BigEndian}; +use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use std::io::{self, Read, Write}; -use std::net::{SocketAddr, ToSocketAddrs, SocketAddrV4, SocketAddrV6, TcpStream, Ipv4Addr}; +use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4, SocketAddrV6, TcpStream, ToSocketAddrs}; -use {ToTargetAddr, TargetAddr}; +use {TargetAddr, ToTargetAddr}; fn read_response(socket: &mut TcpStream) -> io::Result { let mut response = [0u8; 8]; @@ -17,14 +17,18 @@ fn read_response(socket: &mut TcpStream) -> io::Result { 90 => {} 91 => return Err(io::Error::new(io::ErrorKind::Other, "request rejected or failed")), 92 => { - return Err(io::Error::new(io::ErrorKind::PermissionDenied, - "request rejected because SOCKS server cannot connect to \ - idnetd on the client")) + return Err(io::Error::new( + io::ErrorKind::PermissionDenied, + "request rejected because SOCKS server cannot connect to \ + idnetd on the client", + )) } 93 => { - return Err(io::Error::new(io::ErrorKind::PermissionDenied, - "request rejected because the client program and identd \ - report different user-ids")) + return Err(io::Error::new( + io::ErrorKind::PermissionDenied, + "request rejected because the client program and identd \ + report different user-ids", + )) } _ => return Err(io::Error::new(io::ErrorKind::InvalidData, "invalid response code")), } @@ -52,15 +56,17 @@ impl Socks4Stream { /// server does not support SOCKS4A, consider performing the DNS lookup /// locally and passing a `TargetAddr::Ip`. pub fn connect(proxy: T, target: U, userid: &str) -> io::Result - where T: ToSocketAddrs, - U: ToTargetAddr + where + T: ToSocketAddrs, + U: ToTargetAddr, { Self::connect_raw(1, proxy, target, userid) } fn connect_raw(command: u8, proxy: T, target: U, userid: &str) -> io::Result - where T: ToSocketAddrs, - U: ToTargetAddr + where + T: ToSocketAddrs, + U: ToTargetAddr, { let mut socket = TcpStream::connect(proxy)?; @@ -74,8 +80,7 @@ impl Socks4Stream { let addr = match addr { SocketAddr::V4(addr) => addr, SocketAddr::V6(_) => { - return Err(io::Error::new(io::ErrorKind::InvalidInput, - "SOCKS4 does not support IPv6")); + return Err(io::Error::new(io::ErrorKind::InvalidInput, "SOCKS4 does not support IPv6")); } }; let _ = packet.write_u16::(addr.port()); @@ -166,8 +171,9 @@ impl Socks4Listener { /// The proxy will filter incoming connections based on the value of /// `target`. pub fn bind(proxy: T, target: U, userid: &str) -> io::Result - where T: ToSocketAddrs, - U: ToTargetAddr + where + T: ToSocketAddrs, + U: ToTargetAddr, { Socks4Stream::connect_raw(2, proxy, target, userid).map(Socks4Listener) } @@ -202,7 +208,7 @@ impl Socks4Listener { #[cfg(test)] mod test { use std::io::{Read, Write}; - use std::net::{SocketAddr, SocketAddrV4, ToSocketAddrs, TcpStream}; + use std::net::{SocketAddr, SocketAddrV4, TcpStream, ToSocketAddrs}; use super::*; @@ -210,11 +216,9 @@ mod test { "google.com:80" .to_socket_addrs() .unwrap() - .filter_map(|a| { - match a { - SocketAddr::V4(a) => Some(a), - SocketAddr::V6(_) => None, - } + .filter_map(|a| match a { + SocketAddr::V4(a) => Some(a), + SocketAddr::V6(_) => None, }) .next() .unwrap() diff --git a/deps/rust-socks/src/v5.rs b/deps/rust-socks/src/v5.rs index bd9b9f8..44633bf 100644 --- a/deps/rust-socks/src/v5.rs +++ b/deps/rust-socks/src/v5.rs @@ -1,12 +1,11 @@ -use byteorder::{ReadBytesExt, WriteBytesExt, BigEndian}; +use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use std::cmp; use std::io::{self, Read, Write}; -use std::net::{SocketAddr, ToSocketAddrs, SocketAddrV4, SocketAddrV6, TcpStream, Ipv4Addr, - Ipv6Addr, UdpSocket}; +use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, TcpStream, ToSocketAddrs, UdpSocket}; use std::ptr; -use {ToTargetAddr, TargetAddr}; use writev::WritevExt; +use {TargetAddr, ToTargetAddr}; const MAX_ADDR_LEN: usize = 260; @@ -21,8 +20,7 @@ fn read_addr(socket: &mut R) -> io::Result { let len = socket.read_u8()?; let mut domain = vec![0; len as usize]; socket.read_exact(&mut domain)?; - let domain = String::from_utf8(domain) - .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + let domain = String::from_utf8(domain).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; let port = socket.read_u16::()?; Ok(TargetAddr::Domain(domain, port)) } @@ -38,7 +36,6 @@ fn read_addr(socket: &mut R) -> io::Result { } fn read_response(socket: &mut TcpStream) -> io::Result { -xs if socket.read_u8()? != 5 { return Err(io::Error::new(io::ErrorKind::InvalidData, "invalid response version")); } @@ -94,14 +91,14 @@ fn write_addr(mut packet: &mut [u8], target: &TargetAddr) -> io::Result { #[derive(Debug)] enum Authentication<'a> { Password { username: &'a str, password: &'a str }, - None + None, } impl<'a> Authentication<'a> { fn id(&self) -> u8 { match *self { Authentication::Password { .. } => 2, - Authentication::None => 0 + Authentication::None => 0, } } @@ -124,8 +121,9 @@ pub struct Socks5Stream { impl Socks5Stream { /// Connects to a target server through a SOCKS5 proxy. pub fn connect(proxy: T, target: U) -> io::Result - where T: ToSocketAddrs, - U: ToTargetAddr + where + T: ToSocketAddrs, + U: ToTargetAddr, { Self::connect_raw(1, proxy, target, &Authentication::None) } @@ -133,16 +131,18 @@ impl Socks5Stream { /// Connects to a target server through a SOCKS5 proxy using given /// username and password. pub fn connect_with_password(proxy: T, target: U, username: &str, password: &str) -> io::Result - where T: ToSocketAddrs, - U: ToTargetAddr + where + T: ToSocketAddrs, + U: ToTargetAddr, { let auth = Authentication::Password { username, password }; Self::connect_raw(1, proxy, target, &auth) } fn connect_raw(command: u8, proxy: T, target: U, auth: &Authentication) -> io::Result - where T: ToSocketAddrs, - U: ToTargetAddr + where + T: ToSocketAddrs, + U: ToTargetAddr, { let mut socket = TcpStream::connect(proxy)?; @@ -150,10 +150,10 @@ impl Socks5Stream { let packet_len = if auth.is_no_auth() { 3 } else { 4 }; let packet = [ - 5, // protocol version + 5, // protocol version if auth.is_no_auth() { 1 } else { 2 }, // method count - auth.id(), // method - 0, // no auth (always offered) + auth.id(), // method + 0, // no auth (always offered) ]; socket.write_all(&packet[..packet_len])?; @@ -167,18 +167,16 @@ impl Socks5Stream { } if selected_method == 0xff { - return Err(io::Error::new(io::ErrorKind::Other, "no acceptable auth methods")) + return Err(io::Error::new(io::ErrorKind::Other, "no acceptable auth methods")); } if selected_method != auth.id() && selected_method != Authentication::None.id() { - return Err(io::Error::new(io::ErrorKind::Other, "unknown auth method")) + return Err(io::Error::new(io::ErrorKind::Other, "unknown auth method")); } match *auth { - Authentication::Password { username, password } if selected_method == auth.id() => { - Self::password_authentication(&mut socket, username, password)? - }, - _ => () + Authentication::Password { username, password } if selected_method == auth.id() => Self::password_authentication(&mut socket, username, password)?, + _ => (), } let mut packet = [0; MAX_ADDR_LEN + 3]; @@ -198,10 +196,10 @@ impl Socks5Stream { fn password_authentication(socket: &mut TcpStream, username: &str, password: &str) -> io::Result<()> { if username.len() < 1 || username.len() > 255 { - return Err(io::Error::new(io::ErrorKind::InvalidInput, "invalid username")) + return Err(io::Error::new(io::ErrorKind::InvalidInput, "invalid username")); }; if password.len() < 1 || password.len() > 255 { - return Err(io::Error::new(io::ErrorKind::InvalidInput, "invalid password")) + return Err(io::Error::new(io::ErrorKind::InvalidInput, "invalid password")); } let mut packet = [0; 515]; @@ -289,8 +287,9 @@ impl Socks5Listener { /// The proxy will filter incoming connections based on the value of /// `target`. pub fn bind(proxy: T, target: U) -> io::Result - where T: ToSocketAddrs, - U: ToTargetAddr + where + T: ToSocketAddrs, + U: ToTargetAddr, { Socks5Stream::connect_raw(2, proxy, target, &Authentication::None).map(Socks5Listener) } @@ -300,8 +299,9 @@ impl Socks5Listener { /// The proxy will filter incoming connections based on the value of /// `target`. pub fn bind_with_password(proxy: T, target: U, username: &str, password: &str) -> io::Result - where T: ToSocketAddrs, - U: ToTargetAddr + where + T: ToSocketAddrs, + U: ToTargetAddr, { let auth = Authentication::Password { username, password }; Socks5Stream::connect_raw(2, proxy, target, &auth).map(Socks5Listener) @@ -337,8 +337,9 @@ impl Socks5Datagram { /// Creates a UDP socket bound to the specified address which will have its /// traffic routed through the specified proxy. pub fn bind(proxy: T, addr: U) -> io::Result - where T: ToSocketAddrs, - U: ToSocketAddrs + where + T: ToSocketAddrs, + U: ToSocketAddrs, { Self::bind_internal(proxy, addr, &Authentication::None) } @@ -346,16 +347,18 @@ impl Socks5Datagram { /// traffic routed through the specified proxy. The given username and password /// is used to authenticate to the SOCKS proxy. pub fn bind_with_password(proxy: T, addr: U, username: &str, password: &str) -> io::Result - where T: ToSocketAddrs, - U: ToSocketAddrs + where + T: ToSocketAddrs, + U: ToSocketAddrs, { let auth = Authentication::Password { username, password }; Self::bind_internal(proxy, addr, &auth) } fn bind_internal(proxy: T, addr: U, auth: &Authentication) -> io::Result - where T: ToSocketAddrs, - U: ToSocketAddrs + where + T: ToSocketAddrs, + U: ToSocketAddrs, { // we don't know what our IP is from the perspective of the proxy, so // don't try to pass `addr` in here. @@ -365,10 +368,7 @@ impl Socks5Datagram { let socket = UdpSocket::bind(addr)?; socket.connect(&stream.proxy_addr)?; - Ok(Socks5Datagram { - socket: socket, - stream: stream, - }) + Ok(Socks5Datagram { socket: socket, stream: stream }) } /// Like `UdpSocket::send_to`. @@ -379,7 +379,8 @@ impl Socks5Datagram { /// header will be 10 bytes for an IPv4 address, 22 bytes for an IPv6 /// address, and 7 bytes plus the length of the domain for a domain address. pub fn send_to(&self, buf: &[u8], addr: A) -> io::Result - where A: ToTargetAddr + where + A: ToTargetAddr, { let addr = addr.to_target_addr()?; @@ -438,7 +439,7 @@ impl Socks5Datagram { mod test { use std::error::Error; use std::io::{Read, Write}; - use std::net::{ToSocketAddrs, TcpStream, UdpSocket}; + use std::net::{TcpStream, ToSocketAddrs, UdpSocket}; use super::*; @@ -455,12 +456,7 @@ mod test { #[test] fn google_with_password() { let addr = "google.com:80".to_socket_addrs().unwrap().next().unwrap(); - let socket = Socks5Stream::connect_with_password( - SOCKS_PROXY_PASSWD_ONLY, - addr, - "testuser", - "testpass" - ).unwrap(); + let socket = Socks5Stream::connect_with_password(SOCKS_PROXY_PASSWD_ONLY, addr, "testuser", "testpass").unwrap(); google(socket); } @@ -497,24 +493,14 @@ mod test { #[test] fn bind_with_password_supported_but_no_auth_used() { let addr = find_address(); - let listener = Socks5Listener::bind_with_password( - SOCKS_PROXY_NO_AUTH_ONLY, - addr, - "unused_and_invalid_username", - "unused_and_invalid_password" - ).unwrap(); + let listener = Socks5Listener::bind_with_password(SOCKS_PROXY_NO_AUTH_ONLY, addr, "unused_and_invalid_username", "unused_and_invalid_password").unwrap(); bind(listener); } #[test] fn bind_with_password() { let addr = find_address(); - let listener = Socks5Listener::bind_with_password( - "127.0.0.1:1081", - addr, - "testuser", - "testpass" - ).unwrap(); + let listener = Socks5Listener::bind_with_password("127.0.0.1:1081", addr, "testuser", "testpass").unwrap(); bind(listener); } @@ -543,12 +529,7 @@ mod test { #[test] fn associate_with_password() { - let socks = Socks5Datagram::bind_with_password( - SOCKS_PROXY_PASSWD_ONLY, - "127.0.0.1:15414", - "testuser", - "testpass" - ).unwrap(); + let socks = Socks5Datagram::bind_with_password(SOCKS_PROXY_PASSWD_ONLY, "127.0.0.1:15414", "testuser", "testpass").unwrap(); associate(socks, "127.0.0.1:15415"); } @@ -596,12 +577,7 @@ mod test { #[test] fn incorrect_password() { let addr = "google.com:80".to_socket_addrs().unwrap().next().unwrap(); - let err = Socks5Stream::connect_with_password( - SOCKS_PROXY_PASSWD_ONLY, - addr, - "testuser", - "invalid" - ).unwrap_err(); + let err = Socks5Stream::connect_with_password(SOCKS_PROXY_PASSWD_ONLY, addr, "testuser", "invalid").unwrap_err(); assert_eq!(err.kind(), io::ErrorKind::PermissionDenied); assert_eq!(err.description(), "password authentication failed"); @@ -620,57 +596,27 @@ mod test { fn username_and_password_length() { let addr = "google.com:80".to_socket_addrs().unwrap().next().unwrap(); - let err = Socks5Stream::connect_with_password( - SOCKS_PROXY_PASSWD_ONLY, - addr, - &string_of_size(1), - &string_of_size(1) - ).unwrap_err(); + let err = Socks5Stream::connect_with_password(SOCKS_PROXY_PASSWD_ONLY, addr, &string_of_size(1), &string_of_size(1)).unwrap_err(); assert_eq!(err.kind(), io::ErrorKind::PermissionDenied); assert_eq!(err.description(), "password authentication failed"); - let err = Socks5Stream::connect_with_password( - SOCKS_PROXY_PASSWD_ONLY, - addr, - &string_of_size(255), - &string_of_size(255) - ).unwrap_err(); + let err = Socks5Stream::connect_with_password(SOCKS_PROXY_PASSWD_ONLY, addr, &string_of_size(255), &string_of_size(255)).unwrap_err(); assert_eq!(err.kind(), io::ErrorKind::PermissionDenied); assert_eq!(err.description(), "password authentication failed"); - let err = Socks5Stream::connect_with_password( - SOCKS_PROXY_PASSWD_ONLY, - addr, - &string_of_size(0), - &string_of_size(255) - ).unwrap_err(); + let err = Socks5Stream::connect_with_password(SOCKS_PROXY_PASSWD_ONLY, addr, &string_of_size(0), &string_of_size(255)).unwrap_err(); assert_eq!(err.kind(), io::ErrorKind::InvalidInput); assert_eq!(err.description(), "invalid username"); - let err = Socks5Stream::connect_with_password( - SOCKS_PROXY_PASSWD_ONLY, - addr, - &string_of_size(256), - &string_of_size(255) - ).unwrap_err(); + let err = Socks5Stream::connect_with_password(SOCKS_PROXY_PASSWD_ONLY, addr, &string_of_size(256), &string_of_size(255)).unwrap_err(); assert_eq!(err.kind(), io::ErrorKind::InvalidInput); assert_eq!(err.description(), "invalid username"); - let err = Socks5Stream::connect_with_password( - SOCKS_PROXY_PASSWD_ONLY, - addr, - &string_of_size(255), - &string_of_size(0) - ).unwrap_err(); + let err = Socks5Stream::connect_with_password(SOCKS_PROXY_PASSWD_ONLY, addr, &string_of_size(255), &string_of_size(0)).unwrap_err(); assert_eq!(err.kind(), io::ErrorKind::InvalidInput); assert_eq!(err.description(), "invalid password"); - let err = Socks5Stream::connect_with_password( - SOCKS_PROXY_PASSWD_ONLY, - addr, - &string_of_size(255), - &string_of_size(256) - ).unwrap_err(); + let err = Socks5Stream::connect_with_password(SOCKS_PROXY_PASSWD_ONLY, addr, &string_of_size(255), &string_of_size(256)).unwrap_err(); assert_eq!(err.kind(), io::ErrorKind::InvalidInput); assert_eq!(err.description(), "invalid password"); } diff --git a/deps/rust-socks/src/writev.rs b/deps/rust-socks/src/writev.rs index 716e7c9..95ab98e 100644 --- a/deps/rust-socks/src/writev.rs +++ b/deps/rust-socks/src/writev.rs @@ -60,10 +60,10 @@ mod imp { #[cfg(windows)] mod imp { - use winapi; - use ws2_32; use std::os::windows::io::AsRawSocket; use std::ptr; + use winapi; + use ws2_32; use super::*; @@ -81,15 +81,7 @@ mod imp { }, ]; let mut sent = 0; - let r = ws2_32::WSASend( - self.as_raw_socket(), - wsabufs.as_mut_ptr(), - bufs.len() as winapi::DWORD, - &mut sent, - 0, - ptr::null_mut(), - None, - ); + let r = ws2_32::WSASend(self.as_raw_socket(), wsabufs.as_mut_ptr(), bufs.len() as winapi::DWORD, &mut sent, 0, ptr::null_mut(), None); if r == 0 { Ok(sent as usize) } else { @@ -112,15 +104,7 @@ mod imp { ]; let mut recved = 0; let mut flags = 0; - let r = ws2_32::WSARecv( - self.as_raw_socket(), - wsabufs.as_mut_ptr(), - bufs.len() as winapi::DWORD, - &mut recved, - &mut flags, - ptr::null_mut(), - None, - ); + let r = ws2_32::WSARecv(self.as_raw_socket(), wsabufs.as_mut_ptr(), bufs.len() as winapi::DWORD, &mut recved, &mut flags, ptr::null_mut(), None); if r == 0 { Ok(recved as usize) } else { diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..b355306 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,66 @@ +max_width = 200 +hard_tabs = false +tab_spaces = 4 +newline_style = "Auto" +use_small_heuristics = "Default" +indent_style = "Block" +wrap_comments = false +format_code_in_doc_comments = false +comment_width = 80 +normalize_comments = false +normalize_doc_attributes = false +license_template_path = "" +format_strings = false +format_macro_matchers = false +format_macro_bodies = true +empty_item_single_line = true +struct_lit_single_line = true +fn_single_line = false +where_single_line = false +imports_indent = "Block" +imports_layout = "Mixed" +merge_imports = false +reorder_imports = true +reorder_modules = true +reorder_impl_items = false +type_punctuation_density = "Wide" +space_before_colon = false +space_after_colon = true +spaces_around_ranges = false +binop_separator = "Front" +remove_nested_parens = true +combine_control_expr = true +overflow_delimited_expr = false +struct_field_align_threshold = 0 +enum_discrim_align_threshold = 0 +match_arm_blocks = true +force_multiline_blocks = false +fn_args_layout = "Tall" +brace_style = "SameLineWhere" +control_brace_style = "AlwaysSameLine" +trailing_semicolon = true +trailing_comma = "Vertical" +match_block_trailing_comma = false +blank_lines_upper_bound = 1 +blank_lines_lower_bound = 0 +edition = "2015" +version = "One" +inline_attribute_width = 0 +merge_derives = true +use_try_shorthand = false +use_field_init_shorthand = false +force_explicit_abi = true +condense_wildcard_suffixes = false +color = "Auto" +required_version = "1.4.21" +unstable_features = false +disable_all_formatting = false +skip_children = false +hide_parse_errors = false +error_on_line_overflow = false +error_on_unformatted = false +report_todo = "Never" +report_fixme = "Never" +ignore = [] +emit_mode = "Files" +make_backup = false diff --git a/src/acns/mod.rs b/src/acns/mod.rs index bcf6a7b..8df1701 100644 --- a/src/acns/mod.rs +++ b/src/acns/mod.rs @@ -4,4 +4,4 @@ pub mod tor; pub enum ACNError { AuthenticationError(String), ServiceSetupError(String), -} \ No newline at end of file +} diff --git a/src/acns/tor/authentication.rs b/src/acns/tor/authentication.rs index 2dffe78..5b415eb 100644 --- a/src/acns/tor/authentication.rs +++ b/src/acns/tor/authentication.rs @@ -1,24 +1,24 @@ -use std::net::TcpStream; use std::io::Write; +use std::net::TcpStream; pub trait TorAuthenticationMethod { fn authenticate(&self, conn: &mut TcpStream); } pub struct HashedPassword { - password: String + password: String, } impl HashedPassword { pub fn new(password: String) -> HashedPassword { - HashedPassword { - password - } + HashedPassword { password } } } impl TorAuthenticationMethod for HashedPassword { fn authenticate(&self, conn: &mut TcpStream) { - write!(conn, "AUTHENTICATE \"{}\"\r\n", self.password); + match write!(conn, "AUTHENTICATE \"{}\"\r\n", self.password) { + _ => {} // If anything bad happens we will know soon enough... + } } -} \ No newline at end of file +} diff --git a/src/acns/tor/mod.rs b/src/acns/tor/mod.rs index 61744d6..3256aa0 100644 --- a/src/acns/tor/mod.rs +++ b/src/acns/tor/mod.rs @@ -1,14 +1,13 @@ -use std::net::TcpStream; -use std::io::{Error, BufReader, BufRead}; +use crate::acns::tor::authentication::TorAuthenticationMethod; use crate::acns::ACNError; use crate::acns::ACNError::{AuthenticationError, ServiceSetupError}; -use std::io::Write; -use crate::acns::tor::authentication::TorAuthenticationMethod; use ed25519_dalek::ExpandedSecretKey; +use std::io::Write; +use std::io::{BufRead, BufReader, Error}; +use std::net::TcpStream; pub mod authentication; - #[derive(Debug)] pub struct TorDisconnected(()); #[derive(Debug)] @@ -16,26 +15,17 @@ pub struct TorConnected(()); #[derive(Debug)] pub struct TorAuthenticated(()); - #[derive(Debug)] pub struct TorProcess { conn: TcpStream, - status: ConnectionStatus + status: ConnectionStatus, } impl TorProcess { - pub fn connect(control_port: u16) -> Result, Error> { match TcpStream::connect(&format!("127.0.0.1:{}", control_port)) { - Ok(conn) => { - Ok(TorProcess{ - conn, - status: TorConnected(()) - }) - }, - Err(err) => { - return Err(err) - } + Ok(conn) => Ok(TorProcess { conn, status: TorConnected(()) }), + Err(err) => return Err(err), } } } @@ -49,66 +39,73 @@ impl TorProcess { /// the connection. /// We are more strict as we only allow the caller to authenticate impl TorProcess { - pub fn authenticate(mut self, authMethod: Box) -> Result, ACNError> { - authMethod.authenticate(&mut self.conn); + pub fn authenticate(mut self, auth_method: Box) -> Result, ACNError> { + auth_method.authenticate(&mut self.conn); let mut reader = BufReader::new(self.conn.try_clone().unwrap()); let mut result = String::new(); - reader.read_line(&mut result); - match result.as_str() { - "250 OK\r\n" => { - Ok(TorProcess{ + match reader.read_line(&mut result) { + Ok(_n) => match result.as_str() { + "250 OK\r\n" => Ok(TorProcess { conn: self.conn, - status: TorAuthenticated(()) - }) - } - "515 Bad authentication\r\n" => { - Err(AuthenticationError(String::from("515 Bad authentication"))) - } - _ => { - Err(AuthenticationError(String::from(result))) - } + status: TorAuthenticated(()), + }), + "515 Bad authentication\r\n" => Err(AuthenticationError(String::from("515 Bad authentication"))), + _ => Err(AuthenticationError(String::from(result))), + }, + Err(err) => Err(AuthenticationError(err.to_string())), } } } /// We are now authenticated! impl TorProcess { - /// Tell the control port to create a new Onion V3 service given the ed25519 secret key... - pub fn add_onion_v3(&mut self, secret_key: ed25519_dalek::SecretKey, virtual_port: u16, target_port: u16) -> Result { + pub fn add_onion_v3(&mut self, secret_key: ed25519_dalek::SecretKey, virtual_port: u16, target_port: u16) -> Result { let esk = ExpandedSecretKey::from(&secret_key); - write!(self.conn, "ADD_ONION ED25519-V3:{} Flags=Detach Port={},{}\r\n", base64::encode(esk.to_bytes()), virtual_port, target_port); + match write!( + self.conn, + "ADD_ONION ED25519-V3:{} Flags=Detach Port={},{}\r\n", + base64::encode(esk.to_bytes()), + virtual_port, + target_port + ) { + _ => {} + } let mut reader = BufReader::new(self.conn.try_clone().unwrap()); let mut result = String::new(); - reader.read_line(&mut result); - if result.starts_with("250-ServiceID=") { - // We authenticated! - return Ok(result); + match reader.read_line(&mut result) { + Ok(_n) => { + if result.starts_with("250-ServiceID=") { + // We authenticated! + return Ok(result); + } + Err(ServiceSetupError(result)) + } + Err(err) => Err(ServiceSetupError(err.to_string())), } - Err(ServiceSetupError(result)) } } - #[cfg(test)] mod tests { - use crate::acns::tor::TorProcess; use crate::acns::tor::authentication::HashedPassword; - use ed25519_dalek::{Keypair}; + use crate::acns::tor::TorProcess; + use rand::rngs::OsRng; #[test] fn connect_tor() { - let mut auth_control_port = TorProcess::connect(9051).unwrap().authenticate(Box::new(HashedPassword::new(String::from("examplehashedpassword")))).unwrap(); - let mut csprng = OsRng{}; + let mut auth_control_port = TorProcess::connect(9051) + .unwrap() + .authenticate(Box::new(HashedPassword::new(String::from("examplehashedpassword")))) + .unwrap(); + let mut csprng = OsRng {}; let keypair = ed25519_dalek::Keypair::generate(&mut csprng); match auth_control_port.add_onion_v3(keypair.secret, 9878, 10029) { Ok(_) => { // we authenticated! - }, - Err(err) => { - println!("{:?}", err) } + Err(err) => println!("{:?}", err), } } } diff --git a/src/applications/authentication_app.rs b/src/applications/authentication_app.rs index 169b0f1..46d816f 100644 --- a/src/applications/authentication_app.rs +++ b/src/applications/authentication_app.rs @@ -1,104 +1,207 @@ -use crate::primitives::transcript::Transcript; -use std::net::TcpStream; -use std::sync::Arc; +use crate::connections::utils::public_key_to_hostname; +use crate::connections::{Connection, InboundConnection, OutboundConnection}; use crate::primitives::identity::Identity; +use crate::primitives::transcript::Transcript; use ed25519_dalek::PublicKey; -use std::io::{Write, Read}; -use serde::Serialize; +use integer_encoding::VarInt; use serde::Deserialize; -use byteorder::{WriteBytesExt, BigEndian, ReadBytesExt, LittleEndian}; -use integer_encoding::{FixedInt, VarInt}; -use crate::connections::utils::{expect, expect_encrypted, public_key_to_hostname, send_encrypted, send}; +use serde::Serialize; use sha3::Digest; +use std::sync::Arc; +use subtle::ConstantTimeEq; pub struct AuthenicationApp { - identity: Arc + identity: Arc, } +struct AuthenticationSession { + long_term_identity: Arc, + ephemeral_identity: Identity, + local_auth_message_json: String, + remote_auth_message_json: String, + + remote_long_term_identity: ed25519_dalek::PublicKey, + remote_ephemeral_identity: ed25519_dalek::PublicKey, + + challenge: [u8; 64], + + conn: Connection, +} + +/// Macro for constructing the identity exchange...we need to do this for both inbound and outbound and +/// there is slightly less code complication doing this in a macro v.s. duplicating the code. +macro_rules! identity_exchange { + ($conn:ident, $long_term_identity:ident, $ephemeral_identity:ident, $local_auth_message_json: ident, $remote_long_term_identity:ident, $remote_ephemeral_identity: ident, $remote_auth_message_json: ident) => { + let $ephemeral_identity = Identity::initialize_ephemeral_identity(); + let auth_message = AuthMessage { + longterm_public_key: base64::encode($long_term_identity.public_key()), + ephemeral_public_key: base64::encode($ephemeral_identity.public_key().to_bytes()), + }; + let $local_auth_message_json = serde_json::to_string(&auth_message).unwrap(); + + $conn.send(&$local_auth_message_json); + let $remote_auth_message_json = String::from_utf8($conn.expect()).unwrap(); + + let remote_auth_message: AuthMessage = serde_json::from_str($remote_auth_message_json.as_str()).unwrap(); + let $remote_long_term_identity = PublicKey::from_bytes(base64::decode(&remote_auth_message.longterm_public_key).unwrap().as_slice()).unwrap(); + let $remote_ephemeral_identity = PublicKey::from_bytes(base64::decode(&remote_auth_message.ephemeral_public_key).unwrap().as_slice()).unwrap(); + }; +} + +impl AuthenticationSession { + pub fn new_outbound(mut conn: Connection, long_term_identity: Arc) -> AuthenticationSession { + identity_exchange!( + conn, + long_term_identity, + ephemeral_identity, + local_auth_message_json, + remote_long_term_identity, + remote_ephemeral_identity, + remote_auth_message_json + ); + AuthenticationSession { + long_term_identity, + ephemeral_identity, + local_auth_message_json, + remote_auth_message_json, + remote_long_term_identity, + remote_ephemeral_identity, + conn, + challenge: [0; 64], + } + } + + pub fn new_inbound(mut conn: Connection, long_term_identity: Arc) -> AuthenticationSession { + identity_exchange!( + conn, + long_term_identity, + ephemeral_identity, + local_auth_message_json, + remote_long_term_identity, + remote_ephemeral_identity, + remote_auth_message_json + ); + AuthenticationSession { + long_term_identity, + ephemeral_identity, + local_auth_message_json, + remote_auth_message_json, + remote_long_term_identity, + remote_ephemeral_identity, + conn, + challenge: [0; 64], + } + } + + /// generate the message for the local challenge based a new transcript hash. + fn generate_challenge_message(&mut self) -> Vec { + let mut msg = vec![]; + let mut len = [0u8; 2]; + ((self.challenge.len() + self.long_term_identity.hostname().as_bytes().len()) as u16).encode_var(&mut len); + msg.extend_from_slice(len.as_slice()); + msg.extend_from_slice(self.challenge.as_slice()); + msg.extend_from_slice(self.long_term_identity.hostname().as_bytes()); + msg + } + + /// check the challenge from the remote + fn check_remote_challenge(&mut self, remote_challenge: Vec) -> bool { + let mut cmp_challenge = vec![]; + cmp_challenge.extend_from_slice(self.challenge.as_slice()); + cmp_challenge.extend_from_slice(public_key_to_hostname(&self.remote_long_term_identity).as_bytes()); + return remote_challenge.ct_eq(cmp_challenge.as_slice()).unwrap_u8() == 1; + } +} + +impl AuthenticationSession { + pub fn generate_challenge(&mut self, transcript: &mut Transcript) -> Vec { + let l2e = self.long_term_identity.edh(self.remote_ephemeral_identity); + let e2l = self.ephemeral_identity.edh(self.remote_long_term_identity); + let e2e = self.ephemeral_identity.edh(self.remote_ephemeral_identity); + + let mut shared_secret = vec![]; + // inbound calculates shared secret as Sha256(l2e:e2l:e2e) + shared_secret.extend_from_slice(l2e.as_bytes()); + shared_secret.extend_from_slice(e2l.as_bytes()); + shared_secret.extend_from_slice(e2e.as_bytes()); + let key = sha3::Sha3_256::digest(shared_secret.as_slice()); + + // transcript is updated using appropriate values for outbound and inbound hostname and initial messages + transcript.new_protocol("auth-app"); + transcript.add_to_transcript("outbound-hostname", self.long_term_identity.hostname().as_bytes()); + transcript.add_to_transcript("inbound-hostname", public_key_to_hostname(&self.remote_long_term_identity).as_bytes()); + transcript.add_to_transcript("outbound-challenge", self.local_auth_message_json.as_bytes()); + transcript.add_to_transcript("inbound-challenge", self.remote_auth_message_json.as_bytes()); + self.challenge = transcript.commit_to_transcript("3dh-auth-challenge"); + let key = key.clone(); + key.to_vec() + } +} + +impl AuthenticationSession { + pub fn generate_challenge(&mut self, transcript: &mut Transcript) -> Vec { + let l2e = self.long_term_identity.edh(self.remote_ephemeral_identity); + let e2l = self.ephemeral_identity.edh(self.remote_long_term_identity); + let e2e = self.ephemeral_identity.edh(self.remote_ephemeral_identity); + + // inbound calculates shared secret as Sha256(e2l:l2e:e2e) + let mut shared_secret = vec![]; + shared_secret.extend_from_slice(e2l.as_bytes()); + shared_secret.extend_from_slice(l2e.as_bytes()); + shared_secret.extend_from_slice(e2e.as_bytes()); + let key = sha3::Sha3_256::digest(shared_secret.as_slice()); + + // transcript is updated using appropriate values for outbound and inbound hostname and initial messages + transcript.new_protocol("auth-app"); + transcript.add_to_transcript("outbound-hostname", public_key_to_hostname(&self.remote_long_term_identity).as_bytes()); + transcript.add_to_transcript("inbound-hostname", self.long_term_identity.hostname().as_bytes()); + transcript.add_to_transcript("outbound-challenge", self.remote_auth_message_json.as_bytes()); + transcript.add_to_transcript("inbound-challenge", self.local_auth_message_json.as_bytes()); + self.challenge = transcript.commit_to_transcript("3dh-auth-challenge"); + + let key = key.clone(); + key.to_vec() + } +} #[derive(Serialize, Deserialize, Debug)] struct AuthMessage { #[serde(rename(serialize = "LongTermPublicKey", deserialize = "LongTermPublicKey"))] - longterm_public_key: String, + longterm_public_key: String, #[serde(rename(serialize = "EphemeralPublicKey", deserialize = "EphemeralPublicKey"))] ephemeral_public_key: String, } impl AuthenicationApp { pub fn new(identity: Arc) -> AuthenicationApp { - AuthenicationApp { - identity + AuthenicationApp { identity } + } + + pub fn run_outbound(&mut self, conn: &mut Connection, transcript: &mut Transcript) { + let mut auth_session = AuthenticationSession::::new_outbound(conn.try_clone(), self.identity.clone()); + let key = auth_session.generate_challenge(transcript); + conn.enable_encryption(key); + conn.send_encrypted(auth_session.generate_challenge_message()); + let remote_challenge = conn.expect_encrypted(); + if auth_session.check_remote_challenge(remote_challenge) { + println!("Authenticated") + } else { + println!("Failed Authentication"); + conn.shutdown(); } } - pub fn run(&mut self, conn: &mut TcpStream, transcript: &mut Transcript) { - let longterm_public_key = self.identity.public_key().to_bytes(); - let ephemeral_identity = Identity::initialize_ephemeral_identity(); - let auth_message = AuthMessage{longterm_public_key: base64::encode(longterm_public_key), ephemeral_public_key: base64::encode(ephemeral_identity.public_key().to_bytes())}; - let auth_message_json = serde_json::to_string(&auth_message).unwrap(); - println!("TCP Timeout: {:?}", conn.read_timeout()); - let mut len = [0u8;2]; - (auth_message_json.len() as u16).encode_var(&mut len); - // println!("len {} {:?}", auth_message_json.len(), len); - let mut msg = vec![]; - msg.extend_from_slice(len.as_slice()); - msg.extend_from_slice(auth_message_json.as_bytes()); - send(conn, msg); - - let remote_auth_message_json = String::from_utf8(expect(conn)).unwrap(); - println!("Received Auth Message: {}", remote_auth_message_json); - - let remote_auth_message : AuthMessage = serde_json::from_str(remote_auth_message_json.as_str()).unwrap(); - println!("Remote auth message: {:?}",remote_auth_message); - let remote_public_key = PublicKey::from_bytes(base64::decode(&remote_auth_message.longterm_public_key).unwrap().as_slice()).unwrap(); - let remote_ephemeral_key = PublicKey::from_bytes(base64::decode(&remote_auth_message.ephemeral_public_key).unwrap().as_slice()).unwrap(); - - let l2e = self.identity.edh(remote_ephemeral_key); - let e2l = ephemeral_identity.edh(remote_public_key); - let e2e = ephemeral_identity.edh(remote_ephemeral_key); - - let mut shared_secret = vec![]; - // todo if outbound - shared_secret.extend_from_slice(l2e.as_bytes()); - shared_secret.extend_from_slice(e2l.as_bytes()); - shared_secret.extend_from_slice(e2e.as_bytes()); - // todo else - - let key = sha3::Sha3_256::digest(shared_secret.as_slice()); - - transcript.new_protocol("auth-app"); - transcript.add_to_transcript("outbound-hostname", self.identity.hostname().as_bytes()); - transcript.add_to_transcript("inbound-hostname", public_key_to_hostname(&remote_public_key).as_bytes()); - transcript.add_to_transcript("outbound-challenge", auth_message_json.as_bytes()); - transcript.add_to_transcript("inbound-challenge",remote_auth_message_json.as_bytes()); - - let challenge_bytes = transcript.commit_to_transcript("3dh-auth-challenge"); - println!("Calculated Challenge: {}", hex::encode(challenge_bytes)); - - let mut msg = vec![]; - let mut len = [0u8;2]; - ((challenge_bytes.len()+self.identity.hostname().as_bytes().len()) as u16).encode_var(&mut len); - msg.extend_from_slice(len.as_slice()); - msg.extend_from_slice(challenge_bytes.as_slice()); - msg.extend_from_slice(self.identity.hostname().as_bytes()); - println!("Sending: {:?}", hex::encode(&msg)); - send_encrypted(conn, key.as_slice(), msg); - - let challenge = expect_encrypted(conn, key.as_slice()); - let challenge_parts = challenge.split_at(64); - println!("Received Challenge: {:?} {:?}", hex::encode(challenge_parts.0), String::from_utf8(challenge_parts.1.to_vec())); - } -} - -#[cfg(test)] -mod tests { - - - #[test] - fn test_transcript_app() { - //let mut transcript_app = TranscriptApp::NewInstance(); - // transcript_app.Init(); - // transcript_app.Transcript(); - //println!("Transcript App Initial Commit: {:x}", transcript_app.Transcript().challenge_bytes()) + pub fn run_inbound(&mut self, conn: &mut Connection, transcript: &mut Transcript) { + let mut auth_session = AuthenticationSession::::new_inbound(conn.try_clone(), self.identity.clone()); + let key = auth_session.generate_challenge(transcript); + conn.enable_encryption(key); + conn.send_encrypted(auth_session.generate_challenge_message()); + let remote_challenge = conn.expect_encrypted(); + if auth_session.check_remote_challenge(remote_challenge) { + println!("Authenticated") + } else { + println!("Failed Authentication"); + conn.shutdown(); + } } } diff --git a/src/applications/mod.rs b/src/applications/mod.rs index 6ee4941..59c9e0f 100644 --- a/src/applications/mod.rs +++ b/src/applications/mod.rs @@ -1,2 +1 @@ -pub mod transcript_app; pub mod authentication_app; diff --git a/src/applications/transcript_app.rs b/src/applications/transcript_app.rs deleted file mode 100644 index 44b3338..0000000 --- a/src/applications/transcript_app.rs +++ /dev/null @@ -1,44 +0,0 @@ -use crate::primitives::transcript::Transcript; -use std::net::TcpStream; -use std::sync::Arc; - -#[derive(Clone)] -pub struct TranscriptApp { - transcript: Option -} - -impl TranscriptApp { - fn new() -> TranscriptApp { - TranscriptApp { - transcript: None - } - } - - fn run(&mut self, conn: &TcpStream) { - self.transcript = Some(Transcript::new_transcript("primitives-transcript".as_ref())) - } - - fn transcript(&mut self) -> Option<&mut Transcript> { - return self.transcript.as_mut() - } - - fn propagate_transcript(&mut self, transcript: &Transcript) { - match self.transcript { - None => self.transcript = Some(transcript.clone()), - _ => panic!("this is a bug...we should find a nicer rust way of doing this") - } - } -} - -#[cfg(test)] -mod tests { - - - #[test] - fn test_transcript_app() { - //let mut transcript_app = TranscriptApp::NewInstance(); - // transcript_app.Init(); - // transcript_app.Transcript(); - //println!("Transcript App Initial Commit: {:x}", transcript_app.Transcript().challenge_bytes()) - } -} diff --git a/src/connections/mod.rs b/src/connections/mod.rs index 4a9f88c..efe5f35 100644 --- a/src/connections/mod.rs +++ b/src/connections/mod.rs @@ -1,7 +1,11 @@ +use integer_encoding::{FixedInt, VarInt}; +use secretbox::CipherType::Salsa20; +use secretbox::SecretBox; +use std::io::{Read, Write}; +use std::net::{Shutdown, TcpStream}; + /// Connections provides an interface for manage sets of connections on top of a particular /// ACN. - - pub mod service; pub mod utils; @@ -12,7 +16,117 @@ pub enum ServiceError { ListenFailed(String), } +#[derive(Clone)] +pub struct InboundConnection(()); +#[derive(Clone)] +pub struct OutboundConnection(()); -pub struct Hostname { +pub struct Connection { + conn: TcpStream, + direction: Direction, + key: Vec, +} -} \ No newline at end of file +impl Connection +where + Direction: Clone, +{ + pub fn new_inbound(conn: TcpStream) -> Connection { + Connection { + conn, + direction: InboundConnection(()), + key: vec![], + } + } + + pub fn new_outbound(conn: TcpStream) -> Connection { + Connection { + conn, + direction: OutboundConnection(()), + key: vec![], + } + } + + pub fn try_clone(&self) -> Connection { + Connection { + conn: self.conn.try_clone().unwrap(), + direction: self.direction.clone(), + key: self.key.clone(), + } + } + + pub fn enable_encryption(&mut self, key: Vec) { + self.key = key + } + + pub fn send(&mut self, amsg: &String) { + let mut len = [0u8; 2]; + (amsg.len() as u16).encode_var(&mut len); + let mut msg = vec![]; + msg.extend_from_slice(len.as_slice()); + msg.extend_from_slice(amsg.as_bytes()); + let mut msg = msg; + while msg.len() < 8192 { + msg.push(0); + } + match self.conn.write_all(msg.as_slice()) { + _ => {} // If anything bad happens we will know soon enough... + } + } + + pub fn send_encrypted(&mut self, msg: Vec) { + let mut msg = msg; + while msg.len() < 8192 - 40 { + msg.push(0); + } + let secret_box = SecretBox::new(&self.key, Salsa20).unwrap(); + let msg = secret_box.easy_seal(msg.as_slice()); + match self.conn.write_all(msg.as_slice()) { + _ => {} // If anything bad happens we will know soon enough... + } + } + + pub fn expect_encrypted(&mut self) -> Vec { + let secret_box = SecretBox::new(&self.key, Salsa20).unwrap(); + let mut result = [0u8; 8192]; + match self.conn.read_exact(&mut result) { + Err(e) => eprintln!("{}", e.to_string()), + _ => {} + } + + let msg = secret_box.easy_unseal(&result).unwrap(); + let msg = msg.as_slice(); + let _len_bytes = [0u8; 2]; + let len = u16::decode_fixed(&msg[0..2]) as usize; + if len > 8192 { + eprintln!("invalid length: {}", len); + return vec![]; // lol no. + } + msg[2..len + 2].to_vec() + } + + pub fn expect(&mut self) -> Vec { + let mut msg = [0; 8192]; + let result = self.conn.read_exact(&mut msg); + match result { + Err(e) => { + println!("{:?}", e); + vec![] + } + Ok(()) => { + // TODO why did I decide to use varints here?!?! + let len = u16::decode_var(&msg[0..2]).unwrap().0 as usize; + println!("{} [{}]", len, String::from_utf8(msg[2..len + 2].to_vec()).unwrap()); + return msg[2..len + 2].to_vec(); + } + } + } + + pub fn shutdown(&mut self) { + match self.conn.shutdown(Shutdown::Both) { + _ => {} // If anything bad happens we will know soon enough... + } + } +} + +pub struct Hostname {} diff --git a/src/connections/service.rs b/src/connections/service.rs index 6d145f1..50850ee 100644 --- a/src/connections/service.rs +++ b/src/connections/service.rs @@ -1,77 +1,72 @@ -use crate::primitives::identity::Identity; -use crate::connections::{Hostname, ServiceError}; -use std::net::{TcpListener, TcpStream}; -use std::thread::{spawn, JoinHandle}; use crate::connections::ServiceError::{ClosedNormally, ConnectionFailed}; +use crate::connections::{Connection, InboundConnection, OutboundConnection, ServiceError}; +use crate::primitives::identity::Identity; +use std::net::TcpListener; use std::sync::Arc; -use crate::acns::tor::TorProcess; -use socks::{Socks5Stream}; -use socks::TargetAddr::Domain; -use std::io::Write; +use std::thread::{spawn, JoinHandle}; +use socks::Socks5Stream; +use socks::TargetAddr::Domain; pub struct NoListenService(()); pub struct ApplicationListenService(()); pub struct Service { identity: Arc, - listen_service: ListenService + listen_service: ListenService, } impl Service { - pub fn connect(&mut self, hostname:&str, application:F) -> Result<(), ServiceError> where F: FnOnce(TcpStream) + Send + Clone + 'static, { - let conn = Socks5Stream::connect(format!("127.0.0.1:9050"), Domain(format!("{}.onion", hostname),9878)); + pub fn connect(&mut self, hostname: &str, application: F) -> Result<(), ServiceError> + where + F: FnOnce(Connection) + Send + Clone + 'static, + { + let conn = Socks5Stream::connect(format!("127.0.0.1:9050"), Domain(format!("{}.onion", hostname), 9878)); match conn { - Ok(mut conn) => { + Ok(conn) => { let application = application.clone(); - spawn( move || { - application(conn.into_inner()) - }); + spawn(move || application(Connection::::new_inbound(conn.into_inner()))); Ok(()) - }, - Err(err) => { - Err(ConnectionFailed(err.to_string())) } + Err(err) => Err(ConnectionFailed(err.to_string())), } } - } impl Service { pub fn init(identity: Arc) -> Service { Service { identity, - listen_service: NoListenService(()) + listen_service: NoListenService(()), } } - pub fn listen(mut self, port: u16, application: F) -> Result>, ServiceError> where F: FnOnce(TcpStream) + Send + Clone + 'static, { - - let jh =spawn(move || { - let listener = TcpListener::bind(format!("127.0.0.1:{}", port)); - match listener { - Ok(listener) => { - for stream in listener.incoming() { - match stream { - Ok(conn) => { - let application = application.clone(); - spawn( move || { - application(conn) - }); - }, - Err(_) =>{} + pub fn listen(self, port: u16, application: F) -> Result>, ServiceError> + where + F: FnOnce(Connection) + Send + Clone + 'static, + { + let jh = spawn(move || { + let listener = TcpListener::bind(format!("127.0.0.1:{}", port)); + match listener { + Ok(listener) => { + for stream in listener.incoming() { + match stream { + Ok(conn) => { + let application = application.clone(); + spawn(move || application(Connection::::new_outbound(conn))); + } + Err(_) => {} + } } + ClosedNormally } - ClosedNormally - }, - Err(err) => { - ServiceError::ListenFailed(err.to_string()) + Err(err) => ServiceError::ListenFailed(err.to_string()), } - }}); + }); - Ok(Service{ + Ok(Service { identity: self.identity, - listen_service: jh + listen_service: jh, }) } } @@ -80,12 +75,8 @@ impl Service> { pub fn close(self) { let result = self.listen_service.join(); match result { - Ok(err) => { - eprintln!("{:?}", err) - } - _ => { - eprintln!("Error joining listen thread") - } + Ok(err) => eprintln!("{:?}", err), + _ => eprintln!("Error joining listen thread"), } } } @@ -96,28 +87,26 @@ mod tests { use crate::primitives::identity::Identity; use ed25519_dalek::SecretKey; use rand::rngs::OsRng; - use crate::applications::transcript_app::TranscriptApp; - use crate::connections::Hostname; #[test] fn service_state() { - let mut csprng = OsRng{}; + let mut csprng = OsRng {}; let keypair = ed25519_dalek::Keypair::generate(&mut csprng); - let secret_key = SecretKey::from_bytes(&keypair.secret.to_bytes()); + let _secret_key = SecretKey::from_bytes(&keypair.secret.to_bytes()); let identity = Identity::initialize(keypair); - let mut service = Service::init(identity); + let _service = Service::init(identity); //let mut listen_service = service.listen(10000, TranscriptApp::new_instance()).unwrap_or_else(|_| panic!()); // this will not compile! wish we could test that service.connect(Hostname{},TranscriptApp::new_instance()); } #[test] fn service_lifetime() { - let mut csprng = OsRng{}; + let mut csprng = OsRng {}; let keypair = ed25519_dalek::Keypair::generate(&mut csprng); - let secret_key = SecretKey::from_bytes(&keypair.secret.to_bytes()); + let _secret_key = SecretKey::from_bytes(&keypair.secret.to_bytes()); let identity = Identity::initialize(keypair); - let service = Service::init(identity); + let _service = Service::init(identity); //let listen_service = service.listen(1000, TranscriptApp::new_instance()).unwrap_or_else(|_| panic!()); // TODO use trybuild to test that this fails: service.connect(Hostname{},TranscriptApp::new_instance()); } -} \ No newline at end of file +} diff --git a/src/connections/utils.rs b/src/connections/utils.rs index 0c51dd1..ed8b9a5 100644 --- a/src/connections/utils.rs +++ b/src/connections/utils.rs @@ -1,29 +1,6 @@ -use std::net::TcpStream; -use std::io::{Read, Write, BufReader}; -use integer_encoding::{VarInt, FixedInt}; -use secretbox::SecretBox; -use secretbox::CipherType::Salsa20; use ed25519_dalek::PublicKey; use sha3::Digest; -pub fn expect(conn: &mut TcpStream) -> Vec { - let mut msg = [0;8192]; - let mut buf = BufReader::new(conn.try_clone().unwrap()); - let result = buf.read_exact(&mut msg); - match result { - Err(e) =>{ - println!("{:?}",e); - vec![] - } - Ok(()) => { - // TODO why did I decide to use varints here?!?! - let len = u16::decode_var(&msg[0..2]).unwrap().0 as usize; - println!("{} [{}]", len, String::from_utf8(msg[2..len+2].to_vec()).unwrap()); - return msg[2..len+2].to_vec() - } - } -} - pub fn public_key_to_hostname(public_key: &PublicKey) -> String { let mut buf = [0u8; 35]; public_key.to_bytes().iter().copied().enumerate().for_each(|(i, b)| { @@ -38,45 +15,5 @@ pub fn public_key_to_hostname(public_key: &PublicKey) -> String { buf[32] = res_vec[0]; buf[33] = res_vec[1]; buf[34] = 0x03; - base32::encode(base32::Alphabet::RFC4648 {padding:false}, &buf).to_ascii_lowercase() + base32::encode(base32::Alphabet::RFC4648 { padding: false }, &buf).to_ascii_lowercase() } - -pub fn send(conn: &mut TcpStream, msg: Vec) { - let mut msg = msg; - while msg.len() < 8192 { - msg.push(0); - } - // println!("Sending Auth Message: {}", hex::encode(&msg)); - conn.write_all(msg.as_slice()); -} - -pub fn send_encrypted(conn: &mut TcpStream, key: &[u8], msg: Vec) { - let mut msg = msg; - while msg.len() < 8192-40 { - msg.push(0); - } - let secret_box = SecretBox::new(key, Salsa20).unwrap(); - let msg = secret_box.easy_seal(msg.as_slice()); - conn.write_all(msg.as_slice()); -} - -pub fn expect_encrypted(conn: &mut TcpStream, key: &[u8]) -> Vec { - let secret_box = SecretBox::new(key, Salsa20).unwrap(); - let mut result = [0u8;8192]; - match conn.read_exact(&mut result) { - Err(e) =>{ - eprintln!("{}",e.to_string()) - } - _ => {} - } - - let msg = secret_box.easy_unseal(&result).unwrap(); - let msg = msg.as_slice(); - let mut len_bytes = [0u8;2]; - let len = u16::decode_fixed(&msg[0..2]) as usize; - if len > 8192 { - eprintln!("invalid length: {}", len); - return vec![] // lol no. - } - msg[2..len].to_vec() -} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 83ccd80..5b9802a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,9 +6,8 @@ pub mod acns; pub mod applications; -pub mod primitives; pub mod connections; - +pub mod primitives; #[cfg(test)] mod tests { diff --git a/src/primitives/identity.rs b/src/primitives/identity.rs index 43bc36a..908961b 100644 --- a/src/primitives/identity.rs +++ b/src/primitives/identity.rs @@ -1,36 +1,29 @@ -use ed25519_dalek::{Keypair, PublicKey, ExpandedSecretKey}; -use x25519_dalek::{StaticSecret, SharedSecret}; -use x25519_dalek::PublicKey as X25519PublicKey ; +use crate::connections::utils::public_key_to_hostname; +use ed25519_dalek::{ExpandedSecretKey, Keypair, PublicKey}; use rand::rngs::OsRng; use std::intrinsics::transmute; -use sha3::Digest; use std::sync::Arc; -use crate::connections::utils::public_key_to_hostname; - +use x25519_dalek::PublicKey as X25519PublicKey; +use x25519_dalek::{SharedSecret, StaticSecret}; /// Identity - An ed25519 keypair, required for established a Tor v3 onion service and used to /// maintain a consistent cryptographic identity for a peer. pub struct Identity { - keypair: Keypair + keypair: Keypair, } impl Identity { - /// Initialize a persistent identity pub fn initialize(keypair: Keypair) -> Arc { - Arc::new(Identity { - keypair - }) + Arc::new(Identity { keypair }) } /// Initialize an ephemeral identity - used for both ephemeral diffie hellman key exchanges /// in addition to anonymous primitives connections to various onion services. pub fn initialize_ephemeral_identity() -> Identity { - let mut csprng = OsRng{}; + let mut csprng = OsRng {}; let keypair = ed25519_dalek::Keypair::generate(&mut csprng); - Identity { - keypair - } + Identity { keypair } } pub fn public_key(&self) -> ed25519_dalek::PublicKey { @@ -48,8 +41,8 @@ impl Identity { /// This is used as part of the ephemeral diffie hellman 3-way exchange. pub fn edh(&self, remote_pub_key: PublicKey) -> SharedSecret { let esk = ExpandedSecretKey::from(&self.keypair.secret).to_bytes(); - let hsk : *const [u8;32] = unsafe{transmute(esk.as_ptr())}; - let private_x25519 = StaticSecret::from(unsafe{**&hsk}); + let hsk: *const [u8; 32] = unsafe { transmute(esk.as_ptr()) }; + let private_x25519 = StaticSecret::from(unsafe { **&hsk }); let public_x25519: X25519PublicKey = Identity::to_x25519(remote_pub_key); return private_x25519.diffie_hellman(&public_x25519); } @@ -60,7 +53,6 @@ impl Identity { let edwards = curve25519_dalek::edwards::CompressedEdwardsY::from_slice(edwards_pub_key.to_bytes().as_slice()); X25519PublicKey::from(edwards.decompress().unwrap().to_montgomery().to_bytes()) } - } #[cfg(test)] diff --git a/src/primitives/mod.rs b/src/primitives/mod.rs index f1fac7a..7ba5da6 100644 --- a/src/primitives/mod.rs +++ b/src/primitives/mod.rs @@ -1,2 +1,2 @@ pub mod identity; -pub mod transcript; \ No newline at end of file +pub mod transcript; diff --git a/src/primitives/transcript.rs b/src/primitives/transcript.rs index a2f24f1..15fd911 100644 --- a/src/primitives/transcript.rs +++ b/src/primitives/transcript.rs @@ -3,18 +3,18 @@ #[derive(Clone)] pub struct Transcript { mtranscript: merlin::Transcript, - transcript: String + transcript: String, } impl Transcript { - pub fn new_transcript(label: &'static str) -> Transcript{ + pub fn new_transcript(label: &'static str) -> Transcript { Transcript { mtranscript: merlin::Transcript::new(label.as_ref()), - transcript: String::new() + transcript: String::new(), } } - pub fn add_to_transcript(&mut self, label: &'static str, b :&[u8]) { + pub fn add_to_transcript(&mut self, label: &'static str, b: &[u8]) { let op = format!("{} ({}) {};", label, b.len(), hex::encode(b)); self.transcript = format!("{}\n{}", self.transcript, op); self.mtranscript.append_message(label.as_ref(), b); @@ -30,9 +30,9 @@ impl Transcript { self.mtranscript.append_message("protocol".as_ref(), label.as_ref()); } - pub fn commit_to_transcript(&mut self, label: &'static str) -> [u8;64] { - let mut result = [0u8;64]; - self.mtranscript.challenge_bytes(label.as_ref(),result.as_mut_slice()); + pub fn commit_to_transcript(&mut self, label: &'static str) -> [u8; 64] { + let mut result = [0u8; 64]; + self.mtranscript.challenge_bytes(label.as_ref(), result.as_mut_slice()); self.transcript = format!("{}\nextract {}: {}", self.transcript, label, hex::encode(result)); result } @@ -45,19 +45,22 @@ mod tests { #[test] fn test_transcript() { let mut t = Transcript::new_transcript("label"); - t.add_to_transcript("action","test data".as_bytes()); + t.add_to_transcript("action", "test data".as_bytes()); if t.output_transcript_to_audit() != t.output_transcript_to_audit() { - panic!("Multiple Audit Calls should not impact underlying Transcript") + panic!("Multiple Audit Calls should not impact underlying Transcript") } - println!("{}",t.output_transcript_to_audit()); + println!("{}", t.output_transcript_to_audit()); println!("{:?}", t.commit_to_transcript("first commit")); - println!("{}",t.output_transcript_to_audit()); + println!("{}", t.output_transcript_to_audit()); println!("{:?}", t.commit_to_transcript("second commit")); - t.add_to_transcript("action","test data".as_bytes()); + t.add_to_transcript("action", "test data".as_bytes()); // Test vector from golang Tapir tests... - let test_vector :[u8;64] = [239,5,86,166,139,70,247,187,137,37,36,183,53,51,114,115,24,103,110,65,82,190,88,106,4,114,91,194,90,114,107,19,3,33,103,187,122,54,156,213,203,40,132,26,203,130,117,55,161,245,30,102,127,151,233,148,116,212,235,186,46,59,1,8]; + let test_vector: [u8; 64] = [ + 239, 5, 86, 166, 139, 70, 247, 187, 137, 37, 36, 183, 53, 51, 114, 115, 24, 103, 110, 65, 82, 190, 88, 106, 4, 114, 91, 194, 90, 114, 107, 19, 3, 33, 103, 187, 122, 54, 156, 213, 203, 40, + 132, 26, 203, 130, 117, 55, 161, 245, 30, 102, 127, 151, 233, 148, 116, 212, 235, 186, 46, 59, 1, 8, + ]; assert_eq!(test_vector, t.commit_to_transcript("third commit")) } -} \ No newline at end of file +} diff --git a/tests/simple_setup.rs b/tests/simple_setup.rs index 851c8cf..e4f0ce8 100644 --- a/tests/simple_setup.rs +++ b/tests/simple_setup.rs @@ -1,21 +1,23 @@ #[cfg(test)] mod test { + use ed25519_dalek::SecretKey; + use rand::rngs::OsRng; use tapir::acns::tor::authentication::HashedPassword; use tapir::acns::tor::TorProcess; - use rand::rngs::OsRng; - use tapir::primitives::identity::Identity; - use ed25519_dalek::SecretKey; use tapir::connections::service::Service; - use tapir::applications::transcript_app::TranscriptApp; - use std::net::TcpStream; - use std::io::Write; + use tapir::primitives::identity::Identity; + use tapir::applications::authentication_app::AuthenicationApp; + use tapir::connections::{Connection, InboundConnection, OutboundConnection}; use tapir::primitives::transcript::Transcript; #[test] fn test_simple_setup() { - let mut auth_control_port = TorProcess::connect(9051).unwrap().authenticate(Box::new(HashedPassword::new(String::from("examplehashedpassword")))).unwrap(); - let mut csprng = OsRng{}; + let mut auth_control_port = TorProcess::connect(9051) + .unwrap() + .authenticate(Box::new(HashedPassword::new(String::from("examplehashedpassword")))) + .unwrap(); + let mut csprng = OsRng {}; let keypair = ed25519_dalek::Keypair::generate(&mut csprng); match auth_control_port.add_onion_v3(SecretKey::from_bytes(&keypair.secret.to_bytes()).unwrap(), 9878, 10029) { Ok(service_id) => { @@ -24,26 +26,30 @@ mod test { println!("Service Id: {}", service_id); println!("Setup: {}", identity.hostname()); - let mut service = Service::init(identity.clone()); + let service = Service::init(identity.clone()); - let identity = identity.clone(); - let function = |mut conn: TcpStream| { + let outbound_identity = identity.clone(); + let outbound_service = |mut conn: Connection| { let mut transcript = Transcript::new_transcript("tapir-transcript"); - let mut auth_app = AuthenicationApp::new(identity); - auth_app.run(&mut conn, &mut transcript); + let mut auth_app = AuthenicationApp::new(outbound_identity); + auth_app.run_outbound(&mut conn, &mut transcript); }; - // let mut service = service.listen(10029, function.clone()).unwrap_or_else(|_|panic!()); - service.connect("hejcfc3pduhcymo7i3zkoxezbjvw3vdsbavyy7gfsd677l5py7ccnlyd", function.clone()); - // service.close(); - loop{} - }, - Err(err) => { - println!("{:?}", err) + let mut service = service.listen(10029, outbound_service.clone()).unwrap_or_else(|_| panic!()); + + let identity = identity.clone(); + let inbound_service = |mut conn: Connection| { + let mut transcript = Transcript::new_transcript("tapir-transcript"); + let mut auth_app = AuthenicationApp::new(identity); + auth_app.run_inbound(&mut conn, &mut transcript); + }; + match service.connect("hejcfc3pduhcymo7i3zkoxezbjvw3vdsbavyy7gfsd677l5py7ccnlyd", inbound_service.clone()) { + _ => {} + } + service.close(); + loop {} } + Err(err) => println!("{:?}", err), } - } - - -} \ No newline at end of file +}