get_header( 'X-Site-Sync-Key' ); $signature = $request->get_header( 'X-Site-Sync-Signature' ); $timestamp = $request->get_header( 'X-Site-Sync-Timestamp' ); if ( ! $providedKey || ! $signature || ! $timestamp ) { return new WP_Error( 'site_sync_missing_headers', __( 'Missing authentication headers.', 'site-sync' ), array( 'status' => 401 ) ); } $peerKey = $settings->get( 'peer_site_key' ); if ( ! empty( $peerKey ) && ! hash_equals( $peerKey, $providedKey ) ) { return new WP_Error( 'site_sync_invalid_peer', __( 'Peer key does not match.', 'site-sync' ), array( 'status' => 401 ) ); } $sharedSecret = $settings->get( 'shared_key' ); if ( empty( $sharedSecret ) ) { return new WP_Error( 'site_sync_no_secret', __( 'Shared secret is not configured.', 'site-sync' ), array( 'status' => 401 ) ); } $body = $request->get_body() ?? ''; $expected = self::build_signature( $providedKey, $timestamp, $body, $sharedSecret ); if ( ! hash_equals( $expected, $signature ) ) { return new WP_Error( 'site_sync_bad_signature', __( 'Signature verification failed.', 'site-sync' ), array( 'status' => 401 ) ); } $ts = is_numeric( $timestamp ) ? (int) $timestamp : 0; if ( $ts > 0 && abs( time() - $ts ) > 300 ) { return new WP_Error( 'site_sync_stale', __( 'Request timestamp is outside the allowed window.', 'site-sync' ), array( 'status' => 401 ) ); } $cacheKey = 'site_sync_seen_' . md5( $providedKey . $signature . $timestamp ); if ( false !== get_transient( $cacheKey ) ) { return new WP_Error( 'site_sync_replay', __( 'Replay detected.', 'site-sync' ), array( 'status' => 401 ) ); } set_transient( $cacheKey, 1, 5 * MINUTE_IN_SECONDS ); return true; } /** * Build an HMAC signature for the given key, timestamp, and body using the shared secret. * * @param string $key The peer site key. * @param string $timestamp The request timestamp. * @param string $body The request body. * @param string $secret The shared secret key. * @return string The generated HMAC signature. */ public static function build_signature( string $key, string $timestamp, string $body, string $secret ): string { $canonical = implode( "\n", array( $key, $timestamp, $body ) ); return hash_hmac( 'sha256', $canonical, $secret ); } }