diff --git a/Directory.Packages.props b/Directory.Packages.props index 3964b45d7..4352dee4d 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -5,7 +5,7 @@ - + diff --git a/README.md b/README.md index c308a6bfd..ca0e5b327 100644 --- a/README.md +++ b/README.md @@ -102,17 +102,21 @@ The main types provided by this library are: * OpenSSL PKCS#8 PEM format ("BEGIN PRIVATE KEY", "BEGIN ENCRYPTED PRIVATE KEY") * ssh.com format ("BEGIN SSH2 ENCRYPTED PRIVATE KEY") * OpenSSH key format ("BEGIN OPENSSH PRIVATE KEY") + * PuTTY private key format ("PuTTY-User-Key-File-2", "PuTTY-User-Key-File-3") * DSA in * OpenSSL traditional PEM format ("BEGIN DSA PRIVATE KEY") * OpenSSL PKCS#8 PEM format ("BEGIN PRIVATE KEY", "BEGIN ENCRYPTED PRIVATE KEY") * ssh.com format ("BEGIN SSH2 ENCRYPTED PRIVATE KEY") + * PuTTY private key format ("PuTTY-User-Key-File-2", "PuTTY-User-Key-File-3") * ECDSA 256/384/521 in * OpenSSL traditional PEM format ("BEGIN EC PRIVATE KEY") * OpenSSL PKCS#8 PEM format ("BEGIN PRIVATE KEY", "BEGIN ENCRYPTED PRIVATE KEY") * OpenSSH key format ("BEGIN OPENSSH PRIVATE KEY") + * PuTTY private key format ("PuTTY-User-Key-File-2", "PuTTY-User-Key-File-3") * ED25519 in * OpenSSL PKCS#8 PEM format ("BEGIN PRIVATE KEY", "BEGIN ENCRYPTED PRIVATE KEY") * OpenSSH key format ("BEGIN OPENSSH PRIVATE KEY") + * PuTTY private key format ("PuTTY-User-Key-File-2", "PuTTY-User-Key-File-3") Private keys in OpenSSL traditional PEM format can be encrypted using one of the following cipher methods: * DES-EDE3-CBC @@ -124,7 +128,7 @@ Private keys in OpenSSL traditional PEM format can be encrypted using one of the Private keys in OpenSSL PKCS#8 PEM format can be encrypted using any cipher method BouncyCastle supports. -Private keys in ssh.com format can be encrypted using one of the following cipher methods: +Private keys in ssh.com format can be encrypted using the following cipher method: * 3des-cbc Private keys in OpenSSH key format can be encrypted using one of the following cipher methods: @@ -139,6 +143,9 @@ Private keys in OpenSSH key format can be encrypted using one of the following c * aes256-gcm@openssh.com * chacha20-poly1305@openssh.com +Private keys in PuTTY private key format can be encrypted using the following cipher method: +* aes256-cbc + ## Host Key Algorithms **SSH.NET** supports the following host key algorithms: diff --git a/src/Renci.SshNet/PrivateKeyFile.PKCS1.cs b/src/Renci.SshNet/PrivateKeyFile.PKCS1.cs index 0ef8d4bc2..791a1fc1d 100644 --- a/src/Renci.SshNet/PrivateKeyFile.PKCS1.cs +++ b/src/Renci.SshNet/PrivateKeyFile.PKCS1.cs @@ -42,13 +42,11 @@ public Key Parse() { throw new SshPassPhraseNullOrEmptyException("Private key is encrypted but passphrase is empty."); } - - var binarySalt = new byte[_salt.Length / 2]; - for (var i = 0; i < binarySalt.Length; i++) - { - binarySalt[i] = Convert.ToByte(_salt.Substring(i * 2, 2), 16); - } - +#if NET + var binarySalt = Convert.FromHexString(_salt); +#else + var binarySalt = Org.BouncyCastle.Utilities.Encoders.Hex.Decode(_salt); +#endif CipherInfo cipher; switch (_cipherName) { diff --git a/src/Renci.SshNet/PrivateKeyFile.PuTTY.cs b/src/Renci.SshNet/PrivateKeyFile.PuTTY.cs new file mode 100644 index 000000000..627fcb991 --- /dev/null +++ b/src/Renci.SshNet/PrivateKeyFile.PuTTY.cs @@ -0,0 +1,273 @@ +#nullable enable +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Security.Cryptography; +using System.Text; + +using Org.BouncyCastle.Crypto.Generators; +using Org.BouncyCastle.Crypto.Parameters; + +using Renci.SshNet.Abstractions; +using Renci.SshNet.Common; +using Renci.SshNet.Security; +using Renci.SshNet.Security.Cryptography.Ciphers; + +namespace Renci.SshNet +{ + public partial class PrivateKeyFile + { + 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; + private readonly string? _argon2Type; + private readonly string? _argon2Salt; + private readonly string? _argon2Iterations; + private readonly string? _argon2Memory; + private readonly string? _argon2Parallelism; + private readonly byte[] _data; + private readonly string _mac; + private readonly string? _passPhrase; + + public PuTTY(string version, string algorithmName, string encryptionType, string comment, byte[] publicKey, string? argon2Type, string? argon2Salt, string? argon2Iterations, string? argon2Memory, string? argon2Parallelism, byte[] data, string mac, 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; + } + + /// + /// Parses an PuTTY PPK key file. + /// . + /// + public Key Parse() + { + byte[] privateKey; + HMAC hmac; + switch (_encryptionType) + { + case "aes256-cbc": + if (string.IsNullOrEmpty(_passPhrase)) + { + throw new SshPassPhraseNullOrEmptyException("Private key is encrypted but passphrase is empty."); + } + + byte[] cipherKey; + byte[] cipherIV; + switch (_version) + { + case "3": + ThrowHelper.ThrowIfNullOrEmpty(_argon2Type); + ThrowHelper.ThrowIfNullOrEmpty(_argon2Iterations); + ThrowHelper.ThrowIfNullOrEmpty(_argon2Memory); + ThrowHelper.ThrowIfNullOrEmpty(_argon2Parallelism); + ThrowHelper.ThrowIfNullOrEmpty(_argon2Salt); + + var keyData = Argon2( + _argon2Type, + Convert.ToInt32(_argon2Iterations), + Convert.ToInt32(_argon2Memory), + Convert.ToInt32(_argon2Parallelism), +#if NET + Convert.FromHexString(_argon2Salt), +#else + Org.BouncyCastle.Utilities.Encoders.Hex.Decode(_argon2Salt), +#endif + _passPhrase); + + cipherKey = keyData.Take(32); + cipherIV = keyData.Take(32, 16); + + var macKey = keyData.Take(48, 32); + hmac = new HMACSHA256(macKey); + + break; + case "2": + keyData = V2KDF(_passPhrase); + + cipherKey = keyData.Take(32); + cipherIV = new byte[16]; + + macKey = CryptoAbstraction.HashSHA1(Encoding.UTF8.GetBytes("putty-private-key-file-mac-key" + _passPhrase)).Take(20); + hmac = new HMACSHA1(macKey); + + break; + default: + throw new SshException("PuTTY key file version " + _version + " is not supported"); + } + + using (var cipher = new AesCipher(cipherKey, cipherIV, AesCipherMode.CBC, pkcs7Padding: false)) + { + privateKey = cipher.Decrypt(_data); + } + + break; + case "none": + switch (_version) + { + case "3": + hmac = new HMACSHA256(Array.Empty()); + break; + case "2": + var macKey = CryptoAbstraction.HashSHA1(Encoding.UTF8.GetBytes("putty-private-key-file-mac-key")); + hmac = new HMACSHA1(macKey); + break; + default: + throw new SshException("PuTTY key file version " + _version + " is not supported"); + } + + privateKey = _data; + break; + default: + throw new SshException("Encryption " + _encryptionType + " is not supported for PuTTY key file"); + } + + byte[] macData; + using (var macStream = new SshDataStream(256)) + { + macStream.Write(_algorithmName, Encoding.UTF8); + macStream.Write(_encryptionType, Encoding.UTF8); + macStream.Write(_comment, Encoding.UTF8); + macStream.WriteBinary(_publicKey); + macStream.WriteBinary(privateKey); + macData = macStream.ToArray(); + } + + byte[] macValue; + using (hmac) + { + macValue = hmac.ComputeHash(macData); + } +#if NET + var reference = Convert.FromHexString(_mac); +#else + var reference = Org.BouncyCastle.Utilities.Encoders.Hex.Decode(_mac); +#endif + if (!macValue.SequenceEqual(reference)) + { + throw new SshException("MAC verification failed for PuTTY key file"); + } + + var publicKeyReader = new SshDataReader(_publicKey); + var keyType = publicKeyReader.ReadString(Encoding.UTF8); + Debug.Assert(keyType == _algorithmName, $"{nameof(keyType)} is not the same as {nameof(_algorithmName)}"); + + var privateKeyReader = new SshDataReader(privateKey); + + Key parsedKey; + + switch (keyType) + { + case "ssh-ed25519": + parsedKey = new ED25519Key(privateKeyReader.ReadBignum2()); + break; + case "ecdsa-sha2-nistp256": + case "ecdsa-sha2-nistp384": + case "ecdsa-sha2-nistp521": + var curve = publicKeyReader.ReadString(Encoding.ASCII); + var pub = publicKeyReader.ReadBignum2(); + var prv = privateKeyReader.ReadBignum2(); + parsedKey = new EcdsaKey(curve, pub, prv); + break; + case "ssh-dss": + var p = publicKeyReader.ReadBignum(); + var q = publicKeyReader.ReadBignum(); + var g = publicKeyReader.ReadBignum(); + var y = publicKeyReader.ReadBignum(); + var x = privateKeyReader.ReadBignum(); + parsedKey = new DsaKey(p, q, g, y, x); + break; + case "ssh-rsa": + var exponent = publicKeyReader.ReadBignum(); // e + var modulus = publicKeyReader.ReadBignum(); // n + var d = privateKeyReader.ReadBignum(); // d + p = privateKeyReader.ReadBignum(); // p + q = privateKeyReader.ReadBignum(); // q + var inverseQ = privateKeyReader.ReadBignum(); // iqmp + parsedKey = new RsaKey(modulus, exponent, d, p, q, inverseQ); + break; + default: + throw new SshException("Key type " + keyType + " is not supported for PuTTY key file"); + } + + parsedKey.Comment = _comment; + return parsedKey; + } + + private static byte[] Argon2(string type, int iterations, int memory, int parallelism, byte[] salt, string passPhrase) + { + int param; + switch (type) + { + case "Argon2i": + param = Argon2Parameters.Argon2i; + break; + case "Argon2d": + param = Argon2Parameters.Argon2d; + break; + case "Argon2id": + param = Argon2Parameters.Argon2id; + break; + default: + throw new SshException("KDF " + type + " is not supported for PuTTY key file"); + } + + var a2p = new Argon2Parameters.Builder(param) + .WithVersion(Argon2Parameters.Version13) + .WithIterations(iterations) + .WithMemoryAsKB(memory) + .WithParallelism(parallelism) + .WithSalt(salt).Build(); + + var generator = new Argon2BytesGenerator(); + + generator.Init(a2p); + + var output = new byte[80]; + var bytes = generator.GenerateBytes(passPhrase.ToCharArray(), output); + + if (bytes != output.Length) + { + throw new SshException("Failed to generate key via Argon2"); + } + + return output; + } + + private static byte[] V2KDF(string passPhrase) + { + var cipherKey = new List(); + + var passPhraseBytes = Encoding.UTF8.GetBytes(passPhrase); + for (var sequenceNumber = 0; sequenceNumber < 2; sequenceNumber++) + { + using (var sha1 = SHA1.Create()) + { + var sequence = new byte[] { 0, 0, 0, (byte)sequenceNumber }; + _ = sha1.TransformBlock(sequence, 0, 4, outputBuffer: null, 0); + _ = sha1.TransformFinalBlock(passPhraseBytes, 0, passPhraseBytes.Length); + Debug.Assert(sha1.Hash != null, "Hash is null"); + cipherKey.AddRange(sha1.Hash); + } + } + + return cipherKey.ToArray(); + } + } + } +} diff --git a/src/Renci.SshNet/PrivateKeyFile.cs b/src/Renci.SshNet/PrivateKeyFile.cs index 8112d207c..bfbe89fd1 100644 --- a/src/Renci.SshNet/PrivateKeyFile.cs +++ b/src/Renci.SshNet/PrivateKeyFile.cs @@ -25,16 +25,16 @@ namespace Renci.SshNet /// The following private keys are supported: /// /// - /// RSA in OpenSSL PEM, ssh.com and OpenSSH key format + /// RSA in OpenSSL PEM, ssh.com, OpenSSH and PuTTY key format /// /// - /// DSA in OpenSSL PEM and ssh.com format + /// DSA in OpenSSL PEM, ssh.com and PuTTY key format /// /// - /// ECDSA 256/384/521 in OpenSSL PEM and OpenSSH key format + /// ECDSA 256/384/521 in OpenSSL PEM, OpenSSH and PuTTY key format /// /// - /// ED25519 in OpenSSL PEM and OpenSSH key format + /// ED25519 in OpenSSL PEM, OpenSSH and PuTTY key format /// /// /// @@ -73,7 +73,7 @@ namespace Renci.SshNet /// /// /// - /// The following encryption algorithms are supported for OpenSSH format: + /// The following encryption algorithms are supported for OpenSSH key format: /// /// /// 3des-cbc @@ -107,23 +107,37 @@ namespace Renci.SshNet /// /// /// + /// + /// The following encryption algorithms are supported for PuTTY key format: + /// + /// + /// aes256-cbc + /// + /// + /// /// public partial class PrivateKeyFile : IPrivateKeySource, IDisposable { private const string PrivateKeyPattern = @"^-+ *BEGIN (?\w+( \w+)*) *-+\r?\n((Proc-Type: 4,ENCRYPTED\r?\nDEK-Info: (?[A-Z0-9-]+),(?[a-fA-F0-9]+)\r?\n\r?\n)|(Comment: ""?[^\r\n]*""?\r?\n))?(?([a-zA-Z0-9/+=]{1,80}\r?\n)+)(\r?\n)?-+ *END \k *-+"; + private const string PuTTYPrivateKeyPattern = @"^(?PuTTY-User-Key-File)-(?\d+): (?[\w-]+)\r?\nEncryption: (?[\w-]+)\r?\nComment: (?.*)\r?\nPublic-Lines: \d+\r?\n(?(([a-zA-Z0-9/+=]{1,64})\r?\n)+)(Key-Derivation: (?\w+)\r?\nArgon2-Memory: (?\d+)\r?\nArgon2-Passes: (?\d+)\r?\nArgon2-Parallelism: (?\d+)\r?\nArgon2-Salt: (?[a-fA-F0-9]+)\r?\n)?Private-Lines: \d+\r?\n(?(([a-zA-Z0-9/+=]{1,64})\r?\n)+)+Private-MAC: (?[a-fA-F0-9]+)"; private const string CertificatePattern = @"(?[-\w]+@openssh\.com)\s(?[a-zA-Z0-9\/+=]*)(\s+(?.*))?"; #if NET7_0_OR_GREATER private static readonly Regex PrivateKeyRegex = GetPrivateKeyRegex(); + private static readonly Regex PuTTYPrivateKeyRegex = GetPrivateKeyPuTTYRegex(); private static readonly Regex CertificateRegex = GetCertificateRegex(); [GeneratedRegex(PrivateKeyPattern, RegexOptions.Multiline | RegexOptions.ExplicitCapture)] private static partial Regex GetPrivateKeyRegex(); + [GeneratedRegex(PuTTYPrivateKeyPattern, RegexOptions.Multiline | RegexOptions.ExplicitCapture)] + private static partial Regex GetPrivateKeyPuTTYRegex(); + [GeneratedRegex(CertificatePattern, RegexOptions.ExplicitCapture)] private static partial Regex GetCertificateRegex(); #else private static readonly Regex PrivateKeyRegex = new Regex(PrivateKeyPattern, RegexOptions.Compiled | RegexOptions.Multiline | RegexOptions.ExplicitCapture); + private static readonly Regex PuTTYPrivateKeyRegex = new Regex(PuTTYPrivateKeyPattern, RegexOptions.Compiled | RegexOptions.Multiline | RegexOptions.ExplicitCapture); private static readonly Regex CertificateRegex = new Regex(CertificatePattern, RegexOptions.Compiled | RegexOptions.ExplicitCapture); #endif @@ -287,7 +301,14 @@ private void Open(Stream privateKey, string? passPhrase) using (var sr = new StreamReader(privateKey)) { var text = sr.ReadToEnd(); - privateKeyMatch = PrivateKeyRegex.Match(text); + if (text.StartsWith("PuTTY-User-Key-File", StringComparison.Ordinal)) + { + privateKeyMatch = PuTTYPrivateKeyRegex.Match(text); + } + else + { + privateKeyMatch = PrivateKeyRegex.Match(text); + } } if (!privateKeyMatch.Success) @@ -321,6 +342,34 @@ private void Open(Stream privateKey, string? passPhrase) case "SSH2 ENCRYPTED PRIVATE KEY": parser = new SSHCOM(binaryData, passPhrase); break; + case "PuTTY-User-Key-File": + var version = privateKeyMatch.Result("${version}"); + var algorithmName = privateKeyMatch.Result("${algorithmName}"); + var encryptionType = privateKeyMatch.Result("${encryptionType}"); + var comment = privateKeyMatch.Result("${comment}"); + var publicKey = privateKeyMatch.Result("${publicKey}"); + var argon2Type = privateKeyMatch.Result("${argon2Type}"); + var argon2Memory = privateKeyMatch.Result("${argon2Memory}"); + var argon2Passes = privateKeyMatch.Result("${argon2Passes}"); + var argon2Parallelism = privateKeyMatch.Result("${argon2Parallelism}"); + var argon2Salt = privateKeyMatch.Result("${argon2Salt}"); + var mac = privateKeyMatch.Result("${mac}"); + + parser = new PuTTY( + version, + algorithmName, + encryptionType, + comment, + Convert.FromBase64String(publicKey), + argon2Type, + argon2Salt, + argon2Passes, + argon2Memory, + argon2Parallelism, + binaryData, + mac, + passPhrase); + break; default: throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Key '{0}' is not supported.", keyName)); } diff --git a/test/Data/Key.PuTTY2.Ed25519.Encrypted.12345.ppk b/test/Data/Key.PuTTY2.Ed25519.Encrypted.12345.ppk new file mode 100644 index 000000000..fc165774d --- /dev/null +++ b/test/Data/Key.PuTTY2.Ed25519.Encrypted.12345.ppk @@ -0,0 +1,9 @@ +PuTTY-User-Key-File-2: ssh-ed25519 +Encryption: aes256-cbc +Comment: Key.OPENSSH.ED25519 +Public-Lines: 2 +AAAAC3NzaC1lZDI1NTE5AAAAIA0JZnDQrxQZcNALfZYG7LPAW1MYEGvVW5nje7Ol +MGMi +Private-Lines: 1 +f3N2/AkwbgVeXdK155h+JCbWgBXyEk3qEyx+ChUqm4tOUQGiJ95/mTo4RbIjWn+2 +Private-MAC: 823817b8364ce7f52e278e252fd10e2f51ac8554 diff --git a/test/Data/Key.PuTTY2.Ed25519.ppk b/test/Data/Key.PuTTY2.Ed25519.ppk new file mode 100644 index 000000000..63c6b7eb7 --- /dev/null +++ b/test/Data/Key.PuTTY2.Ed25519.ppk @@ -0,0 +1,9 @@ +PuTTY-User-Key-File-2: ssh-ed25519 +Encryption: none +Comment: Key.OPENSSH.ED25519 +Public-Lines: 2 +AAAAC3NzaC1lZDI1NTE5AAAAIA0JZnDQrxQZcNALfZYG7LPAW1MYEGvVW5nje7Ol +MGMi +Private-Lines: 1 +AAAAIADMEXUw9TGuz7JykmHbzPOj8XebpZwo76iuxJtHkvAp +Private-MAC: 6a1329739932b5caaaf48d7fcd61392d498b8f5f diff --git a/test/Data/Key.PuTTY2.RSA.Encrypted.12345.ppk b/test/Data/Key.PuTTY2.RSA.Encrypted.12345.ppk new file mode 100644 index 000000000..b9c67d4db --- /dev/null +++ b/test/Data/Key.PuTTY2.RSA.Encrypted.12345.ppk @@ -0,0 +1,26 @@ +PuTTY-User-Key-File-2: ssh-rsa +Encryption: aes256-cbc +Comment: Key.OPENSSH.RSA +Public-Lines: 6 +AAAAB3NzaC1yc2EAAAADAQABAAABAQDtbs6KCLsePWaxraXweKYs/NqBWYT8Kx4w +oJHE8xO1ZO+hl0y3uF+S2FYDuHbRruhJJ4fa3sWp46lU0YVi9FXcFVawpkkxFx0m +JMJkCMffytiT3Re9neYqso3/d9xCyHg6I+dapPodKqDXiiJXxQ+1TCcTrmyRZLG/ +G34QuVWkKobm8TY78Y0MpATsXNi3q9CKEwVIAEGqO9q7SaNfTTYpiIIyvq+CXxdi +QMDifn4nJBJDHOed+sv3dmhqq6NE/ZtPlSFeBvOvwcXC6pAa9REQJlNMjwGK//q0 +4if3HaERo3q/EMu1dz30TZ3o1bpx2uLBoYUniOBVYMTmZTTTpd09 +Private-Lines: 14 +i38C+M7U/qOvzvOdtEC60v3cn354TMdDpAVk22UuNgQY7u6avW907mUuXjqU7Tjy +G66uQ+6vOnaQR6xTYWn3uI1YMWGsRylLiNZ/O/dIpITv+9xuU5U8qB+nFIH3iD6U +cBuumRKH2k2IxYVBO1nKTt3T3HejIIy3JlFifs9ylahgPD1m8jIfNARnYB09fOQr +g+nV4YNRntOqpf+cUpc+TGnz35oa9O+6fzJY/2hdkOJ+fUBdlYmcKEZZ9PgYHAlM ++aoQhL1vRoqZqzfHZYcMUBlmbRMgWiPfFB59nU0QbZ2uClbFxGGehBnCAsnd8Nni +TafVYckp3kCNLmctrmorsRgM/IXWcdqmrGJO5sHEsutQqs9Dp3KM9xhRP23xoGnZ +ssNw1qEAOP/k9JTCID/oeRE7RvAEMEdCs3l6FHKDvInR5xDuNrGehHdc3vGqklkC +1kBER2vWRX+LANNXcfISRDNajC6XKsDo6aGJRPTMj/tbVJNbjP4Oi6fwcFizV9Z0 +yJiw1yV5AjUc8nG3cuTb1wNZ8DTPccq+W6ir8U+Vmdc67Mhxas6e2UKa5nXC+HqC +jtM1oCPpxNyRoJSuxcWrwcs/yEhFfSOBU9PFsbJcX5i+ilZ2ZSipFc2fOcSx/6RC +U36mELhmHPINoTDOc6qvL/fXRkqtoQGZm2UHoINVLkeMu3ERkxxDk9kFqRcSdFd+ +nSMJt9d3YRSsjhzvro65ugKiYBubugon24frB3nARI9zoe0BZB9H/RW3RPlMT8d9 +H+gElqyiYWZKKJs+Qht6NY0Hv1gp9erMIcTTts/w7ZRc5oS8pLCKOuB9wKYepJm7 +5I0x8qVotiHglDJ7PPbP+3UzrJvGp2nwPzj4qokCZKFhPYNYiCY16V0LaBRoE3mQ +Private-MAC: 191817999553b67a7516223647e90e0b17db8918 diff --git a/test/Data/Key.PuTTY2.RSA.ppk b/test/Data/Key.PuTTY2.RSA.ppk new file mode 100644 index 000000000..33d71db93 --- /dev/null +++ b/test/Data/Key.PuTTY2.RSA.ppk @@ -0,0 +1,26 @@ +PuTTY-User-Key-File-2: ssh-rsa +Encryption: none +Comment: Key.OPENSSH.RSA +Public-Lines: 6 +AAAAB3NzaC1yc2EAAAADAQABAAABAQDtbs6KCLsePWaxraXweKYs/NqBWYT8Kx4w +oJHE8xO1ZO+hl0y3uF+S2FYDuHbRruhJJ4fa3sWp46lU0YVi9FXcFVawpkkxFx0m +JMJkCMffytiT3Re9neYqso3/d9xCyHg6I+dapPodKqDXiiJXxQ+1TCcTrmyRZLG/ +G34QuVWkKobm8TY78Y0MpATsXNi3q9CKEwVIAEGqO9q7SaNfTTYpiIIyvq+CXxdi +QMDifn4nJBJDHOed+sv3dmhqq6NE/ZtPlSFeBvOvwcXC6pAa9REQJlNMjwGK//q0 +4if3HaERo3q/EMu1dz30TZ3o1bpx2uLBoYUniOBVYMTmZTTTpd09 +Private-Lines: 14 +AAABAQDpeCr6CmnM632eu2zPkCN/W0eVJ6yftdpi4JFWA9veY5lK4RbcFR1NrRKv +Z+TWfNIGlSt+qc3eJ3IraDdsPWxsFEOBQpH4Bo1wI3dOnF/GDJV4mFAu8SQR2i/N +BFR/CtdF/GYTeOREZ9Vu/HKWsbynfnFyZfJ16XjqvaLx2PyAhje0qnREy9nhmU1u +FYc93k7HIdYv17eBs5LIjKNCBMpl7OHMStL9f8on9dirPIECo2pnZGDWQqIdGUdL +ooQja3IXBh+H5Fvov3FyHVKo61CFNaKubFLbl2kYPaOBqVd7KLDw+a6pOJYKpSZQ +zHox0Xe0WyKuvngrhAD2Sox5pEu1AAAAgQD+dPDqesFjwMJ9SXwWbqkLY3H5yXje +DZGEAXcm59L1buVHcqkkC2vIZQM0ToQPqib65bGYDPYfAsi08ropvJYpGR6HMDtd +8wU3VWkPHNpSb39rl0yFzWR7HkuyE5HwYjtYUgeM/EQ5Dq9+Zhn3W8iSBQMBWReF +7PFp0BfrxxGnawAAAIEA7t9vXgsFRX/YNMzR32bt9adFrRK3LEb+e36vlKD7aL/J +8VBe9aDlnuSkhpxrTCAiN9ZAbT4VG73zprqja4CQY4I2z0JotMUgBOS90LhCkTY5 +WhN/1mnSgcM4SQ7WrrmJNYn5K3QFaeu18kOabsrhoFWkATT268QPYNSG8ni+P/cA +AACBALFEE9FIau5dLoE3eGPfPWx+nltH6Jdtf5uwec5CUHqTWnVD07NfPLr7+Ip1 +vJ9jt0Qmp11h2XwidQLEfzBBFtgukA7b6ilx2831kJQmElcQdewo1ESmvHzWiAJP +fM4JjTcDudzQZXsq1IT4L5t8bewAoKc12OUcDSS/P2tFjpoM +Private-MAC: 7f487d19cb5d03257c9b9a2e1c7192a5d2396af5 diff --git a/test/Data/Key.PuTTY3.DSA.Encrypted.Argon2id.12345.ppk b/test/Data/Key.PuTTY3.DSA.Encrypted.Argon2id.12345.ppk new file mode 100644 index 000000000..a6205ded4 --- /dev/null +++ b/test/Data/Key.PuTTY3.DSA.Encrypted.Argon2id.12345.ppk @@ -0,0 +1,22 @@ +PuTTY-User-Key-File-3: ssh-dss +Encryption: aes256-cbc +Comment: imported-openssh-key +Public-Lines: 10 +AAAAB3NzaC1kc3MAAACBALVl3fae2O4qwsAK95SUShX0KMUNP+yl/uT3lGH9T/Zp +tnHSlrTxnTWXCl0g91KEeCaEnDDhLxm4aCv1Ag4B/yvcM4u34qkmaNLy2LiAxiqd +obZcNG61Pqwqd5IDkp38LBsn8tmb12xu9NalpUfOiSEB1cyCr4zFZMrm0wtdyJQV +AAAAFQCu+iNkqf/YOAYjYrHSCHFmWAfEYQAAAIAOVJ434UAR3Hn6lA5nWNfFOuUV +H3W7nJaP0FQJiIPx7GUbdxO9qtDNTbWkWL3c9qx5+B7Ole4xM7cvyXPrNQUYDHCF +lS+Ue2x3IeJrkdfZkH9ePP25y5A0J4/c+8XXvQaj4zA5nfw13oy5Ptyd7d3Kq5tE +DM8KiVdIhwkXjUA3PQAAAIEAm8IGZQatS7M6AfNITNWG4TI7Z2aRQjLb9/MWJIID +7c/VQ4zdTZdG3kpk0Gj9n4xreopK5NmYAdj8rtFfPBgmXltsLqt+bBcXkpxW//7W +C29WOXW3t90ySTh+cWuWfr9fV7mf4Ql/6u/ZIgpQNvnNYezazt3fK8EXjI1dAXEu +QxE= +Key-Derivation: Argon2id +Argon2-Memory: 8192 +Argon2-Passes: 8 +Argon2-Parallelism: 1 +Argon2-Salt: 310d916da49faba22ba8d2745777e5c5 +Private-Lines: 1 +xMm0Tg+o7Yq6lAs6L33y2fy3fiDPl6p71iKxm8OAgj4= +Private-MAC: fc2aef48bf90b80b97d06c32c37491db614331c2551ab37865d8719ee6cb5f4f diff --git a/test/Data/Key.PuTTY3.DSA.ppk b/test/Data/Key.PuTTY3.DSA.ppk new file mode 100644 index 000000000..abecad674 --- /dev/null +++ b/test/Data/Key.PuTTY3.DSA.ppk @@ -0,0 +1,17 @@ +PuTTY-User-Key-File-3: ssh-dss +Encryption: none +Comment: imported-openssh-key +Public-Lines: 10 +AAAAB3NzaC1kc3MAAACBALVl3fae2O4qwsAK95SUShX0KMUNP+yl/uT3lGH9T/Zp +tnHSlrTxnTWXCl0g91KEeCaEnDDhLxm4aCv1Ag4B/yvcM4u34qkmaNLy2LiAxiqd +obZcNG61Pqwqd5IDkp38LBsn8tmb12xu9NalpUfOiSEB1cyCr4zFZMrm0wtdyJQV +AAAAFQCu+iNkqf/YOAYjYrHSCHFmWAfEYQAAAIAOVJ434UAR3Hn6lA5nWNfFOuUV +H3W7nJaP0FQJiIPx7GUbdxO9qtDNTbWkWL3c9qx5+B7Ole4xM7cvyXPrNQUYDHCF +lS+Ue2x3IeJrkdfZkH9ePP25y5A0J4/c+8XXvQaj4zA5nfw13oy5Ptyd7d3Kq5tE +DM8KiVdIhwkXjUA3PQAAAIEAm8IGZQatS7M6AfNITNWG4TI7Z2aRQjLb9/MWJIID +7c/VQ4zdTZdG3kpk0Gj9n4xreopK5NmYAdj8rtFfPBgmXltsLqt+bBcXkpxW//7W +C29WOXW3t90ySTh+cWuWfr9fV7mf4Ql/6u/ZIgpQNvnNYezazt3fK8EXjI1dAXEu +QxE= +Private-Lines: 1 +AAAAFBhGOzk+Aimeob964E8+HsQNlyde +Private-MAC: 6c517ac5ede72c006b0115dd9d0830c8e699a1f4d72c708d41f68b6263d974ae diff --git a/test/Data/Key.PuTTY3.ECDSA.Encrypted.Argon2id.12345.ppk b/test/Data/Key.PuTTY3.ECDSA.Encrypted.Argon2id.12345.ppk new file mode 100644 index 000000000..1ceb26f3f --- /dev/null +++ b/test/Data/Key.PuTTY3.ECDSA.Encrypted.Argon2id.12345.ppk @@ -0,0 +1,15 @@ +PuTTY-User-Key-File-3: ecdsa-sha2-nistp256 +Encryption: aes256-cbc +Comment: imported-openssh-key +Public-Lines: 3 +AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEA+TDv5/cqk +g07M8M1aQKS8eUkBXnBOWXw5IMalXR0HnJtQQD6M2eHihjYSp+9oU+/Zi5afR11/ +qDRHLlU/Nx8= +Key-Derivation: Argon2id +Argon2-Memory: 8192 +Argon2-Passes: 8 +Argon2-Parallelism: 1 +Argon2-Salt: d496ed94c393d7c5df6ecd10440210ba +Private-Lines: 1 +7gcJBC98J9hOJVkp0rUJrx8vlWIklCf+/7iQZT+1pCyhaWs+5jm/dIw8aCGMhM0a +Private-MAC: 916979ee4696a5ee7674816259b2ac32c5258eaec94c554a9bc8d090dbc45b50 diff --git a/test/Data/Key.PuTTY3.ECDSA.ppk b/test/Data/Key.PuTTY3.ECDSA.ppk new file mode 100644 index 000000000..1cee78214 --- /dev/null +++ b/test/Data/Key.PuTTY3.ECDSA.ppk @@ -0,0 +1,10 @@ +PuTTY-User-Key-File-3: ecdsa-sha2-nistp256 +Encryption: none +Comment: imported-openssh-key +Public-Lines: 3 +AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEA+TDv5/cqk +g07M8M1aQKS8eUkBXnBOWXw5IMalXR0HnJtQQD6M2eHihjYSp+9oU+/Zi5afR11/ +qDRHLlU/Nx8= +Private-Lines: 1 +AAAAIEdqaFKgJBIibVjyUh1v7Y35LwIQJrocdTaYFLwl7iB0 +Private-MAC: 3df7a232c8e3021a8f9809968b748c9db46ad3c668fa7d3f4d87ec8cbd838522 diff --git a/test/Data/Key.PuTTY3.Ed25519.Encrypted.Argon2d.12345.ppk b/test/Data/Key.PuTTY3.Ed25519.Encrypted.Argon2d.12345.ppk new file mode 100644 index 000000000..d48923d9e --- /dev/null +++ b/test/Data/Key.PuTTY3.Ed25519.Encrypted.Argon2d.12345.ppk @@ -0,0 +1,14 @@ +PuTTY-User-Key-File-3: ssh-ed25519 +Encryption: aes256-cbc +Comment: Key.OPENSSH.ED25519 +Public-Lines: 2 +AAAAC3NzaC1lZDI1NTE5AAAAIA0JZnDQrxQZcNALfZYG7LPAW1MYEGvVW5nje7Ol +MGMi +Key-Derivation: Argon2d +Argon2-Memory: 8192 +Argon2-Passes: 8 +Argon2-Parallelism: 1 +Argon2-Salt: a937df822b7304499c58f1795e90376a +Private-Lines: 1 +EjcQkrXqEA3uJb/TfLt8MM+Gt1pM80S5fn5AlUWiUdhvrCG7pTTSVpaYYP8svW89 +Private-MAC: 673bd514209afa19d14506300f236a6049a915e7c7ec1a67d8df464e948f2654 diff --git a/test/Data/Key.PuTTY3.Ed25519.Encrypted.Argon2i.12345.ppk b/test/Data/Key.PuTTY3.Ed25519.Encrypted.Argon2i.12345.ppk new file mode 100644 index 000000000..85e539757 --- /dev/null +++ b/test/Data/Key.PuTTY3.Ed25519.Encrypted.Argon2i.12345.ppk @@ -0,0 +1,14 @@ +PuTTY-User-Key-File-3: ssh-ed25519 +Encryption: aes256-cbc +Comment: Key.OPENSSH.ED25519 +Public-Lines: 2 +AAAAC3NzaC1lZDI1NTE5AAAAIA0JZnDQrxQZcNALfZYG7LPAW1MYEGvVW5nje7Ol +MGMi +Key-Derivation: Argon2i +Argon2-Memory: 8192 +Argon2-Passes: 8 +Argon2-Parallelism: 1 +Argon2-Salt: bf99b3e5c7d28d566c8c759d8a1a41d2 +Private-Lines: 1 +tbSkuSPLzdnnQuGYnC/L1fv9UnFzCBkwtk5aLDZYJFiD08h5EmpGCfJJHe0lJx9/ +Private-MAC: eee47ba955ba32fc85e3eb7a70d7e5f0359eacbf9f1abd5f02904fa98db513b7 diff --git a/test/Data/Key.PuTTY3.Ed25519.Encrypted.Argon2id.12345.ppk b/test/Data/Key.PuTTY3.Ed25519.Encrypted.Argon2id.12345.ppk new file mode 100644 index 000000000..a7b05be4c --- /dev/null +++ b/test/Data/Key.PuTTY3.Ed25519.Encrypted.Argon2id.12345.ppk @@ -0,0 +1,14 @@ +PuTTY-User-Key-File-3: ssh-ed25519 +Encryption: aes256-cbc +Comment: Key.OPENSSH.ED25519 +Public-Lines: 2 +AAAAC3NzaC1lZDI1NTE5AAAAIA0JZnDQrxQZcNALfZYG7LPAW1MYEGvVW5nje7Ol +MGMi +Key-Derivation: Argon2id +Argon2-Memory: 8192 +Argon2-Passes: 21 +Argon2-Parallelism: 1 +Argon2-Salt: a8bb359d93fc58732e595da9c80b2e88 +Private-Lines: 1 +GHfMNhx9V3k/OKJxNb6QJ93Gwg9/slksETZ0Ns0edelE6hJfUc+XhXnQBLkUHZ6H +Private-MAC: b07d3ddb544a7396637bc607bedd3d14ed28db929f2bdb5af4a94b410c3d9feb diff --git a/test/Data/Key.PuTTY3.Ed25519.ppk b/test/Data/Key.PuTTY3.Ed25519.ppk new file mode 100644 index 000000000..74a330225 --- /dev/null +++ b/test/Data/Key.PuTTY3.Ed25519.ppk @@ -0,0 +1,9 @@ +PuTTY-User-Key-File-3: ssh-ed25519 +Encryption: none +Comment: Key.OPENSSH.ED25519 +Public-Lines: 2 +AAAAC3NzaC1lZDI1NTE5AAAAIA0JZnDQrxQZcNALfZYG7LPAW1MYEGvVW5nje7Ol +MGMi +Private-Lines: 1 +AAAAIADMEXUw9TGuz7JykmHbzPOj8XebpZwo76iuxJtHkvAp +Private-MAC: 273204de25dcb0fc8835d6d08b8045be34358bf64d009a7566d97851449fb9ab diff --git a/test/Data/Key.PuTTY3.RSA.Encrypted.Argon2id.12345.ppk b/test/Data/Key.PuTTY3.RSA.Encrypted.Argon2id.12345.ppk new file mode 100644 index 000000000..5966f7187 --- /dev/null +++ b/test/Data/Key.PuTTY3.RSA.Encrypted.Argon2id.12345.ppk @@ -0,0 +1,31 @@ +PuTTY-User-Key-File-3: ssh-rsa +Encryption: aes256-cbc +Comment: Key.OPENSSH.RSA +Public-Lines: 6 +AAAAB3NzaC1yc2EAAAADAQABAAABAQDtbs6KCLsePWaxraXweKYs/NqBWYT8Kx4w +oJHE8xO1ZO+hl0y3uF+S2FYDuHbRruhJJ4fa3sWp46lU0YVi9FXcFVawpkkxFx0m +JMJkCMffytiT3Re9neYqso3/d9xCyHg6I+dapPodKqDXiiJXxQ+1TCcTrmyRZLG/ +G34QuVWkKobm8TY78Y0MpATsXNi3q9CKEwVIAEGqO9q7SaNfTTYpiIIyvq+CXxdi +QMDifn4nJBJDHOed+sv3dmhqq6NE/ZtPlSFeBvOvwcXC6pAa9REQJlNMjwGK//q0 +4if3HaERo3q/EMu1dz30TZ3o1bpx2uLBoYUniOBVYMTmZTTTpd09 +Key-Derivation: Argon2id +Argon2-Memory: 8192 +Argon2-Passes: 13 +Argon2-Parallelism: 1 +Argon2-Salt: 433d83be99e63cd358ef5df037448734 +Private-Lines: 14 +q3I+FoI3y6nzGIi1lIXMIx4J/yOlIYIqoHQbpQxFiUwoDxYYm8zy4BYexEw/Ox6n +LaKo4LKyvh36+5lrjqMluWsJ+PXrA+GBxSXsanzWG42RPRNbNF7RQCqTrxDRgTSv +GztaXcS5URSIutOl5vePib/9lFC5FlYRp+dCM0CVRpkUezgbZdnOQ5901DhylceT +fUXzpUcEHI4i1kp9awG+4ZpfJf057HuHFhmIvNECirt//LKBH9vrr7NQ7t1Sv8+r +NdzkcUHpCyvPLqII/zaQurgFlrYTCIyqzKHphoVZGa2XrB6VHgt/pvh+a0fOw/lB +c3t377cOr1PTht7AhYoeUPheFaYE9bPyZ5pV0Ai5IlOD94blp94BWU56e8GNx8Gp +SB0zCqrW80Q39OXb3hweyIcVEbb8dfY6sJf3PhzEL1pehUzOFSRlwjGl7BsYZCe3 +uIUyNzczpbvk6OTReGRHM7DX5xz3t/esC7VXP7CpOnjhdr9dZ6dpqGz/vDciyxV9 +Jw/gaipPAQXUf6ls3atooxN1IpLnwtPE1FHAzofV9ixW4TqHmv1H9++94VJKFyA0 +nPUVBtKhM8gT/4ZxnPEDc/sl/EYb9DmuaUC8cTV2O10Wpnj3HhS1Qh43qAguyOu/ +qF7QGETSweXTYSgresMxeLRkX/tia6tz2Rfk1HnP8sRbMyIOyOvU4pNMDuTelGy9 +7OfsS54bcBIM1DTpMZ7HiKctZ6DvjL4VtgIJPlXmc+s4CD6cjbycOHo5iQfjsoS4 +Wh2dHGK3bYVJ+LTA/Z63nasR9CLW26oL76CI/+TXvheyFotzIjtn3iKpJUqS7Dc0 +vpZT3x5OWxmYlu1xZYbzMkLIedE8WaKYjeb3qE3Ox+rN0xIYXoNiCi6jxU+viuF4 +Private-MAC: 3bf7e6fe566fab36eba63d3129929b197f6b07e234ebcf22549d10b1d97e3e2f diff --git a/test/Data/Key.PuTTY3.RSA.ppk b/test/Data/Key.PuTTY3.RSA.ppk new file mode 100644 index 000000000..c938b2dd3 --- /dev/null +++ b/test/Data/Key.PuTTY3.RSA.ppk @@ -0,0 +1,26 @@ +PuTTY-User-Key-File-3: ssh-rsa +Encryption: none +Comment: Key.OPENSSH.RSA +Public-Lines: 6 +AAAAB3NzaC1yc2EAAAADAQABAAABAQDtbs6KCLsePWaxraXweKYs/NqBWYT8Kx4w +oJHE8xO1ZO+hl0y3uF+S2FYDuHbRruhJJ4fa3sWp46lU0YVi9FXcFVawpkkxFx0m +JMJkCMffytiT3Re9neYqso3/d9xCyHg6I+dapPodKqDXiiJXxQ+1TCcTrmyRZLG/ +G34QuVWkKobm8TY78Y0MpATsXNi3q9CKEwVIAEGqO9q7SaNfTTYpiIIyvq+CXxdi +QMDifn4nJBJDHOed+sv3dmhqq6NE/ZtPlSFeBvOvwcXC6pAa9REQJlNMjwGK//q0 +4if3HaERo3q/EMu1dz30TZ3o1bpx2uLBoYUniOBVYMTmZTTTpd09 +Private-Lines: 14 +AAABAQDpeCr6CmnM632eu2zPkCN/W0eVJ6yftdpi4JFWA9veY5lK4RbcFR1NrRKv +Z+TWfNIGlSt+qc3eJ3IraDdsPWxsFEOBQpH4Bo1wI3dOnF/GDJV4mFAu8SQR2i/N +BFR/CtdF/GYTeOREZ9Vu/HKWsbynfnFyZfJ16XjqvaLx2PyAhje0qnREy9nhmU1u +FYc93k7HIdYv17eBs5LIjKNCBMpl7OHMStL9f8on9dirPIECo2pnZGDWQqIdGUdL +ooQja3IXBh+H5Fvov3FyHVKo61CFNaKubFLbl2kYPaOBqVd7KLDw+a6pOJYKpSZQ +zHox0Xe0WyKuvngrhAD2Sox5pEu1AAAAgQD+dPDqesFjwMJ9SXwWbqkLY3H5yXje +DZGEAXcm59L1buVHcqkkC2vIZQM0ToQPqib65bGYDPYfAsi08ropvJYpGR6HMDtd +8wU3VWkPHNpSb39rl0yFzWR7HkuyE5HwYjtYUgeM/EQ5Dq9+Zhn3W8iSBQMBWReF +7PFp0BfrxxGnawAAAIEA7t9vXgsFRX/YNMzR32bt9adFrRK3LEb+e36vlKD7aL/J +8VBe9aDlnuSkhpxrTCAiN9ZAbT4VG73zprqja4CQY4I2z0JotMUgBOS90LhCkTY5 +WhN/1mnSgcM4SQ7WrrmJNYn5K3QFaeu18kOabsrhoFWkATT268QPYNSG8ni+P/cA +AACBALFEE9FIau5dLoE3eGPfPWx+nltH6Jdtf5uwec5CUHqTWnVD07NfPLr7+Ip1 +vJ9jt0Qmp11h2XwidQLEfzBBFtgukA7b6ilx2831kJQmElcQdewo1ESmvHzWiAJP +fM4JjTcDudzQZXsq1IT4L5t8bewAoKc12OUcDSS/P2tFjpoM +Private-MAC: ef76b1cf66a4a28d6fe08c70012c4bfa61771502e496d227dd77580650d20bfd diff --git a/test/Renci.SshNet.Tests/Classes/PrivateKeyFileTest.cs b/test/Renci.SshNet.Tests/Classes/PrivateKeyFileTest.cs index 1e5b5f7f9..d28ba0a06 100644 --- a/test/Renci.SshNet.Tests/Classes/PrivateKeyFileTest.cs +++ b/test/Renci.SshNet.Tests/Classes/PrivateKeyFileTest.cs @@ -344,6 +344,20 @@ public void ConstructorWithFileNameAndPassPhraseShouldBeAbleToReadFileThatIsShar [DataRow("Key.OPENSSH.RSA.Encrypted.Aes.192.CTR.txt", "12345", typeof(RsaKey))] [DataRow("Key.OPENSSH.RSA.Encrypted.txt", "12345", typeof(RsaKey))] [DataRow("Key.OPENSSH.RSA.txt", null, typeof(RsaKey))] + [DataRow("Key.PuTTY2.Ed25519.Encrypted.12345.ppk", "12345", typeof(ED25519Key))] + [DataRow("Key.PuTTY2.Ed25519.ppk", null, typeof(ED25519Key))] + [DataRow("Key.PuTTY2.RSA.Encrypted.12345.ppk", "12345", typeof(RsaKey))] + [DataRow("Key.PuTTY2.RSA.ppk", null, typeof(RsaKey))] + [DataRow("Key.PuTTY3.DSA.Encrypted.Argon2id.12345.ppk", "12345", typeof(DsaKey))] + [DataRow("Key.PuTTY3.DSA.ppk", null, typeof(DsaKey))] + [DataRow("Key.PuTTY3.ECDSA.Encrypted.Argon2id.12345.ppk", "12345", typeof(EcdsaKey))] + [DataRow("Key.PuTTY3.ECDSA.ppk", null, typeof(EcdsaKey))] + [DataRow("Key.PuTTY3.Ed25519.Encrypted.Argon2i.12345.ppk", "12345", typeof(ED25519Key))] + [DataRow("Key.PuTTY3.Ed25519.Encrypted.Argon2d.12345.ppk", "12345", typeof(ED25519Key))] + [DataRow("Key.PuTTY3.Ed25519.Encrypted.Argon2id.12345.ppk", "12345", typeof(ED25519Key))] + [DataRow("Key.PuTTY3.Ed25519.ppk", null, typeof(ED25519Key))] + [DataRow("Key.PuTTY3.RSA.Encrypted.Argon2id.12345.ppk", "12345", typeof(RsaKey))] + [DataRow("Key.PuTTY3.RSA.ppk", null, typeof(RsaKey))] [DataRow("Key.RSA.Encrypted.Aes.128.CBC.12345.txt", "12345", typeof(RsaKey))] [DataRow("Key.RSA.Encrypted.Aes.192.CBC.12345.txt", "12345", typeof(RsaKey))] [DataRow("Key.RSA.Encrypted.Aes.256.CBC.12345.txt", "12345", typeof(RsaKey))] @@ -523,6 +537,86 @@ public void Test_LowercaseSalt() } } + [TestMethod] + public void PuTTYv2_InvalidMac_ThrowsSshException() + { + string pk = """ + PuTTY-User-Key-File-2: ssh-rsa + Encryption: none + Comment: Key.OPENSSH.RSA + Public-Lines: 6 + AAAAB3NzaC1yc2EAAAADAQABAAABAQDtbs6KCLsePWaxraXweKYs/NqBWYT8Kx4w + oJHE8xO1ZO+hl0y3uF+S2FYDuHbRruhJJ4fa3sWp46lU0YVi9FXcFVawpkkxFx0m + JMJkCMffytiT3Re9neYqso3/d9xCyHg6I+dapPodKqDXiiJXxQ+1TCcTrmyRZLG/ + G34QuVWkKobm8TY78Y0MpATsXNi3q9CKEwVIAEGqO9q7SaNfTTYpiIIyvq+CXxdi + QMDifn4nJBJDHOed+sv3dmhqq6NE/ZtPlSFeBvOvwcXC6pAa9REQJlNMjwGK//q0 + 4if3HaERo3q/EMu1dz30TZ3o1bpx2uLBoYUniOBVYMTmZTTTpd09 + Private-Lines: 14 + AAABAQDpeCr6CmnM632eu2zPkCN/W0eVJ6yftdpi4JFWA9veY5lK4RbcFR1NrRKv + Z+TWfNIGlSt+qc3eJ3IraDdsPWxsFEOBQpH4Bo1wI3dOnF/GDJV4mFAu8SQR2i/N + BFR/CtdF/GYTeOREZ9Vu/HKWsbynfnFyZfJ16XjqvaLx2PyAhje0qnREy9nhmU1u + FYc93k7HIdYv17eBs5LIjKNCBMpl7OHMStL9f8on9dirPIECo2pnZGDWQqIdGUdL + ooQja3IXBh+H5Fvov3FyHVKo61CFNaKubFLbl2kYPaOBqVd7KLDw+a6pOJYKpSZQ + zHox0Xe0WyKuvngrhAD2Sox5pEu1AAAAgQD+dPDqesFjwMJ9SXwWbqkLY3H5yXje + DZGEAXcm59L1buVHcqkkC2vIZQM0ToQPqib65bGYDPYfAsi08ropvJYpGR6HMDtd + 8wU3VWkPHNpSb39rl0yFzWR7HkuyE5HwYjtYUgeM/EQ5Dq9+Zhn3W8iSBQMBWReF + 7PFp0BfrxxGnawAAAIEA7t9vXgsFRX/YNMzR32bt9adFrRK3LEb+e36vlKD7aL/J + 8VBe9aDlnuSkhpxrTCAiN9ZAbT4VG73zprqja4CQY4I2z0JotMUgBOS90LhCkTY5 + WhN/1mnSgcM4SQ7WrrmJNYn5K3QFaeu18kOabsrhoFWkATT268QPYNSG8ni+P/cA + AACBALFEE9FIau5dLoE3eGPfPWx+nltH6Jdtf5uwec5CUHqTWnVD07NfPLr7+Ip1 + vJ9jt0Qmp11h2XwidQLEfzBBFtgukA7b6ilx2831kJQmElcQdewo1ESmvHzWiAJP + fM4JjTcDudzQZXsq1IT4L5t8bewAoKc12OUcDSS/P2tFjpoM + Private-MAC: 7f487d19cb5d03257c9b9a2aaaaaaaaaaaaaaaaa + """; + + using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(pk))) + { + var ex = Assert.ThrowsException(() => new PrivateKeyFile(stream)); + + Assert.AreEqual("MAC verification failed for PuTTY key file", ex.Message); + } + } + + [TestMethod] + public void PuTTYv3_InvalidMac_ThrowsSshException() + { + string pk = """ + PuTTY-User-Key-File-3: ssh-rsa + Encryption: none + Comment: Key.OPENSSH.RSA + Public-Lines: 6 + AAAAB3NzaC1yc2EAAAADAQABAAABAQDtbs6KCLsePWaxraXweKYs/NqBWYT8Kx4w + oJHE8xO1ZO+hl0y3uF+S2FYDuHbRruhJJ4fa3sWp46lU0YVi9FXcFVawpkkxFx0m + JMJkCMffytiT3Re9neYqso3/d9xCyHg6I+dapPodKqDXiiJXxQ+1TCcTrmyRZLG/ + G34QuVWkKobm8TY78Y0MpATsXNi3q9CKEwVIAEGqO9q7SaNfTTYpiIIyvq+CXxdi + QMDifn4nJBJDHOed+sv3dmhqq6NE/ZtPlSFeBvOvwcXC6pAa9REQJlNMjwGK//q0 + 4if3HaERo3q/EMu1dz30TZ3o1bpx2uLBoYUniOBVYMTmZTTTpd09 + Private-Lines: 14 + AAABAQDpeCr6CmnM632eu2zPkCN/W0eVJ6yftdpi4JFWA9veY5lK4RbcFR1NrRKv + Z+TWfNIGlSt+qc3eJ3IraDdsPWxsFEOBQpH4Bo1wI3dOnF/GDJV4mFAu8SQR2i/N + BFR/CtdF/GYTeOREZ9Vu/HKWsbynfnFyZfJ16XjqvaLx2PyAhje0qnREy9nhmU1u + FYc93k7HIdYv17eBs5LIjKNCBMpl7OHMStL9f8on9dirPIECo2pnZGDWQqIdGUdL + ooQja3IXBh+H5Fvov3FyHVKo61CFNaKubFLbl2kYPaOBqVd7KLDw+a6pOJYKpSZQ + zHox0Xe0WyKuvngrhAD2Sox5pEu1AAAAgQD+dPDqesFjwMJ9SXwWbqkLY3H5yXje + DZGEAXcm59L1buVHcqkkC2vIZQM0ToQPqib65bGYDPYfAsi08ropvJYpGR6HMDtd + 8wU3VWkPHNpSb39rl0yFzWR7HkuyE5HwYjtYUgeM/EQ5Dq9+Zhn3W8iSBQMBWReF + 7PFp0BfrxxGnawAAAIEA7t9vXgsFRX/YNMzR32bt9adFrRK3LEb+e36vlKD7aL/J + 8VBe9aDlnuSkhpxrTCAiN9ZAbT4VG73zprqja4CQY4I2z0JotMUgBOS90LhCkTY5 + WhN/1mnSgcM4SQ7WrrmJNYn5K3QFaeu18kOabsrhoFWkATT268QPYNSG8ni+P/cA + AACBALFEE9FIau5dLoE3eGPfPWx+nltH6Jdtf5uwec5CUHqTWnVD07NfPLr7+Ip1 + vJ9jt0Qmp11h2XwidQLEfzBBFtgukA7b6ilx2831kJQmElcQdewo1ESmvHzWiAJP + fM4JjTcDudzQZXsq1IT4L5t8bewAoKc12OUcDSS/P2tFjpoM + Private-MAC: ef76b1cf66a4a28d6fe08c70012c4bfa61771502e496d227dddddddddddddddd + """; + + using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(pk))) + { + var ex = Assert.ThrowsException(() => new PrivateKeyFile(stream)); + + Assert.AreEqual("MAC verification failed for PuTTY key file", ex.Message); + } + } + private void SaveStreamToFile(Stream stream, string fileName) { var buffer = new byte[4000];