#include "guncelleme_indir.hpp" #include #include #include #include #include #include #include #include "fiko/cekirdek/cekirdek.hpp" #include "fiko/cekirdek/guncelleme.hpp" #define WIN32_LEAN_AND_MEAN #include #include #include namespace fiko { namespace { #ifndef STATUS_SUCCESS #define STATUS_SUCCESS ((NTSTATUS)0x00000000L) #endif // ── Sürüm paketi imza doğrulama açık anahtarı (ECDSA P-256, X||Y = 64 bayt hex) ── // BOŞ → imza doğrulama ATLANIR (yalnız sha256; yalnız GELİŞTİRME). Üretimde build // sunucusunun özel anahtarıyla eşleşen açık anahtar buraya gömülür → imza ZORUNLU. constexpr const char* IMZA_ACIK_ANAHTAR_HEX = "ac449d4d3b73d71c5b3351a2165460dfafdf1af4c0d69d61f7c31cc4d9617dfd" "a0ce1da26d5615d72d32777c5b3d6321059cbf7096b5ec1c9551e385651f0233"; // hex → bayt. n bayt beklenir (yol uzunluğu 2n). Başarısızsa false. bool hexBayt(const char* hex, unsigned char* out, size_t n) { auto d = [](char c) -> int { if (c >= '0' && c <= '9') return c - '0'; if (c >= 'a' && c <= 'f') return c - 'a' + 10; if (c >= 'A' && c <= 'F') return c - 'A' + 10; return -1; }; for (size_t i = 0; i < n; ++i) { const int hi = d(hex[2 * i]), lo = d(hex[2 * i + 1]); if (hi < 0 || lo < 0) return false; out[i] = (unsigned char)((hi << 4) | lo); } return true; } // 32 baytı küçük-harf hex dizgeye çevir (sha256 karşılaştırması için). std::string baytHex(const unsigned char* b, size_t n) { static const char* h = "0123456789abcdef"; std::string s; s.reserve(n * 2); for (size_t i = 0; i < n; ++i) { s.push_back(h[b[i] >> 4]); s.push_back(h[b[i] & 0xF]); } return s; } // base64 çöz (imza alanı için). Geçersiz karakter → false. bool base64Coz(const std::string& g, std::vector& out) { auto v = [](unsigned char c) -> int { if (c >= 'A' && c <= 'Z') return c - 'A'; if (c >= 'a' && c <= 'z') return c - 'a' + 26; if (c >= '0' && c <= '9') return c - '0' + 52; if (c == '+') return 62; if (c == '/') return 63; return -1; }; out.clear(); int biriktir = 0, bit = 0; for (char ch : g) { if (ch == '=' || ch == '\n' || ch == '\r' || ch == ' ') continue; const int d = v((unsigned char)ch); if (d < 0) return false; biriktir = (biriktir << 6) | d; bit += 6; if (bit >= 8) { bit -= 8; out.push_back((unsigned char)((biriktir >> bit) & 0xFF)); } } return true; } std::wstring genislet(const std::string& s) { return std::wstring(s.begin(), s.end()); } // Dosyanın sha256'sı (BCrypt akışlı — büyük paket belleğe sığmasa da). out[32]. bool sha256Dosya(const std::wstring& yol, unsigned char out[32]) { BCRYPT_ALG_HANDLE alg = nullptr; if (BCryptOpenAlgorithmProvider(&alg, BCRYPT_SHA256_ALGORITHM, nullptr, 0) != STATUS_SUCCESS) return false; BCRYPT_HASH_HANDLE h = nullptr; bool ok = false; if (BCryptCreateHash(alg, &h, nullptr, 0, nullptr, 0, 0) == STATUS_SUCCESS) { std::ifstream f(yol.c_str(), std::ios::binary); if (f) { std::vector buf(65536); ok = true; while (f) { f.read(buf.data(), (std::streamsize)buf.size()); const std::streamsize n = f.gcount(); if (n > 0 && BCryptHashData(h, (PUCHAR)buf.data(), (ULONG)n, 0) != STATUS_SUCCESS) { ok = false; break; } } if (ok && BCryptFinishHash(h, out, 32, 0) != STATUS_SUCCESS) ok = false; } BCryptDestroyHash(h); } BCryptCloseAlgorithmProvider(alg, 0); return ok; } // ECDSA P-256 imza doğrula (BCrypt). digest[32], sig = ham r||s (64 bayt), pub = X||Y (64). bool ecdsaDogrula(const unsigned char digest[32], const unsigned char* sig, size_t sigLen, const unsigned char pub[64]) { if (sigLen != 64) return false; BCRYPT_ALG_HANDLE alg = nullptr; if (BCryptOpenAlgorithmProvider(&alg, BCRYPT_ECDSA_P256_ALGORITHM, nullptr, 0) != STATUS_SUCCESS) return false; std::vector blob(sizeof(BCRYPT_ECCKEY_BLOB) + 64); auto* hdr = reinterpret_cast(blob.data()); hdr->dwMagic = BCRYPT_ECDSA_PUBLIC_P256_MAGIC; hdr->cbKey = 32; std::memcpy(blob.data() + sizeof(BCRYPT_ECCKEY_BLOB), pub, 64); BCRYPT_KEY_HANDLE key = nullptr; bool ok = false; if (BCryptImportKeyPair(alg, nullptr, BCRYPT_ECCPUBLIC_BLOB, &key, blob.data(), (ULONG)blob.size(), 0) == STATUS_SUCCESS) { ok = BCryptVerifySignature(key, nullptr, (PUCHAR)digest, 32, (PUCHAR)sig, (ULONG)sigLen, 0) == STATUS_SUCCESS; BCryptDestroyKey(key); } BCryptCloseAlgorithmProvider(alg, 0); return ok; } // paketUrl → dosyaya indir (ilerleme + iptal). Content-Length'ten oran hesaplar. bool indirDosya(const std::string& url, const std::wstring& hedef, GuncellemeServisi& svc, GorevBaglam& gb, std::string& hata) { const std::wstring wurl = genislet(url); URL_COMPONENTS uc{}; uc.dwStructSize = sizeof(uc); wchar_t host[256] = {}, yol[4096] = {}; uc.lpszHostName = host; uc.dwHostNameLength = 255; uc.lpszUrlPath = yol; uc.dwUrlPathLength = 4095; if (!WinHttpCrackUrl(wurl.c_str(), 0, 0, &uc)) { hata = "paket URL çözümlenemedi"; return false; } host[uc.dwHostNameLength] = 0; const bool https = uc.nScheme == INTERNET_SCHEME_HTTPS; HINTERNET oturum = WinHttpOpen(L"FikoYT-Guncelleme/1.0", WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); if (!oturum) { hata = "WinHttpOpen"; return false; } WinHttpSetTimeouts(oturum, 10000, 10000, 30000, 30000); HINTERNET baglanti = WinHttpConnect(oturum, host, uc.nPort, 0); HINTERNET istek = baglanti ? WinHttpOpenRequest(baglanti, L"GET", yol, nullptr, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, https ? WINHTTP_FLAG_SECURE : 0) : nullptr; if (istek && https) { // dev self-signed: TLS doğrulama gevşet (paket sha256+imza ile korunur) DWORD bayrak = SECURITY_FLAG_IGNORE_UNKNOWN_CA | SECURITY_FLAG_IGNORE_CERT_CN_INVALID | SECURITY_FLAG_IGNORE_CERT_DATE_INVALID | SECURITY_FLAG_IGNORE_CERT_WRONG_USAGE; WinHttpSetOption(istek, WINHTTP_OPTION_SECURITY_FLAGS, &bayrak, sizeof(bayrak)); } bool ok = false; if (istek && WinHttpSendRequest(istek, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0) && WinHttpReceiveResponse(istek, nullptr)) { DWORD kod = 0, kb = sizeof(kod); WinHttpQueryHeaders(istek, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, WINHTTP_HEADER_NAME_BY_INDEX, &kod, &kb, WINHTTP_NO_HEADER_INDEX); if (kod != 200) { hata = "HTTP durum kodu " + std::to_string(kod); } else { // Content-Length (ilerleme paydası). DWORD toplamLen = 0, tb = sizeof(toplamLen); WinHttpQueryHeaders(istek, WINHTTP_QUERY_CONTENT_LENGTH | WINHTTP_QUERY_FLAG_NUMBER, WINHTTP_HEADER_NAME_BY_INDEX, &toplamLen, &tb, WINHTTP_NO_HEADER_INDEX); std::ofstream f(hedef.c_str(), std::ios::binary | std::ios::trunc); if (!f) { hata = "temp dosya açılamadı"; } else { ok = true; u64 indirilen = 0; DWORD var = 0; do { if (gb.iptalIstendi()) { hata = "iptal edildi"; ok = false; break; } var = 0; if (!WinHttpQueryDataAvailable(istek, &var)) break; if (var == 0) break; std::vector parca(var); DWORD okunan = 0; if (!WinHttpReadData(istek, parca.data(), var, &okunan)) { ok = false; break; } if (okunan == 0) break; f.write(parca.data(), (std::streamsize)okunan); indirilen += okunan; if (toplamLen > 0) { const f32 oran = (f32)((f64)indirilen / (f64)toplamLen); svc.ilerlemeAyarla(oran); // benign f32 yarış (yalnız ilerleme çubuğu) gb.ilerlet(oran); } } while (var > 0); f.close(); } } } else if (hata.empty()) { hata = "paket sunucusuna erişilemedi"; } if (istek) WinHttpCloseHandle(istek); if (baglanti) WinHttpCloseHandle(baglanti); WinHttpCloseHandle(oturum); return ok; } // %TEMP%\fikoyt_guncelleme\ dizinini kur + .zip yolu döndür. std::wstring paketTempYolu(const ManifestBilgi& m) { wchar_t temp[MAX_PATH] = {}; GetTempPathW(MAX_PATH, temp); std::wstring dizin = std::wstring(temp) + L"fikoyt_guncelleme"; CreateDirectoryW(dizin.c_str(), nullptr); wchar_t ad[64]; wsprintfW(ad, L"\\%u.%u.%u.zip", (unsigned)m.surum.buyuk, (unsigned)m.surum.kucuk, (unsigned)m.surum.yama); return dizin + ad; } } // namespace bool guncellemeKurBaslat(GuncellemeServisi& svc) { const std::string zip = svc.indirilenYol(); if (zip.empty()) return false; wchar_t exeW[MAX_PATH] = {}; GetModuleFileNameW(nullptr, exeW, MAX_PATH); std::wstring exe(exeW); const size_t k = exe.find_last_of(L"\\/"); const std::wstring dizin = (k != std::wstring::npos) ? exe.substr(0, k) : L"."; const std::wstring updater = dizin + L"\\fiko_updater.exe"; if (GetFileAttributesW(updater.c_str()) == INVALID_FILE_ATTRIBUTES) { svc.hataAyarla("fiko_updater.exe bulunamadı"); return false; } const std::wstring zipW = genislet(zip); std::wstring cmd = L"\"" + updater + L"\" --zip \"" + zipW + L"\" --hedef \"" + dizin + L"\" --pid " + std::to_wstring(GetCurrentProcessId()) + L" --exe \"" + exe + L"\""; STARTUPINFOW si{}; si.cb = sizeof(si); PROCESS_INFORMATION pi{}; std::vector buf(cmd.begin(), cmd.end()); buf.push_back(0); if (!CreateProcessW(updater.c_str(), buf.data(), nullptr, nullptr, FALSE, CREATE_NEW_CONSOLE, nullptr, dizin.c_str(), &si, &pi)) { svc.hataAyarla("updater başlatılamadı"); return false; } CloseHandle(pi.hThread); CloseHandle(pi.hProcess); return true; } void guncellemeIndirBaslat(GuncellemeServisi& svc) { Cekirdek* c = svc.cekirdek(); if (!c) return; const ManifestBilgi m = svc.mevcut(); // kopya (worker'a değer geçer) if (!m.gecerli || m.paketUrl.empty()) { svc.hataAyarla("paket URL yok (manifest eksik)"); return; } const std::wstring hedef = paketTempYolu(m); svc.ilerlemeAyarla(0.0f); svc.durumAyarla(GuncellemeServisi::Durum::Indiriliyor); struct Sonuc { bool ok = false; std::string hata; std::wstring yol; }; auto sonuc = std::make_shared(); sonuc->yol = hedef; c->gorevler().kuyrukla( "Güncelleme indiriliyor", [sonuc, &svc, m, hedef](GorevBaglam& gb) { std::string hata; if (!indirDosya(m.paketUrl, hedef, svc, gb, hata)) { sonuc->hata = hata; return; } // sha256 doğrula (manifest ile eşleşmeli). unsigned char digest[32]; if (!sha256Dosya(hedef, digest)) { sonuc->hata = "sha256 hesaplanamadı"; return; } if (!m.paketSha256.empty()) { std::string bulunan = baytHex(digest, 32), beklenen = m.paketSha256; for (char& ch : beklenen) ch = (char)tolower((unsigned char)ch); if (bulunan != beklenen) { sonuc->hata = "sha256 uyuşmuyor (paket bozuk/değiştirilmiş)"; return; } } // ECDSA imza doğrula (açık anahtar gömülü; boşsa GELİŞTİRME'de atlanır). const std::string pubHex = IMZA_ACIK_ANAHTAR_HEX; if (!pubHex.empty()) { if (m.paketImza.empty()) { sonuc->hata = "imza yok (paket imzalanmamış)"; return; } unsigned char pub[64]; std::vector sig; if (pubHex.size() < 128 || !hexBayt(pubHex.c_str(), pub, 64)) { sonuc->hata = "gömülü açık anahtar geçersiz"; return; } if (!base64Coz(m.paketImza, sig) || !ecdsaDogrula(digest, sig.data(), sig.size(), pub)) { sonuc->hata = "imza doğrulanamadı (güvenilmez paket — kurulum iptal)"; return; } } sonuc->ok = true; }, [sonuc, &svc]() { // main-thread: durum + yol if (sonuc->ok) { std::wstring& w = sonuc->yol; std::string dar(w.begin(), w.end()); // yol ASCII (temp/surum.zip) svc.indirilenYolAyarla(dar); svc.ilerlemeAyarla(1.0f); svc.durumAyarla(GuncellemeServisi::Durum::Dogrulandi); } else { svc.hataAyarla(sonuc->hata.empty() ? "indirme başarısız" : sonuc->hata); } }); } } // namespace fiko