Skip to content
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
13 changes: 13 additions & 0 deletions src/Webhook/WebhookVerifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,18 @@

namespace Fingerprint\ServerAPI\Webhook;

/**
* Verifies Fingerprint webhook signature.
*/
final class WebhookVerifier
{
/**
* Checks whether the webhook signature header is valid for the given data and secret.
*
* @param string $header comma-separated list of versioned signatures
* @param string $data raw webhook request body
* @param string $secret webhook signing secret
*/
public static function IsValidWebhookSignature(string $header, string $data, string $secret): bool
{
$signatures = explode(',', $header);
Expand All @@ -20,6 +30,9 @@ public static function IsValidWebhookSignature(string $header, string $data, str
return false;
}

/**
* Compares the given signature against an HMAC-SHA256 hash of the data.
*/
private static function checkSignature(string $signature, string $data, string $secret): bool
{
$hash = hash_hmac('sha256', $data, $secret);
Expand Down
40 changes: 32 additions & 8 deletions test/WebhookVerifierTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,45 +5,69 @@
use Fingerprint\ServerAPI\Webhook\WebhookVerifier;
use PHPUnit\Framework\TestCase;

/**
* Tests for webhook signature verification.
*/
class WebhookVerifierTest extends TestCase
{
private $secret = 'secret';
private $data = 'data';
private string $secret = 'secret';
private string $data = 'data';

public function testWithValidSignature()
/**
* Verifies that a valid v1 signature is accepted.
*/
public function testWithValidSignature(): void
{
/** @noinspection SpellCheckingInspection */
$validHeader = 'v1=1b2c16b75bd2a870c114153ccda5bcfca63314bc722fa160d690de133ccbb9db';
$result = WebhookVerifier::IsValidWebhookSignature($validHeader, $this->data, $this->secret);
$this->assertTrue($result, 'With valid signature');
}

public function testWithInvalidHeader()
/**
* Verifies that a non-v1 signature version is rejected.
*/
public function testWithInvalidHeader(): void
{
$result = WebhookVerifier::IsValidWebhookSignature('v2=invalid', $this->data, $this->secret);
$this->assertFalse($result, 'With invalid header');
}

public function testWithHeaderWithoutVersion()
/**
* Verifies that a header without a version prefix is rejected.
*/
public function testWithHeaderWithoutVersion(): void
{
$result = WebhookVerifier::IsValidWebhookSignature('invalid', $this->data, $this->secret);
$this->assertFalse($result, 'With header without version');
}

public function testWithEmptyHeader()
/**
* Verifies that an empty header is rejected.
*/
public function testWithEmptyHeader(): void
{
$result = WebhookVerifier::IsValidWebhookSignature('', $this->data, $this->secret);
$this->assertFalse($result, 'With empty header');
}

public function testWithEmptySecret()
/**
* Verifies that an empty secret never matches.
*/
public function testWithEmptySecret(): void
{
/** @noinspection SpellCheckingInspection */
$validHeader = 'v1=1b2c16b75bd2a870c114153ccda5bcfca63314bc722fa160d690de133ccbb9db';
$result = WebhookVerifier::IsValidWebhookSignature($validHeader, $this->data, '');
$this->assertFalse($result, 'With empty secret');
}

public function testWithEmptyData()
/**
* Verifies that empty data does not match a signature computed with non-empty data.
*/
public function testWithEmptyData(): void
{
/** @noinspection SpellCheckingInspection */
$validHeader = 'v1=1b2c16b75bd2a870c114153ccda5bcfca63314bc722fa160d690de133ccbb9db';
$result = WebhookVerifier::IsValidWebhookSignature($validHeader, '', $this->secret);
$this->assertFalse($result, 'With empty data');
Expand Down
Loading