KeyExchange.cs 17 KB

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