Added support for CFB
authorVíťa Tauer <vita@nothrow.cz>
Thu, 13 Aug 2020 12:38:24 +0000 (14:38 +0200)
committerGitHub <noreply@github.com>
Thu, 13 Aug 2020 12:38:24 +0000 (05:38 -0700)
This change brings the Cipher Feedback (CFB) mode to .NET 5 with the same behaviors as .NET Framework around the non-standard application of PKCS#7 padding based on the feedback block size and decryption the data be algorithm blocksize-aligned.

* AES: CFB8 and CFB128
* TripleDES: CFB8 and CFB64
* DES: CFB8
* RC2: Not supported

Additionally, due to a lack of support in CNG, CFB is not supported on Windows 7.

61 files changed:
src/libraries/Common/src/Internal/Cryptography/BasicSymmetricCipher.cs
src/libraries/Common/src/Internal/Cryptography/BasicSymmetricCipherBCrypt.cs
src/libraries/Common/src/Internal/Cryptography/Helpers.cs
src/libraries/Common/src/Internal/Cryptography/UniversalCryptoDecryptor.cs
src/libraries/Common/src/Internal/Cryptography/UniversalCryptoEncryptor.cs
src/libraries/Common/src/Internal/Cryptography/UniversalCryptoTransform.cs
src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Symmetric.cs
src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EVP.Cipher.cs
src/libraries/Common/src/Interop/Windows/BCrypt/AesBCryptModes.cs
src/libraries/Common/src/Interop/Windows/BCrypt/Cng.cs
src/libraries/Common/src/Interop/Windows/BCrypt/DESBCryptModes.cs
src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptChainingModes.cs
src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptPropertyStrings.cs
src/libraries/Common/src/Interop/Windows/BCrypt/TripleDesBCryptModes.cs
src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesCipherTests.cs
src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesContractTests.cs
src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesModeTests.cs
src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DES/DESCipherTests.cs
src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DES/DESContractTests.cs [new file with mode: 0644]
src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/RC2/RC2CipherTests.cs
src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/RC2/RC2ContractTests.cs [new file with mode: 0644]
src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/TripleDES/TripleDESCipherTests.cs
src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/TripleDES/TripleDESContractTests.cs [new file with mode: 0644]
src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_symmetric.c
src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_symmetric.h
src/libraries/Native/Unix/System.Security.Cryptography.Native/opensslshim.h
src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_evp_cipher.c
src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_evp_cipher.h
src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/AesImplementation.OSX.cs
src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/AesImplementation.Unix.cs
src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/AesImplementation.Windows.cs
src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/AesImplementation.cs
src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/AppleCCCryptor.cs
src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/DesImplementation.OSX.cs
src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/DesImplementation.Unix.cs
src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/DesImplementation.Windows.cs
src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/DesImplementation.cs
src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/OpenSslCipher.cs
src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/RC2Implementation.OSX.cs
src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/RC2Implementation.Unix.cs
src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/RC2Implementation.Windows.cs
src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/RC2Implementation.cs
src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/TripleDesImplementation.OSX.cs
src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/TripleDesImplementation.Unix.cs
src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/TripleDesImplementation.Windows.cs
src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/TripleDesImplementation.cs
src/libraries/System.Security.Cryptography.Algorithms/src/Resources/Strings.resx
src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/AesCcm.Windows.cs
src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/AesGcm.Windows.cs
src/libraries/System.Security.Cryptography.Algorithms/tests/System.Security.Cryptography.Algorithms.Tests.csproj
src/libraries/System.Security.Cryptography.Cng/src/Internal/Cryptography/BasicSymmetricCipherNCrypt.cs
src/libraries/System.Security.Cryptography.Cng/src/Internal/Cryptography/CngSymmetricAlgorithmCore.cs
src/libraries/System.Security.Cryptography.Cng/src/Internal/Cryptography/ICngSymmetricAlgorithm.cs
src/libraries/System.Security.Cryptography.Cng/src/System/Security/Cryptography/AesCng.cs
src/libraries/System.Security.Cryptography.Cng/src/System/Security/Cryptography/TripleDESCng.cs
src/libraries/System.Security.Cryptography.Csp/src/Internal/Cryptography/BasicSymmetricCipherCsp.cs
src/libraries/System.Security.Cryptography.Csp/src/System/Security/Cryptography/CapiHelper.Windows.cs
src/libraries/System.Security.Cryptography.Csp/src/System/Security/Cryptography/DESCryptoServiceProvider.Windows.cs
src/libraries/System.Security.Cryptography.Csp/src/System/Security/Cryptography/RC2CryptoServiceProvider.Windows.cs
src/libraries/System.Security.Cryptography.Primitives/src/System/Security/Cryptography/CipherMode.cs
src/libraries/System.Security.Cryptography.Primitives/src/System/Security/Cryptography/SymmetricAlgorithm.cs

