simploxide_client/crypto/fs/
std.rs1use simploxide_api_types::CryptoFile as SxcCryptoFile;
2
3use std::{
4 io::{Read, Seek as _, SeekFrom, Write},
5 path::Path,
6};
7
8use super::{EncryptedFileState, FileCryptoArgs, InvalidAuthTag, Mode, SimplexSecretBox};
9
10pub struct EncryptedFile<S: SimplexSecretBox> {
22 file: ::std::fs::File,
23 state: Box<EncryptedFileState<S>>,
24}
25
26impl<S: SimplexSecretBox> EncryptedFile<S> {
27 pub fn create<P: AsRef<Path>>(path: P) -> std::io::Result<Self> {
28 Ok(Self {
29 file: std::fs::File::create(path)?,
30 state: Box::new(EncryptedFileState::new()),
31 })
32 }
33
34 pub fn create_with_args<P: AsRef<Path>>(
35 path: P,
36 crypto_args: FileCryptoArgs,
37 ) -> std::io::Result<Self> {
38 Ok(Self {
39 file: std::fs::File::create(path)?,
40 state: Box::new(EncryptedFileState::from_args(crypto_args)),
41 })
42 }
43
44 pub fn open<P: AsRef<Path>>(path: P, crypto_args: FileCryptoArgs) -> std::io::Result<Self> {
48 let mut file = std::fs::OpenOptions::new()
49 .write(true)
50 .read(true)
51 .create(false)
52 .open(path)?;
53
54 let size = size_hint(&mut file)?;
55
56 Ok(Self {
57 file,
58 state: Box::new(EncryptedFileState::from_size_and_args(size, crypto_args)?),
59 })
60 }
61
62 pub fn open_read_only<P: AsRef<Path>>(
64 path: P,
65 crypto_args: FileCryptoArgs,
66 ) -> std::io::Result<Self> {
67 let mut file = std::fs::OpenOptions::new()
68 .write(false)
69 .read(true)
70 .create(false)
71 .open(path)?;
72
73 let size = size_hint(&mut file)?;
74
75 Ok(Self {
76 file,
77 state: Box::new(EncryptedFileState::from_size_and_args(size, crypto_args)?),
78 })
79 }
80
81 pub fn prepare_for_overwrite(&mut self) -> std::io::Result<()> {
82 self.file.seek(SeekFrom::Start(0))?;
83 self.file.set_len(0)?;
84 self.state.reset();
85 self.state.mode = Mode::Write;
86
87 Ok(())
88 }
89
90 pub fn crypto_args(&self) -> &FileCryptoArgs {
91 self.state.crypto_args()
92 }
93
94 pub fn optimal_buf_size(&self) -> usize {
95 self.state.optimal_buf_size()
96 }
97
98 pub fn plaintext_size_hint(&self) -> usize {
99 self.state.plaintext_size_hint()
100 }
101
102 pub fn put_auth_tag(mut self) -> std::io::Result<()> {
104 if self.state.mode == Mode::Read {
105 return self.state.assert_writable();
106 } else if self.state.mode == Mode::Write {
107 self.state.mode = Mode::Auth;
108 let tag = self.state.secret_box.auth_tag();
109 self.file.write_all(&tag)?;
110 } else if self.state.mode == Mode::AuthFailure {
111 return Err(InvalidAuthTag::io_error());
112 }
113
114 Ok(())
115 }
116}
117
118impl<S: SimplexSecretBox> Write for EncryptedFile<S> {
119 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
120 self.state.assert_writable()?;
121 let encrypted = self.state.encrypt_chunk(buf);
122 self.file.write_all(encrypted)?;
123 Ok(buf.len())
124 }
125
126 fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> {
127 self.write(buf).map(drop)
128 }
129
130 fn flush(&mut self) -> std::io::Result<()> {
131 self.file.flush()
132 }
133}
134
135impl<S: SimplexSecretBox> Read for EncryptedFile<S> {
136 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
137 if self.state.mode == Mode::AuthFailure {
138 return Err(InvalidAuthTag::io_error());
139 }
140
141 if self.state.mode == Mode::Auth {
142 if self.state.is_all_data_read() {
143 return Ok(0);
144 } else {
145 self.file
146 .read_exact(self.state.auth_tag_buf())
147 .map_err(|_| InvalidAuthTag::io_error())?;
148 self.state.authenticate()?;
149 return Ok(0);
150 }
151 }
152
153 self.state.assert_readable()?;
154
155 if buf.is_empty() {
156 return Err(std::io::Error::new(
157 std::io::ErrorKind::InvalidInput,
158 "reader got exhausted before EOF: the data cannot be authenticated",
159 ));
160 }
161
162 let read_buf = self.state.prep_read_buf(buf.len());
163 let bytes_read = self.file.read(read_buf)?;
164
165 self.state.decrypt_read_buf(bytes_read, buf);
166
167 if self.state.is_all_data_read() {
168 self.state.switch_to_auth_mode();
169 } else if bytes_read == 0 {
170 return Err(std::io::Error::new(
171 std::io::ErrorKind::UnexpectedEof,
172 "file truncated before ciphertext end",
173 ));
174 }
175
176 Ok(bytes_read)
177 }
178}
179
180impl<S: SimplexSecretBox> Drop for EncryptedFile<S> {
181 fn drop(&mut self) {
182 if self.state.mode == Mode::Write {
183 let tag = self.state.secret_box.auth_tag();
184 if let Err(e) = self.file.write_all(&tag) {
185 log::error!("Failed to authenticate a file: {e}");
186 }
187 }
188 }
189}
190
191pub enum StdMaybeCryptoFile<S: SimplexSecretBox> {
193 Plain(::std::fs::File),
194 Encrypted(EncryptedFile<S>),
195}
196
197impl<S: SimplexSecretBox> StdMaybeCryptoFile<S> {
198 pub fn open<P: AsRef<Path>>(
201 path: P,
202 crypto_args: Option<FileCryptoArgs>,
203 ) -> std::io::Result<Self> {
204 match crypto_args {
205 Some(args) => Ok(Self::Encrypted(EncryptedFile::open(path, args)?)),
206 None => Ok(Self::Plain(
207 std::fs::OpenOptions::new()
208 .write(true)
209 .read(true)
210 .create(false)
211 .open(path)?,
212 )),
213 }
214 }
215
216 pub fn open_read_only<P: AsRef<Path>>(
217 path: P,
218 crypto_args: Option<FileCryptoArgs>,
219 ) -> std::io::Result<Self> {
220 match crypto_args {
221 Some(args) => Ok(Self::Encrypted(EncryptedFile::open_read_only(path, args)?)),
222 None => Ok(Self::Plain(
223 std::fs::OpenOptions::new()
224 .write(false)
225 .read(true)
226 .create(false)
227 .open(path)?,
228 )),
229 }
230 }
231
232 pub fn create<P: AsRef<Path>>(
233 path: P,
234 crypto_args: Option<FileCryptoArgs>,
235 ) -> std::io::Result<Self> {
236 match crypto_args {
237 Some(args) => Ok(Self::Encrypted(EncryptedFile::create_with_args(
238 path, args,
239 )?)),
240 None => Ok(Self::Plain(
241 std::fs::OpenOptions::new()
242 .write(true)
243 .create(true)
244 .truncate(true)
245 .open(path)?,
246 )),
247 }
248 }
249
250 pub fn from_crypto_file(crypto_file: SxcCryptoFile) -> std::io::Result<Self> {
251 match crypto_file.crypto_args {
252 Some(args) => {
253 let crypto_args = FileCryptoArgs::try_from(args)?;
254 Self::open(&crypto_file.file_path, Some(crypto_args))
255 }
256 None => Self::open(&crypto_file.file_path, None),
257 }
258 }
259
260 pub fn from_crypto_file_read_only(crypto_file: SxcCryptoFile) -> std::io::Result<Self> {
261 match crypto_file.crypto_args {
262 Some(args) => {
263 let crypto_args = FileCryptoArgs::try_from(args)?;
264 Self::open_read_only(&crypto_file.file_path, Some(crypto_args))
265 }
266 None => Self::open_read_only(&crypto_file.file_path, None),
267 }
268 }
269
270 pub fn size_hint(&mut self) -> std::io::Result<usize> {
271 match self {
272 Self::Plain(f) => size_hint(f),
273 Self::Encrypted(f) => Ok(f.plaintext_size_hint()),
274 }
275 }
276
277 pub fn crypto_args(&self) -> Option<&FileCryptoArgs> {
278 match self {
279 Self::Plain(_) => None,
280 Self::Encrypted(f) => Some(f.crypto_args()),
281 }
282 }
283
284 pub fn prepare_for_overwrite(&mut self) -> std::io::Result<()> {
285 match self {
286 Self::Plain(f) => {
287 f.seek(SeekFrom::Start(0))?;
288 f.set_len(0)?;
289 Ok(())
290 }
291 Self::Encrypted(f) => f.prepare_for_overwrite(),
292 }
293 }
294
295 pub fn put_auth_tag(self) -> std::io::Result<()> {
297 match self {
298 Self::Plain(_) => Ok(()),
299 Self::Encrypted(f) => f.put_auth_tag(),
300 }
301 }
302}
303
304impl<S: SimplexSecretBox> Read for StdMaybeCryptoFile<S> {
305 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
306 match self {
307 Self::Plain(f) => f.read(buf),
308 Self::Encrypted(e) => e.read(buf),
309 }
310 }
311}
312
313impl<S: SimplexSecretBox> Write for StdMaybeCryptoFile<S> {
314 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
315 match self {
316 Self::Plain(f) => f.write(buf),
317 Self::Encrypted(e) => e.write(buf),
318 }
319 }
320
321 fn flush(&mut self) -> std::io::Result<()> {
322 match self {
323 Self::Plain(f) => f.flush(),
324 Self::Encrypted(e) => e.flush(),
325 }
326 }
327
328 fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> {
329 match self {
330 Self::Plain(f) => f.write_all(buf),
331 Self::Encrypted(e) => e.write_all(buf),
332 }
333 }
334}
335
336fn size_hint(file: &mut ::std::fs::File) -> ::std::io::Result<usize> {
337 let size = file.seek(SeekFrom::End(0))?;
338 file.seek(SeekFrom::Start(0))?;
339
340 crate::util::cast_file_size(size)
341}