comtrya_lib/atoms/file/
decrypt.rs1use 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 !self.path.exists() {
38 return Ok(Outcome {
39 side_effects: vec![],
40 should_run: true,
41 });
42 }
43
44 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 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 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 assert_eq!(true, decrypt.plan().unwrap().should_run);
116
117 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 assert_eq!(false, another_decrypt.plan().unwrap().should_run);
126
127 Ok(())
128 }
129
130 #[test]
131 fn it_can_execute() -> anyhow::Result<()> {
132 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 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 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}