Import of apksig using Copybara.
- 242732457 Sync apksig from AOSP to pick up the latest changes by kudasov <kudasov@google.com>
PiperOrigin-RevId: 242732457
Change-Id: I6aeaf6a3c81321335d99a1a0d62bd1097c314329
diff --git a/src/main/java/com/android/apksig/internal/apk/ApkSigningBlockUtils.java b/src/main/java/com/android/apksig/internal/apk/ApkSigningBlockUtils.java
index cc69af3..cfa9844 100644
--- a/src/main/java/com/android/apksig/internal/apk/ApkSigningBlockUtils.java
+++ b/src/main/java/com/android/apksig/internal/apk/ApkSigningBlockUtils.java
@@ -21,10 +21,16 @@
import com.android.apksig.apk.ApkFormatException;
import com.android.apksig.apk.ApkSigningBlockNotFoundException;
import com.android.apksig.apk.ApkUtils;
+import com.android.apksig.internal.asn1.Asn1BerParser;
+import com.android.apksig.internal.asn1.Asn1DecodingException;
+import com.android.apksig.internal.asn1.Asn1DerEncoder;
+import com.android.apksig.internal.asn1.Asn1EncodingException;
import com.android.apksig.internal.util.ByteBufferDataSource;
import com.android.apksig.internal.util.ChainedDataSource;
import com.android.apksig.internal.util.Pair;
import com.android.apksig.internal.util.VerityTreeBuilder;
+import com.android.apksig.internal.x509.RSAPublicKey;
+import com.android.apksig.internal.x509.SubjectPublicKeyInfo;
import com.android.apksig.internal.zip.ZipUtils;
import com.android.apksig.util.DataSink;
import com.android.apksig.util.DataSinks;
@@ -33,6 +39,7 @@
import com.android.apksig.util.RunnablesExecutor;
import java.io.IOException;
+import java.math.BigInteger;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
@@ -58,11 +65,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ForkJoinPool;
-import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicInteger;
-import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
@@ -775,6 +778,53 @@
byte[] encodedPublicKey = null;
if ("X.509".equals(publicKey.getFormat())) {
encodedPublicKey = publicKey.getEncoded();
+ // if the key is an RSA key check for a negative modulus
+ if ("RSA".equals(publicKey.getAlgorithm())) {
+ try {
+ // Parse the encoded public key into the separate elements of the
+ // SubjectPublicKeyInfo to obtain the SubjectPublicKey.
+ ByteBuffer encodedPublicKeyBuffer = ByteBuffer.wrap(encodedPublicKey);
+ SubjectPublicKeyInfo subjectPublicKeyInfo = Asn1BerParser.parse(
+ encodedPublicKeyBuffer, SubjectPublicKeyInfo.class);
+ // The SubjectPublicKey is encoded as a bit string within the
+ // SubjectPublicKeyInfo. The first byte of the encoding is the number of padding
+ // bits; store this and decode the rest of the bit string into the RSA modulus
+ // and exponent.
+ ByteBuffer subjectPublicKeyBuffer = subjectPublicKeyInfo.subjectPublicKey;
+ byte padding = subjectPublicKeyBuffer.get();
+ RSAPublicKey rsaPublicKey = Asn1BerParser.parse(subjectPublicKeyBuffer,
+ RSAPublicKey.class);
+ // if the modulus is negative then attempt to reencode it with a leading 0 sign
+ // byte.
+ if (rsaPublicKey.modulus.compareTo(BigInteger.ZERO) < 0) {
+ // A negative modulus indicates the leading bit in the integer is 1. Per
+ // ASN.1 encoding rules to encode a positive integer with the leading bit
+ // set to 1 a byte containing all zeros should precede the integer encoding.
+ byte[] encodedModulus = rsaPublicKey.modulus.toByteArray();
+ byte[] reencodedModulus = new byte[encodedModulus.length + 1];
+ reencodedModulus[0] = 0;
+ System.arraycopy(encodedModulus, 0, reencodedModulus, 1,
+ encodedModulus.length);
+ rsaPublicKey.modulus = new BigInteger(reencodedModulus);
+ // Once the modulus has been corrected reencode the RSAPublicKey, then
+ // restore the padding value in the bit string and reencode the entire
+ // SubjectPublicKeyInfo to be returned to the caller.
+ byte[] reencodedRSAPublicKey = Asn1DerEncoder.encode(rsaPublicKey);
+ byte[] reencodedSubjectPublicKey =
+ new byte[reencodedRSAPublicKey.length + 1];
+ reencodedSubjectPublicKey[0] = padding;
+ System.arraycopy(reencodedRSAPublicKey, 0, reencodedSubjectPublicKey, 1,
+ reencodedRSAPublicKey.length);
+ subjectPublicKeyInfo.subjectPublicKey = ByteBuffer.wrap(
+ reencodedSubjectPublicKey);
+ encodedPublicKey = Asn1DerEncoder.encode(subjectPublicKeyInfo);
+ }
+ } catch (Asn1DecodingException | Asn1EncodingException e) {
+ System.out.println("Caught a exception encoding the public key: " + e);
+ e.printStackTrace();
+ encodedPublicKey = null;
+ }
+ }
}
if (encodedPublicKey == null) {
try {
diff --git a/src/main/java/com/android/apksig/internal/apk/v2/V2SchemeSigner.java b/src/main/java/com/android/apksig/internal/apk/v2/V2SchemeSigner.java
index d8e4723..f325f8b 100644
--- a/src/main/java/com/android/apksig/internal/apk/v2/V2SchemeSigner.java
+++ b/src/main/java/com/android/apksig/internal/apk/v2/V2SchemeSigner.java
@@ -70,7 +70,7 @@
* protected by signatures inside the block.
*/
- private static final int APK_SIGNATURE_SCHEME_V2_BLOCK_ID = 0x7109871a;
+ public static final int APK_SIGNATURE_SCHEME_V2_BLOCK_ID = 0x7109871a;
/** Hidden constructor to prevent instantiation. */
private V2SchemeSigner() {}
diff --git a/src/main/java/com/android/apksig/internal/apk/v2/V2SchemeVerifier.java b/src/main/java/com/android/apksig/internal/apk/v2/V2SchemeVerifier.java
index 51c40bd..0ef74a6 100644
--- a/src/main/java/com/android/apksig/internal/apk/v2/V2SchemeVerifier.java
+++ b/src/main/java/com/android/apksig/internal/apk/v2/V2SchemeVerifier.java
@@ -370,7 +370,15 @@
return;
}
X509Certificate mainCertificate = result.certs.get(0);
- byte[] certificatePublicKeyBytes = mainCertificate.getPublicKey().getEncoded();
+ byte[] certificatePublicKeyBytes;
+ try {
+ certificatePublicKeyBytes = ApkSigningBlockUtils.encodePublicKey(
+ mainCertificate.getPublicKey());
+ } catch (InvalidKeyException e) {
+ System.out.println("Caught an exception encoding the public key: " + e);
+ e.printStackTrace();
+ certificatePublicKeyBytes = mainCertificate.getPublicKey().getEncoded();
+ }
if (!Arrays.equals(publicKeyBytes, certificatePublicKeyBytes)) {
result.addError(
Issue.V2_SIG_PUBLIC_KEY_MISMATCH_BETWEEN_CERTIFICATE_AND_SIGNATURES_RECORD,
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 16a6408..f263323 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
@@ -432,7 +432,14 @@
return;
}
X509Certificate mainCertificate = result.certs.get(0);
- byte[] certificatePublicKeyBytes = mainCertificate.getPublicKey().getEncoded();
+ byte[] certificatePublicKeyBytes;
+ try {
+ certificatePublicKeyBytes = ApkSigningBlockUtils.encodePublicKey(mainCertificate.getPublicKey());
+ } catch (InvalidKeyException e) {
+ System.out.println("Caught an exception encoding the public key: " + e);
+ e.printStackTrace();
+ certificatePublicKeyBytes = mainCertificate.getPublicKey().getEncoded();
+ }
if (!Arrays.equals(publicKeyBytes, certificatePublicKeyBytes)) {
result.addError(
Issue.V3_SIG_PUBLIC_KEY_MISMATCH_BETWEEN_CERTIFICATE_AND_SIGNATURES_RECORD,
diff --git a/src/main/java/com/android/apksig/internal/x509/RSAPublicKey.java b/src/main/java/com/android/apksig/internal/x509/RSAPublicKey.java
new file mode 100644
index 0000000..521e067
--- /dev/null
+++ b/src/main/java/com/android/apksig/internal/x509/RSAPublicKey.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.apksig.internal.x509;
+
+import com.android.apksig.internal.asn1.Asn1Class;
+import com.android.apksig.internal.asn1.Asn1Field;
+import com.android.apksig.internal.asn1.Asn1Type;
+
+import java.math.BigInteger;
+
+/**
+ * {@code RSAPublicKey} as specified in RFC 3279.
+ */
+@Asn1Class(type = Asn1Type.SEQUENCE)
+public class RSAPublicKey {
+ @Asn1Field(index = 0, type = Asn1Type.INTEGER)
+ public BigInteger modulus;
+
+ @Asn1Field(index = 1, type = Asn1Type.INTEGER)
+ public BigInteger publicExponent;
+}
diff --git a/src/main/java/com/android/apksig/util/RunnablesExecutor.java b/src/main/java/com/android/apksig/util/RunnablesExecutor.java
index 04ec1d8..4215810 100644
--- a/src/main/java/com/android/apksig/util/RunnablesExecutor.java
+++ b/src/main/java/com/android/apksig/util/RunnablesExecutor.java
@@ -17,7 +17,7 @@
package com.android.apksig.util;
public interface RunnablesExecutor {
- RunnablesExecutor SINGLE_THREADED = p -> p.getRunnable().run();
+ RunnablesExecutor SINGLE_THREADED = p -> p.createRunnable().run();
void execute(RunnablesProvider provider);
}
diff --git a/src/main/java/com/android/apksig/util/RunnablesProvider.java b/src/main/java/com/android/apksig/util/RunnablesProvider.java
index 5b7bad2..f96dcfe 100644
--- a/src/main/java/com/android/apksig/util/RunnablesProvider.java
+++ b/src/main/java/com/android/apksig/util/RunnablesProvider.java
@@ -17,5 +17,5 @@
package com.android.apksig.util;
public interface RunnablesProvider {
- Runnable getRunnable();
+ Runnable createRunnable();
}
diff --git a/src/test/java/com/android/apksig/ApkSignerTest.java b/src/test/java/com/android/apksig/ApkSignerTest.java
index 1434017..65d9149 100644
--- a/src/test/java/com/android/apksig/ApkSignerTest.java
+++ b/src/test/java/com/android/apksig/ApkSignerTest.java
@@ -21,8 +21,16 @@
import com.android.apksig.ApkVerifier.Issue;
import com.android.apksig.apk.ApkFormatException;
+import com.android.apksig.apk.ApkUtils;
+import com.android.apksig.internal.apk.ApkSigningBlockUtils;
+import com.android.apksig.internal.apk.SignatureInfo;
+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.ByteBufferDataSource;
import com.android.apksig.internal.util.Resources;
+import com.android.apksig.internal.x509.RSAPublicKey;
+import com.android.apksig.internal.x509.SubjectPublicKeyInfo;
import com.android.apksig.util.DataSinks;
import com.android.apksig.util.DataSource;
import com.android.apksig.util.DataSources;
@@ -44,6 +52,8 @@
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import java.math.BigInteger;
+
@RunWith(JUnit4.class)
public class ApkSignerTest {
@@ -59,6 +69,10 @@
private static final String SECOND_RSA_2048_SIGNER_RESOURCE_NAME = "rsa-2048_2";
private static final String THIRD_RSA_2048_SIGNER_RESOURCE_NAME = "rsa-2048_3";
+ // This is the same cert as above with the modulus reencoded to remove the leading 0 sign bit.
+ private static final String FIRST_RSA_2048_SIGNER_CERT_WITH_NEGATIVE_MODULUS =
+ "rsa-2048_negmod.x509.der";
+
private static final String LINEAGE_RSA_2048_2_SIGNERS_RESOURCE_NAME =
"rsa-2048-lineage-2-signers";
@@ -806,6 +820,83 @@
lineageFromApk.isSignerInLineage((secondSigner)));
}
+ @Test
+ public void testPublicKeyHasPositiveModulusAfterSigning() throws Exception {
+ // The V2 and V3 signature schemes include the public key from the certificate in the
+ // signing block. If a certificate with an RSAPublicKey is improperly encoded with a
+ // negative modulus this was previously written to the signing block as is and failed on
+ // device verification since on device the public key in the certificate was reencoded with
+ // the correct encoding for the modulus. This test uses an improperly encoded certificate to
+ // sign an APK and verifies that the public key in the signing block is corrected with a
+ // positive modulus to allow on device installs / updates.
+ List<ApkSigner.SignerConfig> signersList = Collections.singletonList(
+ getDefaultSignerConfigFromResources(FIRST_RSA_2048_SIGNER_RESOURCE_NAME,
+ FIRST_RSA_2048_SIGNER_CERT_WITH_NEGATIVE_MODULUS));
+ DataSource signedApk = sign("original.apk", new ApkSigner.Builder(signersList)
+ .setV1SigningEnabled(true)
+ .setV2SigningEnabled(true)
+ .setV3SigningEnabled(true));
+ RSAPublicKey v2PublicKey = getRSAPublicKeyFromSigningBlock(signedApk,
+ ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V2);
+ assertTrue("The modulus in the public key in the V2 signing block must not be negative",
+ v2PublicKey.modulus.compareTo(BigInteger.ZERO) > 0);
+ RSAPublicKey v3PublicKey = getRSAPublicKeyFromSigningBlock(signedApk,
+ ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V3);
+ assertTrue("The modulus in the public key in the V3 signing block must not be negative",
+ v3PublicKey.modulus.compareTo(BigInteger.ZERO) > 0);
+ }
+
+ private RSAPublicKey getRSAPublicKeyFromSigningBlock(DataSource apk, int signatureVersionId)
+ throws Exception {
+ int signatureVersionBlockId;
+ switch (signatureVersionId) {
+ case ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V2:
+ signatureVersionBlockId = V2SchemeSigner.APK_SIGNATURE_SCHEME_V2_BLOCK_ID;
+ break;
+ case ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V3:
+ signatureVersionBlockId = V3SchemeSigner.APK_SIGNATURE_SCHEME_V3_BLOCK_ID;
+ break;
+ default:
+ throw new Exception(
+ "Invalid signature version ID specified: " + signatureVersionId);
+ }
+ ApkUtils.ZipSections zipSections = ApkUtils.findZipSections(apk);
+ ApkSigningBlockUtils.Result result = new ApkSigningBlockUtils.Result(
+ signatureVersionId);
+ SignatureInfo signatureInfo = ApkSigningBlockUtils.findSignature(apk, zipSections,
+ signatureVersionBlockId, result);
+ // FORMAT:
+ // * length prefixed sequence of length prefixed signers
+ // * length-prefixed signed data
+ // * V3+ only - minSDK (uint32)
+ // * V3+ only - maxSDK (uint32)
+ // * length-prefixed sequence of length-prefixed signatures:
+ // * length-prefixed bytes: public key (X.509 SubjectPublicKeyInfo, ASN.1 DER encoded)
+ ByteBuffer signers = ApkSigningBlockUtils.getLengthPrefixedSlice(
+ signatureInfo.signatureBlock);
+ ByteBuffer signer = ApkSigningBlockUtils.getLengthPrefixedSlice(signers);
+ // Since all the data is read from the signer block the signedData and signatures are
+ // discarded.
+ ApkSigningBlockUtils.getLengthPrefixedSlice(signer);
+ // For V3+ signature version IDs discard the min / max SDKs as well
+ if (signatureVersionId >= ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V3) {
+ signer.getInt();
+ signer.getInt();
+ }
+ ApkSigningBlockUtils.getLengthPrefixedSlice(signer);
+ ByteBuffer publicKey = ApkSigningBlockUtils.getLengthPrefixedSlice(signer);
+ SubjectPublicKeyInfo subjectPublicKeyInfo = Asn1BerParser.parse(publicKey,
+ SubjectPublicKeyInfo.class);
+ ByteBuffer subjectPublicKeyBuffer = subjectPublicKeyInfo.subjectPublicKey;
+ // The SubjectPublicKey is stored as a bit string in the SubjectPublicKeyInfo with the first
+ // byte indicating the number of padding bits in the public key. Read this first byte to
+ // allow parsing the rest of the RSAPublicKey as a sequence.
+ subjectPublicKeyBuffer.get();
+ RSAPublicKey rsaPublicKey = Asn1BerParser.parse(subjectPublicKeyBuffer,
+ RSAPublicKey.class);
+ return rsaPublicKey;
+ }
+
/**
* Asserts that signing the specified golden input file using the provided signing
* configuration produces output identical to the specified golden output file.
@@ -903,4 +994,13 @@
Resources.toCertificateChain(ApkSignerTest.class, keyNameInResources + ".x509.pem");
return new ApkSigner.SignerConfig.Builder(keyNameInResources, privateKey, certs).build();
}
+
+ private static ApkSigner.SignerConfig getDefaultSignerConfigFromResources(
+ String keyNameInResources, String certNameInResources) throws Exception {
+ PrivateKey privateKey = Resources.toPrivateKey(ApkSignerTest.class,
+ keyNameInResources + ".pk8");
+ List<X509Certificate> certs = Resources.toCertificateChain(ApkSignerTest.class,
+ certNameInResources);
+ return new ApkSigner.SignerConfig.Builder(keyNameInResources, privateKey, certs).build();
+ }
}
diff --git a/src/test/java/com/android/apksig/internal/apk/ApkSigningBlockUtilsTest.java b/src/test/java/com/android/apksig/internal/apk/ApkSigningBlockUtilsTest.java
index 7eb7c9b..1ee3466 100644
--- a/src/test/java/com/android/apksig/internal/apk/ApkSigningBlockUtilsTest.java
+++ b/src/test/java/com/android/apksig/internal/apk/ApkSigningBlockUtilsTest.java
@@ -101,7 +101,7 @@
List<Future<?>> jobs = new ArrayList<>(jobCount);
for (int i = 0; i < jobCount; i++) {
- jobs.add(forkJoinPool.submit(provider.getRunnable()));
+ jobs.add(forkJoinPool.submit(provider.createRunnable()));
}
try {
diff --git a/src/test/resources/com/android/apksig/rsa-2048_negmod.x509.der b/src/test/resources/com/android/apksig/rsa-2048_negmod.x509.der
new file mode 100644
index 0000000..ba7e78e
--- /dev/null
+++ b/src/test/resources/com/android/apksig/rsa-2048_negmod.x509.der
Binary files differ