use std::net::SocketAddr;
use bytes::Bytes;
use crate::error::internal::{AuthErrorKind, CryptoErrorKind, EncodeErrorKind};
use crate::error::{Error, Result};
use crate::message::{MsgFlags, MsgGlobalData, ScopedPdu, SecurityLevel, V3Message, V3MessageData};
use crate::notification::{DerivedKeys, UsmConfig};
use crate::oid::Oid;
use crate::pdu::{Pdu, PduType};
use crate::v3::auth::authenticate_message;
use crate::v3::{LocalizedKey, SaltCounter, UsmSecurityParams};
use crate::value::Value;
use crate::varbind::VarBind;
#[allow(clippy::too_many_arguments)]
pub fn encode_v3_message(
pdu: &Pdu,
msg_id: i32,
engine_id: &[u8],
engine_boots: u32,
engine_time: u32,
security: &UsmConfig,
derived_keys: Option<&DerivedKeys>,
salt_counter: &SaltCounter,
reportable: bool,
msg_max_size: u32,
) -> Result<Vec<u8>> {
let security_level = security.security_level();
let scoped_pdu = ScopedPdu::new(
Bytes::copy_from_slice(engine_id),
security.context_name.clone(),
pdu.clone(),
);
let (msg_data, priv_params) = if security_level.requires_priv() {
let priv_key = derived_keys
.and_then(|d| d.priv_key.as_ref())
.ok_or_else(|| Error::Config("privacy key not available".into()).boxed())?;
let scoped_pdu_bytes = scoped_pdu.encode_to_bytes();
let (ciphertext, salt) = priv_key
.encrypt(
&scoped_pdu_bytes,
engine_boots,
engine_time,
Some(salt_counter),
)
.map_err(|e| Error::Config(e.to_string().into()).boxed())?;
(V3MessageData::Encrypted(ciphertext), salt)
} else {
(V3MessageData::Plaintext(scoped_pdu), Bytes::new())
};
let auth_key = if security_level.requires_auth() {
Some(
derived_keys
.and_then(|d| d.auth_key.as_ref())
.ok_or_else(|| Error::Config("auth key not available".into()).boxed())?,
)
} else {
None
};
let mut usm_params = UsmSecurityParams::new(
Bytes::copy_from_slice(engine_id),
engine_boots,
engine_time,
security.username.clone(),
);
if let Some(key) = &auth_key {
usm_params = usm_params.with_auth_placeholder(key.mac_len());
}
if security_level.requires_priv() {
usm_params = usm_params.with_priv_params(priv_params);
}
let usm_encoded = usm_params.encode();
let msg_flags = MsgFlags::new(security_level, reportable);
let global_data = MsgGlobalData::new(msg_id, msg_max_size as i32, msg_flags);
let msg = match msg_data {
V3MessageData::Plaintext(scoped_pdu) => {
V3Message::new(global_data, usm_encoded, scoped_pdu)
}
V3MessageData::Encrypted(ciphertext) => {
V3Message::new_encrypted(global_data, usm_encoded, ciphertext)
}
};
let mut encoded = msg.encode().to_vec();
if let Some(key) = &auth_key {
if let Some((offset, len)) = UsmSecurityParams::find_auth_params_offset(&encoded) {
authenticate_message(key, &mut encoded, offset, len)
.map_err(|e| Error::Config(e.to_string().into()).boxed())?;
} else {
return Err(Error::Config("could not find auth params position".into()).boxed());
}
}
Ok(encoded)
}
pub(crate) fn sign_v3_message(
auth_key: &LocalizedKey,
message: &mut [u8],
target: SocketAddr,
) -> Result<()> {
let (auth_offset, auth_len) =
UsmSecurityParams::find_auth_params_offset(message).ok_or_else(|| {
tracing::debug!(target: "async_snmp::v3", { kind = %EncodeErrorKind::MissingAuthParams }, "could not find auth params in outgoing V3 message");
Error::MalformedResponse { target }.boxed()
})?;
authenticate_message(auth_key, message, auth_offset, auth_len).map_err(|e| {
tracing::debug!(target: "async_snmp::v3", { error = %e }, "failed to authenticate outgoing V3 message");
Error::Config(e.to_string().into()).boxed()
})
}
pub(crate) fn encode_v3_report(
msg_id: i32,
msg_max_size: i32,
usm: UsmSecurityParams,
report_oid: Oid,
counter_value: u32,
auth_key: Option<&LocalizedKey>,
target: SocketAddr,
) -> Result<Bytes> {
let report_pdu = Pdu {
pdu_type: PduType::Report,
request_id: 0,
error_status: 0,
error_index: 0,
varbinds: vec![VarBind::new(report_oid, Value::Counter32(counter_value))],
};
let security_level = if auth_key.is_some() {
SecurityLevel::AuthNoPriv
} else {
SecurityLevel::NoAuthNoPriv
};
let global = MsgGlobalData::new(msg_id, msg_max_size, MsgFlags::new(security_level, false));
let scoped = ScopedPdu::new(usm.engine_id.clone(), Bytes::new(), report_pdu);
let usm = match auth_key {
Some(key) => usm.with_auth_placeholder(key.mac_len()),
None => usm,
};
let msg = V3Message::new(global, usm.encode(), scoped);
match auth_key {
Some(key) => {
let mut bytes = msg.encode().to_vec();
sign_v3_message(key, &mut bytes, target)?;
Ok(Bytes::from(bytes))
}
None => Ok(msg.encode()),
}
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn encode_v3_response(
response_pdu: Pdu,
msg_id: i32,
msg_max_size: i32,
security_level: SecurityLevel,
usm: UsmSecurityParams,
context_engine_id: Bytes,
context_name: Bytes,
derived_keys: Option<&DerivedKeys>,
salt_counter: &SaltCounter,
target: SocketAddr,
) -> Result<Bytes> {
let global = MsgGlobalData::new(msg_id, msg_max_size, MsgFlags::new(security_level, false));
let scoped = ScopedPdu::new(context_engine_id, context_name, response_pdu);
match security_level {
SecurityLevel::NoAuthNoPriv => Ok(V3Message::new(global, usm.encode(), scoped).encode()),
SecurityLevel::AuthNoPriv => {
let (_, auth_key) = require_auth_key(derived_keys, target)?;
let usm = usm.with_auth_placeholder(auth_key.mac_len());
let mut bytes = V3Message::new(global, usm.encode(), scoped)
.encode()
.to_vec();
sign_v3_message(auth_key, &mut bytes, target)?;
Ok(Bytes::from(bytes))
}
SecurityLevel::AuthPriv => {
let (keys, auth_key) = require_auth_key(derived_keys, target)?;
let priv_key = keys.priv_key.as_ref().ok_or_else(|| {
tracing::debug!(target: "async_snmp::v3", { kind = %CryptoErrorKind::NoPrivKey }, "no privacy key for response");
Error::Auth { target }.boxed()
})?;
let scoped_pdu_bytes = scoped.encode_to_bytes();
let (encrypted, priv_params) = priv_key
.encrypt(
&scoped_pdu_bytes,
usm.engine_boots,
usm.engine_time,
Some(salt_counter),
)
.map_err(|e| {
tracing::debug!(target: "async_snmp::v3", { error = %e }, "encryption failed for response");
Error::Auth { target }.boxed()
})?;
let usm = usm
.with_auth_placeholder(auth_key.mac_len())
.with_priv_params(priv_params);
let mut bytes = V3Message::new_encrypted(global, usm.encode(), encrypted)
.encode()
.to_vec();
sign_v3_message(auth_key, &mut bytes, target)?;
Ok(Bytes::from(bytes))
}
}
}
fn require_auth_key(
derived_keys: Option<&DerivedKeys>,
target: SocketAddr,
) -> Result<(&DerivedKeys, &LocalizedKey)> {
let keys = derived_keys.ok_or_else(|| {
tracing::debug!(target: "async_snmp::v3", { kind = %AuthErrorKind::NoCredentials }, "no credentials for response");
Error::Auth { target }.boxed()
})?;
let auth_key = keys.auth_key.as_ref().ok_or_else(|| {
tracing::debug!(target: "async_snmp::v3", { kind = %AuthErrorKind::NoAuthKey }, "no auth key for response");
Error::Auth { target }.boxed()
})?;
Ok((keys, auth_key))
}