1use aes_gcm::{
46 aead::{Aead, KeyInit, OsRng},
47 Aes256Gcm, Nonce,
48};
49use rand::RngCore;
50use crate::error::{GitCryptError, Result};
51
52pub const KEY_SIZE: usize = 32; pub const NONCE_SIZE: usize = 12; const MAGIC_HEADER: &[u8] = b"GITCRYPT";
57
58#[derive(Clone)]
59pub struct CryptoKey {
60 key: [u8; KEY_SIZE],
61}
62
63impl CryptoKey {
64 pub fn generate() -> Self {
66 let mut key = [0u8; KEY_SIZE];
67 OsRng.fill_bytes(&mut key);
68 Self { key }
69 }
70
71 pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
73 if bytes.len() != KEY_SIZE {
74 return Err(GitCryptError::InvalidKeyFormat);
75 }
76 let mut key = [0u8; KEY_SIZE];
77 key.copy_from_slice(bytes);
78 Ok(Self { key })
79 }
80
81 pub fn as_bytes(&self) -> &[u8] {
83 &self.key
84 }
85
86 pub fn encrypt(&self, plaintext: &[u8]) -> Result<Vec<u8>> {
88 let cipher = Aes256Gcm::new_from_slice(&self.key)
89 .map_err(|e| GitCryptError::Crypto(e.to_string()))?;
90
91 let mut nonce_bytes = [0u8; NONCE_SIZE];
93 OsRng.fill_bytes(&mut nonce_bytes);
94 let nonce = Nonce::from_slice(&nonce_bytes);
95
96 let ciphertext = cipher
98 .encrypt(nonce, plaintext)
99 .map_err(|e| GitCryptError::Crypto(e.to_string()))?;
100
101 let mut result = Vec::with_capacity(MAGIC_HEADER.len() + NONCE_SIZE + ciphertext.len());
103 result.extend_from_slice(MAGIC_HEADER);
104 result.extend_from_slice(&nonce_bytes);
105 result.extend_from_slice(&ciphertext);
106
107 Ok(result)
108 }
109
110 pub fn decrypt(&self, ciphertext: &[u8]) -> Result<Vec<u8>> {
112 let min_size = MAGIC_HEADER.len() + NONCE_SIZE;
113 if ciphertext.len() < min_size {
114 return Err(GitCryptError::Crypto("Ciphertext too short".into()));
115 }
116
117 if &ciphertext[..MAGIC_HEADER.len()] != MAGIC_HEADER {
119 return Err(GitCryptError::Crypto("Invalid encrypted data format".into()));
120 }
121
122 let cipher = Aes256Gcm::new_from_slice(&self.key)
123 .map_err(|e| GitCryptError::Crypto(e.to_string()))?;
124
125 let data = &ciphertext[MAGIC_HEADER.len()..];
127 let (nonce_bytes, encrypted_data) = data.split_at(NONCE_SIZE);
128 let nonce = Nonce::from_slice(nonce_bytes);
129
130 let plaintext = cipher
132 .decrypt(nonce, encrypted_data)
133 .map_err(|e| GitCryptError::Crypto(e.to_string()))?;
134
135 Ok(plaintext)
136 }
137
138 pub fn is_encrypted(data: &[u8]) -> bool {
140 data.len() >= MAGIC_HEADER.len() && &data[..MAGIC_HEADER.len()] == MAGIC_HEADER
141 }
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147
148 #[test]
149 fn test_encrypt_decrypt() {
150 let key = CryptoKey::generate();
151 let plaintext = b"Hello, World!";
152
153 let ciphertext = key.encrypt(plaintext).unwrap();
154 assert_ne!(plaintext.as_slice(), &ciphertext[..]);
155
156 let decrypted = key.decrypt(&ciphertext).unwrap();
157 assert_eq!(plaintext.as_slice(), &decrypted[..]);
158 }
159
160 #[test]
161 fn test_empty_data() {
162 let key = CryptoKey::generate();
163 let plaintext = b"";
164
165 let ciphertext = key.encrypt(plaintext).unwrap();
166 let decrypted = key.decrypt(&ciphertext).unwrap();
167 assert_eq!(plaintext.as_slice(), &decrypted[..]);
168 }
169
170 #[test]
171 fn test_large_data() {
172 let key = CryptoKey::generate();
173 let plaintext = vec![0x42u8; 1024 * 1024]; let ciphertext = key.encrypt(&plaintext).unwrap();
176 let decrypted = key.decrypt(&ciphertext).unwrap();
177 assert_eq!(plaintext, decrypted);
178 }
179
180 #[test]
181 fn test_binary_data() {
182 let key = CryptoKey::generate();
183 let plaintext: Vec<u8> = (0..=255).collect();
184
185 let ciphertext = key.encrypt(&plaintext).unwrap();
186 let decrypted = key.decrypt(&ciphertext).unwrap();
187 assert_eq!(plaintext, decrypted);
188 }
189
190 #[test]
191 fn test_different_keys_produce_different_ciphertext() {
192 let key1 = CryptoKey::generate();
193 let key2 = CryptoKey::generate();
194 let plaintext = b"Same plaintext";
195
196 let ciphertext1 = key1.encrypt(plaintext).unwrap();
197 let ciphertext2 = key2.encrypt(plaintext).unwrap();
198
199 assert_ne!(ciphertext1, ciphertext2);
201 }
202
203 #[test]
204 fn test_same_key_different_nonces() {
205 let key = CryptoKey::generate();
206 let plaintext = b"Same plaintext and key";
207
208 let ciphertext1 = key.encrypt(plaintext).unwrap();
209 let ciphertext2 = key.encrypt(plaintext).unwrap();
210
211 assert_ne!(ciphertext1, ciphertext2);
213
214 assert_eq!(key.decrypt(&ciphertext1).unwrap(), plaintext.as_slice());
216 assert_eq!(key.decrypt(&ciphertext2).unwrap(), plaintext.as_slice());
217 }
218
219 #[test]
220 fn test_wrong_key_fails_decryption() {
221 let key1 = CryptoKey::generate();
222 let key2 = CryptoKey::generate();
223 let plaintext = b"Secret message";
224
225 let ciphertext = key1.encrypt(plaintext).unwrap();
226
227 let result = key2.decrypt(&ciphertext);
229 assert!(result.is_err());
230 }
231
232 #[test]
233 fn test_corrupted_ciphertext_fails() {
234 let key = CryptoKey::generate();
235 let plaintext = b"Secret message";
236
237 let mut ciphertext = key.encrypt(plaintext).unwrap();
238
239 if ciphertext.len() > NONCE_SIZE {
241 ciphertext[NONCE_SIZE] ^= 0xFF;
242 }
243
244 let result = key.decrypt(&ciphertext);
246 assert!(result.is_err());
247 }
248
249 #[test]
250 fn test_truncated_ciphertext_fails() {
251 let key = CryptoKey::generate();
252 let plaintext = b"Secret message";
253
254 let ciphertext = key.encrypt(plaintext).unwrap();
255
256 let truncated = &ciphertext[..5];
258 let result = key.decrypt(truncated);
259 assert!(result.is_err());
260 }
261
262 #[test]
263 fn test_key_from_bytes() {
264 let key_bytes = [0x42u8; KEY_SIZE];
265 let key = CryptoKey::from_bytes(&key_bytes).unwrap();
266 assert_eq!(key.as_bytes(), &key_bytes);
267 }
268
269 #[test]
270 fn test_key_from_invalid_length() {
271 let too_short = vec![0x42u8; KEY_SIZE - 1];
272 let result = CryptoKey::from_bytes(&too_short);
273 assert!(result.is_err());
274
275 let too_long = vec![0x42u8; KEY_SIZE + 1];
276 let result = CryptoKey::from_bytes(&too_long);
277 assert!(result.is_err());
278 }
279
280 #[test]
281 fn test_key_roundtrip() {
282 let key1 = CryptoKey::generate();
283 let key_bytes = key1.as_bytes();
284 let key2 = CryptoKey::from_bytes(key_bytes).unwrap();
285
286 let plaintext = b"Test message";
288 let ciphertext = key1.encrypt(plaintext).unwrap();
289 let decrypted = key2.decrypt(&ciphertext).unwrap();
290 assert_eq!(plaintext.as_slice(), &decrypted[..]);
291 }
292
293 #[test]
294 fn test_unicode_data() {
295 let key = CryptoKey::generate();
296 let plaintext = "Hello, δΈη! ππ¦".as_bytes();
297
298 let ciphertext = key.encrypt(plaintext).unwrap();
299 let decrypted = key.decrypt(&ciphertext).unwrap();
300 assert_eq!(plaintext, &decrypted[..]);
301 assert_eq!(
302 String::from_utf8(decrypted).unwrap(),
303 "Hello, δΈη! ππ¦"
304 );
305 }
306
307 #[test]
308 fn test_ciphertext_has_nonce() {
309 let key = CryptoKey::generate();
310 let plaintext = b"Test";
311
312 let ciphertext = key.encrypt(plaintext).unwrap();
313
314 assert!(ciphertext.len() >= plaintext.len() + NONCE_SIZE);
316
317 assert_eq!(&ciphertext[..NONCE_SIZE].len(), &NONCE_SIZE);
319 }
320}