<PackageReference Include="SSH.NET" Version="2025.0.0" />

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; } } } } }