Elliptic Curve Digital Signature Algorithm (ECDSA) Verify

SendGrid event Webhook notification is a great way to get the current status of the emails. SendGrid webhook API call will keep posting the latest events to the Service.

The URL exposed is publically available and is accessible to everyone. A publically accessible URL must always be secured and should impose authentication and authorization. 

SendGrid provides Elliptic Curve Digital Signature Algorithm (ECDSA) to verify the request coming from SendGrid only.

 

Gnerate ECDSA Signature

Currently, SendGrid provides the option to generate ECDSA signature using UI only.

Go to https://app.sendgrid.com/settings/mail_settingsSigned Event Webhook Requests, Generate Verification Key, a verification key will be generated.

This verification key will be used to verify ECDSA signature will be used to authenticate webhook data is coming from SendGrid.

SpringBoot requires registering Security.addProvider(new BouncyCastleProvider()); at the time of application startup.

package org.wesome.sendgrid;

import com.sendgrid.SendGrid;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.RestController;

import java.security.Security;

@SpringBootApplication
@RestController
public class SendGridApplication {
    @Value("${send.grid.api.key}")
    private String sendGridApiKey;

    public static void main(String[] args) {
        Security.addProvider(new BouncyCastleProvider());
        SpringApplication.run(SendGridApplication.class, args);
    }

    @Bean
    public SendGrid sendGrid() {
        SendGrid sendGrid = new SendGrid(sendGridApiKey);
        return sendGrid;
    }
}
package org.wesome.sendgrid.controller;

import com.sendgrid.Method;
import com.sendgrid.Request;
import com.sendgrid.Response;
import com.sendgrid.SendGrid;
import com.sendgrid.helpers.eventwebhook.EventWebhook;
import com.sun.org.apache.xml.internal.security.signature.InvalidSignatureValueException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.interfaces.ECPublicKey;
import java.util.Map;

@RestController
public class SendGridController {
    @Value("${send.grid.verification.key}")
    private String sendGridVerificationKey;
    @Autowired
    private SendGrid sendGrid;

    @PostMapping("webhook")
    public void webhook(@RequestBody byte[] webhook, @RequestHeader Map<String, String> headers) throws Exception {
        String signature = headers.get("x-twilio-email-event-webhook-signature");
        String timeStamp = headers.get("x-twilio-email-event-webhook-timestamp");
        if (verify(sendGridVerificationKey, webhook, signature, timeStamp)) {
            String str = new String(webhook, StandardCharsets.UTF_8);
            System.out.println("ECDSA signature verification true, Webhook = " + str);
        } else {
            throw new InvalidSignatureValueException("ECDSA signature doesn't match");
        }
    }

    private boolean verify(final String publicKey, final byte[] payload, final String signature, final String timestamp) throws Exception {
        final EventWebhook ew = new EventWebhook();
        final ECPublicKey ellipticCurvePublicKey = ew.ConvertPublicKeyToECDSA(publicKey);
        return ew.VerifySignature(ellipticCurvePublicKey, payload, signature, timestamp);
    }

    @GetMapping("webhook/test")
    public ResponseEntity sendGridAPI() throws IOException {
        Request request = new Request();
        Response response;
        try {
            request.setMethod(Method.POST);
            request.setEndpoint("user/webhooks/event/test");
            request.setBody("{\"url\":\"https://de95-103-112-17-103.ngrok.io/webhook\"}");
            response = sendGrid.api(request);
            System.out.println("Headers := \n" + response.getHeaders());
            System.out.println("Body := \n" + response.getBody());
            System.out.println("StatusCode := \n" + response.getStatusCode());
        } catch (IOException ex) {
            throw ex;
        }
        return new ResponseEntity(HttpStatus.valueOf(response.getStatusCode()).getReasonPhrase(), HttpStatus.valueOf(response.getStatusCode()));
    }
}
plugins {
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
    id 'org.springframework.boot' version '2.5.5'
    id 'io.freefair.lombok' version '6.0.0-m2'
    id 'java'
}

group = 'org.wesome'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = JavaVersion.VERSION_1_8

repositories {
    mavenCentral()
}
dependencies {
    implementation 'com.sendgrid:sendgrid-java:4.7.2'
    implementation('org.springframework.boot:spring-boot-starter-web')
}

test {
    useJUnitPlatform()
}
send.grid.api.key=Your SendGrid Api Key
send.grid.verification.key=Generated Verification Key
curl --location --request GET 'http://localhost:8080/webhook/test'

follow us on