Przeglądaj źródła

Merge remote-tracking branch 'refs/remotes/origin/develop'

Gert Driesen 8 lat temu
rodzic
commit
6445546b70
38 zmienionych plików z 2152 dodań i 228 usunięć
  1. 2 2
      build/nuget/SSH.NET.nuspec
  2. 25 1
      src/Renci.SshNet.Tests.NET35/Renci.SshNet.Tests.NET35.csproj
  3. 136 0
      src/Renci.SshNet.Tests/Classes/Channels/ChannelSessionTest_Dispose_SessionIsConnectedAndChannelIsOpen_ChannelCloseAndChannelEofReceived_DisposeInEventHandler.cs
  4. 58 3
      src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest.cs
  5. 11 8
      src/Renci.SshNet.Tests/Classes/ClientAuthenticationTestBase.cs
  6. 190 0
      src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Failure_MultiList_AllAllowedAuthenticationsHaveReachedPartialSuccessLimit.cs
  7. 30 13
      src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Failure_SingleList_AuthenticationMethodFailed.cs
  8. 24 9
      src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Failure_SingleList_AuthenticationMethodNotConfigured.cs
  9. 54 4
      src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_MultiList_DifferentAllowedAuthenticationsAfterPartialSuccess.cs
  10. 175 0
      src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedByFailureInAlternateBranch.cs
  11. 202 0
      src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedByFailureInAlternateBranch2.cs
  12. 159 0
      src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedByFailureInSameBranch.cs
  13. 185 0
      src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedBySuccessInAlternateBranch.cs
  14. 140 0
      src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedBySuccessInSameBranch.cs
  15. 44 19
      src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_MultiList_PostponePartialAccessAuthenticationMethod.cs
  16. 16 1
      src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_MultiList_SameAllowedAuthenticationsAfterPartialSuccess.cs
  17. 16 1
      src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_MultiList_SkipFailedAuthenticationMethod.cs
  18. 62 13
      src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_SingleList_SameAllowedAuthenticationAfterPartialSuccess.cs
  19. 150 0
      src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_SingleList_SameAllowedAuthenticationAfterPartialSuccess_PartialSuccessLimitReached.cs
  20. 50 3
      src/Renci.SshNet.Tests/Classes/Security/KeyExchangeDiffieHellmanGroup14Sha1Test.cs
  21. 30 31
      src/Renci.SshNet.Tests/Classes/Security/KeyExchangeDiffieHellmanGroup1Sha1Test.cs
  22. 42 0
      src/Renci.SshNet.Tests/Classes/ServiceFactoryTest_CreateClientAuthentication.cs
  23. 0 1
      src/Renci.SshNet.Tests/Classes/ServiceFactoryTest_CreateSftpFileReader_EndLStatThrowsSshException.cs
  24. 9 0
      src/Renci.SshNet.Tests/Renci.SshNet.Tests.csproj
  25. 8 5
      src/Renci.SshNet/Channels/Channel.cs
  26. 43 9
      src/Renci.SshNet/Channels/ChannelSession.cs
  27. 3 0
      src/Renci.SshNet/Channels/ClientChannel.cs
  28. 175 76
      src/Renci.SshNet/ClientAuthentication.cs
  29. 1 1
      src/Renci.SshNet/Properties/CommonAssemblyInfo.cs
  30. 1 0
      src/Renci.SshNet/Renci.SshNet.csproj
  31. 18 8
      src/Renci.SshNet/Security/KeyExchangeDiffieHellman.cs
  32. 31 6
      src/Renci.SshNet/Security/KeyExchangeDiffieHellmanGroup14Sha1.cs
  33. 17 6
      src/Renci.SshNet/Security/KeyExchangeDiffieHellmanGroup1Sha1.cs
  34. 12 1
      src/Renci.SshNet/Security/KeyExchangeDiffieHellmanGroupExchangeSha1.cs
  35. 12 1
      src/Renci.SshNet/Security/KeyExchangeDiffieHellmanGroupExchangeSha256.cs
  36. 2 3
      src/Renci.SshNet/Security/KeyExchangeDiffieHellmanGroupExchangeShaBase.cs
  37. 12 2
      src/Renci.SshNet/Security/KeyExchangeDiffieHellmanGroupSha1.cs
  38. 7 1
      src/Renci.SshNet/ServiceFactory.cs

+ 2 - 2
build/nuget/SSH.NET.nuspec

@@ -2,7 +2,7 @@
 <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
     <metadata>
         <id>SSH.NET</id>
-        <version>2016.1.0-beta3</version>
+        <version>2016.1.0-beta4</version>
         <title>SSH.NET</title>
         <authors>Renci</authors>
         <owners>olegkap,drieseng</owners>
@@ -10,7 +10,7 @@
         <projectUrl>https://github.com/sshnet/SSH.NET/</projectUrl>
         <requireLicenseAcceptance>false</requireLicenseAcceptance>
         <description>SSH.NET is a Secure Shell (SSH) library for .NET, optimized for parallelism and with broad framework support.</description>
-        <releaseNotes>https://github.com/sshnet/SSH.NET/releases/tag/2016.1.0-beta3</releaseNotes>
+        <releaseNotes>https://github.com/sshnet/SSH.NET/releases/tag/2016.1.0-beta4</releaseNotes>
         <summary>A Secure Shell (SSH) library for .NET, optimized for parallelism.</summary>
         <copyright>2012-2017, RENCI</copyright>
         <language>en-US</language>

+ 25 - 1
src/Renci.SshNet.Tests.NET35/Renci.SshNet.Tests.NET35.csproj

@@ -213,9 +213,15 @@
     <Compile Include="..\Renci.SshNet.Tests\Classes\CipherInfoTest.cs">
       <Link>Classes\CipherInfoTest.cs</Link>
     </Compile>
+    <Compile Include="..\Renci.SshNet.Tests\Classes\ClientAuthenticationTest.cs">
+      <Link>Classes\ClientAuthenticationTest.cs</Link>
+    </Compile>
     <Compile Include="..\Renci.SshNet.Tests\Classes\ClientAuthenticationTestBase.cs">
       <Link>Classes\ClientAuthenticationTestBase.cs</Link>
     </Compile>
+    <Compile Include="..\Renci.SshNet.Tests\Classes\ClientAuthenticationTest_Failure_MultiList_AllAllowedAuthenticationsHaveReachedPartialSuccessLimit.cs">
+      <Link>Classes\ClientAuthenticationTest_Failure_MultiList_AllAllowedAuthenticationsHaveReachedPartialSuccessLimit.cs</Link>
+    </Compile>
     <Compile Include="..\Renci.SshNet.Tests\Classes\ClientAuthenticationTest_Failure_SingleList_AuthenticationMethodFailed.cs">
       <Link>Classes\ClientAuthenticationTest_Failure_SingleList_AuthenticationMethodFailed.cs</Link>
     </Compile>
@@ -225,6 +231,21 @@
     <Compile Include="..\Renci.SshNet.Tests\Classes\ClientAuthenticationTest_Success_MultiList_DifferentAllowedAuthenticationsAfterPartialSuccess.cs">
       <Link>Classes\ClientAuthenticationTest_Success_MultiList_DifferentAllowedAuthenticationsAfterPartialSuccess.cs</Link>
     </Compile>
+    <Compile Include="..\Renci.SshNet.Tests\Classes\ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedByFailureInAlternateBranch.cs">
+      <Link>Classes\ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedByFailureInAlternateBranch.cs</Link>
+    </Compile>
+    <Compile Include="..\Renci.SshNet.Tests\Classes\ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedByFailureInAlternateBranch2.cs">
+      <Link>Classes\ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedByFailureInAlternateBranch2.cs</Link>
+    </Compile>
+    <Compile Include="..\Renci.SshNet.Tests\Classes\ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedByFailureInSameBranch.cs">
+      <Link>Classes\ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedByFailureInSameBranch.cs</Link>
+    </Compile>
+    <Compile Include="..\Renci.SshNet.Tests\Classes\ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedBySuccessInAlternateBranch.cs">
+      <Link>Classes\ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedBySuccessInAlternateBranch.cs</Link>
+    </Compile>
+    <Compile Include="..\Renci.SshNet.Tests\Classes\ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedBySuccessInSameBranch.cs">
+      <Link>Classes\ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedBySuccessInSameBranch.cs</Link>
+    </Compile>
     <Compile Include="..\Renci.SshNet.Tests\Classes\ClientAuthenticationTest_Success_MultiList_SameAllowedAuthenticationsAfterPartialSuccess.cs">
       <Link>Classes\ClientAuthenticationTest_Success_MultiList_SameAllowedAuthenticationsAfterPartialSuccess.cs</Link>
     </Compile>
@@ -234,6 +255,9 @@
     <Compile Include="..\Renci.SshNet.Tests\Classes\ClientAuthenticationTest_Success_SingleList_SameAllowedAuthenticationAfterPartialSuccess.cs">
       <Link>Classes\ClientAuthenticationTest_Success_SingleList_SameAllowedAuthenticationAfterPartialSuccess.cs</Link>
     </Compile>
+    <Compile Include="..\Renci.SshNet.Tests\Classes\ClientAuthenticationTest_Success_SingleList_SameAllowedAuthenticationAfterPartialSuccess_PartialSuccessLimitReached.cs">
+      <Link>Classes\ClientAuthenticationTest_Success_SingleList_SameAllowedAuthenticationAfterPartialSuccess_PartialSuccessLimitReached.cs</Link>
+    </Compile>
     <Compile Include="..\Renci.SshNet.Tests\Classes\CommandAsyncResultTest.cs">
       <Link>Classes\CommandAsyncResultTest.cs</Link>
     </Compile>
@@ -1668,7 +1692,7 @@
   <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
   <ProjectExtensions>
     <VisualStudio>
-      <UserProperties ProjectLinkReference="c45379b9-17b1-4e89-bc2e-6d41726413e8" ProjectLinkerExcludeFilter="\\?desktop(\\.*)?$;\\?silverlight(\\.*)?$;\.desktop;\.silverlight;\.xaml;^service references(\\.*)?$;\.clientconfig;^web references(\\.*)?$" />
+      <UserProperties ProjectLinkerExcludeFilter="\\?desktop(\\.*)?$;\\?silverlight(\\.*)?$;\.desktop;\.silverlight;\.xaml;^service references(\\.*)?$;\.clientconfig;^web references(\\.*)?$" ProjectLinkReference="c45379b9-17b1-4e89-bc2e-6d41726413e8" />
     </VisualStudio>
   </ProjectExtensions>
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 

+ 136 - 0
src/Renci.SshNet.Tests/Classes/Channels/ChannelSessionTest_Dispose_SessionIsConnectedAndChannelIsOpen_ChannelCloseAndChannelEofReceived_DisposeInEventHandler.cs

@@ -0,0 +1,136 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Channels;
+using Renci.SshNet.Common;
+using Renci.SshNet.Messages.Connection;
+using Renci.SshNet.Tests.Common;
+
+namespace Renci.SshNet.Tests.Classes.Channels
+{
+    [TestClass]
+    public class ChannelSessionTest_Dispose_SessionIsConnectedAndChannelIsOpen_ChannelCloseAndChannelEofReceived_DisposeInEventHandler
+    {
+        private Mock<ISession> _sessionMock;
+        private uint _localChannelNumber;
+        private uint _localWindowSize;
+        private uint _localPacketSize;
+        private uint _remoteChannelNumber;
+        private uint _remoteWindowSize;
+        private uint _remotePacketSize;
+        private IList<ChannelEventArgs> _channelClosedRegister;
+        private List<ExceptionEventArgs> _channelExceptionRegister;
+        private ChannelSession _channel;
+        private Mock<IConnectionInfo> _connectionInfoMock;
+        private MockSequence _sequence;
+        private SemaphoreLight _sessionSemaphore;
+        private int _initialSessionSemaphoreCount;
+
+        [TestInitialize]
+        public void Initialize()
+        {
+            Arrange();
+            Act();
+        }
+
+        private void Arrange()
+        {
+            var random = new Random();
+            _localChannelNumber = (uint)random.Next(0, int.MaxValue);
+            _localWindowSize = (uint)random.Next(0, int.MaxValue);
+            _localPacketSize = (uint)random.Next(0, int.MaxValue);
+            _remoteChannelNumber = (uint)random.Next(0, int.MaxValue);
+            _remoteWindowSize = (uint)random.Next(0, int.MaxValue);
+            _remotePacketSize = (uint)random.Next(0, int.MaxValue);
+            _channelClosedRegister = new List<ChannelEventArgs>();
+            _channelExceptionRegister = new List<ExceptionEventArgs>();
+            _initialSessionSemaphoreCount = random.Next(10, 20);
+            _sessionSemaphore = new SemaphoreLight(_initialSessionSemaphoreCount);
+
+            _sessionMock = new Mock<ISession>(MockBehavior.Strict);
+            _connectionInfoMock = new Mock<IConnectionInfo>(MockBehavior.Strict);
+
+            _sequence = new MockSequence();
+            _sessionMock.InSequence(_sequence).Setup(p => p.ConnectionInfo).Returns(_connectionInfoMock.Object);
+            _connectionInfoMock.InSequence(_sequence).Setup(p => p.RetryAttempts).Returns(1);
+            _sessionMock.Setup(p => p.SessionSemaphore).Returns(_sessionSemaphore);
+            _sessionMock.InSequence(_sequence)
+                        .Setup(
+                            p =>
+                                p.SendMessage(
+                                    It.Is<ChannelOpenMessage>(
+                                        m =>
+                                            m.LocalChannelNumber == _localChannelNumber &&
+                                            m.InitialWindowSize == _localWindowSize && m.MaximumPacketSize == _localPacketSize &&
+                                            m.Info is SessionChannelOpenInfo)));
+            _sessionMock.InSequence(_sequence)
+                        .Setup(p => p.WaitOnHandle(It.IsNotNull<WaitHandle>()))
+                        .Callback<WaitHandle>(
+                            w =>
+                            {
+                                _sessionMock.Raise(
+                                    s => s.ChannelOpenConfirmationReceived += null,
+                                    new MessageEventArgs<ChannelOpenConfirmationMessage>(
+                                        new ChannelOpenConfirmationMessage(
+                                            _localChannelNumber,
+                                            _remoteWindowSize,
+                                            _remotePacketSize,
+                                            _remoteChannelNumber)));
+                                w.WaitOne();
+                            });
+            _sessionMock.InSequence(_sequence).Setup(p => p.IsConnected).Returns(true);
+            _sessionMock.InSequence(_sequence)
+                        .Setup(p => p.TrySendMessage(It.Is<ChannelCloseMessage>(c => c.LocalChannelNumber == _remoteChannelNumber)))
+                        .Returns(true);
+            _sessionMock.InSequence(_sequence)
+                        .Setup(s => s.WaitOnHandle(It.IsNotNull<EventWaitHandle>()))
+                        .Callback<WaitHandle>(w => w.WaitOne());
+
+            _channel = new ChannelSession(_sessionMock.Object, _localChannelNumber, _localWindowSize, _localPacketSize);
+            _channel.Closed += (sender, args) =>
+                {
+                    _channelClosedRegister.Add(args);
+                    _channel.Dispose();
+                };
+            _channel.Exception += (sender, args) => _channelExceptionRegister.Add(args);
+            _channel.Open();
+
+            _sessionMock.Raise(p => p.ChannelEofReceived += null,
+                               new MessageEventArgs<ChannelEofMessage>(new ChannelEofMessage(_localChannelNumber)));
+            _sessionMock.Raise(p => p.ChannelCloseReceived += null,
+                               new MessageEventArgs<ChannelCloseMessage>(new ChannelCloseMessage(_localChannelNumber)));
+        }
+
+        private void Act()
+        {
+            _channel.Dispose();
+        }
+
+        [TestMethod]
+        public void CurrentCountOfSessionSemaphoreShouldBeEqualToInitialCount()
+        {
+            Assert.AreEqual(_initialSessionSemaphoreCount, _sessionSemaphore.CurrentCount);
+        }
+
+        [TestMethod]
+        public void ExceptionShouldNeverHaveFired()
+        {
+            Assert.AreEqual(0, _channelExceptionRegister.Count, _channelExceptionRegister.AsString());
+        }
+
+        [TestMethod]
+        public void ClosedEventShouldHaveFiredOnce()
+        {
+            Assert.AreEqual(1, _channelClosedRegister.Count);
+            Assert.AreEqual(_localChannelNumber, _channelClosedRegister[0].ChannelNumber);
+        }
+
+        [TestMethod]
+        public void IsOpenShouldReturnFalse()
+        {
+            Assert.IsFalse(_channel.IsOpen);
+        }
+    }
+}

+ 58 - 3
src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest.cs

@@ -12,13 +12,68 @@ namespace Renci.SshNet.Tests.Classes
         [TestInitialize]
         public void Init()
         {
-            _clientAuthentication = new ClientAuthentication();
+            _clientAuthentication = new ClientAuthentication(1);
         }
 
+        [TestMethod]
+        public void Ctor_PartialSuccessLimit_Zero()
+        {
+            const int partialSuccessLimit = 0;
+
+            try
+            {
+                new ClientAuthentication(partialSuccessLimit);
+                Assert.Fail();
+            }
+            catch (ArgumentOutOfRangeException ex)
+            {
+                Assert.IsNull(ex.InnerException);
+                Assert.AreEqual(string.Format("Cannot be less than one.{0}Parameter name: {1}", Environment.NewLine, ex.ParamName), ex.Message);
+                Assert.AreEqual("partialSuccessLimit", ex.ParamName);
+            }
+        }
+
+        [TestMethod]
+        public void Ctor_PartialSuccessLimit_Negative()
+        {
+            var partialSuccessLimit = new Random().Next(int.MinValue, -1);
+
+            try
+            {
+                new ClientAuthentication(partialSuccessLimit);
+                Assert.Fail();
+            }
+            catch (ArgumentOutOfRangeException ex)
+            {
+                Assert.IsNull(ex.InnerException);
+                Assert.AreEqual(string.Format("Cannot be less than one.{0}Parameter name: {1}", Environment.NewLine, ex.ParamName), ex.Message);
+                Assert.AreEqual("partialSuccessLimit", ex.ParamName);
+            }
+        }
+
+        [TestMethod]
+        public void Ctor_PartialSuccessLimit_One()
+        {
+            const int partialSuccessLimit = 1;
+
+            var clientAuthentication = new ClientAuthentication(partialSuccessLimit);
+            Assert.AreEqual(partialSuccessLimit, clientAuthentication.PartialSuccessLimit);
+        }
+
+        [TestMethod]
+        public void Ctor_PartialSuccessLimit_MaxValue()
+        {
+            const int partialSuccessLimit = int.MaxValue;
+
+            var clientAuthentication = new ClientAuthentication(partialSuccessLimit);
+            Assert.AreEqual(partialSuccessLimit, clientAuthentication.PartialSuccessLimit);
+        }
+
+
         [TestMethod]
         public void AuthenticateShouldThrowArgumentNullExceptionWhenConnectionInfoIsNull()
         {
-            IConnectionInfoInternal connectionInfo = null;
+           const IConnectionInfoInternal connectionInfo = null;
             var session = new Mock<ISession>(MockBehavior.Strict).Object;
 
             try
@@ -37,7 +92,7 @@ namespace Renci.SshNet.Tests.Classes
         public void AuthenticateShouldThrowArgumentNullExceptionWhenSessionIsNull()
         {
             var connectionInfo = new Mock<IConnectionInfoInternal>(MockBehavior.Strict).Object;
-            ISession session = null;
+            const ISession session = null;
 
             try
             {

+ 11 - 8
src/Renci.SshNet.Tests/Classes/ClientAuthenticationTestBase.cs

@@ -13,7 +13,8 @@ namespace Renci.SshNet.Tests.Classes
         internal Mock<IAuthenticationMethod> PasswordAuthenticationMethodMock { get; private set; }
         internal Mock<IAuthenticationMethod> PublicKeyAuthenticationMethodMock { get; private set; }
         internal Mock<IAuthenticationMethod> KeyboardInteractiveAuthenticationMethodMock { get; private set; }
-        internal ClientAuthentication ClientAuthentication { get; private set; }
+
+        protected abstract void SetupData();
 
         protected void CreateMocks()
         {
@@ -27,18 +28,20 @@ namespace Renci.SshNet.Tests.Classes
 
         protected abstract void SetupMocks();
 
+        protected virtual void Arrange()
+        {
+            SetupData();
+            CreateMocks();
+            SetupMocks();
+        }
+
         protected abstract void Act();
 
-        protected override void OnInit()
+        protected sealed override void OnInit()
         {
             base.OnInit();
 
-            // Arrange
-            CreateMocks();
-            SetupMocks();
-            ClientAuthentication = new ClientAuthentication();
-
-            // Act
+            Arrange();
             Act();
         }
 

+ 190 - 0
src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Failure_MultiList_AllAllowedAuthenticationsHaveReachedPartialSuccessLimit.cs

@@ -0,0 +1,190 @@
+using System.Collections.Generic;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    /// <summary>
+    /// * ConnectionInfo provides the following authentication methods (in order):
+    ///     o publickey
+    ///     o password
+    /// * Partial success limit is 2
+    /// 
+    ///                               none
+    ///                             (1=FAIL)
+    ///                                 |
+    ///        +------------------------+------------------------+
+    ///        |                        |                        |
+    ///    password      ◄--\       publickey            keyboard-interactive
+    ///    (7=SKIP)         |         (2=PS)
+    ///                     |           |
+    ///                     |        password
+    ///                     |         (3=PS)
+    ///                     |           |
+    ///                     |        password
+    ///                     |         (4=PS)
+    ///                     |           |
+    ///                     |       publickey
+    ///                     |         (5=PS)
+    ///                     |           |
+    ///                     \----   publickey
+    ///                              (6=SKIP)
+    /// </summary>
+    [TestClass]
+    public class ClientAuthenticationTest_Failure_MultiList_AllAllowedAuthenticationsHaveReachedPartialSuccessLimit : ClientAuthenticationTestBase
+    {
+        private int _partialSuccessLimit;
+        private ClientAuthentication _clientAuthentication;
+        private SshAuthenticationException _actualException;
+
+        protected override void SetupData()
+        {
+            _partialSuccessLimit = 2;
+        }
+
+        protected override void SetupMocks()
+        {
+            var seq = new MockSequence();
+
+            SessionMock.InSequence(seq).Setup(p => p.RegisterMessage("SSH_MSG_USERAUTH_FAILURE"));
+            SessionMock.InSequence(seq).Setup(p => p.RegisterMessage("SSH_MSG_USERAUTH_SUCCESS"));
+            SessionMock.InSequence(seq).Setup(p => p.RegisterMessage("SSH_MSG_USERAUTH_BANNER"));
+
+            ConnectionInfoMock.InSequence(seq).Setup(p => p.CreateNoneAuthenticationMethod())
+                              .Returns(NoneAuthenticationMethodMock.Object);
+
+            /* 1 */
+
+            NoneAuthenticationMethodMock.InSequence(seq).Setup(p => p.Authenticate(SessionMock.Object))
+                                        .Returns(AuthenticationResult.Failure);
+            ConnectionInfoMock.InSequence(seq)
+                              .Setup(p => p.AuthenticationMethods)
+                              .Returns(new List<IAuthenticationMethod>
+                                  {
+                                      PublicKeyAuthenticationMethodMock.Object,
+                                      PasswordAuthenticationMethodMock.Object
+                                  });
+            NoneAuthenticationMethodMock.InSequence(seq)
+                                        .Setup(p => p.AllowedAuthentications)
+                                        .Returns(new[] {"password", "publickey", "keyboard-interactive"});
+
+            /* Enumerate supported authentication methods */
+
+            PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
+            PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
+
+            /* 2 */
+
+            PublicKeyAuthenticationMethodMock.InSequence(seq)
+                                             .Setup(p => p.Authenticate(SessionMock.Object))
+                                             .Returns(AuthenticationResult.PartialSuccess);
+            PublicKeyAuthenticationMethodMock.InSequence(seq)
+                                             .Setup(p => p.AllowedAuthentications)
+                                             .Returns(new[] {"password"});
+
+            /* Enumerate supported authentication methods */
+
+            PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
+            PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
+
+            /* 3 */
+
+            PasswordAuthenticationMethodMock.InSequence(seq)
+                                            .Setup(p => p.Authenticate(SessionMock.Object))
+                                            .Returns(AuthenticationResult.PartialSuccess);
+            PasswordAuthenticationMethodMock.InSequence(seq)
+                                            .Setup(p => p.AllowedAuthentications)
+                                            .Returns(new[] {"password"});
+
+            /* Enumerate supported authentication methods */
+
+            PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
+            PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
+
+            /* 4 */
+
+            PasswordAuthenticationMethodMock.InSequence(seq)
+                                            .Setup(p => p.Authenticate(SessionMock.Object))
+                                            .Returns(AuthenticationResult.PartialSuccess);
+            PasswordAuthenticationMethodMock.InSequence(seq)
+                                            .Setup(p => p.AllowedAuthentications)
+                                            .Returns(new[] {"publickey"});
+
+            /* Enumerate supported authentication methods */
+
+            PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
+            PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
+
+            /* 5 */
+
+            PublicKeyAuthenticationMethodMock.InSequence(seq)
+                                             .Setup(p => p.Authenticate(SessionMock.Object))
+                                             .Returns(AuthenticationResult.PartialSuccess);
+            PublicKeyAuthenticationMethodMock.InSequence(seq)
+                                             .Setup(p => p.AllowedAuthentications)
+                                             .Returns(new[] {"publickey"});
+
+            /* Enumerate supported authentication methods */
+
+            PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
+            PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
+
+            /* 6: Record partial success limit reached exception, and skip password authentication method */
+
+            PublicKeyAuthenticationMethodMock.InSequence(seq)
+                                             .Setup(p => p.Name)
+                                             .Returns("publickey-partial1");
+
+            /* 7: Record partial success limit reached exception, and skip password authentication method */
+
+            PasswordAuthenticationMethodMock.InSequence(seq)
+                                            .Setup(p => p.Name)
+                                            .Returns("password-partial1");
+            
+            SessionMock.InSequence(seq).Setup(p => p.UnRegisterMessage("SSH_MSG_USERAUTH_FAILURE"));
+            SessionMock.InSequence(seq).Setup(p => p.UnRegisterMessage("SSH_MSG_USERAUTH_SUCCESS"));
+            SessionMock.InSequence(seq).Setup(p => p.UnRegisterMessage("SSH_MSG_USERAUTH_BANNER"));
+        }
+
+        protected override void Arrange()
+        {
+            base.Arrange();
+
+            _clientAuthentication = new ClientAuthentication(_partialSuccessLimit);
+        }
+
+        protected override void Act()
+        {
+            try
+            {
+                _clientAuthentication.Authenticate(ConnectionInfoMock.Object, SessionMock.Object);
+                Assert.Fail();
+            }
+            catch (SshAuthenticationException ex)
+            {
+                _actualException = ex;
+            }
+        }
+
+        [TestMethod]
+        public void AuthenticateOnPasswordAuthenticationMethodShouldHaveBeenInvokedTwice()
+        {
+            PasswordAuthenticationMethodMock.Verify(p => p.Authenticate(SessionMock.Object), Times.Exactly(2));
+        }
+
+        [TestMethod]
+        public void AuthenticateOnPublicKeyAuthenticationMethodShouldHaveBeenInvokedTwice()
+        {
+            PublicKeyAuthenticationMethodMock.Verify(p => p.Authenticate(SessionMock.Object), Times.Exactly(2));
+        }
+
+        [TestMethod]
+        public void AuthenticateShouldThrowSshAuthenticationException()
+        {
+            Assert.IsNotNull(_actualException);
+            Assert.IsNull(_actualException.InnerException);
+            Assert.AreEqual("Reached authentication attempt limit for method (password-partial1).", _actualException.Message);
+        }
+    }
+}

+ 30 - 13
src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Failure_SingleList_AuthenticationMethodFailed.cs

@@ -8,8 +8,15 @@ namespace Renci.SshNet.Tests.Classes
     [TestClass]
     public class ClientAuthenticationTest_Failure_SingleList_AuthenticationMethodFailed : ClientAuthenticationTestBase
     {
+        private int _partialSuccessLimit;
+        private ClientAuthentication _clientAuthentication;
         private SshAuthenticationException _actualException;
 
+        protected override void SetupData()
+        {
+            _partialSuccessLimit = 1;
+        }
+
         protected override void SetupMocks()
         {
             var seq = new MockSequence();
@@ -21,23 +28,26 @@ namespace Renci.SshNet.Tests.Classes
             ConnectionInfoMock.InSequence(seq).Setup(p => p.CreateNoneAuthenticationMethod())
                 .Returns(NoneAuthenticationMethodMock.Object);
 
-            NoneAuthenticationMethodMock.InSequence(seq).Setup(p => p.Authenticate(SessionMock.Object))
-                .Returns(AuthenticationResult.Failure);
-            ConnectionInfoMock.InSequence(seq).Setup(p => p.AuthenticationMethods)
-                            .Returns(new List<IAuthenticationMethod>
-                {
-                    PublicKeyAuthenticationMethodMock.Object,
-                    PasswordAuthenticationMethodMock.Object
-                });
             NoneAuthenticationMethodMock.InSequence(seq)
-                .Setup(p => p.AllowedAuthentications)
-                .Returns(new[] { "password" });
+                                        .Setup(p => p.Authenticate(SessionMock.Object))
+                                        .Returns(AuthenticationResult.Failure);
+            ConnectionInfoMock.InSequence(seq)
+                              .Setup(p => p.AuthenticationMethods)
+                              .Returns(new List<IAuthenticationMethod>
+                                  {
+                                      PublicKeyAuthenticationMethodMock.Object,
+                                      PasswordAuthenticationMethodMock.Object
+                                  });
+            NoneAuthenticationMethodMock.InSequence(seq)
+                                        .Setup(p => p.AllowedAuthentications)
+                                        .Returns(new[] { "password" });
 
             PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
             PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
 
-            PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Authenticate(SessionMock.Object))
-                .Returns(AuthenticationResult.Failure);
+            PasswordAuthenticationMethodMock.InSequence(seq)
+                                            .Setup(p => p.Authenticate(SessionMock.Object))
+                                            .Returns(AuthenticationResult.Failure);
             // obtain name for inclusion in SshAuthenticationException
             PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
 
@@ -46,11 +56,18 @@ namespace Renci.SshNet.Tests.Classes
             SessionMock.InSequence(seq).Setup(p => p.UnRegisterMessage("SSH_MSG_USERAUTH_BANNER"));
         }
 
+        protected override void Arrange()
+        {
+            base.Arrange();
+
+            _clientAuthentication = new ClientAuthentication(_partialSuccessLimit);
+        }
+
         protected override void Act()
         {
             try
             {
-                ClientAuthentication.Authenticate(ConnectionInfoMock.Object, SessionMock.Object);
+                _clientAuthentication.Authenticate(ConnectionInfoMock.Object, SessionMock.Object);
                 Assert.Fail();
             }
             catch (SshAuthenticationException ex)

+ 24 - 9
src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Failure_SingleList_AuthenticationMethodNotConfigured.cs

@@ -8,8 +8,15 @@ namespace Renci.SshNet.Tests.Classes
     [TestClass]
     public class ClientAuthenticationTest_Failure_SingleList_AuthenticationMethodNotSupported : ClientAuthenticationTestBase
     {
+        private int _partialSuccessLimit;
+        private ClientAuthentication _clientAuthentication;
         private SshAuthenticationException _actualException;
 
+        protected override void SetupData()
+        {
+            _partialSuccessLimit = 1;
+        }
+
         protected override void SetupMocks()
         {
             var seq = new MockSequence();
@@ -21,16 +28,17 @@ namespace Renci.SshNet.Tests.Classes
             ConnectionInfoMock.InSequence(seq).Setup(p => p.CreateNoneAuthenticationMethod())
                 .Returns(NoneAuthenticationMethodMock.Object);
 
-            NoneAuthenticationMethodMock.InSequence(seq).Setup(p => p.Authenticate(SessionMock.Object))
-                .Returns(AuthenticationResult.Failure);
+            NoneAuthenticationMethodMock.InSequence(seq)
+                                        .Setup(p => p.Authenticate(SessionMock.Object))
+                                        .Returns(AuthenticationResult.Failure);
             ConnectionInfoMock.InSequence(seq).Setup(p => p.AuthenticationMethods)
-                            .Returns(new List<IAuthenticationMethod>
-                {
-                    PublicKeyAuthenticationMethodMock.Object
-                });
+                              .Returns(new List<IAuthenticationMethod>
+                                  {
+                                      PublicKeyAuthenticationMethodMock.Object
+                                  });
             NoneAuthenticationMethodMock.InSequence(seq)
-                .Setup(p => p.AllowedAuthentications)
-                .Returns(new[] { "password" });
+                                        .Setup(p => p.AllowedAuthentications)
+                                        .Returns(new[] {"password"});
 
             PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
 
@@ -39,11 +47,18 @@ namespace Renci.SshNet.Tests.Classes
             SessionMock.InSequence(seq).Setup(p => p.UnRegisterMessage("SSH_MSG_USERAUTH_BANNER"));
         }
 
+        protected override void Arrange()
+        {
+            base.Arrange();
+
+            _clientAuthentication = new ClientAuthentication(_partialSuccessLimit);
+        }
+
         protected override void Act()
         {
             try
             {
-                ClientAuthentication.Authenticate(ConnectionInfoMock.Object, SessionMock.Object);
+                _clientAuthentication.Authenticate(ConnectionInfoMock.Object, SessionMock.Object);
                 Assert.Fail();
             }
             catch (SshAuthenticationException ex)

+ 54 - 4
src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_MultiList_DifferentAllowedAuthenticationsAfterPartialSuccess.cs

@@ -4,9 +4,36 @@ using Moq;
 
 namespace Renci.SshNet.Tests.Classes
 {
+    /// <summary>
+    /// * ConnectionInfo provides the following authentication methods (in order):
+    ///     o password
+    ///     o publickey
+    ///     o keyboard-interactive
+    /// * Partial success limit is 1
+    /// * Scenario:
+    ///                           none
+    ///                         (1=FAIL)
+    ///                             |
+    ///             +------------------------------+
+    ///             |                              |
+    ///         publickey                       password
+    ///                                       (2=PARTIAL)
+    ///                                *----------------------*
+    ///                                |                      |
+    ///                       keyboard-interactive        publickey
+    ///                                                  (3=SUCCESS)
+    /// </summary>
     [TestClass]
     public class ClientAuthenticationTest_Success_MultiList_DifferentAllowedAuthenticationsAfterPartialSuccess : ClientAuthenticationTestBase
     {
+        private int _partialSuccessLimit;
+        private ClientAuthentication _clientAuthentication;
+
+        protected override void SetupData()
+        {
+            _partialSuccessLimit = 1;
+        }
+
         protected override void SetupMocks()
         {
             var seq = new MockSequence();
@@ -18,6 +45,8 @@ namespace Renci.SshNet.Tests.Classes
             ConnectionInfoMock.InSequence(seq).Setup(p => p.CreateNoneAuthenticationMethod())
                 .Returns(NoneAuthenticationMethodMock.Object);
 
+            /* 1 */
+
             NoneAuthenticationMethodMock.InSequence(seq).Setup(p => p.Authenticate(SessionMock.Object))
                 .Returns(AuthenticationResult.Failure);
             ConnectionInfoMock.InSequence(seq).Setup(p => p.AuthenticationMethods)
@@ -27,29 +56,50 @@ namespace Renci.SshNet.Tests.Classes
                     PublicKeyAuthenticationMethodMock.Object,
                     KeyboardInteractiveAuthenticationMethodMock.Object,
                 });
-            NoneAuthenticationMethodMock.InSequence(seq).Setup(p => p.AllowedAuthentications).Returns(new[] { "publickey", "password" });
+            NoneAuthenticationMethodMock.InSequence(seq)
+                                        .Setup(p => p.AllowedAuthentications)
+                                        .Returns(new[] {"publickey", "password"});
+
+            /* Enumerate supported authentication methods */
+
             PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
             PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
             KeyboardInteractiveAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("keyboard-interactive");
 
+            /* 2 */
+
             PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Authenticate(SessionMock.Object))
                 .Returns(AuthenticationResult.PartialSuccess);
             PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.AllowedAuthentications)
-                .Returns(new[] { "keyboard-interactive", "publickey" });
+                                            .Returns(new[] {"keyboard-interactive", "publickey"});
+
+            /* Enumerate supported authentication methods */
+
             PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
             PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
             KeyboardInteractiveAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("keyboard-interactive");
 
-            PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Authenticate(SessionMock.Object)).Returns(AuthenticationResult.Success);
+            /* 3 */
+
+            PublicKeyAuthenticationMethodMock.InSequence(seq)
+                                             .Setup(p => p.Authenticate(SessionMock.Object))
+                                             .Returns(AuthenticationResult.Success);
 
             SessionMock.InSequence(seq).Setup(p => p.UnRegisterMessage("SSH_MSG_USERAUTH_FAILURE"));
             SessionMock.InSequence(seq).Setup(p => p.UnRegisterMessage("SSH_MSG_USERAUTH_SUCCESS"));
             SessionMock.InSequence(seq).Setup(p => p.UnRegisterMessage("SSH_MSG_USERAUTH_BANNER"));
         }
 
+        protected override void Arrange()
+        {
+            base.Arrange();
+
+            _clientAuthentication = new ClientAuthentication(_partialSuccessLimit);
+        }
+
         protected override void Act()
         {
-            ClientAuthentication.Authenticate(ConnectionInfoMock.Object, SessionMock.Object);
+            _clientAuthentication.Authenticate(ConnectionInfoMock.Object, SessionMock.Object);
         }
     }
 }

+ 175 - 0
src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedByFailureInAlternateBranch.cs

@@ -0,0 +1,175 @@
+using System.Collections.Generic;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    /// <summary>
+    /// * ConnectionInfo provides the following authentication methods (in order):
+    ///     o password
+    ///     o publickey
+    ///     o keyboard-interactive
+    /// * Partial success limit is 2
+    /// * Scenario:
+    ///                    none
+    ///                  (1=FAIL)
+    ///                     |
+    ///             +------------------------+
+    ///             |                        |
+    ///         publickey          keyboard-interactive
+    ///          (2=PS)             ^    (6=FAIL)
+    ///             |               |
+    ///         password            |
+    ///          (3=PS)             |
+    ///             |               |
+    ///         password            |
+    ///          (4=PS)             |
+    ///             |               |
+    ///         password            |
+    ///         (5=SKIP)            |
+    ///             +---------------+
+    /// </summary>
+    [TestClass]
+    public class ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedByFailureInAlternateBranch : ClientAuthenticationTestBase
+    {
+        private int _partialSuccessLimit;
+        private ClientAuthentication _clientAuthentication;
+        private SshAuthenticationException _actualException;
+
+        protected override void SetupData()
+        {
+            _partialSuccessLimit = 2;
+        }
+
+        protected override void SetupMocks()
+        {
+            var seq = new MockSequence();
+
+            SessionMock.InSequence(seq).Setup(p => p.RegisterMessage("SSH_MSG_USERAUTH_FAILURE"));
+            SessionMock.InSequence(seq).Setup(p => p.RegisterMessage("SSH_MSG_USERAUTH_SUCCESS"));
+            SessionMock.InSequence(seq).Setup(p => p.RegisterMessage("SSH_MSG_USERAUTH_BANNER"));
+
+            ConnectionInfoMock.InSequence(seq).Setup(p => p.CreateNoneAuthenticationMethod())
+                              .Returns(NoneAuthenticationMethodMock.Object);
+
+            /* 1 */
+
+            NoneAuthenticationMethodMock.InSequence(seq).Setup(p => p.Authenticate(SessionMock.Object))
+                                        .Returns(AuthenticationResult.Failure);
+            ConnectionInfoMock.InSequence(seq)
+                              .Setup(p => p.AuthenticationMethods)
+                              .Returns(new List<IAuthenticationMethod>
+                                  {
+                                      PasswordAuthenticationMethodMock.Object,
+                                      PublicKeyAuthenticationMethodMock.Object,
+                                      KeyboardInteractiveAuthenticationMethodMock.Object,
+                                  });
+            NoneAuthenticationMethodMock.InSequence(seq)
+                                        .Setup(p => p.AllowedAuthentications)
+                                        .Returns(new[] {"publickey", "keyboard-interactive"});
+
+            /* Enumerate supported authentication methods */
+
+            PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
+            PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
+            KeyboardInteractiveAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("keyboard-interactive");
+
+            /* 2 */
+
+            PublicKeyAuthenticationMethodMock.InSequence(seq)
+                                             .Setup(p => p.Authenticate(SessionMock.Object))
+                                             .Returns(AuthenticationResult.PartialSuccess);
+            PublicKeyAuthenticationMethodMock.InSequence(seq)
+                                            .Setup(p => p.AllowedAuthentications)
+                                            .Returns(new[] {"password"});
+
+            /* Enumerate supported authentication methods */
+
+            PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
+            PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
+            KeyboardInteractiveAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("keyboard-interactive");
+
+            /* 3 */
+
+            PasswordAuthenticationMethodMock.InSequence(seq)
+                                            .Setup(p => p.Authenticate(SessionMock.Object))
+                                            .Returns(AuthenticationResult.PartialSuccess);
+            PasswordAuthenticationMethodMock.InSequence(seq)
+                                            .Setup(p => p.AllowedAuthentications)
+                                            .Returns(new[] {"password"});
+
+            /* Enumerate supported authentication methods */
+
+            PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
+            PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
+            KeyboardInteractiveAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("keyboard-interactive");
+
+            /* 4 */
+
+            PasswordAuthenticationMethodMock.InSequence(seq)
+                                            .Setup(p => p.Authenticate(SessionMock.Object))
+                                            .Returns(AuthenticationResult.PartialSuccess);
+            PasswordAuthenticationMethodMock.InSequence(seq)
+                                            .Setup(p => p.AllowedAuthentications)
+                                            .Returns(new[] {"password"});
+
+            /* Enumerate supported authentication methods */
+
+            PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
+            PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
+            KeyboardInteractiveAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("keyboard-interactive");
+
+            /* 5: Record partial success limit reached exception, and skip password authentication method */
+
+            PasswordAuthenticationMethodMock.InSequence(seq)
+                                            .Setup(p => p.Name)
+                                            .Returns("password-partial");
+
+            /* 6 */
+
+            KeyboardInteractiveAuthenticationMethodMock.InSequence(seq)
+                                                       .Setup(p => p.Authenticate(SessionMock.Object))
+                                                       .Returns(AuthenticationResult.Failure);
+            KeyboardInteractiveAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("keyboard-interactive-failure");
+
+            SessionMock.InSequence(seq).Setup(p => p.UnRegisterMessage("SSH_MSG_USERAUTH_FAILURE"));
+            SessionMock.InSequence(seq).Setup(p => p.UnRegisterMessage("SSH_MSG_USERAUTH_SUCCESS"));
+            SessionMock.InSequence(seq).Setup(p => p.UnRegisterMessage("SSH_MSG_USERAUTH_BANNER"));
+        }
+
+        protected override void Arrange()
+        {
+            base.Arrange();
+
+            _clientAuthentication = new ClientAuthentication(_partialSuccessLimit);
+        }
+
+        protected override void Act()
+        {
+            try
+            {
+                _clientAuthentication.Authenticate(ConnectionInfoMock.Object, SessionMock.Object);
+                Assert.Fail();
+            }
+            catch (SshAuthenticationException ex)
+            {
+                _actualException = ex;
+            }
+        }
+
+        [TestMethod]
+        public void AuthenticateOnKeyboardInteractiveAuthenticationMethodShouldHaveBeenInvokedOnce()
+        {
+            KeyboardInteractiveAuthenticationMethodMock.Verify(p => p.Authenticate(SessionMock.Object), Times.Once);
+        }
+
+        [TestMethod]
+        public void AuthenticateShouldThrowSshAuthenticationException()
+        {
+            Assert.IsNotNull(_actualException);
+            Assert.IsNull(_actualException.InnerException);
+            Assert.AreEqual("Permission denied (keyboard-interactive-failure).", _actualException.Message);
+        }
+    }
+}

+ 202 - 0
src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedByFailureInAlternateBranch2.cs

@@ -0,0 +1,202 @@
+using System.Collections.Generic;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    /// <summary>
+    /// * ConnectionInfo provides the following authentication methods (in order):
+    ///     o password
+    ///     o publickey
+    ///     o keyboard-interactive
+    /// * Partial success limit is 2
+    /// 
+    ///                    none
+    ///                  (1=FAIL)
+    ///                     |
+    ///             +-------------------+
+    ///             |                   |
+    ///         publickey      keyboard-interactive
+    ///          (2=PS)          ^   (6=PS)
+    ///             |            |      |
+    ///         password         |      +-----------+
+    ///          (3=PS)          |      |           |
+    ///             |            |  password     publickey
+    ///         password         |   (7=SKIP)    (8=FAIL)
+    ///          (4=PS)          |
+    ///             |            |
+    ///         password         |
+    ///         (5=SKIP)         |
+    ///             +------------+
+    /// </summary>
+    [TestClass]
+    public class ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedByFailureInAlternateBranch2 : ClientAuthenticationTestBase
+    {
+        private int _partialSuccessLimit;
+        private ClientAuthentication _clientAuthentication;
+        private SshAuthenticationException _actualException;
+
+        protected override void SetupData()
+        {
+            _partialSuccessLimit = 2;
+        }
+
+        protected override void SetupMocks()
+        {
+            var seq = new MockSequence();
+
+            SessionMock.InSequence(seq).Setup(p => p.RegisterMessage("SSH_MSG_USERAUTH_FAILURE"));
+            SessionMock.InSequence(seq).Setup(p => p.RegisterMessage("SSH_MSG_USERAUTH_SUCCESS"));
+            SessionMock.InSequence(seq).Setup(p => p.RegisterMessage("SSH_MSG_USERAUTH_BANNER"));
+
+            ConnectionInfoMock.InSequence(seq).Setup(p => p.CreateNoneAuthenticationMethod())
+                              .Returns(NoneAuthenticationMethodMock.Object);
+
+            /* 1 */
+
+            NoneAuthenticationMethodMock.InSequence(seq).Setup(p => p.Authenticate(SessionMock.Object))
+                                        .Returns(AuthenticationResult.Failure);
+            ConnectionInfoMock.InSequence(seq)
+                              .Setup(p => p.AuthenticationMethods)
+                              .Returns(new List<IAuthenticationMethod>
+                                  {
+                                      PasswordAuthenticationMethodMock.Object,
+                                      PublicKeyAuthenticationMethodMock.Object,
+                                      KeyboardInteractiveAuthenticationMethodMock.Object,
+                                  });
+            NoneAuthenticationMethodMock.InSequence(seq)
+                                        .Setup(p => p.AllowedAuthentications)
+                                        .Returns(new[] { "publickey", "keyboard-interactive" });
+
+            /* Enumerate supported authentication methods */
+
+            PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
+            PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
+            KeyboardInteractiveAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("keyboard-interactive");
+
+            /* 2 */
+
+            PublicKeyAuthenticationMethodMock.InSequence(seq)
+                                             .Setup(p => p.Authenticate(SessionMock.Object))
+                                             .Returns(AuthenticationResult.PartialSuccess);
+            PublicKeyAuthenticationMethodMock.InSequence(seq)
+                                             .Setup(p => p.AllowedAuthentications)
+                                             .Returns(new[] { "password" });
+
+            /* Enumerate supported authentication methods */
+
+            PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
+            PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
+            KeyboardInteractiveAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("keyboard-interactive");
+
+            /* 3 */
+
+            PasswordAuthenticationMethodMock.InSequence(seq)
+                                            .Setup(p => p.Authenticate(SessionMock.Object))
+                                            .Returns(AuthenticationResult.PartialSuccess);
+            PasswordAuthenticationMethodMock.InSequence(seq)
+                                            .Setup(p => p.AllowedAuthentications)
+                                            .Returns(new[] { "password" });
+
+            /* Enumerate supported authentication methods */
+
+            PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
+            PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
+            KeyboardInteractiveAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("keyboard-interactive");
+
+            /* 4 */
+
+            PasswordAuthenticationMethodMock.InSequence(seq)
+                                            .Setup(p => p.Authenticate(SessionMock.Object))
+                                            .Returns(AuthenticationResult.PartialSuccess);
+            PasswordAuthenticationMethodMock.InSequence(seq)
+                                            .Setup(p => p.AllowedAuthentications)
+                                            .Returns(new[] { "password" });
+
+            /* Enumerate supported authentication methods */
+
+            PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
+            PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
+            KeyboardInteractiveAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("keyboard-interactive");
+
+            /* 5: Record partial success limit reached exception, and skip password authentication method */
+
+            PasswordAuthenticationMethodMock.InSequence(seq)
+                                            .Setup(p => p.Name)
+                                            .Returns("password-partial1");
+
+            /* 6 */
+
+            KeyboardInteractiveAuthenticationMethodMock.InSequence(seq)
+                                                       .Setup(p => p.Authenticate(SessionMock.Object))
+                                                       .Returns(AuthenticationResult.PartialSuccess);
+            KeyboardInteractiveAuthenticationMethodMock.InSequence(seq)
+                                                       .Setup(p => p.AllowedAuthentications)
+                                                       .Returns(new[] {"password", "publickey"});
+
+            /* Enumerate supported authentication methods */
+
+            PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
+            PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
+            KeyboardInteractiveAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("keyboard-interactive");
+
+            /* 7: Record partial success limit reached exception, and skip password authentication method */
+
+            PasswordAuthenticationMethodMock.InSequence(seq)
+                                            .Setup(p => p.Name)
+                                            .Returns("password-partial2");
+
+            /* 8 */
+
+            PublicKeyAuthenticationMethodMock.InSequence(seq)
+                                             .Setup(p => p.Authenticate(SessionMock.Object))
+                                             .Returns(AuthenticationResult.Failure);
+            PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
+
+            SessionMock.InSequence(seq).Setup(p => p.UnRegisterMessage("SSH_MSG_USERAUTH_FAILURE"));
+            SessionMock.InSequence(seq).Setup(p => p.UnRegisterMessage("SSH_MSG_USERAUTH_SUCCESS"));
+            SessionMock.InSequence(seq).Setup(p => p.UnRegisterMessage("SSH_MSG_USERAUTH_BANNER"));
+        }
+
+        protected override void Arrange()
+        {
+            base.Arrange();
+
+            _clientAuthentication = new ClientAuthentication(_partialSuccessLimit);
+        }
+
+        protected override void Act()
+        {
+            try
+            {
+                _clientAuthentication.Authenticate(ConnectionInfoMock.Object, SessionMock.Object);
+                Assert.Fail();
+            }
+            catch (SshAuthenticationException ex)
+            {
+                _actualException = ex;
+            }
+        }
+
+        [TestMethod]
+        public void AuthenticateOnKeyboardInteractiveAuthenticationMethodShouldHaveBeenInvokedOnce()
+        {
+            KeyboardInteractiveAuthenticationMethodMock.Verify(p => p.Authenticate(SessionMock.Object), Times.Once);
+        }
+
+        [TestMethod]
+        public void AuthenticateOnPublicKeyAuthenticationMethodShouldHaveBeenInvokedTwice()
+        {
+            PublicKeyAuthenticationMethodMock.Verify(p => p.Authenticate(SessionMock.Object), Times.Exactly(2));
+        }
+
+        [TestMethod]
+        public void AuthenticateShouldThrowSshAuthenticationException()
+        {
+            Assert.IsNotNull(_actualException);
+            Assert.IsNull(_actualException.InnerException);
+            Assert.AreEqual("Permission denied (publickey).", _actualException.Message);
+        }
+    }
+}

+ 159 - 0
src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedByFailureInSameBranch.cs

@@ -0,0 +1,159 @@
+using System.Collections.Generic;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    /// <summary>
+    /// * ConnectionInfo provides the following authentication methods (in order):
+    ///     o keyboard-interactive
+    ///     o password
+    ///     o publickey
+    /// * Partial success limit is 2
+    /// * Scenario:
+    ///                           none
+    ///                          (1=FAIL)
+    ///                             |
+    ///                         password
+    ///                       (2=PARTIAL)
+    ///                             |
+    ///             +------------------------------+
+    ///             |                              |
+    ///         password                       publickey
+    ///       (4=PARTIAL)                     (3=FAILURE)
+    ///             |
+    ///     keyboard-interactive
+    ///        (5=FAILURE)
+    /// </summary>
+    [TestClass]
+    public class ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedByFailureInSameBranch : ClientAuthenticationTestBase
+    {
+        private int _partialSuccessLimit;
+        private ClientAuthentication _clientAuthentication;
+        private SshAuthenticationException _actualException;
+
+        protected override void SetupData()
+        {
+            _partialSuccessLimit = 2;
+        }
+
+        protected override void SetupMocks()
+        {
+            var seq = new MockSequence();
+
+            SessionMock.InSequence(seq).Setup(p => p.RegisterMessage("SSH_MSG_USERAUTH_FAILURE"));
+            SessionMock.InSequence(seq).Setup(p => p.RegisterMessage("SSH_MSG_USERAUTH_SUCCESS"));
+            SessionMock.InSequence(seq).Setup(p => p.RegisterMessage("SSH_MSG_USERAUTH_BANNER"));
+
+            ConnectionInfoMock.InSequence(seq).Setup(p => p.CreateNoneAuthenticationMethod())
+                              .Returns(NoneAuthenticationMethodMock.Object);
+
+            /* 1 */
+
+            NoneAuthenticationMethodMock.InSequence(seq).Setup(p => p.Authenticate(SessionMock.Object))
+                                        .Returns(AuthenticationResult.Failure);
+            ConnectionInfoMock.InSequence(seq)
+                              .Setup(p => p.AuthenticationMethods)
+                              .Returns(new List<IAuthenticationMethod>
+                                  {
+                                      KeyboardInteractiveAuthenticationMethodMock.Object,
+                                      PasswordAuthenticationMethodMock.Object,
+                                      PublicKeyAuthenticationMethodMock.Object
+                                  });
+            NoneAuthenticationMethodMock.InSequence(seq)
+                                        .Setup(p => p.AllowedAuthentications)
+                                        .Returns(new[] {"password"});
+
+            /* Enumerate supported authentication methods */
+
+            KeyboardInteractiveAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("keyboard-interactive");
+            PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
+            PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
+
+            /* 2 */
+
+            PasswordAuthenticationMethodMock.InSequence(seq)
+                                            .Setup(p => p.Authenticate(SessionMock.Object))
+                                            .Returns(AuthenticationResult.PartialSuccess);
+            PasswordAuthenticationMethodMock.InSequence(seq)
+                                            .Setup(p => p.AllowedAuthentications)
+                                            .Returns(new[] {"password", "publickey"});
+
+            /* Enumerate supported authentication methods */
+
+            KeyboardInteractiveAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("keyboard-interactive");
+            PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
+            PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
+
+            /* 3 */
+
+            PublicKeyAuthenticationMethodMock.InSequence(seq)
+                                             .Setup(p => p.Authenticate(SessionMock.Object))
+                                             .Returns(AuthenticationResult.Failure);
+            PublicKeyAuthenticationMethodMock.InSequence(seq)
+                                             .Setup(p => p.Name)
+                                             .Returns("publickey-failure");
+
+            /* 4 */
+
+            PasswordAuthenticationMethodMock.InSequence(seq)
+                                            .Setup(p => p.Authenticate(SessionMock.Object))
+                                            .Returns(AuthenticationResult.PartialSuccess);
+            PasswordAuthenticationMethodMock.InSequence(seq)
+                                            .Setup(p => p.AllowedAuthentications)
+                                            .Returns(new[] {"keyboard-interactive"});
+
+            /* Enumerate supported authentication methods */
+
+            KeyboardInteractiveAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("keyboard-interactive");
+            PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
+            PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
+
+            /* 5 */
+
+            KeyboardInteractiveAuthenticationMethodMock.InSequence(seq)
+                                                       .Setup(p => p.Authenticate(SessionMock.Object))
+                                                       .Returns(AuthenticationResult.Failure);
+            KeyboardInteractiveAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("keyboard-interactive-failure");
+
+            SessionMock.InSequence(seq).Setup(p => p.UnRegisterMessage("SSH_MSG_USERAUTH_FAILURE"));
+            SessionMock.InSequence(seq).Setup(p => p.UnRegisterMessage("SSH_MSG_USERAUTH_SUCCESS"));
+            SessionMock.InSequence(seq).Setup(p => p.UnRegisterMessage("SSH_MSG_USERAUTH_BANNER"));
+        }
+
+        protected override void Arrange()
+        {
+            base.Arrange();
+
+            _clientAuthentication = new ClientAuthentication(_partialSuccessLimit);
+        }
+
+        protected override void Act()
+        {
+            try
+            {
+                _clientAuthentication.Authenticate(ConnectionInfoMock.Object, SessionMock.Object);
+                Assert.Fail();
+            }
+            catch (SshAuthenticationException ex)
+            {
+                _actualException = ex;
+            }
+        }
+
+        [TestMethod]
+        public void AuthenticateOnKeyboardInteractiveAuthenticationMethodShouldHaveBeenInvokedOnce()
+        {
+            KeyboardInteractiveAuthenticationMethodMock.Verify(p => p.Authenticate(SessionMock.Object), Times.Once);
+        }
+
+        [TestMethod]
+        public void AuthenticateShouldThrowSshAuthenticationException()
+        {
+            Assert.IsNotNull(_actualException);
+            Assert.IsNull(_actualException.InnerException);
+            Assert.AreEqual("Permission denied (keyboard-interactive-failure).", _actualException.Message);
+        }
+    }
+}

+ 185 - 0
src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedBySuccessInAlternateBranch.cs

@@ -0,0 +1,185 @@
+using System.Collections.Generic;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    /// <summary>
+    /// * ConnectionInfo provides the following authentication methods (in order):
+    ///     o password
+    ///     o publickey
+    ///     o keyboard-interactive
+    /// * Partial success limit is 2
+    /// 
+    ///                    none
+    ///                  (1=FAIL)
+    ///                     |
+    ///             +-------------------+
+    ///             |                   |
+    ///         publickey      keyboard-interactive
+    ///          (2=PS)          ^   (6=PS)
+    ///             |            |      |
+    ///         password         |      +-----------+
+    ///          (3=PS)          |      |           |
+    ///             |            |  password     publickey
+    ///         password         |   (7=SKIP)   (8=SUCCESS)
+    ///          (4=PS)          |
+    ///             |            |
+    ///         password         |
+    ///         (5=SKIP)         |
+    ///             +------------+
+    /// </summary>
+    [TestClass]
+    public class ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedBySuccessInAlternateBranch : ClientAuthenticationTestBase
+    {
+        private int _partialSuccessLimit;
+        private ClientAuthentication _clientAuthentication;
+        private SshAuthenticationException _actualException;
+
+        protected override void SetupData()
+        {
+            _partialSuccessLimit = 2;
+        }
+
+        protected override void SetupMocks()
+        {
+            var seq = new MockSequence();
+
+            SessionMock.InSequence(seq).Setup(p => p.RegisterMessage("SSH_MSG_USERAUTH_FAILURE"));
+            SessionMock.InSequence(seq).Setup(p => p.RegisterMessage("SSH_MSG_USERAUTH_SUCCESS"));
+            SessionMock.InSequence(seq).Setup(p => p.RegisterMessage("SSH_MSG_USERAUTH_BANNER"));
+
+            ConnectionInfoMock.InSequence(seq).Setup(p => p.CreateNoneAuthenticationMethod())
+                              .Returns(NoneAuthenticationMethodMock.Object);
+
+            /* 1 */
+
+            NoneAuthenticationMethodMock.InSequence(seq).Setup(p => p.Authenticate(SessionMock.Object))
+                                        .Returns(AuthenticationResult.Failure);
+            ConnectionInfoMock.InSequence(seq)
+                              .Setup(p => p.AuthenticationMethods)
+                              .Returns(new List<IAuthenticationMethod>
+                                  {
+                                      PasswordAuthenticationMethodMock.Object,
+                                      PublicKeyAuthenticationMethodMock.Object,
+                                      KeyboardInteractiveAuthenticationMethodMock.Object,
+                                  });
+            NoneAuthenticationMethodMock.InSequence(seq)
+                                        .Setup(p => p.AllowedAuthentications)
+                                        .Returns(new[] {"publickey", "keyboard-interactive"});
+
+            /* Enumerate supported authentication methods */
+
+            PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
+            PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
+            KeyboardInteractiveAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("keyboard-interactive");
+
+            /* 2 */
+
+            PublicKeyAuthenticationMethodMock.InSequence(seq)
+                                             .Setup(p => p.Authenticate(SessionMock.Object))
+                                             .Returns(AuthenticationResult.PartialSuccess);
+            PublicKeyAuthenticationMethodMock.InSequence(seq)
+                                             .Setup(p => p.AllowedAuthentications)
+                                             .Returns(new[] {"password"});
+
+            /* Enumerate supported authentication methods */
+
+            PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
+            PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
+            KeyboardInteractiveAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("keyboard-interactive");
+
+            /* 3 */
+
+            PasswordAuthenticationMethodMock.InSequence(seq)
+                                            .Setup(p => p.Authenticate(SessionMock.Object))
+                                            .Returns(AuthenticationResult.PartialSuccess);
+            PasswordAuthenticationMethodMock.InSequence(seq)
+                                            .Setup(p => p.AllowedAuthentications)
+                                            .Returns(new[] {"password"});
+
+            /* Enumerate supported authentication methods */
+
+            PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
+            PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
+            KeyboardInteractiveAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("keyboard-interactive");
+
+            /* 4 */
+
+            PasswordAuthenticationMethodMock.InSequence(seq)
+                                            .Setup(p => p.Authenticate(SessionMock.Object))
+                                            .Returns(AuthenticationResult.PartialSuccess);
+            PasswordAuthenticationMethodMock.InSequence(seq)
+                                            .Setup(p => p.AllowedAuthentications)
+                                            .Returns(new[] {"password"});
+
+            /* Enumerate supported authentication methods */
+
+            PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
+            PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
+            KeyboardInteractiveAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("keyboard-interactive");
+
+            /* 5: Record partial success limit reached exception, and skip password authentication method */
+
+            PasswordAuthenticationMethodMock.InSequence(seq)
+                                            .Setup(p => p.Name)
+                                            .Returns("password-partial1");
+
+            /* 6 */
+
+            KeyboardInteractiveAuthenticationMethodMock.InSequence(seq)
+                                                       .Setup(p => p.Authenticate(SessionMock.Object))
+                                                       .Returns(AuthenticationResult.PartialSuccess);
+            KeyboardInteractiveAuthenticationMethodMock.InSequence(seq)
+                                                       .Setup(p => p.AllowedAuthentications)
+                                                       .Returns(new[] {"password", "publickey"});
+
+            /* Enumerate supported authentication methods */
+
+            PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
+            PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
+            KeyboardInteractiveAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("keyboard-interactive");
+
+            /* 7: Record partial success limit reached exception, and skip password authentication method */
+
+            PasswordAuthenticationMethodMock.InSequence(seq)
+                                            .Setup(p => p.Name)
+                                            .Returns("password-partial2");
+
+            /* 8 */
+
+            PublicKeyAuthenticationMethodMock.InSequence(seq)
+                                             .Setup(p => p.Authenticate(SessionMock.Object))
+                                             .Returns(AuthenticationResult.Success);
+
+            SessionMock.InSequence(seq).Setup(p => p.UnRegisterMessage("SSH_MSG_USERAUTH_FAILURE"));
+            SessionMock.InSequence(seq).Setup(p => p.UnRegisterMessage("SSH_MSG_USERAUTH_SUCCESS"));
+            SessionMock.InSequence(seq).Setup(p => p.UnRegisterMessage("SSH_MSG_USERAUTH_BANNER"));
+        }
+
+        protected override void Arrange()
+        {
+            base.Arrange();
+
+            _clientAuthentication = new ClientAuthentication(_partialSuccessLimit);
+        }
+
+        protected override void Act()
+        {
+            _clientAuthentication.Authenticate(ConnectionInfoMock.Object, SessionMock.Object);
+        }
+
+        [TestMethod]
+        public void AuthenticateOnKeyboardInteractiveAuthenticationMethodShouldHaveBeenInvokedOnce()
+        {
+            KeyboardInteractiveAuthenticationMethodMock.Verify(p => p.Authenticate(SessionMock.Object), Times.Once);
+        }
+
+        [TestMethod]
+        public void AuthenticateOnPublicKeyAuthenticationMethodShouldHaveBeenInvokedTwice()
+        {
+            PublicKeyAuthenticationMethodMock.Verify(p => p.Authenticate(SessionMock.Object), Times.Exactly(2));
+        }
+    }
+}

+ 140 - 0
src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedBySuccessInSameBranch.cs

@@ -0,0 +1,140 @@
+using System.Collections.Generic;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    /// <summary>
+    /// * ConnectionInfo provides the following authentication methods (in order):
+    ///     o keyboard-interactive
+    ///     o password
+    ///     o publickey
+    /// * Partial success limit is 2
+    /// * Scenario:
+    ///                           none
+    ///                          (1=FAIL)
+    ///                             |
+    ///                         password
+    ///                       (2=PARTIAL)
+    ///                             |
+    ///             +------------------------------+
+    ///             |                              |
+    ///         password                       publickey
+    ///       (4=PARTIAL)                     (3=FAILURE)
+    ///             |
+    ///     keyboard-interactive
+    ///        (5=FAILURE)
+    /// </summary>
+    [TestClass]
+    public class ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedBySuccessInSameBranch : ClientAuthenticationTestBase
+    {
+        private int _partialSuccessLimit;
+        private ClientAuthentication _clientAuthentication;
+
+        protected override void SetupData()
+        {
+            _partialSuccessLimit = 2;
+        }
+
+        protected override void SetupMocks()
+        {
+            var seq = new MockSequence();
+
+            SessionMock.InSequence(seq).Setup(p => p.RegisterMessage("SSH_MSG_USERAUTH_FAILURE"));
+            SessionMock.InSequence(seq).Setup(p => p.RegisterMessage("SSH_MSG_USERAUTH_SUCCESS"));
+            SessionMock.InSequence(seq).Setup(p => p.RegisterMessage("SSH_MSG_USERAUTH_BANNER"));
+
+            ConnectionInfoMock.InSequence(seq).Setup(p => p.CreateNoneAuthenticationMethod())
+                              .Returns(NoneAuthenticationMethodMock.Object);
+
+            /* 1 */
+
+            NoneAuthenticationMethodMock.InSequence(seq).Setup(p => p.Authenticate(SessionMock.Object))
+                                        .Returns(AuthenticationResult.Failure);
+            ConnectionInfoMock.InSequence(seq)
+                              .Setup(p => p.AuthenticationMethods)
+                              .Returns(new List<IAuthenticationMethod>
+                                  {
+                                      KeyboardInteractiveAuthenticationMethodMock.Object,
+                                      PasswordAuthenticationMethodMock.Object,
+                                      PublicKeyAuthenticationMethodMock.Object
+                                  });
+            NoneAuthenticationMethodMock.InSequence(seq)
+                                        .Setup(p => p.AllowedAuthentications)
+                                        .Returns(new[] {"password"});
+
+            /* Enumerate supported authentication methods */
+
+            KeyboardInteractiveAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("keyboard-interactive");
+            PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
+            PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
+
+            /* 2 */
+
+            PasswordAuthenticationMethodMock.InSequence(seq)
+                                            .Setup(p => p.Authenticate(SessionMock.Object))
+                                            .Returns(AuthenticationResult.PartialSuccess);
+            PasswordAuthenticationMethodMock.InSequence(seq)
+                                            .Setup(p => p.AllowedAuthentications)
+                                            .Returns(new[] {"password", "publickey"});
+
+            /* Enumerate supported authentication methods */
+
+            KeyboardInteractiveAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("keyboard-interactive");
+            PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
+            PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
+
+            /* 3 */
+
+            PublicKeyAuthenticationMethodMock.InSequence(seq)
+                                             .Setup(p => p.Authenticate(SessionMock.Object))
+                                             .Returns(AuthenticationResult.Failure);
+            PublicKeyAuthenticationMethodMock.InSequence(seq)
+                                             .Setup(p => p.Name)
+                                             .Returns("publickey-failure");
+
+            /* 4 */
+
+            PasswordAuthenticationMethodMock.InSequence(seq)
+                                            .Setup(p => p.Authenticate(SessionMock.Object))
+                                            .Returns(AuthenticationResult.PartialSuccess);
+            PasswordAuthenticationMethodMock.InSequence(seq)
+                                            .Setup(p => p.AllowedAuthentications)
+                                            .Returns(new[] {"keyboard-interactive"});
+
+            /* Enumerate supported authentication methods */
+
+            KeyboardInteractiveAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("keyboard-interactive");
+            PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
+            PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
+
+            /* 5 */
+
+            KeyboardInteractiveAuthenticationMethodMock.InSequence(seq)
+                                                       .Setup(p => p.Authenticate(SessionMock.Object))
+                                                       .Returns(AuthenticationResult.Success);
+
+            SessionMock.InSequence(seq).Setup(p => p.UnRegisterMessage("SSH_MSG_USERAUTH_FAILURE"));
+            SessionMock.InSequence(seq).Setup(p => p.UnRegisterMessage("SSH_MSG_USERAUTH_SUCCESS"));
+            SessionMock.InSequence(seq).Setup(p => p.UnRegisterMessage("SSH_MSG_USERAUTH_BANNER"));
+        }
+
+        protected override void Arrange()
+        {
+            base.Arrange();
+
+            _clientAuthentication = new ClientAuthentication(_partialSuccessLimit);
+        }
+
+        protected override void Act()
+        {
+            _clientAuthentication.Authenticate(ConnectionInfoMock.Object, SessionMock.Object);
+        }
+
+        [TestMethod]
+        public void AuthenticateOnKeyboardInteractiveAuthenticationMethodShouldHaveBeenInvokedOnce()
+        {
+            KeyboardInteractiveAuthenticationMethodMock.Verify(p => p.Authenticate(SessionMock.Object), Times.Once);
+        }
+    }
+}

