Skip to content

Commit

Permalink
Fix timestamp format in signature verification
Browse files Browse the repository at this point in the history
  • Loading branch information
ghostzero committed Feb 6, 2021
1 parent 0ec46f3 commit 2abe4f6
Show file tree
Hide file tree
Showing 2 changed files with 33 additions and 6 deletions.
31 changes: 27 additions & 4 deletions src/Objects/EventSubSignature.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,17 @@

namespace romanzipp\Twitch\Objects;

use Carbon\Carbon;
use DateTime;
use romanzipp\Twitch\Exceptions\SignatureVerificationException;
use Symfony\Component\HttpFoundation\HeaderBag;

class EventSubSignature
{
/**
* Represents the timestamp format of twitch's eventsub api.
*/
const TIMESTAMP_PATTERN = '/^(\d+)-(0[1-9]|1[012])-(0[1-9]|[12]\d|3[01])T([01]\d|2[0-3]):([0-5]\d):([0-5]\d|60)(\.\d+)?(([Zz])|([+|\-]([01]\d|2[0-3])))$/';

/**
* Verifies the signature header sent by Twitch. Throws an SignatureVerificationException
* exception if the verification fails for any reason.
Expand All @@ -23,8 +28,8 @@ class EventSubSignature
*/
public static function verifyHeader(string $payload, HeaderBag $headers, string $secret, int $tolerance = 60): void
{
$timestamp = $headers->get('twitch-eventsub-message-timestamp');
$timestamp = Carbon::createFromFormat('Y-m-d\TH:i:s.u\Z', $timestamp, 'UTC');
$rawTimestamp = $headers->get('twitch-eventsub-message-timestamp');
$timestamp = self::getTimestamp($rawTimestamp);

if ( ! is_numeric($timestamp)) {
throw new SignatureVerificationException('Unable to extract timestamp and signatures from header');
Expand All @@ -35,10 +40,28 @@ public static function verifyHeader(string $payload, HeaderBag $headers, string
}

$messageId = $headers->get('twitch-eventsub-message-id');
$expectedSignature = hash_hmac('sha256', $messageId . $timestamp . $payload, $secret);
$expectedSignature = hash_hmac('sha256', $messageId . $rawTimestamp . $payload, $secret);

if ($headers->get('twitch-eventsub-message-signature') !== $expectedSignature) {
throw new SignatureVerificationException();
}
}

private static function getTimestamp(?string $rawTimestamp): ?int
{
if ( ! $rawTimestamp) {
return null;
}

if ( ! preg_match(self::TIMESTAMP_PATTERN, $rawTimestamp, $m)) {
return null;
}

$dateTime = DateTime::createFromFormat(
'Y-m-d\TH:i:s.u\Z',
"$m[1]-$m[2]-$m[3]T$m[4]:$m[5]:$m[6]" . substr($m[7], 0, 6) . 'Z'
);

return $dateTime->getTimestamp();
}
}
8 changes: 6 additions & 2 deletions tests/VerifyEventSubSignatureTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,11 @@ public function testAppAbortsWhenSecretDoesNotMatch(): void

private function withTimestamp($timestamp): void
{
$this->timestamp = $timestamp;
if (is_string($timestamp)) {
$this->timestamp = $timestamp;
} else {
$this->timestamp = date('Y-m-d\TH:i:s.u\Z', $timestamp);
}
}

private function withSignedSignature($secret): self
Expand All @@ -108,7 +112,7 @@ private function withSignedSignature($secret): self
private function withSignature($signature): self
{
$this->request->headers->set('Twitch-Eventsub-Message-Id', $this->messageId);
$this->request->headers->set('Twitch-Eventsub-Message-Timestamp', date('Y-m-d\TH:i:s.u\Z', $this->timestamp));
$this->request->headers->set('Twitch-Eventsub-Message-Timestamp', $this->timestamp);
$this->request->headers->set('Twitch-Eventsub-Message-Signature', $signature);

return $this;
Expand Down

0 comments on commit 2abe4f6

Please sign in to comment.