315 lines
11 KiB
C#
315 lines
11 KiB
C#
using System;
|
|
using System.Net;
|
|
using ln.snmp.types;
|
|
using System.Text;
|
|
using System.Security.Cryptography;
|
|
using ln.snmp.asn1;
|
|
using System.Linq;
|
|
using ln.logging;
|
|
using System.Threading;
|
|
using System.IO;
|
|
|
|
namespace ln.snmp.endpoint
|
|
{
|
|
public enum SnmpV3AuthMethod { MD5, SHA }
|
|
public enum SnmpV3PrivMethod { DES }
|
|
public enum SnmpAuthLevel : int { noAuthNoPriv = 0, authNoPriv = 0x01, authPriv = 0x03 }
|
|
|
|
public class USMEndpoint : SnmpEndpoint
|
|
{
|
|
public OctetString RemoteEngineID { get; set; }
|
|
public Integer CacheAuthoritativeEngineBoots { get; set; }
|
|
public Integer CacheAuthoritativeEngineTime { get; set; }
|
|
|
|
public string Username { get; set; }
|
|
|
|
public byte[] AuthKey { get; set; }
|
|
public byte[] PrivKey { get; set; }
|
|
|
|
public byte[] LocalAuthKey { get; set; }
|
|
public byte[] LocalPrivKey { get; set; }
|
|
|
|
public SnmpV3AuthMethod AuthMethod { get; set; }
|
|
public SnmpV3PrivMethod PrivMethod { get; set; }
|
|
|
|
public override SnmpVersion SnmpVersion => SnmpVersion.V3;
|
|
|
|
private object reportDispatchLock = new object();
|
|
|
|
public USMEndpoint(SNMPEngine snmpEngine,IPEndPoint remoteEndpoint)
|
|
:base(snmpEngine,remoteEndpoint)
|
|
{
|
|
CacheAuthoritativeEngineTime = new Integer();
|
|
CacheAuthoritativeEngineBoots = new Integer();
|
|
}
|
|
|
|
public override void DispatchReceived(SnmpMessage message)
|
|
{
|
|
Logging.Log(LogLevel.DEBUGDETAIL, "Received PDU: {0}",message.snmpPDU);
|
|
|
|
USMMessage usm = message as USMMessage;
|
|
|
|
if (usm.SecurityParameters.msgAuthoritativeEngineBoots.LongValue > 0)
|
|
CacheAuthoritativeEngineBoots.LongValue = usm.SecurityParameters.msgAuthoritativeEngineBoots.LongValue;
|
|
if (usm.SecurityParameters.msgAuthoritativeEngineTime.LongValue > 0)
|
|
CacheAuthoritativeEngineTime.LongValue = usm.SecurityParameters.msgAuthoritativeEngineTime.LongValue;
|
|
|
|
if (message.snmpPDU is Report)
|
|
{
|
|
DispatchReport(message);
|
|
}
|
|
else
|
|
{
|
|
base.DispatchReceived(message);
|
|
}
|
|
}
|
|
|
|
public void DispatchReport(SnmpMessage snmpMessage)
|
|
{
|
|
USMMessage usm = snmpMessage as USMMessage;
|
|
|
|
RemoteEngineID = usm.SecurityParameters.msgAuthoritativeEngineID;
|
|
|
|
|
|
lock (reportDispatchLock)
|
|
{
|
|
Monitor.PulseAll(reportDispatchLock);
|
|
}
|
|
}
|
|
|
|
public override SnmpMessage ApplyAuthentication(SnmpMessage message)
|
|
{
|
|
if (RemoteEngineID == null)
|
|
return message;
|
|
|
|
if (AuthenticateMessage(message as USMMessage))
|
|
return message;
|
|
|
|
throw new Exception("Authentication failed");
|
|
}
|
|
|
|
|
|
public SnmpAuthLevel AuthLevel
|
|
{
|
|
get
|
|
{
|
|
if (PrivKey != null)
|
|
return SnmpAuthLevel.authPriv;
|
|
if (AuthKey != null)
|
|
return SnmpAuthLevel.authNoPriv;
|
|
|
|
return SnmpAuthLevel.noAuthNoPriv;
|
|
}
|
|
}
|
|
|
|
public override PDU DispatchRequest(PDU pdu)
|
|
{
|
|
if ((RemoteEngineID == null) || (RemoteEngineID.Bytes.Length == 0))
|
|
{
|
|
QueryEngineID();
|
|
}
|
|
|
|
int retries = Retries;
|
|
while (retries-- > 0)
|
|
{
|
|
USMMessage request = new USMMessage();
|
|
ScopedPDU scopedPDU = new ScopedPDU();
|
|
|
|
request.MessageID = SNMPEngine.NextMessageID;
|
|
|
|
request.msgData = scopedPDU;
|
|
|
|
if (RemoteEngineID == null)
|
|
{
|
|
scopedPDU.contextEngineID = new OctetString();
|
|
}
|
|
else
|
|
{
|
|
scopedPDU.contextEngineID = RemoteEngineID;
|
|
}
|
|
|
|
scopedPDU.PDU = pdu;
|
|
|
|
try
|
|
{
|
|
USMMessage replyUSM = DispatchRequest(request);
|
|
PDU responsePDU = (replyUSM.msgData as ScopedPDU).PDU;
|
|
return responsePDU;
|
|
}
|
|
catch (TimeoutException)
|
|
{
|
|
}
|
|
}
|
|
throw new TimeoutException();
|
|
}
|
|
|
|
private USMMessage DispatchRequest(USMMessage request)
|
|
{
|
|
SnmpMessage reply = EnqueueRequest(request);
|
|
USMMessage usmReply = reply as USMMessage;
|
|
|
|
return usmReply;
|
|
}
|
|
|
|
public void QueryEngineID()
|
|
{
|
|
USMMessage queryMessage = new USMMessage();
|
|
queryMessage.MessageID = SNMPEngine.NextMessageID;
|
|
|
|
ScopedPDU scopedPDU = new ScopedPDU();
|
|
|
|
queryMessage.msgData = scopedPDU;
|
|
|
|
lock (reportDispatchLock)
|
|
{
|
|
Send(queryMessage);
|
|
if (!Monitor.Wait(reportDispatchLock, SNMPEngine.Timeout))
|
|
throw new TimeoutException("could not query engine id");
|
|
}
|
|
|
|
LocalizeKeys();
|
|
}
|
|
|
|
public bool AuthenticateMessage(USMMessage message)
|
|
{
|
|
message.msgGlobalData.msgFlags.Bytes[0] |= 0x01;
|
|
message.SecurityParameters.msgUserName.StringValue = Username;
|
|
message.SecurityParameters.msgAuthoritativeEngineID = RemoteEngineID == null ? new OctetString() : RemoteEngineID;
|
|
message.SecurityParameters.msgAuthenticationParameters.Bytes = new byte[12];
|
|
message.SecurityParameters.msgAuthoritativeEngineBoots = CacheAuthoritativeEngineBoots != null ? CacheAuthoritativeEngineBoots : new Integer();
|
|
message.SecurityParameters.msgAuthoritativeEngineTime = CacheAuthoritativeEngineTime != null ? CacheAuthoritativeEngineTime : new Integer();
|
|
|
|
byte[] wholeMsg = ((ASN1Value)message).AsByteArray;
|
|
|
|
byte[] extendedAuthKey = new byte[64];
|
|
byte[] authKey = LocalAuthKey;
|
|
|
|
Array.Copy(authKey, extendedAuthKey, authKey.Length);
|
|
|
|
byte[] K1 = new byte[64];
|
|
for (int n = 0; n < 64; n++)
|
|
K1[n] = (byte)(extendedAuthKey[n] ^ IPAD[n]);
|
|
|
|
byte[] K2 = new byte[64];
|
|
for (int n = 0; n < 64; n++)
|
|
K2[n] = (byte)(extendedAuthKey[n] ^ OPAD[n]);
|
|
|
|
using (HashAlgorithm hash = CreateAuthHashAlgorithm())
|
|
{
|
|
byte[] b1 = new byte[K1.Length + wholeMsg.Length];
|
|
|
|
Array.Copy(K1, b1, K1.Length);
|
|
Array.Copy(wholeMsg, 0, b1, K1.Length, wholeMsg.Length);
|
|
|
|
byte[] intermediate = hash.ComputeHash(b1);
|
|
byte[] inter2 = new byte[K2.Length + intermediate.Length];
|
|
|
|
Array.Copy(K2, inter2, K2.Length);
|
|
Array.Copy(intermediate, 0, inter2, K2.Length, intermediate.Length);
|
|
|
|
byte[] mac = hash.ComputeHash(inter2);
|
|
|
|
|
|
Logging.Log(LogLevel.DEBUGDETAIL, "Authentication of {0}", BitConverter.ToString(wholeMsg));
|
|
Logging.Log(LogLevel.DEBUGDETAIL, "AuthKey: {0}", BitConverter.ToString(AuthKey));
|
|
Logging.Log(LogLevel.DEBUGDETAIL, "LocalAuthKey: {0}", BitConverter.ToString(LocalAuthKey));
|
|
Logging.Log(LogLevel.DEBUGDETAIL, "Extended AuthKey: {0}", BitConverter.ToString(extendedAuthKey));
|
|
Logging.Log(LogLevel.DEBUGDETAIL, "");
|
|
Logging.Log(LogLevel.DEBUGDETAIL, "K1: {0}", BitConverter.ToString(K1));
|
|
Logging.Log(LogLevel.DEBUGDETAIL, "K2: {0}", BitConverter.ToString(K2));
|
|
Logging.Log(LogLevel.DEBUGDETAIL, "");
|
|
Logging.Log(LogLevel.DEBUGDETAIL, "AuthToken: {0}", BitConverter.ToString(mac.Take(12).ToArray()));
|
|
|
|
message.SecurityParameters.msgAuthenticationParameters.Bytes = mac.Take(12).ToArray();
|
|
}
|
|
|
|
Logging.Log(LogLevel.DEBUGDETAIL, "Authenticating Message: {0}",BitConverter.ToString(wholeMsg));
|
|
Logging.Log(LogLevel.DEBUGDETAIL, "Authenticated Message: {0}", BitConverter.ToString(((ASN1Value)message).AsByteArray));
|
|
|
|
return true;
|
|
}
|
|
|
|
private byte[] DeriveKey(string passphrase,HashAlgorithm hash)
|
|
{
|
|
using (hash)
|
|
{
|
|
byte[] pass = Encoding.ASCII.GetBytes(passphrase);
|
|
byte[] block = new byte[64];
|
|
|
|
for (int n = 0; n < 1048576; n += 64)
|
|
{
|
|
for (int p = 0; p < block.Length; p++)
|
|
{
|
|
block[p] = pass[(p + n) % pass.Length];
|
|
}
|
|
hash.TransformBlock(block, 0, block.Length, block, 0);
|
|
}
|
|
|
|
hash.TransformFinalBlock(new byte[0], 0, 0);
|
|
|
|
byte[] result = hash.Hash;
|
|
return result;
|
|
}
|
|
}
|
|
|
|
private HashAlgorithm CreateAuthHashAlgorithm()
|
|
{
|
|
return AuthMethod == SnmpV3AuthMethod.SHA ? (HashAlgorithm)SHA1.Create() : (HashAlgorithm)MD5.Create();
|
|
}
|
|
|
|
public void LocalizeKeys()
|
|
{
|
|
if ((AuthKey != null) && (RemoteEngineID != null))
|
|
{
|
|
using (HashAlgorithm hash = CreateAuthHashAlgorithm())
|
|
{
|
|
byte[] engineID = (byte[])RemoteEngineID.Bytes.Clone();
|
|
|
|
hash.TransformBlock(AuthKey, 0, AuthKey.Length, AuthKey, 0);
|
|
hash.TransformBlock(engineID, 0, engineID.Length, engineID, 0);
|
|
hash.TransformBlock(AuthKey, 0, AuthKey.Length, AuthKey, 0);
|
|
|
|
hash.TransformFinalBlock(new byte[0], 0, 0);
|
|
|
|
LocalAuthKey = hash.Hash;
|
|
|
|
Logging.Log(LogLevel.DEBUGDETAIL, "Derived LocalAuthKey from AuthKey: {0}", BitConverter.ToString(LocalAuthKey));
|
|
}
|
|
}
|
|
}
|
|
|
|
public string AuthKeyPhrase
|
|
{
|
|
get => throw new NotSupportedException();
|
|
set
|
|
{
|
|
AuthKey = DeriveKey(
|
|
value,
|
|
CreateAuthHashAlgorithm()
|
|
);
|
|
|
|
Logging.Log(LogLevel.DEBUGDETAIL, "Derived AuthKey from Phrase: {0}",BitConverter.ToString(AuthKey));
|
|
|
|
if (RemoteEngineID != null)
|
|
LocalizeKeys();
|
|
}
|
|
}
|
|
public string PrivKeyPhrase { get; set; }
|
|
|
|
|
|
|
|
|
|
static byte[] IPAD = __PAD(0x36);
|
|
static byte[] OPAD = __PAD(0x5C);
|
|
|
|
public static byte[] __PAD(byte pad)
|
|
{
|
|
byte[] ipad = new byte[0x40];
|
|
for (int n = 0; n < ipad.Length; n++)
|
|
ipad[n] = pad;
|
|
return ipad;
|
|
}
|
|
|
|
}
|
|
}
|