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