The v1 action (org.openintents.openpgp.IOpenPgpService) resolves in Keychain 6.0.4 but its binder does not implement the IOpenPgpService2 AIDL descriptor that OpenPgpApi calls through, so every call died with SecurityException: Binder invocation to an incorrect interface. Bind the IOpenPgpService2 service first (v1 and the very old api.OpenPgpService actions remain as fallbacks). Android 11+ package visibility also hid Keychain's services entirely: resolveService/bindService found nothing from the app even with MANAGE_EXTERNAL_STORAGE granted, so the build now injects a <queries> element (package + intent) into the decoded manifest.
113 lines
4.4 KiB
Bash
Executable File
113 lines
4.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Build (and optionally install) the passgo-gui Android APK.
|
|
#
|
|
# Usage:
|
|
# ./scripts/build.sh [phone|emu] # build + install (default: phone)
|
|
# ./scripts/build.sh phone --no-install # build only
|
|
#
|
|
# Output: cmd/passgo-gui/passgo-gui.apk
|
|
#
|
|
# Prereqs on this workstation:
|
|
# - JDK (java on PATH)
|
|
# - Go + gogio (~/go/bin)
|
|
# - Android SDK at ~/android-sdk (platform-tools, build-tools 35.0.0)
|
|
# - debug keystore at ~/.android/debug.keystore
|
|
# apktool is not a prerequisite: if it is missing, the script downloads
|
|
# v3.0.3 to the stable location ~/android-sdk/tools/apktool.jar.
|
|
#
|
|
# Why the apktool detour: gogio's manifest permissions come from a hardcoded
|
|
# table (only the legacy READ/WRITE_EXTERNAL_STORAGE). On Android 11+ the
|
|
# "All files access" settings toggle is grayed out unless the app declares
|
|
# MANAGE_EXTERNAL_STORAGE, which Permissions.java opens at startup. So we
|
|
# decode the gogio APK, inject the permission, rebuild, and re-sign with
|
|
# the debug key.
|
|
set -euo pipefail
|
|
|
|
REPO=$(cd "$(dirname "$0")/.." && pwd)
|
|
export ANDROID_HOME=${ANDROID_HOME:-$HOME/android-sdk}
|
|
export PATH=$PATH:$ANDROID_HOME/platform-tools:$ANDROID_HOME/build-tools/35.0.0:$HOME/go/bin
|
|
|
|
MODE=${1:-phone}
|
|
INSTALL=1
|
|
[ "${2:-}" = "--no-install" ] && INSTALL=0
|
|
case "$MODE" in
|
|
phone) ARCH="arm64,arm";;
|
|
emu) ARCH="amd64";;
|
|
*) echo "usage: $0 [phone|emu] [--no-install]" >&2; exit 1;;
|
|
esac
|
|
|
|
die() { echo "ERROR: $*" >&2; exit 1; }
|
|
|
|
command -v java >/dev/null 2>&1 || die "java not found"
|
|
command -v gogio >/dev/null 2>&1 || die "gogio not found (need ~/go/bin)"
|
|
command -v apksigner >/dev/null 2>&1 || die "apksigner not found (need $ANDROID_HOME/build-tools/35.0.0)"
|
|
command -v adb >/dev/null 2>&1 || die "adb not found (need $ANDROID_HOME/platform-tools)"
|
|
[ -f "$HOME/.android/debug.keystore" ] || die "debug keystore missing: $HOME/.android/debug.keystore"
|
|
|
|
# apktool: auto-download to a stable location if missing.
|
|
APKTOOL="$ANDROID_HOME/tools/apktool.jar"
|
|
if [ ! -f "$APKTOOL" ]; then
|
|
echo "=== apktool missing; downloading v3.0.3 to $APKTOOL ==="
|
|
mkdir -p "$ANDROID_HOME/tools"
|
|
curl -fsSL -o "$APKTOOL" \
|
|
https://github.com/iBotPeaches/Apktool/releases/download/v3.0.3/apktool_3.0.3.jar \
|
|
|| die "apktool download failed"
|
|
fi
|
|
|
|
WORK=$(mktemp -d)
|
|
trap 'rm -rf "$WORK"' EXIT
|
|
|
|
echo "=== gogio (Go -> APK) ==="
|
|
# targetSdk 34: targeting 35 makes Android 15 force edge-to-edge, which the
|
|
# app does not handle.
|
|
(cd "$REPO/cmd/passgo-gui" && gogio -target android -targetsdk 34 -arch "$ARCH" -o "$WORK/passgo-raw.apk" .)
|
|
[ -f "$WORK/passgo-raw.apk" ] || die "gogio did not produce an APK"
|
|
|
|
echo "=== apktool decode + inject MANAGE_EXTERNAL_STORAGE ==="
|
|
java -jar "$APKTOOL" d "$WORK/passgo-raw.apk" -o "$WORK/decoded" -f >/dev/null
|
|
python3 - "$WORK/decoded/AndroidManifest.xml" << 'PYEOF'
|
|
import sys, pathlib
|
|
p = pathlib.Path(sys.argv[1])
|
|
t = p.read_text()
|
|
perm = '<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>'
|
|
if perm not in t:
|
|
t = t.replace(" <application", " " + perm + "\n <application", 1)
|
|
if perm not in t:
|
|
sys.exit("ERROR: failed to inject MANAGE_EXTERNAL_STORAGE")
|
|
# Android 11+ package visibility: without <queries> the app cannot even
|
|
# resolve (let alone bind) Keychain's OpenPGP service.
|
|
queries = """ <queries>
|
|
<package android:name="org.sufficientlysecure.keychain"/>
|
|
<intent>
|
|
<action android:name="org.openintents.openpgp.IOpenPgpService2"/>
|
|
</intent>
|
|
</queries>
|
|
"""
|
|
if "<queries>" not in t:
|
|
t = t.replace(" <application", queries + " <application", 1)
|
|
if "<queries>" not in t:
|
|
sys.exit("ERROR: failed to inject <queries>")
|
|
p.write_text(t)
|
|
print("manifest: MANAGE_EXTERNAL_STORAGE + <queries> present")
|
|
PYEOF
|
|
|
|
echo "=== apktool rebuild ==="
|
|
java -jar "$APKTOOL" b "$WORK/decoded" -o "$WORK/passgo-unsigned.apk" >/dev/null
|
|
|
|
echo "=== sign ==="
|
|
apksigner sign \
|
|
--ks "$HOME/.android/debug.keystore" --ks-pass pass:android \
|
|
--key-pass pass:android --ks-key-alias androiddebugkey \
|
|
--out "$REPO/cmd/passgo-gui/passgo-gui.apk" "$WORK/passgo-unsigned.apk"
|
|
|
|
# ADB_SERIAL selects the device when several are attached (adb devices).
|
|
ADB="adb${ADB_SERIAL:+ -s $ADB_SERIAL}"
|
|
|
|
if [ "$INSTALL" = "1" ]; then
|
|
echo "=== install ==="
|
|
$ADB get-state >/dev/null 2>&1 || die "no adb device (connect the phone; set ADB_SERIAL if several are attached)"
|
|
$ADB install -r "$REPO/cmd/passgo-gui/passgo-gui.apk"
|
|
fi
|
|
|
|
echo "=== DONE: $REPO/cmd/passgo-gui/passgo-gui.apk ==="
|