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::(key)) + .collect::>>()?; + + 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>>; 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(()) + } +}