AIRA/src/identity.rs

534 lines
26 KiB
Rust

use std::convert::TryInto;
use crypto::CryptoError;
use ed25519_dalek::{Keypair, PUBLIC_KEY_LENGTH};
use rusqlite::{Connection, params};
use platform_dirs::AppDirs;
use utils::to_uuid_bytes;
use uuid::Uuid;
use zeroize::Zeroize;
use crate::{constants, crypto, key_value_table::KeyValueTable, print_error, utils};
const MAIN_TABLE: &str = "main";
const CONTACTS_TABLE: &str = "contacts";
const FILES_TABLE: &str = "files";
const AVATARS_TABLE: &str = "avatars";
const DATABASE_CORRUPED_ERROR: &str = "Database corrupted";
struct DBKeys;
impl<'a> DBKeys {
pub const NAME: &'a str = "name";
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";
pub const AVATAR: &'a str = "avatar";
}
fn bool_to_byte(b: bool) -> u8 {
if b { 75 } else { 30 } //completely arbitrary values
}
fn byte_to_bool(b: u8) -> Result<bool, ()> {
if b == 75 {
Ok(true)
} else if b == 30 {
Ok(false)
} else {
Err(())
}
}
fn get_database_path() -> String {
AppDirs::new(Some(constants::APPLICATION_FOLDER), false).unwrap().data_dir.join(constants::DB_NAME).to_str().unwrap().to_owned()
}
#[derive(Debug, Clone)]
pub struct Message {
pub outgoing: bool,
pub timestamp: u64,
pub data: Vec<u8>,
}
pub struct Contact {
pub uuid: Uuid,
pub public_key: [u8; PUBLIC_KEY_LENGTH],
pub name: String,
pub avatar: Option<Uuid>,
pub verified: bool,
pub seen: bool,
}
struct EncryptedIdentity {
name: String,
encrypted_keypair: Vec<u8>,
salt: Vec<u8>,
encrypted_master_key: Vec<u8>,
encrypted_use_padding: Vec<u8>,
}
pub struct Identity {
pub name: String,
pub keypair: Keypair,
pub master_key: [u8; crypto::MASTER_KEY_LEN],
pub use_padding: bool,
}
impl Identity {
pub fn get_public_key(&self) -> [u8; PUBLIC_KEY_LENGTH] {
self.keypair.public.to_bytes()
}
pub fn add_contact(&self, name: String, avatar_uuid: Option<Uuid>, public_key: [u8; PUBLIC_KEY_LENGTH]) -> Result<Contact, rusqlite::Error> {
let db = Connection::open(get_database_path())?;
db.execute(&("CREATE TABLE IF NOT EXISTS ".to_owned()+CONTACTS_TABLE+"(uuid BLOB PRIMARY KEY, name BLOB, avatar BLOB, key BLOB, verified BLOB, seen BLOB)"), [])?;
let contact_uuid = Uuid::new_v4();
let encrypted_name = crypto::encrypt_data(name.as_bytes(), &self.master_key).unwrap();
let encrypted_public_key = crypto::encrypt_data(&public_key, &self.master_key).unwrap();
let encrypted_verified = crypto::encrypt_data(&[bool_to_byte(false)], &self.master_key).unwrap();
let encrypted_seen = crypto::encrypt_data(&[bool_to_byte(true)], &self.master_key).unwrap();
match avatar_uuid {
Some(avatar_uuid) => db.execute(&format!("INSERT INTO {} (uuid, name, avatar, key, verified, seen) VALUES (?1, ?2, ?3, ?4, ?5, ?6)", CONTACTS_TABLE), params![&contact_uuid.as_bytes()[..], encrypted_name, &avatar_uuid.as_bytes()[..], encrypted_public_key, encrypted_verified, encrypted_seen])?,
None => db.execute(&format!("INSERT INTO {} (uuid, name, key, verified, seen) VALUES (?1, ?2, ?3, ?4, ?5)", CONTACTS_TABLE), params![&contact_uuid.as_bytes()[..], encrypted_name, encrypted_public_key, encrypted_verified, encrypted_seen])?
};
Ok(Contact {
uuid: contact_uuid,
public_key,
name,
avatar: avatar_uuid,
verified: false,
seen: true,
})
}
pub fn remove_contact(uuid: &Uuid) -> Result<usize, rusqlite::Error> {
let db = Connection::open(get_database_path())?;
Identity::delete_conversation(uuid)?;
db.execute(&format!("DELETE FROM {} WHERE uuid=?", CONTACTS_TABLE), [&uuid.as_bytes()[..]])
}
pub fn set_verified(&self, uuid: &Uuid) -> Result<usize, rusqlite::Error> {
let db = Connection::open(get_database_path())?;
let encrypted_verified = crypto::encrypt_data(&[bool_to_byte(true)], &self.master_key).unwrap();
db.execute(&format!("UPDATE {} SET verified=?1 WHERE uuid=?2", CONTACTS_TABLE), [encrypted_verified.as_slice(), &uuid.as_bytes()[..]])
}
pub fn change_contact_name(&self, uuid: &Uuid, new_name: &str) -> Result<usize, rusqlite::Error> {
let db = Connection::open(get_database_path())?;
let encrypted_name = crypto::encrypt_data(new_name.as_bytes(), &self.master_key).unwrap();
db.execute(&format!("UPDATE {} SET name=?1 WHERE uuid=?2", CONTACTS_TABLE), [encrypted_name.as_slice(), uuid.as_bytes()])
}
pub fn set_contact_avatar(&self, contact_uuid: &Uuid, avatar_uuid: Option<&Uuid>) -> Result<usize, rusqlite::Error> {
let db = Connection::open(get_database_path())?;
match avatar_uuid {
Some(avatar_uuid) => db.execute(&format!("UPDATE {} SET avatar=?1 WHERE uuid=?2", CONTACTS_TABLE), params![&avatar_uuid.as_bytes()[..], &contact_uuid.as_bytes()[..]]),
None => {
db.execute(&format!("DELETE FROM {} WHERE uuid=(SELECT avatar FROM {} WHERE uuid=?)", AVATARS_TABLE, CONTACTS_TABLE), params![&contact_uuid.as_bytes()[..]])?;
db.execute(&format!("UPDATE {} SET avatar=NULL WHERE uuid=?", CONTACTS_TABLE), params![&contact_uuid.as_bytes()[..]])
}
}
}
pub fn set_contact_seen(&self, uuid: &Uuid, seen: bool) -> Result<usize, rusqlite::Error> {
let db = Connection::open(get_database_path())?;
let encrypted_seen = crypto::encrypt_data(&[bool_to_byte(seen)], &self.master_key).unwrap();
db.execute(&format!("UPDATE {} SET seen=?1 WHERE uuid=?2", CONTACTS_TABLE), [encrypted_seen.as_slice(), uuid.as_bytes()])
}
pub fn load_contacts(&self) -> Option<Vec<Contact>> {
match Connection::open(get_database_path()) {
Ok(db) => {
if let Ok(mut stmt) = db.prepare(&("SELECT uuid, name, avatar, key, verified, seen FROM ".to_owned()+CONTACTS_TABLE)) {
let mut rows = stmt.query([]).unwrap();
let mut contacts = Vec::new();
while let Ok(Some(row)) = rows.next() {
let encrypted_public_key: Vec<u8> = row.get(3).unwrap();
match crypto::decrypt_data(encrypted_public_key.as_slice(), &self.master_key) {
Ok(public_key) => {
let encrypted_name: Vec<u8> = row.get(1).unwrap();
match crypto::decrypt_data(encrypted_name.as_slice(), &self.master_key) {
Ok(name) => {
let encrypted_verified: Vec<u8> = row.get(4).unwrap();
match crypto::decrypt_data(encrypted_verified.as_slice(), &self.master_key) {
Ok(verified) => {
let encrypted_seen: Vec<u8> = row.get(5).unwrap();
match crypto::decrypt_data(encrypted_seen.as_slice(), &self.master_key) {
Ok(seen) => {
let contact_uuid: Vec<u8> = row.get(0).unwrap();
let avatar_result: Result<Vec<u8>, rusqlite::Error> = row.get(2);
let avatar = match avatar_result {
Ok(avatar_uuid) => Some(Uuid::from_bytes(to_uuid_bytes(&avatar_uuid).unwrap())),
Err(_) => None
};
contacts.push(Contact {
uuid: Uuid::from_bytes(to_uuid_bytes(&contact_uuid).unwrap()),
public_key: public_key.try_into().unwrap(),
name: std::str::from_utf8(name.as_slice()).unwrap().to_owned(),
avatar,
verified: byte_to_bool(verified[0]).unwrap(),
seen: byte_to_bool(seen[0]).unwrap(),
})
}
Err(e) => print_error!(e)
}
}
Err(e) => print_error!(e)
}
}
Err(e) => print_error!(e)
}
}
Err(e) => print_error!(e)
}
}
return Some(contacts);
}
}
Err(e) => print_error!(e)
}
None
}
pub fn clear_cache() -> Result<(), rusqlite::Error> {
let db = Connection::open(get_database_path())?;
let mut stmt = db.prepare(&format!("SELECT name FROM sqlite_master WHERE type='table' AND name='{}'", CONTACTS_TABLE))?;
let mut rows = stmt.query([])?;
let contact_table_exists = rows.next()?.is_some();
if contact_table_exists {
#[allow(unused_must_use)]
{
db.execute(&format!("DELETE FROM {} WHERE contact_uuid IS NULL", FILES_TABLE), []);
db.execute(&format!("DELETE FROM {} WHERE uuid NOT IN (SELECT avatar FROM {})", AVATARS_TABLE, CONTACTS_TABLE), []);
}
} else {
db.execute(&format!("DROP TABLE IF EXISTS {}", FILES_TABLE), [])?;
db.execute(&format!("DROP TABLE IF EXISTS {}", AVATARS_TABLE), [])?;
}
Ok(())
}
pub fn load_file(&self, uuid: Uuid) -> Option<Vec<u8>> {
match Connection::open(get_database_path()) {
Ok(db) => {
let mut stmt = db.prepare(&format!("SELECT uuid, data FROM \"{}\"", FILES_TABLE)).unwrap();
let mut rows = stmt.query([]).unwrap();
while let Ok(Some(row)) = rows.next() {
let encrypted_uuid: Vec<u8> = row.get(0).unwrap();
match crypto::decrypt_data(encrypted_uuid.as_slice(), &self.master_key){
Ok(test_uuid) => {
if test_uuid == uuid.as_bytes() {
let encrypted_data: Vec<u8> = row.get(1).unwrap();
match crypto::decrypt_data(encrypted_data.as_slice(), &self.master_key) {
Ok(data) => return Some(data),
Err(e) => print_error!(e)
}
}
}
Err(e) => print_error!(e)
}
}
}
Err(e) => print_error!(e)
}
None
}
pub fn store_file(&self, contact_uuid: Option<Uuid>, data: &[u8]) -> Result<Uuid, rusqlite::Error> {
let db = Connection::open(get_database_path())?;
db.execute(&format!("CREATE TABLE IF NOT EXISTS \"{}\" (contact_uuid BLOB, uuid BLOB, data BLOB)", FILES_TABLE), [])?;
let file_uuid = Uuid::new_v4();
let encrypted_uuid = crypto::encrypt_data(file_uuid.as_bytes(), &self.master_key).unwrap();
let encrypted_data = crypto::encrypt_data(data, &self.master_key).unwrap();
let query = format!("INSERT INTO \"{}\" (contact_uuid, uuid, data) VALUES (?1, ?2, ?3)", FILES_TABLE);
match contact_uuid {
Some(uuid) => db.execute(&query, params![&uuid.as_bytes()[..], &encrypted_uuid, &encrypted_data])?,
None => db.execute(&query, params![None as Option<Vec<u8>>, &encrypted_uuid, &encrypted_data])?
};
Ok(file_uuid)
}
pub fn store_msg(&self, contact_uuid: &Uuid, message: &Message) -> Result<usize, rusqlite::Error> {
let db = Connection::open(get_database_path())?;
db.execute(&format!("CREATE TABLE IF NOT EXISTS \"{}\" (outgoing BLOB, timestamp BLOB, data BLOB)", contact_uuid), [])?;
let outgoing_byte: u8 = bool_to_byte(message.outgoing);
let encrypted_outgoing = crypto::encrypt_data(&[outgoing_byte], &self.master_key).unwrap();
let encrypted_timestamp = crypto::encrypt_data(&message.timestamp.to_be_bytes(), &self.master_key).unwrap();
let encrypted_data = crypto::encrypt_data(&message.data, &self.master_key).unwrap();
db.execute(&format!("INSERT INTO \"{}\" (outgoing, timestamp, data) VALUES (?1, ?2, ?3)", contact_uuid), params![encrypted_outgoing, encrypted_timestamp, encrypted_data])
}
pub fn load_msgs(&self, contact_uuid: &Uuid, offset: usize, mut count: usize) -> Option<Vec<Message>> {
match Connection::open(get_database_path()) {
Ok(db) => {
if let Ok(mut stmt) = db.prepare(&format!("SELECT count(*) FROM \"{}\"", contact_uuid)) {
let mut rows = stmt.query([]).unwrap();
if let Ok(Some(row)) = rows.next() {
let total: usize = row.get(0).unwrap();
if offset < total {
if offset+count >= total {
count = total-offset;
}
let mut stmt = db.prepare(&format!("SELECT outgoing, timestamp, data FROM \"{}\" LIMIT {} OFFSET {}", contact_uuid, count, total-offset-count)).unwrap();
let mut rows = stmt.query([]).unwrap();
let mut msgs = Vec::new();
while let Ok(Some(row)) = rows.next() {
let encrypted_outgoing: Vec<u8> = row.get(0).unwrap();
match crypto::decrypt_data(encrypted_outgoing.as_slice(), &self.master_key){
Ok(outgoing) => {
if let Ok(outgoing) = byte_to_bool(outgoing[0]) {
let encrypted_timestamp: Vec<u8> = row.get(1).unwrap();
match crypto::decrypt_data(&encrypted_timestamp, &self.master_key) {
Ok(timestamp) => {
let encrypted_data: Vec<u8> = row.get(2).unwrap();
match crypto::decrypt_data(encrypted_data.as_slice(), &self.master_key) {
Ok(data) => msgs.push(Message {
outgoing,
timestamp: u64::from_be_bytes(timestamp.try_into().unwrap()),
data,
}),
Err(e) => print_error!(e)
}
}
Err(e) => print_error!(e)
}
}
}
Err(e) => print_error!(e)
}
}
return Some(msgs);
}
}
}
}
Err(e) => print_error!(e)
}
None
}
#[allow(unused_must_use)]
pub fn delete_conversation(contact_uuid: &Uuid) -> Result<usize, rusqlite::Error> {
let db = Connection::open(get_database_path())?;
db.execute(&format!("DELETE FROM {} WHERE contact_uuid=?", FILES_TABLE), &[&contact_uuid.as_bytes()[..]]);
db.execute(&format!("DROP TABLE IF EXISTS \"{}\"", contact_uuid), [])
}
pub fn change_name(&mut self, new_name: String) -> Result<usize, rusqlite::Error> {
let db = KeyValueTable::new(&get_database_path(), MAIN_TABLE)?;
let result = db.update(DBKeys::NAME, new_name.as_bytes());
if result.is_ok() {
self.name = new_name;
}
result
}
pub fn set_use_padding(&mut self, use_padding: bool) -> Result<usize, rusqlite::Error> {
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 store_avatar(&self, avatar: &[u8]) -> Result<Uuid, rusqlite::Error> {
let db = Connection::open(get_database_path())?;
db.execute(&format!("CREATE TABLE IF NOT EXISTS \"{}\" (uuid BLOB PRIMARY KEY, data BLOB)", AVATARS_TABLE), [])?;
let uuid = Uuid::new_v4();
let encrypted_avatar = crypto::encrypt_data(avatar, &self.master_key).unwrap();
db.execute(&format!("INSERT INTO {} (uuid, data) VALUES (?1, ?2)", AVATARS_TABLE), params![&uuid.as_bytes()[..], encrypted_avatar])?;
Ok(uuid)
}
pub fn get_avatar(&self, avatar_uuid: &Uuid) -> Option<Vec<u8>> {
let db = Connection::open(get_database_path()).ok()?;
let mut stmt = db.prepare(&format!("SELECT data FROM {} WHERE uuid=?", AVATARS_TABLE)).unwrap();
let mut rows = stmt.query(params![&avatar_uuid.as_bytes()[..]]).unwrap();
let encrypted_avatar: Vec<u8> = rows.next().ok()??.get(0).unwrap();
match crypto::decrypt_data(&encrypted_avatar, &self.master_key) {
Ok(avatar) => Some(avatar),
Err(e) => {
print_error!(e);
None
}
}
}
pub fn zeroize(&mut self){
self.master_key.zeroize();
self.keypair.secret.zeroize();
}
fn load_encrypted_identity() -> Result<EncryptedIdentity, rusqlite::Error> {
let db = KeyValueTable::new(&get_database_path(), MAIN_TABLE)?;
let name = db.get(DBKeys::NAME)?;
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,
})
}
pub fn load_identity(password: Option<&[u8]>) -> Result<Identity, String> {
match Identity::load_encrypted_identity() {
Ok(encrypted_identity) => {
let master_key: [u8; crypto::MASTER_KEY_LEN] = match password {
Some(password) => match crypto::decrypt_master_key(&encrypted_identity.encrypted_master_key, password, &encrypted_identity.salt) {
Ok(master_key) => master_key,
Err(e) => return Err(
match e {
CryptoError::DecryptionFailed => "Bad password".to_owned(),
CryptoError::InvalidLength => String::from(DATABASE_CORRUPED_ERROR)
}
)
}
None => if encrypted_identity.encrypted_master_key.len() == crypto::MASTER_KEY_LEN {
encrypted_identity.encrypted_master_key.try_into().unwrap()
} else {
return Err(String::from(DATABASE_CORRUPED_ERROR))
}
};
match crypto::decrypt_data(&encrypted_identity.encrypted_keypair, &master_key) {
Ok(keypair) => {
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);
Err(String::from(DATABASE_CORRUPED_ERROR))
}
}
}
Err(e) => Err(e.to_string())
}
}
pub fn get_identity_name() -> Result<String, rusqlite::Error> {
let db = KeyValueTable::new(&get_database_path(), MAIN_TABLE)?;
Ok(std::str::from_utf8(&db.get(DBKeys::NAME)?).unwrap().to_string())
}
pub fn is_protected() -> Result<bool, rusqlite::Error> {
let db = KeyValueTable::new(&get_database_path(), MAIN_TABLE)?;
Ok(db.get(DBKeys::MASTER_KEY)?.len() != crypto::MASTER_KEY_LEN)
}
pub fn create_identidy(name: &str, password: Option<&[u8]>) -> Result<Identity, rusqlite::Error> {
let keypair = Keypair::generate(&mut rand_7::rngs::OsRng);
let master_key = crypto::generate_master_key();
let encrypted_keypair = crypto::encrypt_data(&keypair.to_bytes(), &master_key).unwrap();
let db = KeyValueTable::new(&get_database_path(), MAIN_TABLE)?;
db.set(DBKeys::NAME, name.as_bytes())?;
db.set(DBKeys::KEYPAIR, &encrypted_keypair)?;
let salt = match password {
Some(password) => {
let (salt, encrypted_master_key) = crypto::encrypt_master_key(master_key, password);
db.set(DBKeys::MASTER_KEY, &encrypted_master_key)?;
salt
}
None => {
db.set(DBKeys::MASTER_KEY, &master_key)?; //storing master_key in plaintext
[0; crypto::SALT_LEN]
}
};
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,
})
}
fn update_master_key(master_key: [u8; crypto::MASTER_KEY_LEN], new_password: Option<&[u8]>) -> Result<usize, rusqlite::Error> {
let db = KeyValueTable::new(&get_database_path(), MAIN_TABLE)?;
let salt = match new_password {
Some(new_password) => {
let (salt, encrypted_master_key) = crypto::encrypt_master_key(master_key, new_password);
db.update(DBKeys::MASTER_KEY, &encrypted_master_key)?;
salt
}
None => {
db.update(DBKeys::MASTER_KEY, &master_key)?;
[0; crypto::SALT_LEN]
}
};
db.update(DBKeys::SALT, &salt)
}
pub fn change_password(old_password: Option<&[u8]>, new_password: Option<&[u8]>) -> Result<bool, String> {
match Identity::load_encrypted_identity() {
Ok(encrypted_identity) => {
let master_key: [u8; crypto::MASTER_KEY_LEN] = match old_password {
Some(old_password) => match crypto::decrypt_master_key(&encrypted_identity.encrypted_master_key, old_password, &encrypted_identity.salt) {
Ok(master_key) => master_key,
Err(e) => return match e {
CryptoError::DecryptionFailed => Ok(false),
CryptoError::InvalidLength => Err(String::from(DATABASE_CORRUPED_ERROR))
}
}
None => if encrypted_identity.encrypted_master_key.len() == crypto::MASTER_KEY_LEN {
encrypted_identity.encrypted_master_key.try_into().unwrap()
} else {
return Err(String::from(DATABASE_CORRUPED_ERROR))
}
};
match Identity::update_master_key(master_key, new_password) {
Ok(_) => Ok(true),
Err(e) => Err(e.to_string())
}
}
Err(e) => Err(e.to_string())
}
}
pub fn set_identity_avatar(avatar: &[u8]) -> Result<usize, rusqlite::Error> {
let db = KeyValueTable::new(&get_database_path(), MAIN_TABLE)?;
db.upsert(DBKeys::AVATAR, avatar)
}
pub fn remove_identity_avatar() -> Result<usize, rusqlite::Error> {
let db = KeyValueTable::new(&get_database_path(), MAIN_TABLE)?;
db.del(DBKeys::AVATAR)
}
pub fn get_identity_avatar() -> Result<Vec<u8>, rusqlite::Error> {
let db = KeyValueTable::new(&get_database_path(), MAIN_TABLE)?;
db.get(DBKeys::AVATAR)
}
pub fn delete_identity() -> Result<(), std::io::Error> {
std::fs::remove_file(get_database_path())
}
}
impl Clone for Identity {
fn clone(&self) -> Self {
Identity {
name: self.name.clone(),
keypair: Keypair::from_bytes(&self.keypair.to_bytes()).unwrap(),
master_key: self.master_key,
use_padding: self.use_padding,
}
}
}