1use std::sync::Arc;
21
22use bytes::Bytes;
23
24use super::crypto::{AesGcmCipher, SecureKey};
25use super::key_metadata::StandardKeyMetadata;
26use super::stream::{AesGcmFileRead, AesGcmFileWrite};
27use crate::Result;
28use crate::io::{FileMetadata, FileRead, FileWrite, InputFile, OutputFile};
29
30pub struct EncryptedInputFile {
34 inner: InputFile,
35 key_metadata: StandardKeyMetadata,
36}
37
38impl EncryptedInputFile {
39 pub fn new(inner: InputFile, key_metadata: StandardKeyMetadata) -> Self {
41 Self {
42 inner,
43 key_metadata,
44 }
45 }
46
47 pub fn location(&self) -> &str {
49 self.inner.location()
50 }
51
52 pub async fn exists(&self) -> Result<bool> {
54 self.inner.exists().await
55 }
56
57 pub async fn metadata(&self) -> Result<FileMetadata> {
61 let raw_meta = self.inner.metadata().await?;
62 let plaintext_size = AesGcmFileRead::calculate_plaintext_length(raw_meta.size)?;
63 Ok(FileMetadata {
64 size: plaintext_size,
65 })
66 }
67
68 pub async fn read(&self) -> Result<Bytes> {
70 let meta = self.metadata().await?;
71 let reader = self.reader().await?;
72 reader.read(0..meta.size).await
73 }
74
75 pub async fn reader(&self) -> Result<Box<dyn FileRead>> {
77 let raw_meta = self.inner.metadata().await?;
78 let raw_reader = self.inner.reader().await?;
79 let cipher = build_cipher(&self.key_metadata)?;
80 let aad_prefix: Box<[u8]> = self.key_metadata.aad_prefix().unwrap_or_default().into();
81 let decrypting = AesGcmFileRead::new(raw_reader, cipher, aad_prefix, raw_meta.size)?;
82 Ok(Box::new(decrypting))
83 }
84
85 pub fn key_metadata(&self) -> &StandardKeyMetadata {
87 &self.key_metadata
88 }
89
90 pub fn into_inner(self) -> InputFile {
92 self.inner
93 }
94}
95
96impl std::fmt::Debug for EncryptedInputFile {
97 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
98 f.debug_struct("EncryptedInputFile")
99 .field("path", &self.inner.location())
100 .finish_non_exhaustive()
101 }
102}
103
104pub struct EncryptedOutputFile {
108 inner: OutputFile,
109 key_metadata: StandardKeyMetadata,
110}
111
112impl EncryptedOutputFile {
113 pub fn new(inner: OutputFile, key_metadata: StandardKeyMetadata) -> Self {
115 Self {
116 inner,
117 key_metadata,
118 }
119 }
120
121 pub fn key_metadata(&self) -> &StandardKeyMetadata {
123 &self.key_metadata
124 }
125
126 pub fn location(&self) -> &str {
128 self.inner.location()
129 }
130
131 pub async fn writer(&self) -> Result<Box<dyn FileWrite>> {
133 let raw_writer = self.inner.writer().await?;
134 let cipher = build_cipher(&self.key_metadata)?;
135 let aad_prefix: Box<[u8]> = self.key_metadata.aad_prefix().unwrap_or_default().into();
136 Ok(Box::new(AesGcmFileWrite::new(
137 raw_writer, cipher, aad_prefix,
138 )))
139 }
140
141 pub async fn write(&self, bs: Bytes) -> Result<()> {
143 let mut writer = self.writer().await?;
144 writer.write(bs).await?;
145 writer.close().await
146 }
147
148 pub async fn delete(&self) -> Result<()> {
150 self.inner.delete().await
151 }
152
153 pub fn into_inner(self) -> OutputFile {
155 self.inner
156 }
157}
158
159impl std::fmt::Debug for EncryptedOutputFile {
160 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
161 f.debug_struct("EncryptedOutputFile")
162 .field("path", &self.inner.location())
163 .finish_non_exhaustive()
164 }
165}
166
167fn build_cipher(metadata: &StandardKeyMetadata) -> Result<Arc<AesGcmCipher>> {
168 let key = SecureKey::new(metadata.encryption_key().as_bytes())?;
169 Ok(Arc::new(AesGcmCipher::new(key)))
170}
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175 use crate::io::FileIO;
176
177 fn key_metadata() -> StandardKeyMetadata {
178 StandardKeyMetadata::new(b"0123456789abcdef").with_aad_prefix(b"test-aad-prefix!")
179 }
180
181 #[tokio::test]
182 async fn test_write_read_roundtrip() {
183 let fileio = FileIO::new_with_memory();
184 let path = "memory:///test/io_roundtrip.bin";
185 let plaintext = b"Hello from EncryptedInputFile/EncryptedOutputFile!";
186
187 let output = EncryptedOutputFile::new(fileio.new_output(path).unwrap(), key_metadata());
188 output.write(Bytes::from(plaintext.to_vec())).await.unwrap();
189
190 let input = EncryptedInputFile::new(fileio.new_input(path).unwrap(), key_metadata());
191 let content = input.read().await.unwrap();
192 assert_eq!(&content[..], plaintext);
193 }
194
195 #[tokio::test]
196 async fn test_metadata_returns_plaintext_size() {
197 let fileio = FileIO::new_with_memory();
198 let path = "memory:///test/io_metadata.bin";
199 let plaintext = b"some bytes to measure";
200
201 let output = EncryptedOutputFile::new(fileio.new_output(path).unwrap(), key_metadata());
202 output.write(Bytes::from(plaintext.to_vec())).await.unwrap();
203
204 let raw_size = fileio
205 .new_input(path)
206 .unwrap()
207 .metadata()
208 .await
209 .unwrap()
210 .size;
211 assert!(
212 raw_size > plaintext.len() as u64,
213 "encrypted file should be larger than plaintext (header + nonce + tag)"
214 );
215
216 let input = EncryptedInputFile::new(fileio.new_input(path).unwrap(), key_metadata());
217 let meta = input.metadata().await.unwrap();
218 assert_eq!(meta.size, plaintext.len() as u64);
219 }
220}