diff --git a/src/credential_manager.rs b/src/credential_manager.rs index 1d1c9df..ece76b8 100644 --- a/src/credential_manager.rs +++ b/src/credential_manager.rs @@ -1,10 +1,10 @@ use std::fs::create_dir_all; -use aes_gcm::{aead::{Aead, OsRng}, AeadCore, Aes256Gcm, Key, KeyInit}; +use aes_gcm::{aead::{Aead, OsRng}, AeadCore, Aes256Gcm, Key, KeyInit, Nonce}; use app_dirs2::{AppDataType, AppInfo, app_root}; use anyhow::{Result, Context}; use keyring::Entry; -use rusqlite::Connection; +use rusqlite::{Connection, MappedRows}; #[derive(Debug)] struct DatabaseCredential { @@ -12,6 +12,7 @@ struct DatabaseCredential { url: String, user: String, totp_comand_encrypted: Option>, + totp_nonce: Option> } pub struct Credential { @@ -62,7 +63,8 @@ impl CredentialManager { id INTEGER PRIMARY KEY, url TEXT NOT NULL, user TEXT NOT NULL, - totp_command_encrypted BLOB + totp_command_encrypted BLOB, + totp_nonce BLOB )", (), // empty list of parameters. )?; @@ -71,21 +73,27 @@ impl CredentialManager { Ok(conn) } - pub fn get_credential(&self, url: &str) -> Result { + fn get_database_credential_iter(&self, url: &str) -> Result> { let database = self.get_database()?; let mut stmt = database.prepare( - "SELECT id, url, user, totp_comand_encrypted FROM Credentials WHERE url = ?1")?; - let database_credential_iter = stmt.query_map([url], |row| { + "SELECT id, url, user, totp_comand_encrypted, totp_nonce FROM Credentials WHERE url=?1")?; + let rows: Vec = stmt.query_map([url], |row| { Ok(DatabaseCredential { id: row.get(0)?, url: row.get(1)?, user: row.get(2)?, - totp_comand_encrypted: row.get(3)? + totp_comand_encrypted: row.get(3)?, + totp_nonce: row.get(4)? }) - })?; + })?.filter_map(|r| r.ok()).collect::>().try_into()?; + + Ok(rows) + } - let database_credential = database_credential_iter.last().context("Database does not contain credential.")??; + pub fn get_credential(&self, url: &str) -> Result { + let database_rows = self.get_database_credential_iter(url)?; + let database_credential = database_rows.first().context("No elements returned from database.")?; let entry = Entry::new(url, &database_credential.user)?; let password = entry.get_password()?; @@ -94,28 +102,71 @@ impl CredentialManager { if database_credential.totp_comand_encrypted.is_some() { let key: &Key = password.as_bytes().into(); + let nonce_vec = database_credential.totp_nonce.clone().context("No nonce provided for credential")?; + let cipher = Aes256Gcm::new(&key); - let nonce = Aes256Gcm::generate_nonce(&mut OsRng); // 96-bits; unique per message + let nonce = Nonce::from_iter(nonce_vec); let plaintext = cipher.decrypt( &nonce, - database_credential.totp_comand_encrypted.context("TOTP command is empty.")?.as_ref())?; + database_credential.totp_comand_encrypted.clone().context("TOTP command is empty.")?.as_ref())?; totp_command = Some(String::from_utf8(plaintext)?); } Ok(Credential::new(&database_credential.user, &password, totp_command)) } - pub fn has_credential(&self, url: &str) -> bool { - false + pub fn has_credential(&self, url: &str) -> Result { + let database_rows = self.get_database_credential_iter(url)?; + + Ok(!database_rows.is_empty()) } - pub fn remove_credential(&self, url: &str) { - //self.delete_entry_keyring(url) + pub fn remove_credential(&self, url: &str) -> Result<()> { + if self.has_credential(url)? { + let database_rows = self.get_database_credential_iter(url)?; + let database_credential = database_rows.first().context("No elements returned from database.")?; + + let entry = Entry::new(&database_credential.url, &database_credential.user)?; + entry.delete_credential()?; + + let database = self.get_database()?; + + database.execute( + "DELETE FROM Credentials WHERE url=?1", + [url].map(|n| n.to_string()), + )?; + } + + Ok(()) } - pub fn set_credential(&self, url: &str, credential: &Credential) { - + pub fn set_credential(&self, url: &str, credential: &Credential) -> Result<()> { + if self.has_credential(url)? { + self.remove_credential(url)?; + } + + let mut totp_comand_encrypted: Option> = None; + let mut totp_nonce: Option> = None; + if credential.totp_command.is_some() { + let key: &Key = credential.password.as_bytes().into(); + + let cipher = Aes256Gcm::new(&key); + let nonce = Aes256Gcm::generate_nonce(&mut OsRng); // 96-bits; unique per message + let ciphertext = cipher.encrypt(&nonce, credential.totp_command.clone().context("TOTP Command does not exist.")?.as_bytes())?; + + totp_comand_encrypted = Some(ciphertext); + totp_nonce = Some(nonce.as_slice().to_vec()); + } - // todo insert totp command + let database = self.get_database()?; + database.execute( + "INSERT INTO Credential (url, user, totp_command_encrypted, totp_nonce) VALUES (?1)", + (url.to_string(), credential.user.to_string(), totp_comand_encrypted, totp_nonce), + )?; + + let entry = Entry::new(url, &credential.user)?; + entry.set_password(&credential.password)?; + + Ok(()) } } \ No newline at end of file diff --git a/src/subcommands/login_subcommand.rs b/src/subcommands/login_subcommand.rs index 9485253..1cb47c1 100644 --- a/src/subcommands/login_subcommand.rs +++ b/src/subcommands/login_subcommand.rs @@ -24,12 +24,12 @@ impl Subcommand for LoginSubcommand { let credential_manager = CredentialManager { }; - if credential_manager.has_credential(url) { - // get password and totp command - } - else { - // get password and totop command - } + // if credential_manager.has_credential(url) { + // // get password and totp command + // } + // else { + // // get password and totop command + // } // try login // if success store diff --git a/src/subcommands/logout_subcommand.rs b/src/subcommands/logout_subcommand.rs index 403a4ab..8e1a720 100644 --- a/src/subcommands/logout_subcommand.rs +++ b/src/subcommands/logout_subcommand.rs @@ -21,9 +21,9 @@ impl Subcommand for LogoutSubcommand { let url = self.url.as_str(); let credential_manager = CredentialManager { }; - if credential_manager.has_credential(url) { - credential_manager.remove_credential(url); - } + // if credential_manager.has_credential(url) { + // credential_manager.remove_credential(url); + // } } fn parse_args(&mut self, arg_matches: &clap::ArgMatches) -> Option<()> {