+ 44 - 19
src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_MultiList_PostponePartialAccessAuthenticationMethod.cs

@@ -7,6 +7,14 @@ namespace Renci.SshNet.Tests.Classes
     [TestClass]
     public class ClientAuthenticationTest_Success_MultiList_PostponePartialAccessAuthenticationMethod : ClientAuthenticationTestBase
     {
+        private int _partialSuccessLimit;
+        private ClientAuthentication _clientAuthentication;
+
+        protected override void SetupData()
+        {
+            _partialSuccessLimit = 3;
+        }
+
         protected override void SetupMocks()
         {
             var seq = new MockSequence();
@@ -16,42 +24,59 @@ namespace Renci.SshNet.Tests.Classes
             SessionMock.InSequence(seq).Setup(p => p.RegisterMessage("SSH_MSG_USERAUTH_BANNER"));
 
             ConnectionInfoMock.InSequence(seq).Setup(p => p.CreateNoneAuthenticationMethod())
-                .Returns(NoneAuthenticationMethodMock.Object);
-
-            NoneAuthenticationMethodMock.InSequence(seq).Setup(p => p.Authenticate(SessionMock.Object))
-                .Returns(AuthenticationResult.Failure);
-            ConnectionInfoMock.InSequence(seq).Setup(p => p.AuthenticationMethods)
-                            .Returns(new List<IAuthenticationMethod>
-                {
-                    KeyboardInteractiveAuthenticationMethodMock.Object,
-                    PasswordAuthenticationMethodMock.Object,
-                    PublicKeyAuthenticationMethodMock.Object
-                });
+                              .Returns(NoneAuthenticationMethodMock.Object);
+
+            NoneAuthenticationMethodMock.InSequence(seq)
+                                        .Setup(p => p.Authenticate(SessionMock.Object))
+                                        .Returns(AuthenticationResult.Failure);
+            ConnectionInfoMock.InSequence(seq)
+                              .Setup(p => p.AuthenticationMethods)
+                              .Returns(new List<IAuthenticationMethod>
+                                  {
+                                      KeyboardInteractiveAuthenticationMethodMock.Object,
+                                      PasswordAuthenticationMethodMock.Object,
+                                      PublicKeyAuthenticationMethodMock.Object
+                                  });
             NoneAuthenticationMethodMock.InSequence(seq).Setup(p => p.AllowedAuthentications).Returns(new[] { "password" });
             KeyboardInteractiveAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("keyboard-interactive");
             PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
             PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
 
-            PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Authenticate(SessionMock.Object))
-                .Returns(AuthenticationResult.PartialSuccess);
-            PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.AllowedAuthentications)
-                .Returns(new[] { "password", "publickey" });
+            PasswordAuthenticationMethodMock.InSequence(seq)
+                                            .Setup(p => p.Authenticate(SessionMock.Object))
+                                            .Returns(AuthenticationResult.PartialSuccess);
+            PasswordAuthenticationMethodMock.InSequence(seq)
+                                            .Setup(p => p.AllowedAuthentications)
+                                            .Returns(new[] {"password", "publickey"});
             KeyboardInteractiveAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("keyboard-interactive");
             PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
             PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
 
-            PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Authenticate(SessionMock.Object)).Returns(AuthenticationResult.Failure);
-            PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
-            PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Authenticate(SessionMock.Object)).Returns(AuthenticationResult.Success);
+            PublicKeyAuthenticationMethodMock.InSequence(seq)
+                                             .Setup(p => p.Authenticate(SessionMock.Object))
+                                             .Returns(AuthenticationResult.Failure);
+            PublicKeyAuthenticationMethodMock.InSequence(seq)
+                                             .Setup(p => p.Name)
+                                             .Returns("publickey");
+            PasswordAuthenticationMethodMock.InSequence(seq)
+                                            .Setup(p => p.Authenticate(SessionMock.Object))
+                                            .Returns(AuthenticationResult.Success);
 
             SessionMock.InSequence(seq).Setup(p => p.UnRegisterMessage("SSH_MSG_USERAUTH_FAILURE"));
             SessionMock.InSequence(seq).Setup(p => p.UnRegisterMessage("SSH_MSG_USERAUTH_SUCCESS"));
             SessionMock.InSequence(seq).Setup(p => p.UnRegisterMessage("SSH_MSG_USERAUTH_BANNER"));
         }
 
+        protected override void Arrange()
+        {
+            base.Arrange();
+
+            _clientAuthentication = new ClientAuthentication(_partialSuccessLimit);
+        }
+
         protected override void Act()
         {
-            ClientAuthentication.Authenticate(ConnectionInfoMock.Object, SessionMock.Object);
+            _clientAuthentication.Authenticate(ConnectionInfoMock.Object, SessionMock.Object);
         }
     }
 }

+ 16 - 1
src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_MultiList_SameAllowedAuthenticationsAfterPartialSuccess.cs

@@ -7,6 +7,14 @@ namespace Renci.SshNet.Tests.Classes
     [TestClass]
     public class ClientAuthenticationTest_Success_MultiList_SameAllowedAuthenticationsAfterPartialSuccess : ClientAuthenticationTestBase
     {
+        private int _partialSuccessLimit;
+        private ClientAuthentication _clientAuthentication;
+
+        protected override void SetupData()
+        {
+            _partialSuccessLimit = 1;
+        }
+
         protected override void SetupMocks()
         {
             var seq = new MockSequence();
@@ -53,9 +61,16 @@ namespace Renci.SshNet.Tests.Classes
             SessionMock.InSequence(seq).Setup(p => p.UnRegisterMessage("SSH_MSG_USERAUTH_BANNER"));
         }
 
+        protected override void Arrange()
+        {
+            base.Arrange();
+
+            _clientAuthentication = new ClientAuthentication(_partialSuccessLimit);
+        }
+
         protected override void Act()
         {
-            ClientAuthentication.Authenticate(ConnectionInfoMock.Object, SessionMock.Object);
+            _clientAuthentication.Authenticate(ConnectionInfoMock.Object, SessionMock.Object);
         }
     }
 }

+ 16 - 1
src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_MultiList_SkipFailedAuthenticationMethod.cs

@@ -7,6 +7,14 @@ namespace Renci.SshNet.Tests.Classes
     [TestClass]
     public class ClientAuthenticationTest_Success_MultiList_SkipFailedAuthenticationMethod : ClientAuthenticationTestBase
     {
+        private int _partialSuccessLimit;
+        private ClientAuthentication _clientAuthentication;
+
+        protected override void SetupData()
+        {
+            _partialSuccessLimit = 1;
+        }
+
         protected override void SetupMocks()
         {
             var seq = new MockSequence();
@@ -47,9 +55,16 @@ namespace Renci.SshNet.Tests.Classes
             SessionMock.InSequence(seq).Setup(p => p.UnRegisterMessage("SSH_MSG_USERAUTH_BANNER"));
         }
 
+        protected override void Arrange()
+        {
+            base.Arrange();
+
+            _clientAuthentication = new ClientAuthentication(_partialSuccessLimit);
+        }
+
         protected override void Act()
         {
-            ClientAuthentication.Authenticate(ConnectionInfoMock.Object, SessionMock.Object);
+            _clientAuthentication.Authenticate(ConnectionInfoMock.Object, SessionMock.Object);
         }
     }
 }

+ 62 - 13
src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_SingleList_SameAllowedAuthenticationAfterPartialSuccess.cs

@@ -4,9 +4,33 @@ using Moq;
 
 namespace Renci.SshNet.Tests.Classes
 {
+    /// <summary>
+    /// * ConnectionInfo provides the following authentication methods (in order):
+    ///     o keyboard-interactive
+    ///     o password
+    ///     o publickey
+    /// * Partial success limit is 2
+    /// * Scenario:
+    ///                           none
+    ///                          (1=FAIL)
+    ///                             |
+    ///                         password
+    ///                       (2=PARTIAL)
+    ///                             |
+    ///                         password
+    ///                       (3=SUCCESS)
+    /// </summary>
     [TestClass]
     public class ClientAuthenticationTest_Success_SingleList_SameAllowedAuthenticationAfterPartialSuccess : ClientAuthenticationTestBase
     {
+        private int _partialSuccessLimit;
+        private ClientAuthentication _clientAuthentication;
+
+        protected override void SetupData()
+        {
+            _partialSuccessLimit = 2;
+        }
+
         protected override void SetupMocks()
         {
             var seq = new MockSequence();
@@ -18,36 +42,61 @@ namespace Renci.SshNet.Tests.Classes
             ConnectionInfoMock.InSequence(seq).Setup(p => p.CreateNoneAuthenticationMethod())
                 .Returns(NoneAuthenticationMethodMock.Object);
 
+            /* 1 */
+
             NoneAuthenticationMethodMock.InSequence(seq).Setup(p => p.Authenticate(SessionMock.Object))
                 .Returns(AuthenticationResult.Failure);
-            ConnectionInfoMock.InSequence(seq).Setup(p => p.AuthenticationMethods)
-                            .Returns(new List<IAuthenticationMethod>
-                {
-                    PublicKeyAuthenticationMethodMock.Object,
-                    PasswordAuthenticationMethodMock.Object
-                });
-            NoneAuthenticationMethodMock.InSequence(seq).Setup(p => p.AllowedAuthentications).Returns(new[] { "password" });
+            ConnectionInfoMock.InSequence(seq)
+                              .Setup(p => p.AuthenticationMethods)
+                              .Returns(new List<IAuthenticationMethod>
+                                  {
+                                      PublicKeyAuthenticationMethodMock.Object,
+                                      PasswordAuthenticationMethodMock.Object
+                                  });
+            NoneAuthenticationMethodMock.InSequence(seq)
+                                        .Setup(p => p.AllowedAuthentications)
+                                        .Returns(new[] {"password"});
+
+            /* Enumerate supported authentication methods */
 
             PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
             PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
 
-            PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Authenticate(SessionMock.Object))
-                .Returns(AuthenticationResult.PartialSuccess);
-            PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.AllowedAuthentications)
-                .Returns(new[] { "password" });
+            /* 2 */
+
+            PasswordAuthenticationMethodMock.InSequence(seq)
+                                            .Setup(p => p.Authenticate(SessionMock.Object))
+                                            .Returns(AuthenticationResult.PartialSuccess);
+            PasswordAuthenticationMethodMock.InSequence(seq)
+                                            .Setup(p => p.AllowedAuthentications)
+                                            .Returns(new[] {"password"});
+
+            /* Enumerate supported authentication methods */
+
             PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
             PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
 
-            PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Authenticate(SessionMock.Object)).Returns(AuthenticationResult.Success);
+            /* 3 */
+
+            PasswordAuthenticationMethodMock.InSequence(seq)
+                                            .Setup(p => p.Authenticate(SessionMock.Object))
+                                            .Returns(AuthenticationResult.Success);
 
             SessionMock.InSequence(seq).Setup(p => p.UnRegisterMessage("SSH_MSG_USERAUTH_FAILURE"));
             SessionMock.InSequence(seq).Setup(p => p.UnRegisterMessage("SSH_MSG_USERAUTH_SUCCESS"));
             SessionMock.InSequence(seq).Setup(p => p.UnRegisterMessage("SSH_MSG_USERAUTH_BANNER"));
         }
 
+        protected override void Arrange()
+        {
+            base.Arrange();
+
+            _clientAuthentication = new ClientAuthentication(_partialSuccessLimit);
+        }
+
         protected override void Act()
         {
-            ClientAuthentication.Authenticate(ConnectionInfoMock.Object, SessionMock.Object);
+            _clientAuthentication.Authenticate(ConnectionInfoMock.Object, SessionMock.Object);
         }
     }
 }

+ 150 - 0
src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_SingleList_SameAllowedAuthenticationAfterPartialSuccess_PartialSuccessLimitReached.cs

@@ -0,0 +1,150 @@
+using System.Collections.Generic;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    /// <summary>
+    /// * ConnectionInfo provides the following authentication methods (in order):
+    ///     o publickey
+    ///     o password
+    /// * Partial success limit is 3
+    /// * Scenario:
+    ///                           none
+    ///                          (1=FAIL)
+    ///                             |
+    ///                         password
+    ///                       (2=PARTIAL)
+    ///                             |
+    ///                         password
+    ///                       (3=PARTIAL)
+    ///                             |
+    ///                         password
+    ///                       (4=PARTIAL)
+    ///                             |
+    ///                         password
+    ///                         (5=SKIP)
+    /// </summary>
+    [TestClass]
+    public class ClientAuthenticationTest_Success_SingleList_SameAllowedAuthenticationAfterPartialSuccess_PartialSuccessLimitReached : ClientAuthenticationTestBase
+    {
+        private int _partialSuccessLimit;
+        private ClientAuthentication _clientAuthentication;
+        private SshAuthenticationException _actualException;
+
+        protected override void SetupData()
+        {
+            _partialSuccessLimit = 3;
+        }
+
+        protected override void SetupMocks()
+        {
+            var seq = new MockSequence();
+
+            SessionMock.InSequence(seq).Setup(p => p.RegisterMessage("SSH_MSG_USERAUTH_FAILURE"));
+            SessionMock.InSequence(seq).Setup(p => p.RegisterMessage("SSH_MSG_USERAUTH_SUCCESS"));
+            SessionMock.InSequence(seq).Setup(p => p.RegisterMessage("SSH_MSG_USERAUTH_BANNER"));
+
+            ConnectionInfoMock.InSequence(seq).Setup(p => p.CreateNoneAuthenticationMethod())
+                              .Returns(NoneAuthenticationMethodMock.Object);
+
+            /* 1 */
+            NoneAuthenticationMethodMock.InSequence(seq).Setup(p => p.Authenticate(SessionMock.Object))
+                                        .Returns(AuthenticationResult.Failure);
+            ConnectionInfoMock.InSequence(seq)
+                              .Setup(p => p.AuthenticationMethods)
+                              .Returns(new List<IAuthenticationMethod>
+                                  {
+                                      PublicKeyAuthenticationMethodMock.Object,
+                                      PasswordAuthenticationMethodMock.Object
+                                  });
+            NoneAuthenticationMethodMock.InSequence(seq)
+                                        .Setup(p => p.AllowedAuthentications)
+                                        .Returns(new[] { "password" });
+
+            /* Enumerate supported authentication methods */
+
+            PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
+            PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
+
+            /* 2 */
+
+            PasswordAuthenticationMethodMock.InSequence(seq)
+                                            .Setup(p => p.Authenticate(SessionMock.Object))
+                                            .Returns(AuthenticationResult.PartialSuccess);
+            PasswordAuthenticationMethodMock.InSequence(seq)
+                                            .Setup(p => p.AllowedAuthentications)
+                                            .Returns(new[] {"password"});
+
+            /* Enumerate supported authentication methods */
+
+            PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
+            PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
+
+            /* 3 */
+
+            PasswordAuthenticationMethodMock.InSequence(seq)
+                                            .Setup(p => p.Authenticate(SessionMock.Object))
+                                            .Returns(AuthenticationResult.PartialSuccess);
+            PasswordAuthenticationMethodMock.InSequence(seq)
+                                            .Setup(p => p.AllowedAuthentications)
+                                            .Returns(new[] { "password" });
+
+            /* Enumerate supported authentication methods */
+
+            PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
+            PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
+
+            /* 4 */
+
+            PasswordAuthenticationMethodMock.InSequence(seq)
+                                            .Setup(p => p.Authenticate(SessionMock.Object))
+                                            .Returns(AuthenticationResult.PartialSuccess);
+            PasswordAuthenticationMethodMock.InSequence(seq)
+                                            .Setup(p => p.AllowedAuthentications)
+                                            .Returns(new[] { "password" });
+
+            /* Enumerate supported authentication methods */
+
+            PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
+            PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
+
+            /* 5: Record partial success limit reached exception, and skip password authentication method */
+
+            PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("x_password_x");
+
+            SessionMock.InSequence(seq).Setup(p => p.UnRegisterMessage("SSH_MSG_USERAUTH_FAILURE"));
+            SessionMock.InSequence(seq).Setup(p => p.UnRegisterMessage("SSH_MSG_USERAUTH_SUCCESS"));
+            SessionMock.InSequence(seq).Setup(p => p.UnRegisterMessage("SSH_MSG_USERAUTH_BANNER"));
+        }
+
+        protected override void Arrange()
+        {
+            base.Arrange();
+
+            _clientAuthentication = new ClientAuthentication(_partialSuccessLimit);
+        }
+
+        protected override void Act()
+        {
+            try
+            {
+                _clientAuthentication.Authenticate(ConnectionInfoMock.Object, SessionMock.Object);
+                Assert.Fail();
+            }
+            catch (SshAuthenticationException ex)
+            {
+                _actualException = ex;
+            }
+        }
+
+        [TestMethod]
+        public void AuthenticateShouldThrowSshAuthenticationException()
+        {
+            Assert.IsNotNull(_actualException);
+            Assert.IsNull(_actualException.InnerException);
+            Assert.AreEqual("Reached authentication attempt limit for method (x_password_x).",_actualException.Message);
+        }
+    }
+}

