Skip to content

Allow signed keys #595

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 2 commits 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
8 changes: 7 additions & 1 deletion src/Renci.SshNet.NET35/Renci.SshNet.NET35.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,9 @@
<Compile Include="..\Renci.SshNet\Common\SshPassPhraseNullOrEmptyException.cs">
<Link>Common\SshPassPhraseNullOrEmptyException.cs</Link>
</Compile>
<Compile Include="..\Renci.SshNet\Common\SshSignatureData.cs">
<Link>Common\SshSignatureData.cs</Link>
</Compile>
<Compile Include="..\Renci.SshNet\Common\TerminalModes.cs">
<Link>Common\TerminalModes.cs</Link>
</Compile>
Expand Down Expand Up @@ -695,6 +698,9 @@
<Compile Include="..\Renci.SshNet\Security\Cryptography\Key.cs">
<Link>Security\Cryptography\Key.cs</Link>
</Compile>
<Compile Include="..\Renci.SshNet\Security\Cryptography\RsaCertificate.cs">
<Link>Security\Cryptography\RsaCertificate.cs</Link>
</Compile>
<Compile Include="..\Renci.SshNet\Security\Cryptography\RsaDigitalSignature.cs">
<Link>Security\Cryptography\RsaDigitalSignature.cs</Link>
</Compile>
Expand Down Expand Up @@ -971,7 +977,7 @@
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<ProjectExtensions>
<VisualStudio>
<UserProperties ProjectLinkerExcludeFilter="\\?desktop(\\.*)?$;\\?silverlight(\\.*)?$;\.desktop;\.silverlight;\.xaml;^service references(\\.*)?$;\.clientconfig;^web references(\\.*)?$" ProjectLinkReference="2f5f8c90-0bd1-424f-997c-7bc6280919d1" />
<UserProperties ProjectLinkReference="2f5f8c90-0bd1-424f-997c-7bc6280919d1" ProjectLinkerExcludeFilter="\\?desktop(\\.*)?$;\\?silverlight(\\.*)?$;\.desktop;\.silverlight;\.xaml;^service references(\\.*)?$;\.clientconfig;^web references(\\.*)?$" />
</VisualStudio>
</ProjectExtensions>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Expand Down
12 changes: 12 additions & 0 deletions src/Renci.SshNet/Common/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,18 @@ public static byte[] Concat(this byte[] first, byte[] second)
return concat;
}

public static DateTime FromUnixTime(this ulong unixTime)
{
var epoch = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
return epoch.AddSeconds(unixTime);
}

public static ulong ToUnixTime(this DateTime date)
{
var epoch = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
return (ulong)(date - epoch).TotalSeconds;
}

internal static bool CanRead(this Socket socket)
{
return SocketAbstraction.CanRead(socket);
Expand Down
63 changes: 63 additions & 0 deletions src/Renci.SshNet/Common/SshSignatureData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using Renci.SshNet.Messages;
using Renci.SshNet.Messages.Authentication;
using System;

namespace Renci.SshNet.Common
{
internal class SshSignatureData : SshData
{
private readonly RequestMessagePublicKey _message;

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

protected override int BufferCapacity
{
get
{
var capacity = base.BufferCapacity;
capacity += 4; // SessionId length
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 SshSignatureData(RequestMessagePublicKey message, byte[] sessionId)
{
_message = message;
_sessionId = sessionId;
_serviceName = ServiceName.Connection.ToArray();
_authenticationMethod = Ascii.GetBytes("publickey");
}

protected override void LoadData()
{
throw new 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);
}
}
}
61 changes: 2 additions & 59 deletions src/Renci.SshNet/PrivateKeyAuthenticationMethod.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ public override AuthenticationResult Authenticate(Session session)
if (KeyFiles.Count < 2)
{
// If only one key file provided then send signature for very first request
var signatureData = new SignatureData(message, session.SessionId).GetBytes();
var signatureData = new SshSignatureData(message, session.SessionId).GetBytes();

message.Signature = keyFile.HostKey.Sign(signatureData);
}
Expand All @@ -94,7 +94,7 @@ public override AuthenticationResult Authenticate(Session session)
keyFile.HostKey.Name,
keyFile.HostKey.Data);

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

signatureMessage.Signature = keyFile.HostKey.Sign(signatureData);

Expand Down Expand Up @@ -192,62 +192,5 @@ protected virtual void Dispose(bool disposing)
}

#endregion

