Add static one-shot methods for HMAC algorithms
authorKevin Jones <kevin@vcsjones.com>
Fri, 4 Jun 2021 19:57:12 +0000 (15:57 -0400)
committerGitHub <noreply@github.com>
Fri, 4 Jun 2021 19:57:12 +0000 (12:57 -0700)
32 files changed:
src/libraries/Common/src/Interop/Android/System.Security.Cryptography.Native.Android/Interop.Hmac.cs
src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Hmac.cs
src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Hmac.cs
src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptAlgPseudoHandle.cs
src/libraries/Common/tests/TestUtilities/System/AssertExtensions.cs
src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/pal_hmac.c
src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/pal_hmac.h
src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/entrypoints.c
src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_hmac.c
src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_hmac.h
src/libraries/Native/Unix/System.Security.Cryptography.Native/entrypoints.c
src/libraries/Native/Unix/System.Security.Cryptography.Native/opensslshim.h
src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_hmac.c
src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_hmac.h
src/libraries/System.Security.Cryptography.Algorithms/ref/System.Security.Cryptography.Algorithms.cs
src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/HashProviderDispenser.Browser.cs
src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/HashProviderDispenser.OSX.cs
src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/HashProviderDispenser.Unix.cs
src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/HashProviderDispenser.Windows.cs
src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/HMACMD5.cs
src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/HMACSHA1.cs
src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/HMACSHA256.cs
src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/HMACSHA384.cs
src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/HMACSHA512.cs
src/libraries/System.Security.Cryptography.Algorithms/tests/HmacMD5Tests.cs
src/libraries/System.Security.Cryptography.Algorithms/tests/HmacSha1Tests.cs
src/libraries/System.Security.Cryptography.Algorithms/tests/HmacSha256Tests.cs
src/libraries/System.Security.Cryptography.Algorithms/tests/HmacSha384Tests.cs
src/libraries/System.Security.Cryptography.Algorithms/tests/HmacSha512Tests.cs
src/libraries/System.Security.Cryptography.Algorithms/tests/HmacTests.cs
src/libraries/System.Security.Cryptography.Algorithms/tests/Rfc2202HmacTests.cs
src/libraries/System.Security.Cryptography.Algorithms/tests/Rfc4231HmacTests.cs

index f332946..38ed534 100644 (file)
@@ -2,6 +2,7 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 
 using System;
+using System.Diagnostics;
 using System.Runtime.InteropServices;
 using Microsoft.Win32.SafeHandles;
 
@@ -29,5 +30,29 @@ internal static partial class Interop
 
         [DllImport(Libraries.AndroidCryptoNative, EntryPoint = "CryptoNative_HmacCurrent")]
         internal static extern int HmacCurrent(SafeHmacCtxHandle ctx, ref byte data, ref int len);
+
+        [DllImport(Libraries.AndroidCryptoNative, EntryPoint = "CryptoNative_HmacOneShot")]
+        private static unsafe extern int HmacOneShot(IntPtr type, byte* key, int keySize, byte* source, int sourceSize, byte* md, ref int mdSize);
+
+        internal static unsafe int HmacOneShot(IntPtr type, ReadOnlySpan<byte> key, ReadOnlySpan<byte> source, Span<byte> destination)
+        {
+            int size = destination.Length;
+            const int Success = 1;
+
+            fixed (byte* pKey = key)
+            fixed (byte* pSource = source)
+            fixed (byte* pDestination = destination)
+            {
+                int result = HmacOneShot(type, pKey, key.Length, pSource, source.Length, pDestination, ref size);
+
+                if (result != Success)
+                {
+                    Debug.Assert(result == 0);
+                    throw CreateOpenSslCryptographicException();
+                }
+            }
+
+            return size;
+        }
     }
 }
index 9898f58..7cedae4 100644 (file)
@@ -35,6 +35,17 @@ internal static partial class Interop
 
         [DllImport(Libraries.AppleCryptoNative, EntryPoint = "AppleCryptoNative_HmacCurrent")]
         private static extern int HmacCurrent(SafeHmacHandle ctx, ref byte pbOutput, int cbOutput);
+
+        [DllImport(Libraries.AppleCryptoNative, EntryPoint = "AppleCryptoNative_HmacOneShot")]
+        internal static unsafe extern int HmacOneShot(
+            PAL_HashAlgorithm algorithm,
+            byte* pKey,
+            int cbKey,
+            byte* pData,
+            int cbData,
+            byte* pOutput,
+            int cbOutput,
+            out int cbDigest);
     }
 }
 
index 5638d20..6074351 100644 (file)
@@ -2,6 +2,7 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 
 using System;
+using System.Diagnostics;
 using System.Runtime.InteropServices;
 using Microsoft.Win32.SafeHandles;
 
@@ -29,5 +30,29 @@ internal static partial class Interop
 
         [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_HmacCurrent")]
         internal static extern int HmacCurrent(SafeHmacCtxHandle ctx, ref byte data, ref int len);
+
+        [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_HmacOneShot")]
+        private static unsafe extern int HmacOneShot(IntPtr type, byte* key, int keySize, byte* source, int sourceSize, byte* md, ref int mdSize);
+
+        internal static unsafe int HmacOneShot(IntPtr type, ReadOnlySpan<byte> key, ReadOnlySpan<byte> source, Span<byte> destination)
+        {
+            int size = destination.Length;
+            const int Success = 1;
+
+            fixed (byte* pKey = key)
+            fixed (byte* pSource = source)
+            fixed (byte* pDestination = destination)
+            {
+                int result = HmacOneShot(type, pKey, key.Length, pSource, source.Length, pDestination, ref size);
+
+                if (result != Success)
+                {
+                    Debug.Assert(result == 0);
+                    throw CreateOpenSslCryptographicException();
+                }
+            }
+
+            return size;
+        }
     }
 }
index 9886f2b..433e6f5 100644 (file)
@@ -17,6 +17,11 @@ internal static partial class Interop
             BCRYPT_SHA256_ALG_HANDLE = 0x00000041,
             BCRYPT_SHA384_ALG_HANDLE = 0x00000051,
             BCRYPT_SHA512_ALG_HANDLE = 0x00000061,
+            BCRYPT_HMAC_MD5_ALG_HANDLE = 0x00000091,
+            BCRYPT_HMAC_SHA1_ALG_HANDLE = 0x000000a1,
+            BCRYPT_HMAC_SHA256_ALG_HANDLE = 0x000000b1,
+            BCRYPT_HMAC_SHA384_ALG_HANDLE = 0x000000c1,
+            BCRYPT_HMAC_SHA512_ALG_HANDLE = 0x000000d1,
             BCRYPT_PBKDF2_ALG_HANDLE = 0x00000331,
         }
 
index 940c4de..f2fb788 100644 (file)
@@ -403,7 +403,7 @@ namespace System
                 {
                     actualItemCountMapping[actualItem] = new ItemCount(1, 1);
                 }
-                
+
                 actualCount++;
             }
 
@@ -431,7 +431,7 @@ namespace System
                 countInfo.Remain--;
             }
         }
-               
+
         /// <summary>
         /// Validates that the actual span is equal to the expected span.
         /// If this fails, determine where the differences are and create an exception with that information.
@@ -475,6 +475,19 @@ namespace System
             }
         }
 
+        public static void FilledWith<T>(T expected, ReadOnlySpan<T> actual)
+        {
+            EqualityComparer<T> comparer = EqualityComparer<T>.Default;
+
+            for (int i = 0; i < actual.Length; i++)
+            {
+                if (!comparer.Equals(expected, actual[i]))
+                {
+                    throw new XunitException($"Expected {expected?.ToString() ?? "null"} at position {i}");
+                }
+            }
+        }
+
         public static void SequenceEqual<T>(Span<T> expected, Span<T> actual) where T : IEquatable<T> => SequenceEqual((ReadOnlySpan<T>)expected, (ReadOnlySpan<T>)actual);
 
         public static void SequenceEqual<T>(T[] expected, T[] actual) where T : IEquatable<T> => SequenceEqual(expected.AsSpan(), actual.AsSpan());
@@ -556,7 +569,7 @@ namespace System
             Assert.Equal(expectedParamName, exception.ParamName);
             return exception;
         }
