﻿# FikoCAD Lisans Sistemi - Guvenlik Denetim Raporu

**Tarih:** 28.09.2026
**Kapsam:** `motor/cekirdek/{lisans.hpp, lisans_dosya.cpp, lisans_sayac.cpp, lisans_uzak.cpp, kripto.cpp, makine_kimlik.cpp}`, `uygulama/fikocad/{lisans_yukle.cpp, lisans_canli.cpp, lisans_uzak.cpp, lisans_uretici_penceresi.cpp}`, `uygulama/fiko_lisans/main.cpp`, `sunucu/lisans-canli/server.js`
**Yontem:** Statik kod okumasi - herhangi bir degisiklik yapilmamistir.

---

## Genel Degerlendirme

Sistem **kriptografik olarak saglam** bir temel uzerine kurulu. Lisans dosyasi ECDSA P-256 ile imzalaniyor, ozel anahtar depoda yok (git taramasi temiz), kriptografi Windows CNG'ye birakilmis, kanal boyunca "sessiz kabul" yok. Ama koruma modeli **yanlis yerde calisiyor**: agirligi `lisansCoz()` imza kontrolunde, oysa asil kazanc `degerlendir()` fonksiyonunda. Muhur (seal) mekanizmasi iyi niyetli ama kolay atlatiliyor.

---

## KRITIK 1 - Uretici Makinesi Tespiti: Tam Lisanssiz Erisim

`uygulama/fikocad/lisans_yukle.cpp:76-81` ve `:411-415`

```cpp
bool lisansUreticiMakinesiMi() {
  const char* profil = std::getenv("USERPROFILE");
  if (!profil || !*profil) return false;
  return fs::is_regular_file(fs::path(profil) / ".fikoyt" / "lisans_ozel.key", ec);
}
```

```cpp
if (g_oturum.ureticiTamErisim) {
    sis.politikaSifirla();          // TUM haklar temizleniyor
    gunluk::bilgi("Uretici makinesi algilandi - lisans politikasi uygulanmadi");
}
```

**Bu, sistemin tamamini atlatir.** Tek kosul: `%USERPROFILE%\.fikoyt\lisans_ozel.key` adli bir dosyanin **var olmasi**. Dosyanin ici kontrol edilmiyor, uzunlugu dogrulanmiyor, imzasi yok.

Asma yolu:

```batch
mkdir "%USERPROFILE%\.fikoyt"
type nul > "%USERPROFILE%\.fikoyt\lisans_ozel.key"
fikocad.exe
```

Sonuc: `politikaSifirla()` -> `kip_ = Serbest` -> tum komutlar acik, `saltOkunur_ = false`, sure kontrolu yok, node-lock yok, kill-switch muaf. **Sifir dosya, sifir imza bilgisi gerekiyor.**

Ayrisa `belge_penceresi.cpp:612` - `FIKO_URETICI` ortam degiskeni de `ureticiTamErisim`'i acIYOR. `FIKO_URETICI=1 fikocad.exe` yeterli.

**Oneri:** Dosya icerigini gercekten dogrula - en azindan 192 hex karakterlik gecerli bir P-256 ozel anahtar olup olmadigini parse et. Daha iyisi: ozel anahtarin public kismiyla imzalanmis bir "uretici sertifikasi" iste.

---

## KRITIK 2 - Zarf Anahtari Sabit ve Dagitilmis exe Icerinde

`motor/cekirdek/src/lisans_dosya.cpp:62`

```cpp
constexpr const char* ZARF_GIZ = "FikoCAD.lisans.zarf.v1/2026#akcali";
```

Kod yorumu bunu durustce kabul ediyor ("BU GIZ SIR DEGILDIR") ve **guvenlik acisindan hakli** - guvenlik imzadan geliyor. Ama:

1. Anahtar `lisans_dosya.cpp` icinde herkese acik sabit. Musteri `strings fikocad.exe` ile AES anahtarini cikarir, `.fkl` dosyasini 3 satir Python ile acar.
2. "Gizlilik katmani" etiketiyle sunulan ozelligin **sifir gizlilik** saglamasi.
3. Musteri `makine=` hash'lerini, tum komut/kota listesini gorebilir.

