KeyExchange.cs 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Security.Cryptography;
  5. using Microsoft.Extensions.Logging;
  6. using Renci.SshNet.Common;
  7. using Renci.SshNet.Compression;
  8. using Renci.SshNet.Messages;
  9. using Renci.SshNet.Messages.Transport;
  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, IKeyExchange
  17. {
  18. private readonly ILogger _logger;
  19. private CipherInfo _clientCipherInfo;
  20. private CipherInfo _serverCipherInfo;
  21. private HashInfo _clientHashInfo;
  22. private HashInfo _serverHashInfo;
  23. private Func<Compressor> _compressorFactory;
  24. private Func<Compressor> _decompressorFactory;
  25. /// <summary>
  26. /// Gets the session.
  27. /// </summary>
  28. /// <value>
  29. /// The session.
  30. /// </value>
  31. protected Session Session { get; private set; }
  32. /// <summary>
  33. /// Gets or sets key exchange shared key.
  34. /// </summary>
  35. /// <value>
  36. /// The shared key.
  37. /// </value>
  38. public byte[] SharedKey { get; protected set; }
  39. private byte[] _exchangeHash;
  40. /// <summary>
  41. /// Gets the exchange hash.
  42. /// </summary>
  43. /// <value>The exchange hash.</value>
  44. public byte[] ExchangeHash
  45. {
  46. get
  47. {
  48. _exchangeHash ??= CalculateHash();
  49. return _exchangeHash;
  50. }
  51. }
  52. /// <summary>
  53. /// Occurs when host key received.
  54. /// </summary>
  55. public event EventHandler<HostKeyEventArgs> HostKeyReceived;
  56. /// <summary>
  57. /// Initializes a new instance of the <see cref="KeyExchange"/> class.
  58. /// </summary>
  59. protected KeyExchange()
  60. {
  61. _logger = SshNetLoggingConfiguration.LoggerFactory.CreateLogger(GetType());
  62. }
  63. /// <inheritdoc/>
  64. public virtual void Start(Session session, KeyExchangeInitMessage message, bool sendClientInitMessage)
  65. {
  66. Session = session;
  67. if (sendClientInitMessage)
  68. {
  69. SendMessage(session.ClientInitMessage);
  70. }
  71. // Determine client encryption algorithm
  72. var clientEncryptionAlgorithmName = (from b in session.ConnectionInfo.Encryptions.Keys
  73. from a in message.EncryptionAlgorithmsClientToServer
  74. where a == b
  75. select a).FirstOrDefault();
  76. if (_logger.IsEnabled(LogLevel.Trace))
  77. {
  78. _logger.LogTrace("[{SessionId}] Encryption client to server: we offer {WeOffer}",
  79. Session.SessionIdHex,
  80. session.ConnectionInfo.Encryptions.Keys.Join(","));
  81. _logger.LogTrace("[{SessionId}] Encryption client to server: they offer {TheyOffer}",
  82. Session.SessionIdHex,
  83. message.EncryptionAlgorithmsClientToServer.Join(","));
  84. }
  85. if (string.IsNullOrEmpty(clientEncryptionAlgorithmName))
  86. {
  87. throw new SshConnectionException("Client encryption algorithm not found", DisconnectReason.KeyExchangeFailed);
  88. }
  89. session.ConnectionInfo.CurrentClientEncryption = clientEncryptionAlgorithmName;
  90. _clientCipherInfo = session.ConnectionInfo.Encryptions[clientEncryptionAlgorithmName];
  91. // Determine server encryption algorithm
  92. var serverDecryptionAlgorithmName = (from b in session.ConnectionInfo.Encryptions.Keys
  93. from a in message.EncryptionAlgorithmsServerToClient
  94. where a == b
  95. select a).FirstOrDefault();
  96. if (_logger.IsEnabled(LogLevel.Trace))
  97. {
  98. _logger.LogTrace("[{SessionId}] Encryption server to client: we offer {WeOffer}",
  99. Session.SessionIdHex,
  100. session.ConnectionInfo.Encryptions.Keys.Join(","));
  101. _logger.LogTrace("[{SessionId}] Encryption server to client: they offer {TheyOffer}",
  102. Session.SessionIdHex,
  103. message.EncryptionAlgorithmsServerToClient.Join(","));
  104. }
  105. if (string.IsNullOrEmpty(serverDecryptionAlgorithmName))
  106. {
  107. throw new SshConnectionException("Server decryption algorithm not found", DisconnectReason.KeyExchangeFailed);
  108. }
  109. session.ConnectionInfo.CurrentServerEncryption = serverDecryptionAlgorithmName;
  110. _serverCipherInfo = session.ConnectionInfo.Encryptions[serverDecryptionAlgorithmName];
  111. if (!_clientCipherInfo.IsAead)
  112. {
  113. // Determine client hmac algorithm
  114. var clientHmacAlgorithmName = (from b in session.ConnectionInfo.HmacAlgorithms.Keys
  115. from a in message.MacAlgorithmsClientToServer
  116. where a == b
  117. select a).FirstOrDefault();
  118. if (_logger.IsEnabled(LogLevel.Trace))
  119. {
  120. _logger.LogTrace("[{SessionId}] MAC client to server: we offer {WeOffer}",
  121. Session.SessionIdHex,
  122. session.ConnectionInfo.HmacAlgorithms.Keys.Join(","));
  123. _logger.LogTrace("[{SessionId}] MAC client to server: they offer {TheyOffer}",
  124. Session.SessionIdHex,
  125. message.MacAlgorithmsClientToServer.Join(","));
  126. }
  127. if (string.IsNullOrEmpty(clientHmacAlgorithmName))
  128. {
  129. throw new SshConnectionException("Client HMAC algorithm not found", DisconnectReason.KeyExchangeFailed);
  130. }
  131. session.ConnectionInfo.CurrentClientHmacAlgorithm = clientHmacAlgorithmName;
  132. _clientHashInfo = session.ConnectionInfo.HmacAlgorithms[clientHmacAlgorithmName];
  133. }
  134. if (!_serverCipherInfo.IsAead)
  135. {
  136. // Determine server hmac algorithm
  137. var serverHmacAlgorithmName = (from b in session.ConnectionInfo.HmacAlgorithms.Keys
  138. from a in message.MacAlgorithmsServerToClient
  139. where a == b
  140. select a).FirstOrDefault();
  141. if (_logger.IsEnabled(LogLevel.Trace))
  142. {
  143. _logger.LogTrace("[{SessionId}] MAC server to client: we offer {WeOffer}",
  144. Session.SessionIdHex,
  145. session.ConnectionInfo.HmacAlgorithms.Keys.Join(","));
  146. _logger.LogTrace("[{SessionId}] MAC server to client: they offer {TheyOffer}",
  147. Session.SessionIdHex,
  148. message.MacAlgorithmsServerToClient.Join(","));
  149. }
  150. if (string.IsNullOrEmpty(serverHmacAlgorithmName))
  151. {
  152. throw new SshConnectionException("Server HMAC algorithm not found", DisconnectReason.KeyExchangeFailed);
  153. }
  154. session.ConnectionInfo.CurrentServerHmacAlgorithm = serverHmacAlgorithmName;
  155. _serverHashInfo = session.ConnectionInfo.HmacAlgorithms[serverHmacAlgorithmName];
  156. }
  157. // Determine compression algorithm
  158. var compressionAlgorithmName = (from b in session.ConnectionInfo.CompressionAlgorithms.Keys
  159. from a in message.CompressionAlgorithmsClientToServer
  160. where a == b
  161. select a).FirstOrDefault();
  162. if (_logger.IsEnabled(LogLevel.Trace))
  163. {
  164. _logger.LogTrace("[{SessionId}] Compression client to server: we offer {WeOffer}",
  165. Session.SessionIdHex,
  166. session.ConnectionInfo.CompressionAlgorithms.Keys.Join(","));
  167. _logger.LogTrace("[{SessionId}] Compression client to server: they offer {TheyOffer}",
  168. Session.SessionIdHex,
  169. message.CompressionAlgorithmsClientToServer.Join(","));
  170. }
  171. if (string.IsNullOrEmpty(compressionAlgorithmName))
  172. {
  173. throw new SshConnectionException("Compression algorithm not found", DisconnectReason.KeyExchangeFailed);
  174. }
  175. session.ConnectionInfo.CurrentClientCompressionAlgorithm = compressionAlgorithmName;
  176. _compressorFactory = session.ConnectionInfo.CompressionAlgorithms[compressionAlgorithmName];
  177. // Determine decompression algorithm
  178. var decompressionAlgorithmName = (from b in session.ConnectionInfo.CompressionAlgorithms.Keys
  179. from a in message.CompressionAlgorithmsServerToClient
  180. where a == b
  181. select a).FirstOrDefault();
  182. if (_logger.IsEnabled(LogLevel.Trace))
  183. {
  184. _logger.LogTrace("[{SessionId}] Compression server to client: we offer {WeOffer}",
  185. Session.SessionIdHex,
  186. session.ConnectionInfo.CompressionAlgorithms.Keys.Join(","));
  187. _logger.LogTrace("[{SessionId}] Compression server to client: they offer {TheyOffer}",
  188. Session.SessionIdHex,
  189. message.CompressionAlgorithmsServerToClient.Join(","));
  190. }
  191. if (string.IsNullOrEmpty(decompressionAlgorithmName))
  192. {
  193. throw new SshConnectionException("Decompression algorithm not found", DisconnectReason.KeyExchangeFailed);
  194. }
  195. session.ConnectionInfo.CurrentServerCompressionAlgorithm = decompressionAlgorithmName;
  196. _decompressorFactory = session.ConnectionInfo.CompressionAlgorithms[decompressionAlgorithmName];
  197. }
  198. /// <summary>
  199. /// Finishes key exchange algorithm.
  200. /// </summary>
  201. public virtual void Finish()
  202. {
  203. if (!ValidateExchangeHash())
  204. {
  205. throw new SshConnectionException("Key exchange negotiation failed.", DisconnectReason.KeyExchangeFailed);
  206. }
  207. SendMessage(new NewKeysMessage());
  208. }
  209. /// <summary>
  210. /// Creates the server side cipher to use.
  211. /// </summary>
  212. /// <param name="isAead"><see langword="true"/> to indicate the cipher is AEAD, <see langword="false"/> to indicate the cipher is not AEAD.</param>
  213. /// <returns>Server cipher.</returns>
  214. public Cipher CreateServerCipher(out bool isAead)
  215. {
  216. isAead = _serverCipherInfo.IsAead;
  217. // Resolve Session ID
  218. var sessionId = Session.SessionId ?? ExchangeHash;
  219. // Calculate server to client initial IV
  220. var serverVector = Hash(GenerateSessionKey(SharedKey, ExchangeHash, 'B', sessionId));
  221. // Calculate server to client encryption
  222. var serverKey = Hash(GenerateSessionKey(SharedKey, ExchangeHash, 'D', sessionId));
  223. serverKey = GenerateSessionKey(SharedKey, ExchangeHash, serverKey, _serverCipherInfo.KeySize / 8);
  224. _logger.LogDebug("[{SessionId}] Creating {ServerEncryption} server cipher.",
  225. Session.SessionIdHex,
  226. Session.ConnectionInfo.CurrentServerEncryption);
  227. // Create server cipher
  228. return _serverCipherInfo.Cipher(serverKey, serverVector);
  229. }
  230. /// <summary>
  231. /// Creates the client side cipher to use.
  232. /// </summary>
  233. /// <param name="isAead"><see langword="true"/> to indicate the cipher is AEAD, <see langword="false"/> to indicate the cipher is not AEAD.</param>
  234. /// <returns>Client cipher.</returns>
  235. public Cipher CreateClientCipher(out bool isAead)
  236. {
  237. isAead = _clientCipherInfo.IsAead;
  238. // Resolve Session ID
  239. var sessionId = Session.SessionId ?? ExchangeHash;
  240. // Calculate client to server initial IV
  241. var clientVector = Hash(GenerateSessionKey(SharedKey, ExchangeHash, 'A', sessionId));
  242. // Calculate client to server encryption
  243. var clientKey = Hash(GenerateSessionKey(SharedKey, ExchangeHash, 'C', sessionId));
  244. clientKey = GenerateSessionKey(SharedKey, ExchangeHash, clientKey, _clientCipherInfo.KeySize / 8);
  245. _logger.LogDebug("[{SessionId}] Creating {ClientEncryption} client cipher.",
  246. Session.SessionIdHex,
  247. Session.ConnectionInfo.CurrentClientEncryption);
  248. // Create client cipher
  249. return _clientCipherInfo.Cipher(clientKey, clientVector);
  250. }
  251. /// <summary>
  252. /// Creates the server side hash algorithm to use.
  253. /// </summary>
  254. /// <param name="isEncryptThenMAC"><see langword="true"/> to enable encrypt-then-MAC, <see langword="false"/> to use encrypt-and-MAC.</param>
  255. /// <returns>
  256. /// The server-side hash algorithm.
  257. /// </returns>
  258. public HashAlgorithm CreateServerHash(out bool isEncryptThenMAC)
  259. {
  260. if (_serverHashInfo == null)
  261. {
  262. isEncryptThenMAC = false;
  263. return null;
  264. }
  265. isEncryptThenMAC = _serverHashInfo.IsEncryptThenMAC;
  266. // Resolve Session ID
  267. var sessionId = Session.SessionId ?? ExchangeHash;
  268. var serverKey = GenerateSessionKey(SharedKey,
  269. ExchangeHash,
  270. Hash(GenerateSessionKey(SharedKey, ExchangeHash, 'F', sessionId)),
  271. _serverHashInfo.KeySize / 8);
  272. _logger.LogDebug("[{SessionId}] Creating {ServerHmacAlgorithm} server hmac algorithm.",
  273. Session.SessionIdHex,
  274. Session.ConnectionInfo.CurrentServerHmacAlgorithm);
  275. return _serverHashInfo.HashAlgorithm(serverKey);
  276. }
  277. /// <summary>
  278. /// Creates the client side hash algorithm to use.
  279. /// </summary>
  280. /// <param name="isEncryptThenMAC"><see langword="true"/> to enable encrypt-then-MAC, <see langword="false"/> to use encrypt-and-MAC.</param>
  281. /// <returns>
  282. /// The client-side hash algorithm.
  283. /// </returns>
  284. public HashAlgorithm CreateClientHash(out bool isEncryptThenMAC)
  285. {
  286. if (_clientHashInfo == null)
  287. {
  288. isEncryptThenMAC = false;
  289. return null;
  290. }
  291. isEncryptThenMAC = _clientHashInfo.IsEncryptThenMAC;
  292. // Resolve Session ID
  293. var sessionId = Session.SessionId ?? ExchangeHash;
  294. var clientKey = GenerateSessionKey(SharedKey,
  295. ExchangeHash,
  296. Hash(GenerateSessionKey(SharedKey, ExchangeHash, 'E', sessionId)),
  297. _clientHashInfo.KeySize / 8);
  298. _logger.LogDebug("[{SessionId}] Creating {ClientHmacAlgorithm} client hmac algorithm.",
  299. Session.SessionIdHex,
  300. Session.ConnectionInfo.CurrentClientHmacAlgorithm);
  301. return _clientHashInfo.HashAlgorithm(clientKey);
  302. }
  303. /// <summary>
  304. /// Creates the compression algorithm to use to deflate data.
  305. /// </summary>
  306. /// <returns>
  307. /// The compression method.
  308. /// </returns>
  309. public Compressor CreateCompressor()
  310. {
  311. if (_compressorFactory is null)
  312. {
  313. return null;
  314. }
  315. _logger.LogDebug("[{SessionId}] Creating {CompressionAlgorithm} client compressor.",
  316. Session.SessionIdHex,
  317. Session.ConnectionInfo.CurrentClientCompressionAlgorithm);
  318. var compressor = _compressorFactory();
  319. compressor.Init(Session);
  320. return compressor;
  321. }
  322. /// <summary>
  323. /// Creates the compression algorithm to use to inflate data.
  324. /// </summary>
  325. /// <returns>
  326. /// The decompression method.
  327. /// </returns>
  328. public Compressor CreateDecompressor()
  329. {
  330. if (_decompressorFactory is null)
  331. {
  332. return null;
  333. }
  334. _logger.LogDebug("[{SessionId}] Creating {ServerCompressionAlgorithm} server decompressor.",
  335. Session.SessionIdHex,
  336. Session.ConnectionInfo.CurrentServerCompressionAlgorithm);
  337. var decompressor = _decompressorFactory();
  338. decompressor.Init(Session);
  339. return decompressor;
  340. }
  341. /// <summary>
  342. /// Determines whether the specified host key can be trusted.
  343. /// </summary>
  344. /// <param name="host">The host algorithm.</param>
  345. /// <returns>
  346. /// <see langword="true"/> if the specified host can be trusted; otherwise, <see langword="false"/>.
  347. /// </returns>
  348. protected bool CanTrustHostKey(KeyHostAlgorithm host)
  349. {
  350. var handlers = HostKeyReceived;
  351. if (handlers != null)
  352. {
  353. var args = new HostKeyEventArgs(host);
  354. handlers(this, args);
  355. return args.CanTrust;
  356. }
  357. return true;
  358. }
  359. /// <summary>
  360. /// Validates the exchange hash.
  361. /// </summary>
  362. /// <returns>true if exchange hash is valid; otherwise false.</returns>
  363. protected abstract bool ValidateExchangeHash();
  364. private protected bool ValidateExchangeHash(byte[] encodedKey, byte[] encodedSignature)
  365. {
  366. var exchangeHash = CalculateHash();
  367. // We need to inspect both the key and signature format identifers to find the correct
  368. // HostAlgorithm instance. Example cases:
  369. // Key identifier Signature identifier | Algorithm name
  370. // ssh-rsa ssh-rsa | ssh-rsa
  371. // ssh-rsa rsa-sha2-256 | rsa-sha2-256
  372. // ssh-rsa-cert-v01@openssh.com ssh-rsa | ssh-rsa-cert-v01@openssh.com
  373. // ssh-rsa-cert-v01@openssh.com rsa-sha2-256 | rsa-sha2-256-cert-v01@openssh.com
  374. var signatureData = new KeyHostAlgorithm.SignatureKeyData();
  375. signatureData.Load(encodedSignature);
  376. string keyName;
  377. using (var keyReader = new SshDataStream(encodedKey))
  378. {
  379. keyName = keyReader.ReadString();
  380. }
  381. string algorithmName;
  382. if (signatureData.AlgorithmName.StartsWith("rsa-sha2", StringComparison.Ordinal))
  383. {
  384. algorithmName = keyName.Replace("ssh-rsa", signatureData.AlgorithmName);
  385. }
  386. else
  387. {
  388. algorithmName = keyName;
  389. }
  390. var keyAlgorithm = Session.ConnectionInfo.HostKeyAlgorithms[algorithmName](encodedKey);
  391. Session.ConnectionInfo.CurrentHostKeyAlgorithm = algorithmName;
  392. return keyAlgorithm.VerifySignatureBlob(exchangeHash, signatureData.Signature) && CanTrustHostKey(keyAlgorithm);
  393. }
  394. /// <summary>
  395. /// Calculates key exchange hash value.
  396. /// </summary>
  397. /// <returns>Key exchange hash.</returns>
  398. protected abstract byte[] CalculateHash();
  399. /// <summary>
  400. /// Hashes the specified data bytes.
  401. /// </summary>
  402. /// <param name="hashData">The hash data.</param>
  403. /// <returns>
  404. /// The hash of the data.
  405. /// </returns>
  406. protected abstract byte[] Hash(byte[] hashData);
  407. /// <summary>
  408. /// Sends SSH message to the server.
  409. /// </summary>
  410. /// <param name="message">The message.</param>
  411. protected void SendMessage(Message message)
  412. {
  413. Session.SendMessage(message);
  414. }
  415. /// <summary>
  416. /// Generates the session key.
  417. /// </summary>
  418. /// <param name="sharedKey">The shared key.</param>
  419. /// <param name="exchangeHash">The exchange hash.</param>
  420. /// <param name="key">The key.</param>
  421. /// <param name="size">The size.</param>
  422. /// <returns>
  423. /// The session key.
  424. /// </returns>
  425. private byte[] GenerateSessionKey(byte[] sharedKey, byte[] exchangeHash, byte[] key, int size)
  426. {
  427. var result = new List<byte>(key);
  428. while (size > result.Count)
  429. {
  430. var sessionKeyAdjustment = new SessionKeyAdjustment
  431. {
  432. SharedKey = sharedKey,
  433. ExchangeHash = exchangeHash,
  434. Key = key,
  435. };
  436. result.AddRange(Hash(sessionKeyAdjustment.GetBytes()));
  437. }
  438. return result.ToArray();
  439. }
  440. /// <summary>
  441. /// Generates the session key.
  442. /// </summary>
  443. /// <param name="sharedKey">The shared key.</param>
  444. /// <param name="exchangeHash">The exchange hash.</param>
  445. /// <param name="p">The p.</param>
  446. /// <param name="sessionId">The session id.</param>
  447. /// <returns>
  448. /// The session key.
  449. /// </returns>
  450. private static byte[] GenerateSessionKey(byte[] sharedKey, byte[] exchangeHash, char p, byte[] sessionId)
  451. {
  452. var sessionKeyGeneration = new SessionKeyGeneration
  453. {
  454. SharedKey = sharedKey,
  455. ExchangeHash = exchangeHash,
  456. Char = p,
  457. SessionId = sessionId
  458. };
  459. return sessionKeyGeneration.GetBytes();
  460. }
  461. private sealed class SessionKeyGeneration : SshData
  462. {
  463. public byte[] SharedKey { get; set; }
  464. public byte[] ExchangeHash { get; set; }
  465. public char Char { get; set; }
  466. public byte[] SessionId { get; set; }
  467. /// <summary>
  468. /// Gets the size of the message in bytes.
  469. /// </summary>
  470. /// <value>
  471. /// The size of the messages in bytes.
  472. /// </value>
  473. protected override int BufferCapacity
  474. {
  475. get
  476. {
  477. var capacity = base.BufferCapacity;
  478. capacity += 4; // SharedKey length
  479. capacity += SharedKey.Length; // SharedKey
  480. capacity += ExchangeHash.Length; // ExchangeHash
  481. capacity += 1; // Char
  482. capacity += SessionId.Length; // SessionId
  483. return capacity;
  484. }
  485. }
  486. protected override void LoadData()
  487. {
  488. throw new NotImplementedException();
  489. }
  490. protected override void SaveData()
  491. {
  492. WriteBinaryString(SharedKey);
  493. Write(ExchangeHash);
  494. Write((byte)Char);
  495. Write(SessionId);
  496. }
  497. }
  498. private sealed class SessionKeyAdjustment : SshData
  499. {
  500. public byte[] SharedKey { get; set; }
  501. public byte[] ExchangeHash { get; set; }
  502. public byte[] Key { get; set; }
  503. /// <summary>
  504. /// Gets the size of the message in bytes.
  505. /// </summary>
  506. /// <value>
  507. /// The size of the messages in bytes.
  508. /// </value>
  509. protected override int BufferCapacity
  510. {
  511. get
  512. {
  513. var capacity = base.BufferCapacity;
  514. capacity += 4; // SharedKey length
  515. capacity += SharedKey.Length; // SharedKey
  516. capacity += ExchangeHash.Length; // ExchangeHash
  517. capacity += Key.Length; // Key
  518. return capacity;
  519. }
  520. }
  521. protected override void LoadData()
  522. {
  523. throw new NotImplementedException();
  524. }
  525. protected override void SaveData()
  526. {
  527. WriteBinaryString(SharedKey);
  528. Write(ExchangeHash);
  529. Write(Key);
  530. }
  531. }
  532. #region IDisposable Members
  533. /// <summary>
  534. /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
  535. /// </summary>
  536. public void Dispose()
  537. {
  538. Dispose(disposing: true);
  539. GC.SuppressFinalize(this);
  540. }
  541. /// <summary>
  542. /// Releases unmanaged and - optionally - managed resources.
  543. /// </summary>
  544. /// <param name="disposing"><see langword="true"/> to release both managed and unmanaged resources; <see langword="false"/> to release only unmanaged resources.</param>
  545. protected virtual void Dispose(bool disposing)
  546. {
  547. }
  548. #endregion
  549. }
  550. }