48 lines
1.4 KiB
PHP
48 lines
1.4 KiB
PHP
<?php
|
|
|
|
function og_timing_safe_equals($a, $b) {
|
|
if (!is_string($a) || !is_string($b)) return false;
|
|
if (function_exists('hash_equals')) {
|
|
return hash_equals($a, $b);
|
|
}
|
|
if (strlen($a) !== strlen($b)) return false;
|
|
$res = 0;
|
|
for ($i = 0; $i < strlen($a); $i++) {
|
|
$res |= ord($a[$i]) ^ ord($b[$i]);
|
|
}
|
|
return $res === 0;
|
|
}
|
|
|
|
function og_hmac_sha256_hex($secret, $message) {
|
|
return hash_hmac('sha256', $message, $secret);
|
|
}
|
|
|
|
function og_require_node_signature($secret, $rawBody, $maxSkewMs = 60000) {
|
|
$ts = $_SERVER['HTTP_X_OG_TIMESTAMP'] ?? null;
|
|
$sigHeader = $_SERVER['HTTP_X_OG_SIGNATURE'] ?? '';
|
|
|
|
if (!$ts || !preg_match('/^\d{13,}$/', $ts)) {
|
|
return ['ok' => false, 'error' => 'missing_or_invalid_timestamp'];
|
|
}
|
|
|
|
if (!preg_match('/^sha256=([0-9a-f]{64})$/', $sigHeader, $m)) {
|
|
return ['ok' => false, 'error' => 'missing_or_invalid_signature'];
|
|
}
|
|
|
|
$now = (int) round(microtime(true) * 1000);
|
|
$tsInt = (int) $ts;
|
|
if (abs($now - $tsInt) > $maxSkewMs) {
|
|
return ['ok' => false, 'error' => 'timestamp_out_of_range'];
|
|
}
|
|
|
|
$msg = $ts . '.' . $rawBody;
|
|
$expected = og_hmac_sha256_hex($secret, $msg);
|
|
$provided = $m[1];
|
|
|
|
if (!og_timing_safe_equals($expected, $provided)) {
|
|
return ['ok' => false, 'error' => 'signature_mismatch'];
|
|
}
|
|
|
|
return ['ok' => true];
|
|
}
|