diff --git a/Cargo.toml b/Cargo.toml
index 4a38c8a..c4aab21 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -8,6 +8,7 @@ edition = "2018"
rand-8 = {package = "rand", version = "0.8.3"}
rand-7 = {package = "rand", version = "0.7.3"}
tokio = {version = "1", features = ["rt", "rt-multi-thread", "macros", "net", "io-util"]}
+async-trait = "0.1.5"
lazy_static = "1.4"
socket2 = "0.4.0"
rusqlite = {version = "0.25.1", features = ["bundled"]}
diff --git a/src/frontend/index.css b/src/frontend/index.css
index e071e65..22da76c 100644
--- a/src/frontend/index.css
+++ b/src/frontend/index.css
@@ -107,9 +107,12 @@ input[type="file"] {
margin: auto;
}
.popup section {
- font-weight: bold;
display: block;
margin-bottom: 20px;
+ border-top: 1px solid black;
+}
+.popup section:first-of-type {
+ border-top: unset;
}
.popup input {
display: block;
@@ -128,6 +131,62 @@ input[type="file"] {
display: inline-block;
vertical-align: middle;
}
+label {
+ cursor: pointer;
+}
+.switch_preference {
+ display: flex;
+ align-items: center;
+}
+.preference_description {
+ flex-grow: 1;
+ width: 0; /*fix unknown display bug of .switch*/
+ margin-right: 20px;
+}
+.preference_description p:last-of-type {
+ font-size: .8em;
+}
+.switch {
+ position: relative;
+ display: inline-block;
+ width: 60px;
+ height: 34px;
+}
+.switch input {
+ opacity: 0;
+ width: 0;
+ height: 0;
+}
+.switch span {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: #ccc;
+ border-radius: 34px;
+ transition: .3s;
+}
+.switch span::before {
+ position: absolute;
+ content: "";
+ height: 26px;
+ width: 26px;
+ left: 4px;
+ bottom: 4px;
+ background-color: white;
+ border-radius: 50%;
+ transition: .3s;
+}
+.switch input:checked + span {
+ background-color: var(--accent);
+}
+.switch input:focus + span {
+ box-shadow: 0 0 1px var(--accent);
+}
+.switch input:checked + span:before {
+ transform: translateX(26px);
+}
#session_info .name {
display: flex;
justify-content: center;
@@ -158,8 +217,6 @@ input[type="file"] {
}
#right_panel {
background-color: #15191E;
- display: flex;
- flex-direction: column;
overflow: hidden;
}
#me {
diff --git a/src/frontend/index.html b/src/frontend/index.html
index 6b1b1db..ca6b87e 100644
--- a/src/frontend/index.html
+++ b/src/frontend/index.html
@@ -68,6 +68,7 @@
let identityFingerprint = "IDENTITY_FINGERPRINT";
let isIdentityProtected = IS_IDENTITY_PROTECTED;
let websocketPort = WEBSOCKET_PORT;
+ let usePadding = PSEC_PADDING;
diff --git a/src/frontend/index.js b/src/frontend/index.js
index 39cdb41..4be2ab3 100644
--- a/src/frontend/index.js
+++ b/src/frontend/index.js
@@ -241,7 +241,9 @@ profile_div.onclick = function() {
fingerprint.textContent = beautifyFingerprint(identityFingerprint);
mainDiv.appendChild(fingerprint);
let sectionName = document.createElement("section");
- sectionName.textContent = "Name:";
+ let titleName = document.createElement("h3");
+ titleName.textContent = "Name:";
+ sectionName.appendChild(titleName);
let inputName = document.createElement("input");
inputName.id = "new_name";
inputName.type = "text";
@@ -255,10 +257,16 @@ profile_div.onclick = function() {
};
sectionName.appendChild(saveNameButton);
mainDiv.appendChild(sectionName);
+ let sectionPadding = document.createElement("section");
+ sectionPadding.appendChild(generateSwitchPreference("Use PSEC padding", "PSEC padding obfuscates the length of your messages but uses more network bandwidth.", usePadding, function(checked) {
+ socket.send("set_use_padding "+checked);
+ usePadding = checked;
+ }));
+ mainDiv.appendChild(sectionPadding);
let sectionPassword = document.createElement("section");
- sectionPassword.textContent = "Change your password:";
- sectionPassword.style.paddingTop = "1em";
- sectionPassword.style.borderTop = "1px solid black";
+ let titlePassword = document.createElement("h3");
+ titlePassword.textContent = "Change your password:";
+ sectionPassword.appendChild(titlePassword);
if (isIdentityProtected) {
let input_old_password = document.createElement("input");
input_old_password.type = "password";
@@ -313,8 +321,9 @@ profile_div.onclick = function() {
sectionPassword.appendChild(changePasswordButton);
mainDiv.appendChild(sectionPassword);
let sectionDelete = document.createElement("section");
- sectionDelete.textContent = "Delete identity:";
- sectionDelete.style.paddingTop = "1em";
+ let deleteTitle = document.createElement("h3");
+ deleteTitle.textContent = "Delete identity:";
+ sectionDelete.appendChild(deleteTitle);
sectionDelete.style.borderTop = "1px solid red";
let p = document.createElement("p");
p.textContent = "Deleting your identity will delete all your conversations (messages and files), all your contacts, and your private key. You won't be able to be recognized by your contacts anymore.";
@@ -879,6 +888,32 @@ function generatePopupWarningTitle() {
h2.textContent = "Warning!";
return h2;
}
+function generateSwitchPreference(title, summary, checked, onSwitch) {
+ let label = document.createElement("label");
+ label.classList.add("switch_preference");
+ let divDesc = document.createElement("div");
+ divDesc.classList.add("preference_description");
+ let h3 = document.createElement("h3");
+ h3.textContent = title;
+ divDesc.appendChild(h3);
+ let pSummary = document.createElement("p");
+ pSummary.textContent = summary;
+ divDesc.appendChild(pSummary);
+ label.appendChild(divDesc);
+ let switchDiv = document.createElement("div");
+ switchDiv.classList.add("switch");
+ let input = document.createElement("input");
+ input.type = "checkbox";
+ input.checked = checked;
+ input.onchange = function() {
+ onSwitch(input.checked);
+ };
+ switchDiv.appendChild(input);
+ let span = document.createElement("span");
+ switchDiv.appendChild(span);
+ label.appendChild(switchDiv);
+ return label;
+}
function generateName(name) {
let p = document.createElement("p");
if (typeof name == "undefined") {
diff --git a/src/identity.rs b/src/identity.rs
index 8cca8a1..4d60308 100644
--- a/src/identity.rs
+++ b/src/identity.rs
@@ -20,6 +20,7 @@ impl<'a> DBKeys {
pub const KEYPAIR: &'a str = "keypair";
pub const SALT: &'a str = "salt";
pub const MASTER_KEY: &'a str = "master_key";
+ pub const USE_PADDING: &'a str = "use_padding";
}
fn bool_to_byte(b: bool) -> u8 {
@@ -44,7 +45,8 @@ struct EncryptedIdentity {
name: String,
encrypted_keypair: Vec,
salt: Vec,
- encrypted_master_key: Vec
+ encrypted_master_key: Vec,
+ encrypted_use_padding: Vec,
}
pub struct Contact {
@@ -59,6 +61,7 @@ pub struct Identity {
pub name: String,
keypair: Keypair,
pub master_key: [u8; crypto::MASTER_KEY_LEN],
+ pub use_padding: bool,
}
impl Identity {
@@ -329,6 +332,13 @@ impl Identity {
result
}
+ pub fn set_use_padding(&mut self, use_padding: bool) -> Result {
+ self.use_padding = use_padding;
+ let db = KeyValueTable::new(&get_database_path(), MAIN_TABLE)?;
+ let encrypted_use_padding = crypto::encrypt_data(&[bool_to_byte(use_padding)], &self.master_key).unwrap();
+ db.update(DBKeys::USE_PADDING, &encrypted_use_padding)
+ }
+
pub fn zeroize(&mut self){
self.master_key.zeroize();
self.keypair.secret.zeroize();
@@ -340,11 +350,13 @@ impl Identity {
let encrypted_keypair = db.get(DBKeys::KEYPAIR)?;
let salt = db.get(DBKeys::SALT)?;
let encrypted_master_key = db.get(DBKeys::MASTER_KEY)?;
+ let encrypted_use_padding = db.get(DBKeys::USE_PADDING)?;
Ok(EncryptedIdentity {
name: std::str::from_utf8(&name).unwrap().to_owned(),
encrypted_keypair,
salt,
encrypted_master_key,
+ encrypted_use_padding,
})
}
@@ -370,11 +382,20 @@ impl Identity {
};
match crypto::decrypt_data(&encrypted_identity.encrypted_keypair, &master_key) {
Ok(keypair) => {
- Ok(Identity{
- name: encrypted_identity.name,
- keypair: Keypair::from_bytes(&keypair[..]).unwrap(),
- master_key: master_key,
- })
+ match crypto::decrypt_data(&encrypted_identity.encrypted_use_padding, &master_key) {
+ Ok(use_padding) => {
+ Ok(Identity{
+ name: encrypted_identity.name,
+ keypair: Keypair::from_bytes(&keypair[..]).unwrap(),
+ master_key,
+ use_padding: byte_to_bool(use_padding[0]).unwrap(),
+ })
+ }
+ Err(e) => {
+ print_error!(e);
+ Err(String::from(DATABASE_CORRUPED_ERROR))
+ }
+ }
}
Err(e) => {
print_error!(e);
@@ -412,10 +433,13 @@ impl Identity {
salt
};
db.set(DBKeys::SALT, &salt)?;
+ let encrypted_use_padding = crypto::encrypt_data(&[bool_to_byte(true)], &master_key).unwrap();
+ db.set(DBKeys::USE_PADDING, &encrypted_use_padding)?;
Ok(Identity {
name: name.to_owned(),
keypair,
master_key,
+ use_padding: true,
})
}
@@ -470,6 +494,7 @@ impl Clone for Identity {
name: self.name.clone(),
keypair: Keypair::from_bytes(&self.keypair.to_bytes()).unwrap(),
master_key: self.master_key,
+ use_padding: self.use_padding,
}
}
}
\ No newline at end of file
diff --git a/src/key_value_table.rs b/src/key_value_table.rs
index 510d1da..88c88c5 100644
--- a/src/key_value_table.rs
+++ b/src/key_value_table.rs
@@ -28,4 +28,7 @@ impl<'a> KeyValueTable<'a> {
pub fn update(&self, key: &str, value: &[u8]) -> Result {
self.db.execute(&format!("UPDATE {} SET value=? WHERE key=\"{}\"", self.table_name, key), params![value])
}
+ /*pub fn upsert(&self, key: &str, value: &[u8]) -> Result {
+ self.db.execute(&format!("INSERT INTO {} (key, value) VALUES(?1, ?2) ON CONFLICT(key) DO UPDATE SET value=?3", self.table_name), params![key, value, value])
+ }*/
}
\ No newline at end of file
diff --git a/src/main.rs b/src/main.rs
index 35ef2f9..eccec77 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -91,7 +91,7 @@ fn load_msgs(session_manager: Arc, ui_connection: &mut UiConnect
async fn websocket_worker(mut ui_connection: UiConnection, global_vars: Arc>, worker_done: Arc>) {
let session_manager = global_vars.read().unwrap().session_manager.clone();
- ui_connection.set_name(&session_manager.get_my_name());
+ ui_connection.set_name(&session_manager.identity.read().unwrap().as_ref().unwrap().name);
session_manager.list_contacts().into_iter().for_each(|contact|{
ui_connection.set_as_contact(contact.0, &contact.1, contact.2, &crypto::generate_fingerprint(&contact.3));
session_manager.last_loaded_msg_offsets.write().unwrap().insert(contact.0, 0);
@@ -238,6 +238,12 @@ async fn websocket_worker(mut ui_connection: UiConnection, global_vars: Arc {
+ let use_padding: bool = args[1].parse().unwrap();
+ if let Err(e) = session_manager.identity.write().unwrap().as_mut().unwrap().set_use_padding(use_padding) {
+ print_error!(e);
+ }
+ }
"change_name" => {
let new_name = &msg[args[0].len()+1..];
match session_manager.change_name(new_name.to_string()).await {
@@ -316,7 +322,7 @@ fn handle_load_file(req: HttpRequest, file_info: web::Query) -> HttpRe
match Uuid::from_str(&file_info.uuid) {
Ok(uuid) => {
let global_vars = req.app_data::>>>().unwrap();
- match global_vars.read().unwrap().session_manager.load_file(uuid) {
+ match global_vars.read().unwrap().session_manager.identity.read().unwrap().as_ref().unwrap().load_file(uuid) {
Some(buffer) => {
return HttpResponse::Ok().header("Content-Disposition", format!("attachment; filename=\"{}\"", escape_double_quote(html_escape::decode_html_entities(&file_info.file_name).to_string()))).content_type("application/octet-stream").body(buffer);
}
@@ -560,12 +566,15 @@ async fn handle_index(req: HttpRequest) -> HttpResponse {
let html = fs::read_to_string("src/frontend/index.html").unwrap();
#[cfg(not(debug_assertions))]
let html = include_str!(concat!(env!("OUT_DIR"), "/index.html"));
+ let public_key = global_vars_read.session_manager.identity.read().unwrap().as_ref().unwrap().get_public_key();
+ let use_padding = global_vars_read.session_manager.identity.read().unwrap().as_ref().unwrap().use_padding.to_string();
HttpResponse::Ok().body(
html
.replace("AIRA_VERSION", env!("CARGO_PKG_VERSION"))
- .replace("IDENTITY_FINGERPRINT", &crypto::generate_fingerprint(&global_vars_read.session_manager.get_my_public_key()))
+ .replace("IDENTITY_FINGERPRINT", &crypto::generate_fingerprint(&public_key))
.replace("WEBSOCKET_PORT", &global_vars_read.websocket_port.to_string())
.replace("IS_IDENTITY_PROTECTED", &Identity::is_protected().unwrap().to_string())
+ .replace("PSEC_PADDING", &use_padding)
)
} else {
index_not_logged_in(global_vars)
diff --git a/src/session_manager/mod.rs b/src/session_manager/mod.rs
index 186c6cc..ee1a676 100644
--- a/src/session_manager/mod.rs
+++ b/src/session_manager/mod.rs
@@ -11,8 +11,7 @@ use uuid::Uuid;
use platform_dirs::UserDirs;
use crate::{constants, crypto, discovery, identity::{Contact, Identity}, print_error, utils::{get_unix_timestamp, get_not_used_path}};
use crate::ui_interface::UiConnection;
-
-use self::session::SessionWrite;
+use self::session::{SessionWrite, PSECWriter};
#[derive(Display, Debug, PartialEq, Eq)]
pub enum SessionError {
@@ -67,7 +66,7 @@ pub struct SessionData {
pub struct SessionManager {
session_counter: RwLock,
pub sessions: RwLock>,
- identity: RwLock