summaryrefslogtreecommitdiffstats
path: root/src/client/database.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/database.rs')
-rw-r--r--src/client/database.rs94
1 files changed, 94 insertions, 0 deletions
diff --git a/src/client/database.rs b/src/client/database.rs
new file mode 100644
index 0000000..03a6c45
--- /dev/null
+++ b/src/client/database.rs
@@ -0,0 +1,94 @@
1use rusqlite::Connection;
2use aes_gcm::{Aes256Gcm, Key, Nonce};
3use aes_gcm::aead::{Aead, KeyInit};
4use pbkdf2::pbkdf2;
5use hmac::Hmac;
6use sha2::Sha256;
7use rand::Rng;
8
9pub fn init_db() -> Connection {
10 let conn = Connection::open("client.db").unwrap();
11 conn.execute(
12 "CREATE TABLE IF NOT EXISTS hosts (
13 host TEXT PRIMARY KEY,
14 last_used DATETIME DEFAULT CURRENT_TIMESTAMP
15 )",
16 [],
17 ).unwrap();
18 conn.execute(
19 "CREATE TABLE IF NOT EXISTS credentials (
20 host TEXT PRIMARY KEY,
21 username TEXT NOT NULL,
22 encrypted_password BLOB NOT NULL
23 )",
24 [],
25 ).unwrap();
26 conn
27}
28
29pub fn get_last_host(conn: &Connection) -> Option<String> {
30 let mut stmt = conn.prepare("SELECT host FROM hosts ORDER BY last_used DESC LIMIT 1").unwrap();
31 let mut rows = stmt.query([]).unwrap();
32 if let Ok(Some(row)) = rows.next() {
33 row.get(0).ok()
34 } else {
35 None
36 }
37}
38
39pub fn save_host(conn: &Connection, host: &str) {
40 conn.execute(
41 "INSERT OR REPLACE INTO hosts (host, last_used) VALUES (?1, CURRENT_TIMESTAMP)",
42 [host],
43 ).unwrap();
44}
45
46pub fn save_credentials(conn: &Connection, host: &str, username: &str, password: &str) -> Result<(), aes_gcm::Error> {
47 let encrypted = encrypt_password(password)?;
48 conn.execute(
49 "INSERT OR REPLACE INTO credentials (host, username, encrypted_password) VALUES (?1, ?2, ?3)",
50 (host, username, &encrypted),
51 ).unwrap();
52 Ok(())
53}
54
55pub 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();
57 let mut rows = stmt.query([host]).unwrap();
58 if let Ok(Some(row)) = rows.next() {
59 let username: String = row.get(0).unwrap();
60 let encrypted: Vec<u8> = row.get(1).unwrap();
61 decrypt_password(&encrypted).map(|password| (username, password))
62 } else {
63 None
64 }
65}
66
67fn 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
74fn 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
86fn 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}