- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
192 lines
6.4 KiB
PHP
192 lines
6.4 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Tests\Framework\Security\RequestSigning;
|
|
|
|
use App\Framework\Http\Headers;
|
|
use App\Framework\Http\HttpRequest;
|
|
use App\Framework\Http\Method;
|
|
use App\Framework\Security\RequestSigning\InMemorySigningKeyRepository;
|
|
use App\Framework\Security\RequestSigning\RequestVerifier;
|
|
use App\Framework\Security\RequestSigning\SigningKey;
|
|
use PHPUnit\Framework\Attributes\Test;
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
final class RequestVerifierTest extends TestCase
|
|
{
|
|
private RequestVerifier $verifier;
|
|
|
|
private InMemorySigningKeyRepository $keyRepository;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
$this->keyRepository = new InMemorySigningKeyRepository();
|
|
$this->verifier = new RequestVerifier($this->keyRepository);
|
|
}
|
|
|
|
#[Test]
|
|
public function it_can_verify_valid_hmac_signature(): void
|
|
{
|
|
$key = SigningKey::createHmac('test-key', 'my-secret-key-that-is-long-enough-for-security');
|
|
$this->keyRepository->store($key);
|
|
|
|
// Manually create a valid signature for testing
|
|
$request = new HttpRequest(
|
|
method: Method::GET,
|
|
path: '/api/test',
|
|
headers: new Headers([
|
|
'Host' => 'example.com',
|
|
'Date' => 'Thu, 05 Jan 2023 21:31:40 GMT',
|
|
'Signature' => 'keyId="test-key",algorithm="hmac-sha256",headers="(request-target) host date",signature="mock-signature"',
|
|
])
|
|
);
|
|
|
|
// We need to mock the signature creation to match what would be generated
|
|
$signingString = "(request-target): get /api/test\nhost: example.com\ndate: Thu, 05 Jan 2023 21:31:40 GMT";
|
|
$expectedSignature = base64_encode(hash_hmac('sha256', $signingString, 'my-secret-key-that-is-long-enough-for-security', true));
|
|
|
|
$validRequest = new HttpRequest(
|
|
method: Method::GET,
|
|
path: '/api/test',
|
|
headers: new Headers([
|
|
'Host' => 'example.com',
|
|
'Date' => 'Thu, 05 Jan 2023 21:31:40 GMT',
|
|
'Signature' => "keyId=\"test-key\",algorithm=\"hmac-sha256\",headers=\"(request-target) host date\",signature=\"{$expectedSignature}\"",
|
|
])
|
|
);
|
|
|
|
$result = $this->verifier->verify($validRequest);
|
|
|
|
$this->assertTrue($result->isSuccess());
|
|
$this->assertEquals('test-key', $result->signature->keyId);
|
|
}
|
|
|
|
#[Test]
|
|
public function it_fails_verification_for_missing_signature(): void
|
|
{
|
|
$request = new HttpRequest(
|
|
method: Method::GET,
|
|
path: '/api/test',
|
|
headers: new Headers(['Host' => 'example.com'])
|
|
);
|
|
|
|
$result = $this->verifier->verify($request);
|
|
|
|
$this->assertTrue($result->isFailure());
|
|
$this->assertEquals('Missing Signature header', $result->errorMessage);
|
|
}
|
|
|
|
#[Test]
|
|
public function it_fails_verification_for_unknown_key(): void
|
|
{
|
|
$request = new HttpRequest(
|
|
method: Method::GET,
|
|
path: '/api/test',
|
|
headers: new Headers([
|
|
'Host' => 'example.com',
|
|
'Date' => 'Thu, 05 Jan 2023 21:31:40 GMT',
|
|
'Signature' => 'keyId="unknown-key",algorithm="hmac-sha256",headers="(request-target) host date",signature="test-signature"',
|
|
])
|
|
);
|
|
|
|
$result = $this->verifier->verify($request);
|
|
|
|
$this->assertTrue($result->isFailure());
|
|
$this->assertEquals('Unknown key ID: unknown-key', $result->errorMessage);
|
|
}
|
|
|
|
#[Test]
|
|
public function it_fails_verification_for_invalid_signature(): void
|
|
{
|
|
$key = SigningKey::createHmac('test-key', 'my-secret-key-that-is-long-enough-for-security');
|
|
$this->keyRepository->store($key);
|
|
|
|
$request = new HttpRequest(
|
|
method: Method::GET,
|
|
path: '/api/test',
|
|
headers: new Headers([
|
|
'Host' => 'example.com',
|
|
'Date' => 'Thu, 05 Jan 2023 21:31:40 GMT',
|
|
'Signature' => 'keyId="test-key",algorithm="hmac-sha256",headers="(request-target) host date",signature="invalid-signature"',
|
|
])
|
|
);
|
|
|
|
$result = $this->verifier->verify($request);
|
|
|
|
$this->assertTrue($result->isFailure());
|
|
$this->assertEquals('Invalid signature', $result->errorMessage);
|
|
}
|
|
|
|
#[Test]
|
|
public function it_can_verify_digest_header(): void
|
|
{
|
|
$body = '{"test": "data"}';
|
|
$expectedHash = base64_encode(hash('sha256', $body, true));
|
|
|
|
$request = new HttpRequest(
|
|
method: Method::POST,
|
|
path: '/api/test',
|
|
headers: new Headers([
|
|
'Digest' => "SHA256={$expectedHash}",
|
|
]),
|
|
body: $body
|
|
);
|
|
|
|
$this->assertTrue($this->verifier->verifyDigest($request));
|
|
}
|
|
|
|
#[Test]
|
|
public function it_fails_digest_verification_for_wrong_hash(): void
|
|
{
|
|
$request = new HttpRequest(
|
|
method: Method::POST,
|
|
path: '/api/test',
|
|
headers: new Headers([
|
|
'Digest' => 'SHA256=wrong-hash',
|
|
]),
|
|
body: '{"test": "data"}'
|
|
);
|
|
|
|
$this->assertFalse($this->verifier->verifyDigest($request));
|
|
}
|
|
|
|
#[Test]
|
|
public function it_passes_digest_verification_when_no_digest_header(): void
|
|
{
|
|
$request = new HttpRequest(
|
|
method: Method::POST,
|
|
path: '/api/test',
|
|
body: '{"test": "data"}'
|
|
);
|
|
|
|
$this->assertTrue($this->verifier->verifyDigest($request));
|
|
}
|
|
|
|
#[Test]
|
|
public function it_fails_verification_for_expired_key(): void
|
|
{
|
|
$expiredKey = SigningKey::createHmac(
|
|
'expired-key',
|
|
'secret-that-is-long-enough-for-security',
|
|
expiresAt: new \DateTimeImmutable('2020-01-01')
|
|
);
|
|
$this->keyRepository->store($expiredKey);
|
|
|
|
$request = new HttpRequest(
|
|
method: Method::GET,
|
|
path: '/api/test',
|
|
headers: new Headers([
|
|
'Host' => 'example.com',
|
|
'Date' => 'Thu, 05 Jan 2023 21:31:40 GMT',
|
|
'Signature' => 'keyId="expired-key",algorithm="hmac-sha256",headers="(request-target) host date",signature="test-signature"',
|
|
])
|
|
);
|
|
|
|
$result = $this->verifier->verify($request);
|
|
|
|
$this->assertTrue($result->isFailure());
|
|
$this->assertEquals('Signing key is not valid', $result->errorMessage);
|
|
}
|
|
}
|