2018-10-02 21:16:42 +02:00
|
|
|
<?php
|
|
|
|
define('MSZ_CSRF_TOLERANCE', 15 * 60); // DO NOT EXCEED 16-BIT INTEGER SIZES, SHIT _WILL_ BREAK
|
|
|
|
define('MSZ_CSRF_HTML', '<input type="hidden" name="%1$s" value="%2$s">');
|
|
|
|
define('MSZ_CSRF_SECRET_STORE', '_msz_csrf_secret');
|
|
|
|
define('MSZ_CSRF_IDENTITY_STORE', '_msz_csrf_identity');
|
|
|
|
define('MSZ_CSRF_TOKEN_STORE', '_msz_csrf_tokens');
|
|
|
|
define('MSZ_CSRF_HASH_ALGO', 'sha256');
|
|
|
|
define('MSZ_CSRF_TOKEN_LENGTH', 76); // 8 + 4 + 64
|
|
|
|
|
2018-10-02 21:37:10 +02:00
|
|
|
// the following three functions DO NOT depend on csrf_init().
|
2018-10-02 21:16:42 +02:00
|
|
|
// $realm = Some kinda identifier for whatever's trying to do a validation.
|
2018-10-02 21:37:10 +02:00
|
|
|
// $identity = When the user is logged in I recommend just using their session key, otherwise IP will be fine.
|
2018-10-02 21:16:42 +02:00
|
|
|
function csrf_token_create(
|
|
|
|
string $realm,
|
|
|
|
string $identity,
|
|
|
|
string $secretKey,
|
|
|
|
?int $timestamp = null,
|
|
|
|
int $tolerance = MSZ_CSRF_TOLERANCE
|
|
|
|
): string {
|
|
|
|
$timestamp = $timestamp ?? time();
|
|
|
|
$token = bin2hex(pack('Vv', $timestamp, $tolerance));
|
|
|
|
|
|
|
|
return $token . csrf_token_hash(
|
|
|
|
MSZ_CSRF_HASH_ALGO,
|
|
|
|
$realm,
|
|
|
|
$identity,
|
|
|
|
$secretKey,
|
|
|
|
$timestamp,
|
|
|
|
$tolerance
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
function csrf_token_hash(
|
|
|
|
string $algo,
|
|
|
|
string $realm,
|
|
|
|
string $identity,
|
|
|
|
string $secretKey,
|
|
|
|
int $timestamp,
|
|
|
|
int $tolerance
|
|
|
|
): string {
|
|
|
|
return hash_hmac(
|
|
|
|
$algo,
|
|
|
|
implode(',', [$realm, $identity, $timestamp, $tolerance]),
|
|
|
|
$secretKey
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
function csrf_token_verify(
|
|
|
|
string $realm,
|
|
|
|
string $token,
|
|
|
|
string $identity,
|
|
|
|
string $secretKey
|
|
|
|
): bool {
|
|
|
|
if (empty($token) || strlen($token) !== MSZ_CSRF_TOKEN_LENGTH) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
[$timestamp, $tolerance] = [0, 0];
|
|
|
|
extract(unpack('Vtimestamp/vtolerance', hex2bin(substr($token, 0, 12))));
|
|
|
|
|
|
|
|
if (time() > $timestamp + $tolerance) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// remove timestamp + tolerance from token
|
|
|
|
$token = substr($token, 12);
|
|
|
|
|
|
|
|
$compare = csrf_token_hash(
|
|
|
|
MSZ_CSRF_HASH_ALGO,
|
|
|
|
$realm,
|
|
|
|
$identity,
|
|
|
|
$secretKey,
|
|
|
|
$timestamp,
|
|
|
|
$tolerance
|
|
|
|
);
|
|
|
|
|
|
|
|
return hash_equals($compare, $token);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sets some defaults
|
|
|
|
function csrf_init(string $secretKey, string $identity): void
|
|
|
|
{
|
|
|
|
$GLOBALS[MSZ_CSRF_SECRET_STORE] = $secretKey;
|
|
|
|
$GLOBALS[MSZ_CSRF_IDENTITY_STORE] = $identity;
|
|
|
|
$GLOBALS[MSZ_CSRF_TOKEN_STORE] = [];
|
|
|
|
}
|
|
|
|
|
|
|
|
function csrf_token(string $realm): string
|
|
|
|
{
|
|
|
|
if (array_key_exists($realm, $GLOBALS[MSZ_CSRF_TOKEN_STORE])) {
|
|
|
|
return $GLOBALS[MSZ_CSRF_TOKEN_STORE][$realm];
|
|
|
|
}
|
|
|
|
|
|
|
|
return $GLOBALS[MSZ_CSRF_TOKEN_STORE][$realm] = csrf_token_create(
|
|
|
|
$realm,
|
|
|
|
$GLOBALS[MSZ_CSRF_IDENTITY_STORE],
|
|
|
|
$GLOBALS[MSZ_CSRF_SECRET_STORE]
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
function csrf_verify(string $realm, string $token): bool
|
|
|
|
{
|
|
|
|
return csrf_token_verify(
|
|
|
|
$realm,
|
|
|
|
$token,
|
|
|
|
$GLOBALS[MSZ_CSRF_IDENTITY_STORE],
|
|
|
|
$GLOBALS[MSZ_CSRF_SECRET_STORE]
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
function csrf_html(string $realm, string $name = 'csrf'): string
|
|
|
|
{
|
|
|
|
return sprintf(MSZ_CSRF_HTML, $name, csrf_token($realm));
|
|
|
|
}
|