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}