diff options
author | vnugent <public@vaughnnugent.com> | 2024-02-29 21:23:26 -0500 |
---|---|---|
committer | vnugent <public@vaughnnugent.com> | 2024-02-29 21:23:26 -0500 |
commit | b679ddd4e647ac915febd0d5a5e488a1e8e48842 (patch) | |
tree | cf414be9a53342e8d59194198cde5bf3c2187fc1 /lib/Hashing.Portable | |
parent | 2b1314c1475e7e1831c691cf349cb89c66fa320c (diff) |
Squashed commit of the following:
commit 231e26e5c6731e6e156d7c0591518e84a3b82f5a
Author: vnugent <public@vaughnnugent.com>
Date: Thu Feb 29 20:59:42 2024 -0500
fix: #5 find and patch Windows compression bug and some deployment
commit d0bfe14e0a0e27172b8dd41f468265e651784837
Author: vnugent <public@vaughnnugent.com>
Date: Wed Feb 21 21:39:19 2024 -0500
fix: #4 fix readme licensing message for accuracy
commit 6f37f152fcd105e40af6689192a36b87eda95f51
Author: vnugent <public@vaughnnugent.com>
Date: Wed Feb 21 21:37:55 2024 -0500
fix: jwt hashalg sizing and public api for hashalg sizes
Diffstat (limited to 'lib/Hashing.Portable')
-rw-r--r-- | lib/Hashing.Portable/src/IdentityUtility/HashingExtensions.cs | 7 | ||||
-rw-r--r-- | lib/Hashing.Portable/src/IdentityUtility/JwtExtensions.cs | 10 | ||||
-rw-r--r-- | lib/Hashing.Portable/src/ManagedHash.cs | 57 | ||||
-rw-r--r-- | lib/Hashing.Portable/src/ManagedHashAlgImpl.cs | 29 | ||||
-rw-r--r-- | lib/Hashing.Portable/tests/ManagedHashTests.cs | 81 |
5 files changed, 141 insertions, 43 deletions
diff --git a/lib/Hashing.Portable/src/IdentityUtility/HashingExtensions.cs b/lib/Hashing.Portable/src/IdentityUtility/HashingExtensions.cs index 2a18cfc..12ba095 100644 --- a/lib/Hashing.Portable/src/IdentityUtility/HashingExtensions.cs +++ b/lib/Hashing.Portable/src/IdentityUtility/HashingExtensions.cs @@ -194,6 +194,13 @@ namespace VNLib.Hashing.IdentityUtility } /// <summary> + /// Gets the hash size in bytes for the current <see cref="HashAlg"/> value + /// </summary> + /// <param name="alg"></param> + /// <returns>The size (in bytes) of the algorithm's hash</returns> + public static int HashSize(this HashAlg alg) => ManagedHash.GetHashSize(alg); + + /// <summary> /// Gets the <see cref="HashAlgorithmName"/> for the current <see cref="HashAlg"/> /// value. /// </summary> diff --git a/lib/Hashing.Portable/src/IdentityUtility/JwtExtensions.cs b/lib/Hashing.Portable/src/IdentityUtility/JwtExtensions.cs index ef6715b..68033cd 100644 --- a/lib/Hashing.Portable/src/IdentityUtility/JwtExtensions.cs +++ b/lib/Hashing.Portable/src/IdentityUtility/JwtExtensions.cs @@ -275,7 +275,7 @@ namespace VNLib.Hashing.IdentityUtility public static void Sign(this JsonWebToken jwt, ReadOnlySpan<byte> key, HashAlg alg) { //Stack hash output buffer, will be the size of the alg - Span<byte> sigOut = stackalloc byte[(int)alg]; + Span<byte> sigOut = stackalloc byte[alg.HashSize()]; //Compute ERRNO count = ManagedHash.ComputeHmac(key, jwt.HeaderAndPayload, sigOut, alg); @@ -394,7 +394,7 @@ namespace VNLib.Hashing.IdentityUtility int sigBufSize = CalcPadding(signature.Length) + signature.Length; //Calc full buffer size - nint bufferSize = MemoryUtil.NearestPage(sigBufSize + (int)alg); + nint bufferSize = MemoryUtil.NearestPage(sigBufSize + alg.HashSize()); //Alloc buffer to decode data, as a full page, all buffers will be used from the block for better cache using UnsafeMemoryHandle<byte> buffer = jwt.Heap.UnsafeAlloc<byte>((int)bufferSize); @@ -442,10 +442,10 @@ namespace VNLib.Hashing.IdentityUtility /// <exception cref="InternalBufferTooSmallException"></exception> public static bool Verify(this JsonWebToken jwt, ReadOnlySpan<byte> key, HashAlg alg) { - _ = jwt ?? throw new ArgumentNullException(nameof(jwt)); + ArgumentNullException.ThrowIfNull(jwt); //Get base64 buffer size for in-place conversion - int bufferSize = Base64.GetMaxEncodedToUtf8Length((int)alg); + int bufferSize = Base64.GetMaxEncodedToUtf8Length(alg.HashSize()); //Alloc buffer for signature output Span<byte> signatureBuffer = stackalloc byte[bufferSize]; @@ -458,7 +458,7 @@ namespace VNLib.Hashing.IdentityUtility } //Do an in-place base64 conversion of the signature to base64url - ERRNO encoded = VnEncoding.Base64UrlEncodeInPlace(signatureBuffer, (int)alg, false); + ERRNO encoded = VnEncoding.Base64UrlEncodeInPlace(signatureBuffer, alg.HashSize(), false); if (!encoded) { diff --git a/lib/Hashing.Portable/src/ManagedHash.cs b/lib/Hashing.Portable/src/ManagedHash.cs index 7791eed..429660f 100644 --- a/lib/Hashing.Portable/src/ManagedHash.cs +++ b/lib/Hashing.Portable/src/ManagedHash.cs @@ -29,7 +29,6 @@ using System.Security.Cryptography; using VNLib.Utils; using VNLib.Utils.Memory; -using VNLib.Hashing.Native.MonoCypher; namespace VNLib.Hashing { @@ -84,7 +83,7 @@ namespace VNLib.Hashing /// Gets a value that indicates whether the current runtime has the required libraries /// available to support the Blake2b hashing algorithm /// </summary> - public static bool SupportsBlake2b => MonoCypherLibrary.CanLoadDefaultLibrary(); + public static bool SupportsBlake2b => IsAlgSupported(HashAlg.BlAKE2B); /// <summary> /// Gets a value that indicates whether the current platform supports the SHA3 @@ -103,8 +102,8 @@ namespace VNLib.Hashing HashAlg.SHA3_512 => Sha3_512.IsSupported, HashAlg.SHA3_384 => Sha3_384.IsSupported, HashAlg.SHA3_256 => Sha3_256.IsSupported, - HashAlg.BlAKE2B => SupportsBlake2b, - HashAlg.SHA512 => true, + HashAlg.BlAKE2B => Blake2b.IsSupported, + HashAlg.SHA512 => true, //Built-in functions are always supported HashAlg.SHA384 => true, HashAlg.SHA256 => true, HashAlg.SHA1 => true, @@ -113,6 +112,26 @@ namespace VNLib.Hashing }; /// <summary> + /// Gets the size of the hash (in bytes) for the specified algorithm + /// </summary> + /// <param name="type">The hash algorithm to get the size of</param> + /// <returns>A positive 32-bit integer size (in bytes) of the algorithm hash size</returns> + /// <exception cref="ArgumentException"></exception> + public static int GetHashSize(HashAlg type) => type switch + { + HashAlg.SHA3_512 => _3_sha512.HashSize, + HashAlg.SHA3_384 => _3_sha384.HashSize, + HashAlg.SHA3_256 => _3_sha256.HashSize, + HashAlg.BlAKE2B => _blake2bAlg.HashSize, + HashAlg.SHA512 => _sha512Alg.HashSize, + HashAlg.SHA384 => _sha384Alg.HashSize, + HashAlg.SHA256 => _sha256Alg.HashSize, + HashAlg.SHA1 => _sha1Alg.HashSize, + HashAlg.MD5 => _md5Alg.HashSize, + _ => throw new ArgumentException("Invalid hash algorithm", nameof(type)) + }; + + /// <summary> /// Uses the UTF8 character encoding to encode the string, then /// attempts to compute the hash and store the results into the output buffer /// </summary> @@ -265,11 +284,11 @@ namespace VNLib.Hashing private static byte[] ComputeHashInternal(HashAlg alg, ReadOnlySpan<char> data, ReadOnlySpan<byte> key = default) { //Alloc output buffer - byte[] output = new byte[HashSize(alg)]; + byte[] output = new byte[GetHashSize(alg)]; //Hash data ERRNO result = ComputeHashInternal(alg, data, output, key); - Debug.Assert(result == HashSize(alg), $"Failed to compute hash using {alg} of size {output.Length}"); + Debug.Assert(result == GetHashSize(alg), $"Failed to compute hash using {alg} of size {output.Length}"); return output; } @@ -288,13 +307,13 @@ namespace VNLib.Hashing private static string ComputeHashInternal(HashAlg alg, ReadOnlySpan<char> data, HashEncodingMode mode, ReadOnlySpan<byte> key = default) { //Alloc stack buffer to store hash output - Span<byte> hashBuffer = stackalloc byte[HashSize(alg)]; + Span<byte> hashBuffer = stackalloc byte[GetHashSize(alg)]; //hash the buffer ERRNO count = ComputeHashInternal(alg, data, hashBuffer, key); //Count should always be the same as the hash size, this should never fail - Debug.Assert(count == HashSize(alg), $"Failed to compute hash using {alg} of size {hashBuffer.Length}"); + Debug.Assert(count == GetHashSize(alg), $"Failed to compute hash using {alg} of size {hashBuffer.Length}"); //Convert to encoded string return mode switch @@ -310,13 +329,13 @@ namespace VNLib.Hashing private static string ComputeHashInternal(HashAlg alg, ReadOnlySpan<byte> data, HashEncodingMode mode, ReadOnlySpan<byte> key = default) { //Alloc stack buffer to store hash output - Span<byte> hashBuffer = stackalloc byte[HashSize(alg)]; + Span<byte> hashBuffer = stackalloc byte[GetHashSize(alg)]; //hash the buffer ERRNO count = ComputeHashInternal(alg, data, hashBuffer, key); //Count should always be the same as the hash size, this should never fail - Debug.Assert(count == HashSize(alg), $"Failed to compute hash using {alg} of size {hashBuffer.Length}"); + Debug.Assert(count == GetHashSize(alg), $"Failed to compute hash using {alg} of size {hashBuffer.Length}"); //Convert to encoded string return mode switch @@ -333,29 +352,15 @@ namespace VNLib.Hashing private static byte[] ComputeHashInternal(HashAlg alg, ReadOnlySpan<byte> data, ReadOnlySpan<byte> key = default) { //Alloc output buffer - byte[] output = new byte[HashSize(alg)]; + byte[] output = new byte[GetHashSize(alg)]; //Hash data ERRNO result = ComputeHashInternal(alg, data, output, key); - Debug.Assert(result == HashSize(alg), $"Failed to compute hash using {alg} of size {output.Length}"); + Debug.Assert(result == GetHashSize(alg), $"Failed to compute hash using {alg} of size {output.Length}"); return output; } - private static int HashSize(HashAlg alg) => alg switch - { - HashAlg.SHA3_512 => _3_sha512.HashSize, - HashAlg.SHA3_384 => _3_sha384.HashSize, - HashAlg.SHA3_256 => _3_sha256.HashSize, - HashAlg.BlAKE2B => _blake2bAlg.HashSize, - HashAlg.SHA512 => _sha512Alg.HashSize, - HashAlg.SHA384 => _sha384Alg.HashSize, - HashAlg.SHA256 => _sha256Alg.HashSize, - HashAlg.SHA1 => _sha1Alg.HashSize, - HashAlg.MD5 => _md5Alg.HashSize, - _ => throw new ArgumentException("Invalid hash algorithm", nameof(alg)) - }; - private static ERRNO ComputeHashInternal(HashAlg alg, ReadOnlySpan<byte> data, Span<byte> buffer, ReadOnlySpan<byte> key = default) { diff --git a/lib/Hashing.Portable/src/ManagedHashAlgImpl.cs b/lib/Hashing.Portable/src/ManagedHashAlgImpl.cs index d53152e..eae8903 100644 --- a/lib/Hashing.Portable/src/ManagedHashAlgImpl.cs +++ b/lib/Hashing.Portable/src/ManagedHashAlgImpl.cs @@ -150,6 +150,8 @@ namespace VNLib.Hashing internal static int MaxHashSize => MCBlake2Module.MaxHashSize; internal static int MaxKeySize => MCBlake2Module.MaxKeySize; + public static bool IsSupported => MonoCypherLibrary.CanLoadDefaultLibrary(); + ///<inheritdoc/> public readonly int HashSize => DefaultBlake2HashSize; @@ -180,9 +182,18 @@ namespace VNLib.Hashing { count = 0; - if (output.Length > MCBlake2Module.MaxHashSize) + /* + * For this specific implementation, the ManagedHash class assumes hashing implementations + * have a fixed hash size. This is not the case for Blake2b, so we need to enforce one. + * + * Since the HasSize property is publicly accessible users can assume the hash size is + * the value returned by the property. So it should be safe to enforce the hash size here. + * + * Any data outside the default hash size is not our problem. + */ + if (output.Length > HashSize) { - return false; + output = output[..HashSize]; } //Test key size @@ -191,17 +202,11 @@ namespace VNLib.Hashing return false; } - //Compute one-shot hash - ERRNO result = MonoCypherLibrary.Shared.Blake2ComputeHmac(key, data, output); + //Compute one-shot hash, a variable length result may occur + count = MonoCypherLibrary.Shared.Blake2ComputeHmac(key, data, output); - if (result < output.Length) - { - count = 0; - return false; - } - - count = output.Length; - return true; + //Output should always be the same size as the hash size + return count == output.Length; } } } diff --git a/lib/Hashing.Portable/tests/ManagedHashTests.cs b/lib/Hashing.Portable/tests/ManagedHashTests.cs index a9fa467..3bcdb33 100644 --- a/lib/Hashing.Portable/tests/ManagedHashTests.cs +++ b/lib/Hashing.Portable/tests/ManagedHashTests.cs @@ -97,5 +97,86 @@ namespace VNLib.Hashing.Tests Assert.IsTrue(testEnc.Length == Math.Abs(hashSize) * 2); } } + + const string TestHashInput = "Hello World!"; + static readonly string[] TestHashOutputHex = + [ + //None + "", + //Md5 + "ed076287532e86365e841e92bfc50d8c", + //Sha1 + "2ef7bde608ce5404e97d5f042f95f89f1c232871", + //Sha2 + "7f83b1657ff1fc53b92dc18148a1d65dfc2d4b1fa3d677284addd200126d9069", + "bfd76c0ebbd006fee583410547c1887b0292be76d582d96c242d2a792723e3fd6fd061f9d5cfd13b8f961358e6adba4a", + "861844d6704e8573fec34d967e20bcfef3d424cf48be04e6dc08f2bd58c729743371015ead891cc3cf1c9d34b49264b510751b1ff9e537937bc46b5d6ff4ecc8", + + //Sha3 + "d0e47486bbf4c16acac26f8b653592973c1362909f90262877089f9c8a4536af", + "f324cbd421326a2abaedf6f395d1a51e189d4a71c755f531289e519f079b224664961e385afcc37da348bd859f34fd1c", + "32400b5e89822de254e8d5d94252c52bdcb27a3562ca593e980364d9848b8041b98eabe16c1a6797484941d2376864a1b0e248b0f7af8b1555a778c336a5bf48", + + //Blake2b (64 bytes/512 bits) + "54b113f499799d2f3c0711da174e3bc724737ad18f63feb286184f0597e1466436705d6c8e8c7d3d3b88f5a22e83496e0043c44a3c2b1700e0e02259f8ac468e" + ]; + + //Known hash sizes to compare against + static readonly int[] HashSizes = + [ + 0, + 16, + 20, + 32, + 48, + 64, + 32, + 48, + 64, + 64 + ]; + + [TestMethod()] + public void ComputeHexHashTest() + { + HashAlg[] algs = Enum.GetValues<HashAlg>(); + + Assert.AreEqual(algs.Length, TestHashOutputHex.Length); + + for (int i = 0; i < algs.Length; i++) + { + if (algs[i] == HashAlg.None) + continue; + + //Skip unsupported algorithms + if (!ManagedHash.IsAlgSupported(algs[i])) + continue; + + string hash = ManagedHash.ComputeHash(TestHashInput, algs[i], HashEncodingMode.Hexadecimal); + + //Make sure exact length (x2 for hex) + Assert.AreEqual(HashSizes[i] * 2, hash.Length); + + //Make sure exact value + Assert.AreEqual(TestHashOutputHex[i], hash, true); + } + } + + [TestMethod()] + public void AlgSizeTest() + { + HashAlg[] algs = Enum.GetValues<HashAlg>(); + + for (int i = 0; i < algs.Length; i++) + { + if (algs[i] == HashAlg.None) + continue; + + //Make sure exact length + Assert.AreEqual(HashSizes[i], ManagedHash.GetHashSize(algs[i])); + } + + Assert.ThrowsException<ArgumentException>(() => ManagedHash.GetHashSize(HashAlg.None)); + } } }
\ No newline at end of file |