I am trying to validate my Function App Secret Key, that is passed from Github Webhook, using .NET CORE 3.1.
In my Github webhook, I inserted default key from Azure function into "Secret" field. Now, I am trying to validate it in my code. For some reason my encrypted secret key is different from the one in webhook.
NOTE: Secret from Github Webhook is encrypted with SHA1 algorithm.
Code:
public static async Task<IActionResult> Run(HttpRequest req, ILogger log)
{
var secretKey = "my_key";
StringValues outHeader;
if (req.Headers.TryGetValue("x-hub-signature", out outHeader))
{
log.LogWarning("==========");
log.LogWarning(outHeader);
log.LogWarning(GetHash(secretKey));
log.LogWarning("==========");
}
string responseMessage = "Everything went well!";
return new OkObjectResult(responseMessage);
}
public static string GetHash(string input)
{
return "sha1=" + string.Join("",
(new SHA1Managed()
.ComputeHash(Encoding.UTF8.GetBytes(input)))
.Select(x => x.ToString("x2"))
.ToArray());
}
Output:
2020-12-13T16:46:47.592 [Warning] ==========
2020-12-13T16:46:47.592 [Warning] sha1=f859bebbf5ec452a7ecd42efc69e0d86a4f25b16
2020-12-13T16:46:47.593 [Warning] sha1=fa1167715f137edff21d55d00adf63afb318b2a6
2020-12-13T16:46:47.593 [Warning] ==========
Official docs covers Node.js solution only.
What is the right way to validate Github Webhook Secret in .NET CORE 3.1? Thank you for any help.
You're not passing the payload here to your GetHash method and the GetHash method doesn't accept the secret. This is my implementation:
using Azure.Identity;
using Azure.Security.KeyVault.Secrets;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;
using System;
using System.IO;
using System.Net;
using System.Net.Mail;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace GitHubWebhooks
{
public static class Security
{
private const string ShaPrefix = "sha256=";
private const string keyVaultUrl = "<keyvault URL or replace with some other security>";
private const string gitHubWebhookSecretSecretName = "GitHubWebHookSecret";
private static KeyVaultSecret gitHubWebhookSecret;
private static async Task FetchSecrets(CancellationToken cancellationToken)
{
var client = new SecretClient(new Uri(keyVaultUrl), new DefaultAzureCredential());
var gitHubWebHookSecretSecretResponse = await client.GetSecretAsync(gitHubWebhookSecretSecretName, cancellationToken: cancellationToken);
gitHubWebhookSecret = gitHubWebHookSecretSecretResponse.Value;
}
// https://davidpine.net/blog/github-profanity-filter/
// https://docs.github.com/en/developers/webhooks-and-events/securing-your-webhooks
public static async Task<bool> IsGithubPushAllowedAsync(HttpRequest request, CancellationToken cancellationToken)
{
if (gitHubWebhookSecret == null)
{
await FetchSecrets(cancellationToken);
}
request.Headers.TryGetValue("X-GitHub-Event", out StringValues eventName);
request.Headers.TryGetValue("X-Hub-Signature-256", out StringValues signatureWithPrefix);
request.Headers.TryGetValue("X-GitHub-Delivery", out StringValues delivery);
if (string.IsNullOrWhiteSpace(eventName))
{
return false;
}
if (string.IsNullOrWhiteSpace(signatureWithPrefix))
{
return false;
}
if (string.IsNullOrWhiteSpace(delivery))
{
return false;
}
string payload;
// https://justsimplycode.com/2020/08/02/reading-httpcontext-request-body-content-returning-empty-after-upgrading-to-net-core-3-from-2-0/
// Request buffering needs to be enabled in app startup configuration.
// The snippet is:
// app.Use((context, next) =>
// {
// context.Request.EnableBuffering();
// return next();
// });
request.Body.Position = 0;
// We don't close the stream as we're not the one who's opened it.
using (var reader = new StreamReader(request.Body, leaveOpen: true))
{
payload = await reader.ReadToEndAsync();
}
if (string.IsNullOrWhiteSpace(payload))
{
return false;
}
string signatureWithPrefixString = signatureWithPrefix;
if (signatureWithPrefixString.StartsWith(ShaPrefix, StringComparison.OrdinalIgnoreCase))
{
var signature = signatureWithPrefixString.Substring(ShaPrefix.Length);
var secret = Encoding.ASCII.GetBytes(gitHubWebhookSecret.Value);
var payloadBytes = Encoding.UTF8.GetBytes(payload);
using (var sha = new HMACSHA256(secret))
{
var hash = sha.ComputeHash(payloadBytes);
var hashString = ToHexString(hash);
if (hashString.Equals(signature))
{
return true;
}
}
}
return false;
}
public static string ToHexString(byte[] bytes)
{
var builder = new StringBuilder(bytes.Length * 2);
foreach (byte b in bytes)
{
builder.AppendFormat("{0:x2}", b);
}
return builder.ToString();
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With