KeyExchange.cs 16 KB

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