From 0880a6a8d4135897c71cade47503a5f9ae84af44 Mon Sep 17 00:00:00 2001 From: Scott Xu Date: Sat, 25 May 2024 11:24:31 +0800 Subject: [PATCH 01/18] Implements ChaCha20 cipher algorithm. --- src/Renci.SshNet/Common/Pack.cs | 36 ++- .../Cryptography/Ciphers/ChaCha20Cipher.cs | 229 ++++++++++++++++++ .../Ciphers/ChaCha20CipherTest.cs | 190 +++++++++++++++ 3 files changed, 443 insertions(+), 12 deletions(-) create mode 100644 src/Renci.SshNet/Security/Cryptography/Ciphers/ChaCha20Cipher.cs create mode 100644 test/Renci.SshNet.Tests/Classes/Security/Cryptography/Ciphers/ChaCha20CipherTest.cs diff --git a/src/Renci.SshNet/Common/Pack.cs b/src/Renci.SshNet/Common/Pack.cs index 6662304c3..bf9dd32fa 100644 --- a/src/Renci.SshNet/Common/Pack.cs +++ b/src/Renci.SshNet/Common/Pack.cs @@ -33,13 +33,24 @@ internal static ushort LittleEndianToUInt16(byte[] buffer) /// Converted . internal static uint LittleEndianToUInt32(byte[] buffer) { + return LittleEndianToUInt32(buffer, offset: 0); + } + + /// + /// Converts little endian bytes into number. + /// + /// The buffer. + /// The buffer offset. + /// Converted . + internal static uint LittleEndianToUInt32(byte[] buffer, int offset) + { #if NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER - return BinaryPrimitives.ReadUInt32LittleEndian(buffer); + return BinaryPrimitives.ReadUInt32LittleEndian(buffer.AsSpan(offset)); #else - uint n = buffer[0]; - n |= (uint)buffer[1] << 8; - n |= (uint)buffer[2] << 16; - n |= (uint)buffer[3] << 24; + uint n = buffer[offset++]; + n |= (uint)buffer[offset++] << 8; + n |= (uint)buffer[offset++] << 16; + n |= (uint)buffer[offset] << 24; return n; #endif } @@ -99,7 +110,7 @@ private static void UInt16ToLittleEndian(ushort value, byte[] buffer) internal static byte[] UInt32ToLittleEndian(uint value) { var buffer = new byte[4]; - UInt32ToLittleEndian(value, buffer); + UInt32ToLittleEndian(value, buffer, offset: 0); return buffer; } @@ -108,15 +119,16 @@ internal static byte[] UInt32ToLittleEndian(uint value) /// /// The number to convert. /// The buffer. - private static void UInt32ToLittleEndian(uint value, byte[] buffer) + /// The buffer offset. + internal static void UInt32ToLittleEndian(uint value, byte[] buffer, int offset) { #if NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER - BinaryPrimitives.WriteUInt32LittleEndian(buffer, value); + BinaryPrimitives.WriteUInt32LittleEndian(buffer.AsSpan(offset), value); #else - buffer[0] = (byte)(value & 0x000000FF); - buffer[1] = (byte)((value & 0x0000FF00) >> 8); - buffer[2] = (byte)((value & 0x00FF0000) >> 16); - buffer[3] = (byte)((value & 0xFF000000) >> 24); + buffer[offset++] = (byte)(value & 0x000000FF); + buffer[offset++] = (byte)((value & 0x0000FF00) >> 8); + buffer[offset++] = (byte)((value & 0x00FF0000) >> 16); + buffer[offset] = (byte)((value & 0xFF000000) >> 24); #endif } diff --git a/src/Renci.SshNet/Security/Cryptography/Ciphers/ChaCha20Cipher.cs b/src/Renci.SshNet/Security/Cryptography/Ciphers/ChaCha20Cipher.cs new file mode 100644 index 000000000..39b492475 --- /dev/null +++ b/src/Renci.SshNet/Security/Cryptography/Ciphers/ChaCha20Cipher.cs @@ -0,0 +1,229 @@ +using System; + +using Renci.SshNet.Common; + +namespace Renci.SshNet.Security.Cryptography.Ciphers +{ + /// + /// Implements ChaCha20 cipher algorithm. + /// + internal sealed class ChaCha20Cipher : StreamCipher + { + private const uint ChachaConst0 = 0x61707865; + private const uint ChachaConst1 = 0x3320646e; + private const uint ChachaConst2 = 0x79622d32; + private const uint ChachaConst3 = 0x6b206574; + + private uint _s00; + private uint _s01; + private uint _s02; + private uint _s03; + private uint _s04; + private uint _s05; + private uint _s06; + private uint _s07; + private uint _s08; + private uint _s09; + private uint _s10; + private uint _s11; + private uint _s12; + private uint _s13; + private uint _s14; + private uint _s15; + + /// + /// Gets the minimum data size. + /// + /// + /// The minimum data size. + /// + public override byte MinimumSize + { + get + { + return 0; + } + } + + /// + /// Initializes a new instance of the class. + /// + /// The key. + /// The nonce. + /// The init counter. + public ChaCha20Cipher(byte[] key, byte[] nonce, uint counter = 0) + : base(key) + { + var keySize = key.Length * 8; + + if (keySize is not 256) + { + throw new ArgumentException(string.Format("KeySize '{0}' is not valid for this algorithm.", keySize)); + } + + SetState(key, nonce.Take(12), counter); + } + + /// + /// Encrypts the specified input. + /// + /// The input. + /// The zero-based offset in at which to begin encrypting. + /// The number of bytes to encrypt from . + /// + /// Encrypted data. + /// + public override byte[] Encrypt(byte[] input, int offset, int length) + { + var output = new byte[length]; + _ = ProcessBytes(input, offset, length, output, 0); + return output; + } + + /// + /// Decrypts the specified input. + /// + /// The input. + /// The zero-based offset in at which to begin decrypting. + /// The number of bytes to decrypt from . + /// + /// The decrypted data. + /// + public override byte[] Decrypt(byte[] input, int offset, int length) + { + return Encrypt(input, offset, length); + } + + private void AdvanceCounter() + { + unchecked + { + if (++_s12 == 0) + { + ++_s13; + } + } + } + + private int ProcessBytes(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset) + { + if ((inputOffset + inputCount) > inputBuffer.Length) + { + throw new ArgumentException("input buffer too short"); + } + + if ((outputOffset + inputCount) > outputBuffer.Length) + { + throw new ArgumentException("output buffer too short"); + } + + var index = 0; + var keyStream = new byte[64]; + + for (var i = 0; i < inputCount; i++) + { + if (index == 0) + { + GenerateKeyStream(keyStream); + AdvanceCounter(); + } + + outputBuffer[outputOffset + i] = (byte)(keyStream[index++] ^ inputBuffer[inputOffset + i]); + index &= 63; + } + + return inputCount; + } + + private void GenerateKeyStream(byte[] output) + { + var s00 = _s00; + var s01 = _s01; + var s02 = _s02; + var s03 = _s03; + var s04 = _s04; + var s05 = _s05; + var s06 = _s06; + var s07 = _s07; + var s08 = _s08; + var s09 = _s09; + var s10 = _s10; + var s11 = _s11; + var s12 = _s12; + var s13 = _s13; + var s14 = _s14; + var s15 = _s15; + + static void QuarterRound(ref uint a, ref uint b, ref uint c, ref uint d) + { + a += b; + d ^= a; + d = d << 16 | d >> 16; + c += d; + b ^= c; + b = b << 12 | b >> 20; + a += b; + d ^= a; + d = d << 8 | d >> 24; + c += d; + b ^= c; + b = b << 7 | b >> 25; + } + + unchecked + { + for (var i = 20; i > 0; i -= 2) + { + QuarterRound(ref s00, ref s04, ref s08, ref s12); + QuarterRound(ref s01, ref s05, ref s09, ref s13); + QuarterRound(ref s02, ref s06, ref s10, ref s14); + QuarterRound(ref s03, ref s07, ref s11, ref s15); + QuarterRound(ref s00, ref s05, ref s10, ref s15); + QuarterRound(ref s01, ref s06, ref s11, ref s12); + QuarterRound(ref s02, ref s07, ref s08, ref s13); + QuarterRound(ref s03, ref s04, ref s09, ref s14); + } + + Pack.UInt32ToLittleEndian(_s00 + s00, output, 0); + Pack.UInt32ToLittleEndian(_s01 + s01, output, 4); + Pack.UInt32ToLittleEndian(_s02 + s02, output, 8); + Pack.UInt32ToLittleEndian(_s03 + s03, output, 12); + Pack.UInt32ToLittleEndian(_s04 + s04, output, 16); + Pack.UInt32ToLittleEndian(_s05 + s05, output, 20); + Pack.UInt32ToLittleEndian(_s06 + s06, output, 24); + Pack.UInt32ToLittleEndian(_s07 + s07, output, 28); + Pack.UInt32ToLittleEndian(_s08 + s08, output, 32); + Pack.UInt32ToLittleEndian(_s09 + s09, output, 36); + Pack.UInt32ToLittleEndian(_s10 + s10, output, 40); + Pack.UInt32ToLittleEndian(_s11 + s11, output, 44); + Pack.UInt32ToLittleEndian(_s12 + s12, output, 48); + Pack.UInt32ToLittleEndian(_s13 + s13, output, 52); + Pack.UInt32ToLittleEndian(_s14 + s14, output, 56); + Pack.UInt32ToLittleEndian(_s15 + s15, output, 60); + } + } + + private void SetState(byte[] key, byte[] nonce, uint counter) + { + _s00 = ChachaConst0; + _s01 = ChachaConst1; + _s02 = ChachaConst2; + _s03 = ChachaConst3; + + _s04 = Pack.LittleEndianToUInt32(key, 0); + _s05 = Pack.LittleEndianToUInt32(key, 4); + _s06 = Pack.LittleEndianToUInt32(key, 8); + _s07 = Pack.LittleEndianToUInt32(key, 12); + _s08 = Pack.LittleEndianToUInt32(key, 16); + _s09 = Pack.LittleEndianToUInt32(key, 20); + _s10 = Pack.LittleEndianToUInt32(key, 24); + _s11 = Pack.LittleEndianToUInt32(key, 28); + + _s12 = counter; + + _s13 = Pack.LittleEndianToUInt32(nonce, 0); + _s14 = Pack.LittleEndianToUInt32(nonce, 4); + _s15 = Pack.LittleEndianToUInt32(nonce, 8); + } + } +} diff --git a/test/Renci.SshNet.Tests/Classes/Security/Cryptography/Ciphers/ChaCha20CipherTest.cs b/test/Renci.SshNet.Tests/Classes/Security/Cryptography/Ciphers/ChaCha20CipherTest.cs new file mode 100644 index 000000000..ef240b9d2 --- /dev/null +++ b/test/Renci.SshNet.Tests/Classes/Security/Cryptography/Ciphers/ChaCha20CipherTest.cs @@ -0,0 +1,190 @@ +using System.Linq; + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using Renci.SshNet.Security.Cryptography.Ciphers; +using Renci.SshNet.Tests.Common; + +namespace Renci.SshNet.Tests.Classes.Security.Cryptography.Ciphers +{ + /// + /// + /// + [TestClass] + public class ChaCha20CipherTest : TestBase + { + [TestMethod] + public void Test_Cipher_ChaCha20() + { + var input1 = new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 + }; + + var key1 = new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 + }; + + var nonce1 = new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + + uint counter1 = 0; + + var expected1 = new byte[] + { + 0x76, 0xb8, 0xe0, 0xad, 0xa0, 0xf1, 0x3d, 0x90, + 0x40, 0x5d, 0x6a, 0xe5, 0x53, 0x86, 0xbd, 0x28, + 0xbd, 0xd2, 0x19, 0xb8, 0xa0, 0x8d, 0xed, 0x1a, + 0xa8, 0x36, 0xef, 0xcc, 0x8b, 0x77, 0x0d, 0xc7, + 0xda, 0x41, 0x59, 0x7c, 0x51, 0x57, 0x48, 0x8d, + 0x77, 0x24, 0xe0, 0x3f, 0xb8, 0xd8, 0x4a, 0x37, + 0x6a, 0x43, 0xb8, 0xf4, 0x15, 0x18, 0xa1, 0x1c, + 0xc3, 0x87, 0xb6, 0x69, 0xb2, 0xee, 0x65, 0x86 + }; + + var input2 = new byte[] + { + 0x4c, 0x61, 0x64, 0x69, 0x65, 0x73, 0x20, 0x61, + 0x6e, 0x64, 0x20, 0x47, 0x65, 0x6e, 0x74, 0x6c, + 0x65, 0x6d, 0x65, 0x6e, 0x20, 0x6f, 0x66, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x61, 0x73, + 0x73, 0x20, 0x6f, 0x66, 0x20, 0x27, 0x39, 0x39, + 0x3a, 0x20, 0x49, 0x66, 0x20, 0x49, 0x20, 0x63, + 0x6f, 0x75, 0x6c, 0x64, 0x20, 0x6f, 0x66, 0x66, + 0x65, 0x72, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x6f, + 0x6e, 0x6c, 0x79, 0x20, 0x6f, 0x6e, 0x65, 0x20, + 0x74, 0x69, 0x70, 0x20, 0x66, 0x6f, 0x72, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x66, 0x75, 0x74, 0x75, + 0x72, 0x65, 0x2c, 0x20, 0x73, 0x75, 0x6e, 0x73, + 0x63, 0x72, 0x65, 0x65, 0x6e, 0x20, 0x77, 0x6f, + 0x75, 0x6c, 0x64, 0x20, 0x62, 0x65, 0x20, 0x69, + 0x74, 0x2e + }; + + var key2 = new byte[] + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f + }; + + var nonce2 = new byte[] + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x00 + }; + + uint counter2 = 1; + + var expected2 = new byte[] + { + 0x6e, 0x2e, 0x35, 0x9a, 0x25, 0x68, 0xf9, 0x80, + 0x41, 0xba, 0x07, 0x28, 0xdd, 0x0d, 0x69, 0x81, + 0xe9, 0x7e, 0x7a, 0xec, 0x1d, 0x43, 0x60, 0xc2, + 0x0a, 0x27, 0xaf, 0xcc, 0xfd, 0x9f, 0xae, 0x0b, + 0xf9, 0x1b, 0x65, 0xc5, 0x52, 0x47, 0x33, 0xab, + 0x8f, 0x59, 0x3d, 0xab, 0xcd, 0x62, 0xb3, 0x57, + 0x16, 0x39, 0xd6, 0x24, 0xe6, 0x51, 0x52, 0xab, + 0x8f, 0x53, 0x0c, 0x35, 0x9f, 0x08, 0x61, 0xd8, + 0x07, 0xca, 0x0d, 0xbf, 0x50, 0x0d, 0x6a, 0x61, + 0x56, 0xa3, 0x8e, 0x08, 0x8a, 0x22, 0xb6, 0x5e, + 0x52, 0xbc, 0x51, 0x4d, 0x16, 0xcc, 0xf8, 0x06, + 0x81, 0x8c, 0xe9, 0x1a, 0xb7, 0x79, 0x37, 0x36, + 0x5a, 0xf9, 0x0b, 0xbf, 0x74, 0xa3, 0x5b, 0xe6, + 0xb4, 0x0b, 0x8e, 0xed, 0xf2, 0x78, 0x5e, 0x42, + 0x87, 0x4d + }; + + var input3 = new byte[] + { + 0x27, 0x54, 0x77, 0x61, 0x73, 0x20, 0x62, 0x72, + 0x69, 0x6c, 0x6c, 0x69, 0x67, 0x2c, 0x20, 0x61, + 0x6e, 0x64, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, + 0x6c, 0x69, 0x74, 0x68, 0x79, 0x20, 0x74, 0x6f, + 0x76, 0x65, 0x73, 0x0a, 0x44, 0x69, 0x64, 0x20, + 0x67, 0x79, 0x72, 0x65, 0x20, 0x61, 0x6e, 0x64, + 0x20, 0x67, 0x69, 0x6d, 0x62, 0x6c, 0x65, 0x20, + 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x77, + 0x61, 0x62, 0x65, 0x3a, 0x0a, 0x41, 0x6c, 0x6c, + 0x20, 0x6d, 0x69, 0x6d, 0x73, 0x79, 0x20, 0x77, + 0x65, 0x72, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x62, 0x6f, 0x72, 0x6f, 0x67, 0x6f, 0x76, 0x65, + 0x73, 0x2c, 0x0a, 0x41, 0x6e, 0x64, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x6d, 0x6f, 0x6d, 0x65, 0x20, + 0x72, 0x61, 0x74, 0x68, 0x73, 0x20, 0x6f, 0x75, + 0x74, 0x67, 0x72, 0x61, 0x62, 0x65, 0x2e + }; + + var key3 = new byte[] + { + 0x1c, 0x92, 0x40, 0xa5, 0xeb, 0x55, 0xd3, 0x8a, + 0xf3, 0x33, 0x88, 0x86, 0x04, 0xf6, 0xb5, 0xf0, + 0x47, 0x39, 0x17, 0xc1, 0x40, 0x2b, 0x80, 0x09, + 0x9d, 0xca, 0x5c, 0xbc, 0x20, 0x70, 0x75, 0xc0 + }; + + var nonce3 = new byte[] + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02 + }; + + uint counter3 = 42; + + var expected3 = new byte[] + { + 0x62, 0xe6, 0x34, 0x7f, 0x95, 0xed, 0x87, 0xa4, + 0x5f, 0xfa, 0xe7, 0x42, 0x6f, 0x27, 0xa1, 0xdf, + 0x5f, 0xb6, 0x91, 0x10, 0x04, 0x4c, 0x0d, 0x73, + 0x11, 0x8e, 0xff, 0xa9, 0x5b, 0x01, 0xe5, 0xcf, + 0x16, 0x6d, 0x3d, 0xf2, 0xd7, 0x21, 0xca, 0xf9, + 0xb2, 0x1e, 0x5f, 0xb1, 0x4c, 0x61, 0x68, 0x71, + 0xfd, 0x84, 0xc5, 0x4f, 0x9d, 0x65, 0xb2, 0x83, + 0x19, 0x6c, 0x7f, 0xe4, 0xf6, 0x05, 0x53, 0xeb, + 0xf3, 0x9c, 0x64, 0x02, 0xc4, 0x22, 0x34, 0xe3, + 0x2a, 0x35, 0x6b, 0x3e, 0x76, 0x43, 0x12, 0xa6, + 0x1a, 0x55, 0x32, 0x05, 0x57, 0x16, 0xea, 0xd6, + 0x96, 0x25, 0x68, 0xf8, 0x7d, 0x3f, 0x3f, 0x77, + 0x04, 0xc6, 0xa8, 0xd1, 0xbc, 0xd1, 0xbf, 0x4d, + 0x50, 0xd6, 0x15, 0x4b, 0x6d, 0xa7, 0x31, 0xb1, + 0x87, 0xb5, 0x8d, 0xfd, 0x72, 0x8a, 0xfa, 0x36, + 0x75, 0x7a, 0x79, 0x7a, 0xc1, 0x88, 0xd1 + }; + + var cipher1 = new ChaCha20Cipher(key1, nonce1, counter1); + var cipher2 = new ChaCha20Cipher(key2, nonce2, counter2); + var cipher3 = new ChaCha20Cipher(key3, nonce3, counter3); + + var output1 = cipher1.Encrypt(input1); + var output2 = cipher2.Encrypt(input2); + var output3 = cipher3.Encrypt(input3); + + if (!output1.SequenceEqual(expected1)) + { + Assert.Fail("Invalid encryption"); + } + + if (!output2.SequenceEqual(expected2)) + { + Assert.Fail("Invalid encryption"); + } + + if (!output3.SequenceEqual(expected3)) + { + Assert.Fail("Invalid encryption"); + } + } + } +} From 5471a327eb07796c2e1cc35a29762ba1d75793c8 Mon Sep 17 00:00:00 2001 From: Scott Xu Date: Sun, 26 May 2024 10:30:32 +0800 Subject: [PATCH 02/18] Implements chacha20-poly1305@openssh.com --- README.md | 1 + src/Renci.SshNet/ConnectionInfo.cs | 6 + .../Security/Cryptography/Cipher.cs | 10 +- .../Cryptography/Ciphers/AesGcmCipher.cs | 12 +- .../Cryptography/Ciphers/ChaCha20Cipher.cs | 34 ++-- .../Ciphers/ChaCha20Poly1305Cipher.cs | 163 ++++++++++++++++++ src/Renci.SshNet/Session.cs | 10 +- .../CipherTests.cs | 6 + 8 files changed, 227 insertions(+), 15 deletions(-) create mode 100644 src/Renci.SshNet/Security/Cryptography/Ciphers/ChaCha20Poly1305Cipher.cs diff --git a/README.md b/README.md index ab8959b3a..ae0a9fadb 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ the missing test once you figure things out. 🤓 ## Encryption Method **SSH.NET** supports the following encryption methods: +* chacha20-poly1305@openssh.com (.NET 6 and higher) * aes128-ctr * aes192-ctr * aes256-ctr diff --git a/src/Renci.SshNet/ConnectionInfo.cs b/src/Renci.SshNet/ConnectionInfo.cs index f5609dc58..ba2bdb6c4 100644 --- a/src/Renci.SshNet/ConnectionInfo.cs +++ b/src/Renci.SshNet/ConnectionInfo.cs @@ -383,6 +383,12 @@ public ConnectionInfo(string host, int port, string username, ProxyTypes proxyTy }; Encryptions = new Dictionary(); +#if NET6_0_OR_GREATER + if (ChaCha20Poly1305.IsSupported) + { + Encryptions.Add("chacha20-poly1305@openssh.com", new CipherInfo(512, (key, iv) => new ChaCha20Poly1305Cipher(key), isAead: true)); + } +#endif Encryptions.Add("aes128-ctr", new CipherInfo(128, (key, iv) => new AesCipher(key, iv, AesCipherMode.CTR, pkcs7Padding: false))); Encryptions.Add("aes192-ctr", new CipherInfo(192, (key, iv) => new AesCipher(key, iv, AesCipherMode.CTR, pkcs7Padding: false))); Encryptions.Add("aes256-ctr", new CipherInfo(256, (key, iv) => new AesCipher(key, iv, AesCipherMode.CTR, pkcs7Padding: false))); diff --git a/src/Renci.SshNet/Security/Cryptography/Cipher.cs b/src/Renci.SshNet/Security/Cryptography/Cipher.cs index d96a214a9..683efbe0e 100644 --- a/src/Renci.SshNet/Security/Cryptography/Cipher.cs +++ b/src/Renci.SshNet/Security/Cryptography/Cipher.cs @@ -22,6 +22,14 @@ public abstract class Cipher /// public virtual int TagSize { get; } + /// + /// Sets the sequence numbner. + /// + /// The sequence number. + public virtual void SetSequenceNumber(uint sequenceNumber) + { + } + /// /// Encrypts the specified input. /// @@ -50,7 +58,7 @@ public byte[] Encrypt(byte[] input) /// /// The decrypted data. /// - public byte[] Decrypt(byte[] input) + public virtual byte[] Decrypt(byte[] input) { return Decrypt(input, 0, input.Length); } diff --git a/src/Renci.SshNet/Security/Cryptography/Ciphers/AesGcmCipher.cs b/src/Renci.SshNet/Security/Cryptography/Ciphers/AesGcmCipher.cs index cd673d04a..1018fb668 100644 --- a/src/Renci.SshNet/Security/Cryptography/Ciphers/AesGcmCipher.cs +++ b/src/Renci.SshNet/Security/Cryptography/Ciphers/AesGcmCipher.cs @@ -12,7 +12,7 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers /// AES GCM cipher implementation. /// . /// - internal sealed class AesGcmCipher : SymmetricCipher, IDisposable + internal sealed class AesGcmCipher : StreamCipher, IDisposable { private readonly byte[] _iv; private readonly AesGcm _aesGcm; @@ -99,6 +99,16 @@ public override byte[] Encrypt(byte[] input, int offset, int length) return output; } + /// + /// Decrypts the first block which is packet length field. + /// + /// The packet length field. + /// The raw packet length field. + public override byte[] Decrypt(byte[] input) + { + return input; + } + /// /// Decrypts the specified input. /// diff --git a/src/Renci.SshNet/Security/Cryptography/Ciphers/ChaCha20Cipher.cs b/src/Renci.SshNet/Security/Cryptography/Ciphers/ChaCha20Cipher.cs index 39b492475..035cbaf50 100644 --- a/src/Renci.SshNet/Security/Cryptography/Ciphers/ChaCha20Cipher.cs +++ b/src/Renci.SshNet/Security/Cryptography/Ciphers/ChaCha20Cipher.cs @@ -14,6 +14,9 @@ internal sealed class ChaCha20Cipher : StreamCipher private const uint ChachaConst2 = 0x79622d32; private const uint ChachaConst3 = 0x6b206574; + private readonly byte[] _keyStream = new byte[64]; + private int _index; + private uint _s00; private uint _s01; private uint _s02; @@ -58,10 +61,22 @@ public ChaCha20Cipher(byte[] key, byte[] nonce, uint counter = 0) if (keySize is not 256) { - throw new ArgumentException(string.Format("KeySize '{0}' is not valid for this algorithm.", keySize)); + throw new ArgumentException(string.Format("KeySize '{0}' is not valid for this algorithm.", keySize), nameof(key)); } - SetState(key, nonce.Take(12), counter); + if (nonce is null) + { + throw new ArgumentNullException(nameof(nonce)); + } + + var nonceSize = nonce.Length * 8; + + if (nonceSize is not 96) + { + throw new ArgumentNullException(string.Format("NonceSize '{0}' is not valid for this algorithm.", nonceSize), nameof(nonce)); + } + + SetState(key, nonce, counter); } /// @@ -94,7 +109,7 @@ public override byte[] Decrypt(byte[] input, int offset, int length) return Encrypt(input, offset, length); } - private void AdvanceCounter() + private void IncrementCounter() { unchecked { @@ -117,19 +132,16 @@ private int ProcessBytes(byte[] inputBuffer, int inputOffset, int inputCount, by throw new ArgumentException("output buffer too short"); } - var index = 0; - var keyStream = new byte[64]; - for (var i = 0; i < inputCount; i++) { - if (index == 0) + if (_index == 0) { - GenerateKeyStream(keyStream); - AdvanceCounter(); + GenerateKeyStream(_keyStream); + IncrementCounter(); } - outputBuffer[outputOffset + i] = (byte)(keyStream[index++] ^ inputBuffer[inputOffset + i]); - index &= 63; + outputBuffer[outputOffset + i] = (byte)(_keyStream[_index++] ^ inputBuffer[inputOffset + i]); + _index &= 63; } return inputCount; diff --git a/src/Renci.SshNet/Security/Cryptography/Ciphers/ChaCha20Poly1305Cipher.cs b/src/Renci.SshNet/Security/Cryptography/Ciphers/ChaCha20Poly1305Cipher.cs new file mode 100644 index 000000000..f20be6877 --- /dev/null +++ b/src/Renci.SshNet/Security/Cryptography/Ciphers/ChaCha20Poly1305Cipher.cs @@ -0,0 +1,163 @@ +#if NET6_0_OR_GREATER +using System; +using System.Buffers.Binary; +using System.Diagnostics; +using System.Security.Cryptography; + +using Renci.SshNet.Common; + +namespace Renci.SshNet.Security.Cryptography.Ciphers +{ + /// + /// ChaCha20Poly1305 cipher implementation. + /// . + /// + internal sealed class ChaCha20Poly1305Cipher : SymmetricCipher, IDisposable + { + private readonly ChaCha20Poly1305 _chacha20poly1305; + + private readonly byte[] _sequenceNumber = new byte[12]; + private ChaCha20Cipher _aadCipher; + + /// + /// Gets the minimun block size. + /// + public override byte MinimumSize + { + get + { + return 16; + } + } + + /// + /// Gets the tag size in bytes. + /// Poly1305 [Poly1305], also by Daniel Bernstein, is a one-time Carter- + /// Wegman MAC that computes a 128 bit integrity tag given a message + /// . + /// + public override int TagSize + { + get + { + return 16; + } + } + + /// + /// Initializes a new instance of the class. + /// + /// The key. + public ChaCha20Poly1305Cipher(byte[] key) + : base(key) + { + _chacha20poly1305 = new ChaCha20Poly1305(key.AsSpan(0, 32)); + } + + /// + /// Encrypts the specified input. + /// + /// + /// 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)] + /// + /// + /// The zero-based offset in at which to begin encrypting. + /// The number of bytes to encrypt from . + /// + /// The encrypted data with below format: + /// + /// [packet length field][padding length field sz][payload][random paddings][Authenticated TAG] + /// [------4 bytes------][------------------Cipher Text--------------------][-------TAG-------] + /// + /// + public override byte[] Encrypt(byte[] input, int offset, int length) + { + var packetLengthField = _aadCipher.Encrypt(input, offset, 4); + var plainText = new ReadOnlySpan(input, offset + 4, length - 4); + + var output = new byte[length + TagSize]; + Array.Copy(packetLengthField, output, 4); + var cipherText = new Span(output, 4, length - 4); + var tag = new Span(output, length, TagSize); + + _chacha20poly1305.Encrypt(nonce: _sequenceNumber, plainText, cipherText, tag, associatedData: packetLengthField); + + return output; + } + + public override void SetSequenceNumber(uint sequenceNumber) + { + BinaryPrimitives.WriteUInt64BigEndian(_sequenceNumber, sequenceNumber); + _aadCipher = new ChaCha20Cipher(Key.Take(32, 32), nonce: _sequenceNumber); + } + + /// + /// Decrypts the first block which is packet length field. + /// + /// The encrpted packet length field. + /// The decrypted packet length field. + public override byte[] Decrypt(byte[] input) + { + return _aadCipher.Decrypt(input); + } + + /// + /// Decrypts the specified 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-------] + /// + /// + /// The zero-based offset in at which to begin decrypting and authenticating. + /// The number of bytes to decrypt and authenticate from . + /// + /// The decrypted data with below format: + /// + /// [padding length field sz][payload][random paddings] + /// [--------------------Plain Text-------------------] + /// + /// + public override byte[] Decrypt(byte[] input, int offset, int length) + { + Debug.Assert(offset == 8, "The offset must be 8"); + + var packetLengthField = new ReadOnlySpan(input, 4, 4); + var cipherText = new ReadOnlySpan(input, offset, length); + var tag = new ReadOnlySpan(input, offset + length, TagSize); + + var output = new byte[length]; + var plainText = new Span(output); + + _chacha20poly1305.Decrypt(nonce: _sequenceNumber, cipherText, tag, plainText, associatedData: packetLengthField); + + return output; + } + + /// + /// Dispose the instance. + /// + /// Set to True to dispose of resouces. + public void Dispose(bool disposing) + { + if (disposing) + { + _chacha20poly1305.Dispose(); + } + } + + /// + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} +#endif diff --git a/src/Renci.SshNet/Session.cs b/src/Renci.SshNet/Session.cs index 6607b6bde..5c73acea1 100644 --- a/src/Renci.SshNet/Session.cs +++ b/src/Renci.SshNet/Session.cs @@ -1058,6 +1058,7 @@ internal void SendMessage(Message message) // Encrypt packet data if (_clientCipher != null) { + _clientCipher.SetSequenceNumber(_outboundPacketSequence); if (_clientEtm) { // The length of the "packet length" field in bytes @@ -1245,9 +1246,14 @@ private Message ReceiveMessage(Socket socket) return null; } - if (_serverCipher != null && !_serverAead && (_serverMac == null || !_serverEtm)) + if (_serverCipher != null) { - firstBlock = _serverCipher.Decrypt(firstBlock); + _serverCipher.SetSequenceNumber(_inboundPacketSequence); + + if (_serverMac == null || !_serverEtm) + { + firstBlock = _serverCipher.Decrypt(firstBlock); + } } packetLength = Pack.BigEndianToUInt32(firstBlock); diff --git a/test/Renci.SshNet.IntegrationTests/CipherTests.cs b/test/Renci.SshNet.IntegrationTests/CipherTests.cs index b2b3fd4c1..90a8d4953 100644 --- a/test/Renci.SshNet.IntegrationTests/CipherTests.cs +++ b/test/Renci.SshNet.IntegrationTests/CipherTests.cs @@ -76,6 +76,12 @@ public void Aes256Gcm() { DoTest(Cipher.Aes256Gcm); } + + [TestMethod] + public void ChaCha20Poly1305() + { + DoTest(Cipher.Chacha20Poly1305); + } #endif private void DoTest(Cipher cipher) { From da9bc082a78a0db85d7728bdb2f33a9f680e6dbd Mon Sep 17 00:00:00 2001 From: Scott Xu Date: Sun, 26 May 2024 14:01:13 +0800 Subject: [PATCH 03/18] Update Cipher.cs --- src/Renci.SshNet/Security/Cryptography/Cipher.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Renci.SshNet/Security/Cryptography/Cipher.cs b/src/Renci.SshNet/Security/Cryptography/Cipher.cs index 683efbe0e..c91a5f673 100644 --- a/src/Renci.SshNet/Security/Cryptography/Cipher.cs +++ b/src/Renci.SshNet/Security/Cryptography/Cipher.cs @@ -1,4 +1,4 @@ -namespace Renci.SshNet.Security.Cryptography +namespace Renci.SshNet.Security.Cryptography { /// /// Base class for cipher implementation. @@ -26,7 +26,7 @@ public abstract class Cipher /// Sets the sequence numbner. /// /// The sequence number. - public virtual void SetSequenceNumber(uint sequenceNumber) + internal virtual void SetSequenceNumber(uint sequenceNumber) { } From aaf49a1e0a67ff55b384673e2cf66d3c0c76802c Mon Sep 17 00:00:00 2001 From: Scott Xu Date: Sun, 26 May 2024 14:27:06 +0800 Subject: [PATCH 04/18] Update ChaCha20Poly1305Cipher.cs --- .../Security/Cryptography/Ciphers/ChaCha20Poly1305Cipher.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Renci.SshNet/Security/Cryptography/Ciphers/ChaCha20Poly1305Cipher.cs b/src/Renci.SshNet/Security/Cryptography/Ciphers/ChaCha20Poly1305Cipher.cs index f20be6877..1e2295d0c 100644 --- a/src/Renci.SshNet/Security/Cryptography/Ciphers/ChaCha20Poly1305Cipher.cs +++ b/src/Renci.SshNet/Security/Cryptography/Ciphers/ChaCha20Poly1305Cipher.cs @@ -88,7 +88,7 @@ public override byte[] Encrypt(byte[] input, int offset, int length) return output; } - public override void SetSequenceNumber(uint sequenceNumber) + internal override void SetSequenceNumber(uint sequenceNumber) { BinaryPrimitives.WriteUInt64BigEndian(_sequenceNumber, sequenceNumber); _aadCipher = new ChaCha20Cipher(Key.Take(32, 32), nonce: _sequenceNumber); From 7780ff80394ae4fd857c10f6757c41001b269167 Mon Sep 17 00:00:00 2001 From: Scott Xu Date: Sun, 26 May 2024 22:59:10 +0800 Subject: [PATCH 05/18] Note that the length of the concatenation of 'packet_length', 'padding_length', 'payload', and 'random padding' MUST be a multiple of the cipher block size or 8, whichever is larger. See https://www.rfc-editor.org/rfc/rfc4253#section-6 --- src/Renci.SshNet/Session.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Renci.SshNet/Session.cs b/src/Renci.SshNet/Session.cs index 5c73acea1..09d222888 100644 --- a/src/Renci.SshNet/Session.cs +++ b/src/Renci.SshNet/Session.cs @@ -1259,7 +1259,7 @@ private Message ReceiveMessage(Socket socket) packetLength = Pack.BigEndianToUInt32(firstBlock); // Test packet minimum and maximum boundaries - if (packetLength < Math.Max((byte)16, blockSize) - 4 || packetLength > MaximumSshPacketSize - 4) + if (packetLength < Math.Max((byte)8, blockSize) - 4 || packetLength > MaximumSshPacketSize - 4) { throw new SshConnectionException(string.Format(CultureInfo.CurrentCulture, "Bad packet length: {0}.", packetLength), DisconnectReason.ProtocolError); From d29d8de754c7abbf0eb383dda7911a45e0a626e0 Mon Sep 17 00:00:00 2001 From: Scott Xu Date: Mon, 27 May 2024 01:25:50 +0800 Subject: [PATCH 06/18] Use Chaos.Nacl Poly1305Donna --- src/Renci.SshNet/Common/Pack.cs | 9 +- src/Renci.SshNet/ConnectionInfo.cs | 7 +- .../Cryptography/Ciphers/AesGcmCipher.cs | 10 --- .../Cryptography/Ciphers/ChaCha20Cipher.cs | 13 ++- .../Ciphers/ChaCha20Poly1305Cipher.cs | 84 +++++++++---------- src/Renci.SshNet/Session.cs | 15 +++- 6 files changed, 59 insertions(+), 79 deletions(-) diff --git a/src/Renci.SshNet/Common/Pack.cs b/src/Renci.SshNet/Common/Pack.cs index bf9dd32fa..69102c170 100644 --- a/src/Renci.SshNet/Common/Pack.cs +++ b/src/Renci.SshNet/Common/Pack.cs @@ -205,14 +205,7 @@ internal static byte[] UInt32ToBigEndian(uint value) return buffer; } - internal static byte[] UInt64ToBigEndian(ulong value) - { - var buffer = new byte[8]; - UInt64ToBigEndian(value, buffer, offset: 0); - return buffer; - } - - private static void UInt64ToBigEndian(ulong value, byte[] buffer, int offset) + internal static void UInt64ToBigEndian(ulong value, byte[] buffer, int offset) { #if NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER BinaryPrimitives.WriteUInt64BigEndian(buffer.AsSpan(offset), value); diff --git a/src/Renci.SshNet/ConnectionInfo.cs b/src/Renci.SshNet/ConnectionInfo.cs index ba2bdb6c4..e7dac85ad 100644 --- a/src/Renci.SshNet/ConnectionInfo.cs +++ b/src/Renci.SshNet/ConnectionInfo.cs @@ -383,12 +383,6 @@ public ConnectionInfo(string host, int port, string username, ProxyTypes proxyTy }; Encryptions = new Dictionary(); -#if NET6_0_OR_GREATER - if (ChaCha20Poly1305.IsSupported) - { - Encryptions.Add("chacha20-poly1305@openssh.com", new CipherInfo(512, (key, iv) => new ChaCha20Poly1305Cipher(key), isAead: true)); - } -#endif Encryptions.Add("aes128-ctr", new CipherInfo(128, (key, iv) => new AesCipher(key, iv, AesCipherMode.CTR, pkcs7Padding: false))); Encryptions.Add("aes192-ctr", new CipherInfo(192, (key, iv) => new AesCipher(key, iv, AesCipherMode.CTR, pkcs7Padding: false))); Encryptions.Add("aes256-ctr", new CipherInfo(256, (key, iv) => new AesCipher(key, iv, AesCipherMode.CTR, pkcs7Padding: false))); @@ -399,6 +393,7 @@ public ConnectionInfo(string host, int port, string username, ProxyTypes proxyTy Encryptions.Add("aes256-gcm@openssh.com", new CipherInfo(256, (key, iv) => new AesGcmCipher(key, iv), isAead: true)); } #endif + Encryptions.Add("chacha20-poly1305@openssh.com", new CipherInfo(512, (key, iv) => new ChaCha20Poly1305Cipher(key), isAead: true)); Encryptions.Add("aes128-cbc", new CipherInfo(128, (key, iv) => new AesCipher(key, iv, AesCipherMode.CBC, pkcs7Padding: false))); Encryptions.Add("aes192-cbc", new CipherInfo(192, (key, iv) => new AesCipher(key, iv, AesCipherMode.CBC, pkcs7Padding: false))); Encryptions.Add("aes256-cbc", new CipherInfo(256, (key, iv) => new AesCipher(key, iv, AesCipherMode.CBC, pkcs7Padding: false))); diff --git a/src/Renci.SshNet/Security/Cryptography/Ciphers/AesGcmCipher.cs b/src/Renci.SshNet/Security/Cryptography/Ciphers/AesGcmCipher.cs index 1018fb668..59a3e1c44 100644 --- a/src/Renci.SshNet/Security/Cryptography/Ciphers/AesGcmCipher.cs +++ b/src/Renci.SshNet/Security/Cryptography/Ciphers/AesGcmCipher.cs @@ -99,16 +99,6 @@ public override byte[] Encrypt(byte[] input, int offset, int length) return output; } - /// - /// Decrypts the first block which is packet length field. - /// - /// The packet length field. - /// The raw packet length field. - public override byte[] Decrypt(byte[] input) - { - return input; - } - /// /// Decrypts the specified input. /// diff --git a/src/Renci.SshNet/Security/Cryptography/Ciphers/ChaCha20Cipher.cs b/src/Renci.SshNet/Security/Cryptography/Ciphers/ChaCha20Cipher.cs index 035cbaf50..13f148f16 100644 --- a/src/Renci.SshNet/Security/Cryptography/Ciphers/ChaCha20Cipher.cs +++ b/src/Renci.SshNet/Security/Cryptography/Ciphers/ChaCha20Cipher.cs @@ -14,9 +14,6 @@ internal sealed class ChaCha20Cipher : StreamCipher private const uint ChachaConst2 = 0x79622d32; private const uint ChachaConst3 = 0x6b206574; - private readonly byte[] _keyStream = new byte[64]; - private int _index; - private uint _s00; private uint _s01; private uint _s02; @@ -132,16 +129,18 @@ private int ProcessBytes(byte[] inputBuffer, int inputOffset, int inputCount, by throw new ArgumentException("output buffer too short"); } + var keyStream = new byte[64]; + var index = 0; for (var i = 0; i < inputCount; i++) { - if (_index == 0) + if (index == 0) { - GenerateKeyStream(_keyStream); + GenerateKeyStream(keyStream); IncrementCounter(); } - outputBuffer[outputOffset + i] = (byte)(_keyStream[_index++] ^ inputBuffer[inputOffset + i]); - _index &= 63; + outputBuffer[outputOffset + i] = (byte)(keyStream[index++] ^ inputBuffer[inputOffset + i]); + index &= 63; } return inputCount; diff --git a/src/Renci.SshNet/Security/Cryptography/Ciphers/ChaCha20Poly1305Cipher.cs b/src/Renci.SshNet/Security/Cryptography/Ciphers/ChaCha20Poly1305Cipher.cs index 1e2295d0c..2a0035a87 100644 --- a/src/Renci.SshNet/Security/Cryptography/Ciphers/ChaCha20Poly1305Cipher.cs +++ b/src/Renci.SshNet/Security/Cryptography/Ciphers/ChaCha20Poly1305Cipher.cs @@ -1,10 +1,9 @@ -#if NET6_0_OR_GREATER -using System; -using System.Buffers.Binary; +using System; using System.Diagnostics; -using System.Security.Cryptography; using Renci.SshNet.Common; +using Renci.SshNet.Messages.Transport; +using Renci.SshNet.Security.Chaos.NaCl.Internal; namespace Renci.SshNet.Security.Cryptography.Ciphers { @@ -12,12 +11,13 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers /// ChaCha20Poly1305 cipher implementation. /// . /// - internal sealed class ChaCha20Poly1305Cipher : SymmetricCipher, IDisposable + internal sealed class ChaCha20Poly1305Cipher : StreamCipher { - private readonly ChaCha20Poly1305 _chacha20poly1305; - private readonly byte[] _sequenceNumber = new byte[12]; private ChaCha20Cipher _aadCipher; + private ChaCha20Cipher _cipher; + private byte[] _polyKeyBytes; + private Array8 _polyKey; /// /// Gets the minimun block size. @@ -51,7 +51,6 @@ public override int TagSize public ChaCha20Poly1305Cipher(byte[] key) : base(key) { - _chacha20poly1305 = new ChaCha20Poly1305(key.AsSpan(0, 32)); } /// @@ -75,29 +74,23 @@ public ChaCha20Poly1305Cipher(byte[] key) /// public override byte[] Encrypt(byte[] input, int offset, int length) { - var packetLengthField = _aadCipher.Encrypt(input, offset, 4); - var plainText = new ReadOnlySpan(input, offset + 4, length - 4); - var output = new byte[length + TagSize]; + + var packetLengthField = _aadCipher.Encrypt(input, offset, 4); Array.Copy(packetLengthField, output, 4); - var cipherText = new Span(output, 4, length - 4); - var tag = new Span(output, length, TagSize); - _chacha20poly1305.Encrypt(nonce: _sequenceNumber, plainText, cipherText, tag, associatedData: packetLengthField); + var cipherText = _cipher.Encrypt(input, offset + 4, length - 4); + Array.Copy(cipherText, 0, output, 4, cipherText.Length); - return output; - } + Poly1305Donna.poly1305_auth(output, length, output, 0, length, ref _polyKey); - internal override void SetSequenceNumber(uint sequenceNumber) - { - BinaryPrimitives.WriteUInt64BigEndian(_sequenceNumber, sequenceNumber); - _aadCipher = new ChaCha20Cipher(Key.Take(32, 32), nonce: _sequenceNumber); + return output; } /// /// Decrypts the first block which is packet length field. /// - /// The encrpted packet length field. + /// The encrypted packet length field. /// The decrypted packet length field. public override byte[] Decrypt(byte[] input) { @@ -127,37 +120,40 @@ public override byte[] Decrypt(byte[] input, int offset, int length) { Debug.Assert(offset == 8, "The offset must be 8"); - var packetLengthField = new ReadOnlySpan(input, 4, 4); - var cipherText = new ReadOnlySpan(input, offset, length); - var tag = new ReadOnlySpan(input, offset + length, TagSize); + var cipherText = input.Take(offset, length); - var output = new byte[length]; - var plainText = new Span(output); + var tag = new byte[TagSize]; + Poly1305Donna.poly1305_auth(tag, 0, input, offset - 4, length + 4, ref _polyKey); +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1_OR_GREATER + if (!System.Security.Cryptography.CryptographicOperations.FixedTimeEquals(tag, new ReadOnlySpan(input, offset + length, TagSize))) +#else + if (!Chaos.NaCl.CryptoBytes.ConstantTimeEquals(tag, 0, input, offset + length, TagSize)) +#endif + { + throw new SshConnectionException("MAC error", DisconnectReason.MacError); + } - _chacha20poly1305.Decrypt(nonce: _sequenceNumber, cipherText, tag, plainText, associatedData: packetLengthField); + var output = _cipher.Decrypt(cipherText); return output; } - /// - /// Dispose the instance. - /// - /// Set to True to dispose of resouces. - public void Dispose(bool disposing) + internal override void SetSequenceNumber(uint sequenceNumber) { - if (disposing) - { - _chacha20poly1305.Dispose(); - } - } + Pack.UInt64ToBigEndian(sequenceNumber, _sequenceNumber, 4); - /// - public void Dispose() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); - GC.SuppressFinalize(this); + _aadCipher = new ChaCha20Cipher(Key.Take(32, 32), nonce: _sequenceNumber); + _cipher = new ChaCha20Cipher(Key.Take(32), nonce: _sequenceNumber); + + _polyKeyBytes = _cipher.Encrypt(new byte[32]); + _polyKey.x0 = Pack.LittleEndianToUInt32(_polyKeyBytes, 0); + _polyKey.x1 = Pack.LittleEndianToUInt32(_polyKeyBytes, 4); + _polyKey.x2 = Pack.LittleEndianToUInt32(_polyKeyBytes, 8); + _polyKey.x3 = Pack.LittleEndianToUInt32(_polyKeyBytes, 12); + _polyKey.x4 = Pack.LittleEndianToUInt32(_polyKeyBytes, 16); + _polyKey.x5 = Pack.LittleEndianToUInt32(_polyKeyBytes, 20); + _polyKey.x6 = Pack.LittleEndianToUInt32(_polyKeyBytes, 24); + _polyKey.x7 = Pack.LittleEndianToUInt32(_polyKeyBytes, 28); } } } -#endif diff --git a/src/Renci.SshNet/Session.cs b/src/Renci.SshNet/Session.cs index 09d222888..7a64405ea 100644 --- a/src/Renci.SshNet/Session.cs +++ b/src/Renci.SshNet/Session.cs @@ -19,6 +19,7 @@ using Renci.SshNet.Messages.Transport; using Renci.SshNet.Security; using Renci.SshNet.Security.Cryptography; +using Renci.SshNet.Security.Cryptography.Ciphers; namespace Renci.SshNet { @@ -1232,6 +1233,7 @@ private Message ReceiveMessage(Socket socket) byte[] data; uint packetLength; + byte[] plainFirstBlock; // avoid reading from socket while IsSocketConnected is attempting to determine whether the // socket is still connected by invoking Socket.Poll(...) and subsequently verifying value of @@ -1246,17 +1248,21 @@ private Message ReceiveMessage(Socket socket) return null; } - if (_serverCipher != null) + plainFirstBlock = firstBlock; + + // First block is not encrypted in AES GCM mode. + if (_serverCipher is not null and not AesGcmCipher) { _serverCipher.SetSequenceNumber(_inboundPacketSequence); + // First block is not encrypted in ETM mode. if (_serverMac == null || !_serverEtm) { - firstBlock = _serverCipher.Decrypt(firstBlock); + plainFirstBlock = _serverCipher.Decrypt(firstBlock); } } - packetLength = Pack.BigEndianToUInt32(firstBlock); + packetLength = Pack.BigEndianToUInt32(plainFirstBlock); // Test packet minimum and maximum boundaries if (packetLength < Math.Max((byte)8, blockSize) - 4 || packetLength > MaximumSshPacketSize - 4) @@ -1282,7 +1288,7 @@ private Message ReceiveMessage(Socket socket) // to read the packet including server MAC in a single pass (except for the initial block). data = new byte[bytesToRead + blockSize + inboundPacketSequenceLength]; Pack.UInt32ToBigEndian(_inboundPacketSequence, data); - Buffer.BlockCopy(firstBlock, 0, data, inboundPacketSequenceLength, firstBlock.Length); + Buffer.BlockCopy(firstBlock, 0, data, inboundPacketSequenceLength, blockSize); if (bytesToRead > 0) { @@ -1324,6 +1330,7 @@ private Message ReceiveMessage(Socket socket) // validate decrypted message against MAC if (_serverMac != null && !_serverEtm) { + Buffer.BlockCopy(plainFirstBlock, 0, data, inboundPacketSequenceLength, blockSize); var clientHash = _serverMac.ComputeHash(data, 0, data.Length - serverMacLength); #if NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1_OR_GREATER if (!CryptographicOperations.FixedTimeEquals(clientHash, new ReadOnlySpan(data, data.Length - serverMacLength, serverMacLength))) From 9983a31dfdee18af08ad5a841093f4ff4047dcf6 Mon Sep 17 00:00:00 2001 From: Scott Xu Date: Mon, 27 May 2024 01:38:37 +0800 Subject: [PATCH 07/18] Fix build. Fix typo. Update README --- README.md | 2 +- src/Renci.SshNet/Common/Pack.cs | 7 +++++++ src/Renci.SshNet/Security/Cryptography/Cipher.cs | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ae0a9fadb..441f2e6a7 100644 --- a/README.md +++ b/README.md @@ -38,12 +38,12 @@ the missing test once you figure things out. 🤓 ## Encryption Method **SSH.NET** supports the following encryption methods: -* chacha20-poly1305@openssh.com (.NET 6 and higher) * aes128-ctr * aes192-ctr * aes256-ctr * aes128-gcm@openssh.com (.NET 6 and higher) * aes256-gcm@openssh.com (.NET 6 and higher) +* chacha20-poly1305@openssh.com (.NET 6 and higher) * aes128-cbc * aes192-cbc * aes256-cbc diff --git a/src/Renci.SshNet/Common/Pack.cs b/src/Renci.SshNet/Common/Pack.cs index 69102c170..27b870283 100644 --- a/src/Renci.SshNet/Common/Pack.cs +++ b/src/Renci.SshNet/Common/Pack.cs @@ -205,6 +205,13 @@ internal static byte[] UInt32ToBigEndian(uint value) return buffer; } + internal static byte[] UInt64ToBigEndian(ulong value) + { + var buffer = new byte[8]; + UInt64ToBigEndian(value, buffer, offset: 0); + return buffer; + } + internal static void UInt64ToBigEndian(ulong value, byte[] buffer, int offset) { #if NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER diff --git a/src/Renci.SshNet/Security/Cryptography/Cipher.cs b/src/Renci.SshNet/Security/Cryptography/Cipher.cs index c91a5f673..a37240c14 100644 --- a/src/Renci.SshNet/Security/Cryptography/Cipher.cs +++ b/src/Renci.SshNet/Security/Cryptography/Cipher.cs @@ -23,7 +23,7 @@ public abstract class Cipher public virtual int TagSize { get; } /// - /// Sets the sequence numbner. + /// Sets the sequence number. /// /// The sequence number. internal virtual void SetSequenceNumber(uint sequenceNumber) From 920d681e6cde2d38cbd7a8845eb017c90140a3c8 Mon Sep 17 00:00:00 2001 From: Scott Xu Date: Mon, 27 May 2024 01:48:23 +0800 Subject: [PATCH 08/18] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 441f2e6a7..1d157fdde 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ the missing test once you figure things out. 🤓 * aes256-ctr * aes128-gcm@openssh.com (.NET 6 and higher) * aes256-gcm@openssh.com (.NET 6 and higher) -* chacha20-poly1305@openssh.com (.NET 6 and higher) +* chacha20-poly1305@openssh.com * aes128-cbc * aes192-cbc * aes256-cbc From bea3b4838c5211ae1e62c3f4281bfa613ea4aa95 Mon Sep 17 00:00:00 2001 From: Scott Xu Date: Mon, 27 May 2024 07:35:08 +0800 Subject: [PATCH 09/18] Fix build --- src/Renci.SshNet/Session.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Renci.SshNet/Session.cs b/src/Renci.SshNet/Session.cs index 7a64405ea..d4bf65980 100644 --- a/src/Renci.SshNet/Session.cs +++ b/src/Renci.SshNet/Session.cs @@ -19,7 +19,6 @@ using Renci.SshNet.Messages.Transport; using Renci.SshNet.Security; using Renci.SshNet.Security.Cryptography; -using Renci.SshNet.Security.Cryptography.Ciphers; namespace Renci.SshNet { @@ -1251,7 +1250,11 @@ private Message ReceiveMessage(Socket socket) plainFirstBlock = firstBlock; // First block is not encrypted in AES GCM mode. - if (_serverCipher is not null and not AesGcmCipher) + if (_serverCipher is not null +#if NET6_0_OR_GREATER + and not Security.Cryptography.Ciphers.AesGcmCipher +#endif + ) { _serverCipher.SetSequenceNumber(_inboundPacketSequence); From ec84664b824d369853ee87848fbabdee4f50c42c Mon Sep 17 00:00:00 2001 From: Scott Xu Date: Mon, 27 May 2024 08:17:24 +0800 Subject: [PATCH 10/18] Remove trailing whitespace --- src/Renci.SshNet/Session.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Renci.SshNet/Session.cs b/src/Renci.SshNet/Session.cs index d4bf65980..16f8fce2e 100644 --- a/src/Renci.SshNet/Session.cs +++ b/src/Renci.SshNet/Session.cs @@ -1252,7 +1252,7 @@ private Message ReceiveMessage(Socket socket) // First block is not encrypted in AES GCM mode. if (_serverCipher is not null #if NET6_0_OR_GREATER - and not Security.Cryptography.Ciphers.AesGcmCipher + and not Security.Cryptography.Ciphers.AesGcmCipher #endif ) { From eecffe623d55f34c69566392215d43513acbc5bc Mon Sep 17 00:00:00 2001 From: Scott Xu Date: Mon, 27 May 2024 08:58:07 +0800 Subject: [PATCH 11/18] Fix build --- src/Renci.SshNet/Session.cs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/Renci.SshNet/Session.cs b/src/Renci.SshNet/Session.cs index 16f8fce2e..8f27b30d9 100644 --- a/src/Renci.SshNet/Session.cs +++ b/src/Renci.SshNet/Session.cs @@ -1232,7 +1232,6 @@ private Message ReceiveMessage(Socket socket) byte[] data; uint packetLength; - byte[] plainFirstBlock; // avoid reading from socket while IsSocketConnected is attempting to determine whether the // socket is still connected by invoking Socket.Poll(...) and subsequently verifying value of @@ -1247,7 +1246,7 @@ private Message ReceiveMessage(Socket socket) return null; } - plainFirstBlock = firstBlock; + var plainFirstBlock = firstBlock; // First block is not encrypted in AES GCM mode. if (_serverCipher is not null @@ -1291,7 +1290,16 @@ and not Security.Cryptography.Ciphers.AesGcmCipher // to read the packet including server MAC in a single pass (except for the initial block). data = new byte[bytesToRead + blockSize + inboundPacketSequenceLength]; Pack.UInt32ToBigEndian(_inboundPacketSequence, data); - Buffer.BlockCopy(firstBlock, 0, data, inboundPacketSequenceLength, blockSize); + + // Use raw packet length field to calculate the mac in AEAD mode. + if (_serverAead) + { + Buffer.BlockCopy(firstBlock, 0, data, inboundPacketSequenceLength, blockSize); + } + else + { + Buffer.BlockCopy(plainFirstBlock, 0, data, inboundPacketSequenceLength, blockSize); + } if (bytesToRead > 0) { @@ -1333,7 +1341,6 @@ and not Security.Cryptography.Ciphers.AesGcmCipher // validate decrypted message against MAC if (_serverMac != null && !_serverEtm) { - Buffer.BlockCopy(plainFirstBlock, 0, data, inboundPacketSequenceLength, blockSize); var clientHash = _serverMac.ComputeHash(data, 0, data.Length - serverMacLength); #if NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1_OR_GREATER if (!CryptographicOperations.FixedTimeEquals(clientHash, new ReadOnlySpan(data, data.Length - serverMacLength, serverMacLength))) From 898cf0824f6fbf96f2a7575b8cbf98f2e44bb167 Mon Sep 17 00:00:00 2001 From: Scott Xu Date: Wed, 17 Jul 2024 18:20:29 +0800 Subject: [PATCH 12/18] Change to BouncyCastle --- .../Cryptography/Ciphers/ChaCha20Cipher.cs | 240 ------------------ .../Ciphers/ChaCha20Poly1305Cipher.cs | 68 +++-- src/Renci.SshNet/Session.cs | 14 +- .../Ciphers/ChaCha20CipherTest.cs | 190 -------------- 4 files changed, 37 insertions(+), 475 deletions(-) delete mode 100644 src/Renci.SshNet/Security/Cryptography/Ciphers/ChaCha20Cipher.cs delete mode 100644 test/Renci.SshNet.Tests/Classes/Security/Cryptography/Ciphers/ChaCha20CipherTest.cs diff --git a/src/Renci.SshNet/Security/Cryptography/Ciphers/ChaCha20Cipher.cs b/src/Renci.SshNet/Security/Cryptography/Ciphers/ChaCha20Cipher.cs deleted file mode 100644 index 13f148f16..000000000 --- a/src/Renci.SshNet/Security/Cryptography/Ciphers/ChaCha20Cipher.cs +++ /dev/null @@ -1,240 +0,0 @@ -using System; - -using Renci.SshNet.Common; - -namespace Renci.SshNet.Security.Cryptography.Ciphers -{ - /// - /// Implements ChaCha20 cipher algorithm. - /// - internal sealed class ChaCha20Cipher : StreamCipher - { - private const uint ChachaConst0 = 0x61707865; - private const uint ChachaConst1 = 0x3320646e; - private const uint ChachaConst2 = 0x79622d32; - private const uint ChachaConst3 = 0x6b206574; - - private uint _s00; - private uint _s01; - private uint _s02; - private uint _s03; - private uint _s04; - private uint _s05; - private uint _s06; - private uint _s07; - private uint _s08; - private uint _s09; - private uint _s10; - private uint _s11; - private uint _s12; - private uint _s13; - private uint _s14; - private uint _s15; - - /// - /// Gets the minimum data size. - /// - /// - /// The minimum data size. - /// - public override byte MinimumSize - { - get - { - return 0; - } - } - - /// - /// Initializes a new instance of the class. - /// - /// The key. - /// The nonce. - /// The init counter. - public ChaCha20Cipher(byte[] key, byte[] nonce, uint counter = 0) - : base(key) - { - var keySize = key.Length * 8; - - if (keySize is not 256) - { - throw new ArgumentException(string.Format("KeySize '{0}' is not valid for this algorithm.", keySize), nameof(key)); - } - - if (nonce is null) - { - throw new ArgumentNullException(nameof(nonce)); - } - - var nonceSize = nonce.Length * 8; - - if (nonceSize is not 96) - { - throw new ArgumentNullException(string.Format("NonceSize '{0}' is not valid for this algorithm.", nonceSize), nameof(nonce)); - } - - SetState(key, nonce, counter); - } - - /// - /// Encrypts the specified input. - /// - /// The input. - /// The zero-based offset in at which to begin encrypting. - /// The number of bytes to encrypt from . - /// - /// Encrypted data. - /// - public override byte[] Encrypt(byte[] input, int offset, int length) - { - var output = new byte[length]; - _ = ProcessBytes(input, offset, length, output, 0); - return output; - } - - /// - /// Decrypts the specified input. - /// - /// The input. - /// The zero-based offset in at which to begin decrypting. - /// The number of bytes to decrypt from . - /// - /// The decrypted data. - /// - public override byte[] Decrypt(byte[] input, int offset, int length) - { - return Encrypt(input, offset, length); - } - - private void IncrementCounter() - { - unchecked - { - if (++_s12 == 0) - { - ++_s13; - } - } - } - - private int ProcessBytes(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset) - { - if ((inputOffset + inputCount) > inputBuffer.Length) - { - throw new ArgumentException("input buffer too short"); - } - - if ((outputOffset + inputCount) > outputBuffer.Length) - { - throw new ArgumentException("output buffer too short"); - } - - var keyStream = new byte[64]; - var index = 0; - for (var i = 0; i < inputCount; i++) - { - if (index == 0) - { - GenerateKeyStream(keyStream); - IncrementCounter(); - } - - outputBuffer[outputOffset + i] = (byte)(keyStream[index++] ^ inputBuffer[inputOffset + i]); - index &= 63; - } - - return inputCount; - } - - private void GenerateKeyStream(byte[] output) - { - var s00 = _s00; - var s01 = _s01; - var s02 = _s02; - var s03 = _s03; - var s04 = _s04; - var s05 = _s05; - var s06 = _s06; - var s07 = _s07; - var s08 = _s08; - var s09 = _s09; - var s10 = _s10; - var s11 = _s11; - var s12 = _s12; - var s13 = _s13; - var s14 = _s14; - var s15 = _s15; - - static void QuarterRound(ref uint a, ref uint b, ref uint c, ref uint d) - { - a += b; - d ^= a; - d = d << 16 | d >> 16; - c += d; - b ^= c; - b = b << 12 | b >> 20; - a += b; - d ^= a; - d = d << 8 | d >> 24; - c += d; - b ^= c; - b = b << 7 | b >> 25; - } - - unchecked - { - for (var i = 20; i > 0; i -= 2) - { - QuarterRound(ref s00, ref s04, ref s08, ref s12); - QuarterRound(ref s01, ref s05, ref s09, ref s13); - QuarterRound(ref s02, ref s06, ref s10, ref s14); - QuarterRound(ref s03, ref s07, ref s11, ref s15); - QuarterRound(ref s00, ref s05, ref s10, ref s15); - QuarterRound(ref s01, ref s06, ref s11, ref s12); - QuarterRound(ref s02, ref s07, ref s08, ref s13); - QuarterRound(ref s03, ref s04, ref s09, ref s14); - } - - Pack.UInt32ToLittleEndian(_s00 + s00, output, 0); - Pack.UInt32ToLittleEndian(_s01 + s01, output, 4); - Pack.UInt32ToLittleEndian(_s02 + s02, output, 8); - Pack.UInt32ToLittleEndian(_s03 + s03, output, 12); - Pack.UInt32ToLittleEndian(_s04 + s04, output, 16); - Pack.UInt32ToLittleEndian(_s05 + s05, output, 20); - Pack.UInt32ToLittleEndian(_s06 + s06, output, 24); - Pack.UInt32ToLittleEndian(_s07 + s07, output, 28); - Pack.UInt32ToLittleEndian(_s08 + s08, output, 32); - Pack.UInt32ToLittleEndian(_s09 + s09, output, 36); - Pack.UInt32ToLittleEndian(_s10 + s10, output, 40); - Pack.UInt32ToLittleEndian(_s11 + s11, output, 44); - Pack.UInt32ToLittleEndian(_s12 + s12, output, 48); - Pack.UInt32ToLittleEndian(_s13 + s13, output, 52); - Pack.UInt32ToLittleEndian(_s14 + s14, output, 56); - Pack.UInt32ToLittleEndian(_s15 + s15, output, 60); - } - } - - private void SetState(byte[] key, byte[] nonce, uint counter) - { - _s00 = ChachaConst0; - _s01 = ChachaConst1; - _s02 = ChachaConst2; - _s03 = ChachaConst3; - - _s04 = Pack.LittleEndianToUInt32(key, 0); - _s05 = Pack.LittleEndianToUInt32(key, 4); - _s06 = Pack.LittleEndianToUInt32(key, 8); - _s07 = Pack.LittleEndianToUInt32(key, 12); - _s08 = Pack.LittleEndianToUInt32(key, 16); - _s09 = Pack.LittleEndianToUInt32(key, 20); - _s10 = Pack.LittleEndianToUInt32(key, 24); - _s11 = Pack.LittleEndianToUInt32(key, 28); - - _s12 = counter; - - _s13 = Pack.LittleEndianToUInt32(nonce, 0); - _s14 = Pack.LittleEndianToUInt32(nonce, 4); - _s15 = Pack.LittleEndianToUInt32(nonce, 8); - } - } -} diff --git a/src/Renci.SshNet/Security/Cryptography/Ciphers/ChaCha20Poly1305Cipher.cs b/src/Renci.SshNet/Security/Cryptography/Ciphers/ChaCha20Poly1305Cipher.cs index 2a0035a87..4be424f6c 100644 --- a/src/Renci.SshNet/Security/Cryptography/Ciphers/ChaCha20Poly1305Cipher.cs +++ b/src/Renci.SshNet/Security/Cryptography/Ciphers/ChaCha20Poly1305Cipher.cs @@ -1,9 +1,14 @@ using System; +using System.Buffers.Binary; using System.Diagnostics; +using Org.BouncyCastle.Crypto.Engines; +using Org.BouncyCastle.Crypto.Macs; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Utilities; + using Renci.SshNet.Common; using Renci.SshNet.Messages.Transport; -using Renci.SshNet.Security.Chaos.NaCl.Internal; namespace Renci.SshNet.Security.Cryptography.Ciphers { @@ -14,10 +19,9 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers internal sealed class ChaCha20Poly1305Cipher : StreamCipher { private readonly byte[] _sequenceNumber = new byte[12]; - private ChaCha20Cipher _aadCipher; - private ChaCha20Cipher _cipher; - private byte[] _polyKeyBytes; - private Array8 _polyKey; + private readonly ChaCha7539Engine _aadCipher = new ChaCha7539Engine(); + private readonly ChaCha7539Engine _cipher = new ChaCha7539Engine(); + private readonly Poly1305 _mac = new Poly1305(); /// /// Gets the minimun block size. @@ -76,13 +80,11 @@ public override byte[] Encrypt(byte[] input, int offset, int length) { var output = new byte[length + TagSize]; - var packetLengthField = _aadCipher.Encrypt(input, offset, 4); - Array.Copy(packetLengthField, output, 4); - - var cipherText = _cipher.Encrypt(input, offset + 4, length - 4); - Array.Copy(cipherText, 0, output, 4, cipherText.Length); + _aadCipher.ProcessBytes(input, offset, 4, output, 0); + _cipher.ProcessBytes(input, offset + 4, length - 4, output, 4); - Poly1305Donna.poly1305_auth(output, length, output, 0, length, ref _polyKey); + _mac.BlockUpdate(output, 0, length); + _ = _mac.DoFinal(output, length); return output; } @@ -94,7 +96,10 @@ public override byte[] Encrypt(byte[] input, int offset, int length) /// The decrypted packet length field. public override byte[] Decrypt(byte[] input) { - return _aadCipher.Decrypt(input); + var output = new byte[input.Length]; + _aadCipher.ProcessBytes(input, 0, input.Length, output, 0); + + return output; } /// @@ -120,40 +125,33 @@ public override byte[] Decrypt(byte[] input, int offset, int length) { Debug.Assert(offset == 8, "The offset must be 8"); - var cipherText = input.Take(offset, length); - var tag = new byte[TagSize]; - Poly1305Donna.poly1305_auth(tag, 0, input, offset - 4, length + 4, ref _polyKey); -#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1_OR_GREATER - if (!System.Security.Cryptography.CryptographicOperations.FixedTimeEquals(tag, new ReadOnlySpan(input, offset + length, TagSize))) -#else - if (!Chaos.NaCl.CryptoBytes.ConstantTimeEquals(tag, 0, input, offset + length, TagSize)) -#endif + _mac.BlockUpdate(input, offset - 4, length + 4); + _ = _mac.DoFinal(tag, 0); + if (!Arrays.FixedTimeEquals(TagSize, tag, 0, input, offset + length)) { throw new SshConnectionException("MAC error", DisconnectReason.MacError); } - var output = _cipher.Decrypt(cipherText); + var output = new byte[length]; + _cipher.ProcessBytes(input, offset, length, output, 0); return output; } internal override void SetSequenceNumber(uint sequenceNumber) { - Pack.UInt64ToBigEndian(sequenceNumber, _sequenceNumber, 4); - - _aadCipher = new ChaCha20Cipher(Key.Take(32, 32), nonce: _sequenceNumber); - _cipher = new ChaCha20Cipher(Key.Take(32), nonce: _sequenceNumber); - - _polyKeyBytes = _cipher.Encrypt(new byte[32]); - _polyKey.x0 = Pack.LittleEndianToUInt32(_polyKeyBytes, 0); - _polyKey.x1 = Pack.LittleEndianToUInt32(_polyKeyBytes, 4); - _polyKey.x2 = Pack.LittleEndianToUInt32(_polyKeyBytes, 8); - _polyKey.x3 = Pack.LittleEndianToUInt32(_polyKeyBytes, 12); - _polyKey.x4 = Pack.LittleEndianToUInt32(_polyKeyBytes, 16); - _polyKey.x5 = Pack.LittleEndianToUInt32(_polyKeyBytes, 20); - _polyKey.x6 = Pack.LittleEndianToUInt32(_polyKeyBytes, 24); - _polyKey.x7 = Pack.LittleEndianToUInt32(_polyKeyBytes, 28); + BinaryPrimitives.WriteUInt64BigEndian(_sequenceNumber.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), _sequenceNumber)); + _cipher.Init(forEncryption: true, new ParametersWithIV(new KeyParameter(Key, 0, 32), _sequenceNumber)); + + var polyKeyBytes = new byte[32]; + _cipher.ProcessBytes(new byte[32], 0, 32, polyKeyBytes, 0); + _mac.Init(new KeyParameter(polyKeyBytes)); } } } diff --git a/src/Renci.SshNet/Session.cs b/src/Renci.SshNet/Session.cs index d0adaabd2..a20914fcc 100644 --- a/src/Renci.SshNet/Session.cs +++ b/src/Renci.SshNet/Session.cs @@ -9,6 +9,8 @@ using System.Threading; using System.Threading.Tasks; +using Org.BouncyCastle.Utilities; + using Renci.SshNet.Abstractions; using Renci.SshNet.Channels; using Renci.SshNet.Common; @@ -1315,11 +1317,7 @@ and not Security.Cryptography.Ciphers.AesGcmCipher if (_serverMac != null && _serverEtm) { var clientHash = _serverMac.ComputeHash(data, 0, data.Length - serverMacLength); -#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1_OR_GREATER - if (!CryptographicOperations.FixedTimeEquals(clientHash, new ReadOnlySpan(data, data.Length - serverMacLength, serverMacLength))) -#else - if (!Security.Chaos.NaCl.CryptoBytes.ConstantTimeEquals(clientHash, 0, data, data.Length - serverMacLength, serverMacLength)) -#endif + if (!Arrays.FixedTimeEquals(serverMacLength, clientHash, 0, data, data.Length - serverMacLength)) { throw new SshConnectionException("MAC error", DisconnectReason.MacError); } @@ -1343,11 +1341,7 @@ and not Security.Cryptography.Ciphers.AesGcmCipher if (_serverMac != null && !_serverEtm) { var clientHash = _serverMac.ComputeHash(data, 0, data.Length - serverMacLength); -#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1_OR_GREATER - if (!CryptographicOperations.FixedTimeEquals(clientHash, new ReadOnlySpan(data, data.Length - serverMacLength, serverMacLength))) -#else - if (!Security.Chaos.NaCl.CryptoBytes.ConstantTimeEquals(clientHash, 0, data, data.Length - serverMacLength, serverMacLength)) -#endif + if (!Arrays.FixedTimeEquals(serverMacLength, clientHash, 0, data, data.Length - serverMacLength)) { throw new SshConnectionException("MAC error", DisconnectReason.MacError); } diff --git a/test/Renci.SshNet.Tests/Classes/Security/Cryptography/Ciphers/ChaCha20CipherTest.cs b/test/Renci.SshNet.Tests/Classes/Security/Cryptography/Ciphers/ChaCha20CipherTest.cs deleted file mode 100644 index ef240b9d2..000000000 --- a/test/Renci.SshNet.Tests/Classes/Security/Cryptography/Ciphers/ChaCha20CipherTest.cs +++ /dev/null @@ -1,190 +0,0 @@ -using System.Linq; - -using Microsoft.VisualStudio.TestTools.UnitTesting; - -using Renci.SshNet.Security.Cryptography.Ciphers; -using Renci.SshNet.Tests.Common; - -namespace Renci.SshNet.Tests.Classes.Security.Cryptography.Ciphers -{ - /// - /// - /// - [TestClass] - public class ChaCha20CipherTest : TestBase - { - [TestMethod] - public void Test_Cipher_ChaCha20() - { - var input1 = new byte[] - { - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0 - }; - - var key1 = new byte[] - { - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0 - }; - - var nonce1 = new byte[] - { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - }; - - uint counter1 = 0; - - var expected1 = new byte[] - { - 0x76, 0xb8, 0xe0, 0xad, 0xa0, 0xf1, 0x3d, 0x90, - 0x40, 0x5d, 0x6a, 0xe5, 0x53, 0x86, 0xbd, 0x28, - 0xbd, 0xd2, 0x19, 0xb8, 0xa0, 0x8d, 0xed, 0x1a, - 0xa8, 0x36, 0xef, 0xcc, 0x8b, 0x77, 0x0d, 0xc7, - 0xda, 0x41, 0x59, 0x7c, 0x51, 0x57, 0x48, 0x8d, - 0x77, 0x24, 0xe0, 0x3f, 0xb8, 0xd8, 0x4a, 0x37, - 0x6a, 0x43, 0xb8, 0xf4, 0x15, 0x18, 0xa1, 0x1c, - 0xc3, 0x87, 0xb6, 0x69, 0xb2, 0xee, 0x65, 0x86 - }; - - var input2 = new byte[] - { - 0x4c, 0x61, 0x64, 0x69, 0x65, 0x73, 0x20, 0x61, - 0x6e, 0x64, 0x20, 0x47, 0x65, 0x6e, 0x74, 0x6c, - 0x65, 0x6d, 0x65, 0x6e, 0x20, 0x6f, 0x66, 0x20, - 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x61, 0x73, - 0x73, 0x20, 0x6f, 0x66, 0x20, 0x27, 0x39, 0x39, - 0x3a, 0x20, 0x49, 0x66, 0x20, 0x49, 0x20, 0x63, - 0x6f, 0x75, 0x6c, 0x64, 0x20, 0x6f, 0x66, 0x66, - 0x65, 0x72, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x6f, - 0x6e, 0x6c, 0x79, 0x20, 0x6f, 0x6e, 0x65, 0x20, - 0x74, 0x69, 0x70, 0x20, 0x66, 0x6f, 0x72, 0x20, - 0x74, 0x68, 0x65, 0x20, 0x66, 0x75, 0x74, 0x75, - 0x72, 0x65, 0x2c, 0x20, 0x73, 0x75, 0x6e, 0x73, - 0x63, 0x72, 0x65, 0x65, 0x6e, 0x20, 0x77, 0x6f, - 0x75, 0x6c, 0x64, 0x20, 0x62, 0x65, 0x20, 0x69, - 0x74, 0x2e - }; - - var key2 = new byte[] - { - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, - 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, - 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, - 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f - }; - - var nonce2 = new byte[] - { - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x00 - }; - - uint counter2 = 1; - - var expected2 = new byte[] - { - 0x6e, 0x2e, 0x35, 0x9a, 0x25, 0x68, 0xf9, 0x80, - 0x41, 0xba, 0x07, 0x28, 0xdd, 0x0d, 0x69, 0x81, - 0xe9, 0x7e, 0x7a, 0xec, 0x1d, 0x43, 0x60, 0xc2, - 0x0a, 0x27, 0xaf, 0xcc, 0xfd, 0x9f, 0xae, 0x0b, - 0xf9, 0x1b, 0x65, 0xc5, 0x52, 0x47, 0x33, 0xab, - 0x8f, 0x59, 0x3d, 0xab, 0xcd, 0x62, 0xb3, 0x57, - 0x16, 0x39, 0xd6, 0x24, 0xe6, 0x51, 0x52, 0xab, - 0x8f, 0x53, 0x0c, 0x35, 0x9f, 0x08, 0x61, 0xd8, - 0x07, 0xca, 0x0d, 0xbf, 0x50, 0x0d, 0x6a, 0x61, - 0x56, 0xa3, 0x8e, 0x08, 0x8a, 0x22, 0xb6, 0x5e, - 0x52, 0xbc, 0x51, 0x4d, 0x16, 0xcc, 0xf8, 0x06, - 0x81, 0x8c, 0xe9, 0x1a, 0xb7, 0x79, 0x37, 0x36, - 0x5a, 0xf9, 0x0b, 0xbf, 0x74, 0xa3, 0x5b, 0xe6, - 0xb4, 0x0b, 0x8e, 0xed, 0xf2, 0x78, 0x5e, 0x42, - 0x87, 0x4d - }; - - var input3 = new byte[] - { - 0x27, 0x54, 0x77, 0x61, 0x73, 0x20, 0x62, 0x72, - 0x69, 0x6c, 0x6c, 0x69, 0x67, 0x2c, 0x20, 0x61, - 0x6e, 0x64, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, - 0x6c, 0x69, 0x74, 0x68, 0x79, 0x20, 0x74, 0x6f, - 0x76, 0x65, 0x73, 0x0a, 0x44, 0x69, 0x64, 0x20, - 0x67, 0x79, 0x72, 0x65, 0x20, 0x61, 0x6e, 0x64, - 0x20, 0x67, 0x69, 0x6d, 0x62, 0x6c, 0x65, 0x20, - 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x77, - 0x61, 0x62, 0x65, 0x3a, 0x0a, 0x41, 0x6c, 0x6c, - 0x20, 0x6d, 0x69, 0x6d, 0x73, 0x79, 0x20, 0x77, - 0x65, 0x72, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, - 0x62, 0x6f, 0x72, 0x6f, 0x67, 0x6f, 0x76, 0x65, - 0x73, 0x2c, 0x0a, 0x41, 0x6e, 0x64, 0x20, 0x74, - 0x68, 0x65, 0x20, 0x6d, 0x6f, 0x6d, 0x65, 0x20, - 0x72, 0x61, 0x74, 0x68, 0x73, 0x20, 0x6f, 0x75, - 0x74, 0x67, 0x72, 0x61, 0x62, 0x65, 0x2e - }; - - var key3 = new byte[] - { - 0x1c, 0x92, 0x40, 0xa5, 0xeb, 0x55, 0xd3, 0x8a, - 0xf3, 0x33, 0x88, 0x86, 0x04, 0xf6, 0xb5, 0xf0, - 0x47, 0x39, 0x17, 0xc1, 0x40, 0x2b, 0x80, 0x09, - 0x9d, 0xca, 0x5c, 0xbc, 0x20, 0x70, 0x75, 0xc0 - }; - - var nonce3 = new byte[] - { - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02 - }; - - uint counter3 = 42; - - var expected3 = new byte[] - { - 0x62, 0xe6, 0x34, 0x7f, 0x95, 0xed, 0x87, 0xa4, - 0x5f, 0xfa, 0xe7, 0x42, 0x6f, 0x27, 0xa1, 0xdf, - 0x5f, 0xb6, 0x91, 0x10, 0x04, 0x4c, 0x0d, 0x73, - 0x11, 0x8e, 0xff, 0xa9, 0x5b, 0x01, 0xe5, 0xcf, - 0x16, 0x6d, 0x3d, 0xf2, 0xd7, 0x21, 0xca, 0xf9, - 0xb2, 0x1e, 0x5f, 0xb1, 0x4c, 0x61, 0x68, 0x71, - 0xfd, 0x84, 0xc5, 0x4f, 0x9d, 0x65, 0xb2, 0x83, - 0x19, 0x6c, 0x7f, 0xe4, 0xf6, 0x05, 0x53, 0xeb, - 0xf3, 0x9c, 0x64, 0x02, 0xc4, 0x22, 0x34, 0xe3, - 0x2a, 0x35, 0x6b, 0x3e, 0x76, 0x43, 0x12, 0xa6, - 0x1a, 0x55, 0x32, 0x05, 0x57, 0x16, 0xea, 0xd6, - 0x96, 0x25, 0x68, 0xf8, 0x7d, 0x3f, 0x3f, 0x77, - 0x04, 0xc6, 0xa8, 0xd1, 0xbc, 0xd1, 0xbf, 0x4d, - 0x50, 0xd6, 0x15, 0x4b, 0x6d, 0xa7, 0x31, 0xb1, - 0x87, 0xb5, 0x8d, 0xfd, 0x72, 0x8a, 0xfa, 0x36, - 0x75, 0x7a, 0x79, 0x7a, 0xc1, 0x88, 0xd1 - }; - - var cipher1 = new ChaCha20Cipher(key1, nonce1, counter1); - var cipher2 = new ChaCha20Cipher(key2, nonce2, counter2); - var cipher3 = new ChaCha20Cipher(key3, nonce3, counter3); - - var output1 = cipher1.Encrypt(input1); - var output2 = cipher2.Encrypt(input2); - var output3 = cipher3.Encrypt(input3); - - if (!output1.SequenceEqual(expected1)) - { - Assert.Fail("Invalid encryption"); - } - - if (!output2.SequenceEqual(expected2)) - { - Assert.Fail("Invalid encryption"); - } - - if (!output3.SequenceEqual(expected3)) - { - Assert.Fail("Invalid encryption"); - } - } - } -} From b600dd248f6af070e0f62086cce7cd22e9f29a58 Mon Sep 17 00:00:00 2001 From: Scott Xu Date: Wed, 17 Jul 2024 18:41:05 +0800 Subject: [PATCH 13/18] Inherit from SymmetricCipher instead of StreamCipher since StreamCipher is deleted --- src/Renci.SshNet/Security/Cryptography/Ciphers/AesGcmCipher.cs | 2 +- .../Security/Cryptography/Ciphers/ChaCha20Poly1305Cipher.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Renci.SshNet/Security/Cryptography/Ciphers/AesGcmCipher.cs b/src/Renci.SshNet/Security/Cryptography/Ciphers/AesGcmCipher.cs index 59a3e1c44..cd673d04a 100644 --- a/src/Renci.SshNet/Security/Cryptography/Ciphers/AesGcmCipher.cs +++ b/src/Renci.SshNet/Security/Cryptography/Ciphers/AesGcmCipher.cs @@ -12,7 +12,7 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers /// AES GCM cipher implementation. /// . /// - internal sealed class AesGcmCipher : StreamCipher, IDisposable + internal sealed class AesGcmCipher : SymmetricCipher, IDisposable { private readonly byte[] _iv; private readonly AesGcm _aesGcm; diff --git a/src/Renci.SshNet/Security/Cryptography/Ciphers/ChaCha20Poly1305Cipher.cs b/src/Renci.SshNet/Security/Cryptography/Ciphers/ChaCha20Poly1305Cipher.cs index 4be424f6c..4301d28fe 100644 --- a/src/Renci.SshNet/Security/Cryptography/Ciphers/ChaCha20Poly1305Cipher.cs +++ b/src/Renci.SshNet/Security/Cryptography/Ciphers/ChaCha20Poly1305Cipher.cs @@ -16,7 +16,7 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers /// ChaCha20Poly1305 cipher implementation. /// . /// - internal sealed class ChaCha20Poly1305Cipher : StreamCipher + internal sealed class ChaCha20Poly1305Cipher : SymmetricCipher { private readonly byte[] _sequenceNumber = new byte[12]; private readonly ChaCha7539Engine _aadCipher = new ChaCha7539Engine(); From 5dbb8d7aa8d7e5a3235d172e0275d36dd19774f5 Mon Sep 17 00:00:00 2001 From: Scott Xu Date: Wed, 17 Jul 2024 21:02:34 +0800 Subject: [PATCH 14/18] Resolve conflicts --- .../Security/Cryptography/Ciphers/ChaCha20Poly1305Cipher.cs | 1 + src/Renci.SshNet/Session.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Renci.SshNet/Security/Cryptography/Ciphers/ChaCha20Poly1305Cipher.cs b/src/Renci.SshNet/Security/Cryptography/Ciphers/ChaCha20Poly1305Cipher.cs index 4301d28fe..ffb3c651c 100644 --- a/src/Renci.SshNet/Security/Cryptography/Ciphers/ChaCha20Poly1305Cipher.cs +++ b/src/Renci.SshNet/Security/Cryptography/Ciphers/ChaCha20Poly1305Cipher.cs @@ -151,6 +151,7 @@ internal override void SetSequenceNumber(uint sequenceNumber) var polyKeyBytes = new byte[32]; _cipher.ProcessBytes(new byte[32], 0, 32, polyKeyBytes, 0); + _cipher.ProcessBytes(new byte[32], 0, 32, new byte[32], 0); // this ProcessBytes is required to set the block counter of ChaCha to 1 _mac.Init(new KeyParameter(polyKeyBytes)); } } diff --git a/src/Renci.SshNet/Session.cs b/src/Renci.SshNet/Session.cs index a20914fcc..834a4cb49 100644 --- a/src/Renci.SshNet/Session.cs +++ b/src/Renci.SshNet/Session.cs @@ -1267,7 +1267,7 @@ and not Security.Cryptography.Ciphers.AesGcmCipher } } - packetLength = BinaryPrimitives.ReadUInt32BigEndian(firstBlock); + packetLength = BinaryPrimitives.ReadUInt32BigEndian(plainFirstBlock); // Test packet minimum and maximum boundaries if (packetLength < Math.Max((byte)8, blockSize) - 4 || packetLength > MaximumSshPacketSize - 4) From 15be6bc44b72cec748101430567620e8f234529f Mon Sep 17 00:00:00 2001 From: Scott Xu Date: Wed, 17 Jul 2024 21:17:56 +0800 Subject: [PATCH 15/18] Move field to local variable --- .../Cryptography/Ciphers/ChaCha20Poly1305Cipher.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Renci.SshNet/Security/Cryptography/Ciphers/ChaCha20Poly1305Cipher.cs b/src/Renci.SshNet/Security/Cryptography/Ciphers/ChaCha20Poly1305Cipher.cs index ffb3c651c..e92ab059a 100644 --- a/src/Renci.SshNet/Security/Cryptography/Ciphers/ChaCha20Poly1305Cipher.cs +++ b/src/Renci.SshNet/Security/Cryptography/Ciphers/ChaCha20Poly1305Cipher.cs @@ -18,7 +18,6 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers /// internal sealed class ChaCha20Poly1305Cipher : SymmetricCipher { - private readonly byte[] _sequenceNumber = new byte[12]; private readonly ChaCha7539Engine _aadCipher = new ChaCha7539Engine(); private readonly ChaCha7539Engine _cipher = new ChaCha7539Engine(); private readonly Poly1305 _mac = new Poly1305(); @@ -141,13 +140,14 @@ public override byte[] Decrypt(byte[] input, int offset, int length) internal override void SetSequenceNumber(uint sequenceNumber) { - BinaryPrimitives.WriteUInt64BigEndian(_sequenceNumber.AsSpan(4), 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), _sequenceNumber)); - _cipher.Init(forEncryption: true, new ParametersWithIV(new KeyParameter(Key, 0, 32), _sequenceNumber)); + _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 polyKeyBytes = new byte[32]; _cipher.ProcessBytes(new byte[32], 0, 32, polyKeyBytes, 0); From fa69068700322e24fdbdc450e98650be07bf3ddd Mon Sep 17 00:00:00 2001 From: Scott Xu Date: Sun, 21 Jul 2024 09:57:18 +0800 Subject: [PATCH 16/18] Compute poly key stream once --- .../Cryptography/Ciphers/ChaCha20Poly1305Cipher.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Renci.SshNet/Security/Cryptography/Ciphers/ChaCha20Poly1305Cipher.cs b/src/Renci.SshNet/Security/Cryptography/Ciphers/ChaCha20Poly1305Cipher.cs index e92ab059a..e67eafc2e 100644 --- a/src/Renci.SshNet/Security/Cryptography/Ciphers/ChaCha20Poly1305Cipher.cs +++ b/src/Renci.SshNet/Security/Cryptography/Ciphers/ChaCha20Poly1305Cipher.cs @@ -149,10 +149,9 @@ internal override void SetSequenceNumber(uint sequenceNumber) _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 polyKeyBytes = new byte[32]; - _cipher.ProcessBytes(new byte[32], 0, 32, polyKeyBytes, 0); - _cipher.ProcessBytes(new byte[32], 0, 32, new byte[32], 0); // this ProcessBytes is required to set the block counter of ChaCha to 1 - _mac.Init(new KeyParameter(polyKeyBytes)); + var keyStream = new byte[64]; + _cipher.ProcessBytes(keyStream, 0, keyStream.Length, keyStream, 0); + _mac.Init(new KeyParameter(keyStream, 0, 32)); } } } From 90826d3461f04bf10ba025dab81e8bd4d4c56942 Mon Sep 17 00:00:00 2001 From: Scott Xu Date: Tue, 23 Jul 2024 20:44:35 +0800 Subject: [PATCH 17/18] Update test/Renci.SshNet.IntegrationTests/CipherTests.cs Co-authored-by: Rob Hague --- test/Renci.SshNet.IntegrationTests/CipherTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/Renci.SshNet.IntegrationTests/CipherTests.cs b/test/Renci.SshNet.IntegrationTests/CipherTests.cs index 90a8d4953..ff11f17ec 100644 --- a/test/Renci.SshNet.IntegrationTests/CipherTests.cs +++ b/test/Renci.SshNet.IntegrationTests/CipherTests.cs @@ -76,13 +76,13 @@ public void Aes256Gcm() { DoTest(Cipher.Aes256Gcm); } - +#endif [TestMethod] public void ChaCha20Poly1305() { DoTest(Cipher.Chacha20Poly1305); } -#endif + private void DoTest(Cipher cipher) { _remoteSshdConfig.ClearCiphers() From 4ff14c6103fe92a4aa3d60a1fb428c9a95e097cd Mon Sep 17 00:00:00 2001 From: Scott Xu Date: Tue, 23 Jul 2024 21:35:44 +0800 Subject: [PATCH 18/18] Fix build; Add net48 integration test in CI --- appveyor.yml | 2 ++ test/Renci.SshNet.IntegrationTests/CipherTests.cs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index f8b7db9fa..e55450412 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -21,12 +21,14 @@ for: - echo build - dotnet build -f net8.0 -c Debug test/Renci.SshNet.Tests/Renci.SshNet.Tests.csproj - dotnet build -f net8.0 -c Debug test/Renci.SshNet.IntegrationTests/Renci.SshNet.IntegrationTests.csproj + - dotnet build -f net48 -c Debug test/Renci.SshNet.IntegrationTests/Renci.SshNet.IntegrationTests.csproj test_script: - sh: echo "Run unit tests" - sh: dotnet test -f net8.0 -c Debug --no-restore --no-build --results-directory artifacts --logger Appveyor --logger "console;verbosity=normal" --logger "liquid.md;LogFileName=linux_unit_test_net_8_report.md" -p:CollectCoverage=true -p:CoverletOutputFormat=cobertura -p:CoverletOutput=../../artifacts/linux_unit_test_net_8_coverage.xml test/Renci.SshNet.Tests/Renci.SshNet.Tests.csproj - sh: echo "Run integration tests" - sh: dotnet test -f net8.0 -c Debug --no-restore --no-build --results-directory artifacts --logger Appveyor --logger "console;verbosity=normal" --logger "liquid.md;LogFileName=linux_integration_test_net_8_report.md" -p:CollectCoverage=true -p:CoverletOutputFormat=cobertura -p:CoverletOutput=../../artifacts/linux_integration_test_net_8_coverage.xml test/Renci.SshNet.IntegrationTests/Renci.SshNet.IntegrationTests.csproj + - sh: dotnet test -f net48 -c Debug --no-restore --no-build --results-directory artifacts --logger Appveyor --logger "console;verbosity=normal" --logger "liquid.md;LogFileName=linux_integration_test_net_48_report.md" -p:CollectCoverage=true -p:CoverletOutputFormat=cobertura -p:CoverletOutput=../../artifacts/linux_integration_test_net_48_coverage.xml --filter Name=ChaCha20Poly1305 test/Renci.SshNet.IntegrationTests/Renci.SshNet.IntegrationTests.csproj - matrix: diff --git a/test/Renci.SshNet.IntegrationTests/CipherTests.cs b/test/Renci.SshNet.IntegrationTests/CipherTests.cs index ff11f17ec..c25c0da2e 100644 --- a/test/Renci.SshNet.IntegrationTests/CipherTests.cs +++ b/test/Renci.SshNet.IntegrationTests/CipherTests.cs @@ -82,7 +82,7 @@ public void ChaCha20Poly1305() { DoTest(Cipher.Chacha20Poly1305); } - + private void DoTest(Cipher cipher) { _remoteSshdConfig.ClearCiphers()