2022-09-13 13:13:11 +00:00
|
|
|
<?php
|
|
|
|
// CSRFP.php
|
|
|
|
// Created: 2021-06-11
|
2023-07-11 21:59:09 +00:00
|
|
|
// Updated: 2023-07-11
|
2022-09-13 13:13:11 +00:00
|
|
|
|
|
|
|
namespace Index\Security;
|
|
|
|
|
2023-07-11 21:59:09 +00:00
|
|
|
use Index\Serialisation\Serialiser;
|
|
|
|
|
2022-09-13 13:13:11 +00:00
|
|
|
/**
|
2023-07-11 21:59:09 +00:00
|
|
|
* Contains a mechanism for validating requests.
|
2022-09-13 13:13:11 +00:00
|
|
|
*/
|
|
|
|
class CSRFP {
|
|
|
|
private const TOLERANCE = 30 * 60;
|
2023-07-11 21:59:09 +00:00
|
|
|
private const EPOCH = 1682985600;
|
|
|
|
private const HASH_ALGO = 'sha3-256';
|
|
|
|
|
|
|
|
private const TIMESTAMP_LENGTH = 4;
|
|
|
|
private const NONCE_LENGTH = 8;
|
|
|
|
private const HASH_LENGTH = 32;
|
2022-09-13 13:13:11 +00:00
|
|
|
|
|
|
|
private string $secretKey;
|
2023-07-11 21:59:09 +00:00
|
|
|
private string $identity;
|
2022-09-13 13:13:11 +00:00
|
|
|
private int $tolerance;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates a new CSRFP instance.
|
|
|
|
*
|
|
|
|
* @param string $secretKey Secret key for HMAC hashes.
|
2023-07-11 21:59:09 +00:00
|
|
|
* @param string $identity Unique identity component for these tokens.
|
|
|
|
* @param int $tolerance Default amount of time a token should remain valid for.
|
2022-09-13 13:13:11 +00:00
|
|
|
* @return CSRFP New CSRFP instance.
|
|
|
|
*/
|
|
|
|
public function __construct(
|
|
|
|
string $secretKey,
|
2023-07-11 21:59:09 +00:00
|
|
|
string $identity,
|
|
|
|
int $tolerance = self::TOLERANCE
|
2022-09-13 13:13:11 +00:00
|
|
|
) {
|
|
|
|
$this->secretKey = $secretKey;
|
2023-07-11 21:59:09 +00:00
|
|
|
$this->identity = $identity;
|
2022-09-13 13:13:11 +00:00
|
|
|
$this->tolerance = $tolerance;
|
|
|
|
}
|
|
|
|
|
2023-07-11 21:59:09 +00:00
|
|
|
private static function time(int $time = -1): int {
|
|
|
|
return ($time < 0 ? time() : $time) - self::EPOCH;
|
2022-09-13 13:13:11 +00:00
|
|
|
}
|
|
|
|
|
2023-07-11 21:59:09 +00:00
|
|
|
private function createHash(int $time, string $nonce): string {
|
|
|
|
return hash_hmac(self::HASH_ALGO, "{$this->identity}!{$time}!{$nonce}", $this->secretKey, true);
|
2022-09-13 13:13:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2023-07-11 21:59:09 +00:00
|
|
|
* Creates a token string.
|
2022-09-13 13:13:11 +00:00
|
|
|
*
|
2023-07-11 21:59:09 +00:00
|
|
|
* @param int $time Timestamp to generate the token for, -1 (default) for now.
|
2022-09-13 13:13:11 +00:00
|
|
|
* @return string Token string.
|
|
|
|
*/
|
2023-07-11 21:59:09 +00:00
|
|
|
public function createToken(int $time = -1): string {
|
|
|
|
$time = self::time($time);
|
|
|
|
$nonce = random_bytes(self::NONCE_LENGTH);
|
|
|
|
$hash = $this->createHash($time, $nonce);
|
|
|
|
|
|
|
|
return Serialiser::uriBase64()->serialise(
|
|
|
|
pack('V', $time) . $nonce . $hash
|
|
|
|
);
|
2022-09-13 13:13:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2023-07-11 21:59:09 +00:00
|
|
|
* Verifies a token string.
|
2022-09-13 13:13:11 +00:00
|
|
|
*
|
|
|
|
* @param string $token Token to test.
|
2023-07-11 21:59:09 +00:00
|
|
|
* @param int $tolerance Amount of seconds for which the token can remain valid, < 0 for whatever the default value is.
|
|
|
|
* @param int $time Point in time for which to check validity for this time.
|
2022-09-13 13:13:11 +00:00
|
|
|
* @return bool true if the token is valid, false if not.
|
|
|
|
*/
|
2023-07-11 21:59:09 +00:00
|
|
|
public function verifyToken(string $token, int $tolerance = -1, int $time = -1): bool {
|
|
|
|
if($tolerance === 0)
|
|
|
|
return false;
|
|
|
|
if($tolerance < 0)
|
|
|
|
$tolerance = $this->tolerance;
|
|
|
|
|
|
|
|
$token = Serialiser::uriBase64()->deserialise($token);
|
|
|
|
if(empty($token))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
$uTime = -1;
|
|
|
|
extract(unpack('VuTime', $token));
|
|
|
|
if($uTime < 0)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
$uNonce = substr($token, self::TIMESTAMP_LENGTH, self::NONCE_LENGTH);
|
|
|
|
if(empty($uNonce))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
$uHash = substr($token, self::TIMESTAMP_LENGTH + self::NONCE_LENGTH, self::HASH_LENGTH);
|
|
|
|
if(empty($uHash))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
$rTime = self::time($time);
|
|
|
|
|
|
|
|
return ($rTime - ($uTime + $tolerance)) < 0
|
|
|
|
&& hash_equals($this->createHash($uTime, $uNonce), $uHash);
|
2022-09-13 13:13:11 +00:00
|
|
|
}
|
|
|
|
}
|