#!/bin/bash # # A script to verify that a locally compiled APK matches the released APK. # _____ _ # |_ _| |_ _ _ ___ ___ _ __ __ _ # | | | ' \| '_/ -_) -_) ' \/ _` |_ # |_| |_||_|_| \___\___|_|_|_\__,_(_) # # Threema for Android # Copyright (c) 2020 Threema GmbH # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License, version 3, # as published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . set -euo pipefail GREEN="\033[0;32m" RED="\033[0;31m" YELLOW="\033[1;33m" RESET="\033[0m" function print_usage() { echo "Usage: $0 -n -v -p [-l ]" echo "" echo "Options:" echo " -n The version name. Example: '4.43k'" echo " -v Variant to verify: Either googleplay or threemashop" echo " -p Path to the APK file extracted from the phone" echo " -l Optional: Path to the locally built APK" echo " -h,--help Print this help and exit" } function log() { echo -en "$1" echo -n "$2 $3" echo -e "$RESET" } function log_major() { log "$GREEN" "==>" "$1"; } function log_minor() { log "$GREEN" "--> " "$1"; } function log_warning() { log "$YELLOW" "==>" "$1"; } function log_error() { log "$RED" "!!!" "Error: $1"; } function fail() { log_error "$1" exit 1 } # Re-implementation of realpath function (hello macOS) function realpath() { OURPWD=$PWD cd "$(dirname "$1")" LINK=$(readlink "$(basename "$1")") while [ "$LINK" ]; do cd "$(dirname "$LINK")" LINK=$(readlink "$(basename "$1")") done REALPATH="$PWD/$(basename "$1")" cd "$OURPWD" echo "$REALPATH" } # Determine script directory DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" # If no arguments are passed, print usage if [ "$#" -lt 1 ]; then print_usage; exit 1; fi # Parse arguments name="" variant="" published_apk="" local_apk="" while [[ "$#" -gt 0 ]]; do case $1 in -n) name="$2"; shift ;; -v) variant="$2"; shift ;; -p) published_apk="$2"; shift ;; -l) local_apk="$2"; shift ;; -h|--help) print_usage; exit 0 ;; *) echo "Unknown parameter passed: $1"; print_usage; exit 1 ;; esac shift done # Process arguments if [[ "$name" == "" || "$name" == .* ]]; then log_error 'Please set a valid version name with "-n ".' fail 'Example: "-n 4.43k"' fi if [[ "$published_apk" == "" ]]; then log_error 'Please set a valid published APK path with "-p ".' fail 'Example: "-p threema-extracted.apk"' fi published_apk=$(realpath "$published_apk") if [[ "$variant" == "" ]]; then log_error 'Please set a valid build variant with "-v ".' fail 'Example: "-v googleplay" or "-v threemashop"' fi case "$variant" in googleplay) variant_name="store_google" ;; threemashop) variant_name="store_threema" ;; *) fail "Invalid build variant: $variant" ;; esac # Validate target directory targetdir=$(realpath "$DIR/../reproduce") if [[ -d "$targetdir" ]]; then fail "The directory $targetdir already exists. Please remove it first." fi mkdir -p "$targetdir"/{published,local} # Unpack published APK if [[ ! -f "$published_apk" ]]; then fail "The published APK $published_apk could not be found." fi log_major "Unpacking published APK" unzip -q -d "$targetdir/published/" "$published_apk" # Determine local APK path if [[ "$local_apk" == "" ]]; then log_major "Determine local APK path" lib_count="$(find "$targetdir/published/lib/" -mindepth 1 -maxdepth 1 -type d | wc -l | xargs)" log_minor "Found $lib_count libs" case "$lib_count" in 1) architecture="$(ls "$targetdir/published/lib/")" ;; 4) architecture="universal" ;; *) fail "Could not determine architecture of published APK" esac log_minor "Architecture: $architecture" if [ -f "$DIR/../release/$name/$variant/app-$variant_name-$architecture-release-unsigned.apk" ]; then local_apk="$(realpath "$DIR/../release/$name/$variant/app-$variant_name-$architecture-release-unsigned.apk")" else local_apk="$(realpath "$DIR/../release/$name/$variant/app-$variant_name-$architecture-release.apk")" fi fi if [[ ! -f "$local_apk" ]]; then fail "The local APK $local_apk could not be found." fi log_major "Comparing the following APKs:" log_minor "Published: $published_apk" log_minor "Local: $local_apk" # Unpack local APK log_major "Unpacking local APK" unzip -q -d "$targetdir/local/" "$local_apk" # Remove meta information (containing things like the signature) log_major "Removing variable files:" for path in META-INF/ resources.arsc; do for target in local published; do log_minor "rm -r $target/$path" rm -r "${targetdir:?}/${target:?}/${path:?}" done done # Diff! log_major "Comparing releases" diff -r "$targetdir/local/" "$targetdir/published/" && success=1 || success=0 if [ $success -eq 1 ]; then log_major "Success! The APKs match." else log_warning "APK could not be verified." log_warning "Don't panic! First, make sure that you have compiled the correct version!" log_warning "If you cannot figure out why the verification failed," log_warning "send us an e-mail to opensource@threema.ch containing the log above." exit 2 fi