fix(reencrypt): also re-encrypt inline encrypted vars in the config
reencrypt only rewrote .age files; the inline 'encrypted = { KEY = "base64" }'
vars in doot.doot were left encrypted to the old recipients, so after adding a
recipient they stayed undecryptable by the new key.
Now each inline var is decrypted, re-encrypted to the current recipients, and
its ciphertext literal swapped in place in the source (textual replace, not an
AST reprint - so the file's formatting and comments are untouched, and an
indirected or non-literal ciphertext is skipped with a warning).
Verified on the real config: all 8 inline vars re-encrypted to the new
recipients and decrypt correctly.
This commit is contained in:
parent
59eae012de
commit
b31953b134
1 changed files with 41 additions and 1 deletions
|
|
@ -55,11 +55,35 @@ pub fn run(config_path: Option<PathBuf>, recipients: Vec<String>) -> 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<PathBuf>, recipients: Vec<String>) -> 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<String> {
|
||||
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)?;
|
||||
|
|
|
|||
Loading…
Reference in a new issue