Use HMAC one-shot where appropriate
authorKevin Jones <kevin@vcsjones.com>
Mon, 7 Jun 2021 16:51:44 +0000 (12:51 -0400)
committerGitHub <noreply@github.com>
Mon, 7 Jun 2021 16:51:44 +0000 (09:51 -0700)
src/libraries/Common/src/System/Security/Cryptography/HashOneShotHelpers.cs [new file with mode: 0644]
src/libraries/System.Security.Cryptography.Algorithms/src/System.Security.Cryptography.Algorithms.csproj
src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/HKDF.cs
src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/UnixExportProvider.cs

diff --git a/src/libraries/Common/src/System/Security/Cryptography/HashOneShotHelpers.cs b/src/libraries/Common/src/System/Security/Cryptography/HashOneShotHelpers.cs
new file mode 100644 (file)
index 0000000..95c39e0
--- /dev/null
@@ -0,0 +1,42 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Diagnostics;
+
+namespace System.Security.Cryptography
+{
+    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA5350", Justification = "Weak algorithms are used as instructed by the caller")]
+    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA5351", Justification = "Weak algorithms are used as instructed by the caller")]
+    internal static class HashOneShotHelpers
+    {
+        public static int MacData(
+            HashAlgorithmName hashAlgorithm,
+            ReadOnlySpan<byte> key,
+            ReadOnlySpan<byte> source,
+            Span<byte> destination)
+        {
+            if (hashAlgorithm == HashAlgorithmName.SHA256)
+            {
+                return HMACSHA256.HashData(key, source, destination);
+            }
+            else if (hashAlgorithm == HashAlgorithmName.SHA1)
+            {
+                return HMACSHA1.HashData(key, source, destination);
+            }
+            else if (hashAlgorithm == HashAlgorithmName.SHA512)
+            {
+                return HMACSHA512.HashData(key, source, destination);
+            }
+            else if (hashAlgorithm == HashAlgorithmName.SHA384)
+            {
+                return HMACSHA384.HashData(key, source, destination);
+            }
+            else if (hashAlgorithm == HashAlgorithmName.MD5)
+            {
+                return HMACMD5.HashData(key, source, destination);
+            }
+
+            throw new CryptographicException(SR.Format(SR.Cryptography_UnknownHashAlgorithm, hashAlgorithm.Name));
+        }
+    }
+}
index 566656d..fd8d0a8 100644 (file)
              Link="Common\System\Security\Cryptography\EccKeyFormatHelper.cs" />
     <Compile Include="$(CommonPath)System\Security\Cryptography\KeyBlobHelpers.cs"
              Link="Common\System\Security\Cryptography\KeyBlobHelpers.cs" />
+    <Compile Include="$(CommonPath)System\Security\Cryptography\HashOneShotHelpers.cs"
+             Link="Common\System\Security\Cryptography\HashOneShotHelpers.cs" />
     <Compile Include="$(CommonPath)System\Security\Cryptography\KeyFormatHelper.cs"
              Link="Common\System\Security\Cryptography\KeyFormatHelper.cs" />
     <Compile Include="$(CommonPath)System\Security\Cryptography\KeySizeHelpers.cs"
index 3982e65..418bfd2 100644 (file)
@@ -67,12 +67,8 @@ namespace System.Security.Cryptography
         private static void Extract(HashAlgorithmName hashAlgorithmName, int hashLength, ReadOnlySpan<byte> ikm, ReadOnlySpan<byte> salt, Span<byte> prk)
         {
             Debug.Assert(HashLength(hashAlgorithmName) == hashLength);
-
-            using (IncrementalHash hmac = IncrementalHash.CreateHMAC(hashAlgorithmName, salt))
-            {
-                hmac.AppendData(ikm);
-                GetHashAndReset(hmac, prk);
-            }
+            int written = HashOneShotHelpers.MacData(hashAlgorithmName, salt, ikm, prk);
+            Debug.Assert(written == prk.Length, $"Bytes written is {written} bytes which does not match output length ({prk.Length} bytes)");
         }
 
         /// <summary>
index f353cf2..0ad2762 100644 (file)
@@ -475,44 +475,35 @@ namespace Internal.Cryptography.Pal
             return new ArraySegment<byte>(authSafe, 0, authSafeLength);
         }
 
+        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA5350", Justification = "HMACSHA1 is required for compat with other platforms")]
         private static unsafe byte[] MacAndEncode(
             AsnWriter tmpWriter,
             ReadOnlyMemory<byte> encodedAuthSafe,
             ReadOnlySpan<char> passwordSpan)
         {
-            // Windows/macOS compatibility: Use HMAC-SHA-1,
-            // other algorithms may not be understood
-            byte[] macKey = new byte[20];
-            Span<byte> macSalt = stackalloc byte[20];
-            Span<byte> macSpan = stackalloc byte[20];
-            HashAlgorithmName hashAlgorithm = HashAlgorithmName.SHA1;
+            const int MacSize = 160 / 8; // HMAC-SHA1 is 160 bits.
+            Span<byte> macKey = stackalloc byte[MacSize];
+            Span<byte> macSalt = stackalloc byte[MacSize];
+            Span<byte> macSpan = stackalloc byte[MacSize];
             RandomNumberGenerator.Fill(macSalt);
 
-            fixed (byte* macKeyPtr = macKey)
-            {
-                Span<byte> macKeySpan = macKey;
-
-                Pkcs12Kdf.DeriveMacKey(
-                    passwordSpan,
-                    hashAlgorithm,
-                    s_windowsPbe.IterationCount,
-                    macSalt,
-                    macKeySpan);
-
-                using (IncrementalHash mac = IncrementalHash.CreateHMAC(hashAlgorithm, macKey))
-                {
-                    mac.AppendData(encodedAuthSafe.Span);
+            Pkcs12Kdf.DeriveMacKey(
+                passwordSpan,
+                HashAlgorithmName.SHA1,
+                s_windowsPbe.IterationCount,
+                macSalt,
+                macKey);
 
-                    if (!mac.TryGetHashAndReset(macSpan, out int bytesWritten) || bytesWritten != macSpan.Length)
-                    {
-                        Debug.Fail($"TryGetHashAndReset wrote {bytesWritten} of {macSpan.Length} bytes");
-                        throw new CryptographicException();
-                    }
-                }
+            int bytesWritten = HMACSHA1.HashData(macKey, encodedAuthSafe.Span, macSpan);
 
-                CryptographicOperations.ZeroMemory(macKeySpan);
+            if (bytesWritten != MacSize)
+            {
+                Debug.Fail($"HMACSHA1.HashData wrote {bytesWritten} of {MacSize} bytes");
+                throw new CryptographicException();
             }
 
+            CryptographicOperations.ZeroMemory(macKey);
+
             // https://tools.ietf.org/html/rfc7292#section-4
             //
             // PFX ::= SEQUENCE {