72 lines
2.6 KiB
Python
72 lines
2.6 KiB
Python
#!/usr/bin/env python3
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
"""Verify an artifact manifest and, when supplied, its local artifact."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import hashlib
|
|
import json
|
|
import re
|
|
from pathlib import Path
|
|
|
|
|
|
def hash_file(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 fail(message: str) -> None:
|
|
raise ValueError(message)
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--manifest", type=Path, required=True)
|
|
parser.add_argument("--artifact", type=Path)
|
|
args = parser.parse_args()
|
|
|
|
document = json.loads(args.manifest.read_text(encoding="utf-8"))
|
|
if document.get("schema_version") != 1:
|
|
fail("unsupported artifact-manifest schema")
|
|
artifact_record = document["artifact"]
|
|
if not re.fullmatch(r"[0-9a-f]{64}", artifact_record["sha256"]):
|
|
fail("invalid artifact SHA-256")
|
|
if not re.fullmatch(r"[0-9a-f]{40}", document["source"]["commit"]):
|
|
fail("invalid source commit")
|
|
if document["source"].get("dirty") is not False:
|
|
fail("artifact source must be clean")
|
|
if not isinstance(document["execution"].get("execution_eligible"), bool):
|
|
fail("artifact execution eligibility must be an explicit boolean")
|
|
if any(document["execution"].get(key) is not False for key in
|
|
("authorized", "transferred", "executed")):
|
|
fail("artifact manifest claims execution authority or activity")
|
|
if document["safety"].get("direct_gnm_imports") != 0:
|
|
fail("artifact manifest claims direct GNM imports")
|
|
gate = document["firmware_gate"]
|
|
if gate["embedded_identifier"] == "NONE" and gate["allowlisted"] is not False:
|
|
fail("NONE firmware gate cannot be allowlisted")
|
|
|
|
if args.artifact is not None:
|
|
artifact = args.artifact.resolve(strict=True)
|
|
if artifact.name != artifact_record["filename"]:
|
|
fail("artifact filename mismatch")
|
|
if artifact.stat().st_size != artifact_record["size"]:
|
|
fail("artifact size mismatch")
|
|
if hash_file(artifact) != artifact_record["sha256"]:
|
|
fail("artifact digest mismatch")
|
|
|
|
print(f"verified artifact manifest: {artifact_record['id']}")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
try:
|
|
raise SystemExit(main())
|
|
except (KeyError, TypeError, ValueError, json.JSONDecodeError) as error:
|
|
print(f"artifact verification failed: {error}")
|
|
raise SystemExit(1) from error
|