Change auth to two-sided encrypted authentication

Signed-off-by: trivernis <trivernis@protonmail.com>
pull/1/head
trivernis 4 years ago
parent da6cd1faaf
commit 6f6142982b
Signed by: Trivernis
GPG Key ID: DFFFCC2C7A02DB45

@ -21,3 +21,6 @@ generic-array = "0.14.4"
typenum = "1.12.0" typenum = "1.12.0"
x25519-dalek = "1.1.0" x25519-dalek = "1.1.0"
crossbeam-utils = "0.8.0" crossbeam-utils = "0.8.0"
[dev-dependencies]
simple_logger = "1.11.0"

@ -48,6 +48,7 @@ impl CryptoStream {
pub fn send(&self, mut event: Event) -> VentedResult<()> { pub fn send(&self, mut event: Event) -> VentedResult<()> {
let number = self.sent_count.fetch_add(1, Ordering::SeqCst); let number = self.sent_count.fetch_add(1, Ordering::SeqCst);
let nonce = generate_nonce(number); let nonce = generate_nonce(number);
let ciphertext = self.secret_box.lock().encrypt( let ciphertext = self.secret_box.lock().encrypt(
&nonce, &nonce,
Payload { Payload {
@ -59,10 +60,14 @@ impl CryptoStream {
let mut length_raw = [0u8; 8]; let mut length_raw = [0u8; 8];
BigEndian::write_u64(&mut length_raw, ciphertext.len() as u64); BigEndian::write_u64(&mut length_raw, ciphertext.len() as u64);
log::trace!("Encoded event '{}' to raw message", event.name);
stream.write(&length_raw)?; stream.write(&length_raw)?;
stream.write(&ciphertext)?; stream.write(&ciphertext)?;
stream.flush()?; stream.flush()?;
log::trace!("Event sent");
Ok(()) Ok(())
} }
@ -75,6 +80,7 @@ impl CryptoStream {
let length = BigEndian::read_u64(&length_raw); let length = BigEndian::read_u64(&length_raw);
let mut ciphertext = vec![0u8; length as usize]; let mut ciphertext = vec![0u8; length as usize];
stream.read(&mut ciphertext)?; stream.read(&mut ciphertext)?;
log::trace!("Received raw message");
let number = self.recv_count.fetch_add(1, Ordering::SeqCst); let number = self.recv_count.fetch_add(1, Ordering::SeqCst);
let nonce = generate_nonce(number); let nonce = generate_nonce(number);
@ -86,7 +92,10 @@ impl CryptoStream {
}, },
)?; )?;
Event::from_bytes(&mut &plaintext[..]) let event = Event::from_bytes(&mut &plaintext[..])?;
log::trace!("Decoded message to event '{}'", event.name);
Ok(event)
} }
} }

@ -24,9 +24,9 @@ pub struct Event {
impl Event { impl Event {
/// Creates a new Event with an empty payload /// Creates a new Event with an empty payload
pub fn new(name: String) -> Self { pub fn new<S: ToString>(name: S) -> Self {
Self { Self {
name, name: name.to_string(),
payload: Vec::with_capacity(0), payload: Vec::with_capacity(0),
} }
} }
@ -34,9 +34,12 @@ impl Event {
impl Event { impl Event {
/// Creates a new Event with a payload /// Creates a new Event with a payload
pub fn with_payload<T: Serialize>(name: String, payload: &T) -> Self { pub fn with_payload<T: Serialize, S: ToString>(name: S, payload: &T) -> Self {
let payload = rmp_serde::encode::to_vec(payload).unwrap(); let payload = rmp_serde::encode::to_vec(payload).unwrap();
Self { name, payload } Self {
name: name.to_string(),
payload,
}
} }
/// Returns the byte representation for the message /// Returns the byte representation for the message

@ -14,6 +14,8 @@ pub enum VentedError {
CryptoError(crypto_box::aead::Error), CryptoError(crypto_box::aead::Error),
UnexpectedEvent(String), UnexpectedEvent(String),
UnknownNode(String), UnknownNode(String),
Rejected,
AuthFailed,
} }
impl fmt::Display for VentedError { impl fmt::Display for VentedError {
@ -28,6 +30,8 @@ impl fmt::Display for VentedError {
Self::UnknownNode(n) => write!(f, "Received connection from unknown node: {}", n), Self::UnknownNode(n) => write!(f, "Received connection from unknown node: {}", n),
Self::NotReady => write!(f, "The connection is still being established."), Self::NotReady => write!(f, "The connection is still being established."),
Self::NotAServer(n) => write!(f, "The given node {} is not a server", n), Self::NotAServer(n) => write!(f, "The given node {} is not a server", n),
Self::Rejected => write!(f, "The connection was rejected"),
Self::AuthFailed => write!(f, "Failed to authenticate the other party"),
} }
} }
} }

@ -11,8 +11,8 @@ use crate::result::VentedError::UnknownNode;
use crate::result::{VentedError, VentedResult}; use crate::result::{VentedError, VentedResult};
use crate::server::data::{Node, ServerConnectionContext}; use crate::server::data::{Node, ServerConnectionContext};
use crate::server::server_events::{ use crate::server::server_events::{
AuthPayload, NodeInformationPayload, AUTH_EVENT, CONNECT_EVENT, CONN_ACCEPT_EVENT, AuthPayload, ChallengePayload, NodeInformationPayload, ACCEPT_EVENT, AUTH_EVENT,
CONN_CHALLENGE_EVENT, CONN_REJECT_EVENT, READY_EVENT, CHALLENGE_EVENT, CONNECT_EVENT, READY_EVENT, REJECT_EVENT,
}; };
use crossbeam_utils::sync::WaitGroup; use crossbeam_utils::sync::WaitGroup;
use parking_lot::Mutex; use parking_lot::Mutex;
@ -188,7 +188,13 @@ impl VentedServer {
let event_handler = Arc::clone(&params.event_handler); let event_handler = Arc::clone(&params.event_handler);
pool.lock().execute(move || { pool.lock().execute(move || {
let stream = VentedServer::get_crypto_stream(params, stream).expect("Listener failed"); let stream = match VentedServer::get_crypto_stream(params, stream) {
Ok(stream) => stream,
Err(e) => {
log::error!("Failed to establish encrypted connection: {}", e);
return;
}
};
event_handler event_handler
.lock() .lock()
.handle_event(Event::new(READY_EVENT.to_string())); .handle_event(Event::new(READY_EVENT.to_string()));
@ -202,19 +208,19 @@ impl VentedServer {
Ok(()) Ok(())
} }
/// Establishes a crypto stream for the given stream
fn get_crypto_stream( fn get_crypto_stream(
params: ServerConnectionContext, params: ServerConnectionContext,
mut stream: TcpStream, stream: TcpStream,
) -> VentedResult<CryptoStream> { ) -> VentedResult<CryptoStream> {
let (node_id, secret_box) = VentedServer::perform_key_exchange( let (node_id, stream) = VentedServer::perform_key_exchange(
params.is_server, params.is_server,
&mut stream, stream,
params.node_id.clone(), params.node_id.clone(),
params.global_secret, params.global_secret,
params.known_nodes, params.known_nodes,
)?; )?;
let stream = CryptoStream::new(stream, secret_box)?;
params params
.connections .connections
.lock() .lock()
@ -251,11 +257,11 @@ impl VentedServer {
/// Performs a key exchange /// Performs a key exchange
fn perform_key_exchange( fn perform_key_exchange(
is_server: bool, is_server: bool,
stream: &mut TcpStream, stream: TcpStream,
own_node_id: String, own_node_id: String,
global_secret: SecretKey, global_secret: SecretKey,
known_nodes: Arc<Mutex<Vec<Node>>>, known_nodes: Arc<Mutex<Vec<Node>>>,
) -> VentedResult<(String, ChaChaBox)> { ) -> VentedResult<(String, CryptoStream)> {
let secret_key = SecretKey::generate(&mut rand::thread_rng()); let secret_key = SecretKey::generate(&mut rand::thread_rng());
if is_server { if is_server {
Self::perform_server_key_exchange( Self::perform_server_key_exchange(
@ -278,15 +284,15 @@ impl VentedServer {
/// Performs the client side DH key exchange /// Performs the client side DH key exchange
fn perform_client_key_exchange( fn perform_client_key_exchange(
mut stream: &mut TcpStream, mut stream: TcpStream,
secret_key: &SecretKey, secret_key: &SecretKey,
own_node_id: String, own_node_id: String,
global_secret: SecretKey, global_secret: SecretKey,
known_nodes: Arc<Mutex<Vec<Node>>>, known_nodes: Arc<Mutex<Vec<Node>>>,
) -> VentedResult<(String, ChaChaBox)> { ) -> VentedResult<(String, CryptoStream)> {
stream.write( stream.write(
&Event::with_payload( &Event::with_payload(
CONNECT_EVENT.to_string(), CONNECT_EVENT,
&NodeInformationPayload { &NodeInformationPayload {
public_key: secret_key.public_key().to_bytes(), public_key: secret_key.public_key().to_bytes(),
node_id: own_node_id, node_id: own_node_id,
@ -296,59 +302,45 @@ impl VentedServer {
)?; )?;
stream.flush()?; stream.flush()?;
let event = Event::from_bytes(&mut stream)?; let event = Event::from_bytes(&mut stream)?;
if event.name != CONN_CHALLENGE_EVENT { if event.name != CONNECT_EVENT {
return Err(VentedError::UnknownNode(event.name)); return Err(VentedError::UnexpectedEvent(event.name));
} }
let NodeInformationPayload { let NodeInformationPayload {
public_key, public_key,
node_id, node_id,
} = event.get_payload::<NodeInformationPayload>().unwrap(); } = event.get_payload::<NodeInformationPayload>().unwrap();
let public_key = PublicKey::from(public_key);
let shared_auth_secret =
StaticSecret::from(global_secret.to_bytes()).diffie_hellman(&public_key);
stream.write( let public_key = PublicKey::from(public_key);
&Event::with_payload( let secret_box = ChaChaBox::new(&public_key, &secret_key);
AUTH_EVENT.to_string(),
&AuthPayload {
calculated_secret: shared_auth_secret.to_bytes(),
},
)
.as_bytes(),
)?;
let event = Event::from_bytes(&mut stream)?; let node_data = if let Some(data) = known_nodes.lock().iter().find(|n| n.id == node_id) {
if event.name != CONN_ACCEPT_EVENT {
return Err(VentedError::UnknownNode(event.name));
}
let known_nodes = known_nodes.lock();
let node_static_info = event.get_payload::<NodeInformationPayload>()?;
let node_data = if let Some(data) = known_nodes
.iter()
.find(|n| n.id == node_static_info.node_id)
{
data.clone() data.clone()
} else { } else {
stream.write(&Event::new(REJECT_EVENT).as_bytes())?;
stream.flush()?;
return Err(UnknownNode(node_id)); return Err(UnknownNode(node_id));
}; };
if node_data.public_key.to_bytes() != node_static_info.public_key {
return Err(UnknownNode(node_id));
}
let secret_box = ChaChaBox::new(&public_key, &secret_key); let mut stream = CryptoStream::new(stream, secret_box)?;
Ok((node_id, secret_box)) log::trace!("Authenticating recipient...");
Self::authenticate_other(&mut stream, node_data.public_key)?;
log::trace!("Authenticating self...");
Self::authenticate_self(&mut stream, StaticSecret::from(global_secret.to_bytes()))?;
log::trace!("Connection fully authenticated.");
Ok((node_id, stream))
} }
/// Performs a DH key exchange by using the crypto_box module and events /// Performs a DH key exchange by using the crypto_box module and events
/// On success it returns a secret box with the established secret and the node id of the client /// On success it returns a secret box with the established secret and the node id of the client
fn perform_server_key_exchange( fn perform_server_key_exchange(
mut stream: &mut TcpStream, mut stream: TcpStream,
secret_key: &SecretKey, secret_key: &SecretKey,
own_node_id: String, own_node_id: String,
global_secret: SecretKey, global_secret: SecretKey,
known_nodes: Arc<Mutex<Vec<Node>>>, known_nodes: Arc<Mutex<Vec<Node>>>,
) -> VentedResult<(String, ChaChaBox)> { ) -> VentedResult<(String, CryptoStream)> {
let event = Event::from_bytes(&mut stream)?; let event = Event::from_bytes(&mut stream)?;
if event.name != CONNECT_EVENT { if event.name != CONNECT_EVENT {
return Err(VentedError::UnexpectedEvent(event.name)); return Err(VentedError::UnexpectedEvent(event.name));
@ -359,54 +351,96 @@ impl VentedServer {
} = event.get_payload::<NodeInformationPayload>().unwrap(); } = event.get_payload::<NodeInformationPayload>().unwrap();
let public_key = PublicKey::from(public_key); let public_key = PublicKey::from(public_key);
let known_nodes = known_nodes.lock(); let node_data = if let Some(data) = known_nodes.lock().iter().find(|n| n.id == node_id) {
let node_data = if let Some(data) = known_nodes.iter().find(|n| n.id == node_id) {
data.clone() data.clone()
} else { } else {
stream.write(&Event::new(CONN_REJECT_EVENT.to_string()).as_bytes())?; stream.write(&Event::new(REJECT_EVENT).as_bytes())?;
stream.flush()?; stream.flush()?;
return Err(UnknownNode(node_id)); return Err(UnknownNode(node_id));
}; };
let secret_box = ChaChaBox::new(&public_key, &secret_key);
stream.write( stream.write(
&Event::with_payload( &Event::with_payload(
CONN_CHALLENGE_EVENT.to_string(), CONNECT_EVENT,
&NodeInformationPayload { &NodeInformationPayload {
public_key: secret_key.public_key().to_bytes(), public_key: secret_key.public_key().to_bytes(),
node_id: own_node_id.clone(), node_id: own_node_id,
}, },
) )
.as_bytes(), .as_bytes(),
)?; )?;
stream.flush()?; stream.flush()?;
let auth_event = Event::from_bytes(&mut stream)?; let secret_box = ChaChaBox::new(&public_key, &secret_key);
let mut stream = CryptoStream::new(stream, secret_box)?;
log::trace!("Authenticating self...");
Self::authenticate_self(&mut stream, StaticSecret::from(global_secret.to_bytes()))?;
log::trace!("Authenticating recipient...");
Self::authenticate_other(&mut stream, node_data.public_key)?;
log::trace!("Connection fully authenticated.");
Ok((node_id, stream))
}
/// Performs the challenged side of the authentication challenge
fn authenticate_self(stream: &CryptoStream, static_secret: StaticSecret) -> VentedResult<()> {
let challenge_event = stream.read()?;
if challenge_event.name != CHALLENGE_EVENT {
stream.send(Event::new(REJECT_EVENT))?;
return Err(VentedError::UnexpectedEvent(challenge_event.name));
}
let ChallengePayload { public_key } = challenge_event.get_payload()?;
let auth_key = static_secret.diffie_hellman(&PublicKey::from(public_key));
stream.send(Event::with_payload(
AUTH_EVENT,
&AuthPayload {
calculated_secret: auth_key.to_bytes(),
},
))?;
let response = stream.read()?;
match response.name.as_str() {
ACCEPT_EVENT => Ok(()),
REJECT_EVENT => Err(VentedError::Rejected),
_ => {
stream.send(Event::new(REJECT_EVENT))?;
Err(VentedError::UnexpectedEvent(response.name))
}
}
}
/// Authenticates the other party by using their stored public key and a generated secret
fn authenticate_other(
stream: &CryptoStream,
other_static_public: PublicKey,
) -> VentedResult<()> {
let auth_secret = SecretKey::generate(&mut rand::thread_rng());
stream.send(Event::with_payload(
CHALLENGE_EVENT,
&ChallengePayload {
public_key: auth_secret.public_key().to_bytes(),
},
))?;
let auth_event = stream.read()?;
if auth_event.name != AUTH_EVENT { if auth_event.name != AUTH_EVENT {
stream.send(Event::new(REJECT_EVENT))?;
return Err(VentedError::UnexpectedEvent(auth_event.name)); return Err(VentedError::UnexpectedEvent(auth_event.name));
} }
let AuthPayload { calculated_secret } = auth_event.get_payload::<AuthPayload>()?; let AuthPayload { calculated_secret } = auth_event.get_payload()?;
let expected_secret = let expected_secret =
StaticSecret::from(secret_key.to_bytes()).diffie_hellman(&node_data.public_key); StaticSecret::from(auth_secret.to_bytes()).diffie_hellman(&other_static_public);
if expected_secret.to_bytes() != calculated_secret { if expected_secret.to_bytes() != calculated_secret {
stream.write(&Event::new(CONN_REJECT_EVENT.to_string()).as_bytes())?; stream.send(Event::new(REJECT_EVENT))?;
stream.flush()?; Err(VentedError::AuthFailed)
return Err(UnknownNode(node_id));
} else { } else {
stream.write( stream.send(Event::new(ACCEPT_EVENT))?;
&Event::with_payload( Ok(())
CONN_ACCEPT_EVENT.to_string(),
&NodeInformationPayload {
node_id: own_node_id,
public_key: global_secret.public_key().to_bytes(),
},
)
.as_bytes(),
)?;
stream.flush()?;
} }
Ok((node_id, secret_box))
} }
} }

@ -1,10 +1,10 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
pub(crate) const CONNECT_EVENT: &str = "client:connect"; pub(crate) const CONNECT_EVENT: &str = "conn:connect";
pub(crate) const AUTH_EVENT: &str = "client:authenticate"; pub(crate) const AUTH_EVENT: &str = "conn:authenticate";
pub(crate) const CONN_CHALLENGE_EVENT: &str = "server:conn_challenge"; pub(crate) const CHALLENGE_EVENT: &str = "conn:challenge";
pub(crate) const CONN_ACCEPT_EVENT: &str = "server:conn_accept"; pub(crate) const ACCEPT_EVENT: &str = "conn:accept";
pub(crate) const CONN_REJECT_EVENT: &str = "server:conn_reject"; pub(crate) const REJECT_EVENT: &str = "conn:reject";
pub const READY_EVENT: &str = "connection:ready"; pub const READY_EVENT: &str = "connection:ready";
@ -14,6 +14,11 @@ pub(crate) struct NodeInformationPayload {
pub public_key: [u8; 32], pub public_key: [u8; 32],
} }
#[derive(Serialize, Deserialize, Debug)]
pub(crate) struct ChallengePayload {
pub public_key: [u8; 32],
}
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub(crate) struct AuthPayload { pub(crate) struct AuthPayload {
pub calculated_secret: [u8; 32], pub calculated_secret: [u8; 32],

@ -8,8 +8,13 @@ use vented::server::data::Node;
use vented::server::server_events::READY_EVENT; use vented::server::server_events::READY_EVENT;
use vented::server::VentedServer; use vented::server::VentedServer;
fn setup() {
simple_logger::SimpleLogger::new().init().unwrap();
}
#[test] #[test]
fn test_server_communication() { fn test_server_communication() {
setup();
let ping_count = Arc::new(AtomicUsize::new(0)); let ping_count = Arc::new(AtomicUsize::new(0));
let pong_count = Arc::new(AtomicUsize::new(0)); let pong_count = Arc::new(AtomicUsize::new(0));
let ready_count = Arc::new(AtomicUsize::new(0)); let ready_count = Arc::new(AtomicUsize::new(0));

Loading…
Cancel
Save