1use ring::aead::{AES_256_GCM, Aad, LessSafeKey, NONCE_LEN, Nonce, UnboundKey};
2use ring::rand::{SecureRandom, SystemRandom};
3use secrecy::{ExposeSecret, SecretBox};
4use thiserror::Error;
5
6const PROJECT_KEY_LEN: usize = 32;
7
8pub type SecretBytes = SecretBox<[u8]>;
9
10#[derive(Debug, Clone, PartialEq, Eq)]
11pub struct EncryptedData {
12 pub nonce: [u8; NONCE_LEN],
13 pub ciphertext: Vec<u8>,
14}
15
16#[derive(Debug, Error)]
17pub enum CryptoError {
18 #[error("project key must be 32 bytes")]
19 InvalidKeyLength,
20 #[error("failed to generate secure random bytes")]
21 RandomFailure,
22 #[error("failed to initialize AES-256-GCM key")]
23 KeyInitFailure,
24 #[error("encryption failed")]
25 EncryptionFailure,
26 #[error("decryption failed")]
27 DecryptionFailure,
28}
29
30pub fn secret_bytes(bytes: Vec<u8>) -> SecretBytes {
31 SecretBox::new(bytes.into_boxed_slice())
32}
33
34pub fn generate_project_key() -> Result<SecretBytes, CryptoError> {
35 let mut key = [0u8; PROJECT_KEY_LEN];
36 SystemRandom::new()
37 .fill(&mut key)
38 .map_err(|_| CryptoError::RandomFailure)?;
39 Ok(secret_bytes(key.to_vec()))
40}
41
42pub fn encrypt(key: &SecretBytes, plaintext: &SecretBytes) -> Result<EncryptedData, CryptoError> {
43 let aead_key = build_key(key.expose_secret())?;
44
45 let mut nonce_bytes = [0u8; NONCE_LEN];
46 SystemRandom::new()
47 .fill(&mut nonce_bytes)
48 .map_err(|_| CryptoError::RandomFailure)?;
49
50 let nonce = Nonce::assume_unique_for_key(nonce_bytes);
51 let mut buffer = plaintext.expose_secret().to_vec();
52
53 aead_key
54 .seal_in_place_append_tag(nonce, Aad::empty(), &mut buffer)
55 .map_err(|_| CryptoError::EncryptionFailure)?;
56
57 Ok(EncryptedData {
58 nonce: nonce_bytes,
59 ciphertext: buffer,
60 })
61}
62
63pub fn decrypt(key: &SecretBytes, encrypted: &EncryptedData) -> Result<SecretBytes, CryptoError> {
64 let aead_key = build_key(key.expose_secret())?;
65 let nonce = Nonce::assume_unique_for_key(encrypted.nonce);
66
67 let mut buffer = encrypted.ciphertext.clone();
68 let plaintext = aead_key
69 .open_in_place(nonce, Aad::empty(), &mut buffer)
70 .map_err(|_| CryptoError::DecryptionFailure)?;
71
72 Ok(secret_bytes(plaintext.to_vec()))
73}
74
75fn build_key(key: &[u8]) -> Result<LessSafeKey, CryptoError> {
76 if key.len() != PROJECT_KEY_LEN {
77 return Err(CryptoError::InvalidKeyLength);
78 }
79
80 let unbound = UnboundKey::new(&AES_256_GCM, key).map_err(|_| CryptoError::KeyInitFailure)?;
81 Ok(LessSafeKey::new(unbound))
82}
83
84#[cfg(test)]
85mod tests {
86 use super::{decrypt, encrypt, generate_project_key, secret_bytes};
87 use ring::rand::{SecureRandom, SystemRandom};
88 use secrecy::ExposeSecret;
89
90 #[test]
91 fn aes_gcm_roundtrip() {
92 let key = generate_project_key().expect("project key generation should succeed");
93
94 let mut plaintext = vec![0u8; 1024 * 1024];
95 SystemRandom::new()
96 .fill(&mut plaintext)
97 .expect("random data generation should succeed");
98
99 let encrypted =
100 encrypt(&key, &secret_bytes(plaintext.clone())).expect("encryption should succeed");
101 let decrypted = decrypt(&key, &encrypted).expect("decryption should succeed");
102
103 assert_eq!(decrypted.expose_secret(), plaintext.as_slice());
104 }
105}