Skip to main content

snmp2/
v3.rs

1use std::{fmt, time::Instant};
2
3#[cfg(feature = "crypto-openssl")]
4use openssl::{
5    hash::{Hasher, MessageDigest},
6    pkey::PKey,
7    sign::Signer,
8};
9
10#[cfg(all(feature = "crypto-rust", not(feature = "crypto-openssl")))]
11use {
12    aes::{Aes128, Aes192, Aes256},
13    cbc::{Decryptor as CbcDecryptor, Encryptor as CbcEncryptor},
14    cfb_mode::{Decryptor as CfbDecryptor, Encryptor as CfbEncryptor},
15    cipher::{AsyncStreamCipher, BlockDecryptMut, BlockEncryptMut, KeyIvInit},
16    des::Des,
17    digest::{Digest, DynDigest},
18    hmac::{Hmac, Mac},
19};
20
21use crate::{
22    AsnReader, BUFFER_SIZE, Error, MessageType, Oid, Pdu, Result, Value, Varbinds, Version, asn1,
23    pdu::{self, Buf},
24    snmp::{self, V3_MSG_FLAGS_AUTH, V3_MSG_FLAGS_PRIVACY, V3_MSG_FLAGS_REPORTABLE},
25};
26
27const ENGINE_TIME_WINDOW: i64 = 150;
28
29#[cfg(feature = "v3")]
30#[derive(Debug, PartialEq, Eq, Clone)]
31pub enum AuthErrorKind {
32    UnsupportedUSM,
33    EngineBootsMismatch,
34    EngineBootsNotProvided,
35    EngineTimeMismatch,
36    NotAuthenticated,
37    UsernameMismatch,
38    EngineIdMismatch,
39    SignatureMismatch,
40    MessageIdMismatch,
41    PrivLengthMismatch,
42    KeyLengthMismatch,
43    PayloadLengthMismatch,
44    ReplyNotEncrypted,
45    SecurityNotProvided,
46    SecurityNotReady,
47    KeyExtensionRequired,
48}
49
50impl fmt::Display for AuthErrorKind {
51    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52        match self {
53            AuthErrorKind::UnsupportedUSM => write!(f, "Unsupported USM"),
54            AuthErrorKind::EngineBootsMismatch => write!(f, "Engine boots counter mismatch"),
55            AuthErrorKind::EngineTimeMismatch => write!(f, "Engine time counter mismatch"),
56            AuthErrorKind::NotAuthenticated => write!(f, "Not authenticated"),
57            AuthErrorKind::EngineBootsNotProvided => write!(f, "Engine boots counter not provided"),
58            AuthErrorKind::EngineIdMismatch => write!(f, "Engine ID mismatch"),
59            AuthErrorKind::UsernameMismatch => write!(f, "Username mismatch"),
60            AuthErrorKind::SignatureMismatch => write!(f, "HMAC signature mismatch"),
61            AuthErrorKind::MessageIdMismatch => write!(f, "Message ID mismatch"),
62            AuthErrorKind::PrivLengthMismatch => write!(f, "Privacy parameters length mismatch"),
63            AuthErrorKind::KeyLengthMismatch => write!(f, "Key length mismatch"),
64            AuthErrorKind::PayloadLengthMismatch => write!(f, "Payload length mismatch"),
65            AuthErrorKind::ReplyNotEncrypted => write!(f, "Not an encrypted reply"),
66            AuthErrorKind::SecurityNotProvided => write!(f, "Security parameters not provided"),
67            AuthErrorKind::SecurityNotReady => write!(f, "Security parameters not ready"),
68            AuthErrorKind::KeyExtensionRequired => {
69                write!(f, "Auth/Priv pair needs a key extension method")
70            }
71        }
72    }
73}
74
75#[derive(Debug, Clone)]
76pub(crate) struct AuthoritativeState {
77    auth_key: Vec<u8>,
78    priv_key: Vec<u8>,
79    pub(crate) engine_id: Vec<u8>,
80    engine_boots: i64,
81    engine_time: i64,
82    engine_time_current: i64,
83    start_time: Instant,
84}
85
86impl Default for AuthoritativeState {
87    fn default() -> Self {
88        Self {
89            auth_key: Vec::new(),
90            priv_key: Vec::new(),
91            engine_id: Vec::new(),
92            engine_boots: 0,
93            engine_time: 0,
94            engine_time_current: 0,
95            start_time: Instant::now(),
96        }
97    }
98}
99
100impl AuthoritativeState {
101    fn update_authoritative(&mut self, engine_boots: i64, engine_time: i64) {
102        self.engine_boots = engine_boots;
103        self.engine_time = engine_time;
104        self.start_time = Instant::now();
105    }
106
107    fn update_authoritative_engine_time(&mut self, engine_time: i64) {
108        self.engine_time = engine_time;
109        self.start_time = Instant::now();
110    }
111
112    fn correct_engine_time(&mut self) {
113        if self.engine_boots == 0 {
114            self.engine_time_current = 0;
115            return;
116        }
117        let max = i32::MAX.into();
118        self.engine_time_current =
119            i64::try_from(self.start_time.elapsed().as_secs()).unwrap() + self.engine_time;
120        if self.engine_time_current >= max {
121            self.engine_time_current -= max;
122            self.engine_boots += 1;
123        }
124    }
125
126    fn generate_key(&self, password: &[u8], auth_protocol: AuthProtocol) -> Result<Vec<u8>> {
127        let mut hasher = auth_protocol.create_hasher()?;
128        let mut password_index = 0;
129        let mut password_buf = vec![0; 64];
130        for _ in 0..16384 {
131            for x in &mut password_buf {
132                *x = password[password_index];
133                password_index += 1;
134                if password_index == password.len() {
135                    password_index = 0;
136                }
137            }
138            hasher.update(&password_buf)?;
139        }
140        let key = hasher.finish()?;
141        password_buf.clear();
142        password_buf.extend_from_slice(&key);
143        password_buf.extend_from_slice(&self.engine_id);
144        password_buf.extend_from_slice(&key);
145        hasher.update(&password_buf)?;
146        #[allow(clippy::implicit_clone)]
147        Ok(hasher.finish()?.to_vec())
148    }
149
150    fn update_auth_key(
151        &mut self,
152        authentication_password: &[u8],
153        auth_protocol: AuthProtocol,
154    ) -> Result<()> {
155        if self.engine_id.is_empty() {
156            self.auth_key.clear();
157            return Err(Error::AuthFailure(AuthErrorKind::NotAuthenticated));
158        }
159        self.auth_key = self.generate_key(authentication_password, auth_protocol)?;
160        Ok(())
161    }
162
163    fn update_priv_key(
164        &mut self,
165        privacy_password: &[u8],
166        auth_protocol: AuthProtocol,
167        cipher: Cipher,
168        extension_method: Option<&KeyExtension>,
169    ) -> Result<()> {
170        if self.engine_id.is_empty() {
171            self.priv_key.clear();
172            return Err(Error::AuthFailure(AuthErrorKind::NotAuthenticated));
173        }
174        self.priv_key = self.generate_key(privacy_password, auth_protocol)?;
175        if !cipher.priv_key_needs_extension(&auth_protocol) {
176            return Ok(());
177        }
178        match extension_method.as_ref() {
179            Some(KeyExtension::Blumenthal) => {
180                self.extend_priv_key_with_blumenthal_method(cipher.priv_key_len(), auth_protocol)?;
181            }
182            Some(KeyExtension::Reeder) => {
183                self.extend_priv_key_with_reeder_method(cipher.priv_key_len(), auth_protocol)?;
184            }
185            None => return Err(Error::AuthFailure(AuthErrorKind::KeyExtensionRequired)),
186        }
187        Ok(())
188    }
189
190    /// Extend `priv_key` to the required length using the Blumenthal algorithm.
191    /// This is used for AES-192/256 privacy keys when Kul is shorter than needed.
192    fn extend_priv_key_with_blumenthal_method(
193        &mut self,
194        need_key_len: usize,
195        auth_protocol: AuthProtocol,
196    ) -> Result<()> {
197        if need_key_len <= self.priv_key.len() {
198            return Ok(());
199        }
200
201        let mut remaining = need_key_len - self.priv_key.len();
202
203        while remaining > 0 {
204            // Hash the current priv_key using the auth protocol’s hash function
205            let mut hasher = auth_protocol.create_hasher()?;
206            hasher.update(&self.priv_key)?;
207            let new_hash = hasher.finish()?; // full digest
208
209            // Append as much as needed
210            let copy_len = remaining.min(new_hash.len());
211            self.priv_key.extend_from_slice(&new_hash[..copy_len]);
212            remaining -= copy_len;
213        }
214
215        Ok(())
216    }
217    /// Extend Kul to the required length using the Reeder method.
218    /// `need_key_len` is the desired privacy key length (e.g. 24 for AES-192, 32 for AES-256).
219    fn extend_priv_key_with_reeder_method(
220        &mut self,
221        need_key_len: usize,
222        auth_protocol: AuthProtocol,
223    ) -> Result<()> {
224        if need_key_len < self.priv_key.len() {
225            return Ok(());
226        }
227        let mut remaining = need_key_len - self.priv_key.len();
228        while remaining > 0 {
229            // Step 1: Ku' = Ku(origKul)
230            // Here we treat the current Kul as the "password"
231            let new_kul = self.generate_key(&self.priv_key, auth_protocol)?;
232
233            // Step 2: append Kul' to the existing Kul
234            let copy_len = remaining.min(new_kul.len());
235            self.priv_key.extend_from_slice(&new_kul[..copy_len]);
236            remaining -= copy_len;
237        }
238        Ok(())
239    }
240}
241
242#[derive(Debug, Copy, Clone, Eq, PartialEq)]
243pub enum KeyExtension {
244    Blumenthal,
245    Reeder,
246}
247
248impl KeyExtension {
249    pub fn other(&self) -> Self {
250        match self {
251            KeyExtension::Blumenthal => KeyExtension::Reeder,
252            KeyExtension::Reeder => KeyExtension::Blumenthal,
253        }
254    }
255}
256
257#[derive(Debug, Clone)]
258pub struct Security {
259    pub(crate) username: Vec<u8>,
260    pub(crate) authentication_password: Vec<u8>,
261    pub(crate) auth: Auth,
262    pub(crate) auth_protocol: AuthProtocol,
263    pub(crate) key_extension_method: Option<KeyExtension>,
264    pub(crate) authoritative_state: AuthoritativeState,
265    pub(crate) plain_buf: Vec<u8>,
266}
267
268impl Security {
269    pub fn new(username: &[u8], authentication_password: &[u8]) -> Self {
270        Self {
271            username: username.to_vec(),
272            authentication_password: authentication_password.to_vec(),
273            auth: Auth::AuthNoPriv,
274            auth_protocol: AuthProtocol::Md5,
275            key_extension_method: None,
276            authoritative_state: AuthoritativeState::default(),
277            plain_buf: Vec::new(),
278        }
279    }
280
281    pub fn with_auth(mut self, auth: Auth) -> Self {
282        self.auth = auth;
283        self
284    }
285
286    pub fn with_auth_protocol(mut self, auth_protocol: AuthProtocol) -> Self {
287        self.auth_protocol = auth_protocol;
288        self
289    }
290
291    pub fn with_key_extension_method(mut self, key_extension_method: KeyExtension) -> Self {
292        self.key_extension_method = Some(key_extension_method);
293        self
294    }
295
296    pub(crate) fn another_key_extension_method(&mut self) -> Option<KeyExtension> {
297        if let Auth::AuthPriv { ref cipher, .. } = self.auth
298            && cipher.priv_key_needs_extension(&self.auth_protocol)
299            && let Some(used_method) = self.key_extension_method
300        {
301            self.key_extension_method = Some(used_method.other());
302            return self.key_extension_method;
303        }
304        None
305    }
306
307    /// Note: the engine_id MUST be provided as a hex array, not as a byte-string.
308    /// E.g. if a target has got an engine id `80003a8c04` set, it should be provided as `&[0x80,
309    /// 0x00, 0x3a, 0x8c, 0x04]`
310    pub fn with_engine_id(mut self, engine_id: &[u8]) -> Result<Self> {
311        self.authoritative_state.engine_id = engine_id.to_vec();
312        self.update_key()?;
313        Ok(self)
314    }
315
316    pub fn with_engine_boots_and_time(mut self, engine_boots: i64, engine_time: i64) -> Self {
317        self.authoritative_state.engine_boots = engine_boots;
318        self.authoritative_state
319            .update_authoritative_engine_time(engine_time);
320        self
321    }
322
323    pub fn reset_engine_id(&mut self) {
324        self.authoritative_state.engine_id.clear();
325        self.authoritative_state.auth_key.clear();
326        self.authoritative_state.priv_key.clear();
327    }
328
329    pub fn reset_engine_counters(&mut self) {
330        self.authoritative_state.engine_boots = 0;
331        self.authoritative_state.update_authoritative_engine_time(0);
332    }
333
334    fn calculate_hmac(&self, data: &[u8]) -> Result<Vec<u8>> {
335        if self.engine_id().is_empty() {
336            return Err(Error::AuthFailure(AuthErrorKind::SecurityNotReady));
337        }
338        #[cfg(feature = "crypto-openssl")]
339        {
340            let pkey = PKey::hmac(&self.authoritative_state.auth_key)?;
341            let mut signer = Signer::new(self.auth_protocol.digest(), &pkey)?;
342            signer.update(data)?;
343            signer.sign_to_vec().map_err(Error::from)
344        }
345        #[cfg(all(feature = "crypto-rust", not(feature = "crypto-openssl")))]
346        {
347            let key = &self.authoritative_state.auth_key;
348            match self.auth_protocol {
349                AuthProtocol::Md5 => {
350                    let mut mac = Hmac::<md5::Md5>::new_from_slice(key)
351                        .map_err(|_| Error::Crypto("Invalid key length".into()))?;
352                    mac.update(data);
353                    Ok(mac.finalize().into_bytes().to_vec())
354                }
355                AuthProtocol::Sha1 => {
356                    let mut mac = Hmac::<sha1::Sha1>::new_from_slice(key)
357                        .map_err(|_| Error::Crypto("Invalid key length".into()))?;
358                    mac.update(data);
359                    Ok(mac.finalize().into_bytes().to_vec())
360                }
361                AuthProtocol::Sha224 => {
362                    let mut mac = Hmac::<sha2::Sha224>::new_from_slice(key)
363                        .map_err(|_| Error::Crypto("Invalid key length".into()))?;
364                    mac.update(data);
365                    Ok(mac.finalize().into_bytes().to_vec())
366                }
367                AuthProtocol::Sha256 => {
368                    let mut mac = Hmac::<sha2::Sha256>::new_from_slice(key)
369                        .map_err(|_| Error::Crypto("Invalid key length".into()))?;
370                    mac.update(data);
371                    Ok(mac.finalize().into_bytes().to_vec())
372                }
373                AuthProtocol::Sha384 => {
374                    let mut mac = Hmac::<sha2::Sha384>::new_from_slice(key)
375                        .map_err(|_| Error::Crypto("Invalid key length".into()))?;
376                    mac.update(data);
377                    Ok(mac.finalize().into_bytes().to_vec())
378                }
379                AuthProtocol::Sha512 => {
380                    let mut mac = Hmac::<sha2::Sha512>::new_from_slice(key)
381                        .map_err(|_| Error::Crypto("Invalid key length".into()))?;
382                    mac.update(data);
383                    Ok(mac.finalize().into_bytes().to_vec())
384                }
385            }
386        }
387    }
388
389    pub(crate) fn update_key(&mut self) -> Result<()> {
390        if !self.need_auth() {
391            return Ok(());
392        }
393
394        self.authoritative_state
395            .update_auth_key(&self.authentication_password, self.auth_protocol)?;
396        if let Auth::AuthPriv {
397            cipher,
398            privacy_password,
399        } = &self.auth
400        {
401            self.authoritative_state.update_priv_key(
402                privacy_password,
403                self.auth_protocol,
404                *cipher,
405                self.key_extension_method.as_ref(),
406            )?;
407        }
408        Ok(())
409    }
410
411    pub fn engine_id(&self) -> &[u8] {
412        &self.authoritative_state.engine_id
413    }
414
415    pub fn engine_boots(&self) -> i64 {
416        self.authoritative_state.engine_boots
417    }
418
419    pub fn engine_time(&self) -> i64 {
420        self.authoritative_state.engine_time
421    }
422
423    pub fn username(&self) -> &[u8] {
424        &self.username
425    }
426
427    /// corrects authoritative state engine time using local monotonic time
428    pub(crate) fn correct_authoritative_engine_time(&mut self) {
429        self.authoritative_state.correct_engine_time();
430    }
431
432    pub(crate) fn need_auth(&self) -> bool {
433        self.auth != Auth::NoAuthNoPriv
434    }
435
436    pub(crate) fn need_encrypt(&self) -> bool {
437        !self.authoritative_state.priv_key.is_empty()
438    }
439
440    pub(crate) fn need_init(&self) -> bool {
441        self.engine_id().is_empty()
442    }
443
444    fn encrypt_des(&self, data: &[u8]) -> Result<(Vec<u8>, Vec<u8>)> {
445        let mut salt = [0; 8];
446        salt[..4].copy_from_slice(&u32::try_from(self.engine_boots())?.to_be_bytes());
447        #[cfg(feature = "crypto-openssl")]
448        openssl::rand::rand_bytes(&mut salt[4..])?;
449        #[cfg(all(feature = "crypto-rust", not(feature = "crypto-openssl")))]
450        {
451            use rand::Rng;
452            rand::rng().fill(&mut salt[4..]);
453        }
454
455        if data.is_empty() {
456            return Ok((vec![], salt.to_vec()));
457        }
458
459        if self.authoritative_state.priv_key.len() < 16 {
460            return Err(Error::AuthFailure(AuthErrorKind::KeyLengthMismatch));
461        }
462
463        let des_key = &self.authoritative_state.priv_key[..8];
464        let pre_iv = &self.authoritative_state.priv_key[8..16];
465
466        let mut iv = [0; 8];
467        for (i, (a, b)) in pre_iv.iter().zip(salt.iter()).enumerate() {
468            iv[i] = a ^ b;
469        }
470
471        #[cfg(feature = "crypto-openssl")]
472        {
473            let cipher = openssl::symm::Cipher::des_cbc();
474            let mut encrypted = vec![0; data.len() + cipher.block_size()];
475            let mut crypter = openssl::symm::Crypter::new(
476                cipher,
477                openssl::symm::Mode::Encrypt,
478                des_key,
479                Some(&iv),
480            )?;
481            let mut count = crypter.update(data, &mut encrypted)?;
482
483            if count < encrypted.len() {
484                count += crypter.finalize(&mut encrypted[count..])?;
485            }
486
487            encrypted.truncate(count);
488            Ok((encrypted, salt.to_vec()))
489        }
490        #[cfg(all(feature = "crypto-rust", not(feature = "crypto-openssl")))]
491        {
492            let mut encryptor = CbcEncryptor::<Des>::new_from_slices(des_key, &iv)
493                .map_err(|e| Error::Crypto(e.to_string()))?;
494
495            // PKCS#7 padding
496            let len = data.len();
497            let pad_len = 8 - (len % 8);
498            let mut padded = Vec::with_capacity(len + pad_len);
499            padded.extend_from_slice(data);
500            padded.extend(std::iter::repeat_n(u8::try_from(pad_len)?, pad_len));
501
502            for chunk in padded.chunks_mut(8) {
503                let block = cipher::generic_array::GenericArray::from_mut_slice(chunk);
504                encryptor.encrypt_block_mut(block);
505            }
506
507            Ok((padded, salt.to_vec()))
508        }
509    }
510
511    fn encrypt_aes(&self, data: &[u8], key_len: usize) -> Result<(Vec<u8>, Vec<u8>)> {
512        let iv_len = 16;
513        let mut iv = Vec::with_capacity(iv_len);
514        iv.extend_from_slice(&u32::try_from(self.engine_boots())?.to_be_bytes());
515        iv.extend_from_slice(&u32::try_from(self.engine_time())?.to_be_bytes());
516        let salt_pos = iv.len();
517        iv.resize(iv_len, 0);
518
519        #[cfg(feature = "crypto-openssl")]
520        openssl::rand::rand_bytes(&mut iv[salt_pos..])?;
521        #[cfg(all(feature = "crypto-rust", not(feature = "crypto-openssl")))]
522        {
523            use rand::Rng;
524            rand::rng().fill(&mut iv[salt_pos..]);
525        }
526
527        if self.authoritative_state.priv_key.len() < key_len {
528            return Err(Error::AuthFailure(AuthErrorKind::KeyLengthMismatch));
529        }
530
531        #[cfg(feature = "crypto-openssl")]
532        {
533            let cipher = match key_len {
534                16 => openssl::symm::Cipher::aes_128_cfb128(),
535                24 => openssl::symm::Cipher::aes_192_cfb128(),
536                32 => openssl::symm::Cipher::aes_256_cfb128(),
537                _ => return Err(Error::Crypto("Invalid key len".into())),
538            };
539
540            let mut crypter = openssl::symm::Crypter::new(
541                cipher,
542                openssl::symm::Mode::Encrypt,
543                &self.authoritative_state.priv_key[..key_len],
544                Some(&iv),
545            )?;
546
547            let mut encrypted = vec![0; data.len() + 16];
548            let mut count = crypter.update(data, &mut encrypted)?;
549
550            if count < encrypted.len() {
551                count += crypter.finalize(&mut encrypted[count..])?;
552            }
553
554            encrypted.truncate(count);
555            Ok((encrypted, iv[salt_pos..].to_vec()))
556        }
557        #[cfg(all(feature = "crypto-rust", not(feature = "crypto-openssl")))]
558        {
559            let key = &self.authoritative_state.priv_key[..key_len];
560            let mut encrypted = data.to_vec();
561
562            match key_len {
563                16 => {
564                    let encryptor = CfbEncryptor::<Aes128>::new_from_slices(key, &iv)
565                        .map_err(|e| Error::Crypto(e.to_string()))?;
566                    encryptor.encrypt(&mut encrypted);
567                }
568                24 => {
569                    let encryptor = CfbEncryptor::<Aes192>::new_from_slices(key, &iv)
570                        .map_err(|e| Error::Crypto(e.to_string()))?;
571                    encryptor.encrypt(&mut encrypted);
572                }
573                32 => {
574                    let encryptor = CfbEncryptor::<Aes256>::new_from_slices(key, &iv)
575                        .map_err(|e| Error::Crypto(e.to_string()))?;
576                    encryptor.encrypt(&mut encrypted);
577                }
578                _ => return Err(Error::Crypto("Invalid key len".into())),
579            }
580
581            Ok((encrypted, iv[salt_pos..].to_vec()))
582        }
583    }
584
585    /// encrypts the data
586    pub(crate) fn encrypt(&self, data: &[u8]) -> Result<(Vec<u8>, Vec<u8>)> {
587        let Auth::AuthPriv {
588            cipher: cipher_kind,
589            ..
590        } = &self.auth
591        else {
592            return Err(Error::AuthFailure(AuthErrorKind::SecurityNotProvided));
593        };
594
595        if self.engine_id().is_empty() {
596            return Err(Error::AuthFailure(AuthErrorKind::SecurityNotReady));
597        }
598
599        match cipher_kind {
600            Cipher::Des => self.encrypt_des(data),
601            Cipher::Aes128 => self.encrypt_aes(data, 16),
602            Cipher::Aes192 => self.encrypt_aes(data, 24),
603            Cipher::Aes256 => self.encrypt_aes(data, 32),
604        }
605    }
606
607    #[cfg(feature = "crypto-openssl")]
608    fn decrypt_data_to_plain_buf(
609        &mut self,
610        mut crypter: openssl::symm::Crypter,
611        block_size: usize,
612        encrypted: &[u8],
613    ) -> Result<()> {
614        self.plain_buf.resize(encrypted.len() + block_size, 0);
615        let mut count = crypter.update(encrypted, &mut self.plain_buf)?;
616
617        if count < self.plain_buf.len() {
618            count += crypter.finalize(&mut self.plain_buf[count..])?;
619        }
620
621        self.plain_buf.truncate(count);
622        Ok(())
623    }
624
625    fn decrypt_des(&mut self, encrypted: &[u8], priv_params: &[u8]) -> Result<()> {
626        if priv_params.len() != 8 {
627            return Err(Error::AuthFailure(AuthErrorKind::PrivLengthMismatch));
628        }
629
630        if self.authoritative_state.priv_key.len() < 16 {
631            return Err(Error::AuthFailure(AuthErrorKind::KeyLengthMismatch));
632        }
633
634        let des_key = &self.authoritative_state.priv_key[..8];
635        let pre_iv = &self.authoritative_state.priv_key[8..16];
636        let mut iv = [0; 8];
637
638        for (i, (a, b)) in pre_iv.iter().zip(priv_params.iter()).enumerate() {
639            iv[i] = a ^ b;
640        }
641
642        #[cfg(feature = "crypto-openssl")]
643        {
644            let cipher = openssl::symm::Cipher::des_cbc();
645            let block_size = 8;
646
647            if !encrypted.len().is_multiple_of(block_size) {
648                return Err(Error::AuthFailure(AuthErrorKind::PayloadLengthMismatch));
649            }
650
651            let crypter = openssl::symm::Crypter::new(
652                cipher,
653                openssl::symm::Mode::Decrypt,
654                des_key,
655                Some(&iv),
656            )?;
657            self.decrypt_data_to_plain_buf(crypter, block_size, encrypted)
658        }
659        #[cfg(all(feature = "crypto-rust", not(feature = "crypto-openssl")))]
660        {
661            let block_size = 8;
662            if !encrypted.len().is_multiple_of(block_size) {
663                return Err(Error::AuthFailure(AuthErrorKind::PayloadLengthMismatch));
664            }
665
666            let mut decryptor = CbcDecryptor::<Des>::new_from_slices(des_key, &iv)
667                .map_err(|e| Error::Crypto(e.to_string()))?;
668
669            self.plain_buf.clear();
670            self.plain_buf.extend_from_slice(encrypted);
671
672            for chunk in self.plain_buf.chunks_mut(8) {
673                let block = cipher::generic_array::GenericArray::from_mut_slice(chunk);
674                decryptor.decrypt_block_mut(block);
675            }
676
677            if let Some(&pad_len) = self.plain_buf.last() {
678                let pad_len = pad_len as usize;
679                if pad_len > 0 && pad_len <= block_size && pad_len <= self.plain_buf.len() {
680                    let new_len = self.plain_buf.len() - pad_len;
681                    self.plain_buf.truncate(new_len);
682                    Ok(())
683                } else {
684                    Err(Error::Crypto("Invalid padding".into()))
685                }
686            } else {
687                Ok(())
688            }
689        }
690    }
691
692    fn decrypt_aes(&mut self, encrypted: &[u8], priv_params: &[u8], key_len: usize) -> Result<()> {
693        let iv_len = 16;
694        let mut iv = Vec::with_capacity(iv_len);
695        iv.extend_from_slice(&u32::try_from(self.engine_boots())?.to_be_bytes());
696        iv.extend_from_slice(&u32::try_from(self.engine_time())?.to_be_bytes());
697        iv.extend_from_slice(priv_params);
698
699        if iv.len() != iv_len {
700            return Err(Error::AuthFailure(AuthErrorKind::PrivLengthMismatch));
701        }
702
703        if self.authoritative_state.priv_key.len() < key_len {
704            return Err(Error::AuthFailure(AuthErrorKind::KeyLengthMismatch));
705        }
706
707        #[cfg(feature = "crypto-openssl")]
708        {
709            let cipher = match key_len {
710                16 => openssl::symm::Cipher::aes_128_cfb128(),
711                24 => openssl::symm::Cipher::aes_192_cfb128(),
712                32 => openssl::symm::Cipher::aes_256_cfb128(),
713                _ => return Err(Error::Crypto("Invalid key len".into())),
714            };
715
716            let crypter = openssl::symm::Crypter::new(
717                cipher,
718                openssl::symm::Mode::Decrypt,
719                &self.authoritative_state.priv_key[..key_len],
720                Some(&iv),
721            )?;
722
723            self.decrypt_data_to_plain_buf(crypter, 16, encrypted)
724        }
725        #[cfg(all(feature = "crypto-rust", not(feature = "crypto-openssl")))]
726        {
727            let key = &self.authoritative_state.priv_key[..key_len];
728            self.plain_buf.clear();
729            self.plain_buf.extend_from_slice(encrypted);
730
731            match key_len {
732                16 => {
733                    let decryptor = CfbDecryptor::<Aes128>::new_from_slices(key, &iv)
734                        .map_err(|e| Error::Crypto(e.to_string()))?;
735                    decryptor.decrypt(&mut self.plain_buf);
736                }
737                24 => {
738                    let decryptor = CfbDecryptor::<Aes192>::new_from_slices(key, &iv)
739                        .map_err(|e| Error::Crypto(e.to_string()))?;
740                    decryptor.decrypt(&mut self.plain_buf);
741                }
742                32 => {
743                    let decryptor = CfbDecryptor::<Aes256>::new_from_slices(key, &iv)
744                        .map_err(|e| Error::Crypto(e.to_string()))?;
745                    decryptor.decrypt(&mut self.plain_buf);
746                }
747                _ => return Err(Error::Crypto("Invalid key len".into())),
748            }
749            Ok(())
750        }
751    }
752
753    /// decrypts the data, the result is stored in `self.plain_buf`
754    fn decrypt(&mut self, encrypted: &[u8], priv_params: &[u8]) -> Result<()> {
755        let Auth::AuthPriv {
756            cipher: cipher_kind,
757            ..
758        } = &self.auth
759        else {
760            return Err(Error::AuthFailure(AuthErrorKind::SecurityNotProvided));
761        };
762
763        match cipher_kind {
764            Cipher::Des => self.decrypt_des(encrypted, priv_params),
765            Cipher::Aes128 => self.decrypt_aes(encrypted, priv_params, 16),
766            Cipher::Aes192 => self.decrypt_aes(encrypted, priv_params, 24),
767            Cipher::Aes256 => self.decrypt_aes(encrypted, priv_params, 32),
768        }
769    }
770}
771
772#[derive(Debug, Clone, Eq, PartialEq)]
773pub enum Auth {
774    NoAuthNoPriv,
775    /// Authentication
776    AuthNoPriv,
777    /// Authentication and encryption
778    AuthPriv {
779        cipher: Cipher,
780        privacy_password: Vec<u8>,
781    },
782}
783
784#[cfg(all(feature = "crypto-rust", not(feature = "crypto-openssl")))]
785pub struct Hasher(Box<dyn DynDigest + Send + Sync>);
786
787#[cfg(all(feature = "crypto-rust", not(feature = "crypto-openssl")))]
788impl Hasher {
789    pub fn new(d: Box<dyn DynDigest + Send + Sync>) -> Result<Self> {
790        Ok(Self(d))
791    }
792
793    pub fn update(&mut self, data: &[u8]) -> Result<()> {
794        self.0.update(data);
795        Ok(())
796    }
797
798    pub fn finish(&mut self) -> Result<Vec<u8>> {
799        Ok(self.0.finalize_reset().to_vec())
800    }
801}
802
803#[derive(Debug, Copy, Clone, Eq, PartialEq)]
804pub enum AuthProtocol {
805    Md5,
806    Sha1,
807    Sha224,
808    Sha256,
809    Sha384,
810    Sha512,
811}
812
813impl AuthProtocol {
814    fn create_hasher(self) -> Result<Hasher> {
815        #[cfg(feature = "crypto-openssl")]
816        {
817            Hasher::new(self.digest()).map_err(Into::into)
818        }
819        #[cfg(all(feature = "crypto-rust", not(feature = "crypto-openssl")))]
820        {
821            let d: Box<dyn DynDigest + Send + Sync> = match self {
822                AuthProtocol::Md5 => Box::new(md5::Md5::new()),
823                AuthProtocol::Sha1 => Box::new(sha1::Sha1::new()),
824                AuthProtocol::Sha224 => Box::new(sha2::Sha224::new()),
825                AuthProtocol::Sha256 => Box::new(sha2::Sha256::new()),
826                AuthProtocol::Sha384 => Box::new(sha2::Sha384::new()),
827                AuthProtocol::Sha512 => Box::new(sha2::Sha512::new()),
828            };
829            Hasher::new(d)
830        }
831    }
832    #[cfg(feature = "crypto-openssl")]
833    fn digest(self) -> MessageDigest {
834        match self {
835            AuthProtocol::Md5 => MessageDigest::md5(),
836            AuthProtocol::Sha1 => MessageDigest::sha1(),
837            AuthProtocol::Sha224 => MessageDigest::sha224(),
838            AuthProtocol::Sha256 => MessageDigest::sha256(),
839            AuthProtocol::Sha384 => MessageDigest::sha384(),
840            AuthProtocol::Sha512 => MessageDigest::sha512(),
841        }
842    }
843
844    fn truncation_length(self) -> usize {
845        match self {
846            AuthProtocol::Md5 | AuthProtocol::Sha1 => 12,
847            AuthProtocol::Sha224 => 16,
848            AuthProtocol::Sha256 => 24,
849            AuthProtocol::Sha384 => 32,
850            AuthProtocol::Sha512 => 48,
851        }
852    }
853}
854
855#[derive(Debug, Copy, Clone, Eq, PartialEq)]
856pub enum Cipher {
857    Des,
858    Aes128,
859    Aes192,
860    Aes256,
861}
862
863impl Cipher {
864    pub fn priv_key_len(&self) -> usize {
865        match self {
866            Cipher::Des | Cipher::Aes128 => 16,
867            Cipher::Aes192 => 24,
868            Cipher::Aes256 => 32,
869        }
870    }
871
872    /// Tells if for given auth_protocol and cipher pair, the priv_key is too short and need to be extended.
873    ///
874    /// The are 5 Auth-Priv pairs, where Auth hasher generates too short input for Priv:
875    /// Auth Kul length vs required Priv key length table:
876    /// Auth Algorithm   Kul Len   DES (16)   AES-128 (16)   AES-192 (24)   AES-256 (32)
877    /// -------------------------------------------------------------------------------
878    /// MD5              16        Enough     Enough         EXTEND         EXTEND
879    /// SHA-1            20        Enough     Enough         EXTEND         EXTEND
880    /// SHA-224          28        Enough     Enough         Enough         EXTEND
881    /// SHA-256          32        Enough     Enough         Enough         Enough
882    /// SHA-384          48        Enough     Enough         Enough         Enough
883    /// SHA-512          64        Enough     Enough         Enough         Enough
884    pub fn priv_key_needs_extension(&self, auth_protocol: &AuthProtocol) -> bool {
885        #[allow(clippy::match_like_matches_macro, clippy::unnested_or_patterns)]
886        match (auth_protocol, self) {
887            (AuthProtocol::Md5, Cipher::Aes192)
888            | (AuthProtocol::Md5, Cipher::Aes256)
889            | (AuthProtocol::Sha1, Cipher::Aes192)
890            | (AuthProtocol::Sha1, Cipher::Aes256)
891            | (AuthProtocol::Sha224, Cipher::Aes256) => true,
892            _ => false,
893        }
894    }
895}
896
897impl<'a> Pdu<'a> {
898    #[allow(clippy::too_many_lines)]
899    pub(crate) fn parse_v3(
900        bytes: &'a [u8],
901        mut rdr: AsnReader<'a>,
902        security: &'a mut Security,
903    ) -> Result<Pdu<'a>> {
904        let truncation_len = security.auth_protocol.truncation_length();
905        let global_data_seq = rdr.read_raw(asn1::TYPE_SEQUENCE)?;
906        let mut global_data_rdr = AsnReader::from_bytes(global_data_seq);
907        let msg_id = global_data_rdr.read_asn_integer()?;
908        let max_size = global_data_rdr.read_asn_integer()?;
909
910        if max_size < 0 || max_size > i64::try_from(BUFFER_SIZE).unwrap() {
911            return Err(Error::BufferOverflow);
912        }
913
914        let flags = global_data_rdr
915            .read_asn_octetstring()?
916            .first()
917            .copied()
918            .unwrap_or_default();
919
920        let security_model = global_data_rdr.read_asn_integer()?;
921        if security_model != 3 {
922            return Err(Error::AuthFailure(AuthErrorKind::UnsupportedUSM));
923        }
924
925        let security_params = rdr.read_asn_octetstring()?;
926        let security_seq = AsnReader::from_bytes(security_params).read_raw(asn1::TYPE_SEQUENCE)?;
927        let mut security_rdr = AsnReader::from_bytes(security_seq);
928        let engine_id = security_rdr.read_asn_octetstring()?;
929        let engine_boots = security_rdr.read_asn_integer()?;
930        let engine_time = security_rdr.read_asn_integer()?;
931
932        let username = security_rdr.read_asn_octetstring()?;
933        let auth_params = security_rdr.read_asn_octetstring().map(<[u8]>::to_vec)?;
934        let auth_params_pos =
935            bytes.len() - rdr.bytes_left() - auth_params.len() - security_rdr.bytes_left();
936        let priv_params = security_rdr.read_asn_octetstring()?;
937
938        // Discovery process requires two steps, each involving a separate request and response pair:
939        // - Authoritive engine ID acknowledgement
940        //   We expect a non-authenticated and not encrypted response with engine ID
941        // - Authoritive engine time synchronization
942        //   We expect an authenticated and not encrypted response with engine time and boots
943        //
944        // Only first step is done when using NoAuthNoPriv security level.
945        //
946        // See RFC3414 section 4 and section 3.2.7.a
947        let mut is_discovery = false;
948
949        let mut prev_engine_time = security.engine_time();
950
951        if flags & V3_MSG_FLAGS_AUTH == 0 {
952            // Unauthenticated REPORT (discovery step)
953            // Update engine_id if unknown
954            if security.authoritative_state.engine_id.is_empty() {
955                security.authoritative_state.engine_id = engine_id.to_vec();
956                security.update_key()?;
957                is_discovery = true;
958            } else if engine_id != security.authoritative_state.engine_id && !engine_id.is_empty() {
959                // If agent reports a different engineID, that’s a mismatch
960                return Err(Error::AuthFailure(AuthErrorKind::EngineIdMismatch));
961            }
962
963            // Many agents include boots/time in the first REPORT.
964            // If provided (non-zero), update authoritative state here.
965            if security.authoritative_state.engine_boots < engine_boots {
966                is_discovery = true;
967                prev_engine_time = engine_time;
968                security
969                    .authoritative_state
970                    .update_authoritative(engine_boots, engine_time);
971            }
972
973            // When in discovery and we updated state, tell caller to retry
974            if is_discovery {
975                return Err(Error::AuthUpdated);
976            }
977
978            // If we still need auth but haven’t updated state, it’s an auth failure
979            if security.need_auth() {
980                return Err(Error::AuthFailure(AuthErrorKind::NotAuthenticated));
981            }
982        } else {
983            // Authenticated path
984            if security.authoritative_state.engine_boots == 0 && engine_boots == 0 {
985                return Err(Error::AuthFailure(AuthErrorKind::EngineBootsNotProvided));
986            }
987
988            if security.authoritative_state.engine_boots < engine_boots {
989                is_discovery = true;
990                prev_engine_time = engine_time;
991                security
992                    .authoritative_state
993                    .update_authoritative(engine_boots, engine_time);
994            } else {
995                security
996                    .authoritative_state
997                    .update_authoritative_engine_time(engine_time);
998            }
999
1000            if username != security.username {
1001                return Err(Error::AuthFailure(AuthErrorKind::UsernameMismatch));
1002            }
1003
1004            if engine_id.is_empty() {
1005                return Err(Error::AuthFailure(AuthErrorKind::NotAuthenticated));
1006            }
1007
1008            if security.authoritative_state.engine_id.is_empty() {
1009                security.authoritative_state.engine_id = engine_id.to_vec();
1010                security.update_key()?;
1011            } else if engine_id != security.authoritative_state.engine_id {
1012                return Err(Error::AuthFailure(AuthErrorKind::EngineIdMismatch));
1013            }
1014
1015            if auth_params.len() != truncation_len
1016                || auth_params_pos + auth_params.len() > bytes.len()
1017            {
1018                return Err(Error::ValueOutOfRange);
1019            }
1020
1021            unsafe {
1022                let auth_params_ptr = bytes.as_ptr().add(auth_params_pos).cast_mut();
1023                // TODO: switch to safe code as the solution may be pretty fragile
1024                std::hint::black_box(|| {
1025                    std::ptr::write_bytes(auth_params_ptr, 0, auth_params.len());
1026                })();
1027            }
1028
1029            if security.need_auth() {
1030                let hmac = security.calculate_hmac(bytes)?;
1031
1032                if hmac.len() < truncation_len || hmac[..truncation_len] != auth_params {
1033                    return Err(Error::AuthFailure(AuthErrorKind::SignatureMismatch));
1034                }
1035            }
1036        }
1037
1038        let scoped_pdu_seq = if flags & V3_MSG_FLAGS_PRIVACY == 0 {
1039            if security.need_encrypt() && !is_discovery {
1040                return Err(Error::AuthFailure(AuthErrorKind::ReplyNotEncrypted));
1041            }
1042
1043            rdr.read_raw(asn1::TYPE_SEQUENCE)?
1044        } else {
1045            let encrypted_pdu = rdr.read_asn_octetstring()?;
1046            security.decrypt(encrypted_pdu, priv_params)?;
1047            let mut rdr = AsnReader::from_bytes(&security.plain_buf);
1048            rdr.read_raw(asn1::TYPE_SEQUENCE)?
1049        };
1050
1051        let mut scoped_pdu_rdr = AsnReader::from_bytes(scoped_pdu_seq);
1052
1053        let _context_engine_id = scoped_pdu_rdr.read_asn_octetstring()?;
1054
1055        let _context_name = scoped_pdu_rdr.read_asn_octetstring()?;
1056
1057        let ident = scoped_pdu_rdr.peek_byte()?;
1058
1059        let message_type = MessageType::from_ident(ident)?;
1060
1061        if message_type == MessageType::Trap {
1062            is_discovery = false;
1063        } else {
1064            if security.engine_boots() > engine_boots {
1065                return Err(Error::AuthFailure(AuthErrorKind::EngineBootsMismatch));
1066            }
1067            if security.engine_boots() == engine_boots
1068                && (engine_time - prev_engine_time).abs() > ENGINE_TIME_WINDOW
1069            {
1070                return Err(Error::AuthFailure(AuthErrorKind::EngineTimeMismatch));
1071            }
1072        }
1073
1074        let mut response_pdu = AsnReader::from_bytes(scoped_pdu_rdr.read_raw(ident)?);
1075
1076        let req_id: i32 = i32::try_from(response_pdu.read_asn_integer()?)?;
1077
1078        let error_status: u32 =
1079            u32::try_from(response_pdu.read_asn_integer()?).map_err(|_| Error::ValueOutOfRange)?;
1080
1081        let error_index: u32 = u32::try_from(response_pdu.read_asn_integer()?)?;
1082
1083        let varbind_bytes = response_pdu.read_raw(asn1::TYPE_SEQUENCE)?;
1084        let varbinds = Varbinds::from_bytes(varbind_bytes);
1085
1086        if is_discovery {
1087            return Err(Error::AuthUpdated);
1088        }
1089
1090        Ok(Pdu {
1091            version: Version::V3 as i64,
1092            community: username,
1093            message_type,
1094            req_id,
1095            error_status,
1096            error_index,
1097            varbinds,
1098            v1_trap_info: None,
1099            v3_msg_id: i32::try_from(msg_id).map_err(|_| Error::ValueOutOfRange)?,
1100        })
1101    }
1102}
1103
1104pub(crate) fn build_init(req_id: i32, buf: &mut Buf) {
1105    buf.reset();
1106    let mut sec_buf = Buf::default();
1107    sec_buf.push_sequence(|sec| {
1108        sec.push_octet_string(&[]); // priv params
1109        sec.push_octet_string(&[]); // auth params
1110        sec.push_octet_string(&[]); // user name
1111        sec.push_integer(0); // time
1112        sec.push_integer(0); // boots
1113        sec.push_octet_string(&[]); // engine ID
1114    });
1115    buf.push_sequence(|message| {
1116        message.push_sequence(|pdu| {
1117            pdu.push_constructed(snmp::MSG_GET, |req| {
1118                req.push_sequence(|_varbinds| {});
1119                req.push_integer(0); // error index
1120                req.push_integer(0); // error status
1121                req.push_integer(req_id.into());
1122            });
1123            pdu.push_octet_string(&[]);
1124            pdu.push_octet_string(&[]);
1125        });
1126        message.push_octet_string(&sec_buf);
1127        message.push_sequence(|global| {
1128            global.push_integer(3); // security_model
1129            global.push_octet_string(&[V3_MSG_FLAGS_REPORTABLE]); // flags
1130            global.push_integer(BUFFER_SIZE.try_into().unwrap()); // max_size
1131            global.push_integer(req_id.into()); // msg_id
1132        });
1133        message.push_integer(Version::V3 as i64);
1134    });
1135}
1136
1137pub(crate) fn build(
1138    ident: u8,
1139    req_id: i32,
1140    values: &[(&Oid, Value)],
1141    non_repeaters: u32,
1142    max_repetitions: u32,
1143    buf: &mut Buf,
1144    security: Option<&Security>,
1145) -> Result<()> {
1146    let security = security.ok_or(Error::AuthFailure(AuthErrorKind::SecurityNotProvided))?;
1147    let truncation_len = security.auth_protocol.truncation_length();
1148    buf.reset();
1149    let mut sec_buf_seq = Buf::default();
1150    sec_buf_seq.reset();
1151    let mut auth_pos = 0;
1152    let mut sec_buf_len = 0;
1153    let mut priv_params: Vec<u8> = Vec::new();
1154    let mut inner_len = 0;
1155    let mut flags = V3_MSG_FLAGS_REPORTABLE;
1156
1157    if security.need_auth() {
1158        flags |= V3_MSG_FLAGS_AUTH;
1159    }
1160
1161    let encrypted = if security.need_encrypt() {
1162        flags |= V3_MSG_FLAGS_PRIVACY;
1163        let mut pdu_buf = Buf::default();
1164        pdu_buf.push_sequence(|buf| {
1165            pdu::build_inner(req_id, ident, values, max_repetitions, non_repeaters, buf);
1166            buf.push_octet_string(&[]);
1167            buf.push_octet_string(security.engine_id());
1168        });
1169        let (encrypted, salt) = security.encrypt(&pdu_buf)?;
1170        priv_params.extend_from_slice(&salt);
1171        Some(encrypted)
1172    } else {
1173        None
1174    };
1175
1176    buf.push_sequence(|buf| {
1177        if let Some(ref encrypted) = encrypted {
1178            buf.push_octet_string(encrypted);
1179        } else {
1180            buf.push_sequence(|buf| {
1181                pdu::build_inner(req_id, ident, values, max_repetitions, non_repeaters, buf);
1182                buf.push_octet_string(&[]);
1183                buf.push_octet_string(security.engine_id());
1184            });
1185        }
1186        let l0 = buf.len();
1187        sec_buf_seq.push_sequence(|buf| {
1188            buf.push_octet_string(&priv_params); // priv params
1189            let l0 = buf.len() - priv_params.len();
1190            buf.push_octet_string(&vec![0u8; truncation_len]); // auth params
1191            let l1 = buf.len() - l0;
1192            buf.push_octet_string(security.username()); // user name
1193            buf.push_integer(security.engine_time()); // time
1194            buf.push_integer(security.engine_boots()); // boots
1195            buf.push_octet_string(security.engine_id()); // engine ID
1196            auth_pos = buf.len() - l1;
1197            sec_buf_len = buf.len();
1198        });
1199        buf.push_octet_string(&sec_buf_seq);
1200        buf.push_sequence(|buf| {
1201            buf.push_integer(3); // security_model
1202            buf.push_octet_string(&[flags]); // flags
1203            buf.push_integer(BUFFER_SIZE.try_into().unwrap()); // max_size
1204            buf.push_integer(req_id.into()); // msg_id
1205        });
1206        buf.push_integer(3); // version
1207        auth_pos = buf.len() - l0 - (sec_buf_len - auth_pos);
1208        inner_len = buf.len();
1209    });
1210
1211    auth_pos += buf.len() - inner_len;
1212    if (auth_pos + truncation_len) > buf.len() {
1213        return Err(Error::ValueOutOfRange);
1214    }
1215
1216    if security.need_auth() {
1217        let hmac = security.calculate_hmac(buf)?;
1218        buf[auth_pos..auth_pos + truncation_len].copy_from_slice(&hmac[..truncation_len]);
1219    }
1220
1221    Ok(())
1222}