AIR GO and APK Signing

Hello you all, this is Seunghoon Kim, a member of the AIR GO development team at LINE. Previously on our blog, we had a chance to introduce AIR GO with this post, Introducing AIR GO. Following up on the article, I’d like to talk about APK Signing and what AIR GO detects in regards to it. Recently, Google introduced the APK Signature Scheme v3 for Android 9 Pie and AIR GO has updated its verification process to check for the new scheme. Today, I’d like to share what I’ve learnt through the process of updating. 

Introduction to APK Signing

Let me start by briefly going through the overview of APK signing.

The purpose of APK Signing

So, what does APK signing do? What is it for? The purpose of it is as follows:

  • Verifies data integrity of APK files.
  • Uniquely identifies APK signers (developers)
  • Verifies the new app when updating the app on Google Play (Both the old and new APKs must have been signed with the same certificate to be updated)

Signing schemes

Android provides the following three types of signing schemes.

  • Scheme v1: JAR signing
  • Scheme v2: APK Signature Scheme v2, introduced in Android 7.0 Nougat
  • Scheme v3: APK Signature Scheme v3, introduced in Android 9 Pie

Android Studio allows you to select the scheme to sign your APK with; as you can see from below there are checkboxes for you to choose a scheme. (This information is valid up to SDK v28.0.3, as of December 27, 2018.)

Coverage per signing scheme

The coverage of each signing scheme is different:

  • v1: APK integrity is partially guaranteed.
  • v2: APK integrity is guaranteed for the whole file, but the same signing key must be used to update your app on Google Play.
  • v3: APK integrity is guaranteed for the whole file, supports key rotation and new keys can be used for updating updating apps on the Google Play. (To use APK Signing Scheme v3, download the source from here and build.)

Let’s have a look at each scheme in more detail.

APK Signing Block Schemes

As of January 2019 (when this post was written), three schemes are available for APK Signing:

Scheme v1 (JAR Signing) 

As the most basic signing scheme, scheme v1 is based on a manifest file. This manifest file contains the names of the files that compose an APK and a the hash of each file contained in the APK. Signatures are verified using the signature file (META-INF/<signer>.SF) and the META-INF/<signer>.(RSA|DSA|EC) file. Only if a file’s signature is valid, the file is loaded onto the memory. Otherwise, the file doesn’t get loaded.

APK Signature Scheme v1 does support verifying the integrity of the files that are included into the APK but not the whole APK, which is why it is weak against vulnerabilities such as the Janus vulnerability. The vulnerability was disclosed in 2017, and Google recommends to use APK Signing Scheme v2 or higher.

Scheme v2

APK Signature Scheme v2 was introduced in Android 7.0 Nougat, to supplement v1. What’s new in v2 is that v2 creates an APK Signing Block in the APK file only for verification. While v1 uses the manifest file for verification, v2 creates signing information based on the data of ZIP entries, stores the information in the signing block and uses it at the time of verification.

An APK Signing Block is created using the contents of ZIP entries, the Central Directory and the End of Central Directory. I will get into more detail about this in the Digest section. The APK Signing Block created is used for verifying the APK at the time of installing the APK on Android.

The Central Directory does not specify the position of the APK Signing Block. This makes the block invisible when an APK is extracted using a tool that parses the file using the information of the Central Directory. For example, with 010 Editor‘s ZIP template, we cannot check the signing block of an APK file. So, to have a go at visualizing and analyzing an APK Signing Block, I modified the Zip Template of 010 Editor. The following is a screenshot of an APK file extracted and the highlighted part is the APK Signing Block of the APK.

An APK Signing Block is structured differently compared to that of ZIP entries (Zip entry: An entry in a ZIP file that contains the headers of the files within the ZIP.) The block structure consists of the following members.

Structure memberMember TypeDescription
uiApkSignBlockSizeuintThe size of the APK Signing Block
uiApkSignBlockExtendSizeuintThe size of the space reserved to indicate the size of an APK Signing Block in case the size value does not fit in the uiApkSignBlockSizemember.
scheme[]structure arrayA list of scheme blocks (structures) an app is signed with
paddingscheme(Option)structureThe padding scheme to be used for aligning data entries before the Central Directory.
uiEndApkSignBlockSizeuintThe size of the APK Signing Block (The value must be equal to the uiApkSignBlockSize member)
uiEndApkSignBlockExtendSizeuintThe size of the space reserved to indicate the size of an APK Signing Block in case the size value is too big for the uiApkSignBlockSize member to represent. (The value must be equal to the uiApkSignBlockExtendSize member.)
signSignature[16]char arrayThe signature indicating the end of the signature. The value is fixed to "APK Sig Block 42"

