diff --git a/src/frontend/index.css b/src/frontend/index.css index 0170aad..705a206 100644 --- a/src/frontend/index.css +++ b/src/frontend/index.css @@ -58,7 +58,7 @@ input[type="file"] { padding: 20px 70px; background-color: #2B2F31; border-radius: 10px; - font-size: 1.3em; + font-size: 1.2em; } .popup_background { height: 100%; @@ -117,6 +117,15 @@ input[type="file"] { display: inline-block; vertical-align: middle; } +.popup .session_info h2 { + text-align: center; +} +.popup .session_info p:first-of-type, .popup .session_info pre { + display: inline-block; +} +.popup .session_info p:nth-of-type(2) { + margin-top: 0; +} .button_row { display: flex; gap: 15px; @@ -156,6 +165,13 @@ input[type="file"] { font-weight: bold; display: inline; } +#me>div:hover p { + color: var(--accent); +} +#identity_fingerprint { + text-align: center; + margin-bottom: 0; +} #left_panel ul:last-of-type, #msg_log { flex-grow: 1; } @@ -233,6 +249,10 @@ input[type="file"] { display: flex; align-items: center; flex-grow: 1; + cursor: pointer; +} +#chat_header>div:hover p { + color: var(--accent); } #chat_header>div>p { font-weight: bold; diff --git a/src/frontend/index.html b/src/frontend/index.html index 3e42a5d..f99b06d 100644 --- a/src/frontend/index.html +++ b/src/frontend/index.html @@ -61,6 +61,7 @@ diff --git a/src/frontend/index.js b/src/frontend/index.js index 2fd139c..b91e83d 100644 --- a/src/frontend/index.js +++ b/src/frontend/index.js @@ -1,7 +1,7 @@ "use strict"; const ENTER_KEY_CODE = 13; -let identity_name = undefined; +let identityName = undefined; let socket = null; let notificationAllowed = false; let currentSessionId = -1; @@ -60,7 +60,7 @@ document.getElementById("delete_conversation").onclick = function() { } document.getElementById("add_contact").onclick = function() { socket.send("contact "+currentSessionId+" "+sessionsData.get(currentSessionId).name); - sessionsData.get(currentSessionId).is_contact = true; + sessionsData.get(currentSessionId).isContact = true; displayHeader(); displaySessions(); } @@ -78,9 +78,9 @@ document.getElementById("remove_contact").onclick = function() { button.onclick = function() { socket.send("uncontact "+currentSessionId); let session = sessionsData.get(currentSessionId); - session.is_contact = false; - session.is_verified = false; - if (!session.is_online) { + session.isContact = false; + session.isVerified = false; + if (!session.isOnline) { sessionsData.delete(currentSessionId); msgHistory.get(currentSessionId).length = 0; } @@ -93,7 +93,44 @@ document.getElementById("remove_contact").onclick = function() { showPopup(mainDiv); } document.getElementById("verify").onclick = function() { - socket.send("fingerprints "+currentSessionId); + let session = sessionsData.get(currentSessionId); + if (typeof session !== "undefined") { + let mainDiv = document.createElement("div"); + mainDiv.appendChild(generatePopupWarningTitle()); + let instructions = document.createElement("p"); + instructions.textContent = "Compare the following fingerprints by a trusted way of communication (such as real life) and be sure they match."; + mainDiv.appendChild(instructions); + let p_local = document.createElement("p"); + p_local.textContent = "Local fingerprint:"; + mainDiv.appendChild(p_local); + let pre_local = document.createElement("pre"); + pre_local.textContent = beautifyFingerprint(identityFingerprint); + mainDiv.appendChild(pre_local); + let p_peer = document.createElement("p"); + p_peer.textContent = "Peer fingerprint:"; + mainDiv.appendChild(p_peer); + let pre_peer = document.createElement("pre"); + pre_peer.textContent = beautifyFingerprint(session.fingerprint); + mainDiv.appendChild(pre_peer); + let buttonRow = document.createElement("div"); + buttonRow.classList.add("button_row"); + let verifyButton = document.createElement("button"); + verifyButton.textContent = "They match"; + verifyButton.onclick = function() { + socket.send("verify "+currentSessionId); + sessionsData.get(currentSessionId).isVerified = true; + removePopup(); + displayHeader(); + displaySessions(); + }; + buttonRow.appendChild(verifyButton); + let cancelButton = document.createElement("button"); + cancelButton.textContent = "They don't match"; + cancelButton.onclick = removePopup; + buttonRow.appendChild(cancelButton); + mainDiv.appendChild(buttonRow); + showPopup(mainDiv); + } } document.getElementById("logout").onclick = function() { let mainDiv = document.createElement("div"); @@ -141,125 +178,9 @@ document.getElementById("attach_file").onchange = function(event) { document.getElementById("file_cancel").onclick = function() { socket.send("abort "+currentSessionId); } - -//source: https://stackoverflow.com/a/14919494 -function humanFileSize(bytes, dp=1) { - const thresh = 1000; - if (Math.abs(bytes) < thresh) { - return bytes + ' B'; - } - const units = ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; - let u = -1; - const r = 10**dp; - do { - bytes /= thresh; - ++u; - } while (Math.round(Math.abs(bytes) * r) / r >= thresh && u < units.length - 1); - return bytes.toFixed(dp) + ' ' + units[u]; -} -//source: https://www.w3schools.com/js/js_cookies.asp -function getCookie(cname) { - var name = cname + "="; - var decodedCookie = decodeURIComponent(document.cookie); - var ca = decodedCookie.split(';'); - for(var i = 0; i div"); profile_div.onclick = function() { let mainDiv = document.createElement("div"); - let avatar = generateAvatar(identity_name); + let avatar = generateAvatar(identityName); mainDiv.appendChild(avatar); + let fingerprint = document.createElement("pre"); + fingerprint.id = "identity_fingerprint"; + fingerprint.textContent = beautifyFingerprint(identityFingerprint); + mainDiv.appendChild(fingerprint); let sectionName = document.createElement("section"); sectionName.textContent = "Name:"; let inputName = document.createElement("input"); inputName.id = "new_name"; inputName.type = "text"; - inputName.value = identity_name; + inputName.value = identityName; sectionName.appendChild(inputName); let saveNameButton = document.createElement("button"); saveNameButton.textContent = "Save"; @@ -370,21 +295,167 @@ profile_div.onclick = function() { mainDiv.appendChild(sectionDelete); showPopup(mainDiv); } +let chatHeader = document.getElementById("chat_header"); +chatHeader.children[0].onclick = function() { + let session = sessionsData.get(currentSessionId); + if (typeof session !== "undefined") { + let mainDiv = document.createElement("div"); + mainDiv.classList.add("session_info"); + mainDiv.appendChild(generateAvatar(session.name)); + let h2 = document.createElement("h2"); + h2.textContent = session.name; + mainDiv.appendChild(h2); + let pFingerprint = document.createElement("p"); + pFingerprint.textContent = "Fingerprint:"; + mainDiv.appendChild(pFingerprint); + let pre = document.createElement("pre"); + pre.textContent = ' '+beautifyFingerprint(session.fingerprint); + mainDiv.appendChild(pre); + if (session.isOnline) { + let pIp = document.createElement("p"); + pIp.textContent = "IP: "+session.ip; + mainDiv.appendChild(pIp); + let pConnection = document.createElement("p"); + pConnection.textContent = "Connection: "; + if (session.outgoing) { + pConnection.textContent += "outgoing"; + } else { + pConnection.textContent += "incomming"; + } + mainDiv.appendChild(pConnection); + } + showPopup(mainDiv); + } +} document.querySelector("#refresher button").onclick = function() { socket.send("refresh"); } -function onNewSession(sessionId, outgoing, name) { +//source: https://stackoverflow.com/a/14919494 +function humanFileSize(bytes, dp=1) { + const thresh = 1000; + if (Math.abs(bytes) < thresh) { + return bytes + ' B'; + } + const units = ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; + let u = -1; + const r = 10**dp; + do { + bytes /= thresh; + ++u; + } while (Math.round(Math.abs(bytes) * r) / r >= thresh && u < units.length - 1); + return bytes.toFixed(dp) + ' ' + units[u]; +} +//source: https://www.w3schools.com/js/js_cookies.asp +function getCookie(cname) { + var name = cname + "="; + var decodedCookie = decodeURIComponent(document.cookie); + var ca = decodedCookie.split(';'); + for(var i = 0; i { let name; if (entry[0]) { //outgoing msg - name = identity_name; + name = identityName; } else { name = sessionsData.get(currentSessionId).name; } diff --git a/src/main.rs b/src/main.rs index 8c543ee..517bbe2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -93,16 +93,25 @@ async fn websocket_worker(mut ui_connection: UiConnection, global_vars: Arc 0 { - ui_connection.set_not_seen(not_seen); + { + let not_seen = session_manager.not_seen.read().unwrap(); + if not_seen.len() > 0 { + ui_connection.set_not_seen(not_seen.clone()); + } } session_manager.get_saved_msgs().into_iter().for_each(|msgs| { ui_connection.load_msgs(&msgs.0, &msgs.1); @@ -181,13 +190,6 @@ async fn websocket_worker(mut ui_connection: UiConnection, global_vars: Arc print_error!(e) } } - "fingerprints" => { - let session_id: usize = args[1].parse().unwrap(); - let (local, peer) = session_manager.get_public_keys(&session_id); - let local = crypto::generate_fingerprint(&local); - let peer = crypto::generate_fingerprint(&peer); - ui_connection.fingerprints(&local, &peer); - } "verify" => { let session_id: usize = args[1].parse().unwrap(); match session_manager.set_verified(&session_id) { @@ -509,9 +511,11 @@ fn index_not_logged_in(global_vars: &Arc>) -> HttpResponse { async fn handle_index(req: HttpRequest) -> HttpResponse { let global_vars = req.app_data::>>>().unwrap(); if is_authenticated(&req) { + let global_vars_read = global_vars.read().unwrap(); HttpResponse::Ok().body( include_str!("frontend/index.html") - .replace("WEBSOCKET_PORT", &global_vars.read().unwrap().websocket_port.to_string()) + .replace("IDENTITY_FINGERPRINT", &crypto::generate_fingerprint(&global_vars_read.session_manager.get_my_public_key())) + .replace("WEBSOCKET_PORT", &global_vars_read.websocket_port.to_string()) .replace("IS_IDENTITY_PROTECTED", &Identity::is_protected().unwrap().to_string()) ) } else { diff --git a/src/session_manager/mod.rs b/src/session_manager/mod.rs index 4672a0f..4eae0cc 100644 --- a/src/session_manager/mod.rs +++ b/src/session_manager/mod.rs @@ -9,7 +9,7 @@ use session::Session; use ed25519_dalek::PUBLIC_KEY_LENGTH; use uuid::Uuid; use platform_dirs::UserDirs; -use crate::{constants, discovery, identity::{Contact, Identity}, utils::{get_unix_timestamp, get_not_used_path}, print_error}; +use crate::{constants, crypto, discovery, identity::{Contact, Identity}, print_error, utils::{get_unix_timestamp, get_not_used_path}}; use crate::ui_interface::UiConnection; #[derive(Display, Debug, PartialEq, Eq)] @@ -19,7 +19,7 @@ pub enum SessionError { TransmissionCorrupted, BufferTooLarge, InvalidSessionId, - Unknown + Unknown, } enum SessionCommand { @@ -55,7 +55,8 @@ pub struct LargeFileDownload { pub struct SessionData { pub name: String, pub outgoing: bool, - peer_public_key: [u8; PUBLIC_KEY_LENGTH], + pub peer_public_key: [u8; PUBLIC_KEY_LENGTH], + pub ip: IpAddr, sender: Sender, pub file_download: Option, } @@ -68,7 +69,7 @@ pub struct SessionManager { loaded_contacts: RwLock>, pub last_loaded_msg_offsets: RwLock>, pub saved_msgs: Mutex)>>>, - not_seen: RwLock>, + pub not_seen: RwLock>, mdns_service: Mutex>, listener_stop_signal: Mutex>>, } @@ -480,6 +481,7 @@ impl SessionManager { } }; if let Some(mut session) = session { + let ip = session.get_ip(); let mut is_contact = false; let session_data = { let mut sessions = session_manager.sessions.write().unwrap(); @@ -493,9 +495,10 @@ impl SessionManager { if is_new_session && session_manager.is_identity_loaded() { //check if we didn't logged out during the handshake let (sender, receiver) = mpsc::channel(32); let session_data = SessionData{ - name: session.get_ip(), + name: ip.to_string(), outgoing, peer_public_key, + ip, sender: sender, file_download: None, }; @@ -524,7 +527,7 @@ impl SessionManager { if let Some(session_data) = session_data { let (session_id, receiver) = session_data; session_manager.with_ui_connection(|ui_connection| { - ui_connection.on_new_session(&session_id, &session.get_ip(), outgoing, None); + ui_connection.on_new_session(&session_id, &ip.to_string(), outgoing, &crypto::generate_fingerprint(&peer_public_key), ip, None); }); if !is_contact { match session.encrypt_and_send(&protocol::ask_name()).await { @@ -567,24 +570,14 @@ impl SessionManager { Ok(()) } - pub fn list_contacts(&self) -> Vec<(usize, String, bool)> { - self.loaded_contacts.read().unwrap().iter().map(|c| (*c.0, c.1.name.clone(), c.1.verified)).collect() + pub fn list_contacts(&self) -> Vec<(usize, String, bool, [u8; PUBLIC_KEY_LENGTH])> { + self.loaded_contacts.read().unwrap().iter().map(|c| (*c.0, c.1.name.clone(), c.1.verified, c.1.public_key)).collect() } pub fn get_saved_msgs(&self) -> HashMap)>> { self.saved_msgs.lock().unwrap().clone() } - pub fn get_peer_public_key(&self, session_id: &usize) -> Option<[u8; PUBLIC_KEY_LENGTH]> { - let sessions = self.sessions.read().unwrap(); - let session = sessions.get(session_id)?; - Some(session.peer_public_key) - } - - pub fn list_not_seen(&self) -> Vec { - self.not_seen.read().unwrap().clone() - } - pub fn set_seen(&self, session_id: usize, seen: bool) { let mut not_seen = self.not_seen.write().unwrap(); if seen { @@ -608,7 +601,7 @@ impl SessionManager { } pub fn add_contact(&self, session_id: usize, name: String) -> Result<(), rusqlite::Error> { - let contact = self.identity.read().unwrap().as_ref().unwrap().add_contact(name, self.get_peer_public_key(&session_id).unwrap())?; + let contact = self.identity.read().unwrap().as_ref().unwrap().add_contact(name, self.sessions.read().unwrap().get(&session_id).unwrap().peer_public_key)?; self.loaded_contacts.write().unwrap().insert(session_id, contact); self.last_loaded_msg_offsets.write().unwrap().insert(session_id, 0); Ok(()) @@ -671,8 +664,8 @@ impl SessionManager { msgs } - pub fn get_public_keys(&self, session_id: &usize) -> ([u8; PUBLIC_KEY_LENGTH], [u8; PUBLIC_KEY_LENGTH]) { - (self.identity.read().unwrap().as_ref().unwrap().get_public_key(), self.loaded_contacts.read().unwrap().get(session_id).unwrap().public_key) + pub fn get_my_public_key(&self) -> [u8; PUBLIC_KEY_LENGTH] { + self.identity.read().unwrap().as_ref().unwrap().get_public_key() } pub fn get_my_name(&self) -> String { diff --git a/src/session_manager/session.rs b/src/session_manager/session.rs index c619791..e3b0215 100644 --- a/src/session_manager/session.rs +++ b/src/session_manager/session.rs @@ -1,4 +1,4 @@ -use std::{io::ErrorKind, convert::TryInto}; +use std::{convert::TryInto, io::ErrorKind, net::IpAddr}; use tokio::{net::TcpStream, io::{AsyncReadExt, AsyncWriteExt}}; use ed25519_dalek; use ed25519_dalek::{ed25519::signature::Signature, Verifier, PUBLIC_KEY_LENGTH, SIGNATURE_LENGTH}; @@ -48,8 +48,8 @@ impl Session { } } - pub fn get_ip(&self) -> String { - self.stream.peer_addr().unwrap().ip().to_string() + pub fn get_ip(&self) -> IpAddr { + self.stream.peer_addr().unwrap().ip() } async fn socket_read(&mut self, buff: &mut [u8]) -> Result { diff --git a/src/ui_interface.rs b/src/ui_interface.rs index c68e39b..90fec19 100644 --- a/src/ui_interface.rs +++ b/src/ui_interface.rs @@ -1,9 +1,9 @@ -use std::net::TcpStream; +use std::net::{IpAddr, TcpStream}; use tungstenite::{WebSocket, protocol::Role, Message}; use crate::{protocol, session_manager::LargeFileDownload}; mod ui_messages { - use std::{iter::FromIterator, str::from_utf8}; + use std::{iter::FromIterator, net::IpAddr, str::from_utf8}; use tungstenite::Message; use uuid::Uuid; use crate::{print_error, session_manager::{protocol, LargeFileDownload, FileState}, utils::to_uuid_bytes}; @@ -27,8 +27,8 @@ mod ui_messages { pub fn on_disconnected(session_id: &usize) -> Message { simple_event("disconnected", session_id) } - pub fn on_new_session(session_id: &usize, name: &str, outgoing: bool) -> Message { - Message::from(format!("new_session {} {} {}", session_id, outgoing, name)) + pub fn on_new_session(session_id: &usize, name: &str, outgoing: bool, fingerprint: &str, ip: IpAddr) -> Message { + Message::from(format!("new_session {} {} {} {} {}", session_id, outgoing, fingerprint, ip, name)) } pub fn on_file_received(session_id: &usize, buffer: &[u8]) -> Option { let uuid = Uuid::from_bytes(to_uuid_bytes(&buffer[1..17])?); @@ -98,11 +98,8 @@ mod ui_messages { pub fn on_name_told(session_id: &usize, name: &str) -> Message { Message::from(format!("name_told {} {}", session_id, name)) } - pub fn set_as_contact(session_id: usize, name: &str, verified: bool) -> Message { - Message::from(format!("is_contact {} {} {}", session_id, verified, name)) - } - pub fn fingerprints(local: &str, peer: &str) -> Message { - Message::from(format!("fingerprints {} {}", local, peer)) + pub fn set_as_contact(session_id: usize, name: &str, verified: bool, fingerprint: &str) -> Message { + Message::from(format!("is_contact {} {} {} {}", session_id, verified, fingerprint, name)) } pub fn set_name(new_name: &str) -> Message { Message::from(format!("set_name {}", new_name)) @@ -156,8 +153,8 @@ impl UiConnection { _ => {} } } - pub fn on_new_session(&mut self, session_id: &usize, name: &str, outgoing: bool, file_transfer: Option<&LargeFileDownload>) { - self.write_message(ui_messages::on_new_session(session_id, name, outgoing)); + pub fn on_new_session(&mut self, session_id: &usize, name: &str, outgoing: bool, fingerprint: &str, ip: IpAddr, file_transfer: Option<&LargeFileDownload>) { + self.write_message(ui_messages::on_new_session(session_id, name, outgoing, fingerprint, ip)); if let Some(file_transfer) = file_transfer { self.write_message(ui_messages::new_file_transfer(session_id, file_transfer)); } @@ -172,8 +169,8 @@ impl UiConnection { pub fn inc_file_transfer(&mut self, session_id: &usize, chunk_size: u64) { self.write_message(ui_messages::inc_file_transfer(session_id, chunk_size)); } - pub fn set_as_contact(&mut self, session_id: usize, name: &str, verified: bool) { - self.write_message(ui_messages::set_as_contact(session_id, name, verified)); + pub fn set_as_contact(&mut self, session_id: usize, name: &str, verified: bool, fingerprint: &str) { + self.write_message(ui_messages::set_as_contact(session_id, name, verified, fingerprint)); } pub fn load_msgs(&mut self, session_id: &usize, msgs: &Vec<(bool, Vec)>) { msgs.into_iter().rev().for_each(|msg| { @@ -186,9 +183,6 @@ impl UiConnection { pub fn set_not_seen(&mut self, session_ids: Vec) { self.write_message(ui_messages::set_not_seen(session_ids)); } - pub fn fingerprints(&mut self, local: &str, peer: &str) { - self.write_message(ui_messages::fingerprints(local, peer)); - } pub fn set_name(&mut self, new_name: &str) { self.write_message(ui_messages::set_name(new_name)); }