|
|
@@ -16,17 +16,46 @@ using System.Diagnostics.CodeAnalysis;
|
|
|
namespace Renci.SshNet
|
|
|
{
|
|
|
/// <summary>
|
|
|
- /// Represents private key information
|
|
|
+ /// Represents private key information.
|
|
|
/// </summary>
|
|
|
/// <example>
|
|
|
/// <code source="..\..\Renci.SshNet.Tests\Data\Key.RSA.txt" language="Text" title="Private RSA key example" />
|
|
|
/// </example>
|
|
|
+ /// <remarks>
|
|
|
+ /// <para>
|
|
|
+ /// Supports RSA and DSA private key in both <c>OpenSSH</c> and <c>ssh.com</c> format.
|
|
|
+ /// </para>
|
|
|
+ /// <para>
|
|
|
+ /// The following encryption algorithms are supported:
|
|
|
+ /// <list type="bullet">
|
|
|
+ /// <item>
|
|
|
+ /// <description>DES-EDE3-CBC</description>
|
|
|
+ /// </item>
|
|
|
+ /// <item>
|
|
|
+ /// <description>DES-EDE3-CFB</description>
|
|
|
+ /// </item>
|
|
|
+ /// <item>
|
|
|
+ /// <description>DES-CBC</description>
|
|
|
+ /// </item>
|
|
|
+ /// <item>
|
|
|
+ /// <description>AES-128-CBC</description>
|
|
|
+ /// </item>
|
|
|
+ /// <item>
|
|
|
+ /// <description>AES-192-CBC</description>
|
|
|
+ /// </item>
|
|
|
+ /// <item>
|
|
|
+ /// <description>AES-256-CBC</description>
|
|
|
+ /// </item>
|
|
|
+ /// </list>
|
|
|
+ /// </para>
|
|
|
+ /// </remarks>
|
|
|
public class PrivateKeyFile : IDisposable
|
|
|
{
|
|
|
+ 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 *-+",
|
|
|
#if SILVERLIGHT
|
|
|
- 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)?(?<data>([a-zA-Z0-9/+=]{1,80}\r?\n)+)-+ *END \k<keyName> PRIVATE KEY *-+", RegexOptions.Multiline);
|
|
|
+ RegexOptions.Multiline);
|
|
|
#else
|
|
|
- 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)?(?<data>([a-zA-Z0-9/+=]{1,80}\r?\n)+)-+ *END \k<keyName> PRIVATE KEY *-+", RegexOptions.Compiled | RegexOptions.Multiline);
|
|
|
+ RegexOptions.Compiled | RegexOptions.Multiline);
|
|
|
#endif
|
|
|
|
|
|
private Key _key;
|
|
|
@@ -101,7 +130,7 @@ namespace Renci.SshNet
|
|
|
using (var sr = new StreamReader(privateKey))
|
|
|
{
|
|
|
var text = sr.ReadToEnd();
|
|
|
- privateKeyMatch = _privateKeyRegex.Match(text);
|
|
|
+ privateKeyMatch = PrivateKeyRegex.Match(text);
|
|
|
}
|
|
|
|
|
|
if (!privateKeyMatch.Success)
|
|
|
@@ -177,7 +206,7 @@ namespace Renci.SshNet
|
|
|
throw new SshException("Invalid SSH2 private key.");
|
|
|
}
|
|
|
|
|
|
- var totalLength = reader.ReadUInt32(); // Read total bytes length including magic number
|
|
|
+ reader.ReadUInt32(); // Read total bytes length including magic number
|
|
|
var keyType = reader.ReadString();
|
|
|
var ssh2CipherName = reader.ReadString();
|
|
|
var blobSize = (int)reader.ReadUInt32();
|
|
|
@@ -187,12 +216,15 @@ namespace Renci.SshNet
|
|
|
{
|
|
|
keyData = reader.ReadBytes(blobSize);
|
|
|
}
|
|
|
- //else if (ssh2CipherName == "3des-cbc")
|
|
|
- //{
|
|
|
- // var key = GetCipherKey(passPhrase, 192 / 8);
|
|
|
- // var ssh2Сipher = new TripleDesCipher(key, null, null);
|
|
|
- // keyData = ssh2Сipher.Decrypt(reader.ReadBytes(blobSize));
|
|
|
- //}
|
|
|
+ else if (ssh2CipherName == "3des-cbc")
|
|
|
+ {
|
|
|
+ if (string.IsNullOrEmpty(passPhrase))
|
|
|
+ throw new SshPassPhraseNullOrEmptyException("Private key is encrypted but passphrase is empty.");
|
|
|
+
|
|
|
+ var key = GetCipherKey(passPhrase, 192 / 8);
|
|
|
+ var ssh2Сipher = new TripleDesCipher(key, new CbcCipherMode(new byte[8]), new PKCS7Padding());
|
|
|
+ keyData = ssh2Сipher.Decrypt(reader.ReadBytes(blobSize));
|
|
|
+ }
|
|
|
else
|
|
|
{
|
|
|
throw new SshException(string.Format("Cipher method '{0}' is not supported.", cipherName));
|
|
|
@@ -204,7 +236,7 @@ namespace Renci.SshNet
|
|
|
|
|
|
var decryptedLength = reader.ReadUInt32();
|
|
|
|
|
|
- if (decryptedLength + 4 != blobSize)
|
|
|
+ if (decryptedLength > blobSize - 4)
|
|
|
throw new SshException("Invalid passphrase.");
|
|
|
|
|
|
if (keyType == "if-modn{sign{rsa-pkcs1-sha1},encrypt{rsa-pkcs1v2-oaep}}")
|
|
|
@@ -245,22 +277,19 @@ namespace Renci.SshNet
|
|
|
|
|
|
private static byte[] GetCipherKey(string passphrase, int length)
|
|
|
{
|
|
|
- List<byte> cipherKey = new List<byte>();
|
|
|
+ var cipherKey = new List<byte>();
|
|
|
|
|
|
using (var md5 = new MD5Hash())
|
|
|
{
|
|
|
- byte[] passwordBytes = Encoding.UTF8.GetBytes(passphrase);
|
|
|
-
|
|
|
- var hash = md5.ComputeHash(passwordBytes.ToArray()).AsEnumerable();
|
|
|
+ var passwordBytes = Encoding.UTF8.GetBytes(passphrase);
|
|
|
|
|
|
+ var hash = md5.ComputeHash(passwordBytes);
|
|
|
cipherKey.AddRange(hash);
|
|
|
|
|
|
while (cipherKey.Count < length)
|
|
|
{
|
|
|
- hash = passwordBytes.Concat(hash);
|
|
|
-
|
|
|
- hash = md5.ComputeHash(hash.ToArray());
|
|
|
-
|
|
|
+ hash = passwordBytes.Concat(hash).ToArray();
|
|
|
+ hash = md5.ComputeHash(hash);
|
|
|
cipherKey.AddRange(hash);
|
|
|
}
|
|
|
}
|
|
|
@@ -289,25 +318,22 @@ namespace Renci.SshNet
|
|
|
if (binarySalt == null)
|
|
|
throw new ArgumentNullException("binarySalt");
|
|
|
|
|
|
- List<byte> cipherKey = new List<byte>();
|
|
|
+ var cipherKey = new List<byte>();
|
|
|
|
|
|
using (var md5 = new MD5Hash())
|
|
|
{
|
|
|
var passwordBytes = Encoding.UTF8.GetBytes(passPhrase);
|
|
|
|
|
|
- // Use 8 bytes binary salkt
|
|
|
- var initVector = passwordBytes.Concat(binarySalt.Take(8));
|
|
|
-
|
|
|
- var hash = md5.ComputeHash(initVector.ToArray()).AsEnumerable();
|
|
|
+ // Use 8 bytes binary salt
|
|
|
+ var initVector = passwordBytes.Concat(binarySalt.Take(8)).ToArray();
|
|
|
|
|
|
+ var hash = md5.ComputeHash(initVector);
|
|
|
cipherKey.AddRange(hash);
|
|
|
|
|
|
while (cipherKey.Count < cipherInfo.KeySize / 8)
|
|
|
{
|
|
|
- hash = hash.Concat(initVector);
|
|
|
-
|
|
|
- hash = md5.ComputeHash(hash.ToArray());
|
|
|
-
|
|
|
+ hash = hash.Concat(initVector).ToArray();
|
|
|
+ hash = md5.ComputeHash(hash);
|
|
|
cipherKey.AddRange(hash);
|
|
|
}
|
|
|
}
|
|
|
@@ -401,7 +427,7 @@ namespace Renci.SshNet
|
|
|
{
|
|
|
var length = (int)base.ReadUInt32();
|
|
|
|
|
|
- length = (int)(length + 7) / 8;
|
|
|
+ length = (length + 7) / 8;
|
|
|
|
|
|
var data = base.ReadBytes(length);
|
|
|
var bytesArray = new byte[data.Length + 1];
|