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