rustd-resolved 0.2.1

A compatibility-oriented reimplementation of systemd-resolved
#!/usr/bin/python3 -I
"""Validate one canonical two-build replacement release directory."""

from __future__ import annotations

import argparse
from datetime import datetime, timezone
import hashlib
import json
from pathlib import Path
import re
import sys
from typing import Any


class ReproducibleReleaseError(RuntimeError):
    pass


EXPECTED_IDENTICAL = {
    "systemd-resolved",
    "resolvectl",
    "libnss_resolve.so.2",
    "files.sha256",
    "rustd-resolved.tar.gz",
}
EXPECTED_ARTIFACTS = EXPECTED_IDENTICAL | {"rust-toolchain.txt"}


def sha256(path: Path) -> str:
    digest = hashlib.sha256()
    with path.open("rb") as stream:
        for chunk in iter(lambda: stream.read(1024 * 1024), b""):
            digest.update(chunk)
    return digest.hexdigest()


def is_hash(value: Any, length: int) -> bool:
    return isinstance(value, str) and re.fullmatch(
        rf"[0-9a-f]{{{length}}}", value
    ) is not None


def arguments() -> argparse.Namespace:
    parser = argparse.ArgumentParser()
    parser.add_argument("--directory", required=True, type=Path)
    parser.add_argument("--source-commit", required=True)
    parser.add_argument("--source-tree", required=True)
    parser.add_argument("--upstream-commit", required=True)
    parser.add_argument("--expected-daemon-sha256", required=True)
    parser.add_argument("--expected-client-sha256", required=True)
    parser.add_argument("--expected-nss-sha256", required=True)
    return parser.parse_args()


