aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Buynytskyy <alexbuy@google.com>2021-10-13 16:30:03 -0700
committerAlex Buynytskyy <alexbuy@google.com>2021-10-14 18:00:27 +0000
commit9d96cb526207a61e47948f7ec1fb590ff9dda8cb (patch)
treeef82fb9bcb160de185e37fafcb473551350144b7
parentbeedf367a5e2bb8c1cac193473fd42804aaf387e (diff)
downloadapksig-9d96cb526207a61e47948f7ec1fb590ff9dda8cb.tar.gz
v4.1: support v3.1 key rotation in v4 signature
Bug: 202011194 Test: ./gradlew test Test: adb install on T/master $ adb install orig.apk Performing Incremental Install Serving... All files should be loaded. Notifying the device. Failure [INSTALL_PARSE_FAILED_NO_CERTIFICATES: Failed to collect certificates from /data/app/vmdl54972609.tmp/orig.apk using APK Signature Scheme v4: V4 signature certificate does not match V2/V3] Performing Streamed Install Success Test: adb install on S (sc-dev) $ adb install orig.apk Performing Incremental Install Serving... All files should be loaded. Notifying the device. Success Install command complete in 229 ms Change-Id: Id1910fbc88f1399ea5b1ea33a43962e7f36c009b
-rw-r--r--src/main/java/com/android/apksig/ApkVerifier.java40
-rw-r--r--src/main/java/com/android/apksig/DefaultApkSignerEngine.java135
-rw-r--r--src/main/java/com/android/apksig/internal/apk/v4/V4SchemeSigner.java56
-rw-r--r--src/main/java/com/android/apksig/internal/apk/v4/V4SchemeVerifier.java37
-rw-r--r--src/main/java/com/android/apksig/internal/apk/v4/V4Signature.java98
-rw-r--r--src/test/java/com/android/apksig/ApkSignerTest.java3
6 files changed, 266 insertions, 103 deletions
diff --git a/src/main/java/com/android/apksig/ApkVerifier.java b/src/main/java/com/android/apksig/ApkVerifier.java
index fb8648c..31b13e3 100644
--- a/src/main/java/com/android/apksig/ApkVerifier.java
+++ b/src/main/java/com/android/apksig/ApkVerifier.java
@@ -490,9 +490,6 @@ public class ApkVerifier {
// The apkDigest field in the v4 signature should match the selected v2/v3.
if (result.isVerifiedUsingV4Scheme()) {
List<Result.V4SchemeSignerInfo> v4Signers = result.getV4SchemeSigners();
- if (v4Signers.size() != 1) {
- result.addError(Issue.V4_SIG_MULTIPLE_SIGNERS);
- }
List<ApkSigningBlockUtils.Result.SignerInfo.ContentDigest> digestsFromV4 =
v4Signers.get(0).getContentDigests();
@@ -502,21 +499,22 @@ public class ApkVerifier {
final byte[] digestFromV4 = digestsFromV4.get(0).getValue();
if (result.isVerifiedUsingV3Scheme()) {
- List<Result.V3SchemeSignerInfo> v3Signers = result.getV3SchemeSigners();
- if (v3Signers.size() != 1) {
+ int expectedSize = result.isVerifiedUsingV31Scheme() ? 2 : 1;
+ if (v4Signers.size() != expectedSize) {
result.addError(Issue.V4_SIG_MULTIPLE_SIGNERS);
}
- // Compare certificates.
- checkV4Certificate(v4Signers.get(0).mCerts, v3Signers.get(0).mCerts, result);
-
- // Compare digests.
- final byte[] digestFromV3 = pickBestDigestForV4(
- v3Signers.get(0).getContentDigests());
- if (!Arrays.equals(digestFromV4, digestFromV3)) {
- result.addError(Issue.V4_SIG_V2_V3_DIGESTS_MISMATCH);
+ checkV4Signer(result.getV3SchemeSigners(), v4Signers.get(0).mCerts, digestFromV4,
+ result);
+ if (result.isVerifiedUsingV31Scheme()) {
+ checkV4Signer(result.getV31SchemeSigners(), v4Signers.get(1).mCerts,
+ digestFromV4, result);
}
} else if (result.isVerifiedUsingV2Scheme()) {
+ if (v4Signers.size() != 1) {
+ result.addError(Issue.V4_SIG_MULTIPLE_SIGNERS);
+ }
+
List<Result.V2SchemeSignerInfo> v2Signers = result.getV2SchemeSigners();
if (v2Signers.size() != 1) {
result.addError(Issue.V4_SIG_MULTIPLE_SIGNERS);
@@ -931,6 +929,22 @@ public class ApkVerifier {
return result;
}
+ private static void checkV4Signer(List<Result.V3SchemeSignerInfo> v3Signers,
+ List<X509Certificate> v4Certs, byte[] digestFromV4, Result result) {
+ if (v3Signers.size() != 1) {
+ result.addError(Issue.V4_SIG_MULTIPLE_SIGNERS);
+ }
+
+ // Compare certificates.
+ checkV4Certificate(v4Certs, v3Signers.get(0).mCerts, result);
+
+ // Compare digests.
+ final byte[] digestFromV3 = pickBestDigestForV4(v3Signers.get(0).getContentDigests());
+ if (!Arrays.equals(digestFromV4, digestFromV3)) {
+ result.addError(Issue.V4_SIG_V2_V3_DIGESTS_MISMATCH);
+ }
+ }
+
private static void checkV4Certificate(List<X509Certificate> v4Certs,
List<X509Certificate> v2v3Certs, Result result) {
try {
diff --git a/src/main/java/com/android/apksig/DefaultApkSignerEngine.java b/src/main/java/com/android/apksig/DefaultApkSignerEngine.java
index aee7cdb..6423804 100644
--- a/src/main/java/com/android/apksig/DefaultApkSignerEngine.java
+++ b/src/main/java/com/android/apksig/DefaultApkSignerEngine.java
@@ -339,18 +339,43 @@ public class DefaultApkSignerEngine implements ApkSignerEngine {
}
}
- private List<ApkSigningBlockUtils.SignerConfig> processV3Configs(
- List<ApkSigningBlockUtils.SignerConfig> rawConfigs) throws InvalidKeyException {
- List<ApkSigningBlockUtils.SignerConfig> processedConfigs = new ArrayList<>();
-
+ private int getDevReleaseRotationMinSdkVersion() {
// 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
+ return mRotationMinSdkVersion == MIN_SDK_WITH_V31_SUPPORT
? DEV_RELEASE_ROTATION_MIN_SDK_VERSION : mRotationMinSdkVersion;
+ }
+
+ private boolean signingLineageHas31Support() {
+ return mSigningCertificateLineage != null
+ && mRotationMinSdkVersion >= MIN_SDK_WITH_V31_SUPPORT
+ && mMinSdkVersion < mRotationMinSdkVersion;
+ }
+
+ private List<ApkSigningBlockUtils.SignerConfig> processV3Configs(
+ List<ApkSigningBlockUtils.SignerConfig> rawConfigs) throws InvalidKeyException {
+ // While the V3 signature scheme supports rotation, it is possible for a caller to specify
+ // a minimum SDK version for rotation that is >= the first SDK version that supports V3.1;
+ // in this case the V3.1 signing block will contain the rotated key, and the V3.0 block
+ // will use the original signing key.
+ if (signingLineageHas31Support()) {
+ SigningCertificateLineage subLineage = mSigningCertificateLineage
+ .getSubLineage(mSignerConfigs.get(0).mCertificates.get(0));
+ if (subLineage.size() != 1) {
+ throw new IllegalArgumentException(
+ "v3.1 signing enabled but the oldest signer in the SigningCertificateLineage"
+ + " for the v3.0 signing block is missing. Please provide"
+ + " the oldest signer to enable v3.1 signing.");
+ }
+ }
+
+ List<ApkSigningBlockUtils.SignerConfig> processedConfigs = new ArrayList<>();
+
+ int rotationMinSdkVersion = getDevReleaseRotationMinSdkVersion();
// 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;
@@ -418,38 +443,47 @@ public class DefaultApkSignerEngine implements ApkSignerEngine {
private List<ApkSigningBlockUtils.SignerConfig> createV3SignerConfigs(
boolean apkSigningBlockPaddingSupported) throws InvalidKeyException {
- // While the V3 signature scheme supports rotation, it is possible for a caller to specify
- // a minimum SDK version for rotation that is >= the first SDK version that supports V3.1;
- // in this case the V3.1 signing block will contain the rotated key, and the V3.0 block
- // will use the original signing key.
- if (mSigningCertificateLineage != null
- && mRotationMinSdkVersion >= MIN_SDK_WITH_V31_SUPPORT
- && mMinSdkVersion < mRotationMinSdkVersion) {
- SigningCertificateLineage subLineage = mSigningCertificateLineage
- .getSubLineage(mSignerConfigs.get(0).mCertificates.get(0));
- if (subLineage.size() != 1) {
- throw new IllegalArgumentException(
- "v3.1 signing enabled but the oldest signer in the SigningCertificateLineage"
- + " for the v3.0 signing block is missing. Please provide"
- + " the oldest signer to enable v3.1 signing.");
- }
- }
-
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);
- if (configs.size() != 1) {
- // V4 only uses signer config to connect back to v3. Use the same filtering logic.
- configs = processV3Configs(configs);
+ private List<ApkSigningBlockUtils.SignerConfig> processV31SignerConfigs(
+ List<ApkSigningBlockUtils.SignerConfig> v3SignerConfigs) {
+ // If the signing key has been rotated, the caller has requested to use the rotated
+ // signing key starting from an SDK version where v3.1 is supported, and the minimum
+ // SDK version for the APK is less than the requested rotation minimum, then the APK
+ // 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.
+ if (!signingLineageHas31Support()) {
+ return null;
}
- if (configs.size() != 1) {
- throw new InvalidKeyException("Only accepting one signer config for V4 Signature.");
+
+ int rotationMinSdkVersion = getDevReleaseRotationMinSdkVersion();
+ List<ApkSigningBlockUtils.SignerConfig> v31SignerConfigs = new ArrayList<>();
+ Iterator<ApkSigningBlockUtils.SignerConfig> v3SignerIterator =
+ v3SignerConfigs.iterator();
+ while (v3SignerIterator.hasNext()) {
+ 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 >= rotationMinSdkVersion) {
+ v31SignerConfigs.add(signerConfig);
+ v3SignerIterator.remove();
+ }
}
- return configs.get(0);
+ return v31SignerConfigs;
+ }
+
+ private V4SchemeSigner.SignerConfig createV4SignerConfig() throws InvalidKeyException {
+ List<ApkSigningBlockUtils.SignerConfig> v4Configs = createSigningBlockSignerConfigs(true,
+ ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V4);
+ if (v4Configs.size() != 1) {
+ // V4 uses signer config to connect back to v3. Use the same filtering logic.
+ v4Configs = processV3Configs(v4Configs);
+ }
+ List<ApkSigningBlockUtils.SignerConfig> v41configs = processV31SignerConfigs(v4Configs);
+ return new V4SchemeSigner.SignerConfig(v4Configs, v41configs);
}
private ApkSigningBlockUtils.SignerConfig createSourceStampSignerConfig()
@@ -1045,32 +1079,9 @@ public class DefaultApkSignerEngine implements ApkSignerEngine {
invalidateV3Signature();
List<ApkSigningBlockUtils.SignerConfig> v3SignerConfigs =
createV3SignerConfigs(apkSigningBlockPaddingSupported);
- // If the signing key has been rotated, the caller has requested to use the rotated
- // signing key starting from an SDK version where v3.1 is supported, and the minimum
- // SDK version for the APK is less than the requested rotation minimum, then the APK
- // 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) {
- List<ApkSigningBlockUtils.SignerConfig> v31SignerConfigs = new ArrayList<>();
- Iterator<ApkSigningBlockUtils.SignerConfig> v3SignerIterator =
- v3SignerConfigs.iterator();
- while (v3SignerIterator.hasNext()) {
- 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 >= rotationMinSdkVersion) {
- v31SignerConfigs.add(signerConfig);
- v3SignerIterator.remove();
- }
- }
+ List<ApkSigningBlockUtils.SignerConfig> v31SignerConfigs = processV31SignerConfigs(
+ v3SignerConfigs);
+ if (v31SignerConfigs != null && v31SignerConfigs.size() > 0) {
ApkSigningBlockUtils.SigningSchemeBlockAndDigests
v31SigningSchemeBlockAndDigests =
new V3SchemeSigner.Builder(beforeCentralDir, zipCentralDirectory, eocd,
@@ -1086,10 +1097,8 @@ public class DefaultApkSignerEngine implements ApkSignerEngine {
zipCentralDirectory, eocd, v3SignerConfigs)
.setRunnablesExecutor(mExecutor)
.setBlockId(V3SchemeConstants.APK_SIGNATURE_SCHEME_V3_BLOCK_ID);
- if (mSigningCertificateLineage != null
- && mRotationMinSdkVersion >= MIN_SDK_WITH_V31_SUPPORT
- && mMinSdkVersion < mRotationMinSdkVersion) {
- builder.setRotationMinSdkVersion(rotationMinSdkVersion);
+ if (signingLineageHas31Support()) {
+ builder.setRotationMinSdkVersion(getDevReleaseRotationMinSdkVersion());
}
v3SigningSchemeBlockAndDigests =
builder.build().generateApkSignatureSchemeV3BlockAndDigests();
@@ -1164,7 +1173,7 @@ public class DefaultApkSignerEngine implements ApkSignerEngine {
throw new SignatureException("Missing V4 output file.");
}
try {
- ApkSigningBlockUtils.SignerConfig v4SignerConfig = createV4SignerConfig();
+ V4SchemeSigner.SignerConfig v4SignerConfig = createV4SignerConfig();
V4SchemeSigner.generateV4Signature(dataSource, v4SignerConfig, outputFile);
} catch (InvalidKeyException | IOException | NoSuchAlgorithmException e) {
if (ignoreFailures) {
@@ -1181,7 +1190,7 @@ public class DefaultApkSignerEngine implements ApkSignerEngine {
throw new SignatureException("Missing V4 output streams.");
}
try {
- ApkSigningBlockUtils.SignerConfig v4SignerConfig = createV4SignerConfig();
+ V4SchemeSigner.SignerConfig v4SignerConfig = createV4SignerConfig();
Pair<V4Signature, byte[]> pair =
V4SchemeSigner.generateV4Signature(dataSource, v4SignerConfig);
pair.getFirst().writeTo(sigOutput);
diff --git a/src/main/java/com/android/apksig/internal/apk/v4/V4SchemeSigner.java b/src/main/java/com/android/apksig/internal/apk/v4/V4SchemeSigner.java
index 74aa629..2f9ecb3 100644
--- a/src/main/java/com/android/apksig/internal/apk/v4/V4SchemeSigner.java
+++ b/src/main/java/com/android/apksig/internal/apk/v4/V4SchemeSigner.java
@@ -22,11 +22,11 @@ import static com.android.apksig.internal.apk.v3.V3SchemeConstants.APK_SIGNATURE
import com.android.apksig.apk.ApkUtils;
import com.android.apksig.internal.apk.ApkSigningBlockUtils;
-import com.android.apksig.internal.apk.ApkSigningBlockUtils.SignerConfig;
import com.android.apksig.internal.apk.ContentDigestAlgorithm;
import com.android.apksig.internal.apk.SignatureAlgorithm;
import com.android.apksig.internal.apk.SignatureInfo;
import com.android.apksig.internal.apk.v2.V2SchemeVerifier;
+import com.android.apksig.internal.apk.v3.V3SchemeConstants;
import com.android.apksig.internal.apk.v3.V3SchemeSigner;
import com.android.apksig.internal.apk.v3.V3SchemeVerifier;
import com.android.apksig.internal.util.Pair;
@@ -43,6 +43,7 @@ import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.SignatureException;
import java.security.cert.CertificateEncodingException;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
@@ -61,7 +62,6 @@ import java.util.Set;
* </p>
* (optional) verityTree: integer size prepended bytes of the verity hash tree.
* <p>
- * TODO(schfan): Add v4 unit tests
*/
public abstract class V4SchemeSigner {
/**
@@ -70,6 +70,23 @@ public abstract class V4SchemeSigner {
private V4SchemeSigner() {
}
+ public static class SignerConfig {
+ final public ApkSigningBlockUtils.SignerConfig v4Config;
+ final public ApkSigningBlockUtils.SignerConfig v41Config;
+
+ public SignerConfig(List<ApkSigningBlockUtils.SignerConfig> v4Configs,
+ List<ApkSigningBlockUtils.SignerConfig> v41Configs) throws InvalidKeyException {
+ if (v4Configs == null || v4Configs.size() != 1) {
+ throw new InvalidKeyException("Only accepting one signer config for V4 Signature.");
+ }
+ if (v41Configs != null && v41Configs.size() != 1) {
+ throw new InvalidKeyException("Only accepting one signer config for V4.1 Signature.");
+ }
+ this.v4Config = v4Configs.get(0);
+ this.v41Config = v41Configs != null ? v41Configs.get(0) : null;
+ }
+ }
+
/**
* Based on a public key, return a signing algorithm that supports verity.
*/
@@ -149,10 +166,10 @@ public abstract class V4SchemeSigner {
return Pair.of(signature, tree);
}
- private static V4Signature generateSignature(
- SignerConfig signerConfig,
+ private static V4Signature.SigningInfo generateSigningInfo(
+ ApkSigningBlockUtils.SignerConfig signerConfig,
V4Signature.HashingInfo hashingInfo,
- byte[] apkDigest, byte[] additionaData, long fileSize)
+ byte[] apkDigest, byte[] additionalData, long fileSize)
throws NoSuchAlgorithmException, InvalidKeyException, SignatureException,
CertificateEncodingException {
if (signerConfig.certificates.isEmpty()) {
@@ -169,7 +186,7 @@ public abstract class V4SchemeSigner {
final byte[] encodedCertificate = encodedCertificates.get(0);
final V4Signature.SigningInfo signingInfoNoSignature = new V4Signature.SigningInfo(apkDigest,
- encodedCertificate, additionaData, publicKey.getEncoded(), -1, null);
+ encodedCertificate, additionalData, publicKey.getEncoded(), -1, null);
final byte[] data = V4Signature.getSignedData(fileSize, hashingInfo,
signingInfoNoSignature);
@@ -184,12 +201,33 @@ public abstract class V4SchemeSigner {
final int signatureAlgorithmId = signatures.get(0).getFirst();
final byte[] signature = signatures.get(0).getSecond();
- final V4Signature.SigningInfo signingInfo = new V4Signature.SigningInfo(apkDigest,
- encodedCertificate, additionaData, publicKey.getEncoded(), signatureAlgorithmId,
+ return new V4Signature.SigningInfo(apkDigest,
+ encodedCertificate, additionalData, publicKey.getEncoded(), signatureAlgorithmId,
signature);
+ }
+
+ private static V4Signature generateSignature(
+ SignerConfig signerConfig,
+ V4Signature.HashingInfo hashingInfo,
+ byte[] apkDigest, byte[] additionalData, long fileSize)
+ throws NoSuchAlgorithmException, InvalidKeyException, SignatureException,
+ CertificateEncodingException {
+ final V4Signature.SigningInfo signingInfo = generateSigningInfo(signerConfig.v4Config,
+ hashingInfo, apkDigest, additionalData, fileSize);
+
+ final V4Signature.SigningInfos signingInfos;
+ if (signerConfig.v41Config != null) {
+ final V4Signature.SigningInfoBlock extSigningBlock = new V4Signature.SigningInfoBlock(
+ V3SchemeConstants.APK_SIGNATURE_SCHEME_V31_BLOCK_ID,
+ generateSigningInfo(signerConfig.v41Config, hashingInfo, apkDigest,
+ additionalData, fileSize).toByteArray());
+ signingInfos = new V4Signature.SigningInfos(signingInfo, extSigningBlock);
+ } else {
+ signingInfos = new V4Signature.SigningInfos(signingInfo);
+ }
return new V4Signature(V4Signature.CURRENT_VERSION, hashingInfo.toByteArray(),
- signingInfo.toByteArray());
+ signingInfos.toByteArray());
}
// Get digest by parsing the V2/V3-signed apk and choosing the first digest of supported type.
diff --git a/src/main/java/com/android/apksig/internal/apk/v4/V4SchemeVerifier.java b/src/main/java/com/android/apksig/internal/apk/v4/V4SchemeVerifier.java
index a6cd9db..c0a9013 100644
--- a/src/main/java/com/android/apksig/internal/apk/v4/V4SchemeVerifier.java
+++ b/src/main/java/com/android/apksig/internal/apk/v4/V4SchemeVerifier.java
@@ -90,20 +90,37 @@ public abstract class V4SchemeVerifier {
V4Signature.HashingInfo hashingInfo = V4Signature.HashingInfo.fromByteArray(
signature.hashingInfo);
- V4Signature.SigningInfo signingInfo = V4Signature.SigningInfo.fromByteArray(
- signature.signingInfo);
- final byte[] signedData = V4Signature.getSignedData(apk.size(), hashingInfo, signingInfo);
+ V4Signature.SigningInfos signingInfos = V4Signature.SigningInfos.fromByteArray(
+ signature.signingInfos);
- // First, verify the signature over signedData.
- ApkSigningBlockUtils.Result.SignerInfo signerInfo = parseAndVerifySignatureBlock(
- signingInfo, signedData);
- result.signers.add(signerInfo);
- if (result.containsErrors()) {
- return result;
+ final ApkSigningBlockUtils.Result.SignerInfo signerInfo;
+
+ // Verify the primary signature over signedData.
+ {
+ V4Signature.SigningInfo signingInfo = signingInfos.signingInfo;
+ final byte[] signedData = V4Signature.getSignedData(apk.size(), hashingInfo,
+ signingInfo);
+ signerInfo = parseAndVerifySignatureBlock(signingInfo, signedData);
+ result.signers.add(signerInfo);
+ if (result.containsErrors()) {
+ return result;
+ }
+ }
+
+ // Verify all subsequent signatures.
+ for (V4Signature.SigningInfoBlock signingInfoBlock : signingInfos.signingInfoBlocks) {
+ V4Signature.SigningInfo signingInfo = V4Signature.SigningInfo.fromByteArray(
+ signingInfoBlock.signingInfo);
+ final byte[] signedData = V4Signature.getSignedData(apk.size(), hashingInfo,
+ signingInfo);
+ result.signers.add(parseAndVerifySignatureBlock(signingInfo, signedData));
+ if (result.containsErrors()) {
+ return result;
+ }
}
- // Second, check if the root hash and the tree are correct.
+ // Check if the root hash and the tree are correct.
verifyRootHashAndTree(apk, signerInfo, hashingInfo.rawRootHash, tree);
if (!result.containsErrors()) {
result.verified = true;
diff --git a/src/main/java/com/android/apksig/internal/apk/v4/V4Signature.java b/src/main/java/com/android/apksig/internal/apk/v4/V4Signature.java
index deabe12..1eac5a2 100644
--- a/src/main/java/com/android/apksig/internal/apk/v4/V4Signature.java
+++ b/src/main/java/com/android/apksig/internal/apk/v4/V4Signature.java
@@ -22,6 +22,8 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.Arrays;
public class V4Signature {
public static final int CURRENT_VERSION = 2;
@@ -29,6 +31,8 @@ public class V4Signature {
public static final int HASHING_ALGORITHM_SHA256 = 1;
public static final byte LOG2_BLOCK_SIZE_4096_BYTES = 12;
+ public static final int MAX_SIGNING_INFOS_SIZE = 7168;
+
public static class HashingInfo {
public final int hashAlgorithm; // only 1 == SHA256 supported
public final byte log2BlockSize; // only 12 (block size 4096) supported now
@@ -82,7 +86,10 @@ public class V4Signature {
}
static SigningInfo fromByteArray(byte[] bytes) throws IOException {
- ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
+ return fromByteBuffer(ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN));
+ }
+
+ static SigningInfo fromByteBuffer(ByteBuffer buffer) throws IOException {
byte[] apkDigest = readBytes(buffer);
byte[] certificate = readBytes(buffer);
byte[] additionalData = readBytes(buffer);
@@ -108,14 +115,93 @@ public class V4Signature {
}
}
- public final int version; // Always 2 for now.
+ public static class SigningInfoBlock {
+ public final int blockId;
+ public final byte[] signingInfo;
+
+ public SigningInfoBlock(int blockId, byte[] signingInfo) {
+ this.blockId = blockId;
+ this.signingInfo = signingInfo;
+ }
+
+ static SigningInfoBlock fromByteBuffer(ByteBuffer buffer) throws IOException {
+ int blockId = buffer.getInt();
+ byte[] signingInfo = readBytes(buffer);
+ return new SigningInfoBlock(blockId, signingInfo);
+ }
+
+ byte[] toByteArray() {
+ final int size = 4/*blockId*/ + bytesSize(this.signingInfo);
+ ByteBuffer buffer = ByteBuffer.allocate(size).order(ByteOrder.LITTLE_ENDIAN);
+ buffer.putInt(this.blockId);
+ writeBytes(buffer, this.signingInfo);
+ return buffer.array();
+ }
+ }
+
+ public static class SigningInfos {
+ public final SigningInfo signingInfo;
+ public final SigningInfoBlock[] signingInfoBlocks;
+
+ public SigningInfos(SigningInfo signingInfo) {
+ this.signingInfo = signingInfo;
+ this.signingInfoBlocks = new SigningInfoBlock[0];
+ }
+
+ public SigningInfos(SigningInfo signingInfo, SigningInfoBlock... signingInfoBlocks) {
+ this.signingInfo = signingInfo;
+ this.signingInfoBlocks = signingInfoBlocks;
+ }
+
+ public static SigningInfos fromByteArray(byte[] bytes) throws IOException {
+ ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
+ SigningInfo signingInfo = SigningInfo.fromByteBuffer(buffer);
+ if (!buffer.hasRemaining()) {
+ return new SigningInfos(signingInfo);
+ }
+ ArrayList<SigningInfoBlock> signingInfoBlocks = new ArrayList<>(1);
+ while (buffer.hasRemaining()) {
+ signingInfoBlocks.add(SigningInfoBlock.fromByteBuffer(buffer));
+ }
+ return new SigningInfos(signingInfo,
+ signingInfoBlocks.toArray(new SigningInfoBlock[signingInfoBlocks.size()]));
+ }
+
+ byte[] toByteArray() {
+ byte[][] arrays = new byte[1 + this.signingInfoBlocks.length][];
+ arrays[0] = this.signingInfo.toByteArray();
+ int size = arrays[0].length;
+ for (int i = 0, isize = this.signingInfoBlocks.length; i < isize; ++i) {
+ arrays[i + 1] = this.signingInfoBlocks[i].toByteArray();
+ size += arrays[i + 1].length;
+ }
+ if (size > MAX_SIGNING_INFOS_SIZE) {
+ throw new IllegalArgumentException(
+ "Combined SigningInfos length exceeded limit of 7K: " + size);
+ }
+
+ // Combine all arrays into one.
+ byte[] result = Arrays.copyOf(arrays[0], size);
+ int offset = arrays[0].length;
+ for (int i = 0, isize = this.signingInfoBlocks.length; i < isize; ++i) {
+ System.arraycopy(arrays[i + 1], 0, result, offset, arrays[i + 1].length);
+ offset += arrays[i + 1].length;
+ }
+ return result;
+ }
+ }
+
+ // Always 2 for now.
+ public final int version;
public final byte[] hashingInfo;
- public final byte[] signingInfo; // Passed as-is to the kernel. Can be retrieved later.
+ // Can contain either SigningInfo or SigningInfo + one or multiple SigningInfoBlock.
+ // Passed as-is to the kernel. Can be retrieved later.
+ public final byte[] signingInfos;
- V4Signature(int version, byte[] hashingInfo, byte[] signingInfo) {
+ V4Signature(int version, byte[] hashingInfo, byte[] signingInfos) {
this.version = version;
this.hashingInfo = hashingInfo;
- this.signingInfo = signingInfo;
+ this.signingInfos = signingInfos;
}
static V4Signature readFrom(InputStream stream) throws IOException {
@@ -131,7 +217,7 @@ public class V4Signature {
public void writeTo(OutputStream stream) throws IOException {
writeIntLE(stream, this.version);
writeBytes(stream, this.hashingInfo);
- writeBytes(stream, this.signingInfo);
+ writeBytes(stream, this.signingInfos);
}
static byte[] getSignedData(long fileSize, HashingInfo hashingInfo, SigningInfo signingInfo) {
diff --git a/src/test/java/com/android/apksig/ApkSignerTest.java b/src/test/java/com/android/apksig/ApkSignerTest.java
index 430a022..efadd7c 100644
--- a/src/test/java/com/android/apksig/ApkSignerTest.java
+++ b/src/test/java/com/android/apksig/ApkSignerTest.java
@@ -1858,7 +1858,6 @@ public class ApkSignerTest {
}
@Test
- @Ignore("TODO(b/202011194): Re-enable once V4 supports V3.1")
public void testV4_rotationMinSdkVersionT_signatureHasOrigAndRotatedKey() throws Exception {
// When an APK is signed with a rotated key and the rotation-min-sdk-version X is set to T+,
// a V3.1 block will be signed with the rotated signing key targeting X and later, and
@@ -1881,7 +1880,7 @@ public class ApkSignerTest {
.setV2SigningEnabled(true)
.setV3SigningEnabled(true)
.setV4SigningEnabled(true)
- .setMinSdkVersionForRotation(AndroidSdkVersion.P)
+ .setMinSdkVersionForRotation(AndroidSdkVersion.T)
.setSigningCertificateLineage(lineage));
ApkVerifier.Result result = verify(signedApk, null);