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),