You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
139 lines
4.4 KiB
Rust
139 lines
4.4 KiB
Rust
use super::crypt;
|
|
use rusqlite::{Connection, NO_PARAMS};
|
|
use rustc_serialize::base64;
|
|
use rustc_serialize::base64::{FromBase64, ToBase64};
|
|
use std::error::Error;
|
|
use std::path::PathBuf;
|
|
|
|
static DB_INIT_QUERY: &'static str = include_str!("init.sql");
|
|
|
|
pub struct DbHandle {
|
|
conn: Connection,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
struct DbProperty {
|
|
name: String,
|
|
value: Option<String>,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct Account {
|
|
pub id: i64,
|
|
pub host: String,
|
|
pub user: String,
|
|
pub password: String,
|
|
}
|
|
|
|
impl DbHandle {
|
|
pub fn new(db_path: &Option<String>) -> DbHandle {
|
|
let path: PathBuf = db_path
|
|
.as_ref()
|
|
.map(|path: &String| PathBuf::from(path))
|
|
.unwrap_or_else(|| dirs::home_dir().unwrap().join(".foil").to_path_buf());
|
|
let mut conn: Connection = Connection::open(path).unwrap();
|
|
let tx = conn.transaction().unwrap();
|
|
tx.execute_batch(DB_INIT_QUERY).unwrap();
|
|
tx.commit().unwrap();
|
|
DbHandle { conn: conn }
|
|
}
|
|
|
|
pub fn get_db_property(&self, name: &str) -> Result<Option<String>, Box<dyn Error>> {
|
|
let mut stmt = self
|
|
.conn
|
|
.prepare("SELECT name, value FROM props WHERE name=$1")
|
|
.unwrap();
|
|
let mut props = stmt.query_map(&[&name], |row| {
|
|
Ok(DbProperty {
|
|
name: row.get(0)?,
|
|
value: row.get(1)?,
|
|
})
|
|
})?;
|
|
|
|
match props.next() {
|
|
Some(prop) => Ok(Some(prop.unwrap().value.unwrap())),
|
|
None => Ok(None),
|
|
}
|
|
}
|
|
|
|
pub fn set_db_property(&self, name: &str, value: &str) {
|
|
self.conn
|
|
.execute(
|
|
"INSERT OR REPLACE INTO props (name, value) VALUES ($1, $2)",
|
|
&[&name, &value],
|
|
)
|
|
.unwrap();
|
|
}
|
|
|
|
pub fn get_db_property_bytes(&self, name: &str) -> Result<Option<Vec<u8>>, Box<dyn Error>> {
|
|
self.get_db_property(name)
|
|
.map(|option| option.map(|prop| prop.from_base64().unwrap()))
|
|
}
|
|
|
|
pub fn set_db_property_bytes(&self, name: &str, value: &Vec<u8>) {
|
|
let b64value: String = value.to_base64(base64::STANDARD);
|
|
self.set_db_property(name, &b64value);
|
|
}
|
|
|
|
pub fn list_accounts(&mut self, master_key: [u8; 32]) -> Vec<Account> {
|
|
let mut stmt = self.conn
|
|
.prepare("SELECT h.iv, h.ciphertext, h.mac, u.iv, u.ciphertext, u.mac, p.iv, p.ciphertext, p.mac, a.id FROM accounts a \
|
|
LEFT JOIN encrypted_values h ON a.server=h.id \
|
|
LEFT JOIN encrypted_values u ON a.user=u.id \
|
|
LEFT JOIN encrypted_values p ON a.password=p.id")
|
|
.unwrap();
|
|
let result: Vec<Account> = {
|
|
let props = stmt
|
|
.query_map(NO_PARAMS, |row| {
|
|
let host: String = String::from_utf8(decrypt_base64(
|
|
row.get(0).unwrap(),
|
|
row.get(1).unwrap(),
|
|
row.get(2).unwrap(),
|
|
master_key,
|
|
))
|
|
.unwrap();
|
|
let user: String = String::from_utf8(decrypt_base64(
|
|
row.get(3).unwrap(),
|
|
row.get(4).unwrap(),
|
|
row.get(5).unwrap(),
|
|
master_key,
|
|
))
|
|
.unwrap();
|
|
let password: String = String::from_utf8(decrypt_base64(
|
|
row.get(6).unwrap(),
|
|
row.get(7).unwrap(),
|
|
row.get(8).unwrap(),
|
|
master_key,
|
|
))
|
|
.unwrap();
|
|
Ok(Account {
|
|
id: row.get(9).unwrap(),
|
|
host: host,
|
|
user: user,
|
|
password: password,
|
|
})
|
|
})
|
|
.unwrap();
|
|
|
|
props.map(move |x| x.unwrap()).collect()
|
|
};
|
|
result
|
|
}
|
|
}
|
|
|
|
fn decrypt_base64(iv: String, ciphertext: String, mac: String, master_key: [u8; 32]) -> Vec<u8> {
|
|
let mut iv_bytes: [u8; 32] = [0; 32];
|
|
let mut mac_bytes: [u8; 32] = [0; 32];
|
|
|
|
iv_bytes.clone_from_slice(&iv.from_base64().unwrap());
|
|
mac_bytes.clone_from_slice(&mac.from_base64().unwrap());
|
|
|
|
let decrypted: Vec<u8> = crypt::decrypt_value(
|
|
ciphertext.from_base64().unwrap(),
|
|
master_key,
|
|
iv_bytes,
|
|
mac_bytes,
|
|
);
|
|
decrypted
|
|
}
|