diff --git a/src/libraries/Common/src/Internal/Cryptography/Helpers.cs b/src/libraries/Common/src/Internal/Cryptography/Helpers.cs index 2ee5ffc39c4f7..6614b3c6d00bd 100644 --- a/src/libraries/Common/src/Internal/Cryptography/Helpers.cs +++ b/src/libraries/Common/src/Internal/Cryptography/Helpers.cs @@ -30,14 +30,9 @@ internal static partial class Helpers return (byte[])(src.Clone()); } - public static int GetPaddingSize(this SymmetricAlgorithm algorithm, CipherMode mode, int feedbackSizeBits) + public static int GetPaddingSize(this SymmetricAlgorithm algorithm, CipherMode mode, int feedbackSizeInBits) { - // CFB8 does not require any padding at all - // otherwise, it is always required to pad for block size - if (mode == CipherMode.CFB && feedbackSizeBits == 8) - return 1; - - return algorithm.BlockSize / 8; + return (mode == CipherMode.CFB ? feedbackSizeInBits : algorithm.BlockSize) / 8; } internal static bool TryCopyToDestination(ReadOnlySpan source, Span destination, out int bytesWritten) diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesCipherOneShotTests.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesCipherOneShotTests.cs index 77d5e0cf77823..bc109f9bd03d5 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesCipherOneShotTests.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesCipherOneShotTests.cs @@ -22,68 +22,88 @@ public class AesCipherOneShotTests : SymmetricOneShotBase [Theory] [MemberData(nameof(TestCases))] - public void OneShotRoundtrip(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - OneShotRoundtripTest(plaintext, ciphertext, padding, mode); + public void OneShotRoundtrip(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + OneShotRoundtripTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void TryDecryptOneShot_DestinationTooSmall(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - TryDecryptOneShot_DestinationTooSmallTest(plaintext, ciphertext, padding, mode); + public void TryDecryptOneShot_DestinationTooSmall(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + TryDecryptOneShot_DestinationTooSmallTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void TryEncryptOneShot_DestinationTooSmall(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - TryEncryptOneShot_DestinationTooSmallTest(plaintext, ciphertext, padding, mode); + public void TryEncryptOneShot_DestinationTooSmall(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + TryEncryptOneShot_DestinationTooSmallTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void TryDecryptOneShot_DestinationJustRight(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - TryDecryptOneShot_DestinationJustRightTest(plaintext, ciphertext, padding, mode); + public void TryDecryptOneShot_DestinationJustRight(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + TryDecryptOneShot_DestinationJustRightTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void TryEncryptOneShot_DestinationJustRight(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - TryEncryptOneShot_DestinationJustRightTest(plaintext, ciphertext, padding, mode); + public void TryEncryptOneShot_DestinationJustRight(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + TryEncryptOneShot_DestinationJustRightTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void TryDecryptOneShot_DestinationLarger(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - TryDecryptOneShot_DestinationLargerTest(plaintext, ciphertext, padding, mode); + public void TryDecryptOneShot_DestinationLarger(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + TryDecryptOneShot_DestinationLargerTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void TryEncryptOneShot_DestinationLarger(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - TryEncryptOneShot_DestinationLargerTest(plaintext, ciphertext, padding, mode); + public void TryEncryptOneShot_DestinationLarger(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + TryEncryptOneShot_DestinationLargerTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void TryDecryptOneShot_Overlaps(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - TryDecryptOneShot_OverlapsTest(plaintext, ciphertext, padding, mode); + public void TryDecryptOneShot_Overlaps(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + TryDecryptOneShot_OverlapsTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void TryEncryptOneShot_Overlaps(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - TryEncryptOneShot_OverlapsTest(plaintext, ciphertext, padding, mode); + public void TryEncryptOneShot_Overlaps(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + TryEncryptOneShot_OverlapsTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void DecryptOneShot_Span(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - DecryptOneShot_SpanTest(plaintext, ciphertext, padding, mode); + public void DecryptOneShot_Span(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + DecryptOneShot_SpanTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void EncryptOneShot_Span(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - EncryptOneShot_SpanTest(plaintext, ciphertext, padding, mode); + public void EncryptOneShot_Span(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + EncryptOneShot_SpanTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void DecryptOneShot_Array(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - DecryptOneShot_ArrayTest(plaintext, ciphertext, padding, mode); + public void DecryptOneShot_Array(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + DecryptOneShot_ArrayTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void EncryptOneShot_Array(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - EncryptOneShot_ArrayTest(plaintext, ciphertext, padding, mode); + public void EncryptOneShot_Array(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + EncryptOneShot_ArrayTest(plaintext, ciphertext, padding, mode, feedbackSize); + + [Fact] + public void EncryptOneShot_CfbFeedbackSizeNotSupported() + { + using (SymmetricAlgorithm alg = CreateAlgorithm()) + { + Assert.ThrowsAny(() => + alg.TryEncryptCfb(ReadOnlySpan.Empty, IV, Span.Empty, out _, feedbackSizeInBits: 120)); + } + } + + [Fact] + public void DecryptOneShot_CfbFeedbackSizeNotSupported() + { + using (SymmetricAlgorithm alg = CreateAlgorithm()) + { + Assert.ThrowsAny(() => + alg.TryDecryptCfb(ReadOnlySpan.Empty, IV, Span.Empty, out _, feedbackSizeInBits: 120)); + } + } public static IEnumerable TestCases { @@ -91,6 +111,7 @@ public static IEnumerable TestCases { yield return new object[] { + // plaintext new byte[] { @@ -568,6 +589,538 @@ public static IEnumerable TestCases PaddingMode.PKCS7, CipherMode.ECB, }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, + 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, + }, + + // ciphertext + new byte[] + { + 0x53, 0x8B, 0x08, 0x3E, 0x07, 0xA4, 0x03, 0x16, + 0x0A, 0x75, 0x1A, 0x15, 0xF6, 0x1D, 0xAB, 0xD9, + 0xD2, + }, + + PaddingMode.PKCS7, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, + 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, + }, + + // ciphertext + new byte[] + { + 0x53, 0x8B, 0x08, 0x3E, 0x07, 0xA4, 0x03, 0x16, + 0x0A, 0x75, 0x1A, 0x15, 0xF6, 0x1D, 0xAB, 0xD9, + }, + + PaddingMode.None, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + + // plaintext + new byte[] + { + 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, + 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, + }, + + // ciphertext + new byte[] + { + 0x53, 0x8B, 0x08, 0x3E, 0x07, 0xA4, 0x03, 0x16, + 0x0A, 0x75, 0x1A, 0x15, 0xF6, 0x1D, 0xAB, 0xD9, + }, + + PaddingMode.Zeros, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, + 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, + }, + + // ciphertext + new byte[] + { + 0x53, 0x8B, 0x08, 0x3E, 0x07, 0xA4, 0x03, 0x16, + 0x0A, 0x75, 0x1A, 0x15, 0xF6, 0x1D, 0xAB, 0xD9, + 0xD2, + }, + + PaddingMode.ANSIX923, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, + 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, + }, + + // ciphertext + new byte[] + { + 0x53, 0x8B, 0x08, 0x3E, 0x07, 0xA4, 0x03, 0x16, + 0x0A, 0x75, 0x1A, 0x15, 0xF6, 0x1D, 0xAB, 0xD9, + 0xD2, + }, + + PaddingMode.ISO10126, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, + 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, + 0x59, + }, + + // ciphertext + new byte[] + { + 0x9A, 0x84, 0x44, 0xEB, 0x82, 0x11, 0xEA, 0x28, + 0x91, 0x8E, 0xA8, 0x40, 0xE4, 0x12, 0x3F, 0x72, + 0xF9, 0x97, + }, + + PaddingMode.PKCS7, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, + 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, + 0x59, + }, + + // ciphertext + new byte[] + { + 0x9A, 0x84, 0x44, 0xEB, 0x82, 0x11, 0xEA, 0x28, + 0x91, 0x8E, 0xA8, 0x40, 0xE4, 0x12, 0x3F, 0x72, + 0xF9, + }, + + PaddingMode.None, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, + 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, + 0x59, + }, + + // ciphertext + new byte[] + { + 0x9A, 0x84, 0x44, 0xEB, 0x82, 0x11, 0xEA, 0x28, + 0x91, 0x8E, 0xA8, 0x40, 0xE4, 0x12, 0x3F, 0x72, + 0xF9, + }, + + PaddingMode.Zeros, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, + 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, + 0x59, + }, + + // ciphertext + new byte[] + { + 0x9A, 0x84, 0x44, 0xEB, 0x82, 0x11, 0xEA, 0x28, + 0x91, 0x8E, 0xA8, 0x40, 0xE4, 0x12, 0x3F, 0x72, + 0xF9, 0x97, + }, + + PaddingMode.ANSIX923, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, + 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, + 0x59, + }, + + // ciphertext + new byte[] + { + 0x9A, 0x84, 0x44, 0xEB, 0x82, 0x11, 0xEA, 0x28, + 0x91, 0x8E, 0xA8, 0x40, 0xE4, 0x12, 0x3F, 0x72, + 0xF9, 0x97, + }, + + PaddingMode.ISO10126, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + // plaintext + Array.Empty(), + + // ciphertext + Array.Empty(), + + PaddingMode.Zeros, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + // plaintext + Array.Empty(), + + // ciphertext + Array.Empty(), + + PaddingMode.None, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + // plaintext + Array.Empty(), + + // ciphertext + new byte[] + { + 0x02, + }, + + PaddingMode.PKCS7, + CipherMode.CFB, + 8, + }; + + // CFB128 is not supported on Windows 7. + if (PlatformDetection.IsNotWindows7) + { + yield return new object[] + { + + // plaintext + new byte[] + { + 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, + 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, + }, + + // ciphertext + new byte[] + { + 0x53, 0x3F, 0x49, 0x1D, 0x53, 0x29, 0x39, 0x67, + 0x8A, 0x06, 0x28, 0x76, 0x34, 0x9A, 0x2D, 0xE3, + 0x2B, 0x63, 0xD4, 0x34, 0x86, 0x05, 0x9B, 0x52, + 0x20, 0x46, 0x65, 0xD5, 0xBC, 0xA1, 0xED, 0x11, + }, + + PaddingMode.PKCS7, + CipherMode.CFB, + 128, + }; + + yield return new object[] + { + + // plaintext + new byte[] + { + 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, + 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, + }, + + // ciphertext + new byte[] + { + 0x53, 0x3F, 0x49, 0x1D, 0x53, 0x29, 0x39, 0x67, + 0x8A, 0x06, 0x28, 0x76, 0x34, 0x9A, 0x2D, 0xE3, + }, + + PaddingMode.None, + CipherMode.CFB, + 128, + }; + + yield return new object[] + { + + // plaintext + new byte[] + { + 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, + 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, + }, + + // ciphertext + new byte[] + { + 0x53, 0x3F, 0x49, 0x1D, 0x53, 0x29, 0x39, 0x67, + 0x8A, 0x06, 0x28, 0x76, 0x34, 0x9A, 0x2D, 0xE3, + }, + + PaddingMode.Zeros, + CipherMode.CFB, + 128, + }; + + yield return new object[] + { + + // plaintext + new byte[] + { + 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, + 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, + }, + + // ciphertext + new byte[] + { + 0x53, 0x3F, 0x49, 0x1D, 0x53, 0x29, 0x39, 0x67, + 0x8A, 0x06, 0x28, 0x76, 0x34, 0x9A, 0x2D, 0xE3, + 0x3B, 0x73, 0xC4, 0x24, 0x96, 0x15, 0x8B, 0x42, + 0x30, 0x56, 0x75, 0xC5, 0xAC, 0xB1, 0xFD, 0x11, + }, + + PaddingMode.ANSIX923, + CipherMode.CFB, + 128, + }; + + yield return new object[] + { + + // plaintext + new byte[] + { + 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, + 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, + }, + + // ciphertext + new byte[] + { + 0x53, 0x3F, 0x49, 0x1D, 0x53, 0x29, 0x39, 0x67, + 0x8A, 0x06, 0x28, 0x76, 0x34, 0x9A, 0x2D, 0xE3, + 0x3E, 0x5D, 0xED, 0x96, 0x51, 0x93, 0xF0, 0x12, + 0x95, 0x98, 0x51, 0x29, 0xB6, 0xF8, 0x84, 0x11, + }, + + PaddingMode.ISO10126, + CipherMode.CFB, + 128, + }; + + yield return new object[] + { + + // plaintext + new byte[] + { + 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, + 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, + 0x59, + }, + + // ciphertext + new byte[] + { + 0x9A, 0x07, 0x33, 0xAB, 0xA8, 0x7E, 0xF9, 0x26, + 0xBA, 0xC0, 0x0E, 0xAF, 0xB7, 0x12, 0x25, 0x39, + 0x0C, 0xD0, 0xD4, 0xF1, 0x60, 0x93, 0xD0, 0x20, + 0x91, 0x11, 0xD8, 0xF6, 0x27, 0xE3, 0xAF, 0x0F, + }, + + PaddingMode.PKCS7, + CipherMode.CFB, + 128, + }; + + yield return new object[] + { + + // plaintext + new byte[] + { + 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, + 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, + 0x59, + }, + + // ciphertext + new byte[] + { + 0x9A, 0x07, 0x33, 0xAB, 0xA8, 0x7E, 0xF9, 0x26, + 0xBA, 0xC0, 0x0E, 0xAF, 0xB7, 0x12, 0x25, 0x39, + 0x0C, 0xDF, 0xDB, 0xFE, 0x6F, 0x9C, 0xDF, 0x2F, + 0x9E, 0x1E, 0xD7, 0xF9, 0x28, 0xEC, 0xA0, 0x00, + }, + + PaddingMode.Zeros, + CipherMode.CFB, + 128, + }; + + yield return new object[] + { + + // plaintext + new byte[] + { + 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, + 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, + 0x59, + }, + + // ciphertext + new byte[] + { + 0x9A, 0x07, 0x33, 0xAB, 0xA8, 0x7E, 0xF9, 0x26, + 0xBA, 0xC0, 0x0E, 0xAF, 0xB7, 0x12, 0x25, 0x39, + 0x0C, 0xDF, 0xDB, 0xFE, 0x6F, 0x9C, 0xDF, 0x2F, + 0x9E, 0x1E, 0xD7, 0xF9, 0x28, 0xEC, 0xA0, 0x0F, + }, + + PaddingMode.ANSIX923, + CipherMode.CFB, + 128, + }; + + yield return new object[] + { + + // plaintext + new byte[] + { + 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, + 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, + 0x59, + }, + + // ciphertext + new byte[] + { + 0x9A, 0x07, 0x33, 0xAB, 0xA8, 0x7E, 0xF9, 0x26, + 0xBA, 0xC0, 0x0E, 0xAF, 0xB7, 0x12, 0x25, 0x39, + 0x0C, 0x0C, 0x39, 0x31, 0x1C, 0xAA, 0x41, 0x45, + 0x78, 0xD0, 0x9F, 0x0F, 0x44, 0xD9, 0x37, 0x0F, + }, + + PaddingMode.ISO10126, + CipherMode.CFB, + 128, + }; + + yield return new object[] + { + + // plaintext + Array.Empty(), + + // ciphertext + new byte[] + { + 0x13, 0x47, 0x4B, 0xA9, 0x1C, 0x31, 0xE1, 0xFE, + 0x23, 0x69, 0x61, 0xE6, 0x27, 0x01, 0xBE, 0xAA, + }, + + PaddingMode.PKCS7, + CipherMode.CFB, + 128, + }; + + yield return new object[] + { + + // plaintext + Array.Empty(), + + // ciphertext + Array.Empty(), + + PaddingMode.Zeros, + CipherMode.CFB, + 128, + }; + + yield return new object[] + { + + // plaintext + Array.Empty(), + + // ciphertext + Array.Empty(), + + PaddingMode.None, + CipherMode.CFB, + 128, + }; + } } } } diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesCipherTests.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesCipherTests.cs index 465958943f8c2..2144dad5aaf1c 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesCipherTests.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesCipherTests.cs @@ -1124,6 +1124,7 @@ private static void TestAesTransformDirectKey( { aes.Mode = cipherMode; aes.Padding = paddingMode; + aes.Key = key; if (feedbackSize.HasValue) { @@ -1135,16 +1136,19 @@ private static void TestAesTransformDirectKey( if (cipherMode == CipherMode.ECB) { - aes.Key = key; liveOneShotDecryptBytes = aes.DecryptEcb(cipherBytes, paddingMode); liveOneShotEncryptBytes = aes.EncryptEcb(plainBytes, paddingMode); } else if (cipherMode == CipherMode.CBC) { - aes.Key = key; liveOneShotDecryptBytes = aes.DecryptCbc(cipherBytes, iv, paddingMode); liveOneShotEncryptBytes = aes.EncryptCbc(plainBytes, iv, paddingMode); } + else if (cipherMode == CipherMode.CFB) + { + liveOneShotDecryptBytes = aes.DecryptCfb(cipherBytes, iv, paddingMode, feedbackSizeInBits: feedbackSize.Value); + liveOneShotEncryptBytes = aes.EncryptCfb(plainBytes, iv, paddingMode, feedbackSizeInBits: feedbackSize.Value); + } } Assert.Equal(cipherBytes, liveEncryptBytes); diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DES/DESCipherOneShotTests.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DES/DESCipherOneShotTests.cs index d775ffb9f128e..0a050f842736c 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DES/DESCipherOneShotTests.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DES/DESCipherOneShotTests.cs @@ -26,68 +26,88 @@ public class DesCipherOneShotTests : SymmetricOneShotBase [Theory] [MemberData(nameof(TestCases))] - public void OneShotRoundtrip(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - OneShotRoundtripTest(plaintext, ciphertext, padding, mode); + public void OneShotRoundtrip(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + OneShotRoundtripTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void TryDecryptOneShot_DestinationTooSmall(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - TryDecryptOneShot_DestinationTooSmallTest(plaintext, ciphertext, padding, mode); + public void TryDecryptOneShot_DestinationTooSmall(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + TryDecryptOneShot_DestinationTooSmallTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void TryEncryptOneShot_DestinationTooSmall(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - TryEncryptOneShot_DestinationTooSmallTest(plaintext, ciphertext, padding, mode); + public void TryEncryptOneShot_DestinationTooSmall(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + TryEncryptOneShot_DestinationTooSmallTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void TryDecryptOneShot_DestinationJustRight(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - TryDecryptOneShot_DestinationJustRightTest(plaintext, ciphertext, padding, mode); + public void TryDecryptOneShot_DestinationJustRight(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + TryDecryptOneShot_DestinationJustRightTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void TryEncryptOneShot_DestinationJustRight(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - TryEncryptOneShot_DestinationJustRightTest(plaintext, ciphertext, padding, mode); + public void TryEncryptOneShot_DestinationJustRight(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + TryEncryptOneShot_DestinationJustRightTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void TryDecryptOneShot_DestinationLarger(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - TryDecryptOneShot_DestinationLargerTest(plaintext, ciphertext, padding, mode); + public void TryDecryptOneShot_DestinationLarger(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + TryDecryptOneShot_DestinationLargerTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void TryEncryptOneShot_DestinationLarger(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - TryEncryptOneShot_DestinationLargerTest(plaintext, ciphertext, padding, mode); + public void TryEncryptOneShot_DestinationLarger(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + TryEncryptOneShot_DestinationLargerTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void TryDecryptOneShot_Overlaps(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - TryDecryptOneShot_OverlapsTest(plaintext, ciphertext, padding, mode); + public void TryDecryptOneShot_Overlaps(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + TryDecryptOneShot_OverlapsTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void TryEncryptOneShot_Overlaps(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - TryEncryptOneShot_OverlapsTest(plaintext, ciphertext, padding, mode); + public void TryEncryptOneShot_Overlaps(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + TryEncryptOneShot_OverlapsTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void DecryptOneShot_Span(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - DecryptOneShot_SpanTest(plaintext, ciphertext, padding, mode); + public void DecryptOneShot_Span(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + DecryptOneShot_SpanTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void EncryptOneShot_Span(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - EncryptOneShot_SpanTest(plaintext, ciphertext, padding, mode); + public void EncryptOneShot_Span(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + EncryptOneShot_SpanTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void DecryptOneShot_Array(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - DecryptOneShot_ArrayTest(plaintext, ciphertext, padding, mode); + public void DecryptOneShot_Array(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + DecryptOneShot_ArrayTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void EncryptOneShot_Array(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - EncryptOneShot_ArrayTest(plaintext, ciphertext, padding, mode); + public void EncryptOneShot_Array(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + EncryptOneShot_ArrayTest(plaintext, ciphertext, padding, mode, feedbackSize); + + [Fact] + public void EncryptOneShot_CfbFeedbackSizeNotSupported() + { + using (SymmetricAlgorithm alg = CreateAlgorithm()) + { + Assert.ThrowsAny(() => + alg.TryEncryptCfb(ReadOnlySpan.Empty, IV, Span.Empty, out _, feedbackSizeInBits: 56)); + } + } + + [Fact] + public void DecryptOneShot_CfbFeedbackSizeNotSupported() + { + using (SymmetricAlgorithm alg = CreateAlgorithm()) + { + Assert.ThrowsAny(() => + alg.TryDecryptCfb(ReadOnlySpan.Empty, IV, Span.Empty, out _, feedbackSizeInBits: 56)); + } + } public static IEnumerable TestCases { @@ -553,6 +573,233 @@ public static IEnumerable TestCases PaddingMode.PKCS7, CipherMode.ECB, }; + + // Windows 7 does not support CFB8 + if (PlatformDetection.IsNotWindows7) + { + yield return new object[] + { + // plaintext + new byte[] + { + 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, + 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, + }, + + // ciphertext + new byte[] + { + 0x6F, 0xF8, 0x24, 0x52, 0x68, 0x8E, 0x53, 0x97, + 0x2A, 0x6B, 0x8A, 0x5E, 0xBE, 0x98, 0x84, 0x28, + 0x39, + }, + + PaddingMode.PKCS7, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, + 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, + }, + + // ciphertext + new byte[] + { + 0x6F, 0xF8, 0x24, 0x52, 0x68, 0x8E, 0x53, 0x97, + 0x2A, 0x6B, 0x8A, 0x5E, 0xBE, 0x98, 0x84, 0x28, + }, + + PaddingMode.None, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, + 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, + }, + + // ciphertext + new byte[] + { + 0x6F, 0xF8, 0x24, 0x52, 0x68, 0x8E, 0x53, 0x97, + 0x2A, 0x6B, 0x8A, 0x5E, 0xBE, 0x98, 0x84, 0x28, + }, + + PaddingMode.Zeros, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, + 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, + }, + + // ciphertext + new byte[] + { + 0x6F, 0xF8, 0x24, 0x52, 0x68, 0x8E, 0x53, 0x97, + 0x2A, 0x6B, 0x8A, 0x5E, 0xBE, 0x98, 0x84, 0x28, + 0x39, + }, + + PaddingMode.ANSIX923, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, + 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, + }, + + // ciphertext + new byte[] + { + 0x6F, 0xF8, 0x24, 0x52, 0x68, 0x8E, 0x53, 0x97, + 0x2A, 0x6B, 0x8A, 0x5E, 0xBE, 0x98, 0x84, 0x28, + 0x39, + }, + + PaddingMode.ISO10126, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, + 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, + 0x59, + }, + + // ciphertext + new byte[] + { + 0xA6, 0x51, 0xB4, 0xF2, 0x2B, 0xFA, 0x22, 0xF5, + 0x15, 0x1E, 0x5E, 0x65, 0x39, 0xFD, 0x84, 0x4F, + 0xE1, 0x7E, + }, + + PaddingMode.PKCS7, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, + 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, + 0x59, + }, + + // ciphertext + new byte[] + { + 0xA6, 0x51, 0xB4, 0xF2, 0x2B, 0xFA, 0x22, 0xF5, + 0x15, 0x1E, 0x5E, 0x65, 0x39, 0xFD, 0x84, 0x4F, + 0xE1, + }, + + PaddingMode.None, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, + 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, + 0x59, + }, + + // ciphertext + new byte[] + { + 0xA6, 0x51, 0xB4, 0xF2, 0x2B, 0xFA, 0x22, 0xF5, + 0x15, 0x1E, 0x5E, 0x65, 0x39, 0xFD, 0x84, 0x4F, + 0xE1, + }, + + PaddingMode.Zeros, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, + 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, + 0x59, + }, + + // ciphertext + new byte[] + { + 0xA6, 0x51, 0xB4, 0xF2, 0x2B, 0xFA, 0x22, 0xF5, + 0x15, 0x1E, 0x5E, 0x65, 0x39, 0xFD, 0x84, 0x4F, + 0xE1, 0x7E, + }, + + PaddingMode.ANSIX923, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, + 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, + 0x59, + }, + + // ciphertext + new byte[] + { + 0xA6, 0x51, 0xB4, 0xF2, 0x2B, 0xFA, 0x22, 0xF5, + 0x15, 0x1E, 0x5E, 0x65, 0x39, 0xFD, 0x84, 0x4F, + 0xE1, 0x7E, + }, + + PaddingMode.ISO10126, + CipherMode.CFB, + 8, + }; + } } } } diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DES/DESCipherTests.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DES/DESCipherTests.cs index d23a48a2851cd..707db864be13f 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DES/DESCipherTests.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DES/DESCipherTests.cs @@ -531,6 +531,7 @@ private static void TestDESTransformDirectKey( { des.Mode = cipherMode; des.Padding = paddingMode; + des.Key = key; if (feedbackSize.HasValue) { @@ -540,17 +541,23 @@ private static void TestDESTransformDirectKey( liveEncryptBytes = DESEncryptDirectKey(des, key, iv, plainBytes); liveDecryptBytes = DESDecryptDirectKey(des, key, iv, cipherBytes); - if (cipherMode == CipherMode.ECB) - { - des.Key = key; - liveOneShotDecryptBytes = des.DecryptEcb(cipherBytes, paddingMode); - liveOneShotEncryptBytes = des.EncryptEcb(plainBytes, paddingMode); - } - else if (cipherMode == CipherMode.CBC) + if (DESFactory.OneShotSupported) { - des.Key = key; - liveOneShotDecryptBytes = des.DecryptCbc(cipherBytes, iv, paddingMode); - liveOneShotEncryptBytes = des.EncryptCbc(plainBytes, iv, paddingMode); + if (cipherMode == CipherMode.ECB) + { + liveOneShotDecryptBytes = des.DecryptEcb(cipherBytes, paddingMode); + liveOneShotEncryptBytes = des.EncryptEcb(plainBytes, paddingMode); + } + else if (cipherMode == CipherMode.CBC) + { + liveOneShotDecryptBytes = des.DecryptCbc(cipherBytes, iv, paddingMode); + liveOneShotEncryptBytes = des.EncryptCbc(plainBytes, iv, paddingMode); + } + else if (cipherMode == CipherMode.CFB) + { + liveOneShotDecryptBytes = des.DecryptCfb(cipherBytes, iv, paddingMode, feedbackSizeInBits: feedbackSize.Value); + liveOneShotEncryptBytes = des.EncryptCfb(plainBytes, iv, paddingMode, feedbackSizeInBits: feedbackSize.Value); + } } } diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DES/DESFactory.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DES/DESFactory.cs index 5b191ca5ea05f..fd7a2f626e412 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DES/DESFactory.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DES/DESFactory.cs @@ -6,13 +6,12 @@ namespace System.Security.Cryptography.Encryption.Des.Tests public interface IDESProvider { DES Create(); + bool OneShotSupported { get; } } public static partial class DESFactory { - public static DES Create() - { - return s_provider.Create(); - } + public static DES Create() => s_provider.Create(); + public static bool OneShotSupported => s_provider.OneShotSupported; } } diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/RC2/RC2CipherOneShotTests.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/RC2/RC2CipherOneShotTests.cs index 411aadaf347e6..24d2befc125c4 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/RC2/RC2CipherOneShotTests.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/RC2/RC2CipherOneShotTests.cs @@ -90,6 +90,26 @@ public void DecryptOneShot_Array(byte[] plaintext, byte[] ciphertext, PaddingMod public void EncryptOneShot_Array(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => EncryptOneShot_ArrayTest(plaintext, ciphertext, padding, mode); + [Fact] + public void EncryptOneShot_CfbNotSupported() + { + using (SymmetricAlgorithm alg = CreateAlgorithm()) + { + Assert.ThrowsAny(() => + alg.TryEncryptCfb(ReadOnlySpan.Empty, IV, Span.Empty, out _)); + } + } + + [Fact] + public void DecryptOneShot_CfbNotSupported() + { + using (SymmetricAlgorithm alg = CreateAlgorithm()) + { + Assert.ThrowsAny(() => + alg.TryDecryptCfb(ReadOnlySpan.Empty, IV, Span.Empty, out _)); + } + } + public static IEnumerable TestCases { get diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/Symmetric/SymmetricOneShotBase.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/Symmetric/SymmetricOneShotBase.cs index 89e6b5d285deb..42a9b584366d9 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/Symmetric/SymmetricOneShotBase.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/Symmetric/SymmetricOneShotBase.cs @@ -17,10 +17,11 @@ public abstract class SymmetricOneShotBase protected abstract byte[] IV { get; } protected abstract SymmetricAlgorithm CreateAlgorithm(); - protected void OneShotRoundtripTest(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) + protected void OneShotRoundtripTest(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) { using (SymmetricAlgorithm alg = CreateAlgorithm()) { + int paddingSizeBytes = mode == CipherMode.CFB ? feedbackSize / 8 : alg.BlockSize / 8; alg.Key = Key; // Set the instance to use a different mode and padding than what will be used @@ -33,37 +34,41 @@ protected void OneShotRoundtripTest(byte[] plaintext, byte[] ciphertext, Padding { CipherMode.ECB => alg.EncryptEcb(plaintext, padding), CipherMode.CBC => alg.EncryptCbc(plaintext, IV, padding), + CipherMode.CFB => alg.EncryptCfb(plaintext, IV, padding, feedbackSize), _ => throw new NotImplementedException(), }; byte[] decrypted = mode switch { CipherMode.ECB => alg.DecryptEcb(encrypted, padding), CipherMode.CBC => alg.DecryptCbc(encrypted, IV, padding), + CipherMode.CFB => alg.DecryptCfb(encrypted, IV, padding, feedbackSize), _ => throw new NotImplementedException(), }; AssertPlaintexts(plaintext, decrypted, padding); - AssertCiphertexts(encrypted, ciphertext, padding, alg.BlockSize / 8); + AssertCiphertexts(encrypted, ciphertext, padding, paddingSizeBytes); decrypted = mode switch { CipherMode.ECB => alg.DecryptEcb(ciphertext, padding), CipherMode.CBC => alg.DecryptCbc(ciphertext, IV, padding), + CipherMode.CFB => alg.DecryptCfb(ciphertext, IV, padding, feedbackSize), _ => throw new NotImplementedException(), }; encrypted = mode switch { CipherMode.ECB => alg.EncryptEcb(decrypted, padding), CipherMode.CBC => alg.EncryptCbc(decrypted, IV, padding), + CipherMode.CFB => alg.EncryptCfb(decrypted, IV, padding, feedbackSize), _ => throw new NotImplementedException(), }; AssertPlaintexts(plaintext, decrypted, padding); - AssertCiphertexts(ciphertext, encrypted, padding, alg.BlockSize / 8); + AssertCiphertexts(ciphertext, encrypted, padding, paddingSizeBytes); } } - protected void TryDecryptOneShot_DestinationTooSmallTest(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) + protected void TryDecryptOneShot_DestinationTooSmallTest(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) { if (plaintext.Length == 0) { @@ -82,6 +87,7 @@ protected void TryDecryptOneShot_DestinationTooSmallTest(byte[] plaintext, byte[ { CipherMode.ECB => alg.TryDecryptEcb(ciphertext, destinationBuffer, padding, out bytesWritten), CipherMode.CBC => alg.TryDecryptCbc(ciphertext, IV, destinationBuffer, out bytesWritten, padding), + CipherMode.CFB => alg.TryDecryptCfb(ciphertext, IV, destinationBuffer, out bytesWritten, padding, feedbackSize), _ => throw new NotImplementedException(), }; @@ -90,7 +96,7 @@ protected void TryDecryptOneShot_DestinationTooSmallTest(byte[] plaintext, byte[ } } - protected void TryEncryptOneShot_DestinationTooSmallTest(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) + protected void TryEncryptOneShot_DestinationTooSmallTest(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) { if (ciphertext.Length == 0) { @@ -109,6 +115,7 @@ protected void TryEncryptOneShot_DestinationTooSmallTest(byte[] plaintext, byte[ { CipherMode.ECB => alg.TryEncryptEcb(plaintext, destinationBuffer, padding, out bytesWritten), CipherMode.CBC => alg.TryEncryptCbc(plaintext, IV, destinationBuffer, out bytesWritten, padding), + CipherMode.CFB => alg.TryEncryptCfb(plaintext, IV, destinationBuffer, out bytesWritten, padding, feedbackSize), _ => throw new NotImplementedException(), }; Assert.False(result, "TryEncrypt"); @@ -116,7 +123,7 @@ protected void TryEncryptOneShot_DestinationTooSmallTest(byte[] plaintext, byte[ } } - protected void TryDecryptOneShot_DestinationJustRightTest(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) + protected void TryDecryptOneShot_DestinationJustRightTest(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) { using (SymmetricAlgorithm alg = CreateAlgorithm()) { @@ -127,11 +134,12 @@ protected void TryDecryptOneShot_DestinationJustRightTest(byte[] plaintext, byte int bytesWritten; bool result = mode switch - { - CipherMode.ECB => alg.TryDecryptEcb(ciphertext, destinationBuffer, padding, out bytesWritten), - CipherMode.CBC => alg.TryDecryptCbc(ciphertext, IV, destinationBuffer, out bytesWritten, padding), - _ => throw new NotImplementedException(), - }; + { + CipherMode.ECB => alg.TryDecryptEcb(ciphertext, destinationBuffer, padding, out bytesWritten), + CipherMode.CBC => alg.TryDecryptCbc(ciphertext, IV, destinationBuffer, out bytesWritten, padding), + CipherMode.CFB => alg.TryDecryptCfb(ciphertext, IV, destinationBuffer, out bytesWritten, padding, feedbackSize), + _ => throw new NotImplementedException(), + }; Assert.True(result, "TryDecrypt"); Assert.Equal(destinationBuffer.Length, bytesWritten); @@ -139,16 +147,18 @@ protected void TryDecryptOneShot_DestinationJustRightTest(byte[] plaintext, byte } } - protected void TryEncryptOneShot_DestinationJustRightTest(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) + protected void TryEncryptOneShot_DestinationJustRightTest(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) { using (SymmetricAlgorithm alg = CreateAlgorithm()) { + int paddingSizeBytes = mode == CipherMode.CFB ? feedbackSize / 8 : alg.BlockSize / 8; alg.Key = Key; int expectedCiphertextSize = mode switch { CipherMode.ECB => alg.GetCiphertextLengthEcb(plaintext.Length, padding), CipherMode.CBC => alg.GetCiphertextLengthCbc(plaintext.Length, padding), + CipherMode.CFB => alg.GetCiphertextLengthCfb(plaintext.Length, padding, feedbackSize), _ => throw new NotImplementedException(), }; Span destinationBuffer = new byte[expectedCiphertextSize]; @@ -158,16 +168,17 @@ protected void TryEncryptOneShot_DestinationJustRightTest(byte[] plaintext, byte { CipherMode.ECB => alg.TryEncryptEcb(plaintext, destinationBuffer, padding, out bytesWritten), CipherMode.CBC => alg.TryEncryptCbc(plaintext, IV, destinationBuffer, out bytesWritten, padding), + CipherMode.CFB => alg.TryEncryptCfb(plaintext, IV, destinationBuffer, out bytesWritten, padding, feedbackSize), _ => throw new NotImplementedException(), }; Assert.True(result, "TryEncrypt"); Assert.Equal(expectedCiphertextSize, bytesWritten); - AssertCiphertexts(ciphertext, destinationBuffer, padding, alg.BlockSize / 8); + AssertCiphertexts(ciphertext, destinationBuffer, padding, paddingSizeBytes); } } - protected void TryDecryptOneShot_DestinationLargerTest(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) + protected void TryDecryptOneShot_DestinationLargerTest(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) { using (SymmetricAlgorithm alg = CreateAlgorithm()) { @@ -183,6 +194,7 @@ protected void TryDecryptOneShot_DestinationLargerTest(byte[] plaintext, byte[] { CipherMode.ECB => alg.TryDecryptEcb(ciphertext, destinationBuffer, padding, out bytesWritten), CipherMode.CBC => alg.TryDecryptCbc(ciphertext, IV, destinationBuffer, out bytesWritten, padding), + CipherMode.CFB => alg.TryDecryptCfb(ciphertext, IV, destinationBuffer, out bytesWritten, padding, feedbackSize), _ => throw new NotImplementedException(), }; @@ -196,10 +208,11 @@ protected void TryDecryptOneShot_DestinationLargerTest(byte[] plaintext, byte[] } } - protected void TryEncryptOneShot_DestinationLargerTest(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) + protected void TryEncryptOneShot_DestinationLargerTest(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) { using (SymmetricAlgorithm alg = CreateAlgorithm()) { + int paddingSizeBytes = mode == CipherMode.CFB ? feedbackSize / 8 : alg.BlockSize / 8; alg.Key = Key; Span largeBuffer = new byte[ciphertext.Length + 10]; @@ -211,18 +224,19 @@ protected void TryEncryptOneShot_DestinationLargerTest(byte[] plaintext, byte[] { CipherMode.ECB => alg.TryEncryptEcb(plaintext, destinationBuffer, padding, out bytesWritten), CipherMode.CBC => alg.TryEncryptCbc(plaintext, IV, destinationBuffer, out bytesWritten, padding), + CipherMode.CFB => alg.TryEncryptCfb(plaintext, IV, destinationBuffer, out bytesWritten, padding, feedbackSize), _ => throw new NotImplementedException(), }; Assert.True(result, "TryEncrypt"); Assert.Equal(destinationBuffer.Length, bytesWritten); - AssertCiphertexts(ciphertext, destinationBuffer, padding, alg.BlockSize / 8); + AssertCiphertexts(ciphertext, destinationBuffer, padding, paddingSizeBytes); AssertExtensions.FilledWith(0xCC, largeBuffer.Slice(ciphertext.Length)); } } - protected void TryDecryptOneShot_OverlapsTest(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) + protected void TryDecryptOneShot_OverlapsTest(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) { (int plaintextOffset, int ciphertextOffset)[] offsets = { @@ -247,6 +261,7 @@ protected void TryDecryptOneShot_OverlapsTest(byte[] plaintext, byte[] ciphertex { CipherMode.ECB => alg.TryDecryptEcb(ciphertextBuffer, destinationBuffer, padding, out bytesWritten), CipherMode.CBC => alg.TryDecryptCbc(ciphertextBuffer, IV, destinationBuffer, out bytesWritten, padding), + CipherMode.CFB => alg.TryDecryptCfb(ciphertextBuffer, IV, destinationBuffer, out bytesWritten, padding, feedbackSize), _ => throw new NotImplementedException(), }; Assert.True(result, "TryDecrypt"); @@ -258,7 +273,7 @@ protected void TryDecryptOneShot_OverlapsTest(byte[] plaintext, byte[] ciphertex } } - protected void TryEncryptOneShot_OverlapsTest(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) + protected void TryEncryptOneShot_OverlapsTest(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) { (int plaintextOffset, int ciphertextOffset)[] offsets = { @@ -269,6 +284,7 @@ protected void TryEncryptOneShot_OverlapsTest(byte[] plaintext, byte[] ciphertex { using (SymmetricAlgorithm alg = CreateAlgorithm()) { + int paddingSizeBytes = mode == CipherMode.CFB ? feedbackSize / 8 : alg.BlockSize / 8; alg.Key = Key; int destinationSize = ciphertext.Length + Math.Max(plaintextOffset, ciphertextOffset); @@ -282,18 +298,19 @@ protected void TryEncryptOneShot_OverlapsTest(byte[] plaintext, byte[] ciphertex { CipherMode.ECB => alg.TryEncryptEcb(plaintextBuffer, destinationBuffer, padding, out bytesWritten), CipherMode.CBC => alg.TryEncryptCbc(plaintextBuffer, IV, destinationBuffer, out bytesWritten, padding), + CipherMode.CFB => alg.TryEncryptCfb(plaintextBuffer, IV, destinationBuffer, out bytesWritten, padding, feedbackSize), _ => throw new NotImplementedException(), }; Assert.True(result, "TryEncrypt"); Assert.Equal(destinationBuffer.Length, bytesWritten); - AssertCiphertexts(ciphertext, destinationBuffer, padding, alg.BlockSize / 8); + AssertCiphertexts(ciphertext, destinationBuffer, padding, paddingSizeBytes); Assert.True(destinationBuffer.Overlaps(plaintextBuffer) || plaintext.Length == 0 || ciphertext.Length == 0); } } } - protected void DecryptOneShot_SpanTest(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) + protected void DecryptOneShot_SpanTest(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) { using (SymmetricAlgorithm alg = CreateAlgorithm()) { @@ -302,6 +319,7 @@ protected void DecryptOneShot_SpanTest(byte[] plaintext, byte[] ciphertext, Padd { CipherMode.ECB => alg.DecryptEcb(ciphertext.AsSpan(), padding), CipherMode.CBC => alg.DecryptCbc(ciphertext.AsSpan(), IV.AsSpan(), padding), + CipherMode.CFB => alg.DecryptCfb(ciphertext.AsSpan(), IV.AsSpan(), padding, feedbackSize), _ => throw new NotImplementedException(), }; @@ -309,23 +327,25 @@ protected void DecryptOneShot_SpanTest(byte[] plaintext, byte[] ciphertext, Padd } } - protected void EncryptOneShot_SpanTest(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) + protected void EncryptOneShot_SpanTest(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) { using (SymmetricAlgorithm alg = CreateAlgorithm()) { alg.Key = Key; + int paddingSizeBytes = mode == CipherMode.CFB ? feedbackSize / 8 : alg.BlockSize / 8; byte[] encrypted = mode switch { CipherMode.ECB => alg.EncryptEcb(plaintext.AsSpan(), padding), CipherMode.CBC => alg.EncryptCbc(plaintext.AsSpan(), IV.AsSpan(), padding), + CipherMode.CFB => alg.EncryptCfb(plaintext.AsSpan(), IV.AsSpan(), padding, feedbackSize), _ => throw new NotImplementedException(), }; - AssertCiphertexts(ciphertext, encrypted, padding, alg.BlockSize / 8); + AssertCiphertexts(ciphertext, encrypted, padding, paddingSizeBytes); } } - protected void DecryptOneShot_ArrayTest(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) + protected void DecryptOneShot_ArrayTest(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) { using (SymmetricAlgorithm alg = CreateAlgorithm()) { @@ -334,6 +354,7 @@ protected void DecryptOneShot_ArrayTest(byte[] plaintext, byte[] ciphertext, Pad { CipherMode.ECB => alg.DecryptEcb(ciphertext, padding), CipherMode.CBC => alg.DecryptCbc(ciphertext, IV, padding), + CipherMode.CFB => alg.DecryptCfb(ciphertext, IV, padding, feedbackSize), _ => throw new NotImplementedException(), }; @@ -341,19 +362,21 @@ protected void DecryptOneShot_ArrayTest(byte[] plaintext, byte[] ciphertext, Pad } } - protected void EncryptOneShot_ArrayTest(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) + protected void EncryptOneShot_ArrayTest(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) { using (SymmetricAlgorithm alg = CreateAlgorithm()) { alg.Key = Key; + int paddingSizeBytes = mode == CipherMode.CFB ? feedbackSize / 8 : alg.BlockSize / 8; byte[] encrypted = mode switch { CipherMode.ECB => alg.EncryptEcb(plaintext, padding), CipherMode.CBC => alg.EncryptCbc(plaintext, IV, padding), + CipherMode.CFB => alg.EncryptCfb(plaintext, IV, padding, feedbackSize), _ => throw new NotImplementedException(), }; - AssertCiphertexts(ciphertext, encrypted, padding, alg.BlockSize / 8); + AssertCiphertexts(ciphertext, encrypted, padding, paddingSizeBytes); } } @@ -398,12 +421,12 @@ private static void AssertPlaintexts(ReadOnlySpan expected, ReadOnlySpan expected, ReadOnlySpan actual, PaddingMode padding, int blockSizeBytes) + private static void AssertCiphertexts(ReadOnlySpan expected, ReadOnlySpan actual, PaddingMode padding, int paddingSizeBytes) { if (padding == PaddingMode.ISO10126) { // The padding is random, so we can't check the exact ciphertext. - AssertExtensions.SequenceEqual(expected[..^blockSizeBytes], actual[..^blockSizeBytes]); + AssertExtensions.SequenceEqual(expected[..^paddingSizeBytes], actual[..^paddingSizeBytes]); } else { diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/TripleDES/TripleDESCipherOneShotTests.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/TripleDES/TripleDESCipherOneShotTests.cs index d5706706858c9..1d9e69c0a87e8 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/TripleDES/TripleDESCipherOneShotTests.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/TripleDES/TripleDESCipherOneShotTests.cs @@ -28,68 +28,88 @@ public class TripleDESCipherOneShotTests : SymmetricOneShotBase [Theory] [MemberData(nameof(TestCases))] - public void OneShotRoundtrip(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - OneShotRoundtripTest(plaintext, ciphertext, padding, mode); + public void OneShotRoundtrip(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + OneShotRoundtripTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void TryDecryptOneShot_DestinationTooSmall(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - TryDecryptOneShot_DestinationTooSmallTest(plaintext, ciphertext, padding, mode); + public void TryDecryptOneShot_DestinationTooSmall(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + TryDecryptOneShot_DestinationTooSmallTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void TryEncryptOneShot_DestinationTooSmall(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - TryEncryptOneShot_DestinationTooSmallTest(plaintext, ciphertext, padding, mode); + public void TryEncryptOneShot_DestinationTooSmall(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + TryEncryptOneShot_DestinationTooSmallTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void TryDecryptOneShot_DestinationJustRight(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - TryDecryptOneShot_DestinationJustRightTest(plaintext, ciphertext, padding, mode); + public void TryDecryptOneShot_DestinationJustRight(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + TryDecryptOneShot_DestinationJustRightTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void TryEncryptOneShot_DestinationJustRight(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - TryEncryptOneShot_DestinationJustRightTest(plaintext, ciphertext, padding, mode); + public void TryEncryptOneShot_DestinationJustRight(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + TryEncryptOneShot_DestinationJustRightTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void TryDecryptOneShot_DestinationLarger(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - TryDecryptOneShot_DestinationLargerTest(plaintext, ciphertext, padding, mode); + public void TryDecryptOneShot_DestinationLarger(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + TryDecryptOneShot_DestinationLargerTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void TryEncryptOneShot_DestinationLarger(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - TryEncryptOneShot_DestinationLargerTest(plaintext, ciphertext, padding, mode); + public void TryEncryptOneShot_DestinationLarger(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + TryEncryptOneShot_DestinationLargerTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void TryDecryptOneShot_Overlaps(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - TryDecryptOneShot_OverlapsTest(plaintext, ciphertext, padding, mode); + public void TryDecryptOneShot_Overlaps(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + TryDecryptOneShot_OverlapsTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void TryEncryptOneShot_Overlaps(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - TryEncryptOneShot_OverlapsTest(plaintext, ciphertext, padding, mode); + public void TryEncryptOneShot_Overlaps(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + TryEncryptOneShot_OverlapsTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void DecryptOneShot_Span(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - DecryptOneShot_SpanTest(plaintext, ciphertext, padding, mode); + public void DecryptOneShot_Span(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + DecryptOneShot_SpanTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void EncryptOneShot_Span(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - EncryptOneShot_SpanTest(plaintext, ciphertext, padding, mode); + public void EncryptOneShot_Span(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + EncryptOneShot_SpanTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void DecryptOneShot_Array(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - DecryptOneShot_ArrayTest(plaintext, ciphertext, padding, mode); + public void DecryptOneShot_Array(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + DecryptOneShot_ArrayTest(plaintext, ciphertext, padding, mode, feedbackSize); [Theory] [MemberData(nameof(TestCases))] - public void EncryptOneShot_Array(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode) => - EncryptOneShot_ArrayTest(plaintext, ciphertext, padding, mode); + public void EncryptOneShot_Array(byte[] plaintext, byte[] ciphertext, PaddingMode padding, CipherMode mode, int feedbackSize = 0) => + EncryptOneShot_ArrayTest(plaintext, ciphertext, padding, mode, feedbackSize); + + [Fact] + public void EncryptOneShot_CfbFeedbackSizeNotSupported() + { + using (SymmetricAlgorithm alg = CreateAlgorithm()) + { + Assert.ThrowsAny(() => + alg.TryEncryptCfb(ReadOnlySpan.Empty, IV, Span.Empty, out _, feedbackSizeInBits: 48)); + } + } + + [Fact] + public void DecryptOneShot_CfbFeedbackSizeNotSupported() + { + using (SymmetricAlgorithm alg = CreateAlgorithm()) + { + Assert.ThrowsAny(() => + alg.TryDecryptCfb(ReadOnlySpan.Empty, IV, Span.Empty, out _, feedbackSizeInBits: 48)); + } + } public static IEnumerable TestCases { @@ -557,6 +577,517 @@ public static IEnumerable TestCases PaddingMode.PKCS7, CipherMode.ECB, }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, + 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, + }, + + // ciphertext + new byte[] + { + 0x46, 0x85, 0x45, 0x43, 0x3E, 0xD9, 0x40, 0xAF, + 0x16, 0xBE, 0xC5, 0xEF, 0xD9, 0x12, 0xFE, 0x07, + 0x66, + }, + + PaddingMode.PKCS7, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, + 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, + }, + + // ciphertext + new byte[] + { + 0x46, 0x85, 0x45, 0x43, 0x3E, 0xD9, 0x40, 0xAF, + 0x16, 0xBE, 0xC5, 0xEF, 0xD9, 0x12, 0xFE, 0x07, + }, + + PaddingMode.Zeros, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, + 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, + }, + + // ciphertext + new byte[] + { + 0x46, 0x85, 0x45, 0x43, 0x3E, 0xD9, 0x40, 0xAF, + 0x16, 0xBE, 0xC5, 0xEF, 0xD9, 0x12, 0xFE, 0x07, + }, + + PaddingMode.None, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, + 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, + }, + + // ciphertext + new byte[] + { + 0x46, 0x85, 0x45, 0x43, 0x3E, 0xD9, 0x40, 0xAF, + 0x16, 0xBE, 0xC5, 0xEF, 0xD9, 0x12, 0xFE, 0x07, + 0x66, + }, + + PaddingMode.ANSIX923, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, + 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, + }, + + // ciphertext + new byte[] + { + 0x46, 0x85, 0x45, 0x43, 0x3E, 0xD9, 0x40, 0xAF, + 0x16, 0xBE, 0xC5, 0xEF, 0xD9, 0x12, 0xFE, 0x07, + 0x66, + }, + + PaddingMode.ISO10126, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, + 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, + 0x59, + }, + + // ciphertext + new byte[] + { + 0x8F, 0xD6, 0x1C, 0xF3, 0xAF, 0x0D, 0x3C, 0x98, + 0xCF, 0x29, 0x20, 0xB3, 0xF3, 0xFC, 0x34, 0xF0, + 0x38, 0xA0, + }, + + PaddingMode.PKCS7, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, + 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, + 0x59, + }, + + // ciphertext + new byte[] + { + 0x8F, 0xD6, 0x1C, 0xF3, 0xAF, 0x0D, 0x3C, 0x98, + 0xCF, 0x29, 0x20, 0xB3, 0xF3, 0xFC, 0x34, 0xF0, + 0x38, + }, + + PaddingMode.None, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, + 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, + 0x59, + }, + + // ciphertext + new byte[] + { + 0x8F, 0xD6, 0x1C, 0xF3, 0xAF, 0x0D, 0x3C, 0x98, + 0xCF, 0x29, 0x20, 0xB3, 0xF3, 0xFC, 0x34, 0xF0, + 0x38, + }, + + PaddingMode.Zeros, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, + 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, + 0x59, + }, + + // ciphertext + new byte[] + { + 0x8F, 0xD6, 0x1C, 0xF3, 0xAF, 0x0D, 0x3C, 0x98, + 0xCF, 0x29, 0x20, 0xB3, 0xF3, 0xFC, 0x34, 0xF0, + 0x38, 0xA0, + }, + + PaddingMode.ANSIX923, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, + 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, + 0x59, + }, + + // ciphertext + new byte[] + { + 0x8F, 0xD6, 0x1C, 0xF3, 0xAF, 0x0D, 0x3C, 0x98, + 0xCF, 0x29, 0x20, 0xB3, 0xF3, 0xFC, 0x34, 0xF0, + 0x38, 0xA0, + }, + + PaddingMode.ISO10126, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + // plaintext + Array.Empty(), + + // ciphertext + new byte[] + { + 0x17, + }, + + PaddingMode.PKCS7, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + // plaintext + Array.Empty(), + + // ciphertext + Array.Empty(), + + PaddingMode.Zeros, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + // plaintext + Array.Empty(), + + // ciphertext + Array.Empty(), + + PaddingMode.None, + CipherMode.CFB, + 8, + }; + + // 3DES CFB64 is not supported on Windows 7. + if (PlatformDetection.IsNotWindows7) + { + yield return new object[] + { + // plaintext + new byte[] + { + 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, + 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, + }, + + // ciphertext + new byte[] + { + 0x46, 0x82, 0xB5, 0xFC, 0x6A, 0xD3, 0x92, 0xF9, + 0xB1, 0xF6, 0xD9, 0xF3, 0xBB, 0x8D, 0x57, 0xE5, + 0xFF, 0x66, 0x88, 0x3C, 0x53, 0xC4, 0x5A, 0xC6, + }, + + PaddingMode.PKCS7, + CipherMode.CFB, + 64, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, + 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, + }, + + // ciphertext + new byte[] + { + 0x46, 0x82, 0xB5, 0xFC, 0x6A, 0xD3, 0x92, 0xF9, + 0xB1, 0xF6, 0xD9, 0xF3, 0xBB, 0x8D, 0x57, 0xE5, + }, + + PaddingMode.None, + CipherMode.CFB, + 64, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, + 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, + }, + + // ciphertext + new byte[] + { + 0x46, 0x82, 0xB5, 0xFC, 0x6A, 0xD3, 0x92, 0xF9, + 0xB1, 0xF6, 0xD9, 0xF3, 0xBB, 0x8D, 0x57, 0xE5, + }, + + PaddingMode.Zeros, + CipherMode.CFB, + 64, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, + 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, + }, + + // ciphertext + new byte[] + { + 0x46, 0x82, 0xB5, 0xFC, 0x6A, 0xD3, 0x92, 0xF9, + 0xB1, 0xF6, 0xD9, 0xF3, 0xBB, 0x8D, 0x57, 0xE5, + 0xF7, 0x6E, 0x80, 0x34, 0x5B, 0xCC, 0x52, 0xC6, + }, + + PaddingMode.ANSIX923, + CipherMode.CFB, + 64, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, + 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, + }, + + // ciphertext + new byte[] + { + 0x46, 0x82, 0xB5, 0xFC, 0x6A, 0xD3, 0x92, 0xF9, + 0xB1, 0xF6, 0xD9, 0xF3, 0xBB, 0x8D, 0x57, 0xE5, + 0x47, 0x0F, 0x9A, 0x12, 0x6F, 0x92, 0xB4, 0xC6, + }, + + PaddingMode.ISO10126, + CipherMode.CFB, + 64, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, + 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, + 0x59, + }, + + // ciphertext + new byte[] + { + 0x8F, 0xBA, 0xCF, 0x4A, 0x91, 0x84, 0x52, 0xB8, + 0xFF, 0x69, 0xE0, 0xAD, 0x2C, 0xEC, 0xE4, 0x8F, + 0xE0, 0x50, 0x64, 0xD5, 0xA3, 0x32, 0x38, 0xA9, + }, + + PaddingMode.PKCS7, + CipherMode.CFB, + 64, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, + 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, + 0x59, + }, + + // ciphertext + new byte[] + { + 0x8F, 0xBA, 0xCF, 0x4A, 0x91, 0x84, 0x52, 0xB8, + 0xFF, 0x69, 0xE0, 0xAD, 0x2C, 0xEC, 0xE4, 0x8F, + 0xE0, 0x57, 0x63, 0xD2, 0xA4, 0x35, 0x3F, 0xAE, + }, + + PaddingMode.Zeros, + CipherMode.CFB, + 64, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, + 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, + 0x59, + }, + + // ciphertext + new byte[] + { + 0x8F, 0xBA, 0xCF, 0x4A, 0x91, 0x84, 0x52, 0xB8, + 0xFF, 0x69, 0xE0, 0xAD, 0x2C, 0xEC, 0xE4, 0x8F, + 0xE0, 0x57, 0x63, 0xD2, 0xA4, 0x35, 0x3F, 0xA9, + }, + + PaddingMode.ANSIX923, + CipherMode.CFB, + 64, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, + 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, + 0x59, + }, + + // ciphertext + new byte[] + { + 0x8F, 0xBA, 0xCF, 0x4A, 0x91, 0x84, 0x52, 0xB8, + 0xFF, 0x69, 0xE0, 0xAD, 0x2C, 0xEC, 0xE4, 0x8F, + 0xE0, 0xE7, 0xF6, 0x44, 0xBE, 0xDD, 0x3D, 0xA9, + }, + + PaddingMode.ISO10126, + CipherMode.CFB, + 64, + }; + + yield return new object[] + { + // plaintext + Array.Empty(), + + // ciphertext + new byte[] + { + 0x1E, 0xE2, 0xAF, 0x50, 0x3D, 0xD3, 0x52, 0x78, + }, + + PaddingMode.PKCS7, + CipherMode.CFB, + 64, + }; + + yield return new object[] + { + // plaintext + Array.Empty(), + + // ciphertext + Array.Empty(), + + PaddingMode.None, + CipherMode.CFB, + 64, + }; + + yield return new object[] + { + // plaintext + Array.Empty(), + + // ciphertext + Array.Empty(), + + PaddingMode.Zeros, + CipherMode.CFB, + 64, + }; + } } } } diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/TripleDES/TripleDESCipherTests.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/TripleDES/TripleDESCipherTests.cs index 64b3203b4b7cc..fc627effbc78d 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/TripleDES/TripleDESCipherTests.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/TripleDES/TripleDESCipherTests.cs @@ -499,6 +499,7 @@ private static void TestTripleDESTransformDirectKey( { tdes.Mode = cipherMode; tdes.Padding = paddingMode; + tdes.Key = key; if (feedbackSize.HasValue) { @@ -510,16 +511,19 @@ private static void TestTripleDESTransformDirectKey( if (cipherMode == CipherMode.ECB) { - tdes.Key = key; liveOneShotDecryptBytes = tdes.DecryptEcb(cipherBytes, paddingMode); liveOneShotEncryptBytes = tdes.EncryptEcb(plainBytes, paddingMode); } else if (cipherMode == CipherMode.CBC) { - tdes.Key = key; liveOneShotDecryptBytes = tdes.DecryptCbc(cipherBytes, iv, paddingMode); liveOneShotEncryptBytes = tdes.EncryptCbc(plainBytes, iv, paddingMode); } + else if (cipherMode == CipherMode.CFB) + { + liveOneShotDecryptBytes = tdes.DecryptCfb(cipherBytes, iv, paddingMode, feedbackSizeInBits: feedbackSize.Value); + liveOneShotEncryptBytes = tdes.EncryptCfb(plainBytes, iv, paddingMode, feedbackSizeInBits: feedbackSize.Value); + } if (liveOneShotDecryptBytes is not null) { diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/AesImplementation.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/AesImplementation.cs index b08732a5aeaf2..dee729afd93da 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/AesImplementation.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/AesImplementation.cs @@ -133,6 +133,58 @@ protected override bool TryDecryptCbcCore( } } + protected override bool TryDecryptCfbCore( + ReadOnlySpan ciphertext, + ReadOnlySpan iv, + Span destination, + PaddingMode paddingMode, + int feedbackSizeInBits, + out int bytesWritten) + { + ValidateCFBFeedbackSize(feedbackSizeInBits); + + UniversalCryptoTransform transform = CreateTransformCore( + CipherMode.CFB, + paddingMode, + Key, + iv: iv.ToArray(), + blockSize: BlockSize / BitsPerByte, + paddingSize: feedbackSizeInBits / BitsPerByte, + feedbackSizeInBits / BitsPerByte, + encrypting: false); + + using (transform) + { + return transform.TransformOneShot(ciphertext, destination, out bytesWritten); + } + } + + protected override bool TryEncryptCfbCore( + ReadOnlySpan plaintext, + ReadOnlySpan iv, + Span destination, + PaddingMode paddingMode, + int feedbackSizeInBits, + out int bytesWritten) + { + ValidateCFBFeedbackSize(feedbackSizeInBits); + + UniversalCryptoTransform transform = CreateTransformCore( + CipherMode.CFB, + paddingMode, + Key, + iv: iv.ToArray(), + blockSize: BlockSize / BitsPerByte, + paddingSize: feedbackSizeInBits / BitsPerByte, + feedbackSizeInBits / BitsPerByte, + encrypting: true); + + using (transform) + { + return transform.TransformOneShot(plaintext, destination, out bytesWritten); + } + } + private ICryptoTransform CreateTransform(byte[] rgbKey, byte[]? rgbIV, bool encrypting) { // note: rbgIV is guaranteed to be cloned before this method, so no need to clone it again diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/DesImplementation.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/DesImplementation.cs index d0b960fc4ae8a..e2fbafdbd15d1 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/DesImplementation.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/DesImplementation.cs @@ -177,6 +177,58 @@ protected override bool TryDecryptCbcCore( } } + protected override bool TryDecryptCfbCore( + ReadOnlySpan ciphertext, + ReadOnlySpan iv, + Span destination, + PaddingMode paddingMode, + int feedbackSizeInBits, + out int bytesWritten) + { + ValidateCFBFeedbackSize(feedbackSizeInBits); + + UniversalCryptoTransform transform = CreateTransformCore( + CipherMode.CFB, + paddingMode, + Key, + iv: iv.ToArray(), + blockSize: BlockSize / BitsPerByte, + feedbackSizeInBits / BitsPerByte, + paddingSize: feedbackSizeInBits / BitsPerByte, + encrypting: false); + + using (transform) + { + return transform.TransformOneShot(ciphertext, destination, out bytesWritten); + } + } + + protected override bool TryEncryptCfbCore( + ReadOnlySpan plaintext, + ReadOnlySpan iv, + Span destination, + PaddingMode paddingMode, + int feedbackSizeInBits, + out int bytesWritten) + { + ValidateCFBFeedbackSize(feedbackSizeInBits); + + UniversalCryptoTransform transform = CreateTransformCore( + CipherMode.CFB, + paddingMode, + Key, + iv: iv.ToArray(), + blockSize: BlockSize / BitsPerByte, + feedbackSizeInBits / BitsPerByte, + paddingSize: feedbackSizeInBits / BitsPerByte, + encrypting: true); + + using (transform) + { + return transform.TransformOneShot(plaintext, destination, out bytesWritten); + } + } + private static void ValidateCFBFeedbackSize(int feedback) { // only 8bits feedback is available on all platforms diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/RC2Implementation.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/RC2Implementation.cs index 33f3998499093..0221bb5775bbb 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/RC2Implementation.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/RC2Implementation.cs @@ -190,6 +190,28 @@ protected override bool TryDecryptCbcCore( } } + protected override bool TryDecryptCfbCore( + ReadOnlySpan ciphertext, + ReadOnlySpan iv, + Span destination, + PaddingMode paddingMode, + int feedbackSizeInBits, + out int bytesWritten) + { + throw new CryptographicException(SR.Format(SR.Cryptography_CipherModeNotSupported, CipherMode.CFB)); + } + + protected override bool TryEncryptCfbCore( + ReadOnlySpan plaintext, + ReadOnlySpan iv, + Span destination, + PaddingMode paddingMode, + int feedbackSizeInBits, + out int bytesWritten) + { + throw new CryptographicException(SR.Format(SR.Cryptography_CipherModeNotSupported, CipherMode.CFB)); + } + private static void ValidateCFBFeedbackSize(int feedback) { // CFB not supported at all diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/TripleDesImplementation.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/TripleDesImplementation.cs index 593287656423f..e79f466f13181 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/TripleDesImplementation.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/TripleDesImplementation.cs @@ -182,6 +182,58 @@ protected override bool TryDecryptCbcCore( } } + protected override bool TryDecryptCfbCore( + ReadOnlySpan ciphertext, + ReadOnlySpan iv, + Span destination, + PaddingMode paddingMode, + int feedbackSizeInBits, + out int bytesWritten) + { + ValidateCFBFeedbackSize(feedbackSizeInBits); + + UniversalCryptoTransform transform = CreateTransformCore( + CipherMode.CFB, + paddingMode, + Key, + iv: iv.ToArray(), + blockSize: BlockSize / BitsPerByte, + paddingSize: feedbackSizeInBits / BitsPerByte, + feedbackSizeInBits / BitsPerByte, + encrypting: false); + + using (transform) + { + return transform.TransformOneShot(ciphertext, destination, out bytesWritten); + } + } + + protected override bool TryEncryptCfbCore( + ReadOnlySpan plaintext, + ReadOnlySpan iv, + Span destination, + PaddingMode paddingMode, + int feedbackSizeInBits, + out int bytesWritten) + { + ValidateCFBFeedbackSize(feedbackSizeInBits); + + UniversalCryptoTransform transform = CreateTransformCore( + CipherMode.CFB, + paddingMode, + Key, + iv: iv.ToArray(), + blockSize: BlockSize / BitsPerByte, + paddingSize: feedbackSizeInBits / BitsPerByte, + feedbackSizeInBits / BitsPerByte, + encrypting: true); + + using (transform) + { + return transform.TransformOneShot(plaintext, destination, out bytesWritten); + } + } + private static void ValidateCFBFeedbackSize(int feedback) { // only 8bits/64bits feedback would be valid. diff --git a/src/libraries/System.Security.Cryptography.Algorithms/tests/DESProvider.cs b/src/libraries/System.Security.Cryptography.Algorithms/tests/DESProvider.cs index e4cb6a6302ee3..4679eb99b1678 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/tests/DESProvider.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/tests/DESProvider.cs @@ -5,10 +5,8 @@ namespace System.Security.Cryptography.Encryption.Des.Tests { internal class DesProvider : IDESProvider { - public DES Create() - { - return DES.Create(); - } + public DES Create() => DES.Create(); + public bool OneShotSupported => true; } public partial class DESFactory diff --git a/src/libraries/System.Security.Cryptography.Cng/src/Internal/Cryptography/CngSymmetricAlgorithmCore.cs b/src/libraries/System.Security.Cryptography.Cng/src/Internal/Cryptography/CngSymmetricAlgorithmCore.cs index 6b323ccf4607a..6da56739bb854 100644 --- a/src/libraries/System.Security.Cryptography.Cng/src/Internal/Cryptography/CngSymmetricAlgorithmCore.cs +++ b/src/libraries/System.Security.Cryptography.Cng/src/Internal/Cryptography/CngSymmetricAlgorithmCore.cs @@ -111,35 +111,35 @@ public ICryptoTransform CreateDecryptor() public ICryptoTransform CreateEncryptor(byte[] rgbKey, byte[]? rgbIV) { - return CreateCryptoTransform(rgbKey, rgbIV, encrypting: true, _outer.Padding, _outer.Mode); + return CreateCryptoTransform(rgbKey, rgbIV, encrypting: true, _outer.Padding, _outer.Mode, _outer.FeedbackSize); } public ICryptoTransform CreateDecryptor(byte[] rgbKey, byte[]? rgbIV) { - return CreateCryptoTransform(rgbKey, rgbIV, encrypting: false, _outer.Padding, _outer.Mode); + return CreateCryptoTransform(rgbKey, rgbIV, encrypting: false, _outer.Padding, _outer.Mode, _outer.FeedbackSize); } private ICryptoTransform CreateCryptoTransform(bool encrypting) { if (KeyInPlainText) { - return CreateCryptoTransform(_outer.BaseKey, _outer.IV, encrypting, _outer.Padding, _outer.Mode); + return CreateCryptoTransform(_outer.BaseKey, _outer.IV, encrypting, _outer.Padding, _outer.Mode, _outer.FeedbackSize); } - return CreatePersistedCryptoTransformCore(ProduceCngKey, _outer.IV, encrypting, _outer.Padding, _outer.Mode); + return CreatePersistedCryptoTransformCore(ProduceCngKey, _outer.IV, encrypting, _outer.Padding, _outer.Mode, _outer.FeedbackSize); } - public UniversalCryptoTransform CreateCryptoTransform(byte[]? iv, bool encrypting, PaddingMode padding, CipherMode mode) + public UniversalCryptoTransform CreateCryptoTransform(byte[]? iv, bool encrypting, PaddingMode padding, CipherMode mode, int feedbackSizeInBits) { if (KeyInPlainText) { - return CreateCryptoTransform(_outer.BaseKey, iv, encrypting, padding, mode); + return CreateCryptoTransform(_outer.BaseKey, iv, encrypting, padding, mode, feedbackSizeInBits); } - return CreatePersistedCryptoTransformCore(ProduceCngKey, iv, encrypting, padding, mode); + return CreatePersistedCryptoTransformCore(ProduceCngKey, iv, encrypting, padding, mode, feedbackSizeInBits); } - private UniversalCryptoTransform CreateCryptoTransform(byte[] rgbKey, byte[]? rgbIV, bool encrypting, PaddingMode padding, CipherMode mode) + private UniversalCryptoTransform CreateCryptoTransform(byte[] rgbKey, byte[]? rgbIV, bool encrypting, PaddingMode padding, CipherMode mode, int feedbackSizeInBits) { if (rgbKey == null) throw new ArgumentNullException(nameof(rgbKey)); @@ -162,19 +162,19 @@ private UniversalCryptoTransform CreateCryptoTransform(byte[] rgbKey, byte[]? rg key = _outer.PreprocessKey(key); - return CreateEphemeralCryptoTransformCore(key, iv, encrypting, padding, mode); + return CreateEphemeralCryptoTransformCore(key, iv, encrypting, padding, mode, feedbackSizeInBits); } - private UniversalCryptoTransform CreateEphemeralCryptoTransformCore(byte[] key, byte[]? iv, bool encrypting, PaddingMode padding, CipherMode mode) + private UniversalCryptoTransform CreateEphemeralCryptoTransformCore(byte[] key, byte[]? iv, bool encrypting, PaddingMode padding, CipherMode mode, int feedbackSizeInBits) { int blockSizeInBytes = _outer.BlockSize.BitSizeToByteSize(); - SafeAlgorithmHandle algorithmModeHandle = _outer.GetEphemeralModeHandle(mode); + SafeAlgorithmHandle algorithmModeHandle = _outer.GetEphemeralModeHandle(mode, feedbackSizeInBits); BasicSymmetricCipher cipher = new BasicSymmetricCipherBCrypt( algorithmModeHandle, mode, blockSizeInBytes, - _outer.GetPaddingSize(mode, _outer.FeedbackSize), + _outer.GetPaddingSize(mode, feedbackSizeInBits), key, ownsParentHandle: false, iv, @@ -183,20 +183,19 @@ private UniversalCryptoTransform CreateEphemeralCryptoTransformCore(byte[] key, return UniversalCryptoTransform.Create(padding, cipher, encrypting); } - private UniversalCryptoTransform CreatePersistedCryptoTransformCore(Func cngKeyFactory, byte[]? iv, bool encrypting, PaddingMode padding, CipherMode mode) + private UniversalCryptoTransform CreatePersistedCryptoTransformCore(Func cngKeyFactory, byte[]? iv, bool encrypting, PaddingMode padding, CipherMode mode, int feedbackSizeInBits) { // note: iv is guaranteed to be cloned before this method, so no need to clone it again int blockSizeInBytes = _outer.BlockSize.BitSizeToByteSize(); - int feedbackSizeInBytes = _outer.FeedbackSize; BasicSymmetricCipher cipher = new BasicSymmetricCipherNCrypt( cngKeyFactory, mode, blockSizeInBytes, iv, encrypting, - feedbackSizeInBytes, - _outer.GetPaddingSize(mode, _outer.FeedbackSize)); + feedbackSizeInBits, + _outer.GetPaddingSize(mode, feedbackSizeInBits)); return UniversalCryptoTransform.Create(padding, cipher, encrypting); } @@ -207,7 +206,7 @@ private CngKey ProduceCngKey() return CngKey.Open(_keyName!, _provider!, _optionOptions); } - private bool KeyInPlainText + public bool KeyInPlainText { get { return _keyName == null; } } diff --git a/src/libraries/System.Security.Cryptography.Cng/src/Internal/Cryptography/ICngSymmetricAlgorithm.cs b/src/libraries/System.Security.Cryptography.Cng/src/Internal/Cryptography/ICngSymmetricAlgorithm.cs index 1e1edf7c792b8..eae2d44dec5c1 100644 --- a/src/libraries/System.Security.Cryptography.Cng/src/Internal/Cryptography/ICngSymmetricAlgorithm.cs +++ b/src/libraries/System.Security.Cryptography.Cng/src/Internal/Cryptography/ICngSymmetricAlgorithm.cs @@ -28,7 +28,7 @@ internal interface ICngSymmetricAlgorithm // Other members. bool IsWeakKey(byte[] key); - SafeAlgorithmHandle GetEphemeralModeHandle(CipherMode mode); + SafeAlgorithmHandle GetEphemeralModeHandle(CipherMode mode, int feedbackSizeInBits); string GetNCryptAlgorithmIdentifier(); byte[] PreprocessKey(byte[] key); int GetPaddingSize(CipherMode mode, int feedbackSizeBits); diff --git a/src/libraries/System.Security.Cryptography.Cng/src/Resources/Strings.resx b/src/libraries/System.Security.Cryptography.Cng/src/Resources/Strings.resx index bc28c3819fc23..b919b72f36266 100644 --- a/src/libraries/System.Security.Cryptography.Cng/src/Resources/Strings.resx +++ b/src/libraries/System.Security.Cryptography.Cng/src/Resources/Strings.resx @@ -126,6 +126,9 @@ Keys used with the RSACng algorithm must have an algorithm group of RSA. + + The specified feedback size '{0}' for CipherMode '{1}' is not supported. + This key is for algorithm '{0}'. Expected '{1}'. diff --git a/src/libraries/System.Security.Cryptography.Cng/src/System/Security/Cryptography/AesCng.cs b/src/libraries/System.Security.Cryptography.Cng/src/System/Security/Cryptography/AesCng.cs index 311bf2702ab0c..dbcbd0bd8cea6 100644 --- a/src/libraries/System.Security.Cryptography.Cng/src/System/Security/Cryptography/AesCng.cs +++ b/src/libraries/System.Security.Cryptography.Cng/src/System/Security/Cryptography/AesCng.cs @@ -102,7 +102,8 @@ protected override bool TryDecryptEcbCore( iv: null, encrypting: false, paddingMode, - CipherMode.ECB); + CipherMode.ECB, + feedbackSizeInBits: 0); using (transform) { @@ -120,7 +121,8 @@ protected override bool TryEncryptEcbCore( iv: null, encrypting: true, paddingMode, - CipherMode.ECB); + CipherMode.ECB, + feedbackSizeInBits: 0); using (transform) { @@ -139,7 +141,8 @@ protected override bool TryEncryptCbcCore( iv: iv.ToArray(), encrypting: true, paddingMode, - CipherMode.CBC); + CipherMode.CBC, + feedbackSizeInBits: 0); using (transform) { @@ -158,7 +161,8 @@ protected override bool TryDecryptCbcCore( iv: iv.ToArray(), encrypting: false, paddingMode, - CipherMode.CBC); + CipherMode.CBC, + feedbackSizeInBits: 0); using (transform) { @@ -166,6 +170,72 @@ protected override bool TryDecryptCbcCore( } } + protected override bool TryDecryptCfbCore( + ReadOnlySpan ciphertext, + ReadOnlySpan iv, + Span destination, + PaddingMode paddingMode, + int feedbackSizeInBits, + out int bytesWritten) + { + ValidateCFBFeedbackSize(feedbackSizeInBits); + + UniversalCryptoTransform transform = _core.CreateCryptoTransform( + iv: iv.ToArray(), + encrypting: false, + paddingMode, + CipherMode.CFB, + feedbackSizeInBits); + + using (transform) + { + return transform.TransformOneShot(ciphertext, destination, out bytesWritten); + } + } + + protected override bool TryEncryptCfbCore( + ReadOnlySpan plaintext, + ReadOnlySpan iv, + Span destination, + PaddingMode paddingMode, + int feedbackSizeInBits, + out int bytesWritten) + { + ValidateCFBFeedbackSize(feedbackSizeInBits); + + UniversalCryptoTransform transform = _core.CreateCryptoTransform( + iv: iv.ToArray(), + encrypting: true, + paddingMode, + CipherMode.CFB, + feedbackSizeInBits); + + using (transform) + { + return transform.TransformOneShot(plaintext, destination, out bytesWritten); + } + } + + private void ValidateCFBFeedbackSize(int feedback) + { + if (_core.KeyInPlainText) + { + // CFB8 and CFB128 are valid for bcrypt keys. + if (feedback != 8 && feedback != 128) + { + throw new CryptographicException(string.Format(SR.Cryptography_CipherModeFeedbackNotSupported, feedback, CipherMode.CFB)); + } + } + else + { + // only CFB8 is supported for ncrypt keys. + if (feedback != 8) + { + throw new CryptographicException(string.Format(SR.Cryptography_CipherModeFeedbackNotSupported, feedback, CipherMode.CFB)); + } + } + } + protected override void Dispose(bool disposing) { base.Dispose(disposing); @@ -184,11 +254,11 @@ int ICngSymmetricAlgorithm.GetPaddingSize(CipherMode mode, int feedbackSizeBits) return this.GetPaddingSize(mode, feedbackSizeBits); } - SafeAlgorithmHandle ICngSymmetricAlgorithm.GetEphemeralModeHandle(CipherMode mode) + SafeAlgorithmHandle ICngSymmetricAlgorithm.GetEphemeralModeHandle(CipherMode mode, int feedbackSizeInBits) { try { - return AesBCryptModes.GetSharedHandle(mode, FeedbackSize / 8); + return AesBCryptModes.GetSharedHandle(mode, feedbackSizeInBits / 8); } catch (NotSupportedException) { diff --git a/src/libraries/System.Security.Cryptography.Cng/src/System/Security/Cryptography/TripleDESCng.cs b/src/libraries/System.Security.Cryptography.Cng/src/System/Security/Cryptography/TripleDESCng.cs index b03c756c1ad98..09d2b02243530 100644 --- a/src/libraries/System.Security.Cryptography.Cng/src/System/Security/Cryptography/TripleDESCng.cs +++ b/src/libraries/System.Security.Cryptography.Cng/src/System/Security/Cryptography/TripleDESCng.cs @@ -103,7 +103,8 @@ protected override bool TryDecryptEcbCore( iv: null, encrypting: false, paddingMode, - CipherMode.ECB); + CipherMode.ECB, + feedbackSizeInBits: 0); using (transform) { @@ -121,7 +122,8 @@ protected override bool TryEncryptEcbCore( iv: null, encrypting: true, paddingMode, - CipherMode.ECB); + CipherMode.ECB, + feedbackSizeInBits: 0); using (transform) { @@ -140,7 +142,8 @@ protected override bool TryEncryptCbcCore( iv: iv.ToArray(), encrypting: true, paddingMode, - CipherMode.CBC); + CipherMode.CBC, + feedbackSizeInBits: 0); using (transform) { @@ -159,7 +162,8 @@ protected override bool TryDecryptCbcCore( iv: iv.ToArray(), encrypting: false, paddingMode, - CipherMode.CBC); + CipherMode.CBC, + feedbackSizeInBits: 0); using (transform) { @@ -167,11 +171,77 @@ protected override bool TryDecryptCbcCore( } } + protected override bool TryDecryptCfbCore( + ReadOnlySpan ciphertext, + ReadOnlySpan iv, + Span destination, + PaddingMode paddingMode, + int feedbackSizeInBits, + out int bytesWritten) + { + ValidateCFBFeedbackSize(feedbackSizeInBits); + + UniversalCryptoTransform transform = _core.CreateCryptoTransform( + iv: iv.ToArray(), + encrypting: false, + paddingMode, + CipherMode.CFB, + feedbackSizeInBits); + + using (transform) + { + return transform.TransformOneShot(ciphertext, destination, out bytesWritten); + } + } + + protected override bool TryEncryptCfbCore( + ReadOnlySpan plaintext, + ReadOnlySpan iv, + Span destination, + PaddingMode paddingMode, + int feedbackSizeInBits, + out int bytesWritten) + { + ValidateCFBFeedbackSize(feedbackSizeInBits); + + UniversalCryptoTransform transform = _core.CreateCryptoTransform( + iv: iv.ToArray(), + encrypting: true, + paddingMode, + CipherMode.CFB, + feedbackSizeInBits); + + using (transform) + { + return transform.TransformOneShot(plaintext, destination, out bytesWritten); + } + } + protected override void Dispose(bool disposing) { base.Dispose(disposing); } + private void ValidateCFBFeedbackSize(int feedback) + { + if (_core.KeyInPlainText) + { + // CFB8 and CFB164 are valid for bcrypt keys. + if (feedback != 8 && feedback != 64) + { + throw new CryptographicException(string.Format(SR.Cryptography_CipherModeFeedbackNotSupported, feedback, CipherMode.CFB)); + } + } + else + { + // only CFB8 is supported for ncrypt keys. + if (feedback != 8) + { + throw new CryptographicException(string.Format(SR.Cryptography_CipherModeFeedbackNotSupported, feedback, CipherMode.CFB)); + } + } + } + byte[] ICngSymmetricAlgorithm.BaseKey { get { return base.Key; } set { base.Key = value; } } int ICngSymmetricAlgorithm.BaseKeySize { get { return base.KeySize; } set { base.KeySize = value; } } @@ -185,9 +255,9 @@ int ICngSymmetricAlgorithm.GetPaddingSize(CipherMode mode, int feedbackSizeBits) return this.GetPaddingSize(mode, feedbackSizeBits); } - SafeAlgorithmHandle ICngSymmetricAlgorithm.GetEphemeralModeHandle(CipherMode mode) + SafeAlgorithmHandle ICngSymmetricAlgorithm.GetEphemeralModeHandle(CipherMode mode, int feedbackSizeInBits) { - return TripleDesBCryptModes.GetSharedHandle(mode, FeedbackSize / 8); + return TripleDesBCryptModes.GetSharedHandle(mode, feedbackSizeInBits / 8); } string ICngSymmetricAlgorithm.GetNCryptAlgorithmIdentifier() diff --git a/src/libraries/System.Security.Cryptography.Cng/tests/AesCngTests.cs b/src/libraries/System.Security.Cryptography.Cng/tests/AesCngTests.cs index dc6d484263c0c..47d4f4b5dd6c4 100644 --- a/src/libraries/System.Security.Cryptography.Cng/tests/AesCngTests.cs +++ b/src/libraries/System.Security.Cryptography.Cng/tests/AesCngTests.cs @@ -25,12 +25,21 @@ public static class AesCngTests [InlineData(256, BlockSizeBytes + BlockSizeBytes / 2, CipherMode.CBC, PaddingMode.Zeros)] // AES192-CBC-PKCS7 at 1.5 blocks [InlineData(192, BlockSizeBytes + BlockSizeBytes / 2, CipherMode.CBC, PaddingMode.PKCS7)] + // AES128-CFB8-NoPadding at 2 blocks + [InlineData(128, 2 * BlockSizeBytes, CipherMode.CFB, PaddingMode.None, 8)] public static void VerifyPersistedKey( int keySize, int plainBytesCount, CipherMode cipherMode, - PaddingMode paddingMode) + PaddingMode paddingMode, + int feedbackSizeInBits = 0) { + // Windows 7 does not support CFB except in CFB8 mode. + if (cipherMode == CipherMode.CFB && feedbackSizeInBits != 8 && PlatformDetection.IsWindows7) + { + return; + } + SymmetricCngTestHelpers.VerifyPersistedKey( s_cngAlgorithm, keySize, @@ -38,7 +47,8 @@ public static void VerifyPersistedKey( keyName => new AesCng(keyName), () => new AesCng(), cipherMode, - paddingMode); + paddingMode, + feedbackSizeInBits); } @@ -88,6 +98,16 @@ public static void VerifyMachineKey() () => new AesCng()); } + [OuterLoop("Creates/Deletes a persisted key, limit exposure to key leaking")] + [ConditionalFact(nameof(SupportsPersistedSymmetricKeys))] + public static void VerifyUnsupportedFeedbackSizeForPersistedCfb() + { + SymmetricCngTestHelpers.VerifyOneShotCfbPersistedUnsupportedFeedbackSize( + s_cngAlgorithm, + keyName => new AesCng(keyName), + notSupportedFeedbackSizeInBits: 128); + } + public static bool SupportsPersistedSymmetricKeys { get { return SymmetricCngTestHelpers.SupportsPersistedSymmetricKeys; } diff --git a/src/libraries/System.Security.Cryptography.Cng/tests/SymmetricCngTestHelpers.cs b/src/libraries/System.Security.Cryptography.Cng/tests/SymmetricCngTestHelpers.cs index 9810072403fa0..9a3c728efb16d 100644 --- a/src/libraries/System.Security.Cryptography.Cng/tests/SymmetricCngTestHelpers.cs +++ b/src/libraries/System.Security.Cryptography.Cng/tests/SymmetricCngTestHelpers.cs @@ -18,7 +18,8 @@ internal static void VerifyPersistedKey( Func persistedFunc, Func ephemeralFunc, CipherMode cipherMode, - PaddingMode paddingMode) + PaddingMode paddingMode, + int feedbackSizeInBits) { string keyName = Guid.NewGuid().ToString(); CngKeyCreationParameters creationParameters = new CngKeyCreationParameters @@ -41,7 +42,8 @@ internal static void VerifyPersistedKey( persistedFunc, ephemeralFunc, cipherMode, - paddingMode); + paddingMode, + feedbackSizeInBits); } finally { @@ -56,7 +58,8 @@ internal static void VerifyPersistedKey( Func persistedFunc, Func ephemeralFunc, CipherMode cipherMode, - PaddingMode paddingMode) + PaddingMode paddingMode, + int feedbackSizeInBits) { byte[] plainBytes = GenerateRandom(plainBytesCount); @@ -66,6 +69,11 @@ internal static void VerifyPersistedKey( persisted.Mode = ephemeral.Mode = cipherMode; persisted.Padding = ephemeral.Padding = paddingMode; + if (cipherMode == CipherMode.CFB) + { + persisted.FeedbackSize = ephemeral.FeedbackSize = feedbackSizeInBits; + } + ephemeral.Key = persisted.Key; ephemeral.GenerateIV(); persisted.IV = ephemeral.IV; @@ -117,6 +125,12 @@ internal static void VerifyPersistedKey( oneShotEphemeralEncrypted = ephemeral.EncryptCbc(plainBytes, ephemeral.IV, paddingMode); oneShotPersistedDecrypted = persisted.DecryptCbc(oneShotEphemeralEncrypted, persisted.IV, paddingMode); } + else if (cipherMode == CipherMode.CFB) + { + oneShotPersistedEncrypted = persisted.EncryptCfb(plainBytes, persisted.IV, paddingMode, feedbackSizeInBits); + oneShotEphemeralEncrypted = ephemeral.EncryptCfb(plainBytes, ephemeral.IV, paddingMode, feedbackSizeInBits); + oneShotPersistedDecrypted = persisted.DecryptCfb(oneShotEphemeralEncrypted, persisted.IV, paddingMode, feedbackSizeInBits); + } if (oneShotPersistedEncrypted is not null) { @@ -280,7 +294,8 @@ public static void VerifyMachineKey( persistedFunc, ephemeralFunc, CipherMode.CBC, - PaddingMode.PKCS7); + PaddingMode.PKCS7, + feedbackSizeInBits: 0); } finally { @@ -289,6 +304,32 @@ public static void VerifyMachineKey( } } + public static void VerifyOneShotCfbPersistedUnsupportedFeedbackSize( + CngAlgorithm algorithm, + Func persistedFunc, + int notSupportedFeedbackSizeInBits) + { + string keyName = Guid.NewGuid().ToString(); + + // We try to delete the key later which will also dispose of it, so no need + // to put this in a using. + CngKey cngKey = CngKey.Create(algorithm, keyName); + + try + { + using (SymmetricAlgorithm alg = persistedFunc(keyName)) + { + byte[] destination = new byte[alg.BlockSize / 8]; + Assert.ThrowsAny(() => + alg.EncryptCfb(Array.Empty(), destination, PaddingMode.None, notSupportedFeedbackSizeInBits)); + } + } + finally + { + cngKey.Delete(); + } + } + private static bool? s_supportsPersistedSymmetricKeys; internal static bool SupportsPersistedSymmetricKeys { diff --git a/src/libraries/System.Security.Cryptography.Cng/tests/TripleDESCngTests.cs b/src/libraries/System.Security.Cryptography.Cng/tests/TripleDESCngTests.cs index d68aa596c27d8..4a913be0a2bbb 100644 --- a/src/libraries/System.Security.Cryptography.Cng/tests/TripleDESCngTests.cs +++ b/src/libraries/System.Security.Cryptography.Cng/tests/TripleDESCngTests.cs @@ -39,10 +39,13 @@ public static void VerifyDefaults() [InlineData(BlockSizeBytes + BlockSizeBytes / 2, CipherMode.CBC, PaddingMode.Zeros)] // 3DES192-CBC-PKCS7 at 1.5 blocks [InlineData(BlockSizeBytes + BlockSizeBytes / 2, CipherMode.CBC, PaddingMode.PKCS7)] + // 3DES192-CFB8-NoPadding at 2 blocks + [InlineData(2 * BlockSizeBytes, CipherMode.CFB, PaddingMode.None, 8)] public static void VerifyPersistedKey( int plainBytesCount, CipherMode cipherMode, - PaddingMode paddingMode) + PaddingMode paddingMode, + int feedbackSizeInBits = 0) { SymmetricCngTestHelpers.VerifyPersistedKey( s_cngAlgorithm, @@ -51,7 +54,8 @@ public static void VerifyPersistedKey( keyName => new TripleDESCng(keyName), () => new TripleDESCng(), cipherMode, - paddingMode); + paddingMode, + feedbackSizeInBits); } [OuterLoop(/* Creates/Deletes a persisted key, limit exposure to key leaking */)] @@ -100,6 +104,16 @@ public static void VerifyMachineKey() () => new TripleDESCng()); } + [OuterLoop("Creates/Deletes a persisted key, limit exposure to key leaking")] + [ConditionalFact(nameof(SupportsPersistedSymmetricKeys))] + public static void VerifyUnsupportedFeedbackSizeForPersistedCfb() + { + SymmetricCngTestHelpers.VerifyOneShotCfbPersistedUnsupportedFeedbackSize( + s_cngAlgorithm, + keyName => new TripleDESCng(keyName), + notSupportedFeedbackSizeInBits: 64); + } + public static bool SupportsPersistedSymmetricKeys { get { return SymmetricCngTestHelpers.SupportsPersistedSymmetricKeys; } diff --git a/src/libraries/System.Security.Cryptography.Csp/tests/DESCryptoServiceProviderProvider.cs b/src/libraries/System.Security.Cryptography.Csp/tests/DESCryptoServiceProviderProvider.cs index 7181d95edc590..7d049de7e6683 100644 --- a/src/libraries/System.Security.Cryptography.Csp/tests/DESCryptoServiceProviderProvider.cs +++ b/src/libraries/System.Security.Cryptography.Csp/tests/DESCryptoServiceProviderProvider.cs @@ -5,10 +5,8 @@ namespace System.Security.Cryptography.Encryption.Des.Tests { public class DESCryptoServiceProviderProvider : IDESProvider { - public DES Create() - { - return new DESCryptoServiceProvider(); - } + public DES Create() => new DESCryptoServiceProvider(); + public bool OneShotSupported => false; } public partial class DESFactory diff --git a/src/libraries/System.Security.Cryptography.Csp/tests/ShimHelpers.cs b/src/libraries/System.Security.Cryptography.Csp/tests/ShimHelpers.cs index b14a21f2daafa..bf8d40dcf4ee1 100644 --- a/src/libraries/System.Security.Cryptography.Csp/tests/ShimHelpers.cs +++ b/src/libraries/System.Security.Cryptography.Csp/tests/ShimHelpers.cs @@ -82,6 +82,8 @@ public static void VerifyAllBaseMembersOverloaded(Type shimType) "TryDecryptEcbCore", "TryEncryptCbcCore", "TryDecryptCbcCore", + "TryEncryptCfbCore", + "TryDecryptCfbCore", }; IEnumerable baseMethods = shimType. diff --git a/src/libraries/System.Security.Cryptography.Primitives/ref/System.Security.Cryptography.Primitives.cs b/src/libraries/System.Security.Cryptography.Primitives/ref/System.Security.Cryptography.Primitives.cs index 5eee91bb97aa5..d04196601cfdc 100644 --- a/src/libraries/System.Security.Cryptography.Primitives/ref/System.Security.Cryptography.Primitives.cs +++ b/src/libraries/System.Security.Cryptography.Primitives/ref/System.Security.Cryptography.Primitives.cs @@ -256,6 +256,9 @@ public void Clear() { } public byte[] DecryptCbc(byte[] ciphertext, byte[] iv, System.Security.Cryptography.PaddingMode paddingMode = System.Security.Cryptography.PaddingMode.PKCS7) { throw null; } public byte[] DecryptCbc(System.ReadOnlySpan ciphertext, System.ReadOnlySpan iv, System.Security.Cryptography.PaddingMode paddingMode = System.Security.Cryptography.PaddingMode.PKCS7) { throw null; } public int DecryptCbc(System.ReadOnlySpan ciphertext, System.ReadOnlySpan iv, System.Span destination, System.Security.Cryptography.PaddingMode paddingMode = System.Security.Cryptography.PaddingMode.PKCS7) { throw null; } + public byte[] DecryptCfb(byte[] ciphertext, byte[] iv, System.Security.Cryptography.PaddingMode paddingMode = System.Security.Cryptography.PaddingMode.None, int feedbackSizeInBits = 8) { throw null; } + public byte[] DecryptCfb(System.ReadOnlySpan ciphertext, System.ReadOnlySpan iv, System.Security.Cryptography.PaddingMode paddingMode = System.Security.Cryptography.PaddingMode.None, int feedbackSizeInBits = 8) { throw null; } + public int DecryptCfb(System.ReadOnlySpan ciphertext, System.ReadOnlySpan iv, System.Span destination, System.Security.Cryptography.PaddingMode paddingMode = System.Security.Cryptography.PaddingMode.None, int feedbackSizeInBits = 8) { throw null; } public byte[] DecryptEcb(byte[] ciphertext, System.Security.Cryptography.PaddingMode paddingMode) { throw null; } public byte[] DecryptEcb(System.ReadOnlySpan ciphertext, System.Security.Cryptography.PaddingMode paddingMode) { throw null; } public int DecryptEcb(System.ReadOnlySpan ciphertext, System.Span destination, System.Security.Cryptography.PaddingMode paddingMode) { throw null; } @@ -264,6 +267,9 @@ protected virtual void Dispose(bool disposing) { } public byte[] EncryptCbc(byte[] plaintext, byte[] iv, System.Security.Cryptography.PaddingMode paddingMode = System.Security.Cryptography.PaddingMode.PKCS7) { throw null; } public byte[] EncryptCbc(System.ReadOnlySpan plaintext, System.ReadOnlySpan iv, System.Security.Cryptography.PaddingMode paddingMode = System.Security.Cryptography.PaddingMode.PKCS7) { throw null; } public int EncryptCbc(System.ReadOnlySpan plaintext, System.ReadOnlySpan iv, System.Span destination, System.Security.Cryptography.PaddingMode paddingMode = System.Security.Cryptography.PaddingMode.PKCS7) { throw null; } + public byte[] EncryptCfb(byte[] plaintext, byte[] iv, System.Security.Cryptography.PaddingMode paddingMode = System.Security.Cryptography.PaddingMode.None, int feedbackSizeInBits = 8) { throw null; } + public byte[] EncryptCfb(System.ReadOnlySpan plaintext, System.ReadOnlySpan iv, System.Security.Cryptography.PaddingMode paddingMode = System.Security.Cryptography.PaddingMode.None, int feedbackSizeInBits = 8) { throw null; } + public int EncryptCfb(System.ReadOnlySpan plaintext, System.ReadOnlySpan iv, System.Span destination, System.Security.Cryptography.PaddingMode paddingMode = System.Security.Cryptography.PaddingMode.None, int feedbackSizeInBits = 8) { throw null; } public byte[] EncryptEcb(byte[] plaintext, System.Security.Cryptography.PaddingMode paddingMode) { throw null; } public byte[] EncryptEcb(System.ReadOnlySpan plaintext, System.Security.Cryptography.PaddingMode paddingMode) { throw null; } public int EncryptEcb(System.ReadOnlySpan plaintext, System.Span destination, System.Security.Cryptography.PaddingMode paddingMode) { throw null; } @@ -274,10 +280,14 @@ protected virtual void Dispose(bool disposing) { } public int GetCiphertextLengthEcb(int plaintextLength, System.Security.Cryptography.PaddingMode paddingMode) { throw null; } public bool TryDecryptCbc(System.ReadOnlySpan ciphertext, System.ReadOnlySpan iv, System.Span destination, out int bytesWritten, System.Security.Cryptography.PaddingMode paddingMode = System.Security.Cryptography.PaddingMode.PKCS7) { throw null; } protected virtual bool TryDecryptCbcCore(System.ReadOnlySpan ciphertext, System.ReadOnlySpan iv, System.Span destination, System.Security.Cryptography.PaddingMode paddingMode, out int bytesWritten) { throw null; } + public bool TryDecryptCfb(System.ReadOnlySpan ciphertext, System.ReadOnlySpan iv, System.Span destination, out int bytesWritten, System.Security.Cryptography.PaddingMode paddingMode = System.Security.Cryptography.PaddingMode.None, int feedbackSizeInBits = 8) { throw null; } + protected virtual bool TryDecryptCfbCore(System.ReadOnlySpan ciphertext, System.ReadOnlySpan iv, System.Span destination, System.Security.Cryptography.PaddingMode paddingMode, int feedbackSizeInBits, out int bytesWritten) { throw null; } public bool TryDecryptEcb(System.ReadOnlySpan ciphertext, System.Span destination, System.Security.Cryptography.PaddingMode paddingMode, out int bytesWritten) { throw null; } protected virtual bool TryDecryptEcbCore(System.ReadOnlySpan ciphertext, System.Span destination, System.Security.Cryptography.PaddingMode paddingMode, out int bytesWritten) { throw null; } public bool TryEncryptCbc(System.ReadOnlySpan plaintext, System.ReadOnlySpan iv, System.Span destination, out int bytesWritten, System.Security.Cryptography.PaddingMode paddingMode = System.Security.Cryptography.PaddingMode.PKCS7) { throw null; } protected virtual bool TryEncryptCbcCore(System.ReadOnlySpan plaintext, System.ReadOnlySpan iv, System.Span destination, System.Security.Cryptography.PaddingMode paddingMode, out int bytesWritten) { throw null; } + public bool TryEncryptCfb(System.ReadOnlySpan plaintext, System.ReadOnlySpan iv, System.Span destination, out int bytesWritten, System.Security.Cryptography.PaddingMode paddingMode = System.Security.Cryptography.PaddingMode.None, int feedbackSizeInBits = 8) { throw null; } + protected virtual bool TryEncryptCfbCore(System.ReadOnlySpan plaintext, System.ReadOnlySpan iv, System.Span destination, System.Security.Cryptography.PaddingMode paddingMode, int feedbackSizeInBits, out int bytesWritten) { throw null; } public bool TryEncryptEcb(System.ReadOnlySpan plaintext, System.Span destination, System.Security.Cryptography.PaddingMode paddingMode, out int bytesWritten) { throw null; } protected virtual bool TryEncryptEcbCore(System.ReadOnlySpan plaintext, System.Span destination, System.Security.Cryptography.PaddingMode paddingMode, out int bytesWritten) { throw null; } public bool ValidKeySize(int bitLength) { throw null; } diff --git a/src/libraries/System.Security.Cryptography.Primitives/src/System/Security/Cryptography/SymmetricAlgorithm.cs b/src/libraries/System.Security.Cryptography.Primitives/src/System/Security/Cryptography/SymmetricAlgorithm.cs index f5485577c0707..5e5cfa33a07b6 100644 --- a/src/libraries/System.Security.Cryptography.Primitives/src/System/Security/Cryptography/SymmetricAlgorithm.cs +++ b/src/libraries/System.Security.Cryptography.Primitives/src/System/Security/Cryptography/SymmetricAlgorithm.cs @@ -980,6 +980,481 @@ public bool TryEncryptCbc( return TryEncryptCbcCore(plaintext, iv, destination, paddingMode, out bytesWritten); } + /// + /// Decrypts data using CFB mode with the specified padding mode and + /// feedback size. + /// + /// The data to decrypt. + /// The initialization vector. + /// The padding mode used to produce the ciphertext and remove during decryption. + /// The feedback size, specified in bits. + /// The decrypted plaintext data. + /// + /// or is . + /// + /// + /// + /// is not a valid padding mode. + /// + /// + /// -or- + /// + /// + /// is not positive or represent a whole number of bytes. + /// + /// + /// + /// is the incorrect length. Callers are expected to pass an initialization vector + /// that is exactly in length, converted to bytes (BlockSize / 8). + /// + /// + /// + /// The ciphertext could not be decrypted successfully. + /// + /// + /// -or- + /// + /// + /// The feedback size is not valid for the algorithm. + /// + /// + /// + /// This method's behavior is defined by . + /// + public byte[] DecryptCfb(byte[] ciphertext, byte[] iv, PaddingMode paddingMode = PaddingMode.None, int feedbackSizeInBits = 8) + { + if (ciphertext is null) + throw new ArgumentNullException(nameof(ciphertext)); + if (iv is null) + throw new ArgumentNullException(nameof(iv)); + + return DecryptCfb( + new ReadOnlySpan(ciphertext), + new ReadOnlySpan(iv), + paddingMode, + feedbackSizeInBits); + } + + /// + /// Decrypts data using CFB mode with the specified padding mode and + /// feedback size. + /// + /// The data to decrypt. + /// The initialization vector. + /// The padding mode used to produce the ciphertext and remove during decryption. + /// The feedback size, specified in bits. + /// The decrypted plaintext data. + /// + /// + /// is not a valid padding mode. + /// + /// + /// -or- + /// + /// + /// is not positive or represent a whole number of bytes. + /// + /// + /// + /// is the incorrect length. Callers are expected to pass an initialization vector + /// that is exactly in length, converted to bytes (BlockSize / 8). + /// + /// + /// + /// The ciphertext could not be decrypted successfully. + /// + /// + /// -or- + /// + /// + /// The feedback size is not valid for the algorithm. + /// + /// + /// + /// This method's behavior is defined by . + /// + public byte[] DecryptCfb( + ReadOnlySpan ciphertext, + ReadOnlySpan iv, + PaddingMode paddingMode = PaddingMode.None, + int feedbackSizeInBits = 8) + { + CheckPaddingMode(paddingMode); + CheckInitializationVectorSize(iv); + CheckFeedbackSize(feedbackSizeInBits); + + // The default is CFB8 with no padding, so allocate a buffer + // that is not from the pool since we can return this directly if + // padding does not need to be removed. + byte[] decryptBuffer = GC.AllocateUninitializedArray(ciphertext.Length); + + if (!TryDecryptCfbCore(ciphertext, iv, decryptBuffer, paddingMode, feedbackSizeInBits, out int written) + || (uint)written > decryptBuffer.Length) + { + // This means decrypting the ciphertext grew in to a larger plaintext or overflowed. + // A user-derived class could do this, but it is not expected in any of the + // implementations that we ship. + throw new CryptographicException(SR.Argument_DestinationTooShort); + } + + // Array.Resize will no-op if the array does not need to be resized. + Array.Resize(ref decryptBuffer, written); + return decryptBuffer; + } + + /// + /// Decrypts data into the specified buffer, using CFB mode with the specified padding mode and + /// feedback size. + /// + /// The data to decrypt. + /// The initialization vector. + /// The buffer to receive the plaintext data. + /// The padding mode used to produce the ciphertext and remove during decryption. + /// The feedback size, specified in bits. + /// The total number of bytes written to . + /// + /// + /// is not a valid padding mode. + /// + /// + /// -or- + /// + /// + /// is not positive or represent a whole number of bytes. + /// + /// + /// + /// + /// is the incorrect length. Callers are expected to pass an initialization vector + /// that is exactly in length, converted to bytes (BlockSize / 8). + /// + /// + /// -or- + /// + /// + /// The buffer in is too small to hold the plaintext data. + /// + /// + /// + /// + /// The ciphertext could not be decrypted successfully. + /// + /// + /// -or- + /// + /// + /// is not valid for the algorithm. + /// + /// + /// + /// This method's behavior is defined by . + /// + public int DecryptCfb( + ReadOnlySpan ciphertext, + ReadOnlySpan iv, + Span destination, + PaddingMode paddingMode = PaddingMode.None, + int feedbackSizeInBits = 8) + { + CheckPaddingMode(paddingMode); + CheckInitializationVectorSize(iv); + CheckFeedbackSize(feedbackSizeInBits); + + if (!TryDecryptCfbCore(ciphertext, iv, destination, paddingMode, feedbackSizeInBits, out int written)) + { + throw new ArgumentException(SR.Argument_DestinationTooShort, nameof(destination)); + } + + return written; + } + + /// + /// Attempts to decrypt data into the specified buffer, using CFB mode + /// with the specified padding mode and feedback size. + /// + /// The data to decrypt. + /// The initialization vector. + /// The buffer to receive the plaintext data. + /// When this method returns, the total number of bytes written to . + /// The padding mode used to produce the ciphertext and remove during decryption. + /// The feedback size, specified in bits. + /// if was large enough to receive the decrypted data; otherwise, . + /// + /// + /// is not a valid padding mode. + /// + /// + /// -or- + /// + /// + /// is not positive or represent a whole number of bytes. + /// + /// + /// + /// is the incorrect length. Callers are expected to pass an initialization vector + /// that is exactly in length, converted to bytes (BlockSize / 8). + /// + /// + /// + /// The ciphertext could not be decrypted successfully. + /// + /// + /// -or- + /// + /// + /// is not valid for the algorithm. + /// + /// + /// + /// This method's behavior is defined by . + /// + public bool TryDecryptCfb( + ReadOnlySpan ciphertext, + ReadOnlySpan iv, + Span destination, + out int bytesWritten, + PaddingMode paddingMode = PaddingMode.None, + int feedbackSizeInBits = 8) + { + CheckPaddingMode(paddingMode); + CheckInitializationVectorSize(iv); + CheckFeedbackSize(feedbackSizeInBits); + + return TryDecryptCfbCore(ciphertext, iv, destination, paddingMode, feedbackSizeInBits, out bytesWritten); + } + + /// + /// Encrypts data using CFB mode with the specified padding mode and feedback size. + /// + /// The data to encrypt. + /// The initialization vector. + /// The padding mode used to produce the ciphertext and remove during decryption. + /// The feedback size, specified in bits. + /// The encrypted ciphertext data. + /// + /// or is . + /// + /// + /// + /// is not a valid padding mode. + /// + /// + /// -or- + /// + /// + /// is not positive or represent a whole number of bytes. + /// + /// + /// + /// is the incorrect length. Callers are expected to pass an initialization vector + /// that is exactly in length, converted to bytes (BlockSize / 8). + /// + /// + /// + /// The plaintext could not be encrypted successfully. + /// + /// + /// -or- + /// + /// + /// The feedback size is not valid for the algorithm. + /// + /// + /// + /// This method's behavior is defined by . + /// + public byte[] EncryptCfb( + byte[] plaintext, + byte[] iv, + PaddingMode paddingMode = PaddingMode.None, + int feedbackSizeInBits = 8) + { + return EncryptCfb( + new ReadOnlySpan(plaintext), + new ReadOnlySpan(iv), + paddingMode, + feedbackSizeInBits); + } + + /// + /// Encrypts data using CFB mode with the specified padding mode and feedback size. + /// + /// The data to encrypt. + /// The initialization vector. + /// The padding mode used to produce the ciphertext and remove during decryption. + /// The feedback size, specified in bits. + /// The encrypted ciphertext data. + /// + /// + /// is not a valid padding mode. + /// + /// + /// -or- + /// + /// + /// is not positive or represent a whole number of bytes. + /// + /// + /// + /// is the incorrect length. Callers are expected to pass an initialization vector + /// that is exactly in length, converted to bytes (BlockSize / 8). + /// + /// + /// + /// The plaintext could not be encrypted successfully. + /// + /// + /// -or- + /// + /// + /// The feedback size is not valid for the algorithm. + /// + /// + /// + /// This method's behavior is defined by . + /// + public byte[] EncryptCfb( + ReadOnlySpan plaintext, + ReadOnlySpan iv, + PaddingMode paddingMode = PaddingMode.None, + int feedbackSizeInBits = 8) + { + CheckPaddingMode(paddingMode); + CheckInitializationVectorSize(iv); + CheckFeedbackSize(feedbackSizeInBits); + + int ciphertextLength = GetCiphertextLengthCfb(plaintext.Length, paddingMode, feedbackSizeInBits); + + // We expect most if not all uses to encrypt to exactly the ciphertextLength + byte[] buffer = GC.AllocateUninitializedArray(ciphertextLength); + + if (!TryEncryptCfbCore(plaintext, iv, buffer, paddingMode, feedbackSizeInBits, out int written) || + written != ciphertextLength) + { + // This means a user-derived implementation added more padding than we expected or + // did something non-standard (encrypt to a partial block). This can't happen for + // multiple padding blocks since the buffer would have been too small in the first + // place. It doesn't make sense to try and support partial block encryption, likely + // something went very wrong. So throw. + throw new CryptographicException(SR.Format(SR.Cryptography_EncryptedIncorrectLength, nameof(TryEncryptCfbCore))); + } + + return buffer; + } + + /// + /// Encrypts data into the specified buffer, using CFB mode with the specified padding mode + /// and feedback size. + /// + /// The data to encrypt. + /// The initialization vector. + /// The buffer to receive the ciphertext data. + /// The padding mode used to produce the ciphertext and remove during decryption. + /// The feedback size, specified in bits. + /// The total number of bytes written to . + /// + /// + /// is not a valid padding mode. + /// + /// + /// -or- + /// + /// + /// is not positive or represent a whole number of bytes. + /// + /// + /// + /// is the incorrect length. Callers are expected to pass an initialization vector + /// that is exactly in length, converted to bytes (BlockSize / 8). + /// + /// + /// + /// The plaintext could not be encrypted successfully. + /// + /// + /// -or- + /// + /// + /// The feedback size is not valid for the algorithm. + /// + /// + /// + /// This method's behavior is defined by . + /// + public int EncryptCfb( + ReadOnlySpan plaintext, + ReadOnlySpan iv, + Span destination, + PaddingMode paddingMode = PaddingMode.None, + int feedbackSizeInBits = 8) + { + CheckPaddingMode(paddingMode); + CheckInitializationVectorSize(iv); + CheckFeedbackSize(feedbackSizeInBits); + + if (!TryEncryptCfbCore(plaintext, iv, destination, paddingMode, feedbackSizeInBits, out int written)) + { + throw new ArgumentException(SR.Argument_DestinationTooShort, nameof(destination)); + } + + return written; + } + + /// + /// Attempts to encrypt data into the specified buffer, using CFB mode with the specified padding mode + /// and feedback size. + /// + /// The data to encrypt. + /// The initialization vector. + /// The buffer to receive the ciphertext data. + /// When this method returns, the total number of bytes written to . + /// The padding mode used to produce the ciphertext and remove during decryption. + /// The feedback size, specified in bits. + /// if was large enough to receive the encrypted data; otherwise, . + /// + /// + /// is not a valid padding mode. + /// + /// + /// -or- + /// + /// + /// is not positive or represent a whole number of bytes. + /// + /// + /// + /// is the incorrect length. Callers are expected to pass an initialization vector + /// that is exactly in length, converted to bytes (BlockSize / 8). + /// + /// + /// + /// The plaintext could not be encrypted successfully. + /// + /// + /// -or- + /// + /// + /// The feedback size is not valid for the algorithm. + /// + /// + /// + /// This method's behavior is defined by . + /// + public bool TryEncryptCfb( + ReadOnlySpan plaintext, + ReadOnlySpan iv, + Span destination, + out int bytesWritten, + PaddingMode paddingMode = PaddingMode.None, + int feedbackSizeInBits = 8) + { + CheckPaddingMode(paddingMode); + CheckInitializationVectorSize(iv); + CheckFeedbackSize(feedbackSizeInBits); + + return TryEncryptCfbCore(plaintext, iv, destination, paddingMode, feedbackSizeInBits, out bytesWritten); + } + /// /// When overridden in a derived class, attempts to encrypt data into the specified /// buffer, using ECB mode with the specified padding mode. @@ -1090,6 +1565,68 @@ protected virtual bool TryDecryptCbcCore( throw new NotSupportedException(SR.NotSupported_SubclassOverride); } + /// + /// When overridden in a derived class, attempts to decrypt data + /// into the specified buffer, using CFB mode with the specified padding mode + /// and feedback size. + /// + /// The data to decrypt. + /// The initialization vector. + /// The buffer to receive the plaintext data. + /// The padding mode used to produce the ciphertext and remove during decryption. + /// The feedback size, specified in bits. + /// When this method returns, the total number of bytes written to . + /// if was large enough to receive the decrypted data; otherwise, . + /// + /// A derived class has not provided an implementation. + /// + /// + /// Derived classes must override this and provide an implementation. + /// + protected virtual bool TryDecryptCfbCore( + ReadOnlySpan ciphertext, + ReadOnlySpan iv, + Span destination, + PaddingMode paddingMode, + int feedbackSizeInBits, + out int bytesWritten) + { + throw new NotSupportedException(SR.NotSupported_SubclassOverride); + } + + /// + /// When overridden in a derived class, attempts to encrypt data into the specified + /// buffer, using CFB mode with the specified padding mode and feedback size. + /// + /// The data to encrypt. + /// The initialization vector. + /// The buffer to receive the ciphertext data. + /// The padding mode used to produce the ciphertext and remove during decryption. + /// The feedback size, specified in bits. + /// When this method returns, the total number of bytes written to . + /// if was large enough to receive the encrypted data; otherwise, . + /// + /// A derived class has not provided an implementation. + /// + /// + /// Derived classes must override this and provide an implementation. + /// + /// Implementations of this method must write precisely + /// GetCiphertextLengthCfb(plaintext.Length, paddingMode, feedbackSizeInBits) + /// bytes to and report that via . + /// + /// + protected virtual bool TryEncryptCfbCore( + ReadOnlySpan plaintext, + ReadOnlySpan iv, + Span destination, + PaddingMode paddingMode, + int feedbackSizeInBits, + out int bytesWritten) + { + throw new NotSupportedException(SR.NotSupported_SubclassOverride); + } + private static void CheckPaddingMode(PaddingMode paddingMode) { if (paddingMode < PaddingMode.None || paddingMode > PaddingMode.ISO10126) @@ -1102,6 +1639,14 @@ private void CheckInitializationVectorSize(ReadOnlySpan iv) throw new ArgumentException(SR.Cryptography_InvalidIVSize, nameof(iv)); } + private void CheckFeedbackSize(int feedbackSizeInBits) + { + if (feedbackSizeInBits < 8 || (feedbackSizeInBits & 0b111) != 0 || feedbackSizeInBits > BlockSize) + { + throw new ArgumentException(SR.Cryptography_InvalidFeedbackSize, nameof(feedbackSizeInBits)); + } + } + protected CipherMode ModeValue; protected PaddingMode PaddingValue; protected byte[]? KeyValue; diff --git a/src/libraries/System.Security.Cryptography.Primitives/tests/SymmetricAlgorithmTests.cs b/src/libraries/System.Security.Cryptography.Primitives/tests/SymmetricAlgorithmTests.cs index 380cd19b099bb..c03909efd391a 100644 --- a/src/libraries/System.Security.Cryptography.Primitives/tests/SymmetricAlgorithmTests.cs +++ b/src/libraries/System.Security.Cryptography.Primitives/tests/SymmetricAlgorithmTests.cs @@ -495,6 +495,322 @@ static bool EncryptImpl( alg.TryEncryptCbc(Array.Empty(), badIv, destination, out _, PaddingMode.None)); } + [Fact] + public static void EncryptCfb_NotSupportedInDerived() + { + AnySizeAlgorithm alg = new AnySizeAlgorithm { BlockSize = 128 }; + + Assert.Throws(() => + alg.EncryptCfb(Array.Empty(), new byte[alg.BlockSize / 8])); + } + + [Fact] + public static void DecryptCfb_NotSupportedInDerived() + { + AnySizeAlgorithm alg = new AnySizeAlgorithm { BlockSize = 128 }; + + Assert.Throws(() => + alg.DecryptCfb(Array.Empty(), new byte[alg.BlockSize / 8])); + } + + [Fact] + public static void EncryptCfb_EncryptProducesIncorrectlyPaddedValue() + { + static bool EncryptImpl( + ReadOnlySpan plaintext, + ReadOnlySpan iv, + Span destination, + PaddingMode paddingMode, + int feedbackSizeInBits, + out int bytesWritten) + { + bytesWritten = destination.Length + 1; + return true; + } + + OneShotSymmetricAlgorithm alg = new OneShotSymmetricAlgorithm + { + BlockSize = 128, + TryEncryptCfbCoreImpl = EncryptImpl, + }; + + Assert.Throws(() => + alg.EncryptCfb(Array.Empty(), new byte[alg.BlockSize / 8], PaddingMode.None)); + } + + [Fact] + public static void DecryptCfb_DecryptBytesWrittenLies() + { + static bool DecryptImpl( + ReadOnlySpan ciphertext, + ReadOnlySpan iv, + Span destination, + PaddingMode paddingMode, + int feedbackSizeInBits, + out int bytesWritten) + { + bytesWritten = destination.Length + 1; + return true; + } + + OneShotSymmetricAlgorithm alg = new OneShotSymmetricAlgorithm + { + BlockSize = 128, + TryDecryptCfbCoreImpl = DecryptImpl, + }; + + Assert.Throws(() => + alg.DecryptCfb(new byte[128 / 8], new byte[128 / 8], feedbackSizeInBits: 128)); + } + + [Fact] + public static void EncryptCfb_EncryptCoreFails() + { + static bool EncryptImpl( + ReadOnlySpan plaintext, + ReadOnlySpan iv, + Span destination, + PaddingMode paddingMode, + int feedbackSizeInBits, + out int bytesWritten) + { + bytesWritten = 0; + return false; + } + + OneShotSymmetricAlgorithm alg = new OneShotSymmetricAlgorithm + { + BlockSize = 128, + TryEncryptCfbCoreImpl = EncryptImpl, + }; + + Assert.Throws(() => + alg.EncryptCfb(Array.Empty(), new byte[128 / 8], feedbackSizeInBits: 128)); + } + + [Fact] + public static void EncryptCfb_EncryptCoreOverflowWritten() + { + static bool EncryptImpl( + ReadOnlySpan plaintext, + ReadOnlySpan iv, + Span destination, + PaddingMode paddingMode, + int feedbackSizeInBits, + out int bytesWritten) + { + bytesWritten = -1; + return true; + } + + OneShotSymmetricAlgorithm alg = new OneShotSymmetricAlgorithm + { + BlockSize = 128, + TryEncryptCfbCoreImpl = EncryptImpl, + }; + + Assert.Throws(() => + alg.EncryptCfb(Array.Empty(), new byte[128 / 8], feedbackSizeInBits: 128)); + } + + [Fact] + public static void DecryptCfb_DecryptCoreFails() + { + static bool DecryptImpl( + ReadOnlySpan ciphertext, + ReadOnlySpan iv, + Span destination, + PaddingMode paddingMode, + int feedbackSizeInBits, + out int bytesWritten) + { + bytesWritten = 0; + return false; + } + + OneShotSymmetricAlgorithm alg = new OneShotSymmetricAlgorithm + { + BlockSize = 128, + TryDecryptCfbCoreImpl = DecryptImpl, + }; + + Assert.Throws(() => + alg.DecryptCfb(Array.Empty(), new byte[128 / 8], feedbackSizeInBits: 128)); + } + + [Fact] + public static void DecryptCfb_DecryptCoreOverflowWritten() + { + static bool DecryptImpl( + ReadOnlySpan ciphertext, + ReadOnlySpan iv, + Span destination, + PaddingMode paddingMode, + int feedbackSizeInBits, + out int bytesWritten) + { + bytesWritten = -1; + return true; + } + + OneShotSymmetricAlgorithm alg = new OneShotSymmetricAlgorithm + { + BlockSize = 128, + TryDecryptCfbCoreImpl = DecryptImpl, + }; + + Assert.Throws(() => + alg.DecryptCfb(Array.Empty(), new byte[128 / 8], feedbackSizeInBits: 8)); + } + + [Fact] + public static void DecryptCfb_BadInitializationVectorLength() + { + static bool DecryptImpl( + ReadOnlySpan ciphertext, + ReadOnlySpan iv, + Span destination, + PaddingMode paddingMode, + int feedbackSizeInBits, + out int bytesWritten) + { + Assert.True(false, "Initialization vector was not validated, core should not have been called."); + bytesWritten = 0; + return false; + } + + OneShotSymmetricAlgorithm alg = new OneShotSymmetricAlgorithm + { + BlockSize = 128, + TryDecryptCfbCoreImpl = DecryptImpl, + }; + + byte[] badIv = new byte[alg.BlockSize / 8 + 1]; + byte[] destination = new byte[128 / 8]; + + AssertExtensions.Throws("iv", () => + alg.DecryptCfb(Array.Empty(), badIv, feedbackSizeInBits: 128)); + + AssertExtensions.Throws("iv", () => + alg.DecryptCfb(Array.Empty(), badIv, destination, feedbackSizeInBits: 128)); + + AssertExtensions.Throws("iv", () => + alg.TryDecryptCfb(Array.Empty(), badIv, destination, out _, feedbackSizeInBits: 128)); + } + + [Fact] + public static void EncryptCfb_BadInitializationVectorLength() + { + static bool EncryptImpl( + ReadOnlySpan plaintext, + ReadOnlySpan iv, + Span destination, + PaddingMode paddingMode, + int feedbackSizeInBits, + out int bytesWritten) + { + Assert.True(false, "Initialization vector was not validated, core should not have been called."); + bytesWritten = 0; + return false; + } + + OneShotSymmetricAlgorithm alg = new OneShotSymmetricAlgorithm + { + BlockSize = 128, + TryEncryptCfbCoreImpl = EncryptImpl, + }; + + byte[] badIv = new byte[alg.BlockSize / 8 + 1]; + byte[] destination = new byte[128 / 8]; + + AssertExtensions.Throws("iv", () => + alg.EncryptCfb(Array.Empty(), badIv, feedbackSizeInBits: 128)); + + AssertExtensions.Throws("iv", () => + alg.EncryptCfb(Array.Empty(), badIv, destination, feedbackSizeInBits: 128)); + + AssertExtensions.Throws("iv", () => + alg.TryEncryptCfb(Array.Empty(), badIv, destination, out _, feedbackSizeInBits: 128)); + } + + [Theory] + [InlineData(-1)] + [InlineData(0)] + [InlineData(19)] + [InlineData(256)] + public static void DecryptCfb_BadFeedbackSizes(int feedbackSize) + { + static bool DecryptImpl( + ReadOnlySpan ciphertext, + ReadOnlySpan iv, + Span destination, + PaddingMode paddingMode, + int feedbackSizeInBits, + out int bytesWritten) + { + Assert.True(false, "Feedback size was not validated, core should not have been called."); + bytesWritten = 0; + return false; + } + + OneShotSymmetricAlgorithm alg = new OneShotSymmetricAlgorithm + { + BlockSize = 128, + TryDecryptCfbCoreImpl = DecryptImpl, + }; + + byte[] iv = new byte[alg.BlockSize / 8]; + byte[] destination = Array.Empty(); + + AssertExtensions.Throws("feedbackSizeInBits", () => + alg.DecryptCfb(Array.Empty(), iv, feedbackSizeInBits: feedbackSize)); + + AssertExtensions.Throws("feedbackSizeInBits", () => + alg.DecryptCfb(Array.Empty(), iv, destination, feedbackSizeInBits: feedbackSize)); + + AssertExtensions.Throws("feedbackSizeInBits", () => + alg.TryDecryptCfb(Array.Empty(), iv, destination, out _, feedbackSizeInBits: feedbackSize)); + } + + [Theory] + [InlineData(-1)] + [InlineData(0)] + [InlineData(19)] + [InlineData(256)] + public static void EncryptCfb_BadFeedbackSizes(int feedbackSize) + { + static bool EncryptImpl( + ReadOnlySpan ciphertext, + ReadOnlySpan iv, + Span destination, + PaddingMode paddingMode, + int feedbackSizeInBits, + out int bytesWritten) + { + Assert.True(false, "Feedback size was not validated, core should not have been called."); + bytesWritten = 0; + return false; + } + + OneShotSymmetricAlgorithm alg = new OneShotSymmetricAlgorithm + { + BlockSize = 128, + TryEncryptCfbCoreImpl = EncryptImpl, + }; + + byte[] iv = new byte[alg.BlockSize / 8]; + byte[] destination = Array.Empty(); + + AssertExtensions.Throws("feedbackSizeInBits", () => + alg.DecryptCfb(Array.Empty(), iv, feedbackSizeInBits: feedbackSize)); + + AssertExtensions.Throws("feedbackSizeInBits", () => + alg.DecryptCfb(Array.Empty(), iv, destination, feedbackSizeInBits: feedbackSize)); + + AssertExtensions.Throws("feedbackSizeInBits", () => + alg.TryDecryptCfb(Array.Empty(), iv, destination, out _, feedbackSizeInBits: feedbackSize)); + } + public static IEnumerable CiphertextLengthTheories { get @@ -622,10 +938,28 @@ public delegate bool TryDecryptCbcCoreFunc( PaddingMode paddingMode, out int bytesWritten); + public delegate bool TryEncryptCfbCoreFunc( + ReadOnlySpan plaintext, + ReadOnlySpan iv, + Span destination, + PaddingMode paddingMode, + int feedbackSizeInBits, + out int bytesWritten); + + public delegate bool TryDecryptCfbCoreFunc( + ReadOnlySpan ciphertext, + ReadOnlySpan iv, + Span destination, + PaddingMode paddingMode, + int feedbackSizeInBits, + out int bytesWritten); + public TryEncryptEcbCoreFunc TryEncryptEcbCoreImpl { get; set; } public TryDecryptEcbCoreFunc TryDecryptEcbCoreImpl { get; set; } public TryEncryptCbcCoreFunc TryEncryptCbcCoreImpl { get; set; } public TryDecryptCbcCoreFunc TryDecryptCbcCoreImpl { get; set; } + public TryEncryptCfbCoreFunc TryEncryptCfbCoreImpl { get; set; } + public TryDecryptCfbCoreFunc TryDecryptCfbCoreImpl { get; set; } protected override bool TryEncryptEcbCore( ReadOnlySpan plaintext, @@ -652,6 +986,22 @@ protected override bool TryDecryptCbcCore( Span destination, PaddingMode paddingMode, out int bytesWritten) => TryDecryptCbcCoreImpl(ciphertext, iv, destination, paddingMode, out bytesWritten); + + protected override bool TryEncryptCfbCore( + ReadOnlySpan plaintext, + ReadOnlySpan iv, + Span destination, + PaddingMode paddingMode, + int feedbackSizeInBits, + out int bytesWritten) => TryEncryptCfbCoreImpl(plaintext, iv, destination, paddingMode, feedbackSizeInBits, out bytesWritten); + + protected override bool TryDecryptCfbCore( + ReadOnlySpan ciphertext, + ReadOnlySpan iv, + Span destination, + PaddingMode paddingMode, + int feedbackSizeInBits, + out int bytesWritten) => TryDecryptCfbCoreImpl(ciphertext, iv, destination, paddingMode, feedbackSizeInBits, out bytesWritten); } } }