index df3ae1f..10bbb33 100644 (file)
@@ -22,10 +22,11 @@ namespace Internal.Cryptography
     //
     internal abstract class BasicSymmetricCipher : IDisposable
     {
-        protected BasicSymmetricCipher(byte[]? iv, int blockSizeInBytes)
+        protected BasicSymmetricCipher(byte[]? iv, int blockSizeInBytes, int paddingSizeInBytes)
         {
             IV = iv;
             BlockSizeInBytes = blockSizeInBytes;
+            PaddingSizeInBytes = paddingSizeInBytes > 0 ? paddingSizeInBytes : blockSizeInBytes;
         }
 
         public abstract int Transform(ReadOnlySpan<byte> input, Span<byte> output);
@@ -33,6 +34,7 @@ namespace Internal.Cryptography
         public abstract int TransformFinal(ReadOnlySpan<byte> input, Span<byte> output);
 
         public int BlockSizeInBytes { get; private set; }
+        public int PaddingSizeInBytes { get; private set; }
 
         public void Dispose()
         {
index 2deb97c..75b748a 100644 (file)
@@ -16,8 +16,8 @@ namespace Internal.Cryptography
         private byte[]? _currentIv;  // CNG mutates this with the updated IV for the next stage on each Encrypt/Decrypt call.
                                      // The base IV holds a copy of the original IV for Reset(), until it is cleared by Dispose().
 
-        public BasicSymmetricCipherBCrypt(SafeAlgorithmHandle algorithm, CipherMode cipherMode, int blockSizeInBytes, byte[] key, bool ownsParentHandle, byte[]? iv, bool encrypting)
-            : base(cipherMode.GetCipherIv(iv), blockSizeInBytes)
+        public BasicSymmetricCipherBCrypt(SafeAlgorithmHandle algorithm, CipherMode cipherMode, int blockSizeInBytes, int paddingSizeInBytes, byte[] key, bool ownsParentHandle, byte[]? iv, bool encrypting)
+            : base(cipherMode.GetCipherIv(iv), blockSizeInBytes, paddingSizeInBytes)
         {
             Debug.Assert(algorithm != null);
 
@@ -63,7 +63,7 @@ namespace Internal.Cryptography
         public override int Transform(ReadOnlySpan<byte> input, Span<byte> output)
         {
             Debug.Assert(input.Length > 0);
-            Debug.Assert((input.Length % BlockSizeInBytes) == 0);
+            Debug.Assert((input.Length % PaddingSizeInBytes) == 0);
 
             int numBytesWritten;
 
@@ -89,7 +89,7 @@ namespace Internal.Cryptography
 
         public override int TransformFinal(ReadOnlySpan<byte> input, Span<byte> output)
         {
-            Debug.Assert((input.Length % BlockSizeInBytes) == 0);
+            Debug.Assert((input.Length % PaddingSizeInBytes) == 0);
 
             int numBytesWritten = 0;
 
index d33c9c2..a825f5d 100644 (file)
@@ -4,6 +4,7 @@
 #nullable enable
 using System;
 using System.Diagnostics.CodeAnalysis;
+using System.Security.Cryptography;
 
 namespace Internal.Cryptography
 {
@@ -20,6 +21,16 @@ namespace Internal.Cryptography
             return (byte[])(src.Clone());
         }
 
+        public static int GetPaddingSize(this SymmetricAlgorithm algorithm)
+        {
+            // CFB8 does not require any padding at all
+            // otherwise, it is always required to pad for block size
+            if (algorithm.Mode == CipherMode.CFB && algorithm.FeedbackSize == 8)
+                return 1;
+
+            return algorithm.BlockSize / 8;
+        }
+
         internal static bool TryCopyToDestination(ReadOnlySpan<byte> source, Span<byte> destination, out int bytesWritten)
         {
             if (source.TryCopyTo(destination))
index 754dbc0..2167ca7 100644 (file)
@@ -68,7 +68,7 @@ namespace Internal.Cryptography
         protected override unsafe int UncheckedTransformFinalBlock(ReadOnlySpan<byte> inputBuffer, Span<byte> outputBuffer)
         {
             // We can't complete decryption on a partial block
-            if (inputBuffer.Length % InputBlockSize != 0)
+            if (inputBuffer.Length % PaddingSizeBytes != 0)
                 throw new CryptographicException(SR.Cryptography_PartialBlock);
 
             //
index 2bf431a..e89baaa 100644 (file)
@@ -61,7 +61,7 @@ namespace Internal.Cryptography
             Debug.Assert(plaintextLength >= 0);
 
              //divisor and factor are same and won't overflow.
-            int wholeBlocks = Math.DivRem(plaintextLength, InputBlockSize, out int remainder) * InputBlockSize;
+            int wholeBlocks = Math.DivRem(plaintextLength, PaddingSizeBytes, out int remainder) * PaddingSizeBytes;
 
             switch (PaddingMode)
             {
@@ -74,7 +74,7 @@ namespace Internal.Cryptography
                 case PaddingMode.PKCS7:
                 case PaddingMode.ANSIX923:
                 case PaddingMode.ISO10126:
-                    return checked(wholeBlocks + InputBlockSize);
+                    return checked(wholeBlocks + PaddingSizeBytes);
                 default:
                     Debug.Fail($"Unknown padding mode {PaddingMode}.");
                     throw new CryptographicException(SR.Cryptography_UnknownPaddingMode);
@@ -84,8 +84,8 @@ namespace Internal.Cryptography
         private int PadBlock(ReadOnlySpan<byte> block, Span<byte> destination)
         {
             int count = block.Length;
-            int paddingRemainder = count % InputBlockSize;
-            int padBytes = InputBlockSize - paddingRemainder;
+            int paddingRemainder = count % PaddingSizeBytes;
+            int padBytes = PaddingSizeBytes - paddingRemainder;
 
             switch (PaddingMode)
             {
index b4406e5..23966df 100644 (file)
@@ -17,7 +17,10 @@ namespace Internal.Cryptography
     //
     internal abstract class UniversalCryptoTransform : ICryptoTransform
     {
-        public static ICryptoTransform Create(PaddingMode paddingMode, BasicSymmetricCipher cipher, bool encrypting)
+        public static ICryptoTransform Create(
+            PaddingMode paddingMode,
+            BasicSymmetricCipher cipher,
+            bool encrypting)
         {
             if (encrypting)
                 return new UniversalCryptoEncryptor(paddingMode, cipher);
@@ -41,6 +44,11 @@ namespace Internal.Cryptography
             get { return true; }
         }
 
+        protected int PaddingSizeBytes
+        {
+            get { return BasicSymmetricCipher.PaddingSizeInBytes; }
+        }
+
         public int InputBlockSize
         {
             get { return BasicSymmetricCipher.BlockSizeInBytes; }
index c32ba1d..2b55500 100644 (file)
@@ -33,6 +33,8 @@ internal static partial class Interop
         {
             ECB = 1,
             CBC = 2,
+            CFB = 3,
+            CFB8 = 10,
         }
 
         internal enum PAL_SymmetricOptions
index 6373116..fe2790b 100644 (file)
@@ -208,6 +208,12 @@ internal static partial class Interop
         [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpAes128Gcm")]
         internal static extern IntPtr EvpAes128Gcm();
 
+        [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpAes128Cfb8")]
+        internal static extern IntPtr EvpAes128Cfb8();
+
+        [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpAes128Cfb128")]
+        internal static extern IntPtr EvpAes128Cfb128();
+
         [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpAes128Ccm")]
         internal static extern IntPtr EvpAes128Ccm();
 
@@ -220,6 +226,12 @@ internal static partial class Interop
         [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpAes192Gcm")]
         internal static extern IntPtr EvpAes192Gcm();
 
+        [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpAes192Cfb8")]
+        internal static extern IntPtr EvpAes192Cfb8();
+
+        [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpAes192Cfb128")]
+        internal static extern IntPtr EvpAes192Cfb128();
+
         [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpAes192Ccm")]
         internal static extern IntPtr EvpAes192Ccm();
 
@@ -232,6 +244,12 @@ internal static partial class Interop
         [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpAes256Gcm")]
         internal static extern IntPtr EvpAes256Gcm();
 
+        [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpAes256Cfb128")]
+        internal static extern IntPtr EvpAes256Cfb128();
+
+        [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpAes256Cfb8")]
+        internal static extern IntPtr EvpAes256Cfb8();
+
         [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpAes256Ccm")]
         internal static extern IntPtr EvpAes256Ccm();
 
@@ -241,12 +259,21 @@ internal static partial class Interop
         [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpDesEcb")]
         internal static extern IntPtr EvpDesEcb();
 
+        [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpDesCfb8")]
+        internal static extern IntPtr EvpDesCfb8();
+
         [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpDes3Cbc")]
         internal static extern IntPtr EvpDes3Cbc();
 
         [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpDes3Ecb")]
         internal static extern IntPtr EvpDes3Ecb();
 
+        [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpDes3Cfb8")]
+        internal static extern IntPtr EvpDes3Cfb8();
+
+        [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpDes3Cfb64")]
+        internal static extern IntPtr EvpDes3Cfb64();
+
         [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpRC2Cbc")]
         internal static extern IntPtr EvpRC2Cbc();
 
index 96da167..ad685bc 100644 (file)
@@ -9,25 +9,39 @@ namespace Internal.Cryptography
 {
     internal static class AesBCryptModes
     {
-        private static readonly SafeAlgorithmHandle s_hAlgCbc = OpenAesAlgorithm(Cng.BCRYPT_CHAIN_MODE_CBC);
-        private static readonly SafeAlgorithmHandle s_hAlgEcb = OpenAesAlgorithm(Cng.BCRYPT_CHAIN_MODE_ECB);
+        private static readonly Lazy<SafeAlgorithmHandle> s_hAlgCbc = OpenAesAlgorithm(Cng.BCRYPT_CHAIN_MODE_CBC);
+        private static readonly Lazy<SafeAlgorithmHandle> s_hAlgEcb = OpenAesAlgorithm(Cng.BCRYPT_CHAIN_MODE_ECB);
+        private static readonly Lazy<SafeAlgorithmHandle> s_hAlgCfb128 = OpenAesAlgorithm(Cng.BCRYPT_CHAIN_MODE_CFB, 16);
+        private static readonly Lazy<SafeAlgorithmHandle> s_hAlgCfb8 = OpenAesAlgorithm(Cng.BCRYPT_CHAIN_MODE_CFB, 1);
 
-        internal static SafeAlgorithmHandle GetSharedHandle(CipherMode cipherMode) =>
+        internal static SafeAlgorithmHandle GetSharedHandle(CipherMode cipherMode, int feedback) =>
             // Windows 8 added support to set the CipherMode value on a key,
             // but Windows 7 requires that it be set on the algorithm before key creation.
-            cipherMode switch
+            (cipherMode, feedback) switch
             {
-                CipherMode.CBC => s_hAlgCbc,
-                CipherMode.ECB => s_hAlgEcb,
+                (CipherMode.CBC, _) => s_hAlgCbc.Value,
+                (CipherMode.ECB, _) => s_hAlgEcb.Value,
+                (CipherMode.CFB, 16) => s_hAlgCfb128.Value,
+                (CipherMode.CFB, 1) => s_hAlgCfb8.Value,
                 _ => throw new NotSupportedException(),
             };
 
-        internal static SafeAlgorithmHandle OpenAesAlgorithm(string cipherMode)
+        internal static Lazy<SafeAlgorithmHandle> OpenAesAlgorithm(string cipherMode, int feedback = 0)
         {
-            SafeAlgorithmHandle hAlg = Cng.BCryptOpenAlgorithmProvider(Cng.BCRYPT_AES_ALGORITHM, null, Cng.OpenAlgorithmProviderFlags.NONE);
-            hAlg.SetCipherMode(cipherMode);
+            return new Lazy<SafeAlgorithmHandle>(() =>
+            {
+                SafeAlgorithmHandle hAlg = Cng.BCryptOpenAlgorithmProvider(Cng.BCRYPT_AES_ALGORITHM, null,
+                    Cng.OpenAlgorithmProviderFlags.NONE);
+                hAlg.SetCipherMode(cipherMode);
+
+                // feedback is in bytes!
+                if (feedback > 0)
+                {
+                    hAlg.SetFeedbackSize(feedback);
+                }
 
-            return hAlg;
+                return hAlg;
+            });
         }
     }
 }
index a0cf905..078288a 100644 (file)
@@ -66,6 +66,7 @@ namespace Internal.NativeCrypto
         public const string BCRYPT_CHAIN_MODE_CBC = "ChainingModeCBC";
         public const string BCRYPT_CHAIN_MODE_ECB = "ChainingModeECB";
         public const string BCRYPT_CHAIN_MODE_GCM = "ChainingModeGCM";
+        public const string BCRYPT_CHAIN_MODE_CFB = "ChainingModeCFB";
         public const string BCRYPT_CHAIN_MODE_CCM = "ChainingModeCCM";
 
         public static SafeAlgorithmHandle BCryptOpenAlgorithmProvider(string pszAlgId, string? pszImplementation, OpenAlgorithmProviderFlags dwFlags)
@@ -77,6 +78,16 @@ namespace Internal.NativeCrypto
             return hAlgorithm;
         }
 
+        public static void SetFeedbackSize(this SafeAlgorithmHandle hAlg, int dwFeedbackSize)
+        {
+            NTSTATUS ntStatus = Interop.BCryptSetIntProperty(hAlg, BCryptPropertyStrings.BCRYPT_MESSAGE_BLOCK_LENGTH, ref dwFeedbackSize, 0);
+
+            if (ntStatus != NTSTATUS.STATUS_SUCCESS)
+            {
+                throw CreateCryptographicException(ntStatus);
+            }
+        }
+
         public static void SetCipherMode(this SafeAlgorithmHandle hAlg, string cipherMode)
         {
             NTSTATUS ntStatus = Interop.BCryptSetProperty(hAlg, BCryptPropertyStrings.BCRYPT_CHAINING_MODE, cipherMode, (cipherMode.Length + 1) * 2, 0);
index f4fd13e..85f9b65 100644 (file)
@@ -9,25 +9,37 @@ namespace Internal.Cryptography
 {
     internal static class DesBCryptModes
     {
-        private static readonly SafeAlgorithmHandle s_hAlgCbc = OpenDesAlgorithm(Cng.BCRYPT_CHAIN_MODE_CBC);
-        private static readonly SafeAlgorithmHandle s_hAlgEcb = OpenDesAlgorithm(Cng.BCRYPT_CHAIN_MODE_ECB);
+        private static readonly Lazy<SafeAlgorithmHandle> s_hAlgCbc = OpenDesAlgorithm(Cng.BCRYPT_CHAIN_MODE_CBC);
+        private static readonly Lazy<SafeAlgorithmHandle> s_hAlgEcb = OpenDesAlgorithm(Cng.BCRYPT_CHAIN_MODE_ECB);
+        private static readonly Lazy<SafeAlgorithmHandle> s_hAlgCfb8 = OpenDesAlgorithm(Cng.BCRYPT_CHAIN_MODE_CFB, 1);
 
-        internal static SafeAlgorithmHandle GetSharedHandle(CipherMode cipherMode) =>
+        internal static SafeAlgorithmHandle GetSharedHandle(CipherMode cipherMode, int feedback) =>
             // Windows 8 added support to set the CipherMode value on a key,
             // but Windows 7 requires that it be set on the algorithm before key creation.
-            cipherMode switch
+            (cipherMode, feedback) switch
             {
-                CipherMode.CBC => s_hAlgCbc,
-                CipherMode.ECB => s_hAlgEcb,
+                (CipherMode.CBC, _) => s_hAlgCbc.Value,
+                (CipherMode.ECB, _) => s_hAlgEcb.Value,
+                (CipherMode.CFB, 1) => s_hAlgCfb8.Value,
                 _ => throw new NotSupportedException(),
             };
 
-        private static SafeAlgorithmHandle OpenDesAlgorithm(string cipherMode)
+        private static Lazy<SafeAlgorithmHandle> OpenDesAlgorithm(string cipherMode, int feedback = 0)
         {
-            SafeAlgorithmHandle hAlg = Cng.BCryptOpenAlgorithmProvider(Cng.BCRYPT_DES_ALGORITHM, null, Cng.OpenAlgorithmProviderFlags.NONE);
-            hAlg.SetCipherMode(cipherMode);
+            return new Lazy<SafeAlgorithmHandle>(() =>
+            {
+                SafeAlgorithmHandle hAlg = Cng.BCryptOpenAlgorithmProvider(Cng.BCRYPT_DES_ALGORITHM,
+                    null,
+                    Cng.OpenAlgorithmProviderFlags.NONE);
+                hAlg.SetCipherMode(cipherMode);
+
+                if (feedback > 0)
+                {
+                    hAlg.SetFeedbackSize(feedback);
+                }
 
-            return hAlg;
+                return hAlg;
+            });
         }
     }
 }
index a544857..6c4b9f1 100644 (file)
@@ -11,5 +11,6 @@ internal partial class Interop
     {
         internal const string BCRYPT_CHAIN_MODE_CBC = "ChainingModeCBC";
         internal const string BCRYPT_CHAIN_MODE_ECB = "ChainingModeECB";
+        internal const string BCRYPT_CHAIN_MODE_CFB = "ChainingModeCFB";
     }
 }
index bcba9b2..6b29607 100644 (file)
@@ -11,6 +11,7 @@ internal partial class Interop
             internal const string BCRYPT_ECC_PARAMETERS = "ECCParameters";
             internal const string BCRYPT_EFFECTIVE_KEY_LENGTH = "EffectiveKeyLength";
             internal const string BCRYPT_HASH_LENGTH = "HashDigestLength";
+            internal const string BCRYPT_MESSAGE_BLOCK_LENGTH = "MessageBlockLength";
         }
     }
 }
index dcffc3b..c20b040 100644 (file)
@@ -10,26 +10,42 @@ namespace Internal.Cryptography
     internal static class TripleDesBCryptModes
     {
         [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA5350", Justification = "We are providing the implementation for TripleDES, not consuming it")]
-        private static readonly SafeAlgorithmHandle s_hAlgCbc = Open3DesAlgorithm(Cng.BCRYPT_CHAIN_MODE_CBC);
+        private static readonly Lazy<SafeAlgorithmHandle> s_hAlgCbc = Open3DesAlgorithm(Cng.BCRYPT_CHAIN_MODE_CBC);
         [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA5350", Justification = "We are providing the implementation for TripleDES, not consuming it")]
-        private static readonly SafeAlgorithmHandle s_hAlgEcb = Open3DesAlgorithm(Cng.BCRYPT_CHAIN_MODE_ECB);
+        private static readonly Lazy<SafeAlgorithmHandle> s_hAlgEcb = Open3DesAlgorithm(Cng.BCRYPT_CHAIN_MODE_ECB);
+        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA5350", Justification = "We are providing the implementation for TripleDES, not consuming it")]
+        private static readonly Lazy<SafeAlgorithmHandle> s_hAlgCfb8 = Open3DesAlgorithm(Cng.BCRYPT_CHAIN_MODE_CFB, 1);
+        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA5350", Justification = "We are providing the implementation for TripleDES, not consuming it")]
+        private static readonly Lazy<SafeAlgorithmHandle> s_hAlgCfb64 = Open3DesAlgorithm(Cng.BCRYPT_CHAIN_MODE_CFB, 8);
 
-        internal static SafeAlgorithmHandle GetSharedHandle(CipherMode cipherMode) =>
+        internal static SafeAlgorithmHandle GetSharedHandle(CipherMode cipherMode, int feedback) =>
             // Windows 8 added support to set the CipherMode value on a key,
             // but Windows 7 requires that it be set on the algorithm before key creation.
-            cipherMode switch
+            (cipherMode, feedback) switch
             {
-                CipherMode.CBC => s_hAlgCbc,
-                CipherMode.ECB => s_hAlgEcb,
+                (CipherMode.CBC, _) => s_hAlgCbc.Value,
+                (CipherMode.ECB, _) => s_hAlgEcb.Value,
+                (CipherMode.CFB, 1) => s_hAlgCfb8.Value,
+                (CipherMode.CFB, 8) => s_hAlgCfb64.Value,
                 _ => throw new NotSupportedException(),
             };
 
-        private static SafeAlgorithmHandle Open3DesAlgorithm(string cipherMode)
+        private static Lazy<SafeAlgorithmHandle> Open3DesAlgorithm(string cipherMode, int feedback = 0)
         {
-            SafeAlgorithmHandle hAlg = Cng.BCryptOpenAlgorithmProvider(Cng.BCRYPT_3DES_ALGORITHM, null, Cng.OpenAlgorithmProviderFlags.NONE);
-            hAlg.SetCipherMode(cipherMode);
+            return new Lazy<SafeAlgorithmHandle>(() =>
+            {
+                SafeAlgorithmHandle hAlg = Cng.BCryptOpenAlgorithmProvider(Cng.BCRYPT_3DES_ALGORITHM, null,
+                    Cng.OpenAlgorithmProviderFlags.NONE);
+                hAlg.SetCipherMode(cipherMode);
+
+                if (feedback > 0)
+                {
+                    // feedback is in bytes!
+                    hAlg.SetFeedbackSize(feedback);
+                }
 
-            return hAlg;
+                return hAlg;
+            });
         }
     }
 }
index 9d7d3e2..67a44f8 100644 (file)
@@ -77,6 +77,28 @@ namespace System.Security.Cryptography.Encryption.Aes.Tests
             TestAesDecrypt(CipherMode.CBC, s_aes256Key, s_aes256CbcIv, encryptedBytes, s_multiBlockBytes);
         }
 
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        public static void DecryptKnownCFB128_256()
+        {
+            byte[] encryptedBytes = new byte[]
+            {
+                0x71, 0x67, 0xD7, 0x86, 0xAA, 0xF8, 0xD9, 0x1A,
+                0x3A, 0xFB, 0xA5, 0x9E, 0x41, 0xCA, 0x39, 0x32,
+                0x6A, 0x42, 0xA4, 0xD2, 0x26, 0x32, 0x85, 0x05,
+                0x5A, 0x98, 0xE4, 0x3A, 0xDA, 0xD7, 0x1B, 0x1A,
+                0x47, 0x08, 0xF7, 0x7F, 0xC7, 0x08, 0xBF, 0x7C,
+                0x57, 0xE9, 0x13, 0xD5, 0x4F, 0x8F, 0x23, 0x76,
+                0xAA, 0xB5, 0x83, 0xE1, 0x5C, 0x48, 0x8A, 0x0D,
+                0x4A, 0xFD, 0x10, 0x7C, 0xF1, 0x1B, 0x86, 0xA8,
+                0xAB, 0x9D, 0x5B, 0x49, 0x9D, 0xA4, 0x9C, 0x9B,
+                0x2B, 0xD1, 0xEC, 0xFF, 0xEB, 0xF7, 0x3B, 0x69,
+                0x80, 0x0F, 0xA6, 0x26, 0x3E, 0xD9, 0x72, 0xB9,
+                0x72, 0x22, 0x85, 0x50, 0x95, 0x59, 0xFA, 0x5F
+            };
+
+            TestAesDecrypt(CipherMode.CFB, s_aes256Key, s_aes256CbcIv, encryptedBytes, s_multiBlockBytes, 128);
+        }
+
         [Fact]
         public static void DecryptKnownECB192()
         {
@@ -99,6 +121,50 @@ namespace System.Security.Cryptography.Encryption.Aes.Tests
             TestAesDecrypt(CipherMode.ECB, s_aes192Key, null, encryptedBytes, s_multiBlockBytes);
         }
 
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        public static void DecryptKnownCFB128_192()
+        {
+            byte[] encryptedBytes = new byte[]
+            {
+                0x7C, 0xC6, 0xEE, 0xD8, 0xED, 0xB5, 0x3F, 0x8A, 
+                0x90, 0x95, 0x12, 0xD2, 0xBC, 0x9A, 0x96, 0x1E, 
+                0x4E, 0xC4, 0xD1, 0x15, 0xA4, 0x7F, 0x32, 0xA4, 
+                0xD1, 0xFD, 0x8E, 0x02, 0x45, 0xE8, 0x93, 0x3C, 
+                0x3C, 0x91, 0x3F, 0xA4, 0x7F, 0x99, 0xF7, 0x3A, 
+                0x53, 0x0C, 0x0B, 0xFD, 0x01, 0xC5, 0xBD, 0x76, 
+                0xB7, 0xCF, 0x2B, 0x52, 0x34, 0xB1, 0xA6, 0xA4, 
+                0x29, 0x2F, 0x7D, 0x1C, 0x97, 0x3A, 0xE2, 0x75, 
+                0x3E, 0xEB, 0xFC, 0xB7, 0xBB, 0x7A, 0xC0, 0x66, 
+                0x34, 0x25, 0xCF, 0x2D, 0xE2, 0x7E, 0x23, 0x06, 
+                0x10, 0xFE, 0xEA, 0xB3, 0x0F, 0x1D, 0x2C, 0xDD, 
+                0x72, 0x64, 0x51, 0x78, 0x1D, 0x75, 0xD2, 0x17
+            };
+
+            TestAesDecrypt(CipherMode.CFB, s_aes192Key, s_aes256CbcIv, encryptedBytes, s_multiBlockBytes, 128);
+        }
+
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        public static void DecryptKnownCFB128_128()
+        {
+            byte[] encryptedBytes = new byte[]
+            {
+                0x5B, 0x63, 0x3D, 0x1C, 0x0C, 0x8E, 0xD4, 0xF4, 
+                0xE5, 0x5F, 0xA0, 0xAF, 0x2F, 0xF5, 0xAE, 0x59, 
+                0xB9, 0xC4, 0xFA, 0x02, 0x11, 0x37, 0xEB, 0x38, 
+                0x5B, 0x2F, 0x1D, 0xF5, 0x03, 0xD1, 0xFD, 0x85, 
+                0x4B, 0xAA, 0x4F, 0x29, 0x94, 0x09, 0x31, 0x4C, 
+                0x4D, 0xD6, 0x99, 0xE3, 0x4D, 0xC4, 0x3A, 0x40, 
+                0x97, 0x58, 0xA5, 0x26, 0x80, 0xA8, 0xCA, 0xFA, 
+                0x6D, 0x19, 0x3B, 0x6B, 0x6F, 0x75, 0x76, 0x83, 
+                0x90, 0x31, 0x07, 0x86, 0x35, 0xD6, 0xAB, 0xB4, 
+                0x65, 0x07, 0x0A, 0x0A, 0xA3, 0x7A, 0xD7, 0x16, 
+                0xE2, 0xC5, 0x3B, 0xE0, 0x42, 0x5F, 0xFA, 0xEF, 
+                0xE1, 0x2E, 0x40, 0x84, 0x36, 0x66, 0xB1, 0xBA
+            };
+
+            TestAesDecrypt(CipherMode.CFB, s_aes128Key, s_aes256CbcIv, encryptedBytes, s_multiBlockBytes, 128);
+        }
+
         [Fact]
         public static void VerifyInPlaceEncryption()
         {
@@ -276,6 +342,32 @@ namespace System.Security.Cryptography.Encryption.Aes.Tests
                 cipherBytes: new byte[] { 0x6C, 0xD0, 0x25, 0x13, 0xE8, 0xD4, 0xDC, 0x98, 0x6B, 0x4A, 0xFE, 0x08, 0x7A, 0x60, 0xBD, 0x0C });
         }
 
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        public static void VerifyKnownTransform_CFB128_8_NoPadding()
+        {
+            TestAesTransformDirectKey(
+                CipherMode.CFB,
+                PaddingMode.None,
+                key: new byte[] { 0x00, 0x01, 0x02, 0x03, 0x05, 0x06, 0x07, 0x08, 0x0A, 0x0B, 0x0C, 0x0D, 0x0F, 0x10, 0x11, 0x12 },
+                iv: new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+                plainBytes: new byte[] { 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59 },
+                cipherBytes: new byte[] { 0xD5, 0x47, 0xC5, 0x23, 0xCF, 0x5D, 0xFF, 0x67, 0x4C, 0xB4, 0xDB, 0x03, 0x96, 0xA3, 0xEB, 0xCF },
+                8);
+        }
+
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        public static void VerifyKnownTransform_CFB128_128_NoPadding()
+        {
+            TestAesTransformDirectKey(
+                CipherMode.CFB,
+                PaddingMode.None,
+                key: new byte[] { 0x00, 0x01, 0x02, 0x03, 0x05, 0x06, 0x07, 0x08, 0x0A, 0x0B, 0x0C, 0x0D, 0x0F, 0x10, 0x11, 0x12 },
+                iv: new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+                plainBytes: new byte[] { 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59 },
+                cipherBytes: new byte[] { 0xD5, 0x91, 0xEE, 0x44, 0xF7, 0xC4, 0x31, 0xFB, 0x40, 0x20, 0x2F, 0x03, 0x6C, 0x14, 0x7C, 0xAC },
+                128);
+        }
+
         [Fact]
         public static void VerifyKnownTransform_CBC128_NoPadding()
         {
@@ -300,6 +392,32 @@ namespace System.Security.Cryptography.Encryption.Aes.Tests
                 cipherBytes: new byte[] { 0x19, 0x46, 0xDA, 0xBF, 0x6A, 0x03, 0xA2, 0xA2, 0xC3, 0xD0, 0xB0, 0x50, 0x80, 0xAE, 0xD6, 0xFC });
         }
 
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        public static void VerifyKnownTransform_CFB128_256_NoPadding()
+        {
+            TestAesTransformDirectKey(
+                CipherMode.CFB,
+                PaddingMode.None,
+                key: new byte[] { 0x00, 0x01, 0x02, 0x03, 0x05, 0x06, 0x07, 0x08, 0x0A, 0x0B, 0x0C, 0x0D, 0x0F, 0x10, 0x11, 0x12, 0x14, 0x15, 0x16, 0x17, 0x19, 0x1A, 0x1B, 0x1C, 0x1E, 0x1F, 0x20, 0x21, 0x23, 0x24, 0x25, 0x26 },
+                iv: new byte[] { 0x83, 0x4E, 0xAD, 0xFC, 0xCA, 0xC7, 0xE1, 0xB3, 0x06, 0x64, 0xB1, 0xAB, 0xA4, 0x48, 0x15, 0xAB },
+                plainBytes: new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+                cipherBytes: new byte[] { 0x19, 0x46, 0xDA, 0xBF, 0x6A, 0x03, 0xA2, 0xA2, 0xC3, 0xD0, 0xB0, 0x50, 0x80, 0xAE, 0xD6, 0xFC },
+                128);
+        }
+
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        public static void VerifyKnownTransform_CFB8_256_NoPadding()
+        {
+            TestAesTransformDirectKey(
+                CipherMode.CFB,
+                PaddingMode.None,
+                key: new byte[] { 0x00, 0x01, 0x02, 0x03, 0x05, 0x06, 0x07, 0x08, 0x0A, 0x0B, 0x0C, 0x0D, 0x0F, 0x10, 0x11, 0x12, 0x14, 0x15, 0x16, 0x17, 0x19, 0x1A, 0x1B, 0x1C, 0x1E, 0x1F, 0x20, 0x21, 0x23, 0x24, 0x25, 0x26 },
+                iv: new byte[] { 0x83, 0x4E, 0xAD, 0xFC, 0xCA, 0xC7, 0xE1, 0xB3, 0x06, 0x64, 0xB1, 0xAB, 0xA4, 0x48, 0x15, 0xAB },
+                plainBytes: new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+                cipherBytes: new byte[] { 0x19, 0x38, 0x0A, 0x23, 0x92, 0x37, 0xC2, 0x7A, 0xBA, 0xD1, 0x82, 0x62, 0xE0, 0x36, 0x83, 0x0C },
+                8);
+        }
+
         [Fact]
         public static void VerifyKnownTransform_CBC128_NoPadding_2()
         {
@@ -312,6 +430,19 @@ namespace System.Security.Cryptography.Encryption.Aes.Tests
                 cipherBytes: new byte[] { 0x0E, 0xDD, 0x33, 0xD3, 0xC6, 0x21, 0xE5, 0x46, 0x45, 0x5B, 0xD8, 0xBA, 0x14, 0x18, 0xBE, 0xC8 });
         }
 
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        public static void VerifyKnownTransform_CFB128_128_NoPadding_2()
+        {
+            TestAesTransformDirectKey(
+                CipherMode.CFB,
+                PaddingMode.None,
+                key: new byte[] { 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+                iv: new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6 },
+                plainBytes: new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6 },
+                cipherBytes: new byte[] { 0xC0, 0xB7, 0x81, 0xC8, 0xC9, 0x80, 0x5A, 0x87, 0x61, 0x0E, 0xB4, 0x36, 0x6D, 0xAC, 0xA1, 0x2E },
+                128);
+        }
+
         [Fact]
         public static void VerifyKnownTransform_CBC128_NoPadding_3()
         {
@@ -324,6 +455,19 @@ namespace System.Security.Cryptography.Encryption.Aes.Tests
                 cipherBytes: new byte[] { 0x3A, 0xD7, 0x8E, 0x72, 0x6C, 0x1E, 0xC0, 0x2B, 0x7E, 0xBF, 0xE9, 0x2B, 0x23, 0xD9, 0xEC, 0x34 });
         }
 
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        public static void VerifyKnownTransform_CFB128_128_NoPadding_3()
+        {
+            TestAesTransformDirectKey(
+                CipherMode.CFB,
+                PaddingMode.None,
+                key: new byte[] { 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+                iv: new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6 },
+                plainBytes: new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6 },
+                cipherBytes: new byte[] { 0xC0, 0xB7, 0x81, 0xC8, 0xC9, 0x80, 0x5A, 0x87, 0x61, 0x0E, 0xB4, 0x36, 0x6D, 0xAC, 0xA1, 0x2E },
+                128);
+        }
+
         [Fact]
         public static void VerifyKnownTransform_CBC192_NoPadding()
         {
@@ -336,6 +480,32 @@ namespace System.Security.Cryptography.Encryption.Aes.Tests
                 cipherBytes: new byte[] { 0xDE, 0x88, 0x5D, 0xC8, 0x7F, 0x5A, 0x92, 0x59, 0x40, 0x82, 0xD0, 0x2C, 0xC1, 0xE1, 0xB4, 0x2C });
         }
 
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        public static void VerifyKnownTransform_CFB128_192_NoPadding()
+        {
+            TestAesTransformDirectKey(
+                CipherMode.CFB,
+                PaddingMode.None,
+                key: new byte[] { 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+                iv: new byte[] { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 },
+                plainBytes: new byte[] { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 },
+                cipherBytes: new byte[] { 0xE9, 0x6E, 0xA7, 0xDA, 0x76, 0x90, 0xCB, 0x98, 0x56, 0x54, 0xE8, 0xFB, 0x86, 0xA3, 0xEB, 0x95 },
+                128);
+        }
+
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        public static void VerifyKnownTransform_CFB8_192_NoPadding()
+        {
+            TestAesTransformDirectKey(
+                CipherMode.CFB,
+                PaddingMode.None,
+                key: new byte[] { 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+                iv: new byte[] { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 },
+                plainBytes: new byte[] { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 },
+                cipherBytes: new byte[] { 0xE9, 0x3E, 0xE5, 0xBF, 0x29, 0xFF, 0x95, 0x6E, 0x6B, 0xD6, 0xE8, 0x6F, 0x9F, 0x6A, 0x05, 0x62 },
+                8);
+        }
+
         [Fact]
         public static void VerifyKnownTransform_CBC192_NoPadding_2()
         {
@@ -348,6 +518,19 @@ namespace System.Security.Cryptography.Encryption.Aes.Tests
                 cipherBytes: new byte[] { 0x6C, 0xD0, 0x25, 0x13, 0xE8, 0xD4, 0xDC, 0x98, 0x6B, 0x4A, 0xFE, 0x08, 0x7A, 0x60, 0xBD, 0x0C });
         }
 
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        public static void VerifyKnownTransform_CFB128_192_NoPadding_2()
+        {
+            TestAesTransformDirectKey(
+                CipherMode.CFB,
+                PaddingMode.None,
+                key: new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+                iv: new byte[] { 0x81, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+                plainBytes: new byte[] { 0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+                cipherBytes: new byte[] { 0xAA, 0xB9, 0xD1, 0x9F, 0x4A, 0x66, 0xEF, 0x3A, 0x9A, 0x60, 0xAF, 0x10, 0xD8, 0x3D, 0x84, 0x10 },
+                128);
+        }
+
         [Fact]
         public static void WrongKeyFailDecrypt()
         {
@@ -442,6 +625,307 @@ namespace System.Security.Cryptography.Encryption.Aes.Tests
             }
         }
 
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        public static void VerifyKnownTransform_CFB8_128_NoPadding_4()
+        {
+            // NIST CAVP AESMMT.ZIP CFB8MMT128.rsp, [ENCRYPT] COUNT=4
+            // plaintext not extended
+            TestAesTransformDirectKey(
+                CipherMode.CFB,
+                PaddingMode.None,
+                key: "5d5e7f20e0a66d3e09e0e5a9912f8a46".HexToByteArray(),
+                iv: "052d7ea0ad1f2956a23b27afe1d87b6b".HexToByteArray(),
+                plainBytes: "b84a90fc6d".HexToByteArray(),
+                cipherBytes: "1a9a61c307".HexToByteArray(),
+                feedbackSize: 8);
+        }
+
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        public static void VerifyKnownTransform_CFB128_128_NoPadding_4_Fails()
+        {
+            Assert.Throws<CryptographicException>(() =>
+                TestAesTransformDirectKey(
+                    CipherMode.CFB,
+                    PaddingMode.None,
+                    key: "5d5e7f20e0a66d3e09e0e5a9912f8a46".HexToByteArray(),
+                    iv: "052d7ea0ad1f2956a23b27afe1d87b6b".HexToByteArray(),
+                    plainBytes: "b84a90fc6d".HexToByteArray(),
+                    cipherBytes: "1a9a61c307".HexToByteArray(),
+                    feedbackSize: 128)
+            );
+        }
+        
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        public static void VerifyKnownTransform_CFB128_128_PKCS7_4()
+        {
+            TestAesTransformDirectKey(
+                CipherMode.CFB,
+                PaddingMode.PKCS7,
+                key: "5d5e7f20e0a66d3e09e0e5a9912f8a46".HexToByteArray(),
+                iv: "052d7ea0ad1f2956a23b27afe1d87b6b".HexToByteArray(),
+                plainBytes: "b84a90fc6d".HexToByteArray(),
+                cipherBytes: "1aae9ac4cd4742f28ed593b48efce7cd".HexToByteArray(),
+                feedbackSize: 128);
+        }
+
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        public static void VerifyKnownTransform_CFB8_128_PKCS7_4()
+        {
+            TestAesTransformDirectKey(
+                CipherMode.CFB,
+                PaddingMode.PKCS7,
+                key: "5d5e7f20e0a66d3e09e0e5a9912f8a46".HexToByteArray(),
+                iv: "052d7ea0ad1f2956a23b27afe1d87b6b".HexToByteArray(),
+                plainBytes: "b84a90fc6d".HexToByteArray(),
+                cipherBytes: "1a9a61c307a4".HexToByteArray(),
+                feedbackSize: 8);
+        }
+
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        public static void VerifyKnownTransform_CFB8_128_NoPadding_0_Extended()
+        {
+            // NIST CAVP AESMMT.ZIP CFB8MMT128.rsp, [ENCRYPT] COUNT=0
+            // plaintext zero-extended to a full block, cipherBytes extended value
+            // provided by .NET Framework
+            TestAesTransformDirectKey(
+                CipherMode.CFB,
+                PaddingMode.None,
+                key: "c57d699d89df7cfbef71c080a6b10ac3".HexToByteArray(),
+                iv: "fcb2bc4c006b87483978796a2ae2c42e".HexToByteArray(),
+                plainBytes: ("61" + "000000000000000000000000000000").HexToByteArray(),
+                cipherBytes: ("24" + "D89FE413C3D37172D6B577E2F94997").HexToByteArray(),
+                feedbackSize: 8);
+        }
+
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        public static void VerifyKnownTransform_CFB8_128_NoPadding_9_Extended()
+        {
+            // NIST CAVP AESMMT.ZIP CFB8MMT128.rsp, [ENCRYPT] COUNT=9
+            // plaintext zero-extended to a full block, cipherBytes extended value
+            // provided by .NET Framework
+            TestAesTransformDirectKey(
+                CipherMode.CFB,
+                PaddingMode.None,
+                key: "3a6f9159263fa6cef2a075caface5817".HexToByteArray(),
+                iv: "0fc23662b7dbf73827f0c7de321ca36e".HexToByteArray(),
+                plainBytes: ("87efeb8d559ed3367728" + "000000000000").HexToByteArray(),
+                cipherBytes: ("8e9c50425614d540ce11" + "7DD85E93D8E0").HexToByteArray(),
+                feedbackSize: 8);
+        }
+
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        public static void VerifyKnownTransform_CFB8_192_NoPadding_0_Extended()
+        {
+            // NIST CAVP AESMMT.ZIP CFB8MMT192.rsp, [ENCRYPT] COUNT=0
+            // plaintext zero-extended to a full block, cipherBytes extended value
+            // provided by .NET Framework
+            TestAesTransformDirectKey(
+                CipherMode.CFB,
+                PaddingMode.None,
+                key: "32a1b0e3da368db563d7316b9779d3327e53d9a6d287ed97".HexToByteArray(),
+                iv: "3dd0e7e21f09d5842f3a699da9b57346".HexToByteArray(),
+                plainBytes: ("54" + "000000000000000000000000000000").HexToByteArray(),
+                cipherBytes: ("6d" + "B3F513638A136D73873517AF1A770F").HexToByteArray(),
+                feedbackSize: 8);
+        }
+
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        public static void VerifyKnownTransform_CFB8_192_NoPadding_9_Extended()
+        {
+            // NIST CAVP AESMMT.ZIP CFB8MMT192.rsp, [ENCRYPT] COUNT=9
+            // plaintext zero-extended to a full block, cipherBytes extended value
+            // provided by .NET Framework
+            TestAesTransformDirectKey(
+                CipherMode.CFB,
+                PaddingMode.None,
+                key: "537e7bf661fd4024a024613f15b13690f7d0c847c1e18965".HexToByteArray(),
+                iv: "3a81f9d9d3c155b0caad5d73349476fc".HexToByteArray(),
+                plainBytes: ("d3d8b9b984adc24237ee" + "000000000000").HexToByteArray(),
+                cipherBytes: ("3879fea72ac99929e53a" + "39552A575D73").HexToByteArray(),
+                feedbackSize: 8);
+        }
+
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        public static void VerifyKnownTransform_CFB8_256_NoPadding_0_Extended()
+        {
+            // NIST CAVP AESMMT.ZIP CFB8MMT256.rsp, [ENCRYPT] COUNT=0
+            // plaintext zero-extended to a full block, cipherBytes extended value
+            // provided by .NET Framework
+            TestAesTransformDirectKey(
+                CipherMode.CFB,
+                PaddingMode.None,
+                key: "34e8091cee09f1bd3ebf1e8f05f51bfbd4899ef2ae006a3a0f7875052cdd46c8".HexToByteArray(),
+                iv: "43eb4dcc4b04a80216a20e4a09a7abb5".HexToByteArray(),
+                plainBytes: ("f9" + "000000000000000000000000000000").HexToByteArray(),
+                cipherBytes: ("28" + "26199F76D20BE53AB4D146CFC6D281").HexToByteArray(),
+                feedbackSize: 8);
+        }
+
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        public static void VerifyKnownTransform_CFB8_256_NoPadding_9_Extended()
+        {
+            // NIST CAVP AESMMT.ZIP CFB8MMT256.rsp, [ENCRYPT] COUNT=9
+            // plaintext zero-extended to a full block, cipherBytes extended value
+            // provided by .NET Framework
+            TestAesTransformDirectKey(
+                CipherMode.CFB,
+                PaddingMode.None,
+                key: "ebbb4566b5e182e0f072466b0b311df38f9175bc0213a5530bce2ec4d74f400d".HexToByteArray(),
+                iv: "0956a48e01002c9e16376d6e308dbad1".HexToByteArray(),
+                plainBytes: ("b0fe25ac8d3d28a2f471" + "000000000000").HexToByteArray(),
+                cipherBytes: ("638c6823e7256fb5626e" + "5EE5C1D7FA17").HexToByteArray(),
+                feedbackSize: 8);
+        }
+
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        public static void VerifyKnownTransform_CFB128_128_NoPadding_0()
+        {
+            // NIST CAVP AESMMT.ZIP CFB128MMT128.rsp, [ENCRYPT] COUNT=0
+            TestAesTransformDirectKey(
+                CipherMode.CFB,
+                PaddingMode.None,
+                key: "085b8af6788fa6bc1a0b47dcf50fbd35".HexToByteArray(),
+                iv: "58cb2b12bb52c6f14b56da9210524864".HexToByteArray(),
+                plainBytes: "4b5a872260293312eea1a570fd39c788".HexToByteArray(),
+                cipherBytes: "e92c80e0cfb6d8b1c27fd58bc3708b16".HexToByteArray(),
+                feedbackSize: 128);
+        }
+
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        public static void VerifyKnownTransform_CFB128_128_NoPadding_1_Extended()
+        {
+            // NIST CAVP AESMMT.ZIP CFB128MMT128.rsp, [ENCRYPT] COUNT=1
+            TestAesTransformDirectKey(
+                CipherMode.CFB,
+                PaddingMode.None,
+                key: "701ccc4c0e36e512ce077f5af6ccb957".HexToByteArray(),
+                iv: "5337ddeaf89a00dd4d58d860de968469".HexToByteArray(),
+                plainBytes: "cc1172f2f80866d0768b25f70fcf6361aab7c627c8488f97525d7d88949beeea".HexToByteArray(),
+                cipherBytes: "cdcf093bb7840df225683b58a479b00d5de5553a7e85eae4b70bf46dc729dd31".HexToByteArray(),
+                feedbackSize: 128);
+        }
+
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        public static void VerifyKnownTransform_CFB128_192_NoPadding_0_Extended()
+        {
+            // NIST CAVP AESMMT.ZIP CFB128MMT192.rsp, [ENCRYPT] COUNT=0
+            TestAesTransformDirectKey(
+                CipherMode.CFB,
+                PaddingMode.None,
+                key: "1bbb30016d3a908827693352ece9833415433618b1d97595".HexToByteArray(),
+                iv: "b2b48e8d60240bf2d9fa05cc2f90c161".HexToByteArray(),
+                plainBytes: "b4e499de51e646fad80030da9dc5e7e2".HexToByteArray(),
+                cipherBytes: "8b7ba98982063a55fca3492269bbe437".HexToByteArray(),
+                feedbackSize: 128);
+        }
+
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        public static void VerifyKnownTransform_CFB128_192_NoPadding_1_Extended()
+        {
+            // NIST CAVP AESMMT.ZIP CFB128MMT192.rsp, [ENCRYPT] COUNT=1
+            TestAesTransformDirectKey(
+                CipherMode.CFB,
+                PaddingMode.None,
+                key: "69f9d29885743826d7c5afc53637e6b1fa9512a10eea9ca9".HexToByteArray(),
+                iv: "3743793c7144a755768437f4ef5a33c8".HexToByteArray(),
+                plainBytes: "f84ebf42a758971c369949e288f775c9cf6a82ab51b286576b45652cd68c3ce6".HexToByteArray(),
+                cipherBytes: "a3bd28bb817bdb3f6492827f2aa3e6e134c254129d8f20dbc92389b7d89702d6".HexToByteArray(),
+                feedbackSize: 128);
+        }
+
+        [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        [InlineData(CipherMode.CBC, 0)]
+        [InlineData(CipherMode.CFB, 128)]
+        [InlineData(CipherMode.CFB, 8)]
+        [InlineData(CipherMode.ECB, 0)]
+        public static void EncryptorReuse_LeadsToSameResults(CipherMode cipherMode, int feedbackSize)
+        {
+            // AppleCCCryptor does not allow calling Reset on CFB cipher.
+            // this test validates that the behavior is taken into consideration.
+            var input = "b72606c98d8e4fabf08839abf7a0ac61".HexToByteArray();
+
+            using (Aes aes = AesFactory.Create())
+            {
+                aes.Mode = cipherMode;
+
+                if (feedbackSize > 0)
+                {
+                    aes.FeedbackSize = feedbackSize;
+                }
+
+                using (ICryptoTransform transform = aes.CreateEncryptor())
+                {
+                    byte[] output1 = transform.TransformFinalBlock(input, 0, input.Length);
+                    byte[] output2 = transform.TransformFinalBlock(input, 0, input.Length);
+
+                    Assert.Equal(output1, output2);
+                }
+            }
+        }
+
+        [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        [InlineData(CipherMode.CBC, 0)]
+        [InlineData(CipherMode.CFB, 128)]
+        [InlineData(CipherMode.CFB, 8)]
+        [InlineData(CipherMode.ECB, 0)]
+        public static void DecryptorReuse_LeadsToSameResults(CipherMode cipherMode, int feedbackSize)
+        {
+            // AppleCCCryptor does not allow calling Reset on CFB cipher.
+            // this test validates that the behavior is taken into consideration.
+            var input = "2981761d979bb1765a28b2dd19125b54".HexToByteArray();
+            var key = "e1c6e6884eee69552dbfee21f22ca92685d5d08ef0e3f37e5b338c533bb8d72c".HexToByteArray();
+            var iv = "cea9f23ae87a637ab0cda6381ecc1202".HexToByteArray();
+
+            using (Aes aes = AesFactory.Create())
+            {
+                aes.Mode = cipherMode;
+                aes.Key = key;
+                aes.IV = iv;
+                aes.Padding = PaddingMode.None;
+
+                if (feedbackSize > 0)
+                {
+                    aes.FeedbackSize = feedbackSize;
+                }
+
+                using (ICryptoTransform transform = aes.CreateDecryptor())
+                {
+                    byte[] output1 = transform.TransformFinalBlock(input, 0, input.Length);
+                    byte[] output2 = transform.TransformFinalBlock(input, 0, input.Length);
+
+                    Assert.Equal(output1, output2);
+                }
+            }
+        }
+
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        public static void VerifyKnownTransform_CFB128_256_NoPadding_0_Extended()
+        {
+            // NIST CAVP AESMMT.ZIP CFB128MMT256.rsp, [ENCRYPT] COUNT=0
+            TestAesTransformDirectKey(
+                CipherMode.CFB,
+                PaddingMode.None,
+                key: "e1c6e6884eee69552dbfee21f22ca92685d5d08ef0e3f37e5b338c533bb8d72c".HexToByteArray(),
+                iv: "cea9f23ae87a637ab0cda6381ecc1202".HexToByteArray(),
+                plainBytes: "b72606c98d8e4fabf08839abf7a0ac61".HexToByteArray(),
+                cipherBytes: "2981761d979bb1765a28b2dd19125b54".HexToByteArray(),
+                feedbackSize: 128);
+        }
+
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        public static void VerifyKnownTransform_CFB128_256_NoPadding_1_Extended()
+        {
+            // NIST CAVP AESMMT.ZIP CFB128MMT256.rsp, [ENCRYPT] COUNT=1
+            TestAesTransformDirectKey(
+                CipherMode.CFB,
+                PaddingMode.None,
+                key: "ae59254c66d8f533e7f5002ced480c33984a421d7816e27be66c34c19bfbc2a8".HexToByteArray(),
+                iv: "821dd21653ece3af675cd25d26017ae3".HexToByteArray(),
+                plainBytes: "3cb4f17e775c2d6d06dd60f15d6c3a103e5131727f9c6cb80d13e00f316eb904".HexToByteArray(),
+                cipherBytes: "ae375db9f28148c460f6c6b6665fcc2ff6b50b8eaf82c64bba8c649efd4731bc".HexToByteArray(),
+                feedbackSize: 128);
+        }
+
         [Fact]
         public static void AesZeroPad()
         {
@@ -555,7 +1039,8 @@ namespace System.Security.Cryptography.Encryption.Aes.Tests
             byte[] key,
             byte[] iv,
             byte[] encryptedBytes,
-            byte[] expectedAnswer)
+            byte[] expectedAnswer,
+            int? feedbackSize = default)
         {
             byte[] decryptedBytes;
 
@@ -563,6 +1048,11 @@ namespace System.Security.Cryptography.Encryption.Aes.Tests
             {
                 aes.Mode = mode;
                 aes.Key = key;
+                
+                if (feedbackSize.HasValue)
+                {
+                    aes.FeedbackSize = feedbackSize.Value;
+                }
 
                 if (iv != null)
                 {
@@ -588,7 +1078,8 @@ namespace System.Security.Cryptography.Encryption.Aes.Tests
             byte[] key,
             byte[] iv,
             byte[] plainBytes,
-            byte[] cipherBytes)
+            byte[] cipherBytes,
+            int? feedbackSize = default)
         {
             byte[] liveEncryptBytes;
             byte[] liveDecryptBytes;
@@ -598,12 +1089,17 @@ namespace System.Security.Cryptography.Encryption.Aes.Tests
                 aes.Mode = cipherMode;
                 aes.Padding = paddingMode;
 
+                if (feedbackSize.HasValue)
+                {
+                    aes.FeedbackSize = feedbackSize.Value;
+                }
+
                 liveEncryptBytes = AesEncryptDirectKey(aes, key, iv, plainBytes);
                 liveDecryptBytes = AesDecryptDirectKey(aes, key, iv, cipherBytes);
             }
 
-            Assert.Equal(plainBytes, liveDecryptBytes);
             Assert.Equal(cipherBytes, liveEncryptBytes);
+            Assert.Equal(plainBytes, liveDecryptBytes);
         }
 
         private static byte[] AesEncryptDirectKey(Aes aes, byte[] key, byte[] iv, byte[] plainBytes)
index 2cab256..8e430d4 100644 (file)
@@ -93,6 +93,65 @@ namespace System.Security.Cryptography.Encryption.Aes.Tests
             }
         }
 
+        [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        [InlineData(0, true)]
+        [InlineData(1, true)]
+        [InlineData(7, true)]
+        [InlineData(9, true)]
+        [InlineData(-1, true)]
+        [InlineData(int.MaxValue, true)]
+        [InlineData(int.MinValue, true)]
+        [InlineData(64, false)]
+        [InlineData(256, true)]
+        [InlineData(127, true)]
+        public static void InvalidCFBFeedbackSizes(int feedbackSize, bool discoverableInSetter)
+        {
+            using (Aes aes = AesFactory.Create())
+            {
+                aes.GenerateKey();
+                aes.Mode = CipherMode.CFB;
+
+                if (discoverableInSetter)
+                {
+                    // there are some key sizes that are invalid for any of the modes,
+                    // so the exception is thrown in the setter
+                    Assert.Throws<CryptographicException>(() =>
+                    {
+                        aes.FeedbackSize = feedbackSize;
+                    });
+                }
+                else
+                {
+                    aes.FeedbackSize = feedbackSize;
+
+                    // however, for CFB only few sizes are valid. Those should throw in the
+                    // actual AES instantiation.
+
+                    Assert.Throws<CryptographicException>(() => aes.CreateDecryptor());
+                    Assert.Throws<CryptographicException>(() => aes.CreateEncryptor());
+                }
+            }
+        }
+
+        [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        [InlineData(8)]
+        [InlineData(128)]
+        public static void ValidCFBFeedbackSizes(int feedbackSize)
+        {
+            using (Aes aes = AesFactory.Create())
+            {
+                aes.GenerateKey();
+                aes.Mode = CipherMode.CFB;
+
+                aes.FeedbackSize = feedbackSize;
+
+                using var decryptor = aes.CreateDecryptor();
+                using var encryptor = aes.CreateEncryptor();
+                Assert.NotNull(decryptor);
+                Assert.NotNull(encryptor);
+            }
+        }
+
         [Theory]
         [InlineData(64, false)]        // smaller than default BlockSize
         [InlineData(129, false)]       // larger than default BlockSize
index 34fa904..e52bf61 100644 (file)
@@ -21,6 +21,18 @@ namespace System.Security.Cryptography.Encryption.Aes.Tests
             SupportsMode(CipherMode.ECB);
         }
 
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        public static void SupportsCFB()
+        {
+            SupportsMode(CipherMode.CFB);
+        }
+
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsWindows7))]
+        public static void Windows7DoesNotSupportCFB()
+        {
+            DoesNotSupportMode(CipherMode.CFB);
+        }
+
         [Fact]
         public static void DoesNotSupportCTS()
         {
@@ -50,7 +62,7 @@ namespace System.Security.Cryptography.Encryption.Aes.Tests
                 // aes.CreateEncryptor() (with an invalid Mode value)
                 // transform.Transform[Final]Block() (with an invalid Mode value)
 
-                Assert.Throws<CryptographicException>(
+                Assert.ThrowsAny<CryptographicException>(
                     () =>
                     {
                         aes.Mode = mode;
index dcfd8dd..bb13c39 100644 (file)
@@ -2,6 +2,7 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 
 using System.Collections.Generic;
+using System.IO;
 using System.Text;
 using Test.Cryptography;
 using Xunit;
@@ -252,6 +253,299 @@ namespace System.Security.Cryptography.Encryption.Des.Tests
             }
         }
 
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        public static void VerifyKnownTransform_CFB8_NoPadding_0()
+        {
+            // NIST CAVS TDESMMT.ZIP TCFB8MMT2.rsp, [DECRYPT] COUNT=0
+            // used only key1, cipherBytes computed using openssl
+            TestDESTransformDirectKey(
+                CipherMode.CFB,
+                PaddingMode.None,
+                key: "fb978a0b6dc2c467".HexToByteArray(),
+                iv: "8b97579ea5ac300f".HexToByteArray(),
+                plainBytes: "80".HexToByteArray(),
+                cipherBytes: "82".HexToByteArray(),
+                feedbackSize: 8
+            );
+        }
+
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        public static void VerifyKnownTransform_CFB8_NoPadding_1()
+        {
+            // NIST CAVS TDESMMT.ZIP TCFB8MMT2.rsp, [DECRYPT] COUNT=1
+            // used only key1, cipherBytes computed using openssl
+            TestDESTransformDirectKey(
+                CipherMode.CFB,
+                PaddingMode.None,
+                key: "9b04c86dd31a8a58".HexToByteArray(),
+                iv: "52cd77d49fc72347".HexToByteArray(),
+                plainBytes: "2fef".HexToByteArray(),
+                cipherBytes: "0fe4".HexToByteArray(),
+                feedbackSize: 8
+            );
+        }
+
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        public static void VerifyKnownTransform_CFB8_NoPadding_2()
+        {
+            // NIST CAVS TDESMMT.ZIP TCFB8MMT2.rsp, [DECRYPT] COUNT=2
+            // used only key1, cipherBytes computed using openssl
+            TestDESTransformDirectKey(
+                CipherMode.CFB,
+                PaddingMode.None,
+                key: "fbb667e340586b5b".HexToByteArray(),
+                iv: "459e8b8736715791".HexToByteArray(),
+                plainBytes: "061704".HexToByteArray(),
+                cipherBytes: "8e9071".HexToByteArray(),
+                feedbackSize: 8
+            );
+        }
+
+        [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        [InlineData(CipherMode.CBC, 0)]
+        [InlineData(CipherMode.CFB, 8)]
+        [InlineData(CipherMode.ECB, 0)]
+        public static void EncryptorReuse_LeadsToSameResults(CipherMode cipherMode, int feedbackSize)
+        {
+            // AppleCCCryptor does not allow calling Reset on CFB cipher.
+            // this test validates that the behavior is taken into consideration.
+            var input = "b72606c98d8e4fabf08839abf7a0ac61".HexToByteArray();
+
+            using (DES des = DESFactory.Create())
+            {
+                des.Mode = cipherMode;
+
+                if (feedbackSize > 0)
+                {
+                    des.FeedbackSize = feedbackSize;
+                }
+
+                using (ICryptoTransform transform = des.CreateEncryptor())
+                {
+                    byte[] output1 = transform.TransformFinalBlock(input, 0, input.Length);
+                    byte[] output2 = transform.TransformFinalBlock(input, 0, input.Length);
+
+                    Assert.Equal(output1, output2);
+                }
+            }
+        }
+
+        [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        [InlineData(CipherMode.CBC, 0)]
+        [InlineData(CipherMode.CFB, 8)]
+        [InlineData(CipherMode.ECB, 0)]
+        public static void DecryptorReuse_LeadsToSameResults(CipherMode cipherMode, int feedbackSize)
+        {
+            // AppleCCCryptor does not allow calling Reset on CFB cipher.
+            // this test validates that the behavior is taken into consideration.
+            var input = "4e6f77206973207468652074696d6520666f7220616c6c20".HexToByteArray();
+            var key = "4a575d02515d40b0".HexToByteArray();
+            var iv = "ab27e9f02affa532".HexToByteArray();
+
+            using (DES des = DESFactory.Create())
+            {
+                des.Mode = cipherMode;
+                des.Key = key;
+                des.IV = iv;
+                des.Padding = PaddingMode.None;
+
+                if (feedbackSize > 0)
+                {
+                    des.FeedbackSize = feedbackSize;
+                }
+
+                using (ICryptoTransform transform = des.CreateDecryptor())
+                {
+                    byte[] output1 = transform.TransformFinalBlock(input, 0, input.Length);
+                    byte[] output2 = transform.TransformFinalBlock(input, 0, input.Length);
+
+                    Assert.Equal(output1, output2);
+                }
+            }
+        }
+
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        public static void VerifyKnownTransform_CFB8_NoPadding_3()
+        {
+            // NIST CAVS TDESMMT.ZIP TCFB8MMT2.rsp, [DECRYPT] COUNT=3
+            // used only key1, cipherBytes computed using openssl
+            TestDESTransformDirectKey(
+                CipherMode.CFB,
+                PaddingMode.None,
+                key: "4a575d02515d40b0".HexToByteArray(),
+                iv: "ab27e9f02affa532".HexToByteArray(),
+                plainBytes: "55f75b95".HexToByteArray(),
+                cipherBytes: "34aa8679".HexToByteArray(),
+                feedbackSize: 8
+            );
+        }
+
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        public static void VerifyKnownTransform_CFB8_PKCS7_3()
+        {
+            // NIST CAVS TDESMMT.ZIP TCFB8MMT2.rsp, [DECRYPT] COUNT=3
+            // used only key1, cipherBytes computed using openssl
+            TestDESTransformDirectKey(
+                CipherMode.CFB,
+                PaddingMode.PKCS7,
+                key: "4a575d02515d40b0".HexToByteArray(),
+                iv: "ab27e9f02affa532".HexToByteArray(),
+                plainBytes: "55f75b95".HexToByteArray(),
+                cipherBytes: "34aa8679ca".HexToByteArray(),
+                feedbackSize: 8
+            );
+        }
+
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        public static void VerifyKnownTransform_CFB8_NoPadding_4()
+        {
+            // NIST CAVS TDESMMT.ZIP TCFB8MMT2.rsp, [DECRYPT] COUNT=4
+            // used only key1, cipherBytes computed using openssl
+            TestDESTransformDirectKey(
+                CipherMode.CFB,
+                PaddingMode.None,
+                key: "91a834855e6bab31".HexToByteArray(),
+                iv: "7838aaad4e64640b".HexToByteArray(),
+                plainBytes: "c3851c0ab4".HexToByteArray(),
+                cipherBytes: "84844450f0".HexToByteArray(),
+                feedbackSize: 8
+            );
+        }
+
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        public static void VerifyKnownTransform_CFB8_NoPadding_5()
+        {
+            // NIST CAVS TDESMMT.ZIP TCFB8MMT2.rsp, [DECRYPT] COUNT=5
+            // used only key1, cipherBytes computed using openssl
+            TestDESTransformDirectKey(
+                CipherMode.CFB,
+                PaddingMode.None,
+                key: "04d923abd9291c3e".HexToByteArray(),
+                iv: "191f8794944e601c".HexToByteArray(),
+                plainBytes: "6fe8f67d2af1".HexToByteArray(),
+                cipherBytes: "6012c9171bb8".HexToByteArray(),
+                feedbackSize: 8
+            );
+        }
+
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        public static void VerifyKnownTransform_CFB8_NoPadding_6()
+        {
+            // NIST CAVS TDESMMT.ZIP TCFB8MMT2.rsp, [DECRYPT] COUNT=6
+            // used only key1, cipherBytes computed using openssl
+            TestDESTransformDirectKey(
+                CipherMode.CFB,
+                PaddingMode.None,
+                key: "a7799e7f5dfe54ce".HexToByteArray(),
+                iv: "370184c749d04a20".HexToByteArray(),
+                plainBytes: "2b4228b769795b".HexToByteArray(),
+                cipherBytes: "58d3de76687976".HexToByteArray(),
+                feedbackSize: 8
+            );
+        }
+
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        public static void VerifyKnownTransform_CFB8_NoPadding_7()
+        {
+            // NIST CAVS TDESMMT.ZIP TCFB8MMT2.rsp, [DECRYPT] COUNT=7
+            // used only key1, cipherBytes computed using openssl
+            TestDESTransformDirectKey(
+                CipherMode.CFB,
+                PaddingMode.None,
+                key: "6bfe3d3df8c1e0d3".HexToByteArray(),
+                iv: "51e4c5c29e858da6".HexToByteArray(),
+                plainBytes: "4cb3554fd0b9ec82".HexToByteArray(),
+                cipherBytes: "16b3595259693776".HexToByteArray(),
+                feedbackSize: 8
+            );
+        }
+
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        public static void VerifyKnownTransform_CFB8_NoPadding_8()
+        {
+            // NIST CAVS TDESMMT.ZIP TCFB8MMT2.rsp, [DECRYPT] COUNT=8
+            // used only key1, cipherBytes computed using openssl
+            TestDESTransformDirectKey(
+                CipherMode.CFB,
+                PaddingMode.None,
+                key: "e0264aec13e63db9".HexToByteArray(),
+                iv: "bd8795dba79930d6".HexToByteArray(),
+                plainBytes: "79068e2943f02914af".HexToByteArray(),
+                cipherBytes: "fe78cb95ce9e4cac2f".HexToByteArray(),
+                feedbackSize: 8
+            );
+        }
+
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        public static void VerifyKnownTransform_CFB8_NoPadding_9()
+        {
+            // NIST CAVS TDESMMT.ZIP TCFB8MMT2.rsp, [DECRYPT] COUNT=9
+            // used only key1, cipherBytes computed using openssl
+            TestDESTransformDirectKey(
+                CipherMode.CFB,
+                PaddingMode.None,
+                key: "7ca28938ba6bec1f".HexToByteArray(),
+                iv: "953896586e49d38f".HexToByteArray(),
+                plainBytes: "2ea956d4a211db6859b7".HexToByteArray(),
+                cipherBytes: "81b850bf481db5df0437".HexToByteArray(),
+                feedbackSize: 8
+            );
+        }
+
+        private static void TestDESTransformDirectKey(
+            CipherMode cipherMode,
+            PaddingMode paddingMode,
+            byte[] key,
+            byte[] iv,
+            byte[] plainBytes,
+            byte[] cipherBytes,
+            int? feedbackSize = default)
+        {
+            byte[] liveEncryptBytes;
+            byte[] liveDecryptBytes;
+
+            using (DES des = DESFactory.Create())
+            {
+                des.Mode = cipherMode;
+                des.Padding = paddingMode;
+
+                if (feedbackSize.HasValue)
+                {
+                    des.FeedbackSize = feedbackSize.Value;
+                }
+
+                liveEncryptBytes = DESEncryptDirectKey(des, key, iv, plainBytes);
+                liveDecryptBytes = DESDecryptDirectKey(des, key, iv, cipherBytes);
+            }
+
+            Assert.Equal(cipherBytes, liveEncryptBytes);
+            Assert.Equal(plainBytes, liveDecryptBytes);
+        }
+
+        private static byte[] DESEncryptDirectKey(DES des, byte[] key, byte[] iv, byte[] plainBytes)
+        {
+            using (MemoryStream output = new MemoryStream())
+            using (CryptoStream cryptoStream = new CryptoStream(output, des.CreateEncryptor(key, iv), CryptoStreamMode.Write))
+            {
+                cryptoStream.Write(plainBytes, 0, plainBytes.Length);
+                cryptoStream.FlushFinalBlock();
+
+                return output.ToArray();
+            }
+        }
+
+        private static byte[] DESDecryptDirectKey(DES des, byte[] key, byte[] iv, byte[] cipherBytes)
+        {
+            using (MemoryStream output = new MemoryStream())
+            using (CryptoStream cryptoStream = new CryptoStream(output, des.CreateDecryptor(key, iv), CryptoStreamMode.Write))
+            {
+                cryptoStream.Write(cipherBytes, 0, cipherBytes.Length);
+                cryptoStream.FlushFinalBlock();
+
+                return output.ToArray();
+            }
+        }
+
         [Theory]
         [InlineData(true, true)]
         [InlineData(true, false)]
diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DES/DESContractTests.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DES/DESContractTests.cs
new file mode 100644 (file)
index 0000000..4fe0e86
--- /dev/null
@@ -0,0 +1,123 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using Test.Cryptography;
+using Xunit;
+
+namespace System.Security.Cryptography.Encryption.Des.Tests
+{
+    public static class DesContractTests
+    {
+        // cfb not available on windows 7
+        [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsWindows7))]
+        [InlineData(0, true)]
+        [InlineData(1, true)]
+        [InlineData(7, true)]
+        [InlineData(9, true)]
+        [InlineData(-1, true)]
+        [InlineData(int.MaxValue, true)]
+        [InlineData(int.MinValue, true)]
+        [InlineData(8, false)]
+        [InlineData(64, false)]
+        [InlineData(256, true)]
+        [InlineData(128, true)]
+        [InlineData(127, true)]
+        public static void Windows7DoesNotSupportCFB(int feedbackSize, bool discoverableInSetter)
+        {
+            using (DES des = DESFactory.Create())
+            {
+                des.GenerateKey();
+                des.Mode = CipherMode.CFB;
+
+                if (discoverableInSetter)
+                {
+                    // there are some key sizes that are invalid for any of the modes,
+                    // so the exception is thrown in the setter
+                    Assert.ThrowsAny<CryptographicException>(() =>
+                    {
+                        des.FeedbackSize = feedbackSize;
+                    });
+                }
+                else
+                {
+                    des.FeedbackSize = feedbackSize;
+
+                    // however, for CFB only few sizes are valid. Those should throw in the
+                    // actual DES instantiation.
+
+                    Assert.ThrowsAny<CryptographicException>(() =>
+                    {
+                        return des.CreateDecryptor();
+                    });
+                    Assert.ThrowsAny<CryptographicException>(() =>
+                    {
+                        return des.CreateEncryptor();
+                    });
+                }
+            }
+        }
+
+        [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        [InlineData(0, true)]
+        [InlineData(1, true)]
+        [InlineData(7, true)]
+        [InlineData(9, true)]
+        [InlineData(-1, true)]
+        [InlineData(int.MaxValue, true)]
+        [InlineData(int.MinValue, true)]
+        [InlineData(64, false)]
+        [InlineData(256, true)]
+        [InlineData(128, true)]
+        [InlineData(127, true)]
+        public static void InvalidCFBFeedbackSizes(int feedbackSize, bool discoverableInSetter)
+        {
+            using (DES des = DESFactory.Create())
+            {
+                des.GenerateKey();
+                des.Mode = CipherMode.CFB;
+
+                if (discoverableInSetter)
+                {
+                    // there are some key sizes that are invalid for any of the modes,
+                    // so the exception is thrown in the setter
+                    Assert.Throws<CryptographicException>(() =>
+                    {
+                        des.FeedbackSize = feedbackSize;
+                    });
+                }
+                else
+                {
+                    des.FeedbackSize = feedbackSize;
+
+                    // however, for CFB only few sizes are valid. Those should throw in the
+                    // actual DES instantiation.
+
+                    Assert.Throws<CryptographicException>(() => des.CreateDecryptor());
+                    Assert.Throws<CryptographicException>(() => des.CreateEncryptor());
+                }
+            }
+        }
+
+        [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        [InlineData(8)]
+        public static void ValidCFBFeedbackSizes(int feedbackSize)
+        {
+            using (DES des = DESFactory.Create())
+            {
+                des.GenerateKey();
+                des.Mode = CipherMode.CFB;
+
+                des.FeedbackSize = feedbackSize;
+
+                using var decryptor = des.CreateDecryptor();
+                using var encryptor = des.CreateEncryptor();
+                Assert.NotNull(decryptor);
+                Assert.NotNull(encryptor);
+            }
+        }
+    }
+}
index 5e62fc9..02e8992 100644 (file)
@@ -162,6 +162,67 @@ namespace System.Security.Cryptography.Encryption.RC2.Tests
             }
         }
 
+        [Theory]
+        [InlineData(CipherMode.CBC, 0)]
+        [InlineData(CipherMode.ECB, 0)]
+        public static void EncryptorReuse_LeadsToSameResults(CipherMode cipherMode, int feedbackSize)
+        {
+            // AppleCCCryptor does not allow calling Reset on CFB cipher.
+            // this test validates that the behavior is taken into consideration.
+            var input = "b72606c98d8e4fabf08839abf7a0ac61".HexToByteArray();
+
+            using (RC2 rc2 = RC2Factory.Create())
+            {
+                rc2.Mode = cipherMode;
+
+                if (feedbackSize > 0)
+                {
+                    rc2.FeedbackSize = feedbackSize;
+                }
+
+                using (ICryptoTransform transform = rc2.CreateEncryptor())
+                {
+                    byte[] output1 = transform.TransformFinalBlock(input, 0, input.Length);
+                    byte[] output2 = transform.TransformFinalBlock(input, 0, input.Length);
+
+                    Assert.Equal(output1, output2);
+                }
+            }
+        }
+
+        [Theory]
+        [InlineData(CipherMode.CBC, 0)]
+        [InlineData(CipherMode.ECB, 0)]
+        public static void DecryptorReuse_LeadsToSameResults(CipherMode cipherMode, int feedbackSize)
+        {
+            // AppleCCCryptor does not allow calling Reset on CFB cipher.
+            // this test validates that the behavior is taken into consideration.
+            var input = "896072ab28e5fdfc".HexToByteArray();
+            var key = "3000000000000000".HexToByteArray();
+            var iv = "3000000000000000".HexToByteArray();
+
+            using (RC2 rc2 = RC2Factory.Create())
+            {
+                rc2.Mode = cipherMode;
+                rc2.Key = key;
+                rc2.IV = iv;
+                rc2.Padding = PaddingMode.None;
+
+                if (feedbackSize > 0)
+                {
+                    rc2.FeedbackSize = feedbackSize;
+                }
+
+                using (ICryptoTransform transform = rc2.CreateDecryptor())
+                {
+                    byte[] output1 = transform.TransformFinalBlock(input, 0, input.Length);
+                    byte[] output2 = transform.TransformFinalBlock(input, 0, input.Length);
+
+                    Assert.Equal(output1, output2);
+                }
+            }
+        }
+
         [Fact]
         public static void RC2ReuseEncryptorDecryptor()
         {
diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/RC2/RC2ContractTests.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/RC2/RC2ContractTests.cs
new file mode 100644 (file)
index 0000000..0555074
--- /dev/null
@@ -0,0 +1,59 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using Test.Cryptography;
+using Xunit;
+
+namespace System.Security.Cryptography.Encryption.RC2.Tests
+{
+    using RC2 = System.Security.Cryptography.RC2;
+
+    public static class RC2ContractTests
+    {
+        [Theory]
+        [InlineData(0, true)]
+        [InlineData(1, true)]
+        [InlineData(7, true)]
+        [InlineData(9, true)]
+        [InlineData(-1, true)]
+        [InlineData(int.MaxValue, true)]
+        [InlineData(int.MinValue, true)]
+        [InlineData(8, false)]
+        [InlineData(64, false)]
+        [InlineData(256, true)]
+        [InlineData(128, true)]
+        [InlineData(127, true)]
+        public static void InvalidCFBFeedbackSizes(int feedbackSize, bool discoverableInSetter)
+        {
+            using (RC2 rc2 = RC2Factory.Create())
+            {
+                rc2.GenerateKey();
+                rc2.Mode = CipherMode.CFB;
+
+                if (discoverableInSetter)
+                {
+                    // there are some key sizes that are invalid for any of the modes,
+                    // so the exception is thrown in the setter
+                    Assert.Throws<CryptographicException>(() =>
+                    {
+                        rc2.FeedbackSize = feedbackSize;
+                    });
+                }
+                else
+                {
+                    rc2.FeedbackSize = feedbackSize;
+
+                    // however, for CFB only few sizes are valid. Those should throw in the
+                    // actual RC2 instantiation.
+
+                    Assert.Throws<CryptographicException>(() => rc2.CreateDecryptor());
+                    Assert.Throws<CryptographicException>(() => rc2.CreateEncryptor());
+                }
+            }
+        }
+    }
+}
index 520a991..cff95ea 100644 (file)
@@ -1,6 +1,7 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
+using System.IO;
 using System.Text;
 using Test.Cryptography;
 using Xunit;
@@ -40,6 +41,455 @@ namespace System.Security.Cryptography.Encryption.TripleDes.Tests
             }
         }
 
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        public static void VerifyKnownTransform_CFB8_NoPadding_0()
+        {
+            // NIST CAVS TDESMMT.ZIP TCFB8MMT2.rsp, [DECRYPT] COUNT=0
+            TestTripleDESTransformDirectKey(
+                CipherMode.CFB,
+                PaddingMode.None,
+                key: "fb978a0b6dc2c467e3cb52329de95161fb978a0b6dc2c467".HexToByteArray(),
+                iv: "8b97579ea5ac300f".HexToByteArray(),
+                plainBytes: "80".HexToByteArray(),
+                cipherBytes: "05".HexToByteArray(),
+                feedbackSize: 8
+            );
+        }
+
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        public static void VerifyKnownTransform_CFB8_NoPadding_1()
+        {
+            // NIST CAVS TDESMMT.ZIP TCFB8MMT2.rsp, [DECRYPT] COUNT=1
+            TestTripleDESTransformDirectKey(
+                CipherMode.CFB,
+                PaddingMode.None,
+                key: "9b04c86dd31a8a589876101549d6e0109b04c86dd31a8a58".HexToByteArray(),
+                iv: "52cd77d49fc72347".HexToByteArray(),
+                plainBytes: "2fef".HexToByteArray(),
+                cipherBytes: "5818".HexToByteArray(),
+                feedbackSize: 8
+            );
+        }
+
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        public static void VerifyKnownTransform_CFB8_NoPadding_2()
+        {
+            // NIST CAVS TDESMMT.ZIP TCFB8MMT2.rsp, [DECRYPT] COUNT=2
+            TestTripleDESTransformDirectKey(
+                CipherMode.CFB,
+                PaddingMode.None,
+                key: "fbb667e340586b5b5ef7c87049b93257fbb667e340586b5b".HexToByteArray(),
+                iv: "459e8b8736715791".HexToByteArray(),
+                plainBytes: "061704".HexToByteArray(),
+                cipherBytes: "93b378".HexToByteArray(),
+                feedbackSize: 8
+            );
+        }
+
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        public static void VerifyKnownTransform_CFB8_PKCS7_2()
+        {
+            // NIST CAVS TDESMMT.ZIP TCFB8MMT2.rsp, [DECRYPT] COUNT=2
+            TestTripleDESTransformDirectKey(
+                CipherMode.CFB,
+                PaddingMode.PKCS7,
+                key: "fbb667e340586b5b5ef7c87049b93257fbb667e340586b5b".HexToByteArray(),
+                iv: "459e8b8736715791".HexToByteArray(),
+                plainBytes: "061704".HexToByteArray(),
+                cipherBytes: "93b37808".HexToByteArray(),
+                feedbackSize: 8
+            );
+        }
+
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        public static void VerifyKnownTransform_CFB64_PKCS7_2()
+        {
+            // NIST CAVS TDESMMT.ZIP TCFB8MMT2.rsp, [DECRYPT] COUNT=2
+            TestTripleDESTransformDirectKey(
+                CipherMode.CFB,
+                PaddingMode.PKCS7,
+                key: "fbb667e340586b5b5ef7c87049b93257fbb667e340586b5b".HexToByteArray(),
+                iv: "459e8b8736715791".HexToByteArray(),
+                plainBytes: "061704".HexToByteArray(),
+                cipherBytes: "931f41eccdab4f99".HexToByteArray(),
+                feedbackSize: 64
+            );
+        }
+
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        public static void VerifyKnownTransform_CFB8_NoPadding_3()
+        {
+            // NIST CAVS TDESMMT.ZIP TCFB8MMT2.rsp, [DECRYPT] COUNT=3
+            TestTripleDESTransformDirectKey(
+                CipherMode.CFB,
+                PaddingMode.None,
+                key: "4a575d02515d40b0a40d830bd9b315134a575d02515d40b0".HexToByteArray(),
+                iv: "ab27e9f02affa532".HexToByteArray(),
+                plainBytes: "55f75b95".HexToByteArray(),
+                cipherBytes: "2ef5dddc".HexToByteArray(),
+                feedbackSize: 8
+            );
+        }
+
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        public static void VerifyKnownTransform_CFB8_NoPadding_4()
+        {
+            // NIST CAVS TDESMMT.ZIP TCFB8MMT2.rsp, [DECRYPT] COUNT=4
+            TestTripleDESTransformDirectKey(
+                CipherMode.CFB,
+                PaddingMode.None,
+                key: "91a834855e6bab31c7fd6be657ceb9ec91a834855e6bab31".HexToByteArray(),
+                iv: "7838aaad4e64640b".HexToByteArray(),
+                plainBytes: "c3851c0ab4".HexToByteArray(),
+                cipherBytes: "fe451f35f1".HexToByteArray(),
+                feedbackSize: 8
+            );
+        }
+
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        public static void VerifyKnownTransform_CFB8_NoPadding_5()
+        {
+            // NIST CAVS TDESMMT.ZIP TCFB8MMT2.rsp, [DECRYPT] COUNT=5
+            TestTripleDESTransformDirectKey(
+                CipherMode.CFB,
+                PaddingMode.None,
+                key: "04d923abd9291c3e4954a8b52fdabcc804d923abd9291c3e".HexToByteArray(),
+                iv: "191f8794944e601c".HexToByteArray(),
+                plainBytes: "6fe8f67d2af1".HexToByteArray(),
+                cipherBytes: "3bd78a8d24ad".HexToByteArray(),
+                feedbackSize: 8
+            );
+        }
+
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        public static void VerifyKnownTransform_CFB8_NoPadding_6()
+        {
+            // NIST CAVS TDESMMT.ZIP TCFB8MMT2.rsp, [DECRYPT] COUNT=6
+            TestTripleDESTransformDirectKey(
+                CipherMode.CFB,
+                PaddingMode.None,
+                key: "a7799e7f5dfe54ce13376401e96de075a7799e7f5dfe54ce".HexToByteArray(),
+                iv: "370184c749d04a20".HexToByteArray(),
+                plainBytes: "2b4228b769795b".HexToByteArray(),
+                cipherBytes: "6f32e4495e4259".HexToByteArray(),
+                feedbackSize: 8
+            );
+        }
+
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        public static void VerifyKnownTransform_CFB8_NoPadding_7()
+        {
+            // NIST CAVS TDESMMT.ZIP TCFB8MMT2.rsp, [DECRYPT] COUNT=7
+            TestTripleDESTransformDirectKey(
+                CipherMode.CFB,
+                PaddingMode.None,
+                key: "6bfe3d3df8c1e0d34ffe0dbf854c940e6bfe3d3df8c1e0d3".HexToByteArray(),
+                iv: "51e4c5c29e858da6".HexToByteArray(),
+                plainBytes: "4cb3554fd0b9ec82".HexToByteArray(),
+                cipherBytes: "72e1738d80d285e2".HexToByteArray(),
+                feedbackSize: 8
+            );
+        }
+
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        public static void VerifyKnownTransform_CFB8_NoPadding_8()
+        {
+            // NIST CAVS TDESMMT.ZIP TCFB8MMT2.rsp, [DECRYPT] COUNT=8
+            TestTripleDESTransformDirectKey(
+                CipherMode.CFB,
+                PaddingMode.None,
+                key: "e0264aec13e63db991f8c120c4b9b6dae0264aec13e63db9".HexToByteArray(),
+                iv: "bd8795dba79930d6".HexToByteArray(),
+                plainBytes: "79068e2943f02914af".HexToByteArray(),
+                cipherBytes: "9b78c5636c5965f88e".HexToByteArray(),
+                feedbackSize: 8
+            );
+        }
+
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        public static void VerifyKnownTransform_CFB8_NoPadding_9()
+        {
+            // NIST CAVS TDESMMT.ZIP TCFB8MMT2.rsp, [DECRYPT] COUNT=9
+            TestTripleDESTransformDirectKey(
+                CipherMode.CFB,
+                PaddingMode.None,
+                key: "7ca28938ba6bec1ffec78f7cd69761947ca28938ba6bec1f".HexToByteArray(),
+                iv: "953896586e49d38f".HexToByteArray(),
+                plainBytes: "2ea956d4a211db6859b7".HexToByteArray(),
+                cipherBytes: "f20e536674a66fa73805".HexToByteArray(),
+                feedbackSize: 8
+            );
+        }
+
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        public static void VerifyKnownTransform_CFB64_NoPadding_0()
+        {
+            // NIST CAVS TDESMMT.ZIP TCFB64MMT2.rsp, [DECRYPT] COUNT=0
+            TestTripleDESTransformDirectKey(
+                CipherMode.CFB,
+                PaddingMode.None,
+                key: "9ee0b59b25865154588551341c4fef9e9ee0b59b25865154".HexToByteArray(),
+                iv: "6e37d197376db595".HexToByteArray(),
+                plainBytes: "dcd3cf9746d6e42b".HexToByteArray(),
+                cipherBytes: "63cad52260e0a1cd".HexToByteArray(),
+                feedbackSize: 64
+            );
+        }
+
+        [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        [InlineData(CipherMode.CBC, 0)]
+        [InlineData(CipherMode.CFB, 8)]
+        [InlineData(CipherMode.CFB, 64)]
+        [InlineData(CipherMode.ECB, 0)]
+        public static void EncryptorReuse_LeadsToSameResults(CipherMode cipherMode, int feedbackSize)
+        {
+            // AppleCCCryptor does not allow calling Reset on CFB cipher.
+            // this test validates that the behavior is taken into consideration.
+            var input = "b72606c98d8e4fabf08839abf7a0ac61".HexToByteArray();
+
+            using (TripleDES tdes = TripleDESFactory.Create())
+            {
+                tdes.Mode = cipherMode;
+
+                if (feedbackSize > 0)
+                {
+                    tdes.FeedbackSize = feedbackSize;
+                }
+
+                using (ICryptoTransform transform = tdes.CreateEncryptor())
+                {
+                    byte[] output1 = transform.TransformFinalBlock(input, 0, input.Length);
+                    byte[] output2 = transform.TransformFinalBlock(input, 0, input.Length);
+
+                    Assert.Equal(output1, output2);
+                }
+            }
+        }
+
+        [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        [InlineData(CipherMode.CBC, 0)]
+        [InlineData(CipherMode.CFB, 8)]
+        [InlineData(CipherMode.CFB, 64)]
+        [InlineData(CipherMode.ECB, 0)]
+        public static void DecryptorReuse_LeadsToSameResults(CipherMode cipherMode, int feedbackSize)
+        {
+            // AppleCCCryptor does not allow calling Reset on CFB cipher.
+            // this test validates that the behavior is taken into consideration.
+            var input = "896072ab28e5fdfc9e8b3610627bf27a".HexToByteArray();
+            var key = "c179d0fdd073a1910e51f1d5fe70047ac179d0fdd073a191".HexToByteArray();
+            var iv = "b956d5426d02b247".HexToByteArray();
+
+            using (TripleDES tdes = TripleDESFactory.Create())
+            {
+                tdes.Mode = cipherMode;
+                tdes.Key = key;
+                tdes.IV = iv;
+                tdes.Padding = PaddingMode.None;
+
+                if (feedbackSize > 0)
+                {
+                    tdes.FeedbackSize = feedbackSize;
+                }
+
+                using (ICryptoTransform transform = tdes.CreateDecryptor())
+                {
+                    byte[] output1 = transform.TransformFinalBlock(input, 0, input.Length);
+                    byte[] output2 = transform.TransformFinalBlock(input, 0, input.Length);
+
+                    Assert.Equal(output1, output2);
+                }
+            }
+        }
+
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        public static void VerifyKnownTransform_CFB64_NoPadding_1()
+        {
+            // NIST CAVS TDESMMT.ZIP TCFB64MMT2.rsp, [DECRYPT] COUNT=1
+            TestTripleDESTransformDirectKey(
+                CipherMode.CFB,
+                PaddingMode.None,
+                key: "c179d0fdd073a1910e51f1d5fe70047ac179d0fdd073a191".HexToByteArray(),
+                iv: "b956d5426d02b247".HexToByteArray(),
+                plainBytes: "32bd529065e26a27643097925e3a726b".HexToByteArray(),
+                cipherBytes: "896072ab28e5fdfc9e8b3610627bf27a".HexToByteArray(),
+                feedbackSize: 64
+            );
+        }
+
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        public static void VerifyKnownTransform_CFB64_NoPadding_2()
+        {
+            // NIST CAVS TDESMMT.ZIP TCFB64MMT2.rsp, [DECRYPT] COUNT=2
+            TestTripleDESTransformDirectKey(
+                CipherMode.CFB,
+                PaddingMode.None,
+                key: "a8084a04495bfb45b3575ee03d732967a8084a04495bfb45".HexToByteArray(),
+                iv: "00fd7b4fdb4b3382".HexToByteArray(),
+                plainBytes: "c20c7041007a67de7b4355be7406095496923b75dfb98080".HexToByteArray(),
+                cipherBytes: "9198c138edb037de25d0bcdebe7b9be10ebd7e7ea103edae".HexToByteArray(),
+                feedbackSize: 64
+            );
+        }
+
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        public static void VerifyKnownTransform_CFB64_NoPadding_3()
+        {
+            // NIST CAVS TDESMMT.ZIP TCFB64MMT2.rsp, [DECRYPT] COUNT=3
+            TestTripleDESTransformDirectKey(
+                CipherMode.CFB,
+                PaddingMode.None,
+                key: "c1497fdf67cecbab80d543f16d13c8d5c1497fdf67cecbab".HexToByteArray(),
+                iv: "a1241ca0fe9378cd".HexToByteArray(),
+                plainBytes: "157dcfa7ad6758335e561fa7dd7f98dca592e9128e7be30ccd1af7dc5a4536d5".HexToByteArray(),
+                cipherBytes: "08fcace492f82282fb3255884a64a231dd438069ffbcb432bd7ec446f5b8adfd".HexToByteArray(),
+                feedbackSize: 64
+            );
+        }
+
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        public static void VerifyKnownTransform_CFB64_NoPadding_4()
+        {
+            // NIST CAVS TDESMMT.ZIP TCFB64MMT2.rsp, [DECRYPT] COUNT=4
+            TestTripleDESTransformDirectKey(
+                CipherMode.CFB,
+                PaddingMode.None,
+                key: "fd0e3262ec38fe5710389d0779c2fb43fd0e3262ec38fe57".HexToByteArray(),
+                iv: "33c9e4adfb4634ac".HexToByteArray(),
+                plainBytes: "37536dda516aab8a992131004134ce48c56fee05261164aae0a88db0f43410617f105e20940cf3e9".HexToByteArray(),
+                cipherBytes: "80e8a96c3fe83857fc738ac7b6639f0d8c28bfa617c56a60fd1b8fbdc36afe9ce3151e161fa5e3a7".HexToByteArray(),
+                feedbackSize: 64
+            );
+        }
+
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        public static void VerifyKnownTransform_CFB64_NoPadding_5()
+        {
+            // NIST CAVS TDESMMT.ZIP TCFB64MMT2.rsp, [DECRYPT] COUNT=5
+            TestTripleDESTransformDirectKey(
+                CipherMode.CFB,
+                PaddingMode.None,
+                key: "ae32253be61040157a7c10b6011fcde3ae32253be6104015".HexToByteArray(),
+                iv: "47be2286dbccdfe6".HexToByteArray(),
+                plainBytes: "e579282129c123c914c700ad8c099b593fe83fdef7be7e5ffb36add9c6b91644cc79c1e457212017488963e16198c528".HexToByteArray(),
+                cipherBytes: "7185c5800ca4d5432b50f5b7920e26296c2913e7e3f847a1ef639e156ba4f9ec6e4b36ded885601d2b9d22f19dc3829f".HexToByteArray(),
+                feedbackSize: 64
+            );
+        }
+
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        public static void VerifyKnownTransform_CFB64_NoPadding_6()
+        {
+            // NIST CAVS TDESMMT.ZIP TCFB64MMT2.rsp, [DECRYPT] COUNT=6
+            TestTripleDESTransformDirectKey(
+                CipherMode.CFB,
+                PaddingMode.None,
+                key: "df83498cec83084acb7aaef26e58f1e0df83498cec83084a".HexToByteArray(),
+                iv: "158d2ca6e70b18f6".HexToByteArray(),
+                plainBytes: "4fb7cf2a244ff20beddf8719b2d9c78ab0710703036f804f08bc1f7927ea9906ba1ef57afd1553c5304c0b72694cd88bb6cb1289772dfff0".HexToByteArray(),
+                cipherBytes: "158b396cd1969a07042e808d0c875d74166ce77291df233fe300c29c5a30b1946575ec02042093537dae3f8d51ed96906e601d9da6e34e14".HexToByteArray(),
+                feedbackSize: 64
+            );
+        }
+
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        public static void VerifyKnownTransform_CFB64_NoPadding_7()
+        {
+            // NIST CAVS TDESMMT.ZIP TCFB64MMT2.rsp, [DECRYPT] COUNT=7
+            TestTripleDESTransformDirectKey(
+                CipherMode.CFB,
+                PaddingMode.None,
+                key: "ce31cd2067c157199bfb3b8ad9ef9223ce31cd2067c15719".HexToByteArray(),
+                iv: "d31741512b6a7471".HexToByteArray(),
+                plainBytes: "a0447f5abebf8623db81b600699ce8373353442908fefe8c63f5e29e22ba1057f759635505ed0ac059887def2d31f6996128d4fbe2df6534429744d7f6496768".HexToByteArray(),
+                cipherBytes: "b3a791b128f003bc28cd17bbb5c68990faec73f88c10b664f1349b045f3fba24c5f51bbb10259c41a72492c2377bb331b6dd34fea25c2eea8adc461bd0c78d6b".HexToByteArray(),
+                feedbackSize: 64
+            );
+        }
+
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        public static void VerifyKnownTransform_CFB64_NoPadding_8()
+        {
+            // NIST CAVS TDESMMT.ZIP TCFB64MMT2.rsp, [DECRYPT] COUNT=8
+            TestTripleDESTransformDirectKey(
+                CipherMode.CFB,
+                PaddingMode.None,
+                key: "5bbc3423bf67e05262d65740708019f15bbc3423bf67e052".HexToByteArray(),
+                iv: "14544ea4813c49d9".HexToByteArray(),
+                plainBytes: "a21f26496f74fd8a93aa5423e2a4fc76facbff015db2f4ef14f08b8c13a29d0561e4e57d04b0b00211f8fba46d025a9c0727c8aebb7d25f27f1606321909ba50e660fa25358c63f9".HexToByteArray(),
+                cipherBytes: "c3acc89b9b6037effc65eacdc23b36c38d0e609566d360eba594e4481108983b4a67a5d9647c776ad5fcc4639116ca95734bd8a3df800fb9a6526a7b29a9fc3cc29079715f44f865".HexToByteArray(),
+                feedbackSize: 64
+            );
+        }
+
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        public static void VerifyKnownTransform_CFB64_NoPadding_9()
+        {
+            // NIST CAVS TDESMMT.ZIP TCFB64MMT2.rsp, [DECRYPT] COUNT=9
+            TestTripleDESTransformDirectKey(
+                CipherMode.CFB,
+                PaddingMode.None,
+                key: "197c738cfb6e0bc2fee57ffb1ca72675197c738cfb6e0bc2".HexToByteArray(),
+                iv: "f1a42447a333caa3".HexToByteArray(),
+                plainBytes: "6f914b6996ee8e7ea625b2fddd7677b4384320be0aba3af81d1210965ac37983f340d5698ddf35d45dfccbf783a50c6eed1a730b5c98675cb6b7645fc8374e10d8b340c44b0eae988c1ef635fab913da".HexToByteArray(),
+                cipherBytes: "8aabb83216e4bd5a3dd20586e598bb8e956dcbf7d09cde17a2cf8b7a788ecb853503ae5981004dfa644300b115f8d1ae0c7f30f25e70e86c4adc51620fd6c71301325c9bdc8dca16588eac08fe6aedfd".HexToByteArray(),
+                feedbackSize: 64
+            );
+        }
+
+        private static byte[] TripleDESEncryptDirectKey(TripleDES tdes, byte[] key, byte[] iv, byte[] plainBytes)
+        {
+            using (MemoryStream output = new MemoryStream())
+            using (CryptoStream cryptoStream = new CryptoStream(output, tdes.CreateEncryptor(key, iv), CryptoStreamMode.Write))
+            {
+                cryptoStream.Write(plainBytes, 0, plainBytes.Length);
+                cryptoStream.FlushFinalBlock();
+
+                return output.ToArray();
+            }
+        }
+
+        private static byte[] TripleDESDecryptDirectKey(TripleDES tdes, byte[] key, byte[] iv, byte[] cipherBytes)
+        {
+            using (MemoryStream output = new MemoryStream())
+            using (CryptoStream cryptoStream = new CryptoStream(output, tdes.CreateDecryptor(key, iv), CryptoStreamMode.Write))
+            {
+                cryptoStream.Write(cipherBytes, 0, cipherBytes.Length);
+                cryptoStream.FlushFinalBlock();
+
+                return output.ToArray();
+            }
+        }
+
+        private static void TestTripleDESTransformDirectKey(
+            CipherMode cipherMode,
+            PaddingMode paddingMode,
+            byte[] key,
+            byte[] iv,
+            byte[] plainBytes,
+            byte[] cipherBytes,
+            int? feedbackSize = default)
+        {
+            byte[] liveEncryptBytes;
+            byte[] liveDecryptBytes;
+
+            using (TripleDES tdes = TripleDESFactory.Create())
+            {
+                tdes.Mode = cipherMode;
+                tdes.Padding = paddingMode;
+
+                if (feedbackSize.HasValue)
+                {
+                    tdes.FeedbackSize = feedbackSize.Value;
+                }
+
+                liveEncryptBytes = TripleDESEncryptDirectKey(tdes, key, iv, plainBytes);
+                liveDecryptBytes = TripleDESDecryptDirectKey(tdes, key, iv, cipherBytes);
+            }
+
+            Assert.Equal(cipherBytes, liveEncryptBytes);
+            Assert.Equal(plainBytes, liveDecryptBytes);
+        }
+
         [Theory]
         [InlineData(192, "e56f72478c7479d169d54c0548b744af5b53efb1cdd26037", "c5629363d957054eba793093b83739bb78711db221a82379")]
         [InlineData(128, "1387b981dbb40f34b915c4ed89fd681a740d3b4869c0b575", "c5629363d957054eba793093b83739bb")]
diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/TripleDES/TripleDESContractTests.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/TripleDES/TripleDESContractTests.cs
new file mode 100644 (file)
index 0000000..91e4479
--- /dev/null
@@ -0,0 +1,121 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Text;
+using Test.Cryptography;
+using Xunit;
+
+namespace System.Security.Cryptography.Encryption.TripleDes.Tests
+{
+    public static class TripleDESContractTests
+    {
+
+        [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsWindows7))]
+        [InlineData(0, true)]
+        [InlineData(1, true)]
+        [InlineData(7, true)]
+        [InlineData(9, true)]
+        [InlineData(-1, true)]
+        [InlineData(int.MaxValue, true)]
+        [InlineData(int.MinValue, true)]
+        [InlineData(8, false)]
+        [InlineData(64, false)]
+        [InlineData(256, true)]
+        [InlineData(128, true)]
+        [InlineData(127, true)]
+        public static void Windows7DoesNotSupportCFB(int feedbackSize, bool discoverableInSetter)
+        {
+            using (TripleDES tdes = TripleDESFactory.Create())
+            {
+                tdes.GenerateKey();
+                tdes.Mode = CipherMode.CFB;
+
+                if (discoverableInSetter)
+                {
+                    // there are some key sizes that are invalid for any of the modes,
+                    // so the exception is thrown in the setter
+                    Assert.ThrowsAny<CryptographicException>(() =>
+                    {
+                        tdes.FeedbackSize = feedbackSize;
+                    });
+                }
+                else
+                {
+                    tdes.FeedbackSize = feedbackSize;
+
+                    // however, for CFB only few sizes are valid. Those should throw in the
+                    // actual AES instantiation.
+
+                    Assert.ThrowsAny<CryptographicException>(() =>
+                    {
+                        return tdes.CreateDecryptor();
+                    });
+                    Assert.ThrowsAny<CryptographicException>(() =>
+                    {
+                        return tdes.CreateEncryptor();
+                    });
+                }
+            }
+        }
+
+        [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        [InlineData(0, true)]
+        [InlineData(1, true)]
+        [InlineData(7, true)]
+        [InlineData(9, true)]
+        [InlineData(-1, true)]
+        [InlineData(int.MaxValue, true)]
+        [InlineData(int.MinValue, true)]
+        [InlineData(256, true)]
+        [InlineData(128, true)]
+        [InlineData(127, true)]
+        public static void InvalidCFBFeedbackSizes(int feedbackSize, bool discoverableInSetter)
+        {
+            using (TripleDES tdes = TripleDESFactory.Create())
+            {
+                tdes.GenerateKey();
+                tdes.Mode = CipherMode.CFB;
+
+                if (discoverableInSetter)
+                {
+                    // there are some key sizes that are invalid for any of the modes,
+                    // so the exception is thrown in the setter
+                    Assert.Throws<CryptographicException>(() =>
+                    {
+                        tdes.FeedbackSize = feedbackSize;
+                    });
+                }
+                else
+                {
+                    tdes.FeedbackSize = feedbackSize;
+
+                    // however, for CFB only few sizes are valid. Those should throw in the
+                    // actual AES instantiation.
+
+                    Assert.Throws<CryptographicException>(() => tdes.CreateDecryptor());
+                    Assert.Throws<CryptographicException>(() => tdes.CreateEncryptor());
+                }
+            }
+        }
+
+        [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
+        [InlineData(8)]
+        [InlineData(64)]
+        public static void ValidCFBFeedbackSizes(int feedbackSize)
+        {
+            using (TripleDES tdes = TripleDESFactory.Create())
+            {
+                tdes.GenerateKey();
+                tdes.Mode = CipherMode.CFB;
+
+                tdes.FeedbackSize = feedbackSize;
+
+                using var decryptor = tdes.CreateDecryptor();
+                using var encryptor = tdes.CreateEncryptor();
+                Assert.NotNull(decryptor);
+                Assert.NotNull(encryptor);
+            }
+        }
+    }
+}
index 278ed6b..3bf5646 100644 (file)
@@ -16,6 +16,8 @@ c_static_assert(PAL_AlgorithmRC2 == kCCAlgorithmRC2);
 
 c_static_assert(PAL_ChainingModeECB == kCCModeECB);
 c_static_assert(PAL_ChainingModeCBC == kCCModeCBC);
+c_static_assert(PAL_ChainingModeCFB == kCCModeCFB);
+c_static_assert(PAL_ChainingModeCFB8 == kCCModeCFB8);
 
 c_static_assert(PAL_PaddingModeNone == ccNoPadding);
 c_static_assert(PAL_PaddingModePkcs7 == ccPKCS7Padding);
@@ -55,7 +57,7 @@ int32_t AppleCryptoNative_CryptorCreate(PAL_SymmetricOperation operation,
     assert(operation == PAL_OperationEncrypt || operation == PAL_OperationDecrypt);
     assert(algorithm == PAL_AlgorithmAES || algorithm == PAL_AlgorithmDES || algorithm == PAL_Algorithm3DES ||
            algorithm == PAL_AlgorithmRC2);
-    assert(chainingMode == PAL_ChainingModeECB || chainingMode == PAL_ChainingModeCBC);
+    assert(chainingMode == PAL_ChainingModeECB || chainingMode == PAL_ChainingModeCBC || chainingMode == PAL_ChainingModeCFB || chainingMode == PAL_ChainingModeCFB8);
     assert(paddingMode == PAL_PaddingModeNone || paddingMode == PAL_PaddingModePkcs7);
     assert(options == 0);
 
index 018e86d..0238635 100644 (file)
@@ -29,6 +29,8 @@ enum
 {
     PAL_ChainingModeECB = 1,
     PAL_ChainingModeCBC = 2,
+    PAL_ChainingModeCFB = 3,
+    PAL_ChainingModeCFB8 = 10,
 };
 typedef uint32_t PAL_ChainingMode;
 
index 825c884..6533de0 100644 (file)
@@ -311,14 +311,20 @@ void SSL_get0_alpn_selected(const SSL* ssl, const unsigned char** protocol, unsi
     REQUIRED_FUNCTION(ERR_reason_error_string) \
     REQUIRED_FUNCTION(EVP_aes_128_cbc) \
     REQUIRED_FUNCTION(EVP_aes_128_ccm) \
+    REQUIRED_FUNCTION(EVP_aes_128_cfb128) \
+    REQUIRED_FUNCTION(EVP_aes_128_cfb8) \
     REQUIRED_FUNCTION(EVP_aes_128_ecb) \
     REQUIRED_FUNCTION(EVP_aes_128_gcm) \
     REQUIRED_FUNCTION(EVP_aes_192_cbc) \
     REQUIRED_FUNCTION(EVP_aes_192_ccm) \
+    REQUIRED_FUNCTION(EVP_aes_192_cfb128) \
+    REQUIRED_FUNCTION(EVP_aes_192_cfb8) \
     REQUIRED_FUNCTION(EVP_aes_192_ecb) \
     REQUIRED_FUNCTION(EVP_aes_192_gcm) \
     REQUIRED_FUNCTION(EVP_aes_256_cbc) \
     REQUIRED_FUNCTION(EVP_aes_256_ccm) \
+    REQUIRED_FUNCTION(EVP_aes_256_cfb128) \
+    REQUIRED_FUNCTION(EVP_aes_256_cfb8) \
     REQUIRED_FUNCTION(EVP_aes_256_ecb) \
     REQUIRED_FUNCTION(EVP_aes_256_gcm) \
     LEGACY_FUNCTION(EVP_CIPHER_CTX_cleanup) \
@@ -333,9 +339,12 @@ void SSL_get0_alpn_selected(const SSL* ssl, const unsigned char** protocol, unsi
     REQUIRED_FUNCTION(EVP_CipherInit_ex) \
     REQUIRED_FUNCTION(EVP_CipherUpdate) \
     REQUIRED_FUNCTION(EVP_des_cbc) \
+    REQUIRED_FUNCTION(EVP_des_cfb8) \
     REQUIRED_FUNCTION(EVP_des_ecb) \
     REQUIRED_FUNCTION(EVP_des_ede3) \
     REQUIRED_FUNCTION(EVP_des_ede3_cbc) \
+    REQUIRED_FUNCTION(EVP_des_ede3_cfb8) \
+    REQUIRED_FUNCTION(EVP_des_ede3_cfb64) \
     REQUIRED_FUNCTION(EVP_DigestFinal_ex) \
     REQUIRED_FUNCTION(EVP_DigestInit_ex) \
     REQUIRED_FUNCTION(EVP_DigestUpdate) \
@@ -704,14 +713,20 @@ FOR_ALL_OPENSSL_FUNCTIONS
 #define ERR_put_error ERR_put_error_ptr
 #define ERR_reason_error_string ERR_reason_error_string_ptr
 #define EVP_aes_128_cbc EVP_aes_128_cbc_ptr
+#define EVP_aes_128_cfb8 EVP_aes_128_cfb8_ptr
+#define EVP_aes_128_cfb128 EVP_aes_128_cfb128_ptr
 #define EVP_aes_128_ecb EVP_aes_128_ecb_ptr
 #define EVP_aes_128_gcm EVP_aes_128_gcm_ptr
 #define EVP_aes_128_ccm EVP_aes_128_ccm_ptr
 #define EVP_aes_192_cbc EVP_aes_192_cbc_ptr
+#define EVP_aes_192_cfb8 EVP_aes_192_cfb8_ptr
+#define EVP_aes_192_cfb128 EVP_aes_192_cfb128_ptr
 #define EVP_aes_192_ecb EVP_aes_192_ecb_ptr
 #define EVP_aes_192_gcm EVP_aes_192_gcm_ptr
 #define EVP_aes_192_ccm EVP_aes_192_ccm_ptr
 #define EVP_aes_256_cbc EVP_aes_256_cbc_ptr
+#define EVP_aes_256_cfb8 EVP_aes_256_cfb8_ptr
+#define EVP_aes_256_cfb128 EVP_aes_256_cfb128_ptr
 #define EVP_aes_256_ecb EVP_aes_256_ecb_ptr
 #define EVP_aes_256_gcm EVP_aes_256_gcm_ptr
 #define EVP_aes_256_ccm EVP_aes_256_ccm_ptr
@@ -727,8 +742,11 @@ FOR_ALL_OPENSSL_FUNCTIONS
 #define EVP_CipherInit_ex EVP_CipherInit_ex_ptr
 #define EVP_CipherUpdate EVP_CipherUpdate_ptr
 #define EVP_des_cbc EVP_des_cbc_ptr
+#define EVP_des_cfb8 EVP_des_cfb8_ptr
 #define EVP_des_ecb EVP_des_ecb_ptr
 #define EVP_des_ede3 EVP_des_ede3_ptr
+#define EVP_des_ede3_cfb8 EVP_des_ede3_cfb8_ptr
+#define EVP_des_ede3_cfb64 EVP_des_ede3_cfb64_ptr
 #define EVP_des_ede3_cbc EVP_des_ede3_cbc_ptr
 #define EVP_DigestFinal_ex EVP_DigestFinal_ex_ptr
 #define EVP_DigestInit_ex EVP_DigestInit_ex_ptr
index fa9b56c..40df23d 100644 (file)
@@ -195,6 +195,16 @@ const EVP_CIPHER* CryptoNative_EvpAes128Gcm()
     return EVP_aes_128_gcm();
 }
 
+const EVP_CIPHER* CryptoNative_EvpAes128Cfb128()
+{
+    return EVP_aes_128_cfb128();
+}
+
+const EVP_CIPHER* CryptoNative_EvpAes128Cfb8()
+{
+    return EVP_aes_128_cfb8();
+}
+
 const EVP_CIPHER* CryptoNative_EvpAes128Ccm()
 {
     return EVP_aes_128_ccm();
@@ -205,6 +215,16 @@ const EVP_CIPHER* CryptoNative_EvpAes192Ecb()
     return EVP_aes_192_ecb();
 }
 
+const EVP_CIPHER* CryptoNative_EvpAes192Cfb128()
+{
+    return EVP_aes_192_cfb128();
+}
+
+const EVP_CIPHER* CryptoNative_EvpAes192Cfb8()
+{
+    return EVP_aes_192_cfb8();
+}
+
 const EVP_CIPHER* CryptoNative_EvpAes192Cbc()
 {
     return EVP_aes_192_cbc();
@@ -225,6 +245,16 @@ const EVP_CIPHER* CryptoNative_EvpAes256Ecb()
     return EVP_aes_256_ecb();
 }
 
+const EVP_CIPHER* CryptoNative_EvpAes256Cfb128()
+{
+    return EVP_aes_256_cfb128();
+}
+
+const EVP_CIPHER* CryptoNative_EvpAes256Cfb8()
+{
+    return EVP_aes_256_cfb8();
+}
+
 const EVP_CIPHER* CryptoNative_EvpAes256Cbc()
 {
     return EVP_aes_256_cbc();
@@ -245,6 +275,11 @@ const EVP_CIPHER* CryptoNative_EvpDesEcb()
     return EVP_des_ecb();
 }
 
+const EVP_CIPHER* CryptoNative_EvpDesCfb8()
+{
+    return EVP_des_cfb8();
+}
+
 const EVP_CIPHER* CryptoNative_EvpDesCbc()
 {
     return EVP_des_cbc();
@@ -255,6 +290,16 @@ const EVP_CIPHER* CryptoNative_EvpDes3Ecb()
     return EVP_des_ede3();
 }
 
+const EVP_CIPHER* CryptoNative_EvpDes3Cfb8()
+{
+    return EVP_des_ede3_cfb8();
+}
+
+const EVP_CIPHER* CryptoNative_EvpDes3Cfb64()
+{
+    return EVP_des_ede3_cfb64();
+}
+
 const EVP_CIPHER* CryptoNative_EvpDes3Cbc()
 {
     return EVP_des_ede3_cbc();
index 4441b06..a48c0dd 100644 (file)
@@ -112,6 +112,22 @@ PALEXPORT const EVP_CIPHER* CryptoNative_EvpAes128Cbc(void);
 
 /*
 Function:
+EvpAes128Cfb8
+
+Direct shim to EVP_aes_128_cfb8.
+*/
+PALEXPORT const EVP_CIPHER* CryptoNative_EvpAes128Cfb8(void);
+
+/*
+Function:
+EvpAes128Cfb128
+
+Direct shim to EVP_aes_128_cfb128.
+*/
+PALEXPORT const EVP_CIPHER* CryptoNative_EvpAes128Cfb128(void);
+
+/*
+Function:
 EvpAes128Gcm
 
 Direct shim to EVP_aes_128_gcm.
@@ -144,6 +160,22 @@ PALEXPORT const EVP_CIPHER* CryptoNative_EvpAes192Cbc(void);
 
 /*
 Function:
+EvpAes192Cfb8
+
+Direct shim to EVP_aes_192_cfb8.
+*/
+PALEXPORT const EVP_CIPHER* CryptoNative_EvpAes192Cfb8(void);
+
+/*
+Function:
+EvpAes192Cfb128
+
+Direct shim to EVP_aes_192_cfb128.
+*/
+PALEXPORT const EVP_CIPHER* CryptoNative_EvpAes192Cfb128(void);
+
+/*
+Function:
 EvpAes192Gcm
 
 Direct shim to EVP_aes_192_gcm.
@@ -176,6 +208,22 @@ PALEXPORT const EVP_CIPHER* CryptoNative_EvpAes256Cbc(void);
 
 /*
 Function:
+EvpAes256Cfb8
+
+Direct shim to EVP_aes_256_cfb8.
+*/
+PALEXPORT const EVP_CIPHER* CryptoNative_EvpAes256Cfb8(void);
+
+/*
+Function:
+EvpAes256Cfb128
+
+Direct shim to EVP_aes_256_cfb128.
+*/
+PALEXPORT const EVP_CIPHER* CryptoNative_EvpAes256Cfb128(void);
+
+/*
+Function:
 EvpAes256Gcm
 
 Direct shim to EVP_aes_256_gcm.
@@ -208,6 +256,22 @@ PALEXPORT const EVP_CIPHER* CryptoNative_EvpDes3Cbc(void);
 
 /*
 Function:
+EvpDes3Cfb8
+
+Direct shim to EVP_des_ede3_cfb8.
+*/
+PALEXPORT const EVP_CIPHER* CryptoNative_EvpDes3Cfb8(void);
+
+/*
+Function:
+EvpDes3Cfb64
+
+Direct shim to EVP_des_ede3_cfb64.
+*/
+PALEXPORT const EVP_CIPHER* CryptoNative_EvpDes3Cfb64(void);
+
+/*
+Function:
 EvpDesEcb
 
 Direct shim to EVP_des_ecb.
@@ -216,6 +280,14 @@ PALEXPORT const EVP_CIPHER* CryptoNative_EvpDesEcb(void);
 
 /*
 Function:
+EvpDesCfb8
+
+Direct shim to EVP_des_cfb8.
+*/
+PALEXPORT const EVP_CIPHER* CryptoNative_EvpDesCfb8(void);
+
+/*
+Function:
 EvpDesCbc
 
 Direct shim to EVP_des_ede_cbc.
index 6df9fa5..042a5f6 100644 (file)
@@ -13,6 +13,8 @@ namespace Internal.Cryptography
             byte[] key,
             byte[]? iv,
             int blockSize,
+            int paddingSize,
+            int feedbackSizeInBytes,
             bool encrypting)
         {
             BasicSymmetricCipher cipher = new AppleCCCryptor(
@@ -21,7 +23,9 @@ namespace Internal.Cryptography
                 blockSize,
                 key,
                 iv,
-                encrypting);
+                encrypting,
+                feedbackSizeInBytes,
+                paddingSize);
 
             return UniversalCryptoTransform.Create(paddingMode, cipher, encrypting);
         }
index 0427917..1e4b413 100644 (file)
@@ -14,43 +14,50 @@ namespace Internal.Cryptography
             byte[] key,
             byte[]? iv,
             int blockSize,
+            int paddingSize,
+            int feedback,
             bool encrypting)
         {
             // The algorithm pointer is a static pointer, so not having any cleanup code is correct.
-            IntPtr algorithm = GetAlgorithm(key.Length * 8, cipherMode);
+            IntPtr algorithm = GetAlgorithm(key.Length * 8, feedback * 8, cipherMode);
 
-            BasicSymmetricCipher cipher = new OpenSslCipher(algorithm, cipherMode, blockSize, key, 0, iv, encrypting);
+            BasicSymmetricCipher cipher = new OpenSslCipher(algorithm, cipherMode, blockSize, paddingSize, key, 0, iv, encrypting);
             return UniversalCryptoTransform.Create(paddingMode, cipher, encrypting);
         }
 
-        private static readonly Tuple<int, CipherMode, Func<IntPtr>>[] s_algorithmInitializers =
+        private static readonly Tuple<int, int, CipherMode, Func<IntPtr>>[] s_algorithmInitializers =
         {
             // Neither OpenSSL nor Cng Aes support CTS mode.
-            // Cng Aes doesn't seem to support CFB mode, and that would
-            // require passing in the feedback size.  Since Windows doesn't support it,
-            // we can skip it here, too.
-            Tuple.Create(128, CipherMode.CBC, (Func<IntPtr>)Interop.Crypto.EvpAes128Cbc),
-            Tuple.Create(128, CipherMode.ECB, (Func<IntPtr>)Interop.Crypto.EvpAes128Ecb),
+            // second parameter is feedback size (required only for CFB).
 
-            Tuple.Create(192, CipherMode.CBC, (Func<IntPtr>)Interop.Crypto.EvpAes192Cbc),
-            Tuple.Create(192, CipherMode.ECB, (Func<IntPtr>)Interop.Crypto.EvpAes192Ecb),
+            Tuple.Create(128, 0, CipherMode.CBC, (Func<IntPtr>)Interop.Crypto.EvpAes128Cbc),
+            Tuple.Create(128, 0, CipherMode.ECB, (Func<IntPtr>)Interop.Crypto.EvpAes128Ecb),
+            Tuple.Create(128, 8, CipherMode.CFB, (Func<IntPtr>)Interop.Crypto.EvpAes128Cfb8),
+            Tuple.Create(128, 128, CipherMode.CFB, (Func<IntPtr>)Interop.Crypto.EvpAes128Cfb128),
 
-            Tuple.Create(256, CipherMode.CBC, (Func<IntPtr>)Interop.Crypto.EvpAes256Cbc),
-            Tuple.Create(256, CipherMode.ECB, (Func<IntPtr>)Interop.Crypto.EvpAes256Ecb),
+            Tuple.Create(192, 0, CipherMode.CBC, (Func<IntPtr>)Interop.Crypto.EvpAes192Cbc),
+            Tuple.Create(192, 0, CipherMode.ECB, (Func<IntPtr>)Interop.Crypto.EvpAes192Ecb),
+            Tuple.Create(192, 8, CipherMode.CFB, (Func<IntPtr>)Interop.Crypto.EvpAes192Cfb8),
+            Tuple.Create(192, 128, CipherMode.CFB, (Func<IntPtr>)Interop.Crypto.EvpAes192Cfb128),
+
+            Tuple.Create(256, 0, CipherMode.CBC, (Func<IntPtr>)Interop.Crypto.EvpAes256Cbc),
+            Tuple.Create(256, 0, CipherMode.ECB, (Func<IntPtr>)Interop.Crypto.EvpAes256Ecb),
+            Tuple.Create(256, 8, CipherMode.CFB, (Func<IntPtr>)Interop.Crypto.EvpAes256Cfb8),
+            Tuple.Create(256, 128, CipherMode.CFB, (Func<IntPtr>)Interop.Crypto.EvpAes256Cfb128),
         };
 
-        private static IntPtr GetAlgorithm(int keySize, CipherMode cipherMode)
+        private static IntPtr GetAlgorithm(int keySize, int feedback, CipherMode cipherMode)
         {
             bool foundKeysize = false;
 
-            foreach (var triplet in s_algorithmInitializers)
+            foreach (var tuple in s_algorithmInitializers)
             {
-                if (triplet.Item1 == keySize && triplet.Item2 == cipherMode)
+                if (tuple.Item1 == keySize && (tuple.Item2 == 0 || tuple.Item2 == feedback) && tuple.Item3 == cipherMode)
                 {
-                    return triplet.Item3();
+                    return tuple.Item4();
                 }
 
-                if (triplet.Item1 == keySize)
+                if (tuple.Item1 == keySize)
                 {
                     foundKeysize = true;
                 }
index 550d5e0..f08ecbb 100644 (file)
@@ -14,11 +14,13 @@ namespace Internal.Cryptography
             byte[] key,
             byte[]? iv,
             int blockSize,
+            int paddingSize,
+            int feedbackSize,
             bool encrypting)
         {
-            SafeAlgorithmHandle algorithm = AesBCryptModes.GetSharedHandle(cipherMode);
+            SafeAlgorithmHandle algorithm = AesBCryptModes.GetSharedHandle(cipherMode, feedbackSize);
 
-            BasicSymmetricCipher cipher = new BasicSymmetricCipherBCrypt(algorithm, cipherMode, blockSize, key, false, iv, encrypting);
+            BasicSymmetricCipher cipher = new BasicSymmetricCipherBCrypt(algorithm, cipherMode, blockSize, paddingSize, key, false, iv, encrypting);
             return UniversalCryptoTransform.Create(paddingMode, cipher, encrypting);
         }
     }
index cb1752d..67303b6 100644 (file)
@@ -65,7 +65,21 @@ namespace Internal.Cryptography
                     throw new ArgumentException(SR.Cryptography_InvalidIVSize, nameof(rgbIV));
             }
 
-            return CreateTransformCore(Mode, Padding, rgbKey, rgbIV, BlockSize / BitsPerByte, encrypting);
+            if (Mode == CipherMode.CFB)
+            {
+                ValidateCFBFeedbackSize(FeedbackSize);
+            }
+
+            return CreateTransformCore(Mode, Padding, rgbKey, rgbIV, BlockSize / BitsPerByte, this.GetPaddingSize(), FeedbackSize / BitsPerByte, encrypting);
+        }
+
+        private static void ValidateCFBFeedbackSize(int feedback)
+        {
+            // only 8bits/128bits feedback would be valid.
+            if (feedback != 8 && feedback != 128)
+            {
+                throw new CryptographicException(string.Format(SR.Cryptography_CipherModeFeedbackNotSupported, feedback, CipherMode.CFB));
+            }
         }
 
         private const int BitsPerByte = 8;
index a584e1d..0400c5c 100644 (file)
@@ -5,6 +5,7 @@ using System;
 using System.Diagnostics;
 using System.Diagnostics.CodeAnalysis;
 using System.Security.Cryptography;
+using Internal.Cryptography;
 
 namespace Internal.Cryptography
 {
@@ -13,18 +14,36 @@ namespace Internal.Cryptography
         private readonly bool _encrypting;
         private SafeAppleCryptorHandle _cryptor;
 
+        // Reset operation is not supported on stream cipher
+        private readonly bool _supportsReset;
+
+        private Interop.AppleCrypto.PAL_SymmetricAlgorithm _algorithm;
+        private CipherMode _cipherMode;
+        private byte[] _key;
+        private int _feedbackSizeInBytes;
+
         public AppleCCCryptor(
             Interop.AppleCrypto.PAL_SymmetricAlgorithm algorithm,
             CipherMode cipherMode,
             int blockSizeInBytes,
             byte[] key,
             byte[]? iv,
-            bool encrypting)
-            : base(cipherMode.GetCipherIv(iv), blockSizeInBytes)
+            bool encrypting,
+            int feedbackSizeInBytes,
+            int paddingSizeInBytes)
+            : base(cipherMode.GetCipherIv(iv), blockSizeInBytes, paddingSizeInBytes)
         {
             _encrypting = encrypting;
 
-            OpenCryptor(algorithm, cipherMode, key);
+            // CFB is streaming cipher, calling CCCryptorReset is not implemented (and is effectively noop)
+            _supportsReset = cipherMode != CipherMode.CFB;
+
+            _algorithm = algorithm;
+            _cipherMode = cipherMode;
+            _key = key;
+            _feedbackSizeInBytes = feedbackSizeInBytes;
+
+            OpenCryptor();
         }
 
         protected override void Dispose(bool disposing)
@@ -41,14 +60,14 @@ namespace Internal.Cryptography
         public override int Transform(ReadOnlySpan<byte> input, Span<byte> output)
         {
             Debug.Assert(input.Length > 0);
-            Debug.Assert((input.Length % BlockSizeInBytes) == 0);
+            Debug.Assert((input.Length % PaddingSizeInBytes) == 0);
 
             return CipherUpdate(input, output);
         }
 
         public override int TransformFinal(ReadOnlySpan<byte> input, Span<byte> output)
         {
-            Debug.Assert((input.Length % BlockSizeInBytes) == 0);
+            Debug.Assert((input.Length % PaddingSizeInBytes) == 0);
             Debug.Assert(input.Length <= output.Length);
 
             int written = ProcessFinalBlock(input, output);
@@ -119,28 +138,25 @@ namespace Internal.Cryptography
         }
 
         [MemberNotNull(nameof(_cryptor))]
-        private unsafe void OpenCryptor(
-            Interop.AppleCrypto.PAL_SymmetricAlgorithm algorithm,
-            CipherMode cipherMode,
-            byte[] key)
+        private unsafe void OpenCryptor()
         {
             int ret;
             int ccStatus;
 
             byte[]? iv = IV;
 
-            fixed (byte* pbKey = key)
+            fixed (byte* pbKey = _key)
             fixed (byte* pbIv = iv)
             {
                 ret = Interop.AppleCrypto.CryptorCreate(
                     _encrypting
                         ? Interop.AppleCrypto.PAL_SymmetricOperation.Encrypt
                         : Interop.AppleCrypto.PAL_SymmetricOperation.Decrypt,
-                    algorithm,
-                    GetPalChainMode(cipherMode),
+                    _algorithm,
+                    GetPalChainMode(_algorithm, _cipherMode, _feedbackSizeInBytes),
                     Interop.AppleCrypto.PAL_PaddingMode.None,
                     pbKey,
-                    key.Length,
+                    _key.Length,
                     pbIv,
                     Interop.AppleCrypto.PAL_SymmetricOptions.None,
                     out _cryptor,
@@ -150,7 +166,7 @@ namespace Internal.Cryptography
             ProcessInteropError(ret, ccStatus);
         }
 
-        private Interop.AppleCrypto.PAL_ChainingMode GetPalChainMode(CipherMode cipherMode)
+        private Interop.AppleCrypto.PAL_ChainingMode GetPalChainMode(Interop.AppleCrypto.PAL_SymmetricAlgorithm algorithm, CipherMode cipherMode, int feedbackSizeInBytes)
         {
             switch (cipherMode)
             {
@@ -158,6 +174,17 @@ namespace Internal.Cryptography
                     return Interop.AppleCrypto.PAL_ChainingMode.CBC;
                 case CipherMode.ECB:
                     return Interop.AppleCrypto.PAL_ChainingMode.ECB;
+                case CipherMode.CFB:
+                    if (feedbackSizeInBytes == 1)
+                    {
+                        return Interop.AppleCrypto.PAL_ChainingMode.CFB8;
+                    }
+
+                    Debug.Assert(
+                        (algorithm == Interop.AppleCrypto.PAL_SymmetricAlgorithm.AES && feedbackSizeInBytes == 16) ||
+                        (algorithm == Interop.AppleCrypto.PAL_SymmetricAlgorithm.TripleDES && feedbackSizeInBytes == 8));
+
+                    return Interop.AppleCrypto.PAL_ChainingMode.CFB;
                 default:
                     throw new PlatformNotSupportedException(SR.Format(SR.Cryptography_CipherModeNotSupported, cipherMode));
             }
@@ -165,17 +192,27 @@ namespace Internal.Cryptography
 
         private unsafe void Reset()
         {
-            int ret;
-            int ccStatus;
-
-            byte[]? iv = IV;
-
-            fixed (byte* pbIv = iv)
+            if (!_supportsReset)
             {
-                ret = Interop.AppleCrypto.CryptorReset(_cryptor, pbIv, out ccStatus);
+                // when CryptorReset is not supported,
+                // dispose & reopen
+                _cryptor?.Dispose();
+                OpenCryptor();
             }
+            else
+            {
+                int ret;
+                int ccStatus;
 
-            ProcessInteropError(ret, ccStatus);
+                byte[]? iv = IV;
+
+                fixed (byte* pbIv = iv)
+                {
+                    ret = Interop.AppleCrypto.CryptorReset(_cryptor, pbIv, out ccStatus);
+                }
+
+                ProcessInteropError(ret, ccStatus);
+            }
         }
 
         private static void ProcessInteropError(int functionReturnCode, int ccStatus)
index 0ffda38..fc88b0a 100644 (file)
@@ -13,6 +13,8 @@ namespace Internal.Cryptography
             byte[] key,
             byte[]? iv,
             int blockSize,
+            int feedbackSizeInBytes,
+            int paddingSize,
             bool encrypting)
         {
             BasicSymmetricCipher cipher = new AppleCCCryptor(
@@ -21,7 +23,9 @@ namespace Internal.Cryptography
                 blockSize,
                 key,
                 iv,
-                encrypting);
+                encrypting,
+                feedbackSizeInBytes,
+                paddingSize);
 
             return UniversalCryptoTransform.Create(paddingMode, cipher, encrypting);
         }
index cc4ecd5..7bf051e 100644 (file)
@@ -2,6 +2,7 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 
 using System;
+using System.Diagnostics;
 using System.Security.Cryptography;
 
 namespace Internal.Cryptography
@@ -14,10 +15,13 @@ namespace Internal.Cryptography
             byte[] key,
             byte[]? iv,
             int blockSize,
+            int feedbackSize,
+            int paddingSize,
             bool encrypting)
         {
             // The algorithm pointer is a static pointer, so not having any cleanup code is correct.
-            IntPtr algorithm;
+            IntPtr algorithm = IntPtr.Zero;
+
             switch (cipherMode)
             {
                 case CipherMode.CBC:
@@ -26,11 +30,17 @@ namespace Internal.Cryptography
                 case CipherMode.ECB:
                     algorithm = Interop.Crypto.EvpDesEcb();
                     break;
+                case CipherMode.CFB:
+
+                    Debug.Assert(feedbackSize == 1, "TripleDES with CFB should have FeedbackSize set to 1");
+                    algorithm = Interop.Crypto.EvpDesCfb8();
+
+                    break;
                 default:
                     throw new NotSupportedException();
             }
 
-            BasicSymmetricCipher cipher = new OpenSslCipher(algorithm, cipherMode, blockSize, key, 0, iv, encrypting);
+            BasicSymmetricCipher cipher = new OpenSslCipher(algorithm, cipherMode, blockSize, paddingSize, key, 0, iv, encrypting);
             return UniversalCryptoTransform.Create(paddingMode, cipher, encrypting);
         }
     }
index 75a9900..a6d53c6 100644 (file)
@@ -14,11 +14,13 @@ namespace Internal.Cryptography
             byte[] key,
             byte[]? iv,
             int blockSize,
+            int feedbackSize,
+            int paddingSize,
             bool encrypting)
         {
-            SafeAlgorithmHandle algorithm = DesBCryptModes.GetSharedHandle(cipherMode);
+            SafeAlgorithmHandle algorithm = DesBCryptModes.GetSharedHandle(cipherMode, feedbackSize);
 
-            BasicSymmetricCipher cipher = new BasicSymmetricCipherBCrypt(algorithm, cipherMode, blockSize, key, false, iv, encrypting);
+            BasicSymmetricCipher cipher = new BasicSymmetricCipherBCrypt(algorithm, cipherMode, blockSize, paddingSize, key, false, iv, encrypting);
             return UniversalCryptoTransform.Create(paddingMode, cipher, encrypting);
         }
     }
index f0557c0..b6ddbc7 100644 (file)
@@ -73,7 +73,21 @@ namespace Internal.Cryptography
                     throw new ArgumentException(SR.Cryptography_InvalidIVSize, nameof(rgbIV));
             }
 
-            return CreateTransformCore(Mode, Padding, rgbKey, rgbIV, BlockSize / BitsPerByte, encrypting);
+            if (Mode == CipherMode.CFB)
+            {
+                ValidateCFBFeedbackSize(FeedbackSize);
+            }
+
+            return CreateTransformCore(Mode, Padding, rgbKey, rgbIV, BlockSize / BitsPerByte, FeedbackSize / BitsPerByte, this.GetPaddingSize(), encrypting);
+        }
+
+        private static void ValidateCFBFeedbackSize(int feedback)
+        {
+            // only 8bits feedback is available on all platforms
+            if (feedback != 8)
+            {
+                throw new CryptographicException(string.Format(SR.Cryptography_CipherModeFeedbackNotSupported, feedback, CipherMode.CFB));
+            }
         }
     }
 }
index 49dc42d..6adbec3 100644 (file)
@@ -16,8 +16,8 @@ namespace Internal.Cryptography
         private readonly bool _encrypting;
         private SafeEvpCipherCtxHandle _ctx;
 
-        public OpenSslCipher(IntPtr algorithm, CipherMode cipherMode, int blockSizeInBytes, byte[] key, int effectiveKeyLength, byte[]? iv, bool encrypting)
-            : base(cipherMode.GetCipherIv(iv), blockSizeInBytes)
+        public OpenSslCipher(IntPtr algorithm, CipherMode cipherMode, int blockSizeInBytes, int paddingSizeInBytes, byte[] key, int effectiveKeyLength, byte[]? iv, bool encrypting)
+            : base(cipherMode.GetCipherIv(iv), blockSizeInBytes, paddingSizeInBytes)
         {
             Debug.Assert(algorithm != IntPtr.Zero);
 
@@ -43,7 +43,7 @@ namespace Internal.Cryptography
         public override unsafe int Transform(ReadOnlySpan<byte> input, Span<byte> output)
         {
             Debug.Assert(input.Length > 0);
-            Debug.Assert((input.Length % BlockSizeInBytes) == 0);
+            Debug.Assert((input.Length % PaddingSizeInBytes) == 0);
 
             // OpenSSL 1.1 does not allow partial overlap.
             if (input.Overlaps(output, out int overlapOffset) && overlapOffset != 0)
@@ -69,7 +69,7 @@ namespace Internal.Cryptography
 
         public override int TransformFinal(ReadOnlySpan<byte> input, Span<byte> output)
         {
-            Debug.Assert((input.Length % BlockSizeInBytes) == 0);
+            Debug.Assert((input.Length % PaddingSizeInBytes) == 0);
             Debug.Assert(input.Length <= output.Length);
 
             int written = ProcessFinalBlock(input, output);
index 757b885..dad0dce 100644 (file)
@@ -14,6 +14,8 @@ namespace Internal.Cryptography
             int effectiveKeyLength,
             byte[]? iv,
             int blockSize,
+            int feedbackSizeInBytes,
+            int paddingSize,
             bool encrypting)
         {
             BasicSymmetricCipher cipher = new AppleCCCryptor(
@@ -22,7 +24,9 @@ namespace Internal.Cryptography
                 blockSize,
                 key,
                 iv,
-                encrypting);
+                encrypting,
+                feedbackSizeInBytes,
+                paddingSize);
 
             return UniversalCryptoTransform.Create(paddingMode, cipher, encrypting);
         }
index d6a5593..3e2a127 100644 (file)
@@ -16,6 +16,8 @@ namespace Internal.Cryptography
             int effectiveKeyLength,
             byte[]? iv,
             int blockSize,
+            int feedbackSize,
+            int paddingSize,
             bool encrypting)
         {
             // The algorithm pointer is a static pointer, so not having any cleanup code is correct.
@@ -32,7 +34,7 @@ namespace Internal.Cryptography
                     throw new NotSupportedException();
             }
 
-            BasicSymmetricCipher cipher = new OpenSslCipher(algorithm, cipherMode, blockSize, key, effectiveKeyLength, iv, encrypting);
+            BasicSymmetricCipher cipher = new OpenSslCipher(algorithm, cipherMode, blockSize, paddingSize, key, effectiveKeyLength, iv, encrypting);
             return UniversalCryptoTransform.Create(paddingMode, cipher, encrypting);
         }
     }
index 616c1e9..0c6139a 100644 (file)
@@ -16,12 +16,14 @@ namespace Internal.Cryptography
             int effectiveKeyLength,
             byte[]? iv,
             int blockSize,
+            int feedbackSize,
+            int paddingSize,
             bool encrypting)
         {
             using (SafeAlgorithmHandle algorithm = RC2BCryptModes.GetHandle(cipherMode, effectiveKeyLength))
             {
                 // The BasicSymmetricCipherBCrypt ctor will increase algorithm reference count and take ownership.
-                BasicSymmetricCipher cipher = new BasicSymmetricCipherBCrypt(algorithm, cipherMode, blockSize, key, true, iv, encrypting);
+                BasicSymmetricCipher cipher = new BasicSymmetricCipherBCrypt(algorithm, cipherMode, blockSize, paddingSize, key, true, iv, encrypting);
                 return UniversalCryptoTransform.Create(paddingMode, cipher, encrypting);
             }
         }
index c01d689..876eb24 100644 (file)
@@ -76,8 +76,24 @@ namespace Internal.Cryptography
                     throw new ArgumentException(SR.Cryptography_InvalidIVSize, nameof(rgbIV));
             }
 
+            if (Mode == CipherMode.CFB)
+            {
+                ValidateCFBFeedbackSize(FeedbackSize);
+            }
+
             int effectiveKeySize = EffectiveKeySizeValue == 0 ? (int)keySize : EffectiveKeySize;
-            return CreateTransformCore(Mode, Padding, rgbKey, effectiveKeySize, rgbIV, BlockSize / BitsPerByte, encrypting);
+            return CreateTransformCore(Mode, Padding, rgbKey, effectiveKeySize, rgbIV, BlockSize / BitsPerByte, FeedbackSize / BitsPerByte, GetPaddingSize(), encrypting);
+        }
+
+        private static void ValidateCFBFeedbackSize(int feedback)
+        {
+            // CFB not supported at all
+            throw new CryptographicException(string.Format(SR.Cryptography_CipherModeFeedbackNotSupported, feedback, CipherMode.CFB));
+        }
+
+        private int GetPaddingSize()
+        {
+            return BlockSize / BitsPerByte;
         }
     }
 }
index 2600f09..1284943 100644 (file)
@@ -13,6 +13,8 @@ namespace Internal.Cryptography
             byte[] key,
             byte[]? iv,
             int blockSize,
+            int paddingSize,
+            int feedbackSizeInBytes,
             bool encrypting)
         {
             BasicSymmetricCipher cipher = new AppleCCCryptor(
@@ -21,7 +23,9 @@ namespace Internal.Cryptography
                 blockSize,
                 key,
                 iv,
-                encrypting);
+                encrypting,
+                feedbackSizeInBytes,
+                paddingSize);
 
             return UniversalCryptoTransform.Create(paddingMode, cipher, encrypting);
         }
index 7987cb6..2efa801 100644 (file)
@@ -14,23 +14,31 @@ namespace Internal.Cryptography
             byte[] key,
             byte[]? iv,
             int blockSize,
+            int paddingSize,
+            int feedbackSize,
             bool encrypting)
         {
             // The algorithm pointer is a static pointer, so not having any cleanup code is correct.
             IntPtr algorithm;
-            switch (cipherMode)
+            switch ((cipherMode, feedbackSize))
             {
-                case CipherMode.CBC:
+                case (CipherMode.CBC, _):
                     algorithm = Interop.Crypto.EvpDes3Cbc();
                     break;
-                case CipherMode.ECB:
+                case (CipherMode.ECB, _):
                     algorithm = Interop.Crypto.EvpDes3Ecb();
                     break;
+                case (CipherMode.CFB, 1):
+                    algorithm = Interop.Crypto.EvpDes3Cfb8();
+                    break;
+                case (CipherMode.CFB, 8):
+                    algorithm = Interop.Crypto.EvpDes3Cfb64();
+                    break;
                 default:
                     throw new NotSupportedException();
             }
 
-            BasicSymmetricCipher cipher = new OpenSslCipher(algorithm, cipherMode, blockSize, key, 0, iv, encrypting);
+            BasicSymmetricCipher cipher = new OpenSslCipher(algorithm, cipherMode, blockSize, paddingSize, key, 0, iv, encrypting);
             return UniversalCryptoTransform.Create(paddingMode, cipher, encrypting);
         }
     }
index 1fa355e..331162e 100644 (file)
@@ -14,11 +14,13 @@ namespace Internal.Cryptography
             byte[] key,
             byte[]? iv,
             int blockSize,
+            int paddingSize,
+            int feedbackSize,
             bool encrypting)
         {
-            SafeAlgorithmHandle algorithm = TripleDesBCryptModes.GetSharedHandle(cipherMode);
+            SafeAlgorithmHandle algorithm = TripleDesBCryptModes.GetSharedHandle(cipherMode, feedbackSize);
 
-            BasicSymmetricCipher cipher = new BasicSymmetricCipherBCrypt(algorithm, cipherMode, blockSize, key, false, iv, encrypting);
+            BasicSymmetricCipher cipher = new BasicSymmetricCipherBCrypt(algorithm, cipherMode, blockSize, paddingSize, key, false, iv, encrypting);
             return UniversalCryptoTransform.Create(paddingMode, cipher, encrypting);
         }
     }
index 38a3710..f5a81ee 100644 (file)
@@ -73,7 +73,21 @@ namespace Internal.Cryptography
                 rgbKey = newkey;
             }
 
-            return CreateTransformCore(Mode, Padding, rgbKey, rgbIV, BlockSize / BitsPerByte, encrypting);
+            if (Mode == CipherMode.CFB)
+            {
+                ValidateCFBFeedbackSize(FeedbackSize);
+            }
+
+            return CreateTransformCore(Mode, Padding, rgbKey, rgbIV, BlockSize / BitsPerByte, this.GetPaddingSize(), FeedbackSize / BitsPerByte, encrypting);
+        }
+
+        private static void ValidateCFBFeedbackSize(int feedback)
+        {
+            // only 8bits/64bits feedback would be valid.
+            if (feedback != 8 && feedback != 64)
+            {
+                throw new CryptographicException(string.Format(SR.Cryptography_CipherModeFeedbackNotSupported, feedback, CipherMode.CFB));
+            }
         }
     }
 }
index 9ca47b5..162f1ab 100644 (file)
   <data name="Cryptography_TransformBeyondEndOfBuffer" xml:space="preserve">
     <value>Attempt to transform beyond end of buffer.</value>
   </data>
+  <data name="Cryptography_CipherModeFeedbackNotSupported" xml:space="preserve">
+    <value>The specified feedback size '{0}' for CipherMode '{1}' is not supported.</value>
+  </data>
   <data name="Cryptography_CipherModeNotSupported" xml:space="preserve">
     <value>The specified CipherMode '{0}' is not supported.</value>
   </data>
index 09a011a..f858454 100644 (file)
@@ -9,7 +9,7 @@ namespace System.Security.Cryptography
 {
     public sealed partial class AesCcm
     {
-        private static readonly SafeAlgorithmHandle s_aesCcm = AesBCryptModes.OpenAesAlgorithm(Cng.BCRYPT_CHAIN_MODE_CCM);
+        private static readonly SafeAlgorithmHandle s_aesCcm = AesBCryptModes.OpenAesAlgorithm(Cng.BCRYPT_CHAIN_MODE_CCM).Value;
         private SafeKeyHandle _keyHandle;
 
         [MemberNotNull(nameof(_keyHandle))]
index a86f713..7798787 100644 (file)
@@ -9,7 +9,7 @@ namespace System.Security.Cryptography
 {
     public partial class AesGcm
     {
-        private static readonly SafeAlgorithmHandle s_aesGcm = AesBCryptModes.OpenAesAlgorithm(Cng.BCRYPT_CHAIN_MODE_GCM);
+        private static readonly SafeAlgorithmHandle s_aesGcm = AesBCryptModes.OpenAesAlgorithm(Cng.BCRYPT_CHAIN_MODE_GCM).Value;
         private SafeKeyHandle _keyHandle;
 
         [MemberNotNull(nameof(_keyHandle))]
index c10e46d..37aaf30 100644 (file)
@@ -61,6 +61,8 @@
              Link="CommonTest\System\Security\Cryptography\AlgorithmImplementations\RSA\TestData.cs" />
     <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\TripleDES\TripleDESCipherTests.cs"
              Link="CommonTest\System\Security\Cryptography\AlgorithmImplementations\TripleDES\TripleDESCipherTests.cs" />
+    <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\TripleDES\TripleDESContractTests.cs"
+             Link="CommonTest\System\Security\Cryptography\AlgorithmImplementations\TripleDES\TripleDESContractTests.cs" />
     <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\TripleDES\TripleDESFactory.cs"
              Link="CommonTest\System\Security\Cryptography\AlgorithmImplementations\TripleDES\TripleDESFactory.cs" />
     <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\TripleDES\TripleDESReusabilityTests.cs"
              Link="CommonTest\System\Security\Cryptography\AsymmetricSignatureFormatter.cs" />
     <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\DES\DESCipherTests.cs"
              Link="CommonTest\System\Security\Cryptography\AlgorithmImplementations\DES\DESCipherTests.cs" />
+    <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\DES\DESContractTests.cs"
+             Link="CommonTest\System\Security\Cryptography\AlgorithmImplementations\DES\DESContractTests.cs" />
     <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\DES\DESFactory.cs"
              Link="CommonTest\System\Security\Cryptography\AlgorithmImplementations\DES\DESFactory.cs" />
     <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\DES\DesTests.cs"
              Link="CommonTest\System\Security\Cryptography\AlgorithmImplementations\ECDsa\ECDsaXml.cs" />
     <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\RC2\RC2CipherTests.cs"
              Link="CommonTest\System\Security\Cryptography\AlgorithmImplementations\RC2\RC2CipherTests.cs" />
+    <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\RC2\RC2ContractTests.cs"
+             Link="CommonTest\System\Security\Cryptography\AlgorithmImplementations\RC2\RC2ContractTests.cs" />
     <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\RC2\RC2Factory.cs"
              Link="CommonTest\System\Security\Cryptography\AlgorithmImplementations\RC2\RC2Factory.cs" />
     <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\RC2\RC2Tests.cs"
index 9b88e7e..2fee494 100644 (file)
@@ -21,8 +21,8 @@ namespace Internal.Cryptography
         //
         // The delegate must instantiate a new CngKey, based on a new underlying NCryptKeyHandle, each time is called.
         //
-        public BasicSymmetricCipherNCrypt(Func<CngKey> cngKeyFactory, CipherMode cipherMode, int blockSizeInBytes, byte[] iv, bool encrypting)
-            : base(iv, blockSizeInBytes)
+        public BasicSymmetricCipherNCrypt(Func<CngKey> cngKeyFactory, CipherMode cipherMode, int blockSizeInBytes, byte[] iv, bool encrypting, int feedbackSizeInBytes, int paddingSize)
+            : base(iv, blockSizeInBytes, paddingSize)
         {
             _encrypting = encrypting;
             _cngKey = cngKeyFactory();
@@ -30,6 +30,7 @@ namespace Internal.Cryptography
             {
                 CipherMode.ECB => s_ECBMode,
                 CipherMode.CBC => s_CBCMode,
+                CipherMode.CFB => s_CFBMode,
                 _ => throw new CryptographicException(SR.Cryptography_InvalidCipherMode),
             };
             _cngKey.SetProperty(chainingModeProperty);
@@ -40,7 +41,7 @@ namespace Internal.Cryptography
         public sealed override int Transform(ReadOnlySpan<byte> input, Span<byte> output)
         {
             Debug.Assert(input.Length > 0);
-            Debug.Assert((input.Length % BlockSizeInBytes) == 0);
+            Debug.Assert((input.Length % PaddingSizeInBytes) == 0);
 
             int numBytesWritten;
             ErrorCode errorCode;
@@ -71,7 +72,7 @@ namespace Internal.Cryptography
 
         public sealed override int TransformFinal(ReadOnlySpan<byte> input, Span<byte> output)
         {
-            Debug.Assert((input.Length % BlockSizeInBytes) == 0);
+            Debug.Assert((input.Length % PaddingSizeInBytes) == 0);
 
             int numBytesWritten = 0;
 
@@ -115,5 +116,7 @@ namespace Internal.Cryptography
             new CngProperty(Interop.NCrypt.NCRYPT_CHAINING_MODE_PROPERTY, Encoding.Unicode.GetBytes(Interop.BCrypt.BCRYPT_CHAIN_MODE_ECB + "\0"), CngPropertyOptions.None);
         private static readonly CngProperty s_CBCMode =
             new CngProperty(Interop.NCrypt.NCRYPT_CHAINING_MODE_PROPERTY, Encoding.Unicode.GetBytes(Interop.BCrypt.BCRYPT_CHAIN_MODE_CBC + "\0"), CngPropertyOptions.None);
+        private static readonly CngProperty s_CFBMode =
+            new CngProperty(Interop.NCrypt.NCRYPT_CHAINING_MODE_PROPERTY, Encoding.Unicode.GetBytes(Interop.BCrypt.BCRYPT_CHAIN_MODE_CFB + "\0"), CngPropertyOptions.None);
     }
 }
index 22ac9bc..6aa654e 100644 (file)
@@ -164,6 +164,7 @@ namespace Internal.Cryptography
                 algorithmModeHandle,
                 _outer.Mode,
                 blockSizeInBytes,
+                _outer.GetPaddingSize(),
                 key,
                 false,
                 iv,
@@ -177,7 +178,8 @@ namespace Internal.Cryptography
             // note: iv is guaranteed to be cloned before this method, so no need to clone it again
 
             int blockSizeInBytes = _outer.BlockSize.BitSizeToByteSize();
-            BasicSymmetricCipher cipher = new BasicSymmetricCipherNCrypt(cngKeyFactory, _outer.Mode, blockSizeInBytes, iv, encrypting);
+            int feedbackSizeInBytes = _outer.FeedbackSize;
+            BasicSymmetricCipher cipher = new BasicSymmetricCipherNCrypt(cngKeyFactory, _outer.Mode, blockSizeInBytes, iv, encrypting, feedbackSizeInBytes, _outer.GetPaddingSize());
             return UniversalCryptoTransform.Create(_outer.Padding, cipher, encrypting);
         }
 
index 105b146..8fdf656 100644 (file)
@@ -16,6 +16,7 @@ namespace Internal.Cryptography
     {
         // SymmetricAlgorithm members used by the core.
         int BlockSize { get; }
+        int FeedbackSize { get; }
         CipherMode Mode { get; }
         PaddingMode Padding { get; }
         byte[] IV { get; set; }
@@ -30,5 +31,6 @@ namespace Internal.Cryptography
         SafeAlgorithmHandle GetEphemeralModeHandle();
         string GetNCryptAlgorithmIdentifier();
         byte[] PreprocessKey(byte[] key);
+        int GetPaddingSize();
     }
 }
index 82f679c..44553ea 100644 (file)
@@ -105,9 +105,21 @@ namespace System.Security.Cryptography
             return false;
         }
 
+        int ICngSymmetricAlgorithm.GetPaddingSize()
+        {
+            return this.GetPaddingSize();
+        }
+
         SafeAlgorithmHandle ICngSymmetricAlgorithm.GetEphemeralModeHandle()
         {
-            return AesBCryptModes.GetSharedHandle(Mode);
+            try
+            {
+                return AesBCryptModes.GetSharedHandle(Mode, FeedbackSize / 8);
+            }
+            catch (NotSupportedException)
+            {
+                throw new CryptographicException(SR.Cryptography_InvalidCipherMode);
+            }
         }
 
         string ICngSymmetricAlgorithm.GetNCryptAlgorithmIdentifier()
index c1dff10..708acee 100644 (file)
@@ -106,9 +106,14 @@ namespace System.Security.Cryptography
             return TripleDES.IsWeakKey(key);
         }
 
+        int ICngSymmetricAlgorithm.GetPaddingSize()
+        {
+            return this.GetPaddingSize();
+        }
+
         SafeAlgorithmHandle ICngSymmetricAlgorithm.GetEphemeralModeHandle()
         {
-            return TripleDesBCryptModes.GetSharedHandle(Mode);
+            return TripleDesBCryptModes.GetSharedHandle(Mode, FeedbackSize / 8);
         }
 
         string ICngSymmetricAlgorithm.GetNCryptAlgorithmIdentifier()
index 75864f8..892df21 100644 (file)
@@ -16,8 +16,8 @@ namespace Internal.Cryptography
         private SafeProvHandle _hProvider;
         private SafeKeyHandle _hKey;
 
-        public BasicSymmetricCipherCsp(int algId, CipherMode cipherMode, int blockSizeInBytes, byte[] key, int effectiveKeyLength, bool addNoSaltFlag, byte[]? iv, bool encrypting)
-            : base(cipherMode.GetCipherIv(iv), blockSizeInBytes)
+        public BasicSymmetricCipherCsp(int algId, CipherMode cipherMode, int blockSizeInBytes, byte[] key, int effectiveKeyLength, bool addNoSaltFlag, byte[]? iv, bool encrypting, int feedbackSize, int paddingSizeInBytes)
+            : base(cipherMode.GetCipherIv(iv), blockSizeInBytes, paddingSizeInBytes)
         {
             _encrypting = encrypting;
 
@@ -25,6 +25,10 @@ namespace Internal.Cryptography
             _hKey = ImportCspBlob(_hProvider, algId, key, addNoSaltFlag);
 
             SetKeyParameter(_hKey, CryptGetKeyParamQueryType.KP_MODE, (int)cipherMode);
+            if (cipherMode == CipherMode.CFB)
+            {
+                SetKeyParameter(_hKey, CryptGetKeyParamQueryType.KP_MODE_BITS, feedbackSize);
+            }
 
             byte[]? currentIv = cipherMode.GetCipherIv(iv);
             if (currentIv != null)
@@ -67,7 +71,7 @@ namespace Internal.Cryptography
 
         public override int TransformFinal(ReadOnlySpan<byte> input, Span<byte> output)
         {
-            Debug.Assert((input.Length % BlockSizeInBytes) == 0);
+            Debug.Assert((input.Length % PaddingSizeInBytes) == 0);
 
             int numBytesWritten = 0;
 
@@ -91,7 +95,7 @@ namespace Internal.Cryptography
         private int Transform(ReadOnlySpan<byte> input, Span<byte> output, bool isFinal)
         {
             Debug.Assert(input.Length > 0);
-            Debug.Assert((input.Length % BlockSizeInBytes) == 0);
+            Debug.Assert((input.Length % PaddingSizeInBytes) == 0);
 
             int numBytesWritten;
             if (_encrypting)
index ea59376..d724958 100644 (file)
@@ -910,7 +910,6 @@ namespace Internal.NativeCrypto
             bool isFinal)
         {
             VerifyValidHandle(hKey);
-            Debug.Assert((input.Length % 8) == 0);
 
             // Figure out how big the encrypted data will be
             int cbEncryptedData = input.Length;
@@ -949,7 +948,6 @@ namespace Internal.NativeCrypto
             Span<byte> output)
         {
             VerifyValidHandle(hKey);
-            Debug.Assert((input.Length % 8) == 0);
 
             byte[] dataToBeDecrypted = new byte[input.Length];
             input.CopyTo(dataToBeDecrypted);
index 9d74b5d..236503f 100644 (file)
@@ -97,7 +97,7 @@ namespace System.Security.Cryptography
                     throw new CryptographicException(SR.Cryptography_InvalidIVSize);
             }
 
-            BasicSymmetricCipher cipher = new BasicSymmetricCipherCsp(CapiHelper.CALG_DES, Mode, BlockSize / BitsPerByte, rgbKey, 0, false, rgbIV, encrypting);
+            BasicSymmetricCipher cipher = new BasicSymmetricCipherCsp(CapiHelper.CALG_DES, Mode, BlockSize / BitsPerByte, rgbKey, 0, false, rgbIV, encrypting, FeedbackSize, this.GetPaddingSize());
             return UniversalCryptoTransform.Create(Padding, cipher, encrypting);
         }
     }
index d4760f3..abdcab7 100644 (file)
@@ -106,7 +106,7 @@ namespace System.Security.Cryptography
             }
 
             int effectiveKeySize = EffectiveKeySizeValue == 0 ? (int)keySize : EffectiveKeySize;
-            BasicSymmetricCipher cipher = new BasicSymmetricCipherCsp(CapiHelper.CALG_RC2, Mode, BlockSize / BitsPerByte, rgbKey, effectiveKeySize, !UseSalt, rgbIV, encrypting);
+            BasicSymmetricCipher cipher = new BasicSymmetricCipherCsp(CapiHelper.CALG_RC2, Mode, BlockSize / BitsPerByte, rgbKey, effectiveKeySize, !UseSalt, rgbIV, encrypting, 0, 0);
             return UniversalCryptoTransform.Create(Padding, cipher, encrypting);
         }
     }
index e56874a..97be086 100644 (file)
@@ -8,6 +8,7 @@ namespace System.Security.Cryptography
     // This enum represents supported cipher chaining modes:
     //  cipher block chaining (CBC),
     //  electronic code book (ECB),
+    //  cipher feedback (CFB),
     //  ciphertext-stealing (CTS).
     // Not all implementations will support all modes.
     public enum CipherMode
@@ -15,7 +16,7 @@ namespace System.Security.Cryptography
         CBC = 1,
         ECB = 2,
         [EditorBrowsable(EditorBrowsableState.Never)] OFB = 3,
-        [EditorBrowsable(EditorBrowsableState.Never)] CFB = 4,
+        CFB = 4,
         CTS = 5
     }
 }
index 8e16664..9ad7b01 100644 (file)
@@ -146,7 +146,7 @@ namespace System.Security.Cryptography
 
             set
             {
-                if (!(value == CipherMode.CBC || value == CipherMode.ECB))
+                if (!(value == CipherMode.CBC || value == CipherMode.ECB || value == CipherMode.CFB))
                     throw new CryptographicException(SR.Cryptography_InvalidCipherMode);
 
                 ModeValue = value;