org.snmp4j.security.USM Maven / Gradle / Ivy
/*_############################################################################
_##
_## SNMP4J - USM.java
_##
_## Copyright (C) 2003-2024 Frank Fock (SNMP4J.org)
_##
_## Licensed under the Apache License, Version 2.0 (the "License");
_## you may not use this file except in compliance with the License.
_## You may obtain a copy of the License at
_##
_## http://www.apache.org/licenses/LICENSE-2.0
_##
_## Unless required by applicable law or agreed to in writing, software
_## distributed under the License is distributed on an "AS IS" BASIS,
_## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
_## See the License for the specific language governing permissions and
_## limitations under the License.
_##
_##########################################################################*/
package org.snmp4j.security;
import org.snmp4j.SNMP4JSettings;
import org.snmp4j.TransportStateReference;
import org.snmp4j.UserTarget;
import org.snmp4j.asn1.BER;
import org.snmp4j.asn1.BER.MutableByte;
import org.snmp4j.asn1.BERInputStream;
import org.snmp4j.asn1.BEROutputStream;
import org.snmp4j.event.CounterEvent;
import org.snmp4j.event.UsmUserEvent;
import org.snmp4j.event.UsmUserListener;
import org.snmp4j.log.LogAdapter;
import org.snmp4j.log.LogFactory;
import org.snmp4j.mp.CounterSupport;
import org.snmp4j.mp.MPv3;
import org.snmp4j.mp.SnmpConstants;
import org.snmp4j.mp.StatusInformation;
import org.snmp4j.smi.Integer32;
import org.snmp4j.smi.OID;
import org.snmp4j.smi.OctetString;
import org.snmp4j.smi.VariableBinding;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* The {@code USM} class implements the User Based Security Model (USM) as defined in RFC 3414.
*
* When a user is added or removed from the USM, a {@link UsmUserEvent} is fired and forwarded to registered listeners.
*
* @author Frank Fock
* @version 2.0
*/
public class USM extends SNMPv3SecurityModel {
public static final int RFC3414_11_2_MIN_PASSWORD_LENGTH = 8;
private static final int MAXLEN_USMUSERNAME = 32;
private static final LogAdapter logger = LogFactory.getLogger(USM.class);
// Table containing localized and non-localized users
private UsmUserTable userTable;
private UsmTimeTable timeTable;
private boolean engineDiscoveryEnabled = true;
private SecurityProtocols securityProtocols;
private final transient List usmUserListeners = new CopyOnWriteArrayList<>();
/**
* Creates a USM with the support for the supplied security protocols.
*
* @param securityProtocols
* the security protocols to support.
* @param localEngineID
* the local engine ID.
* @param engineBoots
* the number of engine boots.
*
* @since 1.2
*/
public USM(SecurityProtocols securityProtocols, OctetString localEngineID, int engineBoots) {
this(securityProtocols, localEngineID, engineBoots, CounterSupport.getInstance());
}
/**
* Creates a USM with the support for the supplied security protocols.
*
* @param securityProtocols
* the security protocols to support.
* @param localEngineID
* the local engine ID.
* @param engineBoots
* the number of engine boots.
* @param counterSupport
* the {@link CounterSupport} instance to be used for counting events.
*
* @since 3.5.0
*/
public USM(SecurityProtocols securityProtocols, OctetString localEngineID, int engineBoots,
CounterSupport counterSupport) {
this.localEngineID = localEngineID;
timeTable = new UsmTimeTable(localEngineID, engineBoots);
userTable = new UsmUserTable();
this.securityProtocols = securityProtocols;
this.counterSupport = counterSupport;
}
/**
* Default constructor with random engine ID with the default enterprise ID and a zero engineBoots counter. The
* security protocols instance defined by {@link org.snmp4j.security.SecurityProtocols#getInstance()} with the
* default protocols is used.
*
* @since 2.2.4
*/
public USM() {
this(SecurityProtocols.getInstance().addDefaultProtocols(),
new OctetString(MPv3.createLocalEngineID(randomID())), 0);
}
private static OctetString randomID() {
Random random = new Random();
byte[] randomID = new byte[8];
random.nextBytes(randomID);
return new SecretOctetString(randomID);
}
public int getID() {
return SECURITY_MODEL_USM;
}
@Override
public boolean supportsEngineIdDiscovery() {
return true;
}
@Override
public boolean hasAuthoritativeEngineID() {
return true;
}
/**
* Sets the local engine ID, number of boots, and time after boot.
*
* @param localEngineID
* the local engine ID.
* @param engineBoots
* the number of engine boots.
* @param engineTime
* the number sendonds since the last boot.
*/
public void setLocalEngine(OctetString localEngineID,
int engineBoots, int engineTime) {
this.localEngineID = localEngineID;
timeTable.setLocalTime(new UsmTimeEntry(localEngineID, engineBoots,
engineTime));
}
/**
* Sets the number of engine boots.
*
* @param engineBoots
* the number of engine boots.
*/
public void setEngineBoots(int engineBoots) {
this.timeTable.setEngineBoots(engineBoots);
}
/**
* Returns the number of engine boots counted for the local engine ID.
*
* @return the number of engine boots (zero based).
*/
public int getEngineBoots() {
return this.timeTable.getEngineBoots();
}
/**
* Returns the number of seconds since the value of the engineBoots object last changed. When incrementing this
* object's value would cause it to exceed its maximum, engineBoots is incremented as if a re-initialization had
* occurred, and this object's value consequently reverts to zero.
*
* @return a positive integer value denoting the number of seconds since the engineBoots value has been changed.
* @since 1.2
*/
public int getEngineTime() {
return this.timeTable.getEngineTime();
}
public SecurityParameters newSecurityParametersInstance() {
return new UsmSecurityParameters(securityProtocols);
}
public SecurityStateReference newSecurityStateReference() {
return new UsmSecurityStateReference();
}
public int generateRequestMessage(int snmpVersion,
byte[] globalData,
int maxMessageSize,
int securityModel,
byte[] securityEngineID,
byte[] securityName,
int securityLevel,
BERInputStream scopedPDU,
SecurityParameters securityParameters,
BEROutputStream wholeMsg,
TransportStateReference tmStateReference,
SecurityStateReference securityStateReference) throws IOException {
if (!(securityStateReference instanceof UsmSecurityStateReference)) {
securityStateReference = new UsmSecurityStateReference();
}
UsmSecurityStateReference usmSecurityStateReference = (UsmSecurityStateReference) securityStateReference;
if (tmStateReference.getTarget() instanceof UserTarget) {
usmSecurityStateReference.applyTargetSecurityInformation(tmStateReference.getTarget());
}
return generateResponseMessage(snmpVersion,
globalData,
maxMessageSize,
securityModel,
securityEngineID,
securityName,
securityLevel,
scopedPDU,
usmSecurityStateReference,
securityParameters,
wholeMsg);
}
/**
* Checks if the specified user is known by this USM.
*
* @param engineID
* the engineID of the user (may be {@code null} if any target should match).
* @param securityName
* the security name of the user to earch for.
*
* @return {@code true} if the user is either known for the specified engine ID or without a specific engine ID
* (discovery only).
*/
public boolean hasUser(OctetString engineID, OctetString securityName) {
UsmUserEntry entry = userTable.getUser(engineID, securityName);
if (entry == null) {
entry = userTable.getUser(securityName);
if ((entry == null) && (securityName.length() > 0)) {
return false;
}
}
return true;
}
/**
* Looks up a {@link org.snmp4j.security.UsmUserEntry} by an engine ID and security name. If a user exists that is
* not localized for the provided engine ID, it will be localized and then the localized user entry is returned. If
* the provided engine ID has a zero length then an empty {@link org.snmp4j.security.UsmUserEntry} is returned with
* just the provided securityName set.
*
* @param engineID
* an engine ID.
* @param securityName
* a security name.
*
* @return a localized {@link org.snmp4j.security.UsmUserEntry} if the provided engineID's length is greater than
* zero and {@code null} if the securityName cannot be found in the USM.
*/
public UsmUserEntry getUser(OctetString engineID, OctetString securityName) {
return getUser(engineID, securityName, UsmUser.LocalizationGrant.any);
}
/**
* Looks up a {@link org.snmp4j.security.UsmUserEntry} by an engine ID and security name. If a user exists that is
* not localized for the provided engine ID, it will be localized and then the localized user entry is returned if
* the specified requested {@link org.snmp4j.security.UsmUser.LocalizationGrant} is allowed by the found
* {@link UsmUser#getLocalizationGrant()}.
* If the provided engine ID has a zero length then an empty {@link org.snmp4j.security.UsmUserEntry} is returned with
* just the provided securityName set.
*
* @param engineID
* an engine ID.
* @param securityName
* a security name.
* @param requiredLocalizationGrant
* defines the localization grant required to execute a localization on behalf of this operation.
*
* @return a localized {@link org.snmp4j.security.UsmUserEntry} if the provided engineID's length is greater than
* zero and the found {@link UsmUser} provides a localization grant that allows the requested localization.
* Otherwise, return {@code null} if the securityName cannot be found in the USM or localization is not granted.
*
* @since 3.8.0
*/
public UsmUserEntry getUser(OctetString engineID, OctetString securityName,
UsmUser.LocalizationGrant requiredLocalizationGrant) {
if (logger.isDebugEnabled()) {
logger.debug("getUser(engineID=" + engineID.toHexString() +
",securityName=" + securityName.toString() +
",requiredLocalizationGrant="+requiredLocalizationGrant+ ")");
}
UsmUserEntry entry = userTable.getUser(engineID, securityName);
if (entry == null) {
entry = userTable.getUser(securityName);
if ((entry == null) && (securityName.length() > 0)) {
if (logger.isDebugEnabled()) {
logger.debug("USM.getUser - User '" + securityName + "' unknown");
}
return null;
} else {
if ((entry == null) || (engineID == null) || (engineID.length() == 0)) {
// do not add user
entry = new UsmUserEntry();
entry.setUserName(securityName);
entry.setUsmUser(new UsmUser(securityName, null, null, null, null));
return entry;
} else if (entry.getUsmUser().isLocalizationGranted(requiredLocalizationGrant)) {
entry = addLocalizedUsmUserEntry(engineID, securityName, entry);
} else {
return null;
}
}
} else if (engineID != null && engineID.length() > 0) {
// check if localization is needed/granted
if (entry.getUsmUser().isLocalizationGranted(requiredLocalizationGrant)) {
entry = addLocalizedUsmUserEntry(engineID, securityName, entry);
}
}
return entry;
}
protected UsmUserEntry addLocalizedUsmUserEntry(OctetString engineID, OctetString securityName, UsmUserEntry entry) {
OID authProtocolOID = entry.getUsmUser().getAuthenticationProtocol();
OID privProtocolOID = entry.getUsmUser().getPrivacyProtocol();
OctetString authPassword = entry.getUsmUser().getAuthenticationPassphrase();
if (authProtocolOID != null && authPassword != null) {
byte[] authKey;
if (entry.getUsmUser().isLocalized()) {
authKey = authPassword.getValue();
} else {
authKey = securityProtocols.passwordToKey(authProtocolOID, authPassword, engineID.getValue());
}
byte[] privKey = null;
OctetString privPassword = entry.getUsmUser().getPrivacyPassphrase();
if (privProtocolOID != null && privPassword != null) {
if (entry.getUsmUser().isLocalized()) {
privKey = privPassword.getValue();
} else {
privKey = securityProtocols.passwordToKey(privProtocolOID,
authProtocolOID,
privPassword,
engineID.getValue());
}
}
else {
privProtocolOID = null;
}
entry = addLocalizedUser(engineID.getValue(), securityName,
authProtocolOID, authKey,
privProtocolOID, privKey);
}
return entry;
}
private static boolean isValidSecurityStateReference(SecurityStateReference securityStateReference) {
return (securityStateReference instanceof UsmSecurityStateReference &&
((UsmSecurityStateReference) securityStateReference).getSecurityName() != null);
}
public int generateResponseMessage(int snmpVersion,
byte[] globalData,
int maxMessageSize,
int securityModel,
byte[] securityEngineID,
byte[] securityName,
int securityLevel,
BERInputStream scopedPDU,
SecurityStateReference securityStateReference,
SecurityParameters securityParameters,
BEROutputStream wholeMsg) throws IOException {
UsmSecurityParameters usmSecurityParams = (UsmSecurityParameters) securityParameters;
if (securityStateReference.isCachedForResponseProcessing() ||
isValidSecurityStateReference(securityStateReference)) {
// this is a response or report or a direct request that does not use USM security LCD:
UsmSecurityStateReference usmSecurityStateReference = (UsmSecurityStateReference) securityStateReference;
if (securityEngineID != null && securityEngineID.length > 0 ||
usmSecurityStateReference.getSecurityEngineID() == null) {
usmSecurityParams.setAuthoritativeEngineID(securityEngineID);
usmSecurityStateReference.setSecurityEngineID(securityEngineID);
} else {
usmSecurityParams.setAuthoritativeEngineID(usmSecurityStateReference.getSecurityEngineID());
}
if (usmSecurityStateReference.getSecurityName() == null) {
OctetString userName = new OctetString(securityName);
usmSecurityStateReference.setSecurityName(userName.getValue());
usmSecurityParams.setUserName(userName);
OctetString secName = getSecurityName(new OctetString(securityEngineID), userName);
if ((secName != null) && (secName.length() <= MAXLEN_USMUSERNAME)) {
usmSecurityParams.setUserName(secName);
}
} else {
usmSecurityParams.setUserName(new OctetString(usmSecurityStateReference.getSecurityName()));
}
usmSecurityParams.setAuthenticationProtocol(usmSecurityStateReference.getAuthenticationProtocol());
usmSecurityParams.setPrivacyProtocol(usmSecurityStateReference.getPrivacyProtocol());
usmSecurityParams.setAuthenticationKey(usmSecurityStateReference.getAuthenticationKey());
usmSecurityParams.setPrivacyKey(usmSecurityStateReference.getPrivacyKey());
} else {
OctetString secEngineID = new OctetString();
if (securityEngineID != null) {
secEngineID.setValue(securityEngineID);
}
OctetString secName = new OctetString(securityName);
UsmUserEntry user = null;
if (secEngineID.length() == 0) {
if (isEngineDiscoveryEnabled()) {
if (hasUser(null, secName)) {
user = new UsmUserEntry();
}
} else {
if (logger.isDebugEnabled()) {
logger.debug("Engine ID unknown and discovery disabled");
}
return SnmpConstants.SNMPv3_USM_UNKNOWN_ENGINEID;
}
} else {
user = getUser(secEngineID, secName, UsmUser.LocalizationGrant.outgoing);
}
if (user == null) {
if (logger.isDebugEnabled()) {
logger.debug("Security name not found for engineID=" +
secEngineID.toHexString() + ", securityName=" +
secName.toHexString());
}
return SnmpConstants.SNMPv3_USM_UNKNOWN_SECURITY_NAME;
}
AuthenticationProtocol auth =
securityProtocols.getAuthenticationProtocol(user.getUsmUser().getAuthenticationProtocol());
PrivacyProtocol priv =
securityProtocols.getPrivacyProtocol(user.getUsmUser().getPrivacyProtocol());
usmSecurityParams.setAuthenticationProtocol(auth);
usmSecurityParams.setPrivacyProtocol(priv);
usmSecurityParams.setAuthenticationKey(user.getAuthenticationKey());
usmSecurityParams.setPrivacyKey(user.getPrivacyKey());
usmSecurityParams.setUserName(user.getUsmUser().getSecurityName());
usmSecurityParams.setAuthoritativeEngineID(secEngineID.getValue());
}
// Check length of userName and engineID
if (usmSecurityParams.getAuthoritativeEngineID().length > MPv3.MAXLEN_ENGINE_ID) {
logger.error("Engine ID too long: " +
usmSecurityParams.getAuthoritativeEngineID().length + ">" +
MPv3.MAXLEN_ENGINE_ID + " for " +
new OctetString(usmSecurityParams.getAuthoritativeEngineID())
.toHexString());
return SnmpConstants.SNMPv3_USM_ENGINE_ID_TOO_LONG;
}
if (securityName.length > MAXLEN_USMUSERNAME) {
logger.error("Security name too long: " +
usmSecurityParams.getAuthoritativeEngineID().length + ">" +
MAXLEN_USMUSERNAME + " for " +
new OctetString(securityName).toHexString());
return SnmpConstants.SNMPv3_USM_SECURITY_NAME_TOO_LONG;
}
if (securityLevel >= SecurityLevel.AUTH_NOPRIV) {
if (securityStateReference.isCachedForResponseProcessing()) {
// request or response
usmSecurityParams.setAuthoritativeEngineBoots(getEngineBoots());
usmSecurityParams.setAuthoritativeEngineTime(getEngineTime());
} else {
// get engineBoots, engineTime
OctetString secEngineID = new OctetString(securityEngineID);
UsmTimeEntry entry = timeTable.getTime(secEngineID);
if (entry == null) {
entry =
new UsmTimeEntry(secEngineID,
usmSecurityParams.getAuthoritativeEngineBoots(),
usmSecurityParams.getAuthoritativeEngineTime());
timeTable.addEntry(entry);
} else {
usmSecurityParams.setAuthoritativeEngineBoots(entry.getEngineBoots());
usmSecurityParams.setAuthoritativeEngineTime(entry.
getLatestReceivedTime());
}
}
}
if ((securityLevel >= SecurityLevel.AUTH_NOPRIV) &&
(usmSecurityParams.getAuthenticationProtocol() == null)) {
return SnmpConstants.SNMPv3_USM_UNSUPPORTED_SECURITY_LEVEL;
}
byte[] scopedPduBytes = buildMessageBuffer(scopedPDU);
if (securityLevel == SecurityLevel.AUTH_PRIV) {
if (usmSecurityParams.getPrivacyProtocol() == null) {
if (logger.isDebugEnabled()) {
logger.debug("Unsupported security level (missing or unsupported privacy protocol): Security params are "
+ usmSecurityParams);
}
return SnmpConstants.SNMPv3_USM_UNSUPPORTED_SECURITY_LEVEL;
}
logger.debug("RFC3414 §3.1.4.a Outgoing message needs to be encrypted");
DecryptParams decryptParams = new DecryptParams();
byte[] encryptedScopedPdu =
usmSecurityParams.getPrivacyProtocol().
encrypt(scopedPduBytes, 0, scopedPduBytes.length,
usmSecurityParams.getPrivacyKey(),
usmSecurityParams.getAuthoritativeEngineBoots(),
usmSecurityParams.getAuthoritativeEngineTime(),
decryptParams);
if (encryptedScopedPdu == null) {
if (logger.isDebugEnabled()) {
logger.debug("Encryption error");
}
return SnmpConstants.SNMPv3_USM_ENCRYPTION_ERROR;
}
usmSecurityParams.setPrivacyParameters(new OctetString(decryptParams.
array));
OctetString encryptedString = new OctetString(encryptedScopedPdu);
BEROutputStream os =
new BEROutputStream(ByteBuffer.allocate(encryptedString.getBERLength()));
encryptedString.encodeBER(os);
scopedPduBytes = os.getBuffer().array();
} else {
logger.debug("RFC3414 §3.1.4.b Outgoing message is not encrypted");
usmSecurityParams.setPrivacyParameters(new OctetString());
}
byte[] wholeMessage;
if (securityLevel >= SecurityLevel.AUTH_NOPRIV) {
/* Build message with authentication */
AuthenticationProtocol authenticationProtocol = usmSecurityParams.getAuthenticationProtocol();
byte[] blank =
new byte[authenticationProtocol.getAuthenticationCodeLength()];
usmSecurityParams.setAuthenticationParameters(new OctetString(blank));
wholeMessage = buildWholeMessage(new Integer32(snmpVersion), scopedPduBytes, globalData, usmSecurityParams);
int authParamsPos =
usmSecurityParams.getAuthParametersPosition() + usmSecurityParams.getSecurityParametersPosition();
boolean authOK = usmSecurityParams.getAuthenticationProtocol().
authenticate(usmSecurityParams.getAuthenticationKey(),
wholeMessage,
0,
wholeMessage.length,
new ByteArrayWindow(wholeMessage,
authParamsPos,
authenticationProtocol.getAuthenticationCodeLength()));
if (!authOK) {
if (logger.isDebugEnabled()) {
logger.debug("Outgoing message could not be authenticated");
}
return SnmpConstants.SNMPv3_USM_AUTHENTICATION_ERROR;
}
} else {
// Set engineBoots and engineTime to zero!
usmSecurityParams.setAuthoritativeEngineBoots(0);
usmSecurityParams.setAuthenticationParameters(new OctetString());
usmSecurityParams.setAuthoritativeEngineTime(0);
//build Message without authentication
wholeMessage =
buildWholeMessage(new Integer32(snmpVersion),
scopedPduBytes, globalData, usmSecurityParams);
}
ByteBuffer buf = ByteBuffer.wrap(wholeMessage).position(wholeMessage.length);
wholeMsg.setBuffer(buf);
// not necessary: wholeMsg.write(wholeMessage);
return SnmpConstants.SNMPv3_USM_OK;
}
private OctetString getSecurityName(OctetString engineID, OctetString securityName) {
if (securityName.length() == 0) {
return securityName;
}
UsmUserEntry user = userTable.getUser(engineID, securityName);
if (user != null) {
return user.getUsmUser().getSecurityName();
} else if (isEngineDiscoveryEnabled()) {
user = userTable.getUser(securityName);
if (user != null) {
return user.getUsmUser().getSecurityName();
}
}
return null;
}
private void updateCachedDataForReport(StatusInformation statusInformation,
int securityLevel,
OID reportID,
UsmSecurityStateReference usmSecurityStateReference,
byte[] securityName) {
usmSecurityStateReference.setSecurityName(securityName);
if (statusInformation != null) {
CounterEvent event = new CounterEvent(this, reportID);
fireIncrementCounter(event);
if (SNMP4JSettings.getReportSecurityLevelStrategy() ==
SNMP4JSettings.ReportSecurityLevelStrategy.noAuthNoPrivIfNeeded) {
statusInformation.setSecurityLevel(new Integer32(SecurityLevel.NOAUTH_NOPRIV));
} else {
statusInformation.setSecurityLevel(new Integer32(securityLevel));
}
statusInformation.setErrorIndication(new VariableBinding(event.getOid(), event.getCurrentValue()));
}
}
public int processIncomingMsg(int snmpVersion, // typically, SNMP version
int maxMessageSize, // of the sending SNMP entity - maxHeaderLength of the MP
SecurityParameters securityParameters, // for the received message
SecurityModel securityModel, // for the received message
int securityLevel, // Level of Security
BERInputStream wholeMsg, // as received on the wire
TransportStateReference tmStateReference,
// output parameters
OctetString securityEngineID, // authoritative SNMP entity
OctetString securityName, // identification of the principal
BEROutputStream scopedPDU, // message (plaintext) payload
Integer32 maxSizeResponseScopedPDU, // maximum size of the Response PDU
SecurityStateReference securityStateReference, // reference to security state information, needed for response
StatusInformation statusInfo
) throws IOException {
UsmSecurityParameters usmSecurityParameters = (UsmSecurityParameters) securityParameters;
UsmSecurityStateReference usmSecurityStateReference = (UsmSecurityStateReference) securityStateReference;
securityEngineID.setValue(usmSecurityParameters.getAuthoritativeEngineID());
byte[] message = buildMessageBuffer(wholeMsg);
if ((securityEngineID.length() == 0) ||
(timeTable.checkEngineID(securityEngineID,
isEngineDiscoveryEnabled(),
usmSecurityParameters.getAuthoritativeEngineBoots(),
usmSecurityParameters.getAuthoritativeEngineTime()) !=
SnmpConstants.SNMPv3_USM_OK)) {
// generate report
if (logger.isDebugEnabled()) {
logger.debug("RFC3414 §3.2.3 Unknown engine ID: '" + securityEngineID.toHexString() + "'");
}
securityEngineID.setValue(usmSecurityParameters.getAuthoritativeEngineID());
securityName.setValue(usmSecurityParameters.getUserName().getValue());
updateCachedDataForReport(statusInfo, securityLevel, SnmpConstants.usmStatsUnknownEngineIDs,
usmSecurityStateReference, securityName.getValue());
return SnmpConstants.SNMPv3_USM_UNKNOWN_ENGINEID;
}
securityName.setValue(usmSecurityParameters.getUserName().getValue());
int scopedPDUPosition = usmSecurityParameters.getScopedPduPosition();
if ((usmSecurityParameters.getUserName().length() > 0) || (securityLevel > SecurityLevel.NOAUTH_NOPRIV)) {
AuthenticationProtocol auth;
PrivacyProtocol priv;
byte[] authKey;
byte[] privKey;
if (usmSecurityStateReference.getSecurityName() != null &&
usmSecurityStateReference.getSecurityEngineID() != null) {
if (!Arrays.equals(usmSecurityStateReference.getSecurityName(), securityName.getValue())) {
reportUnknownSecurityName(securityEngineID, securityName, securityLevel, statusInfo,
usmSecurityStateReference);
return SnmpConstants.SNMPv3_USM_UNKNOWN_SECURITY_NAME;
}
auth = usmSecurityStateReference.getAuthenticationProtocol();
priv = usmSecurityStateReference.getPrivacyProtocol();
authKey = usmSecurityStateReference.getAuthenticationKey();
privKey = usmSecurityStateReference.getPrivacyKey();
} else {
usmSecurityStateReference.setSecurityName(securityName.getValue());
UsmUserEntry user = getUser(securityEngineID, securityName, UsmUser.LocalizationGrant.incoming);
if (user == null) {
reportUnknownSecurityName(securityEngineID, securityName, securityLevel, statusInfo,
usmSecurityStateReference);
return SnmpConstants.SNMPv3_USM_UNKNOWN_SECURITY_NAME;
}
auth = securityProtocols.getAuthenticationProtocol(user.getUsmUser().getAuthenticationProtocol());
priv = securityProtocols.getPrivacyProtocol(user.getUsmUser().getPrivacyProtocol());
authKey = user.getAuthenticationKey();
privKey = user.getPrivacyKey();
usmSecurityStateReference.setAuthenticationKey(user.getAuthenticationKey());
usmSecurityStateReference.setPrivacyKey(user.getPrivacyKey());
usmSecurityStateReference.setAuthenticationProtocol(auth);
usmSecurityStateReference.setPrivacyProtocol(priv);
}
if (((securityLevel >= SecurityLevel.AUTH_NOPRIV) && (auth == null)) ||
(((securityLevel >= SecurityLevel.AUTH_PRIV) && (priv == null)))) {
if (logger.isDebugEnabled()) {
logger.debug("RFC3414 §3.2.5 - Unsupported security level: " +
securityLevel + " by user " + securityName +
" authProtocol=" + auth + ", privProtocol=" + priv);
}
CounterEvent event = new CounterEvent(this, SnmpConstants.usmStatsUnsupportedSecLevels);
fireIncrementCounter(event);
if (SNMP4JSettings.getReportSecurityLevelStrategy() ==
SNMP4JSettings.ReportSecurityLevelStrategy.noAuthNoPrivIfNeeded) {
statusInfo.setSecurityLevel(new Integer32(SecurityLevel.NOAUTH_NOPRIV));
}
statusInfo.setErrorIndication(new VariableBinding(event.getOid(), event.getCurrentValue()));
return SnmpConstants.SNMPv3_USM_UNSUPPORTED_SECURITY_LEVEL;
}
if (securityLevel >= SecurityLevel.AUTH_NOPRIV) {
if (statusInfo != null) {
int authParamsPos = usmSecurityParameters.getAuthParametersPosition() +
usmSecurityParameters.getSecurityParametersPosition();
boolean authentic =
auth.isAuthentic(authKey, message, 0, message.length,
new ByteArrayWindow(message, authParamsPos,
auth.getAuthenticationCodeLength()));
if (!authentic) {
if (logger.isDebugEnabled()) {
logger.debug(
"RFC3414 §3.2.6 Wrong digest -> authentication failure. Security parameters: " +
usmSecurityParameters.getAuthenticationParameters().toHexString());
}
CounterEvent event = new CounterEvent(this, SnmpConstants.usmStatsWrongDigests);
fireIncrementCounter(event);
if (SNMP4JSettings.getReportSecurityLevelStrategy() ==
SNMP4JSettings.ReportSecurityLevelStrategy.noAuthNoPrivIfNeeded) {
statusInfo.setSecurityLevel(new Integer32(SecurityLevel.NOAUTH_NOPRIV));
}
statusInfo.setErrorIndication(new VariableBinding(event.getOid(), event.getCurrentValue()));
return SnmpConstants.SNMPv3_USM_AUTHENTICATION_FAILURE;
}
// check time
int status = timeTable.checkTime(new UsmTimeEntry(securityEngineID,
usmSecurityParameters.getAuthoritativeEngineBoots(),
usmSecurityParameters.getAuthoritativeEngineTime()));
switch (status) {
case SnmpConstants.SNMPv3_USM_NOT_IN_TIME_WINDOW: {
logger.debug("RFC3414 §3.2.7.a Not in time window; engineID='" +
securityEngineID +
"', engineBoots=" +
usmSecurityParameters.getAuthoritativeEngineBoots() +
", engineTime=" +
usmSecurityParameters.getAuthoritativeEngineTime());
CounterEvent event = new CounterEvent(this, SnmpConstants.usmStatsNotInTimeWindows);
fireIncrementCounter(event);
statusInfo.setSecurityLevel(new Integer32(SecurityLevel.AUTH_NOPRIV));
statusInfo.setErrorIndication(new VariableBinding(event.getOid(), event.getCurrentValue()));
return status;
}
case SnmpConstants.SNMPv3_USM_UNKNOWN_ENGINEID: {
if (logger.isDebugEnabled()) {
logger.debug("RFC3414 §3.2.7.b - Unknown engine ID: " + securityEngineID);
}
CounterEvent event = new CounterEvent(this, SnmpConstants.usmStatsUnknownEngineIDs);
fireIncrementCounter(event);
if (SNMP4JSettings.getReportSecurityLevelStrategy() ==
SNMP4JSettings.ReportSecurityLevelStrategy.noAuthNoPrivIfNeeded) {
statusInfo.setSecurityLevel(new Integer32(SecurityLevel.NOAUTH_NOPRIV));
}
statusInfo.setErrorIndication(new VariableBinding(event.getOid(), event.getCurrentValue()));
return status;
}
}
}
if (securityLevel >= SecurityLevel.AUTH_PRIV) {
OctetString privParams = usmSecurityParameters.getPrivacyParameters();
DecryptParams decryptParams = new DecryptParams(privParams.getValue(),
0, privParams.length());
try {
int scopedPDUHeaderLength = message.length - scopedPDUPosition;
ByteBuffer bis = ByteBuffer.wrap(message, scopedPDUPosition, scopedPDUHeaderLength);
BERInputStream scopedPDUHeader = new BERInputStream(bis);
long headerStartingPosition = scopedPDUHeader.getPosition();
int scopedPDULength = BER.decodeHeader(scopedPDUHeader, new MutableByte());
int scopedPDUPayloadPosition = scopedPDUPosition +
(int) (scopedPDUHeader.getPosition() - headerStartingPosition);
scopedPDUHeader.close();
// early release pointer:
scopedPDUHeader = null;
byte[] scopedPduBytes =
priv.decrypt(message, scopedPDUPayloadPosition, scopedPDULength, privKey,
usmSecurityParameters.getAuthoritativeEngineBoots(),
usmSecurityParameters.getAuthoritativeEngineTime(),
decryptParams);
ByteBuffer buf = ByteBuffer.wrap(scopedPduBytes);
scopedPDU.setFilledBuffer(buf);
} catch (Exception ex) {
logger.debug("RFC 3414 §3.2.8 Decryption error: " + ex.getMessage());
return SnmpConstants.SNMPv3_USM_DECRYPTION_ERROR;
}
} else {
int scopedPduLength = message.length - scopedPDUPosition;
ByteBuffer buf = ByteBuffer.wrap(message, scopedPDUPosition, scopedPduLength);
scopedPDU.setFilledBuffer(buf);
}
} else {
int scopedPduLength = message.length - scopedPDUPosition;
ByteBuffer buf = ByteBuffer.wrap(message, scopedPDUPosition, scopedPduLength);
scopedPDU.setFilledBuffer(buf);
}
} else {
int scopedPduLength = message.length - scopedPDUPosition;
ByteBuffer buf = ByteBuffer.wrap(message, scopedPDUPosition, scopedPduLength);
scopedPDU.setFilledBuffer(buf);
}
// compute real max size response pdu according to RFC3414 §3.2.9
int maxSecParamsOverhead = usmSecurityParameters.getBERMaxLength(securityLevel);
maxSizeResponseScopedPDU.setValue(maxMessageSize - maxSecParamsOverhead);
usmSecurityStateReference.setSecurityName(securityName.getValue());
return SnmpConstants.SNMPv3_USM_OK;
}
private void reportUnknownSecurityName(OctetString securityEngineID, OctetString securityName, int securityLevel,
StatusInformation statusInfo,
UsmSecurityStateReference usmSecurityStateReference) {
if (logger.isDebugEnabled()) {
logger.debug("RFC3414 §3.2.4 Unknown security name: " +
securityName.toHexString() + " (" + securityName + ") for engine ID " +
securityEngineID.toHexString());
}
updateCachedDataForReport(statusInfo, securityLevel, SnmpConstants.usmStatsUnknownUserNames,
usmSecurityStateReference, securityName.getValue());
}
protected void fireIncrementCounter(CounterEvent e) {
getCounterSupport().fireIncrementCounter(e);
}
/**
* Adds an USM user to the internal username table.
*
* @param userName
* a username.
* @param user
* the {@code UsmUser} to add.
*/
public void addUser(OctetString userName, UsmUser user) {
addUser(userName, new OctetString(), user);
}
/**
* Adds an USM user to the internal username table. The user's security name is used as userName.
* Since 3.6.1: If the {@link UsmUser} is localized, then that localisation engine ID is used for
* {@link #addUser(OctetString, OctetString, UsmUser)} as the engine ID parameter. This does not
* break implementations using this method call in 3.6.0 or before, because such user were not working
* anyway.
*
* @param user
* the {@code UsmUser} to add.
*
* @since 2.0
*/
public void addUser(UsmUser user) {
if (user.isLocalized()) {
addUser(user.getSecurityName(), user.getLocalizationEngineID(), user);
}
else {
addUser(user.getSecurityName(), new OctetString(), user);
}
}
/**
* Adds an USM user to the internal username table. The user's security name is used as userName. The storage type
* member of the supplied by {@link UsmUserEntry#getStorageType()} defines the storage type of the new USM user
* table entry.
* Entries with same engineID and securityName will be replaced.
*
* Caution: This is a low level call and the provided UsmUserEntry must contain already correctly localized
* authentication and privacy keys as well as a correct user engine ID.
*
* @param usmUserEntry
* the {@link UsmUserEntry} to add.
* @return
* previous entry registered for the same engineID and securityName or {@code null} if no such entry exist.
* @since 2.5.7
*/
public UsmUserEntry addUsmUserEntry(UsmUserEntry usmUserEntry) {
UsmUserEntry previousEntry = userTable.addUser(usmUserEntry);
fireUsmUserChange(new UsmUserEvent(this, usmUserEntry, UsmUserEvent.USER_ADDED));
return previousEntry;
}
/**
* Adds an USM user to the internal username table and associates it with an authoritative engine ID. This user can
* only be used with the specified engine ID - other engine IDs cannot be discovered on behalf of this entry.
*
* The engine ID must be at least {@link MPv3#MINLEN_ENGINE_ID} bytes long and not longer than {@link
* MPv3#MAXLEN_ENGINE_ID}.
*
* The security name of the {@code user} must be not longer than {@link #MAXLEN_USMUSERNAME} bytes.
*
* @param userName
* a username.
* @param engineID
* the authoritative engine ID to be associated with this entry. If {@code engineID} is {@code null} this
* method behaves exactly like {@link #addUser(OctetString userName, UsmUser user)}.
* @param user
* the {@code UsmUser} to add.
*
* @throws IllegalArgumentException
* if (a) the length of the engine ID is less than {@link MPv3#MINLEN_ENGINE_ID} or more than {@link
* MPv3#MAXLEN_ENGINE_ID} bytes (b) if the security name of the {@code user} is longer than {@link
* #MAXLEN_USMUSERNAME}.
*/
public void addUser(OctetString userName, OctetString engineID, UsmUser user) {
byte[] authKey = null;
byte[] privKey = null;
if (user.getSecurityName().length() > MAXLEN_USMUSERNAME) {
String txt = "User '" + user.getSecurityName() +
"' not added because of its too long security name with length " + user.getSecurityName().length();
logger.warn(txt);
throw new IllegalArgumentException(txt);
}
if ((engineID != null) && (engineID.length() > 0)) {
if (engineID.length() < MPv3.MINLEN_ENGINE_ID || engineID.length() > MPv3.MAXLEN_ENGINE_ID) {
String txt = "User '" + userName +
"' not added because of an engine ID of incorrect length " + engineID.length();
logger.warn(txt);
throw new IllegalArgumentException(txt);
}
OctetString authPassphrse = user.getAuthenticationPassphrase();
if (user.getAuthenticationProtocol() != null && authPassphrse != null) {
if (user.isLocalized()) {
authKey = authPassphrse.getValue();
} else {
authKey = securityProtocols.passwordToKey(user.getAuthenticationProtocol(),
authPassphrse, engineID.getValue());
}
OctetString privPassword = user.getPrivacyPassphrase();
if (user.getPrivacyProtocol() != null && privPassword != null) {
if (user.isLocalized()) {
privKey = privPassword.getValue();
} else {
privKey =
securityProtocols.passwordToKey(user.getPrivacyProtocol(),
user.getAuthenticationProtocol(),
privPassword,
engineID.getValue());
}
}
}
}
OctetString userEngineID;
if (user.isLocalized()) {
userEngineID = user.getLocalizationEngineID();
} else {
userEngineID = (engineID == null) ? new OctetString() : engineID;
}
UsmUserEntry entry = new UsmUserEntry(userName, userEngineID, user);
entry.setAuthenticationKey(authKey);
entry.setPrivacyKey(privKey);
addUsmUserEntry(entry);
}
/**
* Updates the USM user entry with the same engine ID and username as the supplied instance and fires an
* appropriate {@code UsmUserEvent}. Note: If {@link UsmUserEntry#getStorageType()} is {@code null}, then the
* storage type of the existing row (if there is one) is copied to the new entry.
*
* @param entry
* an {@code UsmUserEntry} instance not necessarily the same as an already existing entry.
*
* @since 1.2
*/
public void updateUser(UsmUserEntry entry) {
UsmUserEntry oldEntry = userTable.addUser(entry);
if (oldEntry != null && entry.getStorageType() == null) {
entry.setStorageType(oldEntry.getStorageType());
}
fireUsmUserChange(new UsmUserEvent(this, entry,
(oldEntry == null) ?
UsmUserEvent.USER_ADDED :
UsmUserEvent.USER_CHANGED));
}
/**
* Sets the users of this USM. All previously added users and all localized user information will be discarded and
* replaced by the supplied users.
*
* @param users
* a possibly empty {@code UsmUser} array of users.
*
* @since 1.1
*/
public void setUsers(UsmUser[] users) {
if ((users == null) || (users.length == 0)) {
userTable.clear();
} else {
ArrayList v = new ArrayList<>(users.length);
for (UsmUser user : users) {
UsmUserEntry entry =
new UsmUserEntry(user.getSecurityName(),
(UsmUser) user.clone());
v.add(entry);
}
userTable.setUsers(v);
}
}
/**
* Returns the {@code UsmUserTable} instance used by the USM for local storage of USM user information. The returned
* table should not be modified, because modifications will not be reported to registered {@code UsmUserListener}s.
*
* @return the {@code UsmUserTable} instance containing the users known by this USM.
*/
public UsmUserTable getUserTable() {
return userTable;
}
/**
* Returns the {@code UsmTimeTable} instance used by this USM for holding timing information about the local and
* remote SNMP entities.
*
* @return UsmTimeTable
* @since 1.6
*/
public UsmTimeTable getTimeTable() {
return timeTable;
}
/**
* Removes all USM user from the internal username table with the specified username and (optional) engine ID. If
* the engine ID is not provided (null) then any user (including localized) are removed that have the specified
* username.
*
* @param userName
* a username.
* @param engineID
* the authoritative engine ID associated with the user by localization, or {@code null} if all users with
* {@code userName} should be deleted.
*
* @return the removed {@code UsmUser} instances as a List. If the user could be found, an empty list is returned.
* @since 2.2
*/
public List removeAllUsers(OctetString userName, OctetString engineID) {
if (userTable != null) {
List entries = userTable.removeAllUsers(userName, engineID);
if (!entries.isEmpty()) {
List users = new ArrayList();
for (UsmUserEntry entry : entries) {
users.add(entry.getUsmUser());
fireUsmUserChange(new UsmUserEvent(this, entry, UsmUserEvent.USER_REMOVED));
}
return users;
}
}
return Collections.emptyList();
}
/**
* Removes all USM user from the internal username table with the specified username. This is the same as {@link
* #removeAllUsers(org.snmp4j.smi.OctetString, org.snmp4j.smi.OctetString)} with engineID set to {@code null}.
*
* @param userName
* a username.
*
* @return the removed {@code UsmUser} instances as a List. If the user could be found, an empty list is returned.
* @since 2.2
*/
public List removeAllUsers(OctetString userName) {
return removeAllUsers(userName, null);
}
/**
* Removes an USM user from the internal username table.
*
* @param engineID
* the authoritative engine ID associated with the user, or {@code null}
* @param userName
* a username.
*
* @return the removed {@code UsmUser} instance associate with the given {@code userName} or {@code null} if such a
* user could not be found.
* @deprecated If the engineID {@code null} is provided this method does only delete the generic user. All already
* localized users will not be deleted. To delete those users too, use {@link #removeAllUsers(OctetString,
* OctetString)} instead.
*/
@Deprecated
public UsmUser removeUser(OctetString engineID, OctetString userName) {
UsmUserEntry entry = userTable.removeUser(engineID, userName);
if (entry != null) {
fireUsmUserChange(new UsmUserEvent(this, entry, UsmUserEvent.USER_REMOVED));
return entry.getUsmUser();
}
return null;
}
/**
* Removes all users from the USM.
*/
public void removeAllUsers() {
userTable.clear();
fireUsmUserChange(new UsmUserEvent(this, null, UsmUserEvent.USER_REMOVED));
}
/**
* Adds a localized user to the USM.
*
* @param engineID
* the engine ID for which the user has been localized.
* @param userName
* the user's name.
* @param authProtocol
* the authentication protocol ID.
* @param authKey
* the authentication key.
* @param privProtocol
* the privacy protocol ID.
* @param privKey
* the privacy key.
*
* @return the added {@code UsmUserEntry}.
*/
public UsmUserEntry addLocalizedUser(byte[] engineID,
OctetString userName,
OID authProtocol, byte[] authKey,
OID privProtocol, byte[] privKey) {
UsmUserEntry newEntry = new UsmUserEntry(engineID, userName,
authProtocol, authKey,
privProtocol, privKey);
userTable.addUser(newEntry);
fireUsmUserChange(new UsmUserEvent(this, newEntry, UsmUserEvent.USER_ADDED));
return newEntry;
}
/**
* Checks whether engine ID discovery is enabled or not. If enabled, the USM will try to discover unknown engine IDs
* "on-the-fly" while processing the message.
*
* @return {@code true} if discovery is enabled, {@code false} otherwise.
*/
public boolean isEngineDiscoveryEnabled() {
return engineDiscoveryEnabled;
}
/**
* Enables or disables automatic engine ID discovery.
*
* @param engineDiscoveryEnabled
* {@code true} if discovery should be enabled, {@code false} otherwise.
*/
public void setEngineDiscoveryEnabled(boolean engineDiscoveryEnabled) {
this.engineDiscoveryEnabled = engineDiscoveryEnabled;
}
/**
* Removes a {@code UsmUserListener}.
*
* @param l
* a previously added {@code UsmUserListener}.
*/
public void removeUsmUserListener(UsmUserListener l) {
usmUserListeners.remove(l);
}
/**
* Adds a {@code UsmUserListener} that should be informed whenever the internal USM user table is changed.
*
* @param l
* a {@code UsmUserListener} that should be informed about {@link UsmUserEvent} events.
*/
public void addUsmUserListener(UsmUserListener l) {
if (!usmUserListeners.contains(l)) {
usmUserListeners.add(l);
}
}
/**
* Removes the specified engine ID from the internal time cache and thus forces an engine time rediscovery the next
* time the SNMP engine with the supplied ID is contacted.
*
* @param engineID
* the SNMP engine ID whose engine time to remove.
*
* @since 1.6
*/
public void removeEngineTime(OctetString engineID) {
timeTable.removeEntry(engineID);
}
/**
* Fires a {@code UsmUserEvent}.
*
* @param e
* the {@code UsmUserEvent} to fire.
*/
protected void fireUsmUserChange(UsmUserEvent e) {
for (UsmUserListener listener : usmUserListeners) {
listener.usmUserChange(e);
}
}
/**
* Returns the security protocol collection used by this USM.
*
* @return a {@code SecurityProtocols} instance which is by default the same instance as returned by {@link
* SecurityProtocols#getInstance()}.
* @since 1.2
*/
public SecurityProtocols getSecurityProtocols() {
return securityProtocols;
}
}