A scheme block for v2 is structured as below:

Each member of the block structure is described below. Note that the blockId member for v2 is always set to 0x7109871a to indicate that the scheme block is for v2.

Structure MemberMember TypeDescription
uiSchemeBlockSizeuintThe size of the scheme block
uiSchemeblockExtendSizeuintThe size of the space reserved to indicate the size of the scheme block in case the size does not fit in the uiSchemeBlockSize member.
blockIduintThe ID of the scheme used. For v2, this value is fixed to 0x7109871a.
uiSignBlockSizeuintThe size of a sign block including the signed data, signature and public key.
uiSignSizeuintThe size of the SignedData array
SignedData[uiSignSize]char arrayThe signed data
uiSignatureSizeuintThe size of the signature
signatureData[uiSignatureSize]charThe signature
uiPublickeySizeuintThe size of the public key
publickeyData[uiPublickeySize]charThe public key

Scheme v3

Scheme v3 block is structured as below:

See the following table for the description of each struct member. Note that the blockId member for v3 is always set to 0xf05368c0, to indicate that Scheme v3 is being used.

Struct MemberMember TypeDescription
uiSchemeBlockSizeuintThe size of the scheme block
uiSchemeblockExtendSizeuintThe size of the space reserved to indicate the size of a scheme block in case the size does not fit in the uiSchemeBlockSize member.
blockIduintThe ID of the scheme used. For v3, this value is fixed to 0xf05368c0.
uiSignBlockSizeuintThe size of a sign block including the signed data, signature, SDK information and public key.
uiSignSizeuintThe size of the SignedData array
SignedData[uiSignSize]char arrayThe signed data
uiMinSDKuintThe minimum version of SDK required for the APK to work. This member is introduced in Scheme v3.
uiMaxSDKuintThe maximum version of SDK required for the APK to work This member is introduced in Scheme v3.
uiSignatureSizeuintThe size of a the signature
signatureData[uiSignatureSize]char arrayThe signature
uiPublickeySizeuintThe size of the public key
publickeyData[uiPublickeySize]char arrayThe public key

APK Signature Scheme v2

Before I get into the details, I’d like us to look at digests, a rather unknown concept that is used with the SignedData in scheme block.

Digests

First, digests are constructed as below:

  • Chunks: All the blocks excluding an APK Signing Block are broken into chunks of 1MB. The last chunk can be sized less than 1MB. 
  • Digests of chunks: Based on the digest algorithm, the chunks are made into 32-bit or 64-bit long hashes. (An empty digest is placed at the front of the digests, containing the value 0x5 and the number of chunks)
  • Digest: A hash used in app verification which is calculated using the digests of chunks. 

Suppose Sha256 is used as the digest algorithm, and we have 100 chunks. Our digests of chunks will be constructed as shown below. Of the first 40-bit block, the first byte is 0x5a and the following 4 bytes represent the number of chunks.

Each digest of a chunk is calculated by hashing a chunk that is 1MB or less in a size. The digests of chunks are then used to generate another hash, the digest. (At the time verifying an app, a digest is calculated using the same method and then compared with this digest contained in the APK Signing Block.)

Process of verification with Scheme v2

The process of installing an APK file signed with APK Signing Scheme v2 flows as illustrated below. (Note that an APK signed only with v2 cannot be installed on devices that only support the scheme v1.)

The process is as follows:

  1. Android checks if there is an APK Signing Block. If there is not, the verification runs with scheme v1.
  2. Android checks for the signature algorithm ID and extracts the SignedData.
  3. Android calculates the digest.
  4. Android extracts the digest in the SignedData and compares the extracted digest and the one calculated.
  5. Android verifies if the SubjectPublicKeyInfo structure which is contained in the first certificate of the certificates matches the public key.
  6. If the public key is valid, Android starts installing the APK.

APK Signature Scheme v3

A scheme block for APK Signature Scheme v3 is structured almost the same as that of v2 and the block is located in the APK Signing Block, just like v2. In this section, I’ll use a signature tool, apksigner, to check a v3 signature with an APK signed with v3 scheme. Using the tool, you can see that an app is signed with scheme v3 or not, as shown below.

java -jar apksigner.jar verify --verbose certTest.apk 

Verifies

Verified using v1 scheme (JAR signing): true

Verified using v2 scheme (APK Signature Scheme v2): true

