Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -635,3 +635,63 @@ jobs:
run: nix develop .#ci -c ./scripts/ci/verify-release-artifacts.sh artifacts macos ios web latest-json
env:
MAPLE_ENFORCE_IOS_SIGNED_REPRODUCIBILITY: "1"

publish-release-verification-guide:
needs: verify-release-artifacts
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write
attestations: write
artifact-metadata: write
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # was v4
with:
persist-credentials: false

- name: Install Nix
uses: DeterminateSystems/nix-installer-action@ef8a148080ab6020fd15196c2084a2eea5ff2d25 # was v22
with:
github-token: ""

- name: Download release artifacts
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_TAG: ${{ github.event.release.tag_name }}
run: |
mkdir -p artifacts
gh release download "${RELEASE_TAG}" -D artifacts
rm -f \
artifacts/MAPLE_RELEASE_VERIFICATION.md \
artifacts/MAPLE_RELEASE_VERIFICATION.md.sha256

- name: Generate release verification guide
env:
RELEASE_TAG: ${{ github.event.release.tag_name }}
run: |
nix develop .#ci -c ./scripts/ci/release-verification-guide.sh \
artifacts \
MAPLE_RELEASE_VERIFICATION.md
sha256sum MAPLE_RELEASE_VERIFICATION.md > MAPLE_RELEASE_VERIFICATION.md.sha256
sha256sum \
MAPLE_RELEASE_VERIFICATION.md \
MAPLE_RELEASE_VERIFICATION.md.sha256 \
> release-verification-guide-artifacts.sha256
cat release-verification-guide-artifacts.sha256

- name: Attest release verification guide
# Keep the guide upload running if GitHub token policy rejects artifact attestation.
continue-on-error: true
uses: actions/attest@281a49d4cbb0a72c9575a50d18f6deb515a11deb # was v4
with:
subject-checksums: release-verification-guide-artifacts.sha256

- name: Upload release verification guide
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_TAG: ${{ github.event.release.tag_name }}
run: |
gh release upload "${RELEASE_TAG}" \
MAPLE_RELEASE_VERIFICATION.md \
MAPLE_RELEASE_VERIFICATION.md.sha256 \
--clobber
15 changes: 12 additions & 3 deletions scripts/ci/ios-release.sh
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,8 @@ write_ios_canonical_app_manifest_diff() {
sed -E 's/^([0-9a-f]{64}) (.*)$/\2 \1/' "${signed_manifest}" | LC_ALL=C sort > "${signed_by_path}"
comm -3 "${unsigned_by_path}" "${signed_by_path}" > "${out}"
if [ ! -s "${out}" ]; then
printf 'No canonical file manifest differences after stripping Apple signing metadata.\n' > "${out}"
rm -f "${out}"
printf 'verified-ios-canonical-file-manifest-no-diff %s %s\n' "$(basename "${unsigned_manifest}")" "$(basename "${signed_manifest}")"
fi
rm -f "${unsigned_by_path}" "${signed_by_path}"
}
Expand Down Expand Up @@ -200,7 +201,11 @@ if [ "${signed_app_canonical_hash}" != "${unsigned_app_canonical_hash}" ]; then
echo "unsigned=${unsigned_app_canonical_hash}" >&2
echo "archive_canonical=${signed_app_canonical_hash}" >&2
echo "Canonical iOS archive app file manifest diff diagnostic:" >&2
sed -n '1,80p' "${repro_dir}/ios-release-archive-app-vs-unsigned-canonical.diff.txt" >&2
if [ -s "${repro_dir}/ios-release-archive-app-vs-unsigned-canonical.diff.txt" ]; then
sed -n '1,80p' "${repro_dir}/ios-release-archive-app-vs-unsigned-canonical.diff.txt" >&2
else
echo "No canonical iOS archive app file manifest diff was produced." >&2
fi
else
printf 'verified-ios-archive-app %s %s\n' "${signed_app_canonical_hash}" "$(repo_relative_path "${signed_app}")"
fi
Expand Down Expand Up @@ -235,7 +240,11 @@ for artifact in "${ios_artifacts[@]}"; do
echo "unsigned=${unsigned_app_canonical_hash}" >&2
echo "ipa_payload=${ipa_canonical_hash}" >&2
echo "Canonical iOS IPA payload file manifest diff diagnostic:" >&2
sed -n '1,80p' "${payload_diff}" >&2
if [ -s "${payload_diff}" ]; then
sed -n '1,80p' "${payload_diff}" >&2
else
echo "No canonical iOS IPA payload file manifest diff was produced." >&2
fi
if [ "${MAPLE_ENFORCE_IOS_SIGNED_REPRODUCIBILITY:-0}" = "1" ]; then
exit 1
fi
Expand Down
141 changes: 141 additions & 0 deletions scripts/ci/release-verification-guide.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
#!/usr/bin/env bash
set -euo pipefail

source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_common.sh"

usage() {
cat >&2 <<'EOF'
usage: release-verification-guide.sh <artifacts-dir> <out.md>

Generates the user-facing release verification guide that is uploaded as a
release asset after CI verifies the release artifacts.
EOF
}

