Home | History | Annotate | Download | only in apk
      1 /*
      2  * Copyright (C) 2018 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.util.apk;
     18 
     19 import android.util.ArrayMap;
     20 import android.util.Pair;
     21 
     22 import java.io.FileDescriptor;
     23 import java.io.IOException;
     24 import java.io.RandomAccessFile;
     25 import java.nio.BufferUnderflowException;
     26 import java.nio.ByteBuffer;
     27 import java.nio.ByteOrder;
     28 import java.security.DigestException;
     29 import java.security.MessageDigest;
     30 import java.security.NoSuchAlgorithmException;
     31 import java.security.spec.AlgorithmParameterSpec;
     32 import java.security.spec.MGF1ParameterSpec;
     33 import java.security.spec.PSSParameterSpec;
     34 import java.util.Arrays;
     35 import java.util.Map;
     36 
     37 /**
     38  * Utility class for an APK Signature Scheme using the APK Signing Block.
     39  *
     40  * @hide for internal use only.
     41  */
     42 final class ApkSigningBlockUtils {
     43 
     44     private ApkSigningBlockUtils() {
     45     }
     46 
     47     /**
     48      * Returns the APK Signature Scheme block contained in the provided APK file and the
     49      * additional information relevant for verifying the block against the file.
     50      *
     51      * @param blockId the ID value in the APK Signing Block's sequence of ID-value pairs
     52      *                identifying the appropriate block to find, e.g. the APK Signature Scheme v2
     53      *                block ID.
     54      *
     55      * @throws SignatureNotFoundException if the APK is not signed using this scheme.
     56      * @throws IOException if an I/O error occurs while reading the APK file.
     57      */
     58     static SignatureInfo findSignature(RandomAccessFile apk, int blockId)
     59             throws IOException, SignatureNotFoundException {
     60         // Find the ZIP End of Central Directory (EoCD) record.
     61         Pair<ByteBuffer, Long> eocdAndOffsetInFile = getEocd(apk);
     62         ByteBuffer eocd = eocdAndOffsetInFile.first;
     63         long eocdOffset = eocdAndOffsetInFile.second;
     64         if (ZipUtils.isZip64EndOfCentralDirectoryLocatorPresent(apk, eocdOffset)) {
     65             throw new SignatureNotFoundException("ZIP64 APK not supported");
     66         }
     67 
     68         // Find the APK Signing Block. The block immediately precedes the Central Directory.
     69         long centralDirOffset = getCentralDirOffset(eocd, eocdOffset);
     70         Pair<ByteBuffer, Long> apkSigningBlockAndOffsetInFile =
     71                 findApkSigningBlock(apk, centralDirOffset);
     72         ByteBuffer apkSigningBlock = apkSigningBlockAndOffsetInFile.first;
     73         long apkSigningBlockOffset = apkSigningBlockAndOffsetInFile.second;
     74 
     75         // Find the APK Signature Scheme Block inside the APK Signing Block.
     76         ByteBuffer apkSignatureSchemeBlock = findApkSignatureSchemeBlock(apkSigningBlock,
     77                 blockId);
     78 
     79         return new SignatureInfo(
     80                 apkSignatureSchemeBlock,
     81                 apkSigningBlockOffset,
     82                 centralDirOffset,
     83                 eocdOffset,
     84                 eocd);
     85     }
     86 
     87     static void verifyIntegrity(
     88             Map<Integer, byte[]> expectedDigests,
     89             RandomAccessFile apk,
     90             SignatureInfo signatureInfo) throws SecurityException {
     91         if (expectedDigests.isEmpty()) {
     92             throw new SecurityException("No digests provided");
     93         }
     94 
     95         boolean neverVerified = true;
     96 
     97         Map<Integer, byte[]> expected1MbChunkDigests = new ArrayMap<>();
     98         if (expectedDigests.containsKey(CONTENT_DIGEST_CHUNKED_SHA256)) {
     99             expected1MbChunkDigests.put(CONTENT_DIGEST_CHUNKED_SHA256,
    100                     expectedDigests.get(CONTENT_DIGEST_CHUNKED_SHA256));
    101         }
    102         if (expectedDigests.containsKey(CONTENT_DIGEST_CHUNKED_SHA512)) {
    103             expected1MbChunkDigests.put(CONTENT_DIGEST_CHUNKED_SHA512,
    104                     expectedDigests.get(CONTENT_DIGEST_CHUNKED_SHA512));
    105         }
    106         if (!expected1MbChunkDigests.isEmpty()) {
    107             try {
    108                 verifyIntegrityFor1MbChunkBasedAlgorithm(expected1MbChunkDigests, apk.getFD(),
    109                         signatureInfo);
    110                 neverVerified = false;
    111             } catch (IOException e) {
    112                 throw new SecurityException("Cannot get FD", e);
    113             }
    114         }
    115 
    116         if (expectedDigests.containsKey(CONTENT_DIGEST_VERITY_CHUNKED_SHA256)) {
    117             verifyIntegrityForVerityBasedAlgorithm(
    118                     expectedDigests.get(CONTENT_DIGEST_VERITY_CHUNKED_SHA256), apk, signatureInfo);
    119             neverVerified = false;
    120         }
    121 
    122         if (neverVerified) {
    123             throw new SecurityException("No known digest exists for integrity check");
    124         }
    125     }
    126 
    127     private static void verifyIntegrityFor1MbChunkBasedAlgorithm(
    128             Map<Integer, byte[]> expectedDigests,
    129             FileDescriptor apkFileDescriptor,
    130             SignatureInfo signatureInfo) throws SecurityException {
    131         // We need to verify the integrity of the following three sections of the file:
    132         // 1. Everything up to the start of the APK Signing Block.
    133         // 2. ZIP Central Directory.
    134         // 3. ZIP End of Central Directory (EoCD).
    135         // Each of these sections is represented as a separate DataSource instance below.
    136 
    137         // To handle large APKs, these sections are read in 1 MB chunks using memory-mapped I/O to
    138         // avoid wasting physical memory. In most APK verification scenarios, the contents of the
    139         // APK are already there in the OS's page cache and thus mmap does not use additional
    140         // physical memory.
    141         DataSource beforeApkSigningBlock =
    142                 new MemoryMappedFileDataSource(apkFileDescriptor, 0,
    143                         signatureInfo.apkSigningBlockOffset);
    144         DataSource centralDir =
    145                 new MemoryMappedFileDataSource(
    146                         apkFileDescriptor, signatureInfo.centralDirOffset,
    147                         signatureInfo.eocdOffset - signatureInfo.centralDirOffset);
    148 
    149         // For the purposes of integrity verification, ZIP End of Central Directory's field Start of
    150         // Central Directory must be considered to point to the offset of the APK Signing Block.
    151         ByteBuffer eocdBuf = signatureInfo.eocd.duplicate();
    152         eocdBuf.order(ByteOrder.LITTLE_ENDIAN);
    153         ZipUtils.setZipEocdCentralDirectoryOffset(eocdBuf, signatureInfo.apkSigningBlockOffset);
    154         DataSource eocd = new ByteBufferDataSource(eocdBuf);
    155 
    156         int[] digestAlgorithms = new int[expectedDigests.size()];
    157         int digestAlgorithmCount = 0;
    158         for (int digestAlgorithm : expectedDigests.keySet()) {
    159             digestAlgorithms[digestAlgorithmCount] = digestAlgorithm;
    160             digestAlgorithmCount++;
    161         }
    162         byte[][] actualDigests;
    163         try {
    164             actualDigests =
    165                     computeContentDigestsPer1MbChunk(
    166                             digestAlgorithms,
    167                             new DataSource[] {beforeApkSigningBlock, centralDir, eocd});
    168         } catch (DigestException e) {
    169             throw new SecurityException("Failed to compute digest(s) of contents", e);
    170         }
    171         for (int i = 0; i < digestAlgorithms.length; i++) {
    172             int digestAlgorithm = digestAlgorithms[i];
    173             byte[] expectedDigest = expectedDigests.get(digestAlgorithm);
    174             byte[] actualDigest = actualDigests[i];
    175             if (!MessageDigest.isEqual(expectedDigest, actualDigest)) {
    176                 throw new SecurityException(
    177                         getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm)
    178                                 + " digest of contents did not verify");
    179             }
    180         }
    181     }
    182 
    183     private static byte[][] computeContentDigestsPer1MbChunk(
    184             int[] digestAlgorithms,
    185             DataSource[] contents) throws DigestException {
    186         // For each digest algorithm the result is computed as follows:
    187         // 1. Each segment of contents is split into consecutive chunks of 1 MB in size.
    188         //    The final chunk will be shorter iff the length of segment is not a multiple of 1 MB.
    189         //    No chunks are produced for empty (zero length) segments.
    190         // 2. The digest of each chunk is computed over the concatenation of byte 0xa5, the chunk's
    191         //    length in bytes (uint32 little-endian) and the chunk's contents.
    192         // 3. The output digest is computed over the concatenation of the byte 0x5a, the number of
    193         //    chunks (uint32 little-endian) and the concatenation of digests of chunks of all
    194         //    segments in-order.
    195 
    196         long totalChunkCountLong = 0;
    197         for (DataSource input : contents) {
    198             totalChunkCountLong += getChunkCount(input.size());
    199         }
    200         if (totalChunkCountLong >= Integer.MAX_VALUE / 1024) {
    201             throw new DigestException("Too many chunks: " + totalChunkCountLong);
    202         }
    203         int totalChunkCount = (int) totalChunkCountLong;
    204 
    205         byte[][] digestsOfChunks = new byte[digestAlgorithms.length][];
    206         for (int i = 0; i < digestAlgorithms.length; i++) {
    207             int digestAlgorithm = digestAlgorithms[i];
    208             int digestOutputSizeBytes = getContentDigestAlgorithmOutputSizeBytes(digestAlgorithm);
    209             byte[] concatenationOfChunkCountAndChunkDigests =
    210                     new byte[5 + totalChunkCount * digestOutputSizeBytes];
    211             concatenationOfChunkCountAndChunkDigests[0] = 0x5a;
    212             setUnsignedInt32LittleEndian(
    213                     totalChunkCount,
    214                     concatenationOfChunkCountAndChunkDigests,
    215                     1);
    216             digestsOfChunks[i] = concatenationOfChunkCountAndChunkDigests;
    217         }
    218 
    219         byte[] chunkContentPrefix = new byte[5];
    220         chunkContentPrefix[0] = (byte) 0xa5;
    221         int chunkIndex = 0;
    222         MessageDigest[] mds = new MessageDigest[digestAlgorithms.length];
    223         for (int i = 0; i < digestAlgorithms.length; i++) {
    224             String jcaAlgorithmName =
    225                     getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithms[i]);
    226             try {
    227                 mds[i] = MessageDigest.getInstance(jcaAlgorithmName);
    228             } catch (NoSuchAlgorithmException e) {
    229                 throw new RuntimeException(jcaAlgorithmName + " digest not supported", e);
    230             }
    231         }
    232         // TODO: Compute digests of chunks in parallel when beneficial. This requires some research
    233         // into how to parallelize (if at all) based on the capabilities of the hardware on which
    234         // this code is running and based on the size of input.
    235         DataDigester digester = new MultipleDigestDataDigester(mds);
    236         int dataSourceIndex = 0;
    237         for (DataSource input : contents) {
    238             long inputOffset = 0;
    239             long inputRemaining = input.size();
    240             while (inputRemaining > 0) {
    241                 int chunkSize = (int) Math.min(inputRemaining, CHUNK_SIZE_BYTES);
    242                 setUnsignedInt32LittleEndian(chunkSize, chunkContentPrefix, 1);
    243                 for (int i = 0; i < mds.length; i++) {
    244                     mds[i].update(chunkContentPrefix);
    245                 }
    246                 try {
    247                     input.feedIntoDataDigester(digester, inputOffset, chunkSize);
    248                 } catch (IOException e) {
    249                     throw new DigestException(
    250                             "Failed to digest chunk #" + chunkIndex + " of section #"
    251                                     + dataSourceIndex,
    252                             e);
    253                 }
    254                 for (int i = 0; i < digestAlgorithms.length; i++) {
    255                     int digestAlgorithm = digestAlgorithms[i];
    256                     byte[] concatenationOfChunkCountAndChunkDigests = digestsOfChunks[i];
    257                     int expectedDigestSizeBytes =
    258                             getContentDigestAlgorithmOutputSizeBytes(digestAlgorithm);
    259                     MessageDigest md = mds[i];
    260                     int actualDigestSizeBytes =
    261                             md.digest(
    262                                     concatenationOfChunkCountAndChunkDigests,
    263                                     5 + chunkIndex * expectedDigestSizeBytes,
    264                                     expectedDigestSizeBytes);
    265                     if (actualDigestSizeBytes != expectedDigestSizeBytes) {
    266                         throw new RuntimeException(
    267                                 "Unexpected output size of " + md.getAlgorithm() + " digest: "
    268                                         + actualDigestSizeBytes);
    269                     }
    270                 }
    271                 inputOffset += chunkSize;
    272                 inputRemaining -= chunkSize;
    273                 chunkIndex++;
    274             }
    275             dataSourceIndex++;
    276         }
    277 
    278         byte[][] result = new byte[digestAlgorithms.length][];
    279         for (int i = 0; i < digestAlgorithms.length; i++) {
    280             int digestAlgorithm = digestAlgorithms[i];
    281             byte[] input = digestsOfChunks[i];
    282             String jcaAlgorithmName = getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm);
    283             MessageDigest md;
    284             try {
    285                 md = MessageDigest.getInstance(jcaAlgorithmName);
    286             } catch (NoSuchAlgorithmException e) {
    287                 throw new RuntimeException(jcaAlgorithmName + " digest not supported", e);
    288             }
    289             byte[] output = md.digest(input);
    290             result[i] = output;
    291         }
    292         return result;
    293     }
    294 
    295     /**
    296      * Return the verity digest only if the length of digest content looks correct.
    297      * When verity digest is generated, the last incomplete 4k chunk is padded with 0s before
    298      * hashing. This means two almost identical APKs with different number of 0 at the end will have
    299      * the same verity digest. To avoid this problem, the length of the source content (excluding
    300      * Signing Block) is appended to the verity digest, and the digest is returned only if the
    301      * length is consistent to the current APK.
    302      */
    303     static byte[] parseVerityDigestAndVerifySourceLength(
    304             byte[] data, long fileSize, SignatureInfo signatureInfo) throws SecurityException {
    305         // FORMAT:
    306         // OFFSET       DATA TYPE  DESCRIPTION
    307         // * @+0  bytes uint8[32]  Merkle tree root hash of SHA-256
    308         // * @+32 bytes int64      Length of source data
    309         int kRootHashSize = 32;
    310         int kSourceLengthSize = 8;
    311 
    312         if (data.length != kRootHashSize + kSourceLengthSize) {
    313             throw new SecurityException("Verity digest size is wrong: " + data.length);
    314         }
    315         ByteBuffer buffer = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);
    316         buffer.position(kRootHashSize);
    317         long expectedSourceLength = buffer.getLong();
    318 
    319         long signingBlockSize = signatureInfo.centralDirOffset
    320                 - signatureInfo.apkSigningBlockOffset;
    321         if (expectedSourceLength != fileSize - signingBlockSize) {
    322             throw new SecurityException("APK content size did not verify");
    323         }
    324 
    325         return Arrays.copyOfRange(data, 0, kRootHashSize);
    326     }
    327 
    328     private static void verifyIntegrityForVerityBasedAlgorithm(
    329             byte[] expectedDigest,
    330             RandomAccessFile apk,
    331             SignatureInfo signatureInfo) throws SecurityException {
    332         try {
    333             byte[] expectedRootHash = parseVerityDigestAndVerifySourceLength(expectedDigest,
    334                     apk.length(), signatureInfo);
    335             ApkVerityBuilder.ApkVerityResult verity = ApkVerityBuilder.generateApkVerity(apk,
    336                     signatureInfo, new ByteBufferFactory() {
    337                         @Override
    338                         public ByteBuffer create(int capacity) {
    339                             return ByteBuffer.allocate(capacity);
    340                         }
    341                     });
    342             if (!Arrays.equals(expectedRootHash, verity.rootHash)) {
    343                 throw new SecurityException("APK verity digest of contents did not verify");
    344             }
    345         } catch (DigestException | IOException | NoSuchAlgorithmException e) {
    346             throw new SecurityException("Error during verification", e);
    347         }
    348     }
    349 
    350     /**
    351      * Generates the fsverity header and hash tree to be used by kernel for the given apk. This
    352      * method does not check whether the root hash exists in the Signing Block or not.
    353      *
    354      * <p>The output is stored in the {@link ByteBuffer} created by the given {@link
    355      * ByteBufferFactory}.
    356      *
    357      * @return the root hash of the generated hash tree.
    358      */
    359     public static byte[] generateApkVerity(String apkPath, ByteBufferFactory bufferFactory,
    360             SignatureInfo signatureInfo)
    361             throws IOException, SignatureNotFoundException, SecurityException, DigestException,
    362                    NoSuchAlgorithmException {
    363         try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) {
    364             ApkVerityBuilder.ApkVerityResult result = ApkVerityBuilder.generateApkVerity(apk,
    365                     signatureInfo, bufferFactory);
    366             return result.rootHash;
    367         }
    368     }
    369 
    370     /**
    371      * Returns the ZIP End of Central Directory (EoCD) and its offset in the file.
    372      *
    373      * @throws IOException if an I/O error occurs while reading the file.
    374      * @throws SignatureNotFoundException if the EoCD could not be found.
    375      */
    376     static Pair<ByteBuffer, Long> getEocd(RandomAccessFile apk)
    377             throws IOException, SignatureNotFoundException {
    378         Pair<ByteBuffer, Long> eocdAndOffsetInFile =
    379                 ZipUtils.findZipEndOfCentralDirectoryRecord(apk);
    380         if (eocdAndOffsetInFile == null) {
    381             throw new SignatureNotFoundException(
    382                     "Not an APK file: ZIP End of Central Directory record not found");
    383         }
    384         return eocdAndOffsetInFile;
    385     }
    386 
    387     static long getCentralDirOffset(ByteBuffer eocd, long eocdOffset)
    388             throws SignatureNotFoundException {
    389         // Look up the offset of ZIP Central Directory.
    390         long centralDirOffset = ZipUtils.getZipEocdCentralDirectoryOffset(eocd);
    391         if (centralDirOffset > eocdOffset) {
    392             throw new SignatureNotFoundException(
    393                     "ZIP Central Directory offset out of range: " + centralDirOffset
    394                     + ". ZIP End of Central Directory offset: " + eocdOffset);
    395         }
    396         long centralDirSize = ZipUtils.getZipEocdCentralDirectorySizeBytes(eocd);
    397         if (centralDirOffset + centralDirSize != eocdOffset) {
    398             throw new SignatureNotFoundException(
    399                     "ZIP Central Directory is not immediately followed by End of Central"
    400                     + " Directory");
    401         }
    402         return centralDirOffset;
    403     }
    404 
    405     private static long getChunkCount(long inputSizeBytes) {
    406         return (inputSizeBytes + CHUNK_SIZE_BYTES - 1) / CHUNK_SIZE_BYTES;
    407     }
    408 
    409     private static final int CHUNK_SIZE_BYTES = 1024 * 1024;
    410 
    411     static final int SIGNATURE_RSA_PSS_WITH_SHA256 = 0x0101;
    412     static final int SIGNATURE_RSA_PSS_WITH_SHA512 = 0x0102;
    413     static final int SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256 = 0x0103;
    414     static final int SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512 = 0x0104;
    415     static final int SIGNATURE_ECDSA_WITH_SHA256 = 0x0201;
    416     static final int SIGNATURE_ECDSA_WITH_SHA512 = 0x0202;
    417     static final int SIGNATURE_DSA_WITH_SHA256 = 0x0301;
    418     static final int SIGNATURE_VERITY_RSA_PKCS1_V1_5_WITH_SHA256 = 0x0421;
    419     static final int SIGNATURE_VERITY_ECDSA_WITH_SHA256 = 0x0423;
    420     static final int SIGNATURE_VERITY_DSA_WITH_SHA256 = 0x0425;
    421 
    422     static final int CONTENT_DIGEST_CHUNKED_SHA256 = 1;
    423     static final int CONTENT_DIGEST_CHUNKED_SHA512 = 2;
    424     static final int CONTENT_DIGEST_VERITY_CHUNKED_SHA256 = 3;
    425 
    426     static int compareSignatureAlgorithm(int sigAlgorithm1, int sigAlgorithm2) {
    427         int digestAlgorithm1 = getSignatureAlgorithmContentDigestAlgorithm(sigAlgorithm1);
    428         int digestAlgorithm2 = getSignatureAlgorithmContentDigestAlgorithm(sigAlgorithm2);
    429         return compareContentDigestAlgorithm(digestAlgorithm1, digestAlgorithm2);
    430     }
    431 
    432     private static int compareContentDigestAlgorithm(int digestAlgorithm1, int digestAlgorithm2) {
    433         switch (digestAlgorithm1) {
    434             case CONTENT_DIGEST_CHUNKED_SHA256:
    435                 switch (digestAlgorithm2) {
    436                     case CONTENT_DIGEST_CHUNKED_SHA256:
    437                         return 0;
    438                     case CONTENT_DIGEST_CHUNKED_SHA512:
    439                     case CONTENT_DIGEST_VERITY_CHUNKED_SHA256:
    440                         return -1;
    441                     default:
    442                         throw new IllegalArgumentException(
    443                                 "Unknown digestAlgorithm2: " + digestAlgorithm2);
    444                 }
    445             case CONTENT_DIGEST_CHUNKED_SHA512:
    446                 switch (digestAlgorithm2) {
    447                     case CONTENT_DIGEST_CHUNKED_SHA256:
    448                     case CONTENT_DIGEST_VERITY_CHUNKED_SHA256:
    449                         return 1;
    450                     case CONTENT_DIGEST_CHUNKED_SHA512:
    451                         return 0;
    452                     default:
    453                         throw new IllegalArgumentException(
    454                                 "Unknown digestAlgorithm2: " + digestAlgorithm2);
    455                 }
    456             case CONTENT_DIGEST_VERITY_CHUNKED_SHA256:
    457                 switch (digestAlgorithm2) {
    458                     case CONTENT_DIGEST_CHUNKED_SHA512:
    459                         return -1;
    460                     case CONTENT_DIGEST_VERITY_CHUNKED_SHA256:
    461                         return 0;
    462                     case CONTENT_DIGEST_CHUNKED_SHA256:
    463                         return 1;
    464                     default:
    465                         throw new IllegalArgumentException(
    466                                 "Unknown digestAlgorithm2: " + digestAlgorithm2);
    467                 }
    468             default:
    469                 throw new IllegalArgumentException("Unknown digestAlgorithm1: " + digestAlgorithm1);
    470         }
    471     }
    472 
    473     static int getSignatureAlgorithmContentDigestAlgorithm(int sigAlgorithm) {
    474         switch (sigAlgorithm) {
    475             case SIGNATURE_RSA_PSS_WITH_SHA256:
    476             case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
    477             case SIGNATURE_ECDSA_WITH_SHA256:
    478             case SIGNATURE_DSA_WITH_SHA256:
    479                 return CONTENT_DIGEST_CHUNKED_SHA256;
    480             case SIGNATURE_RSA_PSS_WITH_SHA512:
    481             case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
    482             case SIGNATURE_ECDSA_WITH_SHA512:
    483                 return CONTENT_DIGEST_CHUNKED_SHA512;
    484             case SIGNATURE_VERITY_RSA_PKCS1_V1_5_WITH_SHA256:
    485             case SIGNATURE_VERITY_ECDSA_WITH_SHA256:
    486             case SIGNATURE_VERITY_DSA_WITH_SHA256:
    487                 return CONTENT_DIGEST_VERITY_CHUNKED_SHA256;
    488             default:
    489                 throw new IllegalArgumentException(
    490                         "Unknown signature algorithm: 0x"
    491                                 + Long.toHexString(sigAlgorithm & 0xffffffff));
    492         }
    493     }
    494 
    495     static String getContentDigestAlgorithmJcaDigestAlgorithm(int digestAlgorithm) {
    496         switch (digestAlgorithm) {
    497             case CONTENT_DIGEST_CHUNKED_SHA256:
    498             case CONTENT_DIGEST_VERITY_CHUNKED_SHA256:
    499                 return "SHA-256";
    500             case CONTENT_DIGEST_CHUNKED_SHA512:
    501                 return "SHA-512";
    502             default:
    503                 throw new IllegalArgumentException(
    504                         "Unknown content digest algorthm: " + digestAlgorithm);
    505         }
    506     }
    507 
    508     private static int getContentDigestAlgorithmOutputSizeBytes(int digestAlgorithm) {
    509         switch (digestAlgorithm) {
    510             case CONTENT_DIGEST_CHUNKED_SHA256:
    511             case CONTENT_DIGEST_VERITY_CHUNKED_SHA256:
    512                 return 256 / 8;
    513             case CONTENT_DIGEST_CHUNKED_SHA512:
    514                 return 512 / 8;
    515             default:
    516                 throw new IllegalArgumentException(
    517                         "Unknown content digest algorthm: " + digestAlgorithm);
    518         }
    519     }
    520 
    521     static String getSignatureAlgorithmJcaKeyAlgorithm(int sigAlgorithm) {
    522         switch (sigAlgorithm) {
    523             case SIGNATURE_RSA_PSS_WITH_SHA256:
    524             case SIGNATURE_RSA_PSS_WITH_SHA512:
    525             case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
    526             case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
    527             case SIGNATURE_VERITY_RSA_PKCS1_V1_5_WITH_SHA256:
    528                 return "RSA";
    529             case SIGNATURE_ECDSA_WITH_SHA256:
    530             case SIGNATURE_ECDSA_WITH_SHA512:
    531             case SIGNATURE_VERITY_ECDSA_WITH_SHA256:
    532                 return "EC";
    533             case SIGNATURE_DSA_WITH_SHA256:
    534             case SIGNATURE_VERITY_DSA_WITH_SHA256:
    535                 return "DSA";
    536             default:
    537                 throw new IllegalArgumentException(
    538                         "Unknown signature algorithm: 0x"
    539                                 + Long.toHexString(sigAlgorithm & 0xffffffff));
    540         }
    541     }
    542 
    543     static Pair<String, ? extends AlgorithmParameterSpec>
    544             getSignatureAlgorithmJcaSignatureAlgorithm(int sigAlgorithm) {
    545         switch (sigAlgorithm) {
    546             case SIGNATURE_RSA_PSS_WITH_SHA256:
    547                 return Pair.create(
    548                         "SHA256withRSA/PSS",
    549                         new PSSParameterSpec(
    550                                 "SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 256 / 8, 1));
    551             case SIGNATURE_RSA_PSS_WITH_SHA512:
    552                 return Pair.create(
    553                         "SHA512withRSA/PSS",
    554                         new PSSParameterSpec(
    555                                 "SHA-512", "MGF1", MGF1ParameterSpec.SHA512, 512 / 8, 1));
    556             case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
    557             case SIGNATURE_VERITY_RSA_PKCS1_V1_5_WITH_SHA256:
    558                 return Pair.create("SHA256withRSA", null);
    559             case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
    560                 return Pair.create("SHA512withRSA", null);
    561             case SIGNATURE_ECDSA_WITH_SHA256:
    562             case SIGNATURE_VERITY_ECDSA_WITH_SHA256:
    563                 return Pair.create("SHA256withECDSA", null);
    564             case SIGNATURE_ECDSA_WITH_SHA512:
    565                 return Pair.create("SHA512withECDSA", null);
    566             case SIGNATURE_DSA_WITH_SHA256:
    567             case SIGNATURE_VERITY_DSA_WITH_SHA256:
    568                 return Pair.create("SHA256withDSA", null);
    569             default:
    570                 throw new IllegalArgumentException(
    571                         "Unknown signature algorithm: 0x"
    572                                 + Long.toHexString(sigAlgorithm & 0xffffffff));
    573         }
    574     }
    575 
    576     /**
    577      * Returns new byte buffer whose content is a shared subsequence of this buffer's content
    578      * between the specified start (inclusive) and end (exclusive) positions. As opposed to
    579      * {@link ByteBuffer#slice()}, the returned buffer's byte order is the same as the source
    580      * buffer's byte order.
    581      */
    582     static ByteBuffer sliceFromTo(ByteBuffer source, int start, int end) {
    583         if (start < 0) {
    584             throw new IllegalArgumentException("start: " + start);
    585         }
    586         if (end < start) {
    587             throw new IllegalArgumentException("end < start: " + end + " < " + start);
    588         }
    589         int capacity = source.capacity();
    590         if (end > source.capacity()) {
    591             throw new IllegalArgumentException("end > capacity: " + end + " > " + capacity);
    592         }
    593         int originalLimit = source.limit();
    594         int originalPosition = source.position();
    595         try {
    596             source.position(0);
    597             source.limit(end);
    598             source.position(start);
    599             ByteBuffer result = source.slice();
    600             result.order(source.order());
    601             return result;
    602         } finally {
    603             source.position(0);
    604             source.limit(originalLimit);
    605             source.position(originalPosition);
    606         }
    607     }
    608 
    609     /**
    610      * Relative <em>get</em> method for reading {@code size} number of bytes from the current
    611      * position of this buffer.
    612      *
    613      * <p>This method reads the next {@code size} bytes at this buffer's current position,
    614      * returning them as a {@code ByteBuffer} with start set to 0, limit and capacity set to
    615      * {@code size}, byte order set to this buffer's byte order; and then increments the position by
    616      * {@code size}.
    617      */
    618     static ByteBuffer getByteBuffer(ByteBuffer source, int size)
    619             throws BufferUnderflowException {
    620         if (size < 0) {
    621             throw new IllegalArgumentException("size: " + size);
    622         }
    623         int originalLimit = source.limit();
    624         int position = source.position();
    625         int limit = position + size;
    626         if ((limit < position) || (limit > originalLimit)) {
    627             throw new BufferUnderflowException();
    628         }
    629         source.limit(limit);
    630         try {
    631             ByteBuffer result = source.slice();
    632             result.order(source.order());
    633             source.position(limit);
    634             return result;
    635         } finally {
    636             source.limit(originalLimit);
    637         }
    638     }
    639 
    640     static ByteBuffer getLengthPrefixedSlice(ByteBuffer source) throws IOException {
    641         if (source.remaining() < 4) {
    642             throw new IOException(
    643                     "Remaining buffer too short to contain length of length-prefixed field."
    644                             + " Remaining: " + source.remaining());
    645         }
    646         int len = source.getInt();
    647         if (len < 0) {
    648             throw new IllegalArgumentException("Negative length");
    649         } else if (len > source.remaining()) {
    650             throw new IOException("Length-prefixed field longer than remaining buffer."
    651                     + " Field length: " + len + ", remaining: " + source.remaining());
    652         }
    653         return getByteBuffer(source, len);
    654     }
    655 
    656     static byte[] readLengthPrefixedByteArray(ByteBuffer buf) throws IOException {
    657         int len = buf.getInt();
    658         if (len < 0) {
    659             throw new IOException("Negative length");
    660         } else if (len > buf.remaining()) {
    661             throw new IOException("Underflow while reading length-prefixed value. Length: " + len
    662                     + ", available: " + buf.remaining());
    663         }
    664         byte[] result = new byte[len];
    665         buf.get(result);
    666         return result;
    667     }
    668 
    669     static void setUnsignedInt32LittleEndian(int value, byte[] result, int offset) {
    670         result[offset] = (byte) (value & 0xff);
    671         result[offset + 1] = (byte) ((value >>> 8) & 0xff);
    672         result[offset + 2] = (byte) ((value >>> 16) & 0xff);
    673         result[offset + 3] = (byte) ((value >>> 24) & 0xff);
    674     }
    675 
    676     private static final long APK_SIG_BLOCK_MAGIC_HI = 0x3234206b636f6c42L;
    677     private static final long APK_SIG_BLOCK_MAGIC_LO = 0x20676953204b5041L;
    678     private static final int APK_SIG_BLOCK_MIN_SIZE = 32;
    679 
    680     static Pair<ByteBuffer, Long> findApkSigningBlock(
    681             RandomAccessFile apk, long centralDirOffset)
    682                     throws IOException, SignatureNotFoundException {
    683         // FORMAT:
    684         // OFFSET       DATA TYPE  DESCRIPTION
    685         // * @+0  bytes uint64:    size in bytes (excluding this field)
    686         // * @+8  bytes payload
    687         // * @-24 bytes uint64:    size in bytes (same as the one above)
    688         // * @-16 bytes uint128:   magic
    689 
    690         if (centralDirOffset < APK_SIG_BLOCK_MIN_SIZE) {
    691             throw new SignatureNotFoundException(
    692                     "APK too small for APK Signing Block. ZIP Central Directory offset: "
    693                             + centralDirOffset);
    694         }
    695         // Read the magic and offset in file from the footer section of the block:
    696         // * uint64:   size of block
    697         // * 16 bytes: magic
    698         ByteBuffer footer = ByteBuffer.allocate(24);
    699         footer.order(ByteOrder.LITTLE_ENDIAN);
    700         apk.seek(centralDirOffset - footer.capacity());
    701         apk.readFully(footer.array(), footer.arrayOffset(), footer.capacity());
    702         if ((footer.getLong(8) != APK_SIG_BLOCK_MAGIC_LO)
    703                 || (footer.getLong(16) != APK_SIG_BLOCK_MAGIC_HI)) {
    704             throw new SignatureNotFoundException(
    705                     "No APK Signing Block before ZIP Central Directory");
    706         }
    707         // Read and compare size fields
    708         long apkSigBlockSizeInFooter = footer.getLong(0);
    709         if ((apkSigBlockSizeInFooter < footer.capacity())
    710                 || (apkSigBlockSizeInFooter > Integer.MAX_VALUE - 8)) {
    711             throw new SignatureNotFoundException(
    712                     "APK Signing Block size out of range: " + apkSigBlockSizeInFooter);
    713         }
    714         int totalSize = (int) (apkSigBlockSizeInFooter + 8);
    715         long apkSigBlockOffset = centralDirOffset - totalSize;
    716         if (apkSigBlockOffset < 0) {
    717             throw new SignatureNotFoundException(
    718                     "APK Signing Block offset out of range: " + apkSigBlockOffset);
    719         }
    720         ByteBuffer apkSigBlock = ByteBuffer.allocate(totalSize);
    721         apkSigBlock.order(ByteOrder.LITTLE_ENDIAN);
    722         apk.seek(apkSigBlockOffset);
    723         apk.readFully(apkSigBlock.array(), apkSigBlock.arrayOffset(), apkSigBlock.capacity());
    724         long apkSigBlockSizeInHeader = apkSigBlock.getLong(0);
    725         if (apkSigBlockSizeInHeader != apkSigBlockSizeInFooter) {
    726             throw new SignatureNotFoundException(
    727                     "APK Signing Block sizes in header and footer do not match: "
    728                             + apkSigBlockSizeInHeader + " vs " + apkSigBlockSizeInFooter);
    729         }
    730         return Pair.create(apkSigBlock, apkSigBlockOffset);
    731     }
    732 
    733     static ByteBuffer findApkSignatureSchemeBlock(ByteBuffer apkSigningBlock, int blockId)
    734             throws SignatureNotFoundException {
    735         checkByteOrderLittleEndian(apkSigningBlock);
    736         // FORMAT:
    737         // OFFSET       DATA TYPE  DESCRIPTION
    738         // * @+0  bytes uint64:    size in bytes (excluding this field)
    739         // * @+8  bytes pairs
    740         // * @-24 bytes uint64:    size in bytes (same as the one above)
    741         // * @-16 bytes uint128:   magic
    742         ByteBuffer pairs = sliceFromTo(apkSigningBlock, 8, apkSigningBlock.capacity() - 24);
    743 
    744         int entryCount = 0;
    745         while (pairs.hasRemaining()) {
    746             entryCount++;
    747             if (pairs.remaining() < 8) {
    748                 throw new SignatureNotFoundException(
    749                         "Insufficient data to read size of APK Signing Block entry #" + entryCount);
    750             }
    751             long lenLong = pairs.getLong();
    752             if ((lenLong < 4) || (lenLong > Integer.MAX_VALUE)) {
    753                 throw new SignatureNotFoundException(
    754                         "APK Signing Block entry #" + entryCount
    755                                 + " size out of range: " + lenLong);
    756             }
    757             int len = (int) lenLong;
    758             int nextEntryPos = pairs.position() + len;
    759             if (len > pairs.remaining()) {
    760                 throw new SignatureNotFoundException(
    761                         "APK Signing Block entry #" + entryCount + " size out of range: " + len
    762                                 + ", available: " + pairs.remaining());
    763             }
    764             int id = pairs.getInt();
    765             if (id == blockId) {
    766                 return getByteBuffer(pairs, len - 4);
    767             }
    768             pairs.position(nextEntryPos);
    769         }
    770 
    771         throw new SignatureNotFoundException(
    772                 "No block with ID " + blockId + " in APK Signing Block.");
    773     }
    774 
    775     private static void checkByteOrderLittleEndian(ByteBuffer buffer) {
    776         if (buffer.order() != ByteOrder.LITTLE_ENDIAN) {
    777             throw new IllegalArgumentException("ByteBuffer byte order must be little endian");
    778         }
    779     }
    780 
    781     /**
    782      * {@link DataDigester} that updates multiple {@link MessageDigest}s whenever data is fed.
    783      */
    784     private static class MultipleDigestDataDigester implements DataDigester {
    785         private final MessageDigest[] mMds;
    786 
    787         MultipleDigestDataDigester(MessageDigest[] mds) {
    788             mMds = mds;
    789         }
    790 
    791         @Override
    792         public void consume(ByteBuffer buffer) {
    793             buffer = buffer.slice();
    794             for (MessageDigest md : mMds) {
    795                 buffer.position(0);
    796                 md.update(buffer);
    797             }
    798         }
    799     }
    800 
    801 }
    802