PrivateKeyFile.cs 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Text;
  5. using System.Text.RegularExpressions;
  6. using Renci.SshNet.Abstractions;
  7. using Renci.SshNet.Security;
  8. using Renci.SshNet.Common;
  9. using System.Globalization;
  10. using Renci.SshNet.Security.Cryptography.Ciphers;
  11. using Renci.SshNet.Security.Cryptography.Ciphers.Modes;
  12. using Renci.SshNet.Security.Cryptography.Ciphers.Paddings;
  13. using System.Diagnostics.CodeAnalysis;
  14. using Renci.SshNet.Security.Cryptography;
  15. namespace Renci.SshNet
  16. {
  17. /// <summary>
  18. /// Represents private key information.
  19. /// </summary>
  20. /// <example>
  21. /// <code source="..\..\src\Renci.SshNet.Tests\Data\Key.RSA.txt" language="Text" title="Private RSA key example" />
  22. /// </example>
  23. /// <remarks>
  24. /// <para>
  25. /// The following private keys are supported:
  26. /// <list type="bullet">
  27. /// <item>
  28. /// <description>RSA in OpenSSL PEM, ssh.com and OpenSSH key format</description>
  29. /// </item>
  30. /// <item>
  31. /// <description>DSA in OpenSSL PEM and ssh.com format</description>
  32. /// </item>
  33. /// <item>
  34. /// <description>ECDSA 256/384/521 in OpenSSL PEM and OpenSSH key format</description>
  35. /// </item>
  36. /// <item>
  37. /// <description>ED25519 in OpenSSH key format</description>
  38. /// </item>
  39. /// </list>
  40. /// </para>
  41. /// <para>
  42. /// The following encryption algorithms are supported:
  43. /// <list type="bullet">
  44. /// <item>
  45. /// <description>DES-EDE3-CBC</description>
  46. /// </item>
  47. /// <item>
  48. /// <description>DES-EDE3-CFB</description>
  49. /// </item>
  50. /// <item>
  51. /// <description>DES-CBC</description>
  52. /// </item>
  53. /// <item>
  54. /// <description>AES-128-CBC</description>
  55. /// </item>
  56. /// <item>
  57. /// <description>AES-192-CBC</description>
  58. /// </item>
  59. /// <item>
  60. /// <description>AES-256-CBC</description>
  61. /// </item>
  62. /// </list>
  63. /// </para>
  64. /// </remarks>
  65. public class PrivateKeyFile : IDisposable
  66. {
  67. private static readonly Regex PrivateKeyRegex = new Regex(@"^-+ *BEGIN (?<keyName>\w+( \w+)*) PRIVATE KEY *-+\r?\n((Proc-Type: 4,ENCRYPTED\r?\nDEK-Info: (?<cipherName>[A-Z0-9-]+),(?<salt>[A-F0-9]+)\r?\n\r?\n)|(Comment: ""?[^\r\n]*""?\r?\n))?(?<data>([a-zA-Z0-9/+=]{1,80}\r?\n)+)-+ *END \k<keyName> PRIVATE KEY *-+",
  68. #if FEATURE_REGEX_COMPILE
  69. RegexOptions.Compiled | RegexOptions.Multiline);
  70. #else
  71. RegexOptions.Multiline);
  72. #endif
  73. private Key _key;
  74. /// <summary>
  75. /// Gets the host key.
  76. /// </summary>
  77. public HostAlgorithm HostKey { get; private set; }
  78. /// <summary>
  79. /// Initializes a new instance of the <see cref="PrivateKeyFile"/> class.
  80. /// </summary>
  81. /// <param name="privateKey">The private key.</param>
  82. public PrivateKeyFile(Stream privateKey)
  83. {
  84. Open(privateKey, null);
  85. }
  86. /// <summary>
  87. /// Initializes a new instance of the <see cref="PrivateKeyFile"/> class.
  88. /// </summary>
  89. /// <param name="fileName">Name of the file.</param>
  90. /// <exception cref="ArgumentNullException"><paramref name="fileName"/> is <c>null</c> or empty.</exception>
  91. /// <remarks>This method calls <see cref="System.IO.File.Open(string, System.IO.FileMode)"/> internally, this method does not catch exceptions from <see cref="System.IO.File.Open(string, System.IO.FileMode)"/>.</remarks>
  92. public PrivateKeyFile(string fileName)
  93. : this(fileName, null)
  94. {
  95. }
  96. /// <summary>
  97. /// Initializes a new instance of the <see cref="PrivateKeyFile"/> class.
  98. /// </summary>
  99. /// <param name="fileName">Name of the file.</param>
  100. /// <param name="passPhrase">The pass phrase.</param>
  101. /// <exception cref="ArgumentNullException"><paramref name="fileName"/> is <c>null</c> or empty, or <paramref name="passPhrase"/> is <c>null</c>.</exception>
  102. /// <remarks>This method calls <see cref="System.IO.File.Open(string, System.IO.FileMode)"/> internally, this method does not catch exceptions from <see cref="System.IO.File.Open(string, System.IO.FileMode)"/>.</remarks>
  103. public PrivateKeyFile(string fileName, string passPhrase)
  104. {
  105. if (string.IsNullOrEmpty(fileName))
  106. throw new ArgumentNullException("fileName");
  107. using (var keyFile = File.Open(fileName, FileMode.Open, FileAccess.Read, FileShare.Read))
  108. {
  109. Open(keyFile, passPhrase);
  110. }
  111. }
  112. /// <summary>
  113. /// Initializes a new instance of the <see cref="PrivateKeyFile"/> class.
  114. /// </summary>
  115. /// <param name="privateKey">The private key.</param>
  116. /// <param name="passPhrase">The pass phrase.</param>
  117. /// <exception cref="ArgumentNullException"><paramref name="privateKey"/> or <paramref name="passPhrase"/> is <c>null</c>.</exception>
  118. public PrivateKeyFile(Stream privateKey, string passPhrase)
  119. {
  120. Open(privateKey, passPhrase);
  121. }
  122. /// <summary>
  123. /// Opens the specified private key.
  124. /// </summary>
  125. /// <param name="privateKey">The private key.</param>
  126. /// <param name="passPhrase">The pass phrase.</param>
  127. [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope", Justification = "this._key disposed in Dispose(bool) method.")]
  128. private void Open(Stream privateKey, string passPhrase)
  129. {
  130. if (privateKey == null)
  131. throw new ArgumentNullException("privateKey");
  132. Match privateKeyMatch;
  133. using (var sr = new StreamReader(privateKey))
  134. {
  135. var text = sr.ReadToEnd();
  136. privateKeyMatch = PrivateKeyRegex.Match(text);
  137. }
  138. if (!privateKeyMatch.Success)
  139. {
  140. throw new SshException("Invalid private key file.");
  141. }
  142. var keyName = privateKeyMatch.Result("${keyName}");
  143. var cipherName = privateKeyMatch.Result("${cipherName}");
  144. var salt = privateKeyMatch.Result("${salt}");
  145. var data = privateKeyMatch.Result("${data}");
  146. var binaryData = Convert.FromBase64String(data);
  147. byte[] decryptedData;
  148. if (!string.IsNullOrEmpty(cipherName) && !string.IsNullOrEmpty(salt))
  149. {
  150. if (string.IsNullOrEmpty(passPhrase))
  151. throw new SshPassPhraseNullOrEmptyException("Private key is encrypted but passphrase is empty.");
  152. var binarySalt = new byte[salt.Length / 2];
  153. for (var i = 0; i < binarySalt.Length; i++)
  154. binarySalt[i] = Convert.ToByte(salt.Substring(i * 2, 2), 16);
  155. CipherInfo cipher;
  156. switch (cipherName)
  157. {
  158. case "DES-EDE3-CBC":
  159. cipher = new CipherInfo(192, (key, iv) => new TripleDesCipher(key, new CbcCipherMode(iv), new PKCS7Padding()));
  160. break;
  161. case "DES-EDE3-CFB":
  162. cipher = new CipherInfo(192, (key, iv) => new TripleDesCipher(key, new CfbCipherMode(iv), new PKCS7Padding()));
  163. break;
  164. case "DES-CBC":
  165. cipher = new CipherInfo(64, (key, iv) => new DesCipher(key, new CbcCipherMode(iv), new PKCS7Padding()));
  166. break;
  167. case "AES-128-CBC":
  168. cipher = new CipherInfo(128, (key, iv) => new AesCipher(key, new CbcCipherMode(iv), new PKCS7Padding()));
  169. break;
  170. case "AES-192-CBC":
  171. cipher = new CipherInfo(192, (key, iv) => new AesCipher(key, new CbcCipherMode(iv), new PKCS7Padding()));
  172. break;
  173. case "AES-256-CBC":
  174. cipher = new CipherInfo(256, (key, iv) => new AesCipher(key, new CbcCipherMode(iv), new PKCS7Padding()));
  175. break;
  176. default:
  177. throw new SshException(string.Format(CultureInfo.CurrentCulture, "Private key cipher \"{0}\" is not supported.", cipherName));
  178. }
  179. decryptedData = DecryptKey(cipher, binaryData, passPhrase, binarySalt);
  180. }
  181. else
  182. {
  183. decryptedData = binaryData;
  184. }
  185. switch (keyName)
  186. {
  187. case "RSA":
  188. _key = new RsaKey(decryptedData);
  189. HostKey = new KeyHostAlgorithm("ssh-rsa", _key);
  190. break;
  191. case "DSA":
  192. _key = new DsaKey(decryptedData);
  193. HostKey = new KeyHostAlgorithm("ssh-dss", _key);
  194. break;
  195. #if FEATURE_ECDSA
  196. case "EC":
  197. _key = new EcdsaKey(decryptedData);
  198. HostKey = new KeyHostAlgorithm(_key.ToString(), _key);
  199. break;
  200. #endif
  201. case "OPENSSH":
  202. _key = ParseOpenSshV1Key(decryptedData, passPhrase);
  203. HostKey = new KeyHostAlgorithm(_key.ToString(), _key);
  204. break;
  205. case "SSH2 ENCRYPTED":
  206. var reader = new SshDataReader(decryptedData);
  207. var magicNumber = reader.ReadUInt32();
  208. if (magicNumber != 0x3f6ff9eb)
  209. {
  210. throw new SshException("Invalid SSH2 private key.");
  211. }
  212. reader.ReadUInt32(); // Read total bytes length including magic number
  213. var keyType = reader.ReadString(SshData.Ascii);
  214. var ssh2CipherName = reader.ReadString(SshData.Ascii);
  215. var blobSize = (int)reader.ReadUInt32();
  216. byte[] keyData;
  217. if (ssh2CipherName == "none")
  218. {
  219. keyData = reader.ReadBytes(blobSize);
  220. }
  221. else if (ssh2CipherName == "3des-cbc")
  222. {
  223. if (string.IsNullOrEmpty(passPhrase))
  224. throw new SshPassPhraseNullOrEmptyException("Private key is encrypted but passphrase is empty.");
  225. var key = GetCipherKey(passPhrase, 192 / 8);
  226. var ssh2Сipher = new TripleDesCipher(key, new CbcCipherMode(new byte[8]), new PKCS7Padding());
  227. keyData = ssh2Сipher.Decrypt(reader.ReadBytes(blobSize));
  228. }
  229. else
  230. {
  231. throw new SshException(string.Format("Cipher method '{0}' is not supported.", cipherName));
  232. }
  233. // TODO: Create two specific data types to avoid using SshDataReader class
  234. reader = new SshDataReader(keyData);
  235. var decryptedLength = reader.ReadUInt32();
  236. if (decryptedLength > blobSize - 4)
  237. throw new SshException("Invalid passphrase.");
  238. if (keyType == "if-modn{sign{rsa-pkcs1-sha1},encrypt{rsa-pkcs1v2-oaep}}")
  239. {
  240. var exponent = reader.ReadBigIntWithBits();//e
  241. var d = reader.ReadBigIntWithBits();//d
  242. var modulus = reader.ReadBigIntWithBits();//n
  243. var inverseQ = reader.ReadBigIntWithBits();//u
  244. var q = reader.ReadBigIntWithBits();//p
  245. var p = reader.ReadBigIntWithBits();//q
  246. _key = new RsaKey(modulus, exponent, d, p, q, inverseQ);
  247. HostKey = new KeyHostAlgorithm("ssh-rsa", _key);
  248. }
  249. else if (keyType == "dl-modp{sign{dsa-nist-sha1},dh{plain}}")
  250. {
  251. var zero = reader.ReadUInt32();
  252. if (zero != 0)
  253. {
  254. throw new SshException("Invalid private key");
  255. }
  256. var p = reader.ReadBigIntWithBits();
  257. var g = reader.ReadBigIntWithBits();
  258. var q = reader.ReadBigIntWithBits();
  259. var y = reader.ReadBigIntWithBits();
  260. var x = reader.ReadBigIntWithBits();
  261. _key = new DsaKey(p, q, g, y, x);
  262. HostKey = new KeyHostAlgorithm("ssh-dss", _key);
  263. }
  264. else
  265. {
  266. throw new NotSupportedException(string.Format("Key type '{0}' is not supported.", keyType));
  267. }
  268. break;
  269. default:
  270. throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Key '{0}' is not supported.", keyName));
  271. }
  272. }
  273. private static byte[] GetCipherKey(string passphrase, int length)
  274. {
  275. var cipherKey = new List<byte>();
  276. using (var md5 = CryptoAbstraction.CreateMD5())
  277. {
  278. var passwordBytes = Encoding.UTF8.GetBytes(passphrase);
  279. var hash = md5.ComputeHash(passwordBytes);
  280. cipherKey.AddRange(hash);
  281. while (cipherKey.Count < length)
  282. {
  283. hash = passwordBytes.Concat(hash);
  284. hash = md5.ComputeHash(hash);
  285. cipherKey.AddRange(hash);
  286. }
  287. }
  288. return cipherKey.ToArray().Take(length);
  289. }
  290. /// <summary>
  291. /// Decrypts encrypted private key file data.
  292. /// </summary>
  293. /// <param name="cipherInfo">The cipher info.</param>
  294. /// <param name="cipherData">Encrypted data.</param>
  295. /// <param name="passPhrase">Decryption pass phrase.</param>
  296. /// <param name="binarySalt">Decryption binary salt.</param>
  297. /// <returns>Decrypted byte array.</returns>
  298. /// <exception cref="ArgumentNullException"><paramref name="cipherInfo" />, <paramref name="cipherData" />, <paramref name="passPhrase" /> or <paramref name="binarySalt" /> is <c>null</c>.</exception>
  299. private static byte[] DecryptKey(CipherInfo cipherInfo, byte[] cipherData, string passPhrase, byte[] binarySalt)
  300. {
  301. if (cipherInfo == null)
  302. throw new ArgumentNullException("cipherInfo");
  303. if (cipherData == null)
  304. throw new ArgumentNullException("cipherData");
  305. if (binarySalt == null)
  306. throw new ArgumentNullException("binarySalt");
  307. var cipherKey = new List<byte>();
  308. using (var md5 = CryptoAbstraction.CreateMD5())
  309. {
  310. var passwordBytes = Encoding.UTF8.GetBytes(passPhrase);
  311. // Use 8 bytes binary salt
  312. var initVector = passwordBytes.Concat(binarySalt.Take(8));
  313. var hash = md5.ComputeHash(initVector);
  314. cipherKey.AddRange(hash);
  315. while (cipherKey.Count < cipherInfo.KeySize / 8)
  316. {
  317. hash = hash.Concat(initVector);
  318. hash = md5.ComputeHash(hash);
  319. cipherKey.AddRange(hash);
  320. }
  321. }
  322. var cipher = cipherInfo.Cipher(cipherKey.ToArray(), binarySalt);
  323. return cipher.Decrypt(cipherData);
  324. }
  325. /// <summary>
  326. /// Parses an OpenSSH V1 key file (i.e. ED25519 key) according to the the key spec:
  327. /// https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key.
  328. /// </summary>
  329. /// <param name="keyFileData">the key file data (i.e. base64 encoded data between the header/footer)</param>
  330. /// <param name="passPhrase">passphrase or null if there isn't one</param>
  331. /// <returns></returns>
  332. private Key ParseOpenSshV1Key(byte[] keyFileData, string passPhrase)
  333. {
  334. var keyReader = new SshDataReader(keyFileData);
  335. //check magic header
  336. var authMagic = Encoding.UTF8.GetBytes("openssh-key-v1\0");
  337. var keyHeaderBytes = keyReader.ReadBytes(authMagic.Length);
  338. if (!authMagic.IsEqualTo(keyHeaderBytes))
  339. {
  340. throw new SshException("This openssh key does not contain the 'openssh-key-v1' format magic header");
  341. }
  342. //cipher will be "aes256-cbc" if using a passphrase, "none" otherwise
  343. var cipherName = keyReader.ReadString(Encoding.UTF8);
  344. //key derivation function (kdf): bcrypt or nothing
  345. var kdfName = keyReader.ReadString(Encoding.UTF8);
  346. //kdf options length: 24 if passphrase, 0 if no passphrase
  347. var kdfOptionsLen = (int)keyReader.ReadUInt32();
  348. byte[] salt = null;
  349. int rounds = 0;
  350. if (kdfOptionsLen > 0)
  351. {
  352. var saltLength = (int)keyReader.ReadUInt32();
  353. salt = keyReader.ReadBytes(saltLength);
  354. rounds = (int)keyReader.ReadUInt32();
  355. }
  356. //number of public keys, only supporting 1 for now
  357. var numberOfPublicKeys = (int)keyReader.ReadUInt32();
  358. if (numberOfPublicKeys != 1)
  359. {
  360. throw new SshException("At this time only one public key in the openssh key is supported.");
  361. }
  362. //read public key in ssh-format, but we dont need it
  363. keyReader.ReadString(Encoding.UTF8);
  364. //possibly encrypted private key
  365. var privateKeyLength = (int)keyReader.ReadUInt32();
  366. var privateKeyBytes = keyReader.ReadBytes(privateKeyLength);
  367. //decrypt private key if necessary
  368. if (cipherName != "none")
  369. {
  370. if (string.IsNullOrEmpty(passPhrase))
  371. {
  372. throw new SshPassPhraseNullOrEmptyException("Private key is encrypted but passphrase is empty.");
  373. }
  374. if (string.IsNullOrEmpty(kdfName) || kdfName != "bcrypt")
  375. {
  376. throw new SshException("kdf " + kdfName + " is not supported for openssh key file");
  377. }
  378. //inspired by the SSHj library (https://github.com/hierynomus/sshj)
  379. //apply the kdf to derive a key and iv from the passphrase
  380. var passPhraseBytes = Encoding.UTF8.GetBytes(passPhrase);
  381. byte[] keyiv = new byte[48];
  382. new BCrypt().Pbkdf(passPhraseBytes, salt, rounds, keyiv);
  383. byte[] key = new byte[32];
  384. Array.Copy(keyiv, 0, key, 0, 32);
  385. byte[] iv = new byte[16];
  386. Array.Copy(keyiv, 32, iv, 0, 16);
  387. AesCipher cipher;
  388. switch (cipherName)
  389. {
  390. case "aes256-cbc":
  391. cipher = new AesCipher(key, new CbcCipherMode(iv), new PKCS7Padding());
  392. break;
  393. case "aes256-ctr":
  394. cipher = new AesCipher(key, new CtrCipherMode(iv), new PKCS7Padding());
  395. break;
  396. default:
  397. throw new SshException("Cipher '" + cipherName + "' is not supported for an OpenSSH key.");
  398. }
  399. privateKeyBytes = cipher.Decrypt(privateKeyBytes);
  400. }
  401. //validate private key length
  402. privateKeyLength = privateKeyBytes.Length;
  403. if (privateKeyLength % 8 != 0)
  404. {
  405. throw new SshException("The private key section must be a multiple of the block size (8)");
  406. }
  407. //now parse the data we called the private key, it actually contains the public key again
  408. //so we need to parse through it to get the private key bytes, plus there's some
  409. //validation we need to do.
  410. var privateKeyReader = new SshDataReader(privateKeyBytes);
  411. //check ints should match, they wouldn't match for example if the wrong passphrase was supplied
  412. int checkInt1 = (int)privateKeyReader.ReadUInt32();
  413. int checkInt2 = (int)privateKeyReader.ReadUInt32();
  414. if (checkInt1 != checkInt2)
  415. throw new SshException("The random check bytes of the OpenSSH key do not match (" + checkInt1 + " <->" + checkInt2 + ").");
  416. //key type
  417. var keyType = privateKeyReader.ReadString(Encoding.UTF8);
  418. Key parsedKey;
  419. byte[] publicKey;
  420. byte[] unencryptedPrivateKey;
  421. switch (keyType)
  422. {
  423. case "ssh-ed25519":
  424. //public key
  425. publicKey = privateKeyReader.ReadBignum2();
  426. //private key
  427. unencryptedPrivateKey = privateKeyReader.ReadBignum2();
  428. parsedKey = new ED25519Key(publicKey.Reverse(), unencryptedPrivateKey);
  429. break;
  430. #if FEATURE_ECDSA
  431. case "ecdsa-sha2-nistp256":
  432. case "ecdsa-sha2-nistp384":
  433. case "ecdsa-sha2-nistp521":
  434. // curve
  435. int len = (int)privateKeyReader.ReadUInt32();
  436. var curve = Encoding.ASCII.GetString(privateKeyReader.ReadBytes(len));
  437. //public key
  438. publicKey = privateKeyReader.ReadBignum2();
  439. //private key
  440. unencryptedPrivateKey = privateKeyReader.ReadBignum2();
  441. parsedKey = new EcdsaKey(curve, publicKey, unencryptedPrivateKey.TrimLeadingZeros());
  442. break;
  443. #endif
  444. case "ssh-rsa":
  445. var modulus = privateKeyReader.ReadBignum(); //n
  446. var exponent = privateKeyReader.ReadBignum(); //e
  447. var d = privateKeyReader.ReadBignum(); //d
  448. var inverseQ = privateKeyReader.ReadBignum(); // iqmp
  449. var p = privateKeyReader.ReadBignum(); //p
  450. var q = privateKeyReader.ReadBignum(); //q
  451. parsedKey = new RsaKey(modulus, exponent, d, p, q, inverseQ);
  452. break;
  453. default:
  454. throw new SshException("OpenSSH key type '" + keyType + "' is not supported.");
  455. }
  456. //comment, we don't need this but we could log it, not sure if necessary
  457. var comment = privateKeyReader.ReadString(Encoding.UTF8);
  458. //The list of privatekey/comment pairs is padded with the bytes 1, 2, 3, ...
  459. //until the total length is a multiple of the cipher block size.
  460. var padding = privateKeyReader.ReadBytes();
  461. for (int i = 0; i < padding.Length; i++)
  462. {
  463. if ((int)padding[i] != i + 1)
  464. {
  465. throw new SshException("Padding of openssh key format contained wrong byte at position: " + i);
  466. }
  467. }
  468. return parsedKey;
  469. }
  470. #region IDisposable Members
  471. private bool _isDisposed;
  472. /// <summary>
  473. /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
  474. /// </summary>
  475. public void Dispose()
  476. {
  477. Dispose(true);
  478. GC.SuppressFinalize(this);
  479. }
  480. /// <summary>
  481. /// Releases unmanaged and - optionally - managed resources
  482. /// </summary>
  483. /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
  484. protected virtual void Dispose(bool disposing)
  485. {
  486. if (_isDisposed)
  487. return;
  488. if (disposing)
  489. {
  490. var key = _key;
  491. if (key != null)
  492. {
  493. ((IDisposable) key).Dispose();
  494. _key = null;
  495. }
  496. _isDisposed = true;
  497. }
  498. }
  499. /// <summary>
  500. /// Releases unmanaged resources and performs other cleanup operations before the
  501. /// <see cref="PrivateKeyFile"/> is reclaimed by garbage collection.
  502. /// </summary>
  503. ~PrivateKeyFile()
  504. {
  505. Dispose(false);
  506. }
  507. #endregion
  508. private class SshDataReader : SshData
  509. {
  510. public SshDataReader(byte[] data)
  511. {
  512. Load(data);
  513. }
  514. public new uint ReadUInt32()
  515. {
  516. return base.ReadUInt32();
  517. }
  518. public new string ReadString(Encoding encoding)
  519. {
  520. return base.ReadString(encoding);
  521. }
  522. public new byte[] ReadBytes(int length)
  523. {
  524. return base.ReadBytes(length);
  525. }
  526. public new byte[] ReadBytes()
  527. {
  528. return base.ReadBytes();
  529. }
  530. /// <summary>
  531. /// Reads next mpint data type from internal buffer where length specified in bits.
  532. /// </summary>
  533. /// <returns>mpint read.</returns>
  534. public BigInteger ReadBigIntWithBits()
  535. {
  536. var length = (int) base.ReadUInt32();
  537. length = (length + 7) / 8;
  538. var data = base.ReadBytes(length);
  539. var bytesArray = new byte[data.Length + 1];
  540. Buffer.BlockCopy(data, 0, bytesArray, 1, data.Length);
  541. return new BigInteger(bytesArray.Reverse());
  542. }
  543. public BigInteger ReadBignum()
  544. {
  545. return new BigInteger(ReadBignum2().Reverse());
  546. }
  547. public byte[] ReadBignum2()
  548. {
  549. var length = (int)base.ReadUInt32();
  550. return base.ReadBytes(length);
  551. }
  552. protected override void LoadData()
  553. {
  554. }
  555. protected override void SaveData()
  556. {
  557. }
  558. }
  559. }
  560. }