1use crate::error::{GitCryptError, Result};
46use aes_gcm::{
47 aead::{Aead, KeyInit, OsRng},
48 Aes256Gcm, Nonce,
49};
50use rand::RngCore;
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(
120 "Invalid encrypted data format".into(),
121 ));
122 }
123
124 let cipher = Aes256Gcm::new_from_slice(&self.key)
125 .map_err(|e| GitCryptError::Crypto(e.to_string()))?;
126
127 let data = &ciphertext[MAGIC_HEADER.len()..];
129 let (nonce_bytes, encrypted_data) = data.split_at(NONCE_SIZE);
130 let nonce = Nonce::from_slice(nonce_bytes);
131
132 let plaintext = cipher
134 .decrypt(nonce, encrypted_data)
135 .map_err(|e| GitCryptError::Crypto(e.to_string()))?;
136
137 Ok(plaintext)
138 }
139
140 pub fn is_encrypted(data: &[u8]) -> bool {
142 data.len() >= MAGIC_HEADER.len() && &data[..MAGIC_HEADER.len()] == MAGIC_HEADER
143 }
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149
150 #[test]
151 fn test_encrypt_decrypt() {
152 let key = CryptoKey::generate();
153 let plaintext = b"Hello, World!";
154
155 let ciphertext = key.encrypt(plaintext).unwrap();
156 assert_ne!(plaintext.as_slice(), &ciphertext[..]);
157
158 let decrypted = key.decrypt(&ciphertext).unwrap();
159 assert_eq!(plaintext.as_slice(), &decrypted[..]);
160 }
161
162 #[test]
163 fn test_empty_data() {
164 let key = CryptoKey::generate();
165 let plaintext = b"";
166
167 let ciphertext = key.encrypt(plaintext).unwrap();
168 let decrypted = key.decrypt(&ciphertext).unwrap();
169 assert_eq!(plaintext.as_slice(), &decrypted[..]);
170 }
171
172 #[test]
173 fn test_large_data() {
174 let key = CryptoKey::generate();
175 let plaintext = vec![0x42u8; 1024 * 1024]; let ciphertext = key.encrypt(&plaintext).unwrap();
178 let decrypted = key.decrypt(&ciphertext).unwrap();
179 assert_eq!(plaintext, decrypted);
180 }
181
182 #[test]
183 fn test_binary_data() {
184 let key = CryptoKey::generate();
185 let plaintext: Vec<u8> = (0..=255).collect();
186
187 let ciphertext = key.encrypt(&plaintext).unwrap();
188 let decrypted = key.decrypt(&ciphertext).unwrap();
189 assert_eq!(plaintext, decrypted);
190 }
191
192 #[test]
193 fn test_different_keys_produce_different_ciphertext() {
194 let key1 = CryptoKey::generate();
195 let key2 = CryptoKey::generate();
196 let plaintext = b"Same plaintext";
197
198 let ciphertext1 = key1.encrypt(plaintext).unwrap();
199 let ciphertext2 = key2.encrypt(plaintext).unwrap();
200
201 assert_ne!(ciphertext1, ciphertext2);
203 }
204
205 #[test]
206 fn test_same_key_different_nonces() {
207 let key = CryptoKey::generate();
208 let plaintext = b"Same plaintext and key";
209
210 let ciphertext1 = key.encrypt(plaintext).unwrap();
211 let ciphertext2 = key.encrypt(plaintext).unwrap();
212
213 assert_ne!(ciphertext1, ciphertext2);
215
216 assert_eq!(key.decrypt(&ciphertext1).unwrap(), plaintext.as_slice());
218 assert_eq!(key.decrypt(&ciphertext2).unwrap(), plaintext.as_slice());
219 }
220
221 #[test]
222 fn test_wrong_key_fails_decryption() {
223 let key1 = CryptoKey::generate();
224 let key2 = CryptoKey::generate();
225 let plaintext = b"Secret message";
226
227 let ciphertext = key1.encrypt(plaintext).unwrap();
228
229 let result = key2.decrypt(&ciphertext);
231 assert!(result.is_err());
232 }
233
234 #[test]
235 fn test_corrupted_ciphertext_fails() {
236 let key = CryptoKey::generate();
237 let plaintext = b"Secret message";
238
239 let mut ciphertext = key.encrypt(plaintext).unwrap();
240
241 if ciphertext.len() > NONCE_SIZE {
243 ciphertext[NONCE_SIZE] ^= 0xFF;
244 }
245
246 let result = key.decrypt(&ciphertext);
248 assert!(result.is_err());
249 }
250
251 #[test]
252 fn test_truncated_ciphertext_fails() {
253 let key = CryptoKey::generate();
254 let plaintext = b"Secret message";
255
256 let ciphertext = key.encrypt(plaintext).unwrap();
257
258 let truncated = &ciphertext[..5];
260 let result = key.decrypt(truncated);
261 assert!(result.is_err());
262 }
263
264 #[test]
265 fn test_key_from_bytes() {
266 let key_bytes = [0x42u8; KEY_SIZE];
267 let key = CryptoKey::from_bytes(&key_bytes).unwrap();
268 assert_eq!(key.as_bytes(), &key_bytes);
269 }
270
271 #[test]
272 fn test_key_from_invalid_length() {
273 let too_short = vec![0x42u8; KEY_SIZE - 1];
274 let result = CryptoKey::from_bytes(&too_short);
275 assert!(result.is_err());
276
277 let too_long = vec![0x42u8; KEY_SIZE + 1];
278 let result = CryptoKey::from_bytes(&too_long);
279 assert!(result.is_err());
280 }
281
282 #[test]
283 fn test_key_roundtrip() {
284 let key1 = CryptoKey::generate();
285 let key_bytes = key1.as_bytes();
286 let key2 = CryptoKey::from_bytes(key_bytes).unwrap();
287
288 let plaintext = b"Test message";
290 let ciphertext = key1.encrypt(plaintext).unwrap();
291 let decrypted = key2.decrypt(&ciphertext).unwrap();
292 assert_eq!(plaintext.as_slice(), &decrypted[..]);
293 }
294
295 #[test]
296 fn test_unicode_data() {
297 let key = CryptoKey::generate();
298 let plaintext = "Hello, δΈη! ππ¦".as_bytes();
299
300 let ciphertext = key.encrypt(plaintext).unwrap();
301 let decrypted = key.decrypt(&ciphertext).unwrap();
302 assert_eq!(plaintext, &decrypted[..]);
303 assert_eq!(String::from_utf8(decrypted).unwrap(), "Hello, δΈη! ππ¦");
304 }
305
306 #[test]
307 fn test_ciphertext_has_nonce() {
308 let key = CryptoKey::generate();
309 let plaintext = b"Test";
310
311 let ciphertext = key.encrypt(plaintext).unwrap();
312
313 assert!(ciphertext.len() >= plaintext.len() + NONCE_SIZE);
315
316 assert_eq!(&ciphertext[..NONCE_SIZE].len(), &NONCE_SIZE);
318 }
319}