Skip to content

Adding Pagant Support #705

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
228 changes: 228 additions & 0 deletions src/Renci.SshNet/AgentAuthenticationMethod.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
#if NETFRAMEWORK && !NET20 && !NET35
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.NET standard for .NET core is supported by the SSH.NET library in general so new additions to the code base should continue to honor that.

using System;
using System.Linq;
using System.Text;
using System.Threading;
using Renci.SshNet.Common;
using Renci.SshNet.Messages;
using Renci.SshNet.Messages.Authentication;

namespace Renci.SshNet {
/// <summary>
/// Provides functionality to perform private key authentication.
/// </summary>
public class AgentAuthenticationMethod : AuthenticationMethod, IDisposable {
private AuthenticationResult _authenticationResult = AuthenticationResult.Failure;
private EventWaitHandle _authenticationCompleted = new ManualResetEvent (false);
private bool _isSignatureRequired;

/// <summary>
/// Gets authentication method name
/// </summary>
public override string Name {
get { return "publickey"; }
}

/// <summary>
///
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing comment content

/// </summary>
public IAgentProtocol Protocol { get; private set; }

/// <summary>
/// Initializes a new instance of the <see cref="PrivateKeyAuthenticationMethod"/> class.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wrong cref

/// </summary>
/// <param name="username">The username.</param>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: fragment sentences (sentences without subject-verb-complement) should not end with dot ('.')

/// <param name="protocol">The key files.</param>
/// <exception cref="ArgumentException"><paramref name="username"/> is whitespace or null.</exception>
public AgentAuthenticationMethod (string username, IAgentProtocol protocol) : base (username) {
Protocol = protocol;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Existing code curly-bracket alignment style is

accessor returnType methodname()
{
    foobar;
}

not

accessor returnType methodname() {
    foobar;
}


/// <summary>
/// Authenticates the specified session.
/// </summary>
/// <param name="session">The session to authenticate.</param>
/// <returns></returns>
public override AuthenticationResult Authenticate (Session session) {
if (Protocol == null)
return AuthenticationResult.Failure;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can a more specific error / a failure reason be provided?


session.UserAuthenticationSuccessReceived += Session_UserAuthenticationSuccessReceived;
session.UserAuthenticationFailureReceived += Session_UserAuthenticationFailureReceived;
session.UserAuthenticationPublicKeyReceived += Session_UserAuthenticationPublicKeyReceived;

session.RegisterMessage ("SSH_MSG_USERAUTH_PK_OK");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Declare a string const for "SSH_MSG_USERAUTH_PK_OK" and re-use the const instead of repeating the string's hardcoding.


try {
foreach (var identity in Protocol.GetIdentities ()) {
_authenticationCompleted.Reset ();
_isSignatureRequired = false;

var message = new RequestMessagePublicKey (ServiceName.Connection,
Username,
identity.Type,
identity.Blob);

// Send public key authentication request
session.SendMessage (message);

session.WaitOnHandle (_authenticationCompleted);

if (_isSignatureRequired) {
_authenticationCompleted.Reset ();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no space character between method name and opening parenthesis for parameters. Here and through the change.


var signatureMessage = new RequestMessagePublicKey (ServiceName.Connection,
Username,
identity.Type,
identity.Blob);

var signatureData = new SignatureData (message, session.SessionId).GetBytes ();

signatureMessage.Signature = this.Protocol.SignData (identity, signatureData);

// Send public key authentication request with signature
session.SendMessage (signatureMessage);
}

session.WaitOnHandle (_authenticationCompleted);

if (_authenticationResult == AuthenticationResult.Success) {
break;
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Empty line after block closing curly-brace and before next line of code.

return _authenticationResult;
} finally {
session.UserAuthenticationSuccessReceived -= Session_UserAuthenticationSuccessReceived;
session.UserAuthenticationFailureReceived -= Session_UserAuthenticationFailureReceived;
session.UserAuthenticationPublicKeyReceived -= Session_UserAuthenticationPublicKeyReceived;
session.UnRegisterMessage ("SSH_MSG_USERAUTH_PK_OK");
}
}

private void Session_UserAuthenticationSuccessReceived (object sender, MessageEventArgs<SuccessMessage> e) {
this._authenticationResult = AuthenticationResult.Success;

this._authenticationCompleted.Set ();
}

private void Session_UserAuthenticationFailureReceived (object sender, MessageEventArgs<FailureMessage> e) {
if (e.Message.PartialSuccess)
_authenticationResult = AuthenticationResult.PartialSuccess;
else
_authenticationResult = AuthenticationResult.Failure;

// Copy allowed authentication methods
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this assignment really makes a copy or does it just take a reference?

AllowedAuthentications = e.Message.AllowedAuthentications;

_authenticationCompleted.Set ();
}

private void Session_UserAuthenticationPublicKeyReceived (object sender, MessageEventArgs<PublicKeyMessage> e) {
this._isSignatureRequired = true;
this._authenticationCompleted.Set ();
}

#region IDisposable Members

private bool _isDisposed = false;

/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose () {
Dispose (true);
GC.SuppressFinalize (this);
}

/// <summary>
/// Releases unmanaged and - optionally - managed resources
/// </summary>
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected virtual void Dispose (bool disposing) {
// Check to see if Dispose has already been called.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid boiler-plate inline comments for explicit code. Preserve comments when they add context, something unclear about the code, etc.

if (!_isDisposed)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be the opposite case, i.e. return if _isDisposed is true?

return;

if (disposing) {
// If disposing equals true, dispose all managed
// and unmanaged resources.
if (disposing) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

duplicated test of disposing

var authenticationCompleted = _authenticationCompleted;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use Interlocked.Exchange instead. It will return the previous value, acts as a memory barrier, and protect on this thread against race conditions / value caching per thread.

// Dispose managed resources.
if (this._authenticationCompleted != null) {
_authenticationCompleted = null;
authenticationCompleted.Dispose ();
}
}

// Note disposing has been done.
_isDisposed = true;
}
}

/// <summary>
/// Releases unmanaged resources and performs other cleanup operations before the
/// <see cref="PasswordConnectionInfo"/> is reclaimed by garbage collection.
/// </summary>
~AgentAuthenticationMethod () {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Explicit destructor implementation is atypical. Do we really need it here?

// 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 readonly RequestMessagePublicKey _message;

private byte[] _sessionId;
private readonly byte[] _serviceName;
private readonly byte[] _authenticationMethod;

protected override int BufferCapacity {
get {
var capacity = base.BufferCapacity;
capacity += 4; // SessionId length
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Define named constants instead of code comments.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And then collapse the dozen lines into one.

capacity += _sessionId.Length; // SessionId
capacity += 1; // Authentication Message Code
capacity += 4; // UserName length
capacity += _message.Username.Length; // UserName
capacity += 4; // ServiceName length
capacity += _serviceName.Length; // ServiceName
capacity += 4; // AuthenticationMethod length
capacity += _authenticationMethod.Length; // AuthenticationMethod
capacity += 1; // TRUE
capacity += 4; // PublicKeyAlgorithmName length
capacity += _message.PublicKeyAlgorithmName.Length; // PublicKeyAlgorithmName
capacity += 4; // PublicKeyData length
capacity += _message.PublicKeyData.Length; // PublicKeyData
return capacity;
}
}

public SignatureData (RequestMessagePublicKey message, byte[] sessionId) {
_message = message;
_sessionId = sessionId;
_serviceName = Ascii.GetBytes ("ssh-connection");
_authenticationMethod = Ascii.GetBytes ("publickey");
}

protected override void LoadData () {
throw new System.NotImplementedException ();
}

protected override void SaveData () {
WriteBinaryString (_sessionId);
Write ((byte) RequestMessage.AuthenticationMessageCode);
WriteBinaryString (_message.Username);
WriteBinaryString (_serviceName);
WriteBinaryString (_authenticationMethod);
Write ((byte) 1); // TRUE
WriteBinaryString (_message.PublicKeyAlgorithmName);
WriteBinaryString (_message.PublicKeyData);
}
}
}
}
#endif
164 changes: 164 additions & 0 deletions src/Renci.SshNet/AgentConnectionInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
#if NETFRAMEWORK && !NET20 && !NET35
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;

namespace Renci.SshNet {
/// <summary>
/// Provides connection information when private key authentication method is used
/// </summary>
public class AgentConnectionInfo : ConnectionInfo, IDisposable {
/// <summary>
/// Gets the key files used for authentication.
/// </summary>
public IAgentProtocol Protocol { get; private set; }

/// <summary>
/// Initializes a new instance of the <see cref="PrivateKeyConnectionInfo"/> class.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix cref here and through the code change.

/// </summary>
/// <param name="host">Connection host.</param>
/// <param name="username">Connection username.</param>
/// <param name="protocol">Connection key files.</param>
public AgentConnectionInfo (string host, string username, IAgentProtocol protocol) : this (host, 22, username, ProxyTypes.None, string.Empty, 0, string.Empty, string.Empty, protocol) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid hardcoded magical values. Define named constants instead for readability.

When calling a method with multiple parameters of the same type, use named parameters to remove the ambiguity for human readers. Compiler will match type but doesn't know purpose. Human will be able to assert purpose but don't have context of the called method unless named parameters are used.


}

/// <summary>
/// Initializes a new instance of the <see cref="PrivateKeyConnectionInfo"/> class.
/// </summary>
/// <param name="host">Connection host.</param>
/// <param name="port">Connection port.</param>
/// <param name="username">Connection username.</param>
/// <param name="protocol">Connection key files.</param>
public AgentConnectionInfo (string host, int port, string username, IAgentProtocol protocol) : this (host, port, username, ProxyTypes.None, string.Empty, 0, string.Empty, string.Empty, protocol) { }

/// <summary>
/// Initializes a new instance of the <see cref="PasswordConnectionInfo"/> class.
/// </summary>
/// <param name="host">Connection host.</param>
/// <param name="port">The port.</param>
/// <param name="username">Connection username.</param>
/// <param name="proxyType">Type of the proxy.</param>
/// <param name="proxyHost">The proxy host.</param>
/// <param name="proxyPort">The proxy port.</param>
/// <param name="protocol">The key files.</param>
public AgentConnectionInfo (string host, int port, string username, ProxyTypes proxyType, string proxyHost, int proxyPort, IAgentProtocol protocol) : this (host, port, username, proxyType, proxyHost, proxyPort, string.Empty, string.Empty, protocol) { }

/// <summary>
/// Initializes a new instance of the <see cref="PasswordConnectionInfo"/> class.
/// </summary>
/// <param name="host">Connection host.</param>
/// <param name="port">The port.</param>
/// <param name="username">Connection username.</param>
/// <param name="proxyType">Type of the proxy.</param>
/// <param name="proxyHost">The proxy host.</param>
/// <param name="proxyPort">The proxy port.</param>
/// <param name="proxyUsername">The proxy username.</param>
/// <param name="protocol">The key files.</param>
public AgentConnectionInfo (string host, int port, string username, ProxyTypes proxyType, string proxyHost, int proxyPort, string proxyUsername, IAgentProtocol protocol) : this (host, port, username, proxyType, proxyHost, proxyPort, proxyUsername, string.Empty, protocol) { }

/// <summary>
/// Initializes a new instance of the <see cref="PasswordConnectionInfo"/> class.
/// </summary>
/// <param name="host">Connection host.</param>
/// <param name="username">Connection username.</param>
/// <param name="proxyType">Type of the proxy.</param>
/// <param name="proxyHost">The proxy host.</param>
/// <param name="proxyPort">The proxy port.</param>
/// <param name="protocol">The key files.</param>
public AgentConnectionInfo (string host, string username, ProxyTypes proxyType, string proxyHost, int proxyPort, IAgentProtocol protocol) : this (host, 22, username, proxyType, proxyHost, proxyPort, string.Empty, string.Empty, protocol) { }

/// <summary>
/// Initializes a new instance of the <see cref="PasswordConnectionInfo"/> class.
/// </summary>
/// <param name="host">Connection host.</param>
/// <param name="username">Connection username.</param>
/// <param name="proxyType">Type of the proxy.</param>
/// <param name="proxyHost">The proxy host.</param>
/// <param name="proxyPort">The proxy port.</param>
/// <param name="proxyUsername">The proxy username.</param>
/// <param name="protocol">The key files.</param>
public AgentConnectionInfo (string host, string username, ProxyTypes proxyType, string proxyHost, int proxyPort, string proxyUsername, IAgentProtocol protocol) : this (host, 22, username, proxyType, proxyHost, proxyPort, proxyUsername, string.Empty, protocol) { }

/// <summary>
/// Initializes a new instance of the <see cref="PasswordConnectionInfo"/> class.
/// </summary>
/// <param name="host">Connection host.</param>
/// <param name="username">Connection username.</param>
/// <param name="proxyType">Type of the proxy.</param>
/// <param name="proxyHost">The proxy host.</param>
/// <param name="proxyPort">The proxy port.</param>
/// <param name="proxyUsername">The proxy username.</param>
/// <param name="proxyPassword">The proxy password.</param>
/// <param name="protocol">The key files.</param>
public AgentConnectionInfo (string host, string username, ProxyTypes proxyType, string proxyHost, int proxyPort, string proxyUsername, string proxyPassword, IAgentProtocol protocol) : this (host, 22, username, proxyType, proxyHost, proxyPort, proxyUsername, proxyPassword, protocol) { }

/// <summary>
/// Initializes a new instance of the <see cref="PasswordConnectionInfo"/> class.
/// </summary>
/// <param name="host">Connection host.</param>
/// <param name="port">The port.</param>
/// <param name="username">Connection username.</param>
/// <param name="proxyType">Type of the proxy.</param>
/// <param name="proxyHost">The proxy host.</param>
/// <param name="proxyPort">The proxy port.</param>
/// <param name="proxyUsername">The proxy username.</param>
/// <param name="proxyPassword">The proxy password.</param>
/// <param name="protocol">The key files.</param>
public AgentConnectionInfo (string host, int port, string username, ProxyTypes proxyType, string proxyHost, int proxyPort, string proxyUsername, string proxyPassword, IAgentProtocol protocol) : base (host, port, username, proxyType, proxyHost, proxyPort, proxyUsername, proxyPassword, new AgentAuthenticationMethod (username, protocol)) {
this.Protocol = protocol;
}

#region IDisposable Members

private bool isDisposed = false;

/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose () {
Dispose (true);

GC.SuppressFinalize (this);
}

/// <summary>
/// Releases unmanaged and - optionally - managed resources
/// </summary>
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
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.AuthenticationMethods != null) {
foreach (var authenticationMethods in this.AuthenticationMethods.OfType<IDisposable> ()) {
authenticationMethods.Dispose ();
}
}
}

// Note disposing has been done.
isDisposed = true;
}
}

/// <summary>
/// Releases unmanaged resources and performs other cleanup operations before the
/// <see cref="PasswordConnectionInfo"/> is reclaimed by garbage collection.
/// </summary>
~AgentConnectionInfo () {
// Do not re-create Dispose clean-up code here.
// Calling Dispose(false) is optimal in terms of
// readability and maintainability.
Dispose (false);
}

#endregion
}
}
#endif
Loading