rust_keyvault/
crypto.rs

1//! Core cryptographic traits and primitives
2
3use crate::{Error, key::SecretKey, Algorithm, Result};
4use rand_core::{CryptoRng, RngCore};
5use aead::{Aead, KeyInit, Payload, generic_array::typenum::U12};
6use chacha20poly1305::{ChaCha20Poly1305, Key as ChaChaKey, Nonce as ChaChaNonce};
7use aes_gcm::Aes256Gcm;
8
9// Explicit concrete tye aliases with explicit sizes
10type AesKey = aes_gcm::Key<Aes256Gcm>;
11type AesNonce = aes_gcm::Nonce<U12>;
12
13/// Trait for random number generators suitable for cryptographic use
14pub trait SecureRandom: RngCore + CryptoRng {
15    /// Fill a buffer with cryptographically secure random bytes
16    fn fill_secure_bytes(&mut self, dest: &mut [u8]) -> Result<()> {
17        self.try_fill_bytes(dest).map_err(|e| Error::crypto(format!("failed to fill secure bytes: {}", {e})))?;
18        Ok(())
19    }
20}
21
22/// Trait for key generation
23pub trait KeyGenerator {
24    /// The type of key this generator produces
25    type Key;
26
27    /// Generate a new key
28    fn generate<R: SecureRandom>(&self, rng: &mut R) -> Result<Self::Key>;
29
30    /// Generate a new key with specific parameters
31    fn generate_with_params<R: SecureRandom>(
32        &self, rng: &mut R, params: KeyGenParams,
33    ) -> Result<Self::Key>;
34}
35/// A simple symmetric key generator implementation
36pub struct SimpleSymmetricKeyGenerator;
37
38impl KeyGenerator for SimpleSymmetricKeyGenerator {
39    type Key = SecretKey;
40
41    fn generate<R: SecureRandom>(&self, rng: &mut R) -> Result<Self::Key> {
42        let algorithm = Algorithm::ChaCha20Poly1305; // default algorithm
43        let key_len = algorithm.key_size();
44        let mut buf = vec![0u8; key_len]; 
45        rng.fill_secure_bytes(&mut buf)?;
46        SecretKey::from_bytes(buf, algorithm)
47    }
48
49    fn generate_with_params<R: SecureRandom>(
50            &self, rng: &mut R, params: KeyGenParams,
51        ) -> Result<Self::Key> {
52        let algorithm = params.algorithm;
53        let key_len = params.key_size.unwrap_or(algorithm.key_size());
54        let mut buf = vec![0u8; key_len];
55        rng.fill_secure_bytes(&mut buf)?;
56        SecretKey::from_bytes(buf, algorithm)
57    }
58}
59
60/// Parameters for key generation
61#[derive(Debug, Clone)]
62pub struct KeyGenParams {
63    /// Algorithm to generate the key for
64    pub algorithm: Algorithm,
65    /// Optional seed materials (for deterministic generation)
66    pub seed: Option<Vec<u8>>,
67    /// Key size override (if algorithm supports multiple sizes)
68    pub key_size: Option<usize>,
69}
70
71/// Trait for AEAD (Authenticated Encryption with Associated Data)
72pub trait AEAD {
73    /// Nonce size in bytes
74    const NONCE_SIZE: usize;
75    /// Tag size in bytes
76    const TAG_SIZE: usize;
77
78    /// Encrypt plaintext with associated data
79    fn encrypt(
80        &self,
81        key: &SecretKey,
82        nonce: &[u8],
83        plaintext: &[u8],
84        associated_data: &[u8],
85    ) -> Result<Vec<u8>>;
86
87    /// Decrypt ciphertext with associated data
88    fn decrypt(
89        &self,
90        key: &SecretKey,
91        nonce: &[u8],
92        ciphertext: &[u8],
93        associated_data: &[u8],
94    ) -> Result<Vec<u8>>;
95}
96
97/// Runtime AEAD adapter supporting ChaCha20-Poly1305 and AES-GCM
98pub struct RuntimeAead;
99
100impl RuntimeAead {
101    fn check_key_len(key: &SecretKey, expected: usize) -> Result<()> {
102        if key.expose_secret().len() != expected {
103            return Err(Error::crypto(format!(
104                "invalid key size: expected {}, got {}",
105                expected,
106                key.expose_secret().len()
107            )));
108        }
109        Ok(())
110    }
111}
112
113impl crate::crypto::AEAD for RuntimeAead {
114    // both algorithms use a 12-byte nonce and 16-byte tag
115    const NONCE_SIZE: usize = 12;
116    const TAG_SIZE: usize = 16;
117
118    fn encrypt(
119        &self,
120        key: &SecretKey,
121        nonce: &[u8],
122        plaintext: &[u8],
123        associated_data: &[u8],
124    ) -> Result<Vec<u8>> {
125        if nonce.len() != Self::NONCE_SIZE {
126            return Err(Error::crypto(format!("invalid nonce length: expected {}", Self::NONCE_SIZE)));
127        }
128
129        match key.algorithm() {
130            Algorithm::ChaCha20Poly1305 => {
131                Self::check_key_len(key, 32)?;
132                let cipher = ChaCha20Poly1305::new(ChaChaKey::from_slice(key.expose_secret()));
133                // annotate nonce type to help type inference
134                let n: &ChaChaNonce = ChaChaNonce::from_slice(nonce);
135                cipher
136                    .encrypt(n, Payload { msg: plaintext, aad: associated_data })
137                    .map_err(|e| Error::crypto(format!("ChaCha20-Poly1305 encryption failed: {}", {e}))) // produce concrete String
138            }
139            Algorithm::Aes256Gcm => {
140                Self::check_key_len(key, 32)?;
141                let cipher = Aes256Gcm::new(AesKey::from_slice(key.expose_secret()));
142                let n: &AesNonce = AesNonce::from_slice(nonce);
143                cipher
144                    .encrypt(n, Payload { msg: plaintext, aad: associated_data })
145                    .map_err(|e| Error::crypto(format!("AES-256-GCM encryption failed: {}", {e})))
146            }
147            alg => Err(Error::crypto(format!("algorithm {alg:?} not supported for AEAD"))),
148        }
149    }
150
151    fn decrypt(
152        &self,
153        key: &SecretKey,
154        nonce: &[u8],
155        ciphertext: &[u8],
156        associated_data: &[u8],
157    ) -> Result<Vec<u8>> {
158        if nonce.len() != Self::NONCE_SIZE {
159            return Err(Error::crypto(format!("invalid nonce length: expected {}", Self::NONCE_SIZE)));
160        }
161
162        match key.algorithm() {
163            Algorithm::ChaCha20Poly1305 => {
164                Self::check_key_len(key, 32)?;
165                let cipher = ChaCha20Poly1305::new(ChaChaKey::from_slice(key.expose_secret()));
166                let n: &ChaChaNonce = ChaChaNonce::from_slice(nonce);
167                cipher
168                    .decrypt(n, Payload { msg: ciphertext, aad: associated_data })
169                    .map_err(|e| Error::crypto(format!("ChaCha20-Poly1305 decryption failed: {}", {e})))
170            }
171            Algorithm::Aes256Gcm => {
172                Self::check_key_len(key, 32)?;
173                let cipher = Aes256Gcm::new(AesKey::from_slice(key.expose_secret()));
174                let n: &AesNonce = AesNonce::from_slice(nonce);
175                cipher
176                    .decrypt(n, Payload { msg: ciphertext, aad: associated_data })
177                    .map_err(|e| Error::crypto(format!("AES-256-GCM decryption failed: {}", {e})))
178            }
179            alg => Err(Error::crypto(format!("algorithm {alg:?} not supported for AEAD"))),
180        }
181    }
182}
183
184/// Nonce generation strategies
185#[derive(Debug, Clone, Copy)]
186pub enum NonceStrategy {
187    /// Random nonces (requires large nonce space)
188    Random,
189    /// Counter-based (requires persistent state)
190    Counter, 
191    /// Derived from message (requires unique messages)
192    Synthetic,
193}
194
195/// Trait for nonce generation
196pub trait NonceGenerator {
197    /// Generate a nonce for encryption
198    /// 
199    /// CRITICAL: Nonce must never be reused with the same key to avoid breaking security
200    fn generate_nonce(&mut self, message_id: &[u8]) -> Result<Vec<u8>>; 
201
202    /// Get the strategy this generator uses
203    fn strategy(&self) -> NonceStrategy;
204}
205
206/// Nonce generator using a cryptographically secure RNG
207pub struct RandomNonceGenerator<R: SecureRandom> {
208    rng: R,
209    nonce_size: usize,
210}
211
212/// Random nonce generator implementation
213impl<R: SecureRandom> RandomNonceGenerator<R> {
214    /// Create a new random nonce generator
215    pub fn new(rng: R, nonce_size: usize) -> Self {
216        Self { rng, nonce_size }
217    } 
218}
219
220impl<R: SecureRandom> NonceGenerator for RandomNonceGenerator<R> {
221    fn generate_nonce(&mut self, _message_id: &[u8]) -> Result<Vec<u8>> {
222        let mut nonce = vec![0u8; self.nonce_size];
223        self.rng.fill_secure_bytes(&mut nonce)?;
224        Ok(nonce)
225    }
226
227    fn strategy(&self) -> NonceStrategy {
228        NonceStrategy::Random
229    }
230}
231
232/// Blanket implementation for any crypto RNG
233impl<T> SecureRandom for T where T: RngCore + CryptoRng {}
234
235/// Trait for constant-time operations
236pub trait ConstantTime {
237    /// Compare two byte slices in constant time
238    fn ct_eq(&self, other: &[u8]) -> bool;
239
240    /// Select between two values in constant time
241    fn ct_select(condition: bool, a: Self, b: Self) -> Self where Self: Sized;
242}
243
244#[cfg(test)]
245mod tests {
246    use rand_chacha::ChaCha12Rng;
247    use rand_core::SeedableRng;
248
249    use super::*;
250
251    #[test]
252    fn test_keygen_params() {
253        let params = KeyGenParams {
254            algorithm: Algorithm::Aes256Gcm,
255            seed: None,
256            key_size: Some(32),
257        };
258        assert_eq!(params.algorithm, Algorithm::Aes256Gcm);
259        assert_eq!(params.key_size, Some(32));
260        assert_eq!(params.seed, None);
261    }
262
263    #[test]
264    fn test_end_to_end_aead() {
265        // Create deterministic RNG for testing and generate a key
266        let mut rng = ChaCha12Rng::seed_from_u64(42);
267        let generator = SimpleSymmetricKeyGenerator;
268        let key = generator.generate(&mut rng).unwrap();
269
270        // Create AEAD and nonce generator
271        let aead = RuntimeAead;
272        let mut nonce_gen = RandomNonceGenerator::new(
273            ChaCha12Rng::seed_from_u64(123),
274            RuntimeAead::NONCE_SIZE
275        );
276
277        // Let's test the data
278        let plaintext = b"Secret message for testing";
279        let associated_data = b"public metadata";
280
281        // Encrypt
282        let nonce = nonce_gen.generate_nonce(b"msg_001").unwrap ();
283        let ciphertext = aead.encrypt(&key, &nonce, plaintext, associated_data).unwrap();
284
285        // Verify that the ciphertext is different from the plaintext
286        assert_ne!(ciphertext.as_slice(), plaintext);
287
288        // Decrypt and finally verify
289        let decrypted = aead.decrypt(&key, &nonce, &ciphertext, associated_data).unwrap();
290        assert_eq!(decrypted.as_slice(), plaintext);
291    }
292}