diff --git a/Cargo.lock b/Cargo.lock index 249f08a..2b0503b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -172,7 +172,9 @@ dependencies = [ "log", "rand", "rand_core", + "serde", "tokio", + "toml", "x25519-dalek", ] @@ -300,6 +302,12 @@ dependencies = [ "log", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "fiat-crypto" version = "0.2.9" @@ -343,6 +351,12 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" + [[package]] name = "hermit-abi" version = "0.3.9" @@ -355,6 +369,16 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +[[package]] +name = "indexmap" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" +dependencies = [ + "equivalent", + "hashbrown", +] + [[package]] name = "inout" version = "0.1.3" @@ -623,6 +647,15 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +dependencies = [ + "serde", +] + [[package]] name = "signal-hook-registry" version = "1.4.2" @@ -694,6 +727,40 @@ dependencies = [ "syn", ] +[[package]] +name = "toml" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "typenum" version = "1.17.0" @@ -882,6 +949,15 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "winnow" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +dependencies = [ + "memchr", +] + [[package]] name = "x25519-dalek" version = "2.0.1" diff --git a/Cargo.toml b/Cargo.toml index 2092a1e..39cbe66 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,3 +13,5 @@ rand = "0.8.5" rand_core = "0.6.4" crypto = "0.5.1" base64 = "0.21" +serde = { version = "1.0", features = ["derive"] } +toml = "0.8.19" \ No newline at end of file diff --git a/config.toml b/config.toml new file mode 100644 index 0000000..41ae0ce --- /dev/null +++ b/config.toml @@ -0,0 +1,2 @@ +address = "127.0.0.1" +port = "25565" \ No newline at end of file diff --git a/src/client/mod.rs b/src/client/mod.rs new file mode 100644 index 0000000..84830a3 --- /dev/null +++ b/src/client/mod.rs @@ -0,0 +1,157 @@ +pub(crate) mod handlers { + use aes_gcm::{ + aead::{Aead, KeyInit, OsRng}, + Aes256Gcm, Key, Nonce, + }; + + use base64::{engine::general_purpose::STANDARD as BASE64, Engine as _}; + use log::{debug, error, info}; + use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader}; + use tokio::net::TcpStream; + use tokio::sync::broadcast; + use x25519_dalek::{EphemeralSecret, PublicKey}; + + pub async fn handle_client( + socket: TcpStream, + tx: broadcast::Sender, + mut rx: broadcast::Receiver, + ) -> Result<(), Box> { + let (reader, mut writer) = socket.into_split(); + let mut reader = BufReader::new(reader); + let mut line = String::new(); + + let server_secret = EphemeralSecret::random_from_rng(OsRng); + let server_public = PublicKey::from(&server_secret); + + // Send the server's public key to the client + writer.write_all(server_public.as_bytes()).await?; + + // Receive the client's public key + let mut client_public_bytes = [0u8; 32]; + reader.read_exact(&mut client_public_bytes).await?; + let client_public = PublicKey::from(client_public_bytes); + + // Compute the shared secret + let shared_secret = server_secret.diffie_hellman(&client_public); + + let key = Key::::from_slice(shared_secret.as_bytes()); + + let cipher_reader = Aes256Gcm::new(&key); + let cipher_writer = Aes256Gcm::new(&key); + let nonce_reader = Nonce::from_slice(b"unique nonce"); // 96-bits; fixed nonce + let nonce_writer = nonce_reader.clone(); + + debug!("Reciving Username"); + + // Read the username from the client + line.clear(); + reader.read_line(&mut line).await?; + let decoded = BASE64.decode(line.trim().as_bytes())?; + let decrypted = cipher_reader + .decrypt(&nonce_reader, decoded.as_ref()) + .unwrap(); + let username = String::from_utf8(decrypted)?; + let username_read = username.clone(); // Clone for read task + let username_write = username.clone(); // Clone for write task + + // Read task for receiving messages from the client + let read_task = tokio::spawn(async move { + loop { + line.clear(); + match reader.read_line(&mut line).await { + Ok(bytes_read) => { + if bytes_read == 0 { + info!("Client disconnected"); + break; + } + + let decoded = match BASE64.decode(line.trim().as_bytes()) { + Ok(decoded) => decoded, + Err(e) => { + error!("Base64 decode error: {:?}", e); + continue; + } + }; + + let decrypted = match cipher_reader.decrypt(&nonce_reader, decoded.as_ref()) + { + Ok(decrypted) => decrypted, + Err(e) => { + error!("Decryption error: {:?}", e); + continue; + } + }; + + let message = match String::from_utf8(decrypted) { + Ok(msg) => msg, + Err(e) => { + error!("UTF-8 conversion error: {:?}", e); + continue; + } + }; + + info!( + "Received message from {}: {}", + username_read, + message.trim() + ); + + if message.trim() == "/quit" { + info!("Client requested to quit"); + break; + } + + let formatted_message = format!("{}: {}", username_read, message.trim()); + + // Broadcast the message to all clients + match tx.send(formatted_message) { + Ok(_) => info!("Message broadcast successfully"), + Err(e) => { + error!("Failed to broadcast message: {:?}", e); + break; + } + } + } + Err(e) => { + error!("Error reading from client: {:?}", e); + break; + } + } + } + }); + + // Write task for sending messages to the client + let write_task = tokio::spawn(async move { + while let Ok(msg) = rx.recv().await { + if !msg.is_empty() { + // Encrypt the message with error handling + let encrypted = match cipher_writer.encrypt(&nonce_writer, msg.as_bytes()) { + Ok(encrypted) => encrypted, + Err(e) => { + error!("Encryption error: {:?}", e); + continue; + } + }; + + // Base64 encode and format with newline + let message = format!("{}\n", BASE64.encode(&encrypted)); + + // Write with proper error handling + if let Err(e) = writer.write_all(message.as_bytes()).await { + error!("Failed to send message: {:?}", e); + break; + } + } + } + }); + + // Wait for both tasks to complete + tokio::select! { + _ = read_task => (), + _ = write_task => (), + } + + info!("Client handling completed"); + Ok(()) + } +} diff --git a/src/main.rs b/src/main.rs index a68c51e..5c1dabd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,22 +1,43 @@ -use aes_gcm::{ - aead::{Aead, KeyInit, OsRng}, - Aes256Gcm, Key, Nonce, -}; -use base64::{engine::general_purpose::STANDARD as BASE64, Engine as _}; +mod client; +use client::handlers::handle_client; + use colog; use log::{error, info}; -use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader}; -use tokio::net::{TcpListener, TcpStream}; +use serde::Deserialize; +use tokio::net::TcpListener; use tokio::sync::broadcast; -use x25519_dalek::{EphemeralSecret, PublicKey}; + +#[derive(Deserialize, Debug)] +struct Config { + address: String, + port: String, +} #[tokio::main] async fn main() { // Initialize the logger colog::init(); + // Load the configuration from config file + let config = match std::fs::read_to_string("config.toml") { + Ok(config) => match toml::from_str::(&config) { + Ok(config) => config, + Err(e) => { + error!("Failed to parse config file: {:?}", e); + std::process::exit(1); + } + }, + Err(e) => { + error!("Failed to read config file: {:?}", e); + std::process::exit(1); + } + }; + + info!("Configuration loaded: {:?}", config); // Bind a TCP listener to accept incoming connections - let listener = TcpListener::bind("0.0.0.0:8080").await.unwrap(); + let listener = TcpListener::bind(config.address + ":" + config.port.as_str()) + .await + .unwrap(); info!("Server running on port 8080"); // Create a broadcast channel for sharing messages @@ -38,126 +59,3 @@ async fn main() { }); } } - -async fn handle_client( - socket: TcpStream, - tx: broadcast::Sender, - mut rx: broadcast::Receiver, -) -> Result<(), Box> { - let (reader, mut writer) = socket.into_split(); - let mut reader = BufReader::new(reader); - let mut line = String::new(); - - let server_secret = EphemeralSecret::random_from_rng(OsRng); - let server_public = PublicKey::from(&server_secret); - - // Send the server's public key to the client - writer.write_all(server_public.as_bytes()).await?; - - // Receive the client's public key - let mut client_public_bytes = [0u8; 32]; - reader.read_exact(&mut client_public_bytes).await?; - let client_public = PublicKey::from(client_public_bytes); - - // Compute the shared secret - let shared_secret = server_secret.diffie_hellman(&client_public); - - let key = Key::::from_slice(shared_secret.as_bytes()); - - let cipher_reader = Aes256Gcm::new(&key); - let cipher_writer = Aes256Gcm::new(&key); - let nonce_reader = Nonce::from_slice(b"unique nonce"); // 96-bits; fixed nonce - let nonce_writer = nonce_reader.clone(); - - // Read task for receiving messages from the client - let read_task = tokio::spawn(async move { - loop { - line.clear(); - match reader.read_line(&mut line).await { - Ok(bytes_read) => { - if bytes_read == 0 { - info!("Client disconnected"); - break; - } - - let decoded = match BASE64.decode(line.trim().as_bytes()) { - Ok(decoded) => decoded, - Err(e) => { - error!("Base64 decode error: {:?}", e); - continue; - } - }; - - let decrypted = match cipher_reader.decrypt(&nonce_reader, decoded.as_ref()) { - Ok(decrypted) => decrypted, - Err(e) => { - error!("Decryption error: {:?}", e); - continue; - } - }; - - let message = match String::from_utf8(decrypted) { - Ok(msg) => msg, - Err(e) => { - error!("UTF-8 conversion error: {:?}", e); - continue; - } - }; - - info!("Received message: {}", message.trim()); - - if message.trim() == "/quit" { - info!("Client requested to quit"); - break; - } - - // Broadcast the message to all clients - match tx.send(message) { - Ok(_) => info!("Message broadcast successfully"), - Err(e) => { - error!("Failed to broadcast message: {:?}", e); - break; - } - } - } - Err(e) => { - error!("Error reading from client: {:?}", e); - break; - } - } - } - }); - - // Write task for sending messages to the client - let write_task = tokio::spawn(async move { - while let Ok(msg) = rx.recv().await { - if !msg.is_empty() { - // Encrypt the message - let encrypted = match cipher_writer.encrypt(&nonce_writer, msg.as_bytes()) { - Ok(encrypted) => encrypted, - Err(e) => { - error!("Encryption error: {:?}", e); - continue; - } - }; - - // Base64 encode the encrypted message - let encoded = BASE64.encode(&encrypted); - - if let Err(e) = writer.write_all((encoded + "\n").as_bytes()).await { - error!("Failed to send message: {:?}", e); - break; - } - } - } - }); - - // Wait for both tasks to complete - tokio::select! { - _ = read_task => (), - _ = write_task => (), - } - - info!("Client handling completed"); - Ok(()) -} \ No newline at end of file