File size: 3,733 Bytes
ecad52f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#!/usr/bin/env python3
"""Verify the bounded source archive against its manifest; this is not a signature."""
import hashlib
import json
import re
import sys
from pathlib import Path, PurePosixPath
from safe_json import strict_loads


def verify_release(root):
    root = Path(root).resolve()
    failures = []
    try:
        manifest_file = root / "RELEASE_MANIFEST.json"
        if manifest_file.is_symlink() or manifest_file.stat().st_size > 1_048_576:
            raise ValueError("Unsafe manifest")
        manifest = strict_loads(manifest_file.read_text(encoding="utf-8"))
        if not isinstance(manifest, dict) or manifest.get("manifest_format") != "bodul-source-archive-v1":
            raise ValueError("Unknown manifest format")
        files = manifest.get("files")
        if not isinstance(files, dict) or not 1 <= len(files) <= 500:
            raise ValueError("Invalid file list")
        folded = set()
        for name, entry in files.items():
            relative = PurePosixPath(name)
            if not relative.parts or not re.fullmatch(r"[A-Za-z0-9_.\-/]+", name) or relative.is_absolute() or name != relative.as_posix() or any(part in (".", "..") for part in relative.parts) or name == "RELEASE_MANIFEST.json":
                raise ValueError("Unsafe archive path")
            if name.casefold() in folded:
                raise ValueError("Case-insensitive archive path collision")
            folded.add(name.casefold())
            if not isinstance(entry, dict) or set(entry) != {"bytes", "sha256", "sha512"}:
                raise ValueError("Invalid file entry")
            if type(entry["bytes"]) is not int or not 0 <= entry["bytes"] <= 2_000_000:
                raise ValueError("Invalid file size")
            for algorithm, length in (("sha256", 64), ("sha512", 128)):
                if not isinstance(entry[algorithm], str) or not re.fullmatch("[0-9a-f]{" + str(length) + "}", entry[algorithm]):
                    raise ValueError("Invalid digest")
            path = root.joinpath(*relative.parts)
            if not path.is_relative_to(root) or any(parent.is_symlink() for parent in (path, *path.parents) if parent != root and parent.is_relative_to(root)):
                failures.append(name + ": unsafe symlink")
                continue
            if not path.is_file() or path.stat().st_size != entry["bytes"]:
                failures.append(name + ": missing file or changed length")
                continue
            data = path.read_bytes()
            if any(hashlib.new(a, data).hexdigest() != entry[a] for a in ("sha256", "sha512")):
                failures.append(name + ": digest mismatch")
        # Ignore development machinery; reject extra source/data files.
        for path in root.rglob("*"):
            rel = path.relative_to(root)
            if any(part in (".git", ".venv", "__pycache__") for part in rel.parts):
                continue
            if path.is_symlink():
                failures.append(rel.as_posix() + ": unexpected symlink")
            elif path.is_file() and rel.as_posix() not in files and rel.as_posix() != "RELEASE_MANIFEST.json":
                failures.append(rel.as_posix() + ": unlisted file")
        return {"verified": not failures, "files_checked": len(files), "scope": "archive_byte_integrity", "authenticated": False, "failures": failures}
    except (ValueError, TypeError, OSError, KeyError, UnicodeError) as exc:
        return {"verified": False, "scope": "archive_byte_integrity", "authenticated": False, "failures": [str(exc)]}


if __name__ == "__main__":
    result = verify_release(Path(__file__).resolve().parent)
    print(json.dumps(result, sort_keys=True))
    sys.exit(0 if result["verified"] else 2)