<PackageReference Include="SSH.NET" Version="2025.1.0" />

KeyExchange

public abstract class KeyExchange : Algorithm, IKeyExchange, IDisposable
Represents base class for different key exchange algorithm implementations.
using Microsoft.Extensions.Logging; using Renci.SshNet.Common; using Renci.SshNet.Compression; using Renci.SshNet.Messages; using Renci.SshNet.Messages.Transport; using Renci.SshNet.Security.Cryptography; using System; using System.Linq; using System.Security.Cryptography; namespace Renci.SshNet.Security { public abstract class KeyExchange : Algorithm, IKeyExchange, IDisposable { private sealed class SessionKeyGeneration : SshData { public byte[] SharedKey { get; set; } public byte[] ExchangeHash { get; set; } public char Char { get; set; } public byte[] SessionId { get; set; } protected override int BufferCapacity => base.BufferCapacity + 4 + SharedKey.Length + ExchangeHash.Length + 1 + SessionId.Length; protected override void LoadData() { throw new NotImplementedException(); } protected override void SaveData() { WriteBinaryString(SharedKey); Write(ExchangeHash); Write((byte)Char); Write(SessionId); } } private sealed class SessionKeyAdjustment : SshData { public byte[] SharedKey { get; set; } public byte[] ExchangeHash { get; set; } public byte[] Key { get; set; } protected override int BufferCapacity => base.BufferCapacity + 4 + SharedKey.Length + ExchangeHash.Length + Key.Length; protected override void LoadData() { throw new NotImplementedException(); } protected override void SaveData() { WriteBinaryString(SharedKey); Write(ExchangeHash); Write(Key); } } private ILogger _logger; private Func<byte[], KeyHostAlgorithm> _hostKeyAlgorithmFactory; private CipherInfo _clientCipherInfo; private CipherInfo _serverCipherInfo; private HashInfo _clientHashInfo; private HashInfo _serverHashInfo; private Func<Compressor> _compressorFactory; private Func<Compressor> _decompressorFactory; private byte[] _exchangeHash; protected Session Session { get; set; } public byte[] SharedKey { get; set; } public byte[] ExchangeHash { get { if (_exchangeHash == null) _exchangeHash = CalculateHash(); return _exchangeHash; } } public event EventHandler<HostKeyEventArgs> HostKeyReceived; public virtual void Start(Session session, KeyExchangeInitMessage message, bool sendClientInitMessage) { Session = session; _logger = Session.SessionLoggerFactory.CreateLogger(GetType()); if (sendClientInitMessage) SendMessage(session.ClientInitMessage); string text = (from b in session.ConnectionInfo.HostKeyAlgorithms.Keys from a in message.ServerHostKeyAlgorithms where a == b select a).FirstOrDefault(); if (_logger.IsEnabled(LogLevel.Trace)) { _logger.LogTrace("[{SessionId}] Host key algorithm: we offer {WeOffer}", Session.SessionIdHex, session.ConnectionInfo.HostKeyAlgorithms.Keys.Join(",")); _logger.LogTrace("[{SessionId}] Host key algorithm: they offer {TheyOffer}", Session.SessionIdHex, message.ServerHostKeyAlgorithms.Join(",")); } if (text == null) throw new SshConnectionException("No matching host key algorithm (server offers " + message.ServerHostKeyAlgorithms.Join(",") + ")", DisconnectReason.KeyExchangeFailed); session.ConnectionInfo.CurrentHostKeyAlgorithm = text; _hostKeyAlgorithmFactory = session.ConnectionInfo.HostKeyAlgorithms[text]; string text2 = (from b in session.ConnectionInfo.Encryptions.Keys from a in message.EncryptionAlgorithmsClientToServer where a == b select a).FirstOrDefault(); if (_logger.IsEnabled(LogLevel.Trace)) { _logger.LogTrace("[{SessionId}] Encryption client to server: we offer {WeOffer}", Session.SessionIdHex, session.ConnectionInfo.Encryptions.Keys.Join(",")); _logger.LogTrace("[{SessionId}] Encryption client to server: they offer {TheyOffer}", Session.SessionIdHex, message.EncryptionAlgorithmsClientToServer.Join(",")); } if (text2 == null) throw new SshConnectionException("No matching client encryption algorithm (server offers " + message.EncryptionAlgorithmsClientToServer.Join(",") + ")", DisconnectReason.KeyExchangeFailed); session.ConnectionInfo.CurrentClientEncryption = text2; _clientCipherInfo = session.ConnectionInfo.Encryptions[text2]; string text3 = (from b in session.ConnectionInfo.Encryptions.Keys from a in message.EncryptionAlgorithmsServerToClient where a == b select a).FirstOrDefault(); if (_logger.IsEnabled(LogLevel.Trace)) { _logger.LogTrace("[{SessionId}] Encryption server to client: we offer {WeOffer}", Session.SessionIdHex, session.ConnectionInfo.Encryptions.Keys.Join(",")); _logger.LogTrace("[{SessionId}] Encryption server to client: they offer {TheyOffer}", Session.SessionIdHex, message.EncryptionAlgorithmsServerToClient.Join(",")); } if (text3 == null) throw new SshConnectionException("No matching server encryption algorithm (server offers " + message.EncryptionAlgorithmsServerToClient.Join(",") + ")", DisconnectReason.KeyExchangeFailed); session.ConnectionInfo.CurrentServerEncryption = text3; _serverCipherInfo = session.ConnectionInfo.Encryptions[text3]; if (!_clientCipherInfo.IsAead) { string text4 = (from b in session.ConnectionInfo.HmacAlgorithms.Keys from a in message.MacAlgorithmsClientToServer where a == b select a).FirstOrDefault(); if (_logger.IsEnabled(LogLevel.Trace)) { _logger.LogTrace("[{SessionId}] MAC client to server: we offer {WeOffer}", Session.SessionIdHex, session.ConnectionInfo.HmacAlgorithms.Keys.Join(",")); _logger.LogTrace("[{SessionId}] MAC client to server: they offer {TheyOffer}", Session.SessionIdHex, message.MacAlgorithmsClientToServer.Join(",")); } if (text4 == null) throw new SshConnectionException("No matching client MAC algorithm (server offers " + message.MacAlgorithmsClientToServer.Join(",") + ")", DisconnectReason.KeyExchangeFailed); session.ConnectionInfo.CurrentClientHmacAlgorithm = text4; _clientHashInfo = session.ConnectionInfo.HmacAlgorithms[text4]; } if (!_serverCipherInfo.IsAead) { string text5 = (from b in session.ConnectionInfo.HmacAlgorithms.Keys from a in message.MacAlgorithmsServerToClient where a == b select a).FirstOrDefault(); if (_logger.IsEnabled(LogLevel.Trace)) { _logger.LogTrace("[{SessionId}] MAC server to client: we offer {WeOffer}", Session.SessionIdHex, session.ConnectionInfo.HmacAlgorithms.Keys.Join(",")); _logger.LogTrace("[{SessionId}] MAC server to client: they offer {TheyOffer}", Session.SessionIdHex, message.MacAlgorithmsServerToClient.Join(",")); } if (text5 == null) throw new SshConnectionException("No matching server MAC algorithm (server offers " + message.MacAlgorithmsServerToClient.Join(",") + ")", DisconnectReason.KeyExchangeFailed); session.ConnectionInfo.CurrentServerHmacAlgorithm = text5; _serverHashInfo = session.ConnectionInfo.HmacAlgorithms[text5]; } string text6 = (from b in session.ConnectionInfo.CompressionAlgorithms.Keys from a in message.CompressionAlgorithmsClientToServer where a == b select a).FirstOrDefault(); if (_logger.IsEnabled(LogLevel.Trace)) { _logger.LogTrace("[{SessionId}] Compression client to server: we offer {WeOffer}", Session.SessionIdHex, session.ConnectionInfo.CompressionAlgorithms.Keys.Join(",")); _logger.LogTrace("[{SessionId}] Compression client to server: they offer {TheyOffer}", Session.SessionIdHex, message.CompressionAlgorithmsClientToServer.Join(",")); } if (text6 == null) throw new SshConnectionException("No matching client compression algorithm (server offers " + message.CompressionAlgorithmsClientToServer.Join(",") + ")", DisconnectReason.KeyExchangeFailed); session.ConnectionInfo.CurrentClientCompressionAlgorithm = text6; _compressorFactory = session.ConnectionInfo.CompressionAlgorithms[text6]; string text7 = (from b in session.ConnectionInfo.CompressionAlgorithms.Keys from a in message.CompressionAlgorithmsServerToClient where a == b select a).FirstOrDefault(); if (_logger.IsEnabled(LogLevel.Trace)) { _logger.LogTrace("[{SessionId}] Compression server to client: we offer {WeOffer}", Session.SessionIdHex, session.ConnectionInfo.CompressionAlgorithms.Keys.Join(",")); _logger.LogTrace("[{SessionId}] Compression server to client: they offer {TheyOffer}", Session.SessionIdHex, message.CompressionAlgorithmsServerToClient.Join(",")); } if (text7 == null) throw new SshConnectionException("No matching server compression algorithm (server offers " + message.CompressionAlgorithmsServerToClient.Join(",") + ")", DisconnectReason.KeyExchangeFailed); session.ConnectionInfo.CurrentServerCompressionAlgorithm = text7; _decompressorFactory = session.ConnectionInfo.CompressionAlgorithms[text7]; } public virtual void Finish() { if (!ValidateExchangeHash()) throw new SshConnectionException("Host key could not be verified.", DisconnectReason.KeyExchangeFailed); SendMessage(new NewKeysMessage()); } public Cipher CreateServerCipher(out bool isAead) { isAead = _serverCipherInfo.IsAead; byte[] sessionId = Session.SessionId ?? ExchangeHash; byte[] arg = Hash(GenerateSessionKey(SharedKey, ExchangeHash, 'B', sessionId)); byte[] key = Hash(GenerateSessionKey(SharedKey, ExchangeHash, 'D', sessionId)); key = GenerateSessionKey(SharedKey, ExchangeHash, key, _serverCipherInfo.KeySize / 8); _logger.LogDebug("[{SessionId}] Creating {ServerEncryption} server cipher.", Session.SessionIdHex, Session.ConnectionInfo.CurrentServerEncryption); return _serverCipherInfo.Cipher(key, arg); } public Cipher CreateClientCipher(out bool isAead) { isAead = _clientCipherInfo.IsAead; byte[] sessionId = Session.SessionId ?? ExchangeHash; byte[] arg = Hash(GenerateSessionKey(SharedKey, ExchangeHash, 'A', sessionId)); byte[] key = Hash(GenerateSessionKey(SharedKey, ExchangeHash, 'C', sessionId)); key = GenerateSessionKey(SharedKey, ExchangeHash, key, _clientCipherInfo.KeySize / 8); _logger.LogDebug("[{SessionId}] Creating {ClientEncryption} client cipher.", Session.SessionIdHex, Session.ConnectionInfo.CurrentClientEncryption); return _clientCipherInfo.Cipher(key, arg); } public HashAlgorithm CreateServerHash(out bool isEncryptThenMAC) { if (_serverHashInfo == null) { isEncryptThenMAC = false; return null; } isEncryptThenMAC = _serverHashInfo.IsEncryptThenMAC; byte[] sessionId = Session.SessionId ?? ExchangeHash; byte[] arg = GenerateSessionKey(SharedKey, ExchangeHash, Hash(GenerateSessionKey(SharedKey, ExchangeHash, 'F', sessionId)), _serverHashInfo.KeySize / 8); _logger.LogDebug("[{SessionId}] Creating {ServerHmacAlgorithm} server hmac algorithm.", Session.SessionIdHex, Session.ConnectionInfo.CurrentServerHmacAlgorithm); return _serverHashInfo.HashAlgorithm(arg); } public HashAlgorithm CreateClientHash(out bool isEncryptThenMAC) { if (_clientHashInfo == null) { isEncryptThenMAC = false; return null; } isEncryptThenMAC = _clientHashInfo.IsEncryptThenMAC; byte[] sessionId = Session.SessionId ?? ExchangeHash; byte[] arg = GenerateSessionKey(SharedKey, ExchangeHash, Hash(GenerateSessionKey(SharedKey, ExchangeHash, 'E', sessionId)), _clientHashInfo.KeySize / 8); _logger.LogDebug("[{SessionId}] Creating {ClientHmacAlgorithm} client hmac algorithm.", Session.SessionIdHex, Session.ConnectionInfo.CurrentClientHmacAlgorithm); return _clientHashInfo.HashAlgorithm(arg); } public Compressor CreateCompressor() { if (_compressorFactory == null) return null; _logger.LogDebug("[{SessionId}] Creating {CompressionAlgorithm} client compressor.", Session.SessionIdHex, Session.ConnectionInfo.CurrentClientCompressionAlgorithm); Compressor compressor = _compressorFactory(); compressor.Init(Session); return compressor; } public Compressor CreateDecompressor() { if (_decompressorFactory == null) return null; _logger.LogDebug("[{SessionId}] Creating {ServerCompressionAlgorithm} server decompressor.", Session.SessionIdHex, Session.ConnectionInfo.CurrentServerCompressionAlgorithm); Compressor compressor = _decompressorFactory(); compressor.Init(Session); return compressor; } protected bool CanTrustHostKey(KeyHostAlgorithm host) { EventHandler<HostKeyEventArgs> hostKeyReceived = this.HostKeyReceived; if (hostKeyReceived != null) { HostKeyEventArgs hostKeyEventArgs = new HostKeyEventArgs(host); hostKeyReceived(this, hostKeyEventArgs); return hostKeyEventArgs.CanTrust; } return true; } protected abstract bool ValidateExchangeHash(); private protected bool ValidateExchangeHash(byte[] encodedKey, byte[] encodedSignature) { byte[] data = CalculateHash(); KeyHostAlgorithm keyHostAlgorithm = _hostKeyAlgorithmFactory(encodedKey); if (keyHostAlgorithm.VerifySignature(data, encodedSignature)) return CanTrustHostKey(keyHostAlgorithm); return false; } protected abstract byte[] CalculateHash(); protected abstract byte[] Hash(byte[] hashData); protected void SendMessage(Message message) { Session.SendMessage(message); } private byte[] GenerateSessionKey(byte[] sharedKey, byte[] exchangeHash, byte[] key, int size) { while (key.Length < size) { SessionKeyAdjustment sessionKeyAdjustment = new SessionKeyAdjustment { SharedKey = sharedKey, ExchangeHash = exchangeHash, Key = key }; key = key.Concat(Hash(sessionKeyAdjustment.GetBytes())); } return key; } private static byte[] GenerateSessionKey(byte[] sharedKey, byte[] exchangeHash, char p, byte[] sessionId) { return new SessionKeyGeneration { SharedKey = sharedKey, ExchangeHash = exchangeHash, Char = p, SessionId = sessionId }.GetBytes(); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { } } }