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