1use std::io::Read;
10use std::ops::Deref;
11
12use chacha20poly1305::Key;
13use chacha20poly1305::{
14 aead::{Aead, KeyInit},
15 ChaCha20Poly1305, Nonce,
16};
17use serde::{Deserialize, Serialize};
18
19pub const NONCE_SIZE: usize = 12;
21pub const SECRET_SIZE: usize = 32;
23pub const BLAKE3_HASH_SIZE: usize = 32;
25#[allow(dead_code)]
27pub const CHUNK_SIZE: usize = 4096;
28
29#[derive(Debug, thiserror::Error)]
31pub enum SecretError {
32 #[error("secret error: {0}")]
33 Default(#[from] anyhow::Error),
34 #[error("IO error: {0}")]
35 Io(#[from] std::io::Error),
36}
37
38#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
60pub struct Secret([u8; SECRET_SIZE]);
61
62impl Default for Secret {
63 fn default() -> Self {
64 Secret([0; SECRET_SIZE])
65 }
66}
67
68impl Deref for Secret {
69 type Target = [u8; SECRET_SIZE];
70 fn deref(&self) -> &Self::Target {
71 &self.0
72 }
73}
74
75impl From<[u8; SECRET_SIZE]> for Secret {
76 fn from(bytes: [u8; SECRET_SIZE]) -> Self {
77 Secret(bytes)
78 }
79}
80
81impl Secret {
82 pub fn generate() -> Self {
84 let mut buff = [0; SECRET_SIZE];
85 getrandom::getrandom(&mut buff).expect("failed to generate random bytes");
86 Self(buff)
87 }
88
89 pub fn from_slice(data: &[u8]) -> Result<Self, SecretError> {
95 if data.len() != SECRET_SIZE {
96 return Err(anyhow::anyhow!(
97 "invalid secret size, expected {}, got {}",
98 SECRET_SIZE,
99 data.len()
100 )
101 .into());
102 }
103 let mut buff = [0; SECRET_SIZE];
104 buff.copy_from_slice(data);
105 Ok(buff.into())
106 }
107
108 pub fn bytes(&self) -> &[u8] {
110 self.0.as_ref()
111 }
112
113 pub fn encrypt(&self, data: &[u8]) -> Result<Vec<u8>, SecretError> {
123 let plaintext_hash = blake3::hash(data);
125
126 let mut data_with_hash = Vec::with_capacity(BLAKE3_HASH_SIZE + data.len());
128 data_with_hash.extend_from_slice(plaintext_hash.as_bytes());
129 data_with_hash.extend_from_slice(data);
130
131 let key = Key::from_slice(self.bytes());
132 let cipher = ChaCha20Poly1305::new(key);
133
134 let mut nonce_bytes = [0u8; NONCE_SIZE];
136 getrandom::getrandom(&mut nonce_bytes)
137 .map_err(|e| anyhow::anyhow!("failed to generate nonce: {}", e))?;
138 let nonce = Nonce::from_slice(&nonce_bytes);
139
140 let ciphertext = cipher
141 .encrypt(nonce, data_with_hash.as_ref())
142 .map_err(|_| anyhow::anyhow!("encrypt error"))?;
143
144 let mut out = Vec::with_capacity(NONCE_SIZE + ciphertext.len());
145 out.extend_from_slice(nonce.as_ref());
146 out.extend_from_slice(ciphertext.as_ref());
147
148 Ok(out)
149 }
150
151 pub fn decrypt(&self, data: &[u8]) -> Result<Vec<u8>, SecretError> {
164 if data.len() < NONCE_SIZE {
165 return Err(anyhow::anyhow!("data too short for nonce").into());
166 }
167
168 let key = Key::from_slice(self.bytes());
169 let nonce = Nonce::from_slice(&data[..NONCE_SIZE]);
170 let cipher = ChaCha20Poly1305::new(key);
171 let decrypted = cipher
172 .decrypt(nonce, &data[NONCE_SIZE..])
173 .map_err(|_| anyhow::anyhow!("decrypt error"))?;
174
175 if decrypted.len() < BLAKE3_HASH_SIZE {
177 return Err(anyhow::anyhow!("decrypted data too short for hash header").into());
178 }
179
180 let stored_hash = &decrypted[..BLAKE3_HASH_SIZE];
181 let plaintext = &decrypted[BLAKE3_HASH_SIZE..];
182
183 let computed_hash = blake3::hash(plaintext);
185 if stored_hash != computed_hash.as_bytes() {
186 return Err(anyhow::anyhow!("hash verification failed - data corrupted").into());
187 }
188
189 Ok(plaintext.to_vec())
190 }
191
192 pub fn extract_plaintext_hash(
204 &self,
205 data: &[u8],
206 ) -> Result<[u8; BLAKE3_HASH_SIZE], SecretError> {
207 if data.len() < NONCE_SIZE {
208 return Err(anyhow::anyhow!("data too short for nonce").into());
209 }
210
211 let key = Key::from_slice(self.bytes());
212 let nonce = Nonce::from_slice(&data[..NONCE_SIZE]);
213 let cipher = ChaCha20Poly1305::new(key);
214 let decrypted = cipher
215 .decrypt(nonce, &data[NONCE_SIZE..])
216 .map_err(|_| anyhow::anyhow!("decrypt error"))?;
217
218 if decrypted.len() < BLAKE3_HASH_SIZE {
220 return Err(anyhow::anyhow!("decrypted data too short for hash header").into());
221 }
222
223 let mut hash = [0u8; BLAKE3_HASH_SIZE];
224 hash.copy_from_slice(&decrypted[..BLAKE3_HASH_SIZE]);
225
226 Ok(hash)
227 }
228
229 pub fn encrypt_reader<R>(&self, reader: R) -> Result<impl Read, SecretError>
234 where
235 R: Read,
236 {
237 let mut data = Vec::new();
238 let mut reader = reader;
239 reader.read_to_end(&mut data).map_err(SecretError::Io)?;
240
241 let encrypted = self.encrypt(&data)?;
242 Ok(std::io::Cursor::new(encrypted))
243 }
244
245 pub fn decrypt_reader<R>(&self, reader: R) -> Result<impl Read, SecretError>
250 where
251 R: Read,
252 {
253 let mut encrypted_data = Vec::new();
254 let mut reader = reader;
255 reader
256 .read_to_end(&mut encrypted_data)
257 .map_err(SecretError::Io)?;
258
259 let decrypted = self.decrypt(&encrypted_data)?;
260 Ok(std::io::Cursor::new(decrypted))
261 }
262}
263
264#[cfg(test)]
265mod test {
266 use super::*;
267 use std::io::Cursor;
268
269 #[test]
270 fn test_secret_encrypt_decrypt() {
271 let secret = Secret::generate();
272 let data = b"hello world, this is a test message for encryption";
273
274 let encrypted = secret.encrypt(data).unwrap();
275 let decrypted = secret.decrypt(&encrypted).unwrap();
276
277 assert_eq!(data.as_slice(), decrypted.as_slice());
278 }
279
280 #[test]
281 fn test_encrypt_decrypt_reader() {
282 let secret = Secret::generate();
283 let data = b"hello world, this is a test message for reader encryption and decryption";
284
285 let reader = Cursor::new(data.to_vec());
287 let mut encrypted_reader = secret.encrypt_reader(reader).unwrap();
288
289 let mut encrypted_data = Vec::new();
291 encrypted_reader.read_to_end(&mut encrypted_data).unwrap();
292
293 let encrypted_cursor = Cursor::new(encrypted_data);
295 let mut decrypted_reader = secret.decrypt_reader(encrypted_cursor).unwrap();
296
297 let mut decrypted_data = Vec::new();
298 decrypted_reader.read_to_end(&mut decrypted_data).unwrap();
299
300 assert_eq!(data.to_vec(), decrypted_data);
301 }
302
303 #[test]
304 fn test_secret_size_validation() {
305 let too_short = [1u8; 16];
306 let too_long = [1u8; 64];
307
308 assert!(Secret::from_slice(&too_short).is_err());
309 assert!(Secret::from_slice(&too_long).is_err());
310
311 let just_right = [1u8; SECRET_SIZE];
312 assert!(Secret::from_slice(&just_right).is_ok());
313 }
314
315 #[test]
316 fn test_extract_plaintext_hash() {
317 let secret = Secret::generate();
318 let data = b"test data for hash extraction";
319
320 let encrypted = secret.encrypt(data).unwrap();
322
323 let extracted_hash = secret.extract_plaintext_hash(&encrypted).unwrap();
325
326 let expected_hash = blake3::hash(data);
328
329 assert_eq!(extracted_hash, *expected_hash.as_bytes());
330 }
331
332 #[test]
333 fn test_hash_verification_on_decrypt() {
334 let secret = Secret::generate();
335 let data = b"test data for integrity check";
336
337 let mut encrypted = secret.encrypt(data).unwrap();
339
340 let decrypted = secret.decrypt(&encrypted).unwrap();
342 assert_eq!(decrypted, data.to_vec());
343
344 if encrypted.len() > NONCE_SIZE + 16 {
347 encrypted[NONCE_SIZE + 10] ^= 0xFF;
348
349 let result = secret.decrypt(&encrypted);
351 assert!(result.is_err());
352 }
353 }
354
355 #[test]
356 fn test_empty_data_encryption() {
357 let secret = Secret::generate();
358 let data = b"";
359
360 let encrypted = secret.encrypt(data).unwrap();
361 let decrypted = secret.decrypt(&encrypted).unwrap();
362
363 assert_eq!(decrypted, data.to_vec());
364
365 let hash = secret.extract_plaintext_hash(&encrypted).unwrap();
367 let expected_hash = blake3::hash(data);
368 assert_eq!(hash, *expected_hash.as_bytes());
369 }
370}