← pag

Commit aedebcb

0
plików
+0
dodanych
-0
usuniętych
@@ -55,7 +55,7 @@ import threading, itertools
55 55 import uuid # serialNumber SBOM (CycloneDX)
56 56
57 57 # Wersja klienta – do porównania z repo.json["pag_version"] (self-update)
58 -PAG_VERSION = "3.3.23"
58 +PAG_VERSION = "3.3.22"
59 59 from urllib.error import URLError, HTTPError
60 60
61 61 # =============================================================================
@@ -244,6 +244,37 @@ def _gpg_verify_fp(sig_path: str, data_path: str, timeout: int = 30):
244 244 return True, m.group(1).upper()
245 245
246 246
247 +def _verify_local_pkg_signature(pkg_path: str) -> bool:
248 + """Weryfikuje lokalny .pag tym samym kluczem co zaufane repo.
249 +
250 + Otwarcie pliku z menedżera plików nie może omijać łańcucha zaufania.
251 + Brak podpisu, brak przypiętego klucza albo niezgodny fingerprint oznacza
252 + odmowę instalacji (bez trybu automatycznego TOFU dla plików lokalnych).
253 + """
254 + sig_path = next((pkg_path + ext for ext in (".asc", ".sig")
255 + if os.path.isfile(pkg_path + ext)), "")
256 + if not sig_path:
257 + print(f" ❌ Brak podpisu dla pakietu: {pkg_path}")
258 + print(" Wymagany jest plik .pag.asc/.pag.sig z tego samego źródła.")
259 + return False
260 + ok, fingerprint = _gpg_verify_fp(sig_path, pkg_path)
261 + if not ok or not fingerprint:
262 + print(f" ❌ Nieprawidłowy podpis pakietu: {pkg_path}")
263 + return False
264 + expected = _repo_pinned_fp(DEFAULT_REPOS[0])
265 + if not expected:
266 + print(" ❌ Brak przypiętego klucza repo dla lokalnego pakietu.")
267 + print(" Uruchom najpierw: sudo pag sync")
268 + return False
269 + if fingerprint != expected.upper():
270 + print(" ❌ [SECURITY ERROR] Pakiet podpisano innym kluczem repo.")
271 + print(f" Oczekiwany: {expected}")
272 + print(f" Otrzymany: {fingerprint}")
273 + return False
274 + print(f" 🔐 Podpis pakietu OK ({fingerprint})")
275 + return True
276 +
277 +
247 278 # =============================================================================
248 279 # i18n – WIELOJĘZYCZNOŚĆ
249 280 # =============================================================================
@@ -2470,6 +2501,9 @@ def _install_local_pkg_files(paths, install_succeeded):
2470 2501 print(f" ❌ Nie znaleziono pakietu: {p}")
2471 2502 failed += 1
2472 2503 continue
2504 + if not _verify_local_pkg_signature(p):
2505 + failed += 1
2506 + continue
2473 2507 try:
2474 2508 with tarfile.open(p, "r:xz") as tf:
2475 2509 meta = tf.extractfile("metadata.json")
@@ -2698,40 +2732,6 @@ def cmd_install(package_names, as_dep=False, upgrade=False):
2698 2732 else:
2699 2733 downloaded = {}
2700 2734
2701 - # --- Faza 1b: WERYFIKACJA WSZYSTKICH paczek PRZED instalacją ---
2702 - # KLUCZOWE: GPG i SHA256 sprawdzamy dla WSZYSTKICH paczek, ZANIM
2703 - # zainstalujemy choćby jedną. Inaczej instalowany pakiet może podmienić
2704 - # bibliotekę używaną przez sam gpg (np. libassuan 3.0.1 zmienił namespace
2705 - # symboli LIBASSUAN_1.0 -> LIBASSUAN_2.0) i wtedy weryfikacja kolejnych
2706 - # paczek w tej samej transakcji pada, dając fałszywe
2707 - # „NIEPRAWIDŁOWY PODPIS GPG".
2708 - verified = {}
2709 - _vfail = []
2710 - for _name in to_install:
2711 - _pkg = repo_pkgs.get(_name)
2712 - if not _pkg:
2713 - _vfail.append((_name, _('not_found')))
2714 - continue
2715 - _pp = downloaded.get(_name)
2716 - if not _pp:
2717 - _pp = _download_pkg(_pkg)
2718 - if not _pp:
2719 - _vfail.append((_name, _('download_fail')))
2720 - continue
2721 - _gok, _gmsg = _verify_pkg_gpg(_pp, repo_url=_pkg.repo_url)
2722 - if not _gok:
2723 - _vfail.append((_name, f"{_('gpg_fail')}: {_gmsg[:60]}"))
2724 - continue
2725 - if _pkg.sha256 and _sha256_file(_pp) != _pkg.sha256:
2726 - _vfail.append((_name, _('sha256_mismatch')))
2727 - continue
2728 - verified[_name] = _pp
2729 - if _vfail:
2730 - for _n, _m in _vfail:
2731 - print(f" ❌ {_n}: {_m}")
2732 - print(" ↩ Przerwano PRZED instalacją – żadna paczka nie została zmieniona.")
2733 - return 1
2734 -
2735 2735 # --- Faza 2: Instalacja – JEDNA nadpisywana linia postępu (jak przy
2736 2736 # pobieraniu), bez ściany tekstu na każdy pakiet. W trybie
2737 2737 # nieinteraktywnym (logi, netinstall instalatora) wypisujemy linię na
@@ -2780,16 +2780,29 @@ def cmd_install(package_names, as_dep=False, upgrade=False):
2780 2780 _pkg_i += 1
2781 2781 _bar_draw(_pkg_i, f"{name}-{pkg.version}")
2782 2782
2783 - # Ścieżka pobrana i ZWERYFIKOWANA w fazie 1b – PRZED jakąkolwiek
2784 - # instalacją (patrz komentarz przy fazie 1b). Dzięki temu podmiana
2785 - # biblioteki używanej przez gpg nie psuje weryfikacji pozostałych.
2786 - pkg_path = verified.get(name)
2783 + # Pobierz (z cache fazy 1 lub bezpośrednio)
2784 + pkg_path = downloaded.get(name) if name in downloaded else _download_pkg(pkg)
2787 2785 if not pkg_path:
2788 2786 _bar_end()
2789 2787 print(f" ❌ {name}: {_('download_fail')}")
2790 2788 failed.append(name)
2791 2789 break # przerwij transakcję
2792 2790
2791 + # GPG
2792 + gpg_ok, gpg_msg = _verify_pkg_gpg(pkg_path, repo_url=pkg.repo_url)
2793 + if not gpg_ok:
2794 + _bar_end()
2795 + print(f" ❌ {name}: {_('gpg_fail')}: {gpg_msg[:60]}")
2796 + failed.append(name)
2797 + break # PRZERWIJ – niezaufany pakiet
2798 +
2799 + # SHA256 całego pakietu
2800 + if pkg.sha256 and _sha256_file(pkg_path) != pkg.sha256:
2801 + _bar_end()
2802 + print(f" ❌ {name}: {_('sha256_mismatch')}")
2803 + failed.append(name)
2804 + break # PRZERWIJ – uszkodzony pakiet
2805 +
2793 2806 # Przed instalacją zapamiętaj pliki starej wersji (potrzebne w upgrade)
2794 2807 old_files = _db_get_package_files(name) if name in installed_db else []
2795 2808 # Stare sumy SHA256 – do wykrycia zmian użytkownika w plikach /etc (.pacnew)
@@ -5525,7 +5538,7 @@ def main():
5525 5538 with DatabaseLock():
5526 5539 WRITE_COMMANDS = {
5527 5540 "install": lambda: cmd_install(
5528 - [a for a in args if a not in ("-f", "--force")],
5541 + [a for a in args if a not in ("-f", "--force", "--")],
5529 5542 upgrade=("-f" in args or "--force" in args)),
5530 5543 "remove": lambda: cmd_remove(args),
5531 5544 "update": lambda: cmd_update(do_upgrade=True),