From e534228bb933837e1272a5bad8f3f4419da2d1e3 Mon Sep 17 00:00:00 2001 From: Rob Hague Date: Wed, 15 Nov 2023 21:32:33 +0100 Subject: [PATCH] Tweak diagnostics * Make it work (it needs the TRACE symbol defined - lots of head scratching without it) * Expose publicly, but still in DEBUG (to allow programmatic configuration necessary in Core) * Document how to use it * Tweak usage (add some logs, remove key/iv information, override ToString on some Message types) --- .../Abstractions/DiagnosticAbstraction.cs | 68 +++++++++++++++---- .../Messages/Authentication/FailureMessage.cs | 8 +++ .../Authentication/PublicKeyMessage.cs | 6 ++ .../Messages/Authentication/RequestMessage.cs | 6 ++ .../Authentication/RequestMessagePublicKey.cs | 6 ++ src/Renci.SshNet/Renci.SshNet.csproj | 4 +- src/Renci.SshNet/Security/KeyExchange.cs | 26 +++++-- src/Renci.SshNet/Session.cs | 6 +- 8 files changed, 107 insertions(+), 23 deletions(-) diff --git a/src/Renci.SshNet/Abstractions/DiagnosticAbstraction.cs b/src/Renci.SshNet/Abstractions/DiagnosticAbstraction.cs index 014d70689..9de9bbea4 100644 --- a/src/Renci.SshNet/Abstractions/DiagnosticAbstraction.cs +++ b/src/Renci.SshNet/Abstractions/DiagnosticAbstraction.cs @@ -1,27 +1,65 @@ -using System.Diagnostics; +using System.ComponentModel; +using System.Diagnostics; namespace Renci.SshNet.Abstractions { - internal static class DiagnosticAbstraction + /// + /// Provides access to the internals of SSH.NET. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public static class DiagnosticAbstraction { - private static readonly SourceSwitch SourceSwitch = new SourceSwitch("SshNetSwitch"); - - public static bool IsEnabled(TraceEventType traceEventType) - { - return SourceSwitch.ShouldTrace(traceEventType); - } - - private static readonly TraceSource Loggging = #if DEBUG - new TraceSource("SshNet.Logging", SourceLevels.All); -#else - new TraceSource("SshNet.Logging"); -#endif // DEBUG + /// + /// The instance used by SSH.NET. + /// + /// + /// + /// Configuration on .NET Core must be done programmatically, e.g. + /// + /// DiagnosticAbstraction.Source.Switch = new SourceSwitch("sourceSwitch", "Verbose"); + /// DiagnosticAbstraction.Source.Listeners.Remove("Default"); + /// DiagnosticAbstraction.Source.Listeners.Add(new ConsoleTraceListener()); + /// DiagnosticAbstraction.Source.Listeners.Add(new TextWriterTraceListener("trace.log")); + /// + /// + /// + /// On .NET Framework, it is possible to configure via App.config, e.g. + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// ]]> + /// + /// + /// + public static readonly TraceSource Source = new TraceSource("SshNet.Logging"); +#endif + /// + /// Logs a message to at the + /// level. + /// + /// The message to log. [Conditional("DEBUG")] public static void Log(string text) { - Loggging.TraceEvent(TraceEventType.Verbose, + Source.TraceEvent(TraceEventType.Verbose, System.Environment.CurrentManagedThreadId, text); } diff --git a/src/Renci.SshNet/Messages/Authentication/FailureMessage.cs b/src/Renci.SshNet/Messages/Authentication/FailureMessage.cs index 61379600f..f02705ecb 100644 --- a/src/Renci.SshNet/Messages/Authentication/FailureMessage.cs +++ b/src/Renci.SshNet/Messages/Authentication/FailureMessage.cs @@ -60,5 +60,13 @@ internal override void Process(Session session) { session.OnUserAuthenticationFailureReceived(this); } + + /// + public override string ToString() + { +#pragma warning disable MA0089 // Optimize string method usage + return $"SSH_MSG_USERAUTH_FAILURE {string.Join(",", AllowedAuthentications)} ({nameof(PartialSuccess)}:{PartialSuccess})"; +#pragma warning restore MA0089 // Optimize string method usage + } } } diff --git a/src/Renci.SshNet/Messages/Authentication/PublicKeyMessage.cs b/src/Renci.SshNet/Messages/Authentication/PublicKeyMessage.cs index b5582b788..b917ab99a 100644 --- a/src/Renci.SshNet/Messages/Authentication/PublicKeyMessage.cs +++ b/src/Renci.SshNet/Messages/Authentication/PublicKeyMessage.cs @@ -60,5 +60,11 @@ protected override void SaveData() WriteBinaryString(PublicKeyAlgorithmName); WriteBinaryString(PublicKeyData); } + + /// + public override string ToString() + { + return $"SSH_MSG_USERAUTH_PK_OK ({Ascii.GetString(PublicKeyAlgorithmName)})"; + } } } diff --git a/src/Renci.SshNet/Messages/Authentication/RequestMessage.cs b/src/Renci.SshNet/Messages/Authentication/RequestMessage.cs index 5393df3f7..57f60849f 100644 --- a/src/Renci.SshNet/Messages/Authentication/RequestMessage.cs +++ b/src/Renci.SshNet/Messages/Authentication/RequestMessage.cs @@ -106,5 +106,11 @@ internal override void Process(Session session) { throw new NotImplementedException(); } + + /// + public override string ToString() + { + return $"SSH_MSG_USERAUTH_REQUEST ({MethodName})"; + } } } diff --git a/src/Renci.SshNet/Messages/Authentication/RequestMessagePublicKey.cs b/src/Renci.SshNet/Messages/Authentication/RequestMessagePublicKey.cs index 391e60e76..a759ef20c 100644 --- a/src/Renci.SshNet/Messages/Authentication/RequestMessagePublicKey.cs +++ b/src/Renci.SshNet/Messages/Authentication/RequestMessagePublicKey.cs @@ -96,5 +96,11 @@ protected override void SaveData() WriteBinaryString(Signature); } } + + /// + public override string ToString() + { + return $"{base.ToString()} {Ascii.GetString(PublicKeyAlgorithmName)} {(Signature != null ? "with" : "without")} signature."; + } } } diff --git a/src/Renci.SshNet/Renci.SshNet.csproj b/src/Renci.SshNet/Renci.SshNet.csproj index 4ccf6c72e..89e82349d 100644 --- a/src/Renci.SshNet/Renci.SshNet.csproj +++ b/src/Renci.SshNet/Renci.SshNet.csproj @@ -6,7 +6,7 @@ - FEATURE_BINARY_SERIALIZATION;FEATURE_SOCKET_EAP;FEATURE_SOCKET_APM;FEATURE_DNS_SYNC;FEATURE_HASH_RIPEMD160_CREATE;FEATURE_HMAC_RIPEMD160 + $(DefineConstants);FEATURE_BINARY_SERIALIZATION;FEATURE_SOCKET_EAP;FEATURE_SOCKET_APM;FEATURE_DNS_SYNC;FEATURE_HASH_RIPEMD160_CREATE;FEATURE_HMAC_RIPEMD160 @@ -18,6 +18,6 @@ - FEATURE_SOCKET_TAP;FEATURE_SOCKET_APM;FEATURE_SOCKET_EAP;FEATURE_DNS_SYNC;FEATURE_DNS_APM;FEATURE_DNS_TAP + $(DefineConstants);FEATURE_SOCKET_TAP;FEATURE_SOCKET_APM;FEATURE_SOCKET_EAP;FEATURE_DNS_SYNC;FEATURE_DNS_APM;FEATURE_DNS_TAP diff --git a/src/Renci.SshNet/Security/KeyExchange.cs b/src/Renci.SshNet/Security/KeyExchange.cs index 5024941bf..44684a92e 100644 --- a/src/Renci.SshNet/Security/KeyExchange.cs +++ b/src/Renci.SshNet/Security/KeyExchange.cs @@ -183,11 +183,9 @@ public Cipher CreateServerCipher() serverKey = GenerateSessionKey(SharedKey, ExchangeHash, serverKey, _serverCipherInfo.KeySize / 8); - DiagnosticAbstraction.Log(string.Format("[{0}] Creating server cipher (Name:{1},Key:{2},IV:{3})", + DiagnosticAbstraction.Log(string.Format("[{0}] Creating {1} server cipher.", Session.ToHex(Session.SessionId), - Session.ConnectionInfo.CurrentServerEncryption, - Session.ToHex(serverKey), - Session.ToHex(serverVector))); + Session.ConnectionInfo.CurrentServerEncryption)); // Create server cipher return _serverCipherInfo.Cipher(serverKey, serverVector); @@ -210,6 +208,10 @@ public Cipher CreateClientCipher() clientKey = GenerateSessionKey(SharedKey, ExchangeHash, clientKey, _clientCipherInfo.KeySize / 8); + DiagnosticAbstraction.Log(string.Format("[{0}] Creating {1} client cipher.", + Session.ToHex(Session.SessionId), + Session.ConnectionInfo.CurrentClientEncryption)); + // Create client cipher return _clientCipherInfo.Cipher(clientKey, clientVector); } @@ -230,6 +232,10 @@ public HashAlgorithm CreateServerHash() Hash(GenerateSessionKey(SharedKey, ExchangeHash, 'F', sessionId)), _serverHashInfo.KeySize / 8); + DiagnosticAbstraction.Log(string.Format("[{0}] Creating {1} server hmac algorithm.", + Session.ToHex(Session.SessionId), + Session.ConnectionInfo.CurrentServerHmacAlgorithm)); + return _serverHashInfo.HashAlgorithm(serverKey); } @@ -249,6 +255,10 @@ public HashAlgorithm CreateClientHash() Hash(GenerateSessionKey(SharedKey, ExchangeHash, 'E', sessionId)), _clientHashInfo.KeySize / 8); + DiagnosticAbstraction.Log(string.Format("[{0}] Creating {1} client hmac algorithm.", + Session.ToHex(Session.SessionId), + Session.ConnectionInfo.CurrentClientHmacAlgorithm)); + return _clientHashInfo.HashAlgorithm(clientKey); } @@ -265,6 +275,10 @@ public Compressor CreateCompressor() return null; } + DiagnosticAbstraction.Log(string.Format("[{0}] Creating {1} client compressor.", + Session.ToHex(Session.SessionId), + Session.ConnectionInfo.CurrentClientCompressionAlgorithm)); + var compressor = _compressionType.CreateInstance(); compressor.Init(Session); @@ -285,6 +299,10 @@ public Compressor CreateDecompressor() return null; } + DiagnosticAbstraction.Log(string.Format("[{0}] Creating {1} server decompressor.", + Session.ToHex(Session.SessionId), + Session.ConnectionInfo.CurrentServerCompressionAlgorithm)); + var decompressor = _decompressionType.CreateInstance(); decompressor.Init(Session); diff --git a/src/Renci.SshNet/Session.cs b/src/Renci.SshNet/Session.cs index 2d913d072..f05c0e0a0 100644 --- a/src/Renci.SshNet/Session.cs +++ b/src/Renci.SshNet/Session.cs @@ -616,7 +616,7 @@ public void Connect() ServerVersion = ConnectionInfo.ServerVersion = serverIdentification.ToString(); ConnectionInfo.ClientVersion = ClientVersion; - DiagnosticAbstraction.Log(string.Format("Server version '{0}' on '{1}'.", serverIdentification.ProtocolVersion, serverIdentification.SoftwareVersion)); + DiagnosticAbstraction.Log(string.Format("Server version '{0}'.", serverIdentification)); if (!(serverIdentification.ProtocolVersion.Equals("2.0") || serverIdentification.ProtocolVersion.Equals("1.99"))) { @@ -728,7 +728,7 @@ public async Task ConnectAsync(CancellationToken cancellationToken) ServerVersion = ConnectionInfo.ServerVersion = serverIdentification.ToString(); ConnectionInfo.ClientVersion = ClientVersion; - DiagnosticAbstraction.Log(string.Format("Server version '{0}' on '{1}'.", serverIdentification.ProtocolVersion, serverIdentification.SoftwareVersion)); + DiagnosticAbstraction.Log(string.Format("Server version '{0}'.", serverIdentification)); if (!(serverIdentification.ProtocolVersion.Equals("2.0") || serverIdentification.ProtocolVersion.Equals("1.99"))) { @@ -1397,6 +1397,8 @@ internal void OnKeyExchangeInitReceived(KeyExchangeInitMessage message) ConnectionInfo.CurrentKeyExchangeAlgorithm = _keyExchange.Name; + DiagnosticAbstraction.Log(string.Format("[{0}] Performing {1} key exchange.", ToHex(SessionId), ConnectionInfo.CurrentKeyExchangeAlgorithm)); + _keyExchange.HostKeyReceived += KeyExchange_HostKeyReceived; // Start the algorithm implementation