use std::io::{Read, Write};
use crate::error::{Error, Result};
use crate::etree::{self, ParseOps};
use crate::{crypto, prot};
use super::{CommonArgs, SmudgeCleanSubcmd};
#[derive(Copy, Clone, Eq, PartialEq)]
pub enum Mode {
Clean,
Smudge,
}
pub fn run(mode: Mode, a: SmudgeCleanSubcmd, common: CommonArgs) -> Result<()> {
let password = lookup_word_password(&common, &a.word)?;
let mut input = Vec::new();
std::io::stdin().read_to_end(&mut input)?;
let stdout = std::io::stdout();
let mut out = stdout.lock();
let policy = Box::new(crypto::CryptoPolicyDefault {}) as Box<dyn crypto::CryptoPolicy>;
let mut paops = ParseOps::new(policy)?;
paops.passwords.insert(a.word.clone(), password);
match mode {
Mode::Clean => {
paops.crypto.cipheropts.alg = a
.cipher
.clone()
.unwrap_or_else(|| "aes-256-gcm-siv-det".to_string());
if let Some(p) = a.pbkdf.as_ref() {
paops.crypto.pbkdfopts.alg = p.clone();
}
let (ct, extfields) = prot::encrypt(
input,
paops.passwords.get(&a.word).unwrap(),
&mut paops.crypto.rng,
&paops.crypto.pbkdfopts,
&paops.crypto.cipheropts,
&mut paops.crypto.pbkdf_cache,
&*paops.crypto.policy,
)?;
let tree: etree::TextTree = vec![etree::TextNode::Encrypted {
keyw: a.word.clone(),
txt: vec![etree::TextNode::Data(ct)],
extfields,
}];
etree::tree_write(&mut out, &tree, &mut paops)?;
}
Mode::Smudge => {
paops.runtime.fname = "<smudge-stdin>".into();
let cursor = std::io::Cursor::new(input);
let tree = etree::parse(cursor, &mut paops)?;
let ext = extract_first_encrypted(&tree, &a.word).ok_or_else(|| Error::BlockShape {
word: a.word.clone(),
reason: "no ENCRYPTED block for this WORD in input".to_string(),
})?;
let pt = prot::decrypt(
ext.ct,
paops.passwords.get(&a.word).unwrap(),
ext.pbkdf.as_deref(),
ext.cipher.as_deref(),
ext.compress.as_deref(),
&mut paops.crypto.pbkdf_cache,
&*paops.crypto.policy,
)?;
out.write_all(&pt)?;
}
}
out.flush()?;
Ok(())
}
struct EncryptedFields {
ct: Vec<u8>,
pbkdf: Option<String>,
cipher: Option<String>,
compress: Option<String>,
}
fn extract_first_encrypted(tree: &etree::TextTree, word: &str) -> Option<EncryptedFields> {
for node in tree {
match node {
etree::TextNode::Encrypted {
keyw,
txt,
extfields,
} if keyw == word => {
if let Some(first) = txt.first() {
let payload = match first {
etree::TextNode::Data(d) => Some(d.clone()),
_ => None,
};
if let Some(ct) = payload {
return Some(EncryptedFields {
ct,
pbkdf: extfields.get("pbkdf").cloned(),
cipher: extfields.get("cipher").cloned(),
compress: extfields.get("compress").cloned(),
});
}
}
}
etree::TextNode::BeginEnd { txt, .. } => {
if let Some(found) = extract_first_encrypted(txt, word) {
return Some(found);
}
}
_ => {}
}
}
None
}
fn lookup_word_password(common: &CommonArgs, word: &str) -> Result<String> {
for (w, p) in &common.password {
if w == word {
return Ok(p.clone());
}
}
if let Ok(env_val) = std::env::var("ENPROPT_KEY")
&& let Some((w, p)) = env_val.split_once('=')
&& w == word
&& !p.is_empty()
{
return Ok(p.to_string());
}
Err(Error::InvalidArg {
arg: "password",
reason: format!("no password supplied for WORD {word}"),
})
}