PrivateKeyAuthenticationMethod.cs 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Collections.ObjectModel;
  4. using System.Linq;
  5. using System.Threading;
  6. using Renci.SshNet.Common;
  7. using Renci.SshNet.Messages;
  8. using Renci.SshNet.Messages.Authentication;
  9. namespace Renci.SshNet
  10. {
  11. /// <summary>
  12. /// Provides functionality to perform private key authentication.
  13. /// </summary>
  14. public class PrivateKeyAuthenticationMethod : AuthenticationMethod, IDisposable
  15. {
  16. private AuthenticationResult _authenticationResult = AuthenticationResult.Failure;
  17. private EventWaitHandle _authenticationCompleted = new ManualResetEvent(initialState: false);
  18. private bool _isSignatureRequired;
  19. private bool _isDisposed;
  20. /// <summary>
  21. /// Gets the name of the authentication method.
  22. /// </summary>
  23. public override string Name
  24. {
  25. get { return "publickey"; }
  26. }
  27. /// <summary>
  28. /// Gets the key files used for authentication.
  29. /// </summary>
  30. public ICollection<IPrivateKeySource> KeyFiles { get; private set; }
  31. /// <summary>
  32. /// Initializes a new instance of the <see cref="PrivateKeyAuthenticationMethod"/> class.
  33. /// </summary>
  34. /// <param name="username">The username.</param>
  35. /// <param name="keyFiles">The key files.</param>
  36. /// <exception cref="ArgumentException"><paramref name="username"/> is whitespace or <see langword="null"/>.</exception>
  37. public PrivateKeyAuthenticationMethod(string username, params IPrivateKeySource[] keyFiles)
  38. : base(username)
  39. {
  40. ThrowHelper.ThrowIfNull(keyFiles);
  41. KeyFiles = new Collection<IPrivateKeySource>(keyFiles);
  42. }
  43. /// <summary>
  44. /// Authenticates the specified session.
  45. /// </summary>
  46. /// <param name="session">The session to authenticate.</param>
  47. /// <returns>
  48. /// Result of authentication process.
  49. /// </returns>
  50. public override AuthenticationResult Authenticate(Session session)
  51. {
  52. session.UserAuthenticationSuccessReceived += Session_UserAuthenticationSuccessReceived;
  53. session.UserAuthenticationFailureReceived += Session_UserAuthenticationFailureReceived;
  54. session.UserAuthenticationPublicKeyReceived += Session_UserAuthenticationPublicKeyReceived;
  55. session.RegisterMessage("SSH_MSG_USERAUTH_PK_OK");
  56. var hostAlgorithms = KeyFiles.SelectMany(x => x.HostKeyAlgorithms).ToList();
  57. try
  58. {
  59. foreach (var hostAlgorithm in hostAlgorithms)
  60. {
  61. _ = _authenticationCompleted.Reset();
  62. _isSignatureRequired = false;
  63. var message = new RequestMessagePublicKey(ServiceName.Connection,
  64. Username,
  65. hostAlgorithm.Name,
  66. hostAlgorithm.Data);
  67. if (hostAlgorithms.Count == 1)
  68. {
  69. // If only one key file provided then send signature for very first request
  70. var signatureData = new SignatureData(message, session.SessionId).GetBytes();
  71. message.Signature = hostAlgorithm.Sign(signatureData);
  72. }
  73. // Send public key authentication request
  74. session.SendMessage(message);
  75. session.WaitOnHandle(_authenticationCompleted);
  76. if (_isSignatureRequired)
  77. {
  78. _ = _authenticationCompleted.Reset();
  79. var signatureMessage = new RequestMessagePublicKey(ServiceName.Connection,
  80. Username,
  81. hostAlgorithm.Name,
  82. hostAlgorithm.Data);
  83. var signatureData = new SignatureData(message, session.SessionId).GetBytes();
  84. signatureMessage.Signature = hostAlgorithm.Sign(signatureData);
  85. // Send public key authentication request with signature
  86. session.SendMessage(signatureMessage);
  87. }
  88. session.WaitOnHandle(_authenticationCompleted);
  89. if (_authenticationResult is AuthenticationResult.Success or AuthenticationResult.PartialSuccess)
  90. {
  91. break;
  92. }
  93. }
  94. return _authenticationResult;
  95. }
  96. finally
  97. {
  98. session.UserAuthenticationSuccessReceived -= Session_UserAuthenticationSuccessReceived;
  99. session.UserAuthenticationFailureReceived -= Session_UserAuthenticationFailureReceived;
  100. session.UserAuthenticationPublicKeyReceived -= Session_UserAuthenticationPublicKeyReceived;
  101. session.UnRegisterMessage("SSH_MSG_USERAUTH_PK_OK");
  102. }
  103. }
  104. private void Session_UserAuthenticationSuccessReceived(object sender, MessageEventArgs<SuccessMessage> e)
  105. {
  106. _authenticationResult = AuthenticationResult.Success;
  107. _ = _authenticationCompleted.Set();
  108. }
  109. private void Session_UserAuthenticationFailureReceived(object sender, MessageEventArgs<FailureMessage> e)
  110. {
  111. if (e.Message.PartialSuccess)
  112. {
  113. _authenticationResult = AuthenticationResult.PartialSuccess;
  114. }
  115. else
  116. {
  117. _authenticationResult = AuthenticationResult.Failure;
  118. }
  119. // Copy allowed authentication methods
  120. AllowedAuthentications = e.Message.AllowedAuthentications;
  121. _ = _authenticationCompleted.Set();
  122. }
  123. private void Session_UserAuthenticationPublicKeyReceived(object sender, MessageEventArgs<PublicKeyMessage> e)
  124. {
  125. _isSignatureRequired = true;
  126. _ = _authenticationCompleted.Set();
  127. }
  128. /// <summary>
  129. /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
  130. /// </summary>
  131. public void Dispose()
  132. {
  133. Dispose(disposing: true);
  134. GC.SuppressFinalize(this);
  135. }
  136. /// <summary>
  137. /// Releases unmanaged and - optionally - managed resources.
  138. /// </summary>
  139. /// <param name="disposing"><see langword="true"/> to release both managed and unmanaged resources; <see langword="false"/> to release only unmanaged resources.</param>
  140. protected virtual void Dispose(bool disposing)
  141. {
  142. if (_isDisposed)
  143. {
  144. return;
  145. }
  146. if (disposing)
  147. {
  148. var authenticationCompleted = _authenticationCompleted;
  149. if (authenticationCompleted != null)
  150. {
  151. _authenticationCompleted = null;
  152. authenticationCompleted.Dispose();
  153. }
  154. _isDisposed = true;
  155. }
  156. }
  157. private sealed class SignatureData : SshData
  158. {
  159. private readonly RequestMessagePublicKey _message;
  160. private readonly byte[] _sessionId;
  161. private readonly byte[] _serviceName;
  162. private readonly byte[] _authenticationMethod;
  163. protected override int BufferCapacity
  164. {
  165. get
  166. {
  167. var capacity = base.BufferCapacity;
  168. capacity += 4; // SessionId length
  169. capacity += _sessionId.Length; // SessionId
  170. capacity += 1; // Authentication Message Code
  171. capacity += 4; // UserName length
  172. capacity += _message.Username.Length; // UserName
  173. capacity += 4; // ServiceName length
  174. capacity += _serviceName.Length; // ServiceName
  175. capacity += 4; // AuthenticationMethod length
  176. capacity += _authenticationMethod.Length; // AuthenticationMethod
  177. capacity += 1; // TRUE
  178. capacity += 4; // PublicKeyAlgorithmName length
  179. capacity += _message.PublicKeyAlgorithmName.Length; // PublicKeyAlgorithmName
  180. capacity += 4; // PublicKeyData length
  181. capacity += _message.PublicKeyData.Length; // PublicKeyData
  182. return capacity;
  183. }
  184. }
  185. public SignatureData(RequestMessagePublicKey message, byte[] sessionId)
  186. {
  187. _message = message;
  188. _sessionId = sessionId;
  189. _serviceName = ServiceName.Connection.ToArray();
  190. _authenticationMethod = Ascii.GetBytes("publickey");
  191. }
  192. protected override void LoadData()
  193. {
  194. throw new NotImplementedException();
  195. }
  196. protected override void SaveData()
  197. {
  198. WriteBinaryString(_sessionId);
  199. Write((byte)RequestMessage.AuthenticationMessageCode);
  200. WriteBinaryString(_message.Username);
  201. WriteBinaryString(_serviceName);
  202. WriteBinaryString(_authenticationMethod);
  203. Write((byte)1); // TRUE
  204. WriteBinaryString(_message.PublicKeyAlgorithmName);
  205. WriteBinaryString(_message.PublicKeyData);
  206. }
  207. }
  208. }
  209. }