1 /*
2  * Copyright (C) 2016 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 com.android.apksig.apk;
18 
19 import com.android.apksig.internal.apk.AndroidBinXmlParser;
20 import com.android.apksig.internal.apk.v1.V1SchemeVerifier;
21 import com.android.apksig.internal.util.Pair;
22 import com.android.apksig.internal.zip.CentralDirectoryRecord;
23 import com.android.apksig.internal.zip.LocalFileRecord;
24 import com.android.apksig.internal.zip.ZipUtils;
25 import com.android.apksig.util.DataSource;
26 import com.android.apksig.zip.ZipFormatException;
27 import java.io.IOException;
28 import java.nio.ByteBuffer;
29 import java.nio.ByteOrder;
30 import java.security.MessageDigest;
31 import java.security.NoSuchAlgorithmException;
32 import java.util.Arrays;
33 import java.util.Comparator;
34 import java.util.List;
35 
36 /**
37  * APK utilities.
38  */
39 public abstract class ApkUtils {
40 
41     /**
42      * Name of the Android manifest ZIP entry in APKs.
43      */
44     public static final String ANDROID_MANIFEST_ZIP_ENTRY_NAME = "AndroidManifest.xml";
45 
46     /** Name of the SourceStamp certificate hash ZIP entry in APKs. */
47     public static final String SOURCE_STAMP_CERTIFICATE_HASH_ZIP_ENTRY_NAME = "stamp-cert-sha256";
48 
ApkUtils()49     private ApkUtils() {}
50 
51     /**
52      * Finds the main ZIP sections of the provided APK.
53      *
54      * @throws IOException if an I/O error occurred while reading the APK
55      * @throws ZipFormatException if the APK is malformed
56      */
findZipSections(DataSource apk)57     public static ZipSections findZipSections(DataSource apk)
58             throws IOException, ZipFormatException {
59         Pair<ByteBuffer, Long> eocdAndOffsetInFile =
60                 ZipUtils.findZipEndOfCentralDirectoryRecord(apk);
61         if (eocdAndOffsetInFile == null) {
62             throw new ZipFormatException("ZIP End of Central Directory record not found");
63         }
64 
65         ByteBuffer eocdBuf = eocdAndOffsetInFile.getFirst();
66         long eocdOffset = eocdAndOffsetInFile.getSecond();
67         eocdBuf.order(ByteOrder.LITTLE_ENDIAN);
68         long cdStartOffset = ZipUtils.getZipEocdCentralDirectoryOffset(eocdBuf);
69         if (cdStartOffset > eocdOffset) {
70             throw new ZipFormatException(
71                     "ZIP Central Directory start offset out of range: " + cdStartOffset
72                         + ". ZIP End of Central Directory offset: " + eocdOffset);
73         }
74 
75         long cdSizeBytes = ZipUtils.getZipEocdCentralDirectorySizeBytes(eocdBuf);
76         long cdEndOffset = cdStartOffset + cdSizeBytes;
77         if (cdEndOffset > eocdOffset) {
78             throw new ZipFormatException(
79                     "ZIP Central Directory overlaps with End of Central Directory"
80                             + ". CD end: " + cdEndOffset
81                             + ", EoCD start: " + eocdOffset);
82         }
83 
84         int cdRecordCount = ZipUtils.getZipEocdCentralDirectoryTotalRecordCount(eocdBuf);
85 
86         return new ZipSections(
87                 cdStartOffset,
88                 cdSizeBytes,
89                 cdRecordCount,
90                 eocdOffset,
91                 eocdBuf);
92     }
93 
94     /**
95      * Information about the ZIP sections of an APK.
96      */
97     public static class ZipSections {
98         private final long mCentralDirectoryOffset;
99         private final long mCentralDirectorySizeBytes;
100         private final int mCentralDirectoryRecordCount;
101         private final long mEocdOffset;
102         private final ByteBuffer mEocd;
103 
ZipSections( long centralDirectoryOffset, long centralDirectorySizeBytes, int centralDirectoryRecordCount, long eocdOffset, ByteBuffer eocd)104         public ZipSections(
105                 long centralDirectoryOffset,
106                 long centralDirectorySizeBytes,
107                 int centralDirectoryRecordCount,
108                 long eocdOffset,
109                 ByteBuffer eocd) {
110             mCentralDirectoryOffset = centralDirectoryOffset;
111             mCentralDirectorySizeBytes = centralDirectorySizeBytes;
112             mCentralDirectoryRecordCount = centralDirectoryRecordCount;
113             mEocdOffset = eocdOffset;
114             mEocd = eocd;
115         }
116 
117         /**
118          * Returns the start offset of the ZIP Central Directory. This value is taken from the
119          * ZIP End of Central Directory record.
120          */
getZipCentralDirectoryOffset()121         public long getZipCentralDirectoryOffset() {
122             return mCentralDirectoryOffset;
123         }
124 
125         /**
126          * Returns the size (in bytes) of the ZIP Central Directory. This value is taken from the
127          * ZIP End of Central Directory record.
128          */
getZipCentralDirectorySizeBytes()129         public long getZipCentralDirectorySizeBytes() {
130             return mCentralDirectorySizeBytes;
131         }
132 
133         /**
134          * Returns the number of records in the ZIP Central Directory. This value is taken from the
135          * ZIP End of Central Directory record.
136          */
getZipCentralDirectoryRecordCount()137         public int getZipCentralDirectoryRecordCount() {
138             return mCentralDirectoryRecordCount;
139         }
140 
141         /**
142          * Returns the start offset of the ZIP End of Central Directory record. The record extends
143          * until the very end of the APK.
144          */
getZipEndOfCentralDirectoryOffset()145         public long getZipEndOfCentralDirectoryOffset() {
146             return mEocdOffset;
147         }
148 
149         /**
150          * Returns the contents of the ZIP End of Central Directory.
151          */
getZipEndOfCentralDirectory()152         public ByteBuffer getZipEndOfCentralDirectory() {
153             return mEocd;
154         }
155     }
156 
157     /**
158      * Sets the offset of the start of the ZIP Central Directory in the APK's ZIP End of Central
159      * Directory record.
160      *
161      * @param zipEndOfCentralDirectory APK's ZIP End of Central Directory record
162      * @param offset offset of the ZIP Central Directory relative to the start of the archive. Must
163      *        be between {@code 0} and {@code 2^32 - 1} inclusive.
164      */
setZipEocdCentralDirectoryOffset( ByteBuffer zipEndOfCentralDirectory, long offset)165     public static void setZipEocdCentralDirectoryOffset(
166             ByteBuffer zipEndOfCentralDirectory, long offset) {
167         ByteBuffer eocd = zipEndOfCentralDirectory.slice();
168         eocd.order(ByteOrder.LITTLE_ENDIAN);
169         ZipUtils.setZipEocdCentralDirectoryOffset(eocd, offset);
170     }
171 
172     // See https://source.android.com/security/apksigning/v2.html
173     private static final long APK_SIG_BLOCK_MAGIC_HI = 0x3234206b636f6c42L;
174     private static final long APK_SIG_BLOCK_MAGIC_LO = 0x20676953204b5041L;
175     private static final int APK_SIG_BLOCK_MIN_SIZE = 32;
176 
177     /**
178      * Returns the APK Signing Block of the provided APK.
179      *
180      * @throws IOException if an I/O error occurs
181      * @throws ApkSigningBlockNotFoundException if there is no APK Signing Block in the APK
182      *
183      * @see <a href="https://source.android.com/security/apksigning/v2.html">APK Signature Scheme v2</a>
184      */
findApkSigningBlock(DataSource apk, ZipSections zipSections)185     public static ApkSigningBlock findApkSigningBlock(DataSource apk, ZipSections zipSections)
186             throws IOException, ApkSigningBlockNotFoundException {
187         // FORMAT (see https://source.android.com/security/apksigning/v2.html):
188         // OFFSET       DATA TYPE  DESCRIPTION
189         // * @+0  bytes uint64:    size in bytes (excluding this field)
190         // * @+8  bytes payload
191         // * @-24 bytes uint64:    size in bytes (same as the one above)
192         // * @-16 bytes uint128:   magic
193 
194         long centralDirStartOffset = zipSections.getZipCentralDirectoryOffset();
195         long centralDirEndOffset =
196                 centralDirStartOffset + zipSections.getZipCentralDirectorySizeBytes();
197         long eocdStartOffset = zipSections.getZipEndOfCentralDirectoryOffset();
198         if (centralDirEndOffset != eocdStartOffset) {
199             throw new ApkSigningBlockNotFoundException(
200                     "ZIP Central Directory is not immediately followed by End of Central Directory"
201                             + ". CD end: " + centralDirEndOffset
202                             + ", EoCD start: " + eocdStartOffset);
203         }
204 
205         if (centralDirStartOffset < APK_SIG_BLOCK_MIN_SIZE) {
206             throw new ApkSigningBlockNotFoundException(
207                     "APK too small for APK Signing Block. ZIP Central Directory offset: "
208                             + centralDirStartOffset);
209         }
210         // Read the magic and offset in file from the footer section of the block:
211         // * uint64:   size of block
212         // * 16 bytes: magic
213         ByteBuffer footer = apk.getByteBuffer(centralDirStartOffset - 24, 24);
214         footer.order(ByteOrder.LITTLE_ENDIAN);
215         if ((footer.getLong(8) != APK_SIG_BLOCK_MAGIC_LO)
216                 || (footer.getLong(16) != APK_SIG_BLOCK_MAGIC_HI)) {
217             throw new ApkSigningBlockNotFoundException(
218                     "No APK Signing Block before ZIP Central Directory");
219         }
220         // Read and compare size fields
221         long apkSigBlockSizeInFooter = footer.getLong(0);
222         if ((apkSigBlockSizeInFooter < footer.capacity())
223                 || (apkSigBlockSizeInFooter > Integer.MAX_VALUE - 8)) {
224             throw new ApkSigningBlockNotFoundException(
225                     "APK Signing Block size out of range: " + apkSigBlockSizeInFooter);
226         }
227         int totalSize = (int) (apkSigBlockSizeInFooter + 8);
228         long apkSigBlockOffset = centralDirStartOffset - totalSize;
229         if (apkSigBlockOffset < 0) {
230             throw new ApkSigningBlockNotFoundException(
231                     "APK Signing Block offset out of range: " + apkSigBlockOffset);
232         }
233         ByteBuffer apkSigBlock = apk.getByteBuffer(apkSigBlockOffset, 8);
234         apkSigBlock.order(ByteOrder.LITTLE_ENDIAN);
235         long apkSigBlockSizeInHeader = apkSigBlock.getLong(0);
236         if (apkSigBlockSizeInHeader != apkSigBlockSizeInFooter) {
237             throw new ApkSigningBlockNotFoundException(
238                     "APK Signing Block sizes in header and footer do not match: "
239                             + apkSigBlockSizeInHeader + " vs " + apkSigBlockSizeInFooter);
240         }
241         return new ApkSigningBlock(apkSigBlockOffset, apk.slice(apkSigBlockOffset, totalSize));
242     }
243 
244     /**
245      * Information about the location of the APK Signing Block inside an APK.
246      */
247     public static class ApkSigningBlock {
248         private final long mStartOffsetInApk;
249         private final DataSource mContents;
250 
251         /**
252          * Constructs a new {@code ApkSigningBlock}.
253          *
254          * @param startOffsetInApk start offset (in bytes, relative to start of file) of the APK
255          *        Signing Block inside the APK file
256          * @param contents contents of the APK Signing Block
257          */
ApkSigningBlock(long startOffsetInApk, DataSource contents)258         public ApkSigningBlock(long startOffsetInApk, DataSource contents) {
259             mStartOffsetInApk = startOffsetInApk;
260             mContents = contents;
261         }
262 
263         /**
264          * Returns the start offset (in bytes, relative to start of file) of the APK Signing Block.
265          */
getStartOffset()266         public long getStartOffset() {
267             return mStartOffsetInApk;
268         }
269 
270         /**
271          * Returns the data source which provides the full contents of the APK Signing Block,
272          * including its footer.
273          */
getContents()274         public DataSource getContents() {
275             return mContents;
276         }
277     }
278 
279     /**
280      * Returns the contents of the APK's {@code AndroidManifest.xml}.
281      *
282      * @throws IOException if an I/O error occurs while reading the APK
283      * @throws ApkFormatException if the APK is malformed
284      */
getAndroidManifest(DataSource apk)285     public static ByteBuffer getAndroidManifest(DataSource apk)
286             throws IOException, ApkFormatException {
287         ZipSections zipSections;
288         try {
289             zipSections = findZipSections(apk);
290         } catch (ZipFormatException e) {
291             throw new ApkFormatException("Not a valid ZIP archive", e);
292         }
293         List<CentralDirectoryRecord> cdRecords =
294                 V1SchemeVerifier.parseZipCentralDirectory(apk, zipSections);
295         CentralDirectoryRecord androidManifestCdRecord = null;
296         for (CentralDirectoryRecord cdRecord : cdRecords) {
297             if (ANDROID_MANIFEST_ZIP_ENTRY_NAME.equals(cdRecord.getName())) {
298                 androidManifestCdRecord = cdRecord;
299                 break;
300             }
301         }
302         if (androidManifestCdRecord == null) {
303             throw new ApkFormatException("Missing " + ANDROID_MANIFEST_ZIP_ENTRY_NAME);
304         }
305         DataSource lfhSection = apk.slice(0, zipSections.getZipCentralDirectoryOffset());
306 
307         try {
308             return ByteBuffer.wrap(
309                     LocalFileRecord.getUncompressedData(
310                             lfhSection, androidManifestCdRecord, lfhSection.size()));
311         } catch (ZipFormatException e) {
312             throw new ApkFormatException("Failed to read " + ANDROID_MANIFEST_ZIP_ENTRY_NAME, e);
313         }
314     }
315 
316     /**
317      * Android resource ID of the {@code android:minSdkVersion} attribute in AndroidManifest.xml.
318      */
319     private static final int MIN_SDK_VERSION_ATTR_ID = 0x0101020c;
320 
321     /**
322      * Android resource ID of the {@code android:debuggable} attribute in AndroidManifest.xml.
323      */
324     private static final int DEBUGGABLE_ATTR_ID = 0x0101000f;
325 
326     /**
327      * Returns the lowest Android platform version (API Level) supported by an APK with the
328      * provided {@code AndroidManifest.xml}.
329      *
330      * @param androidManifestContents contents of {@code AndroidManifest.xml} in binary Android
331      *        resource format
332      *
333      * @throws MinSdkVersionException if an error occurred while determining the API Level
334      */
getMinSdkVersionFromBinaryAndroidManifest( ByteBuffer androidManifestContents)335     public static int getMinSdkVersionFromBinaryAndroidManifest(
336             ByteBuffer androidManifestContents) throws MinSdkVersionException {
337         // IMPLEMENTATION NOTE: Minimum supported Android platform version number is declared using
338         // uses-sdk elements which are children of the top-level manifest element. uses-sdk element
339         // declares the minimum supported platform version using the android:minSdkVersion attribute
340         // whose default value is 1.
341         // For each encountered uses-sdk element, the Android runtime checks that its minSdkVersion
342         // is not higher than the runtime's API Level and rejects APKs if it is higher. Thus, the
343         // effective minSdkVersion value is the maximum over the encountered minSdkVersion values.
344 
345         try {
346             // If no uses-sdk elements are encountered, Android accepts the APK. We treat this
347             // scenario as though the minimum supported API Level is 1.
348             int result = 1;
349 
350             AndroidBinXmlParser parser = new AndroidBinXmlParser(androidManifestContents);
351             int eventType = parser.getEventType();
352             while (eventType != AndroidBinXmlParser.EVENT_END_DOCUMENT) {
353                 if ((eventType == AndroidBinXmlParser.EVENT_START_ELEMENT)
354                         && (parser.getDepth() == 2)
355                         && ("uses-sdk".equals(parser.getName()))
356                         && (parser.getNamespace().isEmpty())) {
357                     // In each uses-sdk element, minSdkVersion defaults to 1
358                     int minSdkVersion = 1;
359                     for (int i = 0; i < parser.getAttributeCount(); i++) {
360                         if (parser.getAttributeNameResourceId(i) == MIN_SDK_VERSION_ATTR_ID) {
361                             int valueType = parser.getAttributeValueType(i);
362                             switch (valueType) {
363                                 case AndroidBinXmlParser.VALUE_TYPE_INT:
364                                     minSdkVersion = parser.getAttributeIntValue(i);
365                                     break;
366                                 case AndroidBinXmlParser.VALUE_TYPE_STRING:
367                                     minSdkVersion =
368                                             getMinSdkVersionForCodename(
369                                                     parser.getAttributeStringValue(i));
370                                     break;
371                                 default:
372                                     throw new MinSdkVersionException(
373                                             "Unable to determine APK's minimum supported Android"
374                                                     + ": unsupported value type in "
375                                                     + ANDROID_MANIFEST_ZIP_ENTRY_NAME + "'s"
376                                                     + " minSdkVersion"
377                                                     + ". Only integer values supported.");
378                             }
379                             break;
380                         }
381                     }
382                     result = Math.max(result, minSdkVersion);
383                 }
384                 eventType = parser.next();
385             }
386 
387             return result;
388         } catch (AndroidBinXmlParser.XmlParserException e) {
389             throw new MinSdkVersionException(
390                     "Unable to determine APK's minimum supported Android platform version"
391                             + ": malformed binary resource: " + ANDROID_MANIFEST_ZIP_ENTRY_NAME,
392                     e);
393         }
394     }
395 
396     private static class CodenamesLazyInitializer {
397 
398         /**
399          * List of platform codename (first letter of) to API Level mappings. The list must be
400          * sorted by the first letter. For codenames not in the list, the assumption is that the API
401          * Level is incremented by one for every increase in the codename's first letter.
402          */
403         @SuppressWarnings({"rawtypes", "unchecked"})
404         private static final Pair<Character, Integer>[] SORTED_CODENAMES_FIRST_CHAR_TO_API_LEVEL =
405                 new Pair[] {
406             Pair.of('C', 2),
407             Pair.of('D', 3),
408             Pair.of('E', 4),
409             Pair.of('F', 7),
410             Pair.of('G', 8),
411             Pair.of('H', 10),
412             Pair.of('I', 13),
413             Pair.of('J', 15),
414             Pair.of('K', 18),
415             Pair.of('L', 20),
416             Pair.of('M', 22),
417             Pair.of('N', 23),
418             Pair.of('O', 25),
419         };
420 
421         private static final Comparator<Pair<Character, Integer>> CODENAME_FIRST_CHAR_COMPARATOR =
422                 new ByFirstComparator();
423 
424         private static class ByFirstComparator implements Comparator<Pair<Character, Integer>> {
425             @Override
compare(Pair<Character, Integer> o1, Pair<Character, Integer> o2)426             public int compare(Pair<Character, Integer> o1, Pair<Character, Integer> o2) {
427                 char c1 = o1.getFirst();
428                 char c2 = o2.getFirst();
429                 return c1 - c2;
430             }
431         }
432     }
433 
434     /**
435      * Returns the API Level corresponding to the provided platform codename.
436      *
437      * <p>This method is pessimistic. It returns a value one lower than the API Level with which the
438      * platform is actually released (e.g., 23 for N which was released as API Level 24). This is
439      * because new features which first appear in an API Level are not available in the early days
440      * of that platform version's existence, when the platform only has a codename. Moreover, this
441      * method currently doesn't differentiate between initial and MR releases, meaning API Level
442      * returned for MR releases may be more than one lower than the API Level with which the
443      * platform version is actually released.
444      *
445      * @throws CodenameMinSdkVersionException if the {@code codename} is not supported
446      */
getMinSdkVersionForCodename(String codename)447     static int getMinSdkVersionForCodename(String codename) throws CodenameMinSdkVersionException {
448         char firstChar = codename.isEmpty() ? ' ' : codename.charAt(0);
449         // Codenames are case-sensitive. Only codenames starting with A-Z are supported for now.
450         // We only look at the first letter of the codename as this is the most important letter.
451         if ((firstChar >= 'A') && (firstChar <= 'Z')) {
452             Pair<Character, Integer>[] sortedCodenamesFirstCharToApiLevel =
453                     CodenamesLazyInitializer.SORTED_CODENAMES_FIRST_CHAR_TO_API_LEVEL;
454             int searchResult =
455                     Arrays.binarySearch(
456                             sortedCodenamesFirstCharToApiLevel,
457                             Pair.of(firstChar, null), // second element of the pair is ignored here
458                             CodenamesLazyInitializer.CODENAME_FIRST_CHAR_COMPARATOR);
459             if (searchResult >= 0) {
460                 // Exact match -- searchResult is the index of the matching element
461                 return sortedCodenamesFirstCharToApiLevel[searchResult].getSecond();
462             }
463             // Not an exact match -- searchResult is negative and is -(insertion index) - 1.
464             // The element at insertionIndex - 1 (if present) is smaller than firstChar and the
465             // element at insertionIndex (if present) is greater than firstChar.
466             int insertionIndex = -1 - searchResult; // insertionIndex is in [0; array length]
467             if (insertionIndex == 0) {
468                 // 'A' or 'B' -- never released to public
469                 return 1;
470             } else {
471                 // The element at insertionIndex - 1 is the newest older codename.
472                 // API Level bumped by at least 1 for every change in the first letter of codename
473                 Pair<Character, Integer> newestOlderCodenameMapping =
474                         sortedCodenamesFirstCharToApiLevel[insertionIndex - 1];
475                 char newestOlderCodenameFirstChar = newestOlderCodenameMapping.getFirst();
476                 int newestOlderCodenameApiLevel = newestOlderCodenameMapping.getSecond();
477                 return newestOlderCodenameApiLevel + (firstChar - newestOlderCodenameFirstChar);
478             }
479         }
480 
481         throw new CodenameMinSdkVersionException(
482                 "Unable to determine APK's minimum supported Android platform version"
483                         + " : Unsupported codename in " + ANDROID_MANIFEST_ZIP_ENTRY_NAME
484                         + "'s minSdkVersion: \"" + codename + "\"",
485                 codename);
486     }
487 
488     /**
489      * Returns {@code true} if the APK is debuggable according to its {@code AndroidManifest.xml}.
490      * See the {@code android:debuggable} attribute of the {@code application} element.
491      *
492      * @param androidManifestContents contents of {@code AndroidManifest.xml} in binary Android
493      *        resource format
494      *
495      * @throws ApkFormatException if the manifest is malformed
496      */
getDebuggableFromBinaryAndroidManifest( ByteBuffer androidManifestContents)497     public static boolean getDebuggableFromBinaryAndroidManifest(
498             ByteBuffer androidManifestContents) throws ApkFormatException {
499         // IMPLEMENTATION NOTE: Whether the package is debuggable is declared using the first
500         // "application" element which is a child of the top-level manifest element. The debuggable
501         // attribute of this application element is coerced to a boolean value. If there is no
502         // application element or if it doesn't declare the debuggable attribute, the package is
503         // considered not debuggable.
504 
505         try {
506             AndroidBinXmlParser parser = new AndroidBinXmlParser(androidManifestContents);
507             int eventType = parser.getEventType();
508             while (eventType != AndroidBinXmlParser.EVENT_END_DOCUMENT) {
509                 if ((eventType == AndroidBinXmlParser.EVENT_START_ELEMENT)
510                         && (parser.getDepth() == 2)
511                         && ("application".equals(parser.getName()))
512                         && (parser.getNamespace().isEmpty())) {
513                     for (int i = 0; i < parser.getAttributeCount(); i++) {
514                         if (parser.getAttributeNameResourceId(i) == DEBUGGABLE_ATTR_ID) {
515                             int valueType = parser.getAttributeValueType(i);
516                             switch (valueType) {
517                                 case AndroidBinXmlParser.VALUE_TYPE_BOOLEAN:
518                                 case AndroidBinXmlParser.VALUE_TYPE_STRING:
519                                 case AndroidBinXmlParser.VALUE_TYPE_INT:
520                                     String value = parser.getAttributeStringValue(i);
521                                     return ("true".equals(value))
522                                             || ("TRUE".equals(value))
523                                             || ("1".equals(value));
524                                 case AndroidBinXmlParser.VALUE_TYPE_REFERENCE:
525                                     // References to resources are not supported on purpose. The
526                                     // reason is that the resolved value depends on the resource
527                                     // configuration (e.g, MNC/MCC, locale, screen density) used
528                                     // at resolution time. As a result, the same APK may appear as
529                                     // debuggable in one situation and as non-debuggable in another
530                                     // situation. Such APKs may put users at risk.
531                                     throw new ApkFormatException(
532                                             "Unable to determine whether APK is debuggable"
533                                                     + ": " + ANDROID_MANIFEST_ZIP_ENTRY_NAME + "'s"
534                                                     + " android:debuggable attribute references a"
535                                                     + " resource. References are not supported for"
536                                                     + " security reasons. Only constant boolean,"
537                                                     + " string and int values are supported.");
538                                 default:
539                                     throw new ApkFormatException(
540                                             "Unable to determine whether APK is debuggable"
541                                                     + ": " + ANDROID_MANIFEST_ZIP_ENTRY_NAME + "'s"
542                                                     + " android:debuggable attribute uses"
543                                                     + " unsupported value type. Only boolean,"
544                                                     + " string and int values are supported.");
545                             }
546                         }
547                     }
548                     // This application element does not declare the debuggable attribute
549                     return false;
550                 }
551                 eventType = parser.next();
552             }
553 
554             // No application element found
555             return false;
556         } catch (AndroidBinXmlParser.XmlParserException e) {
557             throw new ApkFormatException(
558                     "Unable to determine whether APK is debuggable: malformed binary resource: "
559                             + ANDROID_MANIFEST_ZIP_ENTRY_NAME,
560                     e);
561         }
562     }
563 
564     /**
565      * Returns the package name of the APK according to its {@code AndroidManifest.xml} or
566      * {@code null} if package name is not declared. See the {@code package} attribute of the
567      * {@code manifest} element.
568      *
569      * @param androidManifestContents contents of {@code AndroidManifest.xml} in binary Android
570      *        resource format
571      *
572      * @throws ApkFormatException if the manifest is malformed
573      */
getPackageNameFromBinaryAndroidManifest( ByteBuffer androidManifestContents)574     public static String getPackageNameFromBinaryAndroidManifest(
575             ByteBuffer androidManifestContents) throws ApkFormatException {
576         // IMPLEMENTATION NOTE: Package name is declared as the "package" attribute of the top-level
577         // manifest element. Interestingly, as opposed to most other attributes, Android Package
578         // Manager looks up this attribute by its name rather than by its resource ID.
579 
580         try {
581             AndroidBinXmlParser parser = new AndroidBinXmlParser(androidManifestContents);
582             int eventType = parser.getEventType();
583             while (eventType != AndroidBinXmlParser.EVENT_END_DOCUMENT) {
584                 if ((eventType == AndroidBinXmlParser.EVENT_START_ELEMENT)
585                         && (parser.getDepth() == 1)
586                         && ("manifest".equals(parser.getName()))
587                         && (parser.getNamespace().isEmpty())) {
588                     for (int i = 0; i < parser.getAttributeCount(); i++) {
589                         if ("package".equals(parser.getAttributeName(i))
590                                 && (parser.getNamespace().isEmpty())) {
591                             return parser.getAttributeStringValue(i);
592                         }
593                     }
594                     // No "package" attribute found
595                     return null;
596                 }
597                 eventType = parser.next();
598             }
599 
600             // No manifest element found
601             return null;
602         } catch (AndroidBinXmlParser.XmlParserException e) {
603             throw new ApkFormatException(
604                     "Unable to determine APK package name: malformed binary resource: "
605                             + ANDROID_MANIFEST_ZIP_ENTRY_NAME,
606                     e);
607         }
608     }
609 
computeSha256DigestBytes(byte[] data)610     public static byte[] computeSha256DigestBytes(byte[] data) {
611         MessageDigest messageDigest;
612         try {
613             messageDigest = MessageDigest.getInstance("SHA-256");
614         } catch (NoSuchAlgorithmException e) {
615             throw new IllegalStateException("SHA-256 is not found", e);
616         }
617         messageDigest.update(data);
618         return messageDigest.digest();
619     }
620 }
621