KeyExchange.cs 19 KB

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