376 lines
17 KiB
Rust
376 lines
17 KiB
Rust
use std::{convert::TryInto, path::Path, str::FromStr};
|
|
use crypto::CryptoError;
|
|
use ed25519_dalek::{Keypair, Signer, SIGNATURE_LENGTH, PUBLIC_KEY_LENGTH};
|
|
use rusqlite::{Connection, NO_PARAMS, params};
|
|
use dirs;
|
|
use utils::to_uuid_bytes;
|
|
use uuid::Uuid;
|
|
use zeroize::Zeroize;
|
|
use crate::{constants, crypto, print_error, utils};
|
|
|
|
const DB_NAME: &str = "Identities.db";
|
|
const IDENTITY_TABLE: &str = "identities";
|
|
const CONTACTS_TABLE: &str = "contacts";
|
|
const FILES_TABLE: &str = "files";
|
|
|
|
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 {
|
|
Path::new(&dirs::config_dir().unwrap()).join(constants::APPLICATION_FOLDER).join(DB_NAME).to_str().unwrap().to_owned()
|
|
}
|
|
|
|
struct EncryptedIdentity {
|
|
name: String,
|
|
encrypted_keypair: Vec<u8>,
|
|
salt: Vec<u8>,
|
|
encrypted_master_key: Vec<u8>
|
|
}
|
|
|
|
fn get_identity_by_uuid(uuid: Uuid) -> Result<Option<EncryptedIdentity>, rusqlite::Error>{
|
|
let db = Connection::open(get_database_path())?;
|
|
let mut stmt = db.prepare(&("SELECT name, key, salt, masterkey FROM ".to_owned()+IDENTITY_TABLE+" WHERE uuid=?1"))?;
|
|
let mut rows = stmt.query(vec![&uuid.as_bytes()[..]])?;
|
|
Ok(match rows.next()? {
|
|
Some(row) => {
|
|
let name: Vec<u8> = row.get(0)?;
|
|
Some(
|
|
EncryptedIdentity {
|
|
name: std::str::from_utf8(name.as_slice()).unwrap().to_owned(),
|
|
encrypted_keypair: row.get(1)?,
|
|
salt: row.get(2)?,
|
|
encrypted_master_key: row.get(3)?
|
|
}
|
|
)
|
|
}
|
|
None => None
|
|
})
|
|
}
|
|
|
|
pub struct Contact {
|
|
pub uuid: Uuid,
|
|
pub public_key: [u8; PUBLIC_KEY_LENGTH],
|
|
pub name: String,
|
|
pub verified: bool,
|
|
}
|
|
|
|
pub struct Identity {
|
|
pub uuid: Uuid,
|
|
pub name: String,
|
|
key: Keypair,
|
|
pub master_key: [u8; crypto::MASTER_KEY_LEN]
|
|
}
|
|
|
|
impl Identity {
|
|
|
|
pub fn sign(&self, input: &[u8]) -> [u8; SIGNATURE_LENGTH] {
|
|
self.key.sign(input).to_bytes()
|
|
}
|
|
|
|
pub fn get_public_key(&self) -> [u8; PUBLIC_KEY_LENGTH] {
|
|
self.key.public.to_bytes()
|
|
}
|
|
|
|
fn get_database_path(&self) -> String {
|
|
Path::new(&dirs::config_dir().unwrap()).join(constants::APPLICATION_FOLDER).join(self.uuid.to_string()+".db").to_str().unwrap().to_owned()
|
|
}
|
|
|
|
pub fn add_contact(&self, name: String, public_key: [u8; PUBLIC_KEY_LENGTH]) -> Result<Contact, rusqlite::Error> {
|
|
let db = Connection::open(self.get_database_path())?;
|
|
db.execute(&("CREATE TABLE IF NOT EXISTS ".to_owned()+CONTACTS_TABLE+"(uuid BLOB PRIMARY KEY, name BLOB, key BLOB, verified BLOB)"), NO_PARAMS)?;
|
|
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();
|
|
db.execute(&("INSERT INTO ".to_owned()+CONTACTS_TABLE+" (uuid, name, key, verified) VALUES (?1, ?2, ?3, ?4)"), vec![&contact_uuid.as_bytes()[..], encrypted_name.as_slice(), encrypted_public_key.as_slice(), encrypted_verified.as_slice()])?;
|
|
Ok(Contact {
|
|
uuid: contact_uuid,
|
|
public_key: public_key,
|
|
name: name,
|
|
verified: false
|
|
})
|
|
}
|
|
|
|
pub fn remove_contact(&self, uuid: &Uuid) -> Result<(), rusqlite::Error> {
|
|
let db = Connection::open(self.get_database_path())?;
|
|
db.execute(&("DELETE FROM ".to_owned()+CONTACTS_TABLE+" WHERE uuid=?"), &[&uuid.as_bytes()[..]])?;
|
|
db.execute(&format!("DROP TABLE IF EXISTS \"{}\"", uuid), NO_PARAMS)?;
|
|
db.execute(&format!("DELETE FROM {} WHERE contact_uuid=?", FILES_TABLE), &[&uuid.as_bytes()[..]])?;
|
|
Ok(())
|
|
}
|
|
|
|
pub fn set_verified(&self, uuid: &Uuid) -> Result<(), rusqlite::Error> {
|
|
let db = Connection::open(self.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()[..]])?;
|
|
Ok(())
|
|
}
|
|
|
|
pub fn load_contacts(&self) -> Option<Vec<Contact>> {
|
|
match Connection::open(self.get_database_path()) {
|
|
Ok(db) => {
|
|
match db.prepare(&("SELECT uuid, name, key, verified FROM ".to_owned()+CONTACTS_TABLE)) {
|
|
Ok(mut stmt) => {
|
|
let mut rows = stmt.query(NO_PARAMS).unwrap();
|
|
let mut contacts = Vec::new();
|
|
while let Some(row) = rows.next().unwrap() {
|
|
let encrypted_public_key: Vec<u8> = row.get(2).unwrap();
|
|
match crypto::decrypt_data(encrypted_public_key.as_slice(), &self.master_key){
|
|
Ok(public_key) => {
|
|
if public_key.len() == PUBLIC_KEY_LENGTH {
|
|
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(3).unwrap();
|
|
match crypto::decrypt_data(encrypted_verified.as_slice(), &self.master_key){
|
|
Ok(verified) => {
|
|
let uuid: Vec<u8> = row.get(0).unwrap();
|
|
match to_uuid_bytes(&uuid) {
|
|
Some(uuid_bytes) => {
|
|
contacts.push(Contact {
|
|
uuid: Uuid::from_bytes(uuid_bytes),
|
|
public_key: public_key.try_into().unwrap(),
|
|
name: std::str::from_utf8(name.as_slice()).unwrap().to_owned(),
|
|
verified: byte_to_bool(verified[0]).unwrap()
|
|
})
|
|
}
|
|
None => {}
|
|
}
|
|
}
|
|
Err(e) => print_error(e)
|
|
}
|
|
}
|
|
Err(e) => print_error(e)
|
|
}
|
|
} else {
|
|
print_error("Invalid public key length: database corrupted");
|
|
}
|
|
}
|
|
Err(e) => print_error(e)
|
|
}
|
|
}
|
|
Some(contacts)
|
|
}
|
|
Err(e) => {
|
|
print_error(e);
|
|
None
|
|
}
|
|
}
|
|
}
|
|
Err(e) => {
|
|
print_error(e);
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn clear_temporary_files(&self) -> Result<usize, rusqlite::Error> {
|
|
let db = Connection::open(self.get_database_path())?;
|
|
db.execute(&format!("DELETE FROM {} WHERE contact_uuid IS NULL", FILES_TABLE), NO_PARAMS)
|
|
}
|
|
|
|
pub fn load_file(&self, uuid: Uuid) -> Option<Vec<u8>> {
|
|
match Connection::open(self.get_database_path()) {
|
|
Ok(db) => {
|
|
match db.prepare(&format!("SELECT uuid, data FROM \"{}\"", FILES_TABLE)) {
|
|
Ok(mut stmt) => {
|
|
let mut rows = stmt.query(NO_PARAMS).unwrap();
|
|
while let Some(row) = rows.next().unwrap() {
|
|
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)
|
|
}
|
|
}
|
|
None
|
|
}
|
|
Err(e) => {
|
|
print_error(e);
|
|
None
|
|
}
|
|
}
|
|
}
|
|
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(self.get_database_path())?;
|
|
db.execute(&format!("CREATE TABLE IF NOT EXISTS \"{}\" (contact_uuid BLOB, uuid BLOB, data BLOB)", FILES_TABLE), NO_PARAMS)?;
|
|
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, outgoing: bool, data: &[u8]) -> Result<(), rusqlite::Error> {
|
|
let db = Connection::open(self.get_database_path())?;
|
|
db.execute(&format!("CREATE TABLE IF NOT EXISTS \"{}\" (outgoing BLOB, data BLOB)", contact_uuid), NO_PARAMS)?;
|
|
let outgoing_byte: u8 = bool_to_byte(outgoing);
|
|
let encrypted_outgoing = crypto::encrypt_data(&[outgoing_byte], &self.master_key).unwrap();
|
|
let encrypted_data = crypto::encrypt_data(data, &self.master_key).unwrap();
|
|
db.execute(&format!("INSERT INTO \"{}\" (outgoing, data) VALUES (?1, ?2)", contact_uuid), vec![encrypted_outgoing, encrypted_data])?;
|
|
Ok(())
|
|
}
|
|
|
|
pub fn load_msgs(&self, contact_uuid: &Uuid) -> Option<Vec<(bool, Vec<u8>)>> {
|
|
match Connection::open(self.get_database_path()) {
|
|
Ok(db) => {
|
|
match db.prepare(&format!("SELECT outgoing, data FROM \"{}\"", contact_uuid)) {
|
|
Ok(mut stmt) => {
|
|
let mut rows = stmt.query(NO_PARAMS).unwrap();
|
|
let mut msgs = Vec::new();
|
|
while let Some(row) = rows.next().unwrap() {
|
|
let encrypted_outgoing: Vec<u8> = row.get(0).unwrap();
|
|
match crypto::decrypt_data(encrypted_outgoing.as_slice(), &self.master_key){
|
|
Ok(outgoing) => {
|
|
match byte_to_bool(outgoing[0]) {
|
|
Ok(outgoing) => {
|
|
let encrypted_data: Vec<u8> = row.get(1).unwrap();
|
|
match crypto::decrypt_data(encrypted_data.as_slice(), &self.master_key) {
|
|
Ok(data) => {
|
|
msgs.push(
|
|
(
|
|
outgoing,
|
|
data
|
|
)
|
|
)
|
|
},
|
|
Err(e) => print_error(e)
|
|
}
|
|
}
|
|
Err(_) => {}
|
|
}
|
|
|
|
}
|
|
Err(e) => print_error(e)
|
|
}
|
|
}
|
|
Some(msgs)
|
|
}
|
|
Err(e) => {
|
|
print_error(e);
|
|
None
|
|
}
|
|
}
|
|
}
|
|
Err(e) => {
|
|
print_error(e);
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn zeroize(&mut self){
|
|
self.master_key.zeroize();
|
|
self.key.secret.zeroize();
|
|
}
|
|
|
|
pub fn get_identity(uuid: &str, password: &str) -> Result<Identity, String> {
|
|
let uuid = Uuid::from_str(&uuid).unwrap();
|
|
match get_identity_by_uuid(uuid) {
|
|
Ok(encrypted_identity) => {
|
|
match encrypted_identity {
|
|
Some(encrypted_identity) => {
|
|
match crypto::decrypt_master_key(encrypted_identity.encrypted_master_key.as_slice(), password, encrypted_identity.salt.as_slice()) {
|
|
Ok(master_key) => {
|
|
match crypto::decrypt_data(encrypted_identity.encrypted_keypair.as_slice(), &master_key) {
|
|
Ok(keypair) => {
|
|
Ok(Identity{
|
|
uuid: uuid,
|
|
name: encrypted_identity.name,
|
|
key: Keypair::from_bytes(&keypair[..]).unwrap(),
|
|
master_key: master_key
|
|
})
|
|
}
|
|
Err(e) => {
|
|
print_error(e);
|
|
Err("Database corrupted".to_owned())
|
|
}
|
|
}
|
|
}
|
|
Err(e) => {
|
|
Err(match e {
|
|
CryptoError::DecryptionFailed => "Bad password".to_owned(),
|
|
CryptoError::InvalidLength => "Database corrupted".to_owned()
|
|
})
|
|
}
|
|
}
|
|
}
|
|
None => {
|
|
Err("No such identity".to_owned())
|
|
}
|
|
}
|
|
}
|
|
Err(e) => {
|
|
Err(e.to_string())
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn get_identities() -> Result<Vec<(Uuid, String)>, rusqlite::Error> {
|
|
let db = Connection::open(get_database_path())?;
|
|
let mut stmt = db.prepare(&("SELECT uuid, name FROM ".to_owned()+IDENTITY_TABLE))?;
|
|
let mut rows = stmt.query(NO_PARAMS)?;
|
|
let mut identities = Vec::new();
|
|
while let Some(row) = rows.next()? {
|
|
let uuid: Vec<u8> = row.get(0)?;
|
|
let name: Vec<u8> = row.get(1)?;
|
|
match to_uuid_bytes(&uuid) {
|
|
Some(uuid_bytes) => identities.push(
|
|
(
|
|
Uuid::from_bytes(uuid_bytes),
|
|
std::str::from_utf8(name.as_slice()).unwrap().to_owned()
|
|
)
|
|
),
|
|
None => {}
|
|
}
|
|
}
|
|
Ok(identities)
|
|
}
|
|
|
|
pub fn create_new_identidy(name: &str, password: &str) -> Result<Identity, rusqlite::Error> {
|
|
let db = Connection::open(get_database_path())?;
|
|
db.execute(&("CREATE TABLE IF NOT EXISTS ".to_owned()+IDENTITY_TABLE+"(uuid BLOB PRIMARY KEY, name TEXT, key BLOB, salt BLOB, masterkey BLOB)"), NO_PARAMS)?;
|
|
let uuid = Uuid::new_v4();
|
|
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 (salt, encrypted_master_key) = crypto::encrypt_master_key(master_key, password);
|
|
db.execute(&("INSERT INTO ".to_owned()+IDENTITY_TABLE+" (uuid, name, key, salt, masterkey) VALUES (?1, ?2, ?3, ?4, ?5)"), vec![&uuid.as_bytes()[..], name.as_bytes(), encrypted_keypair.as_slice(), &salt, &encrypted_master_key]).unwrap();
|
|
|
|
Ok(Identity {
|
|
uuid: uuid,
|
|
name: name.to_owned(),
|
|
key: keypair,
|
|
master_key: master_key
|
|
})
|
|
}
|
|
} |