From da5650ceb439786b0b3477e3216692f5157225f8 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 29 Jul 2026 02:06:43 +0200 Subject: [PATCH] fix: keep release signing local --- .github/workflows/release.yml | 94 +++++-------- docs/reproducible-builds.md | 124 +++++++++++++++--- .../prepare-github-release.sh | 63 +++++++++ tools/reproducible-builds/sign-play-bundle.sh | 89 +++++++++++++ tools/reproducible-builds/sign-release.sh | 55 +++++--- .../verify-github-release.sh | 20 ++- 6 files changed, 339 insertions(+), 106 deletions(-) create mode 100755 tools/reproducible-builds/prepare-github-release.sh create mode 100755 tools/reproducible-builds/sign-play-bundle.sh diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a0d28c95..ccf20b33 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,7 +4,7 @@ on: workflow_dispatch: inputs: tag: - description: Existing vX.Y.Z tag to build and publish + description: Existing vX.Y.Z tag to build and attest required: true type: string push: @@ -15,7 +15,6 @@ permissions: contents: read env: - JAVA_VERSION: 21.0.11+10 RELEASE_TAG: ${{ inputs.tag || github.ref_name }} jobs: @@ -40,6 +39,14 @@ jobs: with: ref: ${{ env.RELEASE_TAG }} + - name: Verify workflow provenance ref + run: | + checked_out_commit="$(git rev-parse HEAD)" + if [ "$checked_out_commit" != "$GITHUB_SHA" ]; then + echo "error: run the workflow from the $RELEASE_TAG tag ref so provenance identifies the built commit" >&2 + exit 1 + fi + - name: Validate the Gradle wrapper uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0 with: @@ -60,6 +67,10 @@ jobs: name: Verify reproducibility runs-on: ubuntu-24.04 needs: build + permissions: + attestations: write + contents: read + id-token: write steps: - name: Checkout verification script @@ -82,6 +93,23 @@ jobs: - name: Compare every canonical byte run: tools/reproducible-builds/compare-release.sh "$RUNNER_TEMP/release-a" "$RUNNER_TEMP/release-b" + - name: Prepare public attestation subjects + run: | + mkdir "$RUNNER_TEMP/attestation-subjects" + cp "$RUNNER_TEMP/release-a/BUILDINFO.json" \ + "$RUNNER_TEMP/attestation-subjects/BITCHAT_BUILDINFO.json" + cp "$RUNNER_TEMP/release-a/SHA256SUMS.unsigned" \ + "$RUNNER_TEMP/attestation-subjects/BITCHAT_SHA256SUMS.unsigned" + cp "$RUNNER_TEMP"/release-a/bitchat-android-*-unsigned.apk \ + "$RUNNER_TEMP/attestation-subjects/" + cp "$RUNNER_TEMP/release-a/bitchat-android-release-unsigned.aab" \ + "$RUNNER_TEMP/attestation-subjects/" + + - name: Attest verified unsigned release + uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3 + with: + subject-path: ${{ runner.temp }}/attestation-subjects/* + - name: Upload verified unsigned release uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: @@ -89,65 +117,3 @@ jobs: path: ${{ runner.temp }}/release-a/* retention-days: 30 if-no-files-found: error - - sign-and-release: - name: Sign, attest, and publish - runs-on: ubuntu-24.04 - needs: compare - permissions: - attestations: write - contents: write - id-token: write - - steps: - - name: Checkout tagged source - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - with: - ref: ${{ env.RELEASE_TAG }} - - - name: Set up pinned JDK - uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5 - with: - distribution: temurin - java-version: ${{ env.JAVA_VERSION }} - - - name: Download verified unsigned release - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 - with: - name: verified-unsigned-release - path: release - - - name: Install checksum-verified Android Build Tools - run: tools/reproducible-builds/install-android-build-tools.sh "$RUNNER_TEMP/android-sdk" - - - name: Deterministically sign verified GitHub APKs - env: - ANDROID_SDK_ROOT: ${{ runner.temp }}/android-sdk - BITCHAT_SIGNING_KEY_BASE64: ${{ secrets.SIGNING_KEY }} - BITCHAT_SIGNING_KEY_ALIAS: ${{ secrets.ALIAS }} - BITCHAT_KEYSTORE_PASSWORD: ${{ secrets.KEY_STORE_PASSWORD }} - BITCHAT_KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }} - run: tools/reproducible-builds/sign-release.sh release - - - name: Prepare public manifest names - run: | - mv release/BUILDINFO.json release/BITCHAT_BUILDINFO.json - mv release/SHA256SUMS.unsigned release/BITCHAT_SHA256SUMS.unsigned - sed \ - -e 's/ BUILDINFO.json$/ BITCHAT_BUILDINFO.json/' \ - -e 's/ SHA256SUMS.unsigned$/ BITCHAT_SHA256SUMS.unsigned/' \ - release/SHA256SUMS > release/BITCHAT_SHA256SUMS - rm release/SHA256SUMS - - - name: Attest release provenance - uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3 - with: - subject-path: release/* - - - name: Publish GitHub release - env: - GH_TOKEN: ${{ github.token }} - run: | - gh release create "$RELEASE_TAG" release/* \ - --title "Release $RELEASE_TAG" \ - --notes "Signed installable APKs, canonical unsigned APK/AAB inputs, checksums, and GitHub build-provenance attestations are attached. See docs/reproducible-builds.md for verification instructions." diff --git a/docs/reproducible-builds.md b/docs/reproducible-builds.md index 4286510c..170a647d 100644 --- a/docs/reproducible-builds.md +++ b/docs/reproducible-builds.md @@ -2,13 +2,13 @@ Bitchat's canonical release build produces byte-for-byte reproducible unsigned APKs and an unsigned Android App Bundle (AAB). CI builds the release twice in -independent jobs and refuses to publish it unless every canonical byte matches. +independent jobs and exposes a verified release artifact only when every +canonical byte matches. -The signing key is intentionally not public. Anyone can reproduce the unsigned -artifacts; GitHub Actions signs only the already-verified APKs, checks that -signing itself is deterministic, verifies the expected certificate, and -publishes checksums and provenance attestations for both unsigned and signed -artifacts. +Signing remains local: no keystore or signing password is stored in or exposed +to GitHub Actions. Anyone can reproduce the unsigned artifacts; maintainers +download the verified CI output, sign the selected GitHub APKs and Play upload +AAB locally, then manually publish those exact files. ## What is pinned @@ -97,7 +97,8 @@ tools/reproducible-builds/verify-github-release.sh vX.Y.Z That command: 1. downloads all release APKs, the AAB, build information, and checksum files; -2. verifies each GitHub artifact-attestation subject against this repository; +2. verifies the canonical unsigned build's GitHub artifact-attestation subjects + against this repository; 3. verifies `BITCHAT_SHA256SUMS`; 4. checks that the local source commit is the release commit; 5. rebuilds in the pinned container; and @@ -137,7 +138,7 @@ GitHub's manual equivalents are: ```bash gh release download vX.Y.Z sha256sum -c BITCHAT_SHA256SUMS -gh attestation verify bitchat-android-universal.apk \ +gh attestation verify bitchat-android-universal-unsigned.apk \ --repo permissionlesstech/bitchat-android ``` @@ -195,8 +196,9 @@ maintainers to retain the uploaded AAB, publish its digest and provenance, and record the Play version code and app-signing certificate fingerprint alongside the release. -The current GitHub release workflow publishes the canonical unsigned AAB for -verification; it does not upload to Google Play. +The GitHub workflow builds and attests the canonical unsigned AAB. A maintainer +locally creates `bitchat-android-play-upload.aab` from that exact file and +uploads it manually to Google Play. ## Maintainer release process @@ -204,15 +206,101 @@ Pushing a `vX.Y.Z` tag runs `.github/workflows/release.yml`: 1. two jobs independently build the canonical unsigned release; 2. a comparison job requires exact byte equality; -3. the signing job reads `SIGNING_KEY`, `ALIAS`, `KEY_STORE_PASSWORD`, and - `KEY_PASSWORD` from GitHub Actions secrets; -4. each published GitHub APK is signed twice and the two results must match; -5. the signing certificate must match the public fingerprint in - `gradle.properties`; -6. GitHub generates build-provenance attestations and publishes all artifacts. +3. GitHub generates provenance attestations for the canonical unsigned + artifacts; and +4. Actions uploads `verified-unsigned-release` for the maintainer to download. -Never store a keystore or password in the repository, workflow artifacts, logs, -or build information. +The workflow has read-only repository access and no signing secrets. It does not +create a GitHub Release or upload to Google Play. + +To rerun an existing tag manually, dispatch the workflow against the tag ref +(not the default branch): + +```bash +gh workflow run release.yml \ + --repo permissionlesstech/bitchat-android \ + --ref vX.Y.Z \ + -f tag=vX.Y.Z +``` + +The workflow rejects a dispatch whose selected ref and requested tag resolve to +different commits, keeping the GitHub provenance tied to the source actually +built. + +### 1. Download the verified build + +Find the completed tag workflow's run ID, then download the promoted replica: + +```bash +gh run download RUN_ID \ + --repo permissionlesstech/bitchat-android \ + --name verified-unsigned-release \ + --dir release +``` + +Verify `release/SHA256SUMS.unsigned` before signing. + +### 2. Sign GitHub APKs locally + +Install Android Build Tools 37.0.0 and set these local environment variables: + +- `ANDROID_SDK_ROOT` +- `BITCHAT_GITHUB_KEYSTORE` +- `BITCHAT_GITHUB_KEY_ALIAS` +- `BITCHAT_GITHUB_KEYSTORE_PASSWORD` +- `BITCHAT_GITHUB_KEY_PASSWORD` + +Then run: + +```bash +tools/reproducible-builds/sign-release.sh release +``` + +The helper signs the arm64, x86_64, and universal APKs twice, requires the two +signed results to match, and verifies their certificate against +`BITCHAT_GITHUB_RELEASE_CERT_SHA256` in `gradle.properties`. + +### 3. Sign the Play AAB locally + +Use JDK 21 and the Play upload key—not the Play app-signing key managed by +Google. Set: + +- `JAVA_HOME` +- `BITCHAT_PLAY_UPLOAD_KEYSTORE` +- `BITCHAT_PLAY_UPLOAD_KEY_ALIAS` +- `BITCHAT_PLAY_KEYSTORE_PASSWORD` +- `BITCHAT_PLAY_KEY_PASSWORD` + +Then run: + +```bash +tools/reproducible-builds/sign-play-bundle.sh release +``` + +This creates `release/bitchat-android-play-upload.aab`, verifies its JAR +signature, proves that its non-signature payload matches the canonical unsigned +AAB, and updates `SHA256SUMS`. Upload that exact signed AAB to Play Console; do +not rebuild it in Android Studio. + +### 4. Prepare and publish the GitHub Release + +After both local signing steps: + +```bash +tools/reproducible-builds/prepare-github-release.sh release +gh release create vX.Y.Z release/* \ + --repo permissionlesstech/bitchat-android \ + --verify-tag \ + --draft \ + --title "Release vX.Y.Z" \ + --notes "See docs/reproducible-builds.md for verification instructions." +``` + +Review the draft assets and Play internal-track result before publishing the +GitHub Release and promoting the Play rollout. + +Never store a keystore or password in the repository, GitHub Actions secrets, +workflow artifacts, logs, release notes, or build information. ## Updating dependencies or toolchains diff --git a/tools/reproducible-builds/prepare-github-release.sh b/tools/reproducible-builds/prepare-github-release.sh new file mode 100755 index 00000000..9d1b92e2 --- /dev/null +++ b/tools/reproducible-builds/prepare-github-release.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash + +set -euo pipefail + +RELEASE_DIR="${1:?usage: prepare-github-release.sh RELEASE_DIR}" + +if [ ! -d "$RELEASE_DIR" ]; then + echo "error: release directory not found" >&2 + exit 1 +fi +RELEASE_DIR="$(cd "$RELEASE_DIR" && pwd)" + +if command -v sha256sum >/dev/null 2>&1; then + SHA256=(sha256sum) +elif command -v shasum >/dev/null 2>&1; then + SHA256=(shasum -a 256) +else + echo "error: sha256sum or shasum is required" >&2 + exit 1 +fi + +required=( + BUILDINFO.json + SHA256SUMS + SHA256SUMS.unsigned + bitchat-android-arm64.apk + bitchat-android-play-upload.aab + bitchat-android-universal.apk + bitchat-android-x86_64.apk +) +for artifact in "${required[@]}"; do + if [ ! -f "$RELEASE_DIR/$artifact" ]; then + echo "error: required release artifact missing: $artifact" >&2 + exit 1 + fi +done + +for destination in BITCHAT_BUILDINFO.json BITCHAT_SHA256SUMS BITCHAT_SHA256SUMS.unsigned; do + if [ -e "$RELEASE_DIR/$destination" ]; then + echo "error: public release manifest already exists: $destination" >&2 + exit 1 + fi +done + +( + cd "$RELEASE_DIR" + "${SHA256[@]}" -c SHA256SUMS +) + +mv "$RELEASE_DIR/BUILDINFO.json" "$RELEASE_DIR/BITCHAT_BUILDINFO.json" +mv "$RELEASE_DIR/SHA256SUMS.unsigned" "$RELEASE_DIR/BITCHAT_SHA256SUMS.unsigned" +sed \ + -e 's/ BUILDINFO.json$/ BITCHAT_BUILDINFO.json/' \ + -e 's/ SHA256SUMS.unsigned$/ BITCHAT_SHA256SUMS.unsigned/' \ + "$RELEASE_DIR/SHA256SUMS" > "$RELEASE_DIR/BITCHAT_SHA256SUMS" +rm "$RELEASE_DIR/SHA256SUMS" + +( + cd "$RELEASE_DIR" + "${SHA256[@]}" -c BITCHAT_SHA256SUMS +) + +echo "Release assets are checksummed and ready for manual GitHub publication." diff --git a/tools/reproducible-builds/sign-play-bundle.sh b/tools/reproducible-builds/sign-play-bundle.sh new file mode 100755 index 00000000..fabb8409 --- /dev/null +++ b/tools/reproducible-builds/sign-play-bundle.sh @@ -0,0 +1,89 @@ +#!/usr/bin/env bash + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +RELEASE_DIR="${1:?usage: sign-play-bundle.sh RELEASE_DIR}" + +: "${BITCHAT_PLAY_UPLOAD_KEYSTORE:?BITCHAT_PLAY_UPLOAD_KEYSTORE is required}" +: "${BITCHAT_PLAY_UPLOAD_KEY_ALIAS:?BITCHAT_PLAY_UPLOAD_KEY_ALIAS is required}" +: "${BITCHAT_PLAY_KEYSTORE_PASSWORD:?BITCHAT_PLAY_KEYSTORE_PASSWORD is required}" +: "${BITCHAT_PLAY_KEY_PASSWORD:?BITCHAT_PLAY_KEY_PASSWORD is required}" + +if [ ! -d "$RELEASE_DIR" ]; then + echo "error: release directory not found" >&2 + exit 1 +fi +RELEASE_DIR="$(cd "$RELEASE_DIR" && pwd)" + +if [ ! -f "$BITCHAT_PLAY_UPLOAD_KEYSTORE" ]; then + echo "error: Play upload keystore not found" >&2 + exit 1 +fi +KEYSTORE="$( + cd "$(dirname "$BITCHAT_PLAY_UPLOAD_KEYSTORE")" + printf '%s/%s\n' "$PWD" "$(basename "$BITCHAT_PLAY_UPLOAD_KEYSTORE")" +)" + +if [ -n "${JAVA_HOME:-}" ] && [ -x "$JAVA_HOME/bin/jarsigner" ]; then + JARSIGNER="$JAVA_HOME/bin/jarsigner" +elif command -v jarsigner >/dev/null 2>&1; then + JARSIGNER="$(command -v jarsigner)" +else + echo "error: jarsigner is required; set JAVA_HOME to the pinned JDK" >&2 + exit 1 +fi + +if command -v sha256sum >/dev/null 2>&1; then + SHA256=(sha256sum) +elif command -v shasum >/dev/null 2>&1; then + SHA256=(shasum -a 256) +else + echo "error: sha256sum or shasum is required" >&2 + exit 1 +fi + +( + cd "$RELEASE_DIR" + "${SHA256[@]}" -c SHA256SUMS.unsigned +) + +UNSIGNED_AAB="$RELEASE_DIR/bitchat-android-release-unsigned.aab" +SIGNED_AAB="$RELEASE_DIR/bitchat-android-play-upload.aab" +if [ -e "$SIGNED_AAB" ]; then + echo "error: signed Play upload AAB already exists" >&2 + exit 1 +fi + +TEMP_DIR="$(mktemp -d)" +trap 'rm -rf "$TEMP_DIR"' EXIT +STOREPASS_FILE="$TEMP_DIR/storepass" +KEYPASS_FILE="$TEMP_DIR/keypass" +printf '%s\n' "$BITCHAT_PLAY_KEYSTORE_PASSWORD" > "$STOREPASS_FILE" +printf '%s\n' "$BITCHAT_PLAY_KEY_PASSWORD" > "$KEYPASS_FILE" +chmod 600 "$STOREPASS_FILE" "$KEYPASS_FILE" + +"$JARSIGNER" \ + -keystore "$KEYSTORE" \ + -storepass:file "$STOREPASS_FILE" \ + -keypass:file "$KEYPASS_FILE" \ + -digestalg SHA-256 \ + -signedjar "$SIGNED_AAB" \ + "$UNSIGNED_AAB" \ + "$BITCHAT_PLAY_UPLOAD_KEY_ALIAS" + +"$JARSIGNER" -verify "$SIGNED_AAB" >/dev/null +"$SCRIPT_DIR/compare-archive-payloads.sh" "$UNSIGNED_AAB" "$SIGNED_AAB" + +( + cd "$RELEASE_DIR" + { + for artifact in *; do + [ "$artifact" = "SHA256SUMS" ] && continue + [ -f "$artifact" ] || continue + "${SHA256[@]}" "$artifact" + done + } | sort -k2 > SHA256SUMS +) + +echo "Verified unsigned AAB was signed locally with the Play upload key and checksummed." diff --git a/tools/reproducible-builds/sign-release.sh b/tools/reproducible-builds/sign-release.sh index 854fd58a..226202c8 100755 --- a/tools/reproducible-builds/sign-release.sh +++ b/tools/reproducible-builds/sign-release.sh @@ -9,10 +9,19 @@ RELEASE_DIR="${1:?usage: sign-release.sh RELEASE_DIR}" # shellcheck disable=SC1091 source "$SCRIPT_DIR/TOOLCHAIN.env" -: "${BITCHAT_SIGNING_KEY_BASE64:?BITCHAT_SIGNING_KEY_BASE64 is required}" -: "${BITCHAT_SIGNING_KEY_ALIAS:?BITCHAT_SIGNING_KEY_ALIAS is required}" -: "${BITCHAT_KEYSTORE_PASSWORD:?BITCHAT_KEYSTORE_PASSWORD is required}" -: "${BITCHAT_KEY_PASSWORD:?BITCHAT_KEY_PASSWORD is required}" +: "${BITCHAT_GITHUB_KEYSTORE:?BITCHAT_GITHUB_KEYSTORE is required}" +: "${BITCHAT_GITHUB_KEY_ALIAS:?BITCHAT_GITHUB_KEY_ALIAS is required}" +: "${BITCHAT_GITHUB_KEYSTORE_PASSWORD:?BITCHAT_GITHUB_KEYSTORE_PASSWORD is required}" +: "${BITCHAT_GITHUB_KEY_PASSWORD:?BITCHAT_GITHUB_KEY_PASSWORD is required}" + +if [ ! -f "$BITCHAT_GITHUB_KEYSTORE" ]; then + echo "error: GitHub release keystore not found" >&2 + exit 1 +fi +KEYSTORE="$( + cd "$(dirname "$BITCHAT_GITHUB_KEYSTORE")" + printf '%s/%s\n' "$PWD" "$(basename "$BITCHAT_GITHUB_KEYSTORE")" +)" ANDROID_SDK_HOME="${ANDROID_SDK_ROOT:-${ANDROID_HOME:-}}" if [ -z "$ANDROID_SDK_HOME" ]; then @@ -27,9 +36,18 @@ if [ ! -x "$APKSIGNER" ] || [ ! -x "$ZIPALIGN" ]; then exit 1 fi +if command -v sha256sum >/dev/null 2>&1; then + SHA256=(sha256sum) +elif command -v shasum >/dev/null 2>&1; then + SHA256=(shasum -a 256) +else + echo "error: sha256sum or shasum is required" >&2 + exit 1 +fi + ( cd "$RELEASE_DIR" - sha256sum -c SHA256SUMS.unsigned + "${SHA256[@]}" -c SHA256SUMS.unsigned ) EXPECTED_CERT_SHA256="$( @@ -44,17 +62,8 @@ fi TEMP_DIR="$(mktemp -d)" trap 'rm -rf "$TEMP_DIR"' EXIT -KEYSTORE="$TEMP_DIR/release.jks" - -if base64 --help 2>&1 | grep -q -- '--decode'; then - printf '%s' "$BITCHAT_SIGNING_KEY_BASE64" | base64 --decode > "$KEYSTORE" -else - printf '%s' "$BITCHAT_SIGNING_KEY_BASE64" | base64 -D > "$KEYSTORE" -fi -chmod 600 "$KEYSTORE" - -export BITCHAT_KEYSTORE_PASSWORD -export BITCHAT_KEY_PASSWORD +export BITCHAT_GITHUB_KEYSTORE_PASSWORD +export BITCHAT_GITHUB_KEY_PASSWORD declare -A signed_names=( ["bitchat-android-arm64-unsigned.apk"]="bitchat-android-arm64.apk" @@ -68,14 +77,18 @@ for unsigned_name in "${!signed_names[@]}"; do first_signed="$TEMP_DIR/first.apk" second_signed="$TEMP_DIR/second.apk" + if [ -e "$signed_apk" ]; then + echo "error: signed APK already exists: ${signed_names[$unsigned_name]}" >&2 + exit 1 + fi "$ZIPALIGN" -c -P 16 4 "$unsigned_apk" for output_apk in "$first_signed" "$second_signed"; do "$APKSIGNER" sign \ --ks "$KEYSTORE" \ - --ks-key-alias "$BITCHAT_SIGNING_KEY_ALIAS" \ - --ks-pass env:BITCHAT_KEYSTORE_PASSWORD \ - --key-pass env:BITCHAT_KEY_PASSWORD \ + --ks-key-alias "$BITCHAT_GITHUB_KEY_ALIAS" \ + --ks-pass env:BITCHAT_GITHUB_KEYSTORE_PASSWORD \ + --key-pass env:BITCHAT_GITHUB_KEY_PASSWORD \ --deterministic-dsa-signing true \ --min-sdk-version 26 \ --v1-signing-enabled false \ @@ -113,9 +126,9 @@ done for artifact in *; do [ "$artifact" = "SHA256SUMS" ] && continue [ -f "$artifact" ] || continue - sha256sum "$artifact" + "${SHA256[@]}" "$artifact" done } | sort -k2 > SHA256SUMS ) -echo "Verified unsigned APKs were deterministically signed and checksummed." +echo "Verified unsigned APKs were deterministically signed locally and checksummed." diff --git a/tools/reproducible-builds/verify-github-release.sh b/tools/reproducible-builds/verify-github-release.sh index aa8ec9a8..64cb7574 100755 --- a/tools/reproducible-builds/verify-github-release.sh +++ b/tools/reproducible-builds/verify-github-release.sh @@ -43,10 +43,24 @@ gh release download "$TAG" \ --pattern 'bitchat-android-*.apk' \ --pattern 'bitchat-android-*.aab' -for artifact in "$DOWNLOAD_DIR"/*; do - gh attestation verify "$artifact" --repo "$REPOSITORY" >/dev/null +attested_artifacts=( + BITCHAT_BUILDINFO.json + BITCHAT_SHA256SUMS.unsigned + bitchat-android-arm64-unsigned.apk + bitchat-android-armv7-unsigned.apk + bitchat-android-release-unsigned.aab + bitchat-android-universal-unsigned.apk + bitchat-android-x86-unsigned.apk + bitchat-android-x86_64-unsigned.apk +) +for artifact in "${attested_artifacts[@]}"; do + if [ ! -f "$DOWNLOAD_DIR/$artifact" ]; then + echo "error: attested canonical artifact missing: $artifact" >&2 + exit 1 + fi + gh attestation verify "$DOWNLOAD_DIR/$artifact" --repo "$REPOSITORY" >/dev/null done -echo "GitHub provenance attestations verified." +echo "GitHub provenance attestations for the canonical unsigned build verified." ( cd "$DOWNLOAD_DIR"