Skip to main content

async_snmp/v3/crypto/
rustcrypto.rs

1use super::{CryptoError, CryptoProvider, CryptoResult};
2use crate::v3::{AuthProtocol, PrivProtocol};
3
4/// Default crypto provider backed by the `RustCrypto` crate ecosystem.
5///
6/// This is a stateless unit struct that dispatches to the appropriate
7/// `RustCrypto` implementations based on the protocol enum values.
8pub struct RustCryptoProvider;
9
10// --- Dispatch macro for auth protocol -> concrete hash type ---
11
12macro_rules! dispatch_auth {
13    ($protocol:expr, $fn:ident, $($arg:expr),*) => {
14        match $protocol {
15            AuthProtocol::Md5 => $fn::<md5::Md5>($($arg),*),
16            AuthProtocol::Sha1 => $fn::<sha1::Sha1>($($arg),*),
17            AuthProtocol::Sha224 => $fn::<sha2::Sha224>($($arg),*),
18            AuthProtocol::Sha256 => $fn::<sha2::Sha256>($($arg),*),
19            AuthProtocol::Sha384 => $fn::<sha2::Sha384>($($arg),*),
20            AuthProtocol::Sha512 => $fn::<sha2::Sha512>($($arg),*),
21        }
22    };
23}
24
25impl CryptoProvider for RustCryptoProvider {
26    fn hash(&self, protocol: AuthProtocol, data: &[u8]) -> CryptoResult<Vec<u8>> {
27        Ok(dispatch_auth!(protocol, hash_impl, data))
28    }
29
30    fn password_to_key(&self, protocol: AuthProtocol, password: &[u8]) -> CryptoResult<Vec<u8>> {
31        const EXPANSION_SIZE: usize = 1_048_576; // 1MB
32        Ok(dispatch_auth!(
33            protocol,
34            password_to_key_impl,
35            password,
36            EXPANSION_SIZE
37        ))
38    }
39
40    fn localize_key(
41        &self,
42        protocol: AuthProtocol,
43        master_key: &[u8],
44        engine_id: &[u8],
45    ) -> CryptoResult<Vec<u8>> {
46        Ok(dispatch_auth!(
47            protocol,
48            localize_key_impl,
49            master_key,
50            engine_id
51        ))
52    }
53
54    fn compute_hmac(
55        &self,
56        protocol: AuthProtocol,
57        key: &[u8],
58        slices: &[&[u8]],
59        truncate_len: usize,
60    ) -> CryptoResult<Vec<u8>> {
61        Ok(dispatch_auth!(
62            protocol,
63            compute_hmac_impl,
64            key,
65            slices,
66            truncate_len
67        ))
68    }
69
70    fn encrypt(
71        &self,
72        protocol: PrivProtocol,
73        key: &[u8],
74        iv: &[u8],
75        data: &mut Vec<u8>,
76    ) -> CryptoResult<()> {
77        match protocol {
78            PrivProtocol::Des | PrivProtocol::Des3 => {
79                // RFC 3414 §8.1.1.2: pad only up to the next 8-byte block boundary;
80                // the pad value is irrelevant and no full block is added when already
81                // aligned (i.e. not PKCS7).
82                let block = 8;
83                let padded_len = data.len().next_multiple_of(block);
84                let pad_byte = (padded_len - data.len()) as u8;
85                data.resize(padded_len, pad_byte);
86                match protocol {
87                    PrivProtocol::Des => encrypt_des_cbc(key, iv, data),
88                    _ => encrypt_des3_cbc(key, iv, data),
89                }
90            }
91            PrivProtocol::Aes128 | PrivProtocol::Aes192 | PrivProtocol::Aes256 => {
92                encrypt_aes_cfb(key, iv, data)
93            }
94        }
95    }
96
97    fn decrypt(
98        &self,
99        protocol: PrivProtocol,
100        key: &[u8],
101        iv: &[u8],
102        data: &mut [u8],
103    ) -> CryptoResult<()> {
104        match protocol {
105            PrivProtocol::Des => decrypt_des_cbc(key, iv, data),
106            PrivProtocol::Des3 => decrypt_des3_cbc(key, iv, data),
107            PrivProtocol::Aes128 | PrivProtocol::Aes192 | PrivProtocol::Aes256 => {
108                decrypt_aes_cfb(key, iv, data)
109            }
110        }
111    }
112}
113
114// --- Auth primitive implementations ---
115
116use digest::block_api::BlockSizeUser;
117use digest::{Digest, KeyInit, Mac, OutputSizeUser};
118
119fn hash_impl<D>(data: &[u8]) -> Vec<u8>
120where
121    D: Digest + Default,
122{
123    let mut hasher = D::new();
124    hasher.update(data);
125    hasher.finalize().to_vec()
126}
127
128fn password_to_key_impl<D>(password: &[u8], expansion_size: usize) -> Vec<u8>
129where
130    D: Digest + Default,
131{
132    if password.is_empty() {
133        return vec![0u8; <D as OutputSizeUser>::output_size()];
134    }
135
136    let mut hasher = D::new();
137
138    let mut buf = [0u8; 64];
139    let password_len = password.len();
140    let mut password_index = 0;
141    let mut count = 0;
142
143    while count < expansion_size {
144        for byte in &mut buf {
145            *byte = password[password_index];
146            password_index = (password_index + 1) % password_len;
147        }
148        hasher.update(buf);
149        count += 64;
150    }
151
152    hasher.finalize().to_vec()
153}
154
155fn localize_key_impl<D>(master_key: &[u8], engine_id: &[u8]) -> Vec<u8>
156where
157    D: Digest + Default,
158{
159    let mut hasher = D::new();
160    hasher.update(master_key);
161    hasher.update(engine_id);
162    hasher.update(master_key);
163    hasher.finalize().to_vec()
164}
165
166fn compute_hmac_impl<D>(key: &[u8], slices: &[&[u8]], truncate_len: usize) -> Vec<u8>
167where
168    D: Digest + BlockSizeUser + Clone,
169{
170    use hmac::SimpleHmac;
171
172    let mut mac =
173        <SimpleHmac<D> as KeyInit>::new_from_slice(key).expect("HMAC can take key of any size");
174    for slice in slices {
175        Mac::update(&mut mac, slice);
176    }
177    let result = mac.finalize().into_bytes();
178    result[..truncate_len].to_vec()
179}
180
181// --- Privacy primitive implementations ---
182
183fn encrypt_des_cbc(key: &[u8], iv: &[u8], data: &mut [u8]) -> CryptoResult<()> {
184    use cbc::cipher::{BlockModeEncrypt, KeyIvInit};
185    type DesCbc = cbc::Encryptor<des::Des>;
186
187    let cipher = DesCbc::new_from_slices(key, iv).map_err(|_| {
188        tracing::debug!(target: "async_snmp::crypto", "DES encryption failed: invalid key length");
189        CryptoError::InvalidKeyLength
190    })?;
191    let len = data.len();
192    cipher
193        .encrypt_padded::<cbc::cipher::block_padding::NoPadding>(data, len)
194        .map_err(|_| {
195            tracing::debug!(target: "async_snmp::crypto", "DES encryption failed: cipher error");
196            CryptoError::CipherError
197        })?;
198    Ok(())
199}
200
201fn decrypt_des_cbc(key: &[u8], iv: &[u8], data: &mut [u8]) -> CryptoResult<()> {
202    use cbc::cipher::{BlockModeDecrypt, KeyIvInit};
203    type DesCbc = cbc::Decryptor<des::Des>;
204
205    let cipher = DesCbc::new_from_slices(key, iv).map_err(|_| {
206        tracing::debug!(target: "async_snmp::crypto", "DES decryption failed: invalid key length");
207        CryptoError::InvalidKeyLength
208    })?;
209    cipher
210        .decrypt_padded::<cbc::cipher::block_padding::NoPadding>(data)
211        .map_err(|_| {
212            tracing::debug!(target: "async_snmp::crypto", "DES decryption failed: cipher error");
213            CryptoError::CipherError
214        })?;
215    Ok(())
216}
217
218fn encrypt_des3_cbc(key: &[u8], iv: &[u8], data: &mut [u8]) -> CryptoResult<()> {
219    use cbc::cipher::{BlockModeEncrypt, KeyIvInit};
220    type Des3Cbc = cbc::Encryptor<des::TdesEde3>;
221
222    let cipher = Des3Cbc::new_from_slices(key, iv).map_err(|_| {
223        tracing::debug!(target: "async_snmp::crypto", "3DES encryption failed: invalid key length");
224        CryptoError::InvalidKeyLength
225    })?;
226    let len = data.len();
227    cipher
228        .encrypt_padded::<cbc::cipher::block_padding::NoPadding>(data, len)
229        .map_err(|_| {
230            tracing::debug!(target: "async_snmp::crypto", "3DES encryption failed: cipher error");
231            CryptoError::CipherError
232        })?;
233    Ok(())
234}
235
236fn decrypt_des3_cbc(key: &[u8], iv: &[u8], data: &mut [u8]) -> CryptoResult<()> {
237    use cbc::cipher::{BlockModeDecrypt, KeyIvInit};
238    type Des3Cbc = cbc::Decryptor<des::TdesEde3>;
239
240    let cipher = Des3Cbc::new_from_slices(key, iv).map_err(|_| {
241        tracing::debug!(target: "async_snmp::crypto", "3DES decryption failed: invalid key length");
242        CryptoError::InvalidKeyLength
243    })?;
244    cipher
245        .decrypt_padded::<cbc::cipher::block_padding::NoPadding>(data)
246        .map_err(|_| {
247            tracing::debug!(target: "async_snmp::crypto", "3DES decryption failed: cipher error");
248            CryptoError::CipherError
249        })?;
250    Ok(())
251}
252
253fn encrypt_aes_cfb(key: &[u8], iv: &[u8], data: &mut [u8]) -> CryptoResult<()> {
254    use aes::{Aes128, Aes192, Aes256};
255    use cfb_mode::cipher::KeyIvInit;
256
257    match key.len() {
258        16 => {
259            type Aes128Cfb = cfb_mode::Encryptor<Aes128>;
260            let cipher = Aes128Cfb::new_from_slices(key, iv).map_err(|_| {
261                tracing::debug!(target: "async_snmp::crypto", "AES-128 encryption failed: invalid key length");
262                CryptoError::InvalidKeyLength
263            })?;
264            cipher.encrypt(data);
265        }
266        24 => {
267            type Aes192Cfb = cfb_mode::Encryptor<Aes192>;
268            let cipher = Aes192Cfb::new_from_slices(key, iv).map_err(|_| {
269                tracing::debug!(target: "async_snmp::crypto", "AES-192 encryption failed: invalid key length");
270                CryptoError::InvalidKeyLength
271            })?;
272            cipher.encrypt(data);
273        }
274        32 => {
275            type Aes256Cfb = cfb_mode::Encryptor<Aes256>;
276            let cipher = Aes256Cfb::new_from_slices(key, iv).map_err(|_| {
277                tracing::debug!(target: "async_snmp::crypto", "AES-256 encryption failed: invalid key length");
278                CryptoError::InvalidKeyLength
279            })?;
280            cipher.encrypt(data);
281        }
282        key_len => {
283            tracing::debug!(target: "async_snmp::crypto", { key_len }, "AES encryption failed: unsupported key length");
284            return Err(CryptoError::InvalidKeyLength);
285        }
286    }
287    Ok(())
288}
289
290fn decrypt_aes_cfb(key: &[u8], iv: &[u8], data: &mut [u8]) -> CryptoResult<()> {
291    use aes::{Aes128, Aes192, Aes256};
292    use cfb_mode::cipher::KeyIvInit;
293
294    match key.len() {
295        16 => {
296            type Aes128Cfb = cfb_mode::Decryptor<Aes128>;
297            let cipher = Aes128Cfb::new_from_slices(key, iv).map_err(|_| {
298                tracing::debug!(target: "async_snmp::crypto", "AES-128 decryption failed: invalid key length");
299                CryptoError::InvalidKeyLength
300            })?;
301            cipher.decrypt(data);
302        }
303        24 => {
304            type Aes192Cfb = cfb_mode::Decryptor<Aes192>;
305            let cipher = Aes192Cfb::new_from_slices(key, iv).map_err(|_| {
306                tracing::debug!(target: "async_snmp::crypto", "AES-192 decryption failed: invalid key length");
307                CryptoError::InvalidKeyLength
308            })?;
309            cipher.decrypt(data);
310        }
311        32 => {
312            type Aes256Cfb = cfb_mode::Decryptor<Aes256>;
313            let cipher = Aes256Cfb::new_from_slices(key, iv).map_err(|_| {
314                tracing::debug!(target: "async_snmp::crypto", "AES-256 decryption failed: invalid key length");
315                CryptoError::InvalidKeyLength
316            })?;
317            cipher.decrypt(data);
318        }
319        key_len => {
320            tracing::debug!(target: "async_snmp::crypto", { key_len }, "AES decryption failed: unsupported key length");
321            return Err(CryptoError::InvalidKeyLength);
322        }
323    }
324    Ok(())
325}
326
327#[cfg(test)]
328mod tests {
329    use super::*;
330
331    /// RFC 3414 §8.1.1.2: "if [the length] is not [a multiple of 8], the data
332    /// is padded at the end as necessary." The encrypt operation must handle
333    /// unaligned plaintext by padding to the next block boundary.
334    #[test]
335    fn des_encrypt_pads_unaligned_plaintext() {
336        let provider = RustCryptoProvider;
337        let key = b"\x00\x11\x22\x33\x44\x55\x66\x77";
338        let iv = [0u8; 8];
339        let mut data = b"Hello".to_vec(); // 5 bytes, not a multiple of 8
340
341        let result = provider.encrypt(PrivProtocol::Des, key, &iv, &mut data);
342        assert!(
343            result.is_ok(),
344            "DES encrypt must pad unaligned plaintext, got: {result:?}"
345        );
346        assert_eq!(data.len(), 8, "output must be padded to 8-byte boundary");
347    }
348
349    /// Same as DES: 3DES-CBC must pad unaligned plaintext.
350    #[test]
351    fn des3_encrypt_pads_unaligned_plaintext() {
352        let provider = RustCryptoProvider;
353        let key = [0x01u8; 24];
354        let iv = [0u8; 8];
355        let mut data = b"Hello".to_vec(); // 5 bytes
356
357        let result = provider.encrypt(PrivProtocol::Des3, &key, &iv, &mut data);
358        assert!(
359            result.is_ok(),
360            "3DES encrypt must pad unaligned plaintext, got: {result:?}"
361        );
362        assert_eq!(data.len(), 8, "output must be padded to 8-byte boundary");
363    }
364
365    /// DES roundtrip: unaligned plaintext should encrypt and decrypt correctly.
366    #[test]
367    fn des_roundtrip_unaligned() {
368        let provider = RustCryptoProvider;
369        let key = b"\x00\x11\x22\x33\x44\x55\x66\x77";
370        let iv = [0u8; 8];
371        let plaintext = b"Hello";
372        let mut data = plaintext.to_vec();
373
374        provider
375            .encrypt(PrivProtocol::Des, key, &iv, &mut data)
376            .unwrap();
377        assert_eq!(data.len(), 8);
378
379        provider
380            .decrypt(PrivProtocol::Des, key, &iv, &mut data)
381            .unwrap();
382        assert_eq!(&data[..plaintext.len()], plaintext);
383    }
384
385    /// Already-aligned DES plaintext should still work (no regression).
386    #[test]
387    fn des_encrypt_aligned_unchanged() {
388        let provider = RustCryptoProvider;
389        let key = b"\x00\x11\x22\x33\x44\x55\x66\x77";
390        let iv = [0u8; 8];
391        let mut data = vec![0x41u8; 8]; // already 8 bytes
392
393        let result = provider.encrypt(PrivProtocol::Des, key, &iv, &mut data);
394        assert!(result.is_ok());
395        assert_eq!(data.len(), 8);
396    }
397}