private class SignatureData : SshData
{
private readonly RequestMessagePublicKey _message;

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

protected override int BufferCapacity
{
get
{
var capacity = base.BufferCapacity;
capacity += 4; // SessionId length
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 = ServiceName.Connection.ToArray();
_authenticationMethod = Ascii.GetBytes("publickey");
}

protected override void LoadData()
{
throw new 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);
}
}
}
}
187 changes: 187 additions & 0 deletions src/Renci.SshNet/PrivateKeyCertAuthenticationMethod.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
using Renci.SshNet.Common;
using Renci.SshNet.Messages;
using Renci.SshNet.Messages.Authentication;
using System;
using System.Threading;


namespace Renci.SshNet
{
/// <summary>
/// Provides functionality to perform private key authentication using a
/// signed public key component (a certificate).
/// </summary>
public class PrivateKeyCertAuthenticationMethod : AuthenticationMethod, IDisposable
{
private AuthenticationResult _authenticationResult = AuthenticationResult.Failure;
private EventWaitHandle _authenticationCompleted = new ManualResetEvent(false);

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

/// <summary>
/// Gets the key files used for authentication.
/// </summary>
public PrivateKeyFile KeyFile { get; private set; }

/// <summary>
///
/// </summary>
public PublicKeyCertFile CertificateFile { get; private set; }

/// <summary>
/// Initializes a new instance of the <see cref="PrivateKeyAuthenticationMethod"/> class.
/// </summary>
/// <param name="username">The username.</param>
/// <param name="keyFile">The key files.</param>
/// <exception cref="ArgumentException"><paramref name="username"/> is whitespace or <c>null</c>.</exception>
public PrivateKeyCertAuthenticationMethod(string username, PrivateKeyFile keyFile)
Copy link
Member

Choose a reason for hiding this comment

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

Remove?

: base(username)
{
if (keyFile == null)
throw new ArgumentNullException("keyFiles");

KeyFile = keyFile;
}

/// <summary>
/// Initializes a new instance of the <see cref="PrivateKeyAuthenticationMethod"/> class.
/// </summary>
/// <param name="username">The username.</param>
/// <param name="keyFile">The key files.</param>
/// <param name="certFile"></param>
/// <exception cref="ArgumentException"><paramref name="username"/> is whitespace or <c>null</c>.</exception>
public PrivateKeyCertAuthenticationMethod(string username, PrivateKeyFile keyFile, PublicKeyCertFile certFile)
: base(username)
{
if (keyFile == null)
throw new ArgumentNullException("keyFile");

if (certFile == null)
throw new ArgumentNullException("certFile");

KeyFile = keyFile;
CertificateFile = certFile;
}

/// <summary>
/// Authenticates the specified session.
/// </summary>
/// <param name="session">The session to authenticate.</param>
/// <returns>
/// Result of authentication process.
/// </returns>
public override AuthenticationResult Authenticate(Session session)
{
session.UserAuthenticationSuccessReceived += Session_UserAuthenticationSuccessReceived;
session.UserAuthenticationFailureReceived += Session_UserAuthenticationFailureReceived;
session.UserAuthenticationPublicKeyReceived += Session_UserAuthenticationPublicKeyReceived;

session.RegisterMessage("SSH_MSG_USERAUTH_PK_OK");

try
{
_authenticationCompleted.Reset();

var message = new RequestMessagePublicKey(ServiceName.Connection,
Username,
CertificateFile.HostCertificate.Name,
CertificateFile.HostCertificate.Data);

// Send signature for very first request
var signatureData = new SshSignatureData(message, session.SessionId).GetBytes();
message.Signature = KeyFile.HostKey.Sign(signatureData);

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

session.WaitOnHandle(_authenticationCompleted);

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)
{
_authenticationResult = AuthenticationResult.Success;

_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
AllowedAuthentications = e.Message.AllowedAuthentications;

_authenticationCompleted.Set();
}

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

#region IDisposable Members

private bool _isDisposed;

/// <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)
{
if (_isDisposed)
return;

if (disposing)
{
var authenticationCompleted = _authenticationCompleted;
if (authenticationCompleted != null)
{
_authenticationCompleted = null;
authenticationCompleted.Dispose();
}

_isDisposed = true;
}
}

/// <summary>
/// Releases unmanaged resources and performs other cleanup operations before the
/// <see cref="PasswordConnectionInfo"/> is reclaimed by garbage collection.
/// </summary>
~PrivateKeyCertAuthenticationMethod()
{
Dispose(false);
}

#endregion
}
}
Loading