artifacts_dir="${1:-}"
out="${2:-}"

if [ -z "${artifacts_dir}" ] || [ -z "${out}" ] || [ "${artifacts_dir}" = "-h" ] || [ "${artifacts_dir}" = "--help" ]; then
usage
exit 2
fi

if [ ! -d "${artifacts_dir}" ]; then
echo "Artifacts directory does not exist: ${artifacts_dir}" >&2
exit 1
fi

repo="${GITHUB_REPOSITORY:-OpenSecretCloud/Maple}"
release_tag="${RELEASE_TAG:-${GITHUB_REF_NAME:-}}"
if [ -z "${release_tag}" ]; then
echo "RELEASE_TAG or GITHUB_REF_NAME is required." >&2
exit 1
fi

source_commit="$(git -C "${REPO_ROOT}" rev-parse HEAD)"
release_url="https://github.com/${repo}/releases/tag/${release_tag}"
repo_url="https://github.com/${repo}.git"
source_ref="refs/tags/${release_tag}"
clone_dir="maple-${release_tag}"

asset_names=()
while IFS= read -r -d '' file; do
asset_names+=("$(basename "${file}")")
done < <(find "${artifacts_dir}" -maxdepth 1 -type f -print0 | LC_ALL=C sort -z)

mkdir -p "$(dirname "${out}")"

{
cat <<EOF
# Maple ${release_tag} Release Verification

This guide was generated by release CI after Maple's release artifact verifier passed.

- Repository: \`${repo}\`
- Release: ${release_url}
- Source ref: \`${source_ref}\`
- Source commit: \`${source_commit}\`

## What The Proofs Cover

The release contains final SHA-256 manifests, canonical payload manifests, Tauri updater signatures, and GitHub artifact attestations. Together they let you check that downloaded bytes match the release, that updater signatures match \`latest.json\`, and that signed Android, macOS, and iOS payloads strip back to the reproducible unsigned build products where that platform supports the check.

GitHub attestations prove that release CI produced the published bytes for this tag. The Maple verifier then recomputes the local hashes and canonical payload proofs from the files you downloaded.

## Download The Release And Verifier

Install Nix with flakes enabled, then run:

\`\`\`bash
git clone ${repo_url} ${clone_dir}
cd ${clone_dir}
git checkout ${release_tag}
mkdir -p artifacts
gh release download ${release_tag} --repo ${repo} --dir artifacts
\`\`\`

The \`gh release download\` command is the easiest path because it downloads the installers and the matching \`.sha256\` proof files together.

## Verify GitHub Attestations

To verify that each downloaded release asset has a GitHub provenance attestation for this tag:

\`\`\`bash
for file in artifacts/*; do
gh attestation verify "\${file}" --repo ${repo} --source-ref ${source_ref}
done
\`\`\`

## Verify Reproducibility Proofs

For a broad check of every proof file present in your download:

\`\`\`bash
MAPLE_VERIFY_ALLOW_PLATFORM_SKIPS=1 \\
nix develop .#ci -c ./scripts/ci/verify-release-artifacts.sh artifacts present
\`\`\`

That command verifies portable hashes and skips host-specific checks that cannot run on your current operating system. For stronger platform checks, run the command for your target platform:

\`\`\`bash
# Linux desktop installers: verifies final hashes, Tauri updater signatures,
# AppImage executable metadata, and .deb executable metadata.
nix develop .#desktop-linux -c ./scripts/ci/verify-release-artifacts.sh artifacts linux

# macOS desktop installers: run on macOS with Xcode selected.
nix develop .#ci -c ./scripts/ci/verify-release-artifacts.sh artifacts macos

# iOS IPA: run on macOS with Xcode selected.
MAPLE_ENFORCE_IOS_SIGNED_REPRODUCIBILITY=1 \\
nix develop .#ci -c ./scripts/ci/verify-release-artifacts.sh artifacts ios

# Android APK/AAB: verifies final hashes, signed-vs-unsigned canonical payloads,
# and Android signatures when Android SDK tools are available.
MAPLE_REQUIRE_ANDROID_SIGNATURE_VERIFICATION=1 \\
nix develop .#android -c ./scripts/ci/verify-release-artifacts.sh artifacts android

# Web archive and Tauri updater metadata.
nix develop .#ci -c ./scripts/ci/verify-release-artifacts.sh artifacts web latest-json
\`\`\`

Successful checks print \`verified-...\` lines and exit with status 0. A mismatch exits non-zero and prints the expected and actual digest.

## Release Assets Seen By CI

EOF

if [ "${#asset_names[@]}" -eq 0 ]; then
printf 'No release assets were present when this guide was generated.\n'
else
for name in "${asset_names[@]}"; do
printf -- '- `%s`\n' "${name}"
done
fi

cat <<'EOF'

If an Apple `*.diff.txt` diagnostic file appears in a release, treat it as a reproducibility failure artifact to inspect. Successful Apple signed-vs-unsigned canonical comparisons should not publish no-op diff files.
EOF
} > "${out}"

printf 'wrote-release-verification-guide %s\n' "${out}"
Loading