security(release): rotate compromised F-Droid repo key; keep key out of served tree
All checks were successful
CI / ci (push) Successful in 5m17s

The F-Droid repo signing key (keystore.p12) and its config.yml — including
the keystore passwords in cleartext — were publicly downloadable at
apps.dev.jeanlucmakiola.de/dev/fdroid/ because the release workflow uploaded
the entire fdroid/ working dir into the web-served path. The webserver has
since been locked down to repo/ only; this rotates the now-compromised key
and removes the root cause.

- release.yaml: restore the repo key + config from new CI secrets
  (FDROID_KEYSTORE_BASE64, FDROID_CONFIG_BASE64) instead of the box; upload
  ONLY repo/ so the key never re-enters the served tree.
- release.yaml: fail loudly when the repo key secrets are unset, replacing
  `fdroid update --create-key`, which silently minted a NEW repo key on a
  wiped server and would have broken every user's pinned fingerprint.
- README: publish the new repo fingerprint (C2C0…3425). Existing users must
  remove and re-add the repo.
- .gitignore: ignore *.p12 and the whole /fdroid/ working dir.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-14 12:01:00 +02:00
parent 54aed73726
commit e5be5f1ae5
3 changed files with 38 additions and 19 deletions

View File

@@ -165,27 +165,45 @@ jobs:
$SUDO apt-get install -y sshpass python3-pip $SUDO apt-get install -y sshpass python3-pip
pip3 install --break-system-packages --upgrade fdroidserver pip3 install --break-system-packages --upgrade fdroidserver
- name: Initialize or fetch F-Droid Repository - name: Fetch existing F-Droid repo from Hetzner
env: env:
HOST: ${{ secrets.HETZNER_HOST }} HOST: ${{ secrets.HETZNER_HOST }}
USER: ${{ secrets.HETZNER_USER }} USER: ${{ secrets.HETZNER_USER }}
PASS: ${{ secrets.HETZNER_PASS }} PASS: ${{ secrets.HETZNER_PASS }}
run: | run: |
set -euo pipefail
SSH_OPTS="-o StrictHostKeyChecking=no -o ConnectTimeout=20"
mkdir -p fdroid mkdir -p fdroid
sshpass -p "$PASS" sftp -o StrictHostKeyChecking=no "$USER@$HOST" <<'SFTP' # Pull only the published repo/ (all apps' APKs), any per-app
-mkdir dev # metadata, and the repo icon — enough to rebuild the index without
-mkdir dev/fdroid # dropping the other apps. The signing key is deliberately NOT pulled
-mkdir dev/fdroid/repo # from the box; it comes from CI secrets in the next step so it never
SFTP # has to live in the web-served tree.
sshpass -p "$PASS" scp -o StrictHostKeyChecking=no -r "$USER@$HOST:dev/fdroid/." fdroid/ || (cd fdroid && fdroid init) sshpass -p "$PASS" scp $SSH_OPTS -r "$USER@$HOST:dev/fdroid/repo" fdroid/ 2>/dev/null || true
sshpass -p "$PASS" scp $SSH_OPTS -r "$USER@$HOST:dev/fdroid/metadata" fdroid/ 2>/dev/null || true
sshpass -p "$PASS" scp $SSH_OPTS "$USER@$HOST:dev/fdroid/icon.png" fdroid/ 2>/dev/null || true
mkdir -p fdroid/repo fdroid/metadata
- name: Ensure F-Droid repo signing key and icon - name: Restore F-Droid signing key and config from secrets
env:
FDROID_KEYSTORE_BASE64: ${{ secrets.FDROID_KEYSTORE_BASE64 }}
FDROID_CONFIG_BASE64: ${{ secrets.FDROID_CONFIG_BASE64 }}
run: | run: |
cd fdroid set -euo pipefail
mkdir -p repo/icons # Fail loudly if the repo key is not configured. NEVER auto-generate
if [ ! -f keystore.p12 ]; then # one: a fresh key changes the repo fingerprint and breaks every
fdroid update --create-key # user's pinned repo. (Replaces the old `fdroid update --create-key`
# path, which silently rotated the key on a wiped server.)
if [ -z "${FDROID_KEYSTORE_BASE64:-}" ] || [ -z "${FDROID_CONFIG_BASE64:-}" ]; then
echo "ERROR: FDROID_KEYSTORE_BASE64 / FDROID_CONFIG_BASE64 secrets are not set." >&2
echo "Refusing to continue — will not auto-generate a new repo key." >&2
exit 1
fi fi
echo "$FDROID_KEYSTORE_BASE64" | base64 --decode > fdroid/keystore.p12
echo "$FDROID_CONFIG_BASE64" | base64 --decode > fdroid/config.yml
test -s fdroid/keystore.p12
test -s fdroid/config.yml
mkdir -p fdroid/repo/icons
- name: Copy new APK to repo - name: Copy new APK to repo
run: | run: |
@@ -208,7 +226,7 @@ jobs:
cd fdroid cd fdroid
fdroid update -c fdroid update -c
- name: Upload Repo to Hetzner - name: Upload repo/ to Hetzner
env: env:
HOST: ${{ secrets.HETZNER_HOST }} HOST: ${{ secrets.HETZNER_HOST }}
USER: ${{ secrets.HETZNER_USER }} USER: ${{ secrets.HETZNER_USER }}
@@ -219,9 +237,10 @@ jobs:
sshpass -p "$PASS" sftp $SSH_OPTS "$USER@$HOST" <<'SFTP' sshpass -p "$PASS" sftp $SSH_OPTS "$USER@$HOST" <<'SFTP'
-mkdir dev -mkdir dev
-mkdir dev/fdroid -mkdir dev/fdroid
-mkdir dev/fdroid/repo
SFTP SFTP
sshpass -p "$PASS" scp $SSH_OPTS -r fdroid/. "$USER@$HOST:dev/fdroid/" # Publish ONLY the signed repo/. keystore.p12 and config.yml never
# leave CI, so they can no longer end up in the web-served tree.
sshpass -p "$PASS" scp $SSH_OPTS -r fdroid/repo "$USER@$HOST:dev/fdroid/"
# A Gitea release per tag, carrying the tag's CHANGELOG section as its # A Gitea release per tag, carrying the tag's CHANGELOG section as its
# notes. Deliberately no APK assets — distribution stays with the F-Droid # notes. Deliberately no APK assets — distribution stays with the F-Droid

