Add configuration file and update main to load server settings

This commit is contained in:
Andrea Moro 2025-01-22 11:22:30 +01:00
parent 07b9969416
commit 6761e43cae
5 changed files with 267 additions and 132 deletions

76
Cargo.lock generated
View file

@ -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"

View file

@ -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"

2
config.toml Normal file
View file

@ -0,0 +1,2 @@
address = "127.0.0.1"
port = "25565"

157
src/client/mod.rs Normal file
View file

@ -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<String>,
mut rx: broadcast::Receiver<String>,
) -> Result<(), Box<dyn std::error::Error>> {
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::<Aes256Gcm>::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(())
}
}

View file

@ -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>(&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<String>,
mut rx: broadcast::Receiver<String>,
) -> Result<(), Box<dyn std::error::Error>> {
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::<Aes256Gcm>::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(())
}