Refactor key exchange mechanism to handle key exchange messages issued by the server

This commit is contained in:
olegkap_cp
2010-09-04 01:51:17 +00:00
parent c7d1ea84a0
commit 3a76bf09bd
9 changed files with 413 additions and 387 deletions
@@ -382,6 +382,10 @@ namespace Renci.SshClient.Channels
{
this._channelClosedWaitHandle.Dispose();
}
if (this._channelWindowAdjustWaitHandle != null)
{
this._channelWindowAdjustWaitHandle.Dispose();
}
this.OnDisposing();
}
@@ -41,7 +41,7 @@ namespace Renci.SshClient.Channels
}
public ChannelSftp(Session session, uint channelId)
: base(session, channelId, 0x100000, 0x4000)
: base(session, channelId, 0x100000, 0x0100)
{
}
@@ -3,6 +3,7 @@ using System.Runtime.Serialization;
namespace Renci.SshClient.Common
{
[Serializable]
public class SshException : Exception
{
public bool ShouldDisconnect { get; private set; }
@@ -117,6 +117,7 @@
<Compile Include="Security\CryptoPublicKeyDss.cs" />
<Compile Include="Security\CryptoPublicKeyRsa.cs" />
<Compile Include="Security\KeyExchange.cs" />
<Compile Include="Security\KeyExchangeAlgorithm.cs" />
<Compile Include="Security\KeyExchangeCompletedEventArgs.cs" />
<Compile Include="Security\KeyExchangeDiffieHellman.cs" />
<Compile Include="Security\KeyExchangeFailedEventArgs.cs" />
@@ -1,39 +1,18 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Numerics;
using System.Security.Cryptography;
using System.Threading;
using Renci.SshClient.Common;
using Renci.SshClient.Messages;
using Renci.SshClient.Messages.Transport;
namespace Renci.SshClient.Security
{
internal abstract class KeyExchange : Algorithm
internal class KeyExchange : Algorithm, IDisposable
{
/// <summary>
/// Creates the key exchange algorithm to be used for key exchange.
/// </summary>
/// <param name="message">The message.</param>
/// <returns></returns>
internal static KeyExchange Create(KeyExchangeInitMessage message, Session session)
{
// TODO: Determine key exchange algorithm
var keyExchangeAlgorithm = (from s in message.KeyExchangeAlgorithms
from c in Settings.KeyExchangeAlgorithms.Keys
where s == c
select c).FirstOrDefault();
// TODO: If dont agree on algorithms then send disconnect message
if (keyExchangeAlgorithm == null)
{
throw new InvalidDataException("Failed to negotiate key exchange algorithm.");
}
return Settings.KeyExchangeAlgorithms[keyExchangeAlgorithm](session);
}
private KeyExchangeAlgorithm _keyExchangeAlgorithm;
/// <summary>
/// Specifies negotiated algorithm to encrypt information when sent to the server
@@ -55,95 +34,110 @@ namespace Renci.SshClient.Security
/// </summary>
private Func<IEnumerable<byte>, HMAC> _serverHmacAlgorithm;
private IEnumerable<byte> _exchangeHash;
/// <summary>
/// Gets hash value
/// Gets the key exchange algorithm name.
/// </summary>
public IEnumerable<byte> ExchangeHash
/// <value>Key exchange algorithm name or empty if name not yet defined.</value>
public override string Name
{
get
{
if (this._exchangeHash == null)
{
this._exchangeHash = this.CalculateHash();
}
return this._exchangeHash;
if (this._keyExchangeAlgorithm == null)
return string.Empty;
else
return this._keyExchangeAlgorithm.Name;
}
}
public IEnumerable<byte> SessionId { get; set; }
private EventWaitHandle _waitHandle = new EventWaitHandle(false, EventResetMode.ManualReset);
public HMAC ServerMac { get; set; }
/// <summary>
/// Gets the wait handle that signals that key exchange completed
/// </summary>
/// <value>The wait handle.</value>
public EventWaitHandle WaitHandle
{
get
{
return this._waitHandle;
}
}
public HMAC ClientMac { get; set; }
/// <summary>
/// Gets or sets the session id.
/// </summary>
/// <value>The session id.</value>
public IEnumerable<byte> SessionId { get; private set; }
public Cipher ClientCipher { get; set; }
/// <summary>
/// Gets or sets the server mac algorithm to use.
/// </summary>
/// <value>The server mac.</value>
public HMAC ServerMac { get; private set; }
public Cipher ServerCipher { get; set; }
/// <summary>
/// Gets or sets the client mac algorithm to use.
/// </summary>
/// <value>The client mac.</value>
public HMAC ClientMac { get; private set; }
public Compression ServerDecompression { get; set; }
/// <summary>
/// Gets or sets the client cipher algorithm to use.
/// </summary>
/// <value>The client cipher.</value>
public Cipher ClientCipher { get; private set; }
public Compression ClientCompression { get; set; }
/// <summary>
/// Gets or sets the server cipher algorithm to use.
/// </summary>
/// <value>The server cipher.</value>
public Cipher ServerCipher { get; private set; }
public bool IsCompleted { get; protected set; }
public Compression ServerDecompression { get; private set; }
public bool IsSuccessed { get; protected set; }
public Compression ClientCompression { get; private set; }
/// <summary>
/// Gets or sets a value indicating whether key exchange is in progress.
/// </summary>
/// <value><c>true</c> if [in progress]; otherwise, <c>false</c>.</value>
public bool InProgress { get; protected set; }
/// <summary>
/// Gets or sets the session.
/// </summary>
/// <value>The session.</value>
protected Session Session { get; private set; }
protected string ClientPayload { get; set; }
protected string ServerPayload { get; set; }
protected string HostKey { get; set; }
protected BigInteger ClientExchangeValue { get; set; }
protected BigInteger ServerExchangeValue { get; set; }
protected BigInteger SharedKey { get; set; }
protected string Signature { get; set; }
public event EventHandler<KeyExchangeCompletedEventArgs> Completed;
public event EventHandler<KeyExchangeFailedEventArgs> Failed;
/// <summary>
/// Initializes a new instance of the <see cref="KeyExchange"/> class.
/// </summary>
/// <param name="session">The session.</param>
public KeyExchange(Session session)
{
this.Session = session;
this.SessionId = session.SessionId;
this.ServerDecompression = Compression.None;
this.ClientCompression = Compression.None;
}
public virtual void Start()
public void HandleMessage(KeyExchangeInitMessage message)
{
// TODO: If key exchange initiated by the client no need to send client message again
var clientMessage = new KeyExchangeInitMessage()
this._waitHandle.Reset();
this.InProgress = true;
this.SendMessage(this.Session.ClientInitMessage);
var keyExchangeAlgorithm = (from s in message.KeyExchangeAlgorithms
from c in Settings.KeyExchangeAlgorithms.Keys
where s == c
select c).FirstOrDefault();
// TODO: If dont agree on algorithms then send disconnect message
if (keyExchangeAlgorithm == null)
{
KeyExchangeAlgorithms = Settings.KeyExchangeAlgorithms.Keys,
ServerHostKeyAlgorithms = Settings.HostKeyAlgorithms.Keys,
EncryptionAlgorithmsClientToServer = Settings.Encryptions.Keys,
EncryptionAlgorithmsServerToClient = Settings.Encryptions.Keys,
MacAlgorithmsClientToSserver = Settings.HmacAlgorithms.Keys,
MacAlgorithmsServerToClient = Settings.HmacAlgorithms.Keys,
CompressionAlgorithmsClientToServer = new string[] { "none" },
CompressionAlgorithmsServerToClient = new string[] { "none" },
LanguagesClientToServer = new string[] { string.Empty },
LanguagesServerToClient = new string[] { string.Empty },
FirstKexPacketFollows = false,
Reserved = 0,
};
this.ClientPayload = clientMessage.GetBytes().GetSshString();
this.SendMessage(clientMessage);
}
public virtual void Start(KeyExchangeInitMessage message)
{
this.Start();
throw new InvalidOperationException("Failed to negotiate key exchange algorithm.");
}
// Determine encryption algorithm
var clientEncryptionAlgorithmName = (from a in message.EncryptionAlgorithmsClientToServer
@@ -189,26 +183,43 @@ namespace Renci.SshClient.Security
}
this._serverHmacAlgorithm = Settings.HmacAlgorithms[serverHmacAlgorithmName];
this._keyExchangeAlgorithm = Settings.KeyExchangeAlgorithms[keyExchangeAlgorithm](this.Session);
this._keyExchangeAlgorithm.HandleMessage(message);
}
public virtual void Finish()
public void HandleMessage(NewKeysMessage message)
{
// TODO: Validate that all required properties are set
// Validate hash
var validated = this._keyExchangeAlgorithm.ValidateExchangeHash();
if (validated)
{
this.SendMessage(new NewKeysMessage());
}
else
{
throw new InvalidOperationException("Key exchange negotiation failed.");
}
var exchangeHash = this._keyExchangeAlgorithm.ExchangeHash;
var sharedKey = this._keyExchangeAlgorithm.SharedKey;
// Initialize new encryption algorithms
if (this.SessionId == null)
{
this.SessionId = this.ExchangeHash;
this.SessionId = exchangeHash;
}
// Initialize client cipher
var clientCipher = this._clientCipher();
// Calculate client to server initial IV
var clientVector = this.Hash(this.GenerateSessionKey(this.SharedKey, this.ExchangeHash, 'A', this.SessionId));
var clientVector = this.Hash(this.GenerateSessionKey(sharedKey, exchangeHash, 'A', this.SessionId));
// Calculate client to server encryption
var clientKey = this.Hash(this.GenerateSessionKey(this.SharedKey, this.ExchangeHash, 'C', this.SessionId));
var clientKey = this.Hash(this.GenerateSessionKey(sharedKey, exchangeHash, 'C', this.SessionId));
clientKey = this.GenerateSessionKey(this.SharedKey, this.ExchangeHash, clientKey, clientCipher.KeySize / 8);
clientKey = this.GenerateSessionKey(sharedKey, exchangeHash, clientKey, clientCipher.KeySize / 8);
clientCipher.Init(clientKey, clientVector);
@@ -216,21 +227,21 @@ namespace Renci.SshClient.Security
var serverCipher = this._serverCipher();
// Calculate server to client initial IV
var serverVector = this.Hash(this.GenerateSessionKey(this.SharedKey, this.ExchangeHash, 'B', this.SessionId));
var serverVector = this.Hash(this.GenerateSessionKey(sharedKey, exchangeHash, 'B', this.SessionId));
// Calculate server to client encryption
var serverKey = this.Hash(this.GenerateSessionKey(this.SharedKey, this.ExchangeHash, 'D', this.SessionId));
var serverKey = this.Hash(this.GenerateSessionKey(sharedKey, exchangeHash, 'D', this.SessionId));
serverKey = this.GenerateSessionKey(this.SharedKey, this.ExchangeHash, serverKey, serverCipher.KeySize / 8);
serverKey = this.GenerateSessionKey(sharedKey, exchangeHash, serverKey, serverCipher.KeySize / 8);
serverCipher.Init(serverKey, serverVector);
// Calculate client to server integrity
var MACc2s = this.Hash(this.GenerateSessionKey(this.SharedKey, this.ExchangeHash, 'E', this.SessionId));
var MACc2s = this.Hash(this.GenerateSessionKey(sharedKey, exchangeHash, 'E', this.SessionId));
var clientMac = this._clientHmacAlgorithm(MACc2s);
// Calculate server to client integrity
var MACs2c = this.Hash(this.GenerateSessionKey(this.SharedKey, this.ExchangeHash, 'F', this.SessionId));
var MACs2c = this.Hash(this.GenerateSessionKey(sharedKey, exchangeHash, 'F', this.SessionId));
var serverMac = this._serverHmacAlgorithm(MACs2c);
// TODO: Create compression and decompression objects if any
@@ -242,41 +253,23 @@ namespace Renci.SshClient.Security
this.ServerMac = serverMac;
this.ClientMac = clientMac;
this.IsCompleted = true;
this.RaiseCompleted();
this.InProgress = false;
// Signal that key exchange completed
this._waitHandle.Set();
}
/// <summary>
/// Raises the Completed event.
/// </summary>
/// <param name="sessionId">The session id.</param>
/// <param name="decryption">The decryption to be used.</param>
/// <param name="encryption">The encryption to be used.</param>
/// <param name="serverDecompression">The server decompression.</param>
/// <param name="clientCompression">The client compression.</param>
/// <param name="serverMac">The server mac.</param>
/// <param name="clientMac">The client mac.</param>
protected void RaiseCompleted()
public void HandleMessage<T>(T message) where T : Message
{
if (this.Completed != null)
{
this.Completed(this, new KeyExchangeCompletedEventArgs());
}
this._keyExchangeAlgorithm.HandleMessage(message);
}
/// <summary>
/// Raises the Failed event.
/// </summary>
/// <param name="message">The fail reason message.</param>
protected void RaiseFailed(string message)
private void SendMessage(Message message)
{
if (this.Failed != null)
{
this.Failed(this, new KeyExchangeFailedEventArgs(message));
}
this.Session.SendMessage(message);
}
protected virtual IEnumerable<byte> Hash(IEnumerable<byte> hashBytes)
private IEnumerable<byte> Hash(IEnumerable<byte> hashBytes)
{
using (var md = new System.Security.Cryptography.SHA1CryptoServiceProvider())
{
@@ -290,45 +283,6 @@ namespace Renci.SshClient.Security
}
}
protected bool ValidateExchangeHash()
{
var bytes = this.HostKey.GetSshBytes();
var length = (uint)(this.HostKey[0] << 24 | this.HostKey[1] << 16 | this.HostKey[2] << 8 | this.HostKey[3]);
var algorithmName = bytes.Skip(4).Take((int)length).GetSshString();
var data = bytes.Skip(4 + algorithmName.Length);
CryptoPublicKey key = Settings.HostKeyAlgorithms[algorithmName]();
key.Load(data);
return key.VerifySignature(this.ExchangeHash, this.Signature.GetSshBytes());
}
protected void SendMessage(Message message)
{
this.Session.SendMessage(message);
}
private IEnumerable<byte> CalculateHash()
{
var hashData = new _ExchangeHashData
{
ClientVersion = this.Session.ClientVersion,
ServerVersion = this.Session.ServerVersion,
ClientPayload = this.ClientPayload,
ServerPayload = this.ServerPayload,
HostKey = this.HostKey,
ClientExchangeValue = this.ClientExchangeValue,
ServerExchangeValue = this.ServerExchangeValue,
SharedKey = this.SharedKey,
}.GetBytes();
return this.Hash(hashData);
}
private IEnumerable<byte> GenerateSessionKey(BigInteger sharedKey, IEnumerable<byte> exchangeHash, IEnumerable<byte> key, int size)
{
var result = new List<byte>(key);
@@ -356,58 +310,48 @@ namespace Renci.SshClient.Security
}.GetBytes();
}
private class _ExchangeHashData : SshData
#region IDisposable Members
private bool disposed = false;
public void Dispose()
{
public string ServerVersion { get; set; }
Dispose(true);
public string ClientVersion { get; set; }
GC.SuppressFinalize(this);
}
public string ClientPayload { get; set; }
public string ServerPayload { get; set; }
public string HostKey { get; set; }
public UInt32? MinimumGroupSize { get; set; }
public UInt32? PreferredGroupSize { get; set; }
public UInt32? MaximumGroupSize { get; set; }
public IEnumerable<byte> Prime { get; set; }
public BigInteger ClientExchangeValue { get; set; }
public BigInteger ServerExchangeValue { get; set; }
public BigInteger SharedKey { get; set; }
protected override void LoadData()
private void Dispose(bool disposing)
{
// Check to see if Dispose has already been called.
if (!this.disposed)
{
throw new System.NotImplementedException();
}
// If disposing equals true, dispose all managed
// and unmanaged resources.
if (disposing)
{
// Dispose managed resources.
if (this._waitHandle != null)
{
this._waitHandle.Dispose();
}
}
protected override void SaveData()
{
this.Write(this.ClientVersion);
this.Write(this.ServerVersion);
this.Write(this.ClientPayload);
this.Write(this.ServerPayload);
this.Write(this.HostKey);
if (this.MinimumGroupSize.HasValue)
this.Write(this.MinimumGroupSize.Value);
if (this.PreferredGroupSize.HasValue)
this.Write(this.PreferredGroupSize.Value);
if (this.MaximumGroupSize.HasValue)
this.Write(this.MaximumGroupSize.Value);
if (this.Prime != null)
this.Write(this.Prime);
this.Write(this.ClientExchangeValue);
this.Write(this.ServerExchangeValue);
this.Write(this.SharedKey);
// Note disposing has been done.
disposed = true;
}
}
~KeyExchange()
{
// 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 _SessionKeyGeneration : SshData
{
public BigInteger SharedKey { get; set; }
@@ -0,0 +1,61 @@
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Renci.SshClient.Messages;
namespace Renci.SshClient.Security
{
internal abstract class KeyExchangeAlgorithm : Algorithm
{
public BigInteger SharedKey { get; protected set; }
private IEnumerable<byte> _exchangeHash;
/// <summary>
/// Gets the exchange hash.
/// </summary>
/// <value>The exchange hash.</value>
public IEnumerable<byte> ExchangeHash
{
get
{
if (this._exchangeHash == null)
{
this._exchangeHash = this.CalculateHash();
}
return this._exchangeHash;
}
}
protected Session Session { get; set; }
public KeyExchangeAlgorithm(Session session)
{
this.Session = session;
}
public abstract bool ValidateExchangeHash();
public abstract void HandleMessage<T>(T message) where T : Message;
protected abstract IEnumerable<byte> CalculateHash();
protected IEnumerable<byte> Hash(IEnumerable<byte> hashBytes)
{
using (var md = new System.Security.Cryptography.SHA1CryptoServiceProvider())
{
using (var cs = new System.Security.Cryptography.CryptoStream(System.IO.Stream.Null, md, System.Security.Cryptography.CryptoStreamMode.Write))
{
var hashData = hashBytes.ToArray();
cs.Write(hashData, 0, hashData.Length);
cs.Close();
return md.Hash;
}
}
}
protected void SendMessage(Message message)
{
this.Session.SendMessage(message);
}
}
}
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Security.Cryptography;
@@ -8,7 +9,7 @@ using Renci.SshClient.Messages.Transport;
namespace Renci.SshClient.Security
{
internal class KeyExchangeDiffieHellman : KeyExchange
internal class KeyExchangeDiffieHellman : KeyExchangeAlgorithm
{
private static RNGCryptoServiceProvider _randomizer = new System.Security.Cryptography.RNGCryptoServiceProvider();
@@ -32,6 +33,18 @@ namespace Renci.SshClient.Security
private static BigInteger _group = new BigInteger(new byte[] { 2 });
private string _clientPayload;
private string _serverPayload;
private BigInteger _clientExchangeValue;
private BigInteger _serverExchangeValue;
private string _hostKey;
private string _signature;
private BigInteger _randomValue;
public override string Name
@@ -48,9 +61,38 @@ namespace Renci.SshClient.Security
{
}
public override void Start(KeyExchangeInitMessage message)
public override void HandleMessage<T>(T message)
{
base.Start(message);
this.HandleMessage((dynamic)message);
}
public override bool ValidateExchangeHash()
{
var exchangeHash = this.CalculateHash();
var hostKey = this._hostKey;
var signature = this._signature;
var bytes = hostKey.GetSshBytes();
var length = (uint)(hostKey[0] << 24 | hostKey[1] << 16 | hostKey[2] << 8 | hostKey[3]);
var algorithmName = bytes.Skip(4).Take((int)length).GetSshString();
var data = bytes.Skip(4 + algorithmName.Length);
CryptoPublicKey key = Settings.HostKeyAlgorithms[algorithmName]();
key.Load(data);
return key.VerifySignature(exchangeHash, signature.GetSshBytes());
}
private void HandleMessage(KeyExchangeInitMessage message)
{
this._serverPayload = message.GetBytes().GetSshString();
this._clientPayload = this.Session.ClientInitMessage.GetBytes().GetSshString();
// TODO: Calculate random value correctly, enforce limits
var clientExchangeValue = BigInteger.Zero;
@@ -60,65 +102,94 @@ namespace Renci.SshClient.Security
clientExchangeValue = System.Numerics.BigInteger.ModPow(KeyExchangeDiffieHellman._group, this._randomValue, KeyExchangeDiffieHellman._prime);
}
this.ServerPayload = message.GetBytes().GetSshString();
this.ClientExchangeValue = clientExchangeValue;
this._clientExchangeValue = clientExchangeValue;
// Register expected message replies
this.Session.RegisterMessageType<KeyExchangeDhReplyMessage>(MessageTypes.KeyExchangeDhReply);
this.SendMessage(new KeyExchangeDhInitMessage
{
E = this.ClientExchangeValue,
E = this._clientExchangeValue,
});
this.Session.MessageReceived += SessionInfo_MessageReceived;
}
public override void Finish()
{
base.Finish();
this.Session.MessageReceived -= SessionInfo_MessageReceived;
}
private void SessionInfo_MessageReceived(object sender, MessageReceivedEventArgs e)
{
this.HandleMessage((dynamic)e.Message);
}
private void HandleMessage<T>(T message) where T : Message, new()
{
// Do nothing, handle only known messages
}
/// <summary>
/// Handles the KeyExchangeDhReplyMessage message.
/// </summary>
/// <param name="message">The message.</param>
private void HandleMessage(KeyExchangeDhReplyMessage message)
{
// Unregister message once received
this.Session.UnRegisterMessageType(MessageTypes.KeyExchangeDhReply);
var sharedKey = System.Numerics.BigInteger.ModPow(message.F, this._randomValue, KeyExchangeDiffieHellman._prime);
this._serverExchangeValue = message.F;
this._hostKey = message.HostKey;
this.SharedKey = System.Numerics.BigInteger.ModPow(message.F, this._randomValue, KeyExchangeDiffieHellman._prime);
this._signature = message.Signature;
}
this.ServerExchangeValue = message.F;
this.HostKey = message.HostKey;
this.SharedKey = sharedKey;
this.Signature = message.Signature;
// Validate hash value
if (this.ValidateExchangeHash())
protected override IEnumerable<byte> CalculateHash()
{
var hashData = new _ExchangeHashData
{
this.IsSuccessed = true;
this.SendMessage(new NewKeysMessage());
ClientVersion = this.Session.ClientVersion,
ServerVersion = this.Session.ServerVersion,
ClientPayload = this._clientPayload,
ServerPayload = this._serverPayload,
HostKey = this._hostKey,
ClientExchangeValue = this._clientExchangeValue,
ServerExchangeValue = this._serverExchangeValue,
SharedKey = this.SharedKey,
}.GetBytes();
return this.Hash(hashData);
}
private class _ExchangeHashData : SshData
{
public string ServerVersion { get; set; }
public string ClientVersion { get; set; }
public string ClientPayload { get; set; }
public string ServerPayload { get; set; }
public string HostKey { get; set; }
public UInt32? MinimumGroupSize { get; set; }
public UInt32? PreferredGroupSize { get; set; }
public UInt32? MaximumGroupSize { get; set; }
public IEnumerable<byte> Prime { get; set; }
public BigInteger ClientExchangeValue { get; set; }
public BigInteger ServerExchangeValue { get; set; }
public BigInteger SharedKey { get; set; }
protected override void LoadData()
{
throw new System.NotImplementedException();
}
else
protected override void SaveData()
{
this.IsSuccessed = false;
this.RaiseFailed("Key negotiationed failed.");
this.Write(this.ClientVersion);
this.Write(this.ServerVersion);
this.Write(this.ClientPayload);
this.Write(this.ServerPayload);
this.Write(this.HostKey);
if (this.MinimumGroupSize.HasValue)
this.Write(this.MinimumGroupSize.Value);
if (this.PreferredGroupSize.HasValue)
this.Write(this.PreferredGroupSize.Value);
if (this.MaximumGroupSize.HasValue)
this.Write(this.MaximumGroupSize.Value);
if (this.Prime != null)
this.Write(this.Prime);
this.Write(this.ClientExchangeValue);
this.Write(this.ServerExchangeValue);
this.Write(this.SharedKey);
}
}
}
+78 -133
View File
@@ -62,11 +62,6 @@ namespace Renci.SshClient
/// </summary>
private UInt32 _inboundPacketSequence = 0;
/// <summary>
/// WaitHandle to signal that key exchange was finished, weither it was succesfull or not.
/// </summary>
private EventWaitHandle _keyExhangedFinishedWaitHandle;
/// <summary>
/// WaitHandle to signale that last service request was accepted
/// </summary>
@@ -107,42 +102,6 @@ namespace Renci.SshClient
/// </summary>
private BlockingStack<uint> _channelNumbers;
/// <summary>
/// Gets or sets the HMAC algorithm to use when receiving message from the server.
/// </summary>
/// <value>The server mac.</value>
private HMAC _serverMac;
/// <summary>
/// Gets or sets the HMAC algorithm to use when sending message to the server.
/// </summary>
/// <value>The client mac.</value>
private HMAC _clientMac;
/// <summary>
/// Gets or sets the client cipher which used to encrypt messages sent to server.
/// </summary>
/// <value>The client cipher.</value>
private Cipher _clientCipher;
/// <summary>
/// Gets or sets the server cipher which used to decrypt messages sent by server.
/// </summary>
/// <value>The server cipher.</value>
private Cipher _serverCipher;
/// <summary>
/// Gets or sets the compression algorithm to use when receiving message from the server.
/// </summary>
/// <value>The server decompression.</value>
private Compression _serverDecompression;
/// <summary>
/// Gets or sets the compression algorithm to use when sending message to the server.
/// </summary>
/// <value>The client compression.</value>
private Compression _clientCompression;
/// <summary>
/// Gets a value indicating whether socket connected.
/// </summary>
@@ -166,7 +125,44 @@ namespace Renci.SshClient
/// Gets or sets the session id.
/// </summary>
/// <value>The session id.</value>
public IEnumerable<byte> SessionId { get; private set; }
public IEnumerable<byte> SessionId
{
get
{
return this._keyExhcange.SessionId;
}
}
private Message _clientInitMessage;
/// <summary>
/// Gets the client init message.
/// </summary>
/// <value>The client init message.</value>
public Message ClientInitMessage
{
get
{
if (this._clientInitMessage == null)
{
this._clientInitMessage = new KeyExchangeInitMessage()
{
KeyExchangeAlgorithms = Settings.KeyExchangeAlgorithms.Keys,
ServerHostKeyAlgorithms = Settings.HostKeyAlgorithms.Keys,
EncryptionAlgorithmsClientToServer = Settings.Encryptions.Keys,
EncryptionAlgorithmsServerToClient = Settings.Encryptions.Keys,
MacAlgorithmsClientToSserver = Settings.HmacAlgorithms.Keys,
MacAlgorithmsServerToClient = Settings.HmacAlgorithms.Keys,
CompressionAlgorithmsClientToServer = new string[] { "none" },
CompressionAlgorithmsServerToClient = new string[] { "none" },
LanguagesClientToServer = new string[] { string.Empty },
LanguagesServerToClient = new string[] { string.Empty },
FirstKexPacketFollows = false,
Reserved = 0,
};
}
return this._clientInitMessage;
}
}
/// <summary>
/// Gets or sets the server version string.
@@ -266,7 +262,6 @@ namespace Renci.SshClient
this._channelNumbers.Push((uint)i - 1);
}
var ep = new IPEndPoint(Dns.GetHostAddresses(connectionInfo.Host)[0], connectionInfo.Port);
this._socket = new Socket(ep.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
@@ -319,11 +314,14 @@ namespace Renci.SshClient
this.RegisterMessageType<KeyExchangeInitMessage>(MessageTypes.KeyExchangeInit);
this.RegisterMessageType<NewKeysMessage>(MessageTypes.NewKeys);
this._keyExhcange = new KeyExchange(this);
// Start incoming request listener
this._messageListener = Task.Factory.StartNew(() => { this.MessageListener(); });
// Wait for key exchange to be completed
this.WaitHandle(this._keyExhangedFinishedWaitHandle);
this.WaitHandle(this._keyExhcange.WaitHandle);
// If sessionId is not set then its not connected
if (this.SessionId == null)
@@ -428,8 +426,8 @@ namespace Renci.SshClient
if (!this._socket.Connected)
return;
// Messages can be sent by different thread so we need to synchronize it
var paddingMultiplier = this._clientCipher == null ? (byte)8 : (byte)this._clientCipher.BlockSize; // Should be recalculate base on cipher min lenght if sipher specified
// Messages can be sent by different thread so we need to synchronize it
var paddingMultiplier = this._keyExhcange.ClientCipher == null ? (byte)8 : (byte)this._keyExhcange.ClientCipher.BlockSize; // Should be recalculate base on cipher min lenght if sipher specified
var messageData = message.GetBytes();
@@ -474,15 +472,15 @@ namespace Renci.SshClient
// Encrypt packet data
var encryptedData = packetData.ToList();
if (this._clientCipher != null)
if (this._keyExhcange.ClientCipher != null)
{
encryptedData = new List<byte>(this._clientCipher.Encrypt(packetData));
encryptedData = new List<byte>(this._keyExhcange.ClientCipher.Encrypt(packetData));
}
// Add message authentication code (MAC)
if (this._clientMac != null)
if (this._keyExhcange.ClientMac != null)
{
var hash = this._clientMac.ComputeHash(hashData.ToArray());
var hash = this._keyExhcange.ClientMac.ComputeHash(hashData.ToArray());
encryptedData.AddRange(hash);
}
@@ -513,18 +511,18 @@ namespace Renci.SshClient
List<byte> decryptedData;
var blockSize = this._serverCipher == null ? (byte)8 : (byte)this._serverCipher.BlockSize;
var blockSize = this._keyExhcange.ServerCipher == null ? (byte)8 : (byte)this._keyExhcange.ServerCipher.BlockSize;
// Read packet lenght first
var data = new List<byte>(this.Read(blockSize));
if (this._serverCipher == null)
if (this._keyExhcange.ServerCipher == null)
{
decryptedData = data.ToList();
}
else
{
decryptedData = new List<byte>(this._serverCipher.Decrypt(data));
decryptedData = new List<byte>(this._keyExhcange.ServerCipher.Decrypt(data));
}
var packetLength = (uint)(decryptedData[0] << 24 | decryptedData[1] << 16 | decryptedData[2] << 8 | decryptedData[3]);
@@ -536,26 +534,26 @@ namespace Renci.SshClient
// Read rest of the packet data
int bytesToRead = (int)(packetLength - (blockSize - 4));
if (this._serverCipher == null)
if (this._keyExhcange.ServerCipher == null)
{
decryptedData.AddRange(this.Read(bytesToRead));
}
else
{
decryptedData.AddRange(this._serverCipher.Decrypt(this.Read(bytesToRead)));
decryptedData.AddRange(this._keyExhcange.ServerCipher.Decrypt(this.Read(bytesToRead)));
}
// Validate message against MAC
if (this._serverMac != null)
// Validate message against MAC
if (this._keyExhcange.ServerMac != null)
{
var serverHash = this.Read(this._serverMac.HashSize / 8);
var serverHash = this.Read(this._keyExhcange.ServerMac.HashSize / 8);
var clientHashData = new List<byte>();
clientHashData.AddRange(BitConverter.GetBytes(this._inboundPacketSequence).Reverse());
clientHashData.AddRange(decryptedData);
// Calculate packet hash
var clientHash = this._serverMac.ComputeHash(clientHashData.ToArray());
var clientHash = this._keyExhcange.ServerMac.ComputeHash(clientHashData.ToArray());
if (!serverHash.SequenceEqual(clientHash))
{
@@ -563,12 +561,11 @@ namespace Renci.SshClient
}
}
// TODO: Issue new keys after x number of packets
this._inboundPacketSequence++;
var paddingLength = decryptedData[4];
// TODO: Decrypt message payload
// TODO: Inflate message payload
var payload = decryptedData.Skip(5).Take((int)(packetLength - paddingLength - 1));
@@ -623,40 +620,7 @@ namespace Renci.SshClient
protected virtual void HandleMessage(KeyExchangeInitMessage message)
{
this._keyExhangedFinishedWaitHandle.Reset();
if (message.FirstKexPacketFollows)
{
// TODO: Expect guess packet
throw new NotImplementedException("Guess packets are not supported.");
}
// Create key exchange algorithm
this._keyExhcange = KeyExchange.Create(message, this);
this._keyExhcange.Failed += delegate(object sender, KeyExchangeFailedEventArgs e)
{
this.Disconnect(DisconnectReasonCodes.KeyExchangeFailed, e.Message);
throw new InvalidOperationException(e.Message);
};
this._keyExhcange.Start(message);
}
protected virtual void HandleMessage(NewKeysMessage message)
{
this._keyExhcange.Finish();
this.SessionId = this._keyExhcange.SessionId;
// Update encryption and decryption algorithm
this._serverMac = this._keyExhcange.ServerMac;
this._clientMac = this._keyExhcange.ClientMac;
this._clientCipher = this._keyExhcange.ClientCipher;
this._serverCipher = this._keyExhcange.ServerCipher;
this._serverDecompression = this._keyExhcange.ServerDecompression;
this._clientCompression = this._keyExhcange.ClientCompression;
this._keyExhangedFinishedWaitHandle.Set();
this._keyExhcange.HandleMessage(message);
}
#endregion
@@ -744,7 +708,7 @@ namespace Renci.SshClient
Thread.Sleep(30);
}
else
throw exp; // any serious error occurr
throw; // any serious error occurr
}
} while (received < length);
@@ -777,21 +741,12 @@ namespace Renci.SshClient
Thread.Sleep(30);
}
else
throw ex; // any serious error occurr
throw; // any serious error occurr
}
} while (sent < length);
}
/// <summary>
/// Initiates new key request by the client
/// </summary>
protected void RequestNewKeys()
{
// TODO: Create method to issue new keys when required
//this._keyExhcange.Start();
}
#region Message loading functions
private delegate T LoadFunc<out T>(IEnumerable<byte> data);
@@ -852,35 +807,17 @@ namespace Renci.SshClient
this._outboundPacketSequence = 0;
this._inboundPacketSequence = 0;
this._openChannels = new Dictionary<uint, Channel>();
this._keyExhangedFinishedWaitHandle = new AutoResetEvent(false);
this._serviceAccepted = new AutoResetEvent(false);
this._exceptionWaitHandle = new AutoResetEvent(false);
this._listenerWaitHandle = new AutoResetEvent(false);
this._channelNumbers = new BlockingStack<uint>();
this._exceptionToThrow = null;
this.SessionId = null;
this.ServerVersion = null;
this._keyExhcange = null;
this._serverMac = null;
this._clientMac = null;
this._clientCipher = null;
this._serverCipher = null;
this._serverDecompression = null;
this._clientCompression = null;
this._isAuthenticated = false;
this._isDisconnecting = false;
}
private bool ValidateHash(List<byte> decryptedData, byte[] serverHash, uint packetSequence)
{
var clientHashData = new List<byte>();
clientHashData.AddRange(BitConverter.GetBytes(packetSequence).Reverse());
clientHashData.AddRange(decryptedData);
var clientHash = this._serverMac.ComputeHash(clientHashData.ToArray());
return serverHash.SequenceEqual(clientHash);
}
/// <summary>
/// Perfom neccesary cleanup when client disconects from the server
/// </summary>
@@ -918,15 +855,28 @@ namespace Renci.SshClient
{
try
{
dynamic message = this.ReceiveMessage();
var message = this.ReceiveMessage();
if (message == null)
{
throw new NullReferenceException("The 'message' variable cannot be null");
}
// Handle session messages first
this.HandleMessage(message);
else if (message is DisconnectMessage)
{
// Always handle disconnect message first
this.HandleMessage(message);
break; // Exit message listener loop, no more messages should be handled
}
else if (this._keyExhcange.InProgress)
{
this._keyExhcange.HandleMessage((dynamic)message);
continue; // Get next message, all non kexinit messages should be ignored
}
else
{
// Handle session messages first
this.HandleMessage((dynamic)message);
}
// Raise an event that message received
this.RaiseMessageReceived(this, new MessageReceivedEventArgs(message));
@@ -1011,11 +961,6 @@ namespace Renci.SshClient
this._socket.Dispose();
}
if (this._keyExhangedFinishedWaitHandle != null)
{
this._keyExhangedFinishedWaitHandle.Dispose();
}
if (this._serviceAccepted != null)
{
this._serviceAccepted.Dispose();
+2 -3
View File
@@ -8,7 +8,7 @@ namespace Renci.SshClient
{
internal static class Settings
{
public static IDictionary<string, Func<Session, KeyExchange>> KeyExchangeAlgorithms { get; private set; }
public static IDictionary<string, Func<Session, KeyExchangeAlgorithm>> KeyExchangeAlgorithms { get; private set; }
public static IDictionary<string, Func<Cipher>> Encryptions { get; private set; }
@@ -20,11 +20,10 @@ namespace Renci.SshClient
static Settings()
{
Settings.KeyExchangeAlgorithms = new Dictionary<string, Func<Session, KeyExchange>>()
Settings.KeyExchangeAlgorithms = new Dictionary<string, Func<Session, KeyExchangeAlgorithm>>()
{
{"diffie-hellman-group1-sha1", (a) => { return new KeyExchangeDiffieHellman(a);}}
//"diffie-hellman-group-exchange-sha1"
};
Settings.Encryptions = new Dictionary<string, Func<Cipher>>()