4
.gitignore vendored
View File

@@ -40,6 +40,7 @@ captures/
# Keystore files # Keystore files
*.jks *.jks
*.keystore *.keystore
*.p12
/key.properties /key.properties
# Google Services (e.g. APIs or Firebase) # Google Services (e.g. APIs or Firebase)
@@ -50,8 +51,7 @@ google-services.json
Thumbs.db Thumbs.db
# F-Droid local artifacts (the pipeline generates them in CI) # F-Droid local artifacts (the pipeline generates them in CI)
fdroid/repo/ /fdroid/
fdroid/keystore.p12
# KSP # KSP
.ksp/ .ksp/

View File

@@ -77,12 +77,12 @@ is built, signed, and published there automatically.
*Settings → Repositories → Add*: *Settings → Repositories → Add*:
``` ```
https://apps.dev.jeanlucmakiola.de/dev/fdroid/repo?fingerprint=968F796B05DF622BBE18AD6FC1D1EF788D5A6DA1FF05BBEC6B7043BF10A09465 https://apps.dev.jeanlucmakiola.de/dev/fdroid/repo?fingerprint=C2C0640402BF458FC0ED957AF0B37AA4C14022E72F89CE90B5965B458CF73425
``` ```
<sub>Repo: `https://apps.dev.jeanlucmakiola.de/dev/fdroid/repo` · <sub>Repo: `https://apps.dev.jeanlucmakiola.de/dev/fdroid/repo` ·
fingerprint (SHA-256): fingerprint (SHA-256):
`968F 796B 05DF 622B BE18 AD6F C1D1 EF78 8D5A 6DA1 FF05 BBEC 6B70 43BF 10A0 9465`</sub> `C2C0 6404 02BF 458F C0ED 957A F0B3 7AA4 C140 22E7 2F89 CE90 B596 5B45 8CF7 3425`</sub>
3. Refresh, search for **Calendula**, install. Updates arrive like any 3. Refresh, search for **Calendula**, install. Updates arrive like any
other F-Droid app. other F-Droid app.