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