diff options
Diffstat (limited to 'android/util/apk/ApkSigningBlockUtils.java')
-rw-r--r-- | android/util/apk/ApkSigningBlockUtils.java | 123 |
1 files changed, 111 insertions, 12 deletions
diff --git a/android/util/apk/ApkSigningBlockUtils.java b/android/util/apk/ApkSigningBlockUtils.java index 9279510a..4146f6fa 100644 --- a/android/util/apk/ApkSigningBlockUtils.java +++ b/android/util/apk/ApkSigningBlockUtils.java @@ -16,6 +16,7 @@ package android.util.apk; +import android.util.ArrayMap; import android.util.Pair; import java.io.FileDescriptor; @@ -30,6 +31,7 @@ import java.security.NoSuchAlgorithmException; import java.security.spec.AlgorithmParameterSpec; import java.security.spec.MGF1ParameterSpec; import java.security.spec.PSSParameterSpec; +import java.util.Arrays; import java.util.Map; /** @@ -84,16 +86,41 @@ final class ApkSigningBlockUtils { 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. @@ -105,16 +132,18 @@ final class ApkSigningBlockUtils { // 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()]; @@ -126,7 +155,7 @@ final class ApkSigningBlockUtils { byte[][] actualDigests; try { actualDigests = - computeContentDigests( + computeContentDigestsPer1MbChunk( digestAlgorithms, new DataSource[] {beforeApkSigningBlock, centralDir, eocd}); } catch (DigestException e) { @@ -144,7 +173,7 @@ final class ApkSigningBlockUtils { } } - private static byte[][] computeContentDigests( + private static byte[][] computeContentDigestsPer1MbChunk( int[] digestAlgorithms, DataSource[] contents) throws DigestException { // For each digest algorithm the result is computed as follows: @@ -256,6 +285,46 @@ final class ApkSigningBlockUtils { 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); + } + } + + /** + * Generates the fsverity header and hash tree to be used by kernel for the given apk. This + * method does not check whether the root hash exists in the Signing Block or not. + * + * <p>The output is stored in the {@link ByteBuffer} created by the given {@link + * ByteBufferFactory}. + * + * @return the root hash of the generated hash tree. + */ + public static byte[] generateApkVerity(String apkPath, ByteBufferFactory bufferFactory, + SignatureInfo signatureInfo) + throws IOException, SignatureNotFoundException, SecurityException, DigestException, + NoSuchAlgorithmException { + try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) { + ApkVerityBuilder.ApkVerityResult result = ApkVerityBuilder.generateApkVerity(apk, + signatureInfo, bufferFactory); + return result.rootHash; + } + } + /** * Returns the ZIP End of Central Directory (EoCD) and its offset in the file. * @@ -304,9 +373,13 @@ final class ApkSigningBlockUtils { 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); @@ -321,6 +394,7 @@ final class ApkSigningBlockUtils { case CONTENT_DIGEST_CHUNKED_SHA256: return 0; case CONTENT_DIGEST_CHUNKED_SHA512: + case CONTENT_DIGEST_VERITY_CHUNKED_SHA256: return -1; default: throw new IllegalArgumentException( @@ -329,6 +403,7 @@ final class ApkSigningBlockUtils { 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; @@ -336,6 +411,18 @@ final class ApkSigningBlockUtils { 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); } @@ -352,6 +439,10 @@ final class ApkSigningBlockUtils { 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" @@ -362,6 +453,7 @@ final class ApkSigningBlockUtils { 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"; @@ -374,6 +466,7 @@ final class ApkSigningBlockUtils { 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; @@ -389,11 +482,14 @@ final class ApkSigningBlockUtils { 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( @@ -416,14 +512,17 @@ final class ApkSigningBlockUtils { 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( |