#!/usr/bin/env bash set -euo pipefail # Offline verifier for Export Center bundles (EC10) # Usage: VERIFY_FIXTURE=1 ./verify-export-kit.sh [kit_dir] kit_dir="${1:-src/ExportCenter/__fixtures/export-kit}" for f in manifest.json manifest.sha256 provenance.json manifest.dsse; do if [[ ! -f "${kit_dir}/${f}" ]]; then echo "missing ${kit_dir}/${f}" >&2 exit 1 fi done manifest_sha="$(sha256sum "${kit_dir}/manifest.json" | awk '{print $1}')" expected_sha="$(awk '{print $1}' "${kit_dir}/manifest.sha256")" if [[ "${manifest_sha}" != "${expected_sha}" ]]; then echo "manifest hash mismatch: expected ${expected_sha} got ${manifest_sha}" >&2 exit 1 fi python - <<'PY' "${kit_dir}" "${manifest_sha}" import base64, hashlib, json, sys, pathlib kit = pathlib.Path(sys.argv[1]) manifest_sha = sys.argv[2] manifest = json.loads(kit.joinpath("manifest.json").read_text()) errors = [] selectors = manifest.get("selectors", {}) tenants = selectors.get("tenants", []) tenant = manifest.get("tenant") if tenant and tenants and tenant not in tenants and "*" not in tenants[0]: errors.append(f"tenant {tenant} not included in selectors.tenants {tenants}") digests = [] for item in manifest.get("contents", []): digest = item.get("digest", "") if not digest.startswith("sha256:"): errors.append(f"invalid digest format for {item.get('path')}") continue digests.append(digest.split("sha256:")[1]) rerun_calc = hashlib.sha256("\n".join(sorted(digests)).encode()).hexdigest() rerun_expected = manifest.get("rerunHash") if rerun_expected != f"sha256:{rerun_calc}": errors.append(f"rerunHash mismatch: expected sha256:{rerun_calc} got {rerun_expected}") annotations = manifest["integrity"]["oci"]["annotations"] manifest_digest_hex = annotations["io.stellaops.export.manifest-digest"].split("sha256:")[1] digest_hdr = manifest["integrity"]["httpHeaders"]["Digest"] expected_hdr = "sha-256=" + base64.b64encode(bytes.fromhex(manifest_digest_hex)).decode() if digest_hdr != expected_hdr: errors.append("Digest header does not match manifest digest annotation") log_meta = manifest.get("attestations", {}).get("log") if not log_meta: errors.append("attestation log metadata missing") if not manifest.get("quotas"): errors.append("quotas/backpressure block missing") if len(set(tenants)) > 1 and not manifest.get("approval", {}).get("required"): errors.append("cross-tenant approval required but not present") if errors: for err in errors: print(err) sys.exit(1) print("manifest checks ok") PY python - <<'PY' "${kit_dir}" "${manifest_sha}" import json, sys, pathlib kit = pathlib.Path(sys.argv[1]) manifest_sha = sys.argv[2] prov = json.loads(kit.joinpath("provenance.json").read_text()) manifest_subject = prov["subject"][0]["digest"]["sha256"] if manifest_subject != manifest_sha: print(f"provenance manifest digest mismatch: {manifest_subject} vs {manifest_sha}") sys.exit(1) log = prov["predicate"]["environment"]["logs"] required = ["kind", "logId", "logIndex", "entryDigest", "timestamp"] missing = [k for k in required if k not in log] if missing: print(f"provenance log metadata missing keys: {missing}") sys.exit(1) print("provenance checks ok") PY echo "verify-export-kit: PASS"