#!/usr/bin/env bash set -euo pipefail # Allow CI to fall back to a deterministic test key when MIRROR_SIGN_KEY_B64 is unset, # but forbid this on release/tag builds when REQUIRE_PROD_SIGNING=1. # Throwaway dev key (Ed25519) generated 2025-11-23; matches the value documented in # docs/modules/mirror/signing-runbook.md. Safe for non-production smoke only. DEFAULT_TEST_KEY_B64="LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1DNENBUUF3QlFZREsyVndCQ0lFSURqb3pDRVdKVVFUdW1xZ2gyRmZXcVBaemlQbkdaSzRvOFZRTThGYkZCSEcKLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLQo=" if [[ -z "${MIRROR_SIGN_KEY_B64:-}" ]]; then if [[ "${REQUIRE_PROD_SIGNING:-0}" == "1" ]]; then echo "[error] MIRROR_SIGN_KEY_B64 is required for production signing; refusing to use test key." >&2 exit 1 fi echo "[warn] MIRROR_SIGN_KEY_B64 not set; using embedded test key (non-production) for CI signing" >&2 MIRROR_SIGN_KEY_B64="$DEFAULT_TEST_KEY_B64" fi ROOT=$(cd "$(dirname "$0")/../.." && pwd) KEYDIR="$ROOT/out/mirror/thin/tuf/keys" mkdir -p "$KEYDIR" KEYFILE="$KEYDIR/ci-ed25519.pem" printf "%s" "$MIRROR_SIGN_KEY_B64" | base64 -d > "$KEYFILE" chmod 600 "$KEYFILE" # Export public key for TUF keyid calculation openssl pkey -in "$KEYFILE" -pubout -out "$KEYDIR/ci-ed25519.pub" >/dev/null 2>&1 STAGE=${STAGE:-$ROOT/out/mirror/thin/stage-v1} CREATED=${CREATED:-$(date -u +%Y-%m-%dT%H:%M:%SZ)} TENANT_SCOPE=${TENANT_SCOPE:-tenant-demo} ENV_SCOPE=${ENV_SCOPE:-lab} CHUNK_SIZE=${CHUNK_SIZE:-5242880} CHECKPOINT_FRESHNESS=${CHECKPOINT_FRESHNESS:-86400} OCI=${OCI:-1} SIGN_KEY="$KEYFILE" STAGE="$STAGE" CREATED="$CREATED" TENANT_SCOPE="$TENANT_SCOPE" ENV_SCOPE="$ENV_SCOPE" CHUNK_SIZE="$CHUNK_SIZE" CHECKPOINT_FRESHNESS="$CHECKPOINT_FRESHNESS" OCI="$OCI" "$ROOT/src/Mirror/StellaOps.Mirror.Creator/make-thin-v1.sh" # Default to staged time-anchor unless caller overrides TIME_ANCHOR_FILE=${TIME_ANCHOR_FILE:-$ROOT/out/mirror/thin/stage-v1/layers/time-anchor.json} # Emit milestone summary with hashes for downstream consumers MANIFEST_PATH="$ROOT/out/mirror/thin/mirror-thin-v1.manifest.json" TAR_PATH="$ROOT/out/mirror/thin/mirror-thin-v1.tar.gz" DSSE_PATH="$ROOT/out/mirror/thin/mirror-thin-v1.manifest.dsse.json" BUNDLE_PATH="$ROOT/out/mirror/thin/mirror-thin-v1.bundle.json" BUNDLE_DSSE_PATH="$ROOT/out/mirror/thin/mirror-thin-v1.bundle.dsse.json" TIME_ANCHOR_DSSE_PATH="$TIME_ANCHOR_FILE.dsse.json" TRANSPORT_PATH="$ROOT/out/mirror/thin/stage-v1/layers/transport-plan.json" REKOR_POLICY_PATH="$ROOT/out/mirror/thin/stage-v1/layers/rekor-policy.json" MIRROR_POLICY_PATH="$ROOT/out/mirror/thin/stage-v1/layers/mirror-policy.json" OFFLINE_POLICY_PATH="$ROOT/out/mirror/thin/stage-v1/layers/offline-kit-policy.json" SUMMARY_PATH="$ROOT/out/mirror/thin/milestone.json" sha256() { sha256sum "$1" | awk '{print $1}' } # Sign manifest, bundle meta, and time-anchor (if present) python "$ROOT/scripts/mirror/sign_thin_bundle.py" \ --key "$KEYFILE" \ --manifest "$MANIFEST_PATH" \ --tar "$TAR_PATH" \ --tuf-dir "$ROOT/out/mirror/thin/tuf" \ --bundle "$BUNDLE_PATH" \ --time-anchor "$TIME_ANCHOR_FILE" # Normalize time-anchor DSSE location for bundle meta/summary if [[ -f "$TIME_ANCHOR_FILE.dsse.json" ]]; then cp "$TIME_ANCHOR_FILE.dsse.json" "$TIME_ANCHOR_DSSE_PATH" fi # Refresh bundle meta hashes now that DSSE files exist python - <<'PY' import json, pathlib, hashlib root = pathlib.Path("$ROOT") bundle_path = pathlib.Path("$BUNDLE_PATH") manifest_dsse = pathlib.Path("$DSSE_PATH") bundle_dsse = pathlib.Path("$BUNDLE_DSSE_PATH") time_anchor_dsse = pathlib.Path("$TIME_ANCHOR_DSSE_PATH") def sha(path: pathlib.Path) -> str: h = hashlib.sha256() with path.open('rb') as f: for chunk in iter(lambda: f.read(8192), b''): h.update(chunk) return h.hexdigest() data = json.loads(bundle_path.read_text()) art = data.setdefault('artifacts', {}) if manifest_dsse.exists(): art.setdefault('manifest_dsse', {})['sha256'] = sha(manifest_dsse) if bundle_dsse.exists(): art.setdefault('bundle_dsse', {})['sha256'] = sha(bundle_dsse) if time_anchor_dsse.exists(): art.setdefault('time_anchor_dsse', {})['sha256'] = sha(time_anchor_dsse) bundle_path.write_text(json.dumps(data, indent=2, sort_keys=True) + "\n") sha_path = bundle_path.with_suffix(bundle_path.suffix + '.sha256') sha_path.write_text(f"{sha(bundle_path)} {bundle_path.name}\n") PY cat > "$SUMMARY_PATH" <