Browse Source

Cleaning up Authentication App

trunk
Sarah Jamie Lewis 2 years ago
parent
commit
4d89c33882
  1. 5
      Cargo.toml
  2. 14
      deps/rust-socks/src/lib.rs
  3. 50
      deps/rust-socks/src/v4.rs
  4. 162
      deps/rust-socks/src/v5.rs
  5. 24
      deps/rust-socks/src/writev.rs
  6. 66
      rustfmt.toml
  7. 2
      src/acns/mod.rs
  8. 14
      src/acns/tor/authentication.rs
  9. 93
      src/acns/tor/mod.rs
  10. 241
      src/applications/authentication_app.rs
  11. 1
      src/applications/mod.rs
  12. 44
      src/applications/transcript_app.rs
  13. 122
      src/connections/mod.rs
  14. 103
      src/connections/service.rs
  15. 65
      src/connections/utils.rs
  16. 3
      src/lib.rs
  17. 28
      src/primitives/identity.rs
  18. 2
      src/primitives/mod.rs
  19. 31
      src/primitives/transcript.rs
  20. 52
      tests/simple_setup.rs

5
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"
secretbox = "0.1.2"
subtle = "2.3.0"

14
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() {

50
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<SocketAddrV4> {
let mut response = [0u8; 8];
@ -17,14 +17,18 @@ fn read_response(socket: &mut TcpStream) -> io::Result<SocketAddrV4> {
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<T, U>(proxy: T, target: U, userid: &str) -> io::Result<Socks4Stream>
where T: ToSocketAddrs,
U: ToTargetAddr
where
T: ToSocketAddrs,
U: ToTargetAddr,
{
Self::connect_raw(1, proxy, target, userid)
}
fn connect_raw<T, U>(command: u8, proxy: T, target: U, userid: &str) -> io::Result<Socks4Stream>
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::<BigEndian>(addr.port());
@ -166,8 +171,9 @@ impl Socks4Listener {
/// The proxy will filter incoming connections based on the value of
/// `target`.
pub fn bind<T, U>(proxy: T, target: U, userid: &str) -> io::Result<Socks4Listener>
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()

162
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<R: Read>(socket: &mut R) -> io::Result<TargetAddr> {
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::<BigEndian>()?;
Ok(TargetAddr::Domain(domain, port))
}
@ -38,7 +36,6 @@ fn read_addr<R: Read>(socket: &mut R) -> io::Result<TargetAddr> {
}
fn read_response(socket: &mut TcpStream) -> io::Result<TargetAddr> {
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<usize> {
#[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<T, U>(proxy: T, target: U) -> io::Result<Socks5Stream>
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<T, U>(proxy: T, target: U, username: &str, password: &str) -> io::Result<Socks5Stream>
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<T, U>(command: u8, proxy: T, target: U, auth: &Authentication) -> io::Result<Socks5Stream>
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<T, U>(proxy: T, target: U) -> io::Result<Socks5Listener>
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<T, U>(proxy: T, target: U, username: &str, password: &str) -> io::Result<Socks5Listener>
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<T, U>(proxy: T, addr: U) -> io::Result<Socks5Datagram>
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<T, U>(proxy: T, addr: U, username: &str, password: &str) -> io::Result<Socks5Datagram>
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<T, U>(proxy: T, addr: U, auth: &Authentication) -> io::Result<Socks5Datagram>
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<A>(&self, buf: &[u8], addr: A) -> io::Result<usize>
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");
}

24
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 {

66
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

2
src/acns/mod.rs

@ -4,4 +4,4 @@ pub mod tor;
pub enum ACNError {
AuthenticationError(String),
ServiceSetupError(String),
}
}

14
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...
}
}
}
}

93
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<ConnectionStatus> {
conn: TcpStream,
status: ConnectionStatus
status: ConnectionStatus,
}
impl TorProcess<TorDisconnected> {
pub fn connect(control_port: u16) -> Result<TorProcess<TorConnected>, 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<TorDisconnected> {
/// the connection.
/// We are more strict as we only allow the caller to authenticate
impl TorProcess<TorConnected> {
pub fn authenticate(mut self, authMethod: Box<dyn TorAuthenticationMethod>) -> Result<TorProcess<TorAuthenticated>, ACNError> {
authMethod.authenticate(&mut self.conn);
pub fn authenticate(mut self, auth_method: Box<dyn TorAuthenticationMethod>) -> Result<TorProcess<TorAuthenticated>, 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<TorAuthenticated> {
/// 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<String,ACNError> {
pub fn add_onion_v3(&mut self, secret_key: ed25519_dalek::SecretKey, virtual_port: u16, target_port: u16) -> Result<String, ACNError> {
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),
}
}
}

241
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>
identity: Arc<Identity>,
}
struct AuthenticationSession<Direction> {
long_term_identity: Arc<Identity>,
ephemeral_identity: Identity,
local_auth_message_json: String,
remote_auth_message_json: String,
#[derive(Serialize, Deserialize, Debug)]
struct AuthMessage {
#[serde(rename(serialize = "LongTermPublicKey", deserialize = "LongTermPublicKey"))]
longterm_public_key: String,
#[serde(rename(serialize = "EphemeralPublicKey", deserialize = "EphemeralPublicKey"))]
ephemeral_public_key: String,
remote_long_term_identity: ed25519_dalek::PublicKey,
remote_ephemeral_identity: ed25519_dalek::PublicKey,
challenge: [u8; 64],
conn: Connection<Direction>,
}
impl AuthenicationApp {
pub fn new(identity: Arc<Identity>) -> AuthenicationApp {
AuthenicationApp {
identity
/// 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<Direction> AuthenticationSession<Direction> {
pub fn new_outbound(mut conn: Connection<OutboundConnection>, long_term_identity: Arc<Identity>) -> AuthenticationSession<OutboundConnection> {
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<InboundConnection>, long_term_identity: Arc<Identity>) -> AuthenticationSession<InboundConnection> {
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 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);
/// generate the message for the local challenge based a new transcript hash.
fn generate_challenge_message(&mut self) -> Vec<u8> {
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(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);
msg.extend_from_slice(self.challenge.as_slice());
msg.extend_from_slice(self.long_term_identity.hostname().as_bytes());
msg
}
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();
/// check the challenge from the remote
fn check_remote_challenge(&mut self, remote_challenge: Vec<u8>) -> 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;
}
}
let l2e = self.identity.edh(remote_ephemeral_key);
let e2l = ephemeral_identity.edh(remote_public_key);
let e2e = ephemeral_identity.edh(remote_ephemeral_key);
impl AuthenticationSession<InboundConnection> {
pub fn generate_challenge(&mut self, transcript: &mut Transcript) -> Vec<u8> {
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![];
// todo if outbound
// 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());
// todo else
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.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());
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()
}
}
let challenge_bytes = transcript.commit_to_transcript("3dh-auth-challenge");
println!("Calculated Challenge: {}", hex::encode(challenge_bytes));
impl AuthenticationSession<OutboundConnection> {
pub fn generate_challenge(&mut self, transcript: &mut Transcript) -> Vec<u8> {
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 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()));
// 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()
}
}
#[cfg(test)]
mod tests {
#[derive(Serialize, Deserialize, Debug)]
struct AuthMessage {
#[serde(rename(serialize = "LongTermPublicKey", deserialize = "LongTermPublicKey"))]
longterm_public_key: String,
#[serde(rename(serialize = "EphemeralPublicKey", deserialize = "EphemeralPublicKey"))]
ephemeral_public_key: String,
}
impl AuthenicationApp {
pub fn new(identity: Arc<Identity>) -> AuthenicationApp {
AuthenicationApp { identity }
}
pub fn run_outbound(&mut self, conn: &mut Connection<OutboundConnection>, transcript: &mut Transcript) {
let mut auth_session = AuthenticationSession::<OutboundConnection>::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();
}
}
#[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<InboundConnection>, transcript: &mut Transcript) {
let mut auth_session = AuthenticationSession::<InboundConnection>::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();
}
}
}

