diff options
Diffstat (limited to 'src/bin/client/database.rs')
| -rw-r--r-- | src/bin/client/database.rs | 54 |
1 files changed, 9 insertions, 45 deletions
diff --git a/src/bin/client/database.rs b/src/bin/client/database.rs index 03a6c45..da069a7 100644 --- a/src/bin/client/database.rs +++ b/src/bin/client/database.rs | |||
| @@ -1,10 +1,4 @@ | |||
| 1 | use rusqlite::Connection; | 1 | use rusqlite::Connection; |
| 2 | use aes_gcm::{Aes256Gcm, Key, Nonce}; | ||
| 3 | use aes_gcm::aead::{Aead, KeyInit}; | ||
| 4 | use pbkdf2::pbkdf2; | ||
| 5 | use hmac::Hmac; | ||
| 6 | use sha2::Sha256; | ||
| 7 | use rand::Rng; | ||
| 8 | 2 | ||
| 9 | pub fn init_db() -> Connection { | 3 | pub fn init_db() -> Connection { |
| 10 | let conn = Connection::open("client.db").unwrap(); | 4 | let conn = Connection::open("client.db").unwrap(); |
| @@ -19,7 +13,7 @@ pub fn init_db() -> Connection { | |||
| 19 | "CREATE TABLE IF NOT EXISTS credentials ( | 13 | "CREATE TABLE IF NOT EXISTS credentials ( |
| 20 | host TEXT PRIMARY KEY, | 14 | host TEXT PRIMARY KEY, |
| 21 | username TEXT NOT NULL, | 15 | username TEXT NOT NULL, |
| 22 | encrypted_password BLOB NOT NULL | 16 | password TEXT NOT NULL |
| 23 | )", | 17 | )", |
| 24 | [], | 18 | [], |
| 25 | ).unwrap(); | 19 | ).unwrap(); |
| @@ -43,52 +37,22 @@ pub fn save_host(conn: &Connection, host: &str) { | |||
| 43 | ).unwrap(); | 37 | ).unwrap(); |
| 44 | } | 38 | } |
| 45 | 39 | ||
| 46 | pub fn save_credentials(conn: &Connection, host: &str, username: &str, password: &str) -> Result<(), aes_gcm::Error> { | 40 | pub fn save_credentials(conn: &Connection, host: &str, username: &str, password: &str) -> Result<(), Box<dyn std::error::Error>> { |
| 47 | let encrypted = encrypt_password(password)?; | ||
| 48 | conn.execute( | 41 | conn.execute( |
| 49 | "INSERT OR REPLACE INTO credentials (host, username, encrypted_password) VALUES (?1, ?2, ?3)", | 42 | "INSERT OR REPLACE INTO credentials (host, username, password) VALUES (?1, ?2, ?3)", |
| 50 | (host, username, &encrypted), | 43 | (host, username, password), |
| 51 | ).unwrap(); | 44 | )?; |
| 52 | Ok(()) | 45 | Ok(()) |
| 53 | } | 46 | } |
| 54 | 47 | ||
| 55 | pub fn get_credentials(conn: &Connection, host: &str) -> Option<(String, String)> { | 48 | pub fn get_credentials(conn: &Connection, host: &str) -> Option<(String, String)> { |
| 56 | let mut stmt = conn.prepare("SELECT username, encrypted_password FROM credentials WHERE host = ?1").unwrap(); | 49 | let mut stmt = conn.prepare("SELECT username, password FROM credentials WHERE host = ?1").unwrap(); |
| 57 | let mut rows = stmt.query([host]).unwrap(); | 50 | let mut rows = stmt.query([host]).unwrap(); |
| 58 | if let Ok(Some(row)) = rows.next() { | 51 | if let Ok(Some(row)) = rows.next() { |
| 59 | let username: String = row.get(0).unwrap(); | 52 | let username: String = row.get(0).unwrap(); |
| 60 | let encrypted: Vec<u8> = row.get(1).unwrap(); | 53 | let password: String = row.get(1).unwrap(); |
| 61 | decrypt_password(&encrypted).map(|password| (username, password)) | 54 | Some((username, password)) |
| 62 | } else { | 55 | } else { |
| 63 | None | 56 | None |
| 64 | } | 57 | } |
| 65 | } | 58 | } \ No newline at end of file |
| 66 | |||
| 67 | fn derive_key() -> Key<Aes256Gcm> { | ||
| 68 | let mut key = [0u8; 32]; | ||
| 69 | let salt = b"client_salt"; // Fixed salt for simplicity | ||
| 70 | pbkdf2::<Hmac<Sha256>>(b"client_key", salt, 10000, &mut key).expect("error deriving key"); | ||
| 71 | key.into() | ||
| 72 | } | ||
| 73 | |||
| 74 | fn encrypt_password(password: &str) -> Result<Vec<u8>, aes_gcm::Error> { | ||
| 75 | let key = derive_key(); | ||
| 76 | let cipher = Aes256Gcm::new(&key); | ||
| 77 | let mut nonce_bytes = [0u8; 12]; | ||
| 78 | rand::thread_rng().fill(&mut nonce_bytes); | ||
| 79 | let nonce = Nonce::from_slice(&nonce_bytes); | ||
| 80 | let ciphertext = cipher.encrypt(nonce, password.as_bytes())?; | ||
| 81 | let mut result = nonce_bytes.to_vec(); | ||
| 82 | result.extend(ciphertext); | ||
| 83 | Ok(result) | ||
| 84 | } | ||
| 85 | |||
| 86 | fn decrypt_password(encrypted: &[u8]) -> Option<String> { | ||
| 87 | if encrypted.len() < 12 { | ||
| 88 | return None; | ||
| 89 | } | ||
| 90 | let key = derive_key(); | ||
| 91 | let cipher = Aes256Gcm::new(&key); | ||
| 92 | let nonce = Nonce::from_slice(&encrypted[..12]); | ||
| 93 | cipher.decrypt(nonce, &encrypted[12..]).ok().and_then(|plaintext| String::from_utf8(plaintext).ok()) | ||
| 94 | } | ||
