Cleaning up Authentication App
This commit is contained in:
parent
a97f5ce12c
commit
4d89c33882
|
@ -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"
|
|
@ -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() {
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
|
@ -4,4 +4,4 @@ pub mod tor;
|
|||
pub enum ACNError {
|
||||
AuthenticationError(String),
|
||||
ServiceSetupError(String),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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...
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
||||
remote_long_term_identity: ed25519_dalek::PublicKey,
|
||||
remote_ephemeral_identity: ed25519_dalek::PublicKey,
|
||||
|
||||
challenge: [u8; 64],
|
||||
|
||||
conn: Connection<Direction>,
|
||||
}
|
||||
|
||||
/// 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],
|
||||
}
|
||||
}
|
||||
|
||||
/// 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(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<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;
|
||||
}
|
||||
}
|
||||
|
||||
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![];
|
||||
// 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<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);
|
||||
|
||||
// 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<Identity>) -> AuthenicationApp {
|
||||
AuthenicationApp {
|
||||
identity
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
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<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,2 +1 @@
|
|||
pub mod transcript_app;
|
||||
pub mod authentication_app;
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
|
@ -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<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 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 {}
|
||||
|
|
|
@ -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())
|
||||
});
|
||||
spawn(move || application(Connection::<InboundConnection>::new_inbound(conn.into_inner())));
|
||||
Ok(())
|
||||
},
|
||||
Err(err) => {
|
||||
Err(ConnectionFailed(err.to_string()))
|
||||
}
|
||||
Err(err) => Err(ConnectionFailed(err.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl Service<NoListenService> {
|
||||
pub fn init(identity: Arc<Identity>) -> Service<NoListenService> {
|
||||
Service {
|
||||
identity,
|
||||
listen_service: NoListenService(())
|
||||
listen_service: NoListenService(()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn listen<F>(mut self, port: u16, application: F) -> Result<Service<JoinHandle<ServiceError>>, 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<F>(self, port: u16, application: F) -> Result<Service<JoinHandle<ServiceError>>, ServiceError>
|
||||
where
|
||||
F: FnOnce(Connection<OutboundConnection>) + 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::<OutboundConnection>::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<JoinHandle<ServiceError>> {
|
|||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<u8> {
|
||||
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<u8>) {
|
||||
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<u8>) {
|
||||
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<u8> {
|
||||
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()
|
||||
}
|
|
@ -6,9 +6,8 @@
|
|||
|
||||
pub mod acns;
|
||||
pub mod applications;
|
||||
pub mod primitives;
|
||||
pub mod connections;
|
||||
|
||||
pub mod primitives;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
|
|
@ -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<Identity> {
|
||||
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)]
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
pub mod identity;
|
||||
pub mod transcript;
|
||||
pub mod transcript;
|
||||
|
|
|
@ -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"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<OutboundConnection>| {
|
||||
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<InboundConnection>| {
|
||||
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),
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue