<PackageReference Include="SSH.NET" Version="2025.0.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.Collections.Generic; 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 readonly ILogger _logger; 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; protected KeyExchange() { _logger = SshNetLoggingConfiguration.LoggerFactory.CreateLogger(GetType()); } public virtual void Start(Session session, KeyExchangeInitMessage message, bool sendClientInitMessage) { Session = session; if (sendClientInitMessage) SendMessage(session.ClientInitMessage); string text = (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 (string.IsNullOrEmpty(text)) throw new SshConnectionException("Client encryption algorithm not found", DisconnectReason.KeyExchangeFailed); session.ConnectionInfo.CurrentClientEncryption = text; _clientCipherInfo = session.ConnectionInfo.Encryptions[text]; string text2 = (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 (string.IsNullOrEmpty(text2)) throw new SshConnectionException("Server decryption algorithm not found", DisconnectReason.KeyExchangeFailed); session.ConnectionInfo.CurrentServerEncryption = text2; _serverCipherInfo = session.ConnectionInfo.Encryptions[text2]; if (!_clientCipherInfo.IsAead) { string text3 = (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 (string.IsNullOrEmpty(text3)) throw new SshConnectionException("Client HMAC algorithm not found", DisconnectReason.KeyExchangeFailed); session.ConnectionInfo.CurrentClientHmacAlgorithm = text3; _clientHashInfo = session.ConnectionInfo.HmacAlgorithms[text3]; } if (!_serverCipherInfo.IsAead) { string text4 = (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 (string.IsNullOrEmpty(text4)) throw new SshConnectionException("Server HMAC algorithm not found", DisconnectReason.KeyExchangeFailed); session.ConnectionInfo.CurrentServerHmacAlgorithm = text4; _serverHashInfo = session.ConnectionInfo.HmacAlgorithms[text4]; } string text5 = (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 (string.IsNullOrEmpty(text5)) throw new SshConnectionException("Compression algorithm not found", DisconnectReason.KeyExchangeFailed); session.ConnectionInfo.CurrentClientCompressionAlgorithm = text5; _compressorFactory = session.ConnectionInfo.CompressionAlgorithms[text5]; string text6 = (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 (string.IsNullOrEmpty(text6)) throw new SshConnectionException("Decompression algorithm not found", DisconnectReason.KeyExchangeFailed); session.ConnectionInfo.CurrentServerCompressionAlgorithm = text6; _decompressorFactory = session.ConnectionInfo.CompressionAlgorithms[text6]; } public virtual void Finish() { if (!ValidateExchangeHash()) throw new SshConnectionException("Key exchange negotiation failed.", 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.SignatureKeyData signatureKeyData = new KeyHostAlgorithm.SignatureKeyData(); signatureKeyData.Load(encodedSignature); string text = default(string); using (SshDataStream sshDataStream = new SshDataStream(encodedKey)) text = sshDataStream.ReadString(null); string text2 = (!signatureKeyData.AlgorithmName.StartsWith("rsa-sha2", StringComparison.Ordinal)) ? text : text.Replace("ssh-rsa", signatureKeyData.AlgorithmName); KeyHostAlgorithm keyHostAlgorithm = Session.ConnectionInfo.HostKeyAlgorithms[text2](encodedKey); Session.ConnectionInfo.CurrentHostKeyAlgorithm = text2; if (keyHostAlgorithm.VerifySignatureBlob(data, signatureKeyData.Signature)) 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) { List<byte> list = new List<byte>(key); while (size > list.Count) { SessionKeyAdjustment sessionKeyAdjustment = new SessionKeyAdjustment { SharedKey = sharedKey, ExchangeHash = exchangeHash, Key = key }; list.AddRange(Hash(sessionKeyAdjustment.GetBytes())); } return list.ToArray(); } 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) { } } }