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