diff --git a/SharpSnmpLib/Header.cs b/SharpSnmpLib/Header.cs index 92563519..c3a9c66b 100644 --- a/SharpSnmpLib/Header.cs +++ b/SharpSnmpLib/Header.cs @@ -76,14 +76,27 @@ public Header(ISnmpData data) /// The message id. /// Size of the max message. /// The security level. + /// The security model. /// If you want an empty header, please use . - public Header(Integer32? messageId, Integer32 maxMessageSize, Levels securityLevel) + public Header(Integer32? messageId, Integer32 maxMessageSize, Levels securityLevel, Integer32 securityModel) { - _messageId = messageId; + _messageId = messageId; _maxSize = maxMessageSize ?? throw new ArgumentNullException(nameof(maxMessageSize)); SecurityLevel = securityLevel; _flags = new OctetString(SecurityLevel); - _securityModel = DefaultSecurityModel; + _securityModel = securityModel; + } + + /// + /// Initializes a new instance of the class. + /// + /// The message id. + /// Size of the max message. + /// The security level. + /// If you want an empty header, please use . + public Header(Integer32? messageId, Integer32 maxMessageSize, Levels securityLevel) + : this(messageId, maxMessageSize, securityLevel, DefaultSecurityModel) + { } /// @@ -102,6 +115,15 @@ public Header(Integer32? messageId, Integer32 maxMessageSize, Levels securityLev /// The message ID. public int MessageId => _messageId == null ? throw new InvalidOperationException() : _messageId.ToInt32(); + /// + /// Gets the security model. + /// + /// The security model. + public SecurityModel SecurityModel + { + get { return (SecurityModel)_securityModel.ToInt32(); } + } + #region ISegment Members /// diff --git a/SharpSnmpLib/Messaging/Discoverer.cs b/SharpSnmpLib/Messaging/Discoverer.cs index 263685e8..68a2b1a8 100644 --- a/SharpSnmpLib/Messaging/Discoverer.cs +++ b/SharpSnmpLib/Messaging/Discoverer.cs @@ -317,7 +317,7 @@ public async Task DiscoverAsync(VersionCode version, IPEndPoint broadcastAddress using var udp = new Socket(addressFamily, SocketType.Dgram, ProtocolType.Udp); udp.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, true); var buffer = new ArraySegment(bytes); - await udp.SendToAsync(buffer, SocketFlags.None, broadcastAddress); + await udp.SendToAsync(buffer, SocketFlags.None, broadcastAddress).ConfigureAwait(false); var activeBefore = Interlocked.CompareExchange(ref _active, Active, Inactive); if (activeBefore == Active) @@ -341,7 +341,8 @@ public async Task DiscoverAsync(VersionCode version, IPEndPoint broadcastAddress #else await Task.WhenAny( ReceiveAsync(udp), - Task.Delay(interval)); + Task.Delay(interval)) + .ConfigureAwait(false); #endif Interlocked.CompareExchange(ref _active, Inactive, Active); try @@ -370,7 +371,7 @@ private async Task ReceiveAsync(Socket socket) EndPoint remote = new IPEndPoint(IPAddress.Any, 0); var buffer = new byte[_bufferSize]; - var result = await socket.ReceiveMessageFromAsync(new ArraySegment(buffer), SocketFlags.None, remote); + var result = await socket.ReceiveMessageFromAsync(new ArraySegment(buffer), SocketFlags.None, remote).ConfigureAwait(false); await Task.Factory.StartNew(() => HandleMessage(buffer, result.ReceivedBytes, (IPEndPoint) result.RemoteEndPoint)) .ConfigureAwait(false); } diff --git a/SharpSnmpLib/Messaging/GetBulkRequestMessage.cs b/SharpSnmpLib/Messaging/GetBulkRequestMessage.cs index e5a92981..1cb0d63f 100644 --- a/SharpSnmpLib/Messaging/GetBulkRequestMessage.cs +++ b/SharpSnmpLib/Messaging/GetBulkRequestMessage.cs @@ -107,6 +107,69 @@ public GetBulkRequestMessage(VersionCode version, int messageId, int requestId, { } + /// + /// Creates a with a specific . + /// + /// The version. + /// The message id. + /// The request id. + /// Context name. + /// The non repeaters. + /// The max repetitions. + /// The variables. + /// The privacy provider. + /// Size of the max message. + public GetBulkRequestMessage(VersionCode version, int messageId, int requestId, OctetString contextName, int nonRepeaters, int maxRepetitions, IList variables, IPrivacyProvider privacy, int maxMessageSize) + { + if (variables == null) + { + throw new ArgumentNullException(nameof(variables)); + } + + if (contextName == null) + { + throw new ArgumentNullException(nameof(contextName)); + } + + if (version != VersionCode.V3) + { + throw new ArgumentException("Only v3 is supported.", nameof(version)); + } + + if (privacy == null) + { + throw new ArgumentNullException(nameof(privacy)); + } + + if (nonRepeaters > variables.Count) + { + throw new ArgumentException("nonRepeaters should not be greater than variable count.", nameof(nonRepeaters)); + } + + if (maxRepetitions < 1) + { + throw new ArgumentException("maxRepetitions should be greater than 0.", nameof(maxRepetitions)); + } + + Version = version; + Privacy = privacy; + + // TODO: define more constants. + Header = new Header(new Integer32(messageId), new Integer32(maxMessageSize), privacy.ToSecurityLevel() | Levels.Reportable, new Integer32((int)SecurityModel.Tsm)); + Parameters = SecurityParameters.Empty; + + var pdu = new GetBulkRequestPdu( + requestId, + nonRepeaters, + maxRepetitions, + variables); + var contextEngineId = OctetString.Empty; + Scope = new Scope(contextEngineId, contextName, pdu); + + Privacy.ComputeHash(Version, Header, Parameters, Scope); + _bytes = this.PackMessage(null).ToBytes(); + } + /// /// Creates a with a specific . /// @@ -121,7 +184,8 @@ public GetBulkRequestMessage(VersionCode version, int messageId, int requestId, /// The privacy provider. /// Size of the max message. /// The report. - public GetBulkRequestMessage(VersionCode version, int messageId, int requestId, OctetString userName, OctetString contextName, int nonRepeaters, int maxRepetitions, IList variables, IPrivacyProvider privacy, int maxMessageSize, ISnmpMessage report) + /// The security model. + public GetBulkRequestMessage(VersionCode version, int messageId, int requestId, OctetString userName, OctetString contextName, int nonRepeaters, int maxRepetitions, IList variables, IPrivacyProvider privacy, int maxMessageSize, ISnmpMessage report, SecurityModel securityModel) { if (variables == null) { @@ -160,16 +224,25 @@ public GetBulkRequestMessage(VersionCode version, int messageId, int requestId, Version = version; Privacy = privacy ?? throw new ArgumentNullException(nameof(privacy)); - Header = new Header(new Integer32(messageId), new Integer32(maxMessageSize), privacy.ToSecurityLevel() | Levels.Reportable); + Header = new Header(new Integer32(messageId), new Integer32(maxMessageSize), privacy.ToSecurityLevel() | Levels.Reportable, new Integer32((int)securityModel)); var parameters = report.Parameters; var authenticationProvider = Privacy.AuthenticationProvider; - Parameters = new SecurityParameters( - parameters.EngineId, - parameters.EngineBoots, - parameters.EngineTime, - userName, - authenticationProvider.CleanDigest, - Privacy.Salt); + + if (securityModel == SecurityModel.Tsm) + { + Parameters = SecurityParameters.Empty; + } + else + { + Parameters = new SecurityParameters( + parameters.EngineId, + parameters.EngineBoots, + parameters.EngineTime, + userName, + authenticationProvider.CleanDigest, + Privacy.Salt); + } + var pdu = new GetBulkRequestPdu( requestId, nonRepeaters, @@ -188,6 +261,24 @@ public GetBulkRequestMessage(VersionCode version, int messageId, int requestId, _bytes = this.PackMessage(null).ToBytes(); } + /// + /// Creates a with a specific . + /// + /// The version. + /// The message id. + /// The request id. + /// Name of the user. + /// Context name. + /// The non repeaters. + /// The max repetitions. + /// The variables. + /// The privacy provider. + /// Size of the max message. + /// The report. + public GetBulkRequestMessage(VersionCode version, int messageId, int requestId, OctetString userName, OctetString contextName, int nonRepeaters, int maxRepetitions, IList variables, IPrivacyProvider privacy, int maxMessageSize, ISnmpMessage report) + : this(version, messageId, requestId, userName, contextName, nonRepeaters, maxRepetitions, variables, privacy, maxMessageSize, report, SecurityModel.Usm) + { + } /// /// Creates a with a specific . diff --git a/SharpSnmpLib/Messaging/GetNextRequestMessage.cs b/SharpSnmpLib/Messaging/GetNextRequestMessage.cs index cc8e2123..9d1ca3be 100644 --- a/SharpSnmpLib/Messaging/GetNextRequestMessage.cs +++ b/SharpSnmpLib/Messaging/GetNextRequestMessage.cs @@ -92,6 +92,53 @@ public GetNextRequestMessage(VersionCode version, int messageId, int requestId, { } + /// + /// Initializes a new instance of the class. + /// + /// The version. + /// The message id. + /// The request id. + /// Context name. + /// The variables. + /// The privacy provider. + /// Size of the max message. + public GetNextRequestMessage(VersionCode version, int messageId, int requestId, OctetString contextName, IList variables, IPrivacyProvider privacy, int maxMessageSize) + { + if (variables == null) + { + throw new ArgumentNullException(nameof(variables)); + } + + if (contextName == null) + { + throw new ArgumentNullException(nameof(contextName)); + } + + if (version != VersionCode.V3) + { + throw new ArgumentException("Only v3 is supported.", nameof(version)); + } + + if (privacy == null) + { + throw new ArgumentNullException(nameof(privacy)); + } + + Version = version; + Privacy = privacy; + Header = new Header(new Integer32(messageId), new Integer32(maxMessageSize), privacy.ToSecurityLevel() | Levels.Reportable, new Integer32((int)SecurityModel.Tsm)); + Parameters = SecurityParameters.Empty; + + var pdu = new GetNextRequestPdu( + requestId, + variables); + var contextEngineId = OctetString.Empty; + Scope = new Scope(contextEngineId, contextName, pdu); + + Privacy.ComputeHash(Version, Header, Parameters, Scope); + _bytes = this.PackMessage(null).ToBytes(); + } + /// /// Initializes a new instance of the class. /// @@ -104,7 +151,8 @@ public GetNextRequestMessage(VersionCode version, int messageId, int requestId, /// The privacy provider. /// Size of the max message. /// The report. - public GetNextRequestMessage(VersionCode version, int messageId, int requestId, OctetString userName, OctetString contextName, IList variables, IPrivacyProvider privacy, int maxMessageSize, ISnmpMessage report) + /// The security model + public GetNextRequestMessage(VersionCode version, int messageId, int requestId, OctetString userName, OctetString contextName, IList variables, IPrivacyProvider privacy, int maxMessageSize, ISnmpMessage report, SecurityModel securityModel) { if (variables == null) { @@ -134,16 +182,25 @@ public GetNextRequestMessage(VersionCode version, int messageId, int requestId, Version = version; Privacy = privacy ?? throw new ArgumentNullException(nameof(privacy)); - Header = new Header(new Integer32(messageId), new Integer32(maxMessageSize), privacy.ToSecurityLevel() | Levels.Reportable); + Header = new Header(new Integer32(messageId), new Integer32(maxMessageSize), privacy.ToSecurityLevel() | Levels.Reportable, new Integer32((int)securityModel)); var parameters = report.Parameters; var authenticationProvider = Privacy.AuthenticationProvider; - Parameters = new SecurityParameters( - parameters.EngineId, - parameters.EngineBoots, - parameters.EngineTime, - userName, - authenticationProvider.CleanDigest, - Privacy.Salt); + + if (securityModel == SecurityModel.Tsm) + { + Parameters = SecurityParameters.Empty; + } + else + { + Parameters = new SecurityParameters( + parameters.EngineId, + parameters.EngineBoots, + parameters.EngineTime, + userName, + authenticationProvider.CleanDigest, + Privacy.Salt); + } + var pdu = new GetNextRequestPdu( requestId, variables); @@ -160,6 +217,22 @@ public GetNextRequestMessage(VersionCode version, int messageId, int requestId, _bytes = this.PackMessage(null).ToBytes(); } + /// + /// Initializes a new instance of the class. + /// + /// The version. + /// The message id. + /// The request id. + /// Name of the user. + /// Context name. + /// The variables. + /// The privacy provider. + /// Size of the max message. + /// The report. + public GetNextRequestMessage(VersionCode version, int messageId, int requestId, OctetString userName, OctetString contextName, IList variables, IPrivacyProvider privacy, int maxMessageSize, ISnmpMessage report) + : this(version, messageId, requestId, userName, contextName, variables, privacy, maxMessageSize, report, SecurityModel.Usm) + { + } /// /// Initializes a new instance of the class. diff --git a/SharpSnmpLib/Messaging/GetRequestMessage.cs b/SharpSnmpLib/Messaging/GetRequestMessage.cs index 590336f7..d7b7d9eb 100644 --- a/SharpSnmpLib/Messaging/GetRequestMessage.cs +++ b/SharpSnmpLib/Messaging/GetRequestMessage.cs @@ -83,6 +83,53 @@ public GetRequestMessage(VersionCode version, int messageId, int requestId, Octe { } + /// + /// Initializes a new instance of the class. + /// + /// The version. + /// The message id. + /// The request id. + /// Context name. + /// The variables. + /// The privacy provider. + /// Size of the max message. + public GetRequestMessage(VersionCode version, int messageId, int requestId, OctetString contextName, IList variables, IPrivacyProvider privacy, int maxMessageSize) + { + if (variables == null) + { + throw new ArgumentNullException(nameof(variables)); + } + + if (contextName == null) + { + throw new ArgumentNullException(nameof(contextName)); + } + + if (version != VersionCode.V3) + { + throw new ArgumentException("Only v3 is supported.", nameof(version)); + } + + if (privacy == null) + { + throw new ArgumentNullException(nameof(privacy)); + } + + Version = version; + Privacy = privacy; + Header = new Header(new Integer32(messageId), new Integer32(maxMessageSize), privacy.ToSecurityLevel() | Levels.Reportable, new Integer32((int)SecurityModel.Tsm)); + Parameters = SecurityParameters.Empty; + + var pdu = new GetRequestPdu( + requestId, + variables); + var contextEngineId = OctetString.Empty; + Scope = new Scope(contextEngineId, contextName, pdu); + + Privacy.ComputeHash(Version, Header, Parameters, Scope); + _bytes = this.PackMessage(null).ToBytes(); + } + /// /// Initializes a new instance of the class. /// @@ -95,7 +142,8 @@ public GetRequestMessage(VersionCode version, int messageId, int requestId, Octe /// The privacy provider. /// Size of the max message. /// The report. - public GetRequestMessage(VersionCode version, int messageId, int requestId, OctetString userName, OctetString contextName, IList variables, IPrivacyProvider privacy, int maxMessageSize, ISnmpMessage report) + /// The type of security model + public GetRequestMessage(VersionCode version, int messageId, int requestId, OctetString userName, OctetString contextName, IList variables, IPrivacyProvider privacy, int maxMessageSize, ISnmpMessage report, SecurityModel securityModel) { if (userName == null) { @@ -125,16 +173,25 @@ public GetRequestMessage(VersionCode version, int messageId, int requestId, Octe Version = version; Privacy = privacy ?? throw new ArgumentNullException(nameof(privacy)); - Header = new Header(new Integer32(messageId), new Integer32(maxMessageSize), privacy.ToSecurityLevel() | Levels.Reportable); + Header = new Header(new Integer32(messageId), new Integer32(maxMessageSize), privacy.ToSecurityLevel() | Levels.Reportable, new Integer32((int)securityModel)); var parameters = report.Parameters; var authenticationProvider = Privacy.AuthenticationProvider; - Parameters = new SecurityParameters( - parameters.EngineId, - parameters.EngineBoots, - parameters.EngineTime, - userName, - authenticationProvider.CleanDigest, - Privacy.Salt); + + if (securityModel == SecurityModel.Tsm) + { + Parameters = SecurityParameters.Empty; + } + else + { + Parameters = new SecurityParameters( + parameters.EngineId, + parameters.EngineBoots, + parameters.EngineTime, + userName, + authenticationProvider.CleanDigest, + Privacy.Salt); + } + var pdu = new GetRequestPdu( requestId, variables); @@ -151,6 +208,22 @@ public GetRequestMessage(VersionCode version, int messageId, int requestId, Octe _bytes = this.PackMessage(null).ToBytes(); } + /// + /// Initializes a new instance of the class. + /// + /// The version. + /// The message id. + /// The request id. + /// Name of the user. + /// Context name. + /// The variables. + /// The privacy provider. + /// Size of the max message. + /// The report. + public GetRequestMessage(VersionCode version, int messageId, int requestId, OctetString userName, OctetString contextName, IList variables, IPrivacyProvider privacy, int maxMessageSize, ISnmpMessage report) + : this(version, messageId, requestId, userName, contextName, variables, privacy, maxMessageSize, report, SecurityModel.Usm) + { + } /// /// Initializes a new instance of the class. diff --git a/SharpSnmpLib/Messaging/MessageFactory.cs b/SharpSnmpLib/Messaging/MessageFactory.cs index d677456b..c8d513f9 100644 --- a/SharpSnmpLib/Messaging/MessageFactory.cs +++ b/SharpSnmpLib/Messaging/MessageFactory.cs @@ -142,14 +142,22 @@ private static ISnmpMessage ParseMessage(int first, Stream stream, UserRegistry { header = new Header(body[1]); parameters = new SecurityParameters((OctetString)body[2]); - var temp = registry.Find(parameters.UserName); - if (temp == null) + if (header.SecurityModel == SecurityModel.Tsm) { - // handle decryption exception. - return new MalformedMessage(header.MessageId, parameters.UserName, body[3]); + privacy = new TsmPrivacyProvider(TsmAuthenticationProvider.Instance); + } + else + { + var temp = registry.Find(parameters.UserName ?? OctetString.Empty); + if (temp == null) + { + // handle decryption exception. + return new MalformedMessage(header.MessageId, parameters.UserName ?? OctetString.Empty, body[3]); + } + + privacy = temp; } - privacy = temp; var code = body[3].TypeCode; if (code == SnmpType.Sequence) { @@ -166,7 +174,7 @@ private static ISnmpMessage ParseMessage(int first, Stream stream, UserRegistry catch (SnmpException) { // If decryption failed or gave back invalid data, handle parsing exceptions. - return new MalformedMessage(header.MessageId, parameters.UserName, body[3]); + return new MalformedMessage(header.MessageId, parameters.UserName ?? OctetString.Empty, body[3]); } } else @@ -174,7 +182,7 @@ private static ISnmpMessage ParseMessage(int first, Stream stream, UserRegistry throw new SnmpException(string.Format(CultureInfo.InvariantCulture, "invalid v3 packets scoped data: {0}", code)); } - if (!privacy.VerifyHash(version, header, parameters, body[3], body.GetLengthBytes())) + if (header.SecurityModel != SecurityModel.Tsm && !privacy.VerifyHash(version, header, parameters, body[3], body.GetLengthBytes())) { parameters.IsInvalid = true; } diff --git a/SharpSnmpLib/Messaging/Net6SnmpMessageExtension.cs b/SharpSnmpLib/Messaging/Net6SnmpMessageExtension.cs index 2a96a4d3..b5c8421c 100644 --- a/SharpSnmpLib/Messaging/Net6SnmpMessageExtension.cs +++ b/SharpSnmpLib/Messaging/Net6SnmpMessageExtension.cs @@ -202,8 +202,13 @@ public static async Task GetResponseAsync(this ISnmpMessage reques } var registry = new UserRegistry(); - if (request.Version == VersionCode.V3) + if (request.Version == VersionCode.V3 && request.Header.SecurityModel == SecurityModel.Usm) { + if(request.Parameters.UserName is null) + { + throw new Exception($"{nameof(request)}.{nameof(request.Parameters)}.{nameof(request.Parameters.UserName)} cannot be null when using {SecurityModel.Usm}"); + } + registry.Add(request.Parameters.UserName, request.Privacy); } diff --git a/SharpSnmpLib/Messaging/SecureDiscovery.cs b/SharpSnmpLib/Messaging/SecureDiscovery.cs new file mode 100644 index 00000000..4456e15d --- /dev/null +++ b/SharpSnmpLib/Messaging/SecureDiscovery.cs @@ -0,0 +1,175 @@ +// Discovery type. +// Copyright (C) 2008-2010 Malcolm Crowe, Lex Li, and other contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +/* + * Created by SharpDevelop. + * User: lextm + * Date: 5/24/2009 + * Time: 11:56 AM + * + * To change this template use Tools | Options | Coding | Edit Standard Headers. + */ + +using System; +using System.Globalization; +using System.Net; +using Lextm.SharpSnmpLib.Security; +using System.Threading.Tasks; +using DTLS; + +namespace Lextm.SharpSnmpLib.Messaging +{ + /// + /// Discovery class that participates in SNMP v3 discovery process. + /// + public sealed class SecureDiscovery + { + private readonly ISnmpMessage _discovery; + private static readonly UserRegistry Empty = new(); + private static readonly SecurityParameters DefaultSecurityParameters = + new( + OctetString.Empty, + Integer32.Zero, + Integer32.Zero, + OctetString.Empty, + OctetString.Empty, + OctetString.Empty); + + /// + /// Initializes a new instance of the class. + /// + /// The request id. + /// The message id. + /// The max size of message. + public SecureDiscovery(int messageId, int requestId, int maxMessageSize) + { + _discovery = new GetRequestMessage( + VersionCode.V3, + new Header( + new Integer32(messageId), + new Integer32(maxMessageSize), + Levels.Reportable), + DefaultSecurityParameters, + new Scope( + OctetString.Empty, + OctetString.Empty, + new GetRequestPdu(requestId, [])), + DefaultPrivacyProvider.DefaultPair, + null); + } + + /// + /// Gets the response. + /// + /// The time-out value, in milliseconds. The default value is 0, which indicates an infinite time-out period. Specifying -1 also indicates an infinite time-out period. + /// The receiver. + /// + #if NET6_0 || NET5_0 + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetResponse is incompatible with trimming.")] + #endif + public ReportMessage GetResponse(int timeout, IPEndPoint receiver) + { + if (receiver == null) + { + throw new ArgumentNullException(nameof(receiver)); + } + + using (var socket = receiver.GetSocket()) + { + return (ReportMessage)_discovery.GetResponse(timeout, receiver, Empty, socket); + } + } + + /// + /// Gets the response. + /// + /// The time-out value, in milliseconds. The default value is 0, which indicates an infinite time-out period. Specifying -1 also indicates an infinite time-out period. + /// The time-out value, in milliseconds. The default value is 0, which indicates an infinite time-out period. Specifying -1 also indicates an infinite time-out period. + /// The receiver. + /// The client for dtls. + public async Task GetResponseAsync(int connectionTimeout, int responseTimeout, IPEndPoint receiver, Client client) + { + if (receiver == null) + { + throw new ArgumentNullException(nameof(receiver)); + } + + return (ReportMessage)(await _discovery.GetSecureResponseAsync(TimeSpan.FromMilliseconds(connectionTimeout), TimeSpan.FromMilliseconds(responseTimeout), receiver, client, Empty).ConfigureAwait(false)); + } + + /// + /// Gets the response. + /// + /// The time-out value, in milliseconds. The default value is 0, which indicates an infinite time-out period. Specifying -1 also indicates an infinite time-out period. + /// The time-out value, in milliseconds. The default value is 0, which indicates an infinite time-out period. Specifying -1 also indicates an infinite time-out period. + /// The receiver. + /// The client for dtls. + /// + public async Task GetResponseAsync(TimeSpan connectionTimeout, TimeSpan responseTimeout, IPEndPoint receiver, Client client) + { + if (receiver == null) + { + throw new ArgumentNullException(nameof(receiver)); + } + + return (ReportMessage) (await _discovery.GetSecureResponseAsync(connectionTimeout, responseTimeout, receiver, client, Empty).ConfigureAwait(false)); + } + + /// + /// Gets the response. + /// + /// The receiver. + /// + #if NET6_0 || NET5_0 + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("GetResponseAsync is incompatible with trimming.")] + #endif + public async Task GetResponseAsync(IPEndPoint receiver) + { + if (receiver == null) + { + throw new ArgumentNullException(nameof(receiver)); + } + + using (var socket = receiver.GetSocket()) + { + return (ReportMessage)await _discovery.GetResponseAsync(receiver, Empty, socket).ConfigureAwait(false); + } + } + + /// + /// Converts to the bytes. + /// + /// + public byte[] ToBytes() + { + return _discovery.ToBytes(); + } + + /// + /// Returns a that represents this instance. + /// + /// + /// A that represents this instance. + /// + public override string ToString() + { + return string.Format(CultureInfo.InvariantCulture, "discovery class: message id: {0}; request id: {1}", _discovery.MessageId(), _discovery.RequestId()); + } + } +} diff --git a/SharpSnmpLib/Messaging/SecureMessageExtensions.cs b/SharpSnmpLib/Messaging/SecureMessageExtensions.cs new file mode 100644 index 00000000..aed5f67c --- /dev/null +++ b/SharpSnmpLib/Messaging/SecureMessageExtensions.cs @@ -0,0 +1,145 @@ +using DTLS; +using Lextm.SharpSnmpLib.Security; +using System; +using System.Globalization; +using System.Net; +using System.Threading.Tasks; + +namespace Lextm.SharpSnmpLib.Messaging +{ + /// + /// Extension methods for . + /// + public static class SecureMessageExtensions + { + /// + /// Sends an and handles the response from agent. + /// + /// The . + /// The time-out value, in milliseconds for how long to wait for a connection + /// The time-out value, in milliseconds for how long to wait for a response. The default value is 0, which indicates an infinite time-out period. Specifying -1 also indicates an infinite time-out period. + /// Agent. + /// The DTLS client + /// + public static async Task GetSecureResponseAsync(this ISnmpMessage request, TimeSpan connectionTimeout, TimeSpan responseTimeout, IPEndPoint receiver, Client client) + { + if (request is null) + { + throw new ArgumentNullException(nameof(request)); + } + + if (receiver is null) + { + throw new ArgumentNullException(nameof(receiver)); + } + + if (client is null) + { + throw new ArgumentNullException(nameof(client)); + } + + var registry = new UserRegistry(); + //if (request.Version == VersionCode.V3) + //{ + // registry.Add(request.Parameters.UserName, request.Privacy); + //} + + return await request.GetSecureResponseAsync(connectionTimeout, responseTimeout, receiver, client, registry).ConfigureAwait(false); + } + + /// + /// Sends an and handles the response from agent. + /// + /// The . + /// The time-out value, in milliseconds for how long to wait for a connection + /// The time-out value, in milliseconds for how long to wait for a response. The default value is 0, which indicates an infinite time-out period. Specifying -1 also indicates an infinite time-out period. + /// Agent. + /// The DTLS client + /// + public static async Task GetSecureResponseAsync(this ISnmpMessage request, int connectionTimeout, int responseTimeout, IPEndPoint receiver, Client client) + { + if (request is null) + { + throw new ArgumentNullException(nameof(request)); + } + + if (receiver is null) + { + throw new ArgumentNullException(nameof(receiver)); + } + + if (client is null) + { + throw new ArgumentNullException(nameof(client)); + } + + var registry = new UserRegistry(); + //if (request.Version == VersionCode.V3) + //{ + // registry.Add(request.Parameters.UserName, request.Privacy); + //} + + return await request.GetSecureResponseAsync(TimeSpan.FromMilliseconds(connectionTimeout), TimeSpan.FromMilliseconds(responseTimeout), receiver, client, registry).ConfigureAwait(false); + } + + /// + /// Sends an and handles the response from agent. + /// + /// The . + /// The time-out value, in milliseconds for how long to wait for a connection + /// The time-out value, in milliseconds for how long to wait for a response. The default value is 0, which indicates an infinite time-out period. Specifying -1 also indicates an infinite time-out period. + /// Agent. + /// The DTLS client + /// The user registry. + /// + public static async Task GetSecureResponseAsync(this ISnmpMessage request, TimeSpan connectionTimeout, TimeSpan responseTimeout, IPEndPoint receiver, Client client, UserRegistry registry) + { + if (request == null) + { + throw new ArgumentNullException(nameof(request)); + } + + if (receiver == null) + { + throw new ArgumentNullException(nameof(receiver)); + } + + if (client is null) + { + throw new ArgumentNullException(nameof(client)); + } + + if (registry == null) + { + throw new ArgumentNullException(nameof(registry)); + } + + var requestCode = request.TypeCode(); + if (requestCode == SnmpType.TrapV1Pdu || requestCode == SnmpType.TrapV2Pdu || requestCode == SnmpType.ReportPdu) + { + throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "not a request message: {0}", requestCode)); + } + + var bytes = request.ToBytes(); + await client.ConnectToServerAsync(receiver, responseTimeout, connectionTimeout).ConfigureAwait(false); + var reply = await client.SendAndGetResponseAsync(bytes, responseTimeout).ConfigureAwait(false); + + // Passing 'count' is not necessary because ParseMessages should ignore it, but it offer extra safety (and would avoid an issue if parsing >1 response). + var response = MessageFactory.ParseMessages(reply, 0, reply.Length, registry)[0]; + var responseCode = response.TypeCode(); + if (responseCode == SnmpType.ResponsePdu || responseCode == SnmpType.ReportPdu) + { + var requestId = request.MessageId(); + var responseId = response.MessageId(); + if (responseId != requestId) + { + throw OperationException.Create(string.Format(CultureInfo.InvariantCulture, "wrong response sequence: expected {0}, received {1}", requestId, responseId), receiver.Address); + } + + return response; + } + + throw OperationException.Create(string.Format(CultureInfo.InvariantCulture, "wrong response type: {0}", responseCode), receiver.Address); + } + } +} diff --git a/SharpSnmpLib/Messaging/SetRequestMessage.cs b/SharpSnmpLib/Messaging/SetRequestMessage.cs index 81ec6360..60844605 100644 --- a/SharpSnmpLib/Messaging/SetRequestMessage.cs +++ b/SharpSnmpLib/Messaging/SetRequestMessage.cs @@ -83,6 +83,48 @@ public SetRequestMessage(VersionCode version, int messageId, int requestId, Octe { } + /// + /// Initializes a new instance of the class. + /// + /// The version. + /// The message id. + /// The request id. + /// The context name. + /// The variables. + /// The privacy provider. + /// Size of the max message. + public SetRequestMessage(VersionCode version, int messageId, int requestId, OctetString contextName, IList variables, IPrivacyProvider privacy, int maxMessageSize) + { + if (variables == null) + { + throw new ArgumentNullException(nameof(variables)); + } + + if (contextName == null) + { + throw new ArgumentNullException(nameof(contextName)); + } + + if (version != VersionCode.V3) + { + throw new ArgumentException("Only v3 is supported.", nameof(version)); + } + + Version = version; + Privacy = privacy ?? throw new ArgumentNullException(nameof(privacy)); + Header = new Header(new Integer32(messageId), new Integer32(maxMessageSize), privacy.ToSecurityLevel() | Levels.Reportable, new Integer32((int)SecurityModel.Tsm)); + Parameters = SecurityParameters.Empty; + + var pdu = new SetRequestPdu( + requestId, + variables); + var contextEngineId = OctetString.Empty; + Scope = new Scope(contextEngineId, contextName, pdu); + + Privacy.ComputeHash(Version, Header, Parameters, Scope); + _bytes = this.PackMessage(null).ToBytes(); + } + /// /// Initializes a new instance of the class. /// @@ -95,7 +137,8 @@ public SetRequestMessage(VersionCode version, int messageId, int requestId, Octe /// The privacy provider. /// Size of the max message. /// The report. - public SetRequestMessage(VersionCode version, int messageId, int requestId, OctetString userName, OctetString contextName, IList variables, IPrivacyProvider privacy, int maxMessageSize, ISnmpMessage report) + /// The type of security model + public SetRequestMessage(VersionCode version, int messageId, int requestId, OctetString userName, OctetString contextName, IList variables, IPrivacyProvider privacy, int maxMessageSize, ISnmpMessage report, SecurityModel securityModel) { if (variables == null) { @@ -125,16 +168,25 @@ public SetRequestMessage(VersionCode version, int messageId, int requestId, Octe Version = version; Privacy = privacy ?? throw new ArgumentNullException(nameof(privacy)); - Header = new Header(new Integer32(messageId), new Integer32(maxMessageSize), privacy.ToSecurityLevel() | Levels.Reportable); + Header = new Header(new Integer32(messageId), new Integer32(maxMessageSize), privacy.ToSecurityLevel() | Levels.Reportable, new Integer32((int)securityModel)); var parameters = report.Parameters; var authenticationProvider = Privacy.AuthenticationProvider; - Parameters = new SecurityParameters( - parameters.EngineId, - parameters.EngineBoots, - parameters.EngineTime, - userName, - authenticationProvider.CleanDigest, - Privacy.Salt); + + if (securityModel == SecurityModel.Tsm) + { + Parameters = SecurityParameters.Empty; + } + else + { + Parameters = new SecurityParameters( + parameters.EngineId, + parameters.EngineBoots, + parameters.EngineTime, + userName, + authenticationProvider.CleanDigest, + Privacy.Salt); + } + var pdu = new SetRequestPdu( requestId, variables); @@ -151,6 +203,24 @@ public SetRequestMessage(VersionCode version, int messageId, int requestId, Octe _bytes = this.PackMessage(null).ToBytes(); } + /// + /// Initializes a new instance of the class. + /// + /// The version. + /// The message id. + /// The request id. + /// Name of the user. + /// The context name. + /// The variables. + /// The privacy provider. + /// Size of the max message. + /// The report. + public SetRequestMessage(VersionCode version, int messageId, int requestId, OctetString userName, OctetString contextName, IList variables, IPrivacyProvider privacy, int maxMessageSize, ISnmpMessage report) + : this(version, messageId, requestId, userName, contextName, variables, privacy, maxMessageSize, report, SecurityModel.Usm) + { + + } + /// /// Initializes a new instance of the class. /// diff --git a/SharpSnmpLib/Messaging/SnmpMessageExtension.cs b/SharpSnmpLib/Messaging/SnmpMessageExtension.cs index 577ff07c..d82cabd8 100644 --- a/SharpSnmpLib/Messaging/SnmpMessageExtension.cs +++ b/SharpSnmpLib/Messaging/SnmpMessageExtension.cs @@ -121,7 +121,7 @@ public static OctetString Community(this ISnmpMessage message) throw new ArgumentNullException(nameof(message)); } - return message.Parameters.UserName; + return message.Parameters.UserName ?? OctetString.Empty; } #region sync methods @@ -287,8 +287,13 @@ public static ISnmpMessage GetResponse(this ISnmpMessage request, int timeout, I } var registry = new UserRegistry(); - if (request.Version == VersionCode.V3) + if (request.Version == VersionCode.V3 && request.Header.SecurityModel == SecurityModel.Usm) { + if (request.Parameters.UserName is null) + { + throw new Exception($"{nameof(request)}.{nameof(request.Parameters)}.{nameof(request.Parameters.UserName)} cannot be null when using {SecurityModel.Usm}"); + } + registry.Add(request.Parameters.UserName, request.Privacy); } @@ -449,7 +454,7 @@ public static async Task SendAsync(this ISnmpMessage message, EndPoint manager, } var buffer = new ArraySegment(message.ToBytes()); - await socket.SendToAsync(buffer, SocketFlags.None, manager); + await socket.SendToAsync(buffer, SocketFlags.None, manager).ConfigureAwait(false); } /// @@ -544,8 +549,13 @@ public static async Task GetResponseAsync(this ISnmpMessage reques } var registry = new UserRegistry(); - if (request.Version == VersionCode.V3) + if (request.Version == VersionCode.V3 && request.Header.SecurityModel == SecurityModel.Usm) { + if (request.Parameters.UserName is null) + { + throw new Exception($"{nameof(request)}.{nameof(request.Parameters)}.{nameof(request.Parameters.UserName)} cannot be null when using {SecurityModel.Usm}"); + } + registry.Add(request.Parameters.UserName, request.Privacy); } @@ -591,7 +601,7 @@ public static async Task GetResponseAsync(this ISnmpMessage reques // Whatever you change, try to keep the Send and the Receive close to each other. var buffer = new ArraySegment(bytes); - await udpSocket.SendToAsync(buffer, SocketFlags.None, receiver ?? throw new ArgumentNullException(nameof(receiver))); + await udpSocket.SendToAsync(buffer, SocketFlags.None, receiver ?? throw new ArgumentNullException(nameof(receiver))).ConfigureAwait(false); int count; byte[] reply = new byte[bufSize]; @@ -602,7 +612,7 @@ public static async Task GetResponseAsync(this ISnmpMessage reques try { - var result = await udpSocket.ReceiveMessageFromAsync(new ArraySegment(reply), SocketFlags.None, remote); + var result = await udpSocket.ReceiveMessageFromAsync(new ArraySegment(reply), SocketFlags.None, remote).ConfigureAwait(false); count = result.ReceivedBytes; } catch (SocketException ex) diff --git a/SharpSnmpLib/Security/DESPrivacyProvider.cs b/SharpSnmpLib/Security/DESPrivacyProvider.cs index f679e195..25045455 100644 --- a/SharpSnmpLib/Security/DESPrivacyProvider.cs +++ b/SharpSnmpLib/Security/DESPrivacyProvider.cs @@ -443,7 +443,7 @@ public ISnmpData Encrypt(ISnmpData data, SecurityParameters parameters) throw new ArgumentNullException(nameof(parameters)); } - if (data.TypeCode != SnmpType.Sequence && !(data is ISnmpPdu)) + if (data.TypeCode != SnmpType.Sequence && data is not ISnmpPdu) { throw new ArgumentException("Invalid data type.", nameof(data)); } diff --git a/SharpSnmpLib/Security/TsmAuthenticationProvider.cs b/SharpSnmpLib/Security/TsmAuthenticationProvider.cs new file mode 100644 index 00000000..679101f0 --- /dev/null +++ b/SharpSnmpLib/Security/TsmAuthenticationProvider.cs @@ -0,0 +1,118 @@ +using System; + +namespace Lextm.SharpSnmpLib.Security +{ + /// + /// Tsm Authentication Provider + /// + public sealed class TsmAuthenticationProvider : IAuthenticationProvider + { + private TsmAuthenticationProvider() + { + + } + + private static readonly IAuthenticationProvider _Instance = new TsmAuthenticationProvider(); + + /// + /// instance of TsmAuthenticationProvider + /// + public static IAuthenticationProvider Instance => _Instance; + + /// + /// Computes the hash. + /// + /// The version. + /// The header. + /// The parameters. + /// The scope data. + /// The privacy provider. + /// The length bytes. + /// + public OctetString ComputeHash(VersionCode version, ISegment header, SecurityParameters parameters, ISnmpData data, IPrivacyProvider privacy, byte[]? length) + { + if (header == null) + { + throw new ArgumentNullException(nameof(header)); + } + + if (parameters == null) + { + throw new ArgumentNullException(nameof(parameters)); + } + + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } + + if (privacy == null) + { + throw new ArgumentNullException(nameof(privacy)); + } + + return OctetString.Empty; + } + + /// + /// Computes the hash. + /// + /// + public static OctetString ComputeHash(byte[] buffer, OctetString engineId) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + if (engineId == null) + { + throw new ArgumentNullException(nameof(engineId)); + } + + return OctetString.Empty; + } + + /// + /// Gets the clean digest. + /// + /// The clean digest. + public OctetString CleanDigest => OctetString.Empty; + + /// + /// Converts password to key. + /// + /// + /// + /// + public byte[] PasswordToKey(byte[] password, byte[] engineId) + { + if (password == null) + { + throw new ArgumentNullException(nameof(password)); + } + + if (engineId == null) + { + throw new ArgumentNullException(nameof(engineId)); + } + + // IMPORTANT: this function is not used. + return []; + } + + /// + /// Gets the length of the digest. + /// + /// The length of the digest. + public int DigestLength => 0; + + /// + /// Returns a that represents this instance. + /// + /// + /// A that represents this instance. + /// + public override string ToString() => "TSM authentication provider"; + } +} diff --git a/SharpSnmpLib/Security/TsmPrivacyProvider.cs b/SharpSnmpLib/Security/TsmPrivacyProvider.cs new file mode 100644 index 00000000..db6b9b18 --- /dev/null +++ b/SharpSnmpLib/Security/TsmPrivacyProvider.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections.Generic; + +namespace Lextm.SharpSnmpLib.Security +{ + /// + /// Default privacy provider with default authentication provider. + /// + public sealed class TsmPrivacyProvider : IPrivacyProvider + { + private static readonly IPrivacyProvider _DefaultInstance = new DefaultPrivacyProvider(DefaultAuthenticationProvider.Instance); + + /// + /// Default privacy provider with default authentication provider. + /// + public static IPrivacyProvider DefaultPair => _DefaultInstance; + + /// + /// Initializes a new instance of the class. + /// + /// Authentication provider. + public TsmPrivacyProvider(IAuthenticationProvider auth) + { + AuthenticationProvider = auth ?? throw new ArgumentNullException(nameof(auth)); + } + + #region IPrivacyProvider Members + + /// + /// Corresponding . + /// + public IAuthenticationProvider AuthenticationProvider { get; private set; } + + /// + /// Engine IDs. + /// + /// This is an optional field, and only used by TRAP v2 authentication. + public ICollection? EngineIds { get; set; } + + /// + /// Decrypts the specified data. + /// + /// The data. + /// The parameters. + /// + public ISnmpData Decrypt(ISnmpData data, SecurityParameters parameters) + { + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } + + if (parameters == null) + { + throw new ArgumentNullException(nameof(parameters)); + } + + if (data.TypeCode != SnmpType.Sequence) + { + var newException = new DecryptionException("Default decryption failed"); + throw newException; + } + + return data; + } + + /// + /// Encrypts the specified scope. + /// + /// The scope data. + /// The parameters. + /// + public ISnmpData Encrypt(ISnmpData data, SecurityParameters parameters) + { + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } + + if (parameters == null) + { + throw new ArgumentNullException(nameof(parameters)); + } + + if (data.TypeCode == SnmpType.Sequence || data is ISnmpPdu) + { + return data; + } + + throw new ArgumentException("Invaild data type.", nameof(data)); + } + + /// + /// Gets the salt. + /// + /// The salt. + public OctetString Salt => OctetString.Empty; + + /// + /// Passwords to key. + /// + /// The secret. + /// The engine identifier. + /// + public byte[] PasswordToKey(byte[] secret, byte[] engineId) => AuthenticationProvider.PasswordToKey(secret, engineId); + + #endregion + + /// + /// Returns a that represents this instance. + /// + /// + /// A that represents this instance. + /// + public override string ToString() => "Tsm privacy provider"; + } +} diff --git a/SharpSnmpLib/SecurityModel.cs b/SharpSnmpLib/SecurityModel.cs new file mode 100644 index 00000000..eca4b229 --- /dev/null +++ b/SharpSnmpLib/SecurityModel.cs @@ -0,0 +1,36 @@ +// Security Model enum. +// Copyright (C) 2008-2010 Malcolm Crowe, Lex Li, and other contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +namespace Lextm.SharpSnmpLib +{ + /// + /// Security Model enum for SNMPv3 + /// + public enum SecurityModel + { + /// + /// USM - User-based Security Model + /// + Usm = 3, + /// + /// TSM - Transport Security Model + /// + Tsm = 4 + } +} diff --git a/SharpSnmpLib/SecurityParameters.cs b/SharpSnmpLib/SecurityParameters.cs index c7c85cd2..fb94d38b 100644 --- a/SharpSnmpLib/SecurityParameters.cs +++ b/SharpSnmpLib/SecurityParameters.cs @@ -36,6 +36,19 @@ namespace Lextm.SharpSnmpLib /// public sealed class SecurityParameters : ISegment { + /// + /// Initializes a new instance of the class. + /// + private SecurityParameters() + { + + } + + /// + /// Initializes an empty instance of the class. + /// + public static SecurityParameters Empty => new(); + /// /// Gets the engine ID. /// @@ -58,7 +71,7 @@ public sealed class SecurityParameters : ISegment /// Gets the user name. /// /// The user name. - public OctetString UserName { get; } + public OctetString? UserName { get; } private OctetString? _authenticationParameters; private readonly byte[]? _length; @@ -112,6 +125,11 @@ public SecurityParameters(OctetString parameters) throw new ArgumentNullException(nameof(parameters)); } + if (parameters == OctetString.Empty) + { + return; + } + var container = (Sequence)DataFactory.CreateSnmpData(parameters.GetRaw()); EngineId = (OctetString)container[0]; EngineBoots = (Integer32)container[1]; @@ -149,6 +167,10 @@ public SecurityParameters(OctetString? engineId, Integer32? engineBoots, Integer /// public static SecurityParameters Create(OctetString userName) { + if (userName is null) + { + throw new ArgumentNullException(nameof(userName)); + } return new SecurityParameters(null, null, null, userName, null, null); } @@ -170,7 +192,14 @@ public Sequence ToSequence() /// public ISnmpData GetData(VersionCode version) { - return version == VersionCode.V3 ? new OctetString(ToSequence().ToBytes()) : UserName; + //if empty SecurityParameters, return an empty OctetString + if (_length == null && EngineId == null && EngineBoots == null && EngineTime == null && UserName == null && PrivacyParameters == null + && (AuthenticationParameters == OctetString.Empty || AuthenticationParameters == null)) + { + return OctetString.Empty; + } + + return version == VersionCode.V3 ? new OctetString(ToSequence().ToBytes()) : (UserName ?? OctetString.Empty); } #endregion @@ -183,7 +212,7 @@ public ISnmpData GetData(VersionCode version) /// public override string ToString() { - return string.Format(CultureInfo.InvariantCulture, "Security parameters: engineId: {0};engineBoots: {1};engineTime: {2};userName: {3}; authen hash: {4}; privacy hash: {5}", EngineId, EngineBoots, EngineTime, UserName, AuthenticationParameters == null ? null : AuthenticationParameters.ToHexString(), PrivacyParameters == null ? null : PrivacyParameters.ToHexString()); + return string.Format(CultureInfo.InvariantCulture, "Security parameters: engineId: {0};engineBoots: {1};engineTime: {2};userName: {3}; authen hash: {4}; privacy hash: {5}", EngineId, EngineBoots, EngineTime, UserName, AuthenticationParameters?.ToHexString(), PrivacyParameters?.ToHexString()); } /// diff --git a/SharpSnmpLib/SharpSnmpLib.csproj b/SharpSnmpLib/SharpSnmpLib.csproj index bd61a8d1..962728be 100644 --- a/SharpSnmpLib/SharpSnmpLib.csproj +++ b/SharpSnmpLib/SharpSnmpLib.csproj @@ -40,6 +40,7 @@ S5547;S907;S1133;S101;S1135 + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/Tests/CSharpCore/Unit/Messaging/GetRequestMessageTestFixture.cs b/Tests/CSharpCore/Unit/Messaging/GetRequestMessageTestFixture.cs index 81248db3..03175ca4 100644 --- a/Tests/CSharpCore/Unit/Messaging/GetRequestMessageTestFixture.cs +++ b/Tests/CSharpCore/Unit/Messaging/GetRequestMessageTestFixture.cs @@ -109,6 +109,69 @@ public void TestConstructorV3Auth1() Assert.Equal(ByteTool.Convert(bytes), request.ToBytes()); } + [Fact] + public void TestConstructorV3Auth2() + { + const string bytes = "30 73" + + "02 01 03 " + + "30 0F " + + "02 02 35 41 " + + "02 03 00 FF E3" + + "04 01 05" + + "02 01 03" + + "04 2E " + + "30 2C" + + "04 0D 80 00 1F 88 80 E9 63 00 00 D6 1F F4 49 " + + "02 01 0D " + + "02 01 57 " + + "04 05 6C 65 78 6C 69 " + + "04 0C 1C 6D 67 BF B2 38 ED 63 DF 0A 05 24 " + + "04 00 " + + "30 2D " + + "04 0D 80 00 1F 88 80 E9 63 00 00 D6 1F F4 49 " + + "04 00 " + + "A0 1A 02 02 01 AF 02 01 00 02 01 00 30 0E 30 0C 06 08 2B 06 01 02 01 01 03 00 05 00"; + ReportMessage report = new ReportMessage( + VersionCode.V3, + new Header( + new Integer32(13633), + new Integer32(0xFFE3), + 0), + new SecurityParameters( + new OctetString(ByteTool.Convert("80 00 1F 88 80 E9 63 00 00 D6 1F F4 49")), + new Integer32(0x0d), + new Integer32(0x57), + new OctetString("lexli"), + new OctetString(new byte[12]), + OctetString.Empty), + new Scope( + new OctetString(ByteTool.Convert("80 00 1F 88 80 E9 63 00 00 D6 1F F4 49")), + OctetString.Empty, + new ReportPdu( + 0x01AF, + ErrorCode.NoError, + 0, + new List(1) { new Variable(new ObjectIdentifier("1.3.6.1.2.1.1.3.0")) })), + DefaultPrivacyProvider.DefaultPair, + null); + + IPrivacyProvider privacy = new DefaultPrivacyProvider(new MD5AuthenticationProvider(new OctetString("testpass"))); + GetRequestMessage request = new GetRequestMessage( + VersionCode.V3, + 13633, + 0x01AF, + new OctetString("lexli"), + OctetString.Empty, + new List(1) { new Variable(new ObjectIdentifier("1.3.6.1.2.1.1.3.0")) }, + privacy, + Messenger.MaxMessageSize, + report, + SecurityModel.Usm); + + Assert.Equal(Levels.Authentication | Levels.Reportable, request.Header.SecurityLevel); + Assert.Equal(ByteTool.Convert(bytes), request.ToBytes()); + } + [Fact] public void TestConstructorV2AuthMd5PrivDes() { @@ -238,6 +301,27 @@ public void TestConstructorV3AuthSha() Assert.Equal(ByteTool.Convert(bytes), request.ToBytes()); } + [Fact] + public void TestConstructorV3OverDtls() + { + const string bytes = "30 3c 02 01 03 30 11 02 04 2f 1f 26 a3 02 03 00" + + "ff e3 04 01 07 02 01 04 04 00 30 22 04 00 04 00" + + "a0 1c 02 04 ab 53 bd 3f 02 01 00 02 01 00 30 0e" + + "30 0c 06 08 2b 06 01 02 01 01 03 00 05 00"; + + var request = new GetRequestMessage( + VersionCode.V3, + 790570659, + -1420575425, + OctetString.Empty, + new List() { new Variable(new ObjectIdentifier("1.3.6.1.2.1.1.3.0")) }, + new TsmPrivacyProvider(TsmAuthenticationProvider.Instance), + 65507); + + Assert.Equal(Levels.Authentication | Levels.Reportable | Levels.Privacy, request.Header.SecurityLevel); + Assert.Equal(ByteTool.Convert(bytes), request.ToBytes()); + } + [Fact] public void TestDiscoveryV3() { diff --git a/Tests/CSharpCore/Unit/Security/TsmAuthenticationProviderTestFixture.cs b/Tests/CSharpCore/Unit/Security/TsmAuthenticationProviderTestFixture.cs new file mode 100644 index 00000000..9ad3e072 --- /dev/null +++ b/Tests/CSharpCore/Unit/Security/TsmAuthenticationProviderTestFixture.cs @@ -0,0 +1,19 @@ +using System; +using Lextm.SharpSnmpLib.Security; +using Xunit; + +namespace Lextm.SharpSnmpLib.Unit.Security +{ + public class TsmAuthenticationProviderTestFixture + { + [Fact] + public void Test() + { + var provider = TsmAuthenticationProvider.Instance; + Assert.Equal("TSM authentication provider", provider.ToString()); + Assert.Throws(() => provider.PasswordToKey(null, null)); + Assert.Throws(() => provider.PasswordToKey(new byte[0], null)); + Assert.Equal(new byte[0], provider.PasswordToKey(new byte[0], new byte[0])); + } + } +} diff --git a/Tests/CSharpCore/Unit/Security/TsmPrivacyProviderTestFixture.cs b/Tests/CSharpCore/Unit/Security/TsmPrivacyProviderTestFixture.cs new file mode 100644 index 00000000..9cfc9aca --- /dev/null +++ b/Tests/CSharpCore/Unit/Security/TsmPrivacyProviderTestFixture.cs @@ -0,0 +1,26 @@ +using System; +using Lextm.SharpSnmpLib.Security; +using Xunit; + +namespace Lextm.SharpSnmpLib.Unit.Security +{ + public class TsmPrivacyProviderTestFixture + { + [Fact] + public void Test() + { + var provider = TsmPrivacyProvider.DefaultPair; + Assert.Throws(() => provider.Encrypt(null, null)); + Assert.Throws(() => provider.Encrypt(OctetString.Empty, null)); + Assert.Throws(() => provider.Encrypt(new Null(), SecurityParameters.Create(OctetString.Empty))); + + var expected = new Sequence((byte[])null); + Assert.Equal(expected, provider.Encrypt(expected, SecurityParameters.Create(OctetString.Empty))); + + Assert.Throws(() => provider.Decrypt(null, null)); + Assert.Throws(() => provider.Decrypt(OctetString.Empty, null)); + var result = provider.Decrypt(new Sequence((byte[])null), SecurityParameters.Create(OctetString.Empty)); + Assert.NotNull(result); + } + } +} diff --git a/Tests/CSharpCore/Unit/SecurityParametersTestFixture.cs b/Tests/CSharpCore/Unit/SecurityParametersTestFixture.cs index 89862c04..7d06d1e0 100644 --- a/Tests/CSharpCore/Unit/SecurityParametersTestFixture.cs +++ b/Tests/CSharpCore/Unit/SecurityParametersTestFixture.cs @@ -21,5 +21,11 @@ public void TestToString() obj.AuthenticationParameters = new OctetString("one"); Assert.Throws(() => obj.AuthenticationParameters = new OctetString("me")); } + + [Fact] + public void TestGetData() + { + Assert.Equal(OctetString.Empty, SecurityParameters.Empty.GetData(VersionCode.V3)); + } } }