isValidSignatureVersion($version) && $this->requestTimestampIsAcceptable($time) && $this->isValidSignature($signature, $time, $rawPayload); } private function isValidSignatureVersion(string $version): bool { if ($version !== self::USING_API_VERSION) { $this->logger->error('Version is not accepted', [ 'request_version' => $version, 'accepted_version' => self::USING_API_VERSION, ]); return false; } return true; } private function requestTimestampIsAcceptable(CarbonInterface $timestamp): bool { $calculatedDiff = Carbon::now('UTC')->diffInMinutes($timestamp, true); if ($calculatedDiff > self::PERMITTED_TIMESTAMP_SKEW_IN_MINUTES) { $this->logger->error('request timestamp skew is not matched', [ 'request_timestamp' => $timestamp, 'diff' => $calculatedDiff, 'max_skew_in_minutes' => self::PERMITTED_TIMESTAMP_SKEW_IN_MINUTES, ]); return false; } return true; } private function isValidSignature( #[\SensitiveParameter] string $signature, CarbonInterface $timestamp, #[\SensitiveParameter] string $rawPayload ): bool { $providedSignature = $this->extractSignatureFromCurrentVersion($signature); if (! $providedSignature) { $this->logger->error("Could not extract signature from '{$signature}'"); return false; } $expectedSignature = hash_hmac('sha256', "{$timestamp->timestamp}.{$rawPayload}", $this->config->hmac); $signatureIsValid = hash_equals( $expectedSignature, $providedSignature ); if (! $signatureIsValid) { $this->logger->debug('Could not validate expected signature', [ 'request_signature' => $signature, 'expected' => $expectedSignature, ]); } return $signatureIsValid; } private function extractSignatureFromCurrentVersion(#[\SensitiveParameter] string $signature): ?string { $signatures = explode(',', $signature); foreach ($signatures as $sig) { $parts = explode('=', trim($sig)); if (count($parts) === 2 && trim($parts[0]) === self::USING_API_VERSION) { $hex = trim($parts[1]); if (strlen($hex) === 64) { return strtolower($hex); } } } return null; } }