-               
+
         private class ItemCount
         {
             public int Original { get; set; }
index 4fd381d..7632eed 100644 (file)
@@ -136,3 +136,29 @@ void CryptoNative_HmacDestroy(jobject ctx)
 {
     ReleaseGRef(GetJNIEnv(), ctx);
 }
+
+int32_t CryptoNative_HmacOneShot(intptr_t type,
+                                 uint8_t* key,
+                                 int32_t keyLen,
+                                 uint8_t* source,
+                                 int32_t sourceLen,
+                                 uint8_t* md,
+                                 int32_t* mdSize)
+{
+    jobject hmacCtx = CryptoNative_HmacCreate(key, keyLen, type);
+
+    if (hmacCtx == FAIL)
+        return FAIL;
+
+    int32_t ret = sourceLen == 0 ? SUCCESS : CryptoNative_HmacUpdate(hmacCtx, source, sourceLen);
+
+    if (ret != SUCCESS)
+    {
+        CryptoNative_HmacDestroy(hmacCtx);
+        return ret;
+    }
+
+    ret = CryptoNative_HmacFinal(hmacCtx, md, mdSize);
+    CryptoNative_HmacDestroy(hmacCtx);
+    return ret;
+}
index e44aa5d..671fc6f 100644 (file)
@@ -11,3 +11,10 @@ PALEXPORT int32_t CryptoNative_HmacUpdate(jobject ctx, uint8_t* data, int32_t le
 PALEXPORT int32_t CryptoNative_HmacFinal(jobject ctx, uint8_t* md, int32_t* len);
 PALEXPORT int32_t CryptoNative_HmacCurrent(jobject ctx, uint8_t* md, int32_t* len);
 PALEXPORT void CryptoNative_HmacDestroy(jobject ctx);
+PALEXPORT int32_t CryptoNative_HmacOneShot(intptr_t type,
+                                           uint8_t* key,
+                                           int32_t keyLen,
+                                           uint8_t* source,
+                                           int32_t sourceLen,
+                                           uint8_t* md,
+                                           int32_t* mdSize);
index 90dd2ab..ff4df6f 100644 (file)
@@ -38,6 +38,7 @@ static const Entry s_cryptoAppleNative[] =
     DllImportEntry(AppleCryptoNative_HmacUpdate)
     DllImportEntry(AppleCryptoNative_HmacFinal)
     DllImportEntry(AppleCryptoNative_HmacCurrent)
+    DllImportEntry(AppleCryptoNative_HmacOneShot)
     DllImportEntry(AppleCryptoNative_SecKeychainItemCopyKeychain)
     DllImportEntry(AppleCryptoNative_SecKeychainCreate)
     DllImportEntry(AppleCryptoNative_SecKeychainDelete)
index 25632ea..4ffbc2f 100644 (file)
@@ -116,7 +116,32 @@ int32_t AppleCryptoNative_HmacCurrent(const HmacCtx* ctx, uint8_t* pbOutput)
 {
     if (ctx == NULL || pbOutput == NULL)
         return 0;
-    
+
     HmacCtx dup = *ctx;
     return AppleCryptoNative_HmacFinal(&dup, pbOutput);
 }
+
+int32_t AppleCryptoNative_HmacOneShot(PAL_HashAlgorithm algorithm,
+                                      const uint8_t* pKey,
+                                      int32_t cbKey,
+                                      const uint8_t* pBuf,
+                                      int32_t cbBuf,
+                                      uint8_t* pOutput,
+                                      int32_t cbOutput,
+                                      int32_t* pcbDigest)
+{
+    if (pOutput == NULL || cbOutput <= 0 || pcbDigest == NULL)
+        return -1;
+
+    CCHmacAlgorithm ccAlgorithm = PalAlgorithmToAppleAlgorithm(algorithm);
+    *pcbDigest = GetHmacOutputSize(algorithm);
+
+    if (ccAlgorithm == UINT_MAX)
+        return -1;
+
+    if (cbOutput < *pcbDigest)
+        return -1;
+
+    CCHmac(ccAlgorithm, pKey, cbKey, pBuf, cbBuf, pOutput);
+    return 1;
+}
index 86d05cc..2889165 100644 (file)
@@ -51,3 +51,17 @@ Computes the HMAC of the accumulated data in ctx without resetting the state.
 Returns 1 on success, 0 on error.
 */
 PALEXPORT int32_t AppleCryptoNative_HmacCurrent(const HmacCtx* ctx, uint8_t* pbOutput);
+
+/*
+Computes the HMAC of data with a key in to the pOutput buffer in one step.
+
+Return 1 on success, 0 on error, and negative values for invalid input.
+*/
+PALEXPORT int32_t AppleCryptoNative_HmacOneShot(PAL_HashAlgorithm algorithm,
+                                                const uint8_t* pKey,
+                                                int32_t cbKey,
+                                                const uint8_t* pBuf,
+                                                int32_t cbBuf,
+                                                uint8_t* pOutput,
+                                                int32_t cbOutput,
+                                                int32_t* pcbDigest);
index 3ab9ccf..9969217 100644 (file)
@@ -202,6 +202,7 @@ static const Entry s_cryptoNative[] =
     DllImportEntry(CryptoNative_HmacCurrent)
     DllImportEntry(CryptoNative_HmacDestroy)
     DllImportEntry(CryptoNative_HmacFinal)
+    DllImportEntry(CryptoNative_HmacOneShot)
     DllImportEntry(CryptoNative_HmacReset)
     DllImportEntry(CryptoNative_HmacUpdate)
     DllImportEntry(CryptoNative_LookupFriendlyNameByOid)
index 8bf1bef..09d8351 100644 (file)
@@ -362,6 +362,7 @@ const EVP_CIPHER* EVP_chacha20_poly1305(void);
     REQUIRED_FUNCTION(EVP_sha512) \
     REQUIRED_FUNCTION(EXTENDED_KEY_USAGE_free) \
     REQUIRED_FUNCTION(GENERAL_NAMES_free) \
+    REQUIRED_FUNCTION(HMAC) \
     LEGACY_FUNCTION(HMAC_CTX_cleanup) \
     REQUIRED_FUNCTION(HMAC_CTX_copy) \
     FALLBACK_FUNCTION(HMAC_CTX_free) \
@@ -797,6 +798,7 @@ FOR_ALL_OPENSSL_FUNCTIONS
 #define EVP_sha512 EVP_sha512_ptr
 #define EXTENDED_KEY_USAGE_free EXTENDED_KEY_USAGE_free_ptr
 #define GENERAL_NAMES_free GENERAL_NAMES_free_ptr
+#define HMAC HMAC_ptr
 #define HMAC_CTX_cleanup HMAC_CTX_cleanup_ptr
 #define HMAC_CTX_copy HMAC_CTX_copy_ptr
 #define HMAC_CTX_free HMAC_CTX_free_ptr
index 8871eac..96b0085 100644 (file)
@@ -130,3 +130,36 @@ int32_t CryptoNative_HmacCurrent(const HMAC_CTX* ctx, uint8_t* md, int32_t* len)
 
     return 0;
 }
+
+int32_t CryptoNative_HmacOneShot(const EVP_MD* type,
+                                 const uint8_t* key,
+                                 int32_t keySize,
+                                 const uint8_t* source,
+                                 int32_t sourceSize,
+                                 uint8_t* md,
+                                 int32_t* mdSize)
+{
+    assert(mdSize != NULL && type != NULL && md != NULL && mdSize != NULL);
+    assert(keySize >= 0 && *mdSize >= 0);
+    assert(key != NULL || keySize == 0);
+    assert(source != NULL || sourceSize == 0);
+
+    uint8_t empty = 0;
+
+    if (key == NULL)
+    {
+        if (keySize != 0)
+        {
+            return -1;
+        }
+
+        key = &empty;
+    }
+
+    unsigned int unsignedSource = Int32ToUint32(sourceSize);
+    unsigned int unsignedSize = Int32ToUint32(*mdSize);
+    unsigned char* result = HMAC(type, key, keySize, source, unsignedSource, md, &unsignedSize);
+    *mdSize = Uint32ToInt32(unsignedSize);
+
+    return result == NULL ? 0 : 1;
+}
index e310c05..9f89895 100644 (file)
@@ -69,3 +69,15 @@ PALEXPORT int32_t CryptoNative_HmacFinal(HMAC_CTX* ctx, uint8_t* md, int32_t* le
  * Returns 1 for success or 0 for failure.
  */
 PALEXPORT int32_t CryptoNative_HmacCurrent(const HMAC_CTX* ctx, uint8_t* md, int32_t* len);
+
+/**
+ * Computes the HMAC of data using a key in a single operation.
+ * Returns -1 on invalid input, 0 on failure, and 1 on success.
+ */
+PALEXPORT int32_t CryptoNative_HmacOneShot(const EVP_MD* type,
+                                           const uint8_t* key,
+                                           int32_t keySize,
+                                           const uint8_t* source,
+                                           int32_t sourceSize,
+                                           uint8_t* md,
+                                           int32_t* mdSize);
index a12fae4..3239668 100644 (file)
@@ -469,8 +469,12 @@ namespace System.Security.Cryptography
         protected override void Dispose(bool disposing) { }
         protected override void HashCore(byte[] rgb, int ib, int cb) { }
         protected override void HashCore(System.ReadOnlySpan<byte> source) { }
+        public static byte[] HashData(byte[] key, byte[] source) { throw null; }
+        public static byte[] HashData(System.ReadOnlySpan<byte> key, System.ReadOnlySpan<byte> source) { throw null; }
+        public static int HashData(System.ReadOnlySpan<byte> key, System.ReadOnlySpan<byte> source, System.Span<byte> destination) { throw null; }
         protected override byte[] HashFinal() { throw null; }
         public override void Initialize() { }
+        public static bool TryHashData(System.ReadOnlySpan<byte> key, System.ReadOnlySpan<byte> source, System.Span<byte> destination, out int bytesWritten) { throw null; }
         protected override bool TryHashFinal(System.Span<byte> destination, out int bytesWritten) { throw null; }
     }
     [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")]
@@ -484,8 +488,12 @@ namespace System.Security.Cryptography
         protected override void Dispose(bool disposing) { }
         protected override void HashCore(byte[] rgb, int ib, int cb) { }
         protected override void HashCore(System.ReadOnlySpan<byte> source) { }
+        public static byte[] HashData(byte[] key, byte[] source) { throw null; }
+        public static byte[] HashData(System.ReadOnlySpan<byte> key, System.ReadOnlySpan<byte> source) { throw null; }
+        public static int HashData(System.ReadOnlySpan<byte> key, System.ReadOnlySpan<byte> source, System.Span<byte> destination) { throw null; }
         protected override byte[] HashFinal() { throw null; }
         public override void Initialize() { }
+        public static bool TryHashData(System.ReadOnlySpan<byte> key, System.ReadOnlySpan<byte> source, System.Span<byte> destination, out int bytesWritten) { throw null; }
         protected override bool TryHashFinal(System.Span<byte> destination, out int bytesWritten) { throw null; }
     }
     [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")]
@@ -497,8 +505,12 @@ namespace System.Security.Cryptography
         protected override void Dispose(bool disposing) { }
         protected override void HashCore(byte[] rgb, int ib, int cb) { }
         protected override void HashCore(System.ReadOnlySpan<byte> source) { }
+        public static byte[] HashData(byte[] key, byte[] source) { throw null; }
+        public static byte[] HashData(System.ReadOnlySpan<byte> key, System.ReadOnlySpan<byte> source) { throw null; }
+        public static int HashData(System.ReadOnlySpan<byte> key, System.ReadOnlySpan<byte> source, System.Span<byte> destination) { throw null; }
         protected override byte[] HashFinal() { throw null; }
         public override void Initialize() { }
+        public static bool TryHashData(System.ReadOnlySpan<byte> key, System.ReadOnlySpan<byte> source, System.Span<byte> destination, out int bytesWritten) { throw null; }
         protected override bool TryHashFinal(System.Span<byte> destination, out int bytesWritten) { throw null; }
     }
     [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")]
@@ -511,8 +523,12 @@ namespace System.Security.Cryptography
         protected override void Dispose(bool disposing) { }
         protected override void HashCore(byte[] rgb, int ib, int cb) { }
         protected override void HashCore(System.ReadOnlySpan<byte> source) { }
+        public static byte[] HashData(byte[] key, byte[] source) { throw null; }
+        public static byte[] HashData(System.ReadOnlySpan<byte> key, System.ReadOnlySpan<byte> source) { throw null; }
+        public static int HashData(System.ReadOnlySpan<byte> key, System.ReadOnlySpan<byte> source, System.Span<byte> destination) { throw null; }
         protected override byte[] HashFinal() { throw null; }
         public override void Initialize() { }
+        public static bool TryHashData(System.ReadOnlySpan<byte> key, System.ReadOnlySpan<byte> source, System.Span<byte> destination, out int bytesWritten) { throw null; }
         protected override bool TryHashFinal(System.Span<byte> destination, out int bytesWritten) { throw null; }
     }
     [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")]
@@ -525,8 +541,12 @@ namespace System.Security.Cryptography
         protected override void Dispose(bool disposing) { }
         protected override void HashCore(byte[] rgb, int ib, int cb) { }
         protected override void HashCore(System.ReadOnlySpan<byte> source) { }
+        public static byte[] HashData(byte[] key, byte[] source) { throw null; }
+        public static byte[] HashData(System.ReadOnlySpan<byte> key, System.ReadOnlySpan<byte> source) { throw null; }
+        public static int HashData(System.ReadOnlySpan<byte> key, System.ReadOnlySpan<byte> source, System.Span<byte> destination) { throw null; }
         protected override byte[] HashFinal() { throw null; }
         public override void Initialize() { }
+        public static bool TryHashData(System.ReadOnlySpan<byte> key, System.ReadOnlySpan<byte> source, System.Span<byte> destination, out int bytesWritten) { throw null; }
         protected override bool TryHashFinal(System.Span<byte> destination, out int bytesWritten) { throw null; }
     }
     public sealed partial class IncrementalHash : System.IDisposable
index 17d2292..cc0dc6f 100644 (file)
@@ -26,6 +26,15 @@ namespace Internal.Cryptography
 
         public static class OneShotHashProvider
         {
+            public static unsafe int MacData(
+                string hashAlgorithmId,
+                ReadOnlySpan<byte> key,
+                ReadOnlySpan<byte> source,
+                Span<byte> destination)
+            {
+                throw new PlatformNotSupportedException(SR.SystemSecurityCryptographyAlgorithms_PlatformNotSupported);
+            }
+
             public static int HashData(string hashAlgorithmId, ReadOnlySpan<byte> source, Span<byte> destination)
             {
                 HashProvider provider = HashProviderDispenser.CreateHashProvider(hashAlgorithmId);
index bb14bc8..3bc72d0 100644 (file)
@@ -33,6 +33,40 @@ namespace Internal.Cryptography
 
         internal static class OneShotHashProvider
         {
+            public static unsafe int MacData(
+                string hashAlgorithmId,
+                ReadOnlySpan<byte> key,
+                ReadOnlySpan<byte> source,
+                Span<byte> destination)
+            {
+                Interop.AppleCrypto.PAL_HashAlgorithm algorithm = HashAlgorithmToPal(hashAlgorithmId);
+
+                fixed (byte* pKey = key)
+                fixed (byte* pSource = source)
+                fixed (byte* pDestination = destination)
+                {
+                    int ret = Interop.AppleCrypto.HmacOneShot(
+                        algorithm,
+                        pKey,
+                        key.Length,
+                        pSource,
+                        source.Length,
+                        pDestination,
+                        destination.Length,
+                        out int digestSize);
+
+                    if (ret != 1)
+                    {
+                        Debug.Fail($"MacData return value {ret} was not 1");
+                        throw new CryptographicException();
+                    }
+
+                    Debug.Assert(digestSize <= destination.Length);
+
+                    return digestSize;
+                }
+            }
+
             public static unsafe int HashData(string hashAlgorithmId, ReadOnlySpan<byte> source, Span<byte> destination)
             {
                 Interop.AppleCrypto.PAL_HashAlgorithm algorithm = HashAlgorithmToPal(hashAlgorithmId);
index af984e5..3a7b797 100644 (file)
@@ -25,6 +25,28 @@ namespace Internal.Cryptography
 
         internal static class OneShotHashProvider
         {
+            public static int MacData(
+                string hashAlgorithmId,
+                ReadOnlySpan<byte> key,
+                ReadOnlySpan<byte> source,
+                Span<byte> destination)
+            {
+                IntPtr evpType = Interop.Crypto.HashAlgorithmToEvp(hashAlgorithmId);
+                Debug.Assert(evpType != IntPtr.Zero);
+
+                int hashSize = Interop.Crypto.EvpMdSize(evpType);
+
+                if (hashSize <= 0 || destination.Length < hashSize)
+                {
+                    Debug.Fail("Destination length or hash size not valid.");
+                    throw new CryptographicException();
+                }
+
+                int written = Interop.Crypto.HmacOneShot(evpType, key, source, destination);
+                Debug.Assert(written == hashSize);
+                return written;
+            }
+
             public static unsafe int HashData(string hashAlgorithmId, ReadOnlySpan<byte> source, Span<byte> destination)
             {
                 IntPtr evpType = Interop.Crypto.HashAlgorithmToEvp(hashAlgorithmId);
index 4fa9607..88dd4da 100644 (file)
@@ -30,14 +30,48 @@ namespace Internal.Cryptography
 
         public static class OneShotHashProvider
         {
+            public static unsafe int MacData(
+                string hashAlgorithmId,
+                ReadOnlySpan<byte> key,
+                ReadOnlySpan<byte> source,
+                Span<byte> destination)
+            {
+                int hashSize; // in bytes
+
+                // Use a pseudo-handle if available.
+                if (Interop.BCrypt.PseudoHandlesSupported)
+                {
+                    HashDataUsingPseudoHandle(hashAlgorithmId, source, key, isHmac: true, destination, out hashSize);
+                    return hashSize;
+                }
+                else
+                {
+                    // Pseudo-handle not available. Fall back to a shared handle with no using or dispose.
+                    SafeBCryptAlgorithmHandle cachedAlgorithmHandle = BCryptAlgorithmCache.GetCachedBCryptAlgorithmHandle(
+                        hashAlgorithmId,
+                        BCryptOpenAlgorithmProviderFlags.BCRYPT_ALG_HANDLE_HMAC_FLAG,
+                        out hashSize);
+
+                    if (destination.Length < hashSize)
+                    {
+                        Debug.Fail("Caller should have checked length.");
+                        throw new CryptographicException();
+                    }
+
+                    HashUpdateAndFinish(cachedAlgorithmHandle, hashSize, key, source, destination);
+
+                    return hashSize;
+                }
+            }
+
             public static unsafe int HashData(string hashAlgorithmId, ReadOnlySpan<byte> source, Span<byte> destination)
             {
                 int hashSize; // in bytes
 
-                // Try using a pseudo-handle if available.
+                // Use a pseudo-handle if available.
                 if (Interop.BCrypt.PseudoHandlesSupported)
                 {
-                    HashDataUsingPseudoHandle(hashAlgorithmId, source, destination, out hashSize);
+                    HashDataUsingPseudoHandle(hashAlgorithmId, source, key: default, isHmac : false, destination, out hashSize);
                     return hashSize;
                 }
                 else
@@ -54,42 +88,60 @@ namespace Internal.Cryptography
                         throw new CryptographicException();
                     }
 
-                    HashUpdateAndFinish(cachedAlgorithmHandle, hashSize, source, destination);
+                    HashUpdateAndFinish(cachedAlgorithmHandle, hashSize, key: default, source, destination);
 
                     return hashSize;
                 }
             }
 
-            private static unsafe void HashDataUsingPseudoHandle(string hashAlgorithmId, ReadOnlySpan<byte> source, Span<byte> destination, out int hashSize)
+            private static unsafe void HashDataUsingPseudoHandle(
+                string hashAlgorithmId,
+                ReadOnlySpan<byte> source,
+                ReadOnlySpan<byte> key,
+                bool isHmac,
+                Span<byte> destination,
+                out int hashSize)
             {
                 hashSize = default;
 
+                Debug.Assert(isHmac ? true : key.IsEmpty);
+
                 Interop.BCrypt.BCryptAlgPseudoHandle algHandle;
                 int digestSizeInBytes;
 
                 if (hashAlgorithmId == HashAlgorithmNames.MD5)
                 {
-                    algHandle = Interop.BCrypt.BCryptAlgPseudoHandle.BCRYPT_MD5_ALG_HANDLE;
+                    algHandle = isHmac ?
+                        Interop.BCrypt.BCryptAlgPseudoHandle.BCRYPT_HMAC_MD5_ALG_HANDLE :
+                        Interop.BCrypt.BCryptAlgPseudoHandle.BCRYPT_MD5_ALG_HANDLE;
                     digestSizeInBytes = 128 / 8;
                 }
                 else if (hashAlgorithmId == HashAlgorithmNames.SHA1)
                 {
-                    algHandle = Interop.BCrypt.BCryptAlgPseudoHandle.BCRYPT_SHA1_ALG_HANDLE;
+                    algHandle = isHmac ?
+                        Interop.BCrypt.BCryptAlgPseudoHandle.BCRYPT_HMAC_SHA1_ALG_HANDLE :
+                        Interop.BCrypt.BCryptAlgPseudoHandle.BCRYPT_SHA1_ALG_HANDLE;
                     digestSizeInBytes = 160 / 8;
                 }
                 else if (hashAlgorithmId == HashAlgorithmNames.SHA256)
                 {
-                    algHandle = Interop.BCrypt.BCryptAlgPseudoHandle.BCRYPT_SHA256_ALG_HANDLE;
+                    algHandle = isHmac ?
+                        Interop.BCrypt.BCryptAlgPseudoHandle.BCRYPT_HMAC_SHA256_ALG_HANDLE :
+                        Interop.BCrypt.BCryptAlgPseudoHandle.BCRYPT_SHA256_ALG_HANDLE;
                     digestSizeInBytes = 256 / 8;
                 }
                 else if (hashAlgorithmId == HashAlgorithmNames.SHA384)
                 {
-                    algHandle = Interop.BCrypt.BCryptAlgPseudoHandle.BCRYPT_SHA384_ALG_HANDLE;
+                    algHandle = isHmac ?
+                        Interop.BCrypt.BCryptAlgPseudoHandle.BCRYPT_HMAC_SHA384_ALG_HANDLE :
+                        Interop.BCrypt.BCryptAlgPseudoHandle.BCRYPT_SHA384_ALG_HANDLE;
                     digestSizeInBytes = 384 / 8;
                 }
                 else if (hashAlgorithmId == HashAlgorithmNames.SHA512)
                 {
-                    algHandle = Interop.BCrypt.BCryptAlgPseudoHandle.BCRYPT_SHA512_ALG_HANDLE;
+                    algHandle = isHmac ?
+                        Interop.BCrypt.BCryptAlgPseudoHandle.BCRYPT_HMAC_SHA512_ALG_HANDLE :
+                        Interop.BCrypt.BCryptAlgPseudoHandle.BCRYPT_SHA512_ALG_HANDLE;
                     digestSizeInBytes = 512 / 8;
                 }
                 else
@@ -104,10 +156,11 @@ namespace Internal.Cryptography
                     throw new CryptographicException();
                 }
 
+                fixed (byte* pKey = &MemoryMarshal.GetReference(key))
                 fixed (byte* pSrc = &MemoryMarshal.GetReference(source))
                 fixed (byte* pDest = &MemoryMarshal.GetReference(destination))
                 {
-                    NTSTATUS ntStatus = Interop.BCrypt.BCryptHash((uint)algHandle, pbSecret: null, cbSecret: 0, pSrc, source.Length, pDest, digestSizeInBytes);
+                    NTSTATUS ntStatus = Interop.BCrypt.BCryptHash((uint)algHandle, pKey, key.Length, pSrc, source.Length, pDest, digestSizeInBytes);
 
                     if (ntStatus != NTSTATUS.STATUS_SUCCESS)
                     {
@@ -121,6 +174,7 @@ namespace Internal.Cryptography
             private static void HashUpdateAndFinish(
                 SafeBCryptAlgorithmHandle algHandle,
                 int hashSize,
+                ReadOnlySpan<byte> key,
                 ReadOnlySpan<byte> source,
                 Span<byte> destination)
             {
@@ -129,8 +183,8 @@ namespace Internal.Cryptography
                     out SafeBCryptHashHandle hHash,
                     IntPtr.Zero,
                     0,
-                    default,
-                    0,
+                    key,
+                    key.Length,
                     BCryptCreateHashFlags.None);
 
                 if (ntStatus != NTSTATUS.STATUS_SUCCESS)
index ad65257..c66fcaa 100644 (file)
@@ -1,6 +1,7 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
+using System.Diagnostics;
 using System.Runtime.Versioning;
 using Internal.Cryptography;
 
@@ -14,6 +15,9 @@ namespace System.Security.Cryptography
     [UnsupportedOSPlatform("browser")]
     public class HMACMD5 : HMAC
     {
+        private const int HmacSizeBits = 128;
+        private const int HmacSizeBytes = HmacSizeBits / 8;
+
         public HMACMD5()
             : this(RandomNumberGenerator.GetBytes(BlockSize))
         {
@@ -33,6 +37,7 @@ namespace System.Security.Cryptography
             // we just want to be explicit in all HMAC extended classes
             BlockSizeValue = BlockSize;
             HashSizeValue = _hMacCommon.HashSizeInBits;
+            Debug.Assert(HashSizeValue == HmacSizeBits);
         }
 
         public override byte[] Key
@@ -67,6 +72,89 @@ namespace System.Security.Cryptography
 
         public override void Initialize() => _hMacCommon.Reset();
 
+        /// <summary>
+        /// Computes the HMAC of data using the MD5 algorithm.
+        /// </summary>
+        /// <param name="key">The HMAC key.</param>
+        /// <param name="source">The data to HMAC.</param>
+        /// <returns>The HMAC of the data.</returns>
+        /// <exception cref="ArgumentNullException">
+        /// <paramref name="key" /> or <paramref name="source" /> is <see langword="null" />.
+        /// </exception>
+        public static byte[] HashData(byte[] key, byte[] source)
+        {
+            if (key is null)
+                throw new ArgumentNullException(nameof(key));
+            if (source is null)
+                throw new ArgumentNullException(nameof(source));
+
+            return HashData(new ReadOnlySpan<byte>(key), new ReadOnlySpan<byte>(source));
+        }
+
+        /// <summary>
+        /// Computes the HMAC of data using the MD5 algorithm.
+        /// </summary>
+        /// <param name="key">The HMAC key.</param>
+        /// <param name="source">The data to HMAC.</param>
+        /// <returns>The HMAC of the data.</returns>
+        public static byte[] HashData(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source)
+        {
+            byte[] buffer = new byte[HmacSizeBytes];
+
+            int written = HashData(key, source, buffer.AsSpan());
+            Debug.Assert(written == buffer.Length);
+
+            return buffer;
+        }
+
+        /// <summary>
+        /// Computes the HMAC of data using the MD5 algorithm.
+        /// </summary>
+        /// <param name="key">The HMAC key.</param>
+        /// <param name="source">The data to HMAC.</param>
+        /// <param name="destination">The buffer to receive the HMAC value.</param>
+        /// <returns>The total number of bytes written to <paramref name="destination" />.</returns>
+        /// <exception cref="ArgumentException">
+        /// The buffer in <paramref name="destination"/> is too small to hold the calculated hash
+        /// size. The MD5 algorithm always produces a 128-bit HMAC, or 16 bytes.
+        /// </exception>
+        public static int HashData(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source, Span<byte> destination)
+        {
+            if (!TryHashData(key, source, destination, out int bytesWritten))
+            {
+                throw new ArgumentException(SR.Argument_DestinationTooShort, nameof(destination));
+            }
+
+            return bytesWritten;
+        }
+
+        /// <summary>
+        /// Attempts to compute the HMAC of data using the MD5 algorithm.
+        /// </summary>
+        /// <param name="key">The HMAC key.</param>
+        /// <param name="source">The data to HMAC.</param>
+        /// <param name="destination">The buffer to receive the HMAC value.</param>
+        /// <param name="bytesWritten">
+        /// When this method returns, the total number of bytes written into <paramref name="destination"/>.
+        /// </param>
+        /// <returns>
+        /// <see langword="false"/> if <paramref name="destination"/> is too small to hold the
+        /// calculated hash, <see langword="true"/> otherwise.
+        /// </returns>
+        public static bool TryHashData(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source, Span<byte> destination, out int bytesWritten)
+        {
+            if (destination.Length < HmacSizeBytes)
+            {
+                bytesWritten = 0;
+                return false;
+            }
+
+            bytesWritten = HashProviderDispenser.OneShotHashProvider.MacData(HashAlgorithmNames.MD5, key, source, destination);
+            Debug.Assert(bytesWritten == HmacSizeBytes);
+
+            return true;
+        }
+
         protected override void Dispose(bool disposing)
         {
             if (disposing)
index 1d090d1..39e57c3 100644 (file)
@@ -3,6 +3,7 @@
 
 using Internal.Cryptography;
 using System.ComponentModel;
+using System.Diagnostics;
 using System.Runtime.Versioning;
 
 namespace System.Security.Cryptography
@@ -15,6 +16,9 @@ namespace System.Security.Cryptography
     [UnsupportedOSPlatform("browser")]
     public class HMACSHA1 : HMAC
     {
+        private const int HmacSizeBits = 160;
+        private const int HmacSizeBytes = HmacSizeBits / 8;
+
         public HMACSHA1()
             : this(RandomNumberGenerator.GetBytes(BlockSize))
         {
@@ -34,6 +38,7 @@ namespace System.Security.Cryptography
             // we just want to be explicit in all HMAC extended classes
             BlockSizeValue = BlockSize;
             HashSizeValue = _hMacCommon.HashSizeInBits;
+            Debug.Assert(HashSizeValue == HmacSizeBits);
         }
 
         [EditorBrowsable(EditorBrowsableState.Never)]
@@ -74,6 +79,89 @@ namespace System.Security.Cryptography
 
         public override void Initialize() => _hMacCommon.Reset();
 
+        /// <summary>
+        /// Computes the HMAC of data using the SHA1 algorithm.
+        /// </summary>
+        /// <param name="key">The HMAC key.</param>
+        /// <param name="source">The data to HMAC.</param>
+        /// <returns>The HMAC of the data.</returns>
+        /// <exception cref="ArgumentNullException">
+        /// <paramref name="key" /> or <paramref name="source" /> is <see langword="null" />.
+        /// </exception>
+        public static byte[] HashData(byte[] key, byte[] source)
+        {
+            if (key is null)
+                throw new ArgumentNullException(nameof(key));
+            if (source is null)
+                throw new ArgumentNullException(nameof(source));
+
+            return HashData(new ReadOnlySpan<byte>(key), new ReadOnlySpan<byte>(source));
+        }
+
+        /// <summary>
+        /// Computes the HMAC of data using the SHA1 algorithm.
+        /// </summary>
+        /// <param name="key">The HMAC key.</param>
+        /// <param name="source">The data to HMAC.</param>
+        /// <returns>The HMAC of the data.</returns>
+        public static byte[] HashData(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source)
+        {
+            byte[] buffer = new byte[HmacSizeBytes];
+
+            int written = HashData(key, source, buffer.AsSpan());
+            Debug.Assert(written == buffer.Length);
+
+            return buffer;
+        }
+
+        /// <summary>
+        /// Computes the HMAC of data using the SHA1 algorithm.
+        /// </summary>
+        /// <param name="key">The HMAC key.</param>
+        /// <param name="source">The data to HMAC.</param>
+        /// <param name="destination">The buffer to receive the HMAC value.</param>
+        /// <returns>The total number of bytes written to <paramref name="destination" />.</returns>
+        /// <exception cref="ArgumentException">
+        /// The buffer in <paramref name="destination"/> is too small to hold the calculated hash
+        /// size. The SHA1 algorithm always produces a 160-bit HMAC, or 20 bytes.
+        /// </exception>
+        public static int HashData(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source, Span<byte> destination)
+        {
+            if (!TryHashData(key, source, destination, out int bytesWritten))
+            {
+                throw new ArgumentException(SR.Argument_DestinationTooShort, nameof(destination));
+            }
+
+            return bytesWritten;
+        }
+
+        /// <summary>
+        /// Attempts to compute the HMAC of data using the SHA1 algorithm.
+        /// </summary>
+        /// <param name="key">The HMAC key.</param>
+        /// <param name="source">The data to HMAC.</param>
+        /// <param name="destination">The buffer to receive the HMAC value.</param>
+        /// <param name="bytesWritten">
+        /// When this method returns, the total number of bytes written into <paramref name="destination"/>.
+        /// </param>
+        /// <returns>
+        /// <see langword="false"/> if <paramref name="destination"/> is too small to hold the
+        /// calculated hash, <see langword="true"/> otherwise.
+        /// </returns>
+        public static bool TryHashData(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source, Span<byte> destination, out int bytesWritten)
+        {
+            if (destination.Length < HmacSizeBytes)
+            {
+                bytesWritten = 0;
+                return false;
+            }
+
+            bytesWritten = HashProviderDispenser.OneShotHashProvider.MacData(HashAlgorithmNames.SHA1, key, source, destination);
+            Debug.Assert(bytesWritten == HmacSizeBytes);
+
+            return true;
+        }
+
         protected override void Dispose(bool disposing)
         {
             if (disposing)
index 0bc1109..598c4c2 100644 (file)
@@ -1,11 +1,10 @@
 // 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 Internal.Cryptography;
 using System.Diagnostics;
 using System.Runtime.Versioning;
 using System.Security.Cryptography;
-using Internal.Cryptography;
 
 namespace System.Security.Cryptography
 {
@@ -17,6 +16,9 @@ namespace System.Security.Cryptography
     [UnsupportedOSPlatform("browser")]
     public class HMACSHA256 : HMAC
     {
+        private const int HmacSizeBits = 256;
+        private const int HmacSizeBytes = HmacSizeBits / 8;
+
         public HMACSHA256()
             : this(RandomNumberGenerator.GetBytes(BlockSize))
         {
@@ -36,6 +38,7 @@ namespace System.Security.Cryptography
             // we just want to be explicit in all HMAC extended classes
             BlockSizeValue = BlockSize;
             HashSizeValue = _hMacCommon.HashSizeInBits;
+            Debug.Assert(HashSizeValue == HmacSizeBits);
         }
 
         public override byte[] Key
@@ -70,6 +73,89 @@ namespace System.Security.Cryptography
 
         public override void Initialize() => _hMacCommon.Reset();
 
+        /// <summary>
+        /// Computes the HMAC of data using the SHA256 algorithm.
+        /// </summary>
+        /// <param name="key">The HMAC key.</param>
+        /// <param name="source">The data to HMAC.</param>
+        /// <returns>The HMAC of the data.</returns>
+        /// <exception cref="ArgumentNullException">
+        /// <paramref name="key" /> or <paramref name="source" /> is <see langword="null" />.
+        /// </exception>
+        public static byte[] HashData(byte[] key, byte[] source)
+        {
+            if (key is null)
+                throw new ArgumentNullException(nameof(key));
+            if (source is null)
+                throw new ArgumentNullException(nameof(source));
+
+            return HashData(new ReadOnlySpan<byte>(key), new ReadOnlySpan<byte>(source));
+        }
+
+        /// <summary>
+        /// Computes the HMAC of data using the SHA256 algorithm.
+        /// </summary>
+        /// <param name="key">The HMAC key.</param>
+        /// <param name="source">The data to HMAC.</param>
+        /// <returns>The HMAC of the data.</returns>
+        public static byte[] HashData(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source)
+        {
+            byte[] buffer = new byte[HmacSizeBytes];
+
+            int written = HashData(key, source, buffer.AsSpan());
+            Debug.Assert(written == buffer.Length);
+
+            return buffer;
+        }
+
+        /// <summary>
+        /// Computes the HMAC of data using the SHA256 algorithm.
+        /// </summary>
+        /// <param name="key">The HMAC key.</param>
+        /// <param name="source">The data to HMAC.</param>
+        /// <param name="destination">The buffer to receive the HMAC value.</param>
+        /// <returns>The total number of bytes written to <paramref name="destination" />.</returns>
+        /// <exception cref="ArgumentException">
+        /// The buffer in <paramref name="destination"/> is too small to hold the calculated hash
+        /// size. The SHA256 algorithm always produces a 256-bit HMAC, or 32 bytes.
+        /// </exception>
+        public static int HashData(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source, Span<byte> destination)
+        {
+            if (!TryHashData(key, source, destination, out int bytesWritten))
+            {
+                throw new ArgumentException(SR.Argument_DestinationTooShort, nameof(destination));
+            }
+
+            return bytesWritten;
+        }
+
+        /// <summary>
+        /// Attempts to compute the HMAC of data using the SHA256 algorithm.
+        /// </summary>
+        /// <param name="key">The HMAC key.</param>
+        /// <param name="source">The data to HMAC.</param>
+        /// <param name="destination">The buffer to receive the HMAC value.</param>
+        /// <param name="bytesWritten">
+        /// When this method returns, the total number of bytes written into <paramref name="destination"/>.
+        /// </param>
+        /// <returns>
+        /// <see langword="false"/> if <paramref name="destination"/> is too small to hold the
+        /// calculated hash, <see langword="true"/> otherwise.
+        /// </returns>
+        public static bool TryHashData(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source, Span<byte> destination, out int bytesWritten)
+        {
+            if (destination.Length < HmacSizeBytes)
+            {
+                bytesWritten = 0;
+                return false;
+            }
+
+            bytesWritten = HashProviderDispenser.OneShotHashProvider.MacData(HashAlgorithmNames.SHA256, key, source, destination);
+            Debug.Assert(bytesWritten == HmacSizeBytes);
+
+            return true;
+        }
+
         protected override void Dispose(bool disposing)
         {
             if (disposing)
index 05548b0..bf16716 100644 (file)
@@ -1,11 +1,10 @@
 // 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 Internal.Cryptography;
 using System.Diagnostics;
 using System.Runtime.Versioning;
 using System.Security.Cryptography;
-using Internal.Cryptography;
 
 namespace System.Security.Cryptography
 {
@@ -17,6 +16,9 @@ namespace System.Security.Cryptography
     [UnsupportedOSPlatform("browser")]
     public class HMACSHA384 : HMAC
     {
+        private const int HmacSizeBits = 384;
+        private const int HmacSizeBytes = HmacSizeBits / 8;
+
         public HMACSHA384()
             : this(RandomNumberGenerator.GetBytes(BlockSize))
         {
@@ -35,6 +37,7 @@ namespace System.Security.Cryptography
             // change the default value of BlockSizeValue to 128 instead of 64
             BlockSizeValue = BlockSize;
             HashSizeValue = _hMacCommon.HashSizeInBits;
+            Debug.Assert(HashSizeValue == HmacSizeBits);
         }
 
         public bool ProduceLegacyHmacValues
@@ -86,6 +89,89 @@ namespace System.Security.Cryptography
 
         public override void Initialize() => _hMacCommon.Reset();
 
+        /// <summary>
+        /// Computes the HMAC of data using the SHA384 algorithm.
+        /// </summary>
+        /// <param name="key">The HMAC key.</param>
+        /// <param name="source">The data to HMAC.</param>
+        /// <returns>The HMAC of the data.</returns>
+        /// <exception cref="ArgumentNullException">
+        /// <paramref name="key" /> or <paramref name="source" /> is <see langword="null" />.
+        /// </exception>
+        public static byte[] HashData(byte[] key, byte[] source)
+        {
+            if (key is null)
+                throw new ArgumentNullException(nameof(key));
+            if (source is null)
+                throw new ArgumentNullException(nameof(source));
+
+            return HashData(new ReadOnlySpan<byte>(key), new ReadOnlySpan<byte>(source));
+        }
+
+        /// <summary>
+        /// Computes the HMAC of data using the SHA384 algorithm.
+        /// </summary>
+        /// <param name="key">The HMAC key.</param>
+        /// <param name="source">The data to HMAC.</param>
+        /// <returns>The HMAC of the data.</returns>
+        public static byte[] HashData(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source)
+        {
+            byte[] buffer = new byte[HmacSizeBytes];
+
+            int written = HashData(key, source, buffer.AsSpan());
+            Debug.Assert(written == buffer.Length);
+
+            return buffer;
+        }
+
+        /// <summary>
+        /// Computes the HMAC of data using the SHA384 algorithm.
+        /// </summary>
+        /// <param name="key">The HMAC key.</param>
+        /// <param name="source">The data to HMAC.</param>
+        /// <param name="destination">The buffer to receive the HMAC value.</param>
+        /// <returns>The total number of bytes written to <paramref name="destination" />.</returns>
+        /// <exception cref="ArgumentException">
+        /// The buffer in <paramref name="destination"/> is too small to hold the calculated hash
+        /// size. The SHA384 algorithm always produces a 384-bit HMAC, or 48 bytes.
+        /// </exception>
+        public static int HashData(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source, Span<byte> destination)
+        {
+            if (!TryHashData(key, source, destination, out int bytesWritten))
+            {
+                throw new ArgumentException(SR.Argument_DestinationTooShort, nameof(destination));
+            }
+
+            return bytesWritten;
+        }
+
+        /// <summary>
+        /// Attempts to compute the HMAC of data using the SHA384 algorithm.
+        /// </summary>
+        /// <param name="key">The HMAC key.</param>
+        /// <param name="source">The data to HMAC.</param>
+        /// <param name="destination">The buffer to receive the HMAC value.</param>
+        /// <param name="bytesWritten">
+        /// When this method returns, the total number of bytes written into <paramref name="destination"/>.
+        /// </param>
+        /// <returns>
+        /// <see langword="false"/> if <paramref name="destination"/> is too small to hold the
+        /// calculated hash, <see langword="true"/> otherwise.
+        /// </returns>
+        public static bool TryHashData(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source, Span<byte> destination, out int bytesWritten)
+        {
+            if (destination.Length < HmacSizeBytes)
+            {
+                bytesWritten = 0;
+                return false;
+            }
+
+            bytesWritten = HashProviderDispenser.OneShotHashProvider.MacData(HashAlgorithmNames.SHA384, key, source, destination);
+            Debug.Assert(bytesWritten == HmacSizeBytes);
+
+            return true;
+        }
+
         protected override void Dispose(bool disposing)
         {
             if (disposing)
index 556dd54..9b25cdc 100644 (file)
@@ -1,11 +1,10 @@
 // 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 Internal.Cryptography;
 using System.Diagnostics;
 using System.Runtime.Versioning;
 using System.Security.Cryptography;
-using Internal.Cryptography;
 
 namespace System.Security.Cryptography
 {
@@ -17,6 +16,9 @@ namespace System.Security.Cryptography
     [UnsupportedOSPlatform("browser")]
     public class HMACSHA512 : HMAC
     {
+        private const int HmacSizeBits = 512;
+        private const int HmacSizeBytes = HmacSizeBits / 8;
+
         public HMACSHA512()
             : this(RandomNumberGenerator.GetBytes(BlockSize))
         {
@@ -84,6 +86,89 @@ namespace System.Security.Cryptography
 
         public override void Initialize() => _hMacCommon.Reset();
 
+        /// <summary>
+        /// Computes the HMAC of data using the SHA512 algorithm.
+        /// </summary>
+        /// <param name="key">The HMAC key.</param>
+        /// <param name="source">The data to HMAC.</param>
+        /// <returns>The HMAC of the data.</returns>
+        /// <exception cref="ArgumentNullException">
+        /// <paramref name="key" /> or <paramref name="source" /> is <see langword="null" />.
+        /// </exception>
+        public static byte[] HashData(byte[] key, byte[] source)
+        {
+            if (key is null)
+                throw new ArgumentNullException(nameof(key));
+            if (source is null)
+                throw new ArgumentNullException(nameof(source));
+
+            return HashData(new ReadOnlySpan<byte>(key), new ReadOnlySpan<byte>(source));
+        }
+
+        /// <summary>
+        /// Computes the HMAC of data using the SHA512 algorithm.
+        /// </summary>
+        /// <param name="key">The HMAC key.</param>
+        /// <param name="source">The data to HMAC.</param>
+        /// <returns>The HMAC of the data.</returns>
+        public static byte[] HashData(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source)
+        {
+            byte[] buffer = new byte[HmacSizeBytes];
+
+            int written = HashData(key, source, buffer.AsSpan());
+            Debug.Assert(written == buffer.Length);
+
+            return buffer;
+        }
+
+        /// <summary>
+        /// Computes the HMAC of data using the SHA512 algorithm.
+        /// </summary>
+        /// <param name="key">The HMAC key.</param>
+        /// <param name="source">The data to HMAC.</param>
+        /// <param name="destination">The buffer to receive the HMAC value.</param>
+        /// <returns>The total number of bytes written to <paramref name="destination" />.</returns>
+        /// <exception cref="ArgumentException">
+        /// The buffer in <paramref name="destination"/> is too small to hold the calculated hash
+        /// size. The SHA512 algorithm always produces a 512-bit HMAC, or 64 bytes.
+        /// </exception>
+        public static int HashData(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source, Span<byte> destination)
+        {
+            if (!TryHashData(key, source, destination, out int bytesWritten))
+            {
+                throw new ArgumentException(SR.Argument_DestinationTooShort, nameof(destination));
+            }
+
+            return bytesWritten;
+        }
+
+        /// <summary>
+        /// Attempts to compute the HMAC of data using the SHA512 algorithm.
+        /// </summary>
+        /// <param name="key">The HMAC key.</param>
+        /// <param name="source">The data to HMAC.</param>
+        /// <param name="destination">The buffer to receive the HMAC value.</param>
+        /// <param name="bytesWritten">
+        /// When this method returns, the total number of bytes written into <paramref name="destination"/>.
+        /// </param>
+        /// <returns>
+        /// <see langword="false"/> if <paramref name="destination"/> is too small to hold the
+        /// calculated hash, <see langword="true"/> otherwise.
+        /// </returns>
+        public static bool TryHashData(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source, Span<byte> destination, out int bytesWritten)
+        {
+            if (destination.Length < HmacSizeBytes)
+            {
+                bytesWritten = 0;
+                return false;
+            }
+
+            bytesWritten = HashProviderDispenser.OneShotHashProvider.MacData(HashAlgorithmNames.SHA512, key, source, destination);
+            Debug.Assert(bytesWritten == HmacSizeBytes);
+
+            return true;
+        }
+
         protected override void Dispose(bool disposing)
         {
             if (disposing)
index c3cc9ea..0c6cb58 100644 (file)
@@ -21,63 +21,80 @@ namespace System.Security.Cryptography.Hashing.Algorithms.Tests
             ByteUtils.RepeatByte(0xaa, 80),
         };
 
-        public HmacMD5Tests()
-            : base(s_testKeys2202)
+        private static readonly byte[][] s_testMacs2202 =
         {
-        }
+            null,
+            ByteUtils.HexToByteArray("9294727a3638bb1c13f48ef8158bfc9d"),
+            ByteUtils.HexToByteArray("750c783e6ab0b503eaa86e310a5db738"),
+            ByteUtils.HexToByteArray("56be34521d144c88dbb8c733f0e8b3f6"),
+            ByteUtils.HexToByteArray("697eaf0aca3a3aea3a75164746ffaa79"),
+            ByteUtils.HexToByteArray("56461ef2342edc00f9bab995690efd4c"),
+            ByteUtils.HexToByteArray("6b1ab7fe4bd7bf8f0b62e6ce61b9d0cd"),
+            ByteUtils.HexToByteArray("6f630fad67cda0ee1fb1f562db3aa53e"),
+        };
 
-        protected override HMAC Create()
+        public HmacMD5Tests()
+            : base(s_testKeys2202, s_testMacs2202)
         {
-            return new HMACMD5();
         }
 
-        protected override HashAlgorithm CreateHashAlgorithm()
-        {
-            return MD5.Create();
-        }
+        protected override int BlockSize => 64;
+        protected override int MacSize => 16;
+
+        protected override HMAC Create() => new HMACMD5();
+        protected override HashAlgorithm CreateHashAlgorithm() => MD5.Create();
+        protected override byte[] HashDataOneShot(byte[] key, byte[] source) =>
+            HMACMD5.HashData(key, source);
+
+        protected override byte[] HashDataOneShot(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source) =>
+            HMACMD5.HashData(key, source);
+
+        protected override int HashDataOneShot(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source, Span<byte> destination) =>
+            HMACMD5.HashData(key, source, destination);
 
-        protected override int BlockSize { get { return 64; } }
+        protected override bool TryHashDataOneShot(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source, Span<byte> destination, out int written) =>
+            HMACMD5.TryHashData(key, source, destination, out written);
 
         [Fact]
         public void HmacMD5_Rfc2202_1()
         {
-            VerifyHmac(1, "9294727a3638bb1c13f48ef8158bfc9d");
+            VerifyHmac(1, s_testMacs2202[1]);
         }
 
         [Fact]
         public void HmacMD5_Rfc2202_2()
         {
-            VerifyHmac(2, "750c783e6ab0b503eaa86e310a5db738");
+            VerifyHmac(2, s_testMacs2202[2]);
         }
 
         [Fact]
         public void HmacMD5_Rfc2202_3()
         {
-            VerifyHmac(3, "56be34521d144c88dbb8c733f0e8b3f6");
+            VerifyHmac(3, s_testMacs2202[3]);
         }
 
         [Fact]
         public void HmacMD5_Rfc2202_4()
         {
-            VerifyHmac(4, "697eaf0aca3a3aea3a75164746ffaa79");
+            VerifyHmac(4, s_testMacs2202[4]);
         }
 
         [Fact]
         public void HmacMD5_Rfc2202_5()
         {
-            VerifyHmac(5, "56461ef2342edc00f9bab995690efd4c");
+            VerifyHmac(5, s_testMacs2202[5]);
         }
 
         [Fact]
         public void HmacMD5_Rfc2202_6()
         {
-            VerifyHmac(6, "6b1ab7fe4bd7bf8f0b62e6ce61b9d0cd");
+            VerifyHmac(6, s_testMacs2202[6]);
         }
 
         [Fact]
         public void HmacMD5_Rfc2202_7()
         {
-            VerifyHmac(7, "6f630fad67cda0ee1fb1f562db3aa53e");
+            VerifyHmac(7, s_testMacs2202[7]);
         }
 
         [Fact]
index 4cda1cc..49a9d6d 100644 (file)
@@ -21,22 +21,39 @@ namespace System.Security.Cryptography.Hashing.Algorithms.Tests
             ByteUtils.RepeatByte(0xaa, 80),
         };
 
-        public HmacSha1Tests()
-            : base(s_testKeys2202)
+        private static readonly byte[][] s_testMacs2202 =
         {
-        }
+            null,
+            ByteUtils.HexToByteArray("b617318655057264e28bc0b6fb378c8ef146be00"),
+            ByteUtils.HexToByteArray("effcdf6ae5eb2fa2d27416d5f184df9c259a7c79"),
+            ByteUtils.HexToByteArray("125d7342b9ac11cd91a39af48aa17b4f63f175d3"),
+            ByteUtils.HexToByteArray("4c9007f4026250c6bc8414f9bf50c86c2d7235da"),
+            ByteUtils.HexToByteArray("4c1a03424b55e07fe7f27be1d58bb9324a9a5a04"),
+            ByteUtils.HexToByteArray("aa4ae5e15272d00e95705637ce8a3b55ed402112"),
+            ByteUtils.HexToByteArray("e8e99d0f45237d786d6bbaa7965c7808bbff1a91"),
+        };
 
-        protected override HMAC Create()
+        public HmacSha1Tests()
+            : base(s_testKeys2202, s_testMacs2202)
         {
-            return new HMACSHA1();
         }
 
-        protected override HashAlgorithm CreateHashAlgorithm()
-        {
-            return SHA1.Create();
-        }
+        protected override int BlockSize => 64;
+        protected override int MacSize => 20;
+
+        protected override HMAC Create() => new HMACSHA1();
+        protected override HashAlgorithm CreateHashAlgorithm() => SHA1.Create();
+        protected override byte[] HashDataOneShot(byte[] key, byte[] source) =>
+            HMACSHA1.HashData(key, source);
+
+        protected override byte[] HashDataOneShot(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source) =>
+            HMACSHA1.HashData(key, source);
+
+        protected override int HashDataOneShot(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source, Span<byte> destination) =>
+            HMACSHA1.HashData(key, source, destination);
 
-        protected override int BlockSize { get { return 64; } }
+        protected override bool TryHashDataOneShot(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source, Span<byte> destination, out int written) =>
+            HMACSHA1.TryHashData(key, source, destination, out written);
 
         [Fact]
         public void HmacSha1_Byte_Constructors()
@@ -69,43 +86,43 @@ namespace System.Security.Cryptography.Hashing.Algorithms.Tests
         [Fact]
         public void HmacSha1_Rfc2202_1()
         {
-            VerifyHmac(1, "b617318655057264e28bc0b6fb378c8ef146be00");
+            VerifyHmac(1, s_testMacs2202[1]);
         }
 
         [Fact]
         public void HmacSha1_Rfc2202_2()
         {
-            VerifyHmac(2, "effcdf6ae5eb2fa2d27416d5f184df9c259a7c79");
+            VerifyHmac(2, s_testMacs2202[2]);
         }
 
         [Fact]
         public void HmacSha1_Rfc2202_3()
         {
-            VerifyHmac(3, "125d7342b9ac11cd91a39af48aa17b4f63f175d3");
+            VerifyHmac(3, s_testMacs2202[3]);
         }
 
         [Fact]
         public void HmacSha1_Rfc2202_4()
         {
-            VerifyHmac(4, "4c9007f4026250c6bc8414f9bf50c86c2d7235da");
+            VerifyHmac(4, s_testMacs2202[4]);
         }
 
         [Fact]
         public void HmacSha1_Rfc2202_5()
         {
-            VerifyHmac(5, "4c1a03424b55e07fe7f27be1d58bb9324a9a5a04");
+            VerifyHmac(5, s_testMacs2202[5]);
         }
 
         [Fact]
         public void HmacSha1_Rfc2202_6()
         {
-            VerifyHmac(6, "aa4ae5e15272d00e95705637ce8a3b55ed402112");
+            VerifyHmac(6, s_testMacs2202[6]);
         }
 
         [Fact]
         public void HmacSha1_Rfc2202_7()
         {
-            VerifyHmac(7, "e8e99d0f45237d786d6bbaa7965c7808bbff1a91");
+            VerifyHmac(7, s_testMacs2202[7]);
         }
 
         [Fact]
index a9d12e5..ae9004e 100644 (file)
@@ -1,6 +1,7 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
+using Test.Cryptography;
 using Xunit;
 
 namespace System.Security.Cryptography.Hashing.Algorithms.Tests
@@ -8,59 +9,80 @@ namespace System.Security.Cryptography.Hashing.Algorithms.Tests
     [SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")]
     public class HmacSha256Tests : Rfc4231HmacTests
     {
-        protected override HMAC Create()
+        protected override int BlockSize => 64;
+        protected override int MacSize => 32;
+
+        protected override HMAC Create() => new HMACSHA256();
+        protected override HashAlgorithm CreateHashAlgorithm() => SHA256.Create();
+        protected override byte[] HashDataOneShot(byte[] key, byte[] source) =>
+            HMACSHA256.HashData(key, source);
+
+        protected override byte[] HashDataOneShot(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source) =>
+            HMACSHA256.HashData(key, source);
+
+        protected override int HashDataOneShot(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source, Span<byte> destination) =>
+            HMACSHA256.HashData(key, source, destination);
+
+        protected override bool TryHashDataOneShot(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source, Span<byte> destination, out int written) =>
+            HMACSHA256.TryHashData(key, source, destination, out written);
+
+        private static byte[][] s_testMacs4231 =
         {
-            return new HMACSHA256();
-        }
+            null,
+            ByteUtils.HexToByteArray("b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7"),
+            ByteUtils.HexToByteArray("5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843"),
+            ByteUtils.HexToByteArray("773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514ced565fe"),
+            ByteUtils.HexToByteArray("82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff46729665b"),
+            // RFC 4231 only defines the first 128 bits of this value.
+            ByteUtils.HexToByteArray("a3b6167473100ee06e0c796c2955552b"),
+            ByteUtils.HexToByteArray("60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54"),
+            ByteUtils.HexToByteArray("9b09ffa71b942fcb27635fbcd5b0e944bfdc63644f0713938a7f51535c3a35e2"),
+        };
 
-        protected override HashAlgorithm CreateHashAlgorithm()
+        public HmacSha256Tests() : base(s_testMacs4231)
         {
-            return SHA256.Create();
         }
 
-        protected override int BlockSize { get { return 64; } }
-
         [Fact]
         public void HmacSha256_Rfc4231_1()
         {
-            VerifyHmac(1, "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7");
+            VerifyHmac(1, s_testMacs4231[1]);
         }
 
         [Fact]
         public void HmacSha256_Rfc4231_2()
         {
-            VerifyHmac(2, "5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843");
+            VerifyHmac(2, s_testMacs4231[2]);
         }
 
         [Fact]
         public void HmacSha256_Rfc4231_3()
         {
-            VerifyHmac(3, "773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514ced565fe");
+            VerifyHmac(3, s_testMacs4231[3]);
         }
 
         [Fact]
         public void HmacSha256_Rfc4231_4()
         {
-            VerifyHmac(4, "82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff46729665b");
+            VerifyHmac(4, s_testMacs4231[4]);
         }
 
         [Fact]
         public void HmacSha256_Rfc4231_5()
         {
-            // RFC 4231 only defines the first 128 bits of this value.
-            VerifyHmac(5, "a3b6167473100ee06e0c796c2955552b", 128 / 8);
+            VerifyHmac(5, s_testMacs4231[5]);
         }
 
         [Fact]
         public void HmacSha256_Rfc4231_6()
         {
-            VerifyHmac(6, "60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54");
+            VerifyHmac(6, s_testMacs4231[6]);
         }
 
         [Fact]
         public void HmacSha256_Rfc4231_7()
         {
-            VerifyHmac(7, "9b09ffa71b942fcb27635fbcd5b0e944bfdc63644f0713938a7f51535c3a35e2");
+            VerifyHmac(7, s_testMacs4231[7]);
         }
 
         [Fact]
index 5a5f069..76325f1 100644 (file)
@@ -1,6 +1,7 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
+using Test.Cryptography;
 using Xunit;
 
 namespace System.Security.Cryptography.Hashing.Algorithms.Tests
@@ -8,18 +9,40 @@ namespace System.Security.Cryptography.Hashing.Algorithms.Tests
     [SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")]
     public class HmacSha384Tests : Rfc4231HmacTests
     {
-        protected override HMAC Create()
+        protected override int BlockSize => 128;
+        protected override int MacSize => 48;
+
+        protected override HMAC Create() => new HMACSHA384();
+        protected override HashAlgorithm CreateHashAlgorithm() => SHA384.Create();
+        protected override byte[] HashDataOneShot(byte[] key, byte[] source) =>
+            HMACSHA384.HashData(key, source);
+
+        protected override byte[] HashDataOneShot(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source) =>
+            HMACSHA384.HashData(key, source);
+
+        protected override int HashDataOneShot(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source, Span<byte> destination) =>
+            HMACSHA384.HashData(key, source, destination);
+
+        protected override bool TryHashDataOneShot(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source, Span<byte> destination, out int written) =>
+            HMACSHA384.TryHashData(key, source, destination, out written);
+
+        private static byte[][] s_testMacs4231 =
         {
-            return new HMACSHA384();
-        }
+            null,
+            ByteUtils.HexToByteArray("afd03944d84895626b0825f4ab46907f15f9dadbe4101ec682aa034c7cebc59cfaea9ea9076ede7f4af152e8b2fa9cb6"),
+            ByteUtils.HexToByteArray("af45d2e376484031617f78d2b58a6b1b9c7ef464f5a01b47e42ec3736322445e8e2240ca5e69e2c78b3239ecfab21649"),
+            ByteUtils.HexToByteArray("88062608d3e6ad8a0aa2ace014c8a86f0aa635d947ac9febe83ef4e55966144b2a5ab39dc13814b94e3ab6e101a34f27"),
+            ByteUtils.HexToByteArray("3e8a69b7783c25851933ab6290af6ca77a9981480850009cc5577c6e1f573b4e6801dd23c4a7d679ccf8a386c674cffb"),
+            // RFC 4231 only defines the first 128 bits of this value.
+            ByteUtils.HexToByteArray("3abf34c3503b2a23a46efc619baef897"),
+            ByteUtils.HexToByteArray("4ece084485813e9088d2c63a041bc5b44f9ef1012a2b588f3cd11f05033ac4c60c2ef6ab4030fe8296248df163f44952"),
+            ByteUtils.HexToByteArray("6617178e941f020d351e2f254e8fd32c602420feb0b8fb9adccebb82461e99c5a678cc31e799176d3860e6110c46523e"),
+        };
 
-        protected override HashAlgorithm CreateHashAlgorithm()
+        public HmacSha384Tests() : base(s_testMacs4231)
         {
-            return SHA384.Create();
         }
 
-        protected override int BlockSize { get { return 128; } }
-
         [Fact]
         public void ProduceLegacyHmacValues()
         {
@@ -34,44 +57,43 @@ namespace System.Security.Cryptography.Hashing.Algorithms.Tests
         [Fact]
         public void HmacSha384_Rfc4231_1()
         {
-            VerifyHmac(1, "afd03944d84895626b0825f4ab46907f15f9dadbe4101ec682aa034c7cebc59cfaea9ea9076ede7f4af152e8b2fa9cb6");
+            VerifyHmac(1, s_testMacs4231[1]);
         }
 
         [Fact]
         public void HmacSha384_Rfc4231_2()
         {
-            VerifyHmac(2, "af45d2e376484031617f78d2b58a6b1b9c7ef464f5a01b47e42ec3736322445e8e2240ca5e69e2c78b3239ecfab21649");
+            VerifyHmac(2, s_testMacs4231[2]);
         }
 
         [Fact]
         public void HmacSha384_Rfc4231_3()
         {
-            VerifyHmac(3, "88062608d3e6ad8a0aa2ace014c8a86f0aa635d947ac9febe83ef4e55966144b2a5ab39dc13814b94e3ab6e101a34f27");
+            VerifyHmac(3, s_testMacs4231[3]);
         }
 
         [Fact]
         public void HmacSha384_Rfc4231_4()
         {
-            VerifyHmac(4, "3e8a69b7783c25851933ab6290af6ca77a9981480850009cc5577c6e1f573b4e6801dd23c4a7d679ccf8a386c674cffb");
+            VerifyHmac(4, s_testMacs4231[4]);
         }
 
         [Fact]
         public void HmacSha384_Rfc4231_5()
         {
-            // RFC 4231 only defines the first 128 bits of this value.
-            VerifyHmac(5, "3abf34c3503b2a23a46efc619baef897", 128 / 8);
+            VerifyHmac(5, s_testMacs4231[5]);
         }
 
         [Fact]
         public void HmacSha384_Rfc4231_6()
         {
-            VerifyHmac(6, "4ece084485813e9088d2c63a041bc5b44f9ef1012a2b588f3cd11f05033ac4c60c2ef6ab4030fe8296248df163f44952");
+            VerifyHmac(6, s_testMacs4231[6]);
         }
 
         [Fact]
         public void HmacSha384_Rfc4231_7()
         {
-            VerifyHmac(7, "6617178e941f020d351e2f254e8fd32c602420feb0b8fb9adccebb82461e99c5a678cc31e799176d3860e6110c46523e");
+            VerifyHmac(7, s_testMacs4231[7]);
         }
 
         [Fact]
index 3136ffd..4da1884 100644 (file)
@@ -1,6 +1,7 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
+using Test.Cryptography;
 using Xunit;
 
 namespace System.Security.Cryptography.Hashing.Algorithms.Tests
@@ -8,18 +9,40 @@ namespace System.Security.Cryptography.Hashing.Algorithms.Tests
     [SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")]
     public class HmacSha512Tests : Rfc4231HmacTests
     {
-        protected override HMAC Create()
+        protected override int BlockSize => 128;
+        protected override int MacSize => 64;
+
+        protected override HMAC Create() => new HMACSHA512();
+        protected override HashAlgorithm CreateHashAlgorithm() => SHA512.Create();
+        protected override byte[] HashDataOneShot(byte[] key, byte[] source) =>
+            HMACSHA512.HashData(key, source);
+
+        protected override byte[] HashDataOneShot(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source) =>
+            HMACSHA512.HashData(key, source);
+
+        protected override int HashDataOneShot(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source, Span<byte> destination) =>
+            HMACSHA512.HashData(key, source, destination);
+
+        protected override bool TryHashDataOneShot(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source, Span<byte> destination, out int written) =>
+            HMACSHA512.TryHashData(key, source, destination, out written);
+
+        private static byte[][] s_testMacs4231 =
         {
-            return new HMACSHA512();
-        }
+            null,
+            ByteUtils.HexToByteArray("87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545e17cdedaa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f1702e696c203a126854"),
+            ByteUtils.HexToByteArray("164b7a7bfcf819e2e395fbe73b56e0a387bd64222e831fd610270cd7ea2505549758bf75c05a994a6d034f65f8f0e6fdcaeab1a34d4a6b4b636e070a38bce737"),
+            ByteUtils.HexToByteArray("fa73b0089d56a284efb0f0756c890be9b1b5dbdd8ee81a3655f83e33b2279d39bf3e848279a722c806b485a47e67c807b946a337bee8942674278859e13292fb"),
+            ByteUtils.HexToByteArray("b0ba465637458c6990e5a8c5f61d4af7e576d97ff94b872de76f8050361ee3dba91ca5c11aa25eb4d679275cc5788063a5f19741120c4f2de2adebeb10a298dd"),
+            // RFC 4231 only defines the first 128 bits of this value.
+            ByteUtils.HexToByteArray("415fad6271580a531d4179bc891d87a6"),
+            ByteUtils.HexToByteArray("80b24263c7c1a3ebb71493c1dd7be8b49b46d1f41b4aeec1121b013783f8f3526b56d037e05f2598bd0fd2215d6a1e5295e64f73f63f0aec8b915a985d786598"),
+            ByteUtils.HexToByteArray("e37b6a775dc87dbaa4dfa9f96e5e3ffddebd71f8867289865df5a32d20cdc944b6022cac3c4982b10d5eeb55c3e4de15134676fb6de0446065c97440fa8c6a58"),
+        };
 
-        protected override HashAlgorithm CreateHashAlgorithm()
+        public HmacSha512Tests() : base(s_testMacs4231)
         {
-            return SHA512.Create();
         }
 
-        protected override int BlockSize { get { return 128; } }
-
         [Fact]
         public void ProduceLegacyHmacValues()
         {
@@ -34,44 +57,43 @@ namespace System.Security.Cryptography.Hashing.Algorithms.Tests
         [Fact]
         public void HmacSha512_Rfc4231_1()
         {
-            VerifyHmac(1, "87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545e17cdedaa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f1702e696c203a126854");
+            VerifyHmac(1, s_testMacs4231[1]);
         }
 
         [Fact]
         public void HmacSha512_Rfc4231_2()
         {
-            VerifyHmac(2, "164b7a7bfcf819e2e395fbe73b56e0a387bd64222e831fd610270cd7ea2505549758bf75c05a994a6d034f65f8f0e6fdcaeab1a34d4a6b4b636e070a38bce737");
+            VerifyHmac(2, s_testMacs4231[2]);
         }
 
         [Fact]
         public void HmacSha512_Rfc4231_3()
         {
-            VerifyHmac(3, "fa73b0089d56a284efb0f0756c890be9b1b5dbdd8ee81a3655f83e33b2279d39bf3e848279a722c806b485a47e67c807b946a337bee8942674278859e13292fb");
+            VerifyHmac(3, s_testMacs4231[3]);
         }
 
         [Fact]
         public void HmacSha512_Rfc4231_4()
         {
-            VerifyHmac(4, "b0ba465637458c6990e5a8c5f61d4af7e576d97ff94b872de76f8050361ee3dba91ca5c11aa25eb4d679275cc5788063a5f19741120c4f2de2adebeb10a298dd");
+            VerifyHmac(4, s_testMacs4231[4]);
         }
 
         [Fact]
         public void HmacSha512_Rfc4231_5()
         {
-            // RFC 4231 only defines the first 128 bits of this value.
-            VerifyHmac(5, "415fad6271580a531d4179bc891d87a6", 128 / 8);
+            VerifyHmac(5, s_testMacs4231[5]);
         }
 
         [Fact]
         public void HmacSha512_Rfc4231_6()
         {
-            VerifyHmac(6, "80b24263c7c1a3ebb71493c1dd7be8b49b46d1f41b4aeec1121b013783f8f3526b56d037e05f2598bd0fd2215d6a1e5295e64f73f63f0aec8b915a985d786598");
+            VerifyHmac(6, s_testMacs4231[6]);
         }
 
         [Fact]
         public void HmacSha512_Rfc4231_7()
         {
-            VerifyHmac(7, "e37b6a775dc87dbaa4dfa9f96e5e3ffddebd71f8867289865df5a32d20cdc944b6022cac3c4982b10d5eeb55c3e4de15134676fb6de0446065c97440fa8c6a58");
+            VerifyHmac(7, s_testMacs4231[7]);
         }
 
         [Fact]
index 13a2468..510f58d 100644 (file)
@@ -15,31 +15,36 @@ namespace System.Security.Cryptography.Hashing.Algorithms.Tests
         // They share the same datasets for cases 1-5, but cases 6 and 7 differ.
         private readonly byte[][] _testKeys;
         private readonly byte[][] _testData;
+        private readonly byte[][] _testMacs;
 
-        protected HmacTests(byte[][] testKeys, byte[][] testData)
+        protected HmacTests(byte[][] testKeys, byte[][] testData, byte[][] testMacs)
         {
             _testKeys = testKeys;
             _testData = testData;
+            _testMacs = testMacs;
         }
 
         protected abstract HMAC Create();
 
         protected abstract HashAlgorithm CreateHashAlgorithm();
+        protected abstract byte[] HashDataOneShot(byte[] key, byte[] source);
+        protected abstract byte[] HashDataOneShot(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source);
+        protected abstract int HashDataOneShot(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source, Span<byte> destination);
+        protected abstract bool TryHashDataOneShot(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source, Span<byte> destination, out int written);
 
         protected abstract int BlockSize { get; }
+        protected abstract int MacSize { get; }
 
-        protected void VerifyHmac(
-            int testCaseId,
-            string digest,
-            int truncateSize = -1)
+        protected void VerifyHmac(int testCaseId, byte[] digestBytes)
         {
-            byte[] digestBytes = ByteUtils.HexToByteArray(digest);
             byte[] data = _testData[testCaseId];
             byte[] computedDigest;
+            int truncateSize = digestBytes.Length;
+            AssertExtensions.LessThanOrEqualTo(truncateSize, MacSize);
 
             using (HMAC hmac = Create())
             {
-                Assert.True(hmac.HashSize > 0);
+                Assert.Equal(MacSize, hmac.HashSize / 8);
 
                 byte[] key = (byte[])_testKeys[testCaseId].Clone();
                 hmac.Key = key;
@@ -55,13 +60,7 @@ namespace System.Security.Cryptography.Hashing.Algorithms.Tests
                 computedDigest = hmac.ComputeHash(data);
             }
 
-            if (truncateSize != -1)
-            {
-                byte[] tmp = new byte[truncateSize];
-                Array.Copy(computedDigest, tmp, truncateSize);
-                computedDigest = tmp;
-            }
-
+            computedDigest = Truncate(computedDigest, truncateSize);
             Assert.Equal(digestBytes, computedDigest);
 
             using (HMAC hmac = Create())
@@ -76,14 +75,22 @@ namespace System.Security.Cryptography.Hashing.Algorithms.Tests
                 computedDigest = hmac.Hash;
             }
 
-            if (truncateSize != -1)
-            {
-                byte[] tmp = new byte[truncateSize];
-                Array.Copy(computedDigest, tmp, truncateSize);
-                computedDigest = tmp;
-            }
+            computedDigest = Truncate(computedDigest, truncateSize);
+            Assert.Equal(digestBytes, computedDigest);
+
+            // One shot - allocating and byte array inputs
+            computedDigest = HashDataOneShot(_testKeys[testCaseId], data);
 
+            computedDigest = Truncate(computedDigest, truncateSize);
             Assert.Equal(digestBytes, computedDigest);
+
+            static byte[] Truncate(byte[] digest, int truncateSize)
+            {
+                if (truncateSize == -1)
+                    return digest;
+
+                return digest.AsSpan(0, truncateSize).ToArray();
+            }
         }
 
         protected void VerifyHmac_KeyAlreadySet(
@@ -232,5 +239,152 @@ namespace System.Security.Cryptography.Hashing.Algorithms.Tests
                 AssertExtensions.Throws<ArgumentNullException>("value", () => hash.Key = null);
             }
         }
+
+        [Fact]
+        public void OneShot_NullKey_ArgumentNullException()
+        {
+            AssertExtensions.Throws<ArgumentNullException>("key", () =>
+                HashDataOneShot(key: (byte[])null, source: Array.Empty<byte>()));
+        }
+
+        [Fact]
+        public void OneShot_NullSource_ArgumentNullException()
+        {
+            AssertExtensions.Throws<ArgumentNullException>("source", () =>
+                HashDataOneShot(key: Array.Empty<byte>(), source: (byte[])null));
+        }
+
+        [Fact]
+        public void OneShot_ExistingBuffer_TooSmall()
+        {
+            byte[] buffer = new byte[MacSize - 1];
+            byte[] key = _testKeys[1];
+            byte[] data = _testData[1];
+
+            AssertExtensions.Throws<ArgumentException>("destination", () =>
+                HashDataOneShot(key, data, buffer));
+
+            AssertExtensions.FilledWith<byte>(0, buffer);
+        }
+
+        [Fact]
+        public void OneShot_TryExistingBuffer_TooSmall()
+        {
+            byte[] buffer = new byte[MacSize - 1];
+            byte[] key = _testKeys[1];
+            byte[] data = _testData[1];
+
+            Assert.False(TryHashDataOneShot(key, data, buffer, out int written));
+            Assert.Equal(0, written);
+            AssertExtensions.FilledWith<byte>(0, buffer);
+        }
+
+        [Fact]
+        public void OneShot_TryExistingBuffer_Exact()
+        {
+            for (int caseId = 1; caseId <= 7; caseId++)
+            {
+                byte[] buffer = new byte[MacSize];
+                byte[] key = _testKeys[caseId];
+                byte[] data = _testData[caseId];
+
+                Assert.True(TryHashDataOneShot(key, data, buffer, out int written));
+                Assert.Equal(MacSize, written);
+
+                ReadOnlySpan<byte> expectedMac = _testMacs[caseId];
+                Span<byte> truncatedBuffer = buffer.AsSpan(0, expectedMac.Length);
+                AssertExtensions.SequenceEqual(expectedMac, truncatedBuffer);
+            }
+        }
+
+        [Fact]
+        public void OneShot_TryExistingBuffer_Larger()
+        {
+            for (int caseId = 1; caseId <= 7; caseId++)
+            {
+                Span<byte> buffer = new byte[MacSize + 20];
+                byte[] key = _testKeys[caseId];
+                byte[] data = _testData[caseId];
+
+                buffer.Fill(0xCC);
+                Span<byte> writeBuffer = buffer.Slice(10, MacSize);
+
+                Assert.True(TryHashDataOneShot(key, data, writeBuffer, out int written));
+                Assert.Equal(MacSize, written);
+
+                ReadOnlySpan<byte> expectedMac = _testMacs[caseId];
+                Span<byte> truncatedWriteBuffer = writeBuffer.Slice(0, expectedMac.Length);
+                AssertExtensions.SequenceEqual(expectedMac, truncatedWriteBuffer);
+                AssertExtensions.FilledWith<byte>(0xCC, buffer[..10]);
+                AssertExtensions.FilledWith<byte>(0xCC, buffer[^10..]);
+            }
+        }
+
+        [Theory]
+        [InlineData(0, 10)]
+        [InlineData(10, 10)]
+        [InlineData(10, 0)]
+        [InlineData(10, 20)]
+        public void OneShot_TryExistingBuffer_OverlapsKey(int keyOffset, int bufferOffset)
+        {
+            for (int caseId = 1; caseId <= 7; caseId++)
+            {
+                byte[] key = _testKeys[caseId];
+                byte[] data = _testData[caseId];
+                Span<byte> buffer = new byte[Math.Max(key.Length, MacSize) + Math.Max(keyOffset, bufferOffset)];
+
+                Span<byte> writeBuffer = buffer.Slice(bufferOffset, MacSize);
+                Span<byte> keyBuffer = buffer.Slice(keyOffset, key.Length);
+                key.AsSpan().CopyTo(keyBuffer);
+
+                Assert.True(TryHashDataOneShot(keyBuffer, data, writeBuffer, out int written));
+                Assert.Equal(MacSize, written);
+
+                ReadOnlySpan<byte> expectedMac = _testMacs[caseId];
+                Span<byte> truncatedWriteBuffer = writeBuffer.Slice(0, expectedMac.Length);
+                AssertExtensions.SequenceEqual(expectedMac, truncatedWriteBuffer);
+            }
+        }
+
+        [Theory]
+        [InlineData(0, 10)]
+        [InlineData(10, 10)]
+        [InlineData(10, 0)]
+        [InlineData(10, 20)]
+        public void OneShot_TryExistingBuffer_OverlapsSource(int sourceOffset, int bufferOffset)
+        {
+            for (int caseId = 1; caseId <= 7; caseId++)
+            {
+                byte[] key = _testKeys[caseId];
+                byte[] data = _testData[caseId];
+                Span<byte> buffer = new byte[Math.Max(data.Length, MacSize) + Math.Max(sourceOffset, bufferOffset)];
+
+                Span<byte> writeBuffer = buffer.Slice(bufferOffset, MacSize);
+                Span<byte> dataBuffer = buffer.Slice(sourceOffset, data.Length);
+                data.AsSpan().CopyTo(dataBuffer);
+
+                Assert.True(TryHashDataOneShot(key, dataBuffer, writeBuffer, out int written));
+                Assert.Equal(MacSize, written);
+
+                ReadOnlySpan<byte> expectedMac = _testMacs[caseId];
+                Span<byte> truncatedWriteBuffer = writeBuffer.Slice(0, expectedMac.Length);
+                AssertExtensions.SequenceEqual(expectedMac, truncatedWriteBuffer);
+            }
+        }
+
+        [Theory]
+        [InlineData(new byte[0], new byte[] { 1 })]
+        [InlineData(new byte[] { 1 }, new byte[0])]
+        public void OneShot_Empty_Matches_Instances(byte[] key, byte[] source)
+        {
+            using (HMAC hash = Create())
+            {
+                hash.Key = key;
+                byte[] mac = hash.ComputeHash(source, 0, source.Length);
+
+                byte[] oneShot = HashDataOneShot(key, source);
+                Assert.Equal(mac, oneShot);
+            }
+        }
     }
 }
index e66cd89..8486486 100644 (file)
@@ -24,8 +24,8 @@ namespace System.Security.Cryptography.Hashing.Algorithms.Tests
         // The keys for test cases 1, 3, and 5 for RFC2202 are sized to match the
         // algorithm (16 bytes for MD5, 20 for SHA-1), so they need to be provided by
         // the more derived type.
-        protected Rfc2202HmacTests(byte[][] testKeys) :
-            base(testKeys, s_testData2202)
+        protected Rfc2202HmacTests(byte[][] testKeys, byte[][] testMacs) :
+            base(testKeys, s_testData2202, testMacs)
         {
         }
     }
index af8e3e6..771973a 100644 (file)
@@ -33,8 +33,8 @@ namespace System.Security.Cryptography.Hashing.Algorithms.Tests
             ByteUtils.AsciiBytes("This is a test using a larger than block-size key and a larger than block-size data. The key needs to be hashed before being used by the HMAC algorithm."),
         };
 
-        protected Rfc4231HmacTests() :
-            base(s_testKeys4231, s_testData4231)
+        protected Rfc4231HmacTests(byte[][] testMacs) :
+            base(s_testKeys4231, s_testData4231, testMacs)
         {
         }
     }