PrivateKeyFile
Represents private key information.
using Renci.SshNet.Abstractions;
using Renci.SshNet.Common;
using Renci.SshNet.Security;
using Renci.SshNet.Security.Cryptography;
using Renci.SshNet.Security.Cryptography.Ciphers;
using Renci.SshNet.Security.Cryptography.Ciphers.Modes;
using Renci.SshNet.Security.Cryptography.Ciphers.Paddings;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
namespace Renci.SshNet
{
public class PrivateKeyFile : IDisposable
{
private class SshDataReader : SshData
{
public SshDataReader(byte[] data)
{
Load(data);
}
public new uint ReadUInt32()
{
return base.ReadUInt32();
}
public new string ReadString(Encoding encoding)
{
return base.ReadString(encoding);
}
public new byte[] ReadBytes(int length)
{
return base.ReadBytes(length);
}
public new byte[] ReadBytes()
{
return base.ReadBytes();
}
public BigInteger ReadBigIntWithBits()
{
int num = (int)base.ReadUInt32();
num = (num + 7) / 8;
byte[] array = base.ReadBytes(num);
byte[] array2 = new byte[array.Length + 1];
Buffer.BlockCopy(array, 0, array2, 1, array.Length);
return new BigInteger(array2.Reverse());
}
protected override void LoadData()
{
}
protected override void SaveData()
{
}
}
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 *-+", RegexOptions.Multiline);
private Key _key;
private bool _isDisposed;
public HostAlgorithm HostKey { get; set; }
public PrivateKeyFile(Stream privateKey)
{
Open(privateKey, null);
}
public PrivateKeyFile(string fileName)
: this(fileName, null)
{
}
public PrivateKeyFile(string fileName, string passPhrase)
{
if (string.IsNullOrEmpty(fileName))
throw new ArgumentNullException("fileName");
using (FileStream privateKey = File.Open(fileName, FileMode.Open, FileAccess.Read, FileShare.Read))
Open(privateKey, passPhrase);
}
public PrivateKeyFile(Stream privateKey, string passPhrase)
{
Open(privateKey, passPhrase);
}
private void Open(Stream privateKey, string passPhrase)
{
if (privateKey == null)
throw new ArgumentNullException("privateKey");
Match match = default(Match);
using (StreamReader streamReader = new StreamReader(privateKey)) {
string input = streamReader.ReadToEnd();
match = PrivateKeyRegex.Match(input);
}
if (!match.Success)
throw new SshException("Invalid private key file.");
string text = match.Result("${keyName}");
string text2 = match.Result("${cipherName}");
string text3 = match.Result("${salt}");
byte[] array = Convert.FromBase64String(match.Result("${data}"));
byte[] array3;
if (!string.IsNullOrEmpty(text2) && !string.IsNullOrEmpty(text3)) {
if (string.IsNullOrEmpty(passPhrase))
throw new SshPassPhraseNullOrEmptyException("Private key is encrypted but passphrase is empty.");
byte[] array2 = new byte[text3.Length / 2];
for (int i = 0; i < array2.Length; i++) {
array2[i] = Convert.ToByte(text3.Substring(i * 2, 2), 16);
}
if (text2 == null)
goto IL_025e;
CipherInfo cipherInfo;
if (!(text2 == "DES-EDE3-CBC")) {
if (!(text2 == "DES-EDE3-CFB")) {
if (!(text2 == "DES-CBC")) {
if (!(text2 == "AES-128-CBC")) {
if (!(text2 == "AES-192-CBC")) {
if (!(text2 == "AES-256-CBC"))
goto IL_025e;
cipherInfo = new CipherInfo(256, (byte[] key, byte[] iv) => new AesCipher(key, new CbcCipherMode(iv), new PKCS7Padding()));
} else
cipherInfo = new CipherInfo(192, (byte[] key, byte[] iv) => new AesCipher(key, new CbcCipherMode(iv), new PKCS7Padding()));
} else
cipherInfo = new CipherInfo(128, (byte[] key, byte[] iv) => new AesCipher(key, new CbcCipherMode(iv), new PKCS7Padding()));
} else
cipherInfo = new CipherInfo(64, (byte[] key, byte[] iv) => new DesCipher(key, new CbcCipherMode(iv), new PKCS7Padding()));
} else
cipherInfo = new CipherInfo(192, (byte[] key, byte[] iv) => new TripleDesCipher(key, new CfbCipherMode(iv), new PKCS7Padding()));
} else
cipherInfo = new CipherInfo(192, (byte[] key, byte[] iv) => new TripleDesCipher(key, new CbcCipherMode(iv), new PKCS7Padding()));
array3 = DecryptKey(cipherInfo, array, passPhrase, array2);
} else
array3 = array;
if (text != null) {
if (text == "RSA") {
_key = new RsaKey(array3);
HostKey = new KeyHostAlgorithm("ssh-rsa", _key);
return;
}
if (text == "DSA") {
_key = new DsaKey(array3);
HostKey = new KeyHostAlgorithm("ssh-dss", _key);
return;
}
if (text == "EC") {
_key = new EcdsaKey(array3);
HostKey = new KeyHostAlgorithm(_key.ToString(), _key);
return;
}
if (text == "OPENSSH") {
_key = ParseOpenSshV1Key(array3, passPhrase);
HostKey = new KeyHostAlgorithm(_key.ToString(), _key);
return;
}
if (text == "SSH2 ENCRYPTED") {
SshDataReader sshDataReader = new SshDataReader(array3);
if (sshDataReader.ReadUInt32() != 1064303083)
throw new SshException("Invalid SSH2 private key.");
sshDataReader.ReadUInt32();
string text4 = sshDataReader.ReadString(SshData.Ascii);
string a = sshDataReader.ReadString(SshData.Ascii);
int num = (int)sshDataReader.ReadUInt32();
byte[] data;
if (a == "none")
data = sshDataReader.ReadBytes(num);
else {
if (!(a == "3des-cbc"))
throw new SshException($"""{text2}""");
if (string.IsNullOrEmpty(passPhrase))
throw new SshPassPhraseNullOrEmptyException("Private key is encrypted but passphrase is empty.");
data = new TripleDesCipher(GetCipherKey(passPhrase, 24), new CbcCipherMode(new byte[8]), new PKCS7Padding()).Decrypt(sshDataReader.ReadBytes(num));
}
sshDataReader = new SshDataReader(data);
if (sshDataReader.ReadUInt32() > num - 4)
throw new SshException("Invalid passphrase.");
if (text4 == "if-modn{sign{rsa-pkcs1-sha1},encrypt{rsa-pkcs1v2-oaep}}") {
BigInteger exponent = sshDataReader.ReadBigIntWithBits();
BigInteger d = sshDataReader.ReadBigIntWithBits();
BigInteger modulus = sshDataReader.ReadBigIntWithBits();
BigInteger inverseQ = sshDataReader.ReadBigIntWithBits();
BigInteger q = sshDataReader.ReadBigIntWithBits();
BigInteger p = sshDataReader.ReadBigIntWithBits();
_key = new RsaKey(modulus, exponent, d, p, q, inverseQ);
HostKey = new KeyHostAlgorithm("ssh-rsa", _key);
} else {
if (!(text4 == "dl-modp{sign{dsa-nist-sha1},dh{plain}}"))
throw new NotSupportedException($"""{text4}""");
if (sshDataReader.ReadUInt32() != 0)
throw new SshException("Invalid private key");
BigInteger p2 = sshDataReader.ReadBigIntWithBits();
BigInteger g = sshDataReader.ReadBigIntWithBits();
BigInteger q2 = sshDataReader.ReadBigIntWithBits();
BigInteger y = sshDataReader.ReadBigIntWithBits();
BigInteger x = sshDataReader.ReadBigIntWithBits();
_key = new DsaKey(p2, q2, g, y, x);
HostKey = new KeyHostAlgorithm("ssh-dss", _key);
}
return;
}
}
throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Key '{0}' is not supported.", text));
IL_025e:
throw new SshException(string.Format(CultureInfo.CurrentCulture, "Private key cipher \"{0}\" is not supported.", text2));
}
private static byte[] GetCipherKey(string passphrase, int length)
{
List<byte> list = new List<byte>();
using (MD5 mD = CryptoAbstraction.CreateMD5()) {
byte[] bytes = Encoding.UTF8.GetBytes(passphrase);
byte[] array = mD.ComputeHash(bytes);
list.AddRange(array);
while (list.Count < length) {
array = bytes.Concat(array);
array = mD.ComputeHash(array);
list.AddRange(array);
}
}
return list.ToArray().Take(length);
}
private static byte[] DecryptKey(CipherInfo cipherInfo, byte[] cipherData, string passPhrase, byte[] binarySalt)
{
if (cipherInfo == null)
throw new ArgumentNullException("cipherInfo");
if (cipherData == null)
throw new ArgumentNullException("cipherData");
if (binarySalt == null)
throw new ArgumentNullException("binarySalt");
List<byte> list = new List<byte>();
using (MD5 mD = CryptoAbstraction.CreateMD5()) {
byte[] array = Encoding.UTF8.GetBytes(passPhrase).Concat(binarySalt.Take(8));
byte[] array2 = mD.ComputeHash(array);
list.AddRange(array2);
while (list.Count < cipherInfo.KeySize / 8) {
array2 = array2.Concat(array);
array2 = mD.ComputeHash(array2);
list.AddRange(array2);
}
}
return cipherInfo.Cipher(list.ToArray(), binarySalt).Decrypt(cipherData);
}
private ED25519Key ParseOpenSshV1Key(byte[] keyFileData, string passPhrase)
{
SshDataReader sshDataReader = new SshDataReader(keyFileData);
byte[] bytes = Encoding.UTF8.GetBytes("openssh-key-v1 ");
byte[] right = sshDataReader.ReadBytes(bytes.Length);
if (!bytes.IsEqualTo(right))
throw new SshException("This openssh key does not contain the 'openssh-key-v1' format magic header");
string text = sshDataReader.ReadString(Encoding.UTF8);
string text2 = sshDataReader.ReadString(Encoding.UTF8);
uint num = sshDataReader.ReadUInt32();
byte[] salt = null;
int rounds = 0;
if ((int)num > 0) {
int length = (int)sshDataReader.ReadUInt32();
salt = sshDataReader.ReadBytes(length);
rounds = (int)sshDataReader.ReadUInt32();
}
if (sshDataReader.ReadUInt32() != 1)
throw new SshException("At this time only one public key in the openssh key is supported.");
sshDataReader.ReadUInt32();
string text3 = sshDataReader.ReadString(Encoding.UTF8);
if (text3 != "ssh-ed25519")
throw new SshException("openssh key type: " + text3 + " is not supported");
int length2 = (int)sshDataReader.ReadUInt32();
byte[] array = sshDataReader.ReadBytes(length2);
int length3 = (int)sshDataReader.ReadUInt32();
byte[] array2 = sshDataReader.ReadBytes(length3);
if (text == "aes256-cbc") {
if (string.IsNullOrEmpty(passPhrase))
throw new SshPassPhraseNullOrEmptyException("Private key is encrypted but passphrase is empty.");
if (string.IsNullOrEmpty(text2) || text2 != "bcrypt")
throw new SshException("kdf " + text2 + " is not supported for openssh key file");
byte[] bytes2 = Encoding.UTF8.GetBytes(passPhrase);
byte[] array3 = new byte[48];
new BCrypt().Pbkdf(bytes2, salt, rounds, array3);
byte[] array4 = new byte[32];
Array.Copy(array3, 0, array4, 0, 32);
byte[] array5 = new byte[16];
Array.Copy(array3, 32, array5, 0, 16);
array2 = new AesCipher(array4, new CbcCipherMode(array5), new PKCS7Padding()).Decrypt(array2);
} else if (text != "none") {
throw new SshException("cipher name " + text + " for openssh key file is not supported");
}
length3 = array2.Length;
if (length3 % 8 != 0)
throw new SshException("The private key section must be a multiple of the block size (8)");
SshDataReader sshDataReader2 = new SshDataReader(array2);
int num2 = (int)sshDataReader2.ReadUInt32();
int num3 = (int)sshDataReader2.ReadUInt32();
if (num2 != num3)
throw new SshException("The checkints differed, the openssh key was not correctly decoded.");
sshDataReader2.ReadString(Encoding.UTF8);
int length4 = (int)sshDataReader2.ReadUInt32();
sshDataReader2.ReadBytes(length4);
sshDataReader2.ReadUInt32();
byte[] sk = sshDataReader2.ReadBytes(32);
sshDataReader2.ReadBytes(32);
sshDataReader2.ReadString(Encoding.UTF8);
byte[] array6 = sshDataReader2.ReadBytes();
for (int i = 0; i < array6.Length; i++) {
if (array6[i] != i + 1)
throw new SshException("Padding of openssh key format contained wrong byte at position: " + i.ToString());
}
return new ED25519Key(array.Reverse(), sk);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_isDisposed && disposing) {
Key key = _key;
if (key != null) {
((IDisposable)key).Dispose();
_key = null;
}
_isDisposed = true;
}
}
~PrivateKeyFile()
{
Dispose(false);
}
}
}