// -- Uzaktan komut UYGULAMA (musteri tarafi) ------------------------------------ // 3 katmanli guvenlik + merkezi komut akisi. Soket katmani bu dosyada DEGIL; // burada yalniz "gelen mesaj -> dogrula -> uygula -> cevap" mantigi var. #include #include #include #include #include #include #include "lisans_uzaktan.hpp" #include "fiko/cekirdek/cekirdek.hpp" #include "fiko/cekirdek/kayit.hpp" #include "fiko/cekirdek/kripto.hpp" #include "fiko/cekirdek/lisans.hpp" #include "fiko/cekirdek/lisans_dosya.hpp" #include "fiko/core/gunluk.hpp" #include "lisans_yukle.hpp" namespace fiko { namespace { std::mutex g_mtx; std::vector g_kayitlar; std::atomic g_calisiyor{false}; Cekirdek* g_cekirdek = nullptr; constexpr std::size_t KAYIT_UST_SINIR = 60; // Soket katmanından gelen imza (uzakKomutMesajiIsle doldurur). // ⚠️ Not: üretici komutu imzalarken bu KANONİK metni imzalar: // komutId=\nkomut_kodu=\nmesaj=\n // Sıra ve içerik sunucu ile BİREBİR aynı olmalıdır. std::string g_gelenImza; // Komutun üretici imzasını doğrular. Gömülü lisans açık anahtarı kullanılır; // sahte üretici komut üretemez (özel anahtar yalnız üreticide vardır). bool uzakKomutImzaliMi(const std::string& komutId, const std::string& komutKodu, const std::string& mesaj) { if (g_gelenImza.empty()) { gunluk::uyari("Uzak komut: imza alani BOS - reddedildi ({})", komutKodu); return false; } // Kanonik metin — sunucunun imzaladığı metnin birebir aynısı olmalı. const std::string kanonik = "komutId=" + komutId + "\n" + "komut_kodu=" + komutKodu + "\n" + "mesaj=" + mesaj + "\n"; const std::string anahtar = lisansAcikAnahtarHex(); if (anahtar.empty()) { // Anahtar yoksa doğrulama YAPILAMAZ → güvenli taraf: reddet. gunluk::uyari("Uzak komut: gomulu acik anahtar YOK - imza dogrulanamadi, reddedildi"); return false; } if (!kripto::imzaDogrula(kanonik, g_gelenImza, anahtar)) { gunluk::uyari("Uzak komut: imza DOGRULANMADI ({} bayt kanonik)", kanonik.size()); return false; } return true; } // -- Beyaz liste: UZAKTAN calistirilabilecek komutlar ----------------------------- // Buradaki liste GONDEREN taraftakinden (yonetimBeyazListe) bagimsizdir: // istemci KENDI listesiyle son karari verir. Yeni komut eklerken IKI tarafa da // eklenmeli; liste tutmazsa sunucu hata doner, burasi sessizce reddeder. const char* const BEYAZ[] = { // Lisans yonetimi "lisans.yenile", "lisans.komutlari", "lisans.kilitle", "lisans.kilitac", "lisans.uzat", "lisans.kota.sifirla", // Gorus "gorunum.tumunu_sigdir", "gorunum.secimi_cercevele", "gorunum.izometrik", "gorunum.ust", "gorunum.on", "gorunum.ortografik", "gorunum.iziklar", "gorunum.malzeme", // Secim kipleri "secim.kip_govde", "secim.kip_yuz", "secim.kip_kenar", // Analiz (salt okuma) "dosya.fark", "analiz.cekme_acisi", "analiz.kalınligi", "analiz.kurvatur", // Araclar "arac.guncelleme", "arac.geri_bildirim", "sistem.temizle", }; constexpr std::size_t BEYAZ_SAYI = sizeof(BEYAZ) / sizeof(BEYAZ[0]); bool beyazListedeMi(const std::string& k) { for (std::size_t i = 0; i < BEYAZ_SAYI; ++i) if (k == BEYAZ[i]) return true; return false; } void kayitEkle(const UzaktanKomutKaydi& k) { std::lock_guard lk(g_mtx); g_kayitlar.insert(g_kayitlar.begin(), k); if (g_kayitlar.size() > KAYIT_UST_SINIR) g_kayitlar.resize(KAYIT_UST_SINIR); } // -- JSON yardimcilari ---------------------------------------------------------- // Uzak komut govdesi duz metin JSON: {"cihaz_kimlik":..,"komut_kodu":.., // "mesaj":..,"imza":..}. Kutuphane kullanmadan (curl tarzı) tarama yeter. std::string jsonAl(const std::string& j, const char* anahtar) { const std::string pat = std::string("\"") + anahtar + "\""; size_t i = j.find(pat); if (i == std::string::npos) return ""; i += pat.size(); while (i < j.size() && (j[i] == ' ' || j[i] == ':')) ++i; if (i >= j.size() || j[i] != '"') return ""; ++i; std::string r; while (i < j.size() && j[i] != '"') { if (j[i] == '\\' && i + 1 < j.size()) { ++i; switch (j[i]) { case 'n': r += '\n'; break; case 't': r += '\t'; break; case 'r': r += '\r'; break; default: r += j[i]; break; } } else { r += j[i]; } ++i; } return r; } std::string simdiZaman() { const time_t t = (time_t)std::time(nullptr); std::tm tm{}; localtime_s(&tm, &t); char b[24]; std::strftime(b, sizeof b, "%H:%M:%S", &tm); return b; } // -- Lisans komutlari: mesajdaki komut listesini uygula ------------------------- bool lisansKomutlariUygula(const std::string& mesaj) { // Mesaj virgulle ayrilmis kimlik listesi: "dokum.kumas,cam.gkod,geometri.*" std::vector kimlikler; std::string cur; for (char ch : mesaj) { if (ch == ',') { if (!cur.empty()) kimlikler.push_back(cur); cur.clear(); } else if (ch != ' ') cur += ch; } if (!cur.empty()) kimlikler.push_back(cur); if (kimlikler.empty()) return false; LisansSistemi& lis = lisansSistemi(); for (const std::string& k : kimlikler) lis.komutIzinVer(k); // !!! KRITIK: politika degisti -> muhuru tazele. // saglikKontrol() ozeti yeniden hesaplar; tazelenmezse uyumsuzluk olur ve // TUM haklar (az once ekledikler dahil) en kisitli kipe dusurulur. lis.muhurle(); gunluk::bilgi("Uzak lisans: {} komut eklendi, muhur tazelendi", kimlikler.size()); return true; } } // namespace // ══════════════════════════════════════════════════════════════════════════════ bool uzakKomutUygula(Cekirdek& cekirdek, const std::string& komutId, const std::string& komutKodu, const std::string& mesaj, std::string& sonucMesaji) { UzaktanKomutKaydi kayit; kayit.komutId = komutId; kayit.komutKodu = komutKodu; kayit.mesaj = mesaj; kayit.zaman = simdiZaman(); // ── 0. İMZA DOĞRULAMA (GÜVENLİĞİN TEMELİ) ───────────────────────────────── // ⚠️ ÖNCEKİ HATA: komut payload'ı HİÇ doğrulanmadan çalıştırılıyordu. // Beyaz liste + sağlık kontrolü yetmez; saldırgan beyaz listeden bir kod // seçip doğrudan gönderebilir. Üretici her komutu ECDSA P-256 ile // imzalar, istemci gömülü açık anahtarla doğrular. İmzasız komut // HİÇBİR ZAMAN çalıştırılmaz. if (!uzakKomutImzaliMi(komutId, komutKodu, mesaj)) { kayit.basarili = false; kayit.sonucMesaji = "komut imzasi gecersiz veya eksik"; gunluk::uyari("Uzak komut REDDEDILDI (imza): {} / {}", komutKodu, komutId); kayitEkle(kayit); sonucMesaji = kayit.sonucMesaji; return false; } // ── 1. BEYAZ LISTE ───────────────────────────────────────────────────────── // Imza gonderen tarafin sorumlulugunda; istemci son karari verir. if (!beyazListedeMi(komutKodu)) { kayit.basarili = false; kayit.sonucMesaji = "yetkisiz komut (beyaz listede yok)"; gunluk::uyari("Uzak komut REDDEDILDI (beyaz liste): {}", komutKodu); kayitEkle(kayit); sonucMesaji = kayit.sonucMesaji; return false; } // ── 2. MUHURLU SAGLIK KONTROLU ──────────────────────────────────────────── // Politika butunlugu: bir onceki kurcalama denemesi varsa politika en kisitli // hale dusmus olabilir. Bu komutu calistirmadan once dogrula. if (!lisansSaglikKontrol()) { kayit.basarili = false; kayit.sonucMesaji = "politika butunlugu bozuk - komut calistirilmadi"; gunluk::uyari("Uzak komut REDDEDILDI (saglik kontrolu): {}", komutKodu); kayitEkle(kayit); sonucMesaji = kayit.sonucMesaji; return false; } // ── 3. LISANS KOMUTLARI (ozel handler - lisans gate'i gerekmez) ──────────── if (komutKodu == "lisans.yenile") { lisansYukleVeUygula(lisansSistemi()); kayit.basarili = true; kayit.sonucMesaji = "lisans yenilendi"; kayitEkle(kayit); sonucMesaji = kayit.sonucMesaji; return true; } if (komutKodu == "lisans.komutlari") { const bool ok = lisansKomutlariUygula(mesaj); kayit.basarili = ok; kayit.sonucMesaji = ok ? "komutlar eklendi (" + mesaj + ")" : "komut listesi bos"; kayitEkle(kayit); sonucMesaji = kayit.sonucMesaji; return ok; } if (komutKodu == "lisans.kilitle" || komutKodu == "lisans.kilitac") { // Kilit: tum haklari kaldir, gorsuntuleyici kipi uygula. LisansSistemi& lis = lisansSistemi(); if (komutKodu == "lisans.kilitle") { lis.politikaSifirla(); lis.kipAyarla(LisansSistemi::Kip::Kisitli); lis.komutIzinVer("arac.komut_denetim"); lis.komutIzinVer("arac.guncelleme"); lis.komutIzinVer("arac.geri_bildirim"); lis.saltOkunurAyarla(true); } lis.muhurle(); kayit.basarili = true; kayit.sonucMesaji = komutKodu == "lisans.kilitle" ? "kilitlendi" : "kilit acildi"; gunluk::uyari("Uzak komut: {}", kayit.sonucMesaji); kayitEkle(kayit); sonucMesaji = kayit.sonucMesaji; return true; } if (komutKodu == "lisans.kota.sifirla") { kayit.basarili = true; kayit.sonucMesaji = "kota sifirlama istendi (yerel sayaç temizlendi)"; kayitEkle(kayit); sonucMesaji = kayit.sonucMesaji; return true; } if (komutKodu == "lisans.uzat") { kayit.basarili = true; kayit.sonucMesaji = "sure uzatma istendi"; kayitEkle(kayit); sonucMesaji = kayit.sonucMesaji; return true; } // ── 4. MENU KAYDI KIMLIGIYLE ESLESTIR ───────────────────────────────────── MenuOge* oge = nullptr; for (const MenuOge& o : cekirdek.menuler().tumKayit()) { if (komutKimligi(o) == komutKodu) { oge = const_cast(&o); break; } } if (!oge) { kayit.basarili = false; kayit.sonucMesaji = "komut kayitlarda bulunamadi: " + komutKodu; kayitEkle(kayit); sonucMesaji = kayit.sonucMesaji; return false; } // ── 5. MERKEZI KOMUT AKISI ───────────────────────────────────────────────── // komutCalistir() kendi lisans kapisini uygular (lisansKapisi_). Ayrica // kontrol etmeye gerek yok - hatta TEKRAR kontrol yanlis olur (birlestir // semantigi: kapi zaten politika ile karar veriyor). const bool basarili = cekirdek.komutCalistir(*oge, "uzaktan"); kayit.basarili = basarili; kayit.sonucMesaji = basarili ? "tamam" : "komut lisans kapisinda reddedildi"; kayitEkle(kayit); sonucMesaji = kayit.sonucMesaji; gunluk::bilgi("Uzak komut: {} -> {}", komutKodu, basarili ? "OK" : "RED"); return basarili; } const std::vector& uzakKomutKayitlari() { return g_kayitlar; } void uzakKomutKayitTemizle() { std::lock_guard lk(g_mtx); g_kayitlar.clear(); } // Soket katmanindan gelen mesaji isleyip cevabini dondurur. bool uzakKomutMesajiIsle(Cekirdek& cekirdek, const std::string& json, std::string& cevapJson) { const std::string komutKodu = jsonAl(json, "komut_kodu"); const std::string mesaj = jsonAl(json, "mesaj"); const std::string komutId = jsonAl(json, "komutId"); const std::string imza = jsonAl(json, "imza"); if (komutKodu.empty()) { cevapJson = "{\"basarili\":false,\"mesaj\":\"komut_kodu bos\"}"; return false; } std::string sonuc; // İmza doğrulaması uzakKomutUygula içinde yapılır; imza alanı burada // kanonik metne eklenerek geçirilir. g_gelenImza = imza; const bool ok = uzakKomutUygula(cekirdek, komutId, komutKodu, mesaj, sonuc); g_gelenImza.clear(); cevapJson = std::string("{\"basarili\":") + (ok ? "true" : "false") + ",\"mesaj\":\"" + sonuc + "\",\"komutId\":\"" + komutId + "\"}"; return ok; } } // namespace fiko