1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
|
#!/bin/sh -eu
#
# apk_verify -- Checks the public key and signature in APK files
#
# Copyright (c) 2019 Samuel Lidén Borell <samuel@kodafritt.se>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
if [ $# = 0 ] || [ "$1" = --help ]; then
cat <<EOF
usage: $0 --manual <sha256> <apk file>...
$0 --certname <name> <sha256> <apk file>...
$0 --auto <apk file>...
Checks the public key and signature in APK files.
With --manual, the first argument must be the SHA-256 of the public key
The following arguments are the APK files to verify.
With --certname, you can override the filename base for the certificate
file. If not specified, this will default to CERT.RSA.
With --auto, the public key SHA-256 and the certname is autodetected.
Note that all options must come first, before SHA-256 and file names.
EOF
exit
fi
# Parse command line arguments
# TODO rewrite using getopt?
auto=0
certname=CERT.RSA
trusted_sha256=
if [ "$1" = --certname ]; then
certname="$2"
trusted_sha256="$3"
shift 3
elif [ "$1" = --auto ]; then
auto=1
shift
elif [ "$1" = --manual ]; then
trusted_sha256="$1"
shift
else
echo "Invalid option: $1" >&2
echo "Should be one of --manual, --certname or --auto" >&2
exit 2
fi
if [ "$auto" = 0 ]; then
trusted_sha256=$(echo "$trusted_sha256" | tr '[:upper:]' '[:lower:]')
cleaned=$(echo "$trusted_sha256" | tr -cd 0123456789abcdef)
len=$(printf %s "$trusted_sha256" | wc -c)
if [ "$trusted_sha256" != "$cleaned" ] || [ "$len" != 64 ]; then
echo "Invalid public key SHA-256: $trusted_sha256"
exit 2
fi
fi
# Definitions
any_failure=0
red=$(printf '\033[1;31m')
green=$(printf '\033[32m')
normal=$(printf '\033[0m')
failure() {
echo "$1: ${red}VERIFICATION FAILURE: $2${normal}" >&2
}
success() {
echo "$1: ${green}VALID SIGNATURE${normal}"
}
list_unsigned() {
jarsigner -verify -certs -verbose "$3" | grep -E '^[a-z0-9 ]{10,10}[0-9]' | grep -vE '^sm ' | cut -c '-3,42-' |
grep -vE "^(s +META-INF/MANIFEST\\.MF| +META-INF/$namebase\\.($sigalg|SF))\$"
}
while [ $# -ge 1 ]; do
ok=1
if [ "$auto" = 1 ]; then
case "$(basename "$1" | tr _- ..)" in
com.bankid.bus.*)
# BankID - Swedish eID system
trusted_sha256=ada2624b35fca3cc340e11899454e550fba982b44d82b97efd16aa3a7a467c16
certname=BID_ANDR.RSA;;
se.bankgirot.swish.*)
trusted_sha256=d31d1d5b73dec8cf45a51114d126dfe60cf264e208826c2ce413c4717c022566
certname=CERT.RSA;;
se.slso.app4us.*)
# Alltid Öppet - Swedish health care and covid19 vaccine booking app
trusted_sha256=873c14fc8f5a1000d89dee5708c1992014b2c1218eb9c060e68645ca9047d064
certname=APP4US.RSA;;
Signal.website.*)
# trusted cert is 29f34e5f27f211b424bc5bf9d67162c0eafba2da35af35c16416fc446276ba26
trusted_sha256=29f34e5f27f211b424bc5bf9d67162c0eafba2da35af35c16416fc446276ba26
certname=SIGNAL_S.RSA;;
*)
failure "$1" "unknown APK name. cannot auto-detect key"
any_failure=1
shift
continue
esac
fi
namebase=$(printf %s "$certname" | cut -d '.' -f 1)
sigalg=$(printf %s "$certname" | cut -d '.' -f 2)
jarsigner -verify -certs -verbose "$1" >/dev/null || {
failure "$1" "jarsigner command failed"
ok=0
}
# Time zone can be 3 or 4 letters. If it is 3 letters, then a leading space will be included
# Also, the formatting can change between versions of jarsigner
unsigned=$(jarsigner -verify -certs -verbose -sigfile "$namebase" -sigalg "$sigalg" "$1" | grep -E '^[a-z0-9 ]{10,10}[0-9]' | grep -vE '^sm ' | cut -c '-3,43-' |
grep -cvE "^(s +META-INF/MANIFEST\\.MF| +META-INF/$namebase\\.($sigalg|SF))\$" || true)
if [ "$unsigned" != 0 ]; then
failure "$1" "There are $unsigned unsigned files:"
list_unsigned "$namebase" "$sigalg" "$1" >&2
ok=0
fi
shasum=$(unzip -p "$1" "META-INF/$certname" | openssl pkcs7 -inform der -print_certs | openssl x509 -outform der | sha256sum | cut -d' ' -f 1)
if [ "$shasum" = e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 ]; then # sha256 of empty string
failure "$1" "Could not find public key"
ok=0
elif [ "$shasum" != "$trusted_sha256" ]; then
failure "$1" "Untrusted public key hash: $shasum"
ok=0
fi
if [ "$ok" = 1 ]; then
success "$1"
else
any_failure=1
fi
shift
done
exit $any_failure
|