gabrieldwight / Whatsapp-Business-Cloud-Api-Net

This is C# wrapper of whatsapp business cloud api for .NET
MIT License
283 stars 104 forks source link

Add X-Hub-Signature-256 control #11

Closed Tekkharibo closed 1 year ago

Tekkharibo commented 1 year ago

Hi

I have add on my project the check of the X-Hub-Signature-256 and I share it to you if you want to add it on the library

My Solution Get on controller the X-Hub-Signature-256

[HttpPost]
public async Task<IActionResult> GetMessage()
{
    string stringifiedBody;

    string xHubSignature256 = this.HttpContext.Request.Headers["X-Hub-Signature-256"].ToString();

    using (var sr = new StreamReader(this.HttpContext.Request.Body))
    {
        stringifiedBody = await sr.ReadToEndAsync().ConfigureAwait(false);
    }

    string xHubSignature256Result = FacebookWebhookHelper.CalculateSignature(this._configuration.GetSection("WhatsAppBusinessCloudApiConfiguration")["AppSecret"], stringifiedBody);

    if (!String.Equals(xHubSignature256, xHubSignature256Result, StringComparison.InvariantCultureIgnoreCase))
        return this.Unauthorized("Invalid Signature");

    return this.Ok();
}

Process to check the X-Hub-Signature-256

using System.Security.Cryptography;
using System.Text;

namespace MyCustomNameSpace.Helpers;

public static class FacebookWebhookHelper
{
    /// <summary>
    /// The HTTP request will contain an X-Hub-Signature header which contains the SHA1 signature of the request payload,
    /// using the app secret as the key, and prefixed with sha1=.
    /// Your callback endpoint can verify this signature to validate the integrity and origin of the payload
    /// </summary>
    /// <param name="appSecret">facebook app secret</param>
    /// <param name="payload">body of webhook post request</param>
    /// <returns>calculated signature</returns>
    public static string CalculateSignature(string appSecret, string payload)
    {
        /*
         Please note that the calculation is made on the escaped unicode version of the payload, with lower case hex digits.
         If you just calculate against the decoded bytes, you will end up with a different signature.
         For example, the string äöå should be escaped to \u00e4\u00f6\u00e5.
         */

        using HMACSHA256 hmac = new HMACSHA256(Encoding.UTF8.GetBytes(appSecret));
        hmac.Initialize();
        byte[] hashArray = hmac.ComputeHash(Encoding.UTF8.GetBytes(payload));
        string hash = $"SHA256={BitConverter.ToString(hashArray).Replace("-", string.Empty)}";

        return hash;
    }
}

Other observation If I try to get the object with something other that new StreamReader(this.HttpContext.Request.Body), the X-Hub-Signature-256 control fail any time

gabrieldwight commented 1 year ago

Hi @Tekkharibo, I will add the X-Hub-Signature-256 feature to the library.

sapharos commented 11 months ago

In case someone is using .Net 7 with minimal api you have to add this block in your Program.cs

app.Use((context, next) => { context.Request.EnableBuffering(); return next(); });

and then implement your validation this way:

string stringifiedBody; string xHubSignature256 = context.Request.Headers["X-Hub-Signature-256"].ToString(); context.Request.Body.Seek(0, SeekOrigin.Begin); using (var sr = new StreamReader(context.Request.Body)) { stringifiedBody = await sr.ReadToEndAsync().ConfigureAwait(false); } string xHubSignature256Result = FacebookWebhookHelper.CalculateSignature(_config.GetValue<string("Facebook:AppSecret"), stringifiedBody); if (!String.Equals(xHubSignature256, xHubSignature256Result,StringComparison.InvariantCultureIgnoreCase)) { return Results.Unauthorized(); }

Tekkharibo commented 11 months ago

In case someone is using .Net 7 with minimal api you have to add this block in your Program.cs

app.Use((context, next) => { context.Request.EnableBuffering(); return next(); });

and then implement your validation this way:

string stringifiedBody; string xHubSignature256 = context.Request.Headers["X-Hub-Signature-256"].ToString(); context.Request.Body.Seek(0, SeekOrigin.Begin); using (var sr = new StreamReader(context.Request.Body)) { stringifiedBody = await sr.ReadToEndAsync().ConfigureAwait(false); } string xHubSignature256Result = FacebookWebhookHelper.CalculateSignature(_config.GetValue<string("Facebook:AppSecret"), stringifiedBody); if (!String.Equals(xHubSignature256, xHubSignature256Result,StringComparison.InvariantCultureIgnoreCase)) { return Results.Unauthorized(); }

That is interesting to move it on program.cs like a middleware

sapharos commented 11 months ago

In case someone is using .Net 7 with minimal api you have to add this block in your Program.cs app.Use((context, next) => { context.Request.EnableBuffering(); return next(); }); and then implement your validation this way: string stringifiedBody; string xHubSignature256 = context.Request.Headers["X-Hub-Signature-256"].ToString(); context.Request.Body.Seek(0, SeekOrigin.Begin); using (var sr = new StreamReader(context.Request.Body)) { stringifiedBody = await sr.ReadToEndAsync().ConfigureAwait(false); } string xHubSignature256Result = FacebookWebhookHelper.CalculateSignature(_config.GetValue<string("Facebook:AppSecret"), stringifiedBody); if (!String.Equals(xHubSignature256, xHubSignature256Result,StringComparison.InvariantCultureIgnoreCase)) { return Results.Unauthorized(); }

That is interesting to move it on program.cs like a middleware

Yeah, that's the only way i got it to work, i also tried using endpoint filters but the verification wasn't working.