ClientAuthentication.cs 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using Renci.SshNet.Common;
  5. namespace Renci.SshNet
  6. {
  7. internal class ClientAuthentication
  8. {
  9. public void Authenticate(IConnectionInfo connectionInfo, ISession session)
  10. {
  11. if (connectionInfo == null)
  12. throw new ArgumentNullException("connectionInfo");
  13. if (session == null)
  14. throw new ArgumentNullException("session");
  15. session.RegisterMessage("SSH_MSG_USERAUTH_FAILURE");
  16. session.RegisterMessage("SSH_MSG_USERAUTH_SUCCESS");
  17. session.RegisterMessage("SSH_MSG_USERAUTH_BANNER");
  18. session.UserAuthenticationBannerReceived += connectionInfo.UserAuthenticationBannerReceived;
  19. try
  20. {
  21. // the exception to report an authentication failure with
  22. SshAuthenticationException authenticationException = null;
  23. // try to authenticate against none
  24. var noneAuthenticationMethod = connectionInfo.CreateNoneAuthenticationMethod();
  25. var authenticated = noneAuthenticationMethod.Authenticate(session);
  26. if (authenticated != AuthenticationResult.Success)
  27. {
  28. if (!TryAuthenticate(session, new AuthenticationState(connectionInfo.AuthenticationMethods.ToList()), noneAuthenticationMethod.AllowedAuthentications.ToList(), ref authenticationException))
  29. {
  30. throw authenticationException;
  31. }
  32. }
  33. }
  34. finally
  35. {
  36. session.UserAuthenticationBannerReceived -= connectionInfo.UserAuthenticationBannerReceived;
  37. session.UnRegisterMessage("SSH_MSG_USERAUTH_FAILURE");
  38. session.UnRegisterMessage("SSH_MSG_USERAUTH_SUCCESS");
  39. session.UnRegisterMessage("SSH_MSG_USERAUTH_BANNER");
  40. }
  41. }
  42. private bool TryAuthenticate(ISession session,
  43. AuthenticationState authenticationState,
  44. ICollection<string> allowedAuthenticationMethods,
  45. ref SshAuthenticationException authenticationException)
  46. {
  47. if (!allowedAuthenticationMethods.Any())
  48. {
  49. authenticationException = new SshAuthenticationException("No authentication methods defined on SSH server.");
  50. return false;
  51. }
  52. // we want to try authentication methods in the order in which they were
  53. // passed in the ctor, not the order in which the SSH server returns
  54. // the allowed authentication methods
  55. var matchingAuthenticationMethods = authenticationState.SupportedAuthenticationMethods.Where(a => allowedAuthenticationMethods.Contains(a.Name)).ToList();
  56. if (!matchingAuthenticationMethods.Any())
  57. {
  58. authenticationException = new SshAuthenticationException(string.Format("No suitable authentication method found to complete authentication ({0}).", string.Join(",", allowedAuthenticationMethods.ToArray())));
  59. return false;
  60. }
  61. for (var i = 0; i < matchingAuthenticationMethods.Count; i++)
  62. {
  63. var authenticationMethod = matchingAuthenticationMethods[i];
  64. if (authenticationState.FailedAuthenticationMethods.Contains(authenticationMethod))
  65. continue;
  66. // when the authentication method was previously executed, then skip the authentication
  67. // method as long as there's another authentication method to try; this is done to avoid
  68. // a stack overflow for servers that do not update the list of allowed authentication
  69. // methods after a partial success
  70. if (authenticationState.ExecutedAuthenticationMethods.Contains(authenticationMethod))
  71. {
  72. var isLastAuthenticationMethod = i == (matchingAuthenticationMethods.Count - 1);
  73. if (!isLastAuthenticationMethod)
  74. continue;
  75. }
  76. else
  77. {
  78. // update state to reflect previosuly executed authentication methods
  79. authenticationState.ExecutedAuthenticationMethods.Add(authenticationMethod);
  80. }
  81. var authenticationResult = authenticationMethod.Authenticate(session);
  82. switch (authenticationResult)
  83. {
  84. case AuthenticationResult.PartialSuccess:
  85. if (TryAuthenticate(session, authenticationState, authenticationMethod.AllowedAuthentications.ToList(), ref authenticationException))
  86. {
  87. authenticationResult = AuthenticationResult.Success;
  88. }
  89. break;
  90. case AuthenticationResult.Failure:
  91. authenticationState.FailedAuthenticationMethods.Add(authenticationMethod);
  92. authenticationException = new SshAuthenticationException(string.Format("Permission denied ({0}).", authenticationMethod.Name));
  93. break;
  94. case AuthenticationResult.Success:
  95. authenticationException = null;
  96. break;
  97. }
  98. if (authenticationResult == AuthenticationResult.Success)
  99. return true;
  100. }
  101. return false;
  102. }
  103. private class AuthenticationState
  104. {
  105. private readonly IList<IAuthenticationMethod> _supportedAuthenticationMethods;
  106. public AuthenticationState(IList<IAuthenticationMethod> supportedAuthenticationMethods)
  107. {
  108. _supportedAuthenticationMethods = supportedAuthenticationMethods;
  109. ExecutedAuthenticationMethods = new List<IAuthenticationMethod>();
  110. FailedAuthenticationMethods = new List<IAuthenticationMethod>();
  111. }
  112. /// <summary>
  113. /// Gets the list of authentication methods that were previously executed.
  114. /// </summary>
  115. /// <value>
  116. /// The list of authentication methods that were previously executed.
  117. /// </value>
  118. public IList<IAuthenticationMethod> ExecutedAuthenticationMethods { get; private set; }
  119. /// <summary>
  120. /// Gets the list of authentications methods that failed.
  121. /// </summary>
  122. /// <value>
  123. /// The list of authentications methods that failed.
  124. /// </value>
  125. public IList<IAuthenticationMethod> FailedAuthenticationMethods { get; private set; }
  126. /// <summary>
  127. /// Gets the list of supported authentication methods.
  128. /// </summary>
  129. /// <value>
  130. /// The list of supported authentication methods.
  131. /// </value>
  132. public IEnumerable<IAuthenticationMethod> SupportedAuthenticationMethods
  133. {
  134. get { return _supportedAuthenticationMethods; }
  135. }
  136. }
  137. }
  138. }