PrivateKeyFile.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. using System;
  2. using System.Linq;
  3. using System.Collections.Generic;
  4. using System.IO;
  5. using System.Text;
  6. using System.Text.RegularExpressions;
  7. using Renci.SshNet.Security;
  8. using System.Security.Cryptography;
  9. using System.Security;
  10. using Renci.SshNet.Common;
  11. using System.Globalization;
  12. using Renci.SshNet.Security.Cryptography;
  13. using Renci.SshNet.Security.Cryptography.Ciphers;
  14. using Renci.SshNet.Security.Cryptography.Ciphers.Modes;
  15. using Renci.SshNet.Security.Cryptography.Ciphers.Paddings;
  16. namespace Renci.SshNet
  17. {
  18. /// <summary>
  19. /// old private key information/
  20. /// </summary>
  21. public class PrivateKeyFile
  22. {
  23. #if SILVERLIGHT
  24. private static Regex _privateKeyRegex = new Regex(@"^-----BEGIN (?<keyName>\w+) PRIVATE KEY-----\r?\n(Proc-Type: 4,ENCRYPTED\r?\nDEK-Info: (?<cipherName>[A-Z0-9-]+),(?<salt>[A-F0-9]{16})\r?\n\r?\n)?(?<data>([a-zA-Z0-9/+=]{1,64}\r?\n)+)-----END \k<keyName> PRIVATE KEY-----.*", RegexOptions.Multiline);
  25. #else
  26. private static Regex _privateKeyRegex = new Regex(@"^-----BEGIN (?<keyName>\w+) PRIVATE KEY-----\r?\n(Proc-Type: 4,ENCRYPTED\r?\nDEK-Info: (?<cipherName>[A-Z0-9-]+),(?<salt>[A-F0-9]{16})\r?\n\r?\n)?(?<data>([a-zA-Z0-9/+=]{1,64}\r?\n)+)-----END \k<keyName> PRIVATE KEY-----.*", RegexOptions.Compiled | RegexOptions.Multiline);
  27. #endif
  28. private CryptoPrivateKey _key;
  29. /// <summary>
  30. /// Gets the name of private key algorithm.
  31. /// </summary>
  32. /// <value>
  33. /// The name of the algorithm.
  34. /// </value>
  35. public string AlgorithmName
  36. {
  37. get
  38. {
  39. return this._key.Name;
  40. }
  41. }
  42. /// <summary>
  43. /// Gets the public key.
  44. /// </summary>
  45. public byte[] PublicKey
  46. {
  47. get
  48. {
  49. return this._key.GetPublicKey().GetBytes().ToArray();
  50. }
  51. }
  52. /// <summary>
  53. /// Gets the signature.
  54. /// </summary>
  55. /// <param name="sessionId">The session id.</param>
  56. /// <returns>Signature data</returns>
  57. public byte[] GetSignature(IEnumerable<byte> sessionId)
  58. {
  59. return this._key.GetSignature(sessionId);
  60. }
  61. /// <summary>
  62. /// Initializes a new instance of the <see cref="PrivateKeyFile"/> class.
  63. /// </summary>
  64. /// <param name="privateKey">The private key.</param>
  65. public PrivateKeyFile(Stream privateKey)
  66. {
  67. this.Open(privateKey, null);
  68. }
  69. /// <summary>
  70. /// Initializes a new instance of the <see cref="PrivateKeyFile"/> class.
  71. /// </summary>
  72. /// <param name="fileName">Name of the file.</param>
  73. /// <exception cref="ArgumentNullException"><paramref name="fileName"/> is null or empty.</exception>
  74. /// <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>
  75. public PrivateKeyFile(string fileName)
  76. {
  77. if (string.IsNullOrEmpty(fileName))
  78. throw new ArgumentNullException("fileName");
  79. using (var keyFile = File.Open(fileName, FileMode.Open))
  80. {
  81. this.Open(keyFile, null);
  82. }
  83. }
  84. /// <summary>
  85. /// Initializes a new instance of the <see cref="PrivateKeyFile"/> class.
  86. /// </summary>
  87. /// <param name="fileName">Name of the file.</param>
  88. /// <param name="passPhrase">The pass phrase.</param>
  89. /// <exception cref="ArgumentNullException"><paramref name="fileName"/> is null or empty, or <paramref name="passPhrase"/> is null.</exception>
  90. /// <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>
  91. public PrivateKeyFile(string fileName, string passPhrase)
  92. {
  93. if (string.IsNullOrEmpty(fileName))
  94. throw new ArgumentNullException("fileName");
  95. using (var keyFile = File.Open(fileName, FileMode.Open))
  96. {
  97. this.Open(keyFile, passPhrase);
  98. }
  99. }
  100. /// <summary>
  101. /// Initializes a new instance of the <see cref="PrivateKeyFile"/> class.
  102. /// </summary>
  103. /// <param name="privateKey">The private key.</param>
  104. /// <param name="passPhrase">The pass phrase.</param>
  105. /// <exception cref="ArgumentNullException"><paramref name="privateKey"/> or <paramref name="passPhrase"/> is null.</exception>
  106. public PrivateKeyFile(Stream privateKey, string passPhrase)
  107. {
  108. this.Open(privateKey, passPhrase);
  109. }
  110. /// <summary>
  111. /// Opens the specified private key.
  112. /// </summary>
  113. /// <param name="privateKey">The private key.</param>
  114. /// <param name="passPhrase">The pass phrase.</param>
  115. private void Open(Stream privateKey, string passPhrase)
  116. {
  117. if (privateKey == null)
  118. throw new ArgumentNullException("privateKey");
  119. Match privateKeyMatch = null;
  120. using (StreamReader sr = new StreamReader(privateKey))
  121. {
  122. var text = sr.ReadToEnd();
  123. privateKeyMatch = _privateKeyRegex.Match(text);
  124. }
  125. if (!privateKeyMatch.Success)
  126. {
  127. throw new SshException("Invalid private key file.");
  128. }
  129. var keyName = privateKeyMatch.Result("${keyName}");
  130. var cipherName = privateKeyMatch.Result("${cipherName}");
  131. var salt = privateKeyMatch.Result("${salt}");
  132. var data = privateKeyMatch.Result("${data}");
  133. var binaryData = System.Convert.FromBase64String(data);
  134. IEnumerable<byte> decryptedData;
  135. if (!string.IsNullOrEmpty(cipherName) && !string.IsNullOrEmpty(salt))
  136. {
  137. if (string.IsNullOrEmpty(passPhrase))
  138. throw new SshPassPhraseNullOrEmptyException("Private key is encrypted but passphrase is empty.");
  139. byte[] binarySalt = new byte[salt.Length / 2];
  140. for (int i = 0; i < binarySalt.Length; i++)
  141. binarySalt[i] = Convert.ToByte(salt.Substring(i * 2, 2), 16);
  142. CipherInfo cipher = null;
  143. switch (cipherName)
  144. {
  145. case "DES-EDE3-CBC":
  146. cipher = new CipherInfo(192, (key, iv) => { return new TripleDesCipher(key, new CbcCipherMode(iv), new PKCS7Padding()); });
  147. break;
  148. case "DES-EDE3-CFB":
  149. cipher = new CipherInfo(192, (key, iv) => { return new TripleDesCipher(key, new CfbCipherMode(iv), new PKCS7Padding()); });
  150. break;
  151. case "DES-CBC":
  152. // TODO: Not tested
  153. cipher = new CipherInfo(64, (key, iv) => { return new DesCipher(key, new CbcCipherMode(iv), new PKCS7Padding()); });
  154. break;
  155. case "AES-128-CBC":
  156. // TODO: Not tested
  157. cipher = new CipherInfo(128, (key, iv) => { return new AesCipher(key, new CbcCipherMode(iv), new PKCS7Padding()); });
  158. break;
  159. case "AES-192-CBC":
  160. // TODO: Not tested
  161. cipher = new CipherInfo(192, (key, iv) => { return new AesCipher(key, new CbcCipherMode(iv), new PKCS7Padding()); });
  162. break;
  163. case "AES-256-CBC":
  164. // TODO: Not tested
  165. cipher = new CipherInfo(256, (key, iv) => { return new AesCipher(key, new CbcCipherMode(iv), new PKCS7Padding()); });
  166. break;
  167. default:
  168. throw new SshException(string.Format(CultureInfo.CurrentCulture, "Unknown private key cipher \"{0}\".", cipherName));
  169. }
  170. decryptedData = DecryptKey(cipher, binaryData, passPhrase, binarySalt);
  171. }
  172. else
  173. {
  174. decryptedData = binaryData;
  175. }
  176. switch (keyName)
  177. {
  178. case "RSA":
  179. this._key = new CryptoPrivateKeyRsa();
  180. break;
  181. case "DSA":
  182. this._key = new CryptoPrivateKeyDss();
  183. break;
  184. default:
  185. throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Key '{0}' is not supported.", keyName));
  186. }
  187. this._key.Load(decryptedData);
  188. }
  189. /// <summary>
  190. /// Decrypts encrypted private key file data.
  191. /// </summary>
  192. /// <param name="cipherInfo">The cipher info.</param>
  193. /// <param name="cipherData">Encrypted data.</param>
  194. /// <param name="passPhrase">Decryption pass phrase.</param>
  195. /// <param name="binarySalt">Decryption binary salt.</param>
  196. /// <returns></returns>
  197. /// <exception cref="ArgumentNullException"><paramref name="cipherInfo"/>, <paramref name="cipherData"/>, <paramref name="passPhrase"/> or <paramref name="binarySalt"/> is null.</exception>
  198. public static IEnumerable<byte> DecryptKey(CipherInfo cipherInfo, byte[] cipherData, string passPhrase, byte[] binarySalt)
  199. {
  200. if (cipherInfo == null)
  201. throw new ArgumentNullException("cipherInfo");
  202. if (cipherData == null)
  203. throw new ArgumentNullException("cipherData");
  204. List<byte> cipherKey = new List<byte>();
  205. using (var md5 = new MD5Hash())
  206. {
  207. var passwordBytes = Encoding.UTF8.GetBytes(passPhrase);
  208. var initVector = passwordBytes.Concat(binarySalt);
  209. var hash = md5.ComputeHash(initVector.ToArray()).AsEnumerable();
  210. cipherKey.AddRange(hash);
  211. while (cipherKey.Count < cipherInfo.KeySize / 8)
  212. {
  213. hash = hash.Concat(initVector);
  214. hash = md5.ComputeHash(hash.ToArray());
  215. cipherKey.AddRange(hash);
  216. }
  217. }
  218. var cipher = cipherInfo.Cipher(cipherKey.ToArray(), binarySalt);
  219. return cipher.Decrypt(cipherData);
  220. }
  221. }
  222. }