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 
ApkSigningBlockUtils()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      */
findSignature(RandomAccessFile apk, int blockId)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 
verifyIntegrity( Map<Integer, byte[]> expectedDigests, RandomAccessFile apk, SignatureInfo signatureInfo)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 
verifyIntegrityFor1MbChunkBasedAlgorithm( Map<Integer, byte[]> expectedDigests, FileDescriptor apkFileDescriptor, SignatureInfo signatureInfo)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 
computeContentDigestsPer1MbChunk( int[] digestAlgorithms, DataSource[] contents)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      */
parseVerityDigestAndVerifySourceLength( byte[] data, long fileSize, SignatureInfo signatureInfo)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 
verifyIntegrityForVerityBasedAlgorithm( byte[] expectedDigest, RandomAccessFile apk, SignatureInfo signatureInfo)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      */
generateApkVerity(String apkPath, ByteBufferFactory bufferFactory, SignatureInfo signatureInfo)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      */
getEocd(RandomAccessFile apk)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 
getCentralDirOffset(ByteBuffer eocd, long eocdOffset)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 
getChunkCount(long inputSizeBytes)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 
compareSignatureAlgorithm(int sigAlgorithm1, int sigAlgorithm2)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 
compareContentDigestAlgorithm(int digestAlgorithm1, int digestAlgorithm2)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 
getSignatureAlgorithmContentDigestAlgorithm(int sigAlgorithm)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 
getContentDigestAlgorithmJcaDigestAlgorithm(int digestAlgorithm)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 
getContentDigestAlgorithmOutputSizeBytes(int digestAlgorithm)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 
getSignatureAlgorithmJcaKeyAlgorithm(int sigAlgorithm)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>
getSignatureAlgorithmJcaSignatureAlgorithm(int sigAlgorithm)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      */
sliceFromTo(ByteBuffer source, int start, int end)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      */
getByteBuffer(ByteBuffer source, int size)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 
getLengthPrefixedSlice(ByteBuffer source)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 
readLengthPrefixedByteArray(ByteBuffer buf)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 
setUnsignedInt32LittleEndian(int value, byte[] result, int offset)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 
findApkSigningBlock( RandomAccessFile apk, long centralDirOffset)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 
findApkSignatureSchemeBlock(ByteBuffer apkSigningBlock, int blockId)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 
checkByteOrderLittleEndian(ByteBuffer buffer)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 
MultipleDigestDataDigester(MessageDigest[] mds)787         MultipleDigestDataDigester(MessageDigest[] mds) {
788             mMds = mds;
789         }
790 
791         @Override
consume(ByteBuffer buffer)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