From ef465c18a3e819c5e1522d28f43827d1c3bf56ac Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Fri, 19 Apr 2024 10:28:09 -0400 Subject: [PATCH] SHAKE Read, Reset, and Clone --- .../Interop.Evp.cs | 15 ++ .../Interop.EVP.cs | 22 ++ .../ref/System.Security.Cryptography.cs | 8 + .../src/Resources/Strings.resx | 3 + .../Security/Cryptography/LiteHash.Apple.cs | 2 + .../Security/Cryptography/LiteHash.Browser.cs | 2 + .../Security/Cryptography/LiteHash.Unix.cs | 18 ++ .../Security/Cryptography/LiteHash.Windows.cs | 77 +++--- .../System/Security/Cryptography/Shake128.cs | 110 +++++++- .../System/Security/Cryptography/Shake256.cs | 110 +++++++- .../tests/Shake128Tests.cs | 4 + .../tests/Shake256Tests.cs | 4 + .../tests/ShakeTestDriver.cs | 238 +++++++++++++++++- .../configure.cmake | 5 + .../entrypoints.c | 2 + .../opensslshim.h | 9 + .../pal_crypto_config.h.in | 1 + .../pal_evp.c | 29 ++- .../pal_evp.h | 17 ++ 19 files changed, 639 insertions(+), 37 deletions(-) diff --git a/src/libraries/Common/src/Interop/Android/System.Security.Cryptography.Native.Android/Interop.Evp.cs b/src/libraries/Common/src/Interop/Android/System.Security.Cryptography.Native.Android/Interop.Evp.cs index e94a88249a1d0..59e93080c562e 100644 --- a/src/libraries/Common/src/Interop/Android/System.Security.Cryptography.Native.Android/Interop.Evp.cs +++ b/src/libraries/Common/src/Interop/Android/System.Security.Cryptography.Native.Android/Interop.Evp.cs @@ -56,6 +56,21 @@ internal static unsafe int EvpDigestCurrentXOF(SafeEvpMdCtxHandle ctx, Span destination) + { + _ = ctx; + _ = destination; + Debug.Fail("Should have validated that XOF is not supported before getting here."); + throw new UnreachableException(); + } + internal static readonly int EVP_MAX_MD_SIZE = GetMaxMdSize(); } } diff --git a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EVP.cs b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EVP.cs index 088ee79ab0258..f9fd00f227f4e 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EVP.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EVP.cs @@ -9,6 +9,9 @@ internal static partial class Interop { internal static partial class Crypto { + [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpMdCtxCopyEx")] + internal static partial SafeEvpMdCtxHandle EvpMdCtxCopyEx(SafeEvpMdCtxHandle ctx); + [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpMdCtxCreate")] internal static partial SafeEvpMdCtxHandle EvpMdCtxCreate(IntPtr type); @@ -18,6 +21,13 @@ internal static partial class Crypto [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpDigestReset")] internal static partial int EvpDigestReset(SafeEvpMdCtxHandle ctx, IntPtr type); + [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpDigestSqueeze")] + private static partial int EvpDigestSqueeze( + SafeEvpMdCtxHandle ctx, + Span md, + uint len, + [MarshalAs(UnmanagedType.Bool)] out bool haveFeature); + [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpDigestUpdate")] internal static partial int EvpDigestUpdate(SafeEvpMdCtxHandle ctx, ReadOnlySpan d, int cnt); @@ -89,6 +99,18 @@ internal static unsafe int EvpDigestXOFOneShot(IntPtr type, ReadOnlySpan s return EvpDigestXOFOneShot(type, source, source.Length, destination, (uint)destination.Length); } + internal static int EvpDigestSqueeze(SafeEvpMdCtxHandle ctx, Span destination) + { + int ret = EvpDigestSqueeze(ctx, destination, (uint)destination.Length, out bool haveFeature); + + if (!haveFeature) + { + throw new PlatformNotSupportedException(); + } + + return ret; + } + internal static readonly int EVP_MAX_MD_SIZE = GetMaxMdSize(); } } diff --git a/src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs b/src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs index a6ee388c8ceca..4bd007d9030c3 100644 --- a/src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs +++ b/src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs @@ -2587,6 +2587,7 @@ public Shake128() { } public static bool IsSupported { get { throw null; } } public void AppendData(byte[] data) { } public void AppendData(System.ReadOnlySpan data) { } + public System.Security.Cryptography.Shake128 Clone() { throw null; } public void Dispose() { } public byte[] GetCurrentHash(int outputLength) { throw null; } public void GetCurrentHash(System.Span destination) { } @@ -2599,6 +2600,9 @@ public static void HashData(System.IO.Stream source, System.Span destinati public static void HashData(System.ReadOnlySpan source, System.Span destination) { } public static System.Threading.Tasks.ValueTask HashDataAsync(System.IO.Stream source, int outputLength, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public static System.Threading.Tasks.ValueTask HashDataAsync(System.IO.Stream source, System.Memory destination, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + public byte[] Read(int outputLength) { throw null; } + public void Read(System.Span destination) { } + public void Reset() { } } public sealed partial class Shake256 : System.IDisposable { @@ -2606,6 +2610,7 @@ public Shake256() { } public static bool IsSupported { get { throw null; } } public void AppendData(byte[] data) { } public void AppendData(System.ReadOnlySpan data) { } + public System.Security.Cryptography.Shake256 Clone() { throw null; } public void Dispose() { } public byte[] GetCurrentHash(int outputLength) { throw null; } public void GetCurrentHash(System.Span destination) { } @@ -2618,6 +2623,9 @@ public static void HashData(System.IO.Stream source, System.Span destinati public static void HashData(System.ReadOnlySpan source, System.Span destination) { } public static System.Threading.Tasks.ValueTask HashDataAsync(System.IO.Stream source, int outputLength, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public static System.Threading.Tasks.ValueTask HashDataAsync(System.IO.Stream source, System.Memory destination, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + public byte[] Read(int outputLength) { throw null; } + public void Read(System.Span destination) { } + public void Reset() { } } public partial class SignatureDescription { diff --git a/src/libraries/System.Security.Cryptography/src/Resources/Strings.resx b/src/libraries/System.Security.Cryptography/src/Resources/Strings.resx index b121f8fd75c2d..05c91987126a7 100644 --- a/src/libraries/System.Security.Cryptography/src/Resources/Strings.resx +++ b/src/libraries/System.Security.Cryptography/src/Resources/Strings.resx @@ -780,6 +780,9 @@ The algorithm's block size is not supported. + + This operation is not supported once the algorithm has started reading. + CryptoApi ECDsa keys are not supported. diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/LiteHash.Apple.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/LiteHash.Apple.cs index 0b2fde720a20d..1dbfd5567f02e 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/LiteHash.Apple.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/LiteHash.Apple.cs @@ -39,6 +39,8 @@ internal static LiteXof CreateXof(string hashAlgorithmId) public int Finalize(Span destination) => throw new UnreachableException(); public void Current(Span destination) => throw new UnreachableException(); public int Reset() => throw new UnreachableException(); + public LiteXof Clone() => throw new UnreachableException(); + public void Read(Span destination) => throw new UnreachableException(); public void Dispose() => throw new UnreachableException(); #pragma warning restore IDE0060 #pragma warning restore CA1822 diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/LiteHash.Browser.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/LiteHash.Browser.cs index 92de5db403f3d..5591479f0c794 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/LiteHash.Browser.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/LiteHash.Browser.cs @@ -34,6 +34,8 @@ internal static LiteXof CreateXof(string hashAlgorithmId) public int Finalize(Span destination) => throw new UnreachableException(); public void Current(Span destination) => throw new UnreachableException(); public int Reset() => throw new UnreachableException(); + public LiteXof Clone() => throw new UnreachableException(); + public void Read(Span destination) => throw new UnreachableException(); public void Dispose() => throw new UnreachableException(); #pragma warning restore IDE0060 #pragma warning restore CA1822 diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/LiteHash.Unix.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/LiteHash.Unix.cs index ab09b7217f89a..32ebdb5ce092c 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/LiteHash.Unix.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/LiteHash.Unix.cs @@ -44,6 +44,12 @@ internal LiteXof(IntPtr algorithm) Interop.Crypto.CheckValidOpenSslHandle(_ctx); } + private LiteXof(SafeEvpMdCtxHandle ctx, IntPtr algorithm) + { + _ctx = ctx; + _algorithm = algorithm; + } + public void Append(ReadOnlySpan data) { if (data.IsEmpty) @@ -70,6 +76,18 @@ public void Current(Span destination) Check(Interop.Crypto.EvpDigestCurrentXOF(_ctx, destination)); } + public LiteXof Clone() + { + SafeEvpMdCtxHandle clone = Interop.Crypto.EvpMdCtxCopyEx(_ctx); + Interop.Crypto.CheckValidOpenSslHandle(clone); + return new LiteXof(clone, _algorithm); + } + + public void Read(Span destination) + { + Check(Interop.Crypto.EvpDigestSqueeze(_ctx, destination)); + } + public void Dispose() { _ctx.Dispose(); diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/LiteHash.Windows.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/LiteHash.Windows.cs index bb3df4bb2f4c5..324001daee995 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/LiteHash.Windows.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/LiteHash.Windows.cs @@ -159,10 +159,11 @@ private static void CheckStatus(NTSTATUS status) } } - internal struct LiteXof : ILiteHash + internal readonly struct LiteXof : ILiteHash { + private const int BCRYPT_HASH_DONT_RESET_FLAG = 0x00000001; private readonly nuint _algorithm; - private SafeBCryptHashHandle _hashHandle; + private readonly SafeBCryptHashHandle _hashHandle; internal LiteXof(string algorithm) { @@ -173,7 +174,24 @@ internal LiteXof(string algorithm) _ => throw FailThrow(algorithm), }; - Reset(); + SafeBCryptHashHandle hashHandle; + + NTSTATUS ntStatus = Interop.BCrypt.BCryptCreateHash( + _algorithm, + out hashHandle, + pbHashObject: IntPtr.Zero, + cbHashObject: 0, + secret: ReadOnlySpan.Empty, + cbSecret: 0, + BCryptCreateHashFlags.None); + + if (ntStatus != NTSTATUS.STATUS_SUCCESS) + { + hashHandle.Dispose(); + throw Interop.BCrypt.CreateCryptographicException(ntStatus); + } + + _hashHandle = hashHandle; static Exception FailThrow(string algorithm) { @@ -182,7 +200,13 @@ static Exception FailThrow(string algorithm) } } - public readonly int HashSizeInBytes + private LiteXof(SafeBCryptHashHandle hashHandle, nuint algorithm) + { + _algorithm = algorithm; + _hashHandle = hashHandle; + } + + public int HashSizeInBytes { get { @@ -191,7 +215,7 @@ public readonly int HashSizeInBytes } } - public readonly void Append(ReadOnlySpan data) + public void Append(ReadOnlySpan data) { if (data.IsEmpty) { @@ -206,7 +230,7 @@ public readonly void Append(ReadOnlySpan data) } } - public readonly unsafe int Finalize(Span destination) + public unsafe int Finalize(Span destination) { fixed (byte* pDestination = &Helpers.GetNonNullPinnableReference(destination)) { @@ -221,36 +245,33 @@ public readonly unsafe int Finalize(Span destination) } } - [MemberNotNull(nameof(_hashHandle))] - public void Reset() - { - _hashHandle?.Dispose(); - SafeBCryptHashHandle hashHandle; - - NTSTATUS ntStatus = Interop.BCrypt.BCryptCreateHash( - _algorithm, - out hashHandle, - pbHashObject: IntPtr.Zero, - cbHashObject: 0, - secret: ReadOnlySpan.Empty, - cbSecret: 0, - BCryptCreateHashFlags.None); + public void Reset() => Finalize(Span.Empty); - if (ntStatus != NTSTATUS.STATUS_SUCCESS) + public unsafe void Current(Span destination) + { + using (SafeBCryptHashHandle tmpHash = Interop.BCrypt.BCryptDuplicateHash(_hashHandle)) + fixed (byte* pDestination = &Helpers.GetNonNullPinnableReference(destination)) { - hashHandle.Dispose(); - throw Interop.BCrypt.CreateCryptographicException(ntStatus); + NTSTATUS ntStatus = Interop.BCrypt.BCryptFinishHash(tmpHash, pDestination, destination.Length, dwFlags: 0); + + if (ntStatus != NTSTATUS.STATUS_SUCCESS) + { + throw Interop.BCrypt.CreateCryptographicException(ntStatus); + } } + } - _hashHandle = hashHandle; + public LiteXof Clone() + { + SafeBCryptHashHandle clone = Interop.BCrypt.BCryptDuplicateHash(_hashHandle); + return new LiteXof(clone, _algorithm); } - public readonly unsafe void Current(Span destination) + public unsafe void Read(Span destination) { - using (SafeBCryptHashHandle tmpHash = Interop.BCrypt.BCryptDuplicateHash(_hashHandle)) fixed (byte* pDestination = &Helpers.GetNonNullPinnableReference(destination)) { - NTSTATUS ntStatus = Interop.BCrypt.BCryptFinishHash(tmpHash, pDestination, destination.Length, dwFlags: 0); + NTSTATUS ntStatus = Interop.BCrypt.BCryptFinishHash(_hashHandle, pDestination, destination.Length, dwFlags: BCRYPT_HASH_DONT_RESET_FLAG); if (ntStatus != NTSTATUS.STATUS_SUCCESS) { @@ -259,7 +280,7 @@ public readonly unsafe void Current(Span destination) } } - public readonly void Dispose() + public void Dispose() { _hashHandle.Dispose(); } diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/Shake128.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/Shake128.cs index 14dc8e0eac407..80740c59a657d 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/Shake128.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/Shake128.cs @@ -19,10 +19,10 @@ namespace System.Security.Cryptography /// public sealed partial class Shake128 : IDisposable { - // Some platforms have a mutable struct for LiteXof, do not mark this field as readonly. - private LiteXof _hashProvider; + private readonly LiteXof _hashProvider; private bool _disposed; private ConcurrencyBlock _block; + private bool _reading; /// /// Initializes a new instance of the class. @@ -37,6 +37,11 @@ public Shake128() _hashProvider = LiteHashProvider.CreateXof(HashAlgorithmId); } + internal Shake128(LiteXof hashProvider) + { + _hashProvider = hashProvider; + } + /// /// Gets a value that indicates whether the algorithm is supported on the current platform. /// @@ -71,6 +76,7 @@ public void AppendData(ReadOnlySpan data) using (ConcurrencyBlock.Enter(ref _block)) { + CheckReading(); _hashProvider.Append(data); } } @@ -93,6 +99,7 @@ public byte[] GetHashAndReset(int outputLength) using (ConcurrencyBlock.Enter(ref _block)) { + CheckReading(); byte[] output = new byte[outputLength]; _hashProvider.Finalize(output); _hashProvider.Reset(); @@ -113,6 +120,7 @@ public void GetHashAndReset(Span destination) using (ConcurrencyBlock.Enter(ref _block)) { + CheckReading(); _hashProvider.Finalize(destination); _hashProvider.Reset(); } @@ -136,6 +144,7 @@ public byte[] GetCurrentHash(int outputLength) using (ConcurrencyBlock.Enter(ref _block)) { + CheckReading(); byte[] output = new byte[outputLength]; _hashProvider.Current(output); return output; @@ -155,10 +164,99 @@ public void GetCurrentHash(Span destination) using (ConcurrencyBlock.Enter(ref _block)) { + CheckReading(); _hashProvider.Current(destination); } } + /// + /// Retrieves the hash for the data accumulated from prior calls to the AppendData methods without + /// resetting the object to its initial state and allowing additional calls to continue retrieving the hash. + /// + /// The size of the hash to produce. + /// The computed hash. + /// + /// is negative. + /// + /// An error has occurred during the operation. + /// The object has already been disposed. + /// + /// The platform does not support multiple reads of the hash. can be used + /// to perform a single operation. + /// + public byte[] Read(int outputLength) + { + ArgumentOutOfRangeException.ThrowIfNegative(outputLength); + CheckDisposed(); + + using (ConcurrencyBlock.Enter(ref _block)) + { + byte[] output = new byte[outputLength]; + _hashProvider.Read(output); + _reading = true; + return output; + } + } + + /// + /// Fills the buffer with the hash for the data accumulated from prior calls to the AppendData methods without + /// resetting the object to its initial state and allowing additional calls to continue retrieving the hash. + /// + /// The buffer to fill with the hash. + /// An error has occurred during the operation. + /// The object has already been disposed. + /// + /// The platform does not support multiple reads of the hash. can be used + /// to perform a single operation. + /// + public void Read(Span destination) + { + CheckDisposed(); + + using (ConcurrencyBlock.Enter(ref _block)) + { + _hashProvider.Read(destination); + _reading = true; + } + } + + /// + /// Resets the instance back to its initial state. + /// + /// An error has occurred during the operation. + /// The object has already been disposed. + public void Reset() + { + CheckDisposed(); + + using (ConcurrencyBlock.Enter(ref _block)) + { + _hashProvider.Reset(); + _reading = false; + } + } + + /// + /// Creates a new instance of with the existing appended data preserved. + /// + /// A clone of the current instance. + /// An error has occurred during the operation. + /// + /// The current instance is being read from and cannot be cloned. + /// + /// The object has already been disposed. + public Shake128 Clone() + { + CheckDisposed(); + + using (ConcurrencyBlock.Enter(ref _block)) + { + CheckReading(); + LiteXof clone = _hashProvider.Clone(); + return new Shake128(clone); + } + } + /// /// Release all resources used by the current instance of the class. /// @@ -385,5 +483,13 @@ private static void CheckPlatformSupport() } private void CheckDisposed() => ObjectDisposedException.ThrowIf(_disposed, this); + + private void CheckReading() + { + if (_reading) + { + throw new InvalidOperationException(SR.InvalidOperation_AlreadyReading); + } + } } } diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/Shake256.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/Shake256.cs index c801adde9395c..d861257cf1e48 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/Shake256.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/Shake256.cs @@ -19,10 +19,10 @@ namespace System.Security.Cryptography /// public sealed partial class Shake256 : IDisposable { - // Some platforms have a mutable struct for LiteXof, do not mark this field as readonly. - private LiteXof _hashProvider; + private readonly LiteXof _hashProvider; private bool _disposed; private ConcurrencyBlock _block; + private bool _reading; /// /// Initializes a new instance of the class. @@ -37,6 +37,11 @@ public Shake256() _hashProvider = LiteHashProvider.CreateXof(HashAlgorithmId); } + internal Shake256(LiteXof hashProvider) + { + _hashProvider = hashProvider; + } + /// /// Gets a value that indicates whether the algorithm is supported on the current platform. /// @@ -71,6 +76,7 @@ public void AppendData(ReadOnlySpan data) using (ConcurrencyBlock.Enter(ref _block)) { + CheckReading(); _hashProvider.Append(data); } } @@ -93,6 +99,7 @@ public byte[] GetHashAndReset(int outputLength) using (ConcurrencyBlock.Enter(ref _block)) { + CheckReading(); byte[] output = new byte[outputLength]; _hashProvider.Finalize(output); _hashProvider.Reset(); @@ -113,6 +120,7 @@ public void GetHashAndReset(Span destination) using (ConcurrencyBlock.Enter(ref _block)) { + CheckReading(); _hashProvider.Finalize(destination); _hashProvider.Reset(); } @@ -136,6 +144,7 @@ public byte[] GetCurrentHash(int outputLength) using (ConcurrencyBlock.Enter(ref _block)) { + CheckReading(); byte[] output = new byte[outputLength]; _hashProvider.Current(output); return output; @@ -155,10 +164,99 @@ public void GetCurrentHash(Span destination) using (ConcurrencyBlock.Enter(ref _block)) { + CheckReading(); _hashProvider.Current(destination); } } + /// + /// Retrieves the hash for the data accumulated from prior calls to the AppendData methods without + /// resetting the object to its initial state and allowing additional calls to continue retrieving the hash. + /// + /// The size of the hash to produce. + /// The computed hash. + /// + /// is negative. + /// + /// An error has occurred during the operation. + /// The object has already been disposed. + /// + /// The platform does not support multiple reads of the hash. can be used + /// to perform a single operation. + /// + public byte[] Read(int outputLength) + { + ArgumentOutOfRangeException.ThrowIfNegative(outputLength); + CheckDisposed(); + + using (ConcurrencyBlock.Enter(ref _block)) + { + byte[] output = new byte[outputLength]; + _hashProvider.Read(output); + _reading = true; + return output; + } + } + + /// + /// Fills the buffer with the hash for the data accumulated from prior calls to the AppendData methods without + /// resetting the object to its initial state and allowing additional calls to continue retrieving the hash. + /// + /// The buffer to fill with the hash. + /// An error has occurred during the operation. + /// The object has already been disposed. + /// + /// The platform does not support multiple reads of the hash. can be used + /// to perform a single operation. + /// + public void Read(Span destination) + { + CheckDisposed(); + + using (ConcurrencyBlock.Enter(ref _block)) + { + _hashProvider.Read(destination); + _reading = true; + } + } + + /// + /// Resets the instance back to its initial state. + /// + /// An error has occurred during the operation. + /// The object has already been disposed. + public void Reset() + { + CheckDisposed(); + + using (ConcurrencyBlock.Enter(ref _block)) + { + _hashProvider.Reset(); + _reading = false; + } + } + + /// + /// Creates a new instance of with the existing appended data preserved. + /// + /// A clone of the current instance. + /// An error has occurred during the operation. + /// + /// The current instance is being read from and cannot be cloned. + /// + /// The object has already been disposed. + public Shake256 Clone() + { + CheckDisposed(); + + using (ConcurrencyBlock.Enter(ref _block)) + { + CheckReading(); + LiteXof clone = _hashProvider.Clone(); + return new Shake256(clone); + } + } + /// /// Release all resources used by the current instance of the class. /// @@ -385,5 +483,13 @@ private static void CheckPlatformSupport() } private void CheckDisposed() => ObjectDisposedException.ThrowIf(_disposed, this); + + private void CheckReading() + { + if (_reading) + { + throw new InvalidOperationException(SR.InvalidOperation_AlreadyReading); + } + } } } diff --git a/src/libraries/System.Security.Cryptography/tests/Shake128Tests.cs b/src/libraries/System.Security.Cryptography/tests/Shake128Tests.cs index e203d77f46b6c..8cfa95b57621e 100644 --- a/src/libraries/System.Security.Cryptography/tests/Shake128Tests.cs +++ b/src/libraries/System.Security.Cryptography/tests/Shake128Tests.cs @@ -21,6 +21,10 @@ public class Traits : IShakeTrait public static void GetHashAndReset(Shake128 shake, Span destination) => shake.GetHashAndReset(destination); public static byte[] GetCurrentHash(Shake128 shake, int outputLength) => shake.GetCurrentHash(outputLength); public static void GetCurrentHash(Shake128 shake, Span destination) => shake.GetCurrentHash(destination); + public static void Read(Shake128 shake, Span destination) => shake.Read(destination); + public static byte[] Read(Shake128 shake, int outputLength) => shake.Read(outputLength); + public static void Reset(Shake128 shake) => shake.Reset(); + public static Shake128 Clone(Shake128 shake) => shake.Clone(); public static byte[] HashData(byte[] source, int outputLength) => Shake128.HashData(source, outputLength); public static byte[] HashData(ReadOnlySpan source, int outputLength) => Shake128.HashData(source, outputLength); diff --git a/src/libraries/System.Security.Cryptography/tests/Shake256Tests.cs b/src/libraries/System.Security.Cryptography/tests/Shake256Tests.cs index 9631752e69444..23886ccb2b2dc 100644 --- a/src/libraries/System.Security.Cryptography/tests/Shake256Tests.cs +++ b/src/libraries/System.Security.Cryptography/tests/Shake256Tests.cs @@ -21,6 +21,10 @@ public class Traits : IShakeTrait public static void GetHashAndReset(Shake256 shake, Span destination) => shake.GetHashAndReset(destination); public static byte[] GetCurrentHash(Shake256 shake, int outputLength) => shake.GetCurrentHash(outputLength); public static void GetCurrentHash(Shake256 shake, Span destination) => shake.GetCurrentHash(destination); + public static void Read(Shake256 shake, Span destination) => shake.Read(destination); + public static byte[] Read(Shake256 shake, int outputLength) => shake.Read(outputLength); + public static void Reset(Shake256 shake) => shake.Reset(); + public static Shake256 Clone(Shake256 shake) => shake.Clone(); public static byte[] HashData(byte[] source, int outputLength) => Shake256.HashData(source, outputLength); public static byte[] HashData(ReadOnlySpan source, int outputLength) => Shake256.HashData(source, outputLength); diff --git a/src/libraries/System.Security.Cryptography/tests/ShakeTestDriver.cs b/src/libraries/System.Security.Cryptography/tests/ShakeTestDriver.cs index d0c25b1722eea..658d1b83a80d2 100644 --- a/src/libraries/System.Security.Cryptography/tests/ShakeTestDriver.cs +++ b/src/libraries/System.Security.Cryptography/tests/ShakeTestDriver.cs @@ -12,7 +12,7 @@ namespace System.Security.Cryptography.Tests { - public interface IShakeTrait where TShake : IDisposable, new() + public interface IShakeTrait where TShake : class, IDisposable, new() { static abstract TShake Create(); static abstract bool IsSupported { get; } @@ -22,6 +22,10 @@ namespace System.Security.Cryptography.Tests static abstract void GetHashAndReset(TShake shake, Span destination); static abstract byte[] GetCurrentHash(TShake shake, int outputLength); static abstract void GetCurrentHash(TShake shake, Span destination); + static abstract void Read(TShake shake, Span destination); + static abstract byte[] Read(TShake shake, int outputLength); + static abstract void Reset(TShake shake); + static abstract TShake Clone(TShake shake); static abstract byte[] HashData(byte[] source, int outputLength); static abstract byte[] HashData(ReadOnlySpan source, int outputLength); @@ -37,12 +41,21 @@ namespace System.Security.Cryptography.Tests public abstract class ShakeTestDriver where TShakeTrait : IShakeTrait - where TShake : IDisposable, new() + where TShake : class, IDisposable, new() { protected abstract IEnumerable<(string Msg, string Output)> Fips202Kats { get; } public static bool IsSupported => TShakeTrait.IsSupported; public static bool IsNotSupported => !IsSupported; + public static bool IsReadSupported + { + get + { + const long OpenSsl_3_2_0 = 0x30200000L; + return IsSupported && (PlatformDetection.IsWindows || SafeEvpPKeyHandle.OpenSslVersion >= OpenSsl_3_2_0); + } + } + [ConditionalFact(nameof(IsSupported))] public void KnownAnswerTests_Allocated_AllAtOnce() { @@ -151,6 +164,113 @@ public void KnownAnswerTests_Allocated_Hash_Destination() } } + [ConditionalFact(nameof(IsReadSupported))] + public void KnownAnswerTests_Allocated_Read_Twice() + { + foreach ((string Msg, string Output) kat in Fips202Kats) + { + byte[] message = Convert.FromHexString(kat.Msg); + + using (TShake shake = new TShake()) + { + TShakeTrait.AppendData(shake, message); + Span hash = new byte[kat.Output.Length / 2]; + ReadChunked(shake, hash); + Assert.Equal(kat.Output, Convert.ToHexString(hash), ignoreCase: true); + + TShakeTrait.Reset(shake); + hash.Clear(); + + TShakeTrait.AppendData(shake, message); + ReadChunked(shake, hash); + Assert.Equal(kat.Output, Convert.ToHexString(hash), ignoreCase: true); + } + } + } + + [ConditionalFact(nameof(IsReadSupported))] + public void KnownAnswerTests_Allocated_Read_GetHashAndReset() + { + foreach ((string Msg, string Output) kat in Fips202Kats) + { + byte[] message = Convert.FromHexString(kat.Msg); + + using (TShake shake = new TShake()) + { + TShakeTrait.AppendData(shake, message); + Span hash = new byte[kat.Output.Length / 2]; + ReadChunked(shake, hash); + Assert.Equal(kat.Output, Convert.ToHexString(hash), ignoreCase: true); + + TShakeTrait.Reset(shake); + hash.Clear(); + + TShakeTrait.AppendData(shake, message); + TShakeTrait.GetHashAndReset(shake, hash); + Assert.Equal(kat.Output, Convert.ToHexString(hash), ignoreCase: true); + } + } + } + + [ConditionalFact(nameof(IsSupported))] + public void KnownAnswerTests_Clone_Independent_Unobserved() + { + foreach ((string Msg, string Output) kat in Fips202Kats) + { + byte[] message = Convert.FromHexString(kat.Msg); + byte[] hash = new byte[kat.Output.Length / 2]; + + using (TShake shake = new TShake()) + using (TShake clone = TShakeTrait.Clone(shake)) + { + TShakeTrait.AppendData(shake, "badbadbad"u8); + + TShakeTrait.AppendData(clone, message); + TShakeTrait.GetCurrentHash(clone, hash); + Assert.Equal(kat.Output, Convert.ToHexString(hash), ignoreCase: true); + } + } + } + + [ConditionalFact(nameof(IsSupported))] + public void KnownAnswerTests_Clone_Independent_Disposed() + { + foreach ((string Msg, string Output) kat in Fips202Kats) + { + byte[] message = Convert.FromHexString(kat.Msg); + byte[] hash = new byte[kat.Output.Length / 2]; + + TShake shake = new TShake(); + using (TShake clone = TShakeTrait.Clone(shake)) + { + shake.Dispose(); + + TShakeTrait.AppendData(clone, message); + TShakeTrait.GetCurrentHash(clone, hash); + Assert.Equal(kat.Output, Convert.ToHexString(hash), ignoreCase: true); + } + } + } + + [ConditionalFact(nameof(IsSupported))] + public void KnownAnswerTests_Reset() + { + foreach ((string Msg, string Output) kat in Fips202Kats) + { + byte[] message = Convert.FromHexString(kat.Msg); + byte[] hash = new byte[kat.Output.Length / 2]; + + using (TShake shake = new TShake()) + { + TShakeTrait.AppendData(shake, "badbadbad"u8); + TShakeTrait.Reset(shake); + TShakeTrait.AppendData(shake, message); + TShakeTrait.GetCurrentHash(shake, hash); + Assert.Equal(kat.Output, Convert.ToHexString(hash), ignoreCase: true); + } + } + } + [ConditionalFact(nameof(IsSupported))] public void KnownAnswerTests_OneShot_HashData_ByteArray() { @@ -515,6 +635,10 @@ public void ArgValidation_Allocated_UseAfterDispose() Assert.Throws(() => TShakeTrait.GetHashAndReset(shake, buffer.AsSpan())); Assert.Throws(() => TShakeTrait.GetCurrentHash(shake, outputLength: 1)); Assert.Throws(() => TShakeTrait.GetCurrentHash(shake, buffer.AsSpan())); + Assert.Throws(() => TShakeTrait.Clone(shake)); + Assert.Throws(() => TShakeTrait.Reset(shake)); + Assert.Throws(() => TShakeTrait.Read(shake, buffer.AsSpan())); + Assert.Throws(() => TShakeTrait.Read(shake, outputLength: 1)); } [ConditionalFact(nameof(IsNotSupported))] @@ -539,6 +663,103 @@ public void IsSupported_AgreesWithPlatform() Assert.Equal(TShakeTrait.IsSupported, PlatformDetection.SupportsSha3); } + [ConditionalFact(nameof(IsSupported))] + public void Clone_DifferentInstance() + { + using (TShake shake = new TShake()) + using (TShake clone = TShakeTrait.Clone(shake)) + { + Assert.NotSame(shake, clone); + } + } + + [ConditionalFact(nameof(IsReadSupported))] + public void Read_MixedAppendAfterRead() + { + using (TShake shake = new TShake()) + { + TShakeTrait.Read(shake, Span.Empty); + + Assert.Throws(() => TShakeTrait.AppendData(shake, ReadOnlySpan.Empty)); + Assert.Throws(() => TShakeTrait.AppendData(shake, Array.Empty())); + + TShakeTrait.Reset(shake); + + // Assert.NoThrow + TShakeTrait.AppendData(shake, ReadOnlySpan.Empty); + TShakeTrait.AppendData(shake, Array.Empty()); + } + } + + [ConditionalFact(nameof(IsReadSupported))] + public void Read_MixedCloneAfterRead() + { + using (TShake shake = new TShake()) + { + TShakeTrait.Read(shake, Span.Empty); + Assert.Throws(() => TShakeTrait.Clone(shake)); + + TShakeTrait.Reset(shake); + + using (TShake clone = TShakeTrait.Clone(shake)) + { + Assert.NotNull(clone); + } + } + } + + [ConditionalFact(nameof(IsReadSupported))] + public void Read_MixedGetHashAndReset() + { + using (TShake shake = new TShake()) + { + TShakeTrait.Read(shake, Span.Empty); + Assert.Throws(() => TShakeTrait.GetHashAndReset(shake, Span.Empty)); + Assert.Throws(() => TShakeTrait.GetHashAndReset(shake, outputLength: 0)); + + TShakeTrait.Reset(shake); + + // Assert.NoThrow + TShakeTrait.GetHashAndReset(shake, Span.Empty); + TShakeTrait.GetHashAndReset(shake, outputLength: 0); + } + } + + [ConditionalFact(nameof(IsReadSupported))] + public void Read_MixedGetCurrentHash() + { + using (TShake shake = new TShake()) + { + TShakeTrait.Read(shake, Span.Empty); + + // Cannot GetCurrentHash while reading. + Assert.Throws(() => TShakeTrait.GetCurrentHash(shake, Span.Empty)); + Assert.Throws(() => TShakeTrait.GetCurrentHash(shake, outputLength: 0)); + + TShakeTrait.Reset(shake); + + // Assert.NoThrow + TShakeTrait.GetCurrentHash(shake, Span.Empty); + TShakeTrait.GetCurrentHash(shake, outputLength: 0); + } + } + + [ConditionalFact(nameof(IsSupported))] + public void Read_NotSupported() + { + // This is testing when a TShake can be created, but the platform does not have Read. + if (IsReadSupported) + { + return; + } + + using (TShake shake = new TShake()) + { + Assert.Throws(() => TShakeTrait.Read(shake, Span.Empty)); + Assert.Throws(() => TShakeTrait.Read(shake, outputLength: 0)); + } + } + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] public void GetHashAndReset_ConcurrentUseDoesNotCrashProcess() { @@ -580,5 +801,18 @@ static void ThreadWork(object obj) } } } + + private static void ReadChunked(TShake shake, Span destination) + { + int read = 0; + int outputLength = destination.Length; + + while (read < outputLength) + { + int size = Math.Min(Math.Max(outputLength / 4, 1), outputLength - read); + TShakeTrait.Read(shake, destination.Slice(read, size)); + read += size; + } + } } } diff --git a/src/native/libs/System.Security.Cryptography.Native/configure.cmake b/src/native/libs/System.Security.Cryptography.Native/configure.cmake index 74ed49f5d1916..ea779318c667c 100644 --- a/src/native/libs/System.Security.Cryptography.Native/configure.cmake +++ b/src/native/libs/System.Security.Cryptography.Native/configure.cmake @@ -22,6 +22,11 @@ check_function_exists( HAVE_OPENSSL_SHA3 ) +check_function_exists( + EVP_DigestSqueeze + HAVE_OPENSSL_SHA3_SQUEEZE +) + configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/pal_crypto_config.h.in ${CMAKE_CURRENT_BINARY_DIR}/pal_crypto_config.h) diff --git a/src/native/libs/System.Security.Cryptography.Native/entrypoints.c b/src/native/libs/System.Security.Cryptography.Native/entrypoints.c index 0268221b428b9..a8bcbde96e511 100644 --- a/src/native/libs/System.Security.Cryptography.Native/entrypoints.c +++ b/src/native/libs/System.Security.Cryptography.Native/entrypoints.c @@ -142,6 +142,7 @@ static const Entry s_cryptoNative[] = DllImportEntry(CryptoNative_EvpDigestFinalXOF) DllImportEntry(CryptoNative_EvpDigestOneShot) DllImportEntry(CryptoNative_EvpDigestReset) + DllImportEntry(CryptoNative_EvpDigestSqueeze) DllImportEntry(CryptoNative_EvpDigestUpdate) DllImportEntry(CryptoNative_EvpDigestXOFOneShot) DllImportEntry(CryptoNative_EvpMacFetch) @@ -155,6 +156,7 @@ static const Entry s_cryptoNative[] = DllImportEntry(CryptoNative_EvpMacOneShot) DllImportEntry(CryptoNative_EvpMacReset) DllImportEntry(CryptoNative_EvpMd5) + DllImportEntry(CryptoNative_EvpMdCtxCopyEx) DllImportEntry(CryptoNative_EvpMdCtxCreate) DllImportEntry(CryptoNative_EvpMdCtxDestroy) DllImportEntry(CryptoNative_EvpMdSize) diff --git a/src/native/libs/System.Security.Cryptography.Native/opensslshim.h b/src/native/libs/System.Security.Cryptography.Native/opensslshim.h index b48d39de83cdd..9dd878858822b 100644 --- a/src/native/libs/System.Security.Cryptography.Native/opensslshim.h +++ b/src/native/libs/System.Security.Cryptography.Native/opensslshim.h @@ -178,6 +178,13 @@ const EVP_MD *EVP_shake256(void); int EVP_DigestFinalXOF(EVP_MD_CTX *ctx, unsigned char *md, size_t len); #endif +#if !HAVE_OPENSSL_SHA3_SQUEEZE +#undef HAVE_OPENSSL_SHA3_SQUEEZE +#define HAVE_OPENSSL_SHA3_SQUEEZE 1 +int EVP_DigestSqueeze(EVP_MD_CTX *ctx, unsigned char *out, size_t outlen); +#endif + + #define API_EXISTS(fn) (fn != NULL) // List of all functions from the libssl that are used in the System.Security.Cryptography.Native. @@ -369,6 +376,7 @@ int EVP_DigestFinalXOF(EVP_MD_CTX *ctx, unsigned char *md, size_t len); REQUIRED_FUNCTION(EVP_DigestFinal_ex) \ LIGHTUP_FUNCTION(EVP_DigestFinalXOF) \ REQUIRED_FUNCTION(EVP_DigestInit_ex) \ + LIGHTUP_FUNCTION(EVP_DigestSqueeze) \ REQUIRED_FUNCTION(EVP_DigestUpdate) \ REQUIRED_FUNCTION(EVP_get_digestbyname) \ LIGHTUP_FUNCTION(EVP_MAC_fetch) \ @@ -895,6 +903,7 @@ FOR_ALL_OPENSSL_FUNCTIONS #define EVP_DigestFinal_ex EVP_DigestFinal_ex_ptr #define EVP_DigestFinalXOF EVP_DigestFinalXOF_ptr #define EVP_DigestInit_ex EVP_DigestInit_ex_ptr +#define EVP_DigestSqueeze EVP_DigestSqueeze_ptr #define EVP_DigestUpdate EVP_DigestUpdate_ptr #define EVP_get_digestbyname EVP_get_digestbyname_ptr #define EVP_md5 EVP_md5_ptr diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_crypto_config.h.in b/src/native/libs/System.Security.Cryptography.Native/pal_crypto_config.h.in index d7aef5a7d1b67..c216e88e272e4 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_crypto_config.h.in +++ b/src/native/libs/System.Security.Cryptography.Native/pal_crypto_config.h.in @@ -4,3 +4,4 @@ #cmakedefine01 HAVE_OPENSSL_ALPN #cmakedefine01 HAVE_OPENSSL_CHACHA20POLY1305 #cmakedefine01 HAVE_OPENSSL_SHA3 +#cmakedefine01 HAVE_OPENSSL_SHA3_SQUEEZE diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_evp.c b/src/native/libs/System.Security.Cryptography.Native/pal_evp.c index 7559b4c1970b2..1fcb0fc0058ce 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_evp.c +++ b/src/native/libs/System.Security.Cryptography.Native/pal_evp.c @@ -143,7 +143,7 @@ int32_t CryptoNative_EvpDigestFinalXOF(EVP_MD_CTX* ctx, uint8_t* md, uint32_t le return 0; } -static EVP_MD_CTX* EvpDup(const EVP_MD_CTX* ctx) +EVP_MD_CTX* CryptoNative_EvpMdCtxCopyEx(const EVP_MD_CTX* ctx) { if (ctx == NULL) { @@ -174,7 +174,7 @@ int32_t CryptoNative_EvpDigestCurrent(const EVP_MD_CTX* ctx, uint8_t* md, uint32 { ERR_clear_error(); - EVP_MD_CTX* dup = EvpDup(ctx); + EVP_MD_CTX* dup = CryptoNative_EvpMdCtxCopyEx(ctx); if (dup != NULL) { @@ -190,7 +190,7 @@ int32_t CryptoNative_EvpDigestCurrentXOF(const EVP_MD_CTX* ctx, uint8_t* md, uin { ERR_clear_error(); - EVP_MD_CTX* dup = EvpDup(ctx); + EVP_MD_CTX* dup = CryptoNative_EvpMdCtxCopyEx(ctx); if (dup != NULL) { @@ -262,6 +262,29 @@ int32_t CryptoNative_EvpDigestXOFOneShot(const EVP_MD* type, const void* source, return ret; } +int32_t CryptoNative_EvpDigestSqueeze(EVP_MD_CTX* ctx, uint8_t* md, uint32_t len, int32_t* haveFeature) +{ + ERR_clear_error(); + + if (ctx == NULL || haveFeature == NULL || (md == NULL && len > 0)) + { + return 0; + } + + *haveFeature = 0; + int32_t ret = 0; + +#if HAVE_OPENSSL_SHA3_SQUEEZE + if (API_EXISTS(EVP_DigestSqueeze)) + { + *haveFeature = 1; + ret = EVP_DigestSqueeze(ctx, md, (size_t)len); + } +#endif + + return ret; +} + int32_t CryptoNative_EvpMdSize(const EVP_MD* md) { // No error queue impact. diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_evp.h b/src/native/libs/System.Security.Cryptography.Native/pal_evp.h index f31521ff0e6de..5d8460c7b7d08 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_evp.h +++ b/src/native/libs/System.Security.Cryptography.Native/pal_evp.h @@ -89,6 +89,14 @@ Combines EVP_MD_CTX_create, EVP_DigestUpdate, and EVP_DigestFinalXOF in to a sin */ PALEXPORT int32_t CryptoNative_EvpDigestXOFOneShot(const EVP_MD* type, const void* source, int32_t sourceSize, uint8_t* md, uint32_t len); +/* +Function: +EvpMdCtxCopyEx + +Creates a new EVP_MD_CTX and copies the ctx input using EVP_MD_CTX_copy_ex. Returns NULL on error. +*/ +PALEXPORT EVP_MD_CTX* CryptoNative_EvpMdCtxCopyEx(const EVP_MD_CTX* ctx); + /* Function: EvpMdSize @@ -97,6 +105,15 @@ Direct shim to EVP_MD_size. */ PALEXPORT int32_t CryptoNative_EvpMdSize(const EVP_MD* md); +/* +Function: +EvpDigestSqueeze + +Calls EVP_DigestSqueeze. If the function is not available, haveFeature is set to zero and the return value should +be ignored. If the function is available, haveFeature is set to one and the operation result is returned. +*/ +PALEXPORT int32_t CryptoNative_EvpDigestSqueeze(EVP_MD_CTX* ctx, uint8_t* md, uint32_t len, int32_t* haveFeature); + /* Function: EvpMd5