comtrya_lib/atoms/file/
decrypt.rs

1use crate::atoms::Outcome;
2
3use super::super::Atom;
4use super::FileAtom;
5use age::armor::ArmoredReader;
6use age::secrecy::Secret;
7use std::io::Read;
8use std::path::PathBuf;
9use tracing::error;
10
11pub struct Decrypt {
12    pub encrypted_content: Vec<u8>,
13    pub passphrase: String,
14    pub path: PathBuf,
15}
16
17impl FileAtom for Decrypt {
18    fn get_path(&self) -> &PathBuf {
19        &self.path
20    }
21}
22
23impl std::fmt::Display for Decrypt {
24    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25        write!(
26            f,
27            "The content needs to be decrypted to {}",
28            self.path.as_path().display()
29        )
30    }
31}
32
33impl Atom for Decrypt {
34    fn plan(&self) -> anyhow::Result<Outcome> {
35        // If the file doesn't exist, assume it's because
36        // another atom is going to provide it.
37        if !self.path.exists() {
38            return Ok(Outcome {
39                side_effects: vec![],
40                should_run: true,
41            });
42        }
43
44        // Decrypting file with provided passphrase makes plan work
45        match decrypt(&self.passphrase, &self.encrypted_content) {
46            Ok(_) => Ok(Outcome {
47                side_effects: vec![],
48                should_run: true,
49            }),
50            Err(err) => {
51                error!(
52                    "Cannot decrypt file {} because {:?}. Skipping.",
53                    self.path.display(),
54                    err
55                );
56
57                Ok(Outcome {
58                    side_effects: vec![],
59                    should_run: false,
60                })
61            }
62        }
63    }
64
65    fn execute(&mut self) -> anyhow::Result<()> {
66        let decrypted_content = decrypt(&self.passphrase, &self.encrypted_content)?;
67
68        std::fs::write(&self.path, decrypted_content)?;
69
70        Ok(())
71    }
72}
73
74fn decrypt(passphrase: &str, encrypted_content: &[u8]) -> anyhow::Result<Vec<u8>> {
75    let decryptor = match age::Decryptor::new(ArmoredReader::new(encrypted_content))? {
76        age::Decryptor::Passphrase(d) => Ok(d),
77        _ => Err(anyhow::anyhow!("Cannot create passphrase decryptor!")),
78    }?;
79
80    let mut decrypted = vec![];
81    let secret = Secret::new(passphrase.to_owned());
82    let mut reader = decryptor.decrypt(&secret, None)?;
83    reader.read_to_end(&mut decrypted)?;
84
85    Ok(decrypted)
86}
87
88#[cfg(test)]
89mod tests {
90    use tempfile::NamedTempFile;
91
92    use super::*;
93    use pretty_assertions::assert_eq;
94    use std::io::Write;
95
96    #[test]
97    fn it_can_plan() -> anyhow::Result<()> {
98        // encrypt and write to file
99        let passphrase = "Teal'c".to_string();
100        let content = b"Shol'va";
101        let encrypted_content = encrypt(passphrase.to_owned(), content.to_vec())?;
102
103        // prepare atom
104        let mut file = NamedTempFile::new()?;
105        file.reopen()?;
106        file.write_all(&encrypted_content)?;
107
108        let decrypt = Decrypt {
109            encrypted_content: encrypted_content.to_owned(),
110            path: file.path().to_path_buf(),
111            passphrase,
112        };
113
114        // plan
115        assert_eq!(true, decrypt.plan().unwrap().should_run);
116
117        // prepare another atom
118        let another_decrypt = Decrypt {
119            encrypted_content: encrypted_content.to_owned(),
120            path: file.path().to_path_buf(),
121            passphrase: "fkbr".to_string(),
122        };
123
124        // plan
125        assert_eq!(false, another_decrypt.plan().unwrap().should_run);
126
127        Ok(())
128    }
129
130    #[test]
131    fn it_can_execute() -> anyhow::Result<()> {
132        // encrypt and write to file
133        let passphrase = "Teal'c".to_string();
134        let content = b"Shol'va";
135        let encrypted_content = encrypt(passphrase.to_owned(), content.to_vec())?;
136
137        // prepare atom
138        let mut file = NamedTempFile::new()?;
139        file.reopen()?;
140        file.write_all(&encrypted_content)?;
141
142        let mut decrypt = Decrypt {
143            encrypted_content: encrypted_content.to_owned(),
144            path: file.path().to_path_buf(),
145            passphrase,
146        };
147
148        // plan, execute
149        assert_eq!(true, decrypt.plan().unwrap().should_run);
150        assert_eq!(true, decrypt.execute().is_ok());
151
152        Ok(())
153    }
154
155    fn encrypt(passphrase: String, content: Vec<u8>) -> anyhow::Result<Vec<u8>> {
156        let secret = Secret::new(passphrase);
157        let encryptor = age::Encryptor::with_user_passphrase(secret);
158
159        let mut encrypted = vec![];
160        let mut writer = encryptor.wrap_output(&mut encrypted)?;
161        writer.write_all(&content)?;
162        writer.finish()?;
163
164        Ok(encrypted)
165    }
166}