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

/**
 * Authenticated encryption for secrets stored in the WP options table — the
 * per-site Worker token (`site_token`). The key is derived from wp_salt,
 * which lives in wp-config.php, NOT in the database, so a DB-only compromise
 * (SQL injection, a leaked DB backup) can't read the tokens.
 *
 * Format: "ffc1:" . base64( iv(12) | tag(16) | ciphertext ) — AES-256-GCM.
 * decrypt returns any value WITHOUT the "ffc1:" prefix unchanged, so legacy
 * plaintext keeps working and gets re-encrypted on the next write (migration).
 *
 * Shared class — an identical copy ships in each Forge plugin. The key derives
 * from the site's wp_salt, so a token written by one plugin decrypts in
 * another (bundle installs share tokens across plugins).
 */
if (!class_exists('Forge_Crypto')) {
class Forge_Crypto {
    const PREFIX = 'ffc1:';
    const CIPHER = 'aes-256-gcm';

    /** 32-byte key from the install's auth salt (wp-config, not the DB). */
    private static function key() {
        return hash('sha256', 'forge-crypto|v1|' . wp_salt('auth'), true);
    }

    /**
 * Encrypt a secret for at-rest storage. Empty input stays empty. If the
 * OpenSSL extension is unavailable we fall back to plaintext rather than
 * break the integration (no worse than the pre-encryption behaviour).
 */
    public static function encrypt($plaintext) {
        $plaintext = (string) $plaintext;
        if ($plaintext === '') return '';
        if (!function_exists('openssl_encrypt')) return $plaintext;
        $iv  = random_bytes(12);
        $tag = '';
        $ct  = openssl_encrypt($plaintext, self::CIPHER, self::key(), OPENSSL_RAW_DATA, $iv, $tag);
        if ($ct === false) return $plaintext;
        return self::PREFIX . base64_encode($iv . $tag . $ct);
    }

    /**
 * Decrypt a value produced by encrypt. Values without the "ffc1:" prefix
 * are returned as-is (legacy plaintext / empty). A tampered or undecodable
 * ciphertext returns '' so a corrupt token is never used.
 */
    public static function decrypt($stored) {
        $stored = (string) $stored;
        $plen = strlen(self::PREFIX);
        if ($stored === '' || strncmp($stored, self::PREFIX, $plen) !== 0) {
            return $stored; // legacy plaintext or empty — passthrough
        }
        if (!function_exists('openssl_decrypt')) return '';
        $raw = base64_decode(substr($stored, $plen), true);
        if ($raw === false || strlen($raw) < 28) return '';
        $iv  = substr($raw, 0, 12);
        $tag = substr($raw, 12, 16);
        $ct  = substr($raw, 28);
        $pt  = openssl_decrypt($ct, self::CIPHER, self::key(), OPENSSL_RAW_DATA, $iv, $tag);
        return $pt === false ? '' : $pt;
    }
}
}
