encryptable_tokio_fs/tokio/
encryptable_fs.rs

1use std::fs::Metadata;
2use std::io::ErrorKind;
3use crate::crypto::cryptor::KEY_LEN;
4use crate::crypto::cryptor::{CryptorAsyncReader, CryptorAsyncWriter};
5use std::path::Path;
6use std::pin::Pin;
7use std::task::{Context, Poll};
8use sha2::{Digest, Sha256};
9use tokio::io;
10pub use tokio::fs::*;
11use tokio::io::{AsyncReadExt, AsyncWriteExt};
12
13pub type ChaCha20Key = [u8; KEY_LEN];
14
15pub struct EncryptionKeys {
16    content_key: ChaCha20Key,
17    _path_key: ChaCha20Key,
18}
19
20static mut ENCRYPTION_KEYS: Option<EncryptionKeys> = None;
21
22pub fn set_keys(keys: EncryptionKeys) -> Option<EncryptionKeys> {
23    let old_keys = unsafe {
24        #[allow(static_mut_refs)]
25        ENCRYPTION_KEYS.replace(keys)
26    };
27    std::sync::atomic::fence(std::sync::atomic::Ordering::SeqCst);
28    old_keys
29
30}
31
32pub fn set_keys_from_passphrase(passphrase: &str) {
33
34    assert!(passphrase.len() >= KEY_LEN, "`passphrase` needs to be no less than {KEY_LEN} bytes");
35
36    let key = derive_key(passphrase.as_bytes());
37    let derived_key = derive_key(key.as_ref());
38    set_keys(EncryptionKeys {
39        content_key: key,
40        _path_key: derived_key,
41    });
42}
43
44pub fn set_keys_from_base(key: ChaCha20Key) {
45    let derived_key = derive_key(key.as_ref());
46    set_keys(EncryptionKeys {
47        content_key: key,
48        _path_key: derived_key,
49    });
50}
51
52fn get_content_key() -> Option<&'static ChaCha20Key> {
53    unsafe {
54        #[allow(static_mut_refs)]
55        ENCRYPTION_KEYS.as_ref().map(|keys| &keys.content_key)
56    }
57}
58
59fn derive_key(key: &[u8]) -> ChaCha20Key {
60    let mut hasher = Sha256::new();
61    hasher.update(key);
62    let hash_result = hasher.finalize();
63    hash_result.into()
64}
65
66// Tokio FS API replacements
67////////////////////////////
68
69/// Encryptable replacement for [tokio::fs::read()]
70pub async fn read(path: impl AsRef<Path>) -> io::Result<Vec<u8>> {
71    match get_content_key() {
72        None => tokio::fs::read(path).await,
73        Some(_key) => {
74            let file = File::open(path).await?;
75            let size = file.metadata().await.map(|m| m.len() as usize).ok();
76            let mut contents = Vec::with_capacity(size.unwrap_or(0));
77            let mut reader = io::BufReader::new(file);
78            reader.read_to_end(&mut contents).await?;
79            Ok(contents)
80        },
81    }
82}
83
84/// Encryptable replacement for [tokio::fs::read_to_string()]
85pub async fn read_to_string(path: impl AsRef<Path>) -> std::io::Result<String> {
86    match get_content_key() {
87        None => tokio::fs::read_to_string(path).await,
88        Some(_key) => {
89            let file = File::open(path).await?;
90            let size = file.metadata().await.map(|m| m.len() as usize).ok();
91            let mut string = String::with_capacity(size.unwrap_or(0));
92            let mut contents = unsafe { string.as_mut_vec() };  // from std code
93            let mut reader = io::BufReader::new(file);
94            reader.read_to_end(&mut contents).await?;
95            Ok(string)
96        },
97    }
98}
99
100/// Encryptable replacement for [tokio::fs::write()]
101pub async fn write(path: impl AsRef<Path>, contents: impl AsRef<[u8]>) -> io::Result<()> {
102    match get_content_key() {
103        None => tokio::fs::write(path, contents).await,
104        Some(_key) => {
105            let file = File::create(path).await?;
106            let mut writer = io::BufWriter::new(file);
107            writer.write_all(contents.as_ref()).await?;
108            writer.shutdown().await?;
109            Ok(())
110        },
111    }
112}
113
114/// Encryptable replacement for [tokio::fs::File]
115pub enum File {
116    Plain { tokio_file: tokio::fs::File },
117    EncryptedReader { cryptor_async_reader: CryptorAsyncReader<'static, tokio::fs::File> },
118    EncryptedWriter { cryptor_async_writer: CryptorAsyncWriter<tokio::fs::File> },
119}
120
121impl File {
122
123    /// Encryptable replacement for [tokio::fs::File::open()]
124    pub async fn open(path: impl AsRef<Path>) -> io::Result<File> {
125        Ok(
126            match get_content_key() {
127                None => File::Plain { tokio_file: tokio::fs::File::open(path).await? },
128                Some(key) => File::EncryptedReader { cryptor_async_reader: CryptorAsyncReader::new(tokio::fs::File::open(path).await?, key) }
129            }
130        )
131    }
132
133    /// Encryptable replacement for [tokio::fs::File::create()]
134    pub async fn create(path: impl AsRef<Path>) -> io::Result<File> {
135        Ok(
136            match get_content_key() {
137                None => File::Plain { tokio_file: tokio::fs::File::create(path).await? },
138                Some(key) => File::EncryptedWriter { cryptor_async_writer: CryptorAsyncWriter::new(tokio::fs::File::create(path).await?, key) }
139            }
140        )
141    }
142
143    /// Encryptable replacement for [tokio::fs::metadata()]
144    pub async fn metadata(&self) -> std::io::Result<Metadata> {
145        match self {
146            File::Plain { tokio_file } => tokio_file.metadata().await,
147            File::EncryptedReader { cryptor_async_reader } => cryptor_async_reader.inner().metadata().await,
148            File::EncryptedWriter { cryptor_async_writer } => cryptor_async_writer.inner().metadata().await,
149        }
150    }
151
152
153}
154
155impl io::AsyncRead for File {
156    fn poll_read(
157        self: Pin<&mut Self>,
158        cx: &mut Context<'_>,
159        dst: &mut io::ReadBuf<'_>,
160    ) -> Poll<io::Result<()>> {
161        match self.get_mut() {
162            Self::Plain { tokio_file } => std::pin::pin!(tokio_file).poll_read(cx, dst),
163            Self::EncryptedReader { cryptor_async_reader } => std::pin::pin!(cryptor_async_reader).poll_read(cx, dst),
164            File::EncryptedWriter { .. } => Poll::Ready(Err(io::Error::new(ErrorKind::Unsupported, "Attempted to read from a file opened exclusively for writing"))),
165        }
166    }
167}
168
169impl io::AsyncWrite for File {
170    fn poll_write(
171        self: Pin<&mut Self>,
172        cx: &mut Context<'_>,
173        src: &[u8],
174    ) -> Poll<std::io::Result<usize>> {
175        match self.get_mut() {
176            Self::Plain { tokio_file } => std::pin::pin!(tokio_file).poll_write(cx, src),
177            File::EncryptedWriter { cryptor_async_writer } => std::pin::pin!(cryptor_async_writer).poll_write(cx, src),
178            Self::EncryptedReader { .. } => Poll::Ready(Err(io::Error::new(ErrorKind::Unsupported, "Attempted to write on a file opened exclusively for reading"))),
179        }
180    }
181
182    fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> {
183        match self.get_mut() {
184            Self::Plain { tokio_file } => std::pin::pin!(tokio_file).poll_flush(cx),
185            File::EncryptedWriter { cryptor_async_writer } => std::pin::pin!(cryptor_async_writer).poll_flush(cx),
186            Self::EncryptedReader { .. } => Poll::Ready(Err(io::Error::new(ErrorKind::Unsupported, "Attempted to write (flush) a file opened exclusively for reading"))),
187        }
188    }
189
190    fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> {
191        match self.get_mut() {
192            Self::Plain { tokio_file } => std::pin::pin!(tokio_file).poll_shutdown(cx),
193            File::EncryptedWriter { cryptor_async_writer } => std::pin::pin!(cryptor_async_writer).poll_shutdown(cx),
194            Self::EncryptedReader { .. } => Poll::Ready(Err(io::Error::new(ErrorKind::Unsupported, "Attempted to write (shutdown) on a file opened exclusively for reading"))),
195        }
196    }
197
198}