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