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.DEBUG, "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; } } }