reading and writing passwords
This commit is contained in:
parent
b8f407c2b9
commit
7276f84233
17
src/crypt.rs
17
src/crypt.rs
@ -60,3 +60,20 @@ pub fn decrypt_value(value: Vec<u8>, master_key: [u8; 32], iv: [u8; 32], mac: [u
|
||||
cipher.process(&value, output.as_mut_slice());
|
||||
output
|
||||
}
|
||||
|
||||
pub fn encrypt_value(value: &str, master_key: [u8; 32]) -> EncryptedValue {
|
||||
let mut random = OsRng::new().unwrap();
|
||||
let iv: [u8; 32] = random.gen::<[u8; 32]>();
|
||||
|
||||
let mut cipher = aes::ctr(KeySize::KeySize256, &master_key, &iv);
|
||||
let mut output: Vec<u8> = vec![0; value.len() as usize];
|
||||
cipher.process(value.as_bytes(), output.as_mut_slice());
|
||||
|
||||
let mut hmac = Hmac::new(Sha256::new(), &master_key);
|
||||
hmac.input(&output[..]);
|
||||
EncryptedValue {
|
||||
ciphertext: output,
|
||||
iv: iv,
|
||||
mac: hmac.result(),
|
||||
}
|
||||
}
|
||||
|
59
src/db.rs
59
src/db.rs
@ -1,4 +1,5 @@
|
||||
use super::crypt;
|
||||
use crate::crypt::EncryptedValue;
|
||||
use rusqlite::{Connection, NO_PARAMS};
|
||||
use rustc_serialize::base64;
|
||||
use rustc_serialize::base64::{FromBase64, ToBase64};
|
||||
@ -119,6 +120,64 @@ impl DbHandle {
|
||||
};
|
||||
result
|
||||
}
|
||||
|
||||
pub fn write_encrypted_value(&mut self, val: EncryptedValue) -> i64 {
|
||||
let b64ciphertext: String = val.ciphertext.to_base64(base64::STANDARD);
|
||||
let b64iv: String = val.iv.to_base64(base64::STANDARD);
|
||||
let b64mac: String = val.mac.code().to_base64(base64::STANDARD);
|
||||
let tx = self.conn.transaction().unwrap();
|
||||
tx.execute(
|
||||
"INSERT INTO encrypted_values (iv, ciphertext, mac) VALUES ($1, $2, $3)",
|
||||
&[&b64iv, &b64ciphertext, &b64mac],
|
||||
)
|
||||
.unwrap();
|
||||
let rowid: i64 = tx.last_insert_rowid();
|
||||
let _ = tx.commit().unwrap();
|
||||
rowid
|
||||
}
|
||||
|
||||
pub fn write_account(
|
||||
&mut self,
|
||||
host: EncryptedValue,
|
||||
username: EncryptedValue,
|
||||
password: EncryptedValue,
|
||||
) -> i64 {
|
||||
// TODO: This should be a transaction
|
||||
let host_id = self.write_encrypted_value(host);
|
||||
let user_id = self.write_encrypted_value(username);
|
||||
let password_id = self.write_encrypted_value(password);
|
||||
self.conn
|
||||
.execute(
|
||||
"INSERT INTO accounts (server, user, password) VALUES ($1, $2, $3)",
|
||||
&[&host_id, &user_id, &password_id],
|
||||
)
|
||||
.unwrap();
|
||||
self.conn.last_insert_rowid()
|
||||
}
|
||||
|
||||
pub fn delete_account_with_host(&mut self, master_key: [u8; 32], host: &str) {
|
||||
let accounts: Vec<Account> = self.list_accounts(master_key);
|
||||
let tx = self.conn.transaction().unwrap();
|
||||
|
||||
for account in accounts {
|
||||
if account.host == host {
|
||||
tx.execute(
|
||||
"DELETE FROM encrypted_values WHERE exists \
|
||||
(SELECT 1 FROM accounts WHERE \
|
||||
(accounts.server = encrypted_values.id OR \
|
||||
accounts.user = encrypted_values.id OR \
|
||||
accounts.password = encrypted_values.id) AND \
|
||||
accounts.id=$1);",
|
||||
&[&account.id],
|
||||
)
|
||||
.unwrap();
|
||||
tx.execute("DELETE FROM accounts WHERE id=$1;", &[&account.id])
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
let _ = tx.commit().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn decrypt_base64(iv: String, ciphertext: String, mac: String, master_key: [u8; 32]) -> Vec<u8> {
|
||||
|
48
src/main.rs
48
src/main.rs
@ -1,7 +1,7 @@
|
||||
use crypto::hmac::Hmac;
|
||||
use crypto::mac::{Mac, MacResult};
|
||||
use crypto::sha2::Sha256;
|
||||
use dialoguer::{theme::ColorfulTheme, PasswordInput};
|
||||
use dialoguer::{theme::ColorfulTheme, Input, PasswordInput};
|
||||
use docopt::Docopt;
|
||||
use log::debug;
|
||||
use rand::rngs::OsRng;
|
||||
@ -100,6 +100,46 @@ fn list(mut db_conn: db::DbHandle, master_key: [u8; 32]) {
|
||||
}
|
||||
}
|
||||
|
||||
fn get(mut db_conn: db::DbHandle, master_key: [u8; 32]) {
|
||||
println!("Reading a site from the database");
|
||||
let host: String = Input::with_theme(&ColorfulTheme::default())
|
||||
.with_prompt("hostname")
|
||||
.interact()
|
||||
.unwrap();
|
||||
|
||||
for account in db_conn.list_accounts(master_key) {
|
||||
if account.host == host {
|
||||
println!("username: {}", account.user);
|
||||
println!("password: {}", account.password);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set(mut db_conn: db::DbHandle, master_key: [u8; 32]) {
|
||||
println!("Adding a site to the database");
|
||||
let host: String = Input::with_theme(&ColorfulTheme::default())
|
||||
.with_prompt("hostname")
|
||||
.interact()
|
||||
.unwrap();
|
||||
let username: String = Input::with_theme(&ColorfulTheme::default())
|
||||
.with_prompt("username")
|
||||
.interact()
|
||||
.unwrap();
|
||||
let encrypted_host: crypt::EncryptedValue = crypt::encrypt_value(&host, master_key);
|
||||
let encrypted_username: crypt::EncryptedValue = crypt::encrypt_value(&username, master_key);
|
||||
let encrypted_password: crypt::EncryptedValue = {
|
||||
let password: String = PasswordInput::with_theme(&ColorfulTheme::default())
|
||||
.with_prompt("Site password")
|
||||
.with_confirmation("Repeat site password", "Error: the passwords don't match.")
|
||||
.interact()
|
||||
.unwrap();
|
||||
crypt::encrypt_value(&password, master_key)
|
||||
};
|
||||
db_conn.delete_account_with_host(master_key, &host);
|
||||
let _account_id = db_conn.write_account(encrypted_host, encrypted_username, encrypted_password);
|
||||
println!("Successfully added password");
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
pretty_env_logger::init();
|
||||
let args: Args = Docopt::new(USAGE)
|
||||
@ -116,7 +156,11 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||
|
||||
let master_key: [u8; 32] = get_master_key(&mut db_conn);
|
||||
|
||||
if args.cmd_list {
|
||||
if args.cmd_set {
|
||||
set(db_conn, master_key);
|
||||
} else if args.cmd_get {
|
||||
get(db_conn, master_key);
|
||||
} else if args.cmd_list {
|
||||
list(db_conn, master_key);
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user