snapper_box/crypto/
types.rs1use std::{borrow::Cow, io::Cursor};
9
10use chacha20::{
11 cipher::{NewCipher, StreamCipher},
12 XChaCha20,
13};
14use redacted::RedactedBytes;
15use serde::{de::DeserializeOwned, Deserialize, Serialize};
16use snafu::{ensure, ResultExt};
17use zeroize::Zeroize;
18use zstd::stream::encode_all;
19
20use crate::{
21 crypto::key::{Key, Nonce},
22 error::{BackendError, BadHMAC, Compression, Decompression},
23};
24
25#[derive(Hash, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Zeroize)]
29#[zeroize(drop)]
30pub struct ClearText {
31 pub(crate) payload: Vec<u8>,
33}
34
35impl ClearText {
36 pub fn new<T>(item: &T) -> Result<Self, BackendError>
42 where
43 T: Serialize,
44 {
45 match serde_cbor::to_vec(item) {
47 Ok(payload) => Ok(ClearText { payload }),
48 Err(_) => {
49 Err(BackendError::ItemSerialization)
51 }
52 }
53 }
54
55 pub fn encrypt<K>(
70 self,
71 key: &K,
72 compression: Option<i32>,
73 ) -> Result<CipherText<'static>, BackendError>
74 where
75 K: Key,
76 {
77 let mut payload = if let Some(level) = compression {
79 let input = Cursor::new(&self.payload);
80 encode_all(input, level).context(Compression)?
81 } else {
82 self.payload.clone()
83 };
84 let nonce = Nonce::random();
86 let mut chacha = XChaCha20::new(key.encryption_key(), nonce.nonce());
87 chacha.apply_keystream(&mut payload[..]);
88 let hmac: [u8; 32] = blake3::keyed_hash(key.hmac_key(), &payload[..]).into();
90 Ok(CipherText {
91 compressed: compression.is_some(),
92 nonce,
93 hmac: hmac.into(),
94 payload: payload.into(),
95 })
96 }
97
98 pub fn deserialize<T>(&self) -> Result<T, BackendError>
106 where
107 T: DeserializeOwned,
108 {
109 match serde_cbor::from_slice(&self.payload) {
110 Ok(x) => Ok(x),
111 Err(_) => Err(BackendError::ItemDeserialization),
112 }
113 }
114}
115
116#[derive(Debug, Hash, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
122pub struct CipherText<'a> {
123 pub(crate) compressed: bool,
125 pub(crate) nonce: Nonce,
127 pub(crate) hmac: RedactedBytes<32>,
129 #[serde(serialize_with = "serde_bytes::serialize")]
131 pub(crate) payload: Cow<'a, [u8]>,
132}
133
134impl CipherText<'_> {
135 pub fn decrypt<K>(&self, key: &K) -> Result<ClearText, BackendError>
144 where
145 K: Key,
146 {
147 let hmac = blake3::keyed_hash(key.hmac_key(), &self.payload[..]);
149 ensure!(hmac.eq(&*self.hmac), BadHMAC);
150 let mut payload = self.payload.to_vec();
152 let mut chacha = XChaCha20::new(key.encryption_key(), self.nonce.nonce());
153 chacha.apply_keystream(&mut payload[..]);
154 if self.compressed {
156 let input = Cursor::new(&payload[..]);
157 let output = zstd::decode_all(input).context(Decompression)?;
158 payload.zeroize();
160 Ok(ClearText { payload: output })
161 } else {
162 Ok(ClearText { payload })
163 }
164 }
165
166 pub fn compressed(&self) -> bool {
168 self.compressed
169 }
170}
171
172#[cfg(test)]
174mod tests {
175 use super::*;
176 use crate::crypto::key::RootKey;
177 mod text {
179 use super::*;
180 #[test]
182 fn round_trip() {
183 let key = RootKey::random();
184 let item = "The quick brown fox jumps over the lazy dog";
185 let cleartext = ClearText::new(&item).expect("Failed to make cleartext");
186 let ciphertext = cleartext.encrypt(&key, None).expect("Failed to encrypt");
187 let decrypted = ciphertext.decrypt(&key).expect("Failed to decrypt");
188 let decrypted_item: String = decrypted.deserialize().expect("Failed to deserialize");
189 assert_eq!(decrypted_item, item);
190 }
191 #[test]
193 fn round_trip_compression() {
194 let key = RootKey::random();
195 let item = "The quick brown fox jumps over the lazy dog";
196 let cleartext = ClearText::new(&item).expect("Failed to make cleartext");
197 let ciphertext = cleartext.encrypt(&key, Some(0)).expect("Failed to encrypt");
198 let decrypted = ciphertext.decrypt(&key).expect("Failed to decrypt");
199 let decrypted_item: String = decrypted.deserialize().expect("Failed to deserialize");
200 assert_eq!(decrypted_item, item);
201 }
202 #[test]
204 fn repeated_invokations() {
205 let key = RootKey::random();
206 let item = "The quick brown fox jumps over the lazy dog";
207 let cleartext = ClearText::new(&item).expect("Failed to make cleartext");
208 let ciphertext_1 = cleartext
209 .clone()
210 .encrypt(&key, None)
211 .expect("Failed to encrypt");
212 let ciphertext_2 = cleartext.encrypt(&key, None).expect("Failed to encrypt");
213 assert_ne!(ciphertext_1.nonce, ciphertext_2.nonce);
214 assert_ne!(ciphertext_1.payload, ciphertext_2.payload);
215 }
216 #[test]
218 fn corruption() {
219 let key = RootKey::random();
220 let item = "The quick brown fox jumps over the lazy dog";
221 let cleartext = ClearText::new(&item).expect("Failed to make cleartext");
222 let mut ciphertext = cleartext.encrypt(&key, Some(0)).expect("Failed to encrypt");
223 ciphertext.payload.to_mut()[0] = ciphertext.payload[0].wrapping_add(1_u8);
225 let decrypted = ciphertext.decrypt(&key);
226 match decrypted {
227 Ok(_) => panic!("Somehow decrypted corrupted data"),
228 Err(e) => assert!(matches!(e, BackendError::BadHMAC)),
229 }
230 }
231 #[test]
233 fn wrong_key() {
234 let key = RootKey::random();
235 let wrong_key = RootKey::random();
236 let item = "The quick brown fox jumps over the lazy dog";
237 let cleartext = ClearText::new(&item).expect("Failed to make cleartext");
238 let ciphertext = cleartext.encrypt(&key, Some(0)).expect("Failed to encrypt");
239 let decrypted = ciphertext.decrypt(&wrong_key);
240 match decrypted {
241 Ok(_) => panic!("Somehow decrypted corrupted data"),
242 Err(e) => assert!(matches!(e, BackendError::BadHMAC)),
243 }
244 }
245 }
246}