diff options
author | Michael Groover <mpgroover@google.com> | 2021-10-07 12:13:51 -0700 |
---|---|---|
committer | Michael Groover <mpgroover@google.com> | 2021-10-07 18:10:06 -0700 |
commit | 75eb4616f87b6180f430a2b2dca52e07747b4765 (patch) | |
tree | 730d9e79780f1529185a66701508ea745790050a | |
parent | 2eca28d0576e02369c820683cd227638b79daf8a (diff) | |
download | apksig-75eb4616f87b6180f430a2b2dca52e07747b4765.tar.gz |
Ensure only a single signer is written to V3 block
Android T introduced SDK version targeting for rotation on T+ by
allowing a caller to specify a rotation-min-sdk-version. If this
value is less than T, then the rotated signing key should be used in
the V3.0 signing block. However, a value X > P and < T can cause
multiple signers to be written to the V3 block, one targeting P -
X-1 with the original signing key, and the other targeting X and
later with the rotated key. Since SDK version targeting has not
been thoroughly tested on previous platform releases, this commit
will set the rotation-min-sdk-version to P when a value X < T
is provided to ensure the V3 block only contains the rotated
signer. This remains consistent with the documentation and will
ensure apps that have already rotated prior to T can specify
the version on which they rotated to see the original V3 behavior.
Bug: 202424396
Test: gradlew test
Change-Id: I00dae110b8f9c552e6cd5100491b0caa5e7138f8
4 files changed, 101 insertions, 3 deletions
diff --git a/src/main/java/com/android/apksig/ApkSigner.java b/src/main/java/com/android/apksig/ApkSigner.java index 1903822..365fdbf 100644 --- a/src/main/java/com/android/apksig/ApkSigner.java +++ b/src/main/java/com/android/apksig/ApkSigner.java @@ -17,6 +17,8 @@ package com.android.apksig; import static com.android.apksig.apk.ApkUtils.SOURCE_STAMP_CERTIFICATE_HASH_ZIP_ENTRY_NAME; +import static com.android.apksig.internal.apk.v3.V3SchemeConstants.MIN_SDK_WITH_V31_SUPPORT; +import static com.android.apksig.internal.apk.v3.V3SchemeConstants.MIN_SDK_WITH_V3_SUPPORT; import com.android.apksig.apk.ApkFormatException; import com.android.apksig.apk.ApkSigningBlockNotFoundException; @@ -1329,7 +1331,13 @@ public class ApkSigner { */ public Builder setMinSdkVersionForRotation(int minSdkVersion) { checkInitializedWithoutEngine(); - mRotationMinSdkVersion = minSdkVersion; + // If the provided SDK version does not support v3.1, then use the default SDK version + // with rotation support. + if (minSdkVersion < MIN_SDK_WITH_V31_SUPPORT) { + mRotationMinSdkVersion = MIN_SDK_WITH_V3_SUPPORT; + } else { + mRotationMinSdkVersion = minSdkVersion; + } return this; } diff --git a/src/main/java/com/android/apksig/DefaultApkSignerEngine.java b/src/main/java/com/android/apksig/DefaultApkSignerEngine.java index 72aef0c..f4e7322 100644 --- a/src/main/java/com/android/apksig/DefaultApkSignerEngine.java +++ b/src/main/java/com/android/apksig/DefaultApkSignerEngine.java @@ -24,6 +24,7 @@ import static com.android.apksig.internal.apk.ApkSigningBlockUtils.VERSION_APK_S import static com.android.apksig.internal.apk.ApkSigningBlockUtils.VERSION_JAR_SIGNATURE_SCHEME; import static com.android.apksig.internal.apk.v3.V3SchemeConstants.DEV_RELEASE_ROTATION_MIN_SDK_VERSION; import static com.android.apksig.internal.apk.v3.V3SchemeConstants.MIN_SDK_WITH_V31_SUPPORT; +import static com.android.apksig.internal.apk.v3.V3SchemeConstants.MIN_SDK_WITH_V3_SUPPORT; import com.android.apksig.apk.ApkFormatException; import com.android.apksig.apk.ApkUtils; @@ -1806,6 +1807,13 @@ public class DefaultApkSignerEngine implements ApkSignerEngine { + " v3 without an accompanying SigningCertificateLineage"); } + if (mRotationMinSdkVersion == MIN_SDK_WITH_V31_SUPPORT) { + // To ensure the APK will install on the currently released platform with the + // original signing key, also set the rotation to target a dev release to ensure + // the original signing key block targets up through 31. + mRotationTargetsDevRelease = true; + } + return new DefaultApkSignerEngine( mSignerConfigs, mStampSignerConfig, @@ -1950,7 +1958,13 @@ public class DefaultApkSignerEngine implements ApkSignerEngine { * in the original V3 signing block being used without platform targeting. */ public Builder setMinSdkVersionForRotation(int minSdkVersion) { - mRotationMinSdkVersion = minSdkVersion; + // If the provided SDK version does not support v3.1, then use the default SDK version + // with rotation support. + if (minSdkVersion < MIN_SDK_WITH_V31_SUPPORT) { + mRotationMinSdkVersion = MIN_SDK_WITH_V3_SUPPORT; + } else { + mRotationMinSdkVersion = minSdkVersion; + } return this; } diff --git a/src/main/java/com/android/apksig/internal/util/AndroidSdkVersion.java b/src/main/java/com/android/apksig/internal/util/AndroidSdkVersion.java index a8166e6..bbead72 100644 --- a/src/main/java/com/android/apksig/internal/util/AndroidSdkVersion.java +++ b/src/main/java/com/android/apksig/internal/util/AndroidSdkVersion.java @@ -54,6 +54,9 @@ public abstract class AndroidSdkVersion { /** Android P. */ public static final int P = 28; + /** Android Q. */ + public static final int Q = 29; + /** Android R. */ public static final int R = 30; diff --git a/src/test/java/com/android/apksig/ApkSignerTest.java b/src/test/java/com/android/apksig/ApkSignerTest.java index fb840a9..30c5766 100644 --- a/src/test/java/com/android/apksig/ApkSignerTest.java +++ b/src/test/java/com/android/apksig/ApkSignerTest.java @@ -1533,14 +1533,19 @@ public class ApkSignerTest { .setV4SigningEnabled(false) .setMinSdkVersionForRotation(AndroidSdkVersion.S) .setSigningCertificateLineage(lineage)); - ApkVerifier.Result resultMinRotationS = verify(signedApkMinRotationP, null); + ApkVerifier.Result resultMinRotationS = verify(signedApkMinRotationS, null); assertVerified(resultMinRotationP); assertFalse(resultMinRotationP.isVerifiedUsingV31Scheme()); + assertEquals(1, resultMinRotationP.getV3SchemeSigners().size()); assertResultContainsSigners(resultMinRotationP, true, FIRST_RSA_2048_SIGNER_RESOURCE_NAME, SECOND_RSA_2048_SIGNER_RESOURCE_NAME); assertVerified(resultMinRotationS); assertFalse(resultMinRotationS.isVerifiedUsingV31Scheme()); + // While rotation is targeting S, signer blocks targeting specific SDK versions have not + // been tested in previous platform releases; ensure only a single signer block with the + // rotated key is in the V3 block. + assertEquals(1, resultMinRotationS.getV3SchemeSigners().size()); assertResultContainsSigners(resultMinRotationS, true, FIRST_RSA_2048_SIGNER_RESOURCE_NAME, SECOND_RSA_2048_SIGNER_RESOURCE_NAME); } @@ -1729,6 +1734,74 @@ public class ApkSignerTest { assertTrue(result.getV3SchemeSigners().get(0).getMaxSdkVersion() >= rotationMinSdkVersion); } + @Test + public void testV3_rotationMinSdkVersionLessThanTV3Only_origSignerNotRequired() + throws Exception { + // The v3.1 signature scheme allows a rotation-min-sdk-version be specified to target T+ + // for rotation; however if this value is less than the expected SDK version of T, then + // apksig should just use the rotated signing key in the v3.0 block. An APK that targets + // P+ that wants to use rotation in the v3.0 signing block should only need to provide + // the rotated signing key and lineage; this test ensures this behavior when the + // rotation-min-sdk-version is set to a value > P and < T. + List<ApkSigner.SignerConfig> rsa2048SignerConfigWithLineage = + Arrays.asList( + getDefaultSignerConfigFromResources(SECOND_RSA_2048_SIGNER_RESOURCE_NAME)); + SigningCertificateLineage lineage = + Resources.toSigningCertificateLineage( + ApkSignerTest.class, LINEAGE_RSA_2048_2_SIGNERS_RESOURCE_NAME); + + File signedApkRotationOnQ = sign("original.apk", + new ApkSigner.Builder(rsa2048SignerConfigWithLineage) + .setV1SigningEnabled(false) + .setV2SigningEnabled(false) + .setV3SigningEnabled(true) + .setV4SigningEnabled(false) + .setMinSdkVersion(AndroidSdkVersion.P) + .setMinSdkVersionForRotation(AndroidSdkVersion.Q) + .setSigningCertificateLineage(lineage)); + ApkVerifier.Result resultRotationOnQ = verify(signedApkRotationOnQ, AndroidSdkVersion.P); + + assertVerified(resultRotationOnQ); + assertEquals(1, resultRotationOnQ.getV3SchemeSigners().size()); + assertFalse(resultRotationOnQ.isVerifiedUsingV31Scheme()); + assertResultContainsSigners(resultRotationOnQ, true, FIRST_RSA_2048_SIGNER_RESOURCE_NAME, + SECOND_RSA_2048_SIGNER_RESOURCE_NAME); + } + + @Test + public void testV31_rotationMinSdkVersionT_v30SignerTargetsAtLeast31() throws Exception { + // The T development release is currently using the API level of S until its own SDK is + // finalized. This requires apksig to sign an APK targeting T for rotation with a V3.1 + // block that targets API level 31. By default, apksig will decrement the SDK version for + // the current signer block and use that as the maxSdkVersion for the next signer; however + // this means the original signing key will only target through 30 which would prevent + // an APK signed with V3.1 targeting T from installing on a device running S. This test + // ensures targeting T will use the rotation-targets-dev-release option so that the APK + // can still install on devices with an API level of 31. + List<ApkSigner.SignerConfig> rsa2048SignerConfigWithLineage = + Arrays.asList( + getDefaultSignerConfigFromResources(FIRST_RSA_2048_SIGNER_RESOURCE_NAME), + getDefaultSignerConfigFromResources(SECOND_RSA_2048_SIGNER_RESOURCE_NAME)); + SigningCertificateLineage lineage = + Resources.toSigningCertificateLineage( + ApkSignerTest.class, LINEAGE_RSA_2048_2_SIGNERS_RESOURCE_NAME); + + File signedApk = sign("original.apk", + new ApkSigner.Builder(rsa2048SignerConfigWithLineage) + .setV1SigningEnabled(true) + .setV2SigningEnabled(true) + .setV3SigningEnabled(true) + .setV4SigningEnabled(false) + .setMinSdkVersionForRotation(V3SchemeConstants.MIN_SDK_WITH_V31_SUPPORT) + .setSigningCertificateLineage(lineage)); + ApkVerifier.Result result = verify(signedApk, null); + + assertVerified(result); + assertTrue(result.isVerifiedUsingV31Scheme()); + assertTrue(result.getV31SchemeSigners().get(0).getRotationTargetsDevRelease()); + assertTrue(result.getV3SchemeSigners().get(0).getMaxSdkVersion() >= AndroidSdkVersion.S); + } + /** * Asserts the provided {@code signedApk} contains a signature block with the expected * {@code byte[]} value and block ID as specified in the {@code expectedBlock}. |