import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512;
import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PSS_WITH_SHA256;
import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PSS_WITH_SHA512;
+import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_VERITY_DSA_WITH_SHA256;
+import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_VERITY_ECDSA_WITH_SHA256;
+import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_VERITY_RSA_PKCS1_V1_5_WITH_SHA256;
import static android.util.apk.ApkSigningBlockUtils.compareSignatureAlgorithm;
import static android.util.apk.ApkSigningBlockUtils.getContentDigestAlgorithmJcaDigestAlgorithm;
import static android.util.apk.ApkSigningBlockUtils.getLengthPrefixedSlice;
import android.util.Pair;
import java.io.ByteArrayInputStream;
-import java.io.FileDescriptor;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.BufferUnderflowException;
private static X509Certificate[][] verify(RandomAccessFile apk, boolean verifyIntegrity)
throws SignatureNotFoundException, SecurityException, IOException {
SignatureInfo signatureInfo = findSignature(apk);
- return verify(apk.getFD(), signatureInfo, verifyIntegrity);
+ return verify(apk, signatureInfo, verifyIntegrity);
}
/**
* against the APK file.
*/
private static X509Certificate[][] verify(
- FileDescriptor apkFileDescriptor,
+ RandomAccessFile apk,
SignatureInfo signatureInfo,
- boolean doVerifyIntegrity) throws SecurityException {
+ boolean doVerifyIntegrity) throws SecurityException, IOException {
int signerCount = 0;
Map<Integer, byte[]> contentDigests = new ArrayMap<>();
List<X509Certificate[]> signerCerts = new ArrayList<>();
}
if (doVerifyIntegrity) {
- ApkSigningBlockUtils.verifyIntegrity(
- contentDigests,
- apkFileDescriptor,
- signatureInfo.apkSigningBlockOffset,
- signatureInfo.centralDirOffset,
- signatureInfo.eocdOffset,
- signatureInfo.eocd);
+ ApkSigningBlockUtils.verifyIntegrity(contentDigests, apk, signatureInfo);
}
return signerCerts.toArray(new X509Certificate[signerCerts.size()][]);
}
return;
}
+
private static boolean isSupportedSignatureAlgorithm(int sigAlgorithm) {
switch (sigAlgorithm) {
case SIGNATURE_RSA_PSS_WITH_SHA256:
case SIGNATURE_ECDSA_WITH_SHA256:
case SIGNATURE_ECDSA_WITH_SHA512:
case SIGNATURE_DSA_WITH_SHA256:
+ case SIGNATURE_VERITY_RSA_PKCS1_V1_5_WITH_SHA256:
+ case SIGNATURE_VERITY_ECDSA_WITH_SHA256:
+ case SIGNATURE_VERITY_DSA_WITH_SHA256:
return true;
default:
return false;
import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512;
import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PSS_WITH_SHA256;
import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PSS_WITH_SHA512;
+import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_VERITY_DSA_WITH_SHA256;
+import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_VERITY_ECDSA_WITH_SHA256;
+import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_VERITY_RSA_PKCS1_V1_5_WITH_SHA256;
import static android.util.apk.ApkSigningBlockUtils.compareSignatureAlgorithm;
import static android.util.apk.ApkSigningBlockUtils.getContentDigestAlgorithmJcaDigestAlgorithm;
import static android.util.apk.ApkSigningBlockUtils.getLengthPrefixedSlice;
import android.util.Pair;
import java.io.ByteArrayInputStream;
-import java.io.FileDescriptor;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.BufferUnderflowException;
private static VerifiedSigner verify(RandomAccessFile apk, boolean verifyIntegrity)
throws SignatureNotFoundException, SecurityException, IOException {
SignatureInfo signatureInfo = findSignature(apk);
- return verify(apk.getFD(), signatureInfo, verifyIntegrity);
+ return verify(apk, signatureInfo, verifyIntegrity);
}
/**
* against the APK file.
*/
private static VerifiedSigner verify(
- FileDescriptor apkFileDescriptor,
+ RandomAccessFile apk,
SignatureInfo signatureInfo,
boolean doVerifyIntegrity) throws SecurityException {
int signerCount = 0;
}
if (doVerifyIntegrity) {
- ApkSigningBlockUtils.verifyIntegrity(
- contentDigests,
- apkFileDescriptor,
- signatureInfo.apkSigningBlockOffset,
- signatureInfo.centralDirOffset,
- signatureInfo.eocdOffset,
- signatureInfo.eocd);
+ ApkSigningBlockUtils.verifyIntegrity(contentDigests, apk, signatureInfo);
}
return result;
case SIGNATURE_ECDSA_WITH_SHA256:
case SIGNATURE_ECDSA_WITH_SHA512:
case SIGNATURE_DSA_WITH_SHA256:
+ case SIGNATURE_VERITY_RSA_PKCS1_V1_5_WITH_SHA256:
+ case SIGNATURE_VERITY_ECDSA_WITH_SHA256:
+ case SIGNATURE_VERITY_DSA_WITH_SHA256:
return true;
default:
return false;
package android.util.apk;
+import android.util.ArrayMap;
import android.util.Pair;
import java.io.FileDescriptor;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.MGF1ParameterSpec;
import java.security.spec.PSSParameterSpec;
+import java.util.Arrays;
import java.util.Map;
/**
static void verifyIntegrity(
Map<Integer, byte[]> expectedDigests,
- FileDescriptor apkFileDescriptor,
- long apkSigningBlockOffset,
- long centralDirOffset,
- long eocdOffset,
- ByteBuffer eocdBuf) throws SecurityException {
-
+ RandomAccessFile apk,
+ SignatureInfo signatureInfo) throws SecurityException {
if (expectedDigests.isEmpty()) {
throw new SecurityException("No digests provided");
}
+ Map<Integer, byte[]> expected1MbChunkDigests = new ArrayMap<>();
+ if (expectedDigests.containsKey(CONTENT_DIGEST_CHUNKED_SHA256)) {
+ expected1MbChunkDigests.put(CONTENT_DIGEST_CHUNKED_SHA256,
+ expectedDigests.get(CONTENT_DIGEST_CHUNKED_SHA256));
+ }
+ if (expectedDigests.containsKey(CONTENT_DIGEST_CHUNKED_SHA512)) {
+ expected1MbChunkDigests.put(CONTENT_DIGEST_CHUNKED_SHA512,
+ expectedDigests.get(CONTENT_DIGEST_CHUNKED_SHA512));
+ }
+
+ if (expectedDigests.containsKey(CONTENT_DIGEST_VERITY_CHUNKED_SHA256)) {
+ verifyIntegrityForVerityBasedAlgorithm(
+ expectedDigests.get(CONTENT_DIGEST_VERITY_CHUNKED_SHA256), apk, signatureInfo);
+ } else if (!expected1MbChunkDigests.isEmpty()) {
+ try {
+ verifyIntegrityFor1MbChunkBasedAlgorithm(expected1MbChunkDigests, apk.getFD(),
+ signatureInfo);
+ } catch (IOException e) {
+ throw new SecurityException("Cannot get FD", e);
+ }
+ } else {
+ throw new SecurityException("No known digest exists for integrity check");
+ }
+ }
+
+ private static void verifyIntegrityFor1MbChunkBasedAlgorithm(
+ Map<Integer, byte[]> expectedDigests,
+ FileDescriptor apkFileDescriptor,
+ SignatureInfo signatureInfo) throws SecurityException {
// We need to verify the integrity of the following three sections of the file:
// 1. Everything up to the start of the APK Signing Block.
// 2. ZIP Central Directory.
// APK are already there in the OS's page cache and thus mmap does not use additional
// physical memory.
DataSource beforeApkSigningBlock =
- new MemoryMappedFileDataSource(apkFileDescriptor, 0, apkSigningBlockOffset);
+ new MemoryMappedFileDataSource(apkFileDescriptor, 0,
+ signatureInfo.apkSigningBlockOffset);
DataSource centralDir =
new MemoryMappedFileDataSource(
- apkFileDescriptor, centralDirOffset, eocdOffset - centralDirOffset);
+ apkFileDescriptor, signatureInfo.centralDirOffset,
+ signatureInfo.eocdOffset - signatureInfo.centralDirOffset);
// For the purposes of integrity verification, ZIP End of Central Directory's field Start of
// Central Directory must be considered to point to the offset of the APK Signing Block.
- eocdBuf = eocdBuf.duplicate();
+ ByteBuffer eocdBuf = signatureInfo.eocd.duplicate();
eocdBuf.order(ByteOrder.LITTLE_ENDIAN);
- ZipUtils.setZipEocdCentralDirectoryOffset(eocdBuf, apkSigningBlockOffset);
+ ZipUtils.setZipEocdCentralDirectoryOffset(eocdBuf, signatureInfo.apkSigningBlockOffset);
DataSource eocd = new ByteBufferDataSource(eocdBuf);
int[] digestAlgorithms = new int[expectedDigests.size()];
byte[][] actualDigests;
try {
actualDigests =
- computeContentDigests(
+ computeContentDigestsPer1MbChunk(
digestAlgorithms,
new DataSource[] {beforeApkSigningBlock, centralDir, eocd});
} catch (DigestException e) {
}
}
- private static byte[][] computeContentDigests(
+ private static byte[][] computeContentDigestsPer1MbChunk(
int[] digestAlgorithms,
DataSource[] contents) throws DigestException {
// For each digest algorithm the result is computed as follows:
return result;
}
+ private static void verifyIntegrityForVerityBasedAlgorithm(
+ byte[] expectedRootHash,
+ RandomAccessFile apk,
+ SignatureInfo signatureInfo) throws SecurityException {
+ try {
+ ApkVerityBuilder.ApkVerityResult verity = ApkVerityBuilder.generateApkVerity(apk,
+ signatureInfo, new ByteBufferFactory() {
+ @Override
+ public ByteBuffer create(int capacity) {
+ return ByteBuffer.allocate(capacity);
+ }
+ });
+ if (!Arrays.equals(expectedRootHash, verity.rootHash)) {
+ throw new SecurityException("APK verity digest of contents did not verify");
+ }
+ } catch (DigestException | IOException | NoSuchAlgorithmException e) {
+ throw new SecurityException("Error during verification", e);
+ }
+ }
+
/**
* Returns the ZIP End of Central Directory (EoCD) and its offset in the file.
*
static final int SIGNATURE_ECDSA_WITH_SHA256 = 0x0201;
static final int SIGNATURE_ECDSA_WITH_SHA512 = 0x0202;
static final int SIGNATURE_DSA_WITH_SHA256 = 0x0301;
+ static final int SIGNATURE_VERITY_RSA_PKCS1_V1_5_WITH_SHA256 = 0x0401;
+ static final int SIGNATURE_VERITY_ECDSA_WITH_SHA256 = 0x0403;
+ static final int SIGNATURE_VERITY_DSA_WITH_SHA256 = 0x0405;
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 int compareSignatureAlgorithm(int sigAlgorithm1, int sigAlgorithm2) {
int digestAlgorithm1 = getSignatureAlgorithmContentDigestAlgorithm(sigAlgorithm1);
case CONTENT_DIGEST_CHUNKED_SHA256:
return 0;
case CONTENT_DIGEST_CHUNKED_SHA512:
+ case CONTENT_DIGEST_VERITY_CHUNKED_SHA256:
return -1;
default:
throw new IllegalArgumentException(
case CONTENT_DIGEST_CHUNKED_SHA512:
switch (digestAlgorithm2) {
case CONTENT_DIGEST_CHUNKED_SHA256:
+ case CONTENT_DIGEST_VERITY_CHUNKED_SHA256:
return 1;
case CONTENT_DIGEST_CHUNKED_SHA512:
return 0;
throw new IllegalArgumentException(
"Unknown digestAlgorithm2: " + digestAlgorithm2);
}
+ case CONTENT_DIGEST_VERITY_CHUNKED_SHA256:
+ switch (digestAlgorithm2) {
+ case CONTENT_DIGEST_CHUNKED_SHA512:
+ return -1;
+ case CONTENT_DIGEST_VERITY_CHUNKED_SHA256:
+ return 0;
+ case CONTENT_DIGEST_CHUNKED_SHA256:
+ return 1;
+ default:
+ throw new IllegalArgumentException(
+ "Unknown digestAlgorithm2: " + digestAlgorithm2);
+ }
default:
throw new IllegalArgumentException("Unknown digestAlgorithm1: " + digestAlgorithm1);
}
case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
case SIGNATURE_ECDSA_WITH_SHA512:
return CONTENT_DIGEST_CHUNKED_SHA512;
+ case SIGNATURE_VERITY_RSA_PKCS1_V1_5_WITH_SHA256:
+ case SIGNATURE_VERITY_ECDSA_WITH_SHA256:
+ case SIGNATURE_VERITY_DSA_WITH_SHA256:
+ return CONTENT_DIGEST_VERITY_CHUNKED_SHA256;
default:
throw new IllegalArgumentException(
"Unknown signature algorithm: 0x"
static String getContentDigestAlgorithmJcaDigestAlgorithm(int digestAlgorithm) {
switch (digestAlgorithm) {
case CONTENT_DIGEST_CHUNKED_SHA256:
+ case CONTENT_DIGEST_VERITY_CHUNKED_SHA256:
return "SHA-256";
case CONTENT_DIGEST_CHUNKED_SHA512:
return "SHA-512";
private static int getContentDigestAlgorithmOutputSizeBytes(int digestAlgorithm) {
switch (digestAlgorithm) {
case CONTENT_DIGEST_CHUNKED_SHA256:
+ case CONTENT_DIGEST_VERITY_CHUNKED_SHA256:
return 256 / 8;
case CONTENT_DIGEST_CHUNKED_SHA512:
return 512 / 8;
case SIGNATURE_RSA_PSS_WITH_SHA512:
case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
+ case SIGNATURE_VERITY_RSA_PKCS1_V1_5_WITH_SHA256:
return "RSA";
case SIGNATURE_ECDSA_WITH_SHA256:
case SIGNATURE_ECDSA_WITH_SHA512:
+ case SIGNATURE_VERITY_ECDSA_WITH_SHA256:
return "EC";
case SIGNATURE_DSA_WITH_SHA256:
+ case SIGNATURE_VERITY_DSA_WITH_SHA256:
return "DSA";
default:
throw new IllegalArgumentException(
new PSSParameterSpec(
"SHA-512", "MGF1", MGF1ParameterSpec.SHA512, 512 / 8, 1));
case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
+ case SIGNATURE_VERITY_RSA_PKCS1_V1_5_WITH_SHA256:
return Pair.create("SHA256withRSA", null);
case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
return Pair.create("SHA512withRSA", null);
case SIGNATURE_ECDSA_WITH_SHA256:
+ case SIGNATURE_VERITY_ECDSA_WITH_SHA256:
return Pair.create("SHA256withECDSA", null);
case SIGNATURE_ECDSA_WITH_SHA512:
return Pair.create("SHA512withECDSA", null);
case SIGNATURE_DSA_WITH_SHA256:
+ case SIGNATURE_VERITY_DSA_WITH_SHA256:
return Pair.create("SHA256withDSA", null);
default:
throw new IllegalArgumentException(