aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Buynytskyy <alexbuy@google.com>2020-09-25 22:01:58 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2020-09-25 22:01:58 +0000
commit76bdd3e5d53f4b17061eb0e2fba9a33018ed5d77 (patch)
tree78afebe572e3fac662cfd4cc7a7b75a7ff117e92
parent40c6a61ca35ba7c85798c9f635794351903339a3 (diff)
parentd22c08dee5f211221016f3351f0390b75c0ee43c (diff)
downloadapksig-76bdd3e5d53f4b17061eb0e2fba9a33018ed5d77.tar.gz
Supporting V3 cert rotation logic in V4. am: 6956cda737 am: d22c08dee5
Original change: https://googleplex-android-review.googlesource.com/c/platform/tools/apksig/+/12692886 Change-Id: I31d986059189f11ee879a2f59b3162755053fa2e
-rw-r--r--src/main/java/com/android/apksig/DefaultApkSignerEngine.java25
-rw-r--r--src/test/java/com/android/apksig/ApkSignerTest.java187
2 files changed, 124 insertions, 88 deletions
diff --git a/src/main/java/com/android/apksig/DefaultApkSignerEngine.java b/src/main/java/com/android/apksig/DefaultApkSignerEngine.java
index 12f56ed..90f2a6d 100644
--- a/src/main/java/com/android/apksig/DefaultApkSignerEngine.java
+++ b/src/main/java/com/android/apksig/DefaultApkSignerEngine.java
@@ -311,13 +311,8 @@ public class DefaultApkSignerEngine implements ApkSignerEngine {
}
}
- private List<ApkSigningBlockUtils.SignerConfig> createV3SignerConfigs(
- boolean apkSigningBlockPaddingSupported) throws InvalidKeyException {
- List<ApkSigningBlockUtils.SignerConfig> rawConfigs =
- createSigningBlockSignerConfigs(
- apkSigningBlockPaddingSupported,
- ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V3);
-
+ private List<ApkSigningBlockUtils.SignerConfig> processV3Configs(
+ List<ApkSigningBlockUtils.SignerConfig> rawConfigs) throws InvalidKeyException {
List<ApkSigningBlockUtils.SignerConfig> processedConfigs = new ArrayList<>();
// we have our configs, now touch them up to appropriately cover all SDK levels since APK
@@ -365,13 +360,23 @@ public class DefaultApkSignerEngine implements ApkSignerEngine {
"Provided key algorithms not supported on all desired "
+ "Android SDK versions");
}
+
return processedConfigs;
}
+ private List<ApkSigningBlockUtils.SignerConfig> createV3SignerConfigs(
+ boolean apkSigningBlockPaddingSupported) throws InvalidKeyException {
+ return processV3Configs(createSigningBlockSignerConfigs(apkSigningBlockPaddingSupported,
+ ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V3));
+ }
+
private ApkSigningBlockUtils.SignerConfig createV4SignerConfig() throws InvalidKeyException {
- List<ApkSigningBlockUtils.SignerConfig> configs =
- createSigningBlockSignerConfigs(
- true, ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V4);
+ List<ApkSigningBlockUtils.SignerConfig> configs = createSigningBlockSignerConfigs(true,
+ ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V4);
+ if (configs.size() != 1) {
+ // V4 only uses signer config to connect back to v3. Use the same filtering logic.
+ configs = processV3Configs(configs);
+ }
if (configs.size() != 1) {
throw new InvalidKeyException("Only accepting one signer config for V4 Signature.");
}
diff --git a/src/test/java/com/android/apksig/ApkSignerTest.java b/src/test/java/com/android/apksig/ApkSignerTest.java
index 9697b9a..4e18852 100644
--- a/src/test/java/com/android/apksig/ApkSignerTest.java
+++ b/src/test/java/com/android/apksig/ApkSignerTest.java
@@ -42,23 +42,23 @@ import com.android.apksig.internal.x509.RSAPublicKey;
import com.android.apksig.internal.x509.SubjectPublicKeyInfo;
import com.android.apksig.internal.zip.CentralDirectoryRecord;
import com.android.apksig.internal.zip.LocalFileRecord;
-import com.android.apksig.util.DataSinks;
import com.android.apksig.util.DataSource;
import com.android.apksig.util.DataSources;
-import com.android.apksig.util.ReadableDataSink;
import com.android.apksig.zip.ZipFormatException;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import java.io.File;
+import java.io.FileInputStream;
import java.io.IOException;
+import java.io.RandomAccessFile;
import java.math.BigInteger;
import java.nio.ByteBuffer;
-import java.nio.channels.ByteChannel;
import java.nio.file.Files;
-import java.nio.file.StandardOpenOption;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
@@ -90,6 +90,9 @@ public class ApkSignerTest {
private static final String LINEAGE_RSA_2048_2_SIGNERS_RESOURCE_NAME =
"rsa-2048-lineage-2-signers";
+ @Rule
+ public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+
public static void main(String[] params) throws Exception {
File outDir = (params.length > 0) ? new File(params[0]) : new File(".");
generateGoldenFiles(outDir);
@@ -136,21 +139,24 @@ public class ApkSignerTest {
new ApkSigner.Builder(rsa2048SignerConfig)
.setV1SigningEnabled(true)
.setV2SigningEnabled(false)
- .setV3SigningEnabled(false));
+ .setV3SigningEnabled(false)
+ .setV4SigningEnabled(false));
signGolden(
"golden-legacy-aligned-in.apk",
new File(outDir, "golden-legacy-aligned-v1-out.apk"),
new ApkSigner.Builder(rsa2048SignerConfig)
.setV1SigningEnabled(true)
.setV2SigningEnabled(false)
- .setV3SigningEnabled(false));
+ .setV3SigningEnabled(false)
+ .setV4SigningEnabled(false));
signGolden(
"golden-aligned-in.apk",
new File(outDir, "golden-aligned-v1-out.apk"),
new ApkSigner.Builder(rsa2048SignerConfig)
.setV1SigningEnabled(true)
.setV2SigningEnabled(false)
- .setV3SigningEnabled(false));
+ .setV3SigningEnabled(false)
+ .setV4SigningEnabled(false));
signGolden(
"golden-unaligned-in.apk",
@@ -367,7 +373,13 @@ public class ApkSignerTest {
DataSource in =
DataSources.asDataSource(
ByteBuffer.wrap(Resources.toByteArray(ApkSigner.class, inResourceName)));
- apkSignerBuilder.setInputApk(in).setOutputApk(outFile).build().sign();
+ apkSignerBuilder.setInputApk(in).setOutputApk(outFile);
+
+ File outFileIdSig = new File(outFile.getCanonicalPath() + ".idsig");
+ apkSignerBuilder.setV4SignatureOutputFile(outFileIdSig);
+ apkSignerBuilder.setV4ErrorReportingEnabled(true);
+
+ apkSignerBuilder.build().sign();
}
@Test
@@ -399,7 +411,8 @@ public class ApkSignerTest {
new ApkSigner.Builder(rsa2048SignerConfig)
.setV1SigningEnabled(true)
.setV2SigningEnabled(false)
- .setV3SigningEnabled(false));
+ .setV3SigningEnabled(false)
+ .setV4SigningEnabled(false));
assertGolden(
"golden-unaligned-in.apk",
"golden-unaligned-v2-out.apk",
@@ -474,7 +487,8 @@ public class ApkSignerTest {
new ApkSigner.Builder(rsa2048SignerConfig)
.setV1SigningEnabled(true)
.setV2SigningEnabled(false)
- .setV3SigningEnabled(false));
+ .setV3SigningEnabled(false)
+ .setV4SigningEnabled(false));
assertGolden(
"golden-legacy-aligned-in.apk",
"golden-legacy-aligned-v2-out.apk",
@@ -548,7 +562,8 @@ public class ApkSignerTest {
new ApkSigner.Builder(rsa2048SignerConfig)
.setV1SigningEnabled(true)
.setV2SigningEnabled(false)
- .setV3SigningEnabled(false));
+ .setV3SigningEnabled(false)
+ .setV4SigningEnabled(false));
assertGolden(
"golden-aligned-in.apk",
"golden-aligned-v2-out.apk",
@@ -661,7 +676,7 @@ public class ApkSignerTest {
String in = "original.apk";
// Sign so that the APK is guaranteed to verify on API Level 1+
- DataSource out = sign(in, new ApkSigner.Builder(signers).setMinSdkVersion(1));
+ File out = sign(in, new ApkSigner.Builder(signers).setMinSdkVersion(1));
assertVerified(verifyForMinSdkVersion(out, 1));
// Sign so that the APK is guaranteed to verify on API Level 18+
@@ -679,7 +694,7 @@ public class ApkSignerTest {
String in = "original.apk";
// Sign so that the APK is guaranteed to verify on API Level 1+
- DataSource out = sign(in, new ApkSigner.Builder(signers).setMinSdkVersion(1));
+ File out = sign(in, new ApkSigner.Builder(signers).setMinSdkVersion(1));
assertVerified(verifyForMinSdkVersion(out, 1));
// Sign so that the APK is guaranteed to verify on API Level 21+
@@ -698,7 +713,7 @@ public class ApkSignerTest {
// NOTE: EC APK signatures are not supported prior to API Level 18
// Sign so that the APK is guaranteed to verify on API Level 18+
- DataSource out = sign(in, new ApkSigner.Builder(signers).setMinSdkVersion(18));
+ File out = sign(in, new ApkSigner.Builder(signers).setMinSdkVersion(18));
assertVerified(verifyForMinSdkVersion(out, 18));
// Does not verify on API Level 17 because EC not supported
assertVerificationFailure(
@@ -930,14 +945,13 @@ public class ApkSignerTest {
Arrays.asList(
getDefaultSignerConfigFromResources(FIRST_RSA_2048_SIGNER_RESOURCE_NAME),
getDefaultSignerConfigFromResources(SECOND_RSA_2048_SIGNER_RESOURCE_NAME));
- DataSource out =
+ File out =
sign(
"original.apk",
new ApkSigner.Builder(signerConfigs)
.setV3SigningEnabled(true)
.setSigningCertificateLineage(lineage));
- SigningCertificateLineage lineageFromApk =
- SigningCertificateLineage.readFromApkDataSource(out);
+ SigningCertificateLineage lineageFromApk = SigningCertificateLineage.readFromApkFile(out);
assertTrue(
"The first signer was not in the lineage from the signed APK",
lineageFromApk.isSignerInLineage((firstSigner)));
@@ -960,7 +974,7 @@ public class ApkSignerTest {
getDefaultSignerConfigFromResources(
FIRST_RSA_2048_SIGNER_RESOURCE_NAME,
FIRST_RSA_2048_SIGNER_CERT_WITH_NEGATIVE_MODULUS));
- DataSource signedApk =
+ File signedApk =
sign(
"original.apk",
new ApkSigner.Builder(signersList)
@@ -1009,28 +1023,32 @@ public class ApkSignerTest {
messageDigest.update(sourceStampSigner.getCertificates().get(0).getEncoded());
byte[] expectedStampCertificateDigest = messageDigest.digest();
- DataSource signedApk =
+ File signedApkFile =
sign(
"original.apk",
new ApkSigner.Builder(signers)
.setV1SigningEnabled(true)
.setSourceStampSignerConfig(sourceStampSigner));
- ApkUtils.ZipSections zipSections = findZipSections(signedApk);
- List<CentralDirectoryRecord> cdRecords =
- V1SchemeVerifier.parseZipCentralDirectory(signedApk, zipSections);
- CentralDirectoryRecord stampCdRecord = null;
- for (CentralDirectoryRecord cdRecord : cdRecords) {
- if (SOURCE_STAMP_CERTIFICATE_HASH_ZIP_ENTRY_NAME.equals(cdRecord.getName())) {
- stampCdRecord = cdRecord;
- break;
+ try (RandomAccessFile f = new RandomAccessFile(signedApkFile, "r")) {
+ DataSource signedApk = DataSources.asDataSource(f, 0, f.length());
+
+ ApkUtils.ZipSections zipSections = findZipSections(signedApk);
+ List<CentralDirectoryRecord> cdRecords =
+ V1SchemeVerifier.parseZipCentralDirectory(signedApk, zipSections);
+ CentralDirectoryRecord stampCdRecord = null;
+ for (CentralDirectoryRecord cdRecord : cdRecords) {
+ if (SOURCE_STAMP_CERTIFICATE_HASH_ZIP_ENTRY_NAME.equals(cdRecord.getName())) {
+ stampCdRecord = cdRecord;
+ break;
+ }
}
+ assertNotNull(stampCdRecord);
+ byte[] actualStampCertificateDigest =
+ LocalFileRecord.getUncompressedData(
+ signedApk, stampCdRecord, zipSections.getZipCentralDirectoryOffset());
+ assertArrayEquals(expectedStampCertificateDigest, actualStampCertificateDigest);
}
- assertNotNull(stampCdRecord);
- byte[] actualStampCertificateDigest =
- LocalFileRecord.getUncompressedData(
- signedApk, stampCdRecord, zipSections.getZipCentralDirectoryOffset());
- assertArrayEquals(expectedStampCertificateDigest, actualStampCertificateDigest);
}
@Test
@@ -1041,7 +1059,7 @@ public class ApkSignerTest {
ApkSigner.SignerConfig sourceStampSigner =
getDefaultSignerConfigFromResources(FIRST_RSA_2048_SIGNER_RESOURCE_NAME);
- DataSource signedApk =
+ File signedApk =
sign(
"original-with-stamp-file.apk",
new ApkSigner.Builder(signers)
@@ -1092,7 +1110,7 @@ public class ApkSignerTest {
ApkSigner.SignerConfig sourceStampSigner =
getDefaultSignerConfigFromResources(SECOND_RSA_2048_SIGNER_RESOURCE_NAME);
- DataSource signedApk =
+ File signedApk =
sign(
"original-with-stamp-file.apk",
new ApkSigner.Builder(signers)
@@ -1113,7 +1131,7 @@ public class ApkSignerTest {
Collections.singletonList(
getDefaultSignerConfigFromResources(FIRST_RSA_2048_SIGNER_RESOURCE_NAME));
- DataSource signedApk =
+ File signedApkFile =
sign(
"original.apk",
new ApkSigner.Builder(signersList)
@@ -1121,17 +1139,21 @@ public class ApkSignerTest {
.setV2SigningEnabled(true)
.setV3SigningEnabled(true));
- ApkUtils.ZipSections zipSections = ApkUtils.findZipSections(signedApk);
- ApkSigningBlockUtils.Result result =
- new ApkSigningBlockUtils.Result(ApkSigningBlockUtils.VERSION_SOURCE_STAMP);
- assertThrows(
- ApkSigningBlockUtils.SignatureNotFoundException.class,
- () ->
- ApkSigningBlockUtils.findSignature(
- signedApk,
- zipSections,
- ApkSigningBlockUtils.VERSION_SOURCE_STAMP,
- result));
+ try (RandomAccessFile f = new RandomAccessFile(signedApkFile, "r")) {
+ DataSource signedApk = DataSources.asDataSource(f, 0, f.length());
+
+ ApkUtils.ZipSections zipSections = ApkUtils.findZipSections(signedApk);
+ ApkSigningBlockUtils.Result result =
+ new ApkSigningBlockUtils.Result(ApkSigningBlockUtils.VERSION_SOURCE_STAMP);
+ assertThrows(
+ ApkSigningBlockUtils.SignatureNotFoundException.class,
+ () ->
+ ApkSigningBlockUtils.findSignature(
+ signedApk,
+ zipSections,
+ ApkSigningBlockUtils.VERSION_SOURCE_STAMP,
+ result));
+ }
}
@Test
@@ -1142,13 +1164,14 @@ public class ApkSignerTest {
ApkSigner.SignerConfig sourceStampSigner =
getDefaultSignerConfigFromResources(SECOND_RSA_2048_SIGNER_RESOURCE_NAME);
- DataSource signedApk =
+ File signedApk =
sign(
"original.apk",
new ApkSigner.Builder(signersList)
.setV1SigningEnabled(true)
.setV2SigningEnabled(false)
.setV3SigningEnabled(false)
+ .setV4SigningEnabled(false)
.setSourceStampSignerConfig(sourceStampSigner));
ApkVerifier.Result sourceStampVerificationResult =
@@ -1164,7 +1187,7 @@ public class ApkSignerTest {
ApkSigner.SignerConfig sourceStampSigner =
getDefaultSignerConfigFromResources(SECOND_RSA_2048_SIGNER_RESOURCE_NAME);
- DataSource signedApk =
+ File signedApk =
sign(
"original.apk",
new ApkSigner.Builder(signersList)
@@ -1186,7 +1209,7 @@ public class ApkSignerTest {
ApkSigner.SignerConfig sourceStampSigner =
getDefaultSignerConfigFromResources(SECOND_RSA_2048_SIGNER_RESOURCE_NAME);
- DataSource signedApk =
+ File signedApk =
sign(
"original.apk",
new ApkSigner.Builder(signersList)
@@ -1200,7 +1223,7 @@ public class ApkSignerTest {
assertSourceStampVerified(signedApk, sourceStampVerificationResult);
}
- private RSAPublicKey getRSAPublicKeyFromSigningBlock(DataSource apk, int signatureVersionId)
+ private RSAPublicKey getRSAPublicKeyFromSigningBlock(File apk, int signatureVersionId)
throws Exception {
int signatureVersionBlockId;
switch (signatureVersionId) {
@@ -1247,13 +1270,17 @@ public class ApkSignerTest {
}
private static SignatureInfo getSignatureInfoFromApk(
- DataSource apk, int signatureVersionId, int signatureVersionBlockId)
+ File apkFile, int signatureVersionId, int signatureVersionBlockId)
throws IOException, ZipFormatException,
ApkSigningBlockUtils.SignatureNotFoundException {
- ApkUtils.ZipSections zipSections = ApkUtils.findZipSections(apk);
- ApkSigningBlockUtils.Result result = new ApkSigningBlockUtils.Result(signatureVersionId);
- return ApkSigningBlockUtils.findSignature(
- apk, zipSections, signatureVersionBlockId, result);
+ try (RandomAccessFile f = new RandomAccessFile(apkFile, "r")) {
+ DataSource apk = DataSources.asDataSource(f, 0, f.length());
+ ApkUtils.ZipSections zipSections = ApkUtils.findZipSections(apk);
+ ApkSigningBlockUtils.Result result = new ApkSigningBlockUtils.Result(
+ signatureVersionId);
+ return ApkSigningBlockUtils.findSignature(apk, zipSections, signatureVersionBlockId,
+ result);
+ }
}
/**
@@ -1266,18 +1293,22 @@ public class ApkSignerTest {
ApkSigner.Builder apkSignerBuilder)
throws Exception {
// Sign the provided golden input
- DataSource out = sign(inResourceName, apkSignerBuilder);
+ File out = sign(inResourceName, apkSignerBuilder);
+ assertVerified(verify(out, AndroidSdkVersion.P));
// Assert that the output is identical to the provided golden output
- if (out.size() > Integer.MAX_VALUE) {
- throw new RuntimeException("Output too large: " + out.size() + " bytes");
+ if (out.length() > Integer.MAX_VALUE) {
+ throw new RuntimeException("Output too large: " + out.length() + " bytes");
+ }
+ byte[] outData = new byte[(int)out.length()];
+ try (FileInputStream fis = new FileInputStream(out)) {
+ fis.read(outData);
}
- ByteBuffer actualOutBuf = out.getByteBuffer(0, (int) out.size());
+ ByteBuffer actualOutBuf = ByteBuffer.wrap(outData);
ByteBuffer expectedOutBuf =
ByteBuffer.wrap(Resources.toByteArray(getClass(), expectedOutResourceName));
- int actualStartPos = actualOutBuf.position();
boolean identical = false;
if (actualOutBuf.remaining() == expectedOutBuf.remaining()) {
while (actualOutBuf.hasRemaining()) {
@@ -1291,47 +1322,47 @@ public class ApkSignerTest {
if (identical) {
return;
}
- actualOutBuf.position(actualStartPos);
if (KEEP_FAILING_OUTPUT_AS_FILES) {
File tmp = File.createTempFile(getClass().getSimpleName(), ".apk");
- try (ByteChannel outChannel =
- Files.newByteChannel(
- tmp.toPath(),
- StandardOpenOption.WRITE,
- StandardOpenOption.CREATE,
- StandardOpenOption.TRUNCATE_EXISTING)) {
- while (actualOutBuf.hasRemaining()) {
- outChannel.write(actualOutBuf);
- }
- }
+ Files.copy(out.toPath(), tmp.toPath());
fail(tmp + " differs from " + expectedOutResourceName);
} else {
fail("Output differs from " + expectedOutResourceName);
}
}
- private DataSource sign(String inResourceName, ApkSigner.Builder apkSignerBuilder)
+ private File sign(String inResourceName, ApkSigner.Builder apkSignerBuilder)
throws Exception {
DataSource in =
DataSources.asDataSource(
ByteBuffer.wrap(Resources.toByteArray(getClass(), inResourceName)));
- ReadableDataSink out = DataSinks.newInMemoryDataSink();
- apkSignerBuilder.setInputApk(in).setOutputApk(out).build().sign();
- return out;
+ File outFile = mTemporaryFolder.newFile();
+ apkSignerBuilder.setInputApk(in).setOutputApk(outFile);
+
+ File outFileIdSig = new File(outFile.getCanonicalPath() + ".idsig");
+ apkSignerBuilder.setV4SignatureOutputFile(outFileIdSig);
+ apkSignerBuilder.setV4ErrorReportingEnabled(true);
+
+ apkSignerBuilder.build().sign();
+ return outFile;
}
- private static ApkVerifier.Result verifyForMinSdkVersion(DataSource apk, int minSdkVersion)
+ private static ApkVerifier.Result verifyForMinSdkVersion(File apk, int minSdkVersion)
throws IOException, ApkFormatException, NoSuchAlgorithmException {
return verify(apk, minSdkVersion);
}
- private static ApkVerifier.Result verify(DataSource apk, Integer minSdkVersionOverride)
+ private static ApkVerifier.Result verify(File apk, Integer minSdkVersionOverride)
throws IOException, ApkFormatException, NoSuchAlgorithmException {
ApkVerifier.Builder builder = new ApkVerifier.Builder(apk);
if (minSdkVersionOverride != null) {
builder.setMinCheckedPlatformVersion(minSdkVersionOverride);
}
+ File idSig = new File(apk.getCanonicalPath() + ".idsig");
+ if (idSig.exists()) {
+ builder.setV4SignatureFile(idSig);
+ }
return builder.build().verify();
}
@@ -1339,7 +1370,7 @@ public class ApkSignerTest {
ApkVerifierTest.assertVerified(result);
}
- private static void assertSourceStampVerified(DataSource signedApk, ApkVerifier.Result result)
+ private static void assertSourceStampVerified(File signedApk, ApkVerifier.Result result)
throws ApkSigningBlockUtils.SignatureNotFoundException, IOException,
ZipFormatException {
SignatureInfo signatureInfo =