mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-08-15 06:56:30 +00:00
fix: keep release signing local
This commit is contained in:
parent
a0048554c6
commit
da5650ceb4
94
.github/workflows/release.yml
vendored
94
.github/workflows/release.yml
vendored
@ -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."
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
63
tools/reproducible-builds/prepare-github-release.sh
Executable file
63
tools/reproducible-builds/prepare-github-release.sh
Executable file
@ -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."
|
||||
89
tools/reproducible-builds/sign-play-bundle.sh
Executable file
89
tools/reproducible-builds/sign-play-bundle.sh
Executable file
@ -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."
|
||||
@ -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."
|
||||
|
||||
@ -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"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user