1use super::{CryptoError, CryptoProvider, CryptoResult};
2use crate::v3::{AuthProtocol, PrivProtocol};
3
4pub struct RustCryptoProvider;
9
10macro_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; 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 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
114use 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
181fn 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 #[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(); 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 #[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(); 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 #[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 #[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]; let result = provider.encrypt(PrivProtocol::Des, key, &iv, &mut data);
394 assert!(result.is_ok());
395 assert_eq!(data.len(), 8);
396 }
397}