1use anyhow::{bail, ensure, Context, Result};
37use des::cipher::{Block, BlockDecrypt, BlockEncrypt, KeyInit};
38use hmac::{Hmac, Mac};
39use rasn::ber;
40use rasn::types::{Integer, ObjectIdentifier, OctetString};
41use rasn_snmp::{v2, v3};
42use serde::Serialize;
43use std::collections::HashMap;
44use std::sync::atomic::{AtomicI64, AtomicU32, AtomicU64, Ordering};
45use std::sync::Mutex;
46
47pub const USM_SECURITY_MODEL: i64 = 3;
49pub const FLAG_AUTH: u8 = 0x01;
51pub const FLAG_PRIV: u8 = 0x02;
52pub const FLAG_REPORTABLE: u8 = 0x04;
53pub const TIME_WINDOW_SECS: i64 = 150;
55pub const MAX_ENGINE_BOOTS: i64 = 2_147_483_647;
57pub const MAX_ENGINE_TIME: i64 = 2_147_483_647;
60pub const MAX_MESSAGE_SIZE: i64 = 65_507;
62const PASSWORD_EXPANSION: usize = 1_048_576;
64pub const MAX_USERS: usize = 64;
66pub const MAX_NAME_BYTES: usize = 32;
67pub const MIN_PASSWORD_BYTES: usize = 8;
68pub const MAX_PASSWORD_BYTES: usize = 256;
69pub const MAX_ENGINE_ID_BYTES: usize = 32;
70pub const MIN_ENGINE_ID_BYTES: usize = 5;
71pub const ENGINE_BOOTS_EPOCH_UNIX_SECS: u64 = 1_704_067_200;
74
75static LAST_DEFAULT_BOOTS: AtomicI64 = AtomicI64::new(0);
78
79pub fn default_engine_boots() -> i64 {
90 let wall = crate::utils::clock::SystemTime::now()
91 .duration_since(crate::utils::clock::UNIX_EPOCH)
92 .map(|d| d.as_secs())
93 .unwrap_or(0);
94 let since_epoch = wall.saturating_sub(ENGINE_BOOTS_EPOCH_UNIX_SECS);
95 let since_epoch = i64::try_from(since_epoch).unwrap_or(MAX_ENGINE_BOOTS - 1);
96 let mut previous = LAST_DEFAULT_BOOTS.load(Ordering::Relaxed);
97 loop {
98 let next = since_epoch
99 .max(previous.saturating_add(1))
100 .clamp(1, MAX_ENGINE_BOOTS - 1);
101 match LAST_DEFAULT_BOOTS.compare_exchange_weak(
102 previous,
103 next,
104 Ordering::Relaxed,
105 Ordering::Relaxed,
106 ) {
107 Ok(_) => return next,
108 Err(current) => previous = current,
109 }
110 }
111}
112
113pub fn in_engine_range(value: i64) -> bool {
115 (0..=MAX_ENGINE_TIME).contains(&value)
116}
117
118pub fn agent_from_params(
121 params: Option<&crate::protocol::StartupParams>,
122) -> Result<Option<std::sync::Arc<Agent>>> {
123 let Some(params) = params else {
124 return Ok(None);
125 };
126 let Some(users) = params.get_optional_array("v3_users")? else {
127 return Ok(None);
128 };
129 let users = users
130 .iter()
131 .map(User::from_json)
132 .collect::<Result<Vec<_>>>()?;
133 let engine_id = match params.get_optional_string("engine_id")? {
134 Some(hex_id) => hex::decode(hex_id.trim_start_matches("0x"))
135 .context("engine_id is hexadecimal, as Net-SNMP writes it (e.g. 80001f8880...)")?,
136 None => Agent::random_engine_id(),
137 };
138 let boots = match params.get_optional_i64("engine_boots")? {
139 Some(boots) => boots,
140 None => default_engine_boots(),
141 };
142 Ok(Some(std::sync::Arc::new(Agent::new(
143 engine_id, boots, users,
144 )?)))
145}
146
147pub fn is_v3(data: &[u8]) -> bool {
149 let Ok((0x30, start, _)) = tlv(data, 0) else {
150 return false;
151 };
152 matches!(tlv(data, start), Ok((0x02, s, 1)) if data[s] == 3)
153}
154
155#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
157#[serde(rename_all = "camelCase")]
158pub enum ReportKind {
159 UnsupportedSecLevels,
160 NotInTimeWindows,
161 UnknownUserNames,
162 UnknownEngineIds,
163 WrongDigests,
164 DecryptionErrors,
165}
166
167impl ReportKind {
168 const ALL: [ReportKind; 6] = [
169 ReportKind::UnsupportedSecLevels,
170 ReportKind::NotInTimeWindows,
171 ReportKind::UnknownUserNames,
172 ReportKind::UnknownEngineIds,
173 ReportKind::WrongDigests,
174 ReportKind::DecryptionErrors,
175 ];
176
177 pub fn oid(self) -> Vec<u32> {
179 let n = match self {
180 ReportKind::UnsupportedSecLevels => 1,
181 ReportKind::NotInTimeWindows => 2,
182 ReportKind::UnknownUserNames => 3,
183 ReportKind::UnknownEngineIds => 4,
184 ReportKind::WrongDigests => 5,
185 ReportKind::DecryptionErrors => 6,
186 };
187 vec![1, 3, 6, 1, 6, 3, 15, 1, 1, n, 0]
188 }
189
190 pub fn from_oid(oid: &[u32]) -> Option<ReportKind> {
191 ReportKind::ALL.into_iter().find(|k| k.oid() == oid)
192 }
193
194 pub fn name(self) -> &'static str {
195 match self {
196 ReportKind::UnsupportedSecLevels => "usmStatsUnsupportedSecLevels",
197 ReportKind::NotInTimeWindows => "usmStatsNotInTimeWindows",
198 ReportKind::UnknownUserNames => "usmStatsUnknownUserNames",
199 ReportKind::UnknownEngineIds => "usmStatsUnknownEngineIDs",
200 ReportKind::WrongDigests => "usmStatsWrongDigests",
201 ReportKind::DecryptionErrors => "usmStatsDecryptionErrors",
202 }
203 }
204
205 fn index(self) -> usize {
206 ReportKind::ALL.iter().position(|k| *k == self).unwrap_or(0)
207 }
208}
209
210#[derive(Clone, Copy, Debug, PartialEq, Eq)]
211pub enum AuthProtocol {
212 None,
213 Md5,
214 Sha1,
215 Sha224,
216 Sha256,
217 Sha384,
218 Sha512,
219}
220
221impl AuthProtocol {
222 pub fn parse(s: &str) -> Result<AuthProtocol> {
223 Ok(
224 match s.to_ascii_lowercase().replace(['-', '_'], "").as_str() {
225 "" | "none" => AuthProtocol::None,
226 "md5" => AuthProtocol::Md5,
227 "sha" | "sha1" => AuthProtocol::Sha1,
228 "sha224" => AuthProtocol::Sha224,
229 "sha256" => AuthProtocol::Sha256,
230 "sha384" => AuthProtocol::Sha384,
231 "sha512" => AuthProtocol::Sha512,
232 _ => bail!(
233 "auth_protocol {s:?} is not one of none, md5, sha, sha224, sha256, sha384, sha512"
234 ),
235 },
236 )
237 }
238
239 pub fn name(self) -> &'static str {
240 match self {
241 AuthProtocol::None => "none",
242 AuthProtocol::Md5 => "md5",
243 AuthProtocol::Sha1 => "sha",
244 AuthProtocol::Sha224 => "sha224",
245 AuthProtocol::Sha256 => "sha256",
246 AuthProtocol::Sha384 => "sha384",
247 AuthProtocol::Sha512 => "sha512",
248 }
249 }
250
251 pub fn mac_len(self) -> usize {
253 match self {
254 AuthProtocol::None => 0,
255 AuthProtocol::Md5 | AuthProtocol::Sha1 => 12,
256 AuthProtocol::Sha224 => 16,
257 AuthProtocol::Sha256 => 24,
258 AuthProtocol::Sha384 => 32,
259 AuthProtocol::Sha512 => 48,
260 }
261 }
262
263 fn hash(self, parts: &[&[u8]]) -> Vec<u8> {
264 fn run<D: sha2::Digest>(parts: &[&[u8]]) -> Vec<u8> {
265 let mut d = D::new();
266 for p in parts {
267 d.update(p);
268 }
269 d.finalize().to_vec()
270 }
271 match self {
272 AuthProtocol::None => Vec::new(),
273 AuthProtocol::Md5 => run::<md5::Md5>(parts),
274 AuthProtocol::Sha1 => run::<sha1::Sha1>(parts),
275 AuthProtocol::Sha224 => run::<sha2::Sha224>(parts),
276 AuthProtocol::Sha256 => run::<sha2::Sha256>(parts),
277 AuthProtocol::Sha384 => run::<sha2::Sha384>(parts),
278 AuthProtocol::Sha512 => run::<sha2::Sha512>(parts),
279 }
280 }
281
282 fn mac(self, key: &[u8], message: &[u8]) -> Vec<u8> {
283 fn run<M: Mac + hmac::digest::KeyInit>(key: &[u8], message: &[u8]) -> Vec<u8> {
284 let mut m = <M as hmac::digest::KeyInit>::new_from_slice(key)
285 .expect("HMAC accepts any key length");
286 m.update(message);
287 m.finalize().into_bytes().to_vec()
288 }
289 let full = match self {
290 AuthProtocol::None => return Vec::new(),
291 AuthProtocol::Md5 => run::<Hmac<md5::Md5>>(key, message),
292 AuthProtocol::Sha1 => run::<Hmac<sha1::Sha1>>(key, message),
293 AuthProtocol::Sha224 => run::<Hmac<sha2::Sha224>>(key, message),
294 AuthProtocol::Sha256 => run::<Hmac<sha2::Sha256>>(key, message),
295 AuthProtocol::Sha384 => run::<Hmac<sha2::Sha384>>(key, message),
296 AuthProtocol::Sha512 => run::<Hmac<sha2::Sha512>>(key, message),
297 };
298 full[..self.mac_len()].to_vec()
299 }
300
301 pub fn password_to_key(self, password: &[u8]) -> Vec<u8> {
303 if self == AuthProtocol::None || password.is_empty() {
304 return Vec::new();
305 }
306 let mut block = Vec::with_capacity(PASSWORD_EXPANSION);
307 while block.len() < PASSWORD_EXPANSION {
308 let take = (PASSWORD_EXPANSION - block.len()).min(password.len());
309 block.extend_from_slice(&password[..take]);
310 }
311 self.hash(&[&block])
312 }
313
314 pub fn localize(self, key: &[u8], engine_id: &[u8]) -> Vec<u8> {
316 self.hash(&[key, engine_id, key])
317 }
318}
319
320#[derive(Clone, Copy, Debug, PartialEq, Eq)]
321pub enum PrivProtocol {
322 None,
323 Des,
324 Aes128,
325}
326
327impl PrivProtocol {
328 pub fn parse(s: &str) -> Result<PrivProtocol> {
329 Ok(
330 match s.to_ascii_lowercase().replace(['-', '_'], "").as_str() {
331 "" | "none" => PrivProtocol::None,
332 "des" => PrivProtocol::Des,
333 "aes" | "aes128" => PrivProtocol::Aes128,
334 _ => bail!("priv_protocol {s:?} is not one of none, des, aes"),
335 },
336 )
337 }
338
339 pub fn name(self) -> &'static str {
340 match self {
341 PrivProtocol::None => "none",
342 PrivProtocol::Des => "des",
343 PrivProtocol::Aes128 => "aes",
344 }
345 }
346}
347
348#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
350pub enum SecurityLevel {
351 #[serde(rename = "noAuthNoPriv")]
352 NoAuthNoPriv,
353 #[serde(rename = "authNoPriv")]
354 AuthNoPriv,
355 #[serde(rename = "authPriv")]
356 AuthPriv,
357}
358
359impl SecurityLevel {
360 pub fn from_flags(flags: u8) -> Result<SecurityLevel> {
361 Ok(match (flags & FLAG_AUTH != 0, flags & FLAG_PRIV != 0) {
362 (false, false) => SecurityLevel::NoAuthNoPriv,
363 (true, false) => SecurityLevel::AuthNoPriv,
364 (true, true) => SecurityLevel::AuthPriv,
365 (false, true) => bail!("msgFlags asks for privacy without authentication"),
366 })
367 }
368
369 pub fn flags(self) -> u8 {
370 match self {
371 SecurityLevel::NoAuthNoPriv => 0,
372 SecurityLevel::AuthNoPriv => FLAG_AUTH,
373 SecurityLevel::AuthPriv => FLAG_AUTH | FLAG_PRIV,
374 }
375 }
376
377 pub fn name(self) -> &'static str {
378 match self {
379 SecurityLevel::NoAuthNoPriv => "noAuthNoPriv",
380 SecurityLevel::AuthNoPriv => "authNoPriv",
381 SecurityLevel::AuthPriv => "authPriv",
382 }
383 }
384}
385
386#[derive(Clone)]
388pub struct User {
389 pub name: String,
390 pub auth: AuthProtocol,
391 pub priv_proto: PrivProtocol,
392 auth_ku: Vec<u8>,
394 priv_ku: Vec<u8>,
395}
396
397impl std::fmt::Debug for User {
398 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
399 f.debug_struct("User")
400 .field("name", &self.name)
401 .field("auth", &self.auth)
402 .field("priv", &self.priv_proto)
403 .finish_non_exhaustive()
404 }
405}
406
407impl User {
408 pub fn from_json(v: &serde_json::Value) -> Result<User> {
410 let name = v
411 .get("name")
412 .and_then(|n| n.as_str())
413 .context("a v3 user needs 'name'")?;
414 ensure!(
415 !name.is_empty() && name.len() <= MAX_NAME_BYTES,
416 "a v3 user name is 1 to {MAX_NAME_BYTES} bytes"
417 );
418 let text = |k: &str| v.get(k).and_then(|x| x.as_str()).unwrap_or("");
419 let auth = AuthProtocol::parse(text("auth_protocol"))?;
420 let priv_proto = PrivProtocol::parse(text("priv_protocol"))?;
421 let password = |k: &str, needed: bool| -> Result<Vec<u8>> {
422 let p = text(k);
423 if !needed {
424 ensure!(
425 p.is_empty(),
426 "user {name}: {k} is set for a protocol of none"
427 );
428 return Ok(Vec::new());
429 }
430 ensure!(
431 (MIN_PASSWORD_BYTES..=MAX_PASSWORD_BYTES).contains(&p.len()),
432 "user {name}: {k} must be {MIN_PASSWORD_BYTES} to {MAX_PASSWORD_BYTES} bytes \
433 (RFC 3414 §11.2 sets 8 as the minimum)"
434 );
435 Ok(p.as_bytes().to_vec())
436 };
437 ensure!(
438 priv_proto == PrivProtocol::None || auth != AuthProtocol::None,
439 "user {name}: privacy needs an auth_protocol (there is no noAuthPriv)"
440 );
441 let auth_password = password("auth_password", auth != AuthProtocol::None)?;
442 let priv_password = password("priv_password", priv_proto != PrivProtocol::None)?;
443 Ok(User {
444 name: name.to_string(),
445 auth,
446 priv_proto,
447 auth_ku: auth.password_to_key(&auth_password),
448 priv_ku: auth.password_to_key(&priv_password),
449 })
450 }
451
452 pub fn level(&self) -> SecurityLevel {
453 match (self.auth, self.priv_proto) {
454 (AuthProtocol::None, _) => SecurityLevel::NoAuthNoPriv,
455 (_, PrivProtocol::None) => SecurityLevel::AuthNoPriv,
456 _ => SecurityLevel::AuthPriv,
457 }
458 }
459
460 pub fn localize(&self, engine_id: &[u8]) -> Keys {
461 Keys {
462 auth: self.auth,
463 priv_proto: self.priv_proto,
464 auth_key: self.auth.localize(&self.auth_ku, engine_id),
465 priv_key: if self.priv_proto == PrivProtocol::None {
466 Vec::new()
467 } else {
468 self.auth.localize(&self.priv_ku, engine_id)
469 },
470 }
471 }
472}
473
474#[derive(Clone)]
476pub struct Keys {
477 pub auth: AuthProtocol,
478 pub priv_proto: PrivProtocol,
479 pub auth_key: Vec<u8>,
480 pub priv_key: Vec<u8>,
481}
482
483impl Keys {
484 pub fn encrypt(
486 &self,
487 plain: &[u8],
488 boots: i64,
489 time: i64,
490 salt: u64,
491 ) -> Result<(Vec<u8>, Vec<u8>)> {
492 match self.priv_proto {
493 PrivProtocol::None => bail!("no privacy protocol"),
494 PrivProtocol::Des => {
495 ensure!(
496 self.priv_key.len() >= 16,
497 "DES needs a 16-byte localized key"
498 );
499 let salt = [(boots as u32).to_be_bytes(), (salt as u32).to_be_bytes()].concat();
500 let iv: Vec<u8> = self.priv_key[8..16]
501 .iter()
502 .zip(&salt)
503 .map(|(a, b)| a ^ b)
504 .collect();
505 let cipher = des::Des::new_from_slice(&self.priv_key[..8]).context("DES key")?;
506 let mut data = plain.to_vec();
507 data.resize(plain.len().div_ceil(8) * 8, 0);
508 let mut prev: [u8; 8] = iv.try_into().expect("8 bytes");
509 for block in data.chunks_mut(8) {
510 for (b, p) in block.iter_mut().zip(prev) {
511 *b ^= p;
512 }
513 let mut ga = Block::<des::Des>::default();
514 ga.copy_from_slice(block);
515 cipher.encrypt_block(&mut ga);
516 block.copy_from_slice(&ga);
517 prev.copy_from_slice(block);
518 }
519 Ok((data, salt))
520 }
521 PrivProtocol::Aes128 => {
522 ensure!(
523 self.priv_key.len() >= 16,
524 "AES-128 needs a 16-byte localized key"
525 );
526 let salt = salt.to_be_bytes().to_vec();
527 let iv = aes_iv(boots, time, &salt);
528 let mut data = plain.to_vec();
529 aes_cfb(&self.priv_key[..16], iv, &mut data, true)?;
530 Ok((data, salt))
531 }
532 }
533 }
534
535 pub fn decrypt(
537 &self,
538 cipher_text: &[u8],
539 boots: i64,
540 time: i64,
541 params: &[u8],
542 ) -> Result<Vec<u8>> {
543 ensure!(params.len() == 8, "msgPrivacyParameters must be 8 bytes");
544 match self.priv_proto {
545 PrivProtocol::None => bail!("no privacy protocol"),
546 PrivProtocol::Des => {
547 ensure!(
548 !cipher_text.is_empty() && cipher_text.len() % 8 == 0,
549 "a DES ciphertext is a whole number of 8-byte blocks"
550 );
551 let iv: Vec<u8> = self.priv_key[8..16]
552 .iter()
553 .zip(params)
554 .map(|(a, b)| a ^ b)
555 .collect();
556 let cipher = des::Des::new_from_slice(&self.priv_key[..8]).context("DES key")?;
557 let mut prev: [u8; 8] = iv.try_into().expect("8 bytes");
558 let mut out = Vec::with_capacity(cipher_text.len());
559 for block in cipher_text.chunks(8) {
560 let mut ga = Block::<des::Des>::default();
561 ga.copy_from_slice(block);
562 cipher.decrypt_block(&mut ga);
563 out.extend(ga.iter().zip(prev).map(|(a, b)| a ^ b));
564 prev.copy_from_slice(block);
565 }
566 Ok(out)
567 }
568 PrivProtocol::Aes128 => {
569 let iv = aes_iv(boots, time, params);
570 let mut data = cipher_text.to_vec();
571 aes_cfb(&self.priv_key[..16], iv, &mut data, false)?;
572 Ok(data)
573 }
574 }
575 }
576}
577
578fn aes_iv(boots: i64, time: i64, salt: &[u8]) -> [u8; 16] {
579 let mut iv = [0u8; 16];
580 iv[..4].copy_from_slice(&(boots as u32).to_be_bytes());
581 iv[4..8].copy_from_slice(&(time as u32).to_be_bytes());
582 iv[8..].copy_from_slice(salt);
583 iv
584}
585
586fn aes_cfb(key: &[u8], iv: [u8; 16], data: &mut [u8], encrypt: bool) -> Result<()> {
588 let cipher = aes::Aes128::new_from_slice(key).context("AES key")?;
589 let mut feedback = iv;
590 for chunk in data.chunks_mut(16) {
591 let mut ga = Block::<aes::Aes128>::default();
592 ga.copy_from_slice(&feedback);
593 cipher.encrypt_block(&mut ga);
594 let mut next = [0u8; 16];
595 for (i, b) in chunk.iter_mut().enumerate() {
596 let received = *b;
597 *b ^= ga[i];
598 next[i] = if encrypt { *b } else { received };
601 }
602 feedback = next;
603 }
604 Ok(())
605}
606
607fn tlv(data: &[u8], at: usize) -> Result<(u8, usize, usize)> {
609 let tag = *data.get(at).context("truncated BER")?;
610 let first = *data.get(at + 1).context("truncated BER length")?;
611 let (len, header) = if first < 0x80 {
612 (first as usize, 2)
613 } else {
614 let n = (first & 0x7f) as usize;
615 ensure!((1..=4).contains(&n), "unsupported BER length form");
616 let bytes = data
617 .get(at + 2..at + 2 + n)
618 .context("truncated BER length")?;
619 (
620 bytes.iter().fold(0usize, |a, b| (a << 8) | *b as usize),
621 2 + n,
622 )
623 };
624 let start = at + header;
625 ensure!(
626 start + len <= data.len(),
627 "BER length runs past the message"
628 );
629 Ok((tag, start, len))
630}
631
632pub fn auth_params_span(data: &[u8]) -> Result<(usize, usize)> {
635 let (tag, outer, _) = tlv(data, 0)?;
636 ensure!(tag == 0x30, "not a SEQUENCE");
637 let mut at = outer;
638 for _ in 0..2 {
639 let (_, s, l) = tlv(data, at)?;
640 at = s + l;
641 }
642 let (tag, sec, _) = tlv(data, at)?;
643 ensure!(tag == 0x04, "msgSecurityParameters is not an OCTET STRING");
644 let (tag, seq, _) = tlv(data, sec)?;
645 ensure!(tag == 0x30, "UsmSecurityParameters is not a SEQUENCE");
646 let mut at = seq;
647 for _ in 0..4 {
648 let (_, s, l) = tlv(data, at)?;
649 at = s + l;
650 }
651 let (tag, s, l) = tlv(data, at)?;
652 ensure!(
653 tag == 0x04,
654 "msgAuthenticationParameters is not an OCTET STRING"
655 );
656 Ok((s, l))
657}
658
659pub fn first_tlv_len(data: &[u8]) -> Result<usize> {
661 let (_, s, l) = tlv(data, 0)?;
662 Ok(s + l)
663}
664
665fn int(v: &Integer) -> Result<i64> {
666 i64::try_from(v.clone()).map_err(|_| anyhow::anyhow!("integer out of range"))
667}
668
669pub struct Received {
671 pub msg_id: i64,
672 pub max_size: i64,
673 pub flags: u8,
674 pub usm: v3::USMSecurityParameters,
675 pub data: v3::ScopedPduData,
676}
677
678pub fn decode(data: &[u8]) -> Result<Received> {
687 crate::server::snmp::check_ber_structure(data, crate::server::snmp::MAX_BER_DEPTH)
688 .map_err(|e| anyhow::anyhow!("not an SNMPv3 message: {e}"))?;
689 let msg: v3::Message =
690 ber::decode(data).map_err(|e| anyhow::anyhow!("not an SNMPv3 message: {e}"))?;
691 ensure!(int(&msg.version)? == 3, "not version 3");
692 ensure!(
693 int(&msg.global_data.security_model)? == USM_SECURITY_MODEL,
694 "security model {} is not the USM",
695 msg.global_data.security_model
696 );
697 ensure!(
698 msg.global_data.flags.len() == 1,
699 "msgFlags must be one byte"
700 );
701 crate::server::snmp::check_ber_structure(
702 &msg.security_parameters,
703 crate::server::snmp::MAX_BER_DEPTH,
704 )
705 .map_err(|e| anyhow::anyhow!("bad UsmSecurityParameters: {e}"))?;
706 let usm: v3::USMSecurityParameters = ber::decode(&msg.security_parameters)
707 .map_err(|e| anyhow::anyhow!("bad UsmSecurityParameters: {e}"))?;
708 Ok(Received {
709 msg_id: int(&msg.global_data.message_id)?,
710 max_size: int(&msg.global_data.max_size)?,
711 flags: msg.global_data.flags[0],
712 usm,
713 data: msg.scoped_data,
714 })
715}
716
717pub struct Outgoing<'a> {
719 pub msg_id: i64,
720 pub max_size: i64,
721 pub flags: u8,
722 pub engine_id: &'a [u8],
723 pub boots: i64,
724 pub time: i64,
725 pub user: &'a [u8],
726 pub keys: Option<&'a Keys>,
728 pub salt: u64,
729 pub context_engine_id: &'a [u8],
730 pub context_name: &'a [u8],
731 pub pdu: v2::Pdus,
732}
733
734pub fn encode(out: Outgoing<'_>) -> Result<Vec<u8>> {
736 let level = SecurityLevel::from_flags(out.flags)?;
737 let scoped = v3::ScopedPdu {
738 engine_id: out.context_engine_id.to_vec().into(),
739 name: out.context_name.to_vec().into(),
740 data: out.pdu,
741 };
742 let (scoped_data, priv_params) = if level == SecurityLevel::AuthPriv {
743 let keys = out.keys.context("privacy needs keys")?;
744 let plain = ber::encode(&scoped).map_err(|e| anyhow::anyhow!("scoped PDU: {e}"))?;
745 let (cipher_text, params) = keys.encrypt(&plain, out.boots, out.time, out.salt)?;
746 (v3::ScopedPduData::EncryptedPdu(cipher_text.into()), params)
747 } else {
748 (v3::ScopedPduData::CleartextPdu(scoped), Vec::new())
749 };
750 let mac_len = match level {
751 SecurityLevel::NoAuthNoPriv => 0,
752 _ => out
753 .keys
754 .context("authentication needs keys")?
755 .auth
756 .mac_len(),
757 };
758 let build = |mac: Vec<u8>| -> Result<Vec<u8>> {
759 let usm = v3::USMSecurityParameters {
760 authoritative_engine_id: out.engine_id.to_vec().into(),
761 authoritative_engine_boots: out.boots.into(),
762 authoritative_engine_time: out.time.into(),
763 user_name: out.user.to_vec().into(),
764 authentication_parameters: mac.into(),
765 privacy_parameters: priv_params.clone().into(),
766 };
767 let message = v3::Message {
768 version: 3.into(),
769 global_data: v3::HeaderData {
770 message_id: out.msg_id.into(),
771 max_size: out.max_size.into(),
772 flags: OctetString::from(vec![out.flags]),
773 security_model: USM_SECURITY_MODEL.into(),
774 },
775 security_parameters: ber::encode(&usm)
776 .map_err(|e| anyhow::anyhow!("security parameters: {e}"))?
777 .into(),
778 scoped_data: scoped_data.clone(),
779 };
780 ber::encode(&message).map_err(|e| anyhow::anyhow!("v3 message: {e}"))
781 };
782 let zeroed = build(vec![0; mac_len])?;
783 if mac_len == 0 {
784 return Ok(zeroed);
785 }
786 let keys = out.keys.context("authentication needs keys")?;
787 let mac = keys.auth.mac(&keys.auth_key, &zeroed);
788 let (at, len) = auth_params_span(&zeroed)?;
789 ensure!(
790 len == mac_len,
791 "MAC field is {len} bytes, expected {mac_len}"
792 );
793 let mut message = zeroed;
794 message[at..at + len].copy_from_slice(&mac);
795 Ok(message)
796}
797
798pub fn verify(data: &[u8], keys: &Keys) -> bool {
800 let Ok((at, len)) = auth_params_span(data) else {
801 return false;
802 };
803 if len != keys.auth.mac_len() || len == 0 {
804 return false;
805 }
806 let mut zeroed = data.to_vec();
807 let offered = data[at..at + len].to_vec();
808 zeroed[at..at + len].fill(0);
809 let expected = keys.auth.mac(&keys.auth_key, &zeroed);
810 expected
812 .iter()
813 .zip(&offered)
814 .fold(0u8, |acc, (a, b)| acc | (a ^ b))
815 == 0
816}
817
818pub fn scoped_pdu(rx: &Received, keys: Option<&Keys>) -> Result<v3::ScopedPdu> {
820 match &rx.data {
821 v3::ScopedPduData::CleartextPdu(p) => {
822 ensure!(
823 rx.flags & FLAG_PRIV == 0,
824 "msgFlags says encrypted, the PDU is not"
825 );
826 Ok(p.clone())
827 }
828 v3::ScopedPduData::EncryptedPdu(c) => {
829 ensure!(
830 rx.flags & FLAG_PRIV != 0,
831 "an encrypted PDU without the privacy flag"
832 );
833 let keys = keys.context("an encrypted PDU for a user without privacy")?;
834 let plain = keys.decrypt(
835 c,
836 int(&rx.usm.authoritative_engine_boots)?,
837 int(&rx.usm.authoritative_engine_time)?,
838 &rx.usm.privacy_parameters,
839 )?;
840 crate::server::snmp::check_ber_structure(
841 &plain[..first_tlv_len(&plain)?],
842 crate::server::snmp::MAX_BER_DEPTH,
843 )
844 .map_err(|e| anyhow::anyhow!("decrypted PDU: {e}"))?;
845 ber::decode(&plain[..first_tlv_len(&plain)?])
846 .map_err(|e| anyhow::anyhow!("the decrypted scoped PDU does not decode: {e}"))
847 }
848 }
849}
850
851fn report_pdu(kind: ReportKind, count: u32, request_id: i32) -> v2::Pdus {
852 v2::Pdus::Report(v2::Report(v2::Pdu {
853 request_id,
854 error_status: 0,
855 error_index: 0,
856 variable_bindings: vec![v2::VarBind {
857 name: ObjectIdentifier::new_unchecked(kind.oid().into()),
858 value: v2::VarBindValue::Value(rasn_smi::v2::ObjectSyntax::ApplicationWide(
859 rasn_smi::v2::ApplicationSyntax::Counter(rasn_smi::v1::Counter(count)),
860 )),
861 }],
862 }))
863}
864
865pub struct Agent {
867 pub engine_id: Vec<u8>,
868 pub boots: i64,
869 started: crate::utils::clock::Instant,
870 users: HashMap<Vec<u8>, (User, Keys)>,
871 counters: [AtomicU32; 6],
872 salt: AtomicU64,
873}
874
875pub enum Inbound {
877 Request(Box<Request>),
879 Report(Vec<u8>, ReportKind),
881 Drop(String),
883}
884
885pub struct Request {
887 pub msg_id: i64,
888 pub max_size: i64,
889 pub user: String,
890 pub level: SecurityLevel,
891 pub context_engine_id: Vec<u8>,
892 pub context_name: Vec<u8>,
893 pub pdu: v2::Pdus,
894}
895
896impl Agent {
897 pub fn new(engine_id: Vec<u8>, boots: i64, users: Vec<User>) -> Result<Agent> {
898 ensure!(
899 (MIN_ENGINE_ID_BYTES..=MAX_ENGINE_ID_BYTES).contains(&engine_id.len()),
900 "engine_id must be {MIN_ENGINE_ID_BYTES} to {MAX_ENGINE_ID_BYTES} bytes"
901 );
902 ensure!(
903 (1..MAX_ENGINE_BOOTS).contains(&boots),
904 "engine_boots must be 1 to {}",
905 MAX_ENGINE_BOOTS - 1
906 );
907 ensure!(users.len() <= MAX_USERS, "at most {MAX_USERS} v3 users");
908 let mut table = HashMap::new();
909 for u in users {
910 let keys = u.localize(&engine_id);
911 ensure!(
912 table
913 .insert(u.name.as_bytes().to_vec(), (u.clone(), keys))
914 .is_none(),
915 "v3 user {} is configured twice",
916 u.name
917 );
918 }
919 Ok(Agent {
920 engine_id,
921 boots,
922 started: crate::utils::clock::Instant::now(),
923 users: table,
924 counters: Default::default(),
925 salt: AtomicU64::new(rand::random()),
926 })
927 }
928
929 pub fn random_engine_id() -> Vec<u8> {
932 let mut id = vec![0x80, 0x00, 0x00, 0x00, 0x05];
933 id.extend_from_slice(&rand::random::<[u8; 8]>());
934 id
935 }
936
937 pub fn time(&self) -> i64 {
938 self.started.elapsed().as_secs() as i64
939 }
940
941 pub fn counter(&self, kind: ReportKind) -> u32 {
942 self.counters[kind.index()].load(Ordering::Relaxed)
943 }
944
945 fn report(
946 &self,
947 rx: &Received,
948 kind: ReportKind,
949 keys: Option<&Keys>,
950 request_id: i32,
951 ) -> Result<Vec<u8>> {
952 let count = self.counters[kind.index()]
953 .fetch_add(1, Ordering::Relaxed)
954 .wrapping_add(1);
955 let flags = if keys.is_some() { FLAG_AUTH } else { 0 };
958 encode(Outgoing {
959 msg_id: rx.msg_id,
960 max_size: MAX_MESSAGE_SIZE,
961 flags,
962 engine_id: &self.engine_id,
963 boots: self.boots,
964 time: self.time(),
965 user: &rx.usm.user_name,
966 keys,
967 salt: 0,
968 context_engine_id: &self.engine_id,
969 context_name: b"",
970 pdu: report_pdu(kind, count, request_id),
971 })
972 }
973
974 pub fn receive(&self, data: &[u8]) -> Inbound {
976 let rx = match decode(data) {
977 Ok(rx) => rx,
978 Err(e) => return Inbound::Drop(e.to_string()),
979 };
980 let reportable = rx.flags & FLAG_REPORTABLE != 0;
981 let level = match SecurityLevel::from_flags(rx.flags) {
982 Ok(l) => l,
983 Err(e) => return Inbound::Drop(e.to_string()),
984 };
985 let request_id = match &rx.data {
986 v3::ScopedPduData::CleartextPdu(p) => pdu_request_id(&p.data),
987 _ => 0,
988 };
989 let reply = |kind: ReportKind, keys: Option<&Keys>| -> Inbound {
990 if !reportable {
991 return Inbound::Drop(format!("{} (not reportable)", kind.name()));
992 }
993 match self.report(&rx, kind, keys, request_id) {
994 Ok(bytes) => Inbound::Report(bytes, kind),
995 Err(e) => Inbound::Drop(format!("report could not be built: {e}")),
996 }
997 };
998 if rx.usm.authoritative_engine_id.as_ref() != self.engine_id.as_slice() {
999 return reply(ReportKind::UnknownEngineIds, None);
1000 }
1001 let Some((user, keys)) = self.users.get(rx.usm.user_name.as_ref()) else {
1002 return reply(ReportKind::UnknownUserNames, None);
1003 };
1004 if level != user.level() {
1007 return reply(ReportKind::UnsupportedSecLevels, None);
1008 }
1009 if level != SecurityLevel::NoAuthNoPriv {
1010 if !verify(data, keys) {
1011 return reply(ReportKind::WrongDigests, None);
1012 }
1013 let (Ok(boots), Ok(time)) = (
1014 int(&rx.usm.authoritative_engine_boots),
1015 int(&rx.usm.authoritative_engine_time),
1016 ) else {
1017 return reply(ReportKind::NotInTimeWindows, Some(keys));
1018 };
1019 if !in_engine_range(boots)
1022 || !in_engine_range(time)
1023 || boots == MAX_ENGINE_BOOTS
1024 || boots != self.boots
1025 || time.abs_diff(self.time()) > TIME_WINDOW_SECS.unsigned_abs()
1026 {
1027 return reply(ReportKind::NotInTimeWindows, Some(keys));
1028 }
1029 }
1030 let scoped = match scoped_pdu(&rx, (level == SecurityLevel::AuthPriv).then_some(keys)) {
1031 Ok(s) => s,
1032 Err(_) if level == SecurityLevel::AuthPriv => {
1033 return reply(ReportKind::DecryptionErrors, None)
1034 }
1035 Err(e) => return Inbound::Drop(e.to_string()),
1036 };
1037 Inbound::Request(Box::new(Request {
1038 msg_id: rx.msg_id,
1039 max_size: rx.max_size,
1040 user: user.name.clone(),
1041 level,
1042 context_engine_id: scoped.engine_id.to_vec(),
1043 context_name: scoped.name.to_vec(),
1044 pdu: scoped.data,
1045 }))
1046 }
1047
1048 pub fn respond(&self, request: &Request, pdu: v2::Pdus) -> Result<Vec<u8>> {
1050 let keys = match request.level {
1051 SecurityLevel::NoAuthNoPriv => None,
1052 _ => Some(
1053 &self
1054 .users
1055 .get(request.user.as_bytes())
1056 .context("the user is no longer configured")?
1057 .1,
1058 ),
1059 };
1060 encode(Outgoing {
1061 msg_id: request.msg_id,
1062 max_size: MAX_MESSAGE_SIZE,
1063 flags: request.level.flags(),
1064 engine_id: &self.engine_id,
1065 boots: self.boots,
1066 time: self.time(),
1067 user: request.user.as_bytes(),
1068 keys,
1069 salt: self.salt.fetch_add(1, Ordering::Relaxed),
1070 context_engine_id: &self.engine_id,
1071 context_name: &request.context_name,
1072 pdu,
1073 })
1074 }
1075}
1076
1077fn engine_clock(rx: &Received) -> Result<(i64, i64)> {
1079 let boots = int(&rx.usm.authoritative_engine_boots)?;
1080 let time = int(&rx.usm.authoritative_engine_time)?;
1081 ensure!(
1082 in_engine_range(boots),
1083 "snmpEngineBoots {boots} is outside 0..={MAX_ENGINE_BOOTS}"
1084 );
1085 ensure!(
1086 in_engine_range(time),
1087 "snmpEngineTime {time} is outside 0..={MAX_ENGINE_TIME}"
1088 );
1089 Ok((boots, time))
1090}
1091
1092pub fn pdu_request_id(pdu: &v2::Pdus) -> i32 {
1093 match pdu {
1094 v2::Pdus::GetRequest(p) => p.0.request_id,
1095 v2::Pdus::GetNextRequest(p) => p.0.request_id,
1096 v2::Pdus::GetBulkRequest(p) => p.0.request_id,
1097 v2::Pdus::SetRequest(p) => p.0.request_id,
1098 v2::Pdus::Response(p) => p.0.request_id,
1099 v2::Pdus::InformRequest(p) => p.0.request_id,
1100 v2::Pdus::Trap(p) => p.0.request_id,
1101 v2::Pdus::Report(p) => p.0.request_id,
1102 }
1103}
1104
1105pub struct Manager {
1107 pub user: User,
1108 engine: Mutex<Option<Engine>>,
1109 msg_id: AtomicU32,
1110 salt: AtomicU64,
1111 outstanding: Mutex<std::collections::VecDeque<i64>>,
1113}
1114
1115pub const OUTSTANDING_MESSAGES: usize = 16;
1117
1118#[derive(Clone)]
1119struct Engine {
1120 id: Vec<u8>,
1121 boots: i64,
1122 time: i64,
1123 at: crate::utils::clock::Instant,
1124 keys: Keys,
1125}
1126
1127pub enum Reply {
1129 Pdu(v2::Pdus),
1130 Report(ReportKind, u64),
1132}
1133
1134impl Manager {
1135 pub fn new(user: User) -> Manager {
1136 Manager {
1137 user,
1138 engine: Mutex::new(None),
1139 msg_id: AtomicU32::new(rand::random::<u32>() >> 1),
1140 salt: AtomicU64::new(rand::random()),
1141 outstanding: Mutex::new(std::collections::VecDeque::new()),
1142 }
1143 }
1144
1145 pub fn wrap_request(&self, pdu: v2::Pdus, context_name: &[u8]) -> Result<Vec<u8>> {
1147 let (msg_id, bytes) = self.wrap(pdu, context_name)?;
1148 let mut ids = self
1149 .outstanding
1150 .lock()
1151 .map_err(|_| anyhow::anyhow!("lock"))?;
1152 if ids.len() == OUTSTANDING_MESSAGES {
1153 ids.pop_front();
1154 }
1155 ids.push_back(msg_id);
1156 Ok(bytes)
1157 }
1158
1159 pub fn unwrap_reply(&self, data: &[u8]) -> Result<Reply> {
1161 let msg_id = decode(data)?.msg_id;
1162 let known = self
1163 .outstanding
1164 .lock()
1165 .map_err(|_| anyhow::anyhow!("lock"))?
1166 .contains(&msg_id);
1167 ensure!(
1168 known,
1169 "a reply to message {msg_id}, which this client did not send"
1170 );
1171 let reply = self.unwrap(data, msg_id)?;
1172 if let Ok(mut ids) = self.outstanding.lock() {
1173 ids.retain(|id| *id != msg_id);
1174 }
1175 Ok(reply)
1176 }
1177
1178 pub fn engine_id(&self) -> Option<Vec<u8>> {
1179 self.engine.lock().ok()?.as_ref().map(|e| e.id.clone())
1180 }
1181
1182 fn next_msg_id(&self) -> i64 {
1183 (self.msg_id.fetch_add(1, Ordering::Relaxed) & 0x7fff_ffff) as i64
1184 }
1185
1186 pub fn discovery(&self) -> Result<(i64, Vec<u8>)> {
1188 let msg_id = self.next_msg_id();
1189 let bytes = encode(Outgoing {
1190 msg_id,
1191 max_size: MAX_MESSAGE_SIZE,
1192 flags: FLAG_REPORTABLE,
1193 engine_id: b"",
1194 boots: 0,
1195 time: 0,
1196 user: b"",
1197 keys: None,
1198 salt: 0,
1199 context_engine_id: b"",
1200 context_name: b"",
1201 pdu: v2::Pdus::GetRequest(v2::GetRequest(v2::Pdu {
1202 request_id: rand::random::<i32>() & 0x7fff_ffff,
1203 error_status: 0,
1204 error_index: 0,
1205 variable_bindings: vec![],
1206 })),
1207 })?;
1208 Ok((msg_id, bytes))
1209 }
1210
1211 pub fn learn(&self, data: &[u8], msg_id: i64) -> Result<()> {
1213 let rx = decode(data)?;
1214 ensure!(
1215 rx.msg_id == msg_id,
1216 "the discovery reply answers another message"
1217 );
1218 let id = rx.usm.authoritative_engine_id.to_vec();
1219 ensure!(
1220 (MIN_ENGINE_ID_BYTES..=MAX_ENGINE_ID_BYTES).contains(&id.len()),
1221 "the agent reported a {}-byte engine ID",
1222 id.len()
1223 );
1224 let (boots, time) = engine_clock(&rx)?;
1227 let keys = self.user.localize(&id);
1228 *self
1229 .engine
1230 .lock()
1231 .map_err(|_| anyhow::anyhow!("engine lock"))? = Some(Engine {
1232 id,
1233 boots,
1234 time,
1235 at: crate::utils::clock::Instant::now(),
1236 keys,
1237 });
1238 Ok(())
1239 }
1240
1241 pub fn wrap(&self, pdu: v2::Pdus, context_name: &[u8]) -> Result<(i64, Vec<u8>)> {
1243 let engine = self
1244 .engine
1245 .lock()
1246 .map_err(|_| anyhow::anyhow!("engine lock"))?
1247 .clone()
1248 .context("the agent's engine has not been discovered")?;
1249 let level = self.user.level();
1250 let msg_id = self.next_msg_id();
1251 let bytes = encode(Outgoing {
1252 msg_id,
1253 max_size: MAX_MESSAGE_SIZE,
1254 flags: level.flags() | FLAG_REPORTABLE,
1255 engine_id: &engine.id,
1256 boots: engine.boots,
1257 time: engine
1258 .time
1259 .saturating_add(i64::try_from(engine.at.elapsed().as_secs()).unwrap_or(i64::MAX))
1260 .min(MAX_ENGINE_TIME),
1261 user: self.user.name.as_bytes(),
1262 keys: (level != SecurityLevel::NoAuthNoPriv).then_some(&engine.keys),
1263 salt: self.salt.fetch_add(1, Ordering::Relaxed),
1264 context_engine_id: &engine.id,
1265 context_name,
1266 pdu,
1267 })?;
1268 Ok((msg_id, bytes))
1269 }
1270
1271 pub fn unwrap(&self, data: &[u8], msg_id: i64) -> Result<Reply> {
1274 let rx = decode(data)?;
1275 ensure!(
1276 rx.msg_id == msg_id,
1277 "a reply to message {} while {} is outstanding",
1278 rx.msg_id,
1279 msg_id
1280 );
1281 let mut guard = self
1282 .engine
1283 .lock()
1284 .map_err(|_| anyhow::anyhow!("engine lock"))?;
1285 let engine = guard.as_mut().context("no engine discovered")?;
1286 ensure!(
1287 rx.usm.authoritative_engine_id.as_ref() == engine.id.as_slice(),
1288 "a reply from engine {} while talking to {}",
1289 hex::encode(&rx.usm.authoritative_engine_id),
1290 hex::encode(&engine.id)
1291 );
1292 let level = SecurityLevel::from_flags(rx.flags)?;
1293 let authenticated = level != SecurityLevel::NoAuthNoPriv;
1294 if authenticated {
1295 ensure!(
1296 verify(data, &engine.keys),
1297 "the reply's MAC does not verify"
1298 );
1299 }
1300 let scoped = scoped_pdu(
1301 &rx,
1302 (level == SecurityLevel::AuthPriv).then_some(&engine.keys),
1303 )?;
1304 if let v2::Pdus::Report(r) = &scoped.data {
1305 if authenticated {
1306 let (boots, time) = engine_clock(&rx)?;
1307 engine.boots = boots;
1308 engine.time = time;
1309 engine.at = crate::utils::clock::Instant::now();
1310 }
1311 let vb =
1312 r.0.variable_bindings
1313 .first()
1314 .context("a report with no varbind")?;
1315 let kind = ReportKind::from_oid(&vb.name.to_vec()).with_context(|| {
1316 format!(
1317 "a report naming {}",
1318 vb.name
1319 .iter()
1320 .map(u32::to_string)
1321 .collect::<Vec<_>>()
1322 .join(".")
1323 )
1324 })?;
1325 let count = match &vb.value {
1326 v2::VarBindValue::Value(rasn_smi::v2::ObjectSyntax::ApplicationWide(
1327 rasn_smi::v2::ApplicationSyntax::Counter(c),
1328 )) => c.0 as u64,
1329 _ => 0,
1330 };
1331 return Ok(Reply::Report(kind, count));
1332 }
1333 ensure!(
1335 level == self.user.level(),
1336 "the reply is {}, the request was {}",
1337 level.name(),
1338 self.user.level().name()
1339 );
1340 Ok(Reply::Pdu(scoped.data))
1341 }
1342}