diff --git a/crates/doot-cli/src/commands/reencrypt.rs b/crates/doot-cli/src/commands/reencrypt.rs index 3170d3d..663246d 100644 --- a/crates/doot-cli/src/commands/reencrypt.rs +++ b/crates/doot-cli/src/commands/reencrypt.rs @@ -55,11 +55,35 @@ pub fn run(config_path: Option, recipients: Vec) -> anyhow::Res anyhow::bail!("no recipient keys found"); } - // Also re-encrypt encrypted vars from the doot config let (result, _vars) = super::load(&path)?; let mut count = 0; + // Re-encrypt inline encrypted vars (stored as base64 ciphertext in the config + // source) by rewriting their literals in place. + if !result.encrypted_vars.is_empty() { + let mut source = std::fs::read_to_string(&path)?; + let mut changed = 0; + for (name, b64) in &result.encrypted_vars { + let from = format!("\"{b64}\""); + if !source.contains(&from) { + eprintln!( + "skip var {name}: ciphertext not found verbatim in {}", + path.display() + ); + continue; + } + let new_b64 = reencrypt_var(b64, &identity_key, &keys)?; + source = source.replace(&from, &format!("\"{new_b64}\"")); + eprintln!("re-encrypted var {name}"); + changed += 1; + } + if changed > 0 { + std::fs::write(&path, source)?; + count += changed; + } + } + // Re-encrypt .age files referenced in encrypted: block for (name, rel_path) in &result.encrypted_files { let full_path = if rel_path.is_relative() { @@ -144,6 +168,22 @@ pub fn run(config_path: Option, recipients: Vec) -> anyhow::Res Ok(()) } +/// Decrypt a base64 age ciphertext and re-encrypt it to `recipients`, returning +/// the new base64 ciphertext. +fn reencrypt_var(b64: &str, identity_key: &str, recipients: &[String]) -> anyhow::Result { + let data = doot_core::builtins::crypto::base64_decode(b64) + .map_err(|e| anyhow::anyhow!("invalid base64: {e}"))?; + let plaintext = AgeEncryption::new() + .with_identity(identity_key)? + .decrypt(&data)?; + let mut encryption = AgeEncryption::new(); + for key in recipients { + encryption.add_recipient(key)?; + } + let ciphertext = encryption.encrypt(&plaintext)?; + Ok(doot_core::builtins::crypto::base64_encode(&ciphertext)) +} + fn reencrypt_file(path: &PathBuf, identity_key: &str, recipients: &[String]) -> anyhow::Result<()> { // Decrypt let decryption = AgeEncryption::new().with_identity(identity_key)?;