aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Groover <mpgroover@google.com>2021-09-13 17:33:41 -0700
committerMichael Groover <mpgroover@google.com>2021-09-13 19:36:46 -0700
commitbef2413c90a3543ef4864cdbb4e2674cb969a8fc (patch)
treee0dfe9a3facc04e869012f26d2151f4ad79f7f5e
parentdf09714847e1ce923b121deed11b1ea537cef241 (diff)
downloadapksig-bef2413c90a3543ef4864cdbb4e2674cb969a8fc.tar.gz
Add support to v3.1 sig scheme to target dev release
When a new release is under development, its SDK version is set to the SDK version of the previous release; during the majority of the T development, the SDK version of a device running T will be S (31). Since S- devices do not know about the v3.1 block ID, it is safe to set the minSdkVersion of a v3.1 signer using a rotated key to 31; T devices will recognize the new signature scheme and use the rotated signer, but if the same APK were installed on an S device the v3.1 block would be ignored, and the original signer would be used. However once T is released and U is using the SDK version of T, if rotation needs to target U, just specifying the SDK version of T will not be sufficient since this could then install on T and U devices. The new rotation-targets-dev-release flag will allow a v3.1 signer to target the active release under development; if this new flag is set then the minSdkVersion of the v3.1 signer must be within range of the device's SDK version and the system property ro.build.version.codename must not be set to "REL". Bug: 192301300 Test: gradlew test Change-Id: Id6ca0b6d5db575f301cf715ebaceadfd1df19de0
-rw-r--r--src/apksigner/java/com/android/apksigner/ApkSignerTool.java15
-rw-r--r--src/main/java/com/android/apksig/ApkSigner.java32
-rw-r--r--src/main/java/com/android/apksig/ApkVerifier.java27
-rw-r--r--src/main/java/com/android/apksig/DefaultApkSignerEngine.java66
-rw-r--r--src/main/java/com/android/apksig/internal/apk/v3/V3SchemeConstants.java16
-rw-r--r--src/main/java/com/android/apksig/internal/apk/v3/V3SchemeSigner.java57
-rw-r--r--src/main/java/com/android/apksig/internal/apk/v3/V3SchemeVerifier.java7
-rw-r--r--src/test/java/com/android/apksig/ApkSignerTest.java52
-rw-r--r--src/test/java/com/android/apksig/ApkVerifierTest.java29
-rw-r--r--src/test/resources/com/android/apksig/v3-rsa-2048_2-tgt-dev-release.apkbin0 -> 16791 bytes
-rw-r--r--src/test/resources/com/android/apksig/v31-rsa-2048_2-tgt-34-dev-release.apkbin0 -> 16791 bytes
11 files changed, 283 insertions, 18 deletions
diff --git a/src/apksigner/java/com/android/apksigner/ApkSignerTool.java b/src/apksigner/java/com/android/apksigner/ApkSignerTool.java
index 5a9daf6..4a303b6 100644
--- a/src/apksigner/java/com/android/apksigner/ApkSignerTool.java
+++ b/src/apksigner/java/com/android/apksigner/ApkSignerTool.java
@@ -149,6 +149,7 @@ public class ApkSignerTool {
boolean minSdkVersionSpecified = false;
int maxSdkVersion = Integer.MAX_VALUE;
int rotationMinSdkVersion = V3SchemeConstants.DEFAULT_ROTATION_MIN_SDK_VERSION;
+ boolean rotationTargetsDevRelease = false;
List<SignerParams> signers = new ArrayList<>(1);
SignerParams signerParams = new SignerParams();
SigningCertificateLineage lineage = null;
@@ -180,7 +181,10 @@ public class ApkSignerTool {
} else if ("rotation-min-sdk-version".equals(optionName)) {
rotationMinSdkVersion = optionsParser.getRequiredIntValue(
"Minimum API Level for Rotation");
- } else if ("v1-signing-enabled".equals(optionName)) {
+ } else if ("rotation-targets-dev-release".equals(optionName)) {
+ rotationTargetsDevRelease = optionsParser.getOptionalBooleanValue(true);
+ }
+ else if ("v1-signing-enabled".equals(optionName)) {
v1SigningEnabled = optionsParser.getOptionalBooleanValue(true);
} else if ("v2-signing-enabled".equals(optionName)) {
v2SigningEnabled = optionsParser.getOptionalBooleanValue(true);
@@ -368,7 +372,8 @@ public class ApkSignerTool {
.setV4ErrorReportingEnabled(v4SigningEnabled && v4SigningFlagFound)
.setDebuggableApkPermitted(debuggableApkPermitted)
.setSigningCertificateLineage(lineage)
- .setMinSdkVersionForRotation(rotationMinSdkVersion);
+ .setMinSdkVersionForRotation(rotationMinSdkVersion)
+ .setRotationTargetsDevRelease(rotationTargetsDevRelease);
if (minSdkVersionSpecified) {
apkSignerBuilder.setMinSdkVersion(minSdkVersion);
}
@@ -589,9 +594,13 @@ public class ApkSignerTool {
// signing key can still be used with v3.0; if a v3.1 block is present then also
// include the target SDK versions for both rotation and the original signing key.
if (result.isVerifiedUsingV31Scheme()) {
- for (ApkVerifier.Result.V3SchemeSignerInfo signer : result.getV31SchemeSigners()) {
+ for (ApkVerifier.Result.V3SchemeSignerInfo signer :
+ result.getV31SchemeSigners()) {
+
printCertificate(signer.getCertificate(),
"Signer (minSdkVersion=" + signer.getMinSdkVersion()
+ + (signer.getRotationTargetsDevRelease()
+ ? " (dev release=true)" : "")
+ ", maxSdkVersion=" + signer.getMaxSdkVersion() + ")",
verbose);
}
diff --git a/src/main/java/com/android/apksig/ApkSigner.java b/src/main/java/com/android/apksig/ApkSigner.java
index 917d283..1903822 100644
--- a/src/main/java/com/android/apksig/ApkSigner.java
+++ b/src/main/java/com/android/apksig/ApkSigner.java
@@ -91,6 +91,7 @@ public class ApkSigner {
private final boolean mForceSourceStampOverwrite;
private final Integer mMinSdkVersion;
private final int mRotationMinSdkVersion;
+ private final boolean mRotationTargetsDevRelease;
private final boolean mV1SigningEnabled;
private final boolean mV2SigningEnabled;
private final boolean mV3SigningEnabled;
@@ -121,6 +122,7 @@ public class ApkSigner {
boolean forceSourceStampOverwrite,
Integer minSdkVersion,
int rotationMinSdkVersion,
+ boolean rotationTargetsDevRelease,
boolean v1SigningEnabled,
boolean v2SigningEnabled,
boolean v3SigningEnabled,
@@ -145,6 +147,7 @@ public class ApkSigner {
mForceSourceStampOverwrite = forceSourceStampOverwrite;
mMinSdkVersion = minSdkVersion;
mRotationMinSdkVersion = rotationMinSdkVersion;
+ mRotationTargetsDevRelease = rotationTargetsDevRelease;
mV1SigningEnabled = v1SigningEnabled;
mV2SigningEnabled = v2SigningEnabled;
mV3SigningEnabled = v3SigningEnabled;
@@ -301,7 +304,8 @@ public class ApkSigner {
.setDebuggableApkPermitted(mDebuggableApkPermitted)
.setOtherSignersSignaturesPreserved(mOtherSignersSignaturesPreserved)
.setSigningCertificateLineage(mSigningCertificateLineage)
- .setMinSdkVersionForRotation(mRotationMinSdkVersion);
+ .setMinSdkVersionForRotation(mRotationMinSdkVersion)
+ .setRotationTargetsDevRelease(mRotationTargetsDevRelease);
if (mCreatedBy != null) {
signerEngineBuilder.setCreatedBy(mCreatedBy);
}
@@ -1099,6 +1103,7 @@ public class ApkSigner {
private String mCreatedBy;
private Integer mMinSdkVersion;
private int mRotationMinSdkVersion = V3SchemeConstants.DEFAULT_ROTATION_MIN_SDK_VERSION;
+ private boolean mRotationTargetsDevRelease = false;
private final ApkSignerEngine mSignerEngine;
@@ -1329,6 +1334,30 @@ public class ApkSigner {
}
/**
+ * Sets whether the rotation-min-sdk-version is intended to target a development release;
+ * this is primarily required after the T SDK is finalized, and an APK needs to target U
+ * during its development cycle for rotation.
+ *
+ * <p>This is only required after the T SDK is finalized since S and earlier releases do
+ * not know about the V3.1 block ID, but once T is released and work begins on U, U will
+ * use the SDK version of T during development. Specifying a rotation-min-sdk-version of T's
+ * SDK version along with setting {@code enabled} to true will allow an APK to use the
+ * rotated key on a device running U while causing this to be bypassed for T.
+ *
+ * <p><em>Note:</em>If the rotation-min-sdk-version is less than or equal to 32 (Android
+ * Sv2), then the rotated signing key will be used in the v3.0 signing block and this call
+ * will be a noop.
+ *
+ * <p><em>Note:</em> This method may only be invoked when this builder is not initialized
+ * with an {@link ApkSignerEngine}.
+ */
+ public Builder setRotationTargetsDevRelease(boolean enabled) {
+ checkInitializedWithoutEngine();
+ mRotationTargetsDevRelease = enabled;
+ return this;
+ }
+
+ /**
* Sets whether the APK should be signed using JAR signing (aka v1 signature scheme).
*
* <p>By default, whether APK is signed using JAR signing is determined by {@code
@@ -1567,6 +1596,7 @@ public class ApkSigner {
mForceSourceStampOverwrite,
mMinSdkVersion,
mRotationMinSdkVersion,
+ mRotationTargetsDevRelease,
mV1SigningEnabled,
mV2SigningEnabled,
mV3SigningEnabled,
diff --git a/src/main/java/com/android/apksig/ApkVerifier.java b/src/main/java/com/android/apksig/ApkVerifier.java
index 6bdec91..d1014a3 100644
--- a/src/main/java/com/android/apksig/ApkVerifier.java
+++ b/src/main/java/com/android/apksig/ApkVerifier.java
@@ -1563,6 +1563,7 @@ public class ApkVerifier {
mContentDigests;
private final int mMinSdkVersion;
private final int mMaxSdkVersion;
+ private final boolean mRotationTargetsDevRelease;
private V3SchemeSignerInfo(ApkSigningBlockUtils.Result.SignerInfo result) {
mIndex = result.index;
@@ -1572,6 +1573,9 @@ public class ApkVerifier {
mContentDigests = result.contentDigests;
mMinSdkVersion = result.minSdkVersion;
mMaxSdkVersion = result.maxSdkVersion;
+ mRotationTargetsDevRelease = result.additionalAttributes.stream().mapToInt(
+ attribute -> attribute.getId()).anyMatch(
+ attrId -> attrId == V3SchemeConstants.ROTATION_ON_DEV_RELEASE_ATTR_ID);
}
/**
@@ -1631,6 +1635,19 @@ public class ApkVerifier {
public int getMaxSdkVersion() {
return mMaxSdkVersion;
}
+
+ /**
+ * Returns whether rotation is targeting a development release.
+ *
+ * <p>A development release uses the SDK version of the previously released platform
+ * until the SDK of the development release is finalized. To allow rotation to target
+ * a development release after T, this attribute must be set to ensure rotation is
+ * used on the development release but ignored on the released platform with the same
+ * API level.
+ */
+ public boolean getRotationTargetsDevRelease() {
+ return mRotationTargetsDevRelease;
+ }
}
/**
@@ -2654,6 +2671,16 @@ public class ApkVerifier {
"The APK contains a v3.1 signing block without a v3.0 base block"),
/**
+ * The APK contains a v3.0 signing block with a rotation-targets-dev-release attribute in
+ * the signer; this attribute is only intended for v3.1 signers to indicate they should be
+ * targeting the next development release that is using the SDK version of the previously
+ * released platform SDK version.
+ */
+ V31_ROTATION_TARGETS_DEV_RELEASE_ATTR_ON_V3_SIGNER(
+ "The rotation-targets-dev-release attribute is only supported on v3.1 signers; "
+ + "this attribute will be ignored by the platform in a v3.0 signer"),
+
+ /**
* APK Signing Block contains an unknown entry.
*
* <ul>
diff --git a/src/main/java/com/android/apksig/DefaultApkSignerEngine.java b/src/main/java/com/android/apksig/DefaultApkSignerEngine.java
index c57ca39..72aef0c 100644
--- a/src/main/java/com/android/apksig/DefaultApkSignerEngine.java
+++ b/src/main/java/com/android/apksig/DefaultApkSignerEngine.java
@@ -22,6 +22,7 @@ import static com.android.apksig.internal.apk.ApkSigningBlockUtils.VERITY_PADDIN
import static com.android.apksig.internal.apk.ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V2;
import static com.android.apksig.internal.apk.ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V3;
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 com.android.apksig.apk.ApkFormatException;
@@ -106,6 +107,7 @@ public class DefaultApkSignerEngine implements ApkSignerEngine {
private final SignerConfig mSourceStampSignerConfig;
private final SigningCertificateLineage mSourceStampSigningCertificateLineage;
private final int mRotationMinSdkVersion;
+ private final boolean mRotationTargetsDevRelease;
private final int mMinSdkVersion;
private final SigningCertificateLineage mSigningCertificateLineage;
@@ -189,6 +191,7 @@ public class DefaultApkSignerEngine implements ApkSignerEngine {
SigningCertificateLineage sourceStampSigningCertificateLineage,
int minSdkVersion,
int rotationMinSdkVersion,
+ boolean rotationTargetsDevRelease,
boolean v1SigningEnabled,
boolean v2SigningEnabled,
boolean v3SigningEnabled,
@@ -217,6 +220,7 @@ public class DefaultApkSignerEngine implements ApkSignerEngine {
mSourceStampSigningCertificateLineage = sourceStampSigningCertificateLineage;
mMinSdkVersion = minSdkVersion;
mRotationMinSdkVersion = rotationMinSdkVersion;
+ mRotationTargetsDevRelease = rotationTargetsDevRelease;
mSigningCertificateLineage = signingCertificateLineage;
if (v1SigningEnabled) {
@@ -338,6 +342,14 @@ public class DefaultApkSignerEngine implements ApkSignerEngine {
List<ApkSigningBlockUtils.SignerConfig> rawConfigs) throws InvalidKeyException {
List<ApkSigningBlockUtils.SignerConfig> processedConfigs = new ArrayList<>();
+ // TODO (b/199793805): Once the T SDK is finalized and T development releases are using
+ // the new SDK version, this should be removed and mRotationMinSdkVersion should be used
+ // as is for rotation SDK version targeting.
+ // To support targeting the development release use the API level of the previous
+ // platform release as this is the value returned from Build.Version.SDK_INT until
+ // the SDK is finalized.
+ int rotationMinSdkVersion = mRotationMinSdkVersion == MIN_SDK_WITH_V31_SUPPORT
+ ? DEV_RELEASE_ROTATION_MIN_SDK_VERSION : mRotationMinSdkVersion;
// we have our configs, now touch them up to appropriately cover all SDK levels since APK
// signature scheme v3 was introduced
int currentMinSdk = Integer.MAX_VALUE;
@@ -359,19 +371,29 @@ public class DefaultApkSignerEngine implements ApkSignerEngine {
// this needs to change
config.maxSdkVersion = Integer.MAX_VALUE;
} else {
- // otherwise, we only want to use this signer up to the minimum platform version
- // on which a newer one is acceptable
- config.maxSdkVersion = currentMinSdk - 1;
+ if (mRotationTargetsDevRelease && currentMinSdk == rotationMinSdkVersion) {
+ // The currentMinSdk is both the SDK version for the active development release
+ // as well as the most recent released platform. To ensure the v3.0 signer will
+ // target the released platform, overlap the maxSdkVersion for the v3.0 signer
+ // with the minSdkVersion of the rotated signer in the v3.1 block
+ config.maxSdkVersion = currentMinSdk;
+ } else {
+ // otherwise, we only want to use this signer up to the minimum platform version
+ // on which a newer one is acceptable
+ config.maxSdkVersion = currentMinSdk - 1;
+ }
}
config.minSdkVersion = getMinSdkFromV3SignatureAlgorithms(config.signatureAlgorithms);
// Only use a rotated key and signing lineage if the config's max SDK version is greater
// than that requested to support rotation.
if (mSigningCertificateLineage != null
- && config.maxSdkVersion >= mRotationMinSdkVersion) {
+ && ((mRotationTargetsDevRelease
+ ? config.maxSdkVersion > rotationMinSdkVersion
+ : config.maxSdkVersion >= rotationMinSdkVersion))) {
config.mSigningCertificateLineage =
mSigningCertificateLineage.getSubLineage(config.certificates.get(0));
- if (config.minSdkVersion < mRotationMinSdkVersion) {
- config.minSdkVersion = mRotationMinSdkVersion;
+ if (config.minSdkVersion < rotationMinSdkVersion) {
+ config.minSdkVersion = rotationMinSdkVersion;
}
}
// we know that this config will be used, so add it to our result, order doesn't matter
@@ -1028,6 +1050,11 @@ public class DefaultApkSignerEngine implements ApkSignerEngine {
// should be signed with both the v3.1 signing scheme with the rotated key, and the v3.0
// scheme with the original signing key. If the APK's minSdkVersion is >= the requested
// SDK version for rotation then just use the v3.0 signing block for this.
+ // TODO (b/199793805): Once the T SDK is finalized use the mRotationMinSdkVersion as
+ // is; this is required during T development since the SDK version of devices running
+ // T is the SDK version of the previous release.
+ int rotationMinSdkVersion = mRotationMinSdkVersion == MIN_SDK_WITH_V31_SUPPORT
+ ? DEV_RELEASE_ROTATION_MIN_SDK_VERSION : mRotationMinSdkVersion;
if (mSigningCertificateLineage != null
&& mRotationMinSdkVersion >= MIN_SDK_WITH_V31_SUPPORT
&& mMinSdkVersion < mRotationMinSdkVersion) {
@@ -1038,7 +1065,7 @@ public class DefaultApkSignerEngine implements ApkSignerEngine {
ApkSigningBlockUtils.SignerConfig signerConfig = v3SignerIterator.next();
// All signing configs with a min SDK version that supports v3.1 should be used
// in the v3.1 signing block and removed from the v3.0 block.
- if (signerConfig.minSdkVersion >= MIN_SDK_WITH_V31_SUPPORT) {
+ if (signerConfig.minSdkVersion >= rotationMinSdkVersion) {
v31SignerConfigs.add(signerConfig);
v3SignerIterator.remove();
}
@@ -1049,6 +1076,7 @@ public class DefaultApkSignerEngine implements ApkSignerEngine {
v31SignerConfigs)
.setRunnablesExecutor(mExecutor)
.setBlockId(V3SchemeConstants.APK_SIGNATURE_SCHEME_V31_BLOCK_ID)
+ .setRotationTargetsDevRelease(mRotationTargetsDevRelease)
.build()
.generateApkSignatureSchemeV3BlockAndDigests();
signingSchemeBlocks.add(v31SigningSchemeBlockAndDigests.signingSchemeBlock);
@@ -1059,7 +1087,7 @@ public class DefaultApkSignerEngine implements ApkSignerEngine {
.setBlockId(V3SchemeConstants.APK_SIGNATURE_SCHEME_V3_BLOCK_ID);
if (mRotationMinSdkVersion >= MIN_SDK_WITH_V31_SUPPORT
&& mMinSdkVersion < mRotationMinSdkVersion) {
- builder.setRotationMinSdkVersion(mRotationMinSdkVersion);
+ builder.setRotationMinSdkVersion(rotationMinSdkVersion);
}
v3SigningSchemeBlockAndDigests =
builder.build().generateApkSignatureSchemeV3BlockAndDigests();
@@ -1694,6 +1722,7 @@ public class DefaultApkSignerEngine implements ApkSignerEngine {
private boolean mV2SigningEnabled = true;
private boolean mV3SigningEnabled = true;
private int mRotationMinSdkVersion = V3SchemeConstants.DEFAULT_ROTATION_MIN_SDK_VERSION;
+ private boolean mRotationTargetsDevRelease = false;
private boolean mVerityEnabled = false;
private boolean mDebuggableApkPermitted = true;
private boolean mOtherSignersSignaturesPreserved;
@@ -1783,6 +1812,7 @@ public class DefaultApkSignerEngine implements ApkSignerEngine {
mSourceStampSigningCertificateLineage,
mMinSdkVersion,
mRotationMinSdkVersion,
+ mRotationTargetsDevRelease,
mV1SigningEnabled,
mV2SigningEnabled,
mV3SigningEnabled,
@@ -1923,5 +1953,25 @@ public class DefaultApkSignerEngine implements ApkSignerEngine {
mRotationMinSdkVersion = minSdkVersion;
return this;
}
+
+ /**
+ * Sets whether the rotation-min-sdk-version is intended to target a development release;
+ * this is primarily required after the T SDK is finalized, and an APK needs to target U
+ * during its development cycle for rotation.
+ *
+ * <p>This is only required after the T SDK is finalized since S and earlier releases do
+ * not know about the V3.1 block ID, but once T is released and work begins on U, U will
+ * use the SDK version of T during development. Specifying a rotation-min-sdk-version of T's
+ * SDK version along with setting {@code enabled} to true will allow an APK to use the
+ * rotated key on a device running U while causing this to be bypassed for T.
+ *
+ * <p><em>Note:</em>If the rotation-min-sdk-version is less than or equal to 32 (Android
+ * Sv2), then the rotated signing key will be used in the v3.0 signing block and this call
+ * will be a noop.
+ */
+ public Builder setRotationTargetsDevRelease(boolean enabled) {
+ mRotationTargetsDevRelease = enabled;
+ return this;
+ }
}
}
diff --git a/src/main/java/com/android/apksig/internal/apk/v3/V3SchemeConstants.java b/src/main/java/com/android/apksig/internal/apk/v3/V3SchemeConstants.java
index ac7c80f..33d3fe4 100644
--- a/src/main/java/com/android/apksig/internal/apk/v3/V3SchemeConstants.java
+++ b/src/main/java/com/android/apksig/internal/apk/v3/V3SchemeConstants.java
@@ -31,6 +31,13 @@ public class V3SchemeConstants {
// TODO(b/192301300): Once the signing config has been updated to support specifying a
// minSdkVersion for rotation this should be updated to T.
public static final int DEFAULT_ROTATION_MIN_SDK_VERSION = AndroidSdkVersion.P;
+ /**
+ * The v3.1 signature scheme is initially intended for the T development release, but until
+ * the T SDK is finalized it is using the SDK version of the latest platform release. To support
+ * testing of the v3.1 signature scheme and key rotation on the T development release, the
+ * rotation-min-sdk-version should use the SDK version of S in the v3.1 signer block.
+ */
+ public static final int DEV_RELEASE_ROTATION_MIN_SDK_VERSION = AndroidSdkVersion.S;
/**
* This attribute is intended to be written to the V3.0 signer block as an additional attribute
@@ -39,4 +46,13 @@ public class V3SchemeConstants {
* for rotation in the v3.1 signing block is not X, then the APK should be rejected.
*/
public static final int ROTATION_MIN_SDK_VERSION_ATTR_ID = 0x559f8b02;
+
+ /**
+ * This attribute is written to the V3.1 signer block as an additional attribute to signify that
+ * the rotation-min-sdk-version is targeting a development release. This is required to support
+ * testing rotation on new development releases as the previous platform release SDK version
+ * is used as the development release SDK version until the development release SDK is
+ * finalized.
+ */
+ public static final int ROTATION_ON_DEV_RELEASE_ATTR_ID = 0xc2a6b3ba;
}
diff --git a/src/main/java/com/android/apksig/internal/apk/v3/V3SchemeSigner.java b/src/main/java/com/android/apksig/internal/apk/v3/V3SchemeSigner.java
index 5cb2ba1..ee5d3b4 100644
--- a/src/main/java/com/android/apksig/internal/apk/v3/V3SchemeSigner.java
+++ b/src/main/java/com/android/apksig/internal/apk/v3/V3SchemeSigner.java
@@ -31,6 +31,7 @@ import com.android.apksig.internal.apk.SignatureAlgorithm;
import com.android.apksig.internal.util.Pair;
import com.android.apksig.util.DataSource;
import com.android.apksig.util.RunnablesExecutor;
+
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
@@ -70,6 +71,7 @@ public class V3SchemeSigner {
private final List<SignerConfig> mSignerConfigs;
private final int mBlockId;
private final OptionalInt mOptionalRotationMinSdkVersion;
+ private final boolean mRotationTargetsDevRelease;
private V3SchemeSigner(DataSource beforeCentralDir,
DataSource centralDir,
@@ -77,7 +79,8 @@ public class V3SchemeSigner {
List<SignerConfig> signerConfigs,
RunnablesExecutor executor,
int blockId,
- OptionalInt optionalRotationMinSdkVersion) {
+ OptionalInt optionalRotationMinSdkVersion,
+ boolean rotationTargetsDevRelease) {
mBeforeCentralDir = beforeCentralDir;
mCentralDir = centralDir;
mEocd = eocd;
@@ -85,6 +88,7 @@ public class V3SchemeSigner {
mExecutor = executor;
mBlockId = blockId;
mOptionalRotationMinSdkVersion = optionalRotationMinSdkVersion;
+ mRotationTargetsDevRelease = rotationTargetsDevRelease;
}
/**
@@ -197,6 +201,19 @@ public class V3SchemeSigner {
return result.array();
}
+ private static byte[] generateV31RotationTargetsDevReleaseAttribute() {
+ // FORMAT (little endian):
+ // * length-prefixed bytes: attribute pair
+ // * uint32: ID
+ // * bytes: value - No value is used for this attribute
+ int payloadSize = 4 + 4;
+ ByteBuffer result = ByteBuffer.allocate(payloadSize);
+ result.order(ByteOrder.LITTLE_ENDIAN);
+ result.putInt(payloadSize - 4);
+ result.putInt(V3SchemeConstants.ROTATION_ON_DEV_RELEASE_ATTR_ID);
+ return result.array();
+ }
+
/**
* Generates and returns a new {@link SigningSchemeBlockAndDigests} containing the V3.x
* signing scheme block and digests based on the parameters provided to the {@link Builder}.
@@ -362,7 +379,19 @@ public class V3SchemeSigner {
private byte[] generateAdditionalAttributes(SignerConfig signerConfig) {
if (signerConfig.mSigningCertificateLineage != null) {
- return generateV3SignerAttribute(signerConfig.mSigningCertificateLineage);
+ byte[] lineageAttr = generateV3SignerAttribute(signerConfig.mSigningCertificateLineage);
+ // If this rotation is not targeting a development release, or if this is not a v3.1
+ // signer block then just return the lineage attribute.
+ if (!mRotationTargetsDevRelease
+ || mBlockId != V3SchemeConstants.APK_SIGNATURE_SCHEME_V31_BLOCK_ID) {
+ return lineageAttr;
+ }
+ byte[] devReleaseRotationAttr = generateV31RotationTargetsDevReleaseAttribute();
+ byte[] attributes = new byte[lineageAttr.length + devReleaseRotationAttr.length];
+ System.arraycopy(lineageAttr, 0, attributes, 0, lineageAttr.length);
+ System.arraycopy(devReleaseRotationAttr, 0, attributes, lineageAttr.length,
+ devReleaseRotationAttr.length);
+ return attributes;
} else if (mOptionalRotationMinSdkVersion.isPresent()) {
return generateV3RotationMinSdkVersionStrippingProtectionAttribute(
mOptionalRotationMinSdkVersion.getAsInt());
@@ -398,6 +427,7 @@ public class V3SchemeSigner {
private RunnablesExecutor mExecutor = RunnablesExecutor.MULTI_THREADED;
private int mBlockId = V3SchemeConstants.APK_SIGNATURE_SCHEME_V3_BLOCK_ID;
private OptionalInt mOptionalRotationMinSdkVersion = OptionalInt.empty();
+ private boolean mRotationTargetsDevRelease = false;
/**
* Instantiates a new {@code Builder} with an APK's {@code beforeCentralDir}, {@code
@@ -445,6 +475,26 @@ public class V3SchemeSigner {
}
/**
+ * Sets whether the minimum SDK version of a signer is intended to target a development
+ * release; this is primarily required after the T SDK is finalized, and an APK needs to
+ * target U during its development cycle for rotation.
+ *
+ * <p>This is only required after the T SDK is finalized since S and earlier releases do
+ * not know about the V3.1 block ID, but once T is released and work begins on U, U will
+ * use the SDK version of T during development. A signer with a minimum SDK version of T's
+ * SDK version along with setting {@code enabled} to true will allow an APK to use the
+ * rotated key on a device running U while causing this to be bypassed for T.
+ *
+ * <p><em>Note:</em>If the rotation-min-sdk-version is less than or equal to 32 (Android
+ * Sv2), then the rotated signing key will be used in the v3.0 signing block and this call
+ * will be a noop.
+ */
+ public Builder setRotationTargetsDevRelease(boolean enabled) {
+ mRotationTargetsDevRelease = enabled;
+ return this;
+ }
+
+ /**
* Returns a new {@link V3SchemeSigner} built with the configuration provided to this
* {@code Builder}.
*/
@@ -455,7 +505,8 @@ public class V3SchemeSigner {
mSignerConfigs,
mExecutor,
mBlockId,
- mOptionalRotationMinSdkVersion);
+ mOptionalRotationMinSdkVersion,
+ mRotationTargetsDevRelease);
}
}
}
diff --git a/src/main/java/com/android/apksig/internal/apk/v3/V3SchemeVerifier.java b/src/main/java/com/android/apksig/internal/apk/v3/V3SchemeVerifier.java
index 9b9abba..956027f 100644
--- a/src/main/java/com/android/apksig/internal/apk/v3/V3SchemeVerifier.java
+++ b/src/main/java/com/android/apksig/internal/apk/v3/V3SchemeVerifier.java
@@ -584,6 +584,13 @@ public class V3SchemeVerifier {
result.addError(Issue.V31_BLOCK_MISSING, attrRotationMinSdkVersion);
}
}
+ } else if (id == V3SchemeConstants.ROTATION_ON_DEV_RELEASE_ATTR_ID) {
+ // This attribute should only be used by a v3.1 signer to indicate rotation
+ // is targeting the development release that is using the SDK version of the
+ // previously released platform version.
+ if (mBlockId != V3SchemeConstants.APK_SIGNATURE_SCHEME_V31_BLOCK_ID) {
+ result.addWarning(Issue.V31_ROTATION_TARGETS_DEV_RELEASE_ATTR_ON_V3_SIGNER);
+ }
} else {
result.addWarning(Issue.V3_SIG_UNKNOWN_ADDITIONAL_ATTRIBUTE, id);
}
diff --git a/src/test/java/com/android/apksig/ApkSignerTest.java b/src/test/java/com/android/apksig/ApkSignerTest.java
index 436c8dd..fb840a9 100644
--- a/src/test/java/com/android/apksig/ApkSignerTest.java
+++ b/src/test/java/com/android/apksig/ApkSignerTest.java
@@ -1582,8 +1582,11 @@ public class ApkSignerTest {
assertTrue(resultMinRotationT.isVerifiedUsingV31Scheme());
assertResultContainsSigners(resultMinRotationT, true, FIRST_RSA_2048_SIGNER_RESOURCE_NAME,
SECOND_RSA_2048_SIGNER_RESOURCE_NAME);
+ // Since T is still under development, it is using the SDK version of the previous platform
+ // release, so to test v3.1 on T the rotation-min-sdk-version must target the SDK version
+ // of S.
assertV31SignerTargetsMinApiLevel(resultMinRotationT, SECOND_RSA_2048_SIGNER_RESOURCE_NAME,
- AndroidSdkVersion.T);
+ V3SchemeConstants.DEV_RELEASE_ROTATION_MIN_SDK_VERSION);
assertVerified(resultMinRotationU);
assertTrue(resultMinRotationU.isVerifiedUsingV31Scheme());
assertResultContainsSigners(resultMinRotationU, true, FIRST_RSA_2048_SIGNER_RESOURCE_NAME,
@@ -1674,13 +1677,58 @@ public class ApkSignerTest {
.setSourceStampSignerConfig(rsa2048OriginalSignerConfig));
ApkVerifier.Result result = verify(signedApk, null);
+ // Since T is still under development, it is using the SDK version of the previous platform
+ // release, so to test v3.1 on T the rotation-min-sdk-version must target the SDK version
+ // of S.
assertResultContainsSigners(result, true, FIRST_RSA_2048_SIGNER_RESOURCE_NAME,
SECOND_RSA_2048_SIGNER_RESOURCE_NAME);
assertV31SignerTargetsMinApiLevel(result, SECOND_RSA_2048_SIGNER_RESOURCE_NAME,
- AndroidSdkVersion.T);
+ V3SchemeConstants.DEV_RELEASE_ROTATION_MIN_SDK_VERSION);
assertSourceStampVerified(signedApk, result);
}
+ @Test
+ public void testSetRotationTargetsDevRelease_target34_v30SignerTargetsAtLeast34()
+ throws Exception {
+ // During development of a new platform release the new platform will use the SDK version
+ // of the previously released platform, so in order to test rotation on a new platform
+ // release it must target the SDK version of the previous platform. However an APK signed
+ // with the v3.1 signature scheme and targeting rotation on the previous platform release X
+ // would still use rotation if that APK were installed on a device running release version
+ // X. To support targeting rotation on the main branch, the v3.1 signature scheme supports
+ // a rotation-targets-dev-release attribute; this allows the APK to use the v3.1 signer
+ // block on a development platform with SDK version X while a release platform X will
+ // skip this signer block when it sees this additional attribute. To ensure that the APK
+ // will still target the released platform X, the v3.0 signer must have a maxSdkVersion
+ // of at least X for the signer.
+ 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);
+ int rotationMinSdkVersion = V3SchemeConstants.MIN_SDK_WITH_V31_SUPPORT + 1;
+
+ File signedApk = sign("original.apk",
+ new ApkSigner.Builder(rsa2048SignerConfigWithLineage)
+ .setV1SigningEnabled(true)
+ .setV2SigningEnabled(true)
+ .setV3SigningEnabled(true)
+ .setV4SigningEnabled(false)
+ .setMinSdkVersionForRotation(rotationMinSdkVersion)
+ .setSigningCertificateLineage(lineage)
+ .setRotationTargetsDevRelease(true));
+ ApkVerifier.Result result = verify(signedApk, null);
+
+ assertVerified(result);
+ assertTrue(result.isVerifiedUsingV31Scheme());
+ assertTrue(result.getV31SchemeSigners().get(0).getRotationTargetsDevRelease());
+ assertResultContainsSigners(result, true, FIRST_RSA_2048_SIGNER_RESOURCE_NAME,
+ SECOND_RSA_2048_SIGNER_RESOURCE_NAME);
+ assertTrue(result.getV3SchemeSigners().get(0).getMaxSdkVersion() >= rotationMinSdkVersion);
+ }
+
/**
* Asserts the provided {@code signedApk} contains a signature block with the expected
* {@code byte[]} value and block ID as specified in the {@code expectedBlock}.
diff --git a/src/test/java/com/android/apksig/ApkVerifierTest.java b/src/test/java/com/android/apksig/ApkVerifierTest.java
index 72e7b9b..38cd84c 100644
--- a/src/test/java/com/android/apksig/ApkVerifierTest.java
+++ b/src/test/java/com/android/apksig/ApkVerifierTest.java
@@ -1407,13 +1407,40 @@ public class ApkVerifierTest {
}
@Test
- public void verify31_v31BlockWithoutV3Block_reportsError() throws Exception {
+ public void verifyV31_v31BlockWithoutV3Block_reportsError() throws Exception {
// A v3.1 block must always exist alongside a v3.0 block; if an APK's minSdkVersion is the
// same as the version supporting rotation then it should be written to a v3.0 block.
ApkVerifier.Result result = verify("v31-tgt-33-no-v3-block.apk");
assertVerificationFailure(result, Issue.V31_BLOCK_FOUND_WITHOUT_V3_BLOCK);
}
+ @Test
+ public void verifyV31_rotationTargetsDevRelease_resultReportsDevReleaseFlag() throws Exception {
+ // Development releases use the SDK version of the previous release until the SDK is
+ // finalized. In order to only target the development release and later, the v3.1 signature
+ // scheme supports targeting development releases such that the SDK version X will install
+ // on a device running X with the system property ro.build.version.codename set to a new
+ // development codename (eg T); a release platform will have this set to "REL", and the
+ // platform will ignore the v3.1 signer if the minSdkVersion is X and the codename is "REL".
+ ApkVerifier.Result result = verify("v31-rsa-2048_2-tgt-34-dev-release.apk");
+
+ assertVerified(result);
+ assertV31SignerTargetsMinApiLevel(result, SECOND_RSA_2048_SIGNER_RESOURCE_NAME, 34);
+ assertResultContainsSigners(result, true, FIRST_RSA_2048_SIGNER_RESOURCE_NAME,
+ SECOND_RSA_2048_SIGNER_RESOURCE_NAME);
+ }
+
+ @Test
+ public void verifyV3_v3RotatedSignerTargetsDevRelease_warningReported() throws Exception {
+ // While a v3.1 signer can target a development release, v3.0 does not support the same
+ // attribute since it is only intended for v3.1 with v3.0 using the original signer. This
+ // test verifies a warning is reported if an APK has this flag set on a v3.0 signer since it
+ // will be ignored by the platform.
+ ApkVerifier.Result result = verify("v3-rsa-2048_2-tgt-dev-release.apk");
+
+ assertVerificationWarning(result, Issue.V31_ROTATION_TARGETS_DEV_RELEASE_ATTR_ON_V3_SIGNER);
+ }
+
private ApkVerifier.Result verify(String apkFilenameInResources)
throws IOException, ApkFormatException, NoSuchAlgorithmException {
return verify(apkFilenameInResources, null, null);
diff --git a/src/test/resources/com/android/apksig/v3-rsa-2048_2-tgt-dev-release.apk b/src/test/resources/com/android/apksig/v3-rsa-2048_2-tgt-dev-release.apk
new file mode 100644
index 0000000..d3b2c14
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-rsa-2048_2-tgt-dev-release.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v31-rsa-2048_2-tgt-34-dev-release.apk b/src/test/resources/com/android/apksig/v31-rsa-2048_2-tgt-34-dev-release.apk
new file mode 100644
index 0000000..784f47e
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v31-rsa-2048_2-tgt-34-dev-release.apk
Binary files differ