chore(release): one-shot export of the signing key for Play App Signing
All checks were successful
Play key export (one-shot) / export (push) Successful in 50s

Never merged. Runs PEPK against the release keystore secret and uploads
output.zip, encrypted to Google's public key, as a one-day artifact for
the Play Console's "Export and upload a key from Java keystore". Refuses
unless the keystore's certificate is the one the release APKs carry.
The branch is deleted once the zip is uploaded.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
This commit is contained in:
2026-10-30 16:47:01 -05:00
parent 3d01871c72
commit 82113948f1
2 changed files with 88 additions and 0 deletions

View File

@@ -0,0 +1,11 @@
-----BEGIN PUBLIC KEY-----
MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAsovs0QR6VmHX+q1Zv3SQ
WIkcR04Vwv4WLhbOt/OAaW2dBFsmRyskPx5W5RJ9gS12/BU8sUKAUh4aALwQMfLS
u4Tugb6FpI5rBY10ydMx6D1EbU+UMwdIlBnm8EYoI5oUdY9VEjWihUZHSoBnC/LW
/UbJx+HmA7tA7BEIx4KBmOmZUTnDSCRTi48IBjfKslZem6bkcTeIrxzY+faBZYRM
s9Q11kxEsHbpIJ8af1HTO7JtCRxAlGkzFsT7IyVOtVdpLTKlPcuVsocyY0/Wl641
O9YmEutJnyoQbAekQnkJxyNeq1BuT5syhS6nEDUwqKgiahKHuWr99MqFLDEkuZZ+
rKXDlb1j3CJ0wTAu68aizCtOFMoOuVJjd/rHmuyRePpvZXsql03tQW5PP3R2yrKe
KFSPVNmhWhWqZiaXoqyyLK7uoxjLgF/K0ekHHcKivTm4WvT52V5TTsN2jleMbkpM
EO2KajkBwWkvVBcTaUmt9yFq4fzifPq2z1sebP3RRycTAgMBAAE=
-----END PUBLIC KEY-----

View File

@@ -0,0 +1,77 @@
# ONE-SHOT, never merged: exports the release signing key for Play App Signing
# ("Export and upload a key from Java keystore"). Lives only on the branch
# chore/play-key-export and runs when that branch is pushed; the branch is
# deleted once the zip is uploaded to the Play Console.
#
# What leaves the run: output.zip, which PEPK encrypts with Google's public key
# (.gitea/play-key-export/encryption_public_key.pem), so only Google can open it.
# What the log shows: the certificate's SHA-256 (public; it is in every APK) and
# pepk.jar's checksum. Nothing derived from a secret is ever printed: no set -x,
# no echo of a variable, the keystore is decoded to a private temp dir and removed.
name: Play key export (one-shot)
on:
push:
branches:
- chore/play-key-export
jobs:
export:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Install JDK 17
run: |
apt-get update
apt-get install -y curl openjdk-17-jdk-headless
- name: Check out this branch
uses: actions/checkout@v4
- name: Export the signing key, encrypted to Google
env:
ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
# The certificate every Gitea release APK is signed with (v0.6.0).
EXPECTED_SHA256: '59:7B:79:ED:54:2A:91:43:73:59:03:5D:3F:C4:39:B5:DA:8B:D8:E7:26:E7:30:03:B8:B4:1A:F6:0E:14:A4:82'
run: |
set -euo pipefail
umask 077
WORK="$(mktemp -d)"
trap 'rm -rf "$WORK"' EXIT
printf '%s' "$ANDROID_KEYSTORE_BASE64" | base64 -d > "$WORK/release.jks"
FP="$(keytool -list -v -keystore "$WORK/release.jks" -storepass "$ANDROID_KEYSTORE_PASSWORD" \
-alias "$ANDROID_KEY_ALIAS" | sed -n 's/^[[:space:]]*SHA256: //p')"
echo "Keystore certificate SHA-256: ${FP}"
if [ "$FP" != "$EXPECTED_SHA256" ]; then
echo "::error::not the key the release APKs are signed with; nothing exported"
exit 1
fi
curl -fsSL -o "$WORK/pepk.jar" \
https://www.gstatic.com/play-apps-publisher-rapid/signing-tool/prod/pepk.jar
echo "pepk.jar SHA-256: $(sha256sum "$WORK/pepk.jar" | cut -d' ' -f1)"
mkdir -p out
java -jar "$WORK/pepk.jar" \
--keystore="$WORK/release.jks" \
--alias="$ANDROID_KEY_ALIAS" \
--keystore-pass="$ANDROID_KEYSTORE_PASSWORD" \
--key-pass="$ANDROID_KEY_PASSWORD" \
--output="$PWD/out/output.zip" \
--include-cert \
--rsa-aes-encryption \
--encryption-key-path=.gitea/play-key-export/encryption_public_key.pem \
> /dev/null 2>&1 || { echo "::error::pepk failed (its output is suppressed)"; exit 1; }
ls -l out/output.zip
- name: Upload the encrypted zip
uses: actions/upload-artifact@v3
with:
name: play-signing-key-export
path: out/output.zip
retention-days: 1