server('QUERY_STRING', ''); $signatureParameter = 'signature='; $keyIdParameter = 'key_id='; $signatureStart = strpos($queryString, $signatureParameter); if ($queryString === '' || $signatureStart === false || $signatureStart === 0) { throw new InvalidRewardedAdCallback('Missing rewarded ad signature.'); } $content = substr($queryString, 0, $signatureStart - 1); $signatureAndKey = substr($queryString, $signatureStart); $keyIdStart = strpos($signatureAndKey, $keyIdParameter); if ($keyIdStart === false) { throw new InvalidRewardedAdCallback('Missing rewarded ad key id.'); } $encodedSignature = substr( $signatureAndKey, strlen($signatureParameter), $keyIdStart - strlen($signatureParameter) - 1, ); $keyId = strtok(substr($signatureAndKey, $keyIdStart + strlen($keyIdParameter)), '&'); if ($encodedSignature === '' || $keyId === false || $keyId === '') { throw new InvalidRewardedAdCallback('Invalid rewarded ad signature parameters.'); } $signature = $this->base64UrlDecode(rawurldecode($encodedSignature)); $publicKey = $this->publicKey((string) $keyId); if (openssl_verify($content, $signature, $publicKey, OPENSSL_ALGO_SHA256) !== 1) { throw new InvalidRewardedAdCallback('Invalid rewarded ad signature.'); } } private function publicKey(string $keyId): string { $publicKeys = $this->publicKeys(); if (! isset($publicKeys[$keyId])) { Cache::forget($this->cacheKey()); $publicKeys = $this->publicKeys(); } if (! isset($publicKeys[$keyId])) { throw new InvalidRewardedAdCallback('Unknown rewarded ad signature key.'); } return $publicKeys[$keyId]; } /** * @return array */ private function publicKeys(): array { return Cache::remember( $this->cacheKey(), (int) config('admob.rewarded.public_keys_cache_seconds', 43200), function (): array { $response = Http::timeout(5) ->acceptJson() ->get((string) config('admob.rewarded.public_keys_url')); if (! $response->ok()) { throw new InvalidRewardedAdCallback('Unable to fetch rewarded ad public keys.'); } $keys = $response->json('keys'); if (! is_array($keys)) { throw new InvalidRewardedAdCallback('Invalid rewarded ad public keys.'); } $publicKeys = []; foreach ($keys as $key) { if (! is_array($key) || ! isset($key['keyId'])) { continue; } $pem = $key['pem'] ?? null; if (! is_string($pem) || $pem === '') { continue; } $publicKeys[(string) $key['keyId']] = $pem; } if ($publicKeys === []) { throw new InvalidRewardedAdCallback('No rewarded ad public keys are available.'); } return $publicKeys; } ); } private function base64UrlDecode(string $value): string { $normalized = strtr($value, '-_', '+/'); $padding = strlen($normalized) % 4; if ($padding > 0) { $normalized .= str_repeat('=', 4 - $padding); } $decoded = base64_decode($normalized, true); if ($decoded === false) { throw new InvalidRewardedAdCallback('Invalid rewarded ad signature encoding.'); } return $decoded; } private function cacheKey(): string { return 'admob:ssv-public-keys'; } }