1use std::{
5 convert::TryInto,
6 fs::{rename, File, OpenOptions},
7 io::{Read, Write},
8 path::Path,
9 sync::atomic::{AtomicU8, Ordering},
10};
11
12use crypto::{keys::age, utils::rand};
13use thiserror::Error as DeriveError;
14use zeroize::Zeroizing;
15
16use crate::snapshot::{compress, decompress};
17
18pub const MAGIC: [u8; 5] = [0x50, 0x41, 0x52, 0x54, 0x49];
20
21pub const VERSION: [u8; 2] = [0x3, 0x0];
23pub const KEY_SIZE: usize = 32;
27pub type Key = [u8; KEY_SIZE];
29
30#[derive(Debug, DeriveError)]
31pub enum ReadError {
32 #[error("I/O error: {0}")]
33 Io(#[from] std::io::Error),
34
35 #[error("corrupted file: {0}")]
36 CorruptedContent(String),
37
38 #[error("invalid File: not a snapshot")]
39 InvalidFile,
40
41 #[error("unsupported version: expected `{expected:?}`, found `{found:?}`")]
42 UnsupportedVersion { expected: [u8; 2], found: [u8; 2] },
43
44 #[error("unsupported associated data")]
45 UnsupportedAssociatedData,
46
47 #[error("crypto error: {0:?}")]
48 AgeFormatError(age::DecError),
49}
50
51impl From<age::DecError> for ReadError {
52 fn from(e: age::DecError) -> Self {
53 Self::AgeFormatError(e)
54 }
55}
56
57#[derive(Debug, DeriveError)]
58pub enum WriteError {
59 #[error("I/O error: {0}")]
60 Io(#[from] std::io::Error),
61
62 #[error("generating random bytes failed: {0}")]
63 GenerateRandom(String),
64
65 #[error("corrupted data: {0}")]
66 CorruptedData(String),
67
68 #[error("unsupported associated data")]
69 UnsupportedAssociatedData,
70
71 #[error("incorrect work factor")]
72 IncorrectWorkFactor,
73}
74
75static ENCRYPT_WORK_FACTOR: AtomicU8 = AtomicU8::new(age::RECOMMENDED_MINIMUM_ENCRYPT_WORK_FACTOR);
83
84pub fn get_encrypt_work_factor() -> u8 {
85 ENCRYPT_WORK_FACTOR.load(Ordering::Relaxed)
86}
87
88pub fn try_set_encrypt_work_factor(work_factor: u8) -> Result<(), WriteError> {
89 let _ = age::WorkFactor::try_from(work_factor).map_err(|_| WriteError::IncorrectWorkFactor)?;
90 ENCRYPT_WORK_FACTOR.store(work_factor, Ordering::Relaxed);
91 Ok(())
92}
93
94pub fn encrypt_content<O: Write>(plain: &[u8], output: &mut O, key: &Key) -> Result<(), WriteError> {
108 let work_factor = get_encrypt_work_factor();
109 encrypt_content_with_work_factor(plain, output, key, work_factor)
110}
111
112pub fn encrypt_content_with_work_factor<O: Write>(
125 plain: &[u8],
126 output: &mut O,
127 key: &Key,
128 work_factor: u8,
129) -> Result<(), WriteError> {
130 let work_factor = work_factor.try_into().map_err(|_| WriteError::IncorrectWorkFactor)?;
131 let age = age::encrypt_vec(key, work_factor, plain)
132 .map_err(|e| WriteError::GenerateRandom(format!("failed to generate age randomness: {e:?}")))?;
133 output.write_all(&age[..])?;
134 Ok(())
135}
136
137pub fn decrypt_content<I: Read>(input: &mut I, key: &Key) -> Result<Zeroizing<Vec<u8>>, ReadError> {
142 let max_work_factor = age::RECOMMENDED_MAXIMUM_DECRYPT_WORK_FACTOR;
143 decrypt_content_with_work_factor(input, key, max_work_factor)
144}
145
146pub fn decrypt_content_with_work_factor<I: Read>(
160 input: &mut I,
161 key: &Key,
162 max_work_factor: u8,
163) -> Result<Zeroizing<Vec<u8>>, ReadError> {
164 let mut age = Vec::new();
165 input.read_to_end(&mut age)?;
166
167 age::decrypt_vec(key, max_work_factor, &age[..])
168 .map(Zeroizing::new)
169 .map_err(From::from)
170}
171
172pub fn encrypt_file(plain: &[u8], path: &Path, key: &Key) -> Result<(), WriteError> {
179 let compressed_plain = Zeroizing::new(compress(plain));
182
183 let mut salt = [0u8; 6];
184 rand::fill(&mut salt).map_err(|e| WriteError::GenerateRandom(format!("{}", e)))?;
185
186 let mut s = path.as_os_str().to_os_string();
187 s.push(".");
188 s.push(hex::encode(salt));
189 let tmp = Path::new(&s);
190
191 let mut f = OpenOptions::new().write(true).create_new(true).open(tmp)?;
192 f.write_all(&MAGIC)?;
194 f.write_all(&VERSION)?;
195 encrypt_content(&compressed_plain, &mut f, key)?;
196 f.sync_all()?;
197
198 rename(tmp, path)?;
199
200 Ok(())
201}
202
203pub fn decrypt_file(path: &Path, key: &Key) -> Result<Zeroizing<Vec<u8>>, ReadError> {
206 let mut f: File = OpenOptions::new().read(true).open(path)?;
207 check_min_file_len(&mut f)?;
208 check_header(&mut f)?;
210 let pt = Zeroizing::new(decrypt_content(&mut f, key)?);
211
212 decompress(&pt)
213 .map(Zeroizing::new)
214 .map_err(|e| ReadError::CorruptedContent(format!("decompression failed: {}", e)))
215}
216
217fn check_min_file_len(input: &mut File) -> Result<(), ReadError> {
218 const AGE_HEADER_LEN: usize = 150;
219 const AGE_TAG_LEN: usize = 16;
220 let min = MAGIC.len() + VERSION.len() + AGE_HEADER_LEN + AGE_TAG_LEN;
221 if input.metadata()?.len() >= min as u64 {
222 Ok(())
223 } else {
224 Err(ReadError::InvalidFile)
225 }
226}
227
228fn check_header<I: Read>(input: &mut I) -> Result<(), ReadError> {
230 let mut magic = [0u8; 5];
232 input.read_exact(&mut magic)?;
233 if magic != MAGIC {
234 return Err(ReadError::InvalidFile);
235 }
236
237 let mut version = [0u8; 2];
239 input.read_exact(&mut version)?;
240
241 if version != VERSION {
242 return Err(ReadError::UnsupportedVersion {
243 expected: VERSION,
244 found: version,
245 });
246 }
247
248 Ok(())
249}
250
251#[cfg(test)]
252mod test {
253 use super::*;
254 use stronghold_utils::{
255 random,
256 test_utils::{corrupt, corrupt_file_at},
257 };
258
259 fn random_bytestring() -> Vec<u8> {
260 random::variable_bytestring(4096)
261 }
262
263 fn random_key() -> Key {
264 let mut key: Key = [0u8; KEY_SIZE];
265
266 rand::fill(&mut key).expect("Unable to fill buffer");
267
268 key
269 }
270
271 #[test]
272 fn test_write_read() {
273 let key: Key = random_key();
274 let bs0 = random_bytestring();
275
276 let mut buf = Vec::new();
277 encrypt_content(&bs0, &mut buf, &key).unwrap();
278 let read = decrypt_content(&mut buf.as_slice(), &key).unwrap();
279 assert_eq!(bs0, *read);
280 }
281
282 #[test]
283 #[should_panic]
284 fn test_corrupted_read_write() {
285 let key: Key = random_key();
286 let bs0 = random_bytestring();
287
288 let mut buf = Vec::new();
289 encrypt_content(&bs0, &mut buf, &key).unwrap();
290 corrupt(&mut buf);
291 decrypt_content(&mut buf.as_slice(), &key).unwrap();
292 }
293
294 #[test]
295 fn test_snapshot() {
296 let f = tempfile::tempdir().unwrap();
297 let mut pb = f.into_path();
298 pb.push("snapshot");
299
300 let key: Key = random_key();
301 let bs0 = random_bytestring();
302
303 encrypt_file(&bs0, &pb, &key).unwrap();
304 let bs1 = decrypt_file(&pb, &key).unwrap();
305 assert_eq!(bs0, *bs1);
306 }
307
308 #[test]
309 #[should_panic]
310 fn test_currupted_snapshot() {
311 let f = tempfile::tempdir().unwrap();
312 let mut pb = f.into_path();
313 pb.push("snapshot");
314
315 let key: Key = random_key();
316 let bs0 = random_bytestring();
317
318 encrypt_file(&bs0, &pb, &key).unwrap();
319 corrupt_file_at(&pb);
320 decrypt_file(&pb, &key).unwrap();
321 }
322
323 #[test]
324 fn test_snapshot_overwrite() {
325 let f = tempfile::tempdir().unwrap();
326 let mut pb = f.into_path();
327 pb.push("snapshot");
328
329 encrypt_file(&random_bytestring(), &pb, &random_key()).unwrap();
330
331 let key: Key = random_key();
332 let bs0 = random_bytestring();
333 encrypt_file(&bs0, &pb, &key).unwrap();
334 let bs1 = decrypt_file(&pb, &key).unwrap();
335 assert_eq!(bs0, *bs1);
336 }
337
338 struct TestVector {
339 key: &'static str,
340 data: &'static str,
341 snapshot: &'static str,
342 }
343
344 #[test]
345 fn test_vectors() {
346 let tvs = [
347 TestVector {
348 key: "f6eafe6482445269d3228b3647001c283102116362e870644ba3bfc7f8f109e6",
349 data: "a0dcd6b9a95ca5321cefb443c3d19915eb269072929841d986306982a459229a1866479a64f5ed9ac31ea083ae73859b8e5a3ccd9e3045881602f2ed036d473ef09e88c488f4c0a95e823fd984a1ffd69a9a9d3f7ab63e9bd673020181363b9134f46aa6734a9a9600b01b35740f5161dfad303a8a85ad5edcef31bd76a8d47ae1a46e60824c1023401ddaa5d385f414cc2c1773aca240629e4a80149bdf992d97622c1775399a2c65d5f81f5cfcb79c894971b87a17f655c0c4b88b90ee2ad8bfdcd47d7566b33de7957a5c06d7d5b3cddcf55d45bb78c4d5099753edb51974ab01f9371140b89b56382c7bf28e62e246c2828e0ef45a991cd7b1e5a93ce5587b0e50792c2b44744121e84be0e3d6e01b2488d342622e1602d9a07eca27ffd83fb30e2c0b9700cf45080e415554b75ccfa08913acb9e8",
350 snapshot: "504152544903006167652d656e6372797074696f6e2e6f72672f76310a2d3e20736372797074204d3970496c4d3164662b6353563366716831714465772031310a4e396f794d75686b396e2b75435663314355545555384b656c694f6c733239644d447433376d315a32546b0a2d2d2d204c4f34523579425864416f485865306369365358544d36487265487038527755656842544b444b2b75784d0a2d3ee7908d47abb031c93bcf816a92f09576f4a22301162f4f970c37b20cd242d416632dcdab4b98c50e7f8609945ba035cd2de4feb34bbb681c8541403a5e487602a281f357c8a6682206b277460ca0bf12a1143f5fd5dbbfba5045760bd2b0286a7cca3d5a980708e720424e6efe9b7c183eaadd26cc2f480cf5af1cf5e8175f0a41b8c12ebf7c7b23f6115d9d22ff9288f96099d62ffcd4c8483b30e805011ef172ac0b8aa53c65fa5cbd6ee675bd3c001d92039ab995efd52ad99fdb0156767771c20e73fd90990d6563d78ba923eed588cbd13bfa4288810cdca39541dbf48501b549c9d28cc387ac4c78ac78f753daf8d0418a70cf24d7de61cb9cc861b2be36102e3d3b6079e564b6aa895b1a5880c3d5bc86f10ec0463f076b1351bb5b280c578e5321666fcfe9c0e843a688f60d10b91c48995c4e003067ee8e07acdfa6f39242252d1c5c1442b57deec9c34ab56037d6021b",
351 },
352 TestVector {
353 key: "683bd3d6957bf7276d0a616304f1610c57689a96d90118762f4caa9de9bc5bb6",
354 data: "",
355 snapshot: "504152544903006167652d656e6372797074696f6e2e6f72672f76310a2d3e20736372797074202f754a34476a537776342b562f654546414e43586c512031310a705336736f573047302f63577a62434765534b6f35374b53614d52523842574c37435846664437383956340a2d2d2d206d73664263594c42686a526c785764727235624344367a5749774c586c6c4b5748706e4f65465732314c6b0a7903bc73e10ddb40152023255131650ec8c19d09c1c7c896db83ea0cc6a90d47",
356 },
357 TestVector {
358 key: "cd250a0b070632dc521cfe35805b2846763a4c698d61d85d3b55f115b9a769da",
359 data: "",
360 snapshot: "504152544903006167652d656e6372797074696f6e2e6f72672f76310a2d3e20736372797074204b5042447751786b4e346272584b41343844354a4f412031310a71637a6665775948683378747a5873626b5568662b556b6945796c646b74764b55464f6f525249516265510a2d2d2d20456a6e3850335447474b454a6150576c30432b78382f6a2b5037417755783261553675624a657a6e4767730a823a6568803ea775a18191ef7a9782569fe663f85caf15ecfb651ae485c65665",
361 },
362 TestVector {
363 key: "9cf33b2539a3e9d89d2586ae6783d781de68df155eb2af22abaca3d6094d6db8",
364 data: "",
365 snapshot: "504152544903006167652d656e6372797074696f6e2e6f72672f76310a2d3e20736372797074202b694b446c567a72434f764c77684e7a4535484345772031310a384768764d513331706c784270414b35364e3946646a593335374c71554730744b79516e6a45594d5951770a2d2d2d206d5a456b3139657456464d4e316c636a67765159746762515466752b3131337350684b4545767a457a396f0a081b254bda255364f9b91c8e89b921461db355a3d55a4222aefe66ced11ff6c5",
366 },
367 ];
368
369 for tv in &tvs {
370 let mut key = [0; KEY_SIZE];
371 hex::decode_to_slice(tv.key, &mut key).unwrap();
372 let data = hex::decode(tv.data).unwrap();
373 let snapshot = hex::decode(tv.snapshot).unwrap();
374
375 let mut slice = snapshot.as_slice();
376
377 check_header(&mut slice).unwrap();
379 let pt = decrypt_content(&mut slice, &key).unwrap();
380
381 assert_eq!(*pt, data);
382 }
383 }
384}