true, 'license' => ['product_id' => 123]]; $sig = $signature->sign($data, $timestamp); self::assertTrue($signature->verify($data, $sig, $timestamp)); } #[Test] public function itRejectsInvalidSignature(): void { $signature = new ResponseSignature(self::SECRET_KEY); $timestamp = time(); $data = ['valid' => true]; self::assertFalse($signature->verify($data, 'invalid-signature', $timestamp)); } #[Test] public function itRejectsTamperedData(): void { $signature = new ResponseSignature(self::SECRET_KEY); $timestamp = time(); $originalData = ['valid' => true]; $tamperedData = ['valid' => false]; $sig = $signature->sign($originalData, $timestamp); self::assertFalse($signature->verify($tamperedData, $sig, $timestamp)); } #[Test] public function itRejectsExpiredTimestamp(): void { $signature = new ResponseSignature(self::SECRET_KEY, timestampTolerance: 60); $oldTimestamp = time() - 120; // 2 minutes ago $data = ['valid' => true]; $sig = $signature->sign($data, $oldTimestamp); self::assertFalse($signature->verify($data, $sig, $oldTimestamp)); } #[Test] public function itAcceptsTimestampWithinTolerance(): void { $signature = new ResponseSignature(self::SECRET_KEY, timestampTolerance: 300); $recentTimestamp = time() - 60; // 1 minute ago $data = ['valid' => true]; $sig = $signature->sign($data, $recentTimestamp); self::assertTrue($signature->verify($data, $sig, $recentTimestamp)); } #[Test] public function itDerivesUniqueKeysPerLicense(): void { $key1 = ResponseSignature::deriveKey('LICENSE-001', self::SERVER_SECRET); $key2 = ResponseSignature::deriveKey('LICENSE-002', self::SERVER_SECRET); self::assertNotSame($key1, $key2); self::assertSame(64, strlen($key1)); // SHA256 hex = 64 chars } #[Test] public function itProducesDeterministicKeys(): void { $key1 = ResponseSignature::deriveKey(self::LICENSE_KEY, self::SERVER_SECRET); $key2 = ResponseSignature::deriveKey(self::LICENSE_KEY, self::SERVER_SECRET); self::assertSame($key1, $key2); } #[Test] public function itCreatesFromLicenseKey(): void { $signature = ResponseSignature::fromLicenseKey(self::LICENSE_KEY, self::SERVER_SECRET); $timestamp = time(); $data = ['valid' => true]; $sig = $signature->sign($data, $timestamp); // Verify with same derived key $signature2 = ResponseSignature::fromLicenseKey(self::LICENSE_KEY, self::SERVER_SECRET); self::assertTrue($signature2->verify($data, $sig, $timestamp)); } #[Test] public function itExtractsSignatureFromHeaders(): void { $headers = [ 'X-License-Signature' => 'abc123', 'Content-Type' => 'application/json', ]; self::assertSame('abc123', ResponseSignature::extractSignature($headers)); } #[Test] public function itExtractsTimestampFromHeaders(): void { $headers = [ 'X-License-Timestamp' => '1706000000', 'Content-Type' => 'application/json', ]; self::assertSame(1706000000, ResponseSignature::extractTimestamp($headers)); } #[Test] public function itReturnsNullForMissingHeaders(): void { $headers = ['Content-Type' => 'application/json']; self::assertNull(ResponseSignature::extractSignature($headers)); self::assertNull(ResponseSignature::extractTimestamp($headers)); } #[Test] public function itHandlesLowercaseHeaders(): void { $headers = [ 'x-license-signature' => 'abc123', 'x-license-timestamp' => '1706000000', ]; self::assertSame('abc123', ResponseSignature::extractSignature($headers)); self::assertSame(1706000000, ResponseSignature::extractTimestamp($headers)); } #[Test] public function itProducesConsistentSignaturesForSameData(): void { $signature = new ResponseSignature(self::SECRET_KEY); $timestamp = 1706000000; $data = ['b' => 2, 'a' => 1]; // Unsorted $sig1 = $signature->sign($data, $timestamp); $sig2 = $signature->sign($data, $timestamp); self::assertSame($sig1, $sig2); } #[Test] public function itSortsKeysForConsistentSignatures(): void { $signature = new ResponseSignature(self::SECRET_KEY); $timestamp = 1706000000; $data1 = ['a' => 1, 'b' => 2]; $data2 = ['b' => 2, 'a' => 1]; $sig1 = $signature->sign($data1, $timestamp); $sig2 = $signature->sign($data2, $timestamp); self::assertSame($sig1, $sig2); } }