Bruno Barral
Bruno Barral

Reputation: 67

How to calculate ms-signature for WebHook?

I am developing a webhooks manager in C# with Visual Studio by using Nu-Get package Microsoft CustomWebHooksReceiver.

Then I test my WebHook manager with Postman software.

When trying to connect to my custom WebHooks manager, I get a message about ms-signature value not correct.

Can somebody explain me how to calculate the value expected for ms-signature parameter? I am new to webhooks and I do not understand what is the value I should send with Postman request so my WebHooks manager is being called and available.

I believe ms-signature should include the value of the JSON body of the request and some secret information, but I need a clear description of the calculation.

Upvotes: 0

Views: 1238

Answers (1)

Bruno Barral
Bruno Barral

Reputation: 67

Here is the answer I found on the Internet. The ms-signature is a value calculated by webhooks caller on a secret value given by receiver and the request JSON data. Note that secret key is a secret value shared between webhook sender and receiver.

   public void CalculateSignature()
    {
        const string secretKey = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"; // Secret signature

        byte[] actualHash;
        var secret = Encoding.UTF8.GetBytes(secretKey);
        using (var hasher = new HMACSHA256(secret))
        {
            string data = "INCLUDE HERE JSON VALUE TO SEND";
            byte[] bytes = Encoding.UTF8.GetBytes(data);
            actualHash = hasher.ComputeHash(bytes);
            string hash = Encoding.UTF8.GetString(actualHash);
            var hexString = BitConverter.ToString(actualHash);
            hexString = hexString.Replace("-", "");

        }
    }

The value in hexString must be included as the ms-signature value in the header of your request.

The webhooks controller must implement the following methods to validate the received signature before processing the answer. If not, you'll keep receiving an error message about the request not being valid because of ms-signature value :

private async Task VerifySignature(HttpRequestMessage request)
    {
        IEnumerable<string> headers;
        const string signatureHeaderName = "ms-signature";
        const string secretKey = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"; // Secret signature

        if (request.Headers.TryGetValues(signatureHeaderName, out headers))
        {
            string headerValue = headers.First().Replace("sha256=", "").Trim();
            byte[] expectedHash;
            try
            {
                expectedHash = FromHex(headerValue);
            }
            catch (Exception)
            {
                var invalidEncoding = request.CreateErrorResponse(HttpStatusCode.BadRequest, $"HexValue '{headerValue}' received is not valid. It must be an hex string value.");
                throw new HttpResponseException(invalidEncoding);
            }

            byte[] actualHash;
            var secret = Encoding.UTF8.GetBytes(secretKey);
            using (var hasher = new HMACSHA256(secret))
            {
                var data = await request.Content.ReadAsByteArrayAsync();
                actualHash = hasher.ComputeHash(data);
            }

            if (!SecretEqual(expectedHash, actualHash))
            {
                var message = $"WebHook signature '{signatureHeaderName}' from header is not valid.";
                var badSignature = request.CreateErrorResponse(HttpStatusCode.BadRequest, message);
                throw new HttpResponseException(badSignature);
            }
        }
    }

    private static bool SecretEqual(byte[] inputA, byte[] inputB)
    {
        if (ReferenceEquals(inputA, inputB))
        {
            return true;
        }

        if (inputA == null || inputB == null || inputA.Length != inputB.Length)
        {
            return false;
        }

        var areSame = true;
        for (var i = 0; i < inputA.Length; i++)
        {
            areSame &= inputA[i] == inputB[i];
        }
        return areSame;
    }

    private static byte[] FromHex(string content)
    {
        if (string.IsNullOrEmpty(content))
        {
            return new byte[0];
        }

        byte[] data;
        try
        {
            data = new byte[content.Length / 2];
            var input = 0;
            for (var output = 0; output < data.Length; output++)
            {
                data[output] = Convert.ToByte(new string(new[] { content[input++], content[input++] }), 16);
            }

            if (input != content.Length)
            {
                data = null;
            }
        }
        catch
        {
            data = null;
        }

        if (data == null)
        {
            throw new InvalidOperationException($"Value: '{content}' is not in hex. Please send an hex string.");
        }
        return data;
    }

Upvotes: 1

Related Questions