Use Index for CSRF protection tokens.
This commit is contained in:
parent
ba8115fe10
commit
1186b0daeb
3 changed files with 14 additions and 92 deletions
|
@ -1 +1 @@
|
||||||
Subproject commit f3535117cea575d6ec5c86964da69b2c018028d1
|
Subproject commit 31798a6b536069bc47832545b036c0d62422400c
|
|
@ -214,11 +214,9 @@ if($authToken->isValid()) {
|
||||||
AuthToken::nukeCookie();
|
AuthToken::nukeCookie();
|
||||||
}
|
}
|
||||||
|
|
||||||
CSRF::setGlobalSecretKey($cfg->getValue('csrf.secret', IConfig::T_STR, 'soup'));
|
CSRF::init(
|
||||||
CSRF::setGlobalIdentity(
|
$cfg->getValue('csrf.secret', IConfig::T_STR, 'soup'),
|
||||||
UserSession::hasCurrent()
|
(UserSession::hasCurrent() ? UserSession::getCurrent()->getToken() : ($_SERVER['REMOTE_ADDR'] ?? '::1'))
|
||||||
? UserSession::getCurrent()->getToken()
|
|
||||||
: ($_SERVER['REMOTE_ADDR'] ?? '::1')
|
|
||||||
);
|
);
|
||||||
|
|
||||||
function mszLockdown(): void {
|
function mszLockdown(): void {
|
||||||
|
|
96
src/CSRF.php
96
src/CSRF.php
|
@ -1,104 +1,28 @@
|
||||||
<?php
|
<?php
|
||||||
namespace Misuzu;
|
namespace Misuzu;
|
||||||
|
|
||||||
use Exception;
|
use Index\Security\CSRFP;
|
||||||
use InvalidArgumentException;
|
|
||||||
|
|
||||||
final class CSRF {
|
final class CSRF {
|
||||||
public const TOLERANCE = 30 * 60;
|
private static CSRFP $instance;
|
||||||
public const HASH_ALGO = 'sha1';
|
|
||||||
public const EPOCH = 1575158400;
|
|
||||||
|
|
||||||
private $timestamp = 0;
|
public static function init(string $secretKey, string $identity): void {
|
||||||
private $tolerance = 0;
|
self::$instance = new CSRFP($secretKey, $identity);
|
||||||
|
|
||||||
private static $globalIdentity = '';
|
|
||||||
private static $globalSecretKey = '';
|
|
||||||
|
|
||||||
public function __construct(int $tolerance = self::TOLERANCE, ?int $timestamp = null) {
|
|
||||||
$this->setTolerance($tolerance);
|
|
||||||
$this->setTimestamp($timestamp ?? self::timestamp());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function timestamp(): int {
|
public static function validate(string $token, int $tolerance = -1): bool {
|
||||||
return time() - self::EPOCH;
|
return self::$instance->verifyToken($token, $tolerance);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function setGlobalIdentity(string $identity): void {
|
public static function token(): string {
|
||||||
self::$globalIdentity = $identity;
|
return self::$instance->createToken();
|
||||||
}
|
|
||||||
public static function setGlobalSecretKey(string $secretKey): void {
|
|
||||||
self::$globalSecretKey = $secretKey;
|
|
||||||
}
|
|
||||||
public static function validate(string $token, ?string $identity = null, ?string $secretKey = null): bool {
|
|
||||||
try {
|
|
||||||
return self::decode($token, $identity ?? self::$globalIdentity, $secretKey ?? self::$globalSecretKey)->isValid();
|
|
||||||
} catch(Exception $ex) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public static function token(?string $identity = null, int $tolerance = self::TOLERANCE, ?string $secretKey = null, ?int $timestamp = null): string {
|
|
||||||
return (new static($tolerance, $timestamp))->encode($identity ?? self::$globalIdentity, $secretKey ?? self::$globalSecretKey);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Should be replaced by filters eventually <
|
public static function validateRequest(int $tolerance = -1): bool {
|
||||||
public static function validateRequest($identity = null, ?string $secretKey = null): bool {
|
|
||||||
$token = filter_input(INPUT_POST, '_csrf');
|
$token = filter_input(INPUT_POST, '_csrf');
|
||||||
if(empty($token))
|
if(empty($token))
|
||||||
$token = filter_input(INPUT_GET, 'csrf');
|
$token = filter_input(INPUT_GET, 'csrf');
|
||||||
if(empty($token))
|
|
||||||
return false;
|
|
||||||
return self::validate($token, $identity, $secretKey);
|
|
||||||
}
|
|
||||||
// >
|
|
||||||
|
|
||||||
public static function decode(string $token, string $identity, string $secretKey): CSRF {
|
return self::$instance->verifyToken($token, $tolerance);
|
||||||
$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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue