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