+ 50 - 3
src/Renci.SshNet.Tests/Classes/Security/KeyExchangeDiffieHellmanGroup14Sha1Test.cs

@@ -1,13 +1,60 @@
 using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Renci.SshNet.Common;
+using Renci.SshNet.Security;
 using Renci.SshNet.Tests.Common;
 
 namespace Renci.SshNet.Tests.Classes.Security
 {
-    /// <summary>
-    /// Represents "diffie-hellman-group14-sha1" algorithm implementation.
-    /// </summary>
     [TestClass]
     public class KeyExchangeDiffieHellmanGroup14Sha1Test : TestBase
     {
+        private static readonly byte[] SecondOkleyGroup =
+            {
+                0x00,
+                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc9, 0x0f, 0xda, 0xa2,
+                0x21, 0x68, 0xc2, 0x34, 0xc4, 0xc6, 0x62, 0x8b, 0x80, 0xdc, 0x1c, 0xd1,
+                0x29, 0x02, 0x4e, 0x08, 0x8a, 0x67, 0xcc, 0x74, 0x02, 0x0b, 0xbe, 0xa6,
+                0x3b, 0x13, 0x9b, 0x22, 0x51, 0x4a, 0x08, 0x79, 0x8e, 0x34, 0x04, 0xdd,
+                0xef, 0x95, 0x19, 0xb3, 0xcd, 0x3a, 0x43, 0x1b, 0x30, 0x2b, 0x0a, 0x6d,
+                0xf2, 0x5f, 0x14, 0x37, 0x4f, 0xe1, 0x35, 0x6d, 0x6d, 0x51, 0xc2, 0x45,
+                0xe4, 0x85, 0xb5, 0x76, 0x62, 0x5e, 0x7e, 0xc6, 0xf4, 0x4c, 0x42, 0xe9,
+                0xa6, 0x37, 0xed, 0x6b, 0x0b, 0xff, 0x5c, 0xb6, 0xf4, 0x06, 0xb7, 0xed,
+                0xee, 0x38, 0x6b, 0xfb, 0x5a, 0x89, 0x9f, 0xa5, 0xae, 0x9f, 0x24, 0x11,
+                0x7c, 0x4b, 0x1f, 0xe6, 0x49, 0x28, 0x66, 0x51, 0xec, 0xe4, 0x5b, 0x3d,
+                0xc2, 0x00, 0x7c, 0xb8, 0xa1, 0x63, 0xbf, 0x05, 0x98, 0xda, 0x48, 0x36,
+                0x1c, 0x55, 0xd3, 0x9a, 0x69, 0x16, 0x3f, 0xa8, 0xfd, 0x24, 0xcf, 0x5f,
+                0x83, 0x65, 0x5d, 0x23, 0xdc, 0xa3, 0xad, 0x96, 0x1c, 0x62, 0xf3, 0x56,
+                0x20, 0x85, 0x52, 0xbb, 0x9e, 0xd5, 0x29, 0x07, 0x70, 0x96, 0x96, 0x6d,
+                0x67, 0x0c, 0x35, 0x4e, 0x4a, 0xbc, 0x98, 0x04, 0xf1, 0x74, 0x6c, 0x08,
+                0xca, 0x18, 0x21, 0x7c, 0x32, 0x90, 0x5e, 0x46, 0x2e, 0x36, 0xce, 0x3b,
+                0xe3, 0x9e, 0x77, 0x2c, 0x18, 0x0e, 0x86, 0x03, 0x9b, 0x27, 0x83, 0xa2,
+                0xec, 0x07, 0xa2, 0x8f, 0xb5, 0xc5, 0x5d, 0xf0, 0x6f, 0x4c, 0x52, 0xc9,
+                0xde, 0x2b, 0xcb, 0xf6, 0x95, 0x58, 0x17, 0x18, 0x39, 0x95, 0x49, 0x7c,
+                0xea, 0x95, 0x6a, 0xe5, 0x15, 0xd2, 0x26, 0x18, 0x98, 0xfa, 0x05, 0x10,
+                0x15, 0x72, 0x8e, 0x5a, 0x8a, 0xac, 0xaa, 0x68, 0xff, 0xff, 0xff, 0xff,
+                0xff, 0xff, 0xff, 0xff
+            };
+
+        private KeyExchangeDiffieHellmanGroup14Sha1 _group14;
+
+        protected override void OnInit()
+        {
+            base.OnInit();
+
+            _group14 = new KeyExchangeDiffieHellmanGroup14Sha1();
+        }
+
+        [TestMethod]
+        public void GroupPrimeShouldBeSecondOakleyGroup()
+        {
+            var bytes = _group14.GroupPrime.ToByteArray().Reverse();
+            Assert.IsTrue(SecondOkleyGroup.IsEqualTo(bytes));
+        }
+
+        [TestMethod]
+        public void NameShouldBeDiffieHellmanGroup14Sha1()
+        {
+            Assert.AreEqual("diffie-hellman-group14-sha1", _group14.Name);
+        }
     }
 }

+ 30 - 31
src/Renci.SshNet.Tests/Classes/Security/KeyExchangeDiffieHellmanGroup1Sha1Test.cs

@@ -5,48 +5,47 @@ using Renci.SshNet.Tests.Common;
 
 namespace Renci.SshNet.Tests.Classes.Security
 {
-    /// <summary>
-    ///This is a test class for KeyExchangeDiffieHellmanGroup1Sha1Test and is intended
-    ///to contain all KeyExchangeDiffieHellmanGroup1Sha1Test Unit Tests
-    ///</summary>
     [TestClass]
     public class KeyExchangeDiffieHellmanGroup1Sha1Test : TestBase
     {
-        /// <summary>
-        ///A test for KeyExchangeDiffieHellmanGroup1Sha1 Constructor
-        ///</summary>
-        [TestMethod]
-        [Ignore] // placeholder for actual test
-        public void KeyExchangeDiffieHellmanGroup1Sha1ConstructorTest()
+        private static readonly byte[] SecondOkleyGroup =
+            {
+                0x00,
+                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc9, 0x0f, 0xda, 0xa2,
+                0x21, 0x68, 0xc2, 0x34, 0xc4, 0xc6, 0x62, 0x8b, 0x80, 0xdc, 0x1c, 0xd1,
+                0x29, 0x02, 0x4e, 0x08, 0x8a, 0x67, 0xcc, 0x74, 0x02, 0x0b, 0xbe, 0xa6,
+                0x3b, 0x13, 0x9b, 0x22, 0x51, 0x4a, 0x08, 0x79, 0x8e, 0x34, 0x04, 0xdd,
+                0xef, 0x95, 0x19, 0xb3, 0xcd, 0x3a, 0x43, 0x1b, 0x30, 0x2b, 0x0a, 0x6d,
+                0xf2, 0x5f, 0x14, 0x37, 0x4f, 0xe1, 0x35, 0x6d, 0x6d, 0x51, 0xc2, 0x45,
+                0xe4, 0x85, 0xb5, 0x76, 0x62, 0x5e, 0x7e, 0xc6, 0xf4, 0x4c, 0x42, 0xe9,
+                0xa6, 0x37, 0xed, 0x6b, 0x0b, 0xff, 0x5c, 0xb6, 0xf4, 0x06, 0xb7, 0xed,
+                0xee, 0x38, 0x6b, 0xfb, 0x5a, 0x89, 0x9f, 0xa5, 0xae, 0x9f, 0x24, 0x11,
+                0x7c, 0x4b, 0x1f, 0xe6, 0x49, 0x28, 0x66, 0x51, 0xec, 0xe6, 0x53, 0x81,
+                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
+            };
+
+        private KeyExchangeDiffieHellmanGroup1Sha1 _group1;
+
+        protected override void OnInit()
         {
-            KeyExchangeDiffieHellmanGroup1Sha1 target = new KeyExchangeDiffieHellmanGroup1Sha1();
-            Assert.Inconclusive("TODO: Implement code to verify target");
+            base.OnInit();
+
+            _group1 = new KeyExchangeDiffieHellmanGroup1Sha1();
         }
 
-        /// <summary>
-        ///A test for GroupPrime
-        ///</summary>
         [TestMethod]
-        [Ignore] // placeholder for actual test
-        public void GroupPrimeTest()
+        public void GroupPrimeShouldBeSecondOakleyGroup()
         {
-            KeyExchangeDiffieHellmanGroup1Sha1 target = new KeyExchangeDiffieHellmanGroup1Sha1(); // TODO: Initialize to an appropriate value
-            BigInteger actual;
-            actual = target.GroupPrime;
-            Assert.Inconclusive("Verify the correctness of this test method.");
+            var bytes = _group1.GroupPrime.ToByteArray().Reverse();
+            Assert.IsTrue(SecondOkleyGroup.IsEqualTo(bytes));
+
+            SecondOkleyGroup.Reverse().DebugPrint();
         }
 
-        /// <summary>
-        ///A test for Name
-        ///</summary>
         [TestMethod]
-        [Ignore] // placeholder for actual test
-        public void NameTest()
+        public void NameShouldBeDiffieHellmanGroup1Sha1()
         {
-            KeyExchangeDiffieHellmanGroup1Sha1 target = new KeyExchangeDiffieHellmanGroup1Sha1(); // TODO: Initialize to an appropriate value
-            string actual;
-            actual = target.Name;
-            Assert.Inconclusive("Verify the correctness of this test method.");
+            Assert.AreEqual("diffie-hellman-group1-sha1", _group1.Name);
         }
     }
-}
+}

+ 42 - 0
src/Renci.SshNet.Tests/Classes/ServiceFactoryTest_CreateClientAuthentication.cs

@@ -0,0 +1,42 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    [TestClass]
+    public class ServiceFactoryTest_CreateClientAuthentication
+    {
+        private ServiceFactory _serviceFactory;
+        private IClientAuthentication _actual;
+
+        private void Arrange()
+        {
+            _serviceFactory = new ServiceFactory();
+        }
+
+        [TestInitialize]
+        public void Initialize()
+        {
+            Arrange();
+            Act();
+        }
+
+        private void Act()
+        {
+            _actual = _serviceFactory.CreateClientAuthentication();
+        }
+
+        [TestMethod]
+        public void CreateClientAuthenticationShouldNotReturnNull()
+        {
+            Assert.IsNotNull(_actual);
+        }
+
+        [TestMethod]
+        public void ClientAuthenticationShouldHavePartialSuccessLimitOf5()
+        {
+            var clientAuthentication = _actual as ClientAuthentication;
+            Assert.IsNotNull(clientAuthentication);
+            Assert.AreEqual(5, clientAuthentication.PartialSuccessLimit);
+        }
+    }
+}

+ 0 - 1
src/Renci.SshNet.Tests/Classes/ServiceFactoryTest_CreateSftpFileReader_EndLStatThrowsSshException.cs

@@ -90,6 +90,5 @@ namespace Renci.SshNet.Tests.Classes
             Assert.IsNotNull(_actual);
             Assert.AreSame(_sftpFileReaderMock.Object, _actual);
         }
-
     }
 }

+ 9 - 0
src/Renci.SshNet.Tests/Renci.SshNet.Tests.csproj

@@ -94,6 +94,7 @@
     <Compile Include="Classes\Channels\ChannelSessionTest_Dispose_Disposed.cs" />
     <Compile Include="Classes\Channels\ChannelSessionTest_Dispose_SessionIsConnectedAndChannelIsOpen_ChannelCloseAndChannelEofReceived_SendChannelCloseMessageFailure.cs" />
     <Compile Include="Classes\Channels\ChannelSessionTest_Dispose_SessionIsConnectedAndChannelIsOpen_ChannelCloseAndChannelEofReceived_SendChannelCloseMessageSuccess.cs" />
+    <Compile Include="Classes\Channels\ChannelSessionTest_Dispose_SessionIsConnectedAndChannelIsOpen_ChannelCloseAndChannelEofReceived_DisposeInEventHandler.cs" />
     <Compile Include="Classes\Channels\ChannelSessionTest_Dispose_SessionIsConnectedAndChannelIsOpen_ChannelCloseReceived_SendChannelCloseMessageFailure.cs" />
     <Compile Include="Classes\Channels\ChannelSessionTest_Dispose_SessionIsConnectedAndChannelIsOpen_ChannelCloseReceived_SendChannelCloseMessageSuccess.cs" />
     <Compile Include="Classes\Channels\ChannelSessionTest_Dispose_SessionIsConnectedAndChannelIsOpen_ChannelEofReceived_SendChannelCloseMessageFailure.cs" />
@@ -138,11 +139,18 @@
     <Compile Include="Classes\ClientAuthenticationTestBase.cs" />
     <Compile Include="Classes\ClientAuthenticationTest_Failure_SingleList_AuthenticationMethodFailed.cs" />
     <Compile Include="Classes\ClientAuthenticationTest_Failure_SingleList_AuthenticationMethodNotConfigured.cs" />
+    <Compile Include="Classes\ClientAuthenticationTest_Failure_MultiList_AllAllowedAuthenticationsHaveReachedPartialSuccessLimit.cs" />
     <Compile Include="Classes\ClientAuthenticationTest_Success_MultiList_DifferentAllowedAuthenticationsAfterPartialSuccess.cs" />
+    <Compile Include="Classes\ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedByFailureInAlternateBranch.cs" />
+    <Compile Include="Classes\ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedByFailureInAlternateBranch2.cs" />
+    <Compile Include="Classes\ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedByFailureInSameBranch.cs" />
+    <Compile Include="Classes\ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedBySuccessInAlternateBranch.cs" />
+    <Compile Include="Classes\ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedBySuccessInSameBranch.cs" />
     <Compile Include="Classes\ClientAuthenticationTest_Success_MultiList_PostponePartialAccessAuthenticationMethod.cs" />
     <Compile Include="Classes\ClientAuthenticationTest_Success_MultiList_SameAllowedAuthenticationsAfterPartialSuccess.cs" />
     <Compile Include="Classes\ClientAuthenticationTest_Success_MultiList_SkipFailedAuthenticationMethod.cs" />
     <Compile Include="Classes\ClientAuthenticationTest_Success_SingleList_SameAllowedAuthenticationAfterPartialSuccess.cs" />
+    <Compile Include="Classes\ClientAuthenticationTest_Success_SingleList_SameAllowedAuthenticationAfterPartialSuccess_PartialSuccessLimitReached.cs" />
     <Compile Include="Classes\Common\CountdownEventTest.cs" />
     <Compile Include="Classes\Common\CountdownEventTest_Dispose_NotSet.cs" />
     <Compile Include="Classes\Common\CountdownEventTest_Dispose_Set.cs" />
@@ -262,6 +270,7 @@
     <Compile Include="Classes\Common\PortForwardEventArgsTest.cs" />
     <Compile Include="Classes\Compression\ZlibTest.cs" />
     <Compile Include="Classes\ConnectionInfoTest.cs" />
+    <Compile Include="Classes\ServiceFactoryTest_CreateClientAuthentication.cs" />
     <Compile Include="Classes\ServiceFactoryTest_CreateSftpFileReader_EndLStatThrowsSshException.cs" />
     <Compile Include="Classes\ServiceFactoryTest_CreateSftpFileReader_FileSizeIsAlmostSixTimesGreaterThanChunkSize.cs" />
     <Compile Include="Classes\ServiceFactoryTest_CreateSftpFileReader_FileSizeIsEqualToChunkSize.cs" />

+ 8 - 5
src/Renci.SshNet/Channels/Channel.cs

@@ -400,6 +400,14 @@ namespace Renci.SshNet.Channels
         {
             _closeMessageReceived = true;
 
+            // signal that SSH_MSG_CHANNEL_CLOSE message was received from server
+            // we need to signal this before firing the Closed event, as a subscriber
+            // may very well react to the Closed event by closing or disposing the
+            // channel which in turn will wait for this handle to be signaled
+            var channelClosedWaitHandle = _channelClosedWaitHandle;
+            if (channelClosedWaitHandle != null)
+                channelClosedWaitHandle.Set();
+
             // raise event signaling that the server has closed its end of the channel
             var closed = Closed;
             if (closed != null)
@@ -407,11 +415,6 @@ namespace Renci.SshNet.Channels
                 closed(this, new ChannelEventArgs(LocalChannelNumber));
             }
 
-            // signal that SSH_MSG_CHANNEL_CLOSE message was received from server
-            var channelClosedWaitHandle = _channelClosedWaitHandle;
-            if (channelClosedWaitHandle != null)
-                channelClosedWaitHandle.Set();
-
             // close the channel
             Close();
         }

+ 43 - 9
src/Renci.SshNet/Channels/ChannelSession.cs

