setTolerance($tolerance); $this->setTimestamp($timestamp ?? self::timestamp()); } public static function timestamp(): int { return time() - self::EPOCH; } public static function setGlobalIdentity(string $identity): void { self::$globalIdentity = $identity; } public static function setGlobalSecretKey(string $secretKey): void { self::$globalSecretKey = $secretKey; } public static function validate(string $token): bool { try { return self::decode($token, self::$globalIdentity, self::$globalSecretKey)->isValid(); } catch(Exception $ex) { return false; } } public static function token(): string { return (new static)->encode(self::$globalIdentity, self::$globalSecretKey); } public static function html(): string { return sprintf('', self::token()); } public static function verify(): bool { return self::validate(!empty($_REQUEST['_csrf']) && is_string($_REQUEST['_csrf']) ? $_REQUEST['_csrf'] : ''); } public static function decode(string $token, string $identity, string $secretKey): CSRF { $hash = substr($token, 12); $unpacked = unpack('Vtimestamp/vtolerance', hex2bin(substr($token, 0, 12))); if(empty($hash) || empty($unpacked['timestamp']) || empty($unpacked['tolerance'])) throw new InvalidArgumentException('Invalid token provided.'); $csrf = new static($unpacked['tolerance'], $unpacked['timestamp']); if(!hash_equals($csrf->getHash($identity, $secretKey), $hash)) throw new InvalidArgumentException('Modified token.'); return $csrf; } public function encode(string $identity, string $secretKey): string { $token = bin2hex(pack('Vv', $this->getTimestamp(), $this->getTolerance())); $token .= $this->getHash($identity, $secretKey); return $token; } public function getHash(string $identity, string $secretKey): string { return hash_hmac(self::HASH_ALGO, "{$identity}|{$this->getTimestamp()}|{$this->getTolerance()}", $secretKey); } public function getTimestamp(): int { return $this->timestamp; } public function setTimestamp(int $timestamp): self { if($timestamp < 0 || $timestamp > 0xFFFFFFFF) throw new InvalidArgumentException('Timestamp must be within the constaints of an unsigned 32-bit integer.'); $this->timestamp = $timestamp; return $this; } public function getTolerance(): int { return $this->tolerance; } public function setTolerance(int $tolerance): self { if($tolerance < 0 || $tolerance > 0xFFFF) throw new InvalidArgumentException('Tolerance must be within the constaints of an unsigned 16-bit integer.'); $this->tolerance = $tolerance; return $this; } public function isValid(): bool { $currentTime = self::timestamp(); return $currentTime >= $this->getTimestamp() && $currentTime <= $this->getTimestamp() + $this->getTolerance(); } }