PrivateKeyFile
Represents private key information.
using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Asn1.EdEC;
using Org.BouncyCastle.Asn1.Pkcs;
using Org.BouncyCastle.Asn1.X9;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Pkcs;
using Renci.SshNet.Abstractions;
using Renci.SshNet.Common;
using Renci.SshNet.Security;
using Renci.SshNet.Security.Cryptography;
using Renci.SshNet.Security.Cryptography.Ciphers;
using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Formats.Asn1;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Text.RegularExpressions.Generated;
namespace Renci.SshNet
{
[NullableContext(1)]
[Nullable(0)]
public class PrivateKeyFile : IPrivateKeySource, IDisposable
{
[Nullable(0)]
private sealed 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;
return base.ReadBytes(num).ToBigInteger2();
}
public BigInteger ReadBignum()
{
return base.DataStream.ReadBigInt();
}
public byte[] ReadBignum2()
{
return ReadBinary();
}
protected override void LoadData()
{
}
protected override void SaveData()
{
}
}
private interface IPrivateKeyParser
{
Key Parse();
}
[Nullable(0)]
private sealed class OpenSSH : IPrivateKeyParser
{
private readonly byte[] _data;
[Nullable(2)]
private readonly string _passPhrase;
public OpenSSH(byte[] data, [Nullable(2)] string passPhrase)
{
_data = data;
_passPhrase = passPhrase;
}
public unsafe Key Parse()
{
SshDataReader sshDataReader = new SshDataReader(_data);
ReadOnlySpan<byte> span = new ReadOnlySpan<byte>(&global::<PrivateImplementationDetails>.E86FA4695E4C4A2E623D2981DFB47AC8C0803F2E5F2034A8067F991D8FD236CC, 15);
byte[] array = sshDataReader.ReadBytes(span.Length);
if (!span.SequenceEqual(array))
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.ReadString(Encoding.UTF8);
int num2 = (int)sshDataReader.ReadUInt32();
byte[] array3 = default(byte[]);
if (text != "none") {
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");
int num3 = 16;
if (text != null) {
CipherInfo cipherInfo;
int num4;
byte[] bytes;
byte[] array2;
byte[] arg;
byte[] arg2;
Cipher cipher;
byte[] input;
switch (text.Length) {
case 10:
switch (text[4]) {
case '2':
break;
case '9':
goto IL_01ba;
case '5':
goto IL_01df;
default:
goto IL_042b;
}
if (!(text == "aes128-cbc")) {
if (!(text == "aes128-ctr"))
break;
cipherInfo = new CipherInfo(128, (byte[] key, byte[] iv) => new AesCipher(key, iv, AesCipherMode.CTR, false), false);
} else
cipherInfo = new CipherInfo(128, (byte[] key, byte[] iv) => new AesCipher(key, iv, AesCipherMode.CBC, false), false);
goto IL_0441;
case 22:
switch (text[3]) {
case '1':
break;
case '2':
goto IL_0219;
default:
goto IL_042b;
}
if (!(text == "aes128-gcm@openssh.com"))
break;
cipherInfo = new CipherInfo(128, (byte[] key, byte[] iv) => new AesGcmCipher(key, iv, 0), true);
goto IL_0441;
case 8:
if (!(text == "3des-cbc"))
break;
num3 = 8;
cipherInfo = new CipherInfo(192, (byte[] key, byte[] iv) => new TripleDesCipher(key, iv, System.Security.Cryptography.CipherMode.CBC, false), false);
goto IL_0441;
case 29:
{
if (!(text == "chacha20-poly1305@openssh.com"))
break;
num3 = 12;
cipherInfo = new CipherInfo(256, (byte[] key, byte[] iv) => new ChaCha20Poly1305Cipher(key, 0), true);
goto IL_0441;
}
IL_01df:
if (!(text == "aes256-cbc")) {
if (!(text == "aes256-ctr"))
break;
cipherInfo = new CipherInfo(256, (byte[] key, byte[] iv) => new AesCipher(key, iv, AesCipherMode.CTR, false), false);
} else
cipherInfo = new CipherInfo(256, (byte[] key, byte[] iv) => new AesCipher(key, iv, AesCipherMode.CBC, false), false);
goto IL_0441;
IL_0219:
if (!(text == "aes256-gcm@openssh.com"))
break;
cipherInfo = new CipherInfo(256, (byte[] key, byte[] iv) => new AesGcmCipher(key, iv, 0), true);
goto IL_0441;
IL_01ba:
if (!(text == "aes192-cbc")) {
if (!(text == "aes192-ctr"))
break;
cipherInfo = new CipherInfo(192, (byte[] key, byte[] iv) => new AesCipher(key, iv, AesCipherMode.CTR, false), false);
} else
cipherInfo = new CipherInfo(192, (byte[] key, byte[] iv) => new AesCipher(key, iv, AesCipherMode.CBC, false), false);
goto IL_0441;
IL_0441:
num4 = cipherInfo.KeySize / 8;
bytes = Encoding.UTF8.GetBytes(_passPhrase);
array2 = new byte[num4 + num3];
new Renci.SshNet.Security.Cryptography.BCrypt().Pbkdf(bytes, salt, rounds, array2);
arg = array2.Take(num4);
arg2 = array2.Take(num4, num3);
cipher = cipherInfo.Cipher(arg, arg2);
input = sshDataReader.ReadBytes(num2 + cipher.TagSize);
try {
array3 = cipher.Decrypt(input, 0, num2);
} finally {
(cipher as IDisposable)?.Dispose();
}
goto IL_04e7;
}
}
goto IL_042b;
}
array3 = sshDataReader.ReadBytes(num2);
goto IL_04e7;
IL_042b:
throw new SshException("Cipher '" + text + "' is not supported for an OpenSSH key.");
IL_04e7:
num2 = array3.Length;
if (num2 % 8 != 0)
throw new SshException("The private key section must be a multiple of the block size (8)");
SshDataReader sshDataReader2 = new SshDataReader(array3);
int num5 = (int)sshDataReader2.ReadUInt32();
int num6 = (int)sshDataReader2.ReadUInt32();
if (num5 != num6)
throw new SshException(string.Format(CultureInfo.InvariantCulture, "The random check bytes of the OpenSSH key do not match ({0} <-> {1}).", num5.ToString(CultureInfo.InvariantCulture), num6.ToString(CultureInfo.InvariantCulture)));
string text3 = sshDataReader2.ReadString(Encoding.UTF8);
Key key2;
switch (text3) {
case "ssh-ed25519": {
sshDataReader2.ReadBignum2();
byte[] value = sshDataReader2.ReadBignum2();
key2 = new ED25519Key(value);
break;
}
case "ecdsa-sha2-nistp256":
case "ecdsa-sha2-nistp384":
case "ecdsa-sha2-nistp521": {
int length2 = (int)sshDataReader2.ReadUInt32();
string string = Encoding.ASCII.GetString(sshDataReader2.ReadBytes(length2));
byte[] publickey = sshDataReader2.ReadBignum2();
byte[] value = sshDataReader2.ReadBignum2();
key2 = new EcdsaKey(string, publickey, value.TrimLeadingZeros());
break;
}
case "ssh-rsa": {
BigInteger modulus = sshDataReader2.ReadBignum();
BigInteger exponent = sshDataReader2.ReadBignum();
BigInteger d = sshDataReader2.ReadBignum();
BigInteger inverseQ = sshDataReader2.ReadBignum();
BigInteger p = sshDataReader2.ReadBignum();
BigInteger q = sshDataReader2.ReadBignum();
key2 = new RsaKey(modulus, exponent, d, p, q, inverseQ);
break;
}
default:
throw new SshException("OpenSSH key type '" + text3 + "' is not supported.");
}
key2.Comment = sshDataReader2.ReadString(Encoding.UTF8);
byte[] array4 = sshDataReader2.ReadBytes();
for (int i = 0; i < array4.Length; i++) {
if (array4[i] != i + 1)
throw new SshException("Padding of openssh key format contained wrong byte at position: " + i.ToString(CultureInfo.InvariantCulture));
}
return key2;
}
}
[Nullable(0)]
private sealed class PKCS1 : IPrivateKeyParser
{
private readonly string _cipherName;
private readonly string _salt;
private readonly string _keyName;
private readonly byte[] _data;
[Nullable(2)]
private readonly string _passPhrase;
public PKCS1(string cipherName, string salt, string keyName, byte[] data, [Nullable(2)] string passPhrase)
{
_cipherName = cipherName;
_salt = salt;
_keyName = keyName;
_data = data;
_passPhrase = passPhrase;
}
public Key Parse()
{
byte[] array;
if (!string.IsNullOrEmpty(_cipherName) && !string.IsNullOrEmpty(_salt)) {
if (string.IsNullOrEmpty(_passPhrase))
throw new SshPassPhraseNullOrEmptyException("Private key is encrypted but passphrase is empty.");
byte[] binarySalt = Convert.FromHexString(_salt);
CipherInfo cipherInfo;
switch (_cipherName) {
case "DES-EDE3-CBC":
cipherInfo = new CipherInfo(192, (byte[] key, byte[] iv) => new TripleDesCipher(key, iv, System.Security.Cryptography.CipherMode.CBC, true), false);
break;
case "DES-EDE3-CFB":
cipherInfo = new CipherInfo(192, (byte[] key, byte[] iv) => new TripleDesCipher(key, iv, System.Security.Cryptography.CipherMode.CFB, false), false);
break;
case "AES-128-CBC":
cipherInfo = new CipherInfo(128, (byte[] key, byte[] iv) => new AesCipher(key, iv, AesCipherMode.CBC, true), false);
break;
case "AES-192-CBC":
cipherInfo = new CipherInfo(192, (byte[] key, byte[] iv) => new AesCipher(key, iv, AesCipherMode.CBC, true), false);
break;
case "AES-256-CBC":
cipherInfo = new CipherInfo(256, (byte[] key, byte[] iv) => new AesCipher(key, iv, AesCipherMode.CBC, true), false);
break;
default:
throw new SshException(string.Format(CultureInfo.InvariantCulture, "Private key cipher \"{0}\" is not supported.", _cipherName));
}
array = DecryptKey(cipherInfo, _data, _passPhrase, binarySalt);
} else
array = _data;
string keyName = _keyName;
if (keyName == "RSA PRIVATE KEY")
return new RsaKey(array);
if (keyName == "EC PRIVATE KEY")
return new EcdsaKey(array);
throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Key '{0}' is not supported.", _keyName));
}
private static byte[] DecryptKey(CipherInfo cipherInfo, byte[] cipherData, string passPhrase, byte[] binarySalt)
{
List<byte> list = new List<byte>();
using (MD5 mD = MD5.Create()) {
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);
}
}
Cipher cipher = cipherInfo.Cipher(list.ToArray(), binarySalt);
try {
return cipher.Decrypt(cipherData);
} finally {
(cipher as IDisposable)?.Dispose();
}
}
}
[Nullable(0)]
private sealed class PKCS8 : IPrivateKeyParser
{
private readonly bool _encrypted;
private readonly byte[] _data;
[Nullable(2)]
private readonly string _passPhrase;
public PKCS8(bool encrypted, byte[] data, [Nullable(2)] string passPhrase)
{
_encrypted = encrypted;
_data = data;
_passPhrase = passPhrase;
}
public Key Parse()
{
PrivateKeyInfo privateKeyInfo;
if (_encrypted) {
EncryptedPrivateKeyInfo instance = EncryptedPrivateKeyInfo.GetInstance(_data);
privateKeyInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(_passPhrase?.ToCharArray(), instance);
} else
privateKeyInfo = PrivateKeyInfo.GetInstance(_data);
DerObjectIdentifier algorithm = privateKeyInfo.PrivateKeyAlgorithm.Algorithm;
byte[] octets = privateKeyInfo.PrivateKey.GetOctets();
if (algorithm.Equals(PkcsObjectIdentifiers.RsaEncryption))
return new RsaKey(octets);
if (algorithm.Equals(X9ObjectIdentifiers.IdECPublicKey)) {
AsnReader asnReader = new AsnReader(privateKeyInfo.PrivateKeyAlgorithm.Parameters.GetDerEncoded(), AsnEncodingRules.DER, default(AsnReaderOptions));
string curve = asnReader.ReadObjectIdentifier(null);
asnReader.ThrowIfNotEmpty();
AsnReader asnReader2 = new AsnReader(octets, AsnEncodingRules.DER, default(AsnReaderOptions));
AsnReader asnReader3 = asnReader2.ReadSequence(null);
asnReader2.ThrowIfNotEmpty();
BigInteger bigInteger = asnReader3.ReadInteger(null);
if (bigInteger != BigInteger.One)
throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "EC version '{0}' is not supported.", bigInteger));
byte[] value = asnReader3.ReadOctetString(null);
AsnReader asnReader4 = asnReader3.ReadSequence(new Asn1Tag(TagClass.ContextSpecific, 1, true));
int unusedBitCount = default(int);
byte[] publickey = asnReader4.ReadBitString(out unusedBitCount, null);
asnReader4.ThrowIfNotEmpty();
asnReader3.ThrowIfNotEmpty();
return new EcdsaKey(curve, publickey, value.TrimLeadingZeros());
}
if (algorithm.Equals(EdECObjectIdentifiers.id_Ed25519))
return new ED25519Key(octets);
throw new SshException(string.Format(CultureInfo.InvariantCulture, "Private key algorithm \"{0}\" is not supported.", algorithm));
}
}
[Nullable(0)]
private sealed class PuTTY : IPrivateKeyParser
{
private readonly string _version;
private readonly string _algorithmName;
private readonly string _encryptionType;
private readonly string _comment;
private readonly byte[] _publicKey;
[Nullable(2)]
private readonly string _argon2Type;
[Nullable(2)]
private readonly string _argon2Salt;
[Nullable(2)]
private readonly string _argon2Iterations;
[Nullable(2)]
private readonly string _argon2Memory;
[Nullable(2)]
private readonly string _argon2Parallelism;
private readonly byte[] _data;
private readonly string _mac;
[Nullable(2)]
private readonly string _passPhrase;
public PuTTY(string version, string algorithmName, string encryptionType, string comment, byte[] publicKey, [Nullable(2)] string argon2Type, [Nullable(2)] string argon2Salt, [Nullable(2)] string argon2Iterations, [Nullable(2)] string argon2Memory, [Nullable(2)] string argon2Parallelism, byte[] data, string mac, [Nullable(2)] string passPhrase)
{
_version = version;
_algorithmName = algorithmName;
_encryptionType = encryptionType;
_comment = comment;
_publicKey = publicKey;
_argon2Type = argon2Type;
_argon2Salt = argon2Salt;
_argon2Iterations = argon2Iterations;
_argon2Memory = argon2Memory;
_argon2Parallelism = argon2Parallelism;
_data = data;
_mac = mac;
_passPhrase = passPhrase;
}
public Key Parse()
{
string encryptionType = _encryptionType;
HMAC hMAC;
byte[] array = default(byte[]);
if (!(encryptionType == "aes256-cbc")) {
if (!(encryptionType == "none"))
throw new SshException("Encryption " + _encryptionType + " is not supported for PuTTY key file");
string version = _version;
if (!(version == "3")) {
if (!(version == "2"))
throw new SshException("PuTTY key file version " + _version + " is not supported");
hMAC = new HMACSHA1(CryptoAbstraction.HashSHA1(Encoding.UTF8.GetBytes("putty-private-key-file-mac-key")));
} else
hMAC = new HMACSHA256(Array.Empty<byte>());
array = _data;
} else {
if (string.IsNullOrEmpty(_passPhrase))
throw new SshPassPhraseNullOrEmptyException("Private key is encrypted but passphrase is empty.");
string version = _version;
byte[] key;
byte[] iv;
if (!(version == "3")) {
if (!(version == "2"))
throw new SshException("PuTTY key file version " + _version + " is not supported");
key = V2KDF(_passPhrase).Take(32);
iv = new byte[16];
hMAC = new HMACSHA1(CryptoAbstraction.HashSHA1(Encoding.UTF8.GetBytes("putty-private-key-file-mac-key" + _passPhrase)).Take(20));
} else {
ThrowHelper.ThrowIfNullOrEmpty(_argon2Type, "_argon2Type");
ThrowHelper.ThrowIfNullOrEmpty(_argon2Iterations, "_argon2Iterations");
ThrowHelper.ThrowIfNullOrEmpty(_argon2Memory, "_argon2Memory");
ThrowHelper.ThrowIfNullOrEmpty(_argon2Parallelism, "_argon2Parallelism");
ThrowHelper.ThrowIfNullOrEmpty(_argon2Salt, "_argon2Salt");
byte[] value = Argon2(_argon2Type, Convert.ToInt32(_argon2Iterations), Convert.ToInt32(_argon2Memory), Convert.ToInt32(_argon2Parallelism), Convert.FromHexString(_argon2Salt), _passPhrase);
key = value.Take(32);
iv = value.Take(32, 16);
hMAC = new HMACSHA256(value.Take(48, 32));
}
using (AesCipher aesCipher = new AesCipher(key, iv, AesCipherMode.CBC, false))
array = aesCipher.Decrypt(_data);
}
byte[] buffer = default(byte[]);
using (SshDataStream sshDataStream = new SshDataStream(256)) {
sshDataStream.Write(_algorithmName, Encoding.UTF8);
sshDataStream.Write(_encryptionType, Encoding.UTF8);
sshDataStream.Write(_comment, Encoding.UTF8);
sshDataStream.WriteBinary(_publicKey);
sshDataStream.WriteBinary(array);
buffer = sshDataStream.ToArray();
}
byte[] first = default(byte[]);
using (hMAC)
first = hMAC.ComputeHash(buffer);
byte[] second = Convert.FromHexString(_mac);
if (!first.SequenceEqual(second))
throw new SshException("MAC verification failed for PuTTY key file");
SshDataReader sshDataReader = new SshDataReader(_publicKey);
string text = sshDataReader.ReadString(Encoding.UTF8);
SshDataReader sshDataReader2 = new SshDataReader(array);
Key key2;
switch (text) {
case "ssh-ed25519":
key2 = new ED25519Key(sshDataReader2.ReadBignum2());
break;
case "ecdsa-sha2-nistp256":
case "ecdsa-sha2-nistp384":
case "ecdsa-sha2-nistp521": {
string curve = sshDataReader.ReadString(Encoding.ASCII);
byte[] publickey = sshDataReader.ReadBignum2();
byte[] privatekey = sshDataReader2.ReadBignum2();
key2 = new EcdsaKey(curve, publickey, privatekey);
break;
}
case "ssh-rsa": {
BigInteger exponent = sshDataReader.ReadBignum();
BigInteger modulus = sshDataReader.ReadBignum();
BigInteger d = sshDataReader2.ReadBignum();
BigInteger p = sshDataReader2.ReadBignum();
BigInteger q = sshDataReader2.ReadBignum();
BigInteger inverseQ = sshDataReader2.ReadBignum();
key2 = new RsaKey(modulus, exponent, d, p, q, inverseQ);
break;
}
default:
throw new SshException("Key type " + text + " is not supported for PuTTY key file");
}
key2.Comment = _comment;
return key2;
}
private static byte[] Argon2(string type, int iterations, int memory, int parallelism, byte[] salt, string passPhrase)
{
int type2;
if (!(type == "Argon2i")) {
if (!(type == "Argon2d")) {
if (!(type == "Argon2id"))
throw new SshException("KDF " + type + " is not supported for PuTTY key file");
type2 = Argon2Parameters.Argon2id;
} else
type2 = Argon2Parameters.Argon2d;
} else
type2 = Argon2Parameters.Argon2i;
Argon2Parameters parameters = new Argon2Parameters.Builder(type2).WithVersion(Argon2Parameters.Version13).WithIterations(iterations).WithMemoryAsKB(memory)
.WithParallelism(parallelism)
.WithSalt(salt)
.Build();
Argon2BytesGenerator argon2BytesGenerator = new Argon2BytesGenerator();
argon2BytesGenerator.Init(parameters);
byte[] array = new byte[80];
if (argon2BytesGenerator.GenerateBytes(passPhrase.ToCharArray(), array) != array.Length)
throw new SshException("Failed to generate key via Argon2");
return array;
}
private static byte[] V2KDF(string passPhrase)
{
List<byte> list = new List<byte>();
byte[] bytes = Encoding.UTF8.GetBytes(passPhrase);
for (int i = 0; i < 2; i++) {
using (SHA1 sHA = SHA1.Create()) {
byte[] inputBuffer = new byte[4] {
0,
0,
0,
(byte)i
};
sHA.TransformBlock(inputBuffer, 0, 4, null, 0);
sHA.TransformFinalBlock(bytes, 0, bytes.Length);
list.AddRange(sHA.Hash);
}
}
return list.ToArray();
}
}
[Nullable(0)]
private sealed class SSHCOM : IPrivateKeyParser
{
private readonly byte[] _data;
[Nullable(2)]
private readonly string _passPhrase;
public SSHCOM(byte[] data, [Nullable(2)] string passPhrase)
{
_data = data;
_passPhrase = passPhrase;
}
public Key Parse()
{
SshDataReader sshDataReader = new SshDataReader(_data);
if (sshDataReader.ReadUInt32() != 1064303083)
throw new SshException("Invalid SSH2 private key.");
sshDataReader.ReadUInt32();
string text = sshDataReader.ReadString(SshData.Ascii);
string text2 = sshDataReader.ReadString(SshData.Ascii);
int num = (int)sshDataReader.ReadUInt32();
byte[] data;
if (text2 == "none")
data = sshDataReader.ReadBytes(num);
else {
if (!(text2 == "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 byte[8], System.Security.Cryptography.CipherMode.CBC, false).Decrypt(sshDataReader.ReadBytes(num));
}
sshDataReader = new SshDataReader(data);
if (sshDataReader.ReadUInt32() > num - 4)
throw new SshException("Invalid passphrase.");
if (text.Contains("rsa")) {
BigInteger exponent = sshDataReader.ReadBigIntWithBits();
BigInteger d = sshDataReader.ReadBigIntWithBits();
BigInteger modulus = sshDataReader.ReadBigIntWithBits();
BigInteger inverseQ = sshDataReader.ReadBigIntWithBits();
BigInteger q = sshDataReader.ReadBigIntWithBits();
BigInteger p = sshDataReader.ReadBigIntWithBits();
return new RsaKey(modulus, exponent, d, p, q, inverseQ);
}
throw new NotSupportedException($"""{text}""");
}
private static byte[] GetCipherKey(string passphrase, int length)
{
List<byte> list = new List<byte>();
using (MD5 mD = MD5.Create()) {
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 const string PrivateKeyPattern = "^-+ *BEGIN (?<keyName>\\w+( \\w+)*) *-+\\r?\\n((Proc-Type: 4,ENCRYPTED\\r?\\nDEK-Info: (?<cipherName>[A-Z0-9-]+),(?<salt>[a-fA-F0-9]+)\\r?\\n\\r?\\n)|(Comment: \"?[^\\r\\n]*\"?\\r?\\n))?(?<data>([a-zA-Z0-9/+=]{1,80}\\r?\\n)+)(\\r?\\n)?-+ *END \\k<keyName> *-+";
private const string PuTTYPrivateKeyPattern = "^(?<keyName>PuTTY-User-Key-File)-(?<version>\\d+): (?<algorithmName>[\\w-]+)\\r?\\nEncryption: (?<encryptionType>[\\w-]+)\\r?\\nComment: (?<comment>.*?)\\r?\\nPublic-Lines: \\d+\\r?\\n(?<publicKey>(([a-zA-Z0-9/+=]{1,64})\\r?\\n)+)(Key-Derivation: (?<argon2Type>\\w+)\\r?\\nArgon2-Memory: (?<argon2Memory>\\d+)\\r?\\nArgon2-Passes: (?<argon2Passes>\\d+)\\r?\\nArgon2-Parallelism: (?<argon2Parallelism>\\d+)\\r?\\nArgon2-Salt: (?<argon2Salt>[a-fA-F0-9]+)\\r?\\n)?Private-Lines: \\d+\\r?\\n(?<data>(([a-zA-Z0-9/+=]{1,64})\\r?\\n)+)+Private-MAC: (?<mac>[a-fA-F0-9]+)";
private const string CertificatePattern = "(?<type>[-\\w]+@openssh\\.com)\\s(?<data>[a-zA-Z0-9\\/+=]*)(\\s+(?<comment>.*))?";
private static readonly Regex PrivateKeyRegex = GetPrivateKeyRegex();
private static readonly Regex PuTTYPrivateKeyRegex = GetPrivateKeyPuTTYRegex();
private static readonly Regex CertificateRegex = GetCertificateRegex();
private readonly List<HostAlgorithm> _hostAlgorithms = new List<HostAlgorithm>();
private Key _key;
private bool _isDisposed;
public IReadOnlyCollection<HostAlgorithm> HostKeyAlgorithms => _hostAlgorithms;
public Key Key => _key;
[Nullable(2)]
[field: Nullable(2)]
public Certificate Certificate {
[NullableContext(2)]
get;
[NullableContext(2)]
private set;
}
[GeneratedRegex("^-+ *BEGIN (?<keyName>\\w+( \\w+)*) *-+\\r?\\n((Proc-Type: 4,ENCRYPTED\\r?\\nDEK-Info: (?<cipherName>[A-Z0-9-]+),(?<salt>[a-fA-F0-9]+)\\r?\\n\\r?\\n)|(Comment: \"?[^\\r\\n]*\"?\\r?\\n))?(?<data>([a-zA-Z0-9/+=]{1,80}\\r?\\n)+)(\\r?\\n)?-+ *END \\k<keyName> *-+", RegexOptions.Multiline | RegexOptions.ExplicitCapture)]
[GeneratedCode("System.Text.RegularExpressions.Generator", "8.0.12.16413")]
private static Regex GetPrivateKeyRegex()
{
return <RegexGenerator_g>FF02E8C37B99F41659B0026E2B1005AC8CE9F303D9F9E176C498C4EA009567923__GetPrivateKeyRegex_5.Instance;
}
[GeneratedRegex("^(?<keyName>PuTTY-User-Key-File)-(?<version>\\d+): (?<algorithmName>[\\w-]+)\\r?\\nEncryption: (?<encryptionType>[\\w-]+)\\r?\\nComment: (?<comment>.*?)\\r?\\nPublic-Lines: \\d+\\r?\\n(?<publicKey>(([a-zA-Z0-9/+=]{1,64})\\r?\\n)+)(Key-Derivation: (?<argon2Type>\\w+)\\r?\\nArgon2-Memory: (?<argon2Memory>\\d+)\\r?\\nArgon2-Passes: (?<argon2Passes>\\d+)\\r?\\nArgon2-Parallelism: (?<argon2Parallelism>\\d+)\\r?\\nArgon2-Salt: (?<argon2Salt>[a-fA-F0-9]+)\\r?\\n)?Private-Lines: \\d+\\r?\\n(?<data>(([a-zA-Z0-9/+=]{1,64})\\r?\\n)+)+Private-MAC: (?<mac>[a-fA-F0-9]+)", RegexOptions.Multiline | RegexOptions.ExplicitCapture)]
[GeneratedCode("System.Text.RegularExpressions.Generator", "8.0.12.16413")]
private static Regex GetPrivateKeyPuTTYRegex()
{
return <RegexGenerator_g>FF02E8C37B99F41659B0026E2B1005AC8CE9F303D9F9E176C498C4EA009567923__GetPrivateKeyPuTTYRegex_6.Instance;
}
[GeneratedRegex("(?<type>[-\\w]+@openssh\\.com)\\s(?<data>[a-zA-Z0-9\\/+=]*)(\\s+(?<comment>.*))?", RegexOptions.ExplicitCapture)]
[GeneratedCode("System.Text.RegularExpressions.Generator", "8.0.12.16413")]
private static Regex GetCertificateRegex()
{
return <RegexGenerator_g>FF02E8C37B99F41659B0026E2B1005AC8CE9F303D9F9E176C498C4EA009567923__GetCertificateRegex_7.Instance;
}
public PrivateKeyFile(Key key)
{
ThrowHelper.ThrowIfNull(key, "key");
_key = key;
_hostAlgorithms.Add(new KeyHostAlgorithm(key.ToString(), key));
}
public PrivateKeyFile(Stream privateKey)
: this(privateKey, null, null)
{
}
public PrivateKeyFile(string fileName)
: this(fileName, null, null)
{
}
public PrivateKeyFile(string fileName, [Nullable(2)] string passPhrase)
: this(fileName, passPhrase, null)
{
}
[NullableContext(2)]
public PrivateKeyFile([Nullable(1)] string fileName, string passPhrase, string certificateFileName)
{
ThrowHelper.ThrowIfNull(fileName, "fileName");
using (FileStream privateKey = File.OpenRead(fileName))
Open(privateKey, passPhrase);
if (certificateFileName != null) {
using (FileStream certificate = File.OpenRead(certificateFileName))
OpenCertificate(certificate);
}
}
public PrivateKeyFile(Stream privateKey, [Nullable(2)] string passPhrase)
: this(privateKey, passPhrase, null)
{
}
[NullableContext(2)]
public PrivateKeyFile([Nullable(1)] Stream privateKey, string passPhrase, Stream certificate)
{
ThrowHelper.ThrowIfNull(privateKey, "privateKey");
Open(privateKey, passPhrase);
if (certificate != null)
OpenCertificate(certificate);
}
[MemberNotNull("_key")]
private void Open(Stream privateKey, [Nullable(2)] string passPhrase)
{
Match match = default(Match);
using (StreamReader streamReader = new StreamReader(privateKey)) {
string text = streamReader.ReadToEnd();
match = ((!text.StartsWith("PuTTY-User-Key-File", StringComparison.Ordinal)) ? PrivateKeyRegex.Match(text) : PuTTYPrivateKeyRegex.Match(text));
}
if (!match.Success)
throw new SshException("Invalid private key file.");
string text2 = match.Result("${keyName}");
byte[] data = Convert.FromBase64String(match.Result("${data}"));
if (text2 != null) {
IPrivateKeyParser privateKeyParser;
RsaKey rsaKey;
string version;
string algorithmName;
string encryptionType;
string comment;
string s;
string argon2Type;
string argon2Memory;
string argon2Iterations;
string argon2Parallelism;
string argon2Salt;
string mac;
string cipherName;
string salt;
switch (text2.Length) {
case 15:
switch (text2[0]) {
case 'R':
break;
case 'D':
goto IL_0117;
default:
goto IL_02ab;
}
if (!(text2 == "RSA PRIVATE KEY"))
break;
goto IL_0195;
case 19:
switch (text2[0]) {
case 'O':
break;
case 'P':
goto IL_0171;
default:
goto IL_02ab;
}
if (!(text2 == "OPENSSH PRIVATE KEY"))
break;
privateKeyParser = new OpenSSH(data, passPhrase);
goto IL_02c1;
case 14:
if (!(text2 == "EC PRIVATE KEY"))
break;
goto IL_0195;
case 11:
if (!(text2 == "PRIVATE KEY"))
break;
privateKeyParser = new PKCS8(false, data, passPhrase);
goto IL_02c1;
case 21:
if (!(text2 == "ENCRYPTED PRIVATE KEY"))
break;
privateKeyParser = new PKCS8(true, data, passPhrase);
goto IL_02c1;
case 26:
{
if (!(text2 == "SSH2 ENCRYPTED PRIVATE KEY"))
break;
privateKeyParser = new SSHCOM(data, passPhrase);
goto IL_02c1;
}
IL_0117:
if (!(text2 == "DSA PRIVATE KEY"))
break;
goto IL_0195;
IL_02c1:
_key = privateKeyParser.Parse();
rsaKey = (_key as RsaKey);
if (rsaKey != null) {
_hostAlgorithms.Add(new KeyHostAlgorithm("ssh-rsa", _key));
_hostAlgorithms.Add(new KeyHostAlgorithm("rsa-sha2-512", _key, new RsaDigitalSignature(rsaKey, HashAlgorithmName.SHA512)));
_hostAlgorithms.Add(new KeyHostAlgorithm("rsa-sha2-256", _key, new RsaDigitalSignature(rsaKey, HashAlgorithmName.SHA256)));
} else
_hostAlgorithms.Add(new KeyHostAlgorithm(_key.ToString(), _key));
return;
IL_0171:
if (!(text2 == "PuTTY-User-Key-File"))
break;
version = match.Result("${version}");
algorithmName = match.Result("${algorithmName}");
encryptionType = match.Result("${encryptionType}");
comment = match.Result("${comment}");
s = match.Result("${publicKey}");
argon2Type = match.Result("${argon2Type}");
argon2Memory = match.Result("${argon2Memory}");
argon2Iterations = match.Result("${argon2Passes}");
argon2Parallelism = match.Result("${argon2Parallelism}");
argon2Salt = match.Result("${argon2Salt}");
mac = match.Result("${mac}");
privateKeyParser = new PuTTY(version, algorithmName, encryptionType, comment, Convert.FromBase64String(s), argon2Type, argon2Salt, argon2Iterations, argon2Memory, argon2Parallelism, data, mac, passPhrase);
goto IL_02c1;
IL_0195:
cipherName = match.Result("${cipherName}");
salt = match.Result("${salt}");
privateKeyParser = new PKCS1(cipherName, salt, text2, data, passPhrase);
goto IL_02c1;
}
}
goto IL_02ab;
IL_02ab:
throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Key '{0}' is not supported.", text2));
}
private void OpenCertificate(Stream certificate)
{
Match match = default(Match);
using (StreamReader streamReader = new StreamReader(certificate)) {
string input = streamReader.ReadToEnd();
match = CertificateRegex.Match(input);
}
if (!match.Success)
throw new SshException("Invalid certificate file.");
string s = match.Result("${data}");
Certificate = new Certificate(Convert.FromBase64String(s));
if (!Certificate.Key.Public.SequenceEqual(Key.Public))
throw new ArgumentException("The supplied certificate does not certify the supplied key.");
RsaKey rsaKey = Key as RsaKey;
if (rsaKey != null) {
_hostAlgorithms.Insert(0, new CertificateHostAlgorithm("ssh-rsa-cert-v01@openssh.com", Key, Certificate));
_hostAlgorithms.Insert(0, new CertificateHostAlgorithm("rsa-sha2-256-cert-v01@openssh.com", Key, Certificate, new RsaDigitalSignature(rsaKey, HashAlgorithmName.SHA256)));
_hostAlgorithms.Insert(0, new CertificateHostAlgorithm("rsa-sha2-512-cert-v01@openssh.com", Key, Certificate, new RsaDigitalSignature(rsaKey, HashAlgorithmName.SHA512)));
} else
_hostAlgorithms.Insert(0, new CertificateHostAlgorithm(Certificate.Name, Key, Certificate));
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_isDisposed && disposing) {
IDisposable disposable = _key as IDisposable;
if (disposable != null) {
disposable.Dispose();
_isDisposed = true;
}
}
}
}
}