826 lines
31 KiB
Rust
Raw Normal View History

2025-01-19 01:55:12 +03:00
mod gui;
mod key_operations;
mod network_operations;
mod key_exchange;
mod authentication;
mod encryption;
2025-02-08 22:29:25 +03:00
use gui::create_rocket;
use gui::MessagingApp;
2025-01-19 01:55:12 +03:00
use key_operations::key_operations_dilithium;
use key_operations::key_operations_eddsa;
use network_operations::create_client_with_proxy;
use network_operations::fetch_kyber_pubkey;
use network_operations::fetch_dilithium_pubkeys;
use network_operations::fetch_eddsa_pubkeys;
use network_operations::fetch_ciphertext;
use network_operations::send_kyber_pubkey;
use network_operations::send_dilithium_pubkey;
use network_operations::send_eddsa_pubkey;
use network_operations::send_ciphertext;
use network_operations::send_encrypted_message;
use network_operations::receive_and_fetch_messages;
use key_exchange::kyber_key_exchange;
use key_exchange::perform_ecdh_key_exchange;
use authentication::sign_data_with_dilithium;
use authentication::sign_data_with_eddsa;
use authentication::verify_signature_with_dilithium;
use authentication::verify_signature_with_eddsa;
use encryption::derive_salt_from_password;
use encryption::derive_key;
use encryption::combine_shared_secrets;
use encryption::encrypt_data;
use encryption::decrypt_data;
2025-01-17 18:27:46 +03:00
use oqs::*;
2025-01-19 01:55:12 +03:00
use oqs::sig::{Sig, PublicKey, SecretKey, Algorithm as SigAlgorithm};
use rand::Rng;
use reqwest::blocking::get;
use std::fs;
2025-01-17 18:27:46 +03:00
use std::fs::File;
2025-01-19 01:55:12 +03:00
use std::path::Path;
2025-01-17 18:27:46 +03:00
use std::process::Command;
2025-01-19 01:55:12 +03:00
use std::str::FromStr;
2025-01-17 18:27:46 +03:00
use hex;
2025-01-19 01:55:12 +03:00
use std::io::{self, Write};
2025-01-17 18:27:46 +03:00
use rpassword::read_password;
use std::result::Result;
use std::{
collections::HashSet,
error::Error,
};
2025-01-19 01:55:12 +03:00
use ipnetwork::IpNetwork;
2025-01-17 18:27:46 +03:00
use serde::{Deserialize, Serialize};
2025-01-19 01:55:12 +03:00
use chacha20poly1305::aead::OsRng;
2025-01-17 18:27:46 +03:00
use rand::RngCore;
2025-01-19 01:55:12 +03:00
use sha3::{Sha3_512, Digest};
use ed25519_dalek::VerifyingKey as Ed25519PublicKey;
2025-01-17 18:27:46 +03:00
// Function to get the raw bytes from PublicKey
fn get_raw_bytes_public_key(pk: &PublicKey) -> &[u8] {
2025-01-19 01:55:12 +03:00
pk.as_ref() // Directly return the raw bytes
2025-01-17 18:27:46 +03:00
}
// Function to get the raw bytes from SecretKey
fn get_raw_bytes_secret_key(sk: &SecretKey) -> &[u8] {
2025-01-19 01:55:12 +03:00
sk.as_ref() // Directly return the raw bytes
2025-01-17 18:27:46 +03:00
}
2025-01-19 01:55:12 +03:00
#[derive(Serialize, Deserialize, Debug)] // Make sure it can be serialized and deserialized
struct MessageData {
message: String,
room_id: String,
2025-01-17 18:27:46 +03:00
}
2025-01-19 01:55:12 +03:00
fn fingerprint_dilithium_public_key(public_key: &PublicKey) -> String {
// Access the raw bytes of the public key using as_ref()
let raw_bytes = public_key.as_ref(); // This should return &[u8]
let hashed = Sha3_512::digest(raw_bytes);
hex::encode(hashed)
2025-01-17 18:27:46 +03:00
}
2025-01-19 01:55:12 +03:00
fn fingerprint_eddsa_public_key(public_key: &Ed25519PublicKey) -> String {
// Hash the public key to generate a fingerprint (using SHA-512)
let hashed = Sha3_512::digest(public_key);
hex::encode(hashed)
2025-01-17 18:27:46 +03:00
}
2025-01-19 01:55:12 +03:00
fn request_user_confirmation(fingerprint: &str, own_fingerprint: &str) -> Result<bool, io::Error> {
// If the fingerprint matches your own public key, auto-confirm
if fingerprint == own_fingerprint {
return Ok(true);
}
2025-01-17 18:27:46 +03:00
2025-01-19 01:55:12 +03:00
println!("The fingerprint of the received public key is: {}", fingerprint);
print!("Do you confirm this fingerprint? (yes/no): ");
io::stdout().flush()?;
let mut input = String::new();
io::stdin().read_line(&mut input)?;
let response = input.trim().to_lowercase();
match response.as_str() {
"yes" => Ok(true),
"no" => Ok(false),
_ => {
println!("Invalid input. Please enter 'yes' or 'no'.");
request_user_confirmation(fingerprint, own_fingerprint) // Retry if invalid input
}
}
2025-01-17 18:27:46 +03:00
}
2025-01-19 01:55:12 +03:00
fn generate_random_room_id() -> String {
const ID_LENGTH: usize = 16;
const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
2025-01-17 18:27:46 +03:00
2025-01-19 01:55:12 +03:00
let mut rng = OsRng;
let mut room_id = String::with_capacity(ID_LENGTH);
2025-01-17 18:27:46 +03:00
2025-01-19 01:55:12 +03:00
for _ in 0..ID_LENGTH {
let idx = (rng.next_u32() as usize) % CHARSET.len();
room_id.push(CHARSET[idx] as char);
}
2025-01-17 18:27:46 +03:00
2025-01-19 01:55:12 +03:00
room_id
2025-01-17 18:27:46 +03:00
}
2025-01-19 01:55:12 +03:00
fn load_blacklist(file_path: &str) -> HashSet<IpNetwork> {
match fs::read_to_string(file_path) {
Ok(contents) => contents
.lines()
.filter_map(|line| {
let line = line.trim();
IpNetwork::from_str(line).ok()
})
.collect(),
Err(_) => HashSet::new(),
2025-01-17 18:27:46 +03:00
}
}
2025-01-19 01:55:12 +03:00
fn is_onion_site(url: &str) -> bool {
url.contains(".onion")
2025-01-17 18:27:46 +03:00
}
2025-01-19 01:55:12 +03:00
fn is_eepsite(url: &str) -> bool {
url.contains(".i2p")
2025-01-17 18:27:46 +03:00
}
2025-01-19 01:55:12 +03:00
fn resolve_dns(host: &str) -> Result<String, Box<dyn Error>> {
let output = Command::new("dig")
.args(["+short", host])
.output()?;
2025-01-17 18:27:46 +03:00
2025-01-19 01:55:12 +03:00
if output.status.success() {
let response = String::from_utf8_lossy(&output.stdout);
// Find the first valid IP address in the response
if let Some(ip) = response
.lines()
.filter(|line| line.parse::<std::net::IpAddr>().is_ok())
.next()
{
return Ok(ip.to_string());
2025-01-17 18:27:46 +03:00
}
}
2025-01-19 01:55:12 +03:00
Err("Failed to resolve DNS to an IP address.".into())
2025-01-17 18:27:46 +03:00
}
2025-01-19 01:55:12 +03:00
fn is_ip_blacklisted(ip: &str, blacklist: &HashSet<IpNetwork>) -> bool {
// Try to parse the IP address. If it fails, return false.
let ip: std::net::IpAddr = match ip.parse() {
Ok(ip) => ip,
Err(_) => return false, // Return false if parsing fails
};
2025-01-17 18:27:46 +03:00
2025-01-19 01:55:12 +03:00
// Check if the IP is within any of the CIDR ranges in the blacklist
blacklist.iter().any(|range| range.contains(ip))
}
2025-01-17 18:27:46 +03:00
2025-01-19 01:55:12 +03:00
fn pad_message(message: &str, max_length: usize) -> String {
let current_length = message.len();
2025-01-17 18:27:46 +03:00
2025-01-19 01:55:12 +03:00
if current_length < max_length {
let padding_len = max_length - current_length;
2025-01-17 18:27:46 +03:00
2025-01-19 01:55:12 +03:00
let mut rng = OsRng; // Cryptographically secure RNG
let padding: String = (0..padding_len)
.map(|_| rng.gen_range(33..127) as u8 as char) // ASCII printable characters range
.collect();
2025-01-17 18:27:46 +03:00
2025-01-19 01:55:12 +03:00
// Wrap the padding in <padding> and </padding> tags
return format!("{}<padding>{}</padding>", message, padding);
2025-01-17 18:27:46 +03:00
}
2025-01-19 01:55:12 +03:00
message.to_string() // Return the message as is if it's already at max length
}
2025-01-17 18:27:46 +03:00
2025-01-19 01:55:12 +03:00
// The main function
fn main() -> Result<(), Box<dyn Error>> {
use std::sync::{Arc, Mutex};
use std::{io::{self, Write}, thread, time::Duration};
2025-01-17 18:27:46 +03:00
2025-01-19 01:55:12 +03:00
let sigalg = sig::Sig::new(sig::Algorithm::Dilithium5)?;
2025-01-17 18:27:46 +03:00
2025-01-19 01:55:12 +03:00
// Get user input for the choice of interface
let mut input = String::new();
print!("Choose interface (CLI or GUI): ");
io::stdout().flush()?;
io::stdin().read_line(&mut input)?;
let interface_choice = input.trim().to_string();
input.clear();
2025-01-17 18:27:46 +03:00
2025-01-19 01:55:12 +03:00
// Step 1: Ask user to either create a room ID or join one
println!("Would you like to create a new room or join an existing one?");
println!("Type 'create' to create a new room or 'join' to join an existing one.");
let mut choice = String::new();
io::stdin().read_line(&mut choice)?;
let choice = choice.trim();
let room_id = match choice {
"create" => {
let new_room_id = generate_random_room_id();
println!("Generated new room ID: {}", new_room_id);
new_room_id
2025-01-17 18:27:46 +03:00
}
2025-01-19 01:55:12 +03:00
"join" => {
println!("Enter the room ID to join:");
let mut room_input = String::new();
io::stdin().read_line(&mut room_input)?;
room_input.trim().to_string()
2025-01-17 18:27:46 +03:00
}
2025-01-19 01:55:12 +03:00
_ => {
println!("Invalid choice. Please restart the program and choose 'create' or 'join'.");
return Ok(());
2025-01-17 18:27:46 +03:00
}
};
2025-01-19 01:55:12 +03:00
// Check if cloudflare-ip-blacklist.txt exists in the current directory
let blacklist_file = "cloudflare-ip-blacklist.txt";
if !Path::new(blacklist_file).exists() {
println!("File '{}' not found. Fetching from Codeberg...", blacklist_file);
2025-01-17 18:27:46 +03:00
2025-01-19 01:55:12 +03:00
// Fetch the blacklist file from the raw Codeberg URL
let url = "https://codeberg.org/umutcamliyurt/Amnezichat/raw/branch/main/client/cloudflare-ip-blacklist.txt"; // Replace with actual URL
let response = get(url)?;
2025-01-17 18:27:46 +03:00
2025-01-19 01:55:12 +03:00
if response.status().is_success() {
let content = response.text()?;
// Save the fetched content to cloudflare-ip-blacklist.txt in the current directory
let mut file = File::create(blacklist_file)?;
file.write_all(content.as_bytes())?;
println!("File fetched and saved as '{}'.", blacklist_file);
} else {
println!("Failed to fetch the file from URL.");
return Err("Failed to fetch blacklist.".into());
2025-01-17 18:27:46 +03:00
}
}
2025-01-19 01:55:12 +03:00
// Load the blacklist from the file
let blacklist = load_blacklist("cloudflare-ip-blacklist.txt");
2025-01-17 18:27:46 +03:00
2025-01-19 01:55:12 +03:00
// Get the server URL
let mut input = String::new();
print!("Enter the server URL: ");
io::stdout().flush()?;
io::stdin().read_line(&mut input)?;
let url = input.trim().to_string();
input.clear();
2025-01-17 18:27:46 +03:00
2025-01-19 01:55:12 +03:00
if is_onion_site(&url) {
println!("This is an .onion site. Skipping IP check.");
2025-01-17 18:27:46 +03:00
}
2025-01-19 01:55:12 +03:00
else if is_eepsite(&url)
{
println!("This is an .i2p site. Skipping IP check.");
}
else {
// Extract the host portion of the URL
let host = url
.split('/')
.nth(2)
.unwrap_or(&url) // Extract the host portion
.split(':')
.next()
.unwrap_or(&url);
// Resolve IP of the host
match resolve_dns(host) {
Ok(ip) => {
// Check if the resolved IP is blacklisted
if is_ip_blacklisted(&ip, &blacklist) {
println!("WARNING! The IP {} is in the blacklist.", ip);
println!("The server you're trying to access is behind a Cloudflare reverse proxy.");
println!("Proceed with caution as this setup may expose you to several potential risks:");
println!();
println!("Deanonymization attacks (including 0-click exploits)");
println!("Metadata leaks");
println!("Encryption vulnerabilities");
println!("AI-based traffic analysis");
println!("Connectivity issues");
println!("Other undetected malicious behavior");
println!();
println!("What you can do:");
println!("1. Choose a different server");
println!("2. Self-host your own server");
println!("3. Proceed anyway (Dangerous!)");
println!();
println!("For more info: https://git.calitabby.net/mirrors/deCloudflare");
println!();
println!("Do you want to proceed? (yes/no)");
let mut input = String::new();
io::stdin()
.read_line(&mut input)
.expect("Failed to read input");
let input = input.trim().to_lowercase();
match input.as_str() {
"yes" | "y" => {
println!("Proceeding...");
}
"no" | "n" => {
println!("Operation aborted!");
return Ok(()); // Exit the main function, effectively closing the app
}
_ => {
println!("Invalid input. Please enter 'yes' or 'no'.");
}
}
}
}
Err(e) => {
println!("Failed to resolve IP for the server: {}", e);
}
}
2025-01-17 18:27:46 +03:00
}
2025-01-19 01:55:12 +03:00
// Get the username
print!("Enter your username: ");
io::stdout().flush()?;
io::stdin().read_line(&mut input)?;
let username = input.trim().to_string();
input.clear();
2025-01-17 18:27:46 +03:00
2025-01-19 01:55:12 +03:00
print!("Enter private key encryption password: ");
io::stdout().flush()?;
let private_password = read_password()?.to_string();
2025-01-17 18:27:46 +03:00
2025-01-19 01:55:12 +03:00
println!("Is this a group chat? (yes/no): ");
let mut is_group_chat = String::new();
io::stdin().read_line(&mut is_group_chat)?;
let is_group_chat = is_group_chat.trim().to_lowercase() == "yes";
let room_password = if is_group_chat {
// Loop to get a valid room password for group chat
loop {
print!("Enter room password (must be longer than 8 characters): ");
io::stdout().flush()?; // Ensure the prompt is displayed immediately
let mut input = String::new();
io::stdin().read_line(&mut input)?;
let password_input = input.trim();
if password_input.len() > 8 {
break password_input.to_string(); // Exit the loop with valid password
} else {
println!("Error: Password must be longer than 8 characters. Please try again.");
2025-01-17 18:27:46 +03:00
}
}
} else {
2025-01-19 01:55:12 +03:00
// For one-to-one chat, skip password setup
String::new()
2025-01-17 18:27:46 +03:00
};
2025-01-19 01:55:12 +03:00
// Derive the key from the room password if it's a group chat
let room_password = if is_group_chat {
let salt = derive_salt_from_password(&room_password);
let key = derive_key(&room_password, &salt);
hex::encode(key)
} else {
String::new() // No room password required for one-to-one chat
2025-01-17 18:27:46 +03:00
};
2025-01-19 01:55:12 +03:00
// Skip key exchange and create hybrid_shared_secret if it's a group chat
if is_group_chat {
println!("Skipping key exchange. Using room password as shared secret.");
let hybrid_shared_secret = room_password.clone(); // Use room password directly
println!("Shared secret established.");
println!("You can now start messaging!");
// Shared data setup for messaging
let shared_hybrid_secret = Arc::new(hybrid_shared_secret.clone());
let shared_room_id = Arc::new(Mutex::new(room_id.clone()));
let shared_url = Arc::new(Mutex::new(url.clone()));
// Clone interface_choice before passing to thread
let interface_choice_clone = interface_choice.clone(); // Clone interface_choice
let random_data_thread = {
let shared_room_id = Arc::clone(&shared_room_id);
let shared_url = Arc::clone(&shared_url);
let shared_hybrid_secret = Arc::clone(&shared_hybrid_secret);
thread::spawn(move || loop {
// Generate cryptographically secure random data
let mut random_data = vec![0u8; OsRng.next_u32() as usize % 2048 + 1]; // Random size between 1 and 2048
OsRng.fill_bytes(&mut random_data);
let dummy_message = format!("[DUMMY_DATA]: {:?}", random_data);
let encrypted_dummy_message = match encrypt_data(&dummy_message, &shared_hybrid_secret) {
Ok(data) => data,
Err(e) => {
eprintln!("Error encrypting dummy message: {}", e);
continue;
}
};
let room_id_locked = shared_room_id.lock().unwrap();
let url_locked = shared_url.lock().unwrap();
// Pad the message to a fixed length (e.g., 2048 bytes)
let padded_message = pad_message(&encrypted_dummy_message, 2048);
if let Err(e) = send_encrypted_message(&padded_message, &room_id_locked, &url_locked) {
eprintln!("Error sending dummy message: {}", e);
}
// Sleep for a random interval (1 to 120 seconds)
let sleep_duration = Duration::from_secs(OsRng.next_u32() as u64 % 120 + 1);
thread::sleep(sleep_duration);
})
};
2025-01-17 18:27:46 +03:00
2025-01-19 01:55:12 +03:00
// Spawn message fetch thread
let fetch_thread = thread::spawn({
let shared_hybrid_secret = Arc::clone(&shared_hybrid_secret);
let shared_room_id = Arc::clone(&shared_room_id);
let shared_url = Arc::clone(&shared_url);
let interface_choice_clone = interface_choice_clone.clone(); // Clone here as well
move || loop {
// Lock the shared resources to access their values
let room_id_locked = shared_room_id.lock().unwrap().clone();
let url_locked = shared_url.lock().unwrap().clone();
// Fetch and process messages
match receive_and_fetch_messages(
&room_id_locked,
&shared_hybrid_secret,
&url_locked,
interface_choice_clone.to_lowercase() == "gui", // Pass true for GUI, false for CLI
) {
Ok(messages) => {
// Print messages only if interface is CLI
if interface_choice_clone.to_lowercase() == "cli" {
for message in messages {
println!("{}", message);
}
}
}
Err(e) => {
eprintln!("Error fetching messages: {}", e);
}
}
2025-01-17 18:27:46 +03:00
2025-01-19 01:55:12 +03:00
// Sleep for 10 seconds before the next fetch
thread::sleep(Duration::from_secs(10));
}
});
2025-01-17 18:27:46 +03:00
2025-01-19 01:55:12 +03:00
// Handle GUI or CLI messaging
if interface_choice.to_lowercase() == "gui" {
2025-02-08 22:29:25 +03:00
let rt = rocket::tokio::runtime::Runtime::new().unwrap();
rt.block_on(async {
// Wrap only for passing to run_gui
let shared_hybrid_secret_for_gui = shared_hybrid_secret;
// Correctly clone Arc<Mutex<String>> instead of Arc<String>
let shared_room_id_for_gui: Arc<Mutex<String>> = Arc::clone(&shared_room_id);
let shared_url_for_gui: Arc<Mutex<String>> = Arc::clone(&shared_url);
// Pass the arguments
let app = MessagingApp::new(
username,
shared_hybrid_secret_for_gui,
shared_room_id_for_gui,
shared_url_for_gui,
);
// Await the async launch function
if let Err(e) = create_rocket(app).launch().await {
eprintln!("Rocket server failed: {}", e);
}
});
2025-01-19 01:55:12 +03:00
} else {
loop {
let mut message = String::new();
print!("Enter your message (or type 'exit' to quit): ");
io::stdout().flush()?;
io::stdin().read_line(&mut message)?;
2025-01-17 18:27:46 +03:00
2025-01-19 01:55:12 +03:00
let message = message.trim();
2025-01-17 18:27:46 +03:00
2025-01-19 01:55:12 +03:00
if message == "exit" {
println!("Exiting messaging session.");
break;
}
2025-01-17 18:27:46 +03:00
2025-01-19 01:55:12 +03:00
let message = format!("<strong>{}</strong>: {}", username, message);
2025-01-17 18:27:46 +03:00
2025-01-19 01:55:12 +03:00
// Pad the message to a fixed length (e.g., 2048 bytes)
let padded_message = pad_message(&message, 2048);
2025-01-17 18:27:46 +03:00
2025-01-19 01:55:12 +03:00
let encrypted_message = encrypt_data(&padded_message, &hybrid_shared_secret)?;
send_encrypted_message(&encrypted_message, &room_id, &url)?;
}
2025-01-17 18:27:46 +03:00
}
2025-01-19 01:55:12 +03:00
// Ensure both threads terminate gracefully
if let Err(e) = random_data_thread.join() {
eprintln!("Random data thread terminated with error: {:?}", e);
}
2025-01-17 18:27:46 +03:00
2025-01-19 01:55:12 +03:00
if let Err(e) = fetch_thread.join() {
eprintln!("Fetch thread terminated with error: {:?}", e);
}
2025-01-17 18:27:46 +03:00
2025-01-19 01:55:12 +03:00
return Ok(());
}
2025-01-17 18:27:46 +03:00
2025-01-19 01:55:12 +03:00
// Continue with the key exchange process for one-to-one chat
2025-01-17 18:27:46 +03:00
// Step 2: Load or generate Dilithium5 and EdDSA keys for the user
let dilithium_keys = key_operations_dilithium(&sigalg, &username, &private_password);
let Ok((dilithium_pk, dilithium_sk)) = dilithium_keys else { todo!() };
let eddsa_keys = key_operations_eddsa(&username, &private_password);
let Ok((eddsa_sk, eddsa_pk)) = eddsa_keys else { todo!() };
let encoded_dilithium_pk = hex::encode(&dilithium_pk);
2025-01-19 01:55:12 +03:00
send_dilithium_pubkey(&room_id, &encoded_dilithium_pk, &url);
2025-01-17 18:27:46 +03:00
let encoded_eddsa_pk = hex::encode(&eddsa_pk);
2025-01-19 01:55:12 +03:00
send_eddsa_pubkey(&room_id, &encoded_eddsa_pk, &url);
2025-01-17 18:27:46 +03:00
let fingerprint_dilithium = fingerprint_dilithium_public_key(&dilithium_pk);
println!("Own Dilithium5 fingerprint: {}", fingerprint_dilithium);
let fingerprint_eddsa = match Ed25519PublicKey::from_bytes(&eddsa_pk) {
Ok(public_key) => fingerprint_eddsa_public_key(&public_key),
Err(e) => {
eprintln!("Failed to convert EdDSA public key: {}", e);
return Err(Box::new(e));
}
};
println!("Own EdDSA fingerprint: {}", fingerprint_eddsa);
let mut processed_fingerprints: HashSet<String> = HashSet::new();
processed_fingerprints.insert(fingerprint_dilithium.clone());
processed_fingerprints.insert(fingerprint_eddsa.clone());
let mut all_other_dilithium_keys: Vec<oqs::sig::PublicKey> = Vec::new();
while all_other_dilithium_keys.len() < 1 {
println!("Waiting for Dilithium public key...");
thread::sleep(Duration::from_secs(5));
2025-01-19 01:55:12 +03:00
let encoded_other_dilithium_pks = fetch_dilithium_pubkeys(&room_id, &url);
2025-01-17 18:27:46 +03:00
for encoded_pk in encoded_other_dilithium_pks {
if let Ok(decoded_pk) = hex::decode(&encoded_pk) {
2025-01-19 01:55:12 +03:00
// Create a Sig instance for the "Dilithium5" algorithm
let algorithm = SigAlgorithm::Dilithium5;
// Create a Sig instance for the chosen algorithm
let sig = Sig::new(algorithm).map_err(|_| "Failed to initialize signature scheme")?;
// Convert the decoded public key to a PublicKey using public_key_from_bytes
if let Some(public_key_ref) = sig.public_key_from_bytes(&decoded_pk) {
// Convert PublicKeyRef<'_> to PublicKey by calling to_owned()
let public_key = public_key_ref.to_owned();
2025-01-17 18:27:46 +03:00
let fetched_fingerprint = fingerprint_dilithium_public_key(&public_key);
2025-01-19 01:55:12 +03:00
2025-01-17 18:27:46 +03:00
if fetched_fingerprint == fingerprint_dilithium {
continue;
}
2025-01-19 01:55:12 +03:00
2025-01-17 18:27:46 +03:00
if processed_fingerprints.contains(&fetched_fingerprint) {
continue;
}
2025-01-19 01:55:12 +03:00
2025-01-17 18:27:46 +03:00
if request_user_confirmation(&fetched_fingerprint, &fingerprint_dilithium)? {
2025-01-19 01:55:12 +03:00
// Push the owned PublicKey to the list
2025-01-17 18:27:46 +03:00
all_other_dilithium_keys.push(public_key);
processed_fingerprints.insert(fetched_fingerprint);
} else {
eprintln!("User did not confirm the public key fingerprint.");
}
} else {
eprintln!("Failed to decode valid public key.");
}
} else {
eprintln!("Failed to convert decoded key to PublicKey.");
}
}
}
2025-01-19 01:55:12 +03:00
2025-01-17 18:27:46 +03:00
println!("Received Dilithium5 public key from the server.");
let mut eddsa_key: Option<Ed25519PublicKey> = None;
while eddsa_key.is_none() {
println!("Waiting for EdDSA public key...");
thread::sleep(Duration::from_secs(5));
2025-01-19 01:55:12 +03:00
let encoded_other_eddsa_pks = fetch_eddsa_pubkeys(&room_id, &url);
2025-01-17 18:27:46 +03:00
for encoded_pk in encoded_other_eddsa_pks {
if let Ok(decoded_pk) = hex::decode(&encoded_pk) {
if let Ok(public_key) = Ed25519PublicKey::from_bytes(
decoded_pk.as_slice().try_into().expect("Decoded public key must be 32 bytes long"),
) {
let fetched_fingerprint = fingerprint_eddsa_public_key(&public_key);
if fetched_fingerprint == fingerprint_eddsa {
continue;
}
if processed_fingerprints.contains(&fetched_fingerprint) {
continue;
}
if request_user_confirmation(&fetched_fingerprint, &fingerprint_eddsa)? {
eddsa_key = Some(public_key);
processed_fingerprints.insert(fetched_fingerprint);
break;
} else {
eprintln!("User did not confirm the public key fingerprint.");
}
} else {
eprintln!("Failed to decode valid public key.");
}
} else {
eprintln!("Failed to convert decoded key to PublicKey.");
}
}
}
println!("Received EdDSA public key from the server.");
let mut all_dilithium_pks = vec![dilithium_pk];
all_dilithium_pks.extend(all_other_dilithium_keys);
2025-01-19 01:55:12 +03:00
let kyber_shared_secret = kyber_key_exchange(&room_id, &all_dilithium_pks, &dilithium_sk, &url)?;
2025-01-17 18:27:46 +03:00
let ecdh_shared_secret = if let Some(ref eddsa_key) = eddsa_key {
2025-01-19 01:55:12 +03:00
perform_ecdh_key_exchange(&room_id, &eddsa_sk.to_bytes(), eddsa_key, &url)?
2025-01-17 18:27:46 +03:00
} else {
return Err("EdDSA public key is missing".into());
};
let hybrid_shared_secret = combine_shared_secrets(&kyber_shared_secret, &ecdh_shared_secret)?;
println!("Hybrid shared secret established.");
println!("You can now start messaging!");
2025-01-19 01:55:12 +03:00
let shared_hybrid_secret = Arc::new(hybrid_shared_secret.clone()); // Keep as Arc<String>
let shared_room_id = Arc::new(Mutex::new(room_id.clone())); // Wrap in Mutex
let shared_url = Arc::new(Mutex::new(url.clone())); // Wrap in Mutex
let interface_choice_clone = interface_choice.clone(); // Clone interface_choice before moving it into the thread
let random_data_thread = {
let shared_room_id = Arc::clone(&shared_room_id);
let shared_url = Arc::clone(&shared_url);
let shared_hybrid_secret = Arc::clone(&shared_hybrid_secret);
thread::spawn(move || loop {
// Generate cryptographically secure random data
let mut random_data = vec![0u8; OsRng.next_u32() as usize % 2048 + 1]; // Random size between 1 and 2048
OsRng.fill_bytes(&mut random_data);
let dummy_message = format!("[DUMMY_DATA]: {:?}", random_data);
2025-01-17 18:27:46 +03:00
2025-01-19 01:55:12 +03:00
// Pad the message to a fixed length (e.g., 2048 bytes)
let padded_message = pad_message(&dummy_message, 2048);
let encrypted_dummy_message = match encrypt_data(&padded_message, &shared_hybrid_secret) {
Ok(data) => data,
Err(e) => {
eprintln!("Error encrypting dummy message: {}", e);
continue;
}
};
let room_id_locked = shared_room_id.lock().unwrap();
let url_locked = shared_url.lock().unwrap();
if let Err(e) = send_encrypted_message(&encrypted_dummy_message, &room_id_locked, &url_locked) {
eprintln!("Error sending dummy message: {}", e);
}
// Sleep for a random interval (1 to 120 seconds)
let sleep_duration = Duration::from_secs(OsRng.next_u32() as u64 % 120 + 1);
thread::sleep(sleep_duration);
})
};
// Spawn message fetch thread
let fetch_thread = thread::spawn({
2025-01-17 18:27:46 +03:00
let shared_hybrid_secret = Arc::clone(&shared_hybrid_secret);
2025-01-19 01:55:12 +03:00
let shared_room_id = Arc::clone(&shared_room_id);
2025-01-17 18:27:46 +03:00
let shared_url = Arc::clone(&shared_url);
2025-01-19 01:55:12 +03:00
let interface_choice_clone = interface_choice_clone.clone(); // Clone here as well
2025-01-17 18:27:46 +03:00
2025-01-19 01:55:12 +03:00
move || loop {
// Lock the shared resources to access their values
let room_id_locked = shared_room_id.lock().unwrap().clone();
let url_locked = shared_url.lock().unwrap().clone();
// Fetch and process messages
2025-01-17 18:27:46 +03:00
match receive_and_fetch_messages(
2025-01-19 01:55:12 +03:00
&room_id_locked,
2025-01-17 18:27:46 +03:00
&shared_hybrid_secret,
2025-01-19 01:55:12 +03:00
&url_locked,
interface_choice_clone.to_lowercase() == "gui", // Pass true for GUI, false for CLI
2025-01-17 18:27:46 +03:00
) {
2025-01-19 01:55:12 +03:00
Ok(messages) => {
// Print messages only if interface is CLI
if interface_choice_clone.to_lowercase() == "cli" {
for message in messages {
println!("{}", message);
}
}
}
2025-01-17 18:27:46 +03:00
Err(e) => {
eprintln!("Error fetching messages: {}", e);
}
}
2025-01-19 01:55:12 +03:00
// Sleep for 10 seconds before the next fetch
thread::sleep(Duration::from_secs(10));
2025-01-17 18:27:46 +03:00
}
2025-01-19 01:55:12 +03:00
});
if interface_choice.to_lowercase() == "gui" {
2025-02-08 22:29:25 +03:00
let rt = rocket::tokio::runtime::Runtime::new().unwrap();
rt.block_on(async {
// Wrap only for passing to run_gui
let shared_hybrid_secret_for_gui = shared_hybrid_secret;
2025-01-19 01:55:12 +03:00
2025-02-08 22:29:25 +03:00
// Correctly clone Arc<Mutex<String>> instead of Arc<String>
let shared_room_id_for_gui: Arc<Mutex<String>> = Arc::clone(&shared_room_id);
let shared_url_for_gui: Arc<Mutex<String>> = Arc::clone(&shared_url);
2025-01-19 01:55:12 +03:00
2025-02-08 22:29:25 +03:00
// Pass the arguments
let app = MessagingApp::new(
username,
shared_hybrid_secret_for_gui,
shared_room_id_for_gui,
shared_url_for_gui,
);
2025-01-19 01:55:12 +03:00
2025-02-08 22:29:25 +03:00
// Await the async launch function
if let Err(e) = create_rocket(app).launch().await {
eprintln!("Rocket server failed: {}", e);
}
});
2025-01-19 01:55:12 +03:00
} else {
loop {
let mut message = String::new();
print!("Enter your message (or type 'exit' to quit): ");
io::stdout().flush()?;
io::stdin().read_line(&mut message)?;
let message = message.trim();
if message == "exit" {
println!("Exiting messaging session.");
2025-01-17 18:27:46 +03:00
break;
}
2025-01-19 01:55:12 +03:00
let message = format!("<strong>{}</strong>: {}", username, message);
2025-01-17 18:27:46 +03:00
2025-01-19 01:55:12 +03:00
// Pad the message to a fixed length (e.g., 2048 bytes)
let padded_message = pad_message(&message, 2048);
let encrypted_message = encrypt_data(&padded_message, &hybrid_shared_secret)?;
send_encrypted_message(&encrypted_message, &room_id, &url)?;
2025-01-17 18:27:46 +03:00
}
}
2025-01-19 01:55:12 +03:00
// Ensure both threads terminate gracefully
if let Err(e) = random_data_thread.join() {
eprintln!("Random data thread terminated with error: {:?}", e);
2025-01-17 18:27:46 +03:00
}
2025-01-19 01:55:12 +03:00
if let Err(e) = fetch_thread.join() {
eprintln!("Fetch thread terminated with error: {:?}", e);
2025-01-17 18:27:46 +03:00
}
2025-01-19 01:55:12 +03:00
2025-01-17 18:27:46 +03:00
Ok(())
2025-01-19 01:55:12 +03:00
}
2025-01-17 18:27:46 +03:00
// Function to clear the screen before printing new messages
fn clear_screen() {
if cfg!(target_os = "windows") {
// Windows
Command::new("cmd")
.args(&["/C", "cls"])
.output()
.expect("Failed to clear screen on Windows");
} else {
// Linux/macOS or others
print!("\x1b[2J\x1b[H");
std::io::stdout().flush().unwrap(); // Ensure the command is executed immediately
}
}