Skip to main content

network_protocol/utils/
crypto.rs

1use chacha20poly1305::{
2    aead::{Aead, KeyInit},
3    Key, XChaCha20Poly1305, XNonce,
4};
5use getrandom::fill;
6use zeroize::Zeroize;
7
8use crate::error::{ProtocolError, Result};
9
10pub struct Crypto {
11    cipher: XChaCha20Poly1305,
12}
13
14impl Crypto {
15    pub fn new(key_bytes: &[u8; 32]) -> Self {
16        let key = Key::from_slice(key_bytes);
17        let cipher = XChaCha20Poly1305::new(key);
18        Self { cipher }
19    }
20
21    pub fn encrypt(&self, plaintext: &[u8], nonce: &[u8; 24]) -> Result<Vec<u8>> {
22        let nonce = XNonce::from_slice(nonce);
23        self.cipher
24            .encrypt(nonce, plaintext)
25            .map_err(|_| ProtocolError::EncryptionFailure)
26    }
27
28    pub fn decrypt(&self, ciphertext: &[u8], nonce: &[u8; 24]) -> Result<Vec<u8>> {
29        let nonce = XNonce::from_slice(nonce);
30        self.cipher
31            .decrypt(nonce, ciphertext)
32            .map_err(|_| ProtocolError::DecryptionFailure)
33    }
34
35    /// Generates a secure random 24-byte nonce
36    /// Note: Returned nonce should be zeroized after use if it contains sensitive material
37    #[allow(clippy::expect_used)] // cryptographic RNG failure is unrecoverable
38    pub fn generate_nonce() -> [u8; 24] {
39        let mut nonce = [0u8; 24];
40        fill(&mut nonce).expect("Failed to fill nonce");
41        nonce
42    }
43
44    /// Generate a random 32-byte key material
45    /// Caller is responsible for zeroizing the returned bytes
46    #[allow(clippy::expect_used)] // cryptographic RNG failure is unrecoverable
47    pub fn generate_key() -> [u8; 32] {
48        let mut key = [0u8; 32];
49        fill(&mut key).expect("Failed to fill key");
50        key
51    }
52}
53
54impl Drop for Crypto {
55    fn drop(&mut self) {
56        // Note: XChaCha20Poly1305 doesn't expose its internal key for zeroization
57        // The underlying key material should be zeroized by the chacha20poly1305 crate
58        // This is acceptable as the crate uses secure containers
59    }
60}
61
62/// Shared secret wrapper that zeroizes on drop
63#[derive(Clone)]
64pub struct SharedSecret {
65    secret: [u8; 32],
66}
67
68impl SharedSecret {
69    pub fn new(secret: [u8; 32]) -> Self {
70        Self { secret }
71    }
72
73    pub fn as_bytes(&self) -> &[u8; 32] {
74        &self.secret
75    }
76}
77
78impl Zeroize for SharedSecret {
79    fn zeroize(&mut self) {
80        self.secret.zeroize();
81    }
82}
83
84impl Drop for SharedSecret {
85    fn drop(&mut self) {
86        self.zeroize();
87    }
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93
94    #[test]
95    fn test_crypto_nonce_generation() {
96        let nonce = Crypto::generate_nonce();
97        assert_eq!(nonce.len(), 24);
98        // Nonces should not be all zeros (extremely unlikely with secure RNG)
99        assert!(nonce.iter().any(|&b| b != 0));
100    }
101
102    #[test]
103    fn test_crypto_key_generation() {
104        let key = Crypto::generate_key();
105        assert_eq!(key.len(), 32);
106        // Keys should not be all zeros
107        assert!(key.iter().any(|&b| b != 0));
108    }
109
110    #[test]
111    fn test_shared_secret_zeroize() {
112        let mut secret = SharedSecret::new([0xAB; 32]);
113        secret.zeroize();
114        assert!(secret.as_bytes().iter().all(|&b| b == 0));
115    }
116}