Files
michaelschiemer/tests/Framework/Security/RequestSigning/RequestVerifierTest.php
Michael Schiemer 55a330b223 Enable Discovery debug logging for production troubleshooting
- Add DISCOVERY_LOG_LEVEL=debug
- Add DISCOVERY_SHOW_PROGRESS=true
- Temporary changes for debugging InitializerProcessor fixes on production
2025-08-11 20:13:26 +02:00

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);
}
}