1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.appsecurity.cts;
18 
19 import com.android.tradefed.build.IBuildInfo;
20 import com.android.tradefed.device.DeviceNotAvailableException;
21 import com.android.tradefed.testtype.DeviceTestCase;
22 import com.android.tradefed.testtype.IBuildReceiver;
23 
24 import java.io.BufferedOutputStream;
25 import java.io.File;
26 import java.io.FileOutputStream;
27 import java.io.IOException;
28 import java.io.InputStream;
29 import java.io.OutputStream;
30 import java.util.Locale;
31 
32 /**
33  * Tests for APK signature verification during installation.
34  */
35 public class PkgInstallSignatureVerificationTest extends DeviceTestCase implements IBuildReceiver {
36 
37     private static final String TEST_PKG = "android.appsecurity.cts.tinyapp";
38     private static final String TEST_APK_RESOURCE_PREFIX = "/pkgsigverify/";
39 
40     private static final String[] DSA_KEY_NAMES = {"1024", "2048", "3072"};
41     private static final String[] EC_KEY_NAMES = {"p256", "p384", "p521"};
42     private static final String[] RSA_KEY_NAMES = {"1024", "2048", "3072", "4096", "8192", "16384"};
43     private static final String[] RSA_KEY_NAMES_2048_AND_LARGER =
44             {"2048", "3072", "4096", "8192", "16384"};
45 
46     private IBuildInfo mCtsBuild;
47 
48     @Override
setBuild(IBuildInfo buildInfo)49     public void setBuild(IBuildInfo buildInfo) {
50         mCtsBuild = buildInfo;
51     }
52 
53     @Override
setUp()54     protected void setUp() throws Exception {
55         super.setUp();
56 
57         Utils.prepareSingleUser(getDevice());
58         assertNotNull(mCtsBuild);
59         uninstallPackage();
60     }
61 
62     @Override
tearDown()63     protected void tearDown() throws Exception {
64         try {
65             uninstallPackage();
66         } catch (DeviceNotAvailableException ignored) {
67         } finally {
68             super.tearDown();
69         }
70     }
71 
testInstallOriginalSucceeds()72     public void testInstallOriginalSucceeds() throws Exception {
73         // APK signed with v1 and v2 schemes. Obtained by building
74         // cts/hostsidetests/appsecurity/test-apps/tinyapp.
75         assertInstallSucceeds("original.apk");
76     }
77 
testInstallV1OneSignerMD5withRSA()78     public void testInstallV1OneSignerMD5withRSA() throws Exception {
79         // APK signed with v1 scheme only, one signer.
80         assertInstallSucceedsForEach(
81                 "v1-only-with-rsa-pkcs1-md5-1.2.840.113549.1.1.1-%s.apk", RSA_KEY_NAMES);
82         assertInstallSucceedsForEach(
83                 "v1-only-with-rsa-pkcs1-md5-1.2.840.113549.1.1.4-%s.apk", RSA_KEY_NAMES);
84     }
85 
testInstallV1OneSignerSHA1withRSA()86     public void testInstallV1OneSignerSHA1withRSA() throws Exception {
87         // APK signed with v1 scheme only, one signer.
88         assertInstallSucceedsForEach(
89                 "v1-only-with-rsa-pkcs1-sha1-1.2.840.113549.1.1.1-%s.apk", RSA_KEY_NAMES);
90         assertInstallSucceedsForEach(
91                 "v1-only-with-rsa-pkcs1-sha1-1.2.840.113549.1.1.5-%s.apk", RSA_KEY_NAMES);
92     }
93 
testInstallV1OneSignerSHA224withRSA()94     public void testInstallV1OneSignerSHA224withRSA() throws Exception {
95         // APK signed with v1 scheme only, one signer.
96         assertInstallSucceedsForEach(
97                 "v1-only-with-rsa-pkcs1-sha224-1.2.840.113549.1.1.1-%s.apk", RSA_KEY_NAMES);
98         assertInstallSucceedsForEach(
99                 "v1-only-with-rsa-pkcs1-sha224-1.2.840.113549.1.1.14-%s.apk", RSA_KEY_NAMES);
100     }
101 
testInstallV1OneSignerSHA256withRSA()102     public void testInstallV1OneSignerSHA256withRSA() throws Exception {
103         // APK signed with v1 scheme only, one signer.
104         assertInstallSucceedsForEach(
105                 "v1-only-with-rsa-pkcs1-sha256-1.2.840.113549.1.1.1-%s.apk", RSA_KEY_NAMES);
106         assertInstallSucceedsForEach(
107                 "v1-only-with-rsa-pkcs1-sha256-1.2.840.113549.1.1.11-%s.apk", RSA_KEY_NAMES);
108     }
109 
testInstallV1OneSignerSHA384withRSA()110     public void testInstallV1OneSignerSHA384withRSA() throws Exception {
111         // APK signed with v1 scheme only, one signer.
112         assertInstallSucceedsForEach(
113                 "v1-only-with-rsa-pkcs1-sha384-1.2.840.113549.1.1.1-%s.apk", RSA_KEY_NAMES);
114         assertInstallSucceedsForEach(
115                 "v1-only-with-rsa-pkcs1-sha384-1.2.840.113549.1.1.12-%s.apk", RSA_KEY_NAMES);
116     }
117 
testInstallV1OneSignerSHA512withRSA()118     public void testInstallV1OneSignerSHA512withRSA() throws Exception {
119         // APK signed with v1 scheme only, one signer.
120         assertInstallSucceedsForEach(
121                 "v1-only-with-rsa-pkcs1-sha512-1.2.840.113549.1.1.1-%s.apk", RSA_KEY_NAMES);
122         assertInstallSucceedsForEach(
123                 "v1-only-with-rsa-pkcs1-sha512-1.2.840.113549.1.1.13-%s.apk", RSA_KEY_NAMES);
124     }
125 
testInstallV1OneSignerSHA1withECDSA()126     public void testInstallV1OneSignerSHA1withECDSA() throws Exception {
127         // APK signed with v1 scheme only, one signer.
128         assertInstallSucceedsForEach(
129                 "v1-only-with-ecdsa-sha1-1.2.840.10045.2.1-%s.apk", EC_KEY_NAMES);
130         assertInstallSucceedsForEach(
131                 "v1-only-with-ecdsa-sha1-1.2.840.10045.4.1-%s.apk", EC_KEY_NAMES);
132     }
133 
testInstallV1OneSignerSHA224withECDSA()134     public void testInstallV1OneSignerSHA224withECDSA() throws Exception {
135         // APK signed with v1 scheme only, one signer.
136         assertInstallSucceedsForEach(
137                 "v1-only-with-ecdsa-sha224-1.2.840.10045.2.1-%s.apk", EC_KEY_NAMES);
138         assertInstallSucceedsForEach(
139                 "v1-only-with-ecdsa-sha224-1.2.840.10045.4.3.1-%s.apk", EC_KEY_NAMES);
140     }
141 
testInstallV1OneSignerSHA256withECDSA()142     public void testInstallV1OneSignerSHA256withECDSA() throws Exception {
143         // APK signed with v1 scheme only, one signer.
144         assertInstallSucceedsForEach(
145                 "v1-only-with-ecdsa-sha256-1.2.840.10045.2.1-%s.apk", EC_KEY_NAMES);
146         assertInstallSucceedsForEach(
147                 "v1-only-with-ecdsa-sha256-1.2.840.10045.4.3.2-%s.apk", EC_KEY_NAMES);
148     }
149 
testInstallV1OneSignerSHA384withECDSA()150     public void testInstallV1OneSignerSHA384withECDSA() throws Exception {
151         // APK signed with v1 scheme only, one signer.
152         assertInstallSucceedsForEach(
153                 "v1-only-with-ecdsa-sha384-1.2.840.10045.2.1-%s.apk", EC_KEY_NAMES);
154         assertInstallSucceedsForEach(
155                 "v1-only-with-ecdsa-sha384-1.2.840.10045.4.3.3-%s.apk", EC_KEY_NAMES);
156     }
157 
testInstallV1OneSignerSHA512withECDSA()158     public void testInstallV1OneSignerSHA512withECDSA() throws Exception {
159         // APK signed with v1 scheme only, one signer.
160         assertInstallSucceedsForEach(
161                 "v1-only-with-ecdsa-sha512-1.2.840.10045.2.1-%s.apk", EC_KEY_NAMES);
162         assertInstallSucceedsForEach(
163                 "v1-only-with-ecdsa-sha512-1.2.840.10045.4.3.4-%s.apk", EC_KEY_NAMES);
164     }
165 
testInstallV1OneSignerSHA1withDSA()166     public void testInstallV1OneSignerSHA1withDSA() throws Exception {
167         // APK signed with v1 scheme only, one signer.
168         assertInstallSucceedsForEach(
169                 "v1-only-with-dsa-sha1-1.2.840.10040.4.1-%s.apk", DSA_KEY_NAMES);
170         assertInstallSucceedsForEach(
171                 "v1-only-with-dsa-sha1-1.2.840.10040.4.3-%s.apk", DSA_KEY_NAMES);
172     }
173 
testInstallV1OneSignerSHA224withDSA()174     public void testInstallV1OneSignerSHA224withDSA() throws Exception {
175         // APK signed with v1 scheme only, one signer.
176         assertInstallSucceedsForEach(
177                 "v1-only-with-dsa-sha224-1.2.840.10040.4.1-%s.apk", DSA_KEY_NAMES);
178         assertInstallSucceedsForEach(
179                 "v1-only-with-dsa-sha224-2.16.840.1.101.3.4.3.1-%s.apk", DSA_KEY_NAMES);
180     }
181 
testInstallV1OneSignerSHA256withDSA()182     public void testInstallV1OneSignerSHA256withDSA() throws Exception {
183         // APK signed with v1 scheme only, one signer.
184         assertInstallSucceedsForEach(
185                 "v1-only-with-dsa-sha256-1.2.840.10040.4.1-%s.apk", DSA_KEY_NAMES);
186         assertInstallSucceedsForEach(
187                 "v1-only-with-dsa-sha256-2.16.840.1.101.3.4.3.2-%s.apk", DSA_KEY_NAMES);
188     }
189 
190 //  Android platform doesn't support DSA with SHA-384 and SHA-512.
191 //    public void testInstallV1OneSignerSHA384withDSA() throws Exception {
192 //        // APK signed with v1 scheme only, one signer.
193 //        assertInstallSucceedsForEach(
194 //                "v1-only-with-dsa-sha384-2.16.840.1.101.3.4.3.3-%s.apk", DSA_KEY_NAMES);
195 //    }
196 //
197 //    public void testInstallV1OneSignerSHA512withDSA() throws Exception {
198 //        // APK signed with v1 scheme only, one signer.
199 //        assertInstallSucceedsForEach(
200 //                "v1-only-with-dsa-sha512-2.16.840.1.101.3.4.3.3-%s.apk", DSA_KEY_NAMES);
201 //    }
202 
testInstallV2StrippedFails()203     public void testInstallV2StrippedFails() throws Exception {
204         // APK signed with v1 and v2 schemes, but v2 signature was stripped from the file (by using
205         // zipalign).
206         // This should fail because the v1 signature indicates that the APK was supposed to be
207         // signed with v2 scheme as well, making the platform's anti-stripping protections reject
208         // the APK.
209         assertInstallFailsWithError("v2-stripped.apk", "Signature stripped");
210 
211         // Similar to above, but the X-Android-APK-Signed anti-stripping header in v1 signature
212         // lists unknown signature schemes in addition to APK Signature Scheme v2. Unknown schemes
213         // should be ignored.
214         assertInstallFailsWithError(
215                 "v2-stripped-with-ignorable-signing-schemes.apk", "Signature stripped");
216     }
217 
testInstallV2OneSignerOneSignature()218     public void testInstallV2OneSignerOneSignature() throws Exception {
219         // APK signed with v2 scheme only, one signer, one signature.
220         assertInstallSucceedsForEach("v2-only-with-dsa-sha256-%s.apk", DSA_KEY_NAMES);
221         assertInstallSucceedsForEach("v2-only-with-ecdsa-sha256-%s.apk", EC_KEY_NAMES);
222         assertInstallSucceedsForEach("v2-only-with-rsa-pkcs1-sha256-%s.apk", RSA_KEY_NAMES);
223         assertInstallSucceedsForEach("v2-only-with-rsa-pss-sha256-%s.apk", RSA_KEY_NAMES);
224 
225         // DSA with SHA-512 is not supported by Android platform and thus APK Signature Scheme v2
226         // does not support that either
227         // assertInstallSucceedsForEach("v2-only-with-dsa-sha512-%s.apk", DSA_KEY_NAMES);
228         assertInstallSucceedsForEach("v2-only-with-ecdsa-sha512-%s.apk", EC_KEY_NAMES);
229         assertInstallSucceedsForEach("v2-only-with-rsa-pkcs1-sha512-%s.apk", RSA_KEY_NAMES);
230         assertInstallSucceedsForEach(
231                 "v2-only-with-rsa-pss-sha512-%s.apk",
232                 RSA_KEY_NAMES_2048_AND_LARGER // 1024-bit key is too short for PSS with SHA-512
233                 );
234     }
235 
testInstallV2SignatureDoesNotVerify()236     public void testInstallV2SignatureDoesNotVerify() throws Exception {
237         // APK signed with v2 scheme only, but the signature over signed-data does not verify.
238         String error = "signature did not verify";
239 
240         // Bitflip in certificate field inside signed-data. Based on
241         // v2-only-with-dsa-sha256-1024.apk.
242         assertInstallFailsWithError("v2-only-with-dsa-sha256-1024-sig-does-not-verify.apk", error);
243 
244         // Signature claims to be RSA PKCS#1 v1.5 with SHA-256, but is actually using SHA-512.
245         // Based on v2-only-with-rsa-pkcs1-sha256-2048.apk.
246         assertInstallFailsWithError(
247                 "v2-only-with-rsa-pkcs1-sha256-2048-sig-does-not-verify.apk", error);
248 
249         // Signature claims to be RSA PSS with SHA-256 and 32 bytes of salt, but is actually using 0
250         // bytes of salt. Based on v2-only-with-rsa-pkcs1-sha256-2048.apk. Obtained by modifying APK
251         // signer to use the wrong amount of salt.
252         assertInstallFailsWithError(
253                 "v2-only-with-rsa-pss-sha256-2048-sig-does-not-verify.apk", error);
254 
255         // Bitflip in the ECDSA signature. Based on v2-only-with-ecdsa-sha256-p256.apk.
256         assertInstallFailsWithError(
257                 "v2-only-with-ecdsa-sha256-p256-sig-does-not-verify.apk", error);
258     }
259 
testInstallV2ContentDigestMismatch()260     public void testInstallV2ContentDigestMismatch() throws Exception {
261         // APK signed with v2 scheme only, but the digest of contents does not match the digest
262         // stored in signed-data.
263         String error = "digest of contents did not verify";
264 
265         // Based on v2-only-with-rsa-pkcs1-sha512-4096.apk. Obtained by modifying APK signer to
266         // flip the leftmost bit in content digest before signing signed-data.
267         assertInstallFailsWithError(
268                 "v2-only-with-rsa-pkcs1-sha512-4096-digest-mismatch.apk", error);
269 
270         // Based on v2-only-with-ecdsa-sha256-p256.apk. Obtained by modifying APK signer to flip the
271         // leftmost bit in content digest before signing signed-data.
272         assertInstallFailsWithError(
273                 "v2-only-with-ecdsa-sha256-p256-digest-mismatch.apk", error);
274     }
275 
testInstallNoApkSignatureSchemeBlock()276     public void testInstallNoApkSignatureSchemeBlock() throws Exception {
277         // APK signed with v2 scheme only, but the rules for verifying APK Signature Scheme v2
278         // signatures say that this APK must not be verified using APK Signature Scheme v2.
279 
280         // Obtained from v2-only-with-rsa-pkcs1-sha512-4096.apk by flipping a bit in the magic
281         // field in the footer of APK Signing Block. This makes the APK Signing Block disappear.
282         assertInstallFails("v2-only-wrong-apk-sig-block-magic.apk");
283 
284         // Obtained by modifying APK signer to insert "GARBAGE" between ZIP Central Directory and
285         // End of Central Directory. The APK is otherwise fine and is signed with APK Signature
286         // Scheme v2. Based on v2-only-with-rsa-pkcs1-sha256.apk.
287         assertInstallFails("v2-only-garbage-between-cd-and-eocd.apk");
288 
289         // Obtained by modifying APK signer to truncate the ZIP Central Directory by one byte. The
290         // APK is otherwise fine and is signed with APK Signature Scheme v2. Based on
291         // v2-only-with-rsa-pkcs1-sha256.apk
292         assertInstallFails("v2-only-truncated-cd.apk");
293 
294         // Obtained by modifying the size in APK Signature Block header. Based on
295         // v2-only-with-ecdsa-sha512-p521.apk.
296         assertInstallFails("v2-only-apk-sig-block-size-mismatch.apk");
297 
298         // Obtained by modifying the ID under which APK Signature Scheme v2 Block is stored in
299         // APK Signing Block and by modifying the APK signer to not insert anti-stripping
300         // protections into JAR Signature. The APK should appear as having no APK Signature Scheme
301         // v2 Block and should thus successfully verify using JAR Signature Scheme.
302         assertInstallSucceeds("v1-with-apk-sig-block-but-without-apk-sig-scheme-v2-block.apk");
303     }
304 
testInstallV2UnknownPairIgnoredInApkSigningBlock()305     public void testInstallV2UnknownPairIgnoredInApkSigningBlock() throws Exception {
306         // Obtained by modifying APK signer to emit an unknown ID-value pair into APK Signing Block
307         // before the ID-value pair containing the APK Signature Scheme v2 Block. The unknown
308         // ID-value should be ignored.
309         assertInstallSucceeds("v2-only-unknown-pair-in-apk-sig-block.apk");
310     }
311 
testInstallV2IgnoresUnknownSignatureAlgorithms()312     public void testInstallV2IgnoresUnknownSignatureAlgorithms() throws Exception {
313         // APK is signed with a known signature algorithm and with a couple of unknown ones.
314         // Obtained by modifying APK signer to use "unknown" signature algorithms in addition to
315         // known ones.
316         assertInstallSucceeds("v2-only-with-ignorable-unsupported-sig-algs.apk");
317     }
318 
testInstallV2RejectsMismatchBetweenSignaturesAndDigestsBlocks()319     public void testInstallV2RejectsMismatchBetweenSignaturesAndDigestsBlocks() throws Exception {
320         // APK is signed with a single signature algorithm, but the digests block claims that it is
321         // signed with two different signature algorithms. Obtained by modifying APK Signer to
322         // emit an additional digest record with signature algorithm 0x12345678.
323         assertInstallFailsWithError(
324                 "v2-only-signatures-and-digests-block-mismatch.apk",
325                 "Signature algorithms don't match between digests and signatures records");
326     }
327 
testInstallV2RejectsMismatchBetweenPublicKeyAndCertificate()328     public void testInstallV2RejectsMismatchBetweenPublicKeyAndCertificate() throws Exception {
329         // APK is signed with v2 only. The public key field does not match the public key in the
330         // leaf certificate. Obtained by modifying APK signer to write out a modified leaf
331         // certificate where the RSA modulus has a bitflip.
332         assertInstallFailsWithError(
333                 "v2-only-cert-and-public-key-mismatch.apk",
334                 "Public key mismatch between certificate and signature record");
335     }
336 
testInstallV2RejectsSignerBlockWithNoCertificates()337     public void testInstallV2RejectsSignerBlockWithNoCertificates() throws Exception {
338         // APK is signed with v2 only. There are no certificates listed in the signer block.
339         // Obtained by modifying APK signer to output no certificates.
340         assertInstallFailsWithError("v2-only-no-certs-in-sig.apk", "No certificates listed");
341     }
342 
testInstallTwoSigners()343     public void testInstallTwoSigners() throws Exception {
344         // APK signed by two different signers.
345         assertInstallSucceeds("two-signers.apk");
346         // Because the install attempt below is an update, it also tests that the signing
347         // certificates exposed by v2 signatures above are the same as the one exposed by v1
348         // signatures in this APK.
349         assertInstallSucceeds("v1-only-two-signers.apk");
350         assertInstallSucceeds("v2-only-two-signers.apk");
351     }
352 
testInstallV2TwoSignersRejectsWhenOneBroken()353     public void testInstallV2TwoSignersRejectsWhenOneBroken() throws Exception {
354         // Bitflip in the ECDSA signature of second signer. Based on two-signers.apk.
355         // This asserts that breakage in any signer leads to rejection of the APK.
356         assertInstallFailsWithError(
357                 "two-signers-second-signer-v2-broken.apk", "signature did not verify");
358     }
359 
testInstallV2TwoSignersRejectsWhenOneWithoutSignatures()360     public void testInstallV2TwoSignersRejectsWhenOneWithoutSignatures() throws Exception {
361         // APK v2-signed by two different signers. However, there are no signatures for the second
362         // signer.
363         assertInstallFailsWithError(
364                 "v2-only-two-signers-second-signer-no-sig.apk", "No signatures");
365     }
366 
testInstallV2TwoSignersRejectsWhenOneWithoutSupportedSignatures()367     public void testInstallV2TwoSignersRejectsWhenOneWithoutSupportedSignatures() throws Exception {
368         // APK v2-signed by two different signers. However, there are no supported signatures for
369         // the second signer.
370         assertInstallFailsWithError(
371                 "v2-only-two-signers-second-signer-no-supported-sig.apk",
372                 "No supported signatures");
373     }
374 
testInstallV2RejectsWhenMissingCode()375     public void testInstallV2RejectsWhenMissingCode() throws Exception {
376         // Obtained by removing classes.dex from original.apk and then signing with v2 only.
377         // Although this has nothing to do with v2 signature verification, package manager wants
378         // signature verification / certificate collection to reject APKs with missing code
379         // (classes.dex) unless requested otherwise.
380         assertInstallFailsWithError("v2-only-missing-classes.dex.apk", "code is missing");
381     }
382 
testCorrectCertUsedFromPkcs7SignedDataCertsSet()383     public void testCorrectCertUsedFromPkcs7SignedDataCertsSet() throws Exception {
384         // Obtained by prepending the rsa-1024 certificate to the PKCS#7 SignedData certificates set
385         // of v1-only-with-rsa-pkcs1-sha1-1.2.840.113549.1.1.1-2048.apk META-INF/CERT.RSA. The certs
386         // (in the order of appearance in the file) are thus: rsa-1024, rsa-2048. The package's
387         // signing cert is rsa-2048.
388         assertInstallSucceeds("v1-only-pkcs7-cert-bag-first-cert-not-used.apk");
389 
390         // Check that rsa-1024 was not used as the previously installed package's signing cert.
391         assertInstallFailsWithError(
392                 "v1-only-with-rsa-pkcs1-sha1-1.2.840.113549.1.1.1-1024.apk",
393                 "signatures do not match");
394 
395         // Check that rsa-2048 was used as the previously installed package's signing cert.
396         assertInstallSucceeds("v1-only-with-rsa-pkcs1-sha1-1.2.840.113549.1.1.1-2048.apk");
397     }
398 
testV1SchemeSignatureCertNotReencoded()399     public void testV1SchemeSignatureCertNotReencoded() throws Exception {
400         // Regression test for b/30148997 and b/18228011. When PackageManager does not preserve the
401         // original encoded form of signing certificates, bad things happen, such as rejection of
402         // completely valid updates to apps. The issue in b/30148997 and b/18228011 was that
403         // PackageManager started re-encoding signing certs into DER. This normally produces exactly
404         // the original form because X.509 certificates are supposed to be DER-encoded. However, a
405         // small fraction of Android apps uses X.509 certificates which are not DER-encoded. For
406         // such apps, re-encoding into DER changes the serialized form of the certificate, creating
407         // a mismatch with the serialized form stored in the PackageManager database, leading to the
408         // rejection of updates for the app.
409         //
410         // The signing certs of the two APKs differ only in how the cert's signature is encoded.
411         // From Android's perspective, these two APKs are signed by different entities and thus
412         // cannot be used to update one another. If signature verification code re-encodes certs
413         // into DER, both certs will be exactly the same and Android will accept these APKs as
414         // updates of each other. This test is thus asserting that the two APKs are not accepted as
415         // updates of each other.
416         //
417         // * v1-only-with-rsa-1024.apk cert's signature is DER-encoded
418         // * v1-only-with-rsa-1024-cert-not-der.apk cert's signature is not DER-encoded. It is
419         //   BER-encoded, with length encoded as two bytes instead of just one.
420         //   v1-only-with-rsa-1024-cert-not-der.apk META-INF/CERT.RSA was obtained from
421         //   v1-only-with-rsa-1024.apk META-INF/CERT.RSA by manually modifying the ASN.1 structure.
422         assertInstallSucceeds("v1-only-with-rsa-1024.apk");
423         assertInstallFailsWithError(
424                 "v1-only-with-rsa-1024-cert-not-der.apk", "signatures do not match");
425 
426         uninstallPackage();
427         assertInstallSucceeds("v1-only-with-rsa-1024-cert-not-der.apk");
428         assertInstallFailsWithError("v1-only-with-rsa-1024.apk", "signatures do not match");
429     }
430 
testV2SchemeSignatureCertNotReencoded()431     public void testV2SchemeSignatureCertNotReencoded() throws Exception {
432         // This test is here to catch something like b/30148997 and b/18228011 happening to the
433         // handling of APK Signature Scheme v2 signatures by PackageManager. When PackageManager
434         // does not preserve the original encoded form of signing certificates, bad things happen,
435         // such as rejection of completely valid updates to apps. The issue in b/30148997 and
436         // b/18228011 was that PackageManager started re-encoding signing certs into DER. This
437         // normally produces exactly the original form because X.509 certificates are supposed to be
438         // DER-encoded. However, a small fraction of Android apps uses X.509 certificates which are
439         // not DER-encoded. For such apps, re-encoding into DER changes the serialized form of the
440         // certificate, creating a mismatch with the serialized form stored in the PackageManager
441         // database, leading to the rejection of updates for the app.
442         //
443         // The signing certs of the two APKs differ only in how the cert's signature is encoded.
444         // From Android's perspective, these two APKs are signed by different entities and thus
445         // cannot be used to update one another. If signature verification code re-encodes certs
446         // into DER, both certs will be exactly the same and Android will accept these APKs as
447         // updates of each other. This test is thus asserting that the two APKs are not accepted as
448         // updates of each other.
449         //
450         // * v2-only-with-rsa-pkcs1-sha256-1024.apk cert's signature is DER-encoded
451         // * v2-only-with-rsa-pkcs1-sha256-1024-cert-not-der.apk cert's signature is not DER-encoded
452         //   It is BER-encoded, with length encoded as two bytes instead of just one.
453         assertInstallSucceeds("v2-only-with-rsa-pkcs1-sha256-1024.apk");
454         assertInstallFailsWithError(
455                 "v2-only-with-rsa-pkcs1-sha256-1024-cert-not-der.apk", "signatures do not match");
456 
457         uninstallPackage();
458         assertInstallSucceeds("v2-only-with-rsa-pkcs1-sha256-1024-cert-not-der.apk");
459         assertInstallFailsWithError(
460                 "v2-only-with-rsa-pkcs1-sha256-1024.apk", "signatures do not match");
461     }
462 
testInstallMaxSizedZipEocdComment()463     public void testInstallMaxSizedZipEocdComment() throws Exception {
464         // Obtained by modifying apksigner to produce a max-sized (0xffff bytes long) ZIP End of
465         // Central Directory comment, and signing the original.apk using the modified apksigner.
466         assertInstallSucceeds("v1-only-max-sized-eocd-comment.apk");
467         assertInstallSucceeds("v2-only-max-sized-eocd-comment.apk");
468     }
469 
testInstallEphemeralRequiresV2Signature()470     public void testInstallEphemeralRequiresV2Signature() throws Exception {
471         String expectedNoSigError = "No APK Signature Scheme v2 signature in ephemeral package";
472         assertInstallEphemeralFailsWithError("unsigned-ephemeral.apk", expectedNoSigError);
473         assertInstallEphemeralFailsWithError("v1-only-ephemeral.apk", expectedNoSigError);
474         assertInstallEphemeralSucceeds("v2-only-ephemeral.apk");
475         assertInstallEphemeralSucceeds("v1-v2-ephemeral.apk"); // signed with both schemes
476     }
477 
testInstallEmpty()478     public void testInstallEmpty() throws Exception {
479         assertInstallFailsWithError("empty-unsigned.apk", "Unknown failure");
480         assertInstallFailsWithError("v1-only-empty.apk", "Unknown failure");
481         assertInstallFailsWithError("v2-only-empty.apk", "Unknown failure");
482     }
483 
assertInstallSucceeds(String apkFilenameInResources)484     private void assertInstallSucceeds(String apkFilenameInResources) throws Exception {
485         String installResult = installPackageFromResource(apkFilenameInResources);
486         if (installResult != null) {
487             fail("Failed to install " + apkFilenameInResources + ": " + installResult);
488         }
489     }
490 
assertInstallEphemeralSucceeds(String apkFilenameInResources)491     private void assertInstallEphemeralSucceeds(String apkFilenameInResources) throws Exception {
492         String installResult = installEphemeralPackageFromResource(apkFilenameInResources);
493         if (installResult != null) {
494             fail("Failed to install " + apkFilenameInResources + ": " + installResult);
495         }
496     }
497 
assertInstallSucceedsForEach( String apkFilenamePatternInResources, String[] args)498     private void assertInstallSucceedsForEach(
499             String apkFilenamePatternInResources, String[] args) throws Exception {
500         for (String arg : args) {
501             String apkFilenameInResources =
502                     String.format(Locale.US, apkFilenamePatternInResources, arg);
503             String installResult = installPackageFromResource(apkFilenameInResources);
504             if (installResult != null) {
505                 fail("Failed to install " + apkFilenameInResources + ": " + installResult);
506             }
507             try {
508                 uninstallPackage();
509             } catch (Exception e) {
510                 throw new RuntimeException(
511                         "Failed to uninstall after installing " + apkFilenameInResources, e);
512             }
513         }
514     }
515 
assertInstallFailsWithError( String apkFilenameInResources, String errorSubstring)516     private void assertInstallFailsWithError(
517             String apkFilenameInResources, String errorSubstring) throws Exception {
518         String installResult = installPackageFromResource(apkFilenameInResources);
519         if (installResult == null) {
520             fail("Install of " + apkFilenameInResources + " succeeded but was expected to fail"
521                     + " with \"" + errorSubstring + "\"");
522         }
523         assertContains(
524                 "Install failure message of " + apkFilenameInResources,
525                 errorSubstring,
526                 installResult);
527     }
528 
assertInstallEphemeralFailsWithError( String apkFilenameInResources, String errorSubstring)529     private void assertInstallEphemeralFailsWithError(
530             String apkFilenameInResources, String errorSubstring) throws Exception {
531         String installResult = installEphemeralPackageFromResource(apkFilenameInResources);
532         if (installResult == null) {
533             fail("Install of " + apkFilenameInResources + " succeeded but was expected to fail"
534                     + " with \"" + errorSubstring + "\"");
535         }
536         assertContains(
537                 "Install failure message of " + apkFilenameInResources,
538                 errorSubstring,
539                 installResult);
540     }
541 
assertInstallFails(String apkFilenameInResources)542     private void assertInstallFails(String apkFilenameInResources) throws Exception {
543         String installResult = installPackageFromResource(apkFilenameInResources);
544         if (installResult == null) {
545             fail("Install of " + apkFilenameInResources + " succeeded but was expected to fail");
546         }
547     }
548 
assertContains(String message, String expectedSubstring, String actual)549     private static void assertContains(String message, String expectedSubstring, String actual) {
550         String errorPrefix = ((message != null) && (message.length() > 0)) ? (message + ": ") : "";
551         if (actual == null) {
552             fail(errorPrefix + "Expected to contain \"" + expectedSubstring + "\", but was null");
553         }
554         if (!actual.contains(expectedSubstring)) {
555             fail(errorPrefix + "Expected to contain \"" + expectedSubstring + "\", but was \""
556                     + actual + "\"");
557         }
558     }
559 
installPackageFromResource(String apkFilenameInResources, boolean ephemeral)560     private String installPackageFromResource(String apkFilenameInResources, boolean ephemeral)
561             throws IOException, DeviceNotAvailableException {
562         // ITestDevice.installPackage API requires the APK to be install to be a File. We thus
563         // copy the requested resource into a temporary file, attempt to install it, and delete the
564         // file during cleanup.
565 
566         String fullResourceName = TEST_APK_RESOURCE_PREFIX + apkFilenameInResources;
567         File apkFile = File.createTempFile("pkginstalltest", ".apk");
568         try {
569             try (InputStream in = getClass().getResourceAsStream(fullResourceName);
570                     OutputStream out = new BufferedOutputStream(new FileOutputStream(apkFile))) {
571                 if (in == null) {
572                     throw new IllegalArgumentException("Resource not found: " + fullResourceName);
573                 }
574                 byte[] buf = new byte[65536];
575                 int chunkSize;
576                 while ((chunkSize = in.read(buf)) != -1) {
577                     out.write(buf, 0, chunkSize);
578                 }
579             }
580             if (ephemeral) {
581                 return getDevice().installPackage(apkFile, true, "--ephemeral");
582             } else {
583                 return getDevice().installPackage(apkFile, true);
584             }
585         } finally {
586             apkFile.delete();
587         }
588     }
589 
installPackageFromResource(String apkFilenameInResources)590     private String installPackageFromResource(String apkFilenameInResources)
591             throws IOException, DeviceNotAvailableException {
592         return installPackageFromResource(apkFilenameInResources, false);
593     }
594 
installEphemeralPackageFromResource(String apkFilenameInResources)595     private String installEphemeralPackageFromResource(String apkFilenameInResources)
596             throws IOException, DeviceNotAvailableException {
597         return installPackageFromResource(apkFilenameInResources, true);
598     }
599 
uninstallPackage()600     private String uninstallPackage() throws DeviceNotAvailableException {
601         return getDevice().uninstallPackage(TEST_PKG);
602     }
603 }
604