verify-build.sh 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. #!/usr/bin/env bash
  2. #
  3. # A script to verify that a locally compiled APK matches the released APK.
  4. #
  5. # Steps taken to achieve this:
  6. #
  7. # 1. Unpack both APK files
  8. # 2. Remove meta information (containing things like the signature)
  9. # 3. Recursively diff the two directories to ensure they match
  10. # _____ _
  11. # |_ _| |_ _ _ ___ ___ _ __ __ _
  12. # | | | ' \| '_/ -_) -_) ' \/ _` |_
  13. # |_| |_||_|_| \___\___|_|_|_\__,_(_)
  14. #
  15. # Threema for Android
  16. # Copyright (c) 2020 Threema GmbH
  17. #
  18. # This program is free software: you can redistribute it and/or modify
  19. # it under the terms of the GNU Affero General Public License, version 3,
  20. # as published by the Free Software Foundation.
  21. #
  22. # This program is distributed in the hope that it will be useful,
  23. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  24. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  25. # GNU Affero General Public License for more details.
  26. #
  27. # You should have received a copy of the GNU Affero General Public License
  28. # along with this program. If not, see <https://www.gnu.org/licenses/>.
  29. set -euo pipefail
  30. GREEN="\033[0;32m"
  31. RED="\033[0;31m"
  32. YELLOW="\033[1;33m"
  33. RESET="\033[0m"
  34. function print_usage() {
  35. echo "Usage: $0 -n <version-name> -v <variant> -p <published-apk> [-l <local-apk>]"
  36. echo ""
  37. echo "Options:"
  38. echo " -n <version-name> The version name. Example: '4.43k'"
  39. echo " -v <variant> Variant to verify: Either googleplay_private, threemashop_private, libre_private or hms_private"
  40. echo " -p <published-apk> Path to the APK file extracted from the phone"
  41. echo " -l <local-apk> Optional: Path to the locally built APK"
  42. echo " -h,--help Print this help and exit"
  43. }
  44. function log() {
  45. echo -en "$1"
  46. echo -n "$2 $3"
  47. echo -e "$RESET"
  48. }
  49. function log_major() { log "$GREEN" "==>" "$1"; }
  50. function log_minor() { log "$GREEN" "--> " "$1"; }
  51. function log_warning() { log "$YELLOW" "==>" "$1"; }
  52. function log_error() { log "$RED" "!!!" "Error: $1"; }
  53. function fail() {
  54. log_error "$1"
  55. exit 1
  56. }
  57. # Re-implementation of realpath function (hello macOS)
  58. function realpath() {
  59. OURPWD=$PWD
  60. cd "$(dirname "$1")"
  61. LINK=$(readlink "$(basename "$1")")
  62. while [ "$LINK" ]; do
  63. cd "$(dirname "$LINK")"
  64. LINK=$(readlink "$(basename "$1")")
  65. done
  66. REALPATH="$PWD/$(basename "$1")"
  67. cd "$OURPWD"
  68. echo "$REALPATH"
  69. }
  70. # Determine script directory
  71. DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
  72. # If no arguments are passed, print usage
  73. if [ "$#" -lt 1 ]; then print_usage; exit 1; fi
  74. # Parse arguments
  75. name=""
  76. variant=""
  77. published_apk=""
  78. local_apk=""
  79. while [[ "$#" -gt 0 ]]; do
  80. case $1 in
  81. -n) name="$2"; shift ;;
  82. -v) variant="$2"; shift ;;
  83. -p) published_apk="$2"; shift ;;
  84. -l) local_apk="$2"; shift ;;
  85. -h|--help) print_usage; exit 0 ;;
  86. *) echo "Unknown parameter passed: $1"; print_usage; exit 1 ;;
  87. esac
  88. shift
  89. done
  90. # Process arguments
  91. if [[ "$name" == "" || "$name" == .* ]]; then
  92. log_error 'Please set a valid version name with "-n <name>".'
  93. fail 'Example: "-n 4.43k"'
  94. fi
  95. if [[ "$published_apk" == "" ]]; then
  96. log_error 'Please set a valid published APK path with "-p <path>".'
  97. fail 'Example: "-p threema-extracted.apk"'
  98. fi
  99. published_apk=$(realpath "$published_apk")
  100. if [[ "$variant" == "" ]]; then
  101. log_error 'Please set a valid build variant with "-v <variant>".'
  102. fail 'Example: "-v googleplay_private" or "-v threemashop_private"'
  103. fi
  104. case "$variant" in
  105. googleplay_private) variant_name="store_google" ;;
  106. threemashop_private) variant_name="store_threema" ;;
  107. libre_private) variant_name="libre" ;;
  108. hms_private) variant_name="hms" ;;
  109. *) fail "Invalid build variant: $variant" ;;
  110. esac
  111. # Validate target directory
  112. targetdir=$(realpath "$DIR/../reproduce")
  113. if [[ -d "$targetdir" ]]; then
  114. fail "The directory $targetdir already exists. Please remove it first."
  115. fi
  116. mkdir -p "$targetdir"/{published,local}
  117. # Unpack published APK
  118. if [[ ! -f "$published_apk" ]]; then
  119. fail "The published APK $published_apk could not be found."
  120. fi
  121. log_major "Unpacking published APK"
  122. unzip -q -d "$targetdir/published/" "$published_apk"
  123. # Determine local APK path
  124. if [[ "$local_apk" == "" ]]; then
  125. log_major "Determine local APK path"
  126. lib_count="$(find "$targetdir/published/lib/" -mindepth 1 -maxdepth 1 -type d | wc -l | xargs)"
  127. log_minor "Found $lib_count libs"
  128. case "$lib_count" in
  129. 1) architecture="$(ls "$targetdir/published/lib/")" ;;
  130. 4) architecture="universal" ;;
  131. *) fail "Could not determine architecture of published APK"
  132. esac
  133. log_minor "Architecture: $architecture"
  134. if [ -f "$DIR/../release/$name/$variant/app-$variant_name-$architecture-release-unsigned.apk" ]; then
  135. local_apk="$(realpath "$DIR/../release/$name/$variant/app-$variant_name-$architecture-release-unsigned.apk")"
  136. else
  137. local_apk="$(realpath "$DIR/../release/$name/$variant/app-$variant_name-$architecture-release.apk")"
  138. fi
  139. fi
  140. if [[ ! -f "$local_apk" ]]; then
  141. fail "The local APK $local_apk could not be found."
  142. fi
  143. log_major "Comparing the following APKs:"
  144. log_minor "Published: $published_apk"
  145. log_minor "Local: $local_apk"
  146. # Unpack local APK
  147. log_major "Unpacking local APK"
  148. unzip -q -d "$targetdir/local/" "$local_apk"
  149. # Remove meta information (containing things like the signature)
  150. log_major "Removing meta information, containing things like the app signature:"
  151. for path in META-INF/ resources.arsc; do
  152. for target in local published; do
  153. log_minor "rm -r $target/$path"
  154. rm -r "${targetdir:?}/${target:?}/${path:?}"
  155. done
  156. done
  157. # Diff!
  158. log_major "Comparing releases"
  159. diff -r "$targetdir/local/" "$targetdir/published/" && success=1 || success=0
  160. if [ $success -eq 1 ]; then
  161. log_major "Success! The APKs match."
  162. else
  163. log_warning "APK could not be verified."
  164. log_warning "Don't panic! First, make sure that you have compiled the correct version!"
  165. log_warning "If you cannot figure out why the verification failed,"
  166. log_warning "send us an e-mail to opensource@threema.ch containing the log above."
  167. exit 2
  168. fi