<?php
/**
 * File Path: includes/api/class-mbs-hmac.php
 *
 * My Book Showroom — HMAC Helper
 *
 * HMAC-SHA256 signing and verification for authenticated requests to www.dmrstech.com.
 * Implements the exact canonical string formula locked in design spec §3.
 *
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

class MBS_HMAC { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedClassFound -- mbs_/MBS_ prefix is correct; scanner not configured with plugin prefix

	/**
	 * Sign a request using the exact canonical string formula.
	 *
	 * canonical = license_key . "|" . domain . "|" . timestamp . "|" . nonce
	 * signature = hash_hmac("sha256", canonical, per_license_api_secret)
	 *
	 * @param string $license_key  Raw (decrypted) license key.
	 * @param string $domain       Domain without protocol (parse_url result).
	 * @param int    $timestamp    Unix timestamp (must be within ±300s of server).
	 * @param string $nonce        Current nonce from mbs_api_nonce.
	 * @param string $api_secret   Raw (decrypted) per-license api_secret.
	 * @return string              Hex HMAC-SHA256 signature.
	 */
	public static function sign_request(
		string $license_key,
		string $domain,
		int $timestamp,
		string $nonce,
		string $api_secret
	): string {
		$canonical = $license_key . '|' . $domain . '|' . $timestamp . '|' . $nonce;
		return hash_hmac( 'sha256', $canonical, $api_secret );
	}

	/**
	 * Verify a response signature using timing-safe comparison.
	 *
	 * B-001: Enforces the ±MBS_HMAC_WINDOW timestamp window in addition to
	 * the hash comparison. A cryptographically valid signature with a stale
	 * timestamp is rejected to prevent replay attacks.
	 *
	 * Always pass $timestamp. The default of 0 skips the window check for
	 * backward compatibility but callers should always supply the timestamp
	 * used when computing $expected.
	 *
	 * @param string $expected   Expected signature (computed locally via sign_request()).
	 * @param string $received   Signature received from the server response.
	 * @param int    $timestamp  Unix timestamp used when signing the request (default 0 = skip window check).
	 */
	public static function verify_response( string $expected, ?string $received, int $timestamp = 0 ): bool {
		// QF-005: Reject empty/null values before hash_equals() — hash_equals("","") returns true,
		// which would allow an attacker to bypass signature verification with empty strings.
		// Null is treated identically to empty string (signing failure returns null in some paths).
		if ( '' === $expected || null === $received || '' === $received ) {
			return false;
		}
		// Enforce the replay window when a timestamp is provided.
		if ( $timestamp > 0 && abs( time() - $timestamp ) > MBS_HMAC_WINDOW ) {
			return false;
		}
		return hash_equals( $expected, $received );
	}
}
