This page outlines various methods to ensure that the APK that is installed on your Android device corresponds to the claim that was made in the Claimant Model. This involves pulling the APK in question from your device, checking its code integrity, and performing a log inclusion proof on the extracted artifact.
Verification Process
A transparency log is implemented with a Merkle tree consisting of hashes. A leaf node contains data, and a parent node contains the hash of its children.
Basically, two computations are performed on the Merkle tree to verify the tamper-evident property of transparency logs: the inclusion proof and consistency proof. The former proves that the log includes an entry corresponding to a particular APK version. The log entry includes a hash, which is the SHA256 digest of the code signature token in the form of a JSON Web Token (JWT), which can be obtained from corresponding APKs. The latter proves that when new entries are added to the tree, the new checkpoint is (cryptographically) consistent with the previous version of the tree.
To verify a covered APK, perform an inclusion proof test based on a witnessed checkpoint. Note that we are planning to integrate this log with a public witness network using a standardized witness protocol. This will provide a witnessed checkpoint, which guarantees the consistency of the log.
If you want to convince yourself that the APK you have on your device conforms to the claim that is made in the claimant model, refer to the write-up below.
Inclusion Proof
An Android user can check that a covered APK on their device is in the log by first extracting the APK and its relevant metadata, then comparing their recomputed root hash with the root hash contained in the published checkpoint. If they match, then the Android user can be assured of some protections described in the Threat Model.
How to Verify an APK Inclusion In Log
As described earlier, the list of APKs that are currently covered can be found listed in the Overview page.
Prerequisites to Verifying
Before proceeding to verify that the APK that you have just extracted from your device is in accordance with our claim, you will need the following tools by installing them from a network-connected computer.
Android Debug Bridge (ADB)
ADB
is a tool that communicates with an Android device, available on the
Android SDK Platform Tools website.
bundletool
bundletool
is a tool that is used to build an Android App Bundle (AAB). It can
also be used to convert an AAB into APKs that can be installed on devices.
It can be downloaded from GitHub.
Androguard
Androguard is a collection of tools that is used to analyze APKs. It can be downloaded and installed from the Androguard website.
Inclusion Proof verifier
This is a Go module that we published in a git repository within the Android
Open Source Project (AOSP) named avb
.
It is capable of querying the Google System Services APK transparency log
and outputs whether a package is included in the log.
An example of how this is used can be found in a later
section.
To download this tool, you must first clone the avb
repository.
computer:~$ git clone https://android.googlesource.com/platform/external/avb
The source code of the verifier can be found in tools/transparency/verify
within the avb
repository.
Construct a Payload for Verification
To verify that the APK you extracted from your device according to our claims, you must construct a log payload from information derived from the APK.
Before you begin, make sure that adb
can be used on your device by
enabling adb debugging on
your device.
Then, locate where the APK is installed on your device. For the purposes of this guide, we will make use of the Android System Key Verifier APK (com.google.android.contactkeys) as our working example.
computer:~$ adb shell pm list packages -f | grep contactkeys
package:/data/app/~~i5WYSO4PuAAv798-eHdM7A==/com.google.android.contactkeys-PQCKjnn7xDqjeVhcUDibBA==/base.apk=com.google.android.contactkeys
If the Android System Key Verifier APK is installed on your device, the command above will return a path indicating where it is installed on your device. Else, you won't see any output.
Then, download the APK from your Android device onto the computer you are working using this command (note that the actual location and the APK filename on your device may vary):
computer:~$ mkdir -p /tmp/testdir && cd /tmp/testdir
computer:/tmp/testdir$ adb pull /data/app/~~i5WYSO4PuAAv798-eHdM7A==/com.google.android.contactkeys-PQCKjnn7xDqjeVhcUDibBA==/base.apk ./contactkeys_candidate.apk
To obtain and thus verify the package name of the APK you have just downloaded, you will need to first unzip the APK, as an APK is ultimately a special kind of ZIP file.
computer:/tmp/testdir$ mkdir extracted && unzip contactkeys_candidate.apk -d extracted/
This step unpacks all the files that made up the APK. The package name and
version can be found in the
manifest
of the APK, which is usually in a file named AndroidManifest.xml
.
However, the obtained manifest file is in a binary form, which is not human
readable. In order to convert the binary XML into a human readable form, we make
use of the axml
tool from the androguard
suite (as required
to be installed in the prerequisite) section.
computer:/tmp/testdir$ androguard axml extracted/AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1413" android:versionName="1.48.688082145" android:compileSdkVersion="35" android:compileSdkVersionCodename="VanillaIceCream" android:requiredSplitTypes="" android:splitTypes="" package="com.google.android.contactkeys" platformBuildVersionCode="35" platformBuildVersionName="VanillaIceCream">
From the output above, we now can be sure that this APK's package name is
com.google.android.contactkeys
and the version number (versionCode) is 1413
.
Now, we will search for the code transparency signature within the APK. It
should be a file named code_transparency_signed.jwt
contained within the
META-INF
folder amongst the other extracted files from the APK.
computer:/tmp/testdir$ sha256sum extracted/META-INF/code_transparency_signed.jwt
1779a2aee029112c2c9bfc9390b9678f3e5f4595b39705e8528dd522e8042f11 code_transparency_signed.jwt
With this hash string, we now have every piece of information needed to piece together a log payload according to the format described in the Log Content section. In this example, a corresponding log payload should look like the following:
1779a2aee029112c2c9bfc9390b9678f3e5f4595b39705e8528dd522e8042f11
SHA256(Signed Code Transparency JWT)
com.google.android.contactkeys
1143
Also note the newline character after the package version.
You can save the content into a file, such as payload.txt
. This will come in
handy when doing inclusion proof test later.
Verify the Authenticity of APK Code Signature
Now, we should verify the authenticity of the code signature token
embedded within the APK. To do so, we make use of bundletool
and the public
key of the key pair that has been used to sign it in the first place. They are
published within each section of the respective
APKs. Assuming that you have saved the public key certificate
(e.g. for Android System Key Verifier)
into a file named signing_cert_pubkey.pem
,
follow the guide below to perform code signature verification.
First, you should create a zip archive and add the candidate APK into the zip archive.
computer:/tmp/testdir$ zip -u test.zip contactkeys_candidate.apk
zip warning: test.zip not found or empty
adding: contactkeys_candidate.apk (deflated 58%)
computer:/tmp/testdir$ file test.zip
test.zip: Zip archive data, at least v2.0 to extract, compression method=deflate
We are now ready to make use of bundletool
's check-transparency
command to
verify if the code signature embedded within the candidate APK matches the one
that is published.
computer:/tmp/testdir$ java -jar BUNDLETOOL_INSTALL_PATH/bundletool-all-version.jar check-transparency \
--mode=apk \
--apk-zip=test.zip \
--transparency-key-certificate=signing_cert_pubkey.pem
APK signature is valid. SHA-256 fingerprint of the apk signing key certificate (must be compared with the developer's public key manually): D9 E1 73 5B 2A 39 51 27 3A 87 35 B7 66 9E F1 9E F5 3A F1 C1 27 5C BA 31 39 3C 18 40 8B 03 79 D0
Code transparency signature verified for the provided code transparency key certificate.
Code transparency verified: code related file contents match the code transparency file.
You should make sure that the output of the command above states that both the
code transparency signature and the code transparency are
verified.
If they are not, for example, if you are seeing output such as
Code transparency verification failed because the provided public key
certificate does not match the transparency file
,
this means the code integrity of the APK in question may potentially be
compromised and you should not trust the APK.
Remember to double check that you are verifying them against the correct public
key certificate.
Otherwise, if everything else checks out, this signifies that the authenticity
of the code signature is verified for the APK you are validating.
Verifying Package Inclusion (Inclusion Proof)
Using the payload that you have constructed earlier, you are now ready to test if the package in question has been included in the transparency log.
An inclusion proof tool has been published in the avb
repository within
the Android Open Source Project. To run it:
computer:external/avb/tools/transparency/verify$ PAYLOAD_PATH=PATH_TO_PAYLOAD_DIR/payload.txt
computer:external/avb/tools/transparency/verify$ go build cmd/verifier/verifier.go
computer:external/avb/tools/transparency/verify$ ./verifier --payload_path=${PAYLOAD_PATH} --log_type=google_system_apk
The verifier uses the corresponding checkpoint and the log contents (found at the tile directory) to check that your APK payload is in the transparency log, verifying that it is indeed published by Google.
The output of the command is written to stdout:
OK. inclusion check success!
if the package's code is included in the log,FAILURE
if it isn't.