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; if password.len() < crate::v3::auth::MIN_PASSWORD_LENGTH {
33 return Err(CryptoError::PasswordTooShort);
34 }
35 Ok(dispatch_auth!(
36 protocol,
37 password_to_key_impl,
38 password,
39 EXPANSION_SIZE
40 ))
41 }
42
43 fn localize_key(
44 &self,
45 protocol: AuthProtocol,
46 master_key: &[u8],
47 engine_id: &[u8],
48 ) -> CryptoResult<Vec<u8>> {
49 Ok(dispatch_auth!(
50 protocol,
51 localize_key_impl,
52 master_key,
53 engine_id
54 ))
55 }
56
57 fn compute_hmac(
58 &self,
59 protocol: AuthProtocol,
60 key: &[u8],
61 slices: &[&[u8]],
62 truncate_len: usize,
63 ) -> CryptoResult<Vec<u8>> {
64 Ok(dispatch_auth!(
65 protocol,
66 compute_hmac_impl,
67 key,
68 slices,
69 truncate_len
70 ))
71 }
72
73 fn encrypt(
74 &self,
75 protocol: PrivProtocol,
76 key: &[u8],
77 iv: &[u8],
78 data: &mut Vec<u8>,
79 ) -> CryptoResult<()> {
80 match protocol {
81 PrivProtocol::Des | PrivProtocol::Des3 => {
82 let block = 8;
86 let padded_len = data.len().next_multiple_of(block);
87 let pad_byte = (padded_len - data.len()) as u8;
88 data.resize(padded_len, pad_byte);
89 match protocol {
90 PrivProtocol::Des => encrypt_des_cbc(key, iv, data),
91 _ => encrypt_des3_cbc(key, iv, data),
92 }
93 }
94 PrivProtocol::Aes128 | PrivProtocol::Aes192 | PrivProtocol::Aes256 => {
95 encrypt_aes_cfb(key, iv, data)
96 }
97 }
98 }
99
100 fn decrypt(
101 &self,
102 protocol: PrivProtocol,
103 key: &[u8],
104 iv: &[u8],
105 data: &mut [u8],
106 ) -> CryptoResult<()> {
107 match protocol {
108 PrivProtocol::Des => decrypt_des_cbc(key, iv, data),
109 PrivProtocol::Des3 => decrypt_des3_cbc(key, iv, data),
110 PrivProtocol::Aes128 | PrivProtocol::Aes192 | PrivProtocol::Aes256 => {
111 decrypt_aes_cfb(key, iv, data)
112 }
113 }
114 }
115}
116
117use digest::block_api::BlockSizeUser;
120use digest::{Digest, KeyInit, Mac, OutputSizeUser};
121
122fn hash_impl<D>(data: &[u8]) -> Vec<u8>
123where
124 D: Digest + Default,
125{
126 let mut hasher = D::new();
127 hasher.update(data);
128 hasher.finalize().to_vec()
129}
130
131fn password_to_key_impl<D>(password: &[u8], expansion_size: usize) -> Vec<u8>
132where
133 D: Digest + Default,
134{
135 if password.is_empty() {
136 return vec![0u8; <D as OutputSizeUser>::output_size()];
137 }
138
139 let mut hasher = D::new();
140
141 let mut buf = [0u8; 64];
142 let password_len = password.len();
143 let mut password_index = 0;
144 let mut count = 0;
145
146 while count < expansion_size {
147 for byte in &mut buf {
148 *byte = password[password_index];
149 password_index = (password_index + 1) % password_len;
150 }
151 hasher.update(buf);
152 count += 64;
153 }
154
155 hasher.finalize().to_vec()
156}
157
158fn localize_key_impl<D>(master_key: &[u8], engine_id: &[u8]) -> Vec<u8>
159where
160 D: Digest + Default,
161{
162 let mut hasher = D::new();
163 hasher.update(master_key);
164 hasher.update(engine_id);
165 hasher.update(master_key);
166 hasher.finalize().to_vec()
167}
168
169fn compute_hmac_impl<D>(key: &[u8], slices: &[&[u8]], truncate_len: usize) -> Vec<u8>
170where
171 D: Digest + BlockSizeUser + Clone,
172{
173 use hmac::SimpleHmac;
174
175 let mut mac =
176 <SimpleHmac<D> as KeyInit>::new_from_slice(key).expect("HMAC can take key of any size");
177 for slice in slices {
178 Mac::update(&mut mac, slice);
179 }
180 let result = mac.finalize().into_bytes();
181 result[..truncate_len].to_vec()
182}
183
184fn encrypt_des_cbc(key: &[u8], iv: &[u8], data: &mut [u8]) -> CryptoResult<()> {
187 use cbc::cipher::{BlockModeEncrypt, KeyIvInit};
188 type DesCbc = cbc::Encryptor<des::Des>;
189
190 let cipher = DesCbc::new_from_slices(key, iv).map_err(|_| {
191 tracing::debug!(target: "async_snmp::crypto", "DES encryption failed: invalid key length");
192 CryptoError::InvalidKeyLength
193 })?;
194 let len = data.len();
195 cipher
196 .encrypt_padded::<cbc::cipher::block_padding::NoPadding>(data, len)
197 .map_err(|_| {
198 tracing::debug!(target: "async_snmp::crypto", "DES encryption failed: cipher error");
199 CryptoError::CipherError
200 })?;
201 Ok(())
202}
203
204fn decrypt_des_cbc(key: &[u8], iv: &[u8], data: &mut [u8]) -> CryptoResult<()> {
205 use cbc::cipher::{BlockModeDecrypt, KeyIvInit};
206 type DesCbc = cbc::Decryptor<des::Des>;
207
208 let cipher = DesCbc::new_from_slices(key, iv).map_err(|_| {
209 tracing::debug!(target: "async_snmp::crypto", "DES decryption failed: invalid key length");
210 CryptoError::InvalidKeyLength
211 })?;
212 cipher
213 .decrypt_padded::<cbc::cipher::block_padding::NoPadding>(data)
214 .map_err(|_| {
215 tracing::debug!(target: "async_snmp::crypto", "DES decryption failed: cipher error");
216 CryptoError::CipherError
217 })?;
218 Ok(())
219}
220
221fn encrypt_des3_cbc(key: &[u8], iv: &[u8], data: &mut [u8]) -> CryptoResult<()> {
222 use cbc::cipher::{BlockModeEncrypt, KeyIvInit};
223 type Des3Cbc = cbc::Encryptor<des::TdesEde3>;
224
225 let cipher = Des3Cbc::new_from_slices(key, iv).map_err(|_| {
226 tracing::debug!(target: "async_snmp::crypto", "3DES encryption failed: invalid key length");
227 CryptoError::InvalidKeyLength
228 })?;
229 let len = data.len();
230 cipher
231 .encrypt_padded::<cbc::cipher::block_padding::NoPadding>(data, len)
232 .map_err(|_| {
233 tracing::debug!(target: "async_snmp::crypto", "3DES encryption failed: cipher error");
234 CryptoError::CipherError
235 })?;
236 Ok(())
237}
238
239fn decrypt_des3_cbc(key: &[u8], iv: &[u8], data: &mut [u8]) -> CryptoResult<()> {
240 use cbc::cipher::{BlockModeDecrypt, KeyIvInit};
241 type Des3Cbc = cbc::Decryptor<des::TdesEde3>;
242
243 let cipher = Des3Cbc::new_from_slices(key, iv).map_err(|_| {
244 tracing::debug!(target: "async_snmp::crypto", "3DES decryption failed: invalid key length");
245 CryptoError::InvalidKeyLength
246 })?;
247 cipher
248 .decrypt_padded::<cbc::cipher::block_padding::NoPadding>(data)
249 .map_err(|_| {
250 tracing::debug!(target: "async_snmp::crypto", "3DES decryption failed: cipher error");
251 CryptoError::CipherError
252 })?;
253 Ok(())
254}
255
256fn encrypt_aes_cfb(key: &[u8], iv: &[u8], data: &mut [u8]) -> CryptoResult<()> {
257 use aes::{Aes128, Aes192, Aes256};
258 use cfb_mode::cipher::KeyIvInit;
259
260 match key.len() {
261 16 => {
262 type Aes128Cfb = cfb_mode::Encryptor<Aes128>;
263 let cipher = Aes128Cfb::new_from_slices(key, iv).map_err(|_| {
264 tracing::debug!(target: "async_snmp::crypto", "AES-128 encryption failed: invalid key length");
265 CryptoError::InvalidKeyLength
266 })?;
267 cipher.encrypt(data);
268 }
269 24 => {
270 type Aes192Cfb = cfb_mode::Encryptor<Aes192>;
271 let cipher = Aes192Cfb::new_from_slices(key, iv).map_err(|_| {
272 tracing::debug!(target: "async_snmp::crypto", "AES-192 encryption failed: invalid key length");
273 CryptoError::InvalidKeyLength
274 })?;
275 cipher.encrypt(data);
276 }
277 32 => {
278 type Aes256Cfb = cfb_mode::Encryptor<Aes256>;
279 let cipher = Aes256Cfb::new_from_slices(key, iv).map_err(|_| {
280 tracing::debug!(target: "async_snmp::crypto", "AES-256 encryption failed: invalid key length");
281 CryptoError::InvalidKeyLength
282 })?;
283 cipher.encrypt(data);
284 }
285 key_len => {
286 tracing::debug!(target: "async_snmp::crypto", { key_len }, "AES encryption failed: unsupported key length");
287 return Err(CryptoError::InvalidKeyLength);
288 }
289 }
290 Ok(())
291}
292
293fn decrypt_aes_cfb(key: &[u8], iv: &[u8], data: &mut [u8]) -> CryptoResult<()> {
294 use aes::{Aes128, Aes192, Aes256};
295 use cfb_mode::cipher::KeyIvInit;
296
297 match key.len() {
298 16 => {
299 type Aes128Cfb = cfb_mode::Decryptor<Aes128>;
300 let cipher = Aes128Cfb::new_from_slices(key, iv).map_err(|_| {
301 tracing::debug!(target: "async_snmp::crypto", "AES-128 decryption failed: invalid key length");
302 CryptoError::InvalidKeyLength
303 })?;
304 cipher.decrypt(data);
305 }
306 24 => {
307 type Aes192Cfb = cfb_mode::Decryptor<Aes192>;
308 let cipher = Aes192Cfb::new_from_slices(key, iv).map_err(|_| {
309 tracing::debug!(target: "async_snmp::crypto", "AES-192 decryption failed: invalid key length");
310 CryptoError::InvalidKeyLength
311 })?;
312 cipher.decrypt(data);
313 }
314 32 => {
315 type Aes256Cfb = cfb_mode::Decryptor<Aes256>;
316 let cipher = Aes256Cfb::new_from_slices(key, iv).map_err(|_| {
317 tracing::debug!(target: "async_snmp::crypto", "AES-256 decryption failed: invalid key length");
318 CryptoError::InvalidKeyLength
319 })?;
320 cipher.decrypt(data);
321 }
322 key_len => {
323 tracing::debug!(target: "async_snmp::crypto", { key_len }, "AES decryption failed: unsupported key length");
324 return Err(CryptoError::InvalidKeyLength);
325 }
326 }
327 Ok(())
328}
329
330#[cfg(test)]
331mod tests {
332 use super::*;
333
334 #[test]
338 fn des_encrypt_pads_unaligned_plaintext() {
339 let provider = RustCryptoProvider;
340 let key = b"\x00\x11\x22\x33\x44\x55\x66\x77";
341 let iv = [0u8; 8];
342 let mut data = b"Hello".to_vec(); let result = provider.encrypt(PrivProtocol::Des, key, &iv, &mut data);
345 assert!(
346 result.is_ok(),
347 "DES encrypt must pad unaligned plaintext, got: {result:?}"
348 );
349 assert_eq!(data.len(), 8, "output must be padded to 8-byte boundary");
350 }
351
352 #[test]
354 fn des3_encrypt_pads_unaligned_plaintext() {
355 let provider = RustCryptoProvider;
356 let key = [0x01u8; 24];
357 let iv = [0u8; 8];
358 let mut data = b"Hello".to_vec(); let result = provider.encrypt(PrivProtocol::Des3, &key, &iv, &mut data);
361 assert!(
362 result.is_ok(),
363 "3DES encrypt must pad unaligned plaintext, got: {result:?}"
364 );
365 assert_eq!(data.len(), 8, "output must be padded to 8-byte boundary");
366 }
367
368 #[test]
370 fn des_roundtrip_unaligned() {
371 let provider = RustCryptoProvider;
372 let key = b"\x00\x11\x22\x33\x44\x55\x66\x77";
373 let iv = [0u8; 8];
374 let plaintext = b"Hello";
375 let mut data = plaintext.to_vec();
376
377 provider
378 .encrypt(PrivProtocol::Des, key, &iv, &mut data)
379 .unwrap();
380 assert_eq!(data.len(), 8);
381
382 provider
383 .decrypt(PrivProtocol::Des, key, &iv, &mut data)
384 .unwrap();
385 assert_eq!(&data[..plaintext.len()], plaintext);
386 }
387
388 #[test]
390 fn des_encrypt_aligned_unchanged() {
391 let provider = RustCryptoProvider;
392 let key = b"\x00\x11\x22\x33\x44\x55\x66\x77";
393 let iv = [0u8; 8];
394 let mut data = vec![0x41u8; 8]; let result = provider.encrypt(PrivProtocol::Des, key, &iv, &mut data);
397 assert!(result.is_ok());
398 assert_eq!(data.len(), 8);
399 }
400}