Verified using v3 scheme (APK Signature Scheme v3): true

Number of signers: 1

Now, like we did with v2, let’s use 010 Editor to see an APK Signing Block. The following screenshot shows the APK Signing Block of an APK. The highlighted part in blue is the sign ID (blockId) which is always set to 0xf05368c0 to indicate that the scheme block is based on scheme v3.

Scheme v2 vs. v3

The structure of scheme blocks are identical whether they are based on v2 or v3. Without the blockId, which is fixed for v2 and for v3, we cannot distinguish what version a scheme block is based on. However, there is another way for distinction. Let’s see what it is.

The scheme block for v2 is structured as below (See the full code here).

private static final class V2SignatureSchemeBlock {
 
 private static final class Signer {
  public byte[] signedData;
  public List < Pair < Integer, byte[] >> signatures;
  public byte[] publicKey;
 }
 
 
 private static final class SignedData {
  public List < Pair < Integer, byte[] >> digests;
  public List < byte[] > certificates;
  public byte[] additionalAttributes;
 }
 
}

The scheme block for v3 is structured as below (See the full code here).

private static final class V3SignatureSchemeBlock {
 private static final class Signer {
 
  public byte[] signedData;
  public int minSdkVersion;
  public int maxSdkVersion;
  public List < Pair < Integer, byte[] >> signatures;
  public byte[] publicKey;
 }

 private static final class SignedData {
  public List < Pair < Integer, byte[] >> digests;
  public List < byte[] > certificates;
  public int minSdkVersion;
  public int maxSdkVersion;
  public byte[] additionalAttributes;
 }
 
}

Did you catch the difference? Scheme v3 has two members that are not in v2; minSdkVersion and maxSdkVersion in the SignedDataclass. 

Proof-of-rotation

For apps to be registered on Google Play, they must be signed. Updating apps on the store can be done only if the signing key of the old and the new are identical. This requirement ensures that only the original developer can update an app but has some issues as below.

  • Having to share a single key. If the ownership of an app changes, the original key needs to be transferred to the new developer.
  • Having to re-register your app with a new package name in case you lost access to the original signing key

To resolve these issues, Android 9 Pie introduced Scheme v3 with the concept of proof-of-rotation. Developers can sign an app with the SigningCertificateLineage file that is generated by linking an old signing key with a new one. Apps can be updated with a new signing key only if the trust level between the two keys are confirmed.

SigningCertificateLineage file

The SigningCertificateLineage file is structured as shown below:

The Node represents the relationship between an old signer (old version) and a new signer (new version), with a signature that certifies the child (new) node. Having this signature implies that the two keys can be trusted. The first node does not contain the parent (old-signer) information of an old signer, so a fake node is created. You can see from the following capture the information of the fake node right below the label 0. The second node (represented as 1 in the capture) contains the information of the old-signer and the new signer.

Creating SigningCertificateLineage file and signing the app

To use this proof-of-rotation, you need to create a SigningCertificateLineage file. With this file we can use the certificate:

  1. Create the file by running apksigner with the rotate-out option to connect the two keys used in app signing; one for the old one and the other for new one.
$apksigner rotate-out/path/to/new/file-old-signer-ks release.jks-new-signer-ks release2.jks
  1. Sign the app by running apksigner again with the sign option and the file generated in step 1.
$apksigner sign-lineage/path/to/new/file-ks release.jks-nextsigner-ks release2.jks newapp.apk

Process of verification with Scheme v3

The process of verifying an app with App Signing Scheme v3 is as follows:

  1. Android checks if there is a v3 scheme block in the APK Signing Block. If there is not, then scheme v2 is used for verification.
  2. Android checks for the signature algorithm ID and extracts the signed data.
  3. Android compares the minimum SDK version and the maximum SDK version contained in the SignedData with the platform version.
  4. Android extracts the digest in the SignedData and compares the extracted digest and the one calculated.
  5. Android verifies if the SubjectPublicKeyInfo structure, which is in the first certificate of the certificates, matches the public key.
  6. Android checks if there is an attribute for proof-of-rotation. If there is, Android verifies the certificate information.
  7. If the public key is valid and the certificate is valid, Android starts installing the APK.

AIR GO and APK Signing

AIR GO checks the signing information of an APK file. If an app is signed only with scheme v1, AIR GO recommends the developer to use Scheme v2 or higher, as shown below, for security and performance.


Portions of this page are reproduced from work created and shared by the Android Open Source Project and used according to terms described in the Creative Commons 3.0 Attribution License.

Related Post