Skip to main content

relay_actions/actions/
encrypt.rs

1use std::path::PathBuf;
2
3use crate::{
4    actions::Action,
5    cache::CacheManager,
6    sinks::progress::ProgressSink,
7    storage::{Identity, Storage},
8};
9use relay_lib::{
10    core::{Uuid, Version, chrono::Utc},
11    prelude::{Address, MetaEnvelope, PrivateEnvelope, e2e, sign},
12};
13
14pub struct Encrypt {
15    pub address: Address,
16    pub file: PathBuf,
17    pub subject: Option<String>,
18    pub to: Option<Address>,
19    pub decrypt: bool,
20}
21
22impl Action for Encrypt {
23    type Output = String;
24
25    fn execute(
26        &self,
27        storage: &mut Storage,
28        cache: &mut CacheManager,
29        progress: &mut dyn ProgressSink,
30    ) -> Self::Output {
31        progress.step("Reading file", "Read file");
32        let contents = std::fs::read(&self.file).unwrap_or_else(|e| {
33            progress.error(&format!("{:#?}", e));
34            progress.abort(&format!("Failed to read file: {}", self.file.display()));
35        });
36
37        let extension = self
38            .file
39            .extension()
40            .and_then(|e| e.to_str())
41            .unwrap_or("plain");
42
43        progress.step("Reading identity", "Read identity");
44        let identity = storage
45            .root
46            .get_identity(&self.address)
47            .unwrap_or_else(|| {
48                progress.abort(&format!("No identity found for address: {}", self.address));
49            })
50            .clone();
51
52        if self.decrypt {
53            self.decrypt_file(storage, cache, contents, progress)
54        } else {
55            self.encrypt_file(cache, identity, contents, extension, progress)
56        }
57    }
58}
59
60impl Encrypt {
61    fn decrypt_file(
62        &self,
63        storage: &mut Storage,
64        cache: &mut CacheManager,
65        contents: Vec<u8>,
66        progress: &mut dyn ProgressSink,
67    ) -> String {
68        progress.step("Parsing envelope", "Parsed envelope");
69        let envelope: MetaEnvelope = serde_json::from_slice(&contents)
70            .unwrap_or_else(|_| progress.abort("Failed to parse envelope"));
71
72        let recipient = storage.root.get_identity(&envelope.to).unwrap_or_else(|| {
73            progress.abort(&format!("No local identity for recipient {}", envelope.to))
74        });
75
76        progress.step("Verifying signature", "Verified signature");
77        let sender = if let Some(local) = storage.root.get_identity(&envelope.from) {
78            local.pub_record.clone()
79        } else {
80            cache
81                .records
82                .user(
83                    &mut cache.agents,
84                    envelope.from.agent(),
85                    envelope.from.user(),
86                )
87                .unwrap_or_else(|e| {
88                    progress.error(&format!("{:#?}", e));
89                    progress.abort("Failed to fetch sender record");
90                })
91        };
92
93        sign::verify(&envelope.payload, &sender)
94            .unwrap_or_else(|_| progress.abort("Failed to verify message signature"));
95
96        progress.step("Decrypting message", "Decrypted message");
97        let decrypted = e2e::decrypt(
98            &recipient.static_secret(),
99            &envelope.crypto,
100            &envelope.payload.payload,
101            &[],
102        )
103        .unwrap_or_else(|e| {
104            progress.error(&format!("{:#?}", e));
105            progress.abort("Failed to decrypt message");
106        });
107
108        let private: PrivateEnvelope = serde_json::from_slice(&decrypted)
109            .unwrap_or_else(|_| progress.abort("Failed to parse decrypted payload"));
110
111        if !private.content_type.starts_with("file/") {
112            progress.abort("Decrypted content is not a file");
113        }
114
115        let extension = private
116            .content_type
117            .strip_prefix("file/")
118            .unwrap_or("plain");
119
120        let output_path = self.file.with_file_name(format!(
121            "{}.decrypted.{}",
122            self.file.file_name().unwrap().to_string_lossy(),
123            extension
124        ));
125
126        progress.step("Writing decrypted file", "Wrote decrypted file");
127        std::fs::write(&output_path, private.message).unwrap_or_else(|e| {
128            progress.error(&format!("{:#?}", e));
129            progress.abort(&format!(
130                "Failed to write decrypted file: {}",
131                output_path.display()
132            ));
133        });
134
135        progress.done();
136        output_path.display().to_string()
137    }
138
139    fn encrypt_file(
140        &self,
141        cache: &mut CacheManager,
142        identity: Identity,
143        contents: Vec<u8>,
144        extension: &str,
145        progress: &mut dyn ProgressSink,
146    ) -> String {
147        progress.step("Fetching recipient keys", "Fetched recipient keys");
148        let recipient = if let Some(to) = &self.to {
149            cache
150                .records
151                .user(&mut cache.agents, to.agent(), to.user())
152                .unwrap_or_else(|e| {
153                    progress.error(&format!("{:#?}", e));
154                    progress.abort("Failed to fetch sender record");
155                })
156        } else {
157            identity.pub_record.clone()
158        };
159
160        progress.step("Encrypting file", "Encrypted file");
161        let private = PrivateEnvelope {
162            content_type: format!("file/{}", extension),
163            message: contents,
164            subject: self.subject.as_ref().map(|s| s.trim().as_bytes().to_vec()),
165        };
166
167        let payload =
168            serde_json::to_vec(&private).unwrap_or_else(|_| progress.abort("Serialize payload"));
169
170        let (crypto, ciphertext) = e2e::encrypt(
171            relay_lib::crypto::OsRng,
172            &recipient.public_key(),
173            &payload,
174            &[],
175        )
176        .unwrap_or_else(|_| progress.abort("Failed to encrypt message"));
177
178        progress.step("Signing message", "Signed message");
179        let signed = sign::sign(&ciphertext, &identity.signing_key());
180
181        let envelope = MetaEnvelope {
182            gid: Uuid::new_v4(),
183            version: Version::parse("0.0.1").unwrap(),
184            from: self.address.clone(),
185            to: self.to.clone().unwrap_or_else(|| self.address.clone()),
186            timestamp: Utc::now(),
187            crypto,
188            payload: signed,
189            self_encrypted: false,
190        };
191
192        let output_path = self.file.with_extension("ref");
193
194        progress.step("Writing encrypted file", "Wrote encrypted file");
195        std::fs::write(
196            &output_path,
197            serde_json::to_vec(&envelope).unwrap_or_else(|_| progress.abort("Serialize envelope")),
198        )
199        .unwrap_or_else(|e| {
200            progress.error(&format!("{:#?}", e));
201            progress.abort(&format!(
202                "Failed to write encrypted file: {}",
203                output_path.display()
204            ));
205        });
206
207        progress.done();
208        output_path.display().to_string()
209    }
210}