Skip to content

Commit

Permalink
SNMPv3 privacy mode
Browse files Browse the repository at this point in the history
  • Loading branch information
dvolodin7 committed Jan 19, 2024
1 parent c4b7a5c commit 9dd665b
Show file tree
Hide file tree
Showing 32 changed files with 884 additions and 411 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ To see unreleased changes, please see the [CHANGELOG on the main branch guide](h
## [Unreleased]

### Added
* Basic SNMPv3 support (plaintext, no auth).
* SNMPv3 support.
* Snmpd.engine_id property.
* Tests for all SNMP versions.

Expand Down
9 changes: 8 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,14 @@ name = "gufo_snmp"
strip = "debuginfo"

[dependencies]
md5 = "0.7"
aes = "0.8"
cbc = "0.1"
cfb-mode = "0.8"
cipher = "0.4"
des = "0.8"
digest = "0.10"
enum_dispatch = "0.3"
md-5 = "0.10"
nom = "7.1"
pyo3 = {version = "0.20", features = ["extension-module"]}
rand = "0.8"
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ async with Snmpd(), SnmpSession(addr="127.0.0.1", port=10161) as session:
* SNMP v1/v2c/v3 support.
* SNMP v3 User Security Model:
* Authentication: HMAC-MD5-96, HMAC-SHA-96.
* Privacy: work in progress.
* Privacy: DES, AES128.
* High-performance.
* Zero-copy BER parsing.
* Full Python typing support.
Expand Down
2 changes: 1 addition & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ async with Snmpd(), SnmpSession(addr="127.0.0.1", port=10161) as session:
* SNMP v1/v2c/v3 support.
* SNMP v3 User Security Model:
* Authentication: HMAC-MD5-96, HMAC-SHA-96.
* Privacy: work in progress.
* Privacy: DES, AES128.
* High-performance.
* Zero-copy BER parsing.
* Full Python typing support.
Expand Down
18 changes: 10 additions & 8 deletions docs/standards.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Gufo SNMP implements and is guided by the following standards:
* [RFC-3411][RFC-3411]: An Architecture for Describing Simple Network Management Protocol (SNMP) Management Frameworks.
* [RFC-3412][RFC-3412]: Message Processing and Dispatching for the Simple Network Management Protocol (SNMP)
* [RFC-3414][RFC-3414]: User-based Security Model (USM) for version 3 of the Simple Network Management Protocol (SNMPv3)
* [RFC-3826][RFC-3826]: The Advanced Encryption Standard (AES) Cipher Algorithm in the SNMP User-based Security Model

## ITU-T

Expand All @@ -23,14 +24,15 @@ Gufo SNMP implements and is guided by the following standards:
* [PEP484][PEP484]: Type Hints
* [PEP561][PEP561]: Distributing and Packaging Type Information.

[RFC-1157]: https://datatracker.ietf.org/doc/html/rfc1157
[RFC-1441]: https://datatracker.ietf.org/doc/html/rfc1441
[RFC-1442]: https://datatracker.ietf.org/doc/html/rfc1442
[RFC-1905]: https://datatracker.ietf.org/doc/html/rfc1905
[RFC-2578]: https://datatracker.ietf.org/doc/html/rfc2578
[RFC-3411]: https://datatracker.ietf.org/doc/html/rfc3411
[RFC-3412]: https://datatracker.ietf.org/doc/html/rfc3412
[RFC-3414]: https://datatracker.ietf.org/doc/html/rfc3414
[RFC-1157]: https://www.rfc-editor.org/rfc/rfc1157.html
[RFC-1441]: https://www.rfc-editor.org/rfc/rfc1441.html
[RFC-1442]: https://www.rfc-editor.org/rfc/rfc1442.html
[RFC-1905]: https://www.rfc-editor.org/rfc/rfc1905.html
[RFC-2578]: https://www.rfc-editor.org/rfc/rfc2578.html
[RFC-3411]: https://www.rfc-editor.org/rfc/rfc3411.html
[RFC-3412]: https://www.rfc-editor.org/rfc/rfc3412.html
[RFC-3414]: https://www.rfc-editor.org/rfc/rfc3414.html
[RFC-3826]: https://www.rfc-editor.org/rfc/rfc3826.html
[PEP8]: https://peps.python.org/pep-0008/
[PEP484]: https://peps.python.org/pep-0484/
[PEP561]: https://peps.python.org/pep-0561/
Expand Down
123 changes: 123 additions & 0 deletions src/auth/digest.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// ------------------------------------------------------------------------
// Gufo SNMP: Generic HMAC implementation
// ------------------------------------------------------------------------
// Copyright (C) 2023-24, Gufo Labs
// See LICENSE.md for details
// ------------------------------------------------------------------------
use super::SnmpAuth;
use crate::ber::TAG_OCTET_STRING;
use digest::Digest;
use std::marker::PhantomData;

// KS - key size
// SS - signature size
pub struct DigestAuth<D: Digest, const KS: usize, const SS: usize> {
key: [u8; KS],
_pd: PhantomData<D>,
}

impl<D: Digest, const KS: usize, const SS: usize> Default for DigestAuth<D, KS, SS> {
fn default() -> Self {
let key = [0; KS];
Self {
key,
_pd: Default::default(),
}
}
}

const PADDED_LENGTH: usize = 64;
const ZEROES: [u8; PADDED_LENGTH] = [0; PADDED_LENGTH];
const IPAD_VALUE: u8 = 0x36;
const OPAD_VALUE: u8 = 0x5c;
const IPAD_MASK: [u8; PADDED_LENGTH] = [IPAD_VALUE; PADDED_LENGTH];
const OPAD_MASK: [u8; PADDED_LENGTH] = [OPAD_VALUE; PADDED_LENGTH];

impl<D: Digest, const KS: usize, const SS: usize> SnmpAuth for DigestAuth<D, KS, SS> {
fn from_localized(&mut self, key: &[u8]) {
self.key.clone_from_slice(key);
}
fn from_master(&mut self, key: &[u8], locality: &[u8]) {
let mut out = [0; KS];
self.localize(key, locality, &mut out);
self.key.clone_from_slice(&out);
}
fn localize(&self, key: &[u8], locality: &[u8], out: &mut [u8]) {
let mut hasher = D::new();
hasher.update(key);
hasher.update(locality);
hasher.update(key);
let digest = hasher.finalize();
out.clone_from_slice(&digest[..out.len()]);
}
fn has_auth(&self) -> bool {
true
}
fn placeholder(&self) -> &'static [u8] {
&ZEROES[..SS]
}
fn find_placeholder_offset(&self, whole_msg: &[u8]) -> Option<usize> {
let max_offset = whole_msg.len() - 2 - SS;
let mut offset = 0;
while offset < max_offset {
// 4 + len + 0
if whole_msg[offset] == TAG_OCTET_STRING
&& whole_msg[offset + 1] == SS as u8
&& whole_msg[offset + 2] == 0
{
let r = offset + 2;
offset += 3;
let mut zeroes_left = SS - 1;
while offset < max_offset && whole_msg[offset] == 0 && zeroes_left > 0 {
offset += 1;
zeroes_left -= 1;
}
if zeroes_left == 0 {
return Some(r);
}
} else {
offset += 1;
}
}
None
}
fn sign_and_update(&self, whole_msg: &mut [u8], offset: usize) {
let rest_len = PADDED_LENGTH - KS;
let mut ctx1 = D::new();
// RFC-3414, pp. 6.3.1. Processing an outgoing message
// a) extend the authKey to 64 octets by appending 48 zero octets;
// save it as extendedAuthKey
// >>> Really not necessary
// b) obtain IPAD by replicating the octet 0x36 64 times;
// >>> need only rest
// c) obtain K1 by XORing extendedAuthKey with IPAD;
// 3) Prepend K1 to the wholeMsg and calculate MD5 digest over it according to [RFC1321].
// Instead:
// * append xored key
let k1: Vec<u8> = self.key.iter().map(|&x| x ^ IPAD_VALUE).collect();
ctx1.update(k1);
// * append precalculated rest of IPAD
ctx1.update(&IPAD_MASK[..rest_len]);
// * append whole message
ctx1.update(&whole_msg);
// get MD5
let d1 = ctx1.finalize();
// d) obtain OPAD by replicating the octet 0x5C 64 times;
// >>> Really not necessary
// e) obtain K2 by XORing extendedAuthKey with OPAD.
// 4) Prepend K2 to the result of the step 4 and calculate MD5 digest
// over it according to [RFC1321]. Take the first 12 octets of the
// final digest - this is Message Authentication Code (MAC).
// Instead:
// * append xored key
let mut ctx2 = D::new();
let k2: Vec<u8> = self.key.iter().map(|&x| x ^ OPAD_VALUE).collect();
ctx2.update(k2);
// * append precalculated rest of OPAD
ctx2.update(&OPAD_MASK[..rest_len]);
// * append previous digest
ctx2.update(&d1[..KS]);
let d2 = ctx2.finalize();
whole_msg[offset..offset + SS].copy_from_slice(&d2[0..SS]);
}
}
101 changes: 0 additions & 101 deletions src/auth/md5.rs

This file was deleted.

Loading

0 comments on commit 9dd665b

Please sign in to comment.