serverSecret = defined('WC_LICENSE_SERVER_SECRET') ? WC_LICENSE_SERVER_SECRET : ''; } /** * Register WordPress hooks */ public function register(): void { add_filter('rest_post_dispatch', [$this, 'signResponse'], 10, 3); } /** * Sign REST API response * * @param \WP_REST_Response $response The response object * @param \WP_REST_Server $server The REST server * @param \WP_REST_Request $request The request object * @return \WP_REST_Response */ public function signResponse($response, $server, $request) { // Only sign license API responses if (!$this->shouldSign($request)) { return $response; } $data = $response->get_data(); $licenseKey = $request->get_param('license_key'); if (empty($licenseKey) || !is_array($data) || empty($this->serverSecret)) { return $response; } $headers = $this->createSignatureHeaders($data, $licenseKey); foreach ($headers as $name => $value) { $response->header($name, $value); } return $response; } /** * Check if request should be signed */ private function shouldSign(\WP_REST_Request $request): bool { $route = $request->get_route(); return str_starts_with($route, '/wc-licensed-product/v1/validate') || str_starts_with($route, '/wc-licensed-product/v1/status') || str_starts_with($route, '/wc-licensed-product/v1/activate'); } /** * Create signature headers for response * * @param array $data The response data * @param string $licenseKey The license key from the request * @return array Associative array of headers */ private function createSignatureHeaders(array $data, string $licenseKey): array { $timestamp = time(); $signingKey = $this->deriveKey($licenseKey); // Sort keys for consistent ordering ksort($data); // Build signature payload $payload = $timestamp . ':' . json_encode( $data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE ); return [ 'X-License-Signature' => hash_hmac('sha256', $payload, $signingKey), 'X-License-Timestamp' => (string) $timestamp, ]; } /** * Derive a unique signing key for a license * * Uses HKDF-like key derivation to create a unique signing key * for each license key, preventing cross-license signature attacks. * * @param string $licenseKey The license key * @return string The derived signing key (hex encoded) */ private function deriveKey(string $licenseKey): string { // HKDF-like key derivation $prk = hash_hmac('sha256', $licenseKey, $this->serverSecret, true); return hash_hmac('sha256', $prk . "\x01", $this->serverSecret); } }