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>()