KeyExchange.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Security.Cryptography;
  5. using Renci.SshNet.Common;
  6. using Renci.SshNet.Compression;
  7. using Renci.SshNet.Messages;
  8. using Renci.SshNet.Messages.Transport;
  9. namespace Renci.SshNet.Security
  10. {
  11. /// <summary>
  12. /// Represents base class for different key exchange algorithm implementations
  13. /// </summary>
  14. public abstract class KeyExchange : Algorithm, IDisposable
  15. {
  16. private Type _clientCipherType;
  17. private Type _serverCipherType;
  18. private Type _cientHmacAlgorithmType;
  19. private Type _serverHmacAlgorithmType;
  20. private Type _compressionType;
  21. private Type _decompressionType;
  22. /// <summary>
  23. /// Gets or sets the session.
  24. /// </summary>
  25. /// <value>
  26. /// The session.
  27. /// </value>
  28. protected Session Session { get; private set; }
  29. /// <summary>
  30. /// Gets or sets key exchange shared key.
  31. /// </summary>
  32. /// <value>
  33. /// The shared key.
  34. /// </value>
  35. public BigInteger SharedKey { get; protected set; }
  36. private byte[] _exchangeHash;
  37. /// <summary>
  38. /// Gets the exchange hash.
  39. /// </summary>
  40. /// <value>The exchange hash.</value>
  41. public byte[] ExchangeHash
  42. {
  43. get
  44. {
  45. if (this._exchangeHash == null)
  46. {
  47. this._exchangeHash = this.CalculateHash();
  48. }
  49. return this._exchangeHash;
  50. }
  51. }
  52. /// <summary>
  53. /// Starts key exchange algorithm
  54. /// </summary>
  55. /// <param name="session">The session.</param>
  56. /// <param name="message">Key exchange init message.</param>
  57. public virtual void Start(Session session, KeyExchangeInitMessage message)
  58. {
  59. this.Session = session;
  60. this.SendMessage(session.ClientInitMessage);
  61. // Determine encryption algorithm
  62. var clientEncryptionAlgorithmName = (from b in session.ConnectionInfo.Encryptions.Keys
  63. from a in message.EncryptionAlgorithmsClientToServer
  64. where a == b
  65. select a).FirstOrDefault();
  66. if (string.IsNullOrEmpty(clientEncryptionAlgorithmName))
  67. {
  68. throw new SshConnectionException("Client encryption algorithm not found", DisconnectReason.KeyExchangeFailed);
  69. }
  70. // Determine encryption algorithm
  71. var serverDecryptionAlgorithmName = (from b in session.ConnectionInfo.Encryptions.Keys
  72. from a in message.EncryptionAlgorithmsServerToClient
  73. where a == b
  74. select a).FirstOrDefault();
  75. if (string.IsNullOrEmpty(serverDecryptionAlgorithmName))
  76. {
  77. throw new SshConnectionException("Server decryption algorithm not found", DisconnectReason.KeyExchangeFailed);
  78. }
  79. // Determine client hmac algorithm
  80. var clientHmacAlgorithmName = (from b in session.ConnectionInfo.HmacAlgorithms.Keys
  81. from a in message.MacAlgorithmsClientToServer
  82. where a == b
  83. select a).FirstOrDefault();
  84. if (string.IsNullOrEmpty(clientHmacAlgorithmName))
  85. {
  86. throw new SshConnectionException("Server HMAC algorithm not found", DisconnectReason.KeyExchangeFailed);
  87. }
  88. // Determine server hmac algorithm
  89. var serverHmacAlgorithmName = (from b in session.ConnectionInfo.HmacAlgorithms.Keys
  90. from a in message.MacAlgorithmsServerToClient
  91. where a == b
  92. select a).FirstOrDefault();
  93. if (string.IsNullOrEmpty(serverHmacAlgorithmName))
  94. {
  95. throw new SshConnectionException("Server HMAC algorithm not found", DisconnectReason.KeyExchangeFailed);
  96. }
  97. // Determine compression algorithm
  98. var compressionAlgorithmName = (from b in session.ConnectionInfo.CompressionAlgorithms.Keys
  99. from a in message.CompressionAlgorithmsClientToServer
  100. where a == b
  101. select a).FirstOrDefault();
  102. if (string.IsNullOrEmpty(compressionAlgorithmName))
  103. {
  104. throw new SshConnectionException("Compression algorithm not found", DisconnectReason.KeyExchangeFailed);
  105. }
  106. // Determine decompression algorithm
  107. var decompressionAlgorithmName = (from b in session.ConnectionInfo.CompressionAlgorithms.Keys
  108. from a in message.CompressionAlgorithmsServerToClient
  109. where a == b
  110. select a).FirstOrDefault();
  111. if (string.IsNullOrEmpty(decompressionAlgorithmName))
  112. {
  113. throw new SshConnectionException("Decompression algorithm not found", DisconnectReason.KeyExchangeFailed);
  114. }
  115. this._clientCipherType = session.ConnectionInfo.Encryptions[clientEncryptionAlgorithmName];
  116. this._serverCipherType = session.ConnectionInfo.Encryptions[clientEncryptionAlgorithmName];
  117. this._cientHmacAlgorithmType = session.ConnectionInfo.HmacAlgorithms[clientHmacAlgorithmName];
  118. this._serverHmacAlgorithmType = session.ConnectionInfo.HmacAlgorithms[serverHmacAlgorithmName];
  119. this._compressionType = session.ConnectionInfo.CompressionAlgorithms[compressionAlgorithmName];
  120. this._decompressionType = session.ConnectionInfo.CompressionAlgorithms[decompressionAlgorithmName];
  121. }
  122. /// <summary>
  123. /// Finishes key exchange algorithm.
  124. /// </summary>
  125. public virtual void Finish()
  126. {
  127. // Validate hash
  128. if (this.ValidateExchangeHash())
  129. {
  130. this.SendMessage(new NewKeysMessage());
  131. }
  132. else
  133. {
  134. throw new SshConnectionException("Key exchange negotiation failed.", DisconnectReason.KeyExchangeFailed);
  135. }
  136. }
  137. /// <summary>
  138. /// Creates the server side cipher to use.
  139. /// </summary>
  140. /// <returns></returns>
  141. public Cipher CreateServerCipher()
  142. {
  143. // Resolve Session ID
  144. var sessionId = this.Session.SessionId ?? this.ExchangeHash;
  145. // Create server cipher
  146. var serverCipher = this._serverCipherType.CreateInstance<Cipher>();
  147. // Calculate server to client initial IV
  148. var serverVector = this.Hash(this.GenerateSessionKey(this.SharedKey, this.ExchangeHash, 'B', sessionId));
  149. // Calculate server to client encryption
  150. var serverKey = this.Hash(this.GenerateSessionKey(this.SharedKey, this.ExchangeHash, 'D', sessionId));
  151. serverKey = this.GenerateSessionKey(this.SharedKey, this.ExchangeHash, serverKey, serverCipher.KeySize / 8);
  152. serverCipher.Init(serverKey, serverVector);
  153. return serverCipher;
  154. }
  155. /// <summary>
  156. /// Creates the client side cipher to use.
  157. /// </summary>
  158. /// <returns></returns>
  159. public Cipher CreateClientCipher()
  160. {
  161. // Resolve Session ID
  162. var sessionId = this.Session.SessionId ?? this.ExchangeHash;
  163. // Create client cipher
  164. var clientCipher = this._clientCipherType.CreateInstance<Cipher>();
  165. // Calculate client to server initial IV
  166. var clientVector = this.Hash(this.GenerateSessionKey(this.SharedKey, this.ExchangeHash, 'A', sessionId));
  167. // Calculate client to server encryption
  168. var clientKey = this.Hash(this.GenerateSessionKey(this.SharedKey, this.ExchangeHash, 'C', sessionId));
  169. clientKey = this.GenerateSessionKey(this.SharedKey, this.ExchangeHash, clientKey, clientCipher.KeySize / 8);
  170. clientCipher.Init(clientKey, clientVector);
  171. return clientCipher;
  172. }
  173. /// <summary>
  174. /// Creates the server side hash algorithm to use.
  175. /// </summary>
  176. /// <returns></returns>
  177. public HMac CreateServerHash()
  178. {
  179. // Resolve Session ID
  180. var sessionId = this.Session.SessionId ?? this.ExchangeHash;
  181. // Create server HMac
  182. var serverHMac = this._serverHmacAlgorithmType.CreateInstance<HMac>();
  183. serverHMac.Init(this.Hash(this.GenerateSessionKey(this.SharedKey, this.ExchangeHash, 'F', sessionId)));
  184. return serverHMac;
  185. }
  186. /// <summary>
  187. /// Creates the client side hash algorithm to use.
  188. /// </summary>
  189. /// <returns></returns>
  190. public HMac CreateClientHash()
  191. {
  192. // Resolve Session ID
  193. var sessionId = this.Session.SessionId ?? this.ExchangeHash;
  194. // Create client HMac
  195. var clientHMac = this._cientHmacAlgorithmType.CreateInstance<HMac>();
  196. clientHMac.Init(this.Hash(this.GenerateSessionKey(this.SharedKey, this.ExchangeHash, 'E', sessionId)));
  197. return clientHMac;
  198. }
  199. /// <summary>
  200. /// Creates the compression algorithm to use to deflate data.
  201. /// </summary>
  202. /// <returns></returns>
  203. public Compressor CreateCompressor()
  204. {
  205. if (this._compressionType == null)
  206. return null;
  207. var compressor = this._compressionType.CreateInstance<Compressor>();
  208. compressor.Init(this.Session);
  209. return compressor;
  210. }
  211. /// <summary>
  212. /// Creates the compression algorithm to use to inflate data.
  213. /// </summary>
  214. /// <returns></returns>
  215. public Compressor CreateDecompressor()
  216. {
  217. if (this._compressionType == null)
  218. return null;
  219. var decompressor = this._decompressionType.CreateInstance<Compressor>();
  220. decompressor.Init(this.Session);
  221. return decompressor;
  222. }
  223. /// <summary>
  224. /// Validates the exchange hash.
  225. /// </summary>
  226. /// <returns>true if exchange hash is valid; otherwise false.</returns>
  227. protected abstract bool ValidateExchangeHash();
  228. /// <summary>
  229. /// Calculates key exchange hash value.
  230. /// </summary>
  231. /// <returns>Key exchange hash.</returns>
  232. protected abstract byte[] CalculateHash();
  233. /// <summary>
  234. /// Hashes the specified data bytes.
  235. /// </summary>
  236. /// <param name="hashBytes">Data to hash.</param>
  237. /// <returns>Hashed bytes</returns>
  238. protected virtual byte[] Hash(IEnumerable<byte> hashBytes)
  239. {
  240. using (var sha1 = new Renci.SshNet.Security.Cryptography.SHA1Hash())
  241. {
  242. var hashData = hashBytes.ToArray();
  243. return sha1.ComputeHash(hashData, 0, hashData.Length);
  244. }
  245. }
  246. /// <summary>
  247. /// Sends SSH message to the server
  248. /// </summary>
  249. /// <param name="message">The message.</param>
  250. protected void SendMessage(Message message)
  251. {
  252. this.Session.SendMessage(message);
  253. }
  254. /// <summary>
  255. /// Generates the session key.
  256. /// </summary>
  257. /// <param name="sharedKey">The shared key.</param>
  258. /// <param name="exchangeHash">The exchange hash.</param>
  259. /// <param name="key">The key.</param>
  260. /// <param name="size">The size.</param>
  261. /// <returns></returns>
  262. private byte[] GenerateSessionKey(BigInteger sharedKey, byte[] exchangeHash, IEnumerable<byte> key, int size)
  263. {
  264. var result = new List<byte>(key);
  265. while (size > result.Count)
  266. {
  267. result.AddRange(this.Hash(new _SessionKeyAdjustment
  268. {
  269. SharedKey = sharedKey,
  270. ExcahngeHash = exchangeHash,
  271. Key = key,
  272. }.GetBytes()));
  273. }
  274. return result.ToArray();
  275. }
  276. /// <summary>
  277. /// Generates the session key.
  278. /// </summary>
  279. /// <param name="sharedKey">The shared key.</param>
  280. /// <param name="exchangeHash">The exchange hash.</param>
  281. /// <param name="p">The p.</param>
  282. /// <param name="sessionId">The session id.</param>
  283. /// <returns></returns>
  284. private IEnumerable<byte> GenerateSessionKey(BigInteger sharedKey, IEnumerable<byte> exchangeHash, char p, IEnumerable<byte> sessionId)
  285. {
  286. return new _SessionKeyGeneration
  287. {
  288. SharedKey = sharedKey,
  289. ExchangeHash = exchangeHash,
  290. Char = p,
  291. SessionId = sessionId,
  292. }.GetBytes();
  293. }
  294. private class _SessionKeyGeneration : SshData
  295. {
  296. public BigInteger SharedKey { get; set; }
  297. public IEnumerable<byte> ExchangeHash { get; set; }
  298. public char Char { get; set; }
  299. public IEnumerable<byte> SessionId { get; set; }
  300. protected override void LoadData()
  301. {
  302. throw new NotImplementedException();
  303. }
  304. protected override void SaveData()
  305. {
  306. this.Write(this.SharedKey);
  307. this.Write(this.ExchangeHash);
  308. this.Write((byte)this.Char);
  309. this.Write(this.SessionId);
  310. }
  311. }
  312. private class _SessionKeyAdjustment : SshData
  313. {
  314. public BigInteger SharedKey { get; set; }
  315. public IEnumerable<byte> ExcahngeHash { get; set; }
  316. public IEnumerable<byte> Key { get; set; }
  317. protected override void LoadData()
  318. {
  319. throw new NotImplementedException();
  320. }
  321. protected override void SaveData()
  322. {
  323. this.Write(this.SharedKey);
  324. this.Write(this.ExcahngeHash);
  325. this.Write(this.Key);
  326. }
  327. }
  328. #region IDisposable Members
  329. /// <summary>
  330. /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged ResourceMessages.
  331. /// </summary>
  332. public void Dispose()
  333. {
  334. Dispose(true);
  335. GC.SuppressFinalize(this);
  336. }
  337. /// <summary>
  338. /// Releases unmanaged and - optionally - managed resources
  339. /// </summary>
  340. /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged ResourceMessages.</param>
  341. protected virtual void Dispose(bool disposing)
  342. {
  343. }
  344. /// <summary>
  345. /// Releases unmanaged resources and performs other cleanup operations before the
  346. /// <see cref="KeyExchange"/> is reclaimed by garbage collection.
  347. /// </summary>
  348. ~KeyExchange()
  349. {
  350. // Do not re-create Dispose clean-up code here.
  351. // Calling Dispose(false) is optimal in terms of
  352. // readability and maintainability.
  353. Dispose(false);
  354. }
  355. #endregion
  356. }
  357. }