Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: verify signature from event webhook #622

Merged
merged 6 commits into from
May 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions examples/helpers/eventwebhook/Example.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import com.sendgrid.helpers.eventwebhook.EventWebhook;
import java.security.PublicKey;
import java.security.Security;
import java.security.interfaces.ECPublicKey;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

public class Example {

public static void main(String[] args) {
Security.addProvider(new BouncyCastleProvider());

try {
String publicKey = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEDr2LjtURuePQzplybdC+u4CwrqDqBaWjcMMsTbhdbcwHBcepxo7yAQGhHPTnlvFYPAZFceEu/1FwCM/QmGUhA==";
String payload = "{\"category\":\"example_payload\",\"event\":\"test_event\",\"message_id\":\"message_id\"}";
String signature = "MEUCIQCtIHJeH93Y+qpYeWrySphQgpNGNr/U+UyUlBkU6n7RAwIgJTz2C+8a8xonZGi6BpSzoQsbVRamr2nlxFDWYNH2j/0=";
String timestamp = "1588788367";
EventWebhook ew = new EventWebhook();
ECPublicKey ellipticCurvePublicKey = ew.ConvertPublicKeyToECDSA(publicKey);
boolean valid = ew.VerifySignature(ellipticCurvePublicKey, payload, signature, timestamp);
System.out.println("Valid Signature: " + valid);
} catch (Exception exception) {
Logger.getLogger(Example.class.getName()).log(Level.SEVERE, "something went wrong", exception);
}
}
}
5 changes: 5 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -303,5 +303,10 @@
<version>2.1.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.65</version>
</dependency>
</dependencies>
</project>
68 changes: 68 additions & 0 deletions src/main/java/com/sendgrid/helpers/eventwebhook/EventWebhook.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package com.sendgrid.helpers.eventwebhook;

import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Signature;
import java.security.SignatureException;
import java.security.interfaces.ECPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

/**
* This class allows you to easily use the Event Webhook feature. Read the docs
* for more details:
* https://sendgrid.com/docs/for-developers/tracking-events/event.
*/
public class EventWebhook {
/**
* Convert the public key string to a ECPublicKey.
*
* @param publicKey: verification key under Mail Settings
* @return a public key using the ECDSA algorithm
* @throws NoSuchAlgorithmException
* @throws NoSuchProviderException
* @throws InvalidKeySpecException
*/
public java.security.interfaces.ECPublicKey ConvertPublicKeyToECDSA(String publicKey)
throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException {
byte[] publicKeyInBytes = Base64.getDecoder().decode(publicKey);
KeyFactory factory = KeyFactory.getInstance("ECDSA", "BC");
return (ECPublicKey) factory.generatePublic(new X509EncodedKeySpec(publicKeyInBytes));
}

/**
* Verify signed event webhook requests.
*
* @param publicKey: elliptic curve public key
* @param payload: event payload in the request body
* @param signature: value obtained from the
* 'X-Twilio-Email-Event-Webhook-Signature' header
* @param timestamp: value obtained from the
* 'X-Twilio-Email-Event-Webhook-Timestamp' header
* @return true or false if signature is valid
* @throws NoSuchAlgorithmException
* @throws NoSuchProviderException
* @throws InvalidKeyException
* @throws SignatureException
*/
public boolean VerifySignature(ECPublicKey publicKey, String payload, String signature, String timestamp)
throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException, SignatureException {

// prepend the payload with the timestamp
String payloadWithTimestamp = timestamp + payload;

// create the signature object
Signature signatureObject = Signature.getInstance("SHA256withECDSA", "BC");
signatureObject.initVerify(publicKey);
signatureObject.update(payloadWithTimestamp.getBytes());

// decode the signature
byte[] signatureInBytes = Base64.getDecoder().decode(signature);

// verify the signature
return signatureObject.verify(signatureInBytes);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.sendgrid.helpers.eventwebhook;

/**
* This enum lists headers that get posted to the webhook. Read the docs for
* more details: https://sendgrid.com/docs/for-developers/tracking-events/event.
*/
public enum EventWebhookHeader {
SIGNATURE("X-Twilio-Email-Event-Webhook-Signature"), TIMESTAMP("X-Twilio-Email-Event-Webhook-Timestamp");

public final String name;

EventWebhookHeader(String name) {
this.name = name;
}

@Override
public String toString() {
return name;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.sendgrid.helpers.eventwebhook;

import java.security.Security;
import java.security.interfaces.ECPublicKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.Assert;
import org.junit.Test;

public class EventWebhookTest {
@Test
public void testVerifySignature() throws Exception {

Security.addProvider(new BouncyCastleProvider());

String testPublicKey = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEDr2LjtURuePQzplybdC+u4CwrqDqBaWjcMMsTbhdbcwHBcepxo7yAQGhHPTnlvFYPAZFceEu/1FwCM/QmGUhA==";
String testPayload = "{\"category\":\"example_payload\",\"event\":\"test_event\",\"message_id\":\"message_id\"}";
String testSignature = "MEUCIQCtIHJeH93Y+qpYeWrySphQgpNGNr/U+UyUlBkU6n7RAwIgJTz2C+8a8xonZGi6BpSzoQsbVRamr2nlxFDWYNH2j/0=";
String testTimestamp = "1588788367";

EventWebhook ew = new EventWebhook();
ECPublicKey ellipticCurvePublicKey = ew.ConvertPublicKeyToECDSA(testPublicKey);
boolean isValidSignature = ew.VerifySignature(ellipticCurvePublicKey, testPayload, testSignature,
testTimestamp);

Assert.assertTrue(isValidSignature);
}
}