Expose SshIdentificationReceived event (#1195)

* Fix https://github.com/sshnet/SSH.NET/issues/1191

* Expose `SshIdentificationReceived` event so that lib consumer can adjust based on server identification

* revert unrelated code style change

* revert OpenSSH 6.6 related tests

* revert ConnectionBase

* Add unit tests

* Rename to `ServerIdentificationReceived`

* rename
This commit is contained in:
Scott Xu
2023-11-16 21:35:59 +08:00
committed by GitHub
parent 54d01621aa
commit f9f2b0e5f4
7 changed files with 128 additions and 4 deletions
+13
View File
@@ -153,6 +153,11 @@ namespace Renci.SshNet
/// </example>
public event EventHandler<HostKeyEventArgs> HostKeyReceived;
/// <summary>
/// Occurs when server identification received.
/// </summary>
public event EventHandler<SshIdentificationEventArgs> ServerIdentificationReceived;
/// <summary>
/// Initializes a new instance of the <see cref="BaseClient"/> class.
/// </summary>
@@ -390,6 +395,11 @@ namespace Renci.SshNet
HostKeyReceived?.Invoke(this, e);
}
private void Session_ServerIdentificationReceived(object sender, SshIdentificationEventArgs e)
{
ServerIdentificationReceived?.Invoke(this, e);
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
@@ -532,6 +542,7 @@ namespace Renci.SshNet
private ISession CreateAndConnectSession()
{
var session = _serviceFactory.CreateSession(ConnectionInfo, _serviceFactory.CreateSocketFactory());
session.ServerIdentificationReceived += Session_ServerIdentificationReceived;
session.HostKeyReceived += Session_HostKeyReceived;
session.ErrorOccured += Session_ErrorOccured;
@@ -550,6 +561,7 @@ namespace Renci.SshNet
private async Task<ISession> CreateAndConnectSessionAsync(CancellationToken cancellationToken)
{
var session = _serviceFactory.CreateSession(ConnectionInfo, _serviceFactory.CreateSocketFactory());
session.ServerIdentificationReceived += Session_ServerIdentificationReceived;
session.HostKeyReceived += Session_HostKeyReceived;
session.ErrorOccured += Session_ErrorOccured;
@@ -569,6 +581,7 @@ namespace Renci.SshNet
{
session.ErrorOccured -= Session_ErrorOccured;
session.HostKeyReceived -= Session_HostKeyReceived;
session.ServerIdentificationReceived -= Session_ServerIdentificationReceived;
session.Dispose();
}
@@ -0,0 +1,26 @@
using System;
using Renci.SshNet.Connection;
namespace Renci.SshNet.Common
{
/// <summary>
/// Provides data for the ServerIdentificationReceived events.
/// </summary>
public class SshIdentificationEventArgs : EventArgs
{
/// <summary>
/// Initializes a new instance of the <see cref="SshIdentificationEventArgs"/> class.
/// </summary>
/// <param name="sshIdentification">The SSH identification.</param>
public SshIdentificationEventArgs(SshIdentification sshIdentification)
{
SshIdentification = sshIdentification;
}
/// <summary>
/// Gets the SSH identification.
/// </summary>
public SshIdentification SshIdentification { get; private set; }
}
}
@@ -5,7 +5,7 @@ namespace Renci.SshNet.Connection
/// <summary>
/// Represents an SSH identification.
/// </summary>
internal sealed class SshIdentification
public sealed class SshIdentification
{
/// <summary>
/// Initializes a new instance of the <see cref="SshIdentification"/> class with the specified protocol version
+5
View File
@@ -260,6 +260,11 @@ namespace Renci.SshNet
/// </summary>
event EventHandler<ExceptionEventArgs> ErrorOccured;
/// <summary>
/// Occurs when server identification received.
/// </summary>
event EventHandler<SshIdentificationEventArgs> ServerIdentificationReceived;
/// <summary>
/// Occurs when host key received.
/// </summary>
+9
View File
@@ -366,6 +366,11 @@ namespace Renci.SshNet
/// </summary>
public event EventHandler<EventArgs> Disconnected;
/// <summary>
/// Occurs when server identification received.
/// </summary>
public event EventHandler<SshIdentificationEventArgs> ServerIdentificationReceived;
/// <summary>
/// Occurs when host key received.
/// </summary>
@@ -624,6 +629,8 @@ namespace Renci.SshNet
DisconnectReason.ProtocolVersionNotSupported);
}
ServerIdentificationReceived?.Invoke(this, new SshIdentificationEventArgs(serverIdentification));
// Register Transport response messages
RegisterMessage("SSH_MSG_DISCONNECT");
RegisterMessage("SSH_MSG_IGNORE");
@@ -736,6 +743,8 @@ namespace Renci.SshNet
DisconnectReason.ProtocolVersionNotSupported);
}
ServerIdentificationReceived?.Invoke(this, new SshIdentificationEventArgs(serverIdentification));
// Register Transport response messages
RegisterMessage("SSH_MSG_DISCONNECT");
RegisterMessage("SSH_MSG_IGNORE");
@@ -46,7 +46,8 @@ namespace Renci.SshNet.Tests.Classes
protected Session Session { get; private set; }
protected Socket ClientSocket { get; private set; }
protected Socket ServerSocket { get; private set; }
internal SshIdentification ServerIdentification { get; private set; }
internal SshIdentification ServerIdentification { get; set; }
protected bool CallSessionConnectWhenArrange { get; set; }
[TestInitialize]
public void Setup()
@@ -159,6 +160,8 @@ namespace Renci.SshNet.Tests.Classes
ServerListener.Start();
ClientSocket = new DirectConnector(_socketFactory).Connect(ConnectionInfo);
CallSessionConnectWhenArrange = true;
}
private void CreateMocks()
@@ -180,7 +183,7 @@ namespace Renci.SshNet.Tests.Classes
_ = ServiceFactoryMock.Setup(p => p.CreateProtocolVersionExchange())
.Returns(_protocolVersionExchangeMock.Object);
_ = _protocolVersionExchangeMock.Setup(p => p.Start(Session.ClientVersion, ClientSocket, ConnectionInfo.Timeout))
.Returns(ServerIdentification);
.Returns(() => ServerIdentification);
_ = ServiceFactoryMock.Setup(p => p.CreateKeyExchange(ConnectionInfo.KeyExchangeAlgorithms, new[] { _keyExchangeAlgorithm })).Returns(_keyExchangeMock.Object);
_ = _keyExchangeMock.Setup(p => p.Name)
.Returns(_keyExchangeAlgorithm);
@@ -212,7 +215,10 @@ namespace Renci.SshNet.Tests.Classes
SetupData();
SetupMocks();
Session.Connect();
if (CallSessionConnectWhenArrange)
{
Session.Connect();
}
}
protected virtual void ClientAuthentication_Callback()
@@ -0,0 +1,65 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Renci.SshNet.Connection;
namespace Renci.SshNet.Tests.Classes
{
[TestClass]
public class SessionTest_Connected_ServerIdentificationReceived : SessionTest_ConnectedBase
{
protected override void SetupData()
{
base.SetupData();
CallSessionConnectWhenArrange = false;
Session.ServerIdentificationReceived += (s, e) =>
{
if ((e.SshIdentification.SoftwareVersion.StartsWith("OpenSSH_6.5", System.StringComparison.Ordinal) || e.SshIdentification.SoftwareVersion.StartsWith("OpenSSH_6.6", System.StringComparison.Ordinal))
&& !e.SshIdentification.SoftwareVersion.StartsWith("OpenSSH_6.6.1", System.StringComparison.Ordinal))
{
_ = ConnectionInfo.KeyExchangeAlgorithms.Remove("curve25519-sha256");
_ = ConnectionInfo.KeyExchangeAlgorithms.Remove("curve25519-sha256@libssh.org");
}
};
}
protected override void Act()
{
}
[TestMethod]
[DataRow("OpenSSH_6.5")]
[DataRow("OpenSSH_6.5p1")]
[DataRow("OpenSSH_6.5 PKIX")]
[DataRow("OpenSSH_6.6")]
[DataRow("OpenSSH_6.6p1")]
[DataRow("OpenSSH_6.6 PKIX")]
public void ShouldExcludeCurve25519KexWhenServerIs(string softwareVersion)
{
ServerIdentification = new SshIdentification("2.0", softwareVersion);
Session.Connect();
Assert.IsFalse(ConnectionInfo.KeyExchangeAlgorithms.ContainsKey("curve25519-sha256"));
Assert.IsFalse(ConnectionInfo.KeyExchangeAlgorithms.ContainsKey("curve25519-sha256@libssh.org"));
}
[TestMethod]
[DataRow("OpenSSH_6.6.1")]
[DataRow("OpenSSH_6.6.1p1")]
[DataRow("OpenSSH_6.6.1 PKIX")]
[DataRow("OpenSSH_6.7")]
[DataRow("OpenSSH_6.7p1")]
[DataRow("OpenSSH_6.7 PKIX")]
public void ShouldIncludeCurve25519KexWhenServerIs(string softwareVersion)
{
ServerIdentification = new SshIdentification("2.0", softwareVersion);
Session.Connect();
Assert.IsTrue(ConnectionInfo.KeyExchangeAlgorithms.ContainsKey("curve25519-sha256"));
Assert.IsTrue(ConnectionInfo.KeyExchangeAlgorithms.ContainsKey("curve25519-sha256@libssh.org"));
}
}
}