#!/bin/sh # Verify the release APK's identity, alignment, native payload, and signature. set -eu root=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd) # shellcheck source=versions . "$root/versions" apk=${1:-$root/app/build/outputs/apk/release/app-release-unsigned.apk} [ -f "$apk" ] || { echo "verify-apk: not found: $apk" >&2; exit 1; } command -v readelf >/dev/null 2>&1 || { echo "verify-apk: readelf is required" >&2; exit 1; } tmp=$(mktemp -d) trap 'rm -rf "$tmp"' 0 1 2 3 15 entries=$(unzip -Z1 "$apk") actual=$(printf '%s\n' "$entries" | sed -n 's|^lib/\([^/]*\)/.*|\1|p' | sort -u | tr '\n' ' ' | sed 's/ $//') expected=$(printf '%s\n' "$ABIS" | tr ' ' '\n' | sed '/^$/d' | sort -u | tr '\n' ' ' | sed 's/ $//') [ "$actual" = "$expected" ] || { echo "verify-apk: native ABIs are '$actual', expected '$expected'" >&2 exit 1 } for abi in $ABIS; do for lib in libxrsync.so libxrsh.so; do path="lib/$abi/$lib" printf '%s\n' "$entries" | grep -Fqx "$path" || { echo "verify-apk: missing lib/$abi/$lib" >&2 exit 1 } elf="$tmp/$abi-$lib" unzip -p "$apk" "$path" > "$elf" header=$(readelf -W -h "$elf") printf '%s\n' "$header" | grep -Fq 'DYN (Position-Independent Executable file)' || { echo "verify-apk: $path is not PIE" >&2 exit 1 } case "$abi" in arm64-v8a) machine=AArch64 ;; x86_64) machine='Advanced Micro Devices X86-64' ;; *) echo "verify-apk: no machine check for $abi" >&2; exit 1 ;; esac actual_machine=$(printf '%s\n' "$header" | sed -n 's/^[[:space:]]*Machine:[[:space:]]*//p') [ "$actual_machine" = "$machine" ] || { echo "verify-apk: $path has the wrong machine type" >&2 exit 1 } program_headers=$(readelf -W -l "$elf") printf '%s\n' "$program_headers" | grep -Fq 'GNU_RELRO' || { echo "verify-apk: $path has no RELRO segment" >&2 exit 1 } load_alignment=$(printf '%s\n' "$program_headers" | awk '$1 == "LOAD" { print $NF }' | sort -u) [ "$load_alignment" = "0x4000" ] || { echo "verify-apk: $path LOAD alignment is '$load_alignment', expected 0x4000" >&2 exit 1 } stack=$(printf '%s\n' "$program_headers" | sed -n '/GNU_STACK/p') if [ -z "$stack" ] || printf '%s\n' "$stack" | grep -Eq 'RWE|RWX'; then echo "verify-apk: $path has an executable or missing stack marker" >&2 exit 1 fi dynamic=$(readelf -W -d "$elf") printf '%s\n' "$dynamic" | grep -Fq 'BIND_NOW' || { echo "verify-apk: $path does not use immediate binding" >&2 exit 1 } ! printf '%s\n' "$dynamic" | grep -Eq 'RPATH|RUNPATH|TEXTREL' || { echo "verify-apk: $path has an unsafe dynamic-linker flag" >&2 exit 1 } needed=$(printf '%s\n' "$dynamic" | sed -n 's/.*Shared library: \[\(.*\)\]/\1/p' | LC_ALL=C sort) if [ "$lib" = libxrsh.so ]; then expected_needed=$(printf '%s\n' libc.so libdl.so liblog.so | LC_ALL=C sort) else expected_needed=$(printf '%s\n' libc.so libdl.so | LC_ALL=C sort) fi [ "$needed" = "$expected_needed" ] || { echo "verify-apk: $path system libraries changed:$needed" >&2 exit 1 } sections=$(readelf -W -S "$elf") ! printf '%s\n' "$sections" | grep -Eq '\.debug_|\.symtab' || { echo "verify-apk: $path is not stripped" >&2 exit 1 } if [ "$lib" = libxrsync.so ]; then symbols=$(readelf -W -s "$elf") printf '%s\n' "$symbols" | grep -Fq '__stack_chk_fail' || { echo "verify-apk: $path has no stack protector" >&2 exit 1 } printf '%s\n' "$symbols" | grep -Fq '__memcpy_chk' || { echo "verify-apk: $path has no fortified libc calls" >&2 exit 1 } fi done done native_entries=$(printf '%s\n' "$entries" | sed -n '/^lib\//p') unexpected= if unexpected=$(printf '%s\n' "$native_entries" | grep -Ev '^lib/[^/]+/(libxrsync|libxrsh)\.so$'); then : else status=$? [ "$status" -eq 1 ] || exit "$status" fi [ -z "$unexpected" ] || { echo "verify-apk: unexpected native files:$unexpected" >&2; exit 1; } ! printf '%s\n' "$entries" | grep -Fqx META-INF/version-control-info.textproto || { echo "verify-apk: APK contains a VCS stamp" >&2 exit 1 } for notice in assets/APACHE-2.0 assets/LICENSE assets/THIRD_PARTY; do printf '%s\n' "$entries" | grep -Fqx "$notice" || { echo "verify-apk: missing $notice" >&2 exit 1 } asset="$tmp/$(basename "$notice")" unzip -p "$apk" "$notice" > "$asset" cmp "$asset" "$root/$(basename "$notice")" done zipalign -c -P 16 4 "$apk" >/dev/null badging=$(aapt dump badging "$apk") printf '%s\n' "$badging" | grep -Fq "package: name='invalid.lena.rsend' versionCode='$APP_VERSION_CODE' versionName='$APP_VERSION'" || { echo "verify-apk: package version does not match versions" >&2 exit 1 } printf '%s\n' "$badging" | grep -Fqx "sdkVersion:'$ANDROID_MIN_SDK'" || { echo "verify-apk: minimum SDK does not match versions" >&2 exit 1 } printf '%s\n' "$badging" | grep -Fqx "targetSdkVersion:'$ANDROID_TARGET_SDK'" || { echo "verify-apk: target SDK does not match versions" >&2 exit 1 } ! printf '%s\n' "$badging" | grep -Fq 'application-debuggable' || { echo "verify-apk: release APK is debuggable" >&2 exit 1 } permission_dump=$(aapt dump permissions "$apk") permissions=$(printf '%s\n' "$permission_dump" | sed -n "s/^uses-permission: name='\(.*\)'$/\1/p" | LC_ALL=C sort) expected_permissions=$(cat <&2 exit 1 } signature="$tmp/signature" if apksigner verify --min-sdk-version "$ANDROID_MIN_SDK" --verbose "$apk" >"$signature" 2>&1; then case "$(basename "$apk")" in *-unsigned.apk) echo "verify-apk: unsigned APK unexpectedly has a valid signature" >&2 exit 1 ;; esac # At API 30 apksigner reports only v3 as used, even when valid v1 and v2 # signatures are present. API 23 makes it verify v1 for that platform and # v2/v3 for later platforms in the same pass. schemes="$tmp/schemes" apksigner verify --min-sdk-version 23 --verbose "$apk" >"$schemes" 2>&1 grep -Fq 'Verified using v1 scheme (JAR signing): true' "$schemes" grep -Fq 'Verified using v2 scheme (APK Signature Scheme v2): true' "$schemes" grep -Fq 'Verified using v3 scheme (APK Signature Scheme v3): true' "$schemes" echo "verify-apk: signed APK verified (v1, v2, v3)" else case "$(basename "$apk")" in *-unsigned.apk) ;; *) cat "$signature" >&2 echo "verify-apk: signed APK failed verification" >&2 exit 1 ;; esac ! printf '%s\n' "$entries" | grep -Eq '^META-INF/.*\.(RSA|DSA|EC)$' || { echo "verify-apk: unsigned APK contains a signature file" >&2 exit 1 } echo "verify-apk: unsigned APK" fi echo "verify-apk: ok"