@@ -10,7 +10,7 @@ namespace Renci.SshNet.Channels
     /// <summary>
     /// Implements Session SSH channel.
     /// </summary>
-    internal class ChannelSession : ClientChannel, IChannelSession
+    internal sealed class ChannelSession : ClientChannel, IChannelSession
     {
         /// <summary>
         /// Counts failed channel open attempts
@@ -62,7 +62,7 @@ namespace Renci.SshNet.Channels
         /// <summary>
         /// Opens the channel.
         /// </summary>
-        public virtual void Open()
+        public void Open()
         {
             //  Try to open channel several times
             while (!IsOpen && _failedOpenAttempts < ConnectionInfo.RetryAttempts)
@@ -356,19 +356,53 @@ namespace Renci.SshNet.Channels
         /// <summary>
         /// Sends the channel open message.
         /// </summary>
-        protected void SendChannelOpenMessage()
+        /// <exception cref="SshConnectionException">The client is not connected.</exception>
+        /// <exception cref="SshOperationTimeoutException">The operation timed out.</exception>
+        /// <exception cref="InvalidOperationException">The size of the packet exceeds the maximum size defined by the protocol.</exception>
+        /// <remarks>
+        /// <para>
+        /// When a session semaphore for this instance has not yet been obtained by this or any other thread,
+        /// the thread will block until such a semaphore is available and send a <see cref="ChannelOpenMessage"/>
+        /// to the remote host.
+        /// </para>
+        /// <para>
+        /// Note that the session semaphore is released in any of the following cases:
+        /// <list type="bullet">
+        ///   <item>
+        ///     <description>A <see cref="ChannelOpenFailureMessage"/> is received for the channel being opened.</description>
+        ///   </item>
+        ///   <item>
+        ///     <description>The remote host does not respond to the <see cref="ChannelOpenMessage"/> within the configured <see cref="ConnectionInfo.Timeout"/>.</description>
+        ///   </item>
+        ///   <item>
+        ///     <description>The remote host closes the channel.</description>
+        ///   </item>
+        ///   <item>
+        ///     <description>The <see cref="ChannelSession"/> is disposed.</description>
+        ///   </item>
+        ///   <item>
+        ///     <description>A socket error occurs sending a message to the remote host.</description>
+        ///   </item>
+        /// </list>
+        /// </para>
+        /// <para>
+        /// If the session semaphore was already obtained for this instance (and not released), then this method
+        /// immediately returns control to the caller. This should only happen when another thread has obtain the
+        /// session semaphore and already sent the <see cref="ChannelOpenMessage"/>, but the remote host did not
+        /// confirmed or rejected attempt to open the channel.
+        /// </para>
+        /// </remarks>
+        private void SendChannelOpenMessage()
         {
             // do not allow open to be ChannelOpenMessage to be sent again until we've
             // had a response on the previous attempt for the current channel
             if (Interlocked.CompareExchange(ref _sessionSemaphoreObtained, 1, 0) == 0)
             {
                 SessionSemaphore.Wait();
-                SendMessage(
-                    new ChannelOpenMessage(
-                        LocalChannelNumber,
-                        LocalWindowSize,
-                        LocalPacketSize,
-                        new SessionChannelOpenInfo()));
+                SendMessage(new ChannelOpenMessage(LocalChannelNumber,
+                                                   LocalWindowSize,
+                                                   LocalPacketSize,
+                                                   new SessionChannelOpenInfo()));
             }
         }
 

+ 3 - 0
src/Renci.SshNet/Channels/ClientChannel.cs

@@ -52,6 +52,9 @@ namespace Renci.SshNet.Channels
         /// Send message to open a channel.
         /// </summary>
         /// <param name="message">Message to send</param>
+        /// <exception cref="SshConnectionException">The client is not connected.</exception>
+        /// <exception cref="SshOperationTimeoutException">The operation timed out.</exception>
+        /// <exception cref="InvalidOperationException">The size of the packet exceeds the maximum size defined by the protocol.</exception>
         protected void SendMessage(ChannelOpenMessage message)
         {
             Session.SendMessage(message);

+ 175 - 76
src/Renci.SshNet/ClientAuthentication.cs

@@ -6,6 +6,40 @@ namespace Renci.SshNet
 {
     internal class ClientAuthentication : IClientAuthentication
     {
+        private readonly int _partialSuccessLimit;
+
+        /// <summary>
+        /// Initializes a new <see cref="ClientAuthentication"/> instance.
+        /// </summary>
+        /// <param name="partialSuccessLimit">The number of times an authentication attempt with any given <see cref="IAuthenticationMethod"/> can result in <see cref="AuthenticationResult.PartialSuccess"/> before it is disregarded.</param>
+        /// <exception cref="ArgumentOutOfRangeException"><paramref name="partialSuccessLimit"/> is less than one.</exception>
+        public ClientAuthentication(int partialSuccessLimit)
+        {
+            if (partialSuccessLimit < 1)
+                throw new ArgumentOutOfRangeException("partialSuccessLimit", "Cannot be less than one.");
+
+            _partialSuccessLimit = partialSuccessLimit;
+        }
+
+        /// <summary>
+        /// Gets the number of times an authentication attempt with any given <see cref="IAuthenticationMethod"/> can
+        /// result in <see cref="AuthenticationResult.PartialSuccess"/> before it is disregarded.
+        /// </summary>
+        /// <value>
+        /// The number of times an authentication attempt with any given <see cref="IAuthenticationMethod"/> can result
+        /// in <see cref="AuthenticationResult.PartialSuccess"/> before it is disregarded.
+        /// </value>
+        internal int PartialSuccessLimit
+        {
+            get { return _partialSuccessLimit; }
+        }
+
+        /// <summary>
+        /// Attempts to authentication for a given <see cref="ISession"/> using the <see cref="IConnectionInfoInternal.AuthenticationMethods"/>
+        /// of the specified <see cref="IConnectionInfoInternal"/>.
+        /// </summary>
+        /// <param name="connectionInfo">A <see cref="IConnectionInfoInternal"/> to use for authenticating.</param>
+        /// <param name="session">The <see cref="ISession"/> for which to perform authentication.</param>
         public void Authenticate(IConnectionInfoInternal connectionInfo, ISession session)
         {
             if (connectionInfo == null)
@@ -44,10 +78,10 @@ namespace Renci.SshNet
             }
         }
 
-        private static bool TryAuthenticate(ISession session,
-                                            AuthenticationState authenticationState,
-                                            string[] allowedAuthenticationMethods,
-                                            ref SshAuthenticationException authenticationException)
+        private bool TryAuthenticate(ISession session,
+                                     AuthenticationState authenticationState,
+                                     string[] allowedAuthenticationMethods,
+                                     ref SshAuthenticationException authenticationException)
         {
             if (allowedAuthenticationMethods.Length == 0)
             {
@@ -58,40 +92,39 @@ namespace Renci.SshNet
             // we want to try authentication methods in the order in which they were
             // passed in the ctor, not the order in which the SSH server returns
             // the allowed authentication methods
-            var matchingAuthenticationMethods = GetAllowedAuthenticationMethodsThatAreSupported(authenticationState, allowedAuthenticationMethods);
+            var matchingAuthenticationMethods = authenticationState.GetSupportedAuthenticationMethods(allowedAuthenticationMethods);
             if (matchingAuthenticationMethods.Count == 0)
             {
-                authenticationException = new SshAuthenticationException(string.Format("No suitable authentication method found to complete authentication ({0}).", string.Join(",", allowedAuthenticationMethods)));
+                authenticationException = new SshAuthenticationException(string.Format("No suitable authentication method found to complete authentication ({0}).",
+                                                                                       string.Join(",", allowedAuthenticationMethods)));
                 return false;
             }
 
-            foreach (var authenticationMethod in GetOrderedAuthenticationMethods(authenticationState, matchingAuthenticationMethods))
+            foreach (var authenticationMethod in authenticationState.GetActiveAuthenticationMethods(matchingAuthenticationMethods))
             {
-                if (authenticationState.FailedAuthenticationMethods.Contains(authenticationMethod))
-                    continue;
-
-                // when the authentication method was previously executed, then skip the authentication
-                // method as long as there's another authentication method to try; this is done to avoid
-                // a stack overflow for servers that do not update the list of allowed authentication
+                // guard against a stack overlow for servers that do not update the list of allowed authentication
                 // methods after a partial success
-
-                if (!authenticationState.ExecutedAuthenticationMethods.Contains(authenticationMethod))
+                if (authenticationState.GetPartialSuccessCount(authenticationMethod) >= _partialSuccessLimit)
                 {
-                    // update state to reflect previosuly executed authentication methods
-                    authenticationState.ExecutedAuthenticationMethods.Add(authenticationMethod);
+                    // TODO Get list of all authentication methods that have reached the partial success limit?
+
+                    authenticationException = new SshAuthenticationException(string.Format("Reached authentication attempt limit for method ({0}).",
+                                                                                           authenticationMethod.Name));
+                    continue;
                 }
 
                 var authenticationResult = authenticationMethod.Authenticate(session);
                 switch (authenticationResult)
                 {
                     case AuthenticationResult.PartialSuccess:
+                        authenticationState.RecordPartialSuccess(authenticationMethod);
                         if (TryAuthenticate(session, authenticationState, authenticationMethod.AllowedAuthentications, ref authenticationException))
                         {
                             authenticationResult = AuthenticationResult.Success;
                         }
                         break;
                     case AuthenticationResult.Failure:
-                        authenticationState.FailedAuthenticationMethods.Add(authenticationMethod);
+                        authenticationState.RecordFailure(authenticationMethod);
                         authenticationException = new SshAuthenticationException(string.Format("Permission denied ({0}).", authenticationMethod.Name));
                         break;
                     case AuthenticationResult.Success:
@@ -106,85 +139,151 @@ namespace Renci.SshNet
             return false;
         }
 
-        private static List<IAuthenticationMethod> GetAllowedAuthenticationMethodsThatAreSupported(AuthenticationState authenticationState,
-                                                                                                   string[] allowedAuthenticationMethods)
+        private class AuthenticationState
         {
-            var result = new List<IAuthenticationMethod>();
-
-            foreach (var supportedAuthenticationMethod in authenticationState.SupportedAuthenticationMethods)
-            {
-                var nameOfSupportedAuthenticationMethod = supportedAuthenticationMethod.Name;
+            private readonly IList<IAuthenticationMethod> _supportedAuthenticationMethods;
 
-                for (var i = 0; i < allowedAuthenticationMethods.Length; i++)
-                {
-                    if (allowedAuthenticationMethods[i] == nameOfSupportedAuthenticationMethod)
-                    {
-                        result.Add(supportedAuthenticationMethod);
-                        break;
-                    }
-                }
-            }
+            /// <summary>
+            /// Records if a given <see cref="IAuthenticationMethod"/> has been tried, and how many times this resulted
+            /// in <see cref="AuthenticationResult.PartialSuccess"/>.
+            /// </summary>
+            /// <remarks>
+            /// When there's no entry for a given <see cref="IAuthenticationMethod"/>, then it was never tried.
+            /// </remarks>
+            private readonly Dictionary<IAuthenticationMethod, int> _authenticationMethodPartialSuccessRegister;
 
-            return result;
-        }
+            /// <summary>
+            /// Holds the list of authentications methods that failed.
+            /// </summary>
+            private readonly List<IAuthenticationMethod> _failedAuthenticationMethods;
 
-        private static IEnumerable<IAuthenticationMethod> GetOrderedAuthenticationMethods(AuthenticationState authenticationState, List<IAuthenticationMethod> matchingAuthenticationMethods)
-        {
-            var skippedAuthenticationMethods = new List<IAuthenticationMethod>();
+            public AuthenticationState(IList<IAuthenticationMethod> supportedAuthenticationMethods)
+            {
+                _supportedAuthenticationMethods = supportedAuthenticationMethods;
+                _failedAuthenticationMethods = new List<IAuthenticationMethod>();
+                _authenticationMethodPartialSuccessRegister = new Dictionary<IAuthenticationMethod, int>();
+            }
 
-            for (var i = 0; i < matchingAuthenticationMethods.Count; i++)
+            /// <summary>
+            /// Records a <see cref="AuthenticationResult.Failure"/> authentication attempt for the specified
+            /// <see cref="IAuthenticationMethod"/> .
+            /// </summary>
+            /// <param name="authenticationMethod">An <see cref="IAuthenticationMethod"/> for which to record the result of an authentication attempt.</param>
+            public void RecordFailure(IAuthenticationMethod authenticationMethod)
             {
-                var authenticationMethod = matchingAuthenticationMethods[i];
+                _failedAuthenticationMethods.Add(authenticationMethod);
+            }
 
-                if (authenticationState.ExecutedAuthenticationMethods.Contains(authenticationMethod))
+            /// <summary>
+            /// Records a <see cref="AuthenticationResult.PartialSuccess"/> authentication attempt for the specified
+            /// <see cref="IAuthenticationMethod"/> .
+            /// </summary>
+            /// <param name="authenticationMethod">An <see cref="IAuthenticationMethod"/> for which to record the result of an authentication attempt.</param>
+            public void RecordPartialSuccess(IAuthenticationMethod authenticationMethod)
+            {
+                int partialSuccessCount;
+                if (_authenticationMethodPartialSuccessRegister.TryGetValue(authenticationMethod, out partialSuccessCount))
                 {
-                    skippedAuthenticationMethods.Add(authenticationMethod);
-                    continue;
+                    _authenticationMethodPartialSuccessRegister[authenticationMethod] = ++partialSuccessCount;
+                }
+                else
+                {
+                    _authenticationMethodPartialSuccessRegister.Add(authenticationMethod, 1);
                 }
-
-                yield return authenticationMethod;
             }
 
-            foreach (var authenticationMethod in skippedAuthenticationMethods)
-                yield return authenticationMethod;
-        }
-
-        private class AuthenticationState
-        {
-            private readonly IList<IAuthenticationMethod> _supportedAuthenticationMethods;
-
-            public AuthenticationState(IList<IAuthenticationMethod> supportedAuthenticationMethods)
+            /// <summary>
+            /// Returns the number of times an authentication attempt with the specified <see cref="IAuthenticationMethod"/>
+            /// has resulted in <see cref="AuthenticationResult.PartialSuccess"/>.
+            /// </summary>
+            /// <param name="authenticationMethod">An <see cref="IAuthenticationMethod"/>.</param>
+            /// <returns>
+            /// The number of times an authentication attempt with the specified <see cref="IAuthenticationMethod"/>
+            /// has resulted in <see cref="AuthenticationResult.PartialSuccess"/>.
+            /// </returns>
+            public int GetPartialSuccessCount(IAuthenticationMethod authenticationMethod)
             {
-                _supportedAuthenticationMethods = supportedAuthenticationMethods;
-                ExecutedAuthenticationMethods = new List<IAuthenticationMethod>();
-                FailedAuthenticationMethods = new List<IAuthenticationMethod>();
+                int partialSuccessCount;
+                if (_authenticationMethodPartialSuccessRegister.TryGetValue(authenticationMethod, out partialSuccessCount))
+                {
+                    return partialSuccessCount;
+                }
+                return 0;
             }
 
             /// <summary>
-            /// Gets the list of authentication methods that were previously executed.
+            /// Returns a list of supported authentication methods that match one of the specified allowed authentication
+            /// methods.
             /// </summary>
-            /// <value>
-            /// The list of authentication methods that were previously executed.
-            /// </value>
-            public IList<IAuthenticationMethod> ExecutedAuthenticationMethods { get; private set; }
+            /// <param name="allowedAuthenticationMethods">A list of allowed authentication methods.</param>
+            /// <returns>
+            /// A list of supported authentication methods that match one of the specified allowed authentication methods.
+            /// </returns>
+            /// <remarks>
+            /// The authentication methods are returned in the order in which they were specified in the list that was
+            /// used to initialize the current <see cref="AuthenticationState"/> instance.
+            /// </remarks>
+            public List<IAuthenticationMethod> GetSupportedAuthenticationMethods(string[] allowedAuthenticationMethods)
+            {
+                var result = new List<IAuthenticationMethod>();
 
-            /// <summary>
-            /// Gets the list of authentications methods that failed.
-            /// </summary>
-            /// <value>
-            /// The list of authentications methods that failed.
-            /// </value>
-            public IList<IAuthenticationMethod> FailedAuthenticationMethods { get; private set; }
+                foreach (var supportedAuthenticationMethod in _supportedAuthenticationMethods)
+                {
+                    var nameOfSupportedAuthenticationMethod = supportedAuthenticationMethod.Name;
+
+                    for (var i = 0; i < allowedAuthenticationMethods.Length; i++)
+                    {
+                        if (allowedAuthenticationMethods[i] == nameOfSupportedAuthenticationMethod)
+                        {
+                            result.Add(supportedAuthenticationMethod);
+                            break;
+                        }
+                    }
+                }
+
+                return result;
+            }
 
             /// <summary>
-            /// Gets the list of supported authentication methods.
+            /// Returns the authentication methods from the specified list that have not yet failed.
             /// </summary>
-            /// <value>
-            /// The list of supported authentication methods.
-            /// </value>
-            public IList<IAuthenticationMethod> SupportedAuthenticationMethods
+            /// <param name="matchingAuthenticationMethods">A list of authentication methods.</param>
+            /// <returns>
+            /// The authentication methods from <paramref name="matchingAuthenticationMethods"/> that have not yet failed.
+            /// </returns>
+            /// <remarks>
+            /// <para>
+            /// This method first returns the authentication methods that have not yet been executed, and only then
+            /// returns those for which an authentication attempt resulted in a <see cref="AuthenticationResult.PartialSuccess"/>.
+            /// </para>
+            /// <para>
+            /// Any <see cref="IAuthenticationMethod"/> that has failed is skipped.
+            /// </para>
+            /// </remarks>
+            public IEnumerable<IAuthenticationMethod> GetActiveAuthenticationMethods(List<IAuthenticationMethod> matchingAuthenticationMethods)
             {
-                get { return _supportedAuthenticationMethods; }
+                var skippedAuthenticationMethods = new List<IAuthenticationMethod>();
+
+                for (var i = 0; i < matchingAuthenticationMethods.Count; i++)
+                {
+                    var authenticationMethod = matchingAuthenticationMethods[i];
+
+                    // skip authentication methods that have already failed
+                    if (_failedAuthenticationMethods.Contains(authenticationMethod))
+                        continue;
+
+                    // delay use of authentication methods that had a PartialSuccess result
+                    if (_authenticationMethodPartialSuccessRegister.ContainsKey(authenticationMethod))
+                    {
+                        skippedAuthenticationMethods.Add(authenticationMethod);
+                        continue;
+                    }
+
+                    yield return authenticationMethod;
+                }
+
+                foreach (var authenticationMethod in skippedAuthenticationMethods)
+                    yield return authenticationMethod;
             }
         }
     }

+ 1 - 1
src/Renci.SshNet/Properties/CommonAssemblyInfo.cs

@@ -11,7 +11,7 @@ using System.Runtime.InteropServices;
 
 [assembly: AssemblyVersion("2016.1.0")]
 [assembly: AssemblyFileVersion("2016.1.0")]
-[assembly: AssemblyInformationalVersion("2016.1.0-beta3")]
+[assembly: AssemblyInformationalVersion("2016.1.0-beta4")]
 [assembly: CLSCompliant(false)]
 
 // Setting ComVisible to false makes the types in this assembly not visible 

+ 1 - 0
src/Renci.SshNet/Renci.SshNet.csproj

@@ -47,6 +47,7 @@
   <ItemGroup>
     <Reference Include="System" />
     <Reference Include="System.Core" />
+    <Reference Include="System.Numerics" />
     <Reference Include="System.Xml" />
   </ItemGroup>
   <ItemGroup>

+ 18 - 8
src/Renci.SshNet/Security/KeyExchangeDiffieHellman.cs

@@ -8,7 +8,7 @@ namespace Renci.SshNet.Security
     /// <summary>
     /// Represents base class for Diffie Hellman key exchange algorithm
     /// </summary>
-    public abstract class KeyExchangeDiffieHellman : KeyExchange
+    internal abstract class KeyExchangeDiffieHellman : KeyExchange
     {
         /// <summary>
         /// Specifies key exchange group number.
@@ -43,7 +43,7 @@ namespace Renci.SshNet.Security
         /// <summary>
         /// Specifies random generated number.
         /// </summary>
-        protected BigInteger _randomValue;
+        protected BigInteger _privateExponent;
 
         /// <summary>
         /// Specifies host key data.
@@ -55,6 +55,14 @@ namespace Renci.SshNet.Security
         /// </summary>
         protected byte[] _signature;
 
+        /// <summary>
+        /// Gets the size, in bits, of the computed hash code.
+        /// </summary>
+        /// <value>
+        /// The size, in bits, of the computed hash code.
+        /// </value>
+        protected abstract int HashSize { get; }
+
         /// <summary>
         /// Validates the exchange hash.
         /// </summary>
@@ -102,14 +110,16 @@ namespace Renci.SshNet.Security
             if (_prime.IsZero)
                 throw new ArgumentNullException("_prime");
 
-            var bitLength = _prime.BitLength;
+            // generate private exponent that is twice the hash size (RFC 4419) with a minimum
+            // of 1024 bits (whatever is less)
+            var privateExponentSize = Math.Max(HashSize * 2, 1024);
 
             do
             {
-                _randomValue = BigInteger.Random(bitLength);
-
-                _clientExchangeValue = BigInteger.ModPow(_group, _randomValue, _prime);
-
+                // create private component
+                _privateExponent = BigInteger.Random(privateExponentSize);
+                // generate public component
+                _clientExchangeValue = BigInteger.ModPow(_group, _privateExponent, _prime);
             } while (_clientExchangeValue < 1 || _clientExchangeValue > (_prime - 1));
         }
 
@@ -123,7 +133,7 @@ namespace Renci.SshNet.Security
         {
             _serverExchangeValue = serverExchangeValue;
             _hostKey = hostKey;
-            SharedKey = BigInteger.ModPow(serverExchangeValue, _randomValue, _prime);
+            SharedKey = BigInteger.ModPow(serverExchangeValue, _privateExponent, _prime);
             _signature = signature;
         }
     }

+ 31 - 6
src/Renci.SshNet/Security/KeyExchangeDiffieHellmanGroup14Sha1.cs

@@ -1,5 +1,4 @@
 using Renci.SshNet.Common;
-using System.Globalization;
 
 namespace Renci.SshNet.Security
 {
@@ -8,7 +7,35 @@ namespace Renci.SshNet.Security
     /// </summary>
     internal class KeyExchangeDiffieHellmanGroup14Sha1 : KeyExchangeDiffieHellmanGroupSha1
     {
-        private const string SecondOkleyGroup = "00FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF";
+        /// <summary>
+        /// https://tools.ietf.org/html/rfc2409#section-6.2
+        /// </summary>
+        private static readonly byte[] SecondOkleyGroupReversed =
+            {
+                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x68, 0xaa, 0xac, 0x8a,
+                0x5a, 0x8e, 0x72, 0x15, 0x10, 0x05, 0xfa, 0x98, 0x18, 0x26, 0xd2, 0x15,
+                0xe5, 0x6a, 0x95, 0xea, 0x7c, 0x49, 0x95, 0x39, 0x18, 0x17, 0x58, 0x95,
+                0xf6, 0xcb, 0x2b, 0xde, 0xc9, 0x52, 0x4c, 0x6f, 0xf0, 0x5d, 0xc5, 0xb5,
+                0x8f, 0xa2, 0x07, 0xec, 0xa2, 0x83, 0x27, 0x9b, 0x03, 0x86, 0x0e, 0x18,
+                0x2c, 0x77, 0x9e, 0xe3, 0x3b, 0xce, 0x36, 0x2e, 0x46, 0x5e, 0x90, 0x32,
+                0x7c, 0x21, 0x18, 0xca, 0x08, 0x6c, 0x74, 0xf1, 0x04, 0x98, 0xbc, 0x4a,
+                0x4e, 0x35, 0x0c, 0x67, 0x6d, 0x96, 0x96, 0x70, 0x07, 0x29, 0xd5, 0x9e,
+                0xbb, 0x52, 0x85, 0x20, 0x56, 0xf3, 0x62, 0x1c, 0x96, 0xad, 0xa3, 0xdc,
+                0x23, 0x5d, 0x65, 0x83, 0x5f, 0xcf, 0x24, 0xfd, 0xa8, 0x3f, 0x16, 0x69,
+                0x9a, 0xd3, 0x55, 0x1c, 0x36, 0x48, 0xda, 0x98, 0x05, 0xbf, 0x63, 0xa1,
+                0xb8, 0x7c, 0x00, 0xc2, 0x3d, 0x5b, 0xe4, 0xec, 0x51, 0x66, 0x28, 0x49,
+                0xe6, 0x1f, 0x4b, 0x7c, 0x11, 0x24, 0x9f, 0xae, 0xa5, 0x9f, 0x89, 0x5a,
+                0xfb, 0x6b, 0x38, 0xee, 0xed, 0xb7, 0x06, 0xf4, 0xb6, 0x5c, 0xff, 0x0b,
+                0x6b, 0xed, 0x37, 0xa6, 0xe9, 0x42, 0x4c, 0xf4, 0xc6, 0x7e, 0x5e, 0x62,
+                0x76, 0xb5, 0x85, 0xe4, 0x45, 0xc2, 0x51, 0x6d, 0x6d, 0x35, 0xe1, 0x4f,
+                0x37, 0x14, 0x5f, 0xf2, 0x6d, 0x0a, 0x2b, 0x30, 0x1b, 0x43, 0x3a, 0xcd,
+                0xb3, 0x19, 0x95, 0xef, 0xdd, 0x04, 0x34, 0x8e, 0x79, 0x08, 0x4a, 0x51,
+                0x22, 0x9b, 0x13, 0x3b, 0xa6, 0xbe, 0x0b, 0x02, 0x74, 0xcc, 0x67, 0x8a,
+                0x08, 0x4e, 0x02, 0x29, 0xd1, 0x1c, 0xdc, 0x80, 0x8b, 0x62, 0xc6, 0xc4,
+                0x34, 0xc2, 0x68, 0x21, 0xa2, 0xda, 0x0f, 0xc9, 0xff, 0xff, 0xff, 0xff,
+                0xff, 0xff, 0xff, 0xff,
+                0x00
+            };
 
         /// <summary>
         /// Gets algorithm name.
@@ -28,10 +55,8 @@ namespace Renci.SshNet.Security
         {
             get
             {
-                BigInteger prime;
-                BigInteger.TryParse(SecondOkleyGroup, NumberStyles.AllowHexSpecifier, NumberFormatInfo.CurrentInfo, out prime);
-                return prime;
+                return new BigInteger(SecondOkleyGroupReversed);
             }
         }
     }
-}
+}

+ 17 - 6
src/Renci.SshNet/Security/KeyExchangeDiffieHellmanGroup1Sha1.cs

@@ -1,14 +1,27 @@
 using Renci.SshNet.Common;
-using System.Globalization;
 
 namespace Renci.SshNet.Security
 {
     /// <summary>
     /// Represents "diffie-hellman-group1-sha1" algorithm implementation.
     /// </summary>
-    public class KeyExchangeDiffieHellmanGroup1Sha1 : KeyExchangeDiffieHellmanGroupSha1
+    internal class KeyExchangeDiffieHellmanGroup1Sha1 : KeyExchangeDiffieHellmanGroupSha1
     {
-        private const string SecondOkleyGroup = @"00FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF";
+        private static readonly byte[] SecondOkleyGroupReversed =
+            {
+                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x81, 0x53, 0xe6, 0xec,
+                0x51, 0x66, 0x28, 0x49, 0xe6, 0x1f, 0x4b, 0x7c, 0x11, 0x24, 0x9f, 0xae,
+                0xa5, 0x9f, 0x89, 0x5a, 0xfb, 0x6b, 0x38, 0xee, 0xed, 0xb7, 0x06, 0xf4,
+                0xb6, 0x5c, 0xff, 0x0b, 0x6b, 0xed, 0x37, 0xa6, 0xe9, 0x42, 0x4c, 0xf4,
+                0xc6, 0x7e, 0x5e, 0x62, 0x76, 0xb5, 0x85, 0xe4, 0x45, 0xc2, 0x51, 0x6d,
+                0x6d, 0x35, 0xe1, 0x4f, 0x37, 0x14, 0x5f, 0xf2, 0x6d, 0x0a, 0x2b, 0x30,
+                0x1b, 0x43, 0x3a, 0xcd, 0xb3, 0x19, 0x95, 0xef, 0xdd, 0x04, 0x34, 0x8e,
+                0x79, 0x08, 0x4a, 0x51, 0x22, 0x9b, 0x13, 0x3b, 0xa6, 0xbe, 0x0b, 0x02,
+                0x74, 0xcc, 0x67, 0x8a, 0x08, 0x4e, 0x02, 0x29, 0xd1, 0x1c, 0xdc, 0x80,
+                0x8b, 0x62, 0xc6, 0xc4, 0x34, 0xc2, 0x68, 0x21, 0xa2, 0xda, 0x0f, 0xc9,
+                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+                0x00
+            };
 
         /// <summary>
         /// Gets algorithm name.
@@ -28,9 +41,7 @@ namespace Renci.SshNet.Security
         {
             get
             {
-                BigInteger prime;
-                BigInteger.TryParse(SecondOkleyGroup, NumberStyles.AllowHexSpecifier, NumberFormatInfo.CurrentInfo, out prime);
-                return prime;
+                return new BigInteger(SecondOkleyGroupReversed);
             }
         }
     }

+ 12 - 1
src/Renci.SshNet/Security/KeyExchangeDiffieHellmanGroupExchangeSha1.cs

@@ -3,7 +3,7 @@
     /// <summary>
     /// Represents "diffie-hellman-group-exchange-sha1" algorithm implementation.
     /// </summary>
-    public class KeyExchangeDiffieHellmanGroupExchangeSha1 : KeyExchangeDiffieHellmanGroupExchangeShaBase
+    internal class KeyExchangeDiffieHellmanGroupExchangeSha1 : KeyExchangeDiffieHellmanGroupExchangeShaBase
     {
         /// <summary>
         /// Gets algorithm name.
@@ -12,5 +12,16 @@
         {
             get { return "diffie-hellman-group-exchange-sha1"; }
         }
+
+        /// <summary>
+        /// Gets the size, in bits, of the computed hash code.
+        /// </summary>
+        /// <value>
+        /// The size, in bits, of the computed hash code.
+        /// </value>
+        protected override int HashSize
+        {
+            get { return 160; }
+        }
     }
 }

+ 12 - 1
src/Renci.SshNet/Security/KeyExchangeDiffieHellmanGroupExchangeSha256.cs

@@ -5,7 +5,7 @@ namespace Renci.SshNet.Security
     /// <summary>
     /// Represents "diffie-hellman-group-exchange-sha256" algorithm implementation.
     /// </summary>
-    public class KeyExchangeDiffieHellmanGroupExchangeSha256 : KeyExchangeDiffieHellmanGroupExchangeShaBase
+    internal class KeyExchangeDiffieHellmanGroupExchangeSha256 : KeyExchangeDiffieHellmanGroupExchangeShaBase
     {
         /// <summary>
         /// Gets algorithm name.
@@ -15,6 +15,17 @@ namespace Renci.SshNet.Security
             get { return "diffie-hellman-group-exchange-sha256"; }
         }
 
+        /// <summary>
+        /// Gets the size, in bits, of the computed hash code.
+        /// </summary>
+        /// <value>
+        /// The size, in bits, of the computed hash code.
+        /// </value>
+        protected override int HashSize
+        {
+            get { return 256; }
+        }
+
         /// <summary>
         /// Hashes the specified data bytes.
         /// </summary>

+ 2 - 3
src/Renci.SshNet/Security/KeyExchangeDiffieHellmanGroupExchangeShaBase.cs

@@ -1,12 +1,11 @@
-using Renci.SshNet.Messages;
-using Renci.SshNet.Messages.Transport;
+using Renci.SshNet.Messages.Transport;
 
 namespace Renci.SshNet.Security
 {
     /// <summary>
     /// Base class for "diffie-hellman-group-exchange" algorithms.
     /// </summary>
-    public abstract class KeyExchangeDiffieHellmanGroupExchangeShaBase : KeyExchangeDiffieHellman
+    internal abstract class KeyExchangeDiffieHellmanGroupExchangeShaBase : KeyExchangeDiffieHellman
     {
         private const int MinimumGroupSize = 1024;
         private const int PreferredGroupSize = 1024;

+ 12 - 2
src/Renci.SshNet/Security/KeyExchangeDiffieHellmanGroupSha1.cs

@@ -1,6 +1,5 @@
 using System;
 using Renci.SshNet.Common;
-using Renci.SshNet.Messages;
 using Renci.SshNet.Messages.Transport;
 
 namespace Renci.SshNet.Security
@@ -8,7 +7,7 @@ namespace Renci.SshNet.Security
     /// <summary>
     /// Represents "diffie-hellman-group1-sha1" algorithm implementation.
     /// </summary>
-    public abstract class KeyExchangeDiffieHellmanGroupSha1 : KeyExchangeDiffieHellman
+    internal abstract class KeyExchangeDiffieHellmanGroupSha1 : KeyExchangeDiffieHellman
     {
         /// <summary>
         /// Gets the group prime.
@@ -18,6 +17,17 @@ namespace Renci.SshNet.Security
         /// </value>
         public abstract BigInteger GroupPrime { get; }
 
+        /// <summary>
+        /// Gets the size, in bits, of the computed hash code.
+        /// </summary>
+        /// <value>
+        /// The size, in bits, of the computed hash code.
+        /// </value>
+        protected override int HashSize
+        {
+            get { return 160; }
+        }
+
         /// <summary>
         /// Calculates key exchange hash value.
         /// </summary>

+ 7 - 1
src/Renci.SshNet/ServiceFactory.cs

@@ -15,6 +15,12 @@ namespace Renci.SshNet
     /// </summary>
     internal partial class ServiceFactory : IServiceFactory
     {
+        /// <summary>
+        /// Defines the number of times an authentication attempt with any given <see cref="IAuthenticationMethod"/>
+        /// can result in <see cref="AuthenticationResult.PartialSuccess"/> before it is disregarded.
+        /// </summary>
+        private static int PartialSuccessLimit = 5;
+
         /// <summary>
         /// Creates a <see cref="IClientAuthentication"/>.
         /// </summary>
@@ -23,7 +29,7 @@ namespace Renci.SshNet
         /// </returns>
         public IClientAuthentication CreateClientAuthentication()
         {
-            return new ClientAuthentication();
+            return new ClientAuthentication(PartialSuccessLimit);
         }
 
         /// <summary>