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