For reading and verification only. You may not copy, use, modify or redistribute this code. Full notice on the main page.
src/all_mcp/tray/encryption_verify.py
| 1 | """ALL 2.0 (WP37): run-all verify-source. Listed installed files must match the MANIFEST's SHA-256; nothing else is checked. Nothing is sent. |
| 2 | |
| 3 | Which manifest decides what a pass means. The one bundled in the install lives beside the files it lists: whoever can change an installed file can change that manifest, so a pass |
| 4 | against it proves only that the two agree, never that the files are the ones ALL published. The command says exactly that, prints the manifest's own SHA-256 and names the way to |
| 5 | compare with the published one (a manifest downloaded from the page, passed with --manifest, whose SHA-256 the page also shows). Only that second pass says "the published ones".""" |
| 6 | from __future__ import annotations |
| 7 | |
| 8 | import glob |
| 9 | import hashlib |
| 10 | import json |
| 11 | from pathlib import Path |
| 12 | from typing import Any |
| 13 | |
| 14 | PACKAGE_DIR = Path(__file__).resolve().parent.parent # the all_mcp package this module is installed in |
| 15 | PREFIX = "src/all_mcp/" |
| 16 | BUNDLED_MANIFEST = PACKAGE_DIR / "encryption-source-manifest.json" |
| 17 | SCHEMA = "all-encryption-source/1" |
| 18 | EXIT_OK, EXIT_FAILED, EXIT_USAGE = 0, 1, 64 |
| 19 | |
| 20 | _TEXT = { |
| 21 | "es": { |
| 22 | "title": "Código publicado de ALL {version}: lo instalado", |
| 23 | "version_bad": "Instalada: {installed}. El manifiesto es de la versión {version}; usa el de tu versión.", |
| 24 | "no_manifest": "Esta copia no trae manifiesto. Usa: run-all verify-source --manifest RUTA (el de tu versión).", |
| 25 | "bad_manifest": "No pude leer el manifiesto: no es uno de ALL válido o está dañado.", |
| 26 | "changed": "Cambió: {path}", |
| 27 | "missing": "Falta: {path}", |
| 28 | "extra": "No está en el manifiesto: {path}", |
| 29 | "manifest": "Manifiesto: {source} (SHA-256 {digest})", |
| 30 | "source_bundled": "el que vino con esta instalación", |
| 31 | "source_file": "el archivo que indicaste", |
| 32 | "ok": "Correcto: los {count} archivos del manifiesto que corren en esta computadora coinciden con el manifiesto que indicaste. Compara el SHA-256 de arriba con el que muestra la página del código; si es el mismo, esos archivos son los publicados.", |
| 33 | "ok_bundled": "Los {count} archivos del manifiesto que corren en esta computadora coinciden con el manifiesto que vino con esta instalación. Eso solo muestra que ambos concuerdan: quien pudiera cambiar un archivo podría cambiar también ese manifiesto. Para compararlos con lo publicado, baja el manifiesto de tu versión de la página del código y corre: run-all verify-source --manifest RUTA", |
| 34 | "not_here": "{count} archivos del conjunto no corren en esta computadora. Este comando no los revisa.", |
| 35 | "failed": "No coincide: {count} problema(s) en los archivos del manifiesto.", |
| 36 | "usage": "uso: run-all verify-source [--manifest RUTA] [--json]", |
| 37 | }, |
| 38 | "en": { |
| 39 | "title": "Published code of ALL {version}: what is installed", |
| 40 | "version_bad": "Installed: {installed}. The manifest is for {version}; use the one of your version.", |
| 41 | "no_manifest": "This copy has no manifest. Use: run-all verify-source --manifest PATH (the one of your version).", |
| 42 | "bad_manifest": "I could not read the manifest: it is not a valid ALL one or is damaged.", |
| 43 | "changed": "Changed: {path}", |
| 44 | "missing": "Missing: {path}", |
| 45 | "extra": "Not in the manifest: {path}", |
| 46 | "manifest": "Manifest: {source} (SHA-256 {digest})", |
| 47 | "source_bundled": "the one that came with this install", |
| 48 | "source_file": "the file you gave", |
| 49 | "ok": "OK: the {count} files of the manifest that run on this computer match the manifest you gave. Compare the SHA-256 above with the one the code page shows; if it is the same, those files are the published ones.", |
| 50 | "ok_bundled": "The {count} files of the manifest that run on this computer match the manifest that came with this install. That only shows the two agree: whoever could change a file could change that manifest too. To compare with what was published, download your version's manifest from the code page and run: run-all verify-source --manifest PATH", |
| 51 | "not_here": "{count} files of the set do not run on this computer. This command does not check them.", |
| 52 | "failed": "Does not match: {count} problem(s) in the files of the manifest.", |
| 53 | "usage": "usage: run-all verify-source [--manifest PATH] [--json]", |
| 54 | }, |
| 55 | } |
| 56 | |
| 57 | |
| 58 | class ManifestError(Exception): |
| 59 | """The _TEXT key that says why the manifest cannot be used.""" |
| 60 | |
| 61 | |
| 62 | def _text(key: str, lang: str, **fields: Any) -> str: |
| 63 | return _TEXT["en" if lang == "en" else "es"][key].format(**fields) |
| 64 | |
| 65 | |
| 66 | def manifest_sha256(path: Path | None = None) -> str: |
| 67 | """The SHA-256 of the manifest file itself (the bundled one when `path` is None): the number the code page shows beside the manifest it publishes.""" |
| 68 | return hashlib.sha256((Path(path) if path is not None else BUNDLED_MANIFEST).read_bytes()).hexdigest() |
| 69 | |
| 70 | |
| 71 | def load_manifest(path: Path | None = None) -> dict[str, Any]: |
| 72 | source = Path(path) if path is not None else BUNDLED_MANIFEST |
| 73 | if not source.is_file(): |
| 74 | raise ManifestError("no_manifest") |
| 75 | try: |
| 76 | manifest = json.loads(source.read_text(encoding="utf-8")) |
| 77 | if (manifest.get("schema") == SCHEMA and isinstance(manifest.get("allowlist"), list) |
| 78 | and all(isinstance(e.get("path"), str) and isinstance(e.get("sha256"), str) for e in manifest["files"])): |
| 79 | return manifest |
| 80 | except (OSError, ValueError, AttributeError, TypeError, KeyError): |
| 81 | pass |
| 82 | raise ManifestError("bad_manifest") |
| 83 | |
| 84 | |
| 85 | def verify(manifest: dict[str, Any], *, installed_version: str, package_dir: Path = PACKAGE_DIR) -> dict[str, Any]: |
| 86 | """`ok` only when the version matches, listed files run here and match, no allow-listed file is unlisted, and one file was checked.""" |
| 87 | listed = {entry["path"] for entry in manifest["files"]} |
| 88 | out: dict[str, Any] = {"version": manifest.get("version"), "version_match": manifest.get("version") == installed_version, |
| 89 | "checked": [], "changed": [], "missing": [], "extra": [], "not_installed_here": []} |
| 90 | for entry in manifest["files"]: |
| 91 | rel = entry["path"] |
| 92 | local = package_dir / rel[len(PREFIX):] |
| 93 | if not rel.startswith(PREFIX): |
| 94 | out["not_installed_here"].append(rel) |
| 95 | elif not local.is_file(): |
| 96 | out["missing"].append(rel) |
| 97 | else: |
| 98 | out["checked"].append(rel) |
| 99 | if hashlib.sha256(local.read_bytes()).hexdigest() != entry["sha256"]: |
| 100 | out["changed"].append(rel) |
| 101 | root = package_dir.resolve() |
| 102 | withheld = {rel for rel in manifest.get("withheld", []) if isinstance(rel, str)} # what the allow-list names and the export does not publish (the denylist): not an unlisted file |
| 103 | for pattern in (p for p in manifest["allowlist"] if p.startswith(PREFIX)): |
| 104 | for found in glob.glob(str(root / pattern[len(PREFIX):]), recursive=True, include_hidden=True): |
| 105 | rel = PREFIX + Path(found).relative_to(root).as_posix() |
| 106 | if Path(found).is_file() and rel not in listed and rel not in withheld and rel not in out["extra"]: |
| 107 | out["extra"].append(rel) |
| 108 | out["ok"] = bool(out["version_match"] and out["checked"] and not (out["changed"] or out["missing"] or out["extra"])) |
| 109 | return out |
| 110 | |
| 111 | |
| 112 | def run(args: list[str], lang: str, *, installed_version: str | None = None, |
| 113 | package_dir: Path = PACKAGE_DIR) -> tuple[int, list[str], dict[str, Any] | None]: |
| 114 | """The command: (exit code, lines for the person's terminal, the report for --json).""" |
| 115 | if installed_version is None: |
| 116 | from .. import __version__ as installed_version # noqa: N813 (the version this build runs) |
| 117 | rest = [a for a in args if a != "--json"] |
| 118 | if rest and not (len(rest) == 2 and rest[0] == "--manifest"): |
| 119 | return EXIT_USAGE, [_text("usage", lang)], None |
| 120 | given = Path(rest[1]).expanduser() if rest else None |
| 121 | try: |
| 122 | manifest = load_manifest(given) |
| 123 | digest = manifest_sha256(given) |
| 124 | except ManifestError as error: |
| 125 | return EXIT_FAILED, [_text(str(error), lang)], None |
| 126 | except OSError: |
| 127 | return EXIT_FAILED, [_text("bad_manifest", lang)], None |
| 128 | report = verify(manifest, installed_version=installed_version, package_dir=package_dir) |
| 129 | report["manifest_source"] = "bundled" if given is None else "file" |
| 130 | report["manifest_sha256"] = digest |
| 131 | lines = [_text("title", lang, version=installed_version), |
| 132 | _text("manifest", lang, source=_text("source_bundled" if given is None else "source_file", lang), digest=digest)] |
| 133 | if not report["version_match"]: |
| 134 | lines.append(_text("version_bad", lang, installed=installed_version, version=report["version"])) |
| 135 | for key in ("changed", "missing", "extra"): |
| 136 | lines += [_text(key, lang, path=p) for p in report[key]] |
| 137 | if report["not_installed_here"]: |
| 138 | lines.append(_text("not_here", lang, count=len(report["not_installed_here"]))) |
| 139 | as_json = report if "--json" in args else None |
| 140 | if report["ok"]: |
| 141 | return EXIT_OK, lines + [_text("ok_bundled" if given is None else "ok", lang, count=len(report["checked"]))], as_json |
| 142 | problems = len(report["changed"]) + len(report["missing"]) + len(report["extra"]) + (0 if report["version_match"] else 1) |
| 143 | return EXIT_FAILED, lines + [_text("failed", lang, count=max(problems, 1))], as_json |