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");
|
anyhow::bail!("no recipient keys found");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Also re-encrypt encrypted vars from the doot config
|
|
||||||
let (result, _vars) = super::load(&path)?;
|
let (result, _vars) = super::load(&path)?;
|
||||||
|
|
||||||
let mut count = 0;
|
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
|
// Re-encrypt .age files referenced in encrypted: block
|
||||||
for (name, rel_path) in &result.encrypted_files {
|
for (name, rel_path) in &result.encrypted_files {
|
||||||
let full_path = if rel_path.is_relative() {
|
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(())
|
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<()> {
|
fn reencrypt_file(path: &PathBuf, identity_key: &str, recipients: &[String]) -> anyhow::Result<()> {
|
||||||
// Decrypt
|
// Decrypt
|
||||||
let decryption = AgeEncryption::new().with_identity(identity_key)?;
|
let decryption = AgeEncryption::new().with_identity(identity_key)?;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue