0

I have this validator:

public async Task<bool> IsValidRequest(HttpRequest request, string verificationKey, string payload)
{
    try
    {
        var pk = ConvertPublicKeyToECDSA(verificationKey);
        if (pk == null)
        {
            return false;
        }
        request.Headers.TryGetValue(SIGNATURE_HEADER, out var signatureHeaderValues);
        request.Headers.TryGetValue(TIMESTAMP_HEADER, out var timestampHeaderValues);

        var signature = signatureHeaderValues.FirstOrDefault();
        var timestamp = timestampHeaderValues.FirstOrDefault();

        if (string.IsNullOrWhiteSpace(signature) || string.IsNullOrWhiteSpace(timestamp))
        {
            return false;
        }

        var timestampedPayload = timestamp + payload;
        var decodedSignature = Signature.fromBase64(signature);
        return Ecdsa.verify(timestampedPayload, decodedSignature, pk);
    }
    catch(Exception e)
    {
        var error = e;
        return false;
    }
}

This is partly from their official C# github. However, I don't understand what is a payload? What is it suppose to contain?

When a request comes in from Sendgrids Webhook, I take the object[] Input and serialize it, this is what it looks like:

[{"EmailFunction":"DEFAULT_EMAIL_FUNCTION","ReceiverId":"f695b097-8de4-8b632e930518","email":"test@test.net","event":"delivered","ip":"129.73.147.11","response":"250 OK id=1mUiib-0006ga-Cn","sg_event_id":"ZGVsaXZlcmVkLTAtMTk5ODU4MjgtUDZmTk55OHVSeXV2QWpFYTBqbVp6dy0w","sg_message_id":"P6fNNy8uRyuvAjEa0jmZzw.filterdrecv-656998cfdd-bkftm-1-61514F42-1.0","sg_template_id":"d-769wew2124b43bbfbb9db6ec3d3b5","sg_template_name":"Untitled Version","smtp-id":"<P6fNNy8uRyuvAjEa0jmZzw@geopod-ismtpd-4-0>","timestamp":1632718704,"tls":1}]

Am I supposed to just add a timestamp in front of it?

So far this always fails and returns false.

JianYA
  • 2,750
  • 8
  • 60
  • 136

1 Answers1

0

Twilio SendGrid developer evangelist here.

According to the comments in the C# library the payload is the "event payload in the request body". Further, according to the docs on validating the request:

When verifying the signature, be aware that we deliver a payload that must be used in its raw bytes form. Transformations from raw bytes to a JSON string may remove characters that were used as part of the generated signature.

So, you should not serialize the input you receive, but validate the raw body of the request.

In this example the contents of the HttpRequest body are read with a StreamReader:

    using (var reader = new StreamReader(request.Body))
    {
        requestBody = reader.ReadToEnd();
    }

Taking your example and working with that example code, I came up with this:

using System.IO;

public async Task<bool> IsValidRequest(HttpRequest request, string verificationKey)
{
    try
    {
        var pk = ConvertPublicKeyToECDSA(verificationKey);
        if (pk == null)
        {
            return false;
        }
        request.Headers.TryGetValue(SIGNATURE_HEADER, out var signatureHeaderValues);
        request.Headers.TryGetValue(TIMESTAMP_HEADER, out var timestampHeaderValues);

        var signature = signatureHeaderValues.FirstOrDefault();
        var timestamp = timestampHeaderValues.FirstOrDefault();
        string payload;

        using (var reader = new StreamReader(request.Body))
        {
            payload = reader.ReadToEnd();
        }

        if (string.IsNullOrWhiteSpace(signature) || string.IsNullOrWhiteSpace(timestamp))
        {
            return false;
        }

        var timestampedPayload = timestamp + payload;
        var decodedSignature = Signature.fromBase64(signature);
        return Ecdsa.verify(timestampedPayload, decodedSignature, pk);
    }
    catch(Exception e)
    {
        var error = e;
        return false;
    }
}

I don't have a C# environment to test this with, but hope it helps!

philnash
  • 70,667
  • 10
  • 60
  • 88
  • 1
    Hi @philnash, thanks for replying. I keep getting an exception saying that synchronous operations are not allowed. This is from the StreamReader. How do I solve that issue? – JianYA Sep 29 '21 at 08:04
  • I have updated my code to allow synchronous operations. However, the issue is that the request.body stream always results in an empty string. – JianYA Sep 29 '21 at 08:22
  • Does anything from [these answers](https://stackoverflow.com/questions/35589539/how-do-i-get-the-raw-request-body-from-the-request-content-object-using-net-4-a) help? – philnash Sep 29 '21 at 14:18