#!/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