KeyExchange
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[] { 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[] { 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[] { 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)
{
}
}
}