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

@ -20,4 +20,7 @@ sha2 = "0.9.2"
generic-array = "0.14.4"
typenum = "1.12.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<()> {
let number = self.sent_count.fetch_add(1, Ordering::SeqCst);
let nonce = generate_nonce(number);
let ciphertext = self.secret_box.lock().encrypt(
&nonce,
Payload {
@ -59,10 +60,14 @@ impl CryptoStream {
let mut length_raw = [0u8; 8];
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(&ciphertext)?;
stream.flush()?;
log::trace!("Event sent");
Ok(())
}
@ -75,6 +80,7 @@ impl CryptoStream {
let length = BigEndian::read_u64(&length_raw);
let mut ciphertext = vec![0u8; length as usize];
stream.read(&mut ciphertext)?;
log::trace!("Received raw message");
let number = self.recv_count.fetch_add(1, Ordering::SeqCst);
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 {
/// Creates a new Event with an empty payload
pub fn new(name: String) -> Self {
pub fn new<S: ToString>(name: S) -> Self {
Self {
name,
name: name.to_string(),
payload: Vec::with_capacity(0),
}
}
@ -34,9 +34,12 @@ impl Event {
impl Event {
/// 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();
Self { name, payload }
Self {
name: name.to_string(),
payload,
}
}
/// Returns the byte representation for the message

@ -14,6 +14,8 @@ pub enum VentedError {
CryptoError(crypto_box::aead::Error),
UnexpectedEvent(String),
UnknownNode(String),
Rejected,
AuthFailed,
}
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::NotReady => write!(f, "The connection is still being established."),
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::server::data::{Node, ServerConnectionContext};
use crate::server::server_events::{
AuthPayload, NodeInformationPayload, AUTH_EVENT, CONNECT_EVENT, CONN_ACCEPT_EVENT,
CONN_CHALLENGE_EVENT, CONN_REJECT_EVENT, READY_EVENT,
AuthPayload, ChallengePayload, NodeInformationPayload, ACCEPT_EVENT, AUTH_EVENT,
CHALLENGE_EVENT, CONNECT_EVENT, READY_EVENT, REJECT_EVENT,
};
use crossbeam_utils::sync::WaitGroup;
use parking_lot::Mutex;
@ -188,7 +188,13 @@ impl VentedServer {
let event_handler = Arc::clone(&params.event_handler);
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
.lock()
.handle_event(Event::new(READY_EVENT.to_string()));
@ -202,19 +208,19 @@ impl VentedServer {
Ok(())
}
/// Establishes a crypto stream for the given stream
fn get_crypto_stream(
params: ServerConnectionContext,
mut stream: TcpStream,
stream: TcpStream,
) -> VentedResult<CryptoStream> {
let (node_id, secret_box) = VentedServer::perform_key_exchange(
let (node_id, stream) = VentedServer::perform_key_exchange(
params.is_server,
&mut stream,
stream,
params.node_id.clone(),
params.global_secret,
params.known_nodes,
)?;
let stream = CryptoStream::new(stream, secret_box)?;
params
.connections
.lock()
@ -251,11 +257,11 @@ impl VentedServer {
/// Performs a key exchange
fn perform_key_exchange(
is_server: bool,
stream: &mut TcpStream,
stream: TcpStream,
own_node_id: String,
global_secret: SecretKey,
known_nodes: Arc<Mutex<Vec<Node>>>,
) -> VentedResult<(String, ChaChaBox)> {
) -> VentedResult<(String, CryptoStream)> {
let secret_key = SecretKey::generate(&mut rand::thread_rng());
if is_server {
Self::perform_server_key_exchange(
@ -278,15 +284,15 @@ impl VentedServer {
/// Performs the client side DH key exchange
fn perform_client_key_exchange(
mut stream: &mut TcpStream,
mut stream: TcpStream,
secret_key: &SecretKey,
own_node_id: String,
global_secret: SecretKey,
known_nodes: Arc<Mutex<Vec<Node>>>,
) -> VentedResult<(String, ChaChaBox)> {
) -> VentedResult<(String, CryptoStream)> {
stream.write(
&Event::with_payload(
CONNECT_EVENT.to_string(),
CONNECT_EVENT,
&NodeInformationPayload {
public_key: secret_key.public_key().to_bytes(),
node_id: own_node_id,
@ -296,59 +302,45 @@ impl VentedServer {
)?;
stream.flush()?;
let event = Event::from_bytes(&mut stream)?;
if event.name != CONN_CHALLENGE_EVENT {
return Err(VentedError::UnknownNode(event.name));
if event.name != CONNECT_EVENT {
return Err(VentedError::UnexpectedEvent(event.name));
}
let NodeInformationPayload {
public_key,
node_id,
} = 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(
&Event::with_payload(
AUTH_EVENT.to_string(),
&AuthPayload {
calculated_secret: shared_auth_secret.to_bytes(),
},
)
.as_bytes(),
)?;
let public_key = PublicKey::from(public_key);
let secret_box = ChaChaBox::new(&public_key, &secret_key);
let event = Event::from_bytes(&mut stream)?;
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)
{
let node_data = if let Some(data) = known_nodes.lock().iter().find(|n| n.id == node_id) {
data.clone()
} else {
stream.write(&Event::new(REJECT_EVENT).as_bytes())?;
stream.flush()?;
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
/// On success it returns a secret box with the established secret and the node id of the client
fn perform_server_key_exchange(
mut stream: &mut TcpStream,
mut stream: TcpStream,
secret_key: &SecretKey,
own_node_id: String,
global_secret: SecretKey,
known_nodes: Arc<Mutex<Vec<Node>>>,
) -> VentedResult<(String, ChaChaBox)> {
) -> VentedResult<(String, CryptoStream)> {
let event = Event::from_bytes(&mut stream)?;
if event.name != CONNECT_EVENT {
return Err(VentedError::UnexpectedEvent(event.name));
@ -359,54 +351,96 @@ impl VentedServer {
} = event.get_payload::<NodeInformationPayload>().unwrap();
let public_key = PublicKey::from(public_key);
let known_nodes = known_nodes.lock();
let node_data = if let Some(data) = known_nodes.iter().find(|n| n.id == node_id) {
let node_data = if let Some(data) = known_nodes.lock().iter().find(|n| n.id == node_id) {
data.clone()
} else {
stream.write(&Event::new(CONN_REJECT_EVENT.to_string()).as_bytes())?;
stream.write(&Event::new(REJECT_EVENT).as_bytes())?;
stream.flush()?;
return Err(UnknownNode(node_id));
};
let secret_box = ChaChaBox::new(&public_key, &secret_key);
stream.write(
&Event::with_payload(
CONN_CHALLENGE_EVENT.to_string(),
CONNECT_EVENT,
&NodeInformationPayload {
public_key: secret_key.public_key().to_bytes(),
node_id: own_node_id.clone(),
node_id: own_node_id,
},
)
.as_bytes(),
)?;
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 {
stream.send(Event::new(REJECT_EVENT))?;
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 =
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 {
stream.write(&Event::new(CONN_REJECT_EVENT.to_string()).as_bytes())?;
stream.flush()?;
return Err(UnknownNode(node_id));
stream.send(Event::new(REJECT_EVENT))?;
Err(VentedError::AuthFailed)
} else {
stream.write(
&Event::with_payload(
CONN_ACCEPT_EVENT.to_string(),
&NodeInformationPayload {
node_id: own_node_id,
public_key: global_secret.public_key().to_bytes(),
},
)
.as_bytes(),
)?;
stream.flush()?;
stream.send(Event::new(ACCEPT_EVENT))?;
Ok(())
}
Ok((node_id, secret_box))
}
}

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

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

Loading…
Cancel
Save