Google System Services APK Transparency Full Verification

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.