-
Notifications
You must be signed in to change notification settings - Fork 626
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: verify signature from event webhook (#969)
When enabling the "Signed Event Webhook Requests" feature in Mail Settings, Twilio SendGrid will generate a private and public key pair using the Elliptic Curve Digital Signature Algorithm (ECDSA). Once that is successfully enabled, all new event posts will have two new headers: X-Twilio-Email-Event-Webhook-Signature and X-Twilio-Email-Event-Webhook-Timestamp, which can be used to validate your events. This SDK update will make it easier to verify signatures from signed event webhook requests by using the VerifySignature method. Pass in the public key, event payload, signature, and timestamp to validate. Note: You will need to convert your public key string to an elliptic public key object in order to use the VerifySignature method.
- Loading branch information
childish-sambino
authored
May 29, 2020
1 parent
1712b5a
commit eb22165
Showing
7 changed files
with
179 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,6 +12,5 @@ test.php | |
.vscode | ||
prism* | ||
temp.php | ||
example*.php | ||
TODO.txt | ||
sendgrid-php.zip |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
<?php | ||
|
||
use SendGrid\EventWebhook\EventWebhook; | ||
use SendGrid\EventWebhook\EventWebhookHeader; | ||
|
||
|
||
function isValidSignature($request) | ||
{ | ||
$publicKey = 'base64-encoded public key'; | ||
|
||
$eventWebhook = new EventWebhook(); | ||
$ecPublicKey = $eventWebhook->convertPublicKeyToECDSA($publicKey); | ||
|
||
return $eventWebhook->verifySignature( | ||
$ecPublicKey, | ||
$request->getContent(), | ||
$request->header(EventWebhookHeader::SIGNATURE), | ||
$request->header(EventWebhookHeader::TIMESTAMP) | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
<?php | ||
|
||
namespace SendGrid\EventWebhook; | ||
|
||
use EllipticCurve\Ecdsa; | ||
use EllipticCurve\PublicKey; | ||
use EllipticCurve\Signature; | ||
|
||
/** | ||
* This class allows you to use the Event Webhook feature. Read the docs for | ||
* more details: https://sendgrid.com/docs/for-developers/tracking-events/event | ||
* | ||
* @package SendGrid\EventWebhook | ||
*/ | ||
class EventWebhook | ||
{ | ||
/** | ||
* Convert the public key string to a ECPublicKey. | ||
* | ||
* @param string $publicKey verification key under Mail Settings | ||
* @return PublicKey public key using the ECDSA algorithm | ||
*/ | ||
public function convertPublicKeyToECDSA($publicKey) | ||
{ | ||
return PublicKey::fromString($publicKey); | ||
} | ||
|
||
/** | ||
* Verify signed event webhook requests. | ||
* | ||
* @param PublicKey $publicKey elliptic curve public key | ||
* @param string $payload event payload in the request body | ||
* @param string $signature value obtained from the | ||
* 'X-Twilio-Email-Event-Webhook-Signature' header | ||
* @param string $timestamp value obtained from the | ||
* 'X-Twilio-Email-Event-Webhook-Timestamp' header | ||
* @return bool true or false if signature is valid | ||
*/ | ||
public function verifySignature($publicKey, $payload, $signature, $timestamp) | ||
{ | ||
$timestampedPayload = $timestamp . $payload; | ||
$decodedSignature = Signature::fromBase64($signature); | ||
|
||
return Ecdsa::verify($timestampedPayload, $decodedSignature, $publicKey); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
<?php | ||
|
||
namespace SendGrid\EventWebhook; | ||
|
||
/** | ||
* This class lists headers that get posted to the webhook. Read the docs for | ||
* more details: https://sendgrid.com/docs/for-developers/tracking-events/event | ||
* | ||
* @package SendGrid\EventWebhook | ||
*/ | ||
abstract class EventWebhookHeader | ||
{ | ||
const SIGNATURE = "X-Twilio-Email-Event-Webhook-Signature"; | ||
const TIMESTAMP = "X-Twilio-Email-Event-Webhook-Timestamp"; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
<?php | ||
|
||
namespace SendGrid\Tests\Unit; | ||
|
||
use PHPUnit\Framework\TestCase; | ||
use SendGrid\EventWebhook\EventWebhook; | ||
|
||
/** | ||
* This class tests the EventWebhook functionality. | ||
* | ||
* @package SendGrid\Tests\Unit | ||
*/ | ||
class EventWebhookTest extends TestCase | ||
{ | ||
const PUBLIC_KEY = 'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEDr2LjtURuePQzplybd | ||
C+u4CwrqDqBaWjcMMsTbhdbcwHBcepxo7yAQGhHPTnlvFYPAZFceEu/1FwCM/QmGUhA=='; | ||
const PAYLOAD = '{"category":"example_payload","event":"test_event","message_id":"message_id"}'; | ||
const SIGNATURE = 'MEUCIQCtIHJeH93Y+qpYeWrySphQgpNGNr/U+UyUlBkU6n7RAwIgJTz2 | ||
C+8a8xonZGi6BpSzoQsbVRamr2nlxFDWYNH2j/0='; | ||
const TIMESTAMP = '1588788367'; | ||
|
||
public function testVerifySignature() | ||
{ | ||
$isValidSignature = $this->verify( | ||
self::PUBLIC_KEY, | ||
self::PAYLOAD, | ||
self::SIGNATURE, | ||
self::TIMESTAMP | ||
); | ||
|
||
$this->assertTrue($isValidSignature); | ||
} | ||
|
||
public function testBadKey() | ||
{ | ||
$isValidSignature = $this->verify( | ||
'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEqTxd43gyp8IOEto2LdIfjRQrIbsd4S | ||
XZkLW6jDutdhXSJCWHw8REntlo7aNDthvj+y7GjUuFDb/R1NGe1OPzpA==', | ||
self::PAYLOAD, | ||
self::SIGNATURE, | ||
self::TIMESTAMP | ||
); | ||
|
||
$this->assertFalse($isValidSignature); | ||
} | ||
|
||
public function testBadPayload() | ||
{ | ||
$isValidSignature = $this->verify( | ||
self::PUBLIC_KEY, | ||
'payload', | ||
self::SIGNATURE, | ||
self::TIMESTAMP | ||
); | ||
|
||
$this->assertFalse($isValidSignature); | ||
} | ||
|
||
public function testBadSignature() | ||
{ | ||
$isValidSignature = $this->verify( | ||
self::PUBLIC_KEY, | ||
self::PAYLOAD, | ||
'signature', | ||
self::TIMESTAMP | ||
); | ||
|
||
$this->assertFalse($isValidSignature); | ||
} | ||
|
||
public function testBadTimestamp() | ||
{ | ||
$isValidSignature = $this->verify( | ||
self::PUBLIC_KEY, | ||
self::PAYLOAD, | ||
self::SIGNATURE, | ||
'timestamp' | ||
); | ||
|
||
$this->assertFalse($isValidSignature); | ||
} | ||
|
||
private function verify($publicKey, $payload, $signature, $timestamp) | ||
{ | ||
$eventWebhook = new EventWebhook(); | ||
$ecPublicKey = $eventWebhook->convertPublicKeyToECDSA($publicKey); | ||
return $eventWebhook->verifySignature( | ||
$ecPublicKey, | ||
$payload, | ||
$signature, | ||
$timestamp | ||
); | ||
} | ||
} |