1
src/applications/mod.rs

@ -1,2 +1 @@
pub mod transcript_app;
pub mod authentication_app;

44
src/applications/transcript_app.rs

@ -1,44 +0,0 @@
use crate::primitives::transcript::Transcript;
use std::net::TcpStream;
use std::sync::Arc;
#[derive(Clone)]
pub struct TranscriptApp {
transcript: Option<Transcript>
}
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())
}
}

122
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 Connection<Direction> {
conn: TcpStream,
direction: Direction,
key: Vec<u8>,
}
impl<Direction> Connection<Direction>
where
Direction: Clone,
{
pub fn new_inbound(conn: TcpStream) -> Connection<InboundConnection> {
Connection {
conn,
direction: InboundConnection(()),
key: vec![],
}
}
pub fn new_outbound(conn: TcpStream) -> Connection<OutboundConnection> {
Connection {
conn,
direction: OutboundConnection(()),
key: vec![],
}
}
pub fn try_clone(&self) -> Connection<Direction> {
Connection {
conn: self.conn.try_clone().unwrap(),
direction: self.direction.clone(),
key: self.key.clone(),
}
}
pub struct Hostname {
pub fn enable_encryption(&mut self, key: Vec<u8>) {
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<u8>) {
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<u8> {
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<u8> {
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 {}

103
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<ListenService> {
identity: Arc<Identity>,
listen_service: ListenService
listen_service: ListenService,
}
impl<ListenService> Service<ListenService> {
pub fn connect<F>(&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<F>(&mut self, hostname: &str, application: F) -> Result<(), ServiceError>
where
F: FnOnce(Connection<InboundConnection>) + 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())