def validate(options: argparse.Namespace) -> None:
    directory = options.directory.resolve()
    if not directory.is_dir() or directory.is_symlink():
        raise ReproducibleReleaseError(
            "reproducible release directory is missing or unsafe"
        )
    for label, value in (
        ("source commit", options.source_commit),
        ("source tree", options.source_tree),
        ("upstream commit", options.upstream_commit),
    ):
        if not is_hash(value, 40):
            raise ReproducibleReleaseError(f"{label} is invalid")
    for label, value in (
        ("daemon", options.expected_daemon_sha256),
        ("client", options.expected_client_sha256),
        ("NSS module", options.expected_nss_sha256),
    ):
        if not is_hash(value, 64):
            raise ReproducibleReleaseError(f"expected {label} hash is invalid")

    manifest_path = directory / "manifest.json"
    if manifest_path.is_symlink() or not manifest_path.is_file():
        raise ReproducibleReleaseError("reproducible manifest is missing or unsafe")
    try:
        manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
    except (OSError, json.JSONDecodeError) as error:
        raise ReproducibleReleaseError(
            f"cannot read reproducible manifest: {error}"
        ) from error
    if not isinstance(manifest, dict):
        raise ReproducibleReleaseError("reproducible manifest root is not an object")
    if manifest.get("schema") != 2 or manifest.get("reproducible") is not True:
        raise ReproducibleReleaseError(
            "reproducible manifest schema or result is invalid"
        )
    if manifest.get("build_count") != 2:
        raise ReproducibleReleaseError("reproducible manifest build count differs")
    if manifest.get("source_commit") != options.source_commit:
        raise ReproducibleReleaseError("reproducible manifest source commit differs")
    if manifest.get("source_tree") != options.source_tree:
        raise ReproducibleReleaseError("reproducible manifest source tree differs")
    if manifest.get("upstream_commit") != options.upstream_commit:
        raise ReproducibleReleaseError("reproducible manifest upstream commit differs")
    if (
        manifest.get("rustc_release") != "1.74.0"
        or manifest.get("cargo_release") != "1.74.0"
    ):
        raise ReproducibleReleaseError("reproducible manifest toolchain differs")
    for label in ("rustc", "cargo"):
        if not is_hash(manifest.get(f"{label}_sha256"), 64):
            raise ReproducibleReleaseError(
                f"reproducible manifest {label} executable hash is invalid"
            )

    source_date_epoch = manifest.get("source_date_epoch")
    if (
        not isinstance(source_date_epoch, int)
        or isinstance(source_date_epoch, bool)
        or source_date_epoch < 0
        or source_date_epoch > 253402300799
    ):
        raise ReproducibleReleaseError(
            "reproducible manifest source date epoch is invalid"
        )
    expected_generated_at = datetime.fromtimestamp(
        source_date_epoch, tz=timezone.utc
    ).isoformat()
    if manifest.get("generated_at") != expected_generated_at:
        raise ReproducibleReleaseError(
            "reproducible manifest generated timestamp differs"
        )

    identical = manifest.get("byte_identical")
    if (
        not isinstance(identical, list)
        or len(identical) != len(EXPECTED_IDENTICAL)
        or set(identical) != EXPECTED_IDENTICAL
    ):
        raise ReproducibleReleaseError(
            "reproducible manifest byte-identical set differs"
        )
    raw_artifacts = manifest.get("artifacts")
    if not isinstance(raw_artifacts, list):
        raise ReproducibleReleaseError(
            "reproducible manifest artifact list is missing"
        )
    entries: dict[str, dict[str, Any]] = {}
    for item in raw_artifacts:
        if not isinstance(item, dict):
            raise ReproducibleReleaseError(
                "reproducible manifest artifact entry is malformed"
            )
        name = item.get("name")
        if (
            not isinstance(name, str)
            or not name
            or Path(name).name != name
            or name in entries
        ):
            raise ReproducibleReleaseError(
                "reproducible manifest artifact name is invalid"
            )
        entries[name] = item
    if set(entries) != EXPECTED_ARTIFACTS:
        raise ReproducibleReleaseError(
            "reproducible manifest artifact set differs"
        )

    paths: dict[str, Path] = {}
    for name, entry in entries.items():
        path = directory / name
        if path.is_symlink() or not path.is_file() or path.resolve().parent != directory:
            raise ReproducibleReleaseError(
                f"reproducible artifact is missing or unsafe: {name}"
            )
        size = entry.get("size")
        expected = entry.get("sha256")
        if (
            not isinstance(size, int)
            or isinstance(size, bool)
            or size < 0
            or not is_hash(expected, 64)
            or size != path.stat().st_size
            or expected != sha256(path)
        ):
            raise ReproducibleReleaseError(
                f"reproducible manifest artifact binding differs: {name}"
            )
        paths[name] = path

    expected_hashes = {
        "systemd-resolved": options.expected_daemon_sha256,
        "resolvectl": options.expected_client_sha256,
        "libnss_resolve.so.2": options.expected_nss_sha256,
    }
    for name, expected in expected_hashes.items():
        if entries[name]["sha256"] != expected:
            raise ReproducibleReleaseError(f"{name} hash differs")

    toolchain = paths["rust-toolchain.txt"].read_text(encoding="utf-8")
    if "rustc 1.74.0" not in toolchain or "cargo 1.74.0" not in toolchain:
        raise ReproducibleReleaseError("reproducible toolchain evidence differs")
    if (
        f"rustc_binary_sha256 {manifest['rustc_sha256']}" not in toolchain
        or f"cargo_binary_sha256 {manifest['cargo_sha256']}" not in toolchain
    ):
        raise ReproducibleReleaseError(
            "reproducible toolchain executable hash evidence differs"
        )


def main() -> int:
    validate(arguments())
    print("canonical reproducible release validation passed")
    return 0


if __name__ == "__main__":
    try:
        raise SystemExit(main())
    except (OSError, ReproducibleReleaseError) as error:
        print(f"validate-reproducible-release: {error}", file=sys.stderr)
        raise SystemExit(1) from error