aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Groover <mpgroover@google.com>2021-10-07 12:13:51 -0700
committerMichael Groover <mpgroover@google.com>2021-10-07 18:10:06 -0700
commit75eb4616f87b6180f430a2b2dca52e07747b4765 (patch)
tree730d9e79780f1529185a66701508ea745790050a
parent2eca28d0576e02369c820683cd227638b79daf8a (diff)
downloadapksig-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
-rw-r--r--src/main/java/com/android/apksig/ApkSigner.java10
-rw-r--r--src/main/java/com/android/apksig/DefaultApkSignerEngine.java16
-rw-r--r--src/main/java/com/android/apksig/internal/util/AndroidSdkVersion.java3
-rw-r--r--src/test/java/com/android/apksig/ApkSignerTest.java75
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}.