envvault/cli/commands/
rotate.rs1use zeroize::Zeroize;
8
9use crate::cli::output;
10use crate::cli::{load_keyfile, prompt_new_password, prompt_password_for_vault, vault_path, Cli};
11use crate::config::Settings;
12use crate::crypto::kdf::generate_salt;
13use crate::crypto::keys::MasterKey;
14use crate::errors::Result;
15use crate::vault::format::{StoredArgon2Params, VaultHeader, CURRENT_VERSION};
16use crate::vault::VaultStore;
17
18pub fn execute(cli: &Cli) -> Result<()> {
20 let path = vault_path(cli)?;
21
22 output::info("Enter your current vault password.");
24 let keyfile = load_keyfile(cli)?;
25 let vault_id = path.to_string_lossy();
26 let old_password = prompt_password_for_vault(Some(&vault_id))?;
27 let store = VaultStore::open(&path, old_password.as_bytes(), keyfile.as_deref())?;
28
29 let mut secrets = store.get_all_secrets()?;
31
32 output::info("Choose your new vault password.");
34 let new_password = prompt_new_password()?;
35
36 let cwd = std::env::current_dir()?;
38 let settings = Settings::load(&cwd)?;
39 let params = settings.argon2_params();
40
41 let new_salt = generate_salt();
44 let mut effective_password = match &keyfile {
45 Some(kf) => crate::crypto::keyfile::combine_password_keyfile(new_password.as_bytes(), kf)?,
46 None => new_password.as_bytes().to_vec(),
47 };
48 let mut master_bytes =
49 crate::crypto::kdf::derive_master_key_with_params(&effective_password, &new_salt, ¶ms)?;
50 effective_password.zeroize();
51 let new_master_key = MasterKey::new(master_bytes);
52 master_bytes.zeroize();
53
54 let new_header = VaultHeader {
56 version: CURRENT_VERSION,
57 salt: new_salt.to_vec(),
58 created_at: store.created_at(),
59 environment: store.environment().to_string(),
60 argon2_params: Some(StoredArgon2Params {
61 memory_kib: params.memory_kib,
62 iterations: params.iterations,
63 parallelism: params.parallelism,
64 }),
65 keyfile_hash: store.header().keyfile_hash.clone(),
66 };
67
68 let mut new_store = VaultStore::from_parts(path, new_header, new_master_key);
70
71 for (name, value) in &secrets {
72 new_store.set_secret(name, value)?;
73 }
74
75 for value in secrets.values_mut() {
77 value.zeroize();
78 }
79
80 new_store.save()?;
82
83 crate::audit::log_audit(
84 cli,
85 "rotate-key",
86 None,
87 Some(&format!(
88 "{} secrets re-encrypted",
89 new_store.secret_count()
90 )),
91 );
92
93 output::success(&format!(
94 "Password rotated for '{}' vault ({} secrets re-encrypted)",
95 new_store.environment(),
96 new_store.secret_count()
97 ));
98
99 Ok(())
100}