AIRA/src/identity.rs

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
})
}
}