Snap for 6626121 from d07c3bde5513af873f2e675a6be8d3ac073d5263 to rvc-release
Change-Id: I2e4baabe3e855b5eb1a4bf08862e081f180b252a
diff --git a/src/apksigner/java/com/android/apksigner/ApkSignerTool.java b/src/apksigner/java/com/android/apksigner/ApkSignerTool.java
index 13d56c8..5783518 100644
--- a/src/apksigner/java/com/android/apksigner/ApkSignerTool.java
+++ b/src/apksigner/java/com/android/apksigner/ApkSignerTool.java
@@ -122,6 +122,7 @@
boolean v2SigningEnabled = true;
boolean v3SigningEnabled = true;
boolean v4SigningEnabled = true;
+ boolean forceSourceStampOverwrite = false;
boolean verityEnabled = false;
boolean debuggableApkPermitted = true;
int minSdkVersion = 1;
@@ -161,6 +162,8 @@
} else if ("v4-signing-enabled".equals(optionName)) {
v4SigningEnabled = optionsParser.getOptionalBooleanValue(true);
v4SigningFlagFound = true;
+ } else if ("force-stamp-overwrite".equals(optionName)) {
+ forceSourceStampOverwrite = optionsParser.getOptionalBooleanValue(true);
} else if ("verity-enabled".equals(optionName)) {
verityEnabled = optionsParser.getOptionalBooleanValue(true);
} else if ("debuggable-apk-permitted".equals(optionName)) {
@@ -323,6 +326,7 @@
.setV2SigningEnabled(v2SigningEnabled)
.setV3SigningEnabled(v3SigningEnabled)
.setV4SigningEnabled(v4SigningEnabled)
+ .setForceSourceStampOverwrite(forceSourceStampOverwrite)
.setVerityEnabled(verityEnabled)
.setV4ErrorReportingEnabled(v4SigningEnabled && v4SigningFlagFound)
.setDebuggableApkPermitted(debuggableApkPermitted)
diff --git a/src/apksigner/java/com/android/apksigner/help_sign.txt b/src/apksigner/java/com/android/apksigner/help_sign.txt
index c576712..1285810 100644
--- a/src/apksigner/java/com/android/apksigner/help_sign.txt
+++ b/src/apksigner/java/com/android/apksigner/help_sign.txt
@@ -46,6 +46,10 @@
enabled based on min and max SDK version (see
--min-sdk-version and --max-sdk-version).
+--force-stamp-overwrite Whether to overwrite existing source stamp in the
+ APK, if found. By default, it is set to false. It has no
+ effect if no source stamp signer config is provided.
+
--verity-enabled Whether to enable the verity signature algorithm for the
v2 and v3 signature schemes.
diff --git a/src/main/java/com/android/apksig/ApkSigner.java b/src/main/java/com/android/apksig/ApkSigner.java
index 432fc35..154e917 100644
--- a/src/main/java/com/android/apksig/ApkSigner.java
+++ b/src/main/java/com/android/apksig/ApkSigner.java
@@ -46,6 +46,7 @@
import java.security.SignatureException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -85,6 +86,7 @@
private final List<SignerConfig> mSignerConfigs;
private final SignerConfig mSourceStampSignerConfig;
+ private final boolean mForceSourceStampOverwrite;
private final Integer mMinSdkVersion;
private final boolean mV1SigningEnabled;
private final boolean mV2SigningEnabled;
@@ -112,6 +114,7 @@
private ApkSigner(
List<SignerConfig> signerConfigs,
SignerConfig sourceStampSignerConfig,
+ boolean forceSourceStampOverwrite,
Integer minSdkVersion,
boolean v1SigningEnabled,
boolean v2SigningEnabled,
@@ -133,6 +136,7 @@
mSignerConfigs = signerConfigs;
mSourceStampSignerConfig = sourceStampSignerConfig;
+ mForceSourceStampOverwrite = forceSourceStampOverwrite;
mMinSdkVersion = minSdkVersion;
mV1SigningEnabled = v1SigningEnabled;
mV2SigningEnabled = v2SigningEnabled;
@@ -173,7 +177,7 @@
*/
public void sign()
throws IOException, ApkFormatException, NoSuchAlgorithmException, InvalidKeyException,
- SignatureException, IllegalStateException {
+ SignatureException, IllegalStateException {
Closeable in = null;
DataSource inputApk;
try {
@@ -219,7 +223,7 @@
private void sign(DataSource inputApk, DataSink outputApkOut, DataSource outputApkIn)
throws IOException, ApkFormatException, NoSuchAlgorithmException, InvalidKeyException,
- SignatureException {
+ SignatureException {
// Step 1. Find input APK's main ZIP sections
ApkUtils.ZipSections inputZipSections;
try {
@@ -275,9 +279,9 @@
for (SignerConfig signerConfig : mSignerConfigs) {
engineSignerConfigs.add(
new DefaultApkSignerEngine.SignerConfig.Builder(
- signerConfig.getName(),
- signerConfig.getPrivateKey(),
- signerConfig.getCertificates())
+ signerConfig.getName(),
+ signerConfig.getPrivateKey(),
+ signerConfig.getCertificates())
.build());
}
DefaultApkSignerEngine.Builder signerEngineBuilder =
@@ -295,9 +299,9 @@
if (mSourceStampSignerConfig != null) {
signerEngineBuilder.setStampSignerConfig(
new DefaultApkSignerEngine.SignerConfig.Builder(
- mSourceStampSignerConfig.getName(),
- mSourceStampSignerConfig.getPrivateKey(),
- mSourceStampSignerConfig.getCertificates())
+ mSourceStampSignerConfig.getName(),
+ mSourceStampSignerConfig.getPrivateKey(),
+ mSourceStampSignerConfig.getCertificates())
.build());
}
signerEngine = signerEngineBuilder.build();
@@ -321,6 +325,7 @@
int lastModifiedTimeForNewEntries = -1;
long inputOffset = 0;
long outputOffset = 0;
+ byte[] sourceStampCertificateDigest = null;
Map<String, CentralDirectoryRecord> outputCdRecordsByName =
new HashMap<>(inputCdRecords.size());
for (final CentralDirectoryRecord inputCdRecord : inputCdRecordsSortedByLfhOffset) {
@@ -328,6 +333,16 @@
if (Hints.PIN_BYTE_RANGE_ZIP_ENTRY_NAME.equals(entryName)) {
continue; // We'll re-add below if needed.
}
+ if (SOURCE_STAMP_CERTIFICATE_HASH_ZIP_ENTRY_NAME.equals(entryName)) {
+ try {
+ sourceStampCertificateDigest =
+ LocalFileRecord.getUncompressedData(
+ inputApkLfhSection, inputCdRecord, inputApkLfhSection.size());
+ } catch (ZipFormatException ex) {
+ throw new ApkFormatException("Bad source stamp entry");
+ }
+ continue; // Existing source stamp is handled below as needed.
+ }
ApkSignerEngine.InputJarEntryInstructions entryInstructions =
signerEngine.inputJarEntry(entryName);
boolean shouldOutput;
@@ -379,7 +394,7 @@
if ((lastModifiedDateForNewEntries == -1)
|| (lastModifiedDate > lastModifiedDateForNewEntries)
|| ((lastModifiedDate == lastModifiedDateForNewEntries)
- && (lastModifiedTime > lastModifiedTimeForNewEntries))) {
+ && (lastModifiedTime > lastModifiedTimeForNewEntries))) {
lastModifiedDateForNewEntries = lastModifiedDate;
lastModifiedTimeForNewEntries = lastModifiedTime;
}
@@ -466,15 +481,26 @@
// records.
if (signerEngine.isEligibleForSourceStamp()) {
byte[] uncompressedData = signerEngine.generateSourceStampCertificateDigest();
- outputOffset +=
- outputDataToOutputApk(
- SOURCE_STAMP_CERTIFICATE_HASH_ZIP_ENTRY_NAME,
- uncompressedData,
- outputOffset,
- outputCdRecords,
- lastModifiedTimeForNewEntries,
- lastModifiedDateForNewEntries,
- outputApkOut);
+ if (mForceSourceStampOverwrite
+ || sourceStampCertificateDigest == null
+ || Arrays.equals(uncompressedData, sourceStampCertificateDigest)) {
+ outputOffset +=
+ outputDataToOutputApk(
+ SOURCE_STAMP_CERTIFICATE_HASH_ZIP_ENTRY_NAME,
+ uncompressedData,
+ outputOffset,
+ outputCdRecords,
+ lastModifiedTimeForNewEntries,
+ lastModifiedDateForNewEntries,
+ outputApkOut);
+ } else {
+ throw new ApkFormatException(
+ String.format(
+ "Cannot generate SourceStamp. APK contains an existing entry with"
+ + " the name: %s, and it is different than the provided source"
+ + " stamp certificate",
+ SOURCE_STAMP_CERTIFICATE_HASH_ZIP_ENTRY_NAME));
+ }
}
// Step 8. Generate and output JAR signatures, if necessary. This may output more Local File
@@ -655,7 +681,7 @@
int dataAlignmentMultiple = getInputJarEntryDataAlignmentMultiple(inputRecord);
if ((dataAlignmentMultiple <= 1)
|| ((inputOffset % dataAlignmentMultiple)
- == (outputOffset % dataAlignmentMultiple))) {
+ == (outputOffset % dataAlignmentMultiple))) {
// This record's data will be aligned same as in the input APK.
return new OutputSizeAndDataOffset(
inputRecord.outputRecord(inputLfhSection, outputLfhSection),
@@ -996,6 +1022,7 @@
public static class Builder {
private final List<SignerConfig> mSignerConfigs;
private SignerConfig mSourceStampSignerConfig;
+ private boolean mForceSourceStampOverwrite = false;
private boolean mV1SigningEnabled = true;
private boolean mV2SigningEnabled = true;
private boolean mV3SigningEnabled = true;
@@ -1074,6 +1101,16 @@
}
/**
+ * Sets whether the APK should overwrite existing source stamp, if found.
+ *
+ * @param force {@code true} to require the APK to be overwrite existing source stamp
+ */
+ public Builder setForceSourceStampOverwrite(boolean force) {
+ mForceSourceStampOverwrite = force;
+ return this;
+ }
+
+ /**
* Sets the APK to be signed.
*
* @see #setInputApk(DataSource)
@@ -1278,7 +1315,7 @@
* <p>V4 signing requires that the APK be v2 or v3 signed.
*
* @param enabled {@code true} to require the APK to be signed using APK Signature Scheme v2
- * or v3 and generate an v4 signature file
+ * or v3 and generate an v4 signature file
*/
public Builder setV4SigningEnabled(boolean enabled) {
checkInitializedWithoutEngine();
@@ -1296,7 +1333,7 @@
* the user did not explicitly request the v4 signing.
*
* @param enabled {@code false} to prevent errors encountered during the V4 signing from
- * halting the signing process
+ * halting the signing process
*/
public Builder setV4ErrorReportingEnabled(boolean enabled) {
checkInitializedWithoutEngine();
@@ -1309,7 +1346,7 @@
* schemes.
*
* @param enabled {@code true} to enable the verity signature algorithm for inclusion in the
- * v2 and v3 signature blocks.
+ * v2 and v3 signature blocks.
*/
public Builder setVerityEnabled(boolean enabled) {
checkInitializedWithoutEngine();
@@ -1418,8 +1455,8 @@
mV4SigningEnabled = false;
} else {
throw new IllegalStateException(
- "APK Signature Scheme v4 signing requires at least "
- + "v2 or v3 signing to be enabled");
+ "APK Signature Scheme v4 signing requires at least "
+ + "v2 or v3 signing to be enabled");
}
}
@@ -1428,6 +1465,7 @@
return new ApkSigner(
mSignerConfigs,
mSourceStampSignerConfig,
+ mForceSourceStampOverwrite,
mMinSdkVersion,
mV1SigningEnabled,
mV2SigningEnabled,
diff --git a/src/main/java/com/android/apksig/DefaultApkSignerEngine.java b/src/main/java/com/android/apksig/DefaultApkSignerEngine.java
index e7c6e8f..f0796fb 100644
--- a/src/main/java/com/android/apksig/DefaultApkSignerEngine.java
+++ b/src/main/java/com/android/apksig/DefaultApkSignerEngine.java
@@ -379,7 +379,7 @@
throws InvalidKeyException {
return createSigningBlockSignerConfig(
mSourceStampSignerConfig,
- /* apkSigningBlockPaddingSupported= */ true,
+ /* apkSigningBlockPaddingSupported= */ false,
ApkSigningBlockUtils.VERSION_SOURCE_STAMP);
}
@@ -459,7 +459,7 @@
case ApkSigningBlockUtils.VERSION_SOURCE_STAMP:
newSignerConfig.signatureAlgorithms =
Collections.singletonList(
- SignatureAlgorithm.VERITY_RSA_PKCS1_V1_5_WITH_SHA256);
+ SignatureAlgorithm.RSA_PKCS1_V1_5_WITH_SHA256);
break;
default:
throw new IllegalArgumentException("Unknown APK Signature Scheme ID requested");
diff --git a/src/test/java/com/android/apksig/ApkSignerTest.java b/src/test/java/com/android/apksig/ApkSignerTest.java
index 74f1c7e..560202c 100644
--- a/src/test/java/com/android/apksig/ApkSignerTest.java
+++ b/src/test/java/com/android/apksig/ApkSignerTest.java
@@ -20,6 +20,7 @@
import static com.android.apksig.apk.ApkUtils.findZipSections;
import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
@@ -35,6 +36,7 @@
import com.android.apksig.internal.apk.v2.V2SchemeSigner;
import com.android.apksig.internal.apk.v3.V3SchemeSigner;
import com.android.apksig.internal.asn1.Asn1BerParser;
+import com.android.apksig.internal.util.AndroidSdkVersion;
import com.android.apksig.internal.util.Resources;
import com.android.apksig.internal.x509.RSAPublicKey;
import com.android.apksig.internal.x509.SubjectPublicKeyInfo;
@@ -1032,6 +1034,80 @@
}
@Test
+ public void testSignApk_existingStampFile_sameSourceStamp() throws Exception {
+ List<ApkSigner.SignerConfig> signers =
+ Collections.singletonList(
+ getDefaultSignerConfigFromResources(FIRST_RSA_2048_SIGNER_RESOURCE_NAME));
+ ApkSigner.SignerConfig sourceStampSigner =
+ getDefaultSignerConfigFromResources(FIRST_RSA_2048_SIGNER_RESOURCE_NAME);
+
+ DataSource signedApk =
+ sign(
+ "original-with-stamp-file.apk",
+ new ApkSigner.Builder(signers)
+ .setV1SigningEnabled(true)
+ .setV2SigningEnabled(true)
+ .setV3SigningEnabled(true)
+ .setSourceStampSignerConfig(sourceStampSigner));
+
+ ApkVerifier.Result sourceStampVerificationResult =
+ verify(signedApk, /* minSdkVersionOverride= */ null);
+ assertSourceStampVerified(signedApk, sourceStampVerificationResult);
+ }
+
+ @Test
+ public void testSignApk_existingStampFile_differentSourceStamp() throws Exception {
+ List<ApkSigner.SignerConfig> signers =
+ Collections.singletonList(
+ getDefaultSignerConfigFromResources(FIRST_RSA_2048_SIGNER_RESOURCE_NAME));
+ ApkSigner.SignerConfig sourceStampSigner =
+ getDefaultSignerConfigFromResources(SECOND_RSA_2048_SIGNER_RESOURCE_NAME);
+
+ Exception exception =
+ assertThrows(
+ ApkFormatException.class,
+ () ->
+ sign(
+ "original-with-stamp-file.apk",
+ new ApkSigner.Builder(signers)
+ .setV1SigningEnabled(true)
+ .setV2SigningEnabled(true)
+ .setV3SigningEnabled(true)
+ .setSourceStampSignerConfig(sourceStampSigner)));
+ assertEquals(
+ String.format(
+ "Cannot generate SourceStamp. APK contains an existing entry with the"
+ + " name: %s, and it is different than the provided source stamp"
+ + " certificate",
+ SOURCE_STAMP_CERTIFICATE_HASH_ZIP_ENTRY_NAME),
+ exception.getMessage());
+ }
+
+ @Test
+ public void testSignApk_existingStampFile_differentSourceStamp_forceOverwrite()
+ throws Exception {
+ List<ApkSigner.SignerConfig> signers =
+ Collections.singletonList(
+ getDefaultSignerConfigFromResources(FIRST_RSA_2048_SIGNER_RESOURCE_NAME));
+ ApkSigner.SignerConfig sourceStampSigner =
+ getDefaultSignerConfigFromResources(SECOND_RSA_2048_SIGNER_RESOURCE_NAME);
+
+ DataSource signedApk =
+ sign(
+ "original-with-stamp-file.apk",
+ new ApkSigner.Builder(signers)
+ .setV1SigningEnabled(true)
+ .setV2SigningEnabled(true)
+ .setV3SigningEnabled(true)
+ .setForceSourceStampOverwrite(true)
+ .setSourceStampSignerConfig(sourceStampSigner));
+
+ ApkVerifier.Result sourceStampVerificationResult =
+ verify(signedApk, /* minSdkVersionOverride= */ null);
+ assertSourceStampVerified(signedApk, sourceStampVerificationResult);
+ }
+
+ @Test
public void testSignApk_stampBlock_noStampGenerated() throws Exception {
List<ApkSigner.SignerConfig> signersList =
Collections.singletonList(
@@ -1075,12 +1151,9 @@
.setV3SigningEnabled(false)
.setSourceStampSignerConfig(sourceStampSigner));
- SignatureInfo signatureInfo =
- getSignatureInfoFromApk(
- signedApk,
- ApkSigningBlockUtils.VERSION_SOURCE_STAMP,
- V2SourceStampSigner.V2_SOURCE_STAMP_BLOCK_ID);
- assertNotNull(signatureInfo.signatureBlock);
+ ApkVerifier.Result sourceStampVerificationResult =
+ verify(signedApk, /* minSdkVersionOverride= */ null);
+ assertSourceStampVerified(signedApk, sourceStampVerificationResult);
}
@Test
@@ -1100,12 +1173,9 @@
.setV3SigningEnabled(false)
.setSourceStampSignerConfig(sourceStampSigner));
- SignatureInfo signatureInfo =
- getSignatureInfoFromApk(
- signedApk,
- ApkSigningBlockUtils.VERSION_SOURCE_STAMP,
- V2SourceStampSigner.V2_SOURCE_STAMP_BLOCK_ID);
- assertNotNull(signatureInfo.signatureBlock);
+ ApkVerifier.Result sourceStampVerificationResult =
+ verifyForMinSdkVersion(signedApk, /* minSdkVersion= */ AndroidSdkVersion.N);
+ assertSourceStampVerified(signedApk, sourceStampVerificationResult);
}
@Test
@@ -1125,12 +1195,9 @@
.setV3SigningEnabled(true)
.setSourceStampSignerConfig(sourceStampSigner));
- SignatureInfo signatureInfo =
- getSignatureInfoFromApk(
- signedApk,
- ApkSigningBlockUtils.VERSION_SOURCE_STAMP,
- V2SourceStampSigner.V2_SOURCE_STAMP_BLOCK_ID);
- assertNotNull(signatureInfo.signatureBlock);
+ ApkVerifier.Result sourceStampVerificationResult =
+ verifyForMinSdkVersion(signedApk, /* minSdkVersion= */ AndroidSdkVersion.N);
+ assertSourceStampVerified(signedApk, sourceStampVerificationResult);
}
private RSAPublicKey getRSAPublicKeyFromSigningBlock(DataSource apk, int signatureVersionId)
@@ -1272,6 +1339,18 @@
ApkVerifierTest.assertVerified(result);
}
+ private static void assertSourceStampVerified(DataSource signedApk, ApkVerifier.Result result)
+ throws ApkSigningBlockUtils.SignatureNotFoundException, IOException,
+ ZipFormatException {
+ SignatureInfo signatureInfo =
+ getSignatureInfoFromApk(
+ signedApk,
+ ApkSigningBlockUtils.VERSION_SOURCE_STAMP,
+ V2SourceStampSigner.V2_SOURCE_STAMP_BLOCK_ID);
+ assertNotNull(signatureInfo.signatureBlock);
+ assertTrue(result.isSourceStampVerified());
+ }
+
private static void assertVerificationFailure(ApkVerifier.Result result, Issue expectedIssue) {
ApkVerifierTest.assertVerificationFailure(result, expectedIssue);
}
diff --git a/src/test/resources/com/android/apksig/original-with-stamp-file.apk b/src/test/resources/com/android/apksig/original-with-stamp-file.apk
new file mode 100644
index 0000000..604fe6f
--- /dev/null
+++ b/src/test/resources/com/android/apksig/original-with-stamp-file.apk
Binary files differ