1 /* 2 * Copyright (C) 2017 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 com.android.apksig; 18 19 import static org.junit.Assert.assertEquals; 20 import static org.junit.Assert.assertTrue; 21 import static org.junit.Assert.fail; 22 import static org.junit.Assume.assumeNoException; 23 24 import com.android.apksig.ApkVerifier.Issue; 25 import com.android.apksig.ApkVerifier.IssueWithParams; 26 import com.android.apksig.ApkVerifier.Result.SourceStampInfo.SourceStampVerificationStatus; 27 import com.android.apksig.apk.ApkFormatException; 28 import com.android.apksig.internal.util.AndroidSdkVersion; 29 import com.android.apksig.internal.util.HexEncoding; 30 import com.android.apksig.internal.util.Resources; 31 import com.android.apksig.util.DataSources; 32 33 import org.junit.Assume; 34 import org.junit.Test; 35 import org.junit.runner.RunWith; 36 import org.junit.runners.JUnit4; 37 38 import java.io.IOException; 39 import java.lang.reflect.Field; 40 import java.lang.reflect.Modifier; 41 import java.nio.ByteBuffer; 42 import java.security.InvalidKeyException; 43 import java.security.MessageDigest; 44 import java.security.NoSuchAlgorithmException; 45 import java.security.PublicKey; 46 import java.security.Security; 47 import java.security.Signature; 48 import java.security.cert.CertificateFactory; 49 import java.security.cert.X509Certificate; 50 import java.util.Collection; 51 import java.util.List; 52 import java.util.Locale; 53 import java.util.Objects; 54 import java.util.stream.Collectors; 55 import java.util.stream.Stream; 56 57 @RunWith(JUnit4.class) 58 public class ApkVerifierTest { 59 60 private static final String[] DSA_KEY_NAMES = {"1024", "2048", "3072"}; 61 private static final String[] DSA_KEY_NAMES_1024_AND_SMALLER = {"1024"}; 62 private static final String[] DSA_KEY_NAMES_2048_AND_LARGER = {"2048", "3072"}; 63 private static final String[] EC_KEY_NAMES = {"p256", "p384", "p521"}; 64 private static final String[] RSA_KEY_NAMES = {"1024", "2048", "3072", "4096", "8192", "16384"}; 65 private static final String[] RSA_KEY_NAMES_2048_AND_LARGER = { 66 "2048", "3072", "4096", "8192", "16384" 67 }; 68 69 private static final String RSA_2048_CERT_SHA256_DIGEST = 70 "fb5dbd3c669af9fc236c6991e6387b7f11ff0590997f22d0f5c74ff40e04fca8"; 71 private static final String EC_P256_CERT_SHA256_DIGEST = 72 "6a8b96e278e58f62cfe3584022cec1d0527fcb85a9e5d2e1694eb0405be5b599"; 73 74 @Test testOriginalAccepted()75 public void testOriginalAccepted() throws Exception { 76 // APK signed with v1 and v2 schemes. Obtained by building 77 // cts/hostsidetests/appsecurity/test-apps/tinyapp. 78 // This APK is used as a basis for many of the other tests here. Hence, we check that this 79 // APK verifies. 80 assertVerified(verify("original.apk")); 81 } 82 83 @Test testV1OneSignerMD5withRSAAccepted()84 public void testV1OneSignerMD5withRSAAccepted() throws Exception { 85 // APK signed with v1 scheme only, one signer 86 assertVerifiedForEach( 87 "v1-only-with-rsa-pkcs1-md5-1.2.840.113549.1.1.1-%s.apk", RSA_KEY_NAMES); 88 assertVerifiedForEach( 89 "v1-only-with-rsa-pkcs1-md5-1.2.840.113549.1.1.4-%s.apk", RSA_KEY_NAMES); 90 } 91 92 @Test testV1OneSignerSHA1withRSAAccepted()93 public void testV1OneSignerSHA1withRSAAccepted() throws Exception { 94 // APK signed with v1 scheme only, one signer 95 assertVerifiedForEach( 96 "v1-only-with-rsa-pkcs1-sha1-1.2.840.113549.1.1.1-%s.apk", RSA_KEY_NAMES); 97 assertVerifiedForEach( 98 "v1-only-with-rsa-pkcs1-sha1-1.2.840.113549.1.1.5-%s.apk", RSA_KEY_NAMES); 99 } 100 101 @Test testV1OneSignerSHA224withRSAAccepted()102 public void testV1OneSignerSHA224withRSAAccepted() throws Exception { 103 // APK signed with v1 scheme only, one signer 104 assertVerifiedForEach( 105 "v1-only-with-rsa-pkcs1-sha224-1.2.840.113549.1.1.1-%s.apk", RSA_KEY_NAMES); 106 assertVerifiedForEach( 107 "v1-only-with-rsa-pkcs1-sha224-1.2.840.113549.1.1.14-%s.apk", RSA_KEY_NAMES); 108 } 109 110 @Test testV1OneSignerSHA256withRSAAccepted()111 public void testV1OneSignerSHA256withRSAAccepted() throws Exception { 112 // APK signed with v1 scheme only, one signer 113 assertVerifiedForEach( 114 "v1-only-with-rsa-pkcs1-sha256-1.2.840.113549.1.1.1-%s.apk", RSA_KEY_NAMES); 115 assertVerifiedForEach( 116 "v1-only-with-rsa-pkcs1-sha256-1.2.840.113549.1.1.11-%s.apk", RSA_KEY_NAMES); 117 } 118 119 @Test testV1OneSignerSHA384withRSAAccepted()120 public void testV1OneSignerSHA384withRSAAccepted() throws Exception { 121 // APK signed with v1 scheme only, one signer 122 assertVerifiedForEach( 123 "v1-only-with-rsa-pkcs1-sha384-1.2.840.113549.1.1.1-%s.apk", RSA_KEY_NAMES); 124 assertVerifiedForEach( 125 "v1-only-with-rsa-pkcs1-sha384-1.2.840.113549.1.1.12-%s.apk", RSA_KEY_NAMES); 126 } 127 128 @Test testV1OneSignerSHA512withRSAVerifies()129 public void testV1OneSignerSHA512withRSAVerifies() throws Exception { 130 // APK signed with v1 scheme only, one signer 131 assertVerifiedForEach( 132 "v1-only-with-rsa-pkcs1-sha512-1.2.840.113549.1.1.1-%s.apk", RSA_KEY_NAMES); 133 assertVerifiedForEach( 134 "v1-only-with-rsa-pkcs1-sha512-1.2.840.113549.1.1.13-%s.apk", RSA_KEY_NAMES); 135 } 136 137 @Test testV1OneSignerSHA1withECDSAAccepted()138 public void testV1OneSignerSHA1withECDSAAccepted() throws Exception { 139 // APK signed with v1 scheme only, one signer 140 assertVerifiedForEach("v1-only-with-ecdsa-sha1-1.2.840.10045.2.1-%s.apk", EC_KEY_NAMES); 141 assertVerifiedForEach("v1-only-with-ecdsa-sha1-1.2.840.10045.4.1-%s.apk", EC_KEY_NAMES); 142 } 143 144 @Test testV1OneSignerSHA224withECDSAAccepted()145 public void testV1OneSignerSHA224withECDSAAccepted() throws Exception { 146 // APK signed with v1 scheme only, one signer 147 assertVerifiedForEach("v1-only-with-ecdsa-sha224-1.2.840.10045.2.1-%s.apk", EC_KEY_NAMES); 148 assertVerifiedForEach("v1-only-with-ecdsa-sha224-1.2.840.10045.4.3.1-%s.apk", EC_KEY_NAMES); 149 } 150 151 @Test testV1OneSignerSHA256withECDSAAccepted()152 public void testV1OneSignerSHA256withECDSAAccepted() throws Exception { 153 // APK signed with v1 scheme only, one signer 154 assertVerifiedForEach("v1-only-with-ecdsa-sha256-1.2.840.10045.2.1-%s.apk", EC_KEY_NAMES); 155 assertVerifiedForEach("v1-only-with-ecdsa-sha256-1.2.840.10045.4.3.2-%s.apk", EC_KEY_NAMES); 156 } 157 158 @Test testV1OneSignerSHA384withECDSAAccepted()159 public void testV1OneSignerSHA384withECDSAAccepted() throws Exception { 160 // APK signed with v1 scheme only, one signer 161 assertVerifiedForEach("v1-only-with-ecdsa-sha384-1.2.840.10045.2.1-%s.apk", EC_KEY_NAMES); 162 assertVerifiedForEach("v1-only-with-ecdsa-sha384-1.2.840.10045.4.3.3-%s.apk", EC_KEY_NAMES); 163 } 164 165 @Test testV1OneSignerSHA512withECDSAAccepted()166 public void testV1OneSignerSHA512withECDSAAccepted() throws Exception { 167 // APK signed with v1 scheme only, one signer 168 assertVerifiedForEach("v1-only-with-ecdsa-sha512-1.2.840.10045.2.1-%s.apk", EC_KEY_NAMES); 169 assertVerifiedForEach("v1-only-with-ecdsa-sha512-1.2.840.10045.4.3.4-%s.apk", EC_KEY_NAMES); 170 } 171 172 @Test testV1OneSignerSHA1withDSAAccepted()173 public void testV1OneSignerSHA1withDSAAccepted() throws Exception { 174 // APK signed with v1 scheme only, one signer 175 // NOTE: This test is split into two because JCA Providers shipping with OpenJDK refuse to 176 // verify DSA signatures with keys too long for the SHA-1 digest. 177 assertVerifiedForEach( 178 "v1-only-with-dsa-sha1-1.2.840.10040.4.1-%s.apk", DSA_KEY_NAMES_1024_AND_SMALLER); 179 assertVerifiedForEach( 180 "v1-only-with-dsa-sha1-1.2.840.10040.4.3-%s.apk", DSA_KEY_NAMES_1024_AND_SMALLER); 181 } 182 183 @Test testV1OneSignerSHA1withDSAAcceptedWithKeysTooLongForDigest()184 public void testV1OneSignerSHA1withDSAAcceptedWithKeysTooLongForDigest() throws Exception { 185 // APK signed with v1 scheme only, one signer 186 187 // OpenJDK's default implementation of Signature.SHA1withDSA refuses to verify signatures 188 // created with keys too long for the digest used. Android Package Manager does not reject 189 // such signatures. We thus skip this test if Signature.SHA1withDSA exhibits this issue. 190 PublicKey publicKey = 191 Resources.toCertificate(getClass(), "dsa-2048.x509.pem").getPublicKey(); 192 Signature s = Signature.getInstance("SHA1withDSA"); 193 try { 194 s.initVerify(publicKey); 195 } catch (InvalidKeyException e) { 196 assumeNoException(e); 197 } 198 199 assertVerifiedForEach( 200 "v1-only-with-dsa-sha1-1.2.840.10040.4.1-%s.apk", DSA_KEY_NAMES_2048_AND_LARGER); 201 assertVerifiedForEach( 202 "v1-only-with-dsa-sha1-1.2.840.10040.4.3-%s.apk", DSA_KEY_NAMES_2048_AND_LARGER); 203 } 204 205 @Test testV1OneSignerSHA224withDSAAccepted()206 public void testV1OneSignerSHA224withDSAAccepted() throws Exception { 207 // APK signed with v1 scheme only, one signer 208 // NOTE: This test is split into two because JCA Providers shipping with OpenJDK refuse to 209 // verify DSA signatures with keys too long for the SHA-224 digest. 210 assertVerifiedForEach( 211 "v1-only-with-dsa-sha224-1.2.840.10040.4.1-%s.apk", DSA_KEY_NAMES_1024_AND_SMALLER); 212 assertVerifiedForEach( 213 "v1-only-with-dsa-sha224-2.16.840.1.101.3.4.3.1-%s.apk", 214 DSA_KEY_NAMES_1024_AND_SMALLER); 215 } 216 217 @Test testV1OneSignerSHA224withDSAAcceptedWithKeysTooLongForDigest()218 public void testV1OneSignerSHA224withDSAAcceptedWithKeysTooLongForDigest() throws Exception { 219 // APK signed with v1 scheme only, one signer 220 221 // OpenJDK's default implementation of Signature.SHA224withDSA refuses to verify signatures 222 // created with keys too long for the digest used. Android Package Manager does not reject 223 // such signatures. We thus skip this test if Signature.SHA224withDSA exhibits this issue. 224 PublicKey publicKey = 225 Resources.toCertificate(getClass(), "dsa-2048.x509.pem").getPublicKey(); 226 Signature s = Signature.getInstance("SHA224withDSA"); 227 try { 228 s.initVerify(publicKey); 229 } catch (InvalidKeyException e) { 230 assumeNoException(e); 231 } 232 assertVerifiedForEach( 233 "v1-only-with-dsa-sha224-1.2.840.10040.4.1-%s.apk", DSA_KEY_NAMES_2048_AND_LARGER); 234 assertVerifiedForEach( 235 "v1-only-with-dsa-sha224-2.16.840.1.101.3.4.3.1-%s.apk", 236 DSA_KEY_NAMES_2048_AND_LARGER); 237 } 238 239 @Test testV1OneSignerSHA256withDSAAccepted()240 public void testV1OneSignerSHA256withDSAAccepted() throws Exception { 241 // APK signed with v1 scheme only, one signer 242 assertVerifiedForEach("v1-only-with-dsa-sha256-1.2.840.10040.4.1-%s.apk", DSA_KEY_NAMES); 243 assertVerifiedForEach( 244 "v1-only-with-dsa-sha256-2.16.840.1.101.3.4.3.2-%s.apk", DSA_KEY_NAMES); 245 } 246 247 @Test testV2StrippedRejected()248 public void testV2StrippedRejected() throws Exception { 249 // APK signed with v1 and v2 schemes, but v2 signature was stripped from the file (by using 250 // zipalign). 251 // This should fail because the v1 signature indicates that the APK was supposed to be 252 // signed with v2 scheme as well, making the platform's anti-stripping protections reject 253 // the APK. 254 assertVerificationFailure("v2-stripped.apk", Issue.JAR_SIG_MISSING_APK_SIG_REFERENCED); 255 256 // Similar to above, but the X-Android-APK-Signed anti-stripping header in v1 signature 257 // lists unknown signature schemes in addition to APK Signature Scheme v2. Unknown schemes 258 // should be ignored. 259 assertVerificationFailure( 260 "v2-stripped-with-ignorable-signing-schemes.apk", 261 Issue.JAR_SIG_MISSING_APK_SIG_REFERENCED); 262 } 263 264 @Test testV3StrippedRejected()265 public void testV3StrippedRejected() throws Exception { 266 // APK signed with v2 and v3 schemes, but v3 signature was stripped from the file by 267 // modifying the v3 block ID to be the verity padding block ID. Without the stripping 268 // protection this modification ignores the v3 signing scheme block. 269 assertVerificationFailure("v3-stripped.apk", Issue.V2_SIG_MISSING_APK_SIG_REFERENCED); 270 } 271 272 @Test testSignaturesIgnoredForMaxSDK()273 public void testSignaturesIgnoredForMaxSDK() throws Exception { 274 // The V2 signature scheme was introduced in N, and V3 was introduced in P. This test 275 // verifies a max SDK of pre-P ignores the V3 signature and a max SDK of pre-N ignores both 276 // the V2 and V3 signatures. 277 assertVerified( 278 verifyForMaxSdkVersion( 279 "v1v2v3-with-rsa-2048-lineage-3-signers.apk", AndroidSdkVersion.O)); 280 assertVerified( 281 verifyForMaxSdkVersion( 282 "v1v2v3-with-rsa-2048-lineage-3-signers.apk", AndroidSdkVersion.M)); 283 } 284 285 @Test testV2OneSignerOneSignatureAccepted()286 public void testV2OneSignerOneSignatureAccepted() throws Exception { 287 // APK signed with v2 scheme only, one signer, one signature 288 assertVerifiedForEachForMinSdkVersion( 289 "v2-only-with-dsa-sha256-%s.apk", DSA_KEY_NAMES, AndroidSdkVersion.N); 290 assertVerifiedForEachForMinSdkVersion( 291 "v2-only-with-ecdsa-sha256-%s.apk", EC_KEY_NAMES, AndroidSdkVersion.N); 292 assertVerifiedForEachForMinSdkVersion( 293 "v2-only-with-rsa-pkcs1-sha256-%s.apk", RSA_KEY_NAMES, AndroidSdkVersion.N); 294 // RSA-PSS signatures tested in a separate test below 295 296 // DSA with SHA-512 is not supported by Android platform and thus APK Signature Scheme v2 297 // does not support that either 298 // assertInstallSucceedsForEach("v2-only-with-dsa-sha512-%s.apk", DSA_KEY_NAMES); 299 assertVerifiedForEachForMinSdkVersion( 300 "v2-only-with-ecdsa-sha512-%s.apk", EC_KEY_NAMES, AndroidSdkVersion.N); 301 assertVerifiedForEachForMinSdkVersion( 302 "v2-only-with-rsa-pkcs1-sha512-%s.apk", RSA_KEY_NAMES, AndroidSdkVersion.N); 303 } 304 305 @Test testV3OneSignerOneSignatureAccepted()306 public void testV3OneSignerOneSignatureAccepted() throws Exception { 307 // APK signed with v3 scheme only, one signer, one signature 308 assertVerifiedForEachForMinSdkVersion( 309 "v3-only-with-dsa-sha256-%s.apk", DSA_KEY_NAMES, AndroidSdkVersion.P); 310 assertVerifiedForEachForMinSdkVersion( 311 "v3-only-with-ecdsa-sha256-%s.apk", EC_KEY_NAMES, AndroidSdkVersion.P); 312 assertVerifiedForEachForMinSdkVersion( 313 "v3-only-with-rsa-pkcs1-sha256-%s.apk", RSA_KEY_NAMES, AndroidSdkVersion.P); 314 315 assertVerifiedForEachForMinSdkVersion( 316 "v3-only-with-ecdsa-sha512-%s.apk", EC_KEY_NAMES, AndroidSdkVersion.P); 317 assertVerifiedForEachForMinSdkVersion( 318 "v3-only-with-rsa-pkcs1-sha512-%s.apk", RSA_KEY_NAMES, AndroidSdkVersion.P); 319 } 320 321 @Test testV2OneSignerOneRsaPssSignatureAccepted()322 public void testV2OneSignerOneRsaPssSignatureAccepted() throws Exception { 323 assumeThatRsaPssAvailable(); 324 // APK signed with v2 scheme only, one signer, one signature 325 assertVerifiedForEachForMinSdkVersion( 326 "v2-only-with-rsa-pss-sha256-%s.apk", RSA_KEY_NAMES, AndroidSdkVersion.N); 327 assertVerifiedForEachForMinSdkVersion( 328 "v2-only-with-rsa-pss-sha512-%s.apk", 329 RSA_KEY_NAMES_2048_AND_LARGER, // 1024-bit key is too short for PSS with SHA-512 330 AndroidSdkVersion.N); 331 } 332 333 @Test testV2SignatureDoesNotMatchSignedDataRejected()334 public void testV2SignatureDoesNotMatchSignedDataRejected() throws Exception { 335 // APK signed with v2 scheme only, but the signature over signed-data does not verify 336 337 // Bitflip in certificate field inside signed-data. Based on 338 // v2-only-with-dsa-sha256-1024.apk. 339 assertVerificationFailure( 340 "v2-only-with-dsa-sha256-1024-sig-does-not-verify.apk", 341 Issue.V2_SIG_DID_NOT_VERIFY); 342 343 // Signature claims to be RSA PKCS#1 v1.5 with SHA-256, but is actually using SHA-512. 344 // Based on v2-only-with-rsa-pkcs1-sha256-2048.apk. 345 assertVerificationFailure( 346 "v2-only-with-rsa-pkcs1-sha256-2048-sig-does-not-verify.apk", 347 Issue.V2_SIG_VERIFY_EXCEPTION); 348 349 // Bitflip in the ECDSA signature. Based on v2-only-with-ecdsa-sha256-p256.apk. 350 assertVerificationFailure( 351 "v2-only-with-ecdsa-sha256-p256-sig-does-not-verify.apk", 352 Issue.V2_SIG_DID_NOT_VERIFY); 353 } 354 355 @Test testV3SignatureDoesNotMatchSignedDataRejected()356 public void testV3SignatureDoesNotMatchSignedDataRejected() throws Exception { 357 // APK signed with v3 scheme only, but the signature over signed-data does not verify 358 359 // Bitflip in DSA signature. Based on v3-only-with-dsa-sha256-2048.apk. 360 assertVerificationFailure( 361 "v3-only-with-dsa-sha256-2048-sig-does-not-verify.apk", 362 Issue.V3_SIG_DID_NOT_VERIFY); 363 364 // Bitflip in signed data. Based on v3-only-with-rsa-pkcs1-sha256-3072.apk 365 assertVerificationFailure( 366 "v3-only-with-rsa-pkcs1-sha256-3072-sig-does-not-verify.apk", 367 Issue.V3_SIG_DID_NOT_VERIFY); 368 369 // Based on v3-only-with-ecdsa-sha512-p521 with the signature ID changed to be ECDSA with 370 // SHA-256. 371 assertVerificationFailure( 372 "v3-only-with-ecdsa-sha512-p521-sig-does-not-verify.apk", 373 Issue.V3_SIG_DID_NOT_VERIFY); 374 } 375 376 @Test testV2RsaPssSignatureDoesNotMatchSignedDataRejected()377 public void testV2RsaPssSignatureDoesNotMatchSignedDataRejected() throws Exception { 378 assumeThatRsaPssAvailable(); 379 380 // APK signed with v2 scheme only, but the signature over signed-data does not verify. 381 382 // Signature claims to be RSA PSS with SHA-256 and 32 bytes of salt, but is actually using 0 383 // bytes of salt. Based on v2-only-with-rsa-pkcs1-sha256-2048.apk. Obtained by modifying APK 384 // signer to use the wrong amount of salt. 385 assertVerificationFailure( 386 "v2-only-with-rsa-pss-sha256-2048-sig-does-not-verify.apk", 387 Issue.V2_SIG_DID_NOT_VERIFY); 388 } 389 390 @Test testV2ContentDigestMismatchRejected()391 public void testV2ContentDigestMismatchRejected() throws Exception { 392 // APK signed with v2 scheme only, but the digest of contents does not match the digest 393 // stored in signed-data 394 ApkVerifier.Issue error = Issue.V2_SIG_APK_DIGEST_DID_NOT_VERIFY; 395 396 // Based on v2-only-with-rsa-pkcs1-sha512-4096.apk. Obtained by modifying APK signer to 397 // flip the leftmost bit in content digest before signing signed-data. 398 assertVerificationFailure("v2-only-with-rsa-pkcs1-sha512-4096-digest-mismatch.apk", error); 399 400 // Based on v2-only-with-ecdsa-sha256-p256.apk. Obtained by modifying APK signer to flip the 401 // leftmost bit in content digest before signing signed-data. 402 assertVerificationFailure("v2-only-with-ecdsa-sha256-p256-digest-mismatch.apk", error); 403 } 404 405 @Test testV3ContentDigestMismatchRejected()406 public void testV3ContentDigestMismatchRejected() throws Exception { 407 // APK signed with v3 scheme only, but the digest of contents does not match the digest 408 // stored in signed-data. 409 410 // Based on v3-only-with-rsa-pkcs1-sha512-8192. Obtained by flipping a bit in the local 411 // file header of the APK. 412 assertVerificationFailure( 413 "v3-only-with-rsa-pkcs1-sha512-8192-digest-mismatch.apk", 414 Issue.V3_SIG_APK_DIGEST_DID_NOT_VERIFY); 415 416 // Based on v3-only-with-dsa-sha256-3072.apk. Obtained by modifying APK signer to flip the 417 // leftmost bit in content digest before signing signed-data. 418 assertVerificationFailure( 419 "v3-only-with-dsa-sha256-3072-digest-mismatch.apk", 420 Issue.V3_SIG_APK_DIGEST_DID_NOT_VERIFY); 421 } 422 423 @Test testNoApkSignatureSchemeBlockRejected()424 public void testNoApkSignatureSchemeBlockRejected() throws Exception { 425 // APK signed with v2 scheme only, but the rules for verifying APK Signature Scheme v2 426 // signatures say that this APK must not be verified using APK Signature Scheme v2. 427 428 // Obtained from v2-only-with-rsa-pkcs1-sha512-4096.apk by flipping a bit in the magic 429 // field in the footer of APK Signing Block. This makes the APK Signing Block disappear. 430 assertVerificationFailure( 431 "v2-only-wrong-apk-sig-block-magic.apk", Issue.JAR_SIG_NO_MANIFEST); 432 433 // Obtained by modifying APK signer to insert "GARBAGE" between ZIP Central Directory and 434 // End of Central Directory. The APK is otherwise fine and is signed with APK Signature 435 // Scheme v2. Based on v2-only-with-rsa-pkcs1-sha256.apk. 436 assertVerificationFailure( 437 "v2-only-garbage-between-cd-and-eocd.apk", Issue.JAR_SIG_NO_MANIFEST); 438 439 // Obtained by modifying the size in APK Signature Block header. Based on 440 // v2-only-with-ecdsa-sha512-p521.apk. 441 assertVerificationFailure( 442 "v2-only-apk-sig-block-size-mismatch.apk", Issue.JAR_SIG_NO_MANIFEST); 443 444 // Obtained by modifying the ID under which APK Signature Scheme v2 Block is stored in 445 // APK Signing Block and by modifying the APK signer to not insert anti-stripping 446 // protections into JAR Signature. The APK should appear as having no APK Signature Scheme 447 // v2 Block and should thus successfully verify using JAR Signature Scheme. 448 assertVerified(verify("v1-with-apk-sig-block-but-without-apk-sig-scheme-v2-block.apk")); 449 } 450 451 @Test testNoV3ApkSignatureSchemeBlockRejected()452 public void testNoV3ApkSignatureSchemeBlockRejected() throws Exception { 453 // Obtained from v3-only-with-ecdsa-sha512-p384.apk by flipping a bit in the magic field 454 // in the footer of the APK Signing Block. 455 assertVerificationFailure( 456 "v3-only-with-ecdsa-sha512-p384-wrong-apk-sig-block-magic.apk", 457 Issue.JAR_SIG_NO_MANIFEST); 458 459 // Obtained from v3-only-with-rsa-pkcs1-sha512-4096.apk by modifying the size in the APK 460 // Signature Block header and footer. 461 assertVerificationFailure( 462 "v3-only-with-rsa-pkcs1-sha512-4096-apk-sig-block-size-mismatch.apk", 463 Issue.JAR_SIG_NO_MANIFEST); 464 } 465 466 @Test(expected = ApkFormatException.class) testTruncatedZipCentralDirectoryRejected()467 public void testTruncatedZipCentralDirectoryRejected() throws Exception { 468 // Obtained by modifying APK signer to truncate the ZIP Central Directory by one byte. The 469 // APK is otherwise fine and is signed with APK Signature Scheme v2. Based on 470 // v2-only-with-rsa-pkcs1-sha256.apk 471 verify("v2-only-truncated-cd.apk"); 472 } 473 474 @Test testV2UnknownPairIgnoredInApkSigningBlock()475 public void testV2UnknownPairIgnoredInApkSigningBlock() throws Exception { 476 // Obtained by modifying APK signer to emit an unknown ID-value pair into APK Signing Block 477 // before the ID-value pair containing the APK Signature Scheme v2 Block. The unknown 478 // ID-value should be ignored. 479 assertVerified( 480 verifyForMinSdkVersion( 481 "v2-only-unknown-pair-in-apk-sig-block.apk", AndroidSdkVersion.N)); 482 } 483 484 @Test testV3UnknownPairIgnoredInApkSigningBlock()485 public void testV3UnknownPairIgnoredInApkSigningBlock() throws Exception { 486 // Obtained by modifying APK signer to emit an unknown ID value pair into APK Signing Block 487 // before the ID value pair containing the APK Signature Scheme v3 Block. The unknown 488 // ID value should be ignored. 489 assertVerified( 490 verifyForMinSdkVersion( 491 "v3-only-unknown-pair-in-apk-sig-block.apk", AndroidSdkVersion.P)); 492 } 493 494 @Test testV2UnknownSignatureAlgorithmsIgnored()495 public void testV2UnknownSignatureAlgorithmsIgnored() throws Exception { 496 // APK is signed with a known signature algorithm and with a couple of unknown ones. 497 // Obtained by modifying APK signer to use "unknown" signature algorithms in addition to 498 // known ones. 499 assertVerified( 500 verifyForMinSdkVersion( 501 "v2-only-with-ignorable-unsupported-sig-algs.apk", AndroidSdkVersion.N)); 502 } 503 504 @Test testV3UnknownSignatureAlgorithmsIgnored()505 public void testV3UnknownSignatureAlgorithmsIgnored() throws Exception { 506 // APK is signed with a known signature algorithm and a couple of unknown ones. 507 // Obtained by modifying APK signer to use "unknown" signature algorithms in addition to 508 // known ones. 509 assertVerified( 510 verifyForMinSdkVersion( 511 "v3-only-with-ignorable-unsupported-sig-algs.apk", AndroidSdkVersion.P)); 512 } 513 514 @Test testV3WithOnlyUnknownSignatureAlgorithmsRejected()515 public void testV3WithOnlyUnknownSignatureAlgorithmsRejected() throws Exception { 516 // APK is only signed with an unknown signature algorithm. Obtained by modifying APK 517 // signer's ID for a known signature algorithm. 518 assertVerificationFailure( 519 "v3-only-no-supported-sig-algs.apk", Issue.V3_SIG_NO_SUPPORTED_SIGNATURES); 520 } 521 522 @Test testV2UnknownAdditionalAttributeIgnored()523 public void testV2UnknownAdditionalAttributeIgnored() throws Exception { 524 // APK's v2 signature contains an unknown additional attribute, but is otherwise fine. 525 // Obtained by modifying APK signer to output an additional attribute with ID 0x01020304 526 // and value 0x05060708. 527 assertVerified( 528 verifyForMinSdkVersion("v2-only-unknown-additional-attr.apk", AndroidSdkVersion.N)); 529 } 530 531 @Test testV3UnknownAdditionalAttributeIgnored()532 public void testV3UnknownAdditionalAttributeIgnored() throws Exception { 533 // APK's v3 signature contains unknown additional attributes before and after the lineage. 534 // Obtained by modifying APK signer to output additional attributes with IDs 0x11223344 535 // and 0x99aabbcc with values 0x55667788 and 0xddeeff00 536 assertVerified( 537 verifyForMinSdkVersion("v3-only-unknown-additional-attr.apk", AndroidSdkVersion.P)); 538 539 // APK's v2 and v3 signatures contain unknown additional attributes before and after the 540 // anti-stripping and lineage attributes. 541 assertVerified( 542 verifyForMinSdkVersion("v2v3-unknown-additional-attr.apk", AndroidSdkVersion.P)); 543 } 544 545 @Test testV2MismatchBetweenSignaturesAndDigestsBlockRejected()546 public void testV2MismatchBetweenSignaturesAndDigestsBlockRejected() throws Exception { 547 // APK is signed with a single signature algorithm, but the digests block claims that it is 548 // signed with two different signature algorithms. Obtained by modifying APK Signer to 549 // emit an additional digest record with signature algorithm 0x12345678. 550 assertVerificationFailure( 551 "v2-only-signatures-and-digests-block-mismatch.apk", 552 Issue.V2_SIG_SIG_ALG_MISMATCH_BETWEEN_SIGNATURES_AND_DIGESTS_RECORDS); 553 } 554 555 @Test testV3MismatchBetweenSignaturesAndDigestsBlockRejected()556 public void testV3MismatchBetweenSignaturesAndDigestsBlockRejected() throws Exception { 557 // APK is signed with a single signature algorithm, but the digests block claims that it is 558 // signed with two different signature algorithms. Obtained by modifying APK Signer to 559 // emit an additional digest record with signature algorithm 0x11223344. 560 assertVerificationFailure( 561 "v3-only-signatures-and-digests-block-mismatch.apk", 562 Issue.V3_SIG_SIG_ALG_MISMATCH_BETWEEN_SIGNATURES_AND_DIGESTS_RECORDS); 563 } 564 565 @Test testV2MismatchBetweenPublicKeyAndCertificateRejected()566 public void testV2MismatchBetweenPublicKeyAndCertificateRejected() throws Exception { 567 // APK is signed with v2 only. The public key field does not match the public key in the 568 // leaf certificate. Obtained by modifying APK signer to write out a modified leaf 569 // certificate where the RSA modulus has a bitflip. 570 assertVerificationFailure( 571 "v2-only-cert-and-public-key-mismatch.apk", 572 Issue.V2_SIG_PUBLIC_KEY_MISMATCH_BETWEEN_CERTIFICATE_AND_SIGNATURES_RECORD); 573 } 574 575 @Test testV3MismatchBetweenPublicKeyAndCertificateRejected()576 public void testV3MismatchBetweenPublicKeyAndCertificateRejected() throws Exception { 577 // APK is signed with v3 only. The public key field does not match the public key in the 578 // leaf certificate. Obtained by modifying APK signer to write out a modified leaf 579 // certificate where the RSA modulus has a bitflip. 580 assertVerificationFailure( 581 "v3-only-cert-and-public-key-mismatch.apk", 582 Issue.V3_SIG_PUBLIC_KEY_MISMATCH_BETWEEN_CERTIFICATE_AND_SIGNATURES_RECORD); 583 } 584 585 @Test testV2SignerBlockWithNoCertificatesRejected()586 public void testV2SignerBlockWithNoCertificatesRejected() throws Exception { 587 // APK is signed with v2 only. There are no certificates listed in the signer block. 588 // Obtained by modifying APK signer to output no certificates. 589 assertVerificationFailure("v2-only-no-certs-in-sig.apk", Issue.V2_SIG_NO_CERTIFICATES); 590 } 591 592 @Test testV3SignerBlockWithNoCertificatesRejected()593 public void testV3SignerBlockWithNoCertificatesRejected() throws Exception { 594 // APK is signed with v3 only. There are no certificates listed in the signer block. 595 // Obtained by modifying APK signer to output no certificates. 596 assertVerificationFailure("v3-only-no-certs-in-sig.apk", Issue.V3_SIG_NO_CERTIFICATES); 597 } 598 599 @Test testTwoSignersAccepted()600 public void testTwoSignersAccepted() throws Exception { 601 // APK signed by two different signers 602 assertVerified(verify("two-signers.apk")); 603 assertVerified(verify("v1-only-two-signers.apk")); 604 assertVerified(verifyForMinSdkVersion("v2-only-two-signers.apk", AndroidSdkVersion.N)); 605 } 606 607 @Test testV2TwoSignersRejectedWhenOneBroken()608 public void testV2TwoSignersRejectedWhenOneBroken() throws Exception { 609 // Bitflip in the ECDSA signature of second signer. Based on two-signers.apk. 610 // This asserts that breakage in any signer leads to rejection of the APK. 611 assertVerificationFailure( 612 "two-signers-second-signer-v2-broken.apk", Issue.V2_SIG_DID_NOT_VERIFY); 613 } 614 615 @Test testV2TwoSignersRejectedWhenOneWithoutSignatures()616 public void testV2TwoSignersRejectedWhenOneWithoutSignatures() throws Exception { 617 // APK v2-signed by two different signers. However, there are no signatures for the second 618 // signer. 619 assertVerificationFailure( 620 "v2-only-two-signers-second-signer-no-sig.apk", Issue.V2_SIG_NO_SIGNATURES); 621 } 622 623 @Test testV2TwoSignersRejectedWhenOneWithoutSupportedSignatures()624 public void testV2TwoSignersRejectedWhenOneWithoutSupportedSignatures() throws Exception { 625 // APK v2-signed by two different signers. However, there are no supported signatures for 626 // the second signer. 627 assertVerificationFailure( 628 "v2-only-two-signers-second-signer-no-supported-sig.apk", 629 Issue.V2_SIG_NO_SUPPORTED_SIGNATURES); 630 } 631 632 @Test testCorrectCertUsedFromPkcs7SignedDataCertsSet()633 public void testCorrectCertUsedFromPkcs7SignedDataCertsSet() throws Exception { 634 // Obtained by prepending the rsa-1024 certificate to the PKCS#7 SignedData certificates set 635 // of v1-only-with-rsa-pkcs1-sha1-1.2.840.113549.1.1.1-2048.apk META-INF/CERT.RSA. The certs 636 // (in the order of appearance in the file) are thus: rsa-1024, rsa-2048. The package's 637 // signing cert is rsa-2048. 638 ApkVerifier.Result result = verify("v1-only-pkcs7-cert-bag-first-cert-not-used.apk"); 639 assertVerified(result); 640 List<X509Certificate> signingCerts = result.getSignerCertificates(); 641 assertEquals(1, signingCerts.size()); 642 assertEquals( 643 "fb5dbd3c669af9fc236c6991e6387b7f11ff0590997f22d0f5c74ff40e04fca8", 644 HexEncoding.encode(sha256(signingCerts.get(0).getEncoded()))); 645 } 646 647 @Test testV1SchemeSignatureCertNotReencoded()648 public void testV1SchemeSignatureCertNotReencoded() throws Exception { 649 // Regression test for b/30148997 and b/18228011. When PackageManager does not preserve the 650 // original encoded form of signing certificates, bad things happen, such as rejection of 651 // completely valid updates to apps. The issue in b/30148997 and b/18228011 was that 652 // PackageManager started re-encoding signing certs into DER. This normally produces exactly 653 // the original form because X.509 certificates are supposed to be DER-encoded. However, a 654 // small fraction of Android apps uses X.509 certificates which are not DER-encoded. For 655 // such apps, re-encoding into DER changes the serialized form of the certificate, creating 656 // a mismatch with the serialized form stored in the PackageManager database, leading to the 657 // rejection of updates for the app. 658 // 659 // v1-only-with-rsa-1024-cert-not-der.apk cert's signature is not DER-encoded. It is 660 // BER-encoded, with length encoded as two bytes instead of just one. 661 // v1-only-with-rsa-1024-cert-not-der.apk META-INF/CERT.RSA was obtained from 662 // v1-only-with-rsa-1024.apk META-INF/CERT.RSA by manually modifying the ASN.1 structure. 663 ApkVerifier.Result result = verify("v1-only-with-rsa-1024-cert-not-der.apk"); 664 665 // On JDK 8u131 and newer, when the default (SUN) X.509 CertificateFactory implementation is 666 // used, PKCS #7 signature verification fails because the certificate is not DER-encoded. 667 // This contrived block of code disables this test in this scenario. 668 if (!result.isVerified()) { 669 List<ApkVerifier.Result.V1SchemeSignerInfo> signers = result.getV1SchemeSigners(); 670 if (signers.size() > 0) { 671 ApkVerifier.Result.V1SchemeSignerInfo signer = signers.get(0); 672 for (IssueWithParams issue : signer.getErrors()) { 673 if (issue.getIssue() == Issue.JAR_SIG_PARSE_EXCEPTION) { 674 CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); 675 if ("SUN".equals(certFactory.getProvider().getName())) { 676 Throwable exception = (Throwable) issue.getParams()[1]; 677 Throwable e = exception; 678 while (e != null) { 679 String msg = e.getMessage(); 680 e = e.getCause(); 681 if ((msg != null) 682 && (msg.contains("Redundant length bytes found"))) { 683 Assume.assumeNoException(exception); 684 } 685 } 686 } 687 break; 688 } 689 } 690 } 691 } 692 693 assertVerified(result); 694 List<X509Certificate> signingCerts = result.getSignerCertificates(); 695 assertEquals(1, signingCerts.size()); 696 assertEquals( 697 "c5d4535a7e1c8111687a8374b2198da6f5ff8d811a7a25aa99ef060669342fa9", 698 HexEncoding.encode(sha256(signingCerts.get(0).getEncoded()))); 699 } 700 701 @Test testV1SchemeSignatureCertNotReencoded2()702 public void testV1SchemeSignatureCertNotReencoded2() throws Exception { 703 // Regression test for b/30148997 and b/18228011. When PackageManager does not preserve the 704 // original encoded form of signing certificates, bad things happen, such as rejection of 705 // completely valid updates to apps. The issue in b/30148997 and b/18228011 was that 706 // PackageManager started re-encoding signing certs into DER. This normally produces exactly 707 // the original form because X.509 certificates are supposed to be DER-encoded. However, a 708 // small fraction of Android apps uses X.509 certificates which are not DER-encoded. For 709 // such apps, re-encoding into DER changes the serialized form of the certificate, creating 710 // a mismatch with the serialized form stored in the PackageManager database, leading to the 711 // rejection of updates for the app. 712 // 713 // v1-only-with-rsa-1024-cert-not-der2.apk cert's signature is not DER-encoded. It is 714 // BER-encoded, with the BIT STRING value containing an extraneous leading 0x00 byte. 715 // v1-only-with-rsa-1024-cert-not-der2.apk META-INF/CERT.RSA was obtained from 716 // v1-only-with-rsa-1024.apk META-INF/CERT.RSA by manually modifying the ASN.1 structure. 717 ApkVerifier.Result result = verify("v1-only-with-rsa-1024-cert-not-der2.apk"); 718 assertVerified(result); 719 List<X509Certificate> signingCerts = result.getSignerCertificates(); 720 assertEquals(1, signingCerts.size()); 721 assertEquals( 722 "da3da398de674541313deed77218ce94798531ea5131bb9b1bb4063ba4548cfb", 723 HexEncoding.encode(sha256(signingCerts.get(0).getEncoded()))); 724 } 725 726 @Test testMaxSizedZipEocdCommentAccepted()727 public void testMaxSizedZipEocdCommentAccepted() throws Exception { 728 // Obtained by modifying apksigner to produce a max-sized (0xffff bytes long) ZIP End of 729 // Central Directory comment, and signing the original.apk using the modified apksigner. 730 assertVerified(verify("v1-only-max-sized-eocd-comment.apk")); 731 assertVerified( 732 verifyForMinSdkVersion("v2-only-max-sized-eocd-comment.apk", AndroidSdkVersion.N)); 733 } 734 735 @Test testEmptyApk()736 public void testEmptyApk() throws Exception { 737 // Unsigned empty ZIP archive 738 try { 739 verifyForMinSdkVersion("empty-unsigned.apk", 1); 740 fail("ApkFormatException should've been thrown"); 741 } catch (ApkFormatException expected) { 742 } 743 744 // JAR-signed empty ZIP archive 745 try { 746 verifyForMinSdkVersion("v1-only-empty.apk", 18); 747 fail("ApkFormatException should've been thrown"); 748 } catch (ApkFormatException expected) { 749 } 750 751 // APK Signature Scheme v2 signed empty ZIP archive 752 try { 753 verifyForMinSdkVersion("v2-only-empty.apk", AndroidSdkVersion.N); 754 fail("ApkFormatException should've been thrown"); 755 } catch (ApkFormatException expected) { 756 } 757 758 // APK Signature Scheme v3 signed empty ZIP archive 759 try { 760 verifyForMinSdkVersion("v3-only-empty.apk", AndroidSdkVersion.P); 761 fail("ApkFormatException should've been thrown"); 762 } catch (ApkFormatException expected) { 763 } 764 } 765 766 @Test testTargetSandboxVersion2AndHigher()767 public void testTargetSandboxVersion2AndHigher() throws Exception { 768 // This APK (and its variants below) use minSdkVersion 18, meaning it needs to be signed 769 // with v1 and v2 schemes 770 771 // This APK is signed with v1 and v2 schemes and thus should verify 772 assertVerified(verify("targetSandboxVersion-2.apk")); 773 774 // v1 signature is needed only if minSdkVersion is lower than 24 775 assertVerificationFailure( 776 verify("v2-only-targetSandboxVersion-2.apk"), Issue.JAR_SIG_NO_MANIFEST); 777 assertVerified(verifyForMinSdkVersion("v2-only-targetSandboxVersion-2.apk", 24)); 778 779 // v2 signature is required 780 assertVerificationFailure( 781 verify("v1-only-targetSandboxVersion-2.apk"), 782 Issue.NO_SIG_FOR_TARGET_SANDBOX_VERSION); 783 assertVerificationFailure( 784 verify("unsigned-targetSandboxVersion-2.apk"), 785 Issue.NO_SIG_FOR_TARGET_SANDBOX_VERSION); 786 787 // minSdkVersion 28, meaning v1 signature not needed 788 assertVerified(verify("v2-only-targetSandboxVersion-3.apk")); 789 } 790 791 @Test testTargetSdkMinSchemeVersionNotMet()792 public void testTargetSdkMinSchemeVersionNotMet() throws Exception { 793 // Android 11 / SDK version 30 requires apps targeting this SDK version or higher must be 794 // signed with at least the V2 signature scheme. This test verifies if an app is targeting 795 // this SDK version and is only signed with a V1 signature then the verifier reports the 796 // platform will not accept it. 797 assertVerificationFailure(verify("v1-ec-p256-targetSdk-30.apk"), 798 Issue.MIN_SIG_SCHEME_FOR_TARGET_SDK_NOT_MET); 799 } 800 801 @Test testTargetSdkMinSchemeVersionMet()802 public void testTargetSdkMinSchemeVersionMet() throws Exception { 803 // This test verifies if an app is signed with the minimum required signature scheme version 804 // for the target SDK version then the verifier reports the platform will accept it. 805 assertVerified(verify("v2-ec-p256-targetSdk-30.apk")); 806 807 // If an app is only signed with a signature scheme higher than the required version for the 808 // target SDK the verifier should also report that the platform will accept it. 809 assertVerified(verify("v3-ec-p256-targetSdk-30.apk")); 810 } 811 812 @Test testTargetSdkMinSchemeVersionNotMetMaxLessThanTarget()813 public void testTargetSdkMinSchemeVersionNotMetMaxLessThanTarget() throws Exception { 814 // If the minimum signature scheme for the target SDK version is not met but the maximum 815 // SDK version is less than the target then the verifier should report that the platform 816 // will accept it since the specified max SDK version does not know about the minimum 817 // signature scheme requirement. 818 verifyForMaxSdkVersion("v1-ec-p256-targetSdk-30.apk", 29); 819 } 820 821 @Test testTargetSdkNoUsesSdkElement()822 public void testTargetSdkNoUsesSdkElement() throws Exception { 823 // The target SDK minimum signature scheme version check will attempt to obtain the 824 // targetSdkVersion attribute value from the uses-sdk element in the AndroidManifest. If 825 // the targetSdkVersion is not specified then the verifier should behave the same as the 826 // platform; the minSdkVersion should be used when available and when neither the minimum or 827 // target SDK are specified a default value of 1 should be used. This test verifies that the 828 // verifier does not fail when the uses-sdk element is not specified. 829 verify("v1-only-no-uses-sdk.apk"); 830 } 831 832 @Test testV1MultipleDigestAlgsInManifestAndSignatureFile()833 public void testV1MultipleDigestAlgsInManifestAndSignatureFile() throws Exception { 834 // MANIFEST.MF contains SHA-1 and SHA-256 digests for each entry, .SF contains only SHA-1 835 // digests. This file was obtained by: 836 // jarsigner -sigalg SHA256withRSA -digestalg SHA-256 ... <file> ... 837 // jarsigner -sigalg SHA1withRSA -digestalg SHA1 ... <same file> ... 838 assertVerified(verify("v1-sha1-sha256-manifest-and-sha1-sf.apk")); 839 840 // MANIFEST.MF and .SF contain SHA-1 and SHA-256 digests for each entry. This file was 841 // obtained by modifying apksigner to output multiple digests. 842 assertVerified(verify("v1-sha1-sha256-manifest-and-sf.apk")); 843 844 // One of the digests is wrong in either MANIFEST.MF or .SF. These files were obtained by 845 // modifying apksigner to output multiple digests and to flip a bit to create a wrong 846 // digest. 847 848 // SHA-1 digests in MANIFEST.MF are wrong, but SHA-256 digests are OK. 849 // The APK will fail to verify on API Level 17 and lower, but will verify on API Level 18 850 // and higher. 851 assertVerificationFailure( 852 verify("v1-sha1-sha256-manifest-and-sf-with-sha1-wrong-in-manifest.apk"), 853 Issue.JAR_SIG_ZIP_ENTRY_DIGEST_DID_NOT_VERIFY); 854 assertVerificationFailure( 855 verifyForMaxSdkVersion( 856 "v1-sha1-sha256-manifest-and-sf-with-sha1-wrong-in-manifest.apk", 17), 857 Issue.JAR_SIG_ZIP_ENTRY_DIGEST_DID_NOT_VERIFY); 858 assertVerified( 859 verifyForMinSdkVersion( 860 "v1-sha1-sha256-manifest-and-sf-with-sha1-wrong-in-manifest.apk", 18)); 861 862 // SHA-1 digests in .SF are wrong, but SHA-256 digests are OK. 863 // The APK will fail to verify on API Level 17 and lower, but will verify on API Level 18 864 // and higher. 865 assertVerificationFailure( 866 verify("v1-sha1-sha256-manifest-and-sf-with-sha1-wrong-in-sf.apk"), 867 Issue.JAR_SIG_MANIFEST_SECTION_DIGEST_DID_NOT_VERIFY); 868 assertVerificationFailure( 869 verifyForMaxSdkVersion( 870 "v1-sha1-sha256-manifest-and-sf-with-sha1-wrong-in-sf.apk", 17), 871 Issue.JAR_SIG_MANIFEST_SECTION_DIGEST_DID_NOT_VERIFY); 872 assertVerified( 873 verifyForMinSdkVersion( 874 "v1-sha1-sha256-manifest-and-sf-with-sha1-wrong-in-sf.apk", 18)); 875 876 // SHA-256 digests in MANIFEST.MF are wrong, but SHA-1 digests are OK. 877 // The APK will fail to verify on API Level 18 and higher, but will verify on API Level 17 878 // and lower. 879 assertVerificationFailure( 880 verify("v1-sha1-sha256-manifest-and-sf-with-sha256-wrong-in-manifest.apk"), 881 Issue.JAR_SIG_ZIP_ENTRY_DIGEST_DID_NOT_VERIFY); 882 assertVerificationFailure( 883 verifyForMinSdkVersion( 884 "v1-sha1-sha256-manifest-and-sf-with-sha256-wrong-in-manifest.apk", 18), 885 Issue.JAR_SIG_ZIP_ENTRY_DIGEST_DID_NOT_VERIFY); 886 assertVerified( 887 verifyForMaxSdkVersion( 888 "v1-sha1-sha256-manifest-and-sf-with-sha256-wrong-in-manifest.apk", 17)); 889 890 // SHA-256 digests in .SF are wrong, but SHA-1 digests are OK. 891 // The APK will fail to verify on API Level 18 and higher, but will verify on API Level 17 892 // and lower. 893 assertVerificationFailure( 894 verify("v1-sha1-sha256-manifest-and-sf-with-sha256-wrong-in-sf.apk"), 895 Issue.JAR_SIG_MANIFEST_SECTION_DIGEST_DID_NOT_VERIFY); 896 assertVerificationFailure( 897 verifyForMinSdkVersion( 898 "v1-sha1-sha256-manifest-and-sf-with-sha256-wrong-in-sf.apk", 18), 899 Issue.JAR_SIG_MANIFEST_SECTION_DIGEST_DID_NOT_VERIFY); 900 assertVerified( 901 verifyForMaxSdkVersion( 902 "v1-sha1-sha256-manifest-and-sf-with-sha256-wrong-in-sf.apk", 17)); 903 } 904 905 @Test testV1WithUnsupportedCharacterInZipEntryName()906 public void testV1WithUnsupportedCharacterInZipEntryName() throws Exception { 907 // Android Package Manager does not support ZIP entry names containing CR or LF 908 assertVerificationFailure( 909 verify("v1-only-with-cr-in-entry-name.apk"), 910 Issue.JAR_SIG_UNNNAMED_MANIFEST_SECTION); 911 assertVerificationFailure( 912 verify("v1-only-with-lf-in-entry-name.apk"), 913 Issue.JAR_SIG_UNNNAMED_MANIFEST_SECTION); 914 } 915 916 @Test testWeirdZipCompressionMethod()917 public void testWeirdZipCompressionMethod() throws Exception { 918 // Any ZIP compression method other than STORED is treated as DEFLATED by Android. 919 // This APK declares compression method 21 (neither STORED nor DEFLATED) for CERT.RSA entry, 920 // but the entry is actually Deflate-compressed. 921 assertVerified(verify("weird-compression-method.apk")); 922 } 923 924 @Test testZipCompressionMethodMismatchBetweenLfhAndCd()925 public void testZipCompressionMethodMismatchBetweenLfhAndCd() throws Exception { 926 // Android Package Manager ignores compressionMethod field in Local File Header and always 927 // uses the compressionMethod from Central Directory instead. 928 // In this APK, compression method of CERT.RSA is declared as STORED in Local File Header 929 // and as DEFLATED in Central Directory. The entry is actually Deflate-compressed. 930 assertVerified(verify("mismatched-compression-method.apk")); 931 } 932 933 @Test testV1SignedAttrs()934 public void testV1SignedAttrs() throws Exception { 935 String apk = "v1-only-with-signed-attrs.apk"; 936 assertVerificationFailure( 937 verifyForMinSdkVersion(apk, AndroidSdkVersion.JELLY_BEAN_MR2), 938 Issue.JAR_SIG_VERIFY_EXCEPTION); 939 assertVerified(verifyForMinSdkVersion(apk, AndroidSdkVersion.KITKAT)); 940 941 apk = "v1-only-with-signed-attrs-signerInfo1-good-signerInfo2-good.apk"; 942 assertVerificationFailure( 943 verifyForMinSdkVersion(apk, AndroidSdkVersion.JELLY_BEAN_MR2), 944 Issue.JAR_SIG_VERIFY_EXCEPTION); 945 assertVerified(verifyForMinSdkVersion(apk, AndroidSdkVersion.KITKAT)); 946 } 947 948 @Test testV1SignedAttrsNotInDerOrder()949 public void testV1SignedAttrsNotInDerOrder() throws Exception { 950 // Android does not re-order SignedAttributes despite it being a SET OF. Pre-N, Android 951 // treated them as SEQUENCE OF, meaning no re-ordering is necessary. From N onwards, it 952 // treats them as SET OF, but does not re-encode into SET OF during verification if all 953 // attributes parsed fine. 954 assertVerified(verify("v1-only-with-signed-attrs-wrong-order.apk")); 955 assertVerified( 956 verify("v1-only-with-signed-attrs-signerInfo1-wrong-order-signerInfo2-good.apk")); 957 } 958 959 @Test testV1SignedAttrsMissingContentType()960 public void testV1SignedAttrsMissingContentType() throws Exception { 961 // SignedAttributes must contain ContentType. Pre-N, Android ignores this requirement. 962 // Android N onwards rejects such APKs. 963 String apk = "v1-only-with-signed-attrs-missing-content-type.apk"; 964 assertVerified(verifyForMaxSdkVersion(apk, AndroidSdkVersion.N - 1)); 965 assertVerificationFailure(verify(apk), Issue.JAR_SIG_VERIFY_EXCEPTION); 966 // Assert that this issue fails verification of the entire signature block, rather than 967 // skipping the broken SignerInfo. The second signer info SignerInfo verifies fine, but 968 // verification does not get there. 969 apk = "v1-only-with-signed-attrs-signerInfo1-missing-content-type-signerInfo2-good.apk"; 970 assertVerified(verifyForMaxSdkVersion(apk, AndroidSdkVersion.N - 1)); 971 assertVerificationFailure(verify(apk), Issue.JAR_SIG_VERIFY_EXCEPTION); 972 } 973 974 @Test testV1SignedAttrsWrongContentType()975 public void testV1SignedAttrsWrongContentType() throws Exception { 976 // ContentType of SignedAttributes must equal SignedData.encapContentInfo.eContentType. 977 // Pre-N, Android ignores this requirement. 978 // From N onwards, Android rejects such SignerInfos. 979 String apk = "v1-only-with-signed-attrs-wrong-content-type.apk"; 980 assertVerified(verifyForMaxSdkVersion(apk, AndroidSdkVersion.N - 1)); 981 assertVerificationFailure(verify(apk), Issue.JAR_SIG_DID_NOT_VERIFY); 982 // First SignerInfo does not verify on Android N and newer, but verification moves on to the 983 // second SignerInfo, which verifies. 984 apk = "v1-only-with-signed-attrs-signerInfo1-wrong-content-type-signerInfo2-good.apk"; 985 assertVerified(verifyForMaxSdkVersion(apk, AndroidSdkVersion.N - 1)); 986 assertVerified(verifyForMinSdkVersion(apk, AndroidSdkVersion.N)); 987 // Although the APK's signature verifies on pre-N and N+, we reject such APKs because the 988 // APK's verification results in different verified SignerInfos (and thus potentially 989 // different signing certs) between pre-N and N+. 990 assertVerificationFailure(verify(apk), Issue.JAR_SIG_DID_NOT_VERIFY); 991 } 992 993 @Test testV1SignedAttrsMissingDigest()994 public void testV1SignedAttrsMissingDigest() throws Exception { 995 // Content digest must be present in SignedAttributes 996 String apk = "v1-only-with-signed-attrs-missing-digest.apk"; 997 assertVerificationFailure( 998 verifyForMaxSdkVersion(apk, AndroidSdkVersion.N - 1), 999 Issue.JAR_SIG_VERIFY_EXCEPTION); 1000 assertVerificationFailure( 1001 verifyForMinSdkVersion(apk, AndroidSdkVersion.N), Issue.JAR_SIG_VERIFY_EXCEPTION); 1002 // Assert that this issue fails verification of the entire signature block, rather than 1003 // skipping the broken SignerInfo. The second signer info SignerInfo verifies fine, but 1004 // verification does not get there. 1005 apk = "v1-only-with-signed-attrs-signerInfo1-missing-digest-signerInfo2-good.apk"; 1006 assertVerificationFailure( 1007 verifyForMaxSdkVersion(apk, AndroidSdkVersion.N - 1), 1008 Issue.JAR_SIG_VERIFY_EXCEPTION); 1009 assertVerificationFailure( 1010 verifyForMinSdkVersion(apk, AndroidSdkVersion.N), Issue.JAR_SIG_VERIFY_EXCEPTION); 1011 } 1012 1013 @Test testV1SignedAttrsMultipleGoodDigests()1014 public void testV1SignedAttrsMultipleGoodDigests() throws Exception { 1015 // Only one content digest must be present in SignedAttributes 1016 String apk = "v1-only-with-signed-attrs-multiple-good-digests.apk"; 1017 assertVerificationFailure( 1018 verifyForMaxSdkVersion(apk, AndroidSdkVersion.N - 1), 1019 Issue.JAR_SIG_PARSE_EXCEPTION); 1020 assertVerificationFailure( 1021 verifyForMinSdkVersion(apk, AndroidSdkVersion.N), Issue.JAR_SIG_PARSE_EXCEPTION); 1022 // Assert that this issue fails verification of the entire signature block, rather than 1023 // skipping the broken SignerInfo. The second signer info SignerInfo verifies fine, but 1024 // verification does not get there. 1025 apk = "v1-only-with-signed-attrs-signerInfo1-multiple-good-digests-signerInfo2-good.apk"; 1026 assertVerificationFailure( 1027 verifyForMaxSdkVersion(apk, AndroidSdkVersion.N - 1), 1028 Issue.JAR_SIG_PARSE_EXCEPTION); 1029 assertVerificationFailure( 1030 verifyForMinSdkVersion(apk, AndroidSdkVersion.N), Issue.JAR_SIG_PARSE_EXCEPTION); 1031 } 1032 1033 @Test testV1SignedAttrsWrongDigest()1034 public void testV1SignedAttrsWrongDigest() throws Exception { 1035 // Content digest in SignedAttributes does not match the contents 1036 String apk = "v1-only-with-signed-attrs-wrong-digest.apk"; 1037 assertVerificationFailure( 1038 verifyForMaxSdkVersion(apk, AndroidSdkVersion.N - 1), Issue.JAR_SIG_DID_NOT_VERIFY); 1039 assertVerificationFailure( 1040 verifyForMinSdkVersion(apk, AndroidSdkVersion.N), Issue.JAR_SIG_DID_NOT_VERIFY); 1041 // First SignerInfo does not verify, but Android N and newer moves on to the second 1042 // SignerInfo, which verifies. 1043 apk = "v1-only-with-signed-attrs-signerInfo1-wrong-digest-signerInfo2-good.apk"; 1044 assertVerificationFailure( 1045 verifyForMaxSdkVersion(apk, AndroidSdkVersion.N - 1), Issue.JAR_SIG_DID_NOT_VERIFY); 1046 assertVerified(verifyForMinSdkVersion(apk, AndroidSdkVersion.N)); 1047 } 1048 1049 @Test testV1SignedAttrsWrongSignature()1050 public void testV1SignedAttrsWrongSignature() throws Exception { 1051 // Signature over SignedAttributes does not verify 1052 String apk = "v1-only-with-signed-attrs-wrong-signature.apk"; 1053 assertVerificationFailure( 1054 verifyForMaxSdkVersion(apk, AndroidSdkVersion.N - 1), Issue.JAR_SIG_DID_NOT_VERIFY); 1055 assertVerificationFailure( 1056 verifyForMinSdkVersion(apk, AndroidSdkVersion.N), Issue.JAR_SIG_DID_NOT_VERIFY); 1057 // First SignerInfo does not verify, but Android N and newer moves on to the second 1058 // SignerInfo, which verifies. 1059 apk = "v1-only-with-signed-attrs-signerInfo1-wrong-signature-signerInfo2-good.apk"; 1060 assertVerificationFailure( 1061 verifyForMaxSdkVersion(apk, AndroidSdkVersion.N - 1), Issue.JAR_SIG_DID_NOT_VERIFY); 1062 assertVerified(verifyForMinSdkVersion(apk, AndroidSdkVersion.N)); 1063 } 1064 1065 @Test testSourceStampBlock_correctSignature()1066 public void testSourceStampBlock_correctSignature() throws Exception { 1067 ApkVerifier.Result verificationResult = verify("valid-stamp.apk"); 1068 // Verifies the signature of the APK. 1069 assertVerified(verificationResult); 1070 // Verifies the signature of source stamp. 1071 assertTrue(verificationResult.isSourceStampVerified()); 1072 } 1073 1074 @Test verifySourceStamp_correctSignature()1075 public void verifySourceStamp_correctSignature() throws Exception { 1076 ApkVerifier.Result verificationResult = verifySourceStamp("valid-stamp.apk"); 1077 // Since the API is only verifying the source stamp the result itself should be marked as 1078 // verified. 1079 assertVerified(verificationResult); 1080 assertSourceStampVerificationStatus(verificationResult, 1081 SourceStampVerificationStatus.STAMP_VERIFIED); 1082 1083 // The source stamp can also be verified by platform version; confirm the verification works 1084 // using just the max signature scheme version supported by that platform version. 1085 verificationResult = verifySourceStamp("valid-stamp.apk", 18, 18); 1086 assertVerified(verificationResult); 1087 assertSourceStampVerificationStatus(verificationResult, 1088 SourceStampVerificationStatus.STAMP_VERIFIED); 1089 1090 verificationResult = verifySourceStamp("valid-stamp.apk", 24, 24); 1091 assertVerified(verificationResult); 1092 assertSourceStampVerificationStatus(verificationResult, 1093 SourceStampVerificationStatus.STAMP_VERIFIED); 1094 1095 verificationResult = verifySourceStamp("valid-stamp.apk", 28, 28); 1096 assertVerified(verificationResult); 1097 assertSourceStampVerificationStatus(verificationResult, 1098 SourceStampVerificationStatus.STAMP_VERIFIED); 1099 } 1100 1101 @Test testSourceStampBlock_signatureMissing()1102 public void testSourceStampBlock_signatureMissing() throws Exception { 1103 ApkVerifier.Result verificationResult = verify("stamp-without-block.apk"); 1104 // A broken stamp should not block a signing scheme verified APK. 1105 assertVerified(verificationResult); 1106 assertSourceStampVerificationFailure(verificationResult, Issue.SOURCE_STAMP_SIG_MISSING); 1107 } 1108 1109 @Test verifySourceStamp_signatureMissing()1110 public void verifySourceStamp_signatureMissing() throws Exception { 1111 ApkVerifier.Result verificationResult = verifySourceStamp("stamp-without-block.apk"); 1112 assertSourceStampVerificationStatus(verificationResult, 1113 SourceStampVerificationStatus.STAMP_NOT_VERIFIED); 1114 assertSourceStampVerificationFailure(verificationResult, Issue.SOURCE_STAMP_SIG_MISSING); 1115 } 1116 1117 @Test testSourceStampBlock_certificateMismatch()1118 public void testSourceStampBlock_certificateMismatch() throws Exception { 1119 ApkVerifier.Result verificationResult = verify("stamp-certificate-mismatch.apk"); 1120 // A broken stamp should not block a signing scheme verified APK. 1121 assertVerified(verificationResult); 1122 assertSourceStampVerificationFailure( 1123 verificationResult, 1124 Issue.SOURCE_STAMP_CERTIFICATE_MISMATCH_BETWEEN_SIGNATURE_BLOCK_AND_APK); 1125 } 1126 1127 @Test verifySourceStamp_certificateMismatch()1128 public void verifySourceStamp_certificateMismatch() throws Exception { 1129 ApkVerifier.Result verificationResult = verifySourceStamp("stamp-certificate-mismatch.apk"); 1130 assertSourceStampVerificationStatus(verificationResult, 1131 SourceStampVerificationStatus.STAMP_VERIFICATION_FAILED); 1132 assertSourceStampVerificationFailure( 1133 verificationResult, 1134 Issue.SOURCE_STAMP_CERTIFICATE_MISMATCH_BETWEEN_SIGNATURE_BLOCK_AND_APK); 1135 } 1136 1137 @Test testSourceStampBlock_v1OnlySignatureValidStamp()1138 public void testSourceStampBlock_v1OnlySignatureValidStamp() throws Exception { 1139 ApkVerifier.Result verificationResult = verify("v1-only-with-stamp.apk"); 1140 assertVerified(verificationResult); 1141 assertTrue(verificationResult.isSourceStampVerified()); 1142 } 1143 1144 @Test verifySourceStamp_v1OnlySignatureValidStamp()1145 public void verifySourceStamp_v1OnlySignatureValidStamp() throws Exception { 1146 ApkVerifier.Result verificationResult = verifySourceStamp("v1-only-with-stamp.apk"); 1147 assertVerified(verificationResult); 1148 assertSourceStampVerificationStatus(verificationResult, 1149 SourceStampVerificationStatus.STAMP_VERIFIED); 1150 1151 // Confirm that the source stamp verification succeeds when specifying platform versions 1152 // that supported later signature scheme versions. 1153 verificationResult = verifySourceStamp("v1-only-with-stamp.apk", 28, 28); 1154 assertVerified(verificationResult); 1155 assertSourceStampVerificationStatus(verificationResult, 1156 SourceStampVerificationStatus.STAMP_VERIFIED); 1157 1158 verificationResult = verifySourceStamp("v1-only-with-stamp.apk", 24, 24); 1159 assertVerified(verificationResult); 1160 assertSourceStampVerificationStatus(verificationResult, 1161 SourceStampVerificationStatus.STAMP_VERIFIED); 1162 } 1163 1164 @Test testSourceStampBlock_v2OnlySignatureValidStamp()1165 public void testSourceStampBlock_v2OnlySignatureValidStamp() throws Exception { 1166 ApkVerifier.Result verificationResult = verify("v2-only-with-stamp.apk"); 1167 assertVerified(verificationResult); 1168 assertTrue(verificationResult.isSourceStampVerified()); 1169 } 1170 1171 @Test verifySourceStamp_v2OnlySignatureValidStamp()1172 public void verifySourceStamp_v2OnlySignatureValidStamp() throws Exception { 1173 ApkVerifier.Result verificationResult = verifySourceStamp("v2-only-with-stamp.apk"); 1174 assertVerified(verificationResult); 1175 assertSourceStampVerificationStatus(verificationResult, 1176 SourceStampVerificationStatus.STAMP_VERIFIED); 1177 1178 // Confirm that the source stamp verification succeeds when specifying a platform version 1179 // that supports a later signature scheme version. 1180 verificationResult = verifySourceStamp("v2-only-with-stamp.apk", 28, 28); 1181 assertVerified(verificationResult); 1182 assertSourceStampVerificationStatus(verificationResult, 1183 SourceStampVerificationStatus.STAMP_VERIFIED); 1184 } 1185 1186 @Test testSourceStampBlock_v3OnlySignatureValidStamp()1187 public void testSourceStampBlock_v3OnlySignatureValidStamp() throws Exception { 1188 ApkVerifier.Result verificationResult = verify("v3-only-with-stamp.apk"); 1189 assertVerified(verificationResult); 1190 assertTrue(verificationResult.isSourceStampVerified()); 1191 } 1192 1193 @Test verifySourceStamp_v3OnlySignatureValidStamp()1194 public void verifySourceStamp_v3OnlySignatureValidStamp() throws Exception { 1195 ApkVerifier.Result verificationResult = verifySourceStamp("v3-only-with-stamp.apk"); 1196 assertVerified(verificationResult); 1197 assertSourceStampVerificationStatus(verificationResult, 1198 SourceStampVerificationStatus.STAMP_VERIFIED); 1199 } 1200 1201 @Test testSourceStampBlock_apkHashMismatch_v1SignatureScheme()1202 public void testSourceStampBlock_apkHashMismatch_v1SignatureScheme() throws Exception { 1203 ApkVerifier.Result verificationResult = verify("stamp-apk-hash-mismatch-v1.apk"); 1204 // A broken stamp should not block a signing scheme verified APK. 1205 assertVerified(verificationResult); 1206 assertSourceStampVerificationFailure(verificationResult, Issue.SOURCE_STAMP_DID_NOT_VERIFY); 1207 } 1208 1209 @Test verifySourceStamp_apkHashMismatch_v1SignatureScheme()1210 public void verifySourceStamp_apkHashMismatch_v1SignatureScheme() throws Exception { 1211 ApkVerifier.Result verificationResult = verifySourceStamp("stamp-apk-hash-mismatch-v1.apk"); 1212 assertSourceStampVerificationStatus(verificationResult, 1213 SourceStampVerificationStatus.STAMP_VERIFICATION_FAILED); 1214 assertSourceStampVerificationFailure(verificationResult, Issue.SOURCE_STAMP_DID_NOT_VERIFY); 1215 } 1216 1217 @Test testSourceStampBlock_apkHashMismatch_v2SignatureScheme()1218 public void testSourceStampBlock_apkHashMismatch_v2SignatureScheme() throws Exception { 1219 ApkVerifier.Result verificationResult = verify("stamp-apk-hash-mismatch-v2.apk"); 1220 // A broken stamp should not block a signing scheme verified APK. 1221 assertVerified(verificationResult); 1222 assertSourceStampVerificationFailure(verificationResult, Issue.SOURCE_STAMP_DID_NOT_VERIFY); 1223 } 1224 1225 @Test verifySourceStamp_apkHashMismatch_v2SignatureScheme()1226 public void verifySourceStamp_apkHashMismatch_v2SignatureScheme() throws Exception { 1227 ApkVerifier.Result verificationResult = verifySourceStamp("stamp-apk-hash-mismatch-v2.apk"); 1228 assertSourceStampVerificationStatus(verificationResult, 1229 SourceStampVerificationStatus.STAMP_VERIFICATION_FAILED); 1230 assertSourceStampVerificationFailure(verificationResult, Issue.SOURCE_STAMP_DID_NOT_VERIFY); 1231 } 1232 1233 @Test testSourceStampBlock_apkHashMismatch_v3SignatureScheme()1234 public void testSourceStampBlock_apkHashMismatch_v3SignatureScheme() throws Exception { 1235 ApkVerifier.Result verificationResult = verify("stamp-apk-hash-mismatch-v3.apk"); 1236 // A broken stamp should not block a signing scheme verified APK. 1237 assertVerified(verificationResult); 1238 assertSourceStampVerificationFailure(verificationResult, Issue.SOURCE_STAMP_DID_NOT_VERIFY); 1239 } 1240 1241 @Test verifySourceStamp_apkHashMismatch_v3SignatureScheme()1242 public void verifySourceStamp_apkHashMismatch_v3SignatureScheme() throws Exception { 1243 ApkVerifier.Result verificationResult = verifySourceStamp("stamp-apk-hash-mismatch-v3.apk"); 1244 assertSourceStampVerificationStatus(verificationResult, 1245 SourceStampVerificationStatus.STAMP_VERIFICATION_FAILED); 1246 assertSourceStampVerificationFailure(verificationResult, Issue.SOURCE_STAMP_DID_NOT_VERIFY); 1247 } 1248 1249 @Test testSourceStampBlock_malformedSignature()1250 public void testSourceStampBlock_malformedSignature() throws Exception { 1251 ApkVerifier.Result verificationResult = verify("stamp-malformed-signature.apk"); 1252 // A broken stamp should not block a signing scheme verified APK. 1253 assertVerified(verificationResult); 1254 assertSourceStampVerificationFailure( 1255 verificationResult, Issue.SOURCE_STAMP_MALFORMED_SIGNATURE); 1256 } 1257 1258 @Test verifySourceStamp_malformedSignature()1259 public void verifySourceStamp_malformedSignature() throws Exception { 1260 ApkVerifier.Result verificationResult = verifySourceStamp("stamp-malformed-signature.apk"); 1261 assertSourceStampVerificationStatus(verificationResult, 1262 SourceStampVerificationStatus.STAMP_VERIFICATION_FAILED); 1263 assertSourceStampVerificationFailure( 1264 verificationResult, Issue.SOURCE_STAMP_MALFORMED_SIGNATURE); 1265 } 1266 1267 @Test verifySourceStamp_expectedDigestMatchesActual()1268 public void verifySourceStamp_expectedDigestMatchesActual() throws Exception { 1269 // The ApkVerifier provides an API to specify the expected certificate digest; this test 1270 // verifies that the test runs through to completion when the actual digest matches the 1271 // provided value. 1272 ApkVerifier.Result verificationResult = verifySourceStamp("v3-only-with-stamp.apk", 1273 RSA_2048_CERT_SHA256_DIGEST); 1274 assertVerified(verificationResult); 1275 assertSourceStampVerificationStatus(verificationResult, 1276 SourceStampVerificationStatus.STAMP_VERIFIED); 1277 } 1278 1279 @Test verifySourceStamp_expectedDigestMismatch()1280 public void verifySourceStamp_expectedDigestMismatch() throws Exception { 1281 // If the caller requests source stamp verification with an expected cert digest that does 1282 // not match the actual digest in the APK the verifier should report the mismatch. 1283 ApkVerifier.Result verificationResult = verifySourceStamp("v3-only-with-stamp.apk", 1284 EC_P256_CERT_SHA256_DIGEST); 1285 assertSourceStampVerificationStatus(verificationResult, 1286 SourceStampVerificationStatus.CERT_DIGEST_MISMATCH); 1287 assertSourceStampVerificationFailure(verificationResult, 1288 Issue.SOURCE_STAMP_EXPECTED_DIGEST_MISMATCH); 1289 } 1290 1291 @Test verifySourceStamp_validStampLineage()1292 public void verifySourceStamp_validStampLineage() throws Exception { 1293 ApkVerifier.Result verificationResult = verifySourceStamp("stamp-lineage-valid.apk"); 1294 assertVerified(verificationResult); 1295 assertSourceStampVerificationStatus(verificationResult, 1296 SourceStampVerificationStatus.STAMP_VERIFIED); 1297 } 1298 1299 @Test verifySourceStamp_invalidStampLineage()1300 public void verifySourceStamp_invalidStampLineage() throws Exception { 1301 ApkVerifier.Result verificationResult = verifySourceStamp("stamp-lineage-invalid.apk"); 1302 assertSourceStampVerificationStatus(verificationResult, 1303 SourceStampVerificationStatus.STAMP_VERIFICATION_FAILED); 1304 assertSourceStampVerificationFailure(verificationResult, 1305 Issue.SOURCE_STAMP_POR_CERT_MISMATCH); 1306 } 1307 1308 @Test apkVerificationIssueAdapter_verifyAllBaseIssuesMapped()1309 public void apkVerificationIssueAdapter_verifyAllBaseIssuesMapped() throws Exception { 1310 Field[] fields = ApkVerificationIssue.class.getFields(); 1311 StringBuilder msg = new StringBuilder(); 1312 for (Field field : fields) { 1313 // All public static int fields in the ApkVerificationIssue class should be issue IDs; 1314 // if any are added that are not intended as IDs a filter set should be applied to this 1315 // test. 1316 if (Modifier.isStatic(field.getModifiers()) && field.getType() == int.class) { 1317 if (!ApkVerifier.ApkVerificationIssueAdapter 1318 .sVerificationIssueIdToIssue.containsKey(field.get(null))) { 1319 if (msg.length() > 0) { 1320 msg.append('\n'); 1321 } 1322 msg.append( 1323 "A mapping is required from ApkVerificationIssue." + field.getName() 1324 + " to an ApkVerifier.Issue in ApkVerificationIssueAdapter"); 1325 } 1326 } 1327 } 1328 if (msg.length() > 0) { 1329 fail(msg.toString()); 1330 } 1331 } 1332 verify(String apkFilenameInResources)1333 private ApkVerifier.Result verify(String apkFilenameInResources) 1334 throws IOException, ApkFormatException, NoSuchAlgorithmException { 1335 return verify(apkFilenameInResources, null, null); 1336 } 1337 verifyForMinSdkVersion( String apkFilenameInResources, int minSdkVersion)1338 private ApkVerifier.Result verifyForMinSdkVersion( 1339 String apkFilenameInResources, int minSdkVersion) 1340 throws IOException, ApkFormatException, NoSuchAlgorithmException { 1341 return verify(apkFilenameInResources, minSdkVersion, null); 1342 } 1343 verifyForMaxSdkVersion( String apkFilenameInResources, int maxSdkVersion)1344 private ApkVerifier.Result verifyForMaxSdkVersion( 1345 String apkFilenameInResources, int maxSdkVersion) 1346 throws IOException, ApkFormatException, NoSuchAlgorithmException { 1347 return verify(apkFilenameInResources, null, maxSdkVersion); 1348 } 1349 verify( String apkFilenameInResources, Integer minSdkVersionOverride, Integer maxSdkVersionOverride)1350 private ApkVerifier.Result verify( 1351 String apkFilenameInResources, 1352 Integer minSdkVersionOverride, 1353 Integer maxSdkVersionOverride) 1354 throws IOException, ApkFormatException, NoSuchAlgorithmException { 1355 byte[] apkBytes = Resources.toByteArray(getClass(), apkFilenameInResources); 1356 1357 ApkVerifier.Builder builder = 1358 new ApkVerifier.Builder(DataSources.asDataSource(ByteBuffer.wrap(apkBytes))); 1359 if (minSdkVersionOverride != null) { 1360 builder.setMinCheckedPlatformVersion(minSdkVersionOverride); 1361 } 1362 if (maxSdkVersionOverride != null) { 1363 builder.setMaxCheckedPlatformVersion(maxSdkVersionOverride); 1364 } 1365 return builder.build().verify(); 1366 } 1367 verifySourceStamp(String apkFilenameInResources)1368 private ApkVerifier.Result verifySourceStamp(String apkFilenameInResources) throws Exception { 1369 return verifySourceStamp(apkFilenameInResources, null, null, null); 1370 } 1371 verifySourceStamp(String apkFilenameInResources, String expectedCertDigest)1372 private ApkVerifier.Result verifySourceStamp(String apkFilenameInResources, 1373 String expectedCertDigest) throws Exception { 1374 return verifySourceStamp(apkFilenameInResources, expectedCertDigest, null, null); 1375 } 1376 verifySourceStamp(String apkFilenameInResources, Integer minSdkVersionOverride, Integer maxSdkVersionOverride)1377 private ApkVerifier.Result verifySourceStamp(String apkFilenameInResources, 1378 Integer minSdkVersionOverride, Integer maxSdkVersionOverride) throws Exception { 1379 return verifySourceStamp(apkFilenameInResources, null, minSdkVersionOverride, 1380 maxSdkVersionOverride); 1381 } 1382 verifySourceStamp(String apkFilenameInResources, String expectedCertDigest, Integer minSdkVersionOverride, Integer maxSdkVersionOverride)1383 private ApkVerifier.Result verifySourceStamp(String apkFilenameInResources, 1384 String expectedCertDigest, Integer minSdkVersionOverride, Integer maxSdkVersionOverride) 1385 throws Exception { 1386 byte[] apkBytes = Resources.toByteArray(getClass(), apkFilenameInResources); 1387 ApkVerifier.Builder builder = new ApkVerifier.Builder( 1388 DataSources.asDataSource(ByteBuffer.wrap(apkBytes))); 1389 if (minSdkVersionOverride != null) { 1390 builder.setMinCheckedPlatformVersion(minSdkVersionOverride); 1391 } 1392 if (maxSdkVersionOverride != null) { 1393 builder.setMaxCheckedPlatformVersion(maxSdkVersionOverride); 1394 } 1395 return builder.build().verifySourceStamp(expectedCertDigest); 1396 } 1397 assertVerified(ApkVerifier.Result result)1398 static void assertVerified(ApkVerifier.Result result) { 1399 assertVerified(result, "APK"); 1400 } 1401 assertVerified(ApkVerifier.Result result, String apkId)1402 static void assertVerified(ApkVerifier.Result result, String apkId) { 1403 if (result.isVerified()) { 1404 return; 1405 } 1406 1407 StringBuilder msg = new StringBuilder(); 1408 for (IssueWithParams issue : result.getErrors()) { 1409 if (msg.length() > 0) { 1410 msg.append('\n'); 1411 } 1412 msg.append(issue); 1413 } 1414 for (ApkVerifier.Result.V1SchemeSignerInfo signer : result.getV1SchemeSigners()) { 1415 String signerName = signer.getName(); 1416 for (IssueWithParams issue : signer.getErrors()) { 1417 if (msg.length() > 0) { 1418 msg.append('\n'); 1419 } 1420 msg.append("JAR signer ") 1421 .append(signerName) 1422 .append(": ") 1423 .append(issue.getIssue()) 1424 .append(": ") 1425 .append(issue); 1426 } 1427 } 1428 for (ApkVerifier.Result.V2SchemeSignerInfo signer : result.getV2SchemeSigners()) { 1429 String signerName = "signer #" + (signer.getIndex() + 1); 1430 for (IssueWithParams issue : signer.getErrors()) { 1431 if (msg.length() > 0) { 1432 msg.append('\n'); 1433 } 1434 msg.append("APK Signature Scheme v2 signer ") 1435 .append(signerName) 1436 .append(": ") 1437 .append(issue.getIssue()) 1438 .append(": ") 1439 .append(issue); 1440 } 1441 } 1442 for (ApkVerifier.Result.V3SchemeSignerInfo signer : result.getV3SchemeSigners()) { 1443 String signerName = "signer #" + (signer.getIndex() + 1); 1444 for (IssueWithParams issue : signer.getErrors()) { 1445 if (msg.length() > 0) { 1446 msg.append('\n'); 1447 } 1448 msg.append("APK Signature Scheme v3 signer ") 1449 .append(signerName) 1450 .append(": ") 1451 .append(issue.getIssue()) 1452 .append(": ") 1453 .append(issue); 1454 } 1455 } 1456 1457 fail(apkId + " did not verify: " + msg); 1458 } 1459 assertVerified( String apkFilenameInResources, Integer minSdkVersionOverride, Integer maxSdkVersionOverride)1460 private void assertVerified( 1461 String apkFilenameInResources, 1462 Integer minSdkVersionOverride, 1463 Integer maxSdkVersionOverride) 1464 throws Exception { 1465 assertVerified( 1466 verify(apkFilenameInResources, minSdkVersionOverride, maxSdkVersionOverride), 1467 apkFilenameInResources); 1468 } 1469 assertVerificationFailure(ApkVerifier.Result result, Issue expectedIssue)1470 static void assertVerificationFailure(ApkVerifier.Result result, Issue expectedIssue) { 1471 if (result.isVerified()) { 1472 fail("APK verification succeeded instead of failing with " + expectedIssue); 1473 return; 1474 } 1475 1476 StringBuilder msg = new StringBuilder(); 1477 for (IssueWithParams issue : result.getErrors()) { 1478 if (expectedIssue.equals(issue.getIssue())) { 1479 return; 1480 } 1481 if (msg.length() > 0) { 1482 msg.append('\n'); 1483 } 1484 msg.append(issue); 1485 } 1486 for (ApkVerifier.Result.V1SchemeSignerInfo signer : result.getV1SchemeSigners()) { 1487 String signerName = signer.getName(); 1488 for (ApkVerifier.IssueWithParams issue : signer.getErrors()) { 1489 if (expectedIssue.equals(issue.getIssue())) { 1490 return; 1491 } 1492 if (msg.length() > 0) { 1493 msg.append('\n'); 1494 } 1495 msg.append("JAR signer ") 1496 .append(signerName) 1497 .append(": ") 1498 .append(issue.getIssue()) 1499 .append(" ") 1500 .append(issue); 1501 } 1502 } 1503 for (ApkVerifier.Result.V2SchemeSignerInfo signer : result.getV2SchemeSigners()) { 1504 String signerName = "signer #" + (signer.getIndex() + 1); 1505 for (IssueWithParams issue : signer.getErrors()) { 1506 if (expectedIssue.equals(issue.getIssue())) { 1507 return; 1508 } 1509 if (msg.length() > 0) { 1510 msg.append('\n'); 1511 } 1512 msg.append("APK Signature Scheme v2 signer ") 1513 .append(signerName) 1514 .append(": ") 1515 .append(issue); 1516 } 1517 } 1518 for (ApkVerifier.Result.V3SchemeSignerInfo signer : result.getV3SchemeSigners()) { 1519 String signerName = "signer #" + (signer.getIndex() + 1); 1520 for (IssueWithParams issue : signer.getErrors()) { 1521 if (expectedIssue.equals(issue.getIssue())) { 1522 return; 1523 } 1524 if (msg.length() > 0) { 1525 msg.append('\n'); 1526 } 1527 msg.append("APK Signature Scheme v3 signer ") 1528 .append(signerName) 1529 .append(": ") 1530 .append(issue); 1531 } 1532 } 1533 1534 fail( 1535 "APK failed verification for the wrong reason" 1536 + ". Expected: " 1537 + expectedIssue 1538 + ", actual: " 1539 + msg); 1540 } 1541 assertSourceStampVerificationFailure( ApkVerifier.Result result, Issue expectedIssue)1542 private static void assertSourceStampVerificationFailure( 1543 ApkVerifier.Result result, Issue expectedIssue) { 1544 if (result.isSourceStampVerified()) { 1545 fail( 1546 "APK source stamp verification succeeded instead of failing with " 1547 + expectedIssue); 1548 return; 1549 } 1550 1551 StringBuilder msg = new StringBuilder(); 1552 List<IssueWithParams> resultIssueWithParams = 1553 Stream.of(result.getErrors(), result.getWarnings()) 1554 .filter(Objects::nonNull) 1555 .flatMap(Collection::stream) 1556 .collect(Collectors.toList()); 1557 for (IssueWithParams issue : resultIssueWithParams) { 1558 if (expectedIssue.equals(issue.getIssue())) { 1559 return; 1560 } 1561 if (msg.length() > 0) { 1562 msg.append('\n'); 1563 } 1564 msg.append(issue); 1565 } 1566 1567 ApkVerifier.Result.SourceStampInfo signer = result.getSourceStampInfo(); 1568 if (signer != null) { 1569 List<IssueWithParams> sourceStampIssueWithParams = 1570 Stream.of(signer.getErrors(), signer.getWarnings()) 1571 .filter(Objects::nonNull) 1572 .flatMap(Collection::stream) 1573 .collect(Collectors.toList()); 1574 for (IssueWithParams issue : sourceStampIssueWithParams) { 1575 if (expectedIssue.equals(issue.getIssue())) { 1576 return; 1577 } 1578 if (msg.length() > 0) { 1579 msg.append('\n'); 1580 } 1581 msg.append("APK SourceStamp signer").append(": ").append(issue); 1582 } 1583 } 1584 1585 fail( 1586 "APK source stamp failed verification for the wrong reason" 1587 + ". Expected: " 1588 + expectedIssue 1589 + ", actual: " 1590 + msg); 1591 } 1592 assertSourceStampVerificationStatus(ApkVerifier.Result result, SourceStampVerificationStatus verificationStatus)1593 private static void assertSourceStampVerificationStatus(ApkVerifier.Result result, 1594 SourceStampVerificationStatus verificationStatus) throws Exception { 1595 assertEquals(verificationStatus, 1596 result.getSourceStampInfo().getSourceStampVerificationStatus()); 1597 } 1598 assertVerificationFailure( String apkFilenameInResources, ApkVerifier.Issue expectedIssue)1599 private void assertVerificationFailure( 1600 String apkFilenameInResources, ApkVerifier.Issue expectedIssue) throws Exception { 1601 assertVerificationFailure(verify(apkFilenameInResources), expectedIssue); 1602 } 1603 assertVerifiedForEach(String apkFilenamePatternInResources, String[] args)1604 private void assertVerifiedForEach(String apkFilenamePatternInResources, String[] args) 1605 throws Exception { 1606 assertVerifiedForEach(apkFilenamePatternInResources, args, null, null); 1607 } 1608 assertVerifiedForEach( String apkFilenamePatternInResources, String[] args, Integer minSdkVersionOverride, Integer maxSdkVersionOverride)1609 private void assertVerifiedForEach( 1610 String apkFilenamePatternInResources, 1611 String[] args, 1612 Integer minSdkVersionOverride, 1613 Integer maxSdkVersionOverride) 1614 throws Exception { 1615 for (String arg : args) { 1616 String apkFilenameInResources = 1617 String.format(Locale.US, apkFilenamePatternInResources, arg); 1618 assertVerified(apkFilenameInResources, minSdkVersionOverride, maxSdkVersionOverride); 1619 } 1620 } 1621 assertVerifiedForEachForMinSdkVersion( String apkFilenameInResources, String[] args, int minSdkVersion)1622 private void assertVerifiedForEachForMinSdkVersion( 1623 String apkFilenameInResources, String[] args, int minSdkVersion) throws Exception { 1624 assertVerifiedForEach(apkFilenameInResources, args, minSdkVersion, null); 1625 } 1626 sha256(byte[] msg)1627 private static byte[] sha256(byte[] msg) { 1628 try { 1629 return MessageDigest.getInstance("SHA-256").digest(msg); 1630 } catch (NoSuchAlgorithmException e) { 1631 throw new RuntimeException("Failed to create SHA-256 MessageDigest", e); 1632 } 1633 } 1634 assumeThatRsaPssAvailable()1635 private static void assumeThatRsaPssAvailable() { 1636 Assume.assumeTrue(Security.getProviders("Signature.SHA256withRSA/PSS") != null); 1637 } 1638 } 1639