1.0 Structure
An Android APK is a signed JAR and a signed JAR is a good old-fashioned ZIP file.
If you list the contents of an APK using unzip
you will see something like this
Archive: GoodAppBadApp.apk
Length Date Time Name
-------- ---- ---- ----
624 07-10-13 21:11 res/layout/activity_main.xml
464 07-10-13 21:11 res/menu/activity_main.xml
1588 07-10-13 21:11 AndroidManifest.xml
2504 07-10-13 21:11 resources.arsc
409 07-10-13 21:11 res/drawable-hdpi/ic_action_search.png
4129 07-10-13 21:11 res/drawable-hdpi/ic_launcher.png
1756 07-10-13 21:11 res/drawable-ldpi/ic_launcher.png
311 07-10-13 21:11 res/drawable-mdpi/ic_action_search.png
2654 07-10-13 21:11 res/drawable-mdpi/ic_launcher.png
491 07-10-13 21:11 res/drawable-xhdpi/ic_action_search.png
5456 07-10-13 21:11 res/drawable-xhdpi/ic_launcher.png
366112 07-10-13 21:11 classes.dex
1034 07-10-13 21:11 META-INF/MANIFEST.MF
1087 07-10-13 21:11 META-INF/CERT.SF
776 07-10-13 21:11 META-INF/CERT.RSA
-------- -------
389395 15 files
It is the presence of the manifest file
META-INF/MANIFEST.MF
which make this ZIP file a JAR, and it is the presence of the ‘signature’ files
META-INF/CERT.SF
META-INF/CERT.RSA
that makes it a signed JAR.
2.0 The Meta Files
2.1 MANIFEST.MF
This is what’s in the MANIFEST.MF
file contained in the APK shown in the example above
Manifest-Version: 1.0
Created-By: 1.0 (Android)
Name: res/drawable-xhdpi/ic_launcher.png
SHA1-Digest: TJE1tg63y88xLdIALKGpuLmgh0s=
Name: res/drawable-mdpi/ic_action_search.png
SHA1-Digest: XjltJdEB3tvakTn9CN7KwdaNT68=
Name: AndroidManifest.xml
SHA1-Digest: iXwTXVvLX3gY+ByGhybEJvIxP08=
Name: res/drawable-mdpi/ic_launcher.png
SHA1-Digest: aM73uFWfPOOvq5Kk9Ffd3cWm0OQ=
Name: res/drawable-hdpi/ic_launcher.png
SHA1-Digest: ioH2V9g4FYKkqpDHk7jPryMKtcE=
Name: res/layout/activity_main.xml
SHA1-Digest: zd1eNopMfZ7CmMk8GjyPGPpYloc=
Name: resources.arsc
SHA1-Digest: 7nYq90PW0OZVg8ytyjBEcwQhYiA=
Name: classes.dex
SHA1-Digest: b80KFg5gdqlORweY5LrUS87tbVU=
Name: res/drawable-hdpi/ic_action_search.png
SHA1-Digest: rOWnNxWCWKwkicf+FpYGMg1fX2Y=
Name: res/drawable-xhdpi/ic_action_search.png
SHA1-Digest: WzSVDDIHZpn0cFeuxgKRJete4TU=
Name: res/menu/activity_main.xml
SHA1-Digest: TfxVPQP5IRVlJun7A7PdOqdkp+I=
Name: res/drawable-ldpi/ic_launcher.png
SHA1-Digest: r6Mdl54h2qEvVnqgfsgxU4CysiI=
As you can see there is a section for each file in the APK with the exception of those contained in the META-INF
directory.
The SHA1-Digest
attribute specifies the SHA-1[1] digest of the named file. The value is Base64 encoded.
2.2 CERT.SF
The CERT.SF
file is a ‘signature file’. This is odd because technically it does not contain any signatures in the cryptographic sense, but there it is.
Signature files are identified by the suffix .SF
This is what’s in the CERT.SF
file contained in the APK shown in the example above
Signature-Version: 1.0
Created-By: 1.0 (Android)
SHA1-Digest-Manifest: uy1/gV4Q91cFNXDPKFWnIkdp7Hs=
Name: res/drawable-xhdpi/ic_launcher.png
SHA1-Digest: uQhdAiwpzle4OMU4KiZ8rHZZW0s=
Name: res/drawable-mdpi/ic_action_search.png
SHA1-Digest: jAGFiT7RUDOYcmBQqk2FP01Xtb0=
Name: AndroidManifest.xml
SHA1-Digest: app7CkR5QClO2DngK2SwqyoYT/4=
Name: res/drawable-mdpi/ic_launcher.png
SHA1-Digest: 7AYLMxFKX2g7vudLLZTXlC9DWHI=
Name: res/drawable-hdpi/ic_launcher.png
SHA1-Digest: diYsvf3zRlU+JNDApc7+KLVhhk0=
Name: res/layout/activity_main.xml
SHA1-Digest: tK4fMdQKbbzD/KoE5awGKM7o++Y=
Name: resources.arsc
SHA1-Digest: mMTrtN8w3SByhMHUQ19nJ3OT3wo=
Name: res/drawable-hdpi/ic_action_search.png
SHA1-Digest: WvsoZfcfGbz7vtTCwzXLh3VcxFw=
Name: classes.dex
SHA1-Digest: /okgNUxVNi1erXMkC7ftetu9S9U=
Name: res/drawable-xhdpi/ic_action_search.png
SHA1-Digest: 772ITFXOlprwo44bOLvLMa3Vdeg=
Name: res/menu/activity_main.xml
SHA1-Digest: K1PTOAgvzmJK47MHIsJIWgVr/04=
Name: res/drawable-ldpi/ic_launcher.png
SHA1-Digest: zC+Z/B5ib1ntZmz9D80Rj3muYqE=
The SHA1-Digest-Manifest
in the first section specifies the SHA-1 digest of the manifest file.
In the following sections the SHA1-Digest
attribute specifies the SHA-1 digest of the corresponding section in the manifest file.
2.3 CERT.RSA
The CERT.RSA
file is the ‘signed signature file’.
The CERT.RSA
file contained in the APK shown in the example above is a binary file, so this is a representation of its contents produced using the ubiquitous dumpasn1
.
0 772: SEQUENCE {
4 9: OBJECT IDENTIFIER signedData (1 2 840 113549 1 7 2)
15 757: [0] {
19 753: SEQUENCE {
23 1: INTEGER 1
26 11: SET {
28 9: SEQUENCE {
30 5: OBJECT IDENTIFIER sha1 (1 3 14 3 2 26)
37 0: NULL
: }
: }
39 11: SEQUENCE {
41 9: OBJECT IDENTIFIER data (1 2 840 113549 1 7 1)
: }
52 489: [0] {
56 485: SEQUENCE {
60 334: SEQUENCE {
64 3: [0] {
66 1: INTEGER 2
: }
69 4: INTEGER 1322302491
75 13: SEQUENCE {
77 9: OBJECT IDENTIFIER
: sha1withRSAEncryption (1 2 840 113549 1 1 5)
88 0: NULL
: }
90 55: SEQUENCE {
92 11: SET {
94 9: SEQUENCE {
96 3: OBJECT IDENTIFIER countryName (2 5 4 6)
101 2: PrintableString 'US'
: }
: }
105 16: SET {
107 14: SEQUENCE {
109 3: OBJECT IDENTIFIER organizationName (2 5 4 10)
114 7: PrintableString 'Android'
: }
: }
123 22: SET {
125 20: SEQUENCE {
127 3: OBJECT IDENTIFIER commonName (2 5 4 3)
132 13: PrintableString 'Android Debug'
: }
: }
: }
147 30: SEQUENCE {
149 13: UTCTime 26/11/2011 10:14:51 GMT
164 13: UTCTime 18/11/2041 10:14:51 GMT
: }
179 55: SEQUENCE {
181 11: SET {
183 9: SEQUENCE {
185 3: OBJECT IDENTIFIER countryName (2 5 4 6)
190 2: PrintableString 'US'
: }
: }
194 16: SET {
196 14: SEQUENCE {
198 3: OBJECT IDENTIFIER organizationName (2 5 4 10)
203 7: PrintableString 'Android'
: }
: }
212 22: SET {
214 20: SEQUENCE {
216 3: OBJECT IDENTIFIER commonName (2 5 4 3)
221 13: PrintableString 'Android Debug'
: }
: }
: }
236 159: SEQUENCE {
239 13: SEQUENCE {
241 9: OBJECT IDENTIFIER
: rsaEncryption (1 2 840 113549 1 1 1)
252 0: NULL
: }
254 141: BIT STRING, encapsulates {
258 137: SEQUENCE {
261 129: INTEGER
: 00 8A 0A 1D 53 48 FC 26 6D BF 65 57 04 8B 0B E9
: 58 EE E7 11 CE A8 A2 01 72 7B 34 C1 31 60 B4 64
: FC D3 0D 1E 94 BE B3 5B 55 7F 23 97 B9 3D 6F E8
: 17 17 FE CA 50 EB F3 EA 4B 7C F6 F9 B6 6E E7 5D
: 0D 64 79 00 8F 12 1A 76 09 C1 32 4F 28 E8 27 94
: 34 F5 C8 50 C3 0D E9 2E FF A2 97 71 6C 18 7F 7C
: 8A 78 EA 37 50 20 8D DA 6C F2 3F 65 85 67 6A 7B
: 35 BF 5A 8D 53 DE AD 9D 50 70 30 B4 0D A4 60 D4
: [ Another 1 bytes skipped ]
393 3: INTEGER 65537
: }
: }
: }
: }
398 13: SEQUENCE {
400 9: OBJECT IDENTIFIER
: sha1withRSAEncryption (1 2 840 113549 1 1 5)
411 0: NULL
: }
413 129: BIT STRING
: 2E 77 85 3F F4 AF EE 1E 81 9B 8B BB E2 9C AA 95
: BE 38 1E 20 EA E3 63 BE 2B A7 F2 AB 8A F4 F5 27
: 35 F6 7C 67 CF E5 07 82 D0 B5 D8 00 77 CC 9A 8B
: 86 DD 4C DD 41 88 2E C5 B4 B9 7E E9 C4 B2 B9 FA
: 31 A0 60 6F DA 8D D1 F9 FA F6 94 08 92 C2 58 D6
: BF 93 1E 4D B8 DF DD D3 C1 11 89 14 22 C8 8B 85
: 98 04 7A F5 00 D5 1D F2 E0 54 42 82 46 2D FE 2D
: C3 AB 2B C6 BC E7 07 A2 B8 CD 04 26 01 F1 EB E5
: }
: }
545 228: SET {
548 225: SEQUENCE {
551 1: INTEGER 1
554 63: SEQUENCE {
556 55: SEQUENCE {
558 11: SET {
560 9: SEQUENCE {
562 3: OBJECT IDENTIFIER countryName (2 5 4 6)
567 2: PrintableString 'US'
: }
: }
571 16: SET {
573 14: SEQUENCE {
575 3: OBJECT IDENTIFIER organizationName (2 5 4 10)
580 7: PrintableString 'Android'
: }
: }
589 22: SET {
591 20: SEQUENCE {
593 3: OBJECT IDENTIFIER commonName (2 5 4 3)
598 13: PrintableString 'Android Debug'
: }
: }
: }
613 4: INTEGER 1322302491
: }
619 9: SEQUENCE {
621 5: OBJECT IDENTIFIER sha1 (1 3 14 3 2 26)
628 0: NULL
: }
630 13: SEQUENCE {
632 9: OBJECT IDENTIFIER rsaEncryption (1 2 840 113549 1 1 1)
643 0: NULL
: }
645 128: OCTET STRING
: 22 52 11 23 F5 AA 38 2E 28 FD 96 1E 2C AB 96 FC
: E7 85 95 54 A0 F7 14 49 93 F4 19 A7 F0 E5 CC B9
: FF 82 B6 8D 84 A2 3A DD F6 CA 6A 4F 3A 38 B4 EA
: 69 23 BF B1 29 5F C2 49 48 34 63 35 84 59 C6 69
: 0C E3 F1 D3 90 B5 02 F5 0D 79 CC B4 B0 25 0C BD
: 38 4F B2 FC D1 D7 D4 11 84 EA A1 66 31 F0 08 DA
: 0F F6 94 23 96 5C 83 96 C9 DA CA C0 FC 60 84 C2
: 54 21 DA CA E4 C4 D0 7D FD 98 C4 91 8A 2C 5D E4
: }
: }
: }
: }
: }
If you are well-versed in the arcana of ASN.1
and the myriad security standards you will recognize this as ‘signed data’ as specified by PKCS#7. If you aren’t, you won’t, but that’s what it is nonetheless.
It is the SHA-1 digest of the CERT.SF
file which has been encrypted using the private key corresponding to the public key contained in the accompanying certificate.
There is a bit more to it than that, but that is basically it.
3.0 Signed JAR Verification In Theory
To make life more exciting there can in fact be multiple signature files and signed signature files in a signed JAR but in what follows I am going to assume we are attempting to verify the APK shown in the example above in which there is just the one signature/signed signature file pair.
3.1 Decide Whether You ‘Trust’ The Signer Of The JAR
The signer of the JAR is the entity identified by the certificate which accompanies the signed data in the signed signature file (CERT.RSA
).
The decision to ‘trust’ that entity is often made by going up the chain of certificates looking for one which is defined to be a ‘trust root’.
If the certificate that accompanies the signed data in the signed signature file is self-signed or was not signed directly or indirectly by a certificate defined to be a ‘trust root’ then the JAR should be rejected.
3.2 Verify The Signature File
Compute the digest of the signature file (CERT.SF
) and use the signature in the signed signature file (CERT.SF
) to verify it. If verification fails then reject the JAR.
3.3 Verify The Manifest File
If the expected digest of the manifest file is specified in the signature file compute the digest of the manifest file. If the expected and computed digests do not match then reject the JAR.
Alternatively, for each file in the JAR other than those in the META-INF directory find the corresponding section in the manifest.
If there is no corresponding section reject the JAR.
Otherwise find the expected digest of the section in the signature file.
If there is no expected digest fo the section in the signature file reject the JAR.
Otherwise compute the hash of the section in the manifest. If the computed and expected digests do not match reject the JAR.
3.4 Verify The Remaining Files In The JAR
For each file in the JAR that has not already been verified check that its digest has been specified in the manifest file.
If it has not then reject the JAR.
If the expected digest is specified in the manifest file then compute the actual digest. If the expected and computed digests do not match then reject the JAR.
If all the files in the JAR are successfully verified then accept the JAR.
4.0 Signed JAR Verification In Practice
The Java class java.util.JarFile
supports the piecemeal implicit verification of files in signed JARs
4.1 java.util.JarFile
Source: $(ANDROID_SRC)/libcore/luni/src/main/java/java/util/jar/JarFile.java
4.1.1 Constructing A JarFile
4.1.1.1 JarFile(File)
public JarFile(File file) throws IOException {
this(file, true);
}
4.1.1.2 JarFile(File, boolean)
public JarFile(File file, boolean verify) throws IOException
Passing this constructor a File
argument which represents a signed JAR and a verify
argument of true
will result in a JarFile
instance which supports the verification of individual files within the underlying signed JAR.
4.2 The Verification Of Individual Files Within A Signed JAR Using A JarFile Instance
Given a JarFile
instance initialized to verify an underlying signed JAR as descibed above there is surprisingly no way to explicitly verify the entire contents of the signed JAR.
Nor is there any way to explicitly verify an individual file within the signed JAR.
The only way to verify an individual file is to read the entire contents of the file. Doing this will implicitly verify the file but only at the point when all the contents have been read.
Since verification of a file involves computing the digest of the contents it is obviously necessary to read the entire contents before being able to verify it.
What is extremely problematic is that there is nothing whatsoever in the API documentation that explains that this is what happens and more particularly that it is not safe to act on anything in the contents of a file in a signed JAR, despite the fact that the contents are being made available via the API, until the entire file has been read.
4.2.1 The ‘Verification’ API
4.2.1.1 getInputStream
public InputStream getInputStream(ZipEntry ze) throws IOException
The getInputStream
method returns an InputStream
which can be used to read the contents of the file represented by the JarEntry
argument.
If the JarFile
instance was constructed with a signed JAR and it is being verified then the method returns a an instance of the inner class JarFileInputStream
with an associated JarVerifier.VerifyEntry
instance.
4.2.1.2 The JarFileInputStream read Methods
The read
methods read from the underlying stream. They then write the bytes read to the associated verifier.
If the call results in all of the file data having been read then verify
method of the associated verifier is called. This will throw a SecurityException
if the verification fails.
5.0 Signed JAR Verification During The Installation Of An APK By The Android PackageManagerService
When the PackageManagerService
finds an APK while scanning a directory it calls the method scanPackageLI
.
This triggers the verification of the APK as a signed JAR as follows.
5.1 PackageManagerService
Source: $(ANDROID_SRC)/frameworks/base/services/java/com/android/server/pm/PackageManagerService.java
5.1.1 scanPackageLI
private PackageParser.Package scanPackageLI(
File scanFile,
int parseFlags,
int scanMode,
long currentTime,
UserHandle user)
After determining whether it already knows about the APK, in which case it may return, scanPackageLI
calls the method collectCertificatesLI
.
5.1.2 collectCertificatesLI
private boolean collectCertificatesLI(
PackageParser pp,
PackageSetting ps,
PackageParser.Package pkg,
File srcFile,
int parseFlags)
The method calls collectCertificates
on the PackageParser
argument to do the actual work.
5.2 PackageParser
Class: android.content.pm.PackageParser
Source: $(ANDROID_SRC)/frameworks/base/core/java/android/content/pm/PackageParser.java
5.2.1 collectCertificates
public boolean collectCertificates(Package pkg, int flags)
The method begins by creating a JarFile
using the JarFile(File)
constructor.
It then calls the method entries
on the JarFile
instance.
It then iterates over these entries. For each entry it calls the method loadCertificates
.
5.2.2 loadCertificates
private Certificate[] loadCertificates(JarFile jarFile, JarEntry je, byte[] readBuffer)
The method begins by calling getInputStream
on the JarFile
argument passing it the JarEntry
argument.
It then calls read
repeatedly on the InputStream
, ignoring what it is actually read, until the entire contents of the file have been read.
As we have seen it is the call to the read
method which results in the last of the data being returned which causes the verification of that file.
Notes
-
The official SHA standard is here (This is a PDF document).
Copyright (c) 2013 By Simon Lewis. All Rights Reserved.
Unauthorized use and/or duplication of this material without express and written permission from this blog’s author and owner Simon Lewis is strictly prohibited.
Excerpts and links may be used, provided that full and clear credit is given to Simon Lewis and justanapplication.wordpress.com with appropriate and specific direction to the original content.