Merge "Modify source stamp format" into rvc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
592da34c0b
@@ -420,6 +420,7 @@ final class ApkSigningBlockUtils {
|
||||
static final int CONTENT_DIGEST_CHUNKED_SHA256 = 1;
|
||||
static final int CONTENT_DIGEST_CHUNKED_SHA512 = 2;
|
||||
static final int CONTENT_DIGEST_VERITY_CHUNKED_SHA256 = 3;
|
||||
static final int CONTENT_DIGEST_SHA256 = 4;
|
||||
|
||||
private static final int[] V4_CONTENT_DIGEST_ALGORITHMS =
|
||||
{CONTENT_DIGEST_CHUNKED_SHA512, CONTENT_DIGEST_VERITY_CHUNKED_SHA256,
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package android.util.apk;
|
||||
|
||||
import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_SHA256;
|
||||
import static android.util.apk.ApkSigningBlockUtils.compareSignatureAlgorithm;
|
||||
import static android.util.apk.ApkSigningBlockUtils.getLengthPrefixedSlice;
|
||||
import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmContentDigestAlgorithm;
|
||||
@@ -27,12 +28,10 @@ import android.util.Pair;
|
||||
import android.util.Slog;
|
||||
import android.util.jar.StrictJarFile;
|
||||
|
||||
import libcore.io.IoUtils;
|
||||
import libcore.io.Streams;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.nio.BufferUnderflowException;
|
||||
import java.nio.ByteBuffer;
|
||||
@@ -49,11 +48,13 @@ import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.security.spec.AlgorithmParameterSpec;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.zip.ZipEntry;
|
||||
|
||||
/**
|
||||
@@ -74,7 +75,11 @@ public abstract class SourceStampVerifier {
|
||||
|
||||
private static final int APK_SIGNATURE_SCHEME_V2_BLOCK_ID = 0x7109871a;
|
||||
private static final int APK_SIGNATURE_SCHEME_V3_BLOCK_ID = 0xf05368c0;
|
||||
private static final int SOURCE_STAMP_BLOCK_ID = 0x2b09189e;
|
||||
private static final int SOURCE_STAMP_BLOCK_ID = 0x6dff800d;
|
||||
|
||||
private static final int VERSION_JAR_SIGNATURE_SCHEME = 1;
|
||||
private static final int VERSION_APK_SIGNATURE_SCHEME_V2 = 2;
|
||||
private static final int VERSION_APK_SIGNATURE_SCHEME_V3 = 3;
|
||||
|
||||
/** Name of the SourceStamp certificate hash ZIP entry in APKs. */
|
||||
private static final String SOURCE_STAMP_CERTIFICATE_HASH_ZIP_ENTRY_NAME = "stamp-cert-sha256";
|
||||
@@ -115,7 +120,8 @@ public abstract class SourceStampVerifier {
|
||||
// SourceStamp present.
|
||||
return SourceStampVerificationResult.notPresent();
|
||||
}
|
||||
return verify(apk, sourceStampCertificateDigest);
|
||||
byte[] manifestBytes = getManifestBytes(apkJar);
|
||||
return verify(apk, sourceStampCertificateDigest, manifestBytes);
|
||||
} catch (IOException e) {
|
||||
// Any exception in reading the APK returns a non-present SourceStamp outcome
|
||||
// without affecting the outcome of any of the other signature schemes.
|
||||
@@ -126,22 +132,71 @@ public abstract class SourceStampVerifier {
|
||||
}
|
||||
|
||||
private static SourceStampVerificationResult verify(
|
||||
RandomAccessFile apk, byte[] sourceStampCertificateDigest) {
|
||||
RandomAccessFile apk, byte[] sourceStampCertificateDigest, byte[] manifestBytes) {
|
||||
try {
|
||||
SignatureInfo signatureInfo =
|
||||
ApkSigningBlockUtils.findSignature(apk, SOURCE_STAMP_BLOCK_ID);
|
||||
Map<Integer, byte[]> apkContentDigests = getApkContentDigests(apk);
|
||||
return verify(signatureInfo, apkContentDigests, sourceStampCertificateDigest);
|
||||
} catch (IOException | SignatureNotFoundException e) {
|
||||
Map<Integer, Map<Integer, byte[]>> signatureSchemeApkContentDigests =
|
||||
getSignatureSchemeApkContentDigests(apk, manifestBytes);
|
||||
return verify(
|
||||
signatureInfo,
|
||||
getSignatureSchemeDigests(signatureSchemeApkContentDigests),
|
||||
sourceStampCertificateDigest);
|
||||
} catch (IOException | SignatureNotFoundException | RuntimeException e) {
|
||||
return SourceStampVerificationResult.notVerified();
|
||||
}
|
||||
}
|
||||
|
||||
private static SourceStampVerificationResult verify(
|
||||
SignatureInfo signatureInfo,
|
||||
Map<Integer, byte[]> apkContentDigests,
|
||||
Map<Integer, byte[]> signatureSchemeDigests,
|
||||
byte[] sourceStampCertificateDigest)
|
||||
throws SecurityException, IOException {
|
||||
ByteBuffer sourceStampBlock = signatureInfo.signatureBlock;
|
||||
ByteBuffer sourceStampBlockData =
|
||||
ApkSigningBlockUtils.getLengthPrefixedSlice(sourceStampBlock);
|
||||
|
||||
X509Certificate sourceStampCertificate =
|
||||
verifySourceStampCertificate(sourceStampBlockData, sourceStampCertificateDigest);
|
||||
|
||||
// Parse signed signature schemes block.
|
||||
ByteBuffer signedSignatureSchemes =
|
||||
ApkSigningBlockUtils.getLengthPrefixedSlice(sourceStampBlockData);
|
||||
Map<Integer, ByteBuffer> signedSignatureSchemeData = new HashMap<>();
|
||||
while (signedSignatureSchemes.hasRemaining()) {
|
||||
ByteBuffer signedSignatureScheme =
|
||||
ApkSigningBlockUtils.getLengthPrefixedSlice(signedSignatureSchemes);
|
||||
int signatureSchemeId = signedSignatureScheme.getInt();
|
||||
signedSignatureSchemeData.put(signatureSchemeId, signedSignatureScheme);
|
||||
}
|
||||
|
||||
for (Map.Entry<Integer, byte[]> signatureSchemeDigest : signatureSchemeDigests.entrySet()) {
|
||||
if (!signedSignatureSchemeData.containsKey(signatureSchemeDigest.getKey())) {
|
||||
throw new SecurityException(
|
||||
String.format(
|
||||
"No signatures found for signature scheme %d",
|
||||
signatureSchemeDigest.getKey()));
|
||||
}
|
||||
verifySourceStampSignature(
|
||||
signedSignatureSchemeData.get(signatureSchemeDigest.getKey()),
|
||||
sourceStampCertificate,
|
||||
signatureSchemeDigest.getValue());
|
||||
}
|
||||
|
||||
return SourceStampVerificationResult.verified(sourceStampCertificate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the SourceStamp certificate found in the signing block is the same as the SourceStamp
|
||||
* certificate found in the APK. It returns the verified certificate.
|
||||
*
|
||||
* @param sourceStampBlockData the source stamp block in the APK signing block which contains
|
||||
* the certificate used to sign the stamp digests.
|
||||
* @param sourceStampCertificateDigest the source stamp certificate digest found in the APK.
|
||||
*/
|
||||
private static X509Certificate verifySourceStampCertificate(
|
||||
ByteBuffer sourceStampBlockData, byte[] sourceStampCertificateDigest)
|
||||
throws IOException {
|
||||
CertificateFactory certFactory;
|
||||
try {
|
||||
certFactory = CertificateFactory.getInstance("X.509");
|
||||
@@ -149,17 +204,6 @@ public abstract class SourceStampVerifier {
|
||||
throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e);
|
||||
}
|
||||
|
||||
List<Pair<Integer, byte[]>> digests =
|
||||
apkContentDigests.entrySet().stream()
|
||||
.sorted(Map.Entry.comparingByKey())
|
||||
.map(e -> Pair.create(e.getKey(), e.getValue()))
|
||||
.collect(Collectors.toList());
|
||||
byte[] digestBytes = encodeApkContentDigests(digests);
|
||||
|
||||
ByteBuffer sourceStampBlock = signatureInfo.signatureBlock;
|
||||
ByteBuffer sourceStampBlockData =
|
||||
ApkSigningBlockUtils.getLengthPrefixedSlice(sourceStampBlock);
|
||||
|
||||
// Parse the SourceStamp certificate.
|
||||
byte[] sourceStampEncodedCertificate =
|
||||
ApkSigningBlockUtils.readLengthPrefixedByteArray(sourceStampBlockData);
|
||||
@@ -172,24 +216,30 @@ public abstract class SourceStampVerifier {
|
||||
} catch (CertificateException e) {
|
||||
throw new SecurityException("Failed to decode certificate", e);
|
||||
}
|
||||
sourceStampCertificate =
|
||||
new VerbatimX509Certificate(sourceStampCertificate, sourceStampEncodedCertificate);
|
||||
|
||||
// Verify the SourceStamp certificate found in the signing block is the same as the
|
||||
// SourceStamp certificate found in the APK.
|
||||
try {
|
||||
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
|
||||
messageDigest.update(sourceStampEncodedCertificate);
|
||||
byte[] sourceStampBlockCertificateDigest = messageDigest.digest();
|
||||
if (!Arrays.equals(sourceStampCertificateDigest, sourceStampBlockCertificateDigest)) {
|
||||
throw new SecurityException("Certificate mismatch between APK and signature block");
|
||||
}
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new SecurityException("Failed to find SHA-256", e);
|
||||
byte[] sourceStampBlockCertificateDigest =
|
||||
computeSha256Digest(sourceStampEncodedCertificate);
|
||||
if (!Arrays.equals(sourceStampCertificateDigest, sourceStampBlockCertificateDigest)) {
|
||||
throw new SecurityException("Certificate mismatch between APK and signature block");
|
||||
}
|
||||
|
||||
return new VerbatimX509Certificate(sourceStampCertificate, sourceStampEncodedCertificate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the SourceStamp signature found in the signing block is signed by the SourceStamp
|
||||
* certificate found in the APK.
|
||||
*
|
||||
* @param signedBlockData the source stamp block in the APK signing block which contains the
|
||||
* stamp signed digests.
|
||||
* @param sourceStampCertificate the source stamp certificate used to sign the stamp digests.
|
||||
* @param digest the digest to be verified being signed by the source stamp certificate.
|
||||
*/
|
||||
private static void verifySourceStampSignature(
|
||||
ByteBuffer signedBlockData, X509Certificate sourceStampCertificate, byte[] digest)
|
||||
throws IOException {
|
||||
// Parse the signatures block and identify supported signatures
|
||||
ByteBuffer signatures = ApkSigningBlockUtils.getLengthPrefixedSlice(sourceStampBlockData);
|
||||
ByteBuffer signatures = ApkSigningBlockUtils.getLengthPrefixedSlice(signedBlockData);
|
||||
int signatureCount = 0;
|
||||
int bestSigAlgorithm = -1;
|
||||
byte[] bestSigAlgorithmSignatureBytes = null;
|
||||
@@ -235,7 +285,7 @@ public abstract class SourceStampVerifier {
|
||||
if (jcaSignatureAlgorithmParams != null) {
|
||||
sig.setParameter(jcaSignatureAlgorithmParams);
|
||||
}
|
||||
sig.update(digestBytes);
|
||||
sig.update(digest);
|
||||
sigVerified = sig.verify(bestSigAlgorithmSignatureBytes);
|
||||
} catch (InvalidKeyException
|
||||
| InvalidAlgorithmParameterException
|
||||
@@ -247,27 +297,44 @@ public abstract class SourceStampVerifier {
|
||||
if (!sigVerified) {
|
||||
throw new SecurityException(jcaSignatureAlgorithm + " signature did not verify");
|
||||
}
|
||||
|
||||
return SourceStampVerificationResult.verified(sourceStampCertificate);
|
||||
}
|
||||
|
||||
private static Map<Integer, byte[]> getApkContentDigests(RandomAccessFile apk)
|
||||
throws IOException, SignatureNotFoundException {
|
||||
// Retrieve APK content digests in V3 signing block. If a V3 signature is not found, the APK
|
||||
// content digests would be re-tried from V2 signature.
|
||||
private static Map<Integer, Map<Integer, byte[]>> getSignatureSchemeApkContentDigests(
|
||||
RandomAccessFile apk, byte[] manifestBytes) throws IOException {
|
||||
Map<Integer, Map<Integer, byte[]>> signatureSchemeApkContentDigests = new HashMap<>();
|
||||
|
||||
// Retrieve APK content digests in V3 signing block.
|
||||
try {
|
||||
SignatureInfo v3SignatureInfo =
|
||||
ApkSigningBlockUtils.findSignature(apk, APK_SIGNATURE_SCHEME_V3_BLOCK_ID);
|
||||
return getApkContentDigestsFromSignatureBlock(v3SignatureInfo.signatureBlock);
|
||||
signatureSchemeApkContentDigests.put(
|
||||
VERSION_APK_SIGNATURE_SCHEME_V3,
|
||||
getApkContentDigestsFromSignatureBlock(v3SignatureInfo.signatureBlock));
|
||||
} catch (SignatureNotFoundException e) {
|
||||
// It's fine not to find a V3 signature.
|
||||
}
|
||||
|
||||
// Retrieve APK content digests in V2 signing block. If a V2 signature is not found, the
|
||||
// process of retrieving APK content digests stops, and the stamp is considered un-verified.
|
||||
SignatureInfo v2SignatureInfo =
|
||||
ApkSigningBlockUtils.findSignature(apk, APK_SIGNATURE_SCHEME_V2_BLOCK_ID);
|
||||
return getApkContentDigestsFromSignatureBlock(v2SignatureInfo.signatureBlock);
|
||||
// Retrieve APK content digests in V2 signing block.
|
||||
try {
|
||||
SignatureInfo v2SignatureInfo =
|
||||
ApkSigningBlockUtils.findSignature(apk, APK_SIGNATURE_SCHEME_V2_BLOCK_ID);
|
||||
signatureSchemeApkContentDigests.put(
|
||||
VERSION_APK_SIGNATURE_SCHEME_V2,
|
||||
getApkContentDigestsFromSignatureBlock(v2SignatureInfo.signatureBlock));
|
||||
} catch (SignatureNotFoundException e) {
|
||||
// It's fine not to find a V2 signature.
|
||||
}
|
||||
|
||||
// Retrieve manifest digest.
|
||||
if (manifestBytes != null) {
|
||||
Map<Integer, byte[]> jarSignatureSchemeApkContentDigests = new HashMap<>();
|
||||
jarSignatureSchemeApkContentDigests.put(
|
||||
CONTENT_DIGEST_SHA256, computeSha256Digest(manifestBytes));
|
||||
signatureSchemeApkContentDigests.put(
|
||||
VERSION_JAR_SIGNATURE_SCHEME, jarSignatureSchemeApkContentDigests);
|
||||
}
|
||||
|
||||
return signatureSchemeApkContentDigests;
|
||||
}
|
||||
|
||||
private static Map<Integer, byte[]> getApkContentDigestsFromSignatureBlock(
|
||||
@@ -289,27 +356,45 @@ public abstract class SourceStampVerifier {
|
||||
return apkContentDigests;
|
||||
}
|
||||
|
||||
private static byte[] getSourceStampCertificateDigest(StrictJarFile apkJar) throws IOException {
|
||||
InputStream inputStream = null;
|
||||
try {
|
||||
ZipEntry zipEntry = apkJar.findEntry(SOURCE_STAMP_CERTIFICATE_HASH_ZIP_ENTRY_NAME);
|
||||
if (zipEntry == null) {
|
||||
// SourceStamp certificate hash file not found, which means that there is not
|
||||
// SourceStamp present.
|
||||
return null;
|
||||
}
|
||||
inputStream = apkJar.getInputStream(zipEntry);
|
||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
|
||||
// Trying to read the certificate digest, which should be less than 1024 bytes.
|
||||
byte[] buffer = new byte[1024];
|
||||
int count = inputStream.read(buffer, 0, buffer.length);
|
||||
byteArrayOutputStream.write(buffer, 0, count);
|
||||
|
||||
return byteArrayOutputStream.toByteArray();
|
||||
} finally {
|
||||
IoUtils.closeQuietly(inputStream);
|
||||
private static Map<Integer, byte[]> getSignatureSchemeDigests(
|
||||
Map<Integer, Map<Integer, byte[]>> signatureSchemeApkContentDigests) {
|
||||
Map<Integer, byte[]> digests = new HashMap<>();
|
||||
for (Map.Entry<Integer, Map<Integer, byte[]>> signatureSchemeApkContentDigest :
|
||||
signatureSchemeApkContentDigests.entrySet()) {
|
||||
List<Pair<Integer, byte[]>> apkDigests =
|
||||
getApkDigests(signatureSchemeApkContentDigest.getValue());
|
||||
digests.put(
|
||||
signatureSchemeApkContentDigest.getKey(), encodeApkContentDigests(apkDigests));
|
||||
}
|
||||
return digests;
|
||||
}
|
||||
|
||||
private static List<Pair<Integer, byte[]>> getApkDigests(
|
||||
Map<Integer, byte[]> apkContentDigests) {
|
||||
List<Pair<Integer, byte[]>> digests = new ArrayList<>();
|
||||
for (Map.Entry<Integer, byte[]> apkContentDigest : apkContentDigests.entrySet()) {
|
||||
digests.add(Pair.create(apkContentDigest.getKey(), apkContentDigest.getValue()));
|
||||
}
|
||||
digests.sort(Comparator.comparing(pair -> pair.first));
|
||||
return digests;
|
||||
}
|
||||
|
||||
private static byte[] getSourceStampCertificateDigest(StrictJarFile apkJar) throws IOException {
|
||||
ZipEntry zipEntry = apkJar.findEntry(SOURCE_STAMP_CERTIFICATE_HASH_ZIP_ENTRY_NAME);
|
||||
if (zipEntry == null) {
|
||||
// SourceStamp certificate hash file not found, which means that there is not
|
||||
// SourceStamp present.
|
||||
return null;
|
||||
}
|
||||
return Streams.readFully(apkJar.getInputStream(zipEntry));
|
||||
}
|
||||
|
||||
private static byte[] getManifestBytes(StrictJarFile apkJar) throws IOException {
|
||||
ZipEntry zipEntry = apkJar.findEntry(JarFile.MANIFEST_NAME);
|
||||
if (zipEntry == null) {
|
||||
return null;
|
||||
}
|
||||
return Streams.readFully(apkJar.getInputStream(zipEntry));
|
||||
}
|
||||
|
||||
private static byte[] encodeApkContentDigests(List<Pair<Integer, byte[]>> apkContentDigests) {
|
||||
@@ -329,6 +414,16 @@ public abstract class SourceStampVerifier {
|
||||
return result.array();
|
||||
}
|
||||
|
||||
private static byte[] computeSha256Digest(byte[] input) {
|
||||
try {
|
||||
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
|
||||
messageDigest.update(input);
|
||||
return messageDigest.digest();
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException("Failed to find SHA-256", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void closeApkJar(StrictJarFile apkJar) {
|
||||
try {
|
||||
if (apkJar == null) {
|
||||
|
||||
12
core/java/android/util/apk/TEST_MAPPING
Normal file
12
core/java/android/util/apk/TEST_MAPPING
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"presubmit": [
|
||||
{
|
||||
"name": "FrameworksCoreTests",
|
||||
"options": [
|
||||
{
|
||||
"include-filter": "android.util.apk.SourceStampVerifierTest"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -26,8 +26,11 @@ import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import libcore.io.Streams;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
@@ -46,8 +49,20 @@ import java.util.zip.ZipFile;
|
||||
@RunWith(JUnit4.class)
|
||||
public class SourceStampVerifierTest {
|
||||
|
||||
private final Context mContext =
|
||||
InstrumentationRegistry.getInstrumentation().getTargetContext();
|
||||
private final Context mContext = ApplicationProvider.getApplicationContext();
|
||||
|
||||
private File mPrimaryApk;
|
||||
private File mSecondaryApk;
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
if (mPrimaryApk != null) {
|
||||
mPrimaryApk.delete();
|
||||
}
|
||||
if (mSecondaryApk != null) {
|
||||
mSecondaryApk.delete();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSourceStamp_noStamp() throws Exception {
|
||||
@@ -63,17 +78,11 @@ public class SourceStampVerifierTest {
|
||||
|
||||
@Test
|
||||
public void testSourceStamp_correctSignature() throws Exception {
|
||||
File testApk = getApk("SourceStampVerifierTest/valid-stamp.apk");
|
||||
ZipFile apkZipFile = new ZipFile(testApk);
|
||||
ZipEntry stampCertZipEntry = apkZipFile.getEntry("stamp-cert-sha256");
|
||||
int size = (int) stampCertZipEntry.getSize();
|
||||
byte[] expectedStampCertHash = new byte[size];
|
||||
try (InputStream inputStream = apkZipFile.getInputStream(stampCertZipEntry)) {
|
||||
inputStream.read(expectedStampCertHash);
|
||||
}
|
||||
mPrimaryApk = getApk("SourceStampVerifierTest/valid-stamp.apk");
|
||||
byte[] expectedStampCertHash = getSourceStampCertificateHashFromApk(mPrimaryApk);
|
||||
|
||||
SourceStampVerificationResult result =
|
||||
SourceStampVerifier.verify(testApk.getAbsolutePath());
|
||||
SourceStampVerifier.verify(mPrimaryApk.getAbsolutePath());
|
||||
|
||||
assertTrue(result.isPresent());
|
||||
assertTrue(result.isVerified());
|
||||
@@ -85,10 +94,10 @@ public class SourceStampVerifierTest {
|
||||
|
||||
@Test
|
||||
public void testSourceStamp_signatureMissing() throws Exception {
|
||||
File testApk = getApk("SourceStampVerifierTest/stamp-without-block.apk");
|
||||
mPrimaryApk = getApk("SourceStampVerifierTest/stamp-without-block.apk");
|
||||
|
||||
SourceStampVerificationResult result =
|
||||
SourceStampVerifier.verify(testApk.getAbsolutePath());
|
||||
SourceStampVerifier.verify(mPrimaryApk.getAbsolutePath());
|
||||
|
||||
assertTrue(result.isPresent());
|
||||
assertFalse(result.isVerified());
|
||||
@@ -97,10 +106,10 @@ public class SourceStampVerifierTest {
|
||||
|
||||
@Test
|
||||
public void testSourceStamp_certificateMismatch() throws Exception {
|
||||
File testApk = getApk("SourceStampVerifierTest/stamp-certificate-mismatch.apk");
|
||||
mPrimaryApk = getApk("SourceStampVerifierTest/stamp-certificate-mismatch.apk");
|
||||
|
||||
SourceStampVerificationResult result =
|
||||
SourceStampVerifier.verify(testApk.getAbsolutePath());
|
||||
SourceStampVerifier.verify(mPrimaryApk.getAbsolutePath());
|
||||
|
||||
assertTrue(result.isPresent());
|
||||
assertFalse(result.isVerified());
|
||||
@@ -108,11 +117,35 @@ public class SourceStampVerifierTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSourceStamp_apkHashMismatch() throws Exception {
|
||||
File testApk = getApk("SourceStampVerifierTest/stamp-apk-hash-mismatch.apk");
|
||||
public void testSourceStamp_apkHashMismatch_v1SignatureScheme() throws Exception {
|
||||
mPrimaryApk = getApk("SourceStampVerifierTest/stamp-apk-hash-mismatch-v1.apk");
|
||||
|
||||
SourceStampVerificationResult result =
|
||||
SourceStampVerifier.verify(testApk.getAbsolutePath());
|
||||
SourceStampVerifier.verify(mPrimaryApk.getAbsolutePath());
|
||||
|
||||
assertTrue(result.isPresent());
|
||||
assertFalse(result.isVerified());
|
||||
assertNull(result.getCertificate());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSourceStamp_apkHashMismatch_v2SignatureScheme() throws Exception {
|
||||
mPrimaryApk = getApk("SourceStampVerifierTest/stamp-apk-hash-mismatch-v2.apk");
|
||||
|
||||
SourceStampVerificationResult result =
|
||||
SourceStampVerifier.verify(mPrimaryApk.getAbsolutePath());
|
||||
|
||||
assertTrue(result.isPresent());
|
||||
assertFalse(result.isVerified());
|
||||
assertNull(result.getCertificate());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSourceStamp_apkHashMismatch_v3SignatureScheme() throws Exception {
|
||||
mPrimaryApk = getApk("SourceStampVerifierTest/stamp-apk-hash-mismatch-v3.apk");
|
||||
|
||||
SourceStampVerificationResult result =
|
||||
SourceStampVerifier.verify(mPrimaryApk.getAbsolutePath());
|
||||
|
||||
assertTrue(result.isPresent());
|
||||
assertFalse(result.isVerified());
|
||||
@@ -121,10 +154,10 @@ public class SourceStampVerifierTest {
|
||||
|
||||
@Test
|
||||
public void testSourceStamp_malformedSignature() throws Exception {
|
||||
File testApk = getApk("SourceStampVerifierTest/stamp-malformed-signature.apk");
|
||||
mPrimaryApk = getApk("SourceStampVerifierTest/stamp-malformed-signature.apk");
|
||||
|
||||
SourceStampVerificationResult result =
|
||||
SourceStampVerifier.verify(testApk.getAbsolutePath());
|
||||
SourceStampVerifier.verify(mPrimaryApk.getAbsolutePath());
|
||||
|
||||
assertTrue(result.isPresent());
|
||||
assertFalse(result.isVerified());
|
||||
@@ -133,21 +166,14 @@ public class SourceStampVerifierTest {
|
||||
|
||||
@Test
|
||||
public void testSourceStamp_multiApk_validStamps() throws Exception {
|
||||
File testApk1 = getApk("SourceStampVerifierTest/valid-stamp.apk");
|
||||
File testApk2 = getApk("SourceStampVerifierTest/valid-stamp.apk");
|
||||
ZipFile apkZipFile = new ZipFile(testApk1);
|
||||
ZipEntry stampCertZipEntry = apkZipFile.getEntry("stamp-cert-sha256");
|
||||
int size = (int) stampCertZipEntry.getSize();
|
||||
byte[] expectedStampCertHash = new byte[size];
|
||||
try (InputStream inputStream = apkZipFile.getInputStream(stampCertZipEntry)) {
|
||||
inputStream.read(expectedStampCertHash);
|
||||
}
|
||||
mPrimaryApk = getApk("SourceStampVerifierTest/valid-stamp.apk");
|
||||
mSecondaryApk = getApk("SourceStampVerifierTest/valid-stamp.apk");
|
||||
byte[] expectedStampCertHash = getSourceStampCertificateHashFromApk(mPrimaryApk);
|
||||
List<String> apkFiles = new ArrayList<>();
|
||||
apkFiles.add(testApk1.getAbsolutePath());
|
||||
apkFiles.add(testApk2.getAbsolutePath());
|
||||
apkFiles.add(mPrimaryApk.getAbsolutePath());
|
||||
apkFiles.add(mSecondaryApk.getAbsolutePath());
|
||||
|
||||
SourceStampVerificationResult result =
|
||||
SourceStampVerifier.verify(apkFiles);
|
||||
SourceStampVerificationResult result = SourceStampVerifier.verify(apkFiles);
|
||||
|
||||
assertTrue(result.isPresent());
|
||||
assertTrue(result.isVerified());
|
||||
@@ -159,14 +185,13 @@ public class SourceStampVerifierTest {
|
||||
|
||||
@Test
|
||||
public void testSourceStamp_multiApk_invalidStamps() throws Exception {
|
||||
File testApk1 = getApk("SourceStampVerifierTest/valid-stamp.apk");
|
||||
File testApk2 = getApk("SourceStampVerifierTest/stamp-apk-hash-mismatch.apk");
|
||||
mPrimaryApk = getApk("SourceStampVerifierTest/valid-stamp.apk");
|
||||
mSecondaryApk = getApk("SourceStampVerifierTest/stamp-apk-hash-mismatch-v3.apk");
|
||||
List<String> apkFiles = new ArrayList<>();
|
||||
apkFiles.add(testApk1.getAbsolutePath());
|
||||
apkFiles.add(testApk2.getAbsolutePath());
|
||||
apkFiles.add(mPrimaryApk.getAbsolutePath());
|
||||
apkFiles.add(mSecondaryApk.getAbsolutePath());
|
||||
|
||||
SourceStampVerificationResult result =
|
||||
SourceStampVerifier.verify(apkFiles);
|
||||
SourceStampVerificationResult result = SourceStampVerifier.verify(apkFiles);
|
||||
|
||||
assertTrue(result.isPresent());
|
||||
assertFalse(result.isVerified());
|
||||
@@ -174,10 +199,16 @@ public class SourceStampVerifierTest {
|
||||
}
|
||||
|
||||
private File getApk(String apkPath) throws IOException {
|
||||
File testApk = File.createTempFile("SourceStampApk", ".apk");
|
||||
File apk = File.createTempFile("SourceStampApk", ".apk");
|
||||
try (InputStream inputStream = mContext.getAssets().open(apkPath)) {
|
||||
Files.copy(inputStream, testApk.toPath(), REPLACE_EXISTING);
|
||||
Files.copy(inputStream, apk.toPath(), REPLACE_EXISTING);
|
||||
}
|
||||
return testApk;
|
||||
return apk;
|
||||
}
|
||||
|
||||
private byte[] getSourceStampCertificateHashFromApk(File apk) throws IOException {
|
||||
ZipFile apkZipFile = new ZipFile(apk);
|
||||
ZipEntry stampCertZipEntry = apkZipFile.getEntry("stamp-cert-sha256");
|
||||
return Streams.readFully(apkZipFile.getInputStream(stampCertZipEntry));
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user