<?php
// =============================================================================
// POST /api/guard/key — libapksec trust-anchor bootstrap.
//
// Request body (JSON):
//   { "p": pkg, "c": cert_hex, "t": ts, "s": gs_sig, "b": build_id }
//
// Response (JSON):
//   { "k": "<32 hex chars>" }   // 16 bytes that XOR with the binary's
//                                // auth_key install server_key
//
// Algorithm (verified by Frida):
//   s_expected = sig_gs(auth_key, p, c, t)
//   k_raw      = auth_key XOR target_server_key   (16 bytes)
//   k_hex      = bin2hex(k_raw)
//
// Where `target_server_key` is whatever the binary expects (default for the
// stock IBO Ultra libapksec.so is key_part_a XOR key_part_b = the constant
// stored in config.target_server_key_hex).
// =============================================================================

require_once __DIR__ . '/../../includes/client_common.php';

require_method('POST');
$body = json_request_body();

$pkg  = isset($body['p']) ? (string)$body['p'] : '';
$cert = isset($body['c']) ? (string)$body['c'] : '';
$ts   = isset($body['t']) ? (string)$body['t'] : '';
$s    = isset($body['s']) ? (string)$body['s'] : '';
$bid  = isset($body['b']) ? (string)$body['b'] : '';

$cfg = db_config();
$auth_key_hex   = (string)($cfg['libapksec_auth_key_hex']   ?? '');
$target_key_hex = (string)($cfg['target_server_key_hex']    ?? '0370dd1afec23dabd2639452ca6a4099');
$expected_cert  = (string)($cfg['cert_sha256_hex']          ?? '');
$expected_pkg   = (string)($cfg['pkg_name']                 ?? '');
$expected_bid   = (string)($cfg['libapksec_build_id']       ?? '');

if ($auth_key_hex === '' || strlen($auth_key_hex) !== 32) {
    json_error('server config missing libapksec_auth_key_hex', 500);
}
$auth_key_raw = @hex2bin($auth_key_hex);
if ($auth_key_raw === false) {
    json_error('server config libapksec_auth_key_hex malformed', 500);
}

$expected_s = sig_gs($auth_key_raw, $pkg, $cert, $ts);
$valid      = hash_equals($expected_s, $s) ? 1 : 0;
$cert_ok    = ($expected_cert === '' || strcasecmp($cert, $expected_cert) === 0);
$pkg_ok     = ($expected_pkg  === '' || $pkg === $expected_pkg);
$bid_ok     = ($expected_bid  === '' || strcasecmp($bid, $expected_bid) === 0);

// HARD validate. The key is the libapksec trust anchor for every device on
// every panel sharing this build — leaking it via a sloppy validator forfeits
// the cert pinning entirely. If anything looks wrong, refuse + log + return.
$auth_ok = ($valid && $cert_ok && $pkg_ok && $bid_ok);

// Log either way so the operator can debug rejected calls.
db_exec(
    'INSERT INTO guard_key_log (req_pkg, req_cert, req_ts, req_s, req_s_valid,
                                resp_k_hex, ip_address, user_agent)
     VALUES (?, ?, ?, ?, ?, ?, ?, ?)',
    [$pkg, $cert, (int)$ts, $s, $valid, $auth_ok ? '<deferred>' : '', client_ip(), user_agent()]
);
client_log_prune_table('guard_key_log');

if (!$auth_ok) {
    panel_log_write([
        'level'   => 'warning',
        'message' => 'guard/key rejected — sig=' . ($valid?1:0) . ' cert=' . ($cert_ok?1:0)
                   . ' pkg=' . ($pkg_ok?1:0) . ' bid=' . ($bid_ok?1:0),
        'context' => ['pkg' => $pkg, 'cert' => substr($cert, 0, 16) . '…'],
    ]);
    json_error('forbidden', 403);
}

// Compute response k now that we know the request is legit.
$target_key_raw = @hex2bin($target_key_hex);
if ($target_key_raw === false || strlen($target_key_raw) !== 16) {
    json_error('server config target_server_key_hex malformed (need 32 hex chars)', 500);
}
$k_raw = sig_sk_response_for_target($auth_key_raw, $target_key_raw);
$k_hex = bin2hex($k_raw);

// Backfill the resp_k_hex now that we know it.
db_exec("UPDATE guard_key_log SET resp_k_hex = ? WHERE id = (SELECT MAX(id) FROM guard_key_log)",
        [$k_hex]);

// The Android client expects just {"k": "..."}; some forks return
// {"k": "...", "ok": true}. We mirror the minimal shape — Guard.gk()
// substring-greps for "\"k\":\"" then takes the next 32 chars.
json_response(['k' => $k_hex]);
