Dashboard
PHP:
7.4.33
OS:
Linux
User:
vqweckd
/
/
home
/
vqweckd
/
www
/
wp-content
/
plugins
/
bbc
/
includes
đ€ Upload
đ New File
đ New Folder
Close
Editing: class-bbc-auth.php
<?php if (!defined('ABSPATH')) { exit; } final class BBC_Auth { const MAX_BODY_BYTES = 131072; const MAX_CLOCK_SKEW = 300; const MAX_LEDGER_SIZE = 200; public static function base64url_decode($value) { if (!is_string($value) || !preg_match('/^[A-Za-z0-9_-]+$/', $value)) { return false; } $padding = strlen($value) % 4; if ($padding) { $value .= str_repeat('=', 4 - $padding); } return base64_decode(strtr($value, '-_', '+/'), true); } public static function normalized_origin($value) { $parts = wp_parse_url((string) $value); if (!is_array($parts) || empty($parts['scheme']) || empty($parts['host'])) { return ''; } $scheme = strtolower($parts['scheme']); if ($scheme !== 'http' && $scheme !== 'https') { return ''; } $origin = $scheme . '://' . strtolower($parts['host']); if (!empty($parts['port'])) { $origin .= ':' . intval($parts['port']); } return $origin; } public static function enroll(WP_REST_Request $request) { if (get_option(BBC_OPTION_IDENTITY, false)) { return new WP_Error('bbc_already_enrolled', 'BBC is already enrolled.', array('status' => 409)); } if (!function_exists('sodium_crypto_sign_verify_detached')) { return new WP_Error('bbc_crypto_unavailable', 'Ed25519 verification is unavailable.', array('status' => 503)); } $body = $request->get_json_params(); $site_id = isset($body['site_id']) ? sanitize_text_field($body['site_id']) : ''; $key_id = isset($body['key_id']) ? sanitize_text_field($body['key_id']) : ''; $public_key = isset($body['public_key']) ? sanitize_text_field($body['public_key']) : ''; $origin = isset($body['origin']) ? self::normalized_origin($body['origin']) : ''; $raw_key = self::base64url_decode($public_key); if (!self::is_uuid($site_id) || !self::is_uuid($key_id) || $raw_key === false || strlen($raw_key) !== 32) { return new WP_Error('bbc_invalid_enrollment', 'BBC enrollment data is invalid.', array('status' => 400)); } if ($origin !== self::normalized_origin(home_url('/'))) { return new WP_Error('bbc_origin_mismatch', 'BBC enrollment origin does not match this site.', array('status' => 400)); } $identity = array( 'site_id' => $site_id, 'key_id' => $key_id, 'public_key' => $public_key, 'origin' => $origin, 'enrolled_at' => time(), 'update_base_url' => self::valid_update_url(isset($body['update_base_url']) ? $body['update_base_url'] : ''), 'update_public_key' => self::valid_public_key(isset($body['update_public_key']) ? $body['update_public_key'] : ''), ); if (!add_option(BBC_OPTION_IDENTITY, $identity, '', false)) { return new WP_Error('bbc_already_enrolled', 'BBC enrollment was locked by another request.', array('status' => 409)); } return $identity; } public static function verify_command(WP_REST_Request $request) { $identity = get_option(BBC_OPTION_IDENTITY, false); if (!is_array($identity)) { return new WP_Error('bbc_not_enrolled', 'BBC is not enrolled.', array('status' => 403)); } $raw_body = $request->get_body(); if (!is_string($raw_body) || strlen($raw_body) > self::MAX_BODY_BYTES) { return new WP_Error('bbc_body_rejected', 'BBC command body is invalid.', array('status' => 413)); } if (!self::consume_rate_limit()) { return new WP_Error('bbc_rate_limited', 'BBC command rate exceeded.', array('status' => 429)); } $body = json_decode($raw_body, true); if (!is_array($body)) { return new WP_Error('bbc_invalid_json', 'BBC command body is invalid.', array('status' => 400)); } $command_id = isset($body['command_id']) ? (string) $body['command_id'] : ''; $site_id = isset($body['site_id']) ? (string) $body['site_id'] : ''; $origin = isset($body['origin']) ? self::normalized_origin($body['origin']) : ''; $expires_at = isset($body['expires_at']) ? intval($body['expires_at']) : 0; $key_id = (string) $request->get_header('x-bbc-key-id'); $timestamp = intval($request->get_header('x-bbc-timestamp')); $nonce = (string) $request->get_header('x-bbc-nonce'); $signature = self::base64url_decode($request->get_header('x-bbc-signature')); $body_hash = hash('sha256', $raw_body); $ledger = self::ledger(); $now = time(); if (!self::is_uuid($command_id) || !self::is_uuid($site_id) || !self::is_uuid($key_id) || !preg_match('/^[A-Za-z0-9_-]{22,}$/', $nonce)) { return new WP_Error('bbc_bad_identity', 'BBC command identity is invalid.', array('status' => 403)); } $verification_key = ''; if ($key_id === $identity['key_id']) { $verification_key = $identity['public_key']; } elseif ( isset($identity['previous_key_id'], $identity['previous_public_key'], $identity['previous_valid_until']) && $key_id === $identity['previous_key_id'] && intval($identity['previous_valid_until']) >= $now && isset($ledger[$command_id]) && hash_equals((string) $ledger[$command_id]['body_hash'], $body_hash) ) { $verification_key = $identity['previous_public_key']; } if ($site_id !== $identity['site_id'] || $verification_key === '' || $origin !== $identity['origin']) { return new WP_Error('bbc_identity_mismatch', 'BBC command identity does not match.', array('status' => 403)); } if (abs($now - $timestamp) > self::MAX_CLOCK_SKEW || $expires_at < $now || $expires_at > $timestamp + self::MAX_CLOCK_SKEW) { return new WP_Error('bbc_command_expired', 'BBC command timestamp is invalid.', array('status' => 403, 'server_time' => $now)); } $canonical = "BBC1\nPOST\n/bbc/v1/command\n{$site_id}\n{$key_id}\n{$command_id}\n{$timestamp}\n{$expires_at}\n{$nonce}\n{$body_hash}"; $public_key = self::base64url_decode($verification_key); if ($signature === false || $public_key === false || !sodium_crypto_sign_verify_detached($signature, $canonical, $public_key)) { return new WP_Error('bbc_bad_signature', 'BBC command signature is invalid.', array('status' => 403)); } if (isset($ledger[$command_id]) && hash_equals((string) $ledger[$command_id]['body_hash'], $body_hash)) { $request->set_param('_bbc_cached_result', $ledger[$command_id]['result']); return true; } foreach ($ledger as $entry) { if (isset($entry['nonce']) && hash_equals((string) $entry['nonce'], $nonce)) { return new WP_Error('bbc_replay', 'BBC command was already used.', array('status' => 409)); } } $request->set_param('_bbc_command_meta', array('body_hash' => $body_hash, 'nonce' => $nonce)); return true; } public static function record_result(WP_REST_Request $request, $result) { $body = $request->get_json_params(); $meta = $request->get_param('_bbc_command_meta'); if (!is_array($body) || !is_array($meta) || !isset($body['command_id'])) { return; } $ledger = self::ledger(); $ledger[(string) $body['command_id']] = array( 'body_hash' => $meta['body_hash'], 'nonce' => $meta['nonce'], 'recorded_at' => time(), 'result' => $result, ); uasort($ledger, function ($left, $right) { return intval($right['recorded_at']) - intval($left['recorded_at']); }); update_option(BBC_OPTION_COMMANDS, array_slice($ledger, 0, self::MAX_LEDGER_SIZE, true), false); } public static function rotate_key($key_id, $public_key) { $identity = get_option(BBC_OPTION_IDENTITY, false); $raw_key = self::base64url_decode($public_key); if (!is_array($identity) || !self::is_uuid($key_id) || $raw_key === false || strlen($raw_key) !== 32) { return new WP_Error('bbc_invalid_key', 'BBC replacement key is invalid.', array('status' => 400)); } $identity['previous_key_id'] = isset($identity['key_id']) ? $identity['key_id'] : ''; $identity['previous_public_key'] = isset($identity['public_key']) ? $identity['public_key'] : ''; $identity['previous_valid_until'] = time() + self::MAX_CLOCK_SKEW; $identity['key_id'] = $key_id; $identity['public_key'] = $public_key; $identity['rotated_at'] = time(); update_option(BBC_OPTION_IDENTITY, $identity, false); return true; } private static function ledger() { $ledger = get_option(BBC_OPTION_COMMANDS, array()); return is_array($ledger) ? $ledger : array(); } private static function consume_rate_limit() { $minute = gmdate('YmdHi'); $ip = isset($_SERVER['REMOTE_ADDR']) ? (string) $_SERVER['REMOTE_ADDR'] : 'unknown'; $keys = array('bbc_rate_global_' . $minute => 60, 'bbc_rate_ip_' . hash('sha256', $ip) . '_' . $minute => 20); foreach ($keys as $key => $limit) { $count = intval(get_transient($key)); if ($count >= $limit) { return false; } set_transient($key, $count + 1, 120); } return true; } private static function valid_update_url($value) { $value = esc_url_raw((string) $value, array('https')); return strpos($value, 'https://') === 0 ? untrailingslashit($value) : ''; } private static function valid_public_key($value) { $value = sanitize_text_field((string) $value); $raw = self::base64url_decode($value); return $raw !== false && strlen($raw) === 32 ? $value : ''; } public static function is_uuid($value) { return is_string($value) && preg_match('/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i', $value); } }
Save
Cancel