Skip to main content

vs_store/store/
auth_blobs.rs

1//! `auth_blobs` table CRUD with AES-256-GCM encryption.
2
3use rusqlite::params;
4
5use super::{epoch_secs, Store};
6use crate::auth::{decrypt, encrypt, EncryptedBlob, MasterKey};
7use crate::error::{Result, StoreError};
8use crate::types::AuthBlobMeta;
9
10impl Store {
11    /// Encrypt `plaintext` under `key` and store under `name`. Replaces
12    /// any existing blob with the same name.
13    pub fn save_auth(&mut self, name: &str, key: &MasterKey, plaintext: &[u8]) -> Result<()> {
14        let blob = encrypt(key, plaintext)?;
15        let now = epoch_secs();
16        self.conn().execute(
17            "INSERT INTO auth_blobs(name, ciphertext, nonce, created_at, last_used_at)
18                 VALUES (?1, ?2, ?3, ?4, NULL)
19             ON CONFLICT(name) DO UPDATE
20                SET ciphertext=excluded.ciphertext,
21                    nonce=excluded.nonce,
22                    created_at=excluded.created_at,
23                    last_used_at=NULL",
24            params![name, blob.ciphertext, blob.nonce.to_vec(), now],
25        )?;
26        Ok(())
27    }
28
29    /// Decrypt the named blob. Stamps `last_used_at` on success.
30    pub fn load_auth(&mut self, name: &str, key: &MasterKey) -> Result<Vec<u8>> {
31        let (ciphertext, nonce_vec) = {
32            let mut stmt = self
33                .conn()
34                .prepare("SELECT ciphertext, nonce FROM auth_blobs WHERE name=?1")?;
35            let mut rows = stmt.query([name])?;
36            let row = rows.next()?.ok_or(StoreError::NotFound {
37                kind: "auth_blob",
38                id: name.to_string(),
39            })?;
40            let c: Vec<u8> = row.get(0)?;
41            let n: Vec<u8> = row.get(1)?;
42            (c, n)
43        };
44        if nonce_vec.len() != 12 {
45            return Err(StoreError::Crypto("nonce shape"));
46        }
47        let mut nonce = [0u8; 12];
48        nonce.copy_from_slice(&nonce_vec);
49        let plaintext = decrypt(key, &EncryptedBlob { ciphertext, nonce })?;
50        let now = epoch_secs();
51        self.conn().execute(
52            "UPDATE auth_blobs SET last_used_at=?2 WHERE name=?1",
53            params![name, now],
54        )?;
55        Ok(plaintext)
56    }
57
58    pub fn list_auth(&self) -> Result<Vec<AuthBlobMeta>> {
59        let mut stmt = self
60            .conn()
61            .prepare("SELECT name, created_at, last_used_at FROM auth_blobs ORDER BY name ASC")?;
62        let rows = stmt.query_map([], AuthBlobMeta::from_row)?;
63        Ok(rows.collect::<rusqlite::Result<Vec<_>>>()?)
64    }
65
66    pub fn delete_auth(&mut self, name: &str) -> Result<()> {
67        let n = self
68            .conn()
69            .execute("DELETE FROM auth_blobs WHERE name=?1", params![name])?;
70        if n == 0 {
71            return Err(StoreError::NotFound {
72                kind: "auth_blob",
73                id: name.to_string(),
74            });
75        }
76        Ok(())
77    }
78}