summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEllie Huxtable <ellie@elliehuxtable.com>2024-02-02 14:05:13 +0000
committerEllie Huxtable <ellie@elliehuxtable.com>2024-02-02 18:01:09 +0000
commit212dc928c9c4cc8b03b9cad83f9b8b81044c2b02 (patch)
treebafda4b459395ec0c6954d5a452eb5a022528dde
parent744f0059c2b503e8dc43a004d95b0a086af8c4e5 (diff)
feat: add verify command to local store
This ensures that the local store can be decrypted with the current in-use key. If it cannot, we can go on to perform maintenance operations and get back into a happy state.
-rw-r--r--atuin-client/src/record/sqlite_store.rs12
-rw-r--r--atuin-client/src/record/store.rs1
-rw-r--r--atuin/src/command/client/store.rs3
-rw-r--r--atuin/src/command/client/store/verify.rs26
4 files changed, 42 insertions, 0 deletions
diff --git a/atuin-client/src/record/sqlite_store.rs b/atuin-client/src/record/sqlite_store.rs
index 8bf200c3..5df446b4 100644
--- a/atuin-client/src/record/sqlite_store.rs
+++ b/atuin-client/src/record/sqlite_store.rs
@@ -300,6 +300,18 @@ impl Store for SqliteStore {
Ok(())
}
+
+ /// Verify that every record in this store can be decrypted with the current key
+ /// Someday maybe also check each tag/record can be deserialized, but not for now.
+ async fn verify(&self, key: &[u8; 32]) -> Result<()> {
+ let all = self.load_all().await?;
+
+ all.into_iter()
+ .map(|record| record.decrypt::<PASETO_V4>(key))
+ .collect::<Result<Vec<_>>>()?;
+
+ Ok(())
+ }
}
#[cfg(test)]
diff --git a/atuin-client/src/record/store.rs b/atuin-client/src/record/store.rs
index 9c052213..04fba630 100644
--- a/atuin-client/src/record/store.rs
+++ b/atuin-client/src/record/store.rs
@@ -29,6 +29,7 @@ pub trait Store {
async fn first(&self, host: HostId, tag: &str) -> Result<Option<Record<EncryptedData>>>;
async fn re_encrypt(&self, old_key: &[u8; 32], new_key: &[u8; 32]) -> Result<()>;
+ async fn verify(&self, key: &[u8; 32]) -> Result<()>;
/// Get the next `limit` records, after and including the given index
async fn next(
diff --git a/atuin/src/command/client/store.rs b/atuin/src/command/client/store.rs
index 016f01b7..4729a0f3 100644
--- a/atuin/src/command/client/store.rs
+++ b/atuin/src/command/client/store.rs
@@ -13,6 +13,7 @@ mod push;
mod rebuild;
mod rekey;
+mod verify;
#[derive(Subcommand, Debug)]
#[command(infer_subcommands = true)]
@@ -20,6 +21,7 @@ pub enum Cmd {
Status,
Rebuild(rebuild::Rebuild),
Rekey(rekey::Rekey),
+ Verify(verify::Verify),
#[cfg(feature = "sync")]
Push(push::Push),
@@ -36,6 +38,7 @@ impl Cmd {
Self::Status => self.status(store).await,
Self::Rebuild(rebuild) => rebuild.run(settings, store, database).await,
Self::Rekey(rekey) => rekey.run(settings, store).await,
+ Self::Verify(verify) => verify.run(settings, store).await,
#[cfg(feature = "sync")]
Self::Push(push) => push.run(settings, store).await,
diff --git a/atuin/src/command/client/store/verify.rs b/atuin/src/command/client/store/verify.rs
new file mode 100644
index 00000000..84bec96a
--- /dev/null
+++ b/atuin/src/command/client/store/verify.rs
@@ -0,0 +1,26 @@
+use clap::Args;
+use eyre::Result;
+
+use atuin_client::{
+ encryption::load_key,
+ record::{sqlite_store::SqliteStore, store::Store},
+ settings::Settings,
+};
+
+#[derive(Args, Debug)]
+pub struct Verify {}
+
+impl Verify {
+ pub async fn run(&self, settings: &Settings, store: SqliteStore) -> Result<()> {
+ println!("Verifying local store can be decrypted with the current key");
+
+ let key = load_key(settings)?;
+
+ match store.verify(&key.into()).await {
+ Ok(()) => println!("Local store encryption verified OK"),
+ Err(e) => println!("Failed to verify local store encryption: {e:?}"),
+ }
+
+ Ok(())
+ }
+}