Use substitute SHA-1 implementation in wasm (#44982)
authorLevi Broderick <GrabYourPitchforks@users.noreply.github.com>
Fri, 20 Nov 2020 15:38:00 +0000 (07:38 -0800)
committerGitHub <noreply@github.com>
Fri, 20 Nov 2020 15:38:00 +0000 (10:38 -0500)
* Use different managed SHA-1 implementation

* Add missing call to Start

src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/SHAHashProvider.Browser.cs

index 9fd4ab1..4515b8a 100644 (file)
@@ -4,6 +4,7 @@
 using System;
 using System.IO;
 using System.Diagnostics;
+using System.Numerics;
 using System.Security.Cryptography;
 
 namespace Internal.Cryptography
@@ -84,213 +85,186 @@ namespace Internal.Cryptography
             public abstract byte[] HashFinal();
         }
 
-        // ported from https://github.com/microsoft/referencesource/blob/a48449cb48a9a693903668a71449ac719b76867c/mscorlib/system/security/cryptography/sha1managed.cs
+        // Ported from src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/EventSource.cs.
+        // n.b. It's ok to use a "non-secret purposes" hashing implementation here, as this is only
+        // used in wasm scenarios, and as of the current release we don't make any security guarantees
+        // about our crypto primitives in wasm environments.
         private class SHA1ManagedImplementation : SHAManagedImplementationBase
         {
-            private byte[] _buffer;
-            private long _count; // Number of bytes in the hashed message
-            private uint[] _stateSHA1;
-            private uint[] _expandedBuffer;
+            private Sha1ForNonSecretPurposes _state; // mutable struct - don't make readonly
 
-            public SHA1ManagedImplementation()
+            public override void Initialize()
             {
-                _stateSHA1 = new uint[5];
-                _buffer = new byte[64];
-                _expandedBuffer = new uint[80];
-
-                InitializeState();
+                _state = default;
+                _state.Start();
             }
 
-            public override void Initialize()
+            public override void HashCore(byte[] partIn, int ibStart, int cbSize)
             {
-                InitializeState();
-
-                // Zeroize potentially sensitive information.
-                Array.Clear(_buffer, 0, _buffer.Length);
-                Array.Clear(_expandedBuffer, 0, _expandedBuffer.Length);
+                _state.Append(partIn.AsSpan(ibStart, cbSize));
             }
 
-            private void InitializeState()
+            public override byte[] HashFinal()
             {
-                _count = 0;
-
-                _stateSHA1[0] = 0x67452301;
-                _stateSHA1[1] = 0xefcdab89;
-                _stateSHA1[2] = 0x98badcfe;
-                _stateSHA1[3] = 0x10325476;
-                _stateSHA1[4] = 0xc3d2e1f0;
+                byte[] output = new byte[20];
+                _state.Finish(output);
+                return output;
             }
 
-            /* Copyright (C) RSA Data Security, Inc. created 1993.  This is an
-            unpublished work protected as such under copyright law.  This work
-            contains proprietary, confidential, and trade secret information of
-            RSA Data Security, Inc.  Use, disclosure or reproduction without the
-            express written authorization of RSA Data Security, Inc. is
-            prohibited.
-            */
-
-            /* SHA block update operation. Continues an SHA message-digest
-            operation, processing another message block, and updating the
-            context.
-            */
-            public override unsafe void HashCore(byte[] partIn, int ibStart, int cbSize)
+            /// <summary>
+            /// Implements the SHA1 hashing algorithm. Note that this
+            /// implementation is for hashing public information. Do not
+            /// use this code to hash private data, as this implementation does
+            /// not take any steps to avoid information disclosure.
+            /// </summary>
+            private struct Sha1ForNonSecretPurposes
             {
-                int bufferLen;
-                int partInLen = cbSize;
-                int partInBase = ibStart;
-
-                /* Compute length of buffer */
-                bufferLen = (int)(_count & 0x3f);
-
-                /* Update number of bytes */
-                _count += partInLen;
+                private long length; // Total message length in bits
+                private uint[] w; // Workspace
+                private int pos; // Length of current chunk in bytes
+
+                /// <summary>
+                /// Call Start() to initialize the hash object.
+                /// </summary>
+                public void Start()
+                {
+                    this.w ??= new uint[85];
+
+                    this.length = 0;
+                    this.pos = 0;
+                    this.w[80] = 0x67452301;
+                    this.w[81] = 0xEFCDAB89;
+                    this.w[82] = 0x98BADCFE;
+                    this.w[83] = 0x10325476;
+                    this.w[84] = 0xC3D2E1F0;
+                }
 
-                fixed (uint* stateSHA1 = _stateSHA1)
+                /// <summary>
+                /// Adds an input byte to the hash.
+                /// </summary>
+                /// <param name="input">Data to include in the hash.</param>
+                public void Append(byte input)
                 {
-                    fixed (byte* buffer = _buffer)
+                    this.w[this.pos / 4] = (this.w[this.pos / 4] << 8) | input;
+                    if (64 == ++this.pos)
                     {
-                        fixed (uint* expandedBuffer = _expandedBuffer)
-                        {
-                            if ((bufferLen > 0) && (bufferLen + partInLen >= 64))
-                            {
-                                Buffer.BlockCopy(partIn, partInBase, _buffer, bufferLen, 64 - bufferLen);
-                                partInBase += (64 - bufferLen);
-                                partInLen -= (64 - bufferLen);
-                                SHATransform(expandedBuffer, stateSHA1, buffer);
-                                bufferLen = 0;
-                            }
-
-                            /* Copy input to temporary buffer and hash */
-                            while (partInLen >= 64)
-                            {
-                                Buffer.BlockCopy(partIn, partInBase, _buffer, 0, 64);
-                                partInBase += 64;
-                                partInLen -= 64;
-                                SHATransform(expandedBuffer, stateSHA1, buffer);
-                            }
-
-                            if (partInLen > 0)
-                            {
-                                Buffer.BlockCopy(partIn, partInBase, _buffer, bufferLen, partInLen);
-                            }
-                        }
+                        this.Drain();
                     }
                 }
-            }
 
-            /* SHA finalization. Ends an SHA message-digest operation, writing
-            the message digest.
-                */
-            public override byte[] HashFinal()
-            {
-                byte[] pad;
-                int padLen;
-                long bitCount;
-                byte[] hash = new byte[20];
-
-                /* Compute padding: 80 00 00 ... 00 00 <bit count>
-                */
-
-                padLen = 64 - (int)(_count & 0x3f);
-                if (padLen <= 8)
-                    padLen += 64;
-
-                pad = new byte[padLen];
-                pad[0] = 0x80;
-
-                //  Convert count to bit count
-                bitCount = _count * 8;
-
-                pad[padLen - 8] = (byte)((bitCount >> 56) & 0xff);
-                pad[padLen - 7] = (byte)((bitCount >> 48) & 0xff);
-                pad[padLen - 6] = (byte)((bitCount >> 40) & 0xff);
-                pad[padLen - 5] = (byte)((bitCount >> 32) & 0xff);
-                pad[padLen - 4] = (byte)((bitCount >> 24) & 0xff);
-                pad[padLen - 3] = (byte)((bitCount >> 16) & 0xff);
-                pad[padLen - 2] = (byte)((bitCount >> 8) & 0xff);
-                pad[padLen - 1] = (byte)((bitCount >> 0) & 0xff);
-
-                /* Digest padding */
-                HashCore(pad, 0, pad.Length);
-
-                /* Store digest */
-                SHAUtils.DWORDToBigEndian(hash, _stateSHA1, 5);
-
-                return hash;
-            }
-
-            private unsafe void SHATransform(uint* expandedBuffer, uint* state, byte* block)
-            {
-                uint a = state[0];
-                uint b = state[1];
-                uint c = state[2];
-                uint d = state[3];
-                uint e = state[4];
-
-                int i;
-
-                SHAUtils.DWORDFromBigEndian(expandedBuffer, 16, block);
-                SHAExpand(expandedBuffer);
-
-                /* Round 1 */
-                for (i = 0; i < 20; i += 5)
+                /// <summary>
+                /// Adds input bytes to the hash.
+                /// </summary>
+                /// <param name="input">
+                /// Data to include in the hash. Must not be null.
+                /// </param>
+                public void Append(ReadOnlySpan<byte> input)
                 {
-                    { (e) += (((((a)) << (5)) | (((a)) >> (32 - (5)))) + ((d) ^ ((b) & ((c) ^ (d)))) + (expandedBuffer[i]) + 0x5a827999); (b) = ((((b)) << (30)) | (((b)) >> (32 - (30)))); }
-                    { (d) += (((((e)) << (5)) | (((e)) >> (32 - (5)))) + ((c) ^ ((a) & ((b) ^ (c)))) + (expandedBuffer[i + 1]) + 0x5a827999); (a) = ((((a)) << (30)) | (((a)) >> (32 - (30)))); }
-                    { (c) += (((((d)) << (5)) | (((d)) >> (32 - (5)))) + ((b) ^ ((e) & ((a) ^ (b)))) + (expandedBuffer[i + 2]) + 0x5a827999); (e) = ((((e)) << (30)) | (((e)) >> (32 - (30)))); }; ;
-                    { (b) += (((((c)) << (5)) | (((c)) >> (32 - (5)))) + ((a) ^ ((d) & ((e) ^ (a)))) + (expandedBuffer[i + 3]) + 0x5a827999); (d) = ((((d)) << (30)) | (((d)) >> (32 - (30)))); }; ;
-                    { (a) += (((((b)) << (5)) | (((b)) >> (32 - (5)))) + ((e) ^ ((c) & ((d) ^ (e)))) + (expandedBuffer[i + 4]) + 0x5a827999); (c) = ((((c)) << (30)) | (((c)) >> (32 - (30)))); }; ;
+                    foreach (byte b in input)
+                    {
+                        this.Append(b);
+                    }
                 }
 
-                /* Round 2 */
-                for (; i < 40; i += 5)
+                /// <summary>
+                /// Retrieves the hash value.
+                /// Note that after calling this function, the hash object should
+                /// be considered uninitialized. Subsequent calls to Append or
+                /// Finish will produce useless results. Call Start() to
+                /// reinitialize.
+                /// </summary>
+                /// <param name="output">
+                /// Buffer to receive the hash value. Must not be null.
+                /// Up to 20 bytes of hash will be written to the output buffer.
+                /// If the buffer is smaller than 20 bytes, the remaining hash
+                /// bytes will be lost. If the buffer is larger than 20 bytes, the
+                /// rest of the buffer is left unmodified.
+                /// </param>
+                public void Finish(byte[] output)
                 {
-                    { (e) += (((((a)) << (5)) | (((a)) >> (32 - (5)))) + ((b) ^ (c) ^ (d)) + (expandedBuffer[i]) + 0x6ed9eba1); (b) = ((((b)) << (30)) | (((b)) >> (32 - (30)))); }; ;
-                    { (d) += (((((e)) << (5)) | (((e)) >> (32 - (5)))) + ((a) ^ (b) ^ (c)) + (expandedBuffer[i + 1]) + 0x6ed9eba1); (a) = ((((a)) << (30)) | (((a)) >> (32 - (30)))); }; ;
-                    { (c) += (((((d)) << (5)) | (((d)) >> (32 - (5)))) + ((e) ^ (a) ^ (b)) + (expandedBuffer[i + 2]) + 0x6ed9eba1); (e) = ((((e)) << (30)) | (((e)) >> (32 - (30)))); }; ;
-                    { (b) += (((((c)) << (5)) | (((c)) >> (32 - (5)))) + ((d) ^ (e) ^ (a)) + (expandedBuffer[i + 3]) + 0x6ed9eba1); (d) = ((((d)) << (30)) | (((d)) >> (32 - (30)))); }; ;
-                    { (a) += (((((b)) << (5)) | (((b)) >> (32 - (5)))) + ((c) ^ (d) ^ (e)) + (expandedBuffer[i + 4]) + 0x6ed9eba1); (c) = ((((c)) << (30)) | (((c)) >> (32 - (30)))); }; ;
-                }
+                    long l = this.length + 8 * this.pos;
+                    this.Append(0x80);
+                    while (this.pos != 56)
+                    {
+                        this.Append(0x00);
+                    }
 
-                /* Round 3 */
-                for (; i < 60; i += 5)
-                {
-                    { (e) += (((((a)) << (5)) | (((a)) >> (32 - (5)))) + (((b) & (c)) | ((d) & ((b) | (c)))) + (expandedBuffer[i]) + 0x8f1bbcdc); (b) = ((((b)) << (30)) | (((b)) >> (32 - (30)))); }; ;
-                    { (d) += (((((e)) << (5)) | (((e)) >> (32 - (5)))) + (((a) & (b)) | ((c) & ((a) | (b)))) + (expandedBuffer[i + 1]) + 0x8f1bbcdc); (a) = ((((a)) << (30)) | (((a)) >> (32 - (30)))); }; ;
-                    { (c) += (((((d)) << (5)) | (((d)) >> (32 - (5)))) + (((e) & (a)) | ((b) & ((e) | (a)))) + (expandedBuffer[i + 2]) + 0x8f1bbcdc); (e) = ((((e)) << (30)) | (((e)) >> (32 - (30)))); }; ;
-                    { (b) += (((((c)) << (5)) | (((c)) >> (32 - (5)))) + (((d) & (e)) | ((a) & ((d) | (e)))) + (expandedBuffer[i + 3]) + 0x8f1bbcdc); (d) = ((((d)) << (30)) | (((d)) >> (32 - (30)))); }; ;
-                    { (a) += (((((b)) << (5)) | (((b)) >> (32 - (5)))) + (((c) & (d)) | ((e) & ((c) | (d)))) + (expandedBuffer[i + 4]) + 0x8f1bbcdc); (c) = ((((c)) << (30)) | (((c)) >> (32 - (30)))); }; ;
+                    unchecked
+                    {
+                        this.Append((byte)(l >> 56));
+                        this.Append((byte)(l >> 48));
+                        this.Append((byte)(l >> 40));
+                        this.Append((byte)(l >> 32));
+                        this.Append((byte)(l >> 24));
+                        this.Append((byte)(l >> 16));
+                        this.Append((byte)(l >> 8));
+                        this.Append((byte)l);
+
+                        int end = output.Length < 20 ? output.Length : 20;
+                        for (int i = 0; i != end; i++)
+                        {
+                            uint temp = this.w[80 + i / 4];
+                            output[i] = (byte)(temp >> 24);
+                            this.w[80 + i / 4] = temp << 8;
+                        }
+                    }
                 }
 
-                /* Round 4 */
-                for (; i < 80; i += 5)
+                /// <summary>
+                /// Called when this.pos reaches 64.
+                /// </summary>
+                private void Drain()
                 {
-                    { (e) += (((((a)) << (5)) | (((a)) >> (32 - (5)))) + ((b) ^ (c) ^ (d)) + (expandedBuffer[i]) + 0xca62c1d6); (b) = ((((b)) << (30)) | (((b)) >> (32 - (30)))); }; ;
-                    { (d) += (((((e)) << (5)) | (((e)) >> (32 - (5)))) + ((a) ^ (b) ^ (c)) + (expandedBuffer[i + 1]) + 0xca62c1d6); (a) = ((((a)) << (30)) | (((a)) >> (32 - (30)))); }; ;
-                    { (c) += (((((d)) << (5)) | (((d)) >> (32 - (5)))) + ((e) ^ (a) ^ (b)) + (expandedBuffer[i + 2]) + 0xca62c1d6); (e) = ((((e)) << (30)) | (((e)) >> (32 - (30)))); }; ;
-                    { (b) += (((((c)) << (5)) | (((c)) >> (32 - (5)))) + ((d) ^ (e) ^ (a)) + (expandedBuffer[i + 3]) + 0xca62c1d6); (d) = ((((d)) << (30)) | (((d)) >> (32 - (30)))); }; ;
-                    { (a) += (((((b)) << (5)) | (((b)) >> (32 - (5)))) + ((c) ^ (d) ^ (e)) + (expandedBuffer[i + 4]) + 0xca62c1d6); (c) = ((((c)) << (30)) | (((c)) >> (32 - (30)))); }; ;
-                }
+                    for (int i = 16; i != 80; i++)
+                    {
+                        this.w[i] = BitOperations.RotateLeft(this.w[i - 3] ^ this.w[i - 8] ^ this.w[i - 14] ^ this.w[i - 16], 1);
+                    }
 
-                state[0] += a;
-                state[1] += b;
-                state[2] += c;
-                state[3] += d;
-                state[4] += e;
-            }
+                    unchecked
+                    {
+                        uint a = this.w[80];
+                        uint b = this.w[81];
+                        uint c = this.w[82];
+                        uint d = this.w[83];
+                        uint e = this.w[84];
 
-            /* Expands x[0..15] into x[16..79], according to the recurrence
-            x[i] = x[i-3] ^ x[i-8] ^ x[i-14] ^ x[i-16].
-            */
-            private unsafe void SHAExpand(uint* x)
-            {
-                int i;
-                uint tmp;
+                        for (int i = 0; i != 20; i++)
+                        {
+                            const uint k = 0x5A827999;
+                            uint f = (b & c) | ((~b) & d);
+                            uint temp = BitOperations.RotateLeft(a, 5) + f + e + k + this.w[i]; e = d; d = c; c = BitOperations.RotateLeft(b, 30); b = a; a = temp;
+                        }
 
-                for (i = 16; i < 80; i++)
-                {
-                    tmp = (x[i - 3] ^ x[i - 8] ^ x[i - 14] ^ x[i - 16]);
-                    x[i] = ((tmp << 1) | (tmp >> 31));
+                        for (int i = 20; i != 40; i++)
+                        {
+                            uint f = b ^ c ^ d;
+                            const uint k = 0x6ED9EBA1;
+                            uint temp = BitOperations.RotateLeft(a, 5) + f + e + k + this.w[i]; e = d; d = c; c = BitOperations.RotateLeft(b, 30); b = a; a = temp;
+                        }
+
+                        for (int i = 40; i != 60; i++)
+                        {
+                            uint f = (b & c) | (b & d) | (c & d);
+                            const uint k = 0x8F1BBCDC;
+                            uint temp = BitOperations.RotateLeft(a, 5) + f + e + k + this.w[i]; e = d; d = c; c = BitOperations.RotateLeft(b, 30); b = a; a = temp;
+                        }
+
+                        for (int i = 60; i != 80; i++)
+                        {
+                            uint f = b ^ c ^ d;
+                            const uint k = 0xCA62C1D6;
+                            uint temp = BitOperations.RotateLeft(a, 5) + f + e + k + this.w[i]; e = d; d = c; c = BitOperations.RotateLeft(b, 30); b = a; a = temp;
+                        }
+
+                        this.w[80] += a;
+                        this.w[81] += b;
+                        this.w[82] += c;
+                        this.w[83] += d;
+                        this.w[84] += e;
+                    }
+
+                    this.length += 512; // 64 bytes == 512 bits
+                    this.pos = 0;
                 }
             }
         }