diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index 58622d41..3988729e 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -523,23 +523,39 @@ jobs: -PMINSTREL_VERSION_NAME=${{ steps.ver.outputs.name }} \ -PMINSTREL_VERSION_CODE=${{ steps.ver.outputs.code }} - # The APK every phone updates from must carry the release key: Android - # updates an app in place only when the signer matches. Gradle signs - # with the release key or leaves the APK unsigned, so this catches a - # wrong key, an unsigned build and a debug signer before anything - # publishes (family idea #5103, practice 3). apksigner, not keytool: - # keytool prints nothing for a v2-only APK. + # The APK every phone updates from must carry THE release key: Android + # updates an app in place only when the signer matches, so an APK + # signed by any other key (debug, a regenerated keystore, a swapped + # secret) reaches no installed phone. The certificate's digest is + # pinned below; it is public, not a secret. Gradle signs with the + # release key or leaves the APK unsigned, and an unsigned build fails + # here too, as there is no app-release.apk to verify (family idea + # #5103, practice 3). apksigner, not keytool: keytool prints nothing + # for a v2-only APK. + # + # Rotating the key on purpose means every install must be removed and + # reinstalled; change the digest here in the same commit. - name: The APK carries the release key shell: bash + env: + # CN=Minstrel, O=FabledSword. Read from run 8446 (#5116). + RELEASE_CERT_SHA256: 43d183307bc46b821789d90444a960b137f78f2166ff431efb2406d0fceaf612 run: | set -euo pipefail sdk="${ANDROID_HOME:-${ANDROID_SDK_ROOT:-}}" signer="$(ls "$sdk"/build-tools/*/apksigner 2>/dev/null | sort -V | tail -1 || true)" test -n "$signer" || { echo "::error::no apksigner under '$sdk/build-tools'"; exit 1; } certs="$("$signer" verify --print-certs app/build/outputs/apk/release/app-release.apk)" - printf '%s\n' "$certs" | grep -E 'Signer #1 certificate (DN|SHA-256 digest)' - if printf '%s' "$certs" | grep -q 'CN=Android Debug'; then - echo "::error::the release APK is signed with a debug key" + printf '%s\n' "$certs" | grep -E '^Signer #[0-9]+ certificate (DN|SHA-256 digest)' + # One signer, and it is ours. A second signer would be a lineage or + # a mistake; either way not something to ship unexamined. + digests="$(printf '%s\n' "$certs" | sed -n 's/^Signer #[0-9]* certificate SHA-256 digest: //p')" + if [ "$digests" != "$RELEASE_CERT_SHA256" ]; then + if printf '%s' "$certs" | grep -q 'CN=Android Debug'; then + echo "::error::the release APK is signed with a debug key" + else + echo "::error::the release APK is not signed by the release key: got '${digests//$'\n'/ }', want ${RELEASE_CERT_SHA256}" + fi exit 1 fi