diff --git a/README.md b/README.md index 8c4d8ece5..b3cc4ec1d 100644 --- a/README.md +++ b/README.md @@ -101,7 +101,7 @@ The main types provided by this library are: * ECDSA 256/384/521 in OpenSSL PEM format ("BEGIN EC PRIVATE KEY") * ECDSA 256/384/521, ED25519 and RSA in OpenSSH key format ("BEGIN OPENSSH PRIVATE KEY") -Private keys can be encrypted using one of the following cipher methods: +Private keys in OpenSSL PEM and ssh.com format can be encrypted using one of the following cipher methods: * DES-EDE3-CBC * DES-EDE3-CFB * DES-CBC @@ -109,6 +109,18 @@ Private keys can be encrypted using one of the following cipher methods: * AES-192-CBC * AES-256-CBC +Private keys in OpenSSH key format can be encrypted using one of the following cipher methods: +* 3des-cbc +* aes128-cbc +* aes192-cbc +* aes256-cbc +* aes128-ctr +* aes192-ctr +* aes256-ctr +* aes128-gcm@openssh.com +* aes256-gcm@openssh.com +* chacha20-poly1305@openssh.com + ## Host Key Algorithms **SSH.NET** supports the following host key algorithms: diff --git a/src/Renci.SshNet/ConnectionInfo.cs b/src/Renci.SshNet/ConnectionInfo.cs index 51279f4a4..5ced89984 100644 --- a/src/Renci.SshNet/ConnectionInfo.cs +++ b/src/Renci.SshNet/ConnectionInfo.cs @@ -386,9 +386,9 @@ public ConnectionInfo(string host, int port, string username, ProxyTypes proxyTy { "aes128-ctr", new CipherInfo(128, (key, iv) => new AesCipher(key, iv, AesCipherMode.CTR, pkcs7Padding: false)) }, { "aes192-ctr", new CipherInfo(192, (key, iv) => new AesCipher(key, iv, AesCipherMode.CTR, pkcs7Padding: false)) }, { "aes256-ctr", new CipherInfo(256, (key, iv) => new AesCipher(key, iv, AesCipherMode.CTR, pkcs7Padding: false)) }, - { "aes128-gcm@openssh.com", new CipherInfo(128, (key, iv) => new AesGcmCipher(key, iv), isAead: true) }, - { "aes256-gcm@openssh.com", new CipherInfo(256, (key, iv) => new AesGcmCipher(key, iv), isAead: true) }, - { "chacha20-poly1305@openssh.com", new CipherInfo(512, (key, iv) => new ChaCha20Poly1305Cipher(key), isAead: true) }, + { "aes128-gcm@openssh.com", new CipherInfo(128, (key, iv) => new AesGcmCipher(key, iv, aadLength: 4), isAead: true) }, + { "aes256-gcm@openssh.com", new CipherInfo(256, (key, iv) => new AesGcmCipher(key, iv, aadLength: 4), isAead: true) }, + { "chacha20-poly1305@openssh.com", new CipherInfo(512, (key, iv) => new ChaCha20Poly1305Cipher(key, aadLength: 4), isAead: true) }, { "aes128-cbc", new CipherInfo(128, (key, iv) => new AesCipher(key, iv, AesCipherMode.CBC, pkcs7Padding: false)) }, { "aes192-cbc", new CipherInfo(192, (key, iv) => new AesCipher(key, iv, AesCipherMode.CBC, pkcs7Padding: false)) }, { "aes256-cbc", new CipherInfo(256, (key, iv) => new AesCipher(key, iv, AesCipherMode.CBC, pkcs7Padding: false)) }, diff --git a/src/Renci.SshNet/PrivateKeyFile.cs b/src/Renci.SshNet/PrivateKeyFile.cs index fbfd65408..bdc9a4c91 100644 --- a/src/Renci.SshNet/PrivateKeyFile.cs +++ b/src/Renci.SshNet/PrivateKeyFile.cs @@ -39,7 +39,7 @@ namespace Renci.SshNet /// /// /// - /// The following encryption algorithms are supported: + /// The following encryption algorithms are supported for OpenSSL PEM and ssh.com format: /// /// /// DES-EDE3-CBC @@ -60,6 +60,39 @@ namespace Renci.SshNet /// AES-256-CBC /// /// + /// The following encryption algorithms are supported for OpenSSH format: + /// + /// + /// 3des-cbc + /// + /// + /// aes128-cbc + /// + /// + /// aes192-cbc + /// + /// + /// aes256-cbc + /// + /// + /// aes128-ctr + /// + /// + /// aes192-ctr + /// + /// + /// aes256-ctr + /// + /// + /// aes128-gcm@openssh.com + /// + /// + /// aes256-gcm@openssh.com + /// + /// + /// chacha20-poly1305@openssh.com + /// + /// /// /// public partial class PrivateKeyFile : IPrivateKeySource, IDisposable @@ -450,7 +483,17 @@ private static byte[] DecryptKey(CipherInfo cipherInfo, byte[] cipherData, strin var cipher = cipherInfo.Cipher(cipherKey.ToArray(), binarySalt); - return cipher.Decrypt(cipherData); + try + { + return cipher.Decrypt(cipherData); + } + finally + { + if (cipher is IDisposable disposable) + { + disposable.Dispose(); + } + } } /// @@ -474,7 +517,7 @@ private static Key ParseOpenSshV1Key(byte[] keyFileData, string passPhrase) throw new SshException("This openssh key does not contain the 'openssh-key-v1' format magic header"); } - // cipher will be "aes256-cbc" if using a passphrase, "none" otherwise + // cipher will be "aes256-cbc" or other cipher if using a passphrase, "none" otherwise var cipherName = keyReader.ReadString(Encoding.UTF8); // key derivation function (kdf): bcrypt or nothing @@ -503,7 +546,7 @@ private static Key ParseOpenSshV1Key(byte[] keyFileData, string passPhrase) // possibly encrypted private key var privateKeyLength = (int)keyReader.ReadUInt32(); - var privateKeyBytes = keyReader.ReadBytes(privateKeyLength); + byte[] privateKeyBytes; // decrypt private key if necessary if (cipherName != "none") @@ -518,38 +561,76 @@ private static Key ParseOpenSshV1Key(byte[] keyFileData, string passPhrase) throw new SshException("kdf " + kdfName + " is not supported for openssh key file"); } - // inspired by the SSHj library (https://github.com/hierynomus/sshj) - // apply the kdf to derive a key and iv from the passphrase - var passPhraseBytes = Encoding.UTF8.GetBytes(passPhrase); - var keyiv = new byte[48]; - new BCrypt().Pbkdf(passPhraseBytes, salt, rounds, keyiv); - var key = new byte[32]; - Array.Copy(keyiv, 0, key, 0, 32); - var iv = new byte[16]; - Array.Copy(keyiv, 32, iv, 0, 16); - - AesCipher cipher; + var ivLength = 16; + CipherInfo cipherInfo; switch (cipherName) { + case "3des-cbc": + ivLength = 8; + cipherInfo = new CipherInfo(192, (key, iv) => new TripleDesCipher(key, new CbcCipherMode(iv), padding: null)); + break; + case "aes128-cbc": + cipherInfo = new CipherInfo(128, (key, iv) => new AesCipher(key, iv, AesCipherMode.CBC, pkcs7Padding: false)); + break; + case "aes192-cbc": + cipherInfo = new CipherInfo(192, (key, iv) => new AesCipher(key, iv, AesCipherMode.CBC, pkcs7Padding: false)); + break; case "aes256-cbc": - cipher = new AesCipher(key, iv, AesCipherMode.CBC, pkcs7Padding: false); + cipherInfo = new CipherInfo(256, (key, iv) => new AesCipher(key, iv, AesCipherMode.CBC, pkcs7Padding: false)); + break; + case "aes128-ctr": + cipherInfo = new CipherInfo(128, (key, iv) => new AesCipher(key, iv, AesCipherMode.CTR, pkcs7Padding: false)); + break; + case "aes192-ctr": + cipherInfo = new CipherInfo(192, (key, iv) => new AesCipher(key, iv, AesCipherMode.CTR, pkcs7Padding: false)); break; case "aes256-ctr": - cipher = new AesCipher(key, iv, AesCipherMode.CTR, pkcs7Padding: false); + cipherInfo = new CipherInfo(256, (key, iv) => new AesCipher(key, iv, AesCipherMode.CTR, pkcs7Padding: false)); + break; + case "aes128-gcm@openssh.com": + cipherInfo = new CipherInfo(128, (key, iv) => new AesGcmCipher(key, iv, aadLength: 0), isAead: true); + break; + case "aes256-gcm@openssh.com": + cipherInfo = new CipherInfo(256, (key, iv) => new AesGcmCipher(key, iv, aadLength: 0), isAead: true); + break; + case "chacha20-poly1305@openssh.com": + ivLength = 12; + cipherInfo = new CipherInfo(256, (key, iv) => new ChaCha20Poly1305Cipher(key, aadLength: 0), isAead: true); break; default: throw new SshException("Cipher '" + cipherName + "' is not supported for an OpenSSH key."); } + var keyLength = cipherInfo.KeySize / 8; + + // inspired by the SSHj library (https://github.com/hierynomus/sshj) + // apply the kdf to derive a key and iv from the passphrase + var passPhraseBytes = Encoding.UTF8.GetBytes(passPhrase); + var keyiv = new byte[keyLength + ivLength]; + new BCrypt().Pbkdf(passPhraseBytes, salt, rounds, keyiv); + + var key = keyiv.Take(keyLength); + var iv = keyiv.Take(keyLength, ivLength); + + var cipher = cipherInfo.Cipher(key, iv); + var cipherData = keyReader.ReadBytes(privateKeyLength + cipher.TagSize); + try { - privateKeyBytes = cipher.Decrypt(privateKeyBytes); + privateKeyBytes = cipher.Decrypt(cipherData, 0, privateKeyLength); } finally { - cipher.Dispose(); + if (cipher is IDisposable disposable) + { + disposable.Dispose(); + } } } + else + { + privateKeyBytes = keyReader.ReadBytes(privateKeyLength); + } // validate private key length privateKeyLength = privateKeyBytes.Length; diff --git a/src/Renci.SshNet/Security/Cryptography/Ciphers/AesGcmCipher.cs b/src/Renci.SshNet/Security/Cryptography/Ciphers/AesGcmCipher.cs index fb0926c5c..b40c2343f 100644 --- a/src/Renci.SshNet/Security/Cryptography/Ciphers/AesGcmCipher.cs +++ b/src/Renci.SshNet/Security/Cryptography/Ciphers/AesGcmCipher.cs @@ -12,9 +12,9 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers /// internal sealed partial class AesGcmCipher : SymmetricCipher, IDisposable { - private const int PacketLengthFieldLength = 4; private const int TagSizeInBytes = 16; private readonly byte[] _iv; + private readonly int _aadLength; #if NET6_0_OR_GREATER private readonly Impl _impl; #else @@ -55,11 +55,13 @@ public override int TagSize /// /// The key. /// The IV. - public AesGcmCipher(byte[] key, byte[] iv) + /// The length of additional associated data. + public AesGcmCipher(byte[] key, byte[] iv, int aadLength) : base(key) { // SSH AES-GCM requires a 12-octet Initial IV _iv = iv.Take(12); + _aadLength = aadLength; #if NET6_0_OR_GREATER if (System.Security.Cryptography.AesGcm.IsSupported) { @@ -78,8 +80,7 @@ public AesGcmCipher(byte[] key, byte[] iv) /// /// The input data with below format: /// - /// [outbound sequence field][packet length field][padding length field sz][payload][random paddings] - /// [----4 bytes----(offset)][------4 bytes------][----------------Plain Text---------------(length)] + /// [----(offset)][----AAD----][----Plain Text----(length)] /// /// /// The zero-based offset in at which to begin encrypting. @@ -87,23 +88,22 @@ public AesGcmCipher(byte[] key, byte[] iv) /// /// The encrypted data with below format: /// - /// [packet length field][padding length field sz][payload][random paddings][Authenticated TAG] - /// [------4 bytes------][------------------Cipher Text--------------------][-------TAG-------] + /// [----AAD----][----Cipher Text----][----TAG----] /// /// public override byte[] Encrypt(byte[] input, int offset, int length) { var output = new byte[length + TagSize]; - Buffer.BlockCopy(input, offset, output, 0, PacketLengthFieldLength); + Buffer.BlockCopy(input, offset, output, 0, _aadLength); _impl.Encrypt( input, - plainTextOffset: offset + PacketLengthFieldLength, - plainTextLength: length - PacketLengthFieldLength, + plainTextOffset: offset + _aadLength, + plainTextLength: length - _aadLength, associatedDataOffset: offset, - associatedDataLength: PacketLengthFieldLength, + associatedDataLength: _aadLength, output, - cipherTextOffset: PacketLengthFieldLength); + cipherTextOffset: _aadLength); IncrementCounter(); @@ -116,8 +116,7 @@ public override byte[] Encrypt(byte[] input, int offset, int length) /// /// The input data with below format: /// - /// [inbound sequence field][packet length field][padding length field sz][payload][random paddings][Authenticated TAG] - /// [--------4 bytes-------][--4 bytes--(offset)][--------------Cipher Text----------------(length)][-------TAG-------] + /// [----][----AAD----(offset)][----Cipher Text----(length)][----TAG----] /// /// /// The zero-based offset in at which to begin decrypting and authenticating. @@ -125,13 +124,12 @@ public override byte[] Encrypt(byte[] input, int offset, int length) /// /// The decrypted data with below format: /// - /// [padding length field sz][payload][random paddings] - /// [--------------------Plain Text-------------------] + /// [----Plain Text----] /// /// public override byte[] Decrypt(byte[] input, int offset, int length) { - Debug.Assert(offset == 8, "The offset must be 8"); + Debug.Assert(offset >= _aadLength, "The offset must be greater than or equals to aad length"); var output = new byte[length]; @@ -139,8 +137,8 @@ public override byte[] Decrypt(byte[] input, int offset, int length) input, cipherTextOffset: offset, cipherTextLength: length, - associatedDataOffset: offset - PacketLengthFieldLength, - associatedDataLength: PacketLengthFieldLength, + associatedDataOffset: offset - _aadLength, + associatedDataLength: _aadLength, output, plainTextOffset: 0); diff --git a/src/Renci.SshNet/Security/Cryptography/Ciphers/ChaCha20Poly1305Cipher.cs b/src/Renci.SshNet/Security/Cryptography/Ciphers/ChaCha20Poly1305Cipher.cs index e67eafc2e..5f63660c2 100644 --- a/src/Renci.SshNet/Security/Cryptography/Ciphers/ChaCha20Poly1305Cipher.cs +++ b/src/Renci.SshNet/Security/Cryptography/Ciphers/ChaCha20Poly1305Cipher.cs @@ -18,9 +18,13 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers /// internal sealed class ChaCha20Poly1305Cipher : SymmetricCipher { - private readonly ChaCha7539Engine _aadCipher = new ChaCha7539Engine(); - private readonly ChaCha7539Engine _cipher = new ChaCha7539Engine(); - private readonly Poly1305 _mac = new Poly1305(); + private readonly byte[] _iv; + private readonly int _aadLength; + private readonly KeyParameter _aadKeyParameter; + private readonly KeyParameter _keyParameter; + private readonly ChaCha7539Engine _aadCipher; + private readonly ChaCha7539Engine _cipher; + private readonly Poly1305 _mac; /// /// Gets the minimun block size. @@ -51,9 +55,23 @@ public override int TagSize /// Initializes a new instance of the class. /// /// The key. - public ChaCha20Poly1305Cipher(byte[] key) + /// The length of additional associated data. + public ChaCha20Poly1305Cipher(byte[] key, int aadLength) : base(key) { + _iv = new byte[12]; + _aadLength = aadLength; + + _keyParameter = new KeyParameter(key, 0, 32); + _cipher = new ChaCha7539Engine(); + + if (aadLength > 0) + { + _aadKeyParameter = new KeyParameter(key, 32, 32); + _aadCipher = new ChaCha7539Engine(); + } + + _mac = new Poly1305(); } /// @@ -62,8 +80,7 @@ public ChaCha20Poly1305Cipher(byte[] key) /// /// The input data with below format: /// - /// [outbound sequence field][packet length field][padding length field sz][payload][random paddings] - /// [----4 bytes----(offset)][------4 bytes------][----------------Plain Text---------------(length)] + /// [----(offset)][----AAD----][----Plain Text----(length)] /// /// /// The zero-based offset in at which to begin encrypting. @@ -71,16 +88,22 @@ public ChaCha20Poly1305Cipher(byte[] key) /// /// The encrypted data with below format: /// - /// [packet length field][padding length field sz][payload][random paddings][Authenticated TAG] - /// [------4 bytes------][------------------Cipher Text--------------------][-------TAG-------] + /// [----Cipher AAD----][----Cipher Text----][----TAG----] /// /// public override byte[] Encrypt(byte[] input, int offset, int length) { + _aadCipher?.Init(forEncryption: true, new ParametersWithIV(_aadKeyParameter, _iv)); + _cipher.Init(forEncryption: true, new ParametersWithIV(_keyParameter, _iv)); + + var keyStream = new byte[64]; + _cipher.ProcessBytes(keyStream, 0, keyStream.Length, keyStream, 0); + _mac.Init(new KeyParameter(keyStream, 0, 32)); + var output = new byte[length + TagSize]; - _aadCipher.ProcessBytes(input, offset, 4, output, 0); - _cipher.ProcessBytes(input, offset + 4, length - 4, output, 4); + _aadCipher?.ProcessBytes(input, offset, _aadLength, output, 0); + _cipher.ProcessBytes(input, offset + _aadLength, length - _aadLength, output, _aadLength); _mac.BlockUpdate(output, 0, length); _ = _mac.DoFinal(output, length); @@ -89,12 +112,16 @@ public override byte[] Encrypt(byte[] input, int offset, int length) } /// - /// Decrypts the first block which is packet length field. + /// Decrypts the AAD. /// - /// The encrypted packet length field. - /// The decrypted packet length field. + /// The encrypted AAD. + /// The decrypted AAD. public override byte[] Decrypt(byte[] input) { + Debug.Assert(_aadCipher != null, "The aadCipher must not be null"); + + _aadCipher.Init(forEncryption: false, new ParametersWithIV(_aadKeyParameter, _iv)); + var output = new byte[input.Length]; _aadCipher.ProcessBytes(input, 0, input.Length, output, 0); @@ -107,8 +134,7 @@ public override byte[] Decrypt(byte[] input) /// /// The input data with below format: /// - /// [inbound sequence field][packet length field][padding length field sz][payload][random paddings][Authenticated TAG] - /// [--------4 bytes-------][--4 bytes--(offset)][--------------Cipher Text----------------(length)][-------TAG-------] + /// [----][----Cipher AAD----(offset)][----Cipher Text----(length)][----TAG----] /// /// /// The zero-based offset in at which to begin decrypting and authenticating. @@ -116,16 +142,21 @@ public override byte[] Decrypt(byte[] input) /// /// The decrypted data with below format: /// - /// [padding length field sz][payload][random paddings] - /// [--------------------Plain Text-------------------] + /// [----Plain Text----] /// /// public override byte[] Decrypt(byte[] input, int offset, int length) { - Debug.Assert(offset == 8, "The offset must be 8"); + Debug.Assert(offset >= _aadLength, "The offset must be greater than or equals to aad length"); + + _cipher.Init(forEncryption: false, new ParametersWithIV(_keyParameter, _iv)); + + var keyStream = new byte[64]; + _cipher.ProcessBytes(keyStream, 0, keyStream.Length, keyStream, 0); + _mac.Init(new KeyParameter(keyStream, 0, 32)); var tag = new byte[TagSize]; - _mac.BlockUpdate(input, offset - 4, length + 4); + _mac.BlockUpdate(input, offset - _aadLength, length + _aadLength); _ = _mac.DoFinal(tag, 0); if (!Arrays.FixedTimeEquals(TagSize, tag, 0, input, offset + length)) { @@ -140,18 +171,7 @@ public override byte[] Decrypt(byte[] input, int offset, int length) internal override void SetSequenceNumber(uint sequenceNumber) { - var iv = new byte[12]; - BinaryPrimitives.WriteUInt64BigEndian(iv.AsSpan(4), sequenceNumber); - - // ChaCha20 encryption and decryption is completely - // symmetrical, so the 'forEncryption' is - // irrelevant. (Like 90% of stream ciphers) - _aadCipher.Init(forEncryption: true, new ParametersWithIV(new KeyParameter(Key, 32, 32), iv)); - _cipher.Init(forEncryption: true, new ParametersWithIV(new KeyParameter(Key, 0, 32), iv)); - - var keyStream = new byte[64]; - _cipher.ProcessBytes(keyStream, 0, keyStream.Length, keyStream, 0); - _mac.Init(new KeyParameter(keyStream, 0, 32)); + BinaryPrimitives.WriteUInt64BigEndian(_iv.AsSpan(4), sequenceNumber); } } } diff --git a/test/Data/Key.OPENSSH.ED25519.Encrypted.3Des.CBC.pub b/test/Data/Key.OPENSSH.ED25519.Encrypted.3Des.CBC.pub new file mode 100644 index 000000000..a719d918d --- /dev/null +++ b/test/Data/Key.OPENSSH.ED25519.Encrypted.3Des.CBC.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIC5fr8EY9Gb65zsvrCsGDF7+AXJgDJrAkvBAFDiYBBiX SSH.NET diff --git a/test/Data/Key.OPENSSH.ED25519.Encrypted.3Des.CBC.txt b/test/Data/Key.OPENSSH.ED25519.Encrypted.3Des.CBC.txt new file mode 100644 index 000000000..442d55ec9 --- /dev/null +++ b/test/Data/Key.OPENSSH.ED25519.Encrypted.3Des.CBC.txt @@ -0,0 +1,8 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAACDNkZXMtY2JjAAAABmJjcnlwdAAAABgAAAAQEMaDlDt8i7 +NL7keWLs6LGAAAABAAAAABAAAAMwAAAAtzc2gtZWQyNTUxOQAAACAuX6/BGPRm+uc7L6wr +Bgxe/gFyYAyawJLwQBQ4mAQYlwAAAJC/OWi6TtpChMbEYxOK4TuUencG4ULol0k3hJ4905 +LI0+etT6s5fqr8W3D93A6ElxGLtxtkhgLfhTC4DIo4fPxD7mHNKtjbbyJn2oqzxqUlvETS +d9gg7Ph+zw4a+/GMhhIlPc79D6QxISSNrdtFccvLwTVukhkm04OGIyNJzE5qOz4g78lejZ +pKjFzVLY+WjSg= +-----END OPENSSH PRIVATE KEY----- diff --git a/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.128.CBC.pub b/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.128.CBC.pub new file mode 100644 index 000000000..9afd37c24 --- /dev/null +++ b/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.128.CBC.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMYdAxNcki2uVihPLcIxl6Zk27MBZdI2ef7NBMIYFHtW SSH.NET diff --git a/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.128.CBC.txt b/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.128.CBC.txt new file mode 100644 index 000000000..305ea8c1b --- /dev/null +++ b/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.128.CBC.txt @@ -0,0 +1,8 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAACmFlczEyOC1jYmMAAAAGYmNyeXB0AAAAGAAAABApmviQDZ +dmF4eaa1glB0maAAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIMYdAxNcki2uVihP +LcIxl6Zk27MBZdI2ef7NBMIYFHtWAAAAkHLroaoIoLkV3nmNck418+ndWFFEsvbk3AURyG +3fZboIGmjTCFGWDigRmB/w75jKwurLdfXvORWMxqaxZ0sjnHFwt0i/pumOv61nFC0timVV +VSoCWcAgyBsl2+lnRhnORlY5THVvgU6zF6p8Mf8ONMT59HXRxc9VMRc+eJhY3wb3q0BIN+ +vSQ7PXvnKrq7/mbA== +-----END OPENSSH PRIVATE KEY----- diff --git a/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.128.CTR.pub b/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.128.CTR.pub new file mode 100644 index 000000000..856fe28fe --- /dev/null +++ b/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.128.CTR.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIP44SYIMQiq6RfzllvHztr7ATNkXbAFqFZtukvHa0VaU SSH.NET diff --git a/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.128.CTR.txt b/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.128.CTR.txt new file mode 100644 index 000000000..8c49a39fb --- /dev/null +++ b/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.128.CTR.txt @@ -0,0 +1,8 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAACmFlczEyOC1jdHIAAAAGYmNyeXB0AAAAGAAAABAGS7ibrm +I3evObqzYMOyFsAAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIP44SYIMQiq6Rfzl +lvHztr7ATNkXbAFqFZtukvHa0VaUAAAAkHezd7R3B/U5jmRG9ayxcB5v9eIjjM2eZ/7O6z +7waNtAIO36Ve+BY5qIduP4t8qdZ1JDFHaPxx/WLqezfV0hqRLzR4Pm/bcAHN60610x0BSF +xHDC7nFej/X4Sr0SEplqsCfqfk5B4wsmdLUGwvpxIqoUCsCLx4YVaAlG/OezZUJ6b6lViZ +zcaaG2MGRlm28f9Q== +-----END OPENSSH PRIVATE KEY----- diff --git a/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.128.GCM.pub b/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.128.GCM.pub new file mode 100644 index 000000000..31a491628 --- /dev/null +++ b/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.128.GCM.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJrCQwF23e9NNyfNKyYFhmbAme9DahSl8S7IVdjHFUZk SSH.NET diff --git a/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.128.GCM.txt b/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.128.GCM.txt new file mode 100644 index 000000000..067372172 --- /dev/null +++ b/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.128.GCM.txt @@ -0,0 +1,8 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAAFmFlczEyOC1nY21Ab3BlbnNzaC5jb20AAAAGYmNyeXB0AA +AAGAAAABAVgfqUe6lBEiQrnz6KqwwkAAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAA +IJrCQwF23e9NNyfNKyYFhmbAme9DahSl8S7IVdjHFUZkAAAAkJn29lzPneR2zVKGlR1+mw +qDiwgKDEHdGVC/LgNQwxsaOryYEwA8fK0pBx4Ai+oRJZIAL430ZvwXSNrbkRo6oLkOouxH +0Cx9Uq4ETgumAAMAuyPecU8POBTUQUJsbA35EJZofxceVOa8iKleGSEKzVlQeSESY78AFH +U3bsmAJ21q3daVGXKrH5RIi3vSDE5IdRWMqonvhXSSLk8HADgR4Kw= +-----END OPENSSH PRIVATE KEY----- diff --git a/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.192.CBC.pub b/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.192.CBC.pub new file mode 100644 index 000000000..e64cb6532 --- /dev/null +++ b/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.192.CBC.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPjSxcsIj5ycJxIhhcMQuJAYTgANIXUa4Y1WEiOrblXA SSH.NET diff --git a/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.192.CBC.txt b/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.192.CBC.txt new file mode 100644 index 000000000..591f28ca2 --- /dev/null +++ b/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.192.CBC.txt @@ -0,0 +1,8 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAACmFlczE5Mi1jYmMAAAAGYmNyeXB0AAAAGAAAABCO+DfG2m +YhNIGKmiDRH4cTAAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIPjSxcsIj5ycJxIh +hcMQuJAYTgANIXUa4Y1WEiOrblXAAAAAkC9fsItDX1HaNpw9PBxBX/Eedlu72MGGO7osjJ +QtuvNb5VhcR07iYhxyw97F7MeraRYNvLrWQyxURB1BkZaSCemBrQJ/ljgEOhU5IgqmlooI +T4x5BuChnet8HfPJ5Ws9fd5WMOtfMpdO8ZHkJM5VPiSUUhfgWSV0YGKY4Q7luDcpLHzBZk +ZpossNHwNKsBMC9g== +-----END OPENSSH PRIVATE KEY----- diff --git a/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.192.CTR.pub b/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.192.CTR.pub new file mode 100644 index 000000000..dc77b8e8c --- /dev/null +++ b/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.192.CTR.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIERKx068MaW5RqzljZBz1nZw6OIzp4zyUjTKlJQ45wAg SSH.NET diff --git a/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.192.CTR.txt b/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.192.CTR.txt new file mode 100644 index 000000000..05a06f06f --- /dev/null +++ b/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.192.CTR.txt @@ -0,0 +1,8 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAACmFlczE5Mi1jdHIAAAAGYmNyeXB0AAAAGAAAABCV/tolDk +gwraQcGNlFOu+GAAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIERKx068MaW5Rqzl +jZBz1nZw6OIzp4zyUjTKlJQ45wAgAAAAkGnQfFCyY4tOQO+dNs+1Zzt6l5UbRHMjECnvuF +M3P5oFi0FXRSNSODXvZzZWCn9EVtaICV0bP+UKx9SfVAAkS64ZHl8n0IlRI9PmmthP6yZr +N7RzXaejppY/ZqQm+yH7S1cNb9KsAEIGMUlws34KlPCitc1HKJu8r9UmQGXaXXur/l47f5 +AVP+RmKjdDZy7FvA== +-----END OPENSSH PRIVATE KEY----- diff --git a/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.256.CBC.pub b/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.256.CBC.pub new file mode 100644 index 000000000..6096d4437 --- /dev/null +++ b/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.256.CBC.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIC+D1UJTH4n4ipIrHFeBBVjDJkhnjFzhZfewDxNAg5hb SSH.NET diff --git a/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.256.CBC.txt b/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.256.CBC.txt new file mode 100644 index 000000000..c8f3072c6 --- /dev/null +++ b/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.256.CBC.txt @@ -0,0 +1,8 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jYmMAAAAGYmNyeXB0AAAAGAAAABAwPJUhck +LArH2ovPv8EjcMAAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIC+D1UJTH4n4ipIr +HFeBBVjDJkhnjFzhZfewDxNAg5hbAAAAkIrxGvN1uUkltnk7X6fktv3KWQVL1oSuJ10kK8 +HY854Dp8qbGi0sFPcBrX/0y7kTyN58L72UWzjWwUZXrG/n6mb+PHXQbfr4MO5R/+BMnIA0 +0NmEj4nzY/9WCtU7En30zv5IF1MiLU2x7qPBqwdkOIKkTz+cTOXo0PCqT1s/Y2cbMUarS/ +cPdZ1FL5JwOcbqvQ== +-----END OPENSSH PRIVATE KEY----- diff --git a/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.256.CTR.pub b/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.256.CTR.pub new file mode 100644 index 000000000..069798587 --- /dev/null +++ b/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.256.CTR.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJiK1qYA1G7CJwZrDhleCnSXM0YgZtVA4lEqDBlrV6LE SSH.NET diff --git a/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.256.CTR.txt b/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.256.CTR.txt new file mode 100644 index 000000000..e2990c982 --- /dev/null +++ b/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.256.CTR.txt @@ -0,0 +1,8 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABCLdsObaY +r3GiQxDA6fA1AlAAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIJiK1qYA1G7CJwZr +DhleCnSXM0YgZtVA4lEqDBlrV6LEAAAAkClmUCCE7AqpnGJCaI44L/mCRBVMCEXi7O9FNG +VLklY6VJV//S77A696OKqeEZxv17l9VnkL+KIbn5dgf05JzMAXFGgaUO9xsz++21A/3bfF +lQ1oghMQPN6iXVg2eVTTzLrlxW8+9wQRr+RZ+38rL55fnU187f/evLGajjJdVhtzw/PvKg +sCJ4xZHaGOUR6OxQ== +-----END OPENSSH PRIVATE KEY----- diff --git a/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.256.GCM.pub b/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.256.GCM.pub new file mode 100644 index 000000000..9aceccee7 --- /dev/null +++ b/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.256.GCM.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKs32WTgxZkPlc1Xg4q6m9H2MRsMYDOuRXXXKNa8sBIM scott@SCOTTLAPTOP diff --git a/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.256.GCM.txt b/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.256.GCM.txt new file mode 100644 index 000000000..fd65439c0 --- /dev/null +++ b/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.256.GCM.txt @@ -0,0 +1,8 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAAFmFlczI1Ni1nY21Ab3BlbnNzaC5jb20AAAAGYmNyeXB0AA +AAGAAAABDlF34NcdHSHJFFHrK8PK/FAAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAA +IGgS0NrTKMQcchUDkewJOtOC5J+nmrdny2rxCN0koPHdAAAAkHCy69z0kHw/kCs4a+LCBn +2R37rprUEqHSiykVEPKxRDqpjqitaVNGzvPo6uhUclW9xxsAufMYv+Mn/Rz5ZLqHSV1Jio +zdBZrAkM13DJpW6xKVjbjGTr7zXpnjr1dgMs1tmq/T9F503Dky84u9qA+jUVczFitWuwvn +JMHIJ7zgAI2fp5z+aq51lH9rLAp6Vmjoal5MRsZhIHBrrwheBEWPE= +-----END OPENSSH PRIVATE KEY----- diff --git a/test/Data/Key.OPENSSH.ED25519.Encrypted.ChaCha20.Poly1305.pub b/test/Data/Key.OPENSSH.ED25519.Encrypted.ChaCha20.Poly1305.pub new file mode 100644 index 000000000..438c80f0d --- /dev/null +++ b/test/Data/Key.OPENSSH.ED25519.Encrypted.ChaCha20.Poly1305.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBquS79wnun0ksZv6JJMgHzoZhzqH6Lkft+1sHTHFOjY SSH.NET diff --git a/test/Data/Key.OPENSSH.ED25519.Encrypted.ChaCha20.Poly1305.txt b/test/Data/Key.OPENSSH.ED25519.Encrypted.ChaCha20.Poly1305.txt new file mode 100644 index 000000000..4d1974258 --- /dev/null +++ b/test/Data/Key.OPENSSH.ED25519.Encrypted.ChaCha20.Poly1305.txt @@ -0,0 +1,8 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAAHWNoYWNoYTIwLXBvbHkxMzA1QG9wZW5zc2guY29tAAAABm +JjcnlwdAAAABgAAAAQoCDdnsgOWkWp01akN/vJjwAAABAAAAABAAAAMwAAAAtzc2gtZWQy +NTUxOQAAACAarku/cJ7p9JLGb+iSTIB86GYc6h+i5H7ftbB0xxTo2AAAAJDUnN2iwK9ZgA +fpExo5jBaxI90HMxcUmgUCc4H7fTOqKousn4oyK9SfXH66IS9S1id7D+6KfVHBnmF+aH5q +g75pAGaHUGncm5PY3Lfun4KD5mDbN5cnloFDKb+z4pJq6FhpCeg9GyiYbFeIz8HrDeEugK +RKeq5tKWibhDLX6Ywp0+gQH1Rb14atw2Yy1zV9KX900we18Jjhl7R+FvkrFkUC +-----END OPENSSH PRIVATE KEY----- diff --git a/test/Renci.SshNet.Tests/Classes/PrivateKeyFileTest.cs b/test/Renci.SshNet.Tests/Classes/PrivateKeyFileTest.cs index d07c3867c..009c9d915 100644 --- a/test/Renci.SshNet.Tests/Classes/PrivateKeyFileTest.cs +++ b/test/Renci.SshNet.Tests/Classes/PrivateKeyFileTest.cs @@ -562,11 +562,110 @@ public void Test_PrivateKey_OPENSSH_ED25519() } [TestMethod()] - [Owner("bhalbright")] + [Owner("scott-xu")] + [TestCategory("PrivateKey")] + public void Test_PrivateKey_OPENSSH_ED25519_ENCRYPTED_3DES_CBC() + { + using (var stream = GetData("Key.OPENSSH.ED25519.Encrypted.3Des.CBC.txt")) + { + _ = new PrivateKeyFile(stream, "12345"); + } + } + + [TestMethod()] + [Owner("scott-xu")] + [TestCategory("PrivateKey")] + public void Test_PrivateKey_OPENSSH_ED25519_ENCRYPTED_AES_128_CBC() + { + using (var stream = GetData("Key.OPENSSH.ED25519.Encrypted.Aes.128.CBC.txt")) + { + _ = new PrivateKeyFile(stream, "12345"); + } + } + + [TestMethod()] + [Owner("scott-xu")] + [TestCategory("PrivateKey")] + public void Test_PrivateKey_OPENSSH_ED25519_ENCRYPTED_AES_192_CBC() + { + using (var stream = GetData("Key.OPENSSH.ED25519.Encrypted.Aes.192.CBC.txt")) + { + _ = new PrivateKeyFile(stream, "12345"); + } + } + + [TestMethod()] + [Owner("scott-xu")] + [TestCategory("PrivateKey")] + public void Test_PrivateKey_OPENSSH_ED25519_ENCRYPTED_AES_256_CBC() + { + using (var stream = GetData("Key.OPENSSH.ED25519.Encrypted.Aes.256.CBC.txt")) + { + _ = new PrivateKeyFile(stream, "12345"); + } + } + + [TestMethod()] + [Owner("scott-xu")] + [TestCategory("PrivateKey")] + public void Test_PrivateKey_OPENSSH_ED25519_ENCRYPTED_AES_128_CTR() + { + using (var stream = GetData("Key.OPENSSH.ED25519.Encrypted.Aes.128.CTR.txt")) + { + _ = new PrivateKeyFile(stream, "12345"); + } + } + + [TestMethod()] + [Owner("scott-xu")] + [TestCategory("PrivateKey")] + public void Test_PrivateKey_OPENSSH_ED25519_ENCRYPTED_AES_192_CTR() + { + using (var stream = GetData("Key.OPENSSH.ED25519.Encrypted.Aes.192.CTR.txt")) + { + _ = new PrivateKeyFile(stream, "12345"); + } + } + + [TestMethod()] + [Owner("scott-xu")] + [TestCategory("PrivateKey")] + public void Test_PrivateKey_OPENSSH_ED25519_ENCRYPTED_AES_256_CTR() + { + using (var stream = GetData("Key.OPENSSH.ED25519.Encrypted.Aes.256.CTR.txt")) + { + _ = new PrivateKeyFile(stream, "12345"); + } + } + + [TestMethod()] + [Owner("scott-xu")] + [TestCategory("PrivateKey")] + public void Test_PrivateKey_OPENSSH_ED25519_ENCRYPTED_AES_128_GCM() + { + using (var stream = GetData("Key.OPENSSH.ED25519.Encrypted.Aes.128.GCM.txt")) + { + _ = new PrivateKeyFile(stream, "12345"); + } + } + + [TestMethod()] + [Owner("scott-xu")] + [TestCategory("PrivateKey")] + public void Test_PrivateKey_OPENSSH_ED25519_ENCRYPTED_AES_256_GCM() + { + using (var stream = GetData("Key.OPENSSH.ED25519.Encrypted.Aes.256.GCM.txt")) + { + _ = new PrivateKeyFile(stream, "12345"); + } + } + + [TestMethod()] + [Owner("scott-xu")] [TestCategory("PrivateKey")] - public void Test_PrivateKey_OPENSSH_ED25519_ENCRYPTED() + public void Test_PrivateKey_OPENSSH_ED25519_ENCRYPTED_ChaCha20_Poly1305() { - using (var stream = GetData("Key.OPENSSH.ED25519.Encrypted.txt")) + using (var stream = GetData("Key.OPENSSH.ED25519.Encrypted.ChaCha20.Poly1305.txt")) { _ = new PrivateKeyFile(stream, "12345"); }