| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524 |
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Security.Cryptography;
- using Renci.SshNet.Common;
- using Renci.SshNet.Compression;
- using Renci.SshNet.Messages;
- using Renci.SshNet.Messages.Transport;
- using Renci.SshNet.Security.Cryptography;
- namespace Renci.SshNet.Security
- {
- /// <summary>
- /// Represents base class for different key exchange algorithm implementations
- /// </summary>
- public abstract class KeyExchange : Algorithm, IKeyExchange
- {
- private CipherInfo _clientCipherInfo;
- private CipherInfo _serverCipherInfo;
- private HashInfo _clientHashInfo;
- private HashInfo _serverHashInfo;
- private Type _compressionType;
- private Type _decompressionType;
- /// <summary>
- /// Gets or sets the session.
- /// </summary>
- /// <value>
- /// The session.
- /// </value>
- protected Session Session { get; private set; }
- /// <summary>
- /// Gets or sets key exchange shared key.
- /// </summary>
- /// <value>
- /// The shared key.
- /// </value>
- public BigInteger SharedKey { get; protected set; }
- private byte[] _exchangeHash;
- /// <summary>
- /// Gets the exchange hash.
- /// </summary>
- /// <value>The exchange hash.</value>
- public byte[] ExchangeHash
- {
- get
- {
- if (_exchangeHash == null)
- {
- _exchangeHash = CalculateHash();
- }
- return _exchangeHash;
- }
- }
- /// <summary>
- /// Occurs when host key received.
- /// </summary>
- public event EventHandler<HostKeyEventArgs> HostKeyReceived;
- /// <summary>
- /// Starts key exchange algorithm
- /// </summary>
- /// <param name="session">The session.</param>
- /// <param name="message">Key exchange init message.</param>
- public virtual void Start(Session session, KeyExchangeInitMessage message)
- {
- Session = session;
- SendMessage(session.ClientInitMessage);
- // Determine encryption algorithm
- var clientEncryptionAlgorithmName = (from b in session.ConnectionInfo.Encryptions.Keys
- from a in message.EncryptionAlgorithmsClientToServer
- where a == b
- select a).FirstOrDefault();
- if (string.IsNullOrEmpty(clientEncryptionAlgorithmName))
- {
- throw new SshConnectionException("Client encryption algorithm not found", DisconnectReason.KeyExchangeFailed);
- }
- session.ConnectionInfo.CurrentClientEncryption = clientEncryptionAlgorithmName;
- // Determine encryption algorithm
- var serverDecryptionAlgorithmName = (from b in session.ConnectionInfo.Encryptions.Keys
- from a in message.EncryptionAlgorithmsServerToClient
- where a == b
- select a).FirstOrDefault();
- if (string.IsNullOrEmpty(serverDecryptionAlgorithmName))
- {
- throw new SshConnectionException("Server decryption algorithm not found", DisconnectReason.KeyExchangeFailed);
- }
- session.ConnectionInfo.CurrentServerEncryption = serverDecryptionAlgorithmName;
- // Determine client hmac algorithm
- var clientHmacAlgorithmName = (from b in session.ConnectionInfo.HmacAlgorithms.Keys
- from a in message.MacAlgorithmsClientToServer
- where a == b
- select a).FirstOrDefault();
- if (string.IsNullOrEmpty(clientHmacAlgorithmName))
- {
- throw new SshConnectionException("Server HMAC algorithm not found", DisconnectReason.KeyExchangeFailed);
- }
- session.ConnectionInfo.CurrentClientHmacAlgorithm = clientHmacAlgorithmName;
- // Determine server hmac algorithm
- var serverHmacAlgorithmName = (from b in session.ConnectionInfo.HmacAlgorithms.Keys
- from a in message.MacAlgorithmsServerToClient
- where a == b
- select a).FirstOrDefault();
- if (string.IsNullOrEmpty(serverHmacAlgorithmName))
- {
- throw new SshConnectionException("Server HMAC algorithm not found", DisconnectReason.KeyExchangeFailed);
- }
- session.ConnectionInfo.CurrentServerHmacAlgorithm = serverHmacAlgorithmName;
- // Determine compression algorithm
- var compressionAlgorithmName = (from b in session.ConnectionInfo.CompressionAlgorithms.Keys
- from a in message.CompressionAlgorithmsClientToServer
- where a == b
- select a).LastOrDefault();
- if (string.IsNullOrEmpty(compressionAlgorithmName))
- {
- throw new SshConnectionException("Compression algorithm not found", DisconnectReason.KeyExchangeFailed);
- }
- session.ConnectionInfo.CurrentClientCompressionAlgorithm = compressionAlgorithmName;
- // Determine decompression algorithm
- var decompressionAlgorithmName = (from b in session.ConnectionInfo.CompressionAlgorithms.Keys
- from a in message.CompressionAlgorithmsServerToClient
- where a == b
- select a).LastOrDefault();
- if (string.IsNullOrEmpty(decompressionAlgorithmName))
- {
- throw new SshConnectionException("Decompression algorithm not found", DisconnectReason.KeyExchangeFailed);
- }
- session.ConnectionInfo.CurrentServerCompressionAlgorithm = decompressionAlgorithmName;
- _clientCipherInfo = session.ConnectionInfo.Encryptions[clientEncryptionAlgorithmName];
- _serverCipherInfo = session.ConnectionInfo.Encryptions[serverDecryptionAlgorithmName];
- _clientHashInfo = session.ConnectionInfo.HmacAlgorithms[clientHmacAlgorithmName];
- _serverHashInfo = session.ConnectionInfo.HmacAlgorithms[serverHmacAlgorithmName];
- _compressionType = session.ConnectionInfo.CompressionAlgorithms[compressionAlgorithmName];
- _decompressionType = session.ConnectionInfo.CompressionAlgorithms[decompressionAlgorithmName];
- }
- /// <summary>
- /// Finishes key exchange algorithm.
- /// </summary>
- public virtual void Finish()
- {
- // Validate hash
- if (ValidateExchangeHash())
- {
- SendMessage(new NewKeysMessage());
- }
- else
- {
- throw new SshConnectionException("Key exchange negotiation failed.", DisconnectReason.KeyExchangeFailed);
- }
- }
- /// <summary>
- /// Creates the server side cipher to use.
- /// </summary>
- /// <returns>Server cipher.</returns>
- public Cipher CreateServerCipher()
- {
- // Resolve Session ID
- var sessionId = Session.SessionId ?? ExchangeHash;
- // Calculate server to client initial IV
- var serverVector = Hash(GenerateSessionKey(SharedKey, ExchangeHash, 'B', sessionId));
- // Calculate server to client encryption
- var serverKey = Hash(GenerateSessionKey(SharedKey, ExchangeHash, 'D', sessionId));
- serverKey = GenerateSessionKey(SharedKey, ExchangeHash, serverKey, _serverCipherInfo.KeySize / 8);
-
- // Create server cipher
- return _serverCipherInfo.Cipher(serverKey, serverVector);
- }
- /// <summary>
- /// Creates the client side cipher to use.
- /// </summary>
- /// <returns>Client cipher.</returns>
- public Cipher CreateClientCipher()
- {
- // Resolve Session ID
- var sessionId = Session.SessionId ?? ExchangeHash;
- // Calculate client to server initial IV
- var clientVector = Hash(GenerateSessionKey(SharedKey, ExchangeHash, 'A', sessionId));
- // Calculate client to server encryption
- var clientKey = Hash(GenerateSessionKey(SharedKey, ExchangeHash, 'C', sessionId));
- clientKey = GenerateSessionKey(SharedKey, ExchangeHash, clientKey, _clientCipherInfo.KeySize / 8);
- // Create client cipher
- return _clientCipherInfo.Cipher(clientKey, clientVector);
- }
- /// <summary>
- /// Creates the server side hash algorithm to use.
- /// </summary>
- /// <returns>Hash algorithm</returns>
- public HashAlgorithm CreateServerHash()
- {
- // Resolve Session ID
- var sessionId = Session.SessionId ?? ExchangeHash;
- var serverKey = Hash(GenerateSessionKey(SharedKey, ExchangeHash, 'F', sessionId));
- serverKey = GenerateSessionKey(SharedKey, ExchangeHash, serverKey, _serverHashInfo.KeySize / 8);
- //return serverHMac;
- return _serverHashInfo.HashAlgorithm(serverKey);
- }
- /// <summary>
- /// Creates the client side hash algorithm to use.
- /// </summary>
- /// <returns>Hash algorithm</returns>
- public HashAlgorithm CreateClientHash()
- {
- // Resolve Session ID
- var sessionId = Session.SessionId ?? ExchangeHash;
- var clientKey = Hash(GenerateSessionKey(SharedKey, ExchangeHash, 'E', sessionId));
-
- clientKey = GenerateSessionKey(SharedKey, ExchangeHash, clientKey, _clientHashInfo.KeySize / 8);
- //return clientHMac;
- return _clientHashInfo.HashAlgorithm(clientKey);
- }
- /// <summary>
- /// Creates the compression algorithm to use to deflate data.
- /// </summary>
- /// <returns>Compression method.</returns>
- public Compressor CreateCompressor()
- {
- if (_compressionType == null)
- return null;
- var compressor = _compressionType.CreateInstance<Compressor>();
- compressor.Init(Session);
- return compressor;
- }
- /// <summary>
- /// Creates the compression algorithm to use to inflate data.
- /// </summary>
- /// <returns>Compression method.</returns>
- public Compressor CreateDecompressor()
- {
- if (_compressionType == null)
- return null;
- var decompressor = _decompressionType.CreateInstance<Compressor>();
- decompressor.Init(Session);
- return decompressor;
- }
- /// <summary>
- /// Determines whether the specified host key can be trusted.
- /// </summary>
- /// <param name="host">The host algorithm.</param>
- /// <returns>
- /// <c>true</c> if the specified host can be trusted; otherwise, <c>false</c>.
- /// </returns>
- protected bool CanTrustHostKey(KeyHostAlgorithm host)
- {
- var handlers = HostKeyReceived;
- if (handlers != null)
- {
- var args = new HostKeyEventArgs(host);
- handlers(this, args);
- return args.CanTrust;
- }
- return true;
- }
- /// <summary>
- /// Validates the exchange hash.
- /// </summary>
- /// <returns>true if exchange hash is valid; otherwise false.</returns>
- protected abstract bool ValidateExchangeHash();
- /// <summary>
- /// Calculates key exchange hash value.
- /// </summary>
- /// <returns>Key exchange hash.</returns>
- protected abstract byte[] CalculateHash();
- /// <summary>
- /// Hashes the specified data bytes.
- /// </summary>
- /// <param name="hashData">The hash data.</param>
- /// <returns>
- /// Hashed bytes
- /// </returns>
- protected virtual byte[] Hash(byte[] hashData)
- {
- using (var sha1 = HashAlgorithmFactory.CreateSHA1())
- {
- return sha1.ComputeHash(hashData, 0, hashData.Length);
- }
- }
- /// <summary>
- /// Sends SSH message to the server
- /// </summary>
- /// <param name="message">The message.</param>
- protected void SendMessage(Message message)
- {
- Session.SendMessage(message);
- }
- /// <summary>
- /// Generates the session key.
- /// </summary>
- /// <param name="sharedKey">The shared key.</param>
- /// <param name="exchangeHash">The exchange hash.</param>
- /// <param name="key">The key.</param>
- /// <param name="size">The size.</param>
- /// <returns></returns>
- private byte[] GenerateSessionKey(BigInteger sharedKey, byte[] exchangeHash, byte[] key, int size)
- {
- var result = new List<byte>(key);
- while (size > result.Count)
- {
- result.AddRange(Hash(new _SessionKeyAdjustment
- {
- SharedKey = sharedKey,
- ExcahngeHash = exchangeHash,
- Key = key,
- }.GetBytes()));
- }
- return result.ToArray();
- }
- /// <summary>
- /// Generates the session key.
- /// </summary>
- /// <param name="sharedKey">The shared key.</param>
- /// <param name="exchangeHash">The exchange hash.</param>
- /// <param name="p">The p.</param>
- /// <param name="sessionId">The session id.</param>
- /// <returns></returns>
- private byte[] GenerateSessionKey(BigInteger sharedKey, byte[] exchangeHash, char p, byte[] sessionId)
- {
- return new _SessionKeyGeneration
- {
- SharedKey = sharedKey,
- ExchangeHash = exchangeHash,
- Char = p,
- SessionId = sessionId,
- }.GetBytes();
- }
- private class _SessionKeyGeneration : SshData
- {
- #if TUNING
- private byte[] _sharedKey;
- public BigInteger SharedKey
- {
- private get { return _sharedKey.ToBigInteger(); }
- set { _sharedKey = value.ToByteArray().Reverse(); }
- }
- #else
- public BigInteger SharedKey { get; set; }
- #endif
- public byte[] ExchangeHash { get; set; }
- public char Char { get; set; }
- public byte[] SessionId { get; set; }
- #if TUNING
- /// <summary>
- /// Gets the size of the message in bytes.
- /// </summary>
- /// <value>
- /// The size of the messages in bytes.
- /// </value>
- protected override int BufferCapacity
- {
- get
- {
- var capacity = base.BufferCapacity;
- capacity += 4; // SharedKey length
- capacity += _sharedKey.Length; // SharedKey
- capacity += ExchangeHash.Length; // ExchangeHash
- capacity += 1; // Char
- capacity += SessionId.Length; // SessionId
- return capacity;
- }
- }
- #endif
- protected override void LoadData()
- {
- throw new NotImplementedException();
- }
- protected override void SaveData()
- {
- #if TUNING
- WriteBinaryString(_sharedKey);
- #else
- this.Write(this.SharedKey);
- #endif
- Write(ExchangeHash);
- Write((byte)Char);
- Write(SessionId);
- }
- }
- private class _SessionKeyAdjustment : SshData
- {
- #if TUNING
- private byte[] _sharedKey;
- public BigInteger SharedKey
- {
- private get { return _sharedKey.ToBigInteger(); }
- set { _sharedKey = value.ToByteArray().Reverse(); }
- }
- #else
- public BigInteger SharedKey { get; set; }
- #endif
- public byte[] ExcahngeHash { get; set; }
- public byte[] Key { get; set; }
- #if TUNING
- /// <summary>
- /// Gets the size of the message in bytes.
- /// </summary>
- /// <value>
- /// The size of the messages in bytes.
- /// </value>
- protected override int BufferCapacity
- {
- get
- {
- var capacity = base.BufferCapacity;
- capacity += 4; // SharedKey length
- capacity += _sharedKey.Length; // SharedKey
- capacity += ExcahngeHash.Length; // ExchangeHash
- capacity += Key.Length; // Key
- return capacity;
- }
- }
- #endif
- protected override void LoadData()
- {
- throw new NotImplementedException();
- }
- protected override void SaveData()
- {
- #if TUNING
- WriteBinaryString(_sharedKey);
- #else
- this.Write(this.SharedKey);
- #endif
- Write(ExcahngeHash);
- Write(Key);
- }
- }
- #region IDisposable Members
- /// <summary>
- /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged ResourceMessages.
- /// </summary>
- public void Dispose()
- {
- Dispose(true);
- GC.SuppressFinalize(this);
- }
- /// <summary>
- /// Releases unmanaged and - optionally - managed resources
- /// </summary>
- /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged ResourceMessages.</param>
- protected virtual void Dispose(bool disposing)
- {
- }
- /// <summary>
- /// Releases unmanaged resources and performs other cleanup operations before the
- /// <see cref="KeyExchange"/> is reclaimed by garbage collection.
- /// </summary>
- ~KeyExchange()
- {
- // Do not re-create Dispose clean-up code here.
- // Calling Dispose(false) is optimal in terms of
- // readability and maintainability.
- Dispose(false);
- }
- #endregion
- }
- }
|