using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Collections.ObjectModel; using Renci.SshNet.Messages.Authentication; using Renci.SshNet.Messages; using Renci.SshNet.Common; using System.Threading; namespace Renci.SshNet { /// /// Provides functionality to perform private key authentication. /// public class PrivateKeyAuthenticationMethod : AuthenticationMethod, IDisposable { private AuthenticationResult _authenticationResult = AuthenticationResult.Failure; private EventWaitHandle _authenticationCompleted = new ManualResetEvent(false); private bool _isSignatureRequired; /// /// Gets authentication method name /// public override string Name { get { return "publickey"; } } /// /// Gets the key files used for authentication. /// public ICollection KeyFiles { get; private set; } /// /// Initializes a new instance of the class. /// /// The host. /// The port. /// The username. /// The key files. /// is whitespace or null. public PrivateKeyAuthenticationMethod(string username, params PrivateKeyFile[] keyFiles) : base(username) { // TODO: Should throw on keyFiles == null here? this.KeyFiles = new Collection(keyFiles); } /// /// Authenticates the specified session. /// /// The session to authenticate. /// public override AuthenticationResult Authenticate(Session session) { if (this.KeyFiles == null) return AuthenticationResult.Failure; session.UserAuthenticationSuccessReceived += Session_UserAuthenticationSuccessReceived; session.UserAuthenticationFailureReceived += Session_UserAuthenticationFailureReceived; session.MessageReceived += Session_MessageReceived; session.RegisterMessage("SSH_MSG_USERAUTH_PK_OK"); foreach (var keyFile in this.KeyFiles) { this._authenticationCompleted.Reset(); this._isSignatureRequired = false; var message = new RequestMessagePublicKey(ServiceName.Connection, this.Username, keyFile.HostKey.Name, keyFile.HostKey.Data); if (this.KeyFiles.Count < 2) { // If only one key file provided then send signature for very first request var signatureData = new SignatureData(message, session.SessionId).GetBytes(); message.Signature = keyFile.HostKey.Sign(signatureData); } // Send public key authentication request session.SendMessage(message); session.WaitHandle(this._authenticationCompleted); if (this._isSignatureRequired) { this._authenticationCompleted.Reset(); var signatureMessage = new RequestMessagePublicKey(ServiceName.Connection, this.Username, keyFile.HostKey.Name, keyFile.HostKey.Data); var signatureData = new SignatureData(message, session.SessionId).GetBytes(); signatureMessage.Signature = keyFile.HostKey.Sign(signatureData); // Send public key authentication request with signature session.SendMessage(signatureMessage); } session.WaitHandle(this._authenticationCompleted); if (this._authenticationResult == AuthenticationResult.Success) { break; } } session.UserAuthenticationSuccessReceived -= Session_UserAuthenticationSuccessReceived; session.UserAuthenticationFailureReceived -= Session_UserAuthenticationFailureReceived; session.MessageReceived -= Session_MessageReceived; session.UnRegisterMessage("SSH_MSG_USERAUTH_PK_OK"); return this._authenticationResult; } private void Session_UserAuthenticationSuccessReceived(object sender, MessageEventArgs e) { this._authenticationResult = AuthenticationResult.Success; this._authenticationCompleted.Set(); } private void Session_UserAuthenticationFailureReceived(object sender, MessageEventArgs e) { if (e.Message.PartialSuccess) this._authenticationResult = AuthenticationResult.PartialSuccess; else this._authenticationResult = AuthenticationResult.Failure; // Copy allowed authentication methods this.AllowedAuthentications = e.Message.AllowedAuthentications.ToList(); this._authenticationCompleted.Set(); } private void Session_MessageReceived(object sender, MessageEventArgs e) { var publicKeyMessage = e.Message as PublicKeyMessage; if (publicKeyMessage != null) { this._isSignatureRequired = true; this._authenticationCompleted.Set(); } } #region IDisposable Members private bool isDisposed = false; /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// Releases unmanaged and - optionally - managed resources /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected virtual void Dispose(bool disposing) { // Check to see if Dispose has already been called. if (!this.isDisposed) { // If disposing equals true, dispose all managed // and unmanaged resources. if (disposing) { // Dispose managed resources. if (this._authenticationCompleted != null) { this._authenticationCompleted.Dispose(); this._authenticationCompleted = null; } } // Note disposing has been done. isDisposed = true; } } /// /// Releases unmanaged resources and performs other cleanup operations before the /// is reclaimed by garbage collection. /// ~PrivateKeyAuthenticationMethod() { // Do not re-create Dispose clean-up code here. // Calling Dispose(false) is optimal in terms of // readability and maintainability. Dispose(false); } #endregion private class SignatureData : SshData { private RequestMessagePublicKey _message; private byte[] _sessionId; public SignatureData(RequestMessagePublicKey message, byte[] sessionId) { this._message = message; this._sessionId = sessionId; } protected override void LoadData() { throw new System.NotImplementedException(); } protected override void SaveData() { this.WriteBinaryString(this._sessionId); this.Write((byte)50); this.Write(this._message.Username); this.WriteAscii("ssh-connection"); this.WriteAscii("publickey"); this.Write((byte)1); this.WriteAscii(this._message.PublicKeyAlgorithmName); this.WriteBinaryString(this._message.PublicKeyData); } } } }