From a50dcc8d0db2ce4066fd65dc0d7e471edc09b06d Mon Sep 17 00:00:00 2001 From: Levi Broderick Date: Wed, 5 May 2021 14:25:08 -0700 Subject: [PATCH] Add ChaCha20Poly1305 skeleton (#52030) - Also adds AesGcm.IsSupported and AesCcm.IsSupported - The IsSupported APIs will return false on browser rather than throw PNSE The current ChaCha20Poly1305 implementation only works on recent Win10 builds. However, we should be set up for somebody to add support for other OSes in the near future, assuming we can ride on top of other publicly-exposed implementations. --- .../Windows/BCrypt/AEADBCryptHandles.cs | 49 ++ .../Windows/BCrypt/BCryptAeadHandleCache.cs | 52 ++ .../Common/src/Interop/Windows/BCrypt/Cng.cs | 1 + ...System.Security.Cryptography.Algorithms.cs | 14 + .../src/ExcludeApiList.PNSE.Browser.txt | 3 + ...em.Security.Cryptography.Algorithms.csproj | 15 +- ...sAEAD.Windows.cs => AeadCommon.Windows.cs} | 38 +- .../Security/Cryptography/AeadCommon.cs | 29 ++ .../System/Security/Cryptography/AesAEAD.cs | 23 +- .../Security/Cryptography/AesCcm.Android.cs | 4 +- .../Cryptography/AesCcm.NotSupported.cs | 10 + .../Security/Cryptography/AesCcm.Unix.cs | 4 +- .../Security/Cryptography/AesCcm.Windows.cs | 11 +- .../System/Security/Cryptography/AesCcm.cs | 14 +- .../Security/Cryptography/AesGcm.Android.cs | 4 +- .../Cryptography/AesGcm.NotSupported.cs | 10 + .../Security/Cryptography/AesGcm.Unix.cs | 4 +- .../Security/Cryptography/AesGcm.Windows.cs | 11 +- .../System/Security/Cryptography/AesGcm.cs | 14 +- .../ChaCha20Poly1305.NotSupported.cs | 48 ++ .../Cryptography/ChaCha20Poly1305.Windows.cs | 47 ++ .../Security/Cryptography/ChaCha20Poly1305.cs | 105 ++++ .../tests/AesCcmTests.cs | 37 +- .../tests/AesGcmTests.cs | 38 +- .../tests/ChaCha20Poly1305Tests.cs | 454 ++++++++++++++++++ .../tests/CngUtility.cs | 56 +++ .../{AesAEADTests.cs => CommonAEADTests.cs} | 29 +- ...urity.Cryptography.Algorithms.Tests.csproj | 4 +- 28 files changed, 1018 insertions(+), 110 deletions(-) create mode 100644 src/libraries/Common/src/Interop/Windows/BCrypt/AEADBCryptHandles.cs create mode 100644 src/libraries/Common/src/Interop/Windows/BCrypt/BCryptAeadHandleCache.cs rename src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/{AesAEAD.Windows.cs => AeadCommon.Windows.cs} (66%) create mode 100644 src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/AeadCommon.cs create mode 100644 src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/AesCcm.NotSupported.cs create mode 100644 src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/AesGcm.NotSupported.cs create mode 100644 src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ChaCha20Poly1305.NotSupported.cs create mode 100644 src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ChaCha20Poly1305.Windows.cs create mode 100644 src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ChaCha20Poly1305.cs create mode 100644 src/libraries/System.Security.Cryptography.Algorithms/tests/ChaCha20Poly1305Tests.cs create mode 100644 src/libraries/System.Security.Cryptography.Algorithms/tests/CngUtility.cs rename src/libraries/System.Security.Cryptography.Algorithms/tests/{AesAEADTests.cs => CommonAEADTests.cs} (63%) diff --git a/src/libraries/Common/src/Interop/Windows/BCrypt/AEADBCryptHandles.cs b/src/libraries/Common/src/Interop/Windows/BCrypt/AEADBCryptHandles.cs new file mode 100644 index 00000000000..a05c5ae962d --- /dev/null +++ b/src/libraries/Common/src/Interop/Windows/BCrypt/AEADBCryptHandles.cs @@ -0,0 +1,49 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Security.Cryptography; +using System.Threading; +using Internal.NativeCrypto; + +namespace Internal.Cryptography +{ + internal static class AeadBCryptHandles + { + private static SafeAlgorithmHandle? s_aesCcm; + private static SafeAlgorithmHandle? s_aesGcm; + private static SafeAlgorithmHandle? s_chaCha20Poly1305; + + internal static SafeAlgorithmHandle AesCcm => GetCachedAlgorithmHandle(ref s_aesCcm, Cng.BCRYPT_AES_ALGORITHM, Cng.BCRYPT_CHAIN_MODE_CCM); + internal static SafeAlgorithmHandle AesGcm => GetCachedAlgorithmHandle(ref s_aesGcm, Cng.BCRYPT_AES_ALGORITHM, Cng.BCRYPT_CHAIN_MODE_GCM); + + internal static bool IsChaCha20Poly1305Supported { get; } = OperatingSystem.IsWindowsVersionAtLeast(10, 0, 20142); + internal static SafeAlgorithmHandle ChaCha20Poly1305 => GetCachedAlgorithmHandle(ref s_chaCha20Poly1305, Cng.BCRYPT_CHACHA20_POLY1305_ALGORITHM); + + private static SafeAlgorithmHandle GetCachedAlgorithmHandle(ref SafeAlgorithmHandle? handle, string algId, string? chainingMode = null) + { + // Do we already have a handle to this algorithm? + SafeAlgorithmHandle? existingHandle = Volatile.Read(ref handle); + if (existingHandle != null) { return existingHandle; } + + // No cached handle exists; create a new handle. It's ok if multiple threads call + // this concurrently. Only one handle will "win" and the rest will be destroyed. + SafeAlgorithmHandle newHandle = Cng.BCryptOpenAlgorithmProvider(algId, null, Cng.OpenAlgorithmProviderFlags.NONE); + if (chainingMode != null) + { + newHandle.SetCipherMode(chainingMode); + } + + existingHandle = Interlocked.CompareExchange(ref handle, newHandle, null); + if (existingHandle != null) + { + newHandle.Dispose(); + return existingHandle; + } + else + { + return newHandle; + } + } + } +} diff --git a/src/libraries/Common/src/Interop/Windows/BCrypt/BCryptAeadHandleCache.cs b/src/libraries/Common/src/Interop/Windows/BCrypt/BCryptAeadHandleCache.cs new file mode 100644 index 00000000000..60941688533 --- /dev/null +++ b/src/libraries/Common/src/Interop/Windows/BCrypt/BCryptAeadHandleCache.cs @@ -0,0 +1,52 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Security.Cryptography; +using System.Threading; +using Internal.NativeCrypto; + +namespace Internal.Cryptography +{ + internal static class BCryptAeadHandleCache + { + private static SafeAlgorithmHandle? s_aesCcm; + private static SafeAlgorithmHandle? s_aesGcm; + private static SafeAlgorithmHandle? s_chaCha20Poly1305; + + internal static SafeAlgorithmHandle AesCcm => GetCachedAlgorithmHandle(ref s_aesCcm, Cng.BCRYPT_AES_ALGORITHM, Cng.BCRYPT_CHAIN_MODE_CCM); + internal static SafeAlgorithmHandle AesGcm => GetCachedAlgorithmHandle(ref s_aesGcm, Cng.BCRYPT_AES_ALGORITHM, Cng.BCRYPT_CHAIN_MODE_GCM); + + internal static bool IsChaCha20Poly1305Supported { get; } = OperatingSystem.IsWindowsVersionAtLeast(10, 0, 20142); + internal static SafeAlgorithmHandle ChaCha20Poly1305 => GetCachedAlgorithmHandle(ref s_chaCha20Poly1305, Cng.BCRYPT_CHACHA20_POLY1305_ALGORITHM); + + private static SafeAlgorithmHandle GetCachedAlgorithmHandle(ref SafeAlgorithmHandle? handle, string algId, string? chainingMode = null) + { + // Do we already have a handle to this algorithm? + SafeAlgorithmHandle? existingHandle = Volatile.Read(ref handle); + if (existingHandle != null) + { + return existingHandle; + } + + // No cached handle exists; create a new handle. It's ok if multiple threads call + // this concurrently. Only one handle will "win" and the rest will be destroyed. + SafeAlgorithmHandle newHandle = Cng.BCryptOpenAlgorithmProvider(algId, null, Cng.OpenAlgorithmProviderFlags.NONE); + if (chainingMode != null) + { + newHandle.SetCipherMode(chainingMode); + } + + existingHandle = Interlocked.CompareExchange(ref handle, newHandle, null); + if (existingHandle != null) + { + newHandle.Dispose(); + return existingHandle; + } + else + { + return newHandle; + } + } + } +} diff --git a/src/libraries/Common/src/Interop/Windows/BCrypt/Cng.cs b/src/libraries/Common/src/Interop/Windows/BCrypt/Cng.cs index 7642b9faeaf..273a0a89863 100644 --- a/src/libraries/Common/src/Interop/Windows/BCrypt/Cng.cs +++ b/src/libraries/Common/src/Interop/Windows/BCrypt/Cng.cs @@ -60,6 +60,7 @@ namespace Internal.NativeCrypto public const string BCRYPT_3DES_ALGORITHM = "3DES"; public const string BCRYPT_AES_ALGORITHM = "AES"; + public const string BCRYPT_CHACHA20_POLY1305_ALGORITHM = "CHACHA20_POLY1305"; public const string BCRYPT_DES_ALGORITHM = "DES"; public const string BCRYPT_RC2_ALGORITHM = "RC2"; diff --git a/src/libraries/System.Security.Cryptography.Algorithms/ref/System.Security.Cryptography.Algorithms.cs b/src/libraries/System.Security.Cryptography.Algorithms/ref/System.Security.Cryptography.Algorithms.cs index 7423845019a..8d3302158e0 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/ref/System.Security.Cryptography.Algorithms.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/ref/System.Security.Cryptography.Algorithms.cs @@ -19,6 +19,7 @@ namespace System.Security.Cryptography { public AesCcm(byte[] key) { } public AesCcm(System.ReadOnlySpan key) { } + public static bool IsSupported { get { throw null; } } public static System.Security.Cryptography.KeySizes NonceByteSizes { get { throw null; } } public static System.Security.Cryptography.KeySizes TagByteSizes { get { throw null; } } public void Decrypt(byte[] nonce, byte[] ciphertext, byte[] tag, byte[] plaintext, byte[]? associatedData = null) { } @@ -32,6 +33,7 @@ namespace System.Security.Cryptography { public AesGcm(byte[] key) { } public AesGcm(System.ReadOnlySpan key) { } + public static bool IsSupported { get { throw null; } } public static System.Security.Cryptography.KeySizes NonceByteSizes { get { throw null; } } public static System.Security.Cryptography.KeySizes TagByteSizes { get { throw null; } } public void Decrypt(byte[] nonce, byte[] ciphertext, byte[] tag, byte[] plaintext, byte[]? associatedData = null) { } @@ -97,6 +99,18 @@ namespace System.Security.Cryptography public abstract void SetHashAlgorithm(string strName); public abstract void SetKey(System.Security.Cryptography.AsymmetricAlgorithm key); } + [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")] + public sealed partial class ChaCha20Poly1305 : System.IDisposable + { + public ChaCha20Poly1305(byte[] key) { } + public ChaCha20Poly1305(System.ReadOnlySpan key) { } + public static bool IsSupported { get { throw null; } } + public void Decrypt(byte[] nonce, byte[] ciphertext, byte[] tag, byte[] plaintext, byte[]? associatedData = null) { } + public void Decrypt(System.ReadOnlySpan nonce, System.ReadOnlySpan ciphertext, System.ReadOnlySpan tag, System.Span plaintext, System.ReadOnlySpan associatedData = default(System.ReadOnlySpan)) { } + public void Dispose() { } + public void Encrypt(byte[] nonce, byte[] plaintext, byte[] ciphertext, byte[] tag, byte[]? associatedData = null) { } + public void Encrypt(System.ReadOnlySpan nonce, System.ReadOnlySpan plaintext, System.Span ciphertext, System.Span tag, System.ReadOnlySpan associatedData = default(System.ReadOnlySpan)) { } + } public partial class CryptoConfig { public CryptoConfig() { } diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/ExcludeApiList.PNSE.Browser.txt b/src/libraries/System.Security.Cryptography.Algorithms/src/ExcludeApiList.PNSE.Browser.txt index bca0f309a1f..39c223fa55e 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/ExcludeApiList.PNSE.Browser.txt +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/ExcludeApiList.PNSE.Browser.txt @@ -1,3 +1,6 @@ +M:System.Security.Cryptography.AesCcm.IsSupported +M:System.Security.Cryptography.AesGcm.IsSupported +M:System.Security.Cryptography.ChaCha20Poly1305.IsSupported T:System.Security.Cryptography.CryptoConfig T:System.Security.Cryptography.RandomNumberGenerator T:System.Security.Cryptography.IncrementalHash diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System.Security.Cryptography.Algorithms.csproj b/src/libraries/System.Security.Cryptography.Algorithms/src/System.Security.Cryptography.Algorithms.csproj index fc83d7af8fa..35bf864a448 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/System.Security.Cryptography.Algorithms.csproj +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System.Security.Cryptography.Algorithms.csproj @@ -1,4 +1,4 @@ - + true $(DefineConstants);INTERNAL_ASYMMETRIC_IMPLEMENTATIONS @@ -27,6 +27,7 @@ + @@ -36,6 +37,7 @@ + @@ -276,9 +278,10 @@ + - + @@ -304,6 +307,8 @@ Link="Common\Interop\Windows\Interop.Libraries.cs" /> + + @@ -673,6 +679,7 @@ + @@ -693,6 +700,9 @@ + + + @@ -711,6 +721,7 @@ + diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/AesAEAD.Windows.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/AeadCommon.Windows.cs similarity index 66% rename from src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/AesAEAD.Windows.cs rename to src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/AeadCommon.Windows.cs index ef7bbd6f0bc..fe10a0ed2ca 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/AesAEAD.Windows.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/AeadCommon.Windows.cs @@ -5,13 +5,14 @@ using Internal.Cryptography; using Internal.NativeCrypto; using static Interop.BCrypt; using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Runtime.CompilerServices; namespace System.Security.Cryptography { - internal static partial class AesAEAD + internal static partial class AeadCommon { public static unsafe void Encrypt( - SafeAlgorithmHandle algorithm, SafeKeyHandle keyHandle, ReadOnlySpan nonce, ReadOnlySpan associatedData, @@ -19,11 +20,12 @@ namespace System.Security.Cryptography Span ciphertext, Span tag) { - fixed (byte* plaintextBytes = plaintext) - fixed (byte* nonceBytes = nonce) - fixed (byte* ciphertextBytes = ciphertext) - fixed (byte* tagBytes = tag) - fixed (byte* associatedDataBytes = associatedData) + // bcrypt sometimes misbehaves when given nullptr buffers; ensure non-nullptr + fixed (byte* plaintextBytes = &GetNonNullPinnableReference(plaintext)) + fixed (byte* nonceBytes = &GetNonNullPinnableReference(nonce)) + fixed (byte* ciphertextBytes = &GetNonNullPinnableReference(ciphertext)) + fixed (byte* tagBytes = &GetNonNullPinnableReference(tag)) + fixed (byte* associatedDataBytes = &GetNonNullPinnableReference(associatedData)) { BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO authInfo = BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO.Create(); authInfo.pbNonce = nonceBytes; @@ -55,7 +57,6 @@ namespace System.Security.Cryptography } public static unsafe void Decrypt( - SafeAlgorithmHandle algorithm, SafeKeyHandle keyHandle, ReadOnlySpan nonce, ReadOnlySpan associatedData, @@ -64,11 +65,12 @@ namespace System.Security.Cryptography Span plaintext, bool clearPlaintextOnFailure) { - fixed (byte* plaintextBytes = plaintext) - fixed (byte* nonceBytes = nonce) - fixed (byte* ciphertextBytes = ciphertext) - fixed (byte* tagBytes = tag) - fixed (byte* associatedDataBytes = associatedData) + // bcrypt sometimes misbehaves when given nullptr buffers; ensure non-nullptr + fixed (byte* plaintextBytes = &GetNonNullPinnableReference(plaintext)) + fixed (byte* nonceBytes = &GetNonNullPinnableReference(nonce)) + fixed (byte* ciphertextBytes = &GetNonNullPinnableReference(ciphertext)) + fixed (byte* tagBytes = &GetNonNullPinnableReference(tag)) + fixed (byte* associatedDataBytes = &GetNonNullPinnableReference(associatedData)) { BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO authInfo = BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO.Create(); authInfo.pbNonce = nonceBytes; @@ -108,5 +110,15 @@ namespace System.Security.Cryptography } } } + + // Implementations below based on internal MemoryMarshal.GetNonNullPinnableReference methods. + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static unsafe ref readonly byte GetNonNullPinnableReference(ReadOnlySpan buffer) + => ref buffer.Length != 0 ? ref MemoryMarshal.GetReference(buffer) : ref Unsafe.AsRef((void*)1); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static unsafe ref byte GetNonNullPinnableReference(Span buffer) + => ref buffer.Length != 0 ? ref MemoryMarshal.GetReference(buffer) : ref Unsafe.AsRef((void*)1); } } diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/AeadCommon.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/AeadCommon.cs new file mode 100644 index 00000000000..d2e9320e3e8 --- /dev/null +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/AeadCommon.cs @@ -0,0 +1,29 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Internal.Cryptography; + +namespace System.Security.Cryptography +{ + internal static partial class AeadCommon + { + public static void CheckArgumentsForNull( + byte[] nonce, + byte[] plaintext, + byte[] ciphertext, + byte[] tag) + { + if (nonce == null) + throw new ArgumentNullException(nameof(nonce)); + + if (plaintext == null) + throw new ArgumentNullException(nameof(plaintext)); + + if (ciphertext == null) + throw new ArgumentNullException(nameof(ciphertext)); + + if (tag == null) + throw new ArgumentNullException(nameof(tag)); + } + } +} diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/AesAEAD.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/AesAEAD.cs index b919b04eac0..26322fe8226 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/AesAEAD.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/AesAEAD.cs @@ -7,31 +7,12 @@ namespace System.Security.Cryptography { internal static partial class AesAEAD { - public static void CheckKeySize(int keySizeInBits) + public static void CheckKeySize(int keySizeInBytes) { - if (keySizeInBits != 128 && keySizeInBits != 192 && keySizeInBits != 256) + if (keySizeInBytes != (128 / 8) && keySizeInBytes != (192 / 8) && keySizeInBytes != (256 / 8)) { throw new CryptographicException(SR.Cryptography_InvalidKeySize); } } - - public static void CheckArgumentsForNull( - byte[] nonce, - byte[] plaintext, - byte[] ciphertext, - byte[] tag) - { - if (nonce == null) - throw new ArgumentNullException(nameof(nonce)); - - if (plaintext == null) - throw new ArgumentNullException(nameof(plaintext)); - - if (ciphertext == null) - throw new ArgumentNullException(nameof(ciphertext)); - - if (tag == null) - throw new ArgumentNullException(nameof(tag)); - } } } diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/AesCcm.Android.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/AesCcm.Android.cs index d6c5b9726db..07c20a176be 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/AesCcm.Android.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/AesCcm.Android.cs @@ -17,7 +17,7 @@ namespace System.Security.Cryptography _key = key.ToArray(); } - private void EncryptInternal( + private void EncryptCore( ReadOnlySpan nonce, ReadOnlySpan plaintext, Span ciphertext, @@ -97,7 +97,7 @@ namespace System.Security.Cryptography } } - private void DecryptInternal( + private void DecryptCore( ReadOnlySpan nonce, ReadOnlySpan ciphertext, ReadOnlySpan tag, diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/AesCcm.NotSupported.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/AesCcm.NotSupported.cs new file mode 100644 index 00000000000..170540eec2c --- /dev/null +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/AesCcm.NotSupported.cs @@ -0,0 +1,10 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Security.Cryptography +{ + public partial class AesCcm + { + public static bool IsSupported => false; + } +} diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/AesCcm.Unix.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/AesCcm.Unix.cs index 03da1e4ad73..cd455c31abc 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/AesCcm.Unix.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/AesCcm.Unix.cs @@ -19,7 +19,7 @@ namespace System.Security.Cryptography _key = key.ToArray(); } - private void EncryptInternal( + private void EncryptCore( ReadOnlySpan nonce, ReadOnlySpan plaintext, Span ciphertext, @@ -73,7 +73,7 @@ namespace System.Security.Cryptography } } - private void DecryptInternal( + private void DecryptCore( ReadOnlySpan nonce, ReadOnlySpan ciphertext, ReadOnlySpan tag, diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/AesCcm.Windows.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/AesCcm.Windows.cs index f858454edd2..95884bea902 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/AesCcm.Windows.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/AesCcm.Windows.cs @@ -9,26 +9,25 @@ namespace System.Security.Cryptography { public sealed partial class AesCcm { - private static readonly SafeAlgorithmHandle s_aesCcm = AesBCryptModes.OpenAesAlgorithm(Cng.BCRYPT_CHAIN_MODE_CCM).Value; private SafeKeyHandle _keyHandle; [MemberNotNull(nameof(_keyHandle))] private void ImportKey(ReadOnlySpan key) { - _keyHandle = Interop.BCrypt.BCryptImportKey(s_aesCcm, key); + _keyHandle = Interop.BCrypt.BCryptImportKey(BCryptAeadHandleCache.AesCcm, key); } - private void EncryptInternal( + private void EncryptCore( ReadOnlySpan nonce, ReadOnlySpan plaintext, Span ciphertext, Span tag, ReadOnlySpan associatedData = default) { - AesAEAD.Encrypt(s_aesCcm, _keyHandle, nonce, associatedData, plaintext, ciphertext, tag); + AeadCommon.Encrypt(_keyHandle, nonce, associatedData, plaintext, ciphertext, tag); } - private void DecryptInternal( + private void DecryptCore( ReadOnlySpan nonce, ReadOnlySpan ciphertext, ReadOnlySpan tag, @@ -36,7 +35,7 @@ namespace System.Security.Cryptography ReadOnlySpan associatedData = default) { // BCrypt implementation of CCM clears plaintext for you on failure - AesAEAD.Decrypt(s_aesCcm, _keyHandle, nonce, associatedData, ciphertext, tag, plaintext, clearPlaintextOnFailure: false); + AeadCommon.Decrypt(_keyHandle, nonce, associatedData, ciphertext, tag, plaintext, clearPlaintextOnFailure: false); } public void Dispose() diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/AesCcm.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/AesCcm.cs index 1e1f32adc87..5940ecece6c 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/AesCcm.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/AesCcm.cs @@ -14,7 +14,7 @@ namespace System.Security.Cryptography public AesCcm(ReadOnlySpan key) { - AesAEAD.CheckKeySize(key.Length * 8); + AesAEAD.CheckKeySize(key.Length); ImportKey(key); } @@ -23,13 +23,15 @@ namespace System.Security.Cryptography if (key == null) throw new ArgumentNullException(nameof(key)); - AesAEAD.CheckKeySize(key.Length * 8); + AesAEAD.CheckKeySize(key.Length); ImportKey(key); } + public static bool IsSupported => true; + public void Encrypt(byte[] nonce, byte[] plaintext, byte[] ciphertext, byte[] tag, byte[]? associatedData = null) { - AesAEAD.CheckArgumentsForNull(nonce, plaintext, ciphertext, tag); + AeadCommon.CheckArgumentsForNull(nonce, plaintext, ciphertext, tag); Encrypt((ReadOnlySpan)nonce, plaintext, ciphertext, tag, associatedData); } @@ -41,12 +43,12 @@ namespace System.Security.Cryptography ReadOnlySpan associatedData = default) { CheckParameters(plaintext, ciphertext, nonce, tag); - EncryptInternal(nonce, plaintext, ciphertext, tag, associatedData); + EncryptCore(nonce, plaintext, ciphertext, tag, associatedData); } public void Decrypt(byte[] nonce, byte[] ciphertext, byte[] tag, byte[] plaintext, byte[]? associatedData = null) { - AesAEAD.CheckArgumentsForNull(nonce, plaintext, ciphertext, tag); + AeadCommon.CheckArgumentsForNull(nonce, plaintext, ciphertext, tag); Decrypt((ReadOnlySpan)nonce, ciphertext, tag, plaintext, associatedData); } @@ -58,7 +60,7 @@ namespace System.Security.Cryptography ReadOnlySpan associatedData = default) { CheckParameters(plaintext, ciphertext, nonce, tag); - DecryptInternal(nonce, ciphertext, tag, plaintext, associatedData); + DecryptCore(nonce, ciphertext, tag, plaintext, associatedData); } private static void CheckParameters( diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/AesGcm.Android.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/AesGcm.Android.cs index fd6b898db67..8a699770102 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/AesGcm.Android.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/AesGcm.Android.cs @@ -26,7 +26,7 @@ namespace System.Security.Cryptography Interop.Crypto.CipherSetNonceLength(_ctxHandle, NonceSize); } - private void EncryptInternal( + private void EncryptCore( ReadOnlySpan nonce, ReadOnlySpan plaintext, Span ciphertext, @@ -101,7 +101,7 @@ namespace System.Security.Cryptography } } - private void DecryptInternal( + private void DecryptCore( ReadOnlySpan nonce, ReadOnlySpan ciphertext, ReadOnlySpan tag, diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/AesGcm.NotSupported.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/AesGcm.NotSupported.cs new file mode 100644 index 00000000000..efbf1a729b5 --- /dev/null +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/AesGcm.NotSupported.cs @@ -0,0 +1,10 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Security.Cryptography +{ + public partial class AesGcm + { + public static bool IsSupported => false; + } +} diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/AesGcm.Unix.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/AesGcm.Unix.cs index d050604168a..b18ce7f9c58 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/AesGcm.Unix.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/AesGcm.Unix.cs @@ -25,7 +25,7 @@ namespace System.Security.Cryptography Interop.Crypto.EvpCipherSetGcmNonceLength(_ctxHandle, NonceSize); } - private void EncryptInternal( + private void EncryptCore( ReadOnlySpan nonce, ReadOnlySpan plaintext, Span ciphertext, @@ -70,7 +70,7 @@ namespace System.Security.Cryptography Interop.Crypto.EvpCipherGetGcmTag(_ctxHandle, tag); } - private void DecryptInternal( + private void DecryptCore( ReadOnlySpan nonce, ReadOnlySpan ciphertext, ReadOnlySpan tag, diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/AesGcm.Windows.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/AesGcm.Windows.cs index 7798787e5b5..31e6f647835 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/AesGcm.Windows.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/AesGcm.Windows.cs @@ -9,33 +9,32 @@ namespace System.Security.Cryptography { public partial class AesGcm { - private static readonly SafeAlgorithmHandle s_aesGcm = AesBCryptModes.OpenAesAlgorithm(Cng.BCRYPT_CHAIN_MODE_GCM).Value; private SafeKeyHandle _keyHandle; [MemberNotNull(nameof(_keyHandle))] private void ImportKey(ReadOnlySpan key) { - _keyHandle = Interop.BCrypt.BCryptImportKey(s_aesGcm, key); + _keyHandle = Interop.BCrypt.BCryptImportKey(BCryptAeadHandleCache.AesGcm, key); } - private void EncryptInternal( + private void EncryptCore( ReadOnlySpan nonce, ReadOnlySpan plaintext, Span ciphertext, Span tag, ReadOnlySpan associatedData = default) { - AesAEAD.Encrypt(s_aesGcm, _keyHandle, nonce, associatedData, plaintext, ciphertext, tag); + AeadCommon.Encrypt(_keyHandle, nonce, associatedData, plaintext, ciphertext, tag); } - private void DecryptInternal( + private void DecryptCore( ReadOnlySpan nonce, ReadOnlySpan ciphertext, ReadOnlySpan tag, Span plaintext, ReadOnlySpan associatedData = default) { - AesAEAD.Decrypt(s_aesGcm, _keyHandle, nonce, associatedData, ciphertext, tag, plaintext, clearPlaintextOnFailure: true); + AeadCommon.Decrypt(_keyHandle, nonce, associatedData, ciphertext, tag, plaintext, clearPlaintextOnFailure: true); } public void Dispose() diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/AesGcm.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/AesGcm.cs index 07bbb6daa0e..db1e51dd87f 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/AesGcm.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/AesGcm.cs @@ -15,7 +15,7 @@ namespace System.Security.Cryptography public AesGcm(ReadOnlySpan key) { - AesAEAD.CheckKeySize(key.Length * 8); + AesAEAD.CheckKeySize(key.Length); ImportKey(key); } @@ -24,13 +24,15 @@ namespace System.Security.Cryptography if (key == null) throw new ArgumentNullException(nameof(key)); - AesAEAD.CheckKeySize(key.Length * 8); + AesAEAD.CheckKeySize(key.Length); ImportKey(key); } + public static bool IsSupported => true; + public void Encrypt(byte[] nonce, byte[] plaintext, byte[] ciphertext, byte[] tag, byte[]? associatedData = null) { - AesAEAD.CheckArgumentsForNull(nonce, plaintext, ciphertext, tag); + AeadCommon.CheckArgumentsForNull(nonce, plaintext, ciphertext, tag); Encrypt((ReadOnlySpan)nonce, plaintext, ciphertext, tag, associatedData); } @@ -42,12 +44,12 @@ namespace System.Security.Cryptography ReadOnlySpan associatedData = default) { CheckParameters(plaintext, ciphertext, nonce, tag); - EncryptInternal(nonce, plaintext, ciphertext, tag, associatedData); + EncryptCore(nonce, plaintext, ciphertext, tag, associatedData); } public void Decrypt(byte[] nonce, byte[] ciphertext, byte[] tag, byte[] plaintext, byte[]? associatedData = null) { - AesAEAD.CheckArgumentsForNull(nonce, plaintext, ciphertext, tag); + AeadCommon.CheckArgumentsForNull(nonce, plaintext, ciphertext, tag); Decrypt((ReadOnlySpan)nonce, ciphertext, tag, plaintext, associatedData); } @@ -59,7 +61,7 @@ namespace System.Security.Cryptography ReadOnlySpan associatedData = default) { CheckParameters(plaintext, ciphertext, nonce, tag); - DecryptInternal(nonce, ciphertext, tag, plaintext, associatedData); + DecryptCore(nonce, ciphertext, tag, plaintext, associatedData); } private static void CheckParameters( diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ChaCha20Poly1305.NotSupported.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ChaCha20Poly1305.NotSupported.cs new file mode 100644 index 00000000000..05e4d39531f --- /dev/null +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ChaCha20Poly1305.NotSupported.cs @@ -0,0 +1,48 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; + +namespace System.Security.Cryptography +{ + public partial class ChaCha20Poly1305 + { + public static bool IsSupported => false; + +#if !BROWSER // allow GenFacades to handle browser target + private void ImportKey(ReadOnlySpan key) + { + Debug.Fail("Instance ctor should fail before we reach this point."); + throw new NotImplementedException(); + } + + private void EncryptCore( + ReadOnlySpan nonce, + ReadOnlySpan plaintext, + Span ciphertext, + Span tag, + ReadOnlySpan associatedData = default) + { + Debug.Fail("Instance ctor should fail before we reach this point."); + throw new NotImplementedException(); + } + + private void DecryptCore( + ReadOnlySpan nonce, + ReadOnlySpan ciphertext, + ReadOnlySpan tag, + Span plaintext, + ReadOnlySpan associatedData = default) + { + Debug.Fail("Instance ctor should fail before we reach this point."); + throw new NotImplementedException(); + } + + public void Dispose() + { + Debug.Fail("Instance ctor should fail before we reach this point."); + // no-op + } +#endif + } +} diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ChaCha20Poly1305.Windows.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ChaCha20Poly1305.Windows.cs new file mode 100644 index 00000000000..984a8da718f --- /dev/null +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ChaCha20Poly1305.Windows.cs @@ -0,0 +1,47 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics.CodeAnalysis; +using Internal.Cryptography; +using Internal.NativeCrypto; + +namespace System.Security.Cryptography +{ + public partial class ChaCha20Poly1305 + { + private SafeKeyHandle _keyHandle; + + public static bool IsSupported => BCryptAeadHandleCache.IsChaCha20Poly1305Supported; + + [MemberNotNull(nameof(_keyHandle))] + private void ImportKey(ReadOnlySpan key) + { + _keyHandle = Interop.BCrypt.BCryptImportKey(BCryptAeadHandleCache.ChaCha20Poly1305, key); + } + + private void EncryptCore( + ReadOnlySpan nonce, + ReadOnlySpan plaintext, + Span ciphertext, + Span tag, + ReadOnlySpan associatedData = default) + { + AeadCommon.Encrypt(_keyHandle, nonce, associatedData, plaintext, ciphertext, tag); + } + + private void DecryptCore( + ReadOnlySpan nonce, + ReadOnlySpan ciphertext, + ReadOnlySpan tag, + Span plaintext, + ReadOnlySpan associatedData = default) + { + AeadCommon.Decrypt(_keyHandle, nonce, associatedData, ciphertext, tag, plaintext, clearPlaintextOnFailure: true); + } + + public void Dispose() + { + _keyHandle.Dispose(); + } + } +} diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ChaCha20Poly1305.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ChaCha20Poly1305.cs new file mode 100644 index 00000000000..874133cc39f --- /dev/null +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ChaCha20Poly1305.cs @@ -0,0 +1,105 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.Versioning; +using Internal.Cryptography; + +namespace System.Security.Cryptography +{ + [UnsupportedOSPlatform("browser")] + public sealed partial class ChaCha20Poly1305 : IDisposable + { + // Per https://tools.ietf.org/html/rfc7539, ChaCha20Poly1305 AEAD requires a 256-bit key and 96-bit nonce, + // and it produces a 128-bit tag. We don't expose NonceByteSizes / TagByteSizes properties because callers + // are expected to know this. + + private const int KeySizeInBytes = 256 / 8; + private const int NonceSizeInBytes = 96 / 8; + private const int TagSizeInBytes = 128 / 8; + + public ChaCha20Poly1305(ReadOnlySpan key) + { + ThrowIfNotSupported(); + + CheckKeySize(key.Length); + ImportKey(key); + } + + public ChaCha20Poly1305(byte[] key) + { + ThrowIfNotSupported(); + + if (key == null) + throw new ArgumentNullException(nameof(key)); + + CheckKeySize(key.Length); + ImportKey(key); + } + + private static void CheckKeySize(int keySizeInBytes) + { + if (keySizeInBytes != KeySizeInBytes) + { + throw new CryptographicException(SR.Cryptography_InvalidKeySize); + } + } + + public void Encrypt(byte[] nonce, byte[] plaintext, byte[] ciphertext, byte[] tag, byte[]? associatedData = null) + { + AeadCommon.CheckArgumentsForNull(nonce, plaintext, ciphertext, tag); + Encrypt((ReadOnlySpan)nonce, plaintext, ciphertext, tag, associatedData); + } + + public void Encrypt( + ReadOnlySpan nonce, + ReadOnlySpan plaintext, + Span ciphertext, + Span tag, + ReadOnlySpan associatedData = default) + { + CheckParameters(plaintext, ciphertext, nonce, tag); + EncryptCore(nonce, plaintext, ciphertext, tag, associatedData); + } + + public void Decrypt(byte[] nonce, byte[] ciphertext, byte[] tag, byte[] plaintext, byte[]? associatedData = null) + { + AeadCommon.CheckArgumentsForNull(nonce, plaintext, ciphertext, tag); + Decrypt((ReadOnlySpan)nonce, ciphertext, tag, plaintext, associatedData); + } + + public void Decrypt( + ReadOnlySpan nonce, + ReadOnlySpan ciphertext, + ReadOnlySpan tag, + Span plaintext, + ReadOnlySpan associatedData = default) + { + CheckParameters(plaintext, ciphertext, nonce, tag); + DecryptCore(nonce, ciphertext, tag, plaintext, associatedData); + } + + private static void CheckParameters( + ReadOnlySpan plaintext, + ReadOnlySpan ciphertext, + ReadOnlySpan nonce, + ReadOnlySpan tag) + { + if (plaintext.Length != ciphertext.Length) + throw new ArgumentException(SR.Cryptography_PlaintextCiphertextLengthMismatch); + + if (nonce.Length != NonceSizeInBytes) + throw new ArgumentException(SR.Cryptography_InvalidNonceLength, nameof(nonce)); + + if (tag.Length != TagSizeInBytes) + throw new ArgumentException(SR.Cryptography_InvalidTagLength, nameof(tag)); + } + + private static void ThrowIfNotSupported() + { + if (!IsSupported) + { + throw new PlatformNotSupportedException(SR.Format(SR.Cryptography_AlgorithmNotSupported, nameof(ChaCha20Poly1305))); + } + } + } +} diff --git a/src/libraries/System.Security.Cryptography.Algorithms/tests/AesCcmTests.cs b/src/libraries/System.Security.Cryptography.Algorithms/tests/AesCcmTests.cs index 3e60346f40a..6f5f77aa501 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/tests/AesCcmTests.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/tests/AesCcmTests.cs @@ -8,21 +8,11 @@ using Xunit; namespace System.Security.Cryptography.Algorithms.Tests { - [SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")] - public class AesCcmTests : AesAEADTests + [ConditionalClass(typeof(AesCcm), nameof(AesCcm.IsSupported))] + public class AesCcmTests : CommonAEADTests { [Theory] - [InlineData(0, 1)] - [InlineData(0, 30)] - [InlineData(1, 1)] - [InlineData(1, 100)] - [InlineData(7, 12)] - [InlineData(16, 16)] - [InlineData(17, 29)] - [InlineData(32, 7)] - [InlineData(41, 25)] - [InlineData(48, 22)] - [InlineData(50, 5)] + [MemberData(nameof(EncryptTamperAADDecryptTestInputs))] public static void EncryptTamperAADDecrypt(int dataLength, int additionalDataLength) { byte[] additionalData = new byte[additionalDataLength]; @@ -190,12 +180,7 @@ namespace System.Security.Cryptography.Algorithms.Tests } [Theory] - [InlineData(0, 1)] - [InlineData(1, 0)] - [InlineData(3, 4)] - [InlineData(4, 3)] - [InlineData(20, 120)] - [InlineData(120, 20)] + [MemberData(nameof(PlaintextAndCiphertextSizeDifferTestInputs))] public static void PlaintextAndCiphertextSizeDiffer(int ptLen, int ctLen) { byte[] key = new byte[16]; @@ -690,4 +675,18 @@ namespace System.Security.Cryptography.Algorithms.Tests }, }; } + + public class AesCcmIsSupportedTests + { + public class AesGcmIsSupportedTests + { + [Fact] + public static void CheckIsSupported() + { + bool expectedIsSupported = !PlatformDetection.IsBrowser; + + Assert.Equal(expectedIsSupported, AesCcm.IsSupported); + } + } + } } diff --git a/src/libraries/System.Security.Cryptography.Algorithms/tests/AesGcmTests.cs b/src/libraries/System.Security.Cryptography.Algorithms/tests/AesGcmTests.cs index 70188d08f42..d95482f2c47 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/tests/AesGcmTests.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/tests/AesGcmTests.cs @@ -8,21 +8,11 @@ using Xunit; namespace System.Security.Cryptography.Algorithms.Tests { - [SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")] - public class AesGcmTests : AesAEADTests + [ConditionalClass(typeof(AesGcm), nameof(AesGcm.IsSupported))] + public class AesGcmTests : CommonAEADTests { [Theory] - [InlineData(0, 1)] - [InlineData(0, 30)] - [InlineData(1, 1)] - [InlineData(1, 100)] - [InlineData(7, 12)] - [InlineData(16, 16)] - [InlineData(17, 29)] - [InlineData(32, 7)] - [InlineData(41, 25)] - [InlineData(48, 22)] - [InlineData(50, 5)] + [MemberData(nameof(EncryptTamperAADDecryptTestInputs))] public static void EncryptTamperAADDecrypt(int dataLength, int additionalDataLength) { byte[] additionalData = new byte[additionalDataLength]; @@ -190,12 +180,7 @@ namespace System.Security.Cryptography.Algorithms.Tests } [Theory] - [InlineData(0, 1)] - [InlineData(1, 0)] - [InlineData(3, 4)] - [InlineData(4, 3)] - [InlineData(20, 120)] - [InlineData(120, 20)] + [MemberData(nameof(PlaintextAndCiphertextSizeDifferTestInputs))] public static void PlaintextAndCiphertextSizeDiffer(int ptLen, int ctLen) { byte[] key = new byte[16]; @@ -278,7 +263,7 @@ namespace System.Security.Cryptography.Algorithms.Tests } [Fact] - public static void InplaceEncrypDecrypt() + public static void InplaceEncryptDecrypt() { byte[] key = "d5a194ed90cfe08abecd4691997ceb2c".HexToByteArray(); byte[] nonce = new byte[12]; @@ -298,7 +283,7 @@ namespace System.Security.Cryptography.Algorithms.Tests } [Fact] - public static void InplaceEncrypTamperTagDecrypt() + public static void InplaceEncryptTamperTagDecrypt() { byte[] key = "d5a194ed90cfe08abecd4691997ceb2c".HexToByteArray(); byte[] nonce = new byte[12]; @@ -851,4 +836,15 @@ namespace System.Security.Cryptography.Algorithms.Tests }, }; } + + public class AesGcmIsSupportedTests + { + [Fact] + public static void CheckIsSupported() + { + bool expectedIsSupported = !PlatformDetection.IsBrowser; + + Assert.Equal(expectedIsSupported, AesGcm.IsSupported); + } + } } diff --git a/src/libraries/System.Security.Cryptography.Algorithms/tests/ChaCha20Poly1305Tests.cs b/src/libraries/System.Security.Cryptography.Algorithms/tests/ChaCha20Poly1305Tests.cs new file mode 100644 index 00000000000..abd5a555080 --- /dev/null +++ b/src/libraries/System.Security.Cryptography.Algorithms/tests/ChaCha20Poly1305Tests.cs @@ -0,0 +1,454 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Linq; +using Test.Cryptography; +using Xunit; + +namespace System.Security.Cryptography.Algorithms.Tests +{ + [ConditionalClass(typeof(ChaCha20Poly1305), nameof(ChaCha20Poly1305.IsSupported))] + public class ChaCha20Poly1305Tests : CommonAEADTests + { + private const int KeySizeInBytes = 256 / 8; + private const int NonceSizeInBytes = 96 / 8; + private const int TagSizeInBytes = 128 / 8; + + [Theory] + [MemberData(nameof(EncryptTamperAADDecryptTestInputs))] + public static void EncryptTamperAADDecrypt(int dataLength, int additionalDataLength) + { + byte[] additionalData = new byte[additionalDataLength]; + RandomNumberGenerator.Fill(additionalData); + + byte[] plaintext = Enumerable.Range(1, dataLength).Select((x) => (byte)x).ToArray(); + byte[] ciphertext = new byte[dataLength]; + byte[] key = RandomNumberGenerator.GetBytes(KeySizeInBytes); + byte[] nonce = RandomNumberGenerator.GetBytes(NonceSizeInBytes); + byte[] tag = new byte[TagSizeInBytes]; + + using (var chaChaPoly = new ChaCha20Poly1305(key)) + { + chaChaPoly.Encrypt(nonce, plaintext, ciphertext, tag, additionalData); + + additionalData[0] ^= 1; + + byte[] decrypted = new byte[dataLength]; + Assert.Throws( + () => chaChaPoly.Decrypt(nonce, ciphertext, tag, decrypted, additionalData)); + } + } + + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(2)] + [InlineData(16)] // 128-bit keys disallowed + [InlineData(17)] + [InlineData(24)] // 192-bit keys disallowed + [InlineData(29)] + [InlineData(33)] + public static void InvalidKeyLength(int keyLength) + { + byte[] key = new byte[keyLength]; + Assert.Throws(() => new ChaCha20Poly1305(key)); + } + + [Theory] + [MemberData(nameof(GetInvalidNonceSizes))] + public static void InvalidNonceSize(int nonceSize) + { + int dataLength = 30; + byte[] plaintext = Enumerable.Range(1, dataLength).Select((x) => (byte)x).ToArray(); + byte[] ciphertext = new byte[dataLength]; + byte[] key = RandomNumberGenerator.GetBytes(KeySizeInBytes); + byte[] nonce = RandomNumberGenerator.GetBytes(nonceSize); + byte[] tag = new byte[TagSizeInBytes]; + + using (var chaChaPoly = new ChaCha20Poly1305(key)) + { + Assert.Throws("nonce", () => chaChaPoly.Encrypt(nonce, plaintext, ciphertext, tag)); + } + } + + [Theory] + [MemberData(nameof(GetInvalidTagSizes))] + public static void InvalidTagSize(int tagSize) + { + int dataLength = 30; + byte[] plaintext = Enumerable.Range(1, dataLength).Select((x) => (byte)x).ToArray(); + byte[] ciphertext = new byte[dataLength]; + byte[] key = RandomNumberGenerator.GetBytes(KeySizeInBytes); + byte[] nonce = RandomNumberGenerator.GetBytes(NonceSizeInBytes); + byte[] tag = new byte[tagSize]; + + using (var chaChaPoly = new ChaCha20Poly1305(key)) + { + Assert.Throws("tag", () => chaChaPoly.Encrypt(nonce, plaintext, ciphertext, tag)); + } + } + + [Fact] + public static void ValidNonceAndTagSize() + { + const int dataLength = 35; + byte[] plaintext = Enumerable.Range(1, dataLength).Select((x) => (byte)x).ToArray(); + byte[] ciphertext = new byte[dataLength]; + byte[] key = RandomNumberGenerator.GetBytes(KeySizeInBytes); + byte[] nonce = RandomNumberGenerator.GetBytes(NonceSizeInBytes); + byte[] tag = new byte[TagSizeInBytes]; + + using (var chaChaPoly = new ChaCha20Poly1305(key)) + { + chaChaPoly.Encrypt(nonce, plaintext, ciphertext, tag); + + byte[] decrypted = new byte[dataLength]; + chaChaPoly.Decrypt(nonce, ciphertext, tag, decrypted); + Assert.Equal(plaintext, decrypted); + } + } + + [Fact] + public static void TwoEncryptionsAndDecryptionsUsingOneInstance() + { + byte[] key = "fde37f01fe9ca260f432e0ed98b3e0bb23895ca1ca1ce2cfcaaca2ccc98889d7".HexToByteArray(); + byte[] originalData1 = Enumerable.Range(1, 15).Select((x) => (byte)x).ToArray(); + byte[] originalData2 = Enumerable.Range(14, 97).Select((x) => (byte)x).ToArray(); + byte[] associatedData2 = Enumerable.Range(100, 109).Select((x) => (byte)x).ToArray(); + byte[] nonce1 = "b41329dd64af2c3036661b46".HexToByteArray(); + byte[] nonce2 = "8ba10892e8b87d031196bf99".HexToByteArray(); + + byte[] expectedCiphertext1 = "75f5aafbbabab80a3cfa2ecfd1bc58".HexToByteArray(); + byte[] expectedTag1 = "1ed70acc454fba01f0354e93eba9b428".HexToByteArray(); + + byte[] expectedCiphertext2 = ( + "f95cc19929463ba96a2cfc21fac5345ec308e2748995ba285af6b21ca3d665bc" + + "00144604b38e9645fb2d5f5893fc78871bd8f5fc91caaa013eac5f80397fd65c" + + "358c239f013f3c75da17ddbd14de01eb67f5204dfa787986fb27a098fe21b2c5" + + "07").HexToByteArray(); + byte[] expectedTag2 = "9877f87f29f68b5f9efb071c1351ccf6".HexToByteArray(); + + using (var chaChaPoly = new ChaCha20Poly1305(key)) + { + byte[] ciphertext1 = new byte[originalData1.Length]; + byte[] tag1 = new byte[expectedTag1.Length]; + chaChaPoly.Encrypt(nonce1, originalData1, ciphertext1, tag1); + Assert.Equal(expectedCiphertext1, ciphertext1); + Assert.Equal(expectedTag1, tag1); + + byte[] ciphertext2 = new byte[originalData2.Length]; + byte[] tag2 = new byte[expectedTag2.Length]; + chaChaPoly.Encrypt(nonce2, originalData2, ciphertext2, tag2, associatedData2); + Assert.Equal(expectedCiphertext2, ciphertext2); + Assert.Equal(expectedTag2, tag2); + + byte[] plaintext1 = new byte[originalData1.Length]; + chaChaPoly.Decrypt(nonce1, ciphertext1, tag1, plaintext1); + Assert.Equal(originalData1, plaintext1); + + byte[] plaintext2 = new byte[originalData2.Length]; + chaChaPoly.Decrypt(nonce2, ciphertext2, tag2, plaintext2, associatedData2); + Assert.Equal(originalData2, plaintext2); + } + } + + [Theory] + [MemberData(nameof(PlaintextAndCiphertextSizeDifferTestInputs))] + public static void PlaintextAndCiphertextSizeDiffer(int ptLen, int ctLen) + { + byte[] key = new byte[KeySizeInBytes]; + byte[] nonce = new byte[NonceSizeInBytes]; + byte[] plaintext = new byte[ptLen]; + byte[] ciphertext = new byte[ctLen]; + byte[] tag = new byte[TagSizeInBytes]; + + using (var chaChaPoly = new ChaCha20Poly1305(key)) + { + Assert.Throws(() => chaChaPoly.Encrypt(nonce, plaintext, ciphertext, tag)); + Assert.Throws(() => chaChaPoly.Decrypt(nonce, ciphertext, tag, plaintext)); + } + } + + [Fact] + public static void NullKey() + { + Assert.Throws(() => new ChaCha20Poly1305((byte[])null)); + } + + [Fact] + public static void EncryptDecryptNullNonce() + { + byte[] key = "fde37f01fe9ca260f432e0ed98b3e0bb23895ca1ca1ce2cfcaaca2ccc98889d7".HexToByteArray(); + byte[] plaintext = new byte[0]; + byte[] ciphertext = new byte[0]; + byte[] tag = new byte[TagSizeInBytes]; + + using (var chaChaPoly = new ChaCha20Poly1305(key)) + { + Assert.Throws(() => chaChaPoly.Encrypt((byte[])null, plaintext, ciphertext, tag)); + Assert.Throws(() => chaChaPoly.Decrypt((byte[])null, ciphertext, tag, plaintext)); + } + } + + [Fact] + public static void EncryptDecryptNullPlaintext() + { + byte[] key = "fde37f01fe9ca260f432e0ed98b3e0bb23895ca1ca1ce2cfcaaca2ccc98889d7".HexToByteArray(); + byte[] nonce = new byte[NonceSizeInBytes]; + byte[] ciphertext = new byte[0]; + byte[] tag = new byte[TagSizeInBytes]; + + using (var chaChaPoly = new ChaCha20Poly1305(key)) + { + Assert.Throws(() => chaChaPoly.Encrypt(nonce, (byte[])null, ciphertext, tag)); + Assert.Throws(() => chaChaPoly.Decrypt(nonce, ciphertext, tag, (byte[])null)); + } + } + + [Fact] + public static void EncryptDecryptNullCiphertext() + { + byte[] key = "fde37f01fe9ca260f432e0ed98b3e0bb23895ca1ca1ce2cfcaaca2ccc98889d7".HexToByteArray(); + byte[] nonce = new byte[NonceSizeInBytes]; + byte[] plaintext = new byte[0]; + byte[] tag = new byte[TagSizeInBytes]; + + using (var chaChaPoly = new ChaCha20Poly1305(key)) + { + Assert.Throws(() => chaChaPoly.Encrypt(nonce, plaintext, (byte[])null, tag)); + Assert.Throws(() => chaChaPoly.Decrypt(nonce, (byte[])null, tag, plaintext)); + } + } + + [Fact] + public static void EncryptDecryptNullTag() + { + byte[] key = "fde37f01fe9ca260f432e0ed98b3e0bb23895ca1ca1ce2cfcaaca2ccc98889d7".HexToByteArray(); + byte[] nonce = new byte[NonceSizeInBytes]; + byte[] plaintext = new byte[0]; + byte[] ciphertext = new byte[0]; + + using (var chaChaPoly = new ChaCha20Poly1305(key)) + { + Assert.Throws(() => chaChaPoly.Encrypt(nonce, plaintext, ciphertext, (byte[])null)); + Assert.Throws(() => chaChaPoly.Decrypt(nonce, ciphertext, (byte[])null, plaintext)); + } + } + + [Fact] + public static void InplaceEncryptDecrypt() + { + byte[] key = "fde37f01fe9ca260f432e0ed98b3e0bb23895ca1ca1ce2cfcaaca2ccc98889d7".HexToByteArray(); + byte[] nonce = RandomNumberGenerator.GetBytes(NonceSizeInBytes); + byte[] originalPlaintext = new byte[] { 1, 2, 8, 12, 16, 99, 0 }; + byte[] data = (byte[])originalPlaintext.Clone(); + byte[] tag = new byte[TagSizeInBytes]; + + using (var chaChaPoly = new ChaCha20Poly1305(key)) + { + chaChaPoly.Encrypt(nonce, data, data, tag); + Assert.NotEqual(originalPlaintext, data); + + chaChaPoly.Decrypt(nonce, data, tag, data); + Assert.Equal(originalPlaintext, data); + } + } + + [Fact] + public static void InplaceEncryptTamperTagDecrypt() + { + byte[] key = "fde37f01fe9ca260f432e0ed98b3e0bb23895ca1ca1ce2cfcaaca2ccc98889d7".HexToByteArray(); + byte[] nonce = RandomNumberGenerator.GetBytes(NonceSizeInBytes); + byte[] originalPlaintext = new byte[] { 1, 2, 8, 12, 16, 99, 0 }; + byte[] data = (byte[])originalPlaintext.Clone(); + byte[] tag = new byte[TagSizeInBytes]; + + using (var chaChaPoly = new ChaCha20Poly1305(key)) + { + chaChaPoly.Encrypt(nonce, data, data, tag); + Assert.NotEqual(originalPlaintext, data); + + tag[0] ^= 1; + + Assert.Throws( + () => chaChaPoly.Decrypt(nonce, data, tag, data)); + Assert.Equal(new byte[data.Length], data); + } + } + + [Theory] + [MemberData(nameof(GetRfc8439TestCases))] + public static void Rfc8439Tests(AEADTest testCase) + { + using (var chaChaPoly = new ChaCha20Poly1305(testCase.Key)) + { + byte[] ciphertext = new byte[testCase.Plaintext.Length]; + byte[] tag = new byte[testCase.Tag.Length]; + chaChaPoly.Encrypt(testCase.Nonce, testCase.Plaintext, ciphertext, tag, testCase.AssociatedData); + Assert.Equal(testCase.Ciphertext, ciphertext); + Assert.Equal(testCase.Tag, tag); + + byte[] plaintext = new byte[testCase.Plaintext.Length]; + chaChaPoly.Decrypt(testCase.Nonce, ciphertext, tag, plaintext, testCase.AssociatedData); + Assert.Equal(testCase.Plaintext, plaintext); + } + } + + [Theory] + [MemberData(nameof(GetRfc8439TestCases))] + public static void Rfc8439TestsTamperTag(AEADTest testCase) + { + using (var chaChaPoly = new ChaCha20Poly1305(testCase.Key)) + { + byte[] ciphertext = new byte[testCase.Plaintext.Length]; + byte[] tag = new byte[testCase.Tag.Length]; + chaChaPoly.Encrypt(testCase.Nonce, testCase.Plaintext, ciphertext, tag, testCase.AssociatedData); + Assert.Equal(testCase.Ciphertext, ciphertext); + Assert.Equal(testCase.Tag, tag); + + tag[0] ^= 1; + + byte[] plaintext = RandomNumberGenerator.GetBytes(testCase.Plaintext.Length); + Assert.Throws( + () => chaChaPoly.Decrypt(testCase.Nonce, ciphertext, tag, plaintext, testCase.AssociatedData)); + Assert.Equal(new byte[plaintext.Length], plaintext); + } + } + + public static IEnumerable GetInvalidNonceSizes() + { + yield return new object[] { 0 }; + yield return new object[] { 8 }; + yield return new object[] { 11 }; + yield return new object[] { 13 }; + yield return new object[] { 16 }; + } + + public static IEnumerable GetInvalidTagSizes() + { + yield return new object[] { 0 }; + yield return new object[] { 8 }; + yield return new object[] { 12 }; + yield return new object[] { 15 }; + yield return new object[] { 17 }; + } + + // https://tools.ietf.org/html/rfc8439 + private const string Rfc8439TestVectors = "RFC 8439 Test Vectors"; + + public static IEnumerable GetRfc8439TestCases() + { + foreach (AEADTest test in s_rfc8439TestVectors) + { + yield return new object[] { test }; + } + } + + // CaseId is unique per test case + private static readonly AEADTest[] s_rfc8439TestVectors = new AEADTest[] + { + new AEADTest + { + Source = Rfc8439TestVectors, + CaseId = 1, // RFC 8439, Sec. 2.8.2 + Key = "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f".HexToByteArray(), + Nonce = "070000004041424344454647".HexToByteArray(), + Plaintext = ( + "4c616469657320616e642047656e746c" + + "656d656e206f662074686520636c6173" + + "73206f66202739393a20496620492063" + + "6f756c64206f6666657220796f75206f" + + "6e6c79206f6e652074697020666f7220" + + "746865206675747572652c2073756e73" + + "637265656e20776f756c642062652069" + + "742e").HexToByteArray(), + AssociatedData = "50515253c0c1c2c3c4c5c6c7".HexToByteArray(), + Ciphertext = ( + "d31a8d34648e60db7b86afbc53ef7ec2" + + "a4aded51296e08fea9e2b5a736ee62d6" + + "3dbea45e8ca9671282fafb69da92728b" + + "1a71de0a9e060b2905d6a5b67ecd3b36" + + "92ddbd7f2d778b8c9803aee328091b58" + + "fab324e4fad675945585808b4831d7bc" + + "3ff4def08e4b7a9de576d26586cec64b" + + "6116").HexToByteArray(), + Tag = "1ae10b594f09e26a7e902ecbd0600691".HexToByteArray() + }, + new AEADTest + { + Source = Rfc8439TestVectors, + CaseId = 2, // RFC 8439, Appendix A.5 + Key = "1c9240a5eb55d38af333888604f6b5f0473917c1402b80099dca5cbc207075c0".HexToByteArray(), + Nonce = "000000000102030405060708".HexToByteArray(), + Plaintext = ( + "496e7465726e65742d44726166747320" + + "61726520647261667420646f63756d65" + + "6e74732076616c696420666f72206120" + + "6d6178696d756d206f6620736978206d" + + "6f6e74687320616e64206d6179206265" + + "20757064617465642c207265706c6163" + + "65642c206f72206f62736f6c65746564" + + "206279206f7468657220646f63756d65" + + "6e747320617420616e792074696d652e" + + "20497420697320696e617070726f7072" + + "6961746520746f2075736520496e7465" + + "726e65742d4472616674732061732072" + + "65666572656e6365206d617465726961" + + "6c206f7220746f206369746520746865" + + "6d206f74686572207468616e20617320" + + "2fe2809c776f726b20696e2070726f67" + + "726573732e2fe2809d").HexToByteArray(), + AssociatedData = "f33388860000000000004e91".HexToByteArray(), + Ciphertext = ( + "64a0861575861af460f062c79be643bd" + + "5e805cfd345cf389f108670ac76c8cb2" + + "4c6cfc18755d43eea09ee94e382d26b0" + + "bdb7b73c321b0100d4f03b7f355894cf" + + "332f830e710b97ce98c8a84abd0b9481" + + "14ad176e008d33bd60f982b1ff37c855" + + "9797a06ef4f0ef61c186324e2b350638" + + "3606907b6a7c02b0f9f6157b53c867e4" + + "b9166c767b804d46a59b5216cde7a4e9" + + "9040c5a40433225ee282a1b0a06c523e" + + "af4534d7f83fa1155b0047718cbc546a" + + "0d072b04b3564eea1b422273f548271a" + + "0bb2316053fa76991955ebd63159434e" + + "cebb4e466dae5a1073a6727627097a10" + + "49e617d91d361094fa68f0ff77987130" + + "305beaba2eda04df997b714d6c6f2c29" + + "a6ad5cb4022b02709b").HexToByteArray(), + Tag = "eead9d67890cbb22392336fea1851f38".HexToByteArray() + } + }; + } + + public class ChaCha20Poly1305IsSupportedTests + { + public static bool RuntimeSaysIsNotSupported => !ChaCha20Poly1305.IsSupported; + + [ConditionalFact(nameof(RuntimeSaysIsNotSupported))] + public static void CtorThrowsPNSEIfNotSupported() + { + byte[] key = RandomNumberGenerator.GetBytes(256 / 8); + + Assert.Throws(() => new ChaCha20Poly1305(key)); + Assert.Throws(() => new ChaCha20Poly1305(key.AsSpan())); + } + + [Fact] + public static void CheckIsSupported() + { + bool expectedIsSupported = false; // assume not supported unless environment advertises support + + if (PlatformDetection.IsWindows) + { + // Runtime uses a hardcoded OS version to determine support. + // The test queries the OS directly to ensure our version check is correct. + expectedIsSupported = CngUtility.IsAlgorithmSupported("CHACHA20_POLY1305"); + } + + Assert.Equal(expectedIsSupported, ChaCha20Poly1305.IsSupported); + } + } +} diff --git a/src/libraries/System.Security.Cryptography.Algorithms/tests/CngUtility.cs b/src/libraries/System.Security.Cryptography.Algorithms/tests/CngUtility.cs new file mode 100644 index 00000000000..e22d6ae8e78 --- /dev/null +++ b/src/libraries/System.Security.Cryptography.Algorithms/tests/CngUtility.cs @@ -0,0 +1,56 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.InteropServices; +using Xunit; + +namespace System.Security.Cryptography.Algorithms.Tests +{ + internal static class CngUtility + { + private const string BCRYPT_LIB = "bcrypt.dll"; + private const string MS_PRIMITIVE_PROVIDER = "Microsoft Primitive Provider"; + + public static bool IsAlgorithmSupported(string algId, string implementation = MS_PRIMITIVE_PROVIDER) + { + Assert.True(PlatformDetection.IsWindows, "Caller should not invoke this method for non-Windows platforms."); + + int ntStatus = BCryptOpenAlgorithmProvider(out SafeBCryptAlgorithmHandle handle, algId, implementation, 0); + bool isSupported = ntStatus == 0 && handle != null && !handle.IsInvalid; + handle?.Dispose(); + return isSupported; + } + + // https://docs.microsoft.com/windows/win32/api/bcrypt/nf-bcrypt-bcryptclosealgorithmprovider + [DllImport(BCRYPT_LIB, CallingConvention = CallingConvention.Winapi)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + private static extern int BCryptCloseAlgorithmProvider( + [In] IntPtr hAlgorithm, + [In] uint dwFlags); + + // https://docs.microsoft.com/windows/win32/api/bcrypt/nf-bcrypt-bcryptopenalgorithmprovider + [DllImport(BCRYPT_LIB, CallingConvention = CallingConvention.Winapi)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + private static extern int BCryptOpenAlgorithmProvider( + [Out] out SafeBCryptAlgorithmHandle phAlgorithm, + [In, MarshalAs(UnmanagedType.LPWStr)] string pszAlgId, + [In, MarshalAs(UnmanagedType.LPWStr)] string pszImplementation, + [In] uint dwFlags); + + internal sealed class SafeBCryptAlgorithmHandle : SafeHandle + { + public SafeBCryptAlgorithmHandle() + : base(IntPtr.Zero, ownsHandle: true) + { + } + + public override bool IsInvalid => handle == IntPtr.Zero; + + protected sealed override bool ReleaseHandle() + { + int ntStatus = BCryptCloseAlgorithmProvider(handle, 0); + return ntStatus == 0; + } + } + } +} diff --git a/src/libraries/System.Security.Cryptography.Algorithms/tests/AesAEADTests.cs b/src/libraries/System.Security.Cryptography.Algorithms/tests/CommonAEADTests.cs similarity index 63% rename from src/libraries/System.Security.Cryptography.Algorithms/tests/AesAEADTests.cs rename to src/libraries/System.Security.Cryptography.Algorithms/tests/CommonAEADTests.cs index ba8d60b11ab..eb774ecd879 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/tests/AesAEADTests.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/tests/CommonAEADTests.cs @@ -5,8 +5,35 @@ using System.Collections.Generic; namespace System.Security.Cryptography.Algorithms.Tests { - public abstract class AesAEADTests + public abstract class CommonAEADTests { + public static IEnumerable EncryptTamperAADDecryptTestInputs() + { + // yield return { dataLength, additionalDataLength }; + yield return new object[] { 0, 1 }; + yield return new object[] { 0, 30 }; + yield return new object[] { 1, 1 }; + yield return new object[] { 1, 100 }; + yield return new object[] { 7, 12 }; + yield return new object[] { 16, 16 }; + yield return new object[] { 17, 29 }; + yield return new object[] { 32, 7 }; + yield return new object[] { 41, 25 }; + yield return new object[] { 48, 22 }; + yield return new object[] { 50, 5 }; + } + + public static IEnumerable PlaintextAndCiphertextSizeDifferTestInputs() + { + // yield return { ptLen, ctLen }; + yield return new object[] { 0, 1 }; + yield return new object[] { 1, 0 }; + yield return new object[] { 3, 4 }; + yield return new object[] { 4, 3 }; + yield return new object[] { 20, 120 }; + yield return new object[] { 120, 20 }; + } + protected static bool MatchesKeySizes(int size, KeySizes keySizes) { if (size < keySizes.MinSize || size > keySizes.MaxSize) diff --git a/src/libraries/System.Security.Cryptography.Algorithms/tests/System.Security.Cryptography.Algorithms.Tests.csproj b/src/libraries/System.Security.Cryptography.Algorithms/tests/System.Security.Cryptography.Algorithms.Tests.csproj index 77ce3aa220d..c70b83e36a9 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/tests/System.Security.Cryptography.Algorithms.Tests.csproj +++ b/src/libraries/System.Security.Cryptography.Algorithms/tests/System.Security.Cryptography.Algorithms.Tests.csproj @@ -236,9 +236,11 @@ Link="CommonTest\System\Security\Cryptography\AlgorithmImplementations\RSA\RSASignatureFormatter.cs" /> - + + + -- 2.34.1