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