Skip to content

Decode Keys Manifest #2

Decode Keys Manifest

Decode Keys Manifest #2

Workflow file for this run

name: Decode Keys Manifest
# Signs and publishes the trusted-keys manifest (`decode-keys.json`) that lists
# the decode-publisher keys the app trusts. Separate channel from app releases
# (`registry-keys-<date>`, not `v*`) so rotating publisher keys never touches
# an app build. This workflow ships inert: `SIGNING_PRIVATE_KEY_B64` (the
# pinned root key) is not configured until go-live, so a run with no secret
# fails at the signing step and nothing is published.
on:
workflow_dispatch:
schedule:
# Weekly refresh so `issuedAt`/`expiresAt` never drift stale even with no
# key rotation.
- cron: "0 6 * * 1"
concurrency:
group: decode-keys
cancel-in-progress: false
permissions:
contents: write
env:
MANIFEST_SCHEMA: 1
ROOT_KEY_ID: cosign-root-2026
MANIFEST_VALID_DAYS: 14
jobs:
publish:
name: Sign and publish the trusted-keys manifest
runs-on: macos-26
# Authorization is the ios-release environment's required reviewers: GitHub
# pauses this job for the trusted operator's manual approval before it runs,
# on both a manual dispatch and the scheduled refresh. No in-workflow gate.
environment: ios-release
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Stamp issuedAt/expiresAt and assemble the manifest
run: |
set -euo pipefail
MANIFEST_DIR="$RUNNER_TEMP/decode-keys"
rm -rf "$MANIFEST_DIR"; mkdir -p "$MANIFEST_DIR"
echo "MANIFEST_DIR=${MANIFEST_DIR}" >> "$GITHUB_ENV"
ISSUED_AT="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
EXPIRES_AT="$(date -u -v+"${MANIFEST_VALID_DAYS}"d +%Y-%m-%dT%H:%M:%SZ)"
CHANNEL="registry-keys-$(date -u +%Y-%m-%d)"
echo "CHANNEL=${CHANNEL}" >> "$GITHUB_ENV"
# The trusted decode-publisher keys. verify_manifest checks the served
# bundle's signature against these (keyed by keyId); retire a key by
# marking it "revoked" or dropping it from a later issued manifest.
jq -n \
--argjson schema "$MANIFEST_SCHEMA" \
--arg issuedAt "$ISSUED_AT" \
--arg expiresAt "$EXPIRES_AT" \
--arg rootKeyId "$ROOT_KEY_ID" \
'{schema: $schema, issuedAt: $issuedAt, expiresAt: $expiresAt, rootKeyId: $rootKeyId, publishers: [{keyId: "cosign-registry-2026", publicKey: "G73jaV/CvI6Ew1Foe50N1Kutx1ousZ9S8+2UqG3pxgQ=", status: "active"}]}' \
> "$MANIFEST_DIR/decode-keys.json"
cat "$MANIFEST_DIR/decode-keys.json"
- name: Sign the manifest with SignJsonFile
env:
SIGNING_PRIVATE_KEY_B64: ${{ secrets.SIGNING_PRIVATE_KEY_B64 }}
run: |
set -euo pipefail
if [[ -z "${SIGNING_PRIVATE_KEY_B64}" ]]; then
echo "SIGNING_PRIVATE_KEY_B64 is not configured; the decode-keys manifest ships inert until go-live" >&2
exit 1
fi
xcrun swiftc Tools/SignJsonFile.swift -o "$RUNNER_TEMP/sign-json-file"
"$RUNNER_TEMP/sign-json-file" \
"$MANIFEST_DIR/decode-keys.json" \
"$MANIFEST_DIR/decode-keys.sig"
shasum -a 256 "$MANIFEST_DIR/decode-keys.json" "$MANIFEST_DIR/decode-keys.sig"
- name: Publish the registry-keys release
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
gh release create "$CHANNEL" \
--title "$CHANNEL" \
--notes "Trusted-keys manifest for the decode registry. Signed by the pinned root key ($ROOT_KEY_ID); the relay serves this at /cosign/v1/decode-keys. Separate from app releases (v*)." \
"$MANIFEST_DIR/decode-keys.json" \
"$MANIFEST_DIR/decode-keys.sig"