#!/usr/bin/env python3 import hashlib import http.server import io import os import pathlib import plistlib import shutil import ssl import subprocess import sys import tarfile import tempfile import threading binary = str(pathlib.Path(sys.argv[1]).resolve()) def run(*args, status=0): result = subprocess.run([binary, *map(str, args)], capture_output=True, text=True) assert result.returncode == status, (args, result.returncode, result.stderr) return result.stdout def tar_bytes(entries, mode="w"): buffer = io.BytesIO() with tarfile.open(fileobj=buffer, mode=mode) as archive: for name, data in entries: entry = tarfile.TarInfo(name) entry.size = len(data) archive.addfile(entry, io.BytesIO(data)) return buffer.getvalue() def origin(package): data = subprocess.run(["lz4", "-d", "-c", str(package)], capture_output=True, check=True).stdout with tarfile.open(fileobj=io.BytesIO(data), mode="r:") as archive: return archive.extractfile("HOLY/origin").read().decode() def repodata(package, *, package_hash=None, package_name="fixture", broken=False, public_key=None, claims=None, version="1.0_1"): digest = package_hash or hashlib.sha256(package).hexdigest() row = {"architecture": "x86_64", "pkgver": f"fixture-{version}", "filename-sha256": digest, "filename-size": len(package), "run_depends": [], "shlib-requires": [], "shlib-provides": ["libfixture.so.1"] if claims is None else claims} index = b"\nint fixture(void) { return puts(\"fixture\"); }\n", capture_output=True, check=True) library_data = library.read_bytes() library_props = plistlib.dumps({"pkgname": "fixture", "pkgver": "fixture-1.0_2", "version": "1.0_2", "architecture": "x86_64"}) library_files = plistlib.dumps({"files": [{"file": "/usr/lib/libfixture.so.1", "sha256": hashlib.sha256(library_data).hexdigest(), "size": len(library_data)}]}) library_package = tar_bytes((("./props.plist", library_props), ("./files.plist", library_files), ("./usr/lib/libfixture.so.1", library_data)), "w:gz") (serve / "fixture-1.0_2.x86_64.xbps").write_bytes(library_package) library_index = root / "library-repodata" library_index.write_bytes(repodata(library_package, version="1.0_2")) library_catalog = root / "library-catalog" run("xbps", "index", library_index, "--sha256", hashlib.sha256(library_index.read_bytes()).hexdigest(), "--source", "fixture", "--base", base, "--output", library_catalog) verified = root / "verified-soname" run("xbps", "fetch", "fixture", "1.0_2", "x86_64", "--catalog", library_catalog, "--output", verified, "--ca-file", root / "cert.pem", "--import", "--require-soname", "libfixture.so.1") assert "soname-provider verified-payload" in (verified / "conversion").read_text() converted = root / "converted" run("import", fetched / package_hash, "--source", "fixture", "--format", "xbps", "--output", converted) assert len(list(converted.glob("*.holy"))) == 1 direct_origin = origin(next(converted.glob("*.holy"))) assert "verification unverified\n" in direct_origin assert "index-sha256 " not in direct_origin private_key = root / "repo-private.pem" public_key = root / "repo-public.pem" subprocess.run(["openssl", "genpkey", "-algorithm", "RSA", "-pkeyopt", "rsa_keygen_bits:2048", "-out", str(private_key)], capture_output=True, check=True) subprocess.run(["openssl", "pkey", "-in", str(private_key), "-pubout", "-out", str(public_key)], capture_output=True, check=True) signature = serve / "fixture-1.0_1.x86_64.xbps.sig2" subprocess.run(["openssl", "dgst", "-sha256", "-sign", str(private_key), "-out", str(signature), str(serve / "fixture-1.0_1.x86_64.xbps")], capture_output=True, check=True) signed_index = root / "signed-repodata" signed_index.write_bytes(repodata(package, public_key=public_key.read_bytes())) signed_hash = hashlib.sha256(signed_index.read_bytes()).hexdigest() signed_catalog = root / "signed-catalog" run("xbps", "index", signed_index, "--sha256", signed_hash, "--source", "fixture", "--base", base, "--output", signed_catalog, "--public-key", public_key) (serve / "x86_64-repodata").write_bytes(signed_index.read_bytes()) signed_sync = root / "signed-sync" run("xbps", "sync", base, "x86_64", "--sha256", signed_hash, "--source", "fixture", "--output", signed_sync, "--ca-file", root / "cert.pem", "--public-key", public_key) assert "verification key-matched" in (signed_sync / "conversion").read_text() target = root / "target" target.mkdir() run("db", "init", "--root", target) config = root / "source.conf" config.write_text(f'[source fixture]\ntype xbps\nurl "{base}"\n' f'trust require\npublic-key "{public_key}"\n') plan = root / "source.plan" plan.write_text(run("source", "plan", "--config", config, "--root", target)) run("source", "apply", plan, "--sha256", hashlib.sha256(plan.read_bytes()).hexdigest(), "--root", target) registered = target / "cache" run("xbps", "sync-source", "fixture", "x86_64", "--root", target, "--sha256", signed_hash, "--output", registered, "--ca-file", root / "cert.pem", "--public-key", public_key) assert "source-id " in (registered / "conversion").read_text() run("info", "fixture:fixture", "--root", target, status=3) assert package_hash in run("info", "fixture:fixture", "--arch", "x86_64", "--root", target) assert "fixture 1.0_1 x86_64" in run( "search", "fixture", "--source", "fixture", "--arch", "x86_64", "--root", target) assert 'source "fixture"' in run( "search", "fixture", "--arch", "x86_64", "--root", target) run("search", "/usr/share/fixture", "--source", "fixture", "--arch", "x86_64", "--root", target, "--file", status=6) copied_catalog = root / "copied-catalog" shutil.copytree(registered, copied_catalog) run("info", "fixture:fixture", "--arch", "x86_64", "--catalog", copied_catalog, "--root", target, status=6) run("fetch", "fixture:fixture", "--version", "1.0_1", "--arch", "x86_64", "--catalog", copied_catalog, "--root", target, "--output", root / "copied-fetch", status=6) generic_target = root / "generic-target" generic_target.mkdir() run("db", "init", "--root", generic_target) generic_plan = root / "generic.plan" generic_plan.write_text(run("source", "plan", "--config", config, "--root", generic_target)) run("source", "apply", generic_plan, "--sha256", hashlib.sha256(generic_plan.read_bytes()).hexdigest(), "--root", generic_target) run("sync", "fixture", "--arch", "x86_64", "--root", generic_target, "--sha256", signed_hash, "--output", root / "generic-cache", "--ca-file", root / "cert.pem", status=6) run("sync", "fixture", "--arch", "x86_64", "--root", generic_target, "--sha256", signed_hash, "--output", root / "generic-cache", "--ca-file", root / "cert.pem", "--public-key", public_key) assert "source-id " in (root / "generic-cache/conversion").read_text() run("fetch", "fixture:fixture", "--root", target, "--output", root / "generic-missing-version", status=3) run("fetch", "fixture:fixture", "--version", "1.0_1", "--arch", "x86_64", "--root", target, "--output", root / "generic-missing-key", status=6) run("fetch", "fixture:fixture", "--version", "1.0_1", "--arch", "x86_64", "--root", target, "--output", root / "generic-fetch", "--ca-file", root / "cert.pem", "--public-key", public_key, "--import") assert (root / "generic-fetch" / package_hash).read_bytes() == package assert "imported yes" in (root / "generic-fetch/conversion").read_text() assert package_hash in run("xbps", "info", "fixture", "--catalog", registered, "--source", "fixture", "--root", target) assert package_hash in run("xbps", "info", "fixture", "--source", "fixture", "--index-arch", "x86_64", "--root", target) assert package_hash in run("xbps", "providers", "libfixture.so.1", "--source", "fixture", "--index-arch", "x86_64", "--root", target) run("xbps", "fetch", "fixture", "1.0_1", "x86_64", "--source", "fixture", "--index-arch", "x86_64", "--root", target, "--output", root / "bound-fetch", "--ca-file", root / "cert.pem", "--public-key", public_key, "--import") assert (root / "bound-fetch" / package_hash).read_bytes() == package assert "source-id " in (root / "bound-fetch" / "conversion").read_text() assert "imported yes" in (root / "bound-fetch" / "conversion").read_text() signed_origin = origin(next((root / "bound-fetch" / "converted").glob("*.holy"))) signed_receipt = (root / "bound-fetch" / "conversion").read_text() assert "verification rsa-sha256\n" in signed_origin assert "public-key-sha256 " in signed_origin assert "signature-sha256 " in signed_origin assert f"index-sha256 {signed_hash}\n" in signed_origin assert f'source-url "{base}"\n' in signed_origin for field in ("public-key-sha256", "signature-sha256"): assert next(line for line in signed_origin.splitlines() if line.startswith(field + " ")) in signed_receipt run("xbps", "fetch", "fixture", "1.0_1", "x86_64", "--catalog", registered, "--output", root / "unbound-fetch", "--ca-file", root / "cert.pem", "--public-key", public_key) assert "source-id " not in (root / "unbound-fetch" / "conversion").read_text() run("xbps", "fetch", "fixture", "1.0_1", "x86_64", "--catalog", registered, "--output", root / "registered-fetch", "--source", "fixture", "--root", target, "--ca-file", root / "cert.pem", "--public-key", public_key) run("xbps", "fetch", "fixture", "1.0_1", "x86_64", "--catalog", registered, "--output", root / "missing-key-fetch", "--source", "fixture", "--root", target, "--ca-file", root / "cert.pem", status=6) run("xbps", "info", "fixture", "--catalog", signed_sync, "--source", "fixture", "--root", target, status=6) conversion = registered / "conversion" saved_conversion = conversion.read_bytes() conversion.write_bytes(saved_conversion + b"x-test changed\n") run("xbps", "info", "fixture", "--source", "fixture", "--index-arch", "x86_64", "--root", target, status=6) conversion.write_bytes(saved_conversion) renamed = root / "renamed.conf" renamed.write_text(config.read_text().replace("source fixture", "source void")) renamed_plan = root / "renamed.plan" renamed_plan.write_text(run("source", "plan", "--config", renamed, "--root", target)) run("source", "apply", renamed_plan, "--sha256", hashlib.sha256(renamed_plan.read_bytes()).hexdigest(), "--root", target) assert package_hash in run("xbps", "info", "fixture", "--source", "void", "--index-arch", "x86_64", "--root", target) assert package_hash in run("xbps", "providers", "libfixture.so.1", "--source", "void", "--index-arch", "x86_64", "--root", target) moved_target = root / "moved-target" target.rename(moved_target) target = moved_target registered = target / "cache" assert package_hash in run("xbps", "info", "fixture", "--source", "void", "--index-arch", "x86_64", "--root", target) rotated = root / "rotated.conf" rotated.write_text(renamed.read_text().replace(base, "https://localhost:65535/")) rotated_plan = root / "rotated.plan" rotated_plan.write_text(run("source", "plan", "--config", rotated, "--root", target)) run("source", "apply", rotated_plan, "--sha256", hashlib.sha256(rotated_plan.read_bytes()).hexdigest(), "--root", target) run("xbps", "info", "fixture", "--catalog", registered, "--source", "void", "--root", target, status=6) run("xbps", "info", "fixture", "--source", "void", "--index-arch", "x86_64", "--root", target, status=6) signed_fetch = root / "signed-fetch" run("xbps", "fetch", "fixture", "1.0_1", "x86_64", "--catalog", signed_catalog, "--output", signed_fetch, "--ca-file", root / "cert.pem", "--public-key", public_key) assert "verification rsa-sha256" in (signed_fetch / "conversion").read_text() wrong_key = root / "wrong-public.pem" wrong_private = root / "wrong-private.pem" subprocess.run(["openssl", "genpkey", "-algorithm", "RSA", "-pkeyopt", "rsa_keygen_bits:2048", "-out", str(wrong_private)], capture_output=True, check=True) subprocess.run(["openssl", "pkey", "-in", str(wrong_private), "-pubout", "-out", str(wrong_key)], capture_output=True, check=True) run("xbps", "index", signed_index, "--sha256", signed_hash, "--source", "fixture", "--base", base, "--output", root / "wrong-key-index", "--public-key", wrong_key, status=4) run("xbps", "fetch", "fixture", "1.0_1", "x86_64", "--catalog", signed_catalog, "--output", root / "wrong-key-fetch", "--ca-file", root / "cert.pem", "--public-key", wrong_key, status=4) signature.write_bytes(b"invalid signature") run("xbps", "fetch", "fixture", "1.0_1", "x86_64", "--catalog", signed_catalog, "--output", root / "bad-signature-fetch", "--ca-file", root / "cert.pem", "--public-key", public_key, status=4) (catalog / "capabilities").write_bytes(b"tampered") run("xbps", "providers", "libfixture.so.1", "--catalog", catalog, status=6) run("xbps", "info", "fixture", "--catalog", catalog, status=6) (catalog / "catalog").write_bytes(b"tampered") run("xbps", "info", "fixture", "--catalog", catalog, status=6) malformed = root / "malformed-repodata" malformed.write_bytes(repodata(package, broken=True)) run("xbps", "index", malformed, "--sha256", hashlib.sha256(malformed.read_bytes()).hexdigest(), "--source", "fixture", "--base", base, "--output", root / "bad-index", status=2) malformed_claim = root / "malformed-claim-repodata" malformed_claim.write_bytes(repodata(package, claims=["bad claim"])) run("xbps", "index", malformed_claim, "--sha256", hashlib.sha256(malformed_claim.read_bytes()).hexdigest(), "--source", "fixture", "--base", base, "--output", root / "bad-claim-index", status=2) wrong = root / "wrong-repodata" wrong.write_bytes(repodata(package, package_hash="0" * 64)) wrong_catalog = root / "wrong-catalog" run("xbps", "index", wrong, "--sha256", hashlib.sha256(wrong.read_bytes()).hexdigest(), "--source", "fixture", "--base", base, "--output", wrong_catalog) run("xbps", "fetch", "fixture", "1.0_1", "x86_64", "--catalog", wrong_catalog, "--output", root / "wrong-fetch", "--ca-file", root / "cert.pem", status=4) alien_props = plistlib.dumps({"pkgname": "other", "pkgver": "other-1.0_1", "version": "1.0_1", "architecture": "x86_64"}) alien_package = tar_bytes((("./props.plist", alien_props), ("./files.plist", files), ("./usr/share/fixture", body)), "w:gz") (serve / "fixture-1.0_1.x86_64.xbps").write_bytes(alien_package) alien_index = root / "alien-repodata" alien_index.write_bytes(repodata(alien_package)) alien_catalog = root / "alien-catalog" run("xbps", "index", alien_index, "--sha256", hashlib.sha256(alien_index.read_bytes()).hexdigest(), "--source", "fixture", "--base", base, "--output", alien_catalog) run("xbps", "fetch", "fixture", "1.0_1", "x86_64", "--catalog", alien_catalog, "--output", root / "alien-fetch", "--ca-file", root / "cert.pem", status=4) finally: server.shutdown() thread.join() print("XBPS index, HTTPS sync, fetch and rejection fixtures passed") if __name__ == "__main__": main()