"""Build authenticated RF-001 agent release and bootstrap packages.""" from __future__ import annotations import argparse from dataclasses import dataclass import hashlib import hmac import json from pathlib import Path, PurePosixPath import re import shutil from typing import BinaryIO, Mapping import zipfile try: from tools.rf001_agent_update import ( RELEASE_SOURCE_FILES, UPDATE_MANIFEST_DOMAIN, build_release_archive, canonical_json_bytes, sha256_file, validate_release_identity, ) except ModuleNotFoundError: # direct execution from tools/ from rf001_agent_update import ( # type: ignore[no-redef] RELEASE_SOURCE_FILES, UPDATE_MANIFEST_DOMAIN, build_release_archive, canonical_json_bytes, sha256_file, validate_release_identity, ) BOOTSTRAP_ID_PATTERN = re.compile( r"^rf001-bootstrap-[0-9]{4}\.[0-9]{2}\.[0-9]{2}\.[0-9]+$" ) BOOTSTRAP_MANIFEST_NAME = "BOOTSTRAP_MANIFEST.json" BOOTSTRAP_SIGNATURE_NAME = "BOOTSTRAP_MANIFEST.hmac" REFERENCE_SHA256 = "fa260719659b2984fe29588238415622cd75c6daefec6cc9130ba1bf851bd3f9" MAX_BOOTSTRAP_BYTES = 64 * 1024 * 1024 @dataclass(frozen=True) class BootstrapInspection: bootstrap_id: str agent_release_id: str agent_version: str protocol_version: int package_sha256: str package_bytes: int content_manifest_sha256: str file_count: int def _token_bytes(path: Path) -> bytes: value = path.read_text(encoding="utf-8-sig").strip().encode("utf-8") if len(value) < 32: raise ValueError("existing signing token is missing or too short") return value def _validate_archive_name(name: str) -> None: path = PurePosixPath(name) if ( not name or "\\" in name or name.startswith("/") or path.is_absolute() or any(part in ("", ".", "..") or ":" in part for part in path.parts) ): raise ValueError(f"unsafe bootstrap path: {name!r}") def _source_files( source_root: Path, *, agent_release_id: str, ) -> dict[str, Path]: result = { "INSTALL_RX_AGENT.cmd": source_root / "INSTALL_RX_AGENT.cmd", "README_RX_AGENT.txt": source_root / "tools" / "rf001_bootstrap_readme.txt", "UNINSTALL_RX_AGENT.cmd": source_root / "UNINSTALL_RX_AGENT.cmd", "launcher/rf001_agent_launcher.py": source_root / "tools" / "rf001_agent_launcher.py", "reference/lab065_reference_staircase.cs8": source_root / ".local" / "rf001_probe" / "lab065_reference_staircase.cs8", "tools/install_rf001_rx_autostart.ps1": source_root / "tools" / "install_rf001_rx_autostart.ps1", "tools/uninstall_rf001_rx_autostart.ps1": source_root / "tools" / "uninstall_rf001_rx_autostart.ps1", } for archive_name, relative in RELEASE_SOURCE_FILES.items(): result[f"versions/{agent_release_id}/{archive_name}"] = source_root / relative return result def _copy_to_zip( archive: zipfile.ZipFile, name: str, source: Path, *, timestamp: tuple[int, int, int, int, int, int], ) -> None: info = zipfile.ZipInfo(name, timestamp) info.compress_type = zipfile.ZIP_DEFLATED info.external_attr = 0o100600 << 16 with source.open("rb") as input_stream, archive.open(info, "w") as output_stream: shutil.copyfileobj(input_stream, output_stream, length=1024 * 1024) def _write_bytes( archive: zipfile.ZipFile, name: str, value: bytes, *, timestamp: tuple[int, int, int, int, int, int], ) -> None: info = zipfile.ZipInfo(name, timestamp) info.compress_type = zipfile.ZIP_DEFLATED info.external_attr = 0o100600 << 16 archive.writestr(info, value) def build_bootstrap_archive( source_root: Path, package_path: Path, *, bootstrap_id: str, agent_release_id: str, agent_version: str, protocol_version: int, token_path: Path, agent_release_bootstrap_id: str | None = None, ) -> BootstrapInspection: if BOOTSTRAP_ID_PATTERN.fullmatch(bootstrap_id) is None: raise ValueError("bootstrap_id is invalid") if protocol_version <= 0: raise ValueError("protocol_version is invalid") release_bootstrap_id = agent_release_bootstrap_id or bootstrap_id if BOOTSTRAP_ID_PATTERN.fullmatch(release_bootstrap_id) is None: raise ValueError("agent_release_bootstrap_id is invalid") source_root = source_root.resolve() validate_release_identity( source_root, release_id=agent_release_id, requested_agent_version=agent_version, ) sources = _source_files(source_root, agent_release_id=agent_release_id) for name, source in sources.items(): _validate_archive_name(name) if not source.is_file(): raise FileNotFoundError(f"bootstrap source is absent: {source}") reference = sources["reference/lab065_reference_staircase.cs8"] if sha256_file(reference) != REFERENCE_SHA256: raise ValueError("staircase reference SHA256 is invalid") ready = canonical_json_bytes( { "agent_version": agent_version, "bootstrap_id": release_bootstrap_id, "hardware_accessed": False, "protocol_version": protocol_version, "release_id": agent_release_id, "rf_tx": 0, "schema_version": 1, "status": "READY", } ) + b"\n" ready_name = f"versions/{agent_release_id}/READY.json" rows = [ { "path": name, "bytes": source.stat().st_size, "sha256": sha256_file(source), } for name, source in sources.items() ] rows.append( { "path": ready_name, "bytes": len(ready), "sha256": hashlib.sha256(ready).hexdigest(), } ) manifest: dict[str, object] = { "schema_version": 1, "package_kind": "BOOTSTRAP", "bootstrap_id": bootstrap_id, "agent_release_bootstrap_id": release_bootstrap_id, "agent_release_id": agent_release_id, "agent_version": agent_version, "protocol_version": protocol_version, "main_server": "http://192.168.255.185:8765", "allowed_rx_ipv4": "192.168.255.56", "reference_sha256": REFERENCE_SHA256, "files": sorted(rows, key=lambda row: str(row["path"])), "hardware_accessed": False, "rf_tx": 0, } manifest_raw = canonical_json_bytes(manifest) signature = hmac.new( _token_bytes(token_path), UPDATE_MANIFEST_DOMAIN + manifest_raw, hashlib.sha256, ).hexdigest().encode("ascii") + b"\n" package_path.parent.mkdir(parents=True, exist_ok=True) if package_path.exists(): raise FileExistsError(f"bootstrap package already exists: {package_path}") timestamp = (2026, 9, 29, 0, 0, 0) with zipfile.ZipFile( package_path, "x", compression=zipfile.ZIP_DEFLATED, compresslevel=9, ) as archive: _write_bytes( archive, BOOTSTRAP_MANIFEST_NAME, manifest_raw, timestamp=timestamp ) _write_bytes( archive, BOOTSTRAP_SIGNATURE_NAME, signature, timestamp=timestamp ) _write_bytes(archive, ready_name, ready, timestamp=timestamp) for name in sorted(sources): _copy_to_zip(archive, name, sources[name], timestamp=timestamp) return inspect_bootstrap_archive(package_path, token_path=token_path) def _digest_stream(stream: BinaryIO) -> str: digest = hashlib.sha256() while block := stream.read(1024 * 1024): digest.update(block) return digest.hexdigest() def inspect_bootstrap_archive( package_path: Path, *, token_path: Path, ) -> BootstrapInspection: package_bytes = package_path.stat().st_size if package_bytes <= 0 or package_bytes > MAX_BOOTSTRAP_BYTES: raise ValueError("bootstrap package size is outside the limit") with zipfile.ZipFile(package_path, "r") as archive: infos: dict[str, zipfile.ZipInfo] = {} for info in archive.infolist(): _validate_archive_name(info.filename) key = info.filename.casefold() if key in (name.casefold() for name in infos): raise ValueError("duplicate bootstrap archive entry") if info.is_dir() or info.flag_bits & 0x1: raise ValueError("bootstrap archive contains a prohibited entry") infos[info.filename] = info if BOOTSTRAP_MANIFEST_NAME not in infos or BOOTSTRAP_SIGNATURE_NAME not in infos: raise ValueError("bootstrap authentication metadata is absent") manifest_raw = archive.read(infos[BOOTSTRAP_MANIFEST_NAME]) manifest = json.loads(manifest_raw.decode("utf-8")) if not isinstance(manifest, dict) or manifest_raw != canonical_json_bytes(manifest): raise ValueError("bootstrap manifest is not canonical JSON") supplied = archive.read(infos[BOOTSTRAP_SIGNATURE_NAME]).decode("ascii").strip() expected = hmac.new( _token_bytes(token_path), UPDATE_MANIFEST_DOMAIN + manifest_raw, hashlib.sha256, ).hexdigest() if not hmac.compare_digest(supplied, expected): raise ValueError("bootstrap manifest signature mismatch") rows = manifest.get("files") if not isinstance(rows, list): raise ValueError("bootstrap file list is invalid") expected_names = {BOOTSTRAP_MANIFEST_NAME, BOOTSTRAP_SIGNATURE_NAME} for row in rows: if not isinstance(row, dict): raise ValueError("bootstrap file entry is invalid") name = row.get("path") if not isinstance(name, str): raise ValueError("bootstrap file path is invalid") _validate_archive_name(name) if name in expected_names or name not in infos: raise ValueError("bootstrap file set is invalid") expected_names.add(name) info = infos[name] if info.file_size != row.get("bytes"): raise ValueError(f"bootstrap file size mismatch: {name}") with archive.open(info, "r") as stream: digest = _digest_stream(stream) if digest != row.get("sha256"): raise ValueError(f"bootstrap file SHA256 mismatch: {name}") if set(infos) != expected_names: raise ValueError("bootstrap archive contains unexpected files") return BootstrapInspection( bootstrap_id=str(manifest["bootstrap_id"]), agent_release_id=str(manifest["agent_release_id"]), agent_version=str(manifest["agent_version"]), protocol_version=int(manifest["protocol_version"]), package_sha256=sha256_file(package_path), package_bytes=package_bytes, content_manifest_sha256=hashlib.sha256(manifest_raw).hexdigest(), file_count=len(rows), ) def main() -> None: parser = argparse.ArgumentParser(description=__doc__) subparsers = parser.add_subparsers(dest="command", required=True) release = subparsers.add_parser("release") release.add_argument("--source-root", type=Path, required=True) release.add_argument("--package", type=Path, required=True) release.add_argument("--release-id", required=True) release.add_argument("--agent-version", required=True) release.add_argument("--protocol-version", type=int, default=1) bootstrap = subparsers.add_parser("bootstrap") bootstrap.add_argument("--source-root", type=Path, required=True) bootstrap.add_argument("--package", type=Path, required=True) bootstrap.add_argument("--bootstrap-id", required=True) bootstrap.add_argument("--agent-release-id", required=True) bootstrap.add_argument("--agent-version", required=True) bootstrap.add_argument("--agent-release-bootstrap-id") bootstrap.add_argument("--protocol-version", type=int, default=1) bootstrap.add_argument("--token-file", type=Path, required=True) arguments = parser.parse_args() if arguments.command == "release": inspection = build_release_archive( arguments.source_root, arguments.package, release_id=arguments.release_id, agent_version=arguments.agent_version, protocol_version=arguments.protocol_version, ) else: inspection = build_bootstrap_archive( arguments.source_root, arguments.package, bootstrap_id=arguments.bootstrap_id, agent_release_id=arguments.agent_release_id, agent_version=arguments.agent_version, protocol_version=arguments.protocol_version, token_path=arguments.token_file, agent_release_bootstrap_id=arguments.agent_release_bootstrap_id, ) print(f"PACKAGE = {arguments.package.resolve()}") print(f"PACKAGE SHA256 = {inspection.package_sha256}") print(f"PACKAGE BYTES = {inspection.package_bytes}") print("HARDWARE ACCESSED = false") print("RF TX = 0") if __name__ == "__main__": main()