### Kota sayaci da ayni sebepten kirilabilir

`lisans_sayac.cpp:30-37` sayaç satirlarini "HMAC"liyor ama:

```cpp
const std::string tuz = "fiko.sayac.v1|" + p.kurulum + p.cpu + "|" + anahtar + "|" +
                        std::to_string(pencere) + "|" + std::to_string(sayi);
u8 d[32];
if (!kripto::sha256(tuz.data(), tuz.size(), d)) return "";
return kripto::baytHex(d, 32).substr(0, 16);
```

Sorunlar:
- `HMAC` degil, duz `sha256` (prefix/suffix direnci yok)
- "Gizli" anahtar yok: tuz tamamen kaynak kodda acik
- `lisans_sayac.dat` elle duzenlenip `sayi` sifirlanabilir, deger `makine_kimlik.cpp` ile birebir yeniden uretilir
- `pencere` degeri de eski bir zamana cekilebilir

**Oneri:** Uygulama sırrı (per-build rastgele, imzayla birlikte) karıştır. Kota sayaci zaten cihaz geneli - makine parmak izisiyle baglamak ek guvenlik saglamiyor, sadece kolay kirilir olmasini sagliyor. Zarf icin: anahtar uretici makinesinden turetilip kurulum ile tasiinsa gercek gizlilik saglanir, aksi halde zarf katmanini kaldirmak daha dogru (mevcut tasarimin guvenligi imzadan geliyor).

---

## YUKSEK 3 - Muhur (Seal) Kolay Atlatiliyor

`motor/cekirdek/include/fiko/cekirdek/lisans.hpp:389-411`

```cpp
u64 politikaOzeti() const {
  u64 h = 1469598103934665603ull;            // FNV-1a offset
  for (char c : kip_ == Kip::Serbest ? 'S' : 'K') { h ^= (u8)c; h *= 1099511628211ull; }
  auto kar = [&](const std::string& s) {
    for (char c : s) { h ^= (u8)c; h *= 1099511628211ull; }
    h ^= 0x5F; h *= 1099511628211ull;
  };
  kar(gecerlilikBitis_); kar(makineHash_);
  for (const auto& k : izinliKomutlar_) kar(k.anahtar + "|" + k.hakAdi);
  // ...
}
```

`muhurGecerlMi()` **kendi kendini dogrulayan bir ozettir**: hash ayni bellegindeki veriden yeniden hesaplanip karsilastiriliyor.

Uc atlatma yolu:

1. **Kara kutu (boxing) - asil zafiyet.** `muhur_` ve `gecerlilikBitis_` `LisansSistemi` nesnesinin belleginde. Saldirgan `g_lisans` sembolunu bulup `muhurle()` cagirir (tek arguman, yan etkisiz), veya `saglikKontrol()` icindeki `if`i NOP'lar. `LisansSistemi` header-only (`lisans.hpp`) - `muhurle()` **cagrildigi yerde inline**. Saldirganin derledigi inline kopyayi degistirmesi yeterli.

2. **Politika genisletme + yeniden muhurleme.** `saglikKontrol()` (`:392-411`) yaklasik 1.5 saniyede bir kontrol ediyor. Aradaki pencerede saldirgan `izinliKomutlar_` set'ine `"*"` enjekte eder, isini yapar, sonra `muhurle()` cagirir. CAM ciktisi gibi uzun islerde rahat.

3. **Yan etkisizlik sayesinde kolay.** `lisansSaglikKontrol()` 4 yerde cagriliyor (`dosya_islemleri.cpp:549, 800, 1068` + `lisans_yukle.cpp:274`) ama `saglikKontrol()` **void** donuyor - sonucu derleyici Release derlemede tamamen silebilir.

Ayrica `saltOkunur_` alani ozete **hic dahil degil**.

**Oneri:**
- `muhurle()`/`saglikKontrol()`'u `lisans.cpp`'ye tasi (inline olmasin), `__declspec(noinline)`
- Kontrolu 1.5 sn yerine **her komut calistirmada** (veya `kapiKarari()` icinde) yap
- `saglikKontrol()`'u `bool` dondur, cagri noktalarinda **kullanilmasini zorunlu kil**
- `saltOkunur_` ve `muhur_`'u ozete dahil et
- Gercek dogrulama: muhuru **imzali bir blob**'a cevir - lisans yuklenirken `politikaOzeti()` imzali dosyaya yazilir, `saglikKontrol()` bellegindeki ozetle karsilastirir

---

## YUKSEK 4 - Node-Lock `makineTolerans=3` Varsayilani Tutarsiz

`lisans_dosya.hpp:64` / `lisans_dosya.cpp:317-330` / `makine_kimlik.cpp:86-91`

```cpp
int makineTolerans = 3;   // varsayilan - ama sadece 4 alan uretiliyor
```

**Sorun 1 - varsayilan imkansiz.** 4 alan uretiliyor (`kurulum`, `disk`, `cpu`, `bios`). 4 `makine=` satiri yoksa node-lock **her zaman basarisiz** olur. Uretici varsayilani anlamiyor olabilir; lisans denerken "makine uyusmuyor" gorur ve nedenini anlamaz.

**Sorun 2 - guvenlik acigi yonu.** Tolerans 3, 4 alandan 3'u eslesmesi yeterli. `bios` alani (`SystemManufacturer|SystemProductName|BaseBoardProduct` - anakart bilgisi) **cok sayida ayni model bilgisayarda aynidir**. `kurulum` disarida kalsaydi bile pratikte cok sayida makinede gecerli.

**Sorun 3 - kismi eslesmenin sifirlanmasi.** `eslesen` her yuklemeden bastan hesaplaniyor, kalici degil. Lisans "3/4 kalici" kesildiyse sonraki acilista 2 kalir ve lisans duser - toleransli node-lock'un **amacini** (destek yukunu azaltmak) boşa cikarir.

**Oneri:** `makineTolerans` alanina `>= 1` zorunlulugu + `= alan sayisi - 1` gibi tavan koy. En az bir **tekil** alan (MachineGuid) zorunlu eslesin, tolerans bundan bagimsiz sayilsin.

---

## YUKSEK 5 - Saat Geri Alma Korumasi Asilabilir

`lisans_yukle.cpp:133-142`

```cpp
constexpr i64 SAAT_TOLERANS = 86400;              // 1 gun
constexpr const char* SON_ZAMAN_ANAHTAR = "lisans_son_zaman";

bool saatGeriAlinmisMi(i64 suan) {
  const std::string kayit = ayarOku(SON_ZAMAN_ANAHTAR, "");
  const i64 sonGorulen = kayit.empty() ? 0 : std::strtoll(kayit.c_str(), nullptr, 10);
  const bool geri = sonGorulen != 0 && suan + SAAT_TOLERANS < sonGorulen;
  if (suan > sonGorulen) ayarYaz(SON_ZAMAN_ANAHTAR, std::to_string(suan));
  return geri;
}
```

Damga `ayar_kalici.cpp` uzerinden **exe yanindaki `fikocad.ayar`** dosyasina duz metin yaziliyor.

**Asma yollari (kolaydan zora):**
1. `fikocad.ayar` dosyasindaki **tek satiri sil** -> `sonGorulen = 0` -> `geri = false` -> saat kontrolu **hic calismaz**
2. Degeri elle kucult (1 gun tolerans dahilinde)
3. VM snapshot / dosya geri yukleme

Ayrica yalniz `bilgi.bitis != 0` (sureli) lisanslari etkiliyor - **suresiz lisansta kota icin hicbir zaman tabani yok.**

**Oneri:** Damgayi imzala veya `sha256(exe_path + machine_kurulum + zaman)` ile karistir; `%ProgramData%\FikoCAD` altina da yaz; toleransi 1 gunden kucult (NTP kaymasi 5-10 dakikayi gecmez); en etkilisi: canli kanal varsa sunucu saatini esas al.

---

## ORTA 6 - Coklu Lisans Birlestirme Kurallari Celiskili

`lisans_dosya.cpp:289-312`

```cpp
if (!birlestir || !l.kisitli)
  sis.kipAyarla(l.kisitli ? Kip::Kisitli : Kip::Serbest);
```

Iki lisans senaryosunda sonuc **siraya gore degisiyor** - deterministik olmayan bir lisans anlami. Ayrica sure birlestirmesinde `l.bitis == 0` (suresiz) ozel durum yazilmis, `max()` mantigiyla karismis - kirilgan.

**Oneri:** Birlestirme semantigini acikca tanimla ve dokumante et: "suresiz lisans varsa tum sure kisiti duser mi, yoksa paket basina mi?" `test_lisans_dosya.cpp`'de coklu lisans testi kontrol edilmeli.

---

## ORTA 7 - "Baglanti ZORUNLU" Musteri Tarafindan Kapatilabilir

`lisans_canli.cpp:694-704`

```cpp
bool uzakCanliZorunluIhlalMi() {
  const bool zorunlu = ayarOku("lisans_canli_zorunlu", "0") == "1";
  if (!zorunlu) return false;
```

Bu ayar **Komut Denetim panelinden musteri tarafindan degistirilebilir** (`uzakCanliAyarBolumu`, `:758`). Varsayilan `"0"` = kapali.

**Asma yolu:** musteri `fikocad.ayar`'a `lisans_canli_zorunlu=0` yazar -> aninda engelleme ozelligi tamamen devre disi kalir. "Odemesi geciken musteriyi 5 dakika icinde kilitle" senaryosu, musteri bir ayari sifirlayarak engellenir.

Bu kabul edilebilir bir trade-off olabilir ama ad yaniltici - kullanici bunun koruma oldugunu saniyor.

**Oneri:** Bu ayari musteriye gosterme. `FIKO_URETICI_UI` veya derleme-time flag ile yalniz dagitici/satici surumunde acilabilir olsun. Panelde salt-okuma "durum" goster, degistirme dugmesi sunma.

---

## ORTA 8 - Sunucu Tarafi Sorunlari

`server.js` - **Guvenlik modeli dogru**: sunucu imza dogrulamaz, sadece relay yapar; istemci gomulu acik anahtarla dogrular. Sunucu ele gecirilse bile sahte karar uretilemez.

Sorunlar:

1. **Rate limiting yok** - `/admin/karar` ve `/admin/clients` uclarinda limit yok.
2. **`lastDecision` Map'i sinirsiz buyuyor** (`:122`) - hicbir temizleme/yaslandirma yok. Binlerce seri -> bellek sizintisi. **Kalici blacklist etkisi:** eski bir "engelli" karari sunucuda kalici kalir, musteri odeme yaptiktan sonra bile yeni karar gonderilene kadar engelli kalir (`:72` yeni baglanan istemciye `lastDecision`'i hemen gonderir).
3. **Baglanti siniri yok** - `nsp.on('connection')` her baglantiyi kabul ediyor.
4. **`ADMIN_TOKEN` duz string karsilastirma** (`:95`) - `timingSafeEqual` kullanilmali.
5. **TLS varsayilan degil** - istemci `https://fikoyt.com:9000/...` kullaniyor ama nginx onunde mi belirsiz.

---

## ORTA 9 - Lisans Arama Yollari - Ortam Degiskeni Enjeksiyonu

`lisans_yukle.cpp:297-310`

```cpp
if (const char* env = std::getenv("FIKO_LISANS")) {
  if (*env) yollar.push_back(env);
```

`FIKO_LISANS` tamamen kullanici kontrolunde. Iki sonuc:
- Kullanici ayni makinede iki fikocad farkli lisanslarla acabilir (`lisans_sayac` cihaz geneli oldugu icin **kotalar paylasilir** - iyi)
- Ama **node-lock ve sure kontrolu dosya basina**: iki lisans = iki bagimsiz sure kontrolu
- Kullanici `fikocad.ayar`'dan `lisans_son_zaman` satirini silip sure kontrolunu surekli atlayabilir

**Oneri:** `FIKO_LISANS`'i gelistirici moduna bagla (`#ifdef` veya `lisansUreticiMakinesiMi()`), musteri derlemesinde tamamen kaldir.

---

## ORTA 10 - Eksik Zorunlu Alanlar - Belirsizlik Kaynagi

1. **`bitis` zorunlu degil** (0 = suresiz). Uretici yanlislikla `0` yazarsa **sinirsiz lisans** uretilir ve UI "suresiz" gosterir - fark edilmez. En tehlikeli belirsizliklerden biri.

2. **`UrunFarkli` olu kod** (`lisans_dosya.hpp:78`) - bu durum **hicbir yerde uretilmiyor**. `urun` alani karsilastirilmiyor. Ya olu kod ya eksik uygulama.

3. **`seviye` alani** ayristiriliyor ama `lisansUygula`'da hic gecmiyor.

4. **`kisitli` varsayilani `true`** ama `!l.kisitli` durumunda kip yazilmiyor - yani `kisitli=0` yazmak ile hic yazmamak **ayni sonucu** veriyor. Ince ama kafa karistirici.

---

## UI INCELEMESI

### Uretici UI (`lisans_uretici_penceresi.cpp`)

**Iyi taraflar:**
- Zorunlu alan eksikse dugme pasif + kirmizi uyari (`:1280-1288`)
- Uretim sonrasi **diskten geri okuma + dogrulama** (`:1113-1136`)
- `duzMetin` varsayilan **kapali**; acikken "⚠ okunabilir" uyarisi (`:1274-1277`)
- Yeni anahtar uretirken "ESKI LISANSLAR GEÇERSIZ OLUR" uyarisi (`:1259`)

**Sorunlar:**
1. **Gecmis/cok uzak tarih dogrulamasi yok.** `eksikAlan(d)` sadece bos alanlari kontrol ediyor. 2 yil once veya 2050 tarihi kontrol edilmiyor - lisans hemen gecersiz olur veya 24 yil gecerli olur. Uretim oncesi `gecerliMi()` kontrolu sart.
2. **`enUzunHex` (`:79-92`)** anahtar dosyasindan hex ayikliyor - kirilgan. Format sabitlenmeli.
3. **`kalanGun()` 32-bit `int` donuyor** - pratikte sorun degil ama teknik borc.
4. **`makineTolerans` ayarlanamiyor** - varsayilan 3 kaliyor (Yuksek 4'e bak).

### Musteri Tarafi UI (`lisans_yukle.cpp` + Komut Denetim paneli)

**Iyi taraflar:**
- `lisansBanneriCiz()` her karede, kosulsuz lisans durumunu gosteriyor
- Toast throttle'i 1.5 sn
- Makine kimligi 3 kanaldan (pano/dosya/e-posta) ayni metinle paylasilabiliyor
- Goruntuleyici kipinde bile Komut Denetim'e erisim var

**Sorun:**
Lisans yuklenmeden once kullanici **hiçbir geri bildirim almiyor**. Yukleme basarisiz olursa `goruntuleyiciKipiUygula` calisiyor ama hangi komutlarin neden kilitli oldugu ancak komuta tiklayinca toast ile belli oluyor. Proaktif "Lisans yuklenemedi - Simdi yukle" CTA eksik.

---

## IYI YAPILMIS KISIMLAR (Bunlari Koruyun)

1. **Kriptografi CNG'ye birakilmis** - `kripto.cpp` Windows BCrypt kullaniyor, elle AES/ECDSA yazmamis. Non-Windows'ta **her sey `false` donuyor** (`:299-308`) - sessiz kabul yok.

2. **Kanonik govde normalizasyonu** (`lisans_dosya.cpp:39-52`) - BOM atilir, satirlar kirpilir, yorumlar duser, `imza=` haric tutulur. Musteri Notepad'de acip kaydedince imza bozulmaz. **Cok iyi tasarim** ve test edilmis (`test_lisans_dosya.cpp:99`).

3. **Kirilamayan girdi testleri** - `test_lisans_dosya.cpp:129` "bozuk/kesik/cop lisans dosyasi ÇÖKERTMEMELI".

4. **Dagitilmis exe'de ozel anahtar yok** - git taramasi temiz, `LISANS_ACIK_ANAHTAR_HEX` gomulu ama sadece acik anahtar.

5. **Gomulu anahtar boslsa lisans dogrulanamaz** (`:359-363`) - sessiz bypass yerine gunluk + panel uyarisi.

6. **Kill-switch imza modeli** - `canliKararCoz` gomulu acik anahtarla dogruluyor, MITM karar enjekte edemez.

7. **Bagimsiz kontrol noktalari** - `lisansSaglikKontrol()` 4 farkli dosya yolundan cagriliyor (NOP'lanirsa digerleri yakalar).

8. **Sure doldugunda durust davranis** - `LisansDurum::SuresiDolmus` ayri durum, `kalanGun()` UI'da. Kotuye kullanim yok, seffaflik var.

---

## ONCELIKLENDIRILMIS AKSIYON LISTESI

| # | Oncelik | Aksiyon | Satir |
|---|---------|---------|-------|
| 1 | KRITIK | `ureticiTamErisim` kontrolunu dosya **icerigine** dayandir (P-256 ozel anahtar parse et) | `lisans_yukle.cpp:76-81` |
| 2 | KRITIK | `FIKO_URETICI` ortam degiskenini musteri derlemesinden tamamen kaldir | `belge_penceresi.cpp:612` |
| 3 | KRITIK | Kota sayaci HMAC'ina uygulama sirri karistir (mevcut HMAC kirilabilir) | `lisans_sayac.cpp:30-37` |
| 4 | YUKSEK | `muhurle`/`saglikKontrol`'u `lisans.cpp`'ye tasi, `noinline`, her komutta kontrol et | `lisans.hpp:389-411` |
| 5 | YUKSEK | Saat damgasini duz metin yerine imzali/sifreli kaydet, toleransi dusur | `lisans_yukle.cpp:133-142` |
| 6 | YUKSEK | `makineTolerans` icin tavan + tekil alan zorunlulugu ekle | `lisans_dosya.hpp:64` |
| 7 | YUKSEK | Coklu lisans birlestirme semantigini netlestir + test ekle | `lisans_dosya.cpp:289-312` |
| 8 | ORTA | "Baglanti ZORUNLU" ayarini musteriye gizle, panelde salt-okuma goster | `lisans_canli.cpp:758` |
| 9 | ORTA | Uretici UI'da gecmis/cok uzak tarih dogrulamasi ekle | `lisans_uretici_penceresi.cpp:1069` |
| 10 | ORTA | Sunucuya `lastDecision` TTL + rate limiting + `timingSafeEqual` | `server.js:116-141` |
| 11 | ORTA | `FIKO_LISANS` ortam degiskenini gelistirici moduna bagla | `lisans_yukle.cpp:299` |
| 12 | ORTA | `UrunFarkli` olu kodunu ya uygula ya sil; `seviye` alanini netlestir | `lisans_dosya.hpp:78` |
| 13 | ORTA | Lisans yukleme basarisizsa proaktif CTA goster | `belge_penceresi.cpp:4539` |
| 14 | DUSUK | Anahtar dosya formatini sabitle (hex ayiklama yerine net parse) | `lisans_uretici_penceresi.cpp:79-92` |

---

## SONUC - "Lisans Bittiğinde Kullanici Ne Yapabilir?"

**Meşru yollar (tasarım gereği açık):**
- Yeni lisans dosyasi alip `lisans.fkl` yerine koyup "Yeniden Yukle" -> yeni haklar
- Goruntuleyici kipinde bile Komut Denetim'e girip makine kimligini paylasabilir, yeni lisans talep edebilir
- Coklu lisans: yeni paket lisansini ekleyip hak birlestirme

**Gerçekçi aşma yolları (kolaydan zora):**

1. `%USERPROFILE%\.fikoyt\lisans_ozel.key` olustur -> **tam erisim, imza/karmasiklik yok** ← en buyuk risk
2. `fikocad.ayar`'dan `lisans_son_zaman` satirini sil -> saat korumasi kalici devre disi
3. Kota sayacini duzenle + sha256'i yeniden hesapla -> kota sifirlanir
4. `muhurle()`'yi cagir veya `saglikKontrol`'u NOP'la -> politika muhur korumasi duser
5. `.fkl` zarfini coz, icerigi incele (gizlilik ihlali, dogrudan asma degil)

**Ozet:** Kriptografik katman saglam, ama 3 yerel kontrol (uretici tespiti, saat damgasi, kota sayaci) elle atlatilabilir. #1 numarali bulgu tek satir komutla tum sistemi bypass ediyor - buna ilk mudahale edilmeli. Ikinci sirada muhurun calisma zamani yapisi ve kota sayacinin kriptografik gucu geliyor.
