diff --git a/Renci.SshClient/Renci.SshClient/Channels/Channel.cs b/Renci.SshClient/Renci.SshClient/Channels/Channel.cs
index a9c2a62c..bacdae26 100644
--- a/Renci.SshClient/Renci.SshClient/Channels/Channel.cs
+++ b/Renci.SshClient/Renci.SshClient/Channels/Channel.cs
@@ -382,6 +382,10 @@ namespace Renci.SshClient.Channels
{
this._channelClosedWaitHandle.Dispose();
}
+ if (this._channelWindowAdjustWaitHandle != null)
+ {
+ this._channelWindowAdjustWaitHandle.Dispose();
+ }
this.OnDisposing();
}
diff --git a/Renci.SshClient/Renci.SshClient/Channels/ChannelSftp.cs b/Renci.SshClient/Renci.SshClient/Channels/ChannelSftp.cs
index 25960ec2..8516579f 100644
--- a/Renci.SshClient/Renci.SshClient/Channels/ChannelSftp.cs
+++ b/Renci.SshClient/Renci.SshClient/Channels/ChannelSftp.cs
@@ -41,7 +41,7 @@ namespace Renci.SshClient.Channels
}
public ChannelSftp(Session session, uint channelId)
- : base(session, channelId, 0x100000, 0x4000)
+ : base(session, channelId, 0x100000, 0x0100)
{
}
diff --git a/Renci.SshClient/Renci.SshClient/Common/SshException.cs b/Renci.SshClient/Renci.SshClient/Common/SshException.cs
index f888e24a..973579ad 100644
--- a/Renci.SshClient/Renci.SshClient/Common/SshException.cs
+++ b/Renci.SshClient/Renci.SshClient/Common/SshException.cs
@@ -3,6 +3,7 @@ using System.Runtime.Serialization;
namespace Renci.SshClient.Common
{
+ [Serializable]
public class SshException : Exception
{
public bool ShouldDisconnect { get; private set; }
diff --git a/Renci.SshClient/Renci.SshClient/Renci.SshClient.csproj b/Renci.SshClient/Renci.SshClient/Renci.SshClient.csproj
index dc9c735a..2fcf1482 100644
--- a/Renci.SshClient/Renci.SshClient/Renci.SshClient.csproj
+++ b/Renci.SshClient/Renci.SshClient/Renci.SshClient.csproj
@@ -117,6 +117,7 @@
+
diff --git a/Renci.SshClient/Renci.SshClient/Security/KeyExchange.cs b/Renci.SshClient/Renci.SshClient/Security/KeyExchange.cs
index 8e969b27..5a39c7e5 100644
--- a/Renci.SshClient/Renci.SshClient/Security/KeyExchange.cs
+++ b/Renci.SshClient/Renci.SshClient/Security/KeyExchange.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
{
- ///
- /// Creates the key exchange algorithm to be used for key exchange.
- ///
- /// The message.
- ///
- 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;
///
/// Specifies negotiated algorithm to encrypt information when sent to the server
@@ -55,95 +34,110 @@ namespace Renci.SshClient.Security
///
private Func, HMAC> _serverHmacAlgorithm;
- private IEnumerable _exchangeHash;
///
- /// Gets hash value
+ /// Gets the key exchange algorithm name.
///
- public IEnumerable ExchangeHash
+ /// Key exchange algorithm name or empty if name not yet defined.
+ 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 SessionId { get; set; }
+ private EventWaitHandle _waitHandle = new EventWaitHandle(false, EventResetMode.ManualReset);
- public HMAC ServerMac { get; set; }
+ ///
+ /// Gets the wait handle that signals that key exchange completed
+ ///
+ /// The wait handle.
+ public EventWaitHandle WaitHandle
+ {
+ get
+ {
+ return this._waitHandle;
+ }
+ }
- public HMAC ClientMac { get; set; }
+ ///
+ /// Gets or sets the session id.
+ ///
+ /// The session id.
+ public IEnumerable SessionId { get; private set; }
- public Cipher ClientCipher { get; set; }
+ ///
+ /// Gets or sets the server mac algorithm to use.
+ ///
+ /// The server mac.
+ public HMAC ServerMac { get; private set; }
- public Cipher ServerCipher { get; set; }
+ ///
+ /// Gets or sets the client mac algorithm to use.
+ ///
+ /// The client mac.
+ public HMAC ClientMac { get; private set; }
- public Compression ServerDecompression { get; set; }
+ ///
+ /// Gets or sets the client cipher algorithm to use.
+ ///
+ /// The client cipher.
+ public Cipher ClientCipher { get; private set; }
- public Compression ClientCompression { get; set; }
+ ///
+ /// Gets or sets the server cipher algorithm to use.
+ ///
+ /// The server cipher.
+ 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; }
+ ///
+ /// Gets or sets a value indicating whether key exchange is in progress.
+ ///
+ /// true if [in progress]; otherwise, false.
+ public bool InProgress { get; protected set; }
+
+ ///
+ /// Gets or sets the session.
+ ///
+ /// The session.
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 Completed;
-
- public event EventHandler Failed;
-
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The session.
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();
}
- ///
- /// Raises the Completed event.
- ///
- /// The session id.
- /// The decryption to be used.
- /// The encryption to be used.
- /// The server decompression.
- /// The client compression.
- /// The server mac.
- /// The client mac.
- protected void RaiseCompleted()
+ public void HandleMessage(T message) where T : Message
{
- if (this.Completed != null)
- {
- this.Completed(this, new KeyExchangeCompletedEventArgs());
- }
+ this._keyExchangeAlgorithm.HandleMessage(message);
}
- ///
- /// Raises the Failed event.
- ///
- /// The fail reason message.
- 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 Hash(IEnumerable hashBytes)
+ private IEnumerable Hash(IEnumerable 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 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 GenerateSessionKey(BigInteger sharedKey, IEnumerable exchangeHash, IEnumerable key, int size)
{
var result = new List(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 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; }
diff --git a/Renci.SshClient/Renci.SshClient/Security/KeyExchangeAlgorithm.cs b/Renci.SshClient/Renci.SshClient/Security/KeyExchangeAlgorithm.cs
new file mode 100644
index 00000000..d001c5fc
--- /dev/null
+++ b/Renci.SshClient/Renci.SshClient/Security/KeyExchangeAlgorithm.cs
@@ -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 _exchangeHash;
+ ///
+ /// Gets the exchange hash.
+ ///
+ /// The exchange hash.
+ public IEnumerable 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 message) where T : Message;
+
+ protected abstract IEnumerable CalculateHash();
+
+ protected IEnumerable Hash(IEnumerable 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);
+ }
+ }
+}
diff --git a/Renci.SshClient/Renci.SshClient/Security/KeyExchangeDiffieHellman.cs b/Renci.SshClient/Renci.SshClient/Security/KeyExchangeDiffieHellman.cs
index 17884a98..e933b5b5 100644
--- a/Renci.SshClient/Renci.SshClient/Security/KeyExchangeDiffieHellman.cs
+++ b/Renci.SshClient/Renci.SshClient/Security/KeyExchangeDiffieHellman.cs
@@ -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 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(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 message) where T : Message, new()
- {
- // Do nothing, handle only known messages
- }
-
- ///
- /// Handles the KeyExchangeDhReplyMessage message.
- ///
- /// The message.
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 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 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);
}
}
}
diff --git a/Renci.SshClient/Renci.SshClient/Session.cs b/Renci.SshClient/Renci.SshClient/Session.cs
index fae528bf..fdd52b6a 100644
--- a/Renci.SshClient/Renci.SshClient/Session.cs
+++ b/Renci.SshClient/Renci.SshClient/Session.cs
@@ -62,11 +62,6 @@ namespace Renci.SshClient
///
private UInt32 _inboundPacketSequence = 0;
- ///
- /// WaitHandle to signal that key exchange was finished, weither it was succesfull or not.
- ///
- private EventWaitHandle _keyExhangedFinishedWaitHandle;
-
///
/// WaitHandle to signale that last service request was accepted
///
@@ -107,42 +102,6 @@ namespace Renci.SshClient
///
private BlockingStack _channelNumbers;
- ///
- /// Gets or sets the HMAC algorithm to use when receiving message from the server.
- ///
- /// The server mac.
- private HMAC _serverMac;
-
- ///
- /// Gets or sets the HMAC algorithm to use when sending message to the server.
- ///
- /// The client mac.
- private HMAC _clientMac;
-
- ///
- /// Gets or sets the client cipher which used to encrypt messages sent to server.
- ///
- /// The client cipher.
- private Cipher _clientCipher;
-
- ///
- /// Gets or sets the server cipher which used to decrypt messages sent by server.
- ///
- /// The server cipher.
- private Cipher _serverCipher;
-
- ///
- /// Gets or sets the compression algorithm to use when receiving message from the server.
- ///
- /// The server decompression.
- private Compression _serverDecompression;
-
- ///
- /// Gets or sets the compression algorithm to use when sending message to the server.
- ///
- /// The client compression.
- private Compression _clientCompression;
-
///
/// Gets a value indicating whether socket connected.
///
@@ -166,7 +125,44 @@ namespace Renci.SshClient
/// Gets or sets the session id.
///
/// The session id.
- public IEnumerable SessionId { get; private set; }
+ public IEnumerable SessionId
+ {
+ get
+ {
+ return this._keyExhcange.SessionId;
+ }
+ }
+
+ private Message _clientInitMessage;
+ ///
+ /// Gets the client init message.
+ ///
+ /// The client init message.
+ 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;
+ }
+ }
///
/// 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(MessageTypes.KeyExchangeInit);
this.RegisterMessageType(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(this._clientCipher.Encrypt(packetData));
+ encryptedData = new List(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 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(this.Read(blockSize));
- if (this._serverCipher == null)
+ if (this._keyExhcange.ServerCipher == null)
{
decryptedData = data.ToList();
}
else
{
- decryptedData = new List(this._serverCipher.Decrypt(data));
+ decryptedData = new List(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();
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);
}
- ///
- /// Initiates new key request by the client
- ///
- protected void RequestNewKeys()
- {
- // TODO: Create method to issue new keys when required
- //this._keyExhcange.Start();
- }
-
#region Message loading functions
private delegate T LoadFunc(IEnumerable data);
@@ -852,35 +807,17 @@ namespace Renci.SshClient
this._outboundPacketSequence = 0;
this._inboundPacketSequence = 0;
this._openChannels = new Dictionary();
- this._keyExhangedFinishedWaitHandle = new AutoResetEvent(false);
this._serviceAccepted = new AutoResetEvent(false);
this._exceptionWaitHandle = new AutoResetEvent(false);
this._listenerWaitHandle = new AutoResetEvent(false);
this._channelNumbers = new BlockingStack();
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 decryptedData, byte[] serverHash, uint packetSequence)
- {
- var clientHashData = new List();
- clientHashData.AddRange(BitConverter.GetBytes(packetSequence).Reverse());
- clientHashData.AddRange(decryptedData);
-
- var clientHash = this._serverMac.ComputeHash(clientHashData.ToArray());
- return serverHash.SequenceEqual(clientHash);
- }
-
///
/// Perfom neccesary cleanup when client disconects from the server
///
@@ -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();
diff --git a/Renci.SshClient/Renci.SshClient/Settings.cs b/Renci.SshClient/Renci.SshClient/Settings.cs
index a873cc3c..5b7d43a2 100644
--- a/Renci.SshClient/Renci.SshClient/Settings.cs
+++ b/Renci.SshClient/Renci.SshClient/Settings.cs
@@ -8,7 +8,7 @@ namespace Renci.SshClient
{
internal static class Settings
{
- public static IDictionary> KeyExchangeAlgorithms { get; private set; }
+ public static IDictionary> KeyExchangeAlgorithms { get; private set; }
public static IDictionary> Encryptions { get; private set; }
@@ -20,11 +20,10 @@ namespace Renci.SshClient
static Settings()
{
- Settings.KeyExchangeAlgorithms = new Dictionary>()
+ Settings.KeyExchangeAlgorithms = new Dictionary>()
{
{"diffie-hellman-group1-sha1", (a) => { return new KeyExchangeDiffieHellman(a);}}
//"diffie-hellman-group-exchange-sha1"
-
};
Settings.Encryptions = new Dictionary>()