using Microsoft.AspNetCore.Mvc; using Campus_live_rewrite.api.server; namespace Campus_live_rewrite.Controllers { // Patch handshake. CampusPatch (the DLL) POSTs here once at startup (main.cpp) with the // shared secret in the X-Campus-Patch header. The SERVER holds the real secret and compares: // a match answers 200, anything else answers 403. A DLL with the wrong/absent header is // denied here, and main.cpp aborts on a non-200 so that client never connects. // // The secret lives ONLY on the server (ConfigCampus.PatchSignature) - the DLL just echoes // what it was built with; the server is what decides. [ApiController] public class PatchController : ControllerBase { public const string HeaderName = "X-Campus-Patch"; [HttpPost("/api/patch/verify")] [HttpGet("/api/patch/verify")] public IActionResult Verify() { string sent = Request.Headers[HeaderName].FirstOrDefault() ?? ""; string expected = ConfigCampus.PatchSignature ?? ""; string ip = Request.Headers["CF-Connecting-IP"].FirstOrDefault() ?? Request.Headers["X-Forwarded-For"].FirstOrDefault() ?? HttpContext.Connection.RemoteIpAddress?.ToString() ?? "?"; bool ok = !string.IsNullOrEmpty(expected) && FixedTimeEquals(sent, expected); if (!ok) { Console.WriteLine($"[patchverify] DENIED {ip} - signature {(string.IsNullOrEmpty(sent) ? "absent" : "mismatch")}"); return StatusCode(StatusCodes.Status403Forbidden, new { ok = false }); } // Just a friendly startup check for the DLL - real enforcement is the per-request // X-Campus-Patch header validated in PatchGateMiddleware. No whitelist is kept. Console.WriteLine($"[patchverify] OK {ip}"); return Ok(new { ok = true }); } // Constant-time compare so the secret can't be brute-forced a byte at a time by timing. private static bool FixedTimeEquals(string a, string b) { var ab = System.Text.Encoding.UTF8.GetBytes(a ?? ""); var bb = System.Text.Encoding.UTF8.GetBytes(b ?? ""); return System.Security.Cryptography.CryptographicOperations.FixedTimeEquals(ab, bb); } } }