async_snmp/notification/
types.rs1use bytes::Bytes;
7
8use crate::message::SecurityLevel;
9use crate::v3::{AuthProtocol, LocalizedKey, PrivKey, PrivProtocol};
10
11#[derive(Clone)]
23pub struct UsmConfig {
24 pub username: Bytes,
26 pub auth: Option<(AuthProtocol, Vec<u8>)>,
28 pub privacy: Option<(PrivProtocol, Vec<u8>)>,
30 pub context_name: Bytes,
32 pub master_keys: Option<crate::v3::MasterKeys>,
34}
35
36impl UsmConfig {
37 pub fn new(username: impl Into<Bytes>) -> Self {
39 Self {
40 username: username.into(),
41 auth: None,
42 privacy: None,
43 context_name: Bytes::new(),
44 master_keys: None,
45 }
46 }
47
48 #[must_use]
50 pub fn auth(mut self, protocol: AuthProtocol, password: impl AsRef<[u8]>) -> Self {
51 self.auth = Some((protocol, password.as_ref().to_vec()));
52 self
53 }
54
55 #[must_use]
57 pub fn privacy(mut self, protocol: PrivProtocol, password: impl AsRef<[u8]>) -> Self {
58 self.privacy = Some((protocol, password.as_ref().to_vec()));
59 self
60 }
61
62 #[must_use]
64 pub fn context_name(mut self, context_name: impl Into<Bytes>) -> Self {
65 self.context_name = context_name.into();
66 self
67 }
68
69 #[must_use]
75 pub fn with_master_keys(mut self, master_keys: crate::v3::MasterKeys) -> Self {
76 self.master_keys = Some(master_keys);
77 self
78 }
79
80 pub fn security_level(&self) -> SecurityLevel {
82 if let Some(ref master_keys) = self.master_keys {
84 if master_keys.priv_protocol().is_some() {
85 return SecurityLevel::AuthPriv;
86 }
87 return SecurityLevel::AuthNoPriv;
88 }
89
90 match (&self.auth, &self.privacy) {
91 (None, _) => SecurityLevel::NoAuthNoPriv,
92 (Some(_), None) => SecurityLevel::AuthNoPriv,
93 (Some(_), Some(_)) => SecurityLevel::AuthPriv,
94 }
95 }
96
97 pub fn derive_keys(&self, engine_id: &[u8]) -> crate::v3::CryptoResult<DerivedKeys> {
103 if let Some(ref master_keys) = self.master_keys {
105 tracing::trace!(target: "async_snmp::client", { engine_id_len = engine_id.len(), auth_protocol = ?master_keys.auth_protocol(), priv_protocol = ?master_keys.priv_protocol() }, "localizing from cached master keys");
106 let (auth_key, priv_key) = master_keys.localize(engine_id)?;
107 tracing::trace!(target: "async_snmp::client", "key localization complete");
108 return Ok(DerivedKeys {
109 auth_key: Some(auth_key),
110 priv_key,
111 });
112 }
113
114 tracing::trace!(target: "async_snmp::client", { engine_id_len = engine_id.len(), has_auth = self.auth.is_some(), has_priv = self.privacy.is_some() }, "deriving localized keys from passwords");
116
117 let auth_key = self.auth.as_ref().map(|(protocol, password)| {
118 tracing::trace!(target: "async_snmp::client", { auth_protocol = ?protocol }, "deriving auth key");
119 LocalizedKey::from_password(*protocol, password, engine_id)
120 }).transpose()?;
121
122 let priv_key = match (&self.auth, &self.privacy) {
123 (Some((auth_protocol, _)), Some((priv_protocol, priv_password))) => {
124 tracing::trace!(target: "async_snmp::client", { priv_protocol = ?priv_protocol }, "deriving privacy key");
125 Some(PrivKey::from_password(
126 *auth_protocol,
127 *priv_protocol,
128 priv_password,
129 engine_id,
130 )?)
131 }
132 _ => None,
133 };
134
135 tracing::trace!(target: "async_snmp::client", "key derivation complete");
136 Ok(DerivedKeys { auth_key, priv_key })
137 }
138}
139
140impl std::fmt::Debug for UsmConfig {
141 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
142 f.debug_struct("UsmConfig")
143 .field("username", &String::from_utf8_lossy(&self.username))
144 .field("auth", &self.auth.as_ref().map(|(p, _)| p))
145 .field("privacy", &self.privacy.as_ref().map(|(p, _)| p))
146 .field("context_name", &String::from_utf8_lossy(&self.context_name))
147 .field(
148 "master_keys",
149 &self.master_keys.as_ref().map(|mk| {
150 format!(
151 "MasterKeys({:?}, {:?})",
152 mk.auth_protocol(),
153 mk.priv_protocol()
154 )
155 }),
156 )
157 .finish()
158 }
159}
160
161#[derive(Debug)]
165pub struct DerivedKeys {
166 pub auth_key: Option<LocalizedKey>,
168 pub priv_key: Option<PrivKey>,
170}
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175
176 #[test]
177 fn test_usm_user_config_no_auth() {
178 let config = UsmConfig::new(Bytes::from_static(b"testuser"));
179 assert_eq!(config.security_level(), SecurityLevel::NoAuthNoPriv);
180 assert!(config.auth.is_none());
181 assert!(config.privacy.is_none());
182 }
183
184 #[test]
185 fn test_usm_user_config_auth_only() {
186 let config = UsmConfig::new(Bytes::from_static(b"testuser"))
187 .auth(AuthProtocol::Sha1, b"password123");
188 assert_eq!(config.security_level(), SecurityLevel::AuthNoPriv);
189 assert!(config.auth.is_some());
190 assert!(config.privacy.is_none());
191 assert!(config.context_name.is_empty());
192 }
193
194 #[test]
195 fn test_usm_user_config_auth_priv() {
196 let config = UsmConfig::new(Bytes::from_static(b"testuser"))
197 .auth(AuthProtocol::Sha256, b"authpass")
198 .privacy(PrivProtocol::Aes128, b"privpass");
199 assert_eq!(config.security_level(), SecurityLevel::AuthPriv);
200 assert!(config.auth.is_some());
201 assert!(config.privacy.is_some());
202 }
203
204 #[test]
205 fn test_usm_user_config_context_name() {
206 let config = UsmConfig::new(Bytes::from_static(b"testuser")).context_name("ctx");
207 assert_eq!(config.context_name.as_ref(), b"ctx");
208 }
209
210 #[test]
211 fn test_usm_user_config_derive_keys() {
212 let config = UsmConfig::new(Bytes::from_static(b"testuser"))
213 .auth(AuthProtocol::Sha1, b"password123");
214
215 let engine_id = b"test-engine-id";
216 let keys = config.derive_keys(engine_id).unwrap();
217
218 assert!(keys.auth_key.is_some());
219 assert!(keys.priv_key.is_none());
220 }
221
222 #[test]
223 fn test_usm_user_config_derive_keys_with_privacy() {
224 let config = UsmConfig::new(Bytes::from_static(b"testuser"))
225 .auth(AuthProtocol::Sha256, b"authpass")
226 .privacy(PrivProtocol::Aes128, b"privpass");
227
228 let engine_id = b"test-engine-id";
229 let keys = config.derive_keys(engine_id).unwrap();
230
231 assert!(keys.auth_key.is_some());
232 assert!(keys.priv_key.is_some());
233 }
234}