diff --git a/Fluid.Tests/MiscFiltersTests.cs b/Fluid.Tests/MiscFiltersTests.cs index 65a43ac7..9077821a 100644 --- a/Fluid.Tests/MiscFiltersTests.cs +++ b/Fluid.Tests/MiscFiltersTests.cs @@ -913,6 +913,50 @@ public async Task Sha256() Assert.Equal("c7ac4687585ab5d3d5030db5a5cfc959fdf4e608cc396f1f615db345e35adb9e", result.ToStringValue()); } + [Theory] + [InlineData(null, "Fluid", "")] + [InlineData("secret_key", null, "")] + [InlineData("", "", "fbdb1d1b18aa6c08324b7d64b71fb76370690e1d")] + [InlineData("", "Fluid", "47ab4d87fabf7a7162d59c57298780904de9e245")] + [InlineData("secret_key", "Fluid", "1061ea276551355150b8581aa64dca829d41e357")] + public async Task HmacSha1(string key, string value, string expected) + { + // Arrange + FluidValue input = value is null + ? NilValue.Empty + : new StringValue(value); + var arguments = new FilterArguments(FluidValue.Create(key, TemplateOptions.Default)); + var context = new TemplateContext(); + + // Act + var result = await MiscFilters.HmacSha1(input, arguments, context); + + // Assert + Assert.Equal(expected, result.ToStringValue()); + } + + [Theory] + [InlineData(null, "Fluid", "")] + [InlineData("secret_key", null, "")] + [InlineData("", "", "b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad")] + [InlineData("", "Fluid", "e9f2db8bd3900c469e4b560227c5d53b48f644208a13de05bb400f7611d1a623")] + [InlineData("secret_key", "Fluid", "ac08ee5cdd007e1069680e93eb512049f5ff12afd0fe101de5c9b5043a047ea4")] + public async Task HmacSha256(string key, string value, string expected) + { + // Arrange + FluidValue input = value is null + ? NilValue.Empty + : new StringValue(value); + var arguments = new FilterArguments(FluidValue.Create(key, TemplateOptions.Default)); + var context = new TemplateContext(); + + // Act + var result = await MiscFilters.HmacSha256(input, arguments, context); + + // Assert + Assert.Equal(expected, result.ToStringValue()); + } + public static class TestObjects { public class Node diff --git a/Fluid/Filters/MiscFilters.cs b/Fluid/Filters/MiscFilters.cs index 064c819e..53565a56 100644 --- a/Fluid/Filters/MiscFilters.cs +++ b/Fluid/Filters/MiscFilters.cs @@ -1,8 +1,10 @@ +using Fluid.Utils; using Fluid.Values; using System.Buffers; using System.Globalization; using System.Net; using System.Reflection; +using System.Security.Cryptography; using System.Text; using System.Text.Json; using TimeZoneConverter; @@ -41,6 +43,8 @@ public static FilterCollection WithMiscFilters(this FilterCollection filters) filters.AddFilter("md5", MD5); filters.AddFilter("sha1", Sha1); filters.AddFilter("sha256", Sha256); + filters.AddFilter("hmac_sha1", HmacSha1); + filters.AddFilter("hmac_sha256", HmacSha256); return filters; } @@ -933,5 +937,46 @@ public static ValueTask Sha256(FluidValue input, FilterArguments arg return new StringValue(builder.ToString()); #endif } + + public static ValueTask HmacSha1(FluidValue input, FilterArguments arguments, TemplateContext context) => ComputeHmac("HMACSHA1", input, arguments); + + public static ValueTask HmacSha256(FluidValue input, FilterArguments arguments, TemplateContext context) => ComputeHmac("HMACSHA256", input, arguments); + + private static ValueTask ComputeHmac(string algorithm, FluidValue input, FilterArguments arguments) + { + var key = arguments.At(0); + if (key.IsNil() || input.IsNil()) + { + return StringValue.Empty; + } + + var value = input.ToStringValue(); + var keyBytes = Encoding.UTF8.GetBytes(key.ToStringValue()); + +#if NET6_0_OR_GREATER +#pragma warning disable CA5350 + var hash = algorithm switch + { + "HMACSHA1" => HMACSHA1.HashData(keyBytes, Encoding.UTF8.GetBytes(value)), + "HMACSHA256" => HMACSHA256.HashData(keyBytes, Encoding.UTF8.GetBytes(value)), + _ => throw new ArgumentException("Unsupported HMAC algorithm", nameof(algorithm)) + }; +#pragma warning restore CA5350 + + return new StringValue(HexUtilities.ToHexLower(hash)); +#else + using var provider = HMAC.Create(algorithm); + provider.Key = keyBytes; + var builder = new StringBuilder(64); +#pragma warning disable CA1850 + foreach (var b in provider.ComputeHash(Encoding.UTF8.GetBytes(value))) +#pragma warning restore CA1850 + { + builder.Append(b.ToString("x2")); + } + + return new StringValue(builder.ToString()); +#endif + } } }