< Summary

Information
Class: Renci.SshNet.Security.KeyExchange
Assembly: Renci.SshNet
File(s): \home\appveyor\projects\ssh-net\src\Renci.SshNet\Security\KeyExchange.cs
Line coverage
87%
Covered lines: 200
Uncovered lines: 29
Coverable lines: 229
Total lines: 550
Line coverage: 87.3%
Branch coverage
61%
Covered branches: 21
Total branches: 34
Branch coverage: 61.7%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity Line coverage
get_Session()100%1100%
get_SharedKey()100%1100%
get_ExchangeHash()100%2100%
Start(...)50%1279.31%
Finish()100%2100%
CreateServerCipher()50%2100%
CreateClientCipher()50%2100%
CreateServerHash()50%2100%
CreateClientHash()50%2100%
CreateCompressor()50%245.45%
CreateDecompressor()50%245.45%
CanTrustHostKey(...)50%288.88%
ValidateExchangeHash(...)100%2100%
SendMessage(...)100%1100%
GenerateSessionKey(...)100%2100%
GenerateSessionKey(...)100%1100%
get_SharedKey()100%1100%
get_ExchangeHash()100%1100%
get_Char()100%1100%
get_SessionId()100%1100%
get_BufferCapacity()100%1100%
LoadData()100%10%
SaveData()100%1100%
get_SharedKey()100%1100%
get_ExchangeHash()100%1100%
get_Key()100%1100%
get_BufferCapacity()100%1100%
LoadData()100%10%
SaveData()100%1100%
Dispose()100%1100%
Dispose(...)100%1100%
Finalize()100%1100%

File(s)

\home\appveyor\projects\ssh-net\src\Renci.SshNet\Security\KeyExchange.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.Linq;
 4using System.Security.Cryptography;
 5
 6using Renci.SshNet.Abstractions;
 7using Renci.SshNet.Common;
 8using Renci.SshNet.Compression;
 9using Renci.SshNet.Messages;
 10using Renci.SshNet.Messages.Transport;
 11using Renci.SshNet.Security.Cryptography;
 12
 13namespace Renci.SshNet.Security
 14{
 15    /// <summary>
 16    /// Represents base class for different key exchange algorithm implementations.
 17    /// </summary>
 18    public abstract class KeyExchange : Algorithm, IKeyExchange
 19    {
 20        private CipherInfo _clientCipherInfo;
 21        private CipherInfo _serverCipherInfo;
 22        private HashInfo _clientHashInfo;
 23        private HashInfo _serverHashInfo;
 24        private Type _compressionType;
 25        private Type _decompressionType;
 26
 27        /// <summary>
 28        /// Gets the session.
 29        /// </summary>
 30        /// <value>
 31        /// The session.
 32        /// </value>
 3239933        protected Session Session { get; private set; }
 34
 35        /// <summary>
 36        /// Gets or sets key exchange shared key.
 37        /// </summary>
 38        /// <value>
 39        /// The shared key.
 40        /// </value>
 1557641        public byte[] SharedKey { get; protected set; }
 42
 43        private byte[] _exchangeHash;
 44
 45        /// <summary>
 46        /// Gets the exchange hash.
 47        /// </summary>
 48        /// <value>The exchange hash.</value>
 49        public byte[] ExchangeHash
 50        {
 51            get
 1317852            {
 1317853                _exchangeHash ??= CalculateHash();
 54
 1317855                return _exchangeHash;
 1317856            }
 57        }
 58
 59        /// <summary>
 60        /// Occurs when host key received.
 61        /// </summary>
 62        public event EventHandler<HostKeyEventArgs> HostKeyReceived;
 63
 64        /// <summary>
 65        /// Starts key exchange algorithm.
 66        /// </summary>
 67        /// <param name="session">The session.</param>
 68        /// <param name="message">Key exchange init message.</param>
 69        public virtual void Start(Session session, KeyExchangeInitMessage message)
 119970        {
 119971            Session = session;
 72
 119973            SendMessage(session.ClientInitMessage);
 74
 75            // Determine encryption algorithm
 119976            var clientEncryptionAlgorithmName = (from b in session.ConnectionInfo.Encryptions.Keys
 370277                                                 from a in message.EncryptionAlgorithmsClientToServer
 244078                                                 where a == b
 239879                                                 select a).FirstOrDefault();
 80
 119981            if (string.IsNullOrEmpty(clientEncryptionAlgorithmName))
 082            {
 083                throw new SshConnectionException("Client encryption algorithm not found", DisconnectReason.KeyExchangeFa
 84            }
 85
 119986            session.ConnectionInfo.CurrentClientEncryption = clientEncryptionAlgorithmName;
 87
 88            // Determine encryption algorithm
 119989            var serverDecryptionAlgorithmName = (from b in session.ConnectionInfo.Encryptions.Keys
 370290                                                 from a in message.EncryptionAlgorithmsServerToClient
 244091                                                 where a == b
 239892                                                 select a).FirstOrDefault();
 119993            if (string.IsNullOrEmpty(serverDecryptionAlgorithmName))
 094            {
 095                throw new SshConnectionException("Server decryption algorithm not found", DisconnectReason.KeyExchangeFa
 96            }
 97
 119998            session.ConnectionInfo.CurrentServerEncryption = serverDecryptionAlgorithmName;
 99
 100            // Determine client hmac algorithm
 1199101            var clientHmacAlgorithmName = (from b in session.ConnectionInfo.HmacAlgorithms.Keys
 39105102                                           from a in message.MacAlgorithmsClientToServer
 35496103                                           where a == b
 2398104                                           select a).FirstOrDefault();
 1199105            if (string.IsNullOrEmpty(clientHmacAlgorithmName))
 0106            {
 0107                throw new SshConnectionException("Server HMAC algorithm not found", DisconnectReason.KeyExchangeFailed);
 108            }
 109
 1199110            session.ConnectionInfo.CurrentClientHmacAlgorithm = clientHmacAlgorithmName;
 111
 112            // Determine server hmac algorithm
 1199113            var serverHmacAlgorithmName = (from b in session.ConnectionInfo.HmacAlgorithms.Keys
 39105114                                           from a in message.MacAlgorithmsServerToClient
 35496115                                           where a == b
 2398116                                           select a).FirstOrDefault();
 1199117            if (string.IsNullOrEmpty(serverHmacAlgorithmName))
 0118            {
 0119                throw new SshConnectionException("Server HMAC algorithm not found", DisconnectReason.KeyExchangeFailed);
 120            }
 121
 1199122            session.ConnectionInfo.CurrentServerHmacAlgorithm = serverHmacAlgorithmName;
 123
 124            // Determine compression algorithm
 1199125            var compressionAlgorithmName = (from b in session.ConnectionInfo.CompressionAlgorithms.Keys
 3597126                                            from a in message.CompressionAlgorithmsClientToServer
 2398127                                            where a == b
 2398128                                            select a).LastOrDefault();
 1199129            if (string.IsNullOrEmpty(compressionAlgorithmName))
 0130            {
 0131                throw new SshConnectionException("Compression algorithm not found", DisconnectReason.KeyExchangeFailed);
 132            }
 133
 1199134            session.ConnectionInfo.CurrentClientCompressionAlgorithm = compressionAlgorithmName;
 135
 136            // Determine decompression algorithm
 1199137            var decompressionAlgorithmName = (from b in session.ConnectionInfo.CompressionAlgorithms.Keys
 3597138                                              from a in message.CompressionAlgorithmsServerToClient
 2398139                                              where a == b
 2398140                                              select a).LastOrDefault();
 1199141            if (string.IsNullOrEmpty(decompressionAlgorithmName))
 0142            {
 0143                throw new SshConnectionException("Decompression algorithm not found", DisconnectReason.KeyExchangeFailed
 144            }
 145
 1199146            session.ConnectionInfo.CurrentServerCompressionAlgorithm = decompressionAlgorithmName;
 147
 1199148            _clientCipherInfo = session.ConnectionInfo.Encryptions[clientEncryptionAlgorithmName];
 1199149            _serverCipherInfo = session.ConnectionInfo.Encryptions[serverDecryptionAlgorithmName];
 1199150            _clientHashInfo = session.ConnectionInfo.HmacAlgorithms[clientHmacAlgorithmName];
 1199151            _serverHashInfo = session.ConnectionInfo.HmacAlgorithms[serverHmacAlgorithmName];
 1199152            _compressionType = session.ConnectionInfo.CompressionAlgorithms[compressionAlgorithmName];
 1199153            _decompressionType = session.ConnectionInfo.CompressionAlgorithms[decompressionAlgorithmName];
 1199154        }
 155
 156        /// <summary>
 157        /// Finishes key exchange algorithm.
 158        /// </summary>
 159        public virtual void Finish()
 1199160        {
 1199161            if (!ValidateExchangeHash())
 1162            {
 1163                throw new SshConnectionException("Key exchange negotiation failed.", DisconnectReason.KeyExchangeFailed)
 164            }
 165
 1198166            SendMessage(new NewKeysMessage());
 1198167        }
 168
 169        /// <summary>
 170        /// Creates the server side cipher to use.
 171        /// </summary>
 172        /// <returns>Server cipher.</returns>
 173        public Cipher CreateServerCipher()
 1198174        {
 175            // Resolve Session ID
 1198176            var sessionId = Session.SessionId ?? ExchangeHash;
 177
 178            // Calculate server to client initial IV
 1198179            var serverVector = Hash(GenerateSessionKey(SharedKey, ExchangeHash, 'B', sessionId));
 180
 181            // Calculate server to client encryption
 1198182            var serverKey = Hash(GenerateSessionKey(SharedKey, ExchangeHash, 'D', sessionId));
 183
 1198184            serverKey = GenerateSessionKey(SharedKey, ExchangeHash, serverKey, _serverCipherInfo.KeySize / 8);
 185
 1198186            DiagnosticAbstraction.Log(string.Format("[{0}] Creating {1} server cipher.",
 1198187                                                    Session.ToHex(Session.SessionId),
 1198188                                                    Session.ConnectionInfo.CurrentServerEncryption));
 189
 190            // Create server cipher
 1198191            return _serverCipherInfo.Cipher(serverKey, serverVector);
 1198192        }
 193
 194        /// <summary>
 195        /// Creates the client side cipher to use.
 196        /// </summary>
 197        /// <returns>Client cipher.</returns>
 198        public Cipher CreateClientCipher()
 1198199        {
 200            // Resolve Session ID
 1198201            var sessionId = Session.SessionId ?? ExchangeHash;
 202
 203            // Calculate client to server initial IV
 1198204            var clientVector = Hash(GenerateSessionKey(SharedKey, ExchangeHash, 'A', sessionId));
 205
 206            // Calculate client to server encryption
 1198207            var clientKey = Hash(GenerateSessionKey(SharedKey, ExchangeHash, 'C', sessionId));
 208
 1198209            clientKey = GenerateSessionKey(SharedKey, ExchangeHash, clientKey, _clientCipherInfo.KeySize / 8);
 210
 1198211            DiagnosticAbstraction.Log(string.Format("[{0}] Creating {1} client cipher.",
 1198212                                                    Session.ToHex(Session.SessionId),
 1198213                                                    Session.ConnectionInfo.CurrentClientEncryption));
 214
 215            // Create client cipher
 1198216            return _clientCipherInfo.Cipher(clientKey, clientVector);
 1198217        }
 218
 219        /// <summary>
 220        /// Creates the server side hash algorithm to use.
 221        /// </summary>
 222        /// <returns>
 223        /// The server-side hash algorithm.
 224        /// </returns>
 225        public HashAlgorithm CreateServerHash()
 1198226        {
 227            // Resolve Session ID
 1198228            var sessionId = Session.SessionId ?? ExchangeHash;
 229
 1198230            var serverKey = GenerateSessionKey(SharedKey,
 1198231                                               ExchangeHash,
 1198232                                               Hash(GenerateSessionKey(SharedKey, ExchangeHash, 'F', sessionId)),
 1198233                                               _serverHashInfo.KeySize / 8);
 234
 1198235            DiagnosticAbstraction.Log(string.Format("[{0}] Creating {1} server hmac algorithm.",
 1198236                                                    Session.ToHex(Session.SessionId),
 1198237                                                    Session.ConnectionInfo.CurrentServerHmacAlgorithm));
 238
 1198239            return _serverHashInfo.HashAlgorithm(serverKey);
 1198240        }
 241
 242        /// <summary>
 243        /// Creates the client side hash algorithm to use.
 244        /// </summary>
 245        /// <returns>
 246        /// The client-side hash algorithm.
 247        /// </returns>
 248        public HashAlgorithm CreateClientHash()
 1198249        {
 250            // Resolve Session ID
 1198251            var sessionId = Session.SessionId ?? ExchangeHash;
 252
 1198253            var clientKey = GenerateSessionKey(SharedKey,
 1198254                                               ExchangeHash,
 1198255                                               Hash(GenerateSessionKey(SharedKey, ExchangeHash, 'E', sessionId)),
 1198256                                               _clientHashInfo.KeySize / 8);
 257
 1198258            DiagnosticAbstraction.Log(string.Format("[{0}] Creating {1} client hmac algorithm.",
 1198259                                                    Session.ToHex(Session.SessionId),
 1198260                                                    Session.ConnectionInfo.CurrentClientHmacAlgorithm));
 261
 1198262            return _clientHashInfo.HashAlgorithm(clientKey);
 1198263        }
 264
 265        /// <summary>
 266        /// Creates the compression algorithm to use to deflate data.
 267        /// </summary>
 268        /// <returns>
 269        /// The compression method.
 270        /// </returns>
 271        public Compressor CreateCompressor()
 1198272        {
 1198273            if (_compressionType is null)
 1198274            {
 1198275                return null;
 276            }
 277
 0278            DiagnosticAbstraction.Log(string.Format("[{0}] Creating {1} client compressor.",
 0279                                                    Session.ToHex(Session.SessionId),
 0280                                                    Session.ConnectionInfo.CurrentClientCompressionAlgorithm));
 281
 0282            var compressor = _compressionType.CreateInstance<Compressor>();
 283
 0284            compressor.Init(Session);
 285
 0286            return compressor;
 1198287        }
 288
 289        /// <summary>
 290        /// Creates the compression algorithm to use to inflate data.
 291        /// </summary>
 292        /// <returns>
 293        /// The decompression method.
 294        /// </returns>
 295        public Compressor CreateDecompressor()
 1198296        {
 1198297            if (_decompressionType is null)
 1198298            {
 1198299                return null;
 300            }
 301
 0302            DiagnosticAbstraction.Log(string.Format("[{0}] Creating {1} server decompressor.",
 0303                                                    Session.ToHex(Session.SessionId),
 0304                                                    Session.ConnectionInfo.CurrentServerCompressionAlgorithm));
 305
 0306            var decompressor = _decompressionType.CreateInstance<Compressor>();
 307
 0308            decompressor.Init(Session);
 309
 0310            return decompressor;
 1198311        }
 312
 313        /// <summary>
 314        /// Determines whether the specified host key can be trusted.
 315        /// </summary>
 316        /// <param name="host">The host algorithm.</param>
 317        /// <returns>
 318        /// <see langword="true"/> if the specified host can be trusted; otherwise, <see langword="false"/>.
 319        /// </returns>
 320        protected bool CanTrustHostKey(KeyHostAlgorithm host)
 1199321        {
 1199322            var handlers = HostKeyReceived;
 1199323            if (handlers != null)
 1199324            {
 1199325                var args = new HostKeyEventArgs(host);
 1199326                handlers(this, args);
 1199327                return args.CanTrust;
 328            }
 329
 0330            return true;
 1199331        }
 332
 333        /// <summary>
 334        /// Validates the exchange hash.
 335        /// </summary>
 336        /// <returns>true if exchange hash is valid; otherwise false.</returns>
 337        protected abstract bool ValidateExchangeHash();
 338
 339        private protected bool ValidateExchangeHash(byte[] encodedKey, byte[] encodedSignature)
 1199340        {
 1199341            var exchangeHash = CalculateHash();
 342
 1199343            var signatureData = new KeyHostAlgorithm.SignatureKeyData();
 1199344            signatureData.Load(encodedSignature);
 345
 1199346            var keyAlgorithm = Session.ConnectionInfo.HostKeyAlgorithms[signatureData.AlgorithmName](encodedKey);
 347
 1199348            Session.ConnectionInfo.CurrentHostKeyAlgorithm = signatureData.AlgorithmName;
 349
 1199350            if (CanTrustHostKey(keyAlgorithm))
 1198351            {
 352                // keyAlgorithm.VerifySignature decodes the signature data before verifying.
 353                // But as we have already decoded the data to find the signature algorithm,
 354                // we just verify the decoded data directly through the DigitalSignature.
 1198355                return keyAlgorithm.DigitalSignature.Verify(exchangeHash, signatureData.Signature);
 356            }
 357
 1358            return false;
 1199359        }
 360
 361        /// <summary>
 362        /// Calculates key exchange hash value.
 363        /// </summary>
 364        /// <returns>Key exchange hash.</returns>
 365        protected abstract byte[] CalculateHash();
 366
 367        /// <summary>
 368        /// Hashes the specified data bytes.
 369        /// </summary>
 370        /// <param name="hashData">The hash data.</param>
 371        /// <returns>
 372        /// The hash of the data.
 373        /// </returns>
 374        protected abstract byte[] Hash(byte[] hashData);
 375
 376        /// <summary>
 377        /// Sends SSH message to the server.
 378        /// </summary>
 379        /// <param name="message">The message.</param>
 380        protected void SendMessage(Message message)
 3602381        {
 3602382            Session.SendMessage(message);
 3602383        }
 384
 385        /// <summary>
 386        /// Generates the session key.
 387        /// </summary>
 388        /// <param name="sharedKey">The shared key.</param>
 389        /// <param name="exchangeHash">The exchange hash.</param>
 390        /// <param name="key">The key.</param>
 391        /// <param name="size">The size.</param>
 392        /// <returns>
 393        /// The session key.
 394        /// </returns>
 395        private byte[] GenerateSessionKey(byte[] sharedKey, byte[] exchangeHash, byte[] key, int size)
 4792396        {
 4792397            var result = new List<byte>(key);
 398
 4798399            while (size > result.Count)
 6400            {
 6401                var sessionKeyAdjustment = new SessionKeyAdjustment
 6402                    {
 6403                        SharedKey = sharedKey,
 6404                        ExchangeHash = exchangeHash,
 6405                        Key = key,
 6406                    };
 407
 6408                result.AddRange(Hash(sessionKeyAdjustment.GetBytes()));
 6409            }
 410
 4792411            return result.ToArray();
 4792412        }
 413
 414        /// <summary>
 415        /// Generates the session key.
 416        /// </summary>
 417        /// <param name="sharedKey">The shared key.</param>
 418        /// <param name="exchangeHash">The exchange hash.</param>
 419        /// <param name="p">The p.</param>
 420        /// <param name="sessionId">The session id.</param>
 421        /// <returns>
 422        /// The session key.
 423        /// </returns>
 424        private static byte[] GenerateSessionKey(byte[] sharedKey, byte[] exchangeHash, char p, byte[] sessionId)
 7188425        {
 7188426            var sessionKeyGeneration = new SessionKeyGeneration
 7188427                {
 7188428                    SharedKey = sharedKey,
 7188429                    ExchangeHash = exchangeHash,
 7188430                    Char = p,
 7188431                    SessionId = sessionId
 7188432                };
 7188433            return sessionKeyGeneration.GetBytes();
 7188434        }
 435
 436        private sealed class SessionKeyGeneration : SshData
 437        {
 21564438            public byte[] SharedKey { get; set; }
 439
 21564440            public byte[] ExchangeHash { get; set; }
 441
 14376442            public char Char { get; set; }
 443
 21564444            public byte[] SessionId { get; set; }
 445
 446            /// <summary>
 447            /// Gets the size of the message in bytes.
 448            /// </summary>
 449            /// <value>
 450            /// The size of the messages in bytes.
 451            /// </value>
 452            protected override int BufferCapacity
 453            {
 454                get
 7188455                {
 7188456                    var capacity = base.BufferCapacity;
 7188457                    capacity += 4; // SharedKey length
 7188458                    capacity += SharedKey.Length; // SharedKey
 7188459                    capacity += ExchangeHash.Length; // ExchangeHash
 7188460                    capacity += 1; // Char
 7188461                    capacity += SessionId.Length; // SessionId
 7188462                    return capacity;
 7188463                }
 464            }
 465
 466            protected override void LoadData()
 0467            {
 0468                throw new NotImplementedException();
 469            }
 470
 471            protected override void SaveData()
 7188472            {
 7188473                WriteBinaryString(SharedKey);
 7188474                Write(ExchangeHash);
 7188475                Write((byte) Char);
 7188476                Write(SessionId);
 7188477            }
 478        }
 479
 480        private sealed class SessionKeyAdjustment : SshData
 481        {
 18482            public byte[] SharedKey { get; set; }
 483
 18484            public byte[] ExchangeHash { get; set; }
 485
 18486            public byte[] Key { get; set; }
 487
 488            /// <summary>
 489            /// Gets the size of the message in bytes.
 490            /// </summary>
 491            /// <value>
 492            /// The size of the messages in bytes.
 493            /// </value>
 494            protected override int BufferCapacity
 495            {
 496                get
 6497                {
 6498                    var capacity = base.BufferCapacity;
 6499                    capacity += 4; // SharedKey length
 6500                    capacity += SharedKey.Length; // SharedKey
 6501                    capacity += ExchangeHash.Length; // ExchangeHash
 6502                    capacity += Key.Length; // Key
 6503                    return capacity;
 6504                }
 505            }
 506
 507            protected override void LoadData()
 0508            {
 0509                throw new NotImplementedException();
 510            }
 511
 512            protected override void SaveData()
 6513            {
 6514                WriteBinaryString(SharedKey);
 6515                Write(ExchangeHash);
 6516                Write(Key);
 6517            }
 518        }
 519
 520        #region IDisposable Members
 521
 522        /// <summary>
 523        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
 524        /// </summary>
 525        public void Dispose()
 1199526        {
 1199527            Dispose(disposing: true);
 1199528            GC.SuppressFinalize(this);
 1199529        }
 530
 531        /// <summary>
 532        /// Releases unmanaged and - optionally - managed resources.
 533        /// </summary>
 534        /// <param name="disposing"><see langword="true"/> to release both managed and unmanaged resources; <see langwor
 535        protected virtual void Dispose(bool disposing)
 1223536        {
 1223537        }
 538
 539        /// <summary>
 540        /// Releases unmanaged resources and performs other cleanup operations before the
 541        /// <see cref="KeyExchange"/> is reclaimed by garbage collection.
 542        /// </summary>
 543        ~KeyExchange()
 48544        {
 24545            Dispose(disposing: false);
 48546        }
 547
 548        #endregion
 549    }
 550}