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 ApkSigningBlockUtils()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 */ findSignature(RandomAccessFile apk, int blockId)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 verifyIntegrity( Map<Integer, byte[]> expectedDigests, RandomAccessFile apk, SignatureInfo signatureInfo)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 verifyIntegrityFor1MbChunkBasedAlgorithm( Map<Integer, byte[]> expectedDigests, FileDescriptor apkFileDescriptor, SignatureInfo signatureInfo)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 computeContentDigestsPer1MbChunk( int[] digestAlgorithms, DataSource[] contents)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 */ parseVerityDigestAndVerifySourceLength( byte[] data, long fileSize, SignatureInfo signatureInfo)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 verifyIntegrityForVerityBasedAlgorithm( byte[] expectedDigest, RandomAccessFile apk, SignatureInfo signatureInfo)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 VerityBuilder.VerityResult verity = VerityBuilder.generateApkVerityTree(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 * Returns the ZIP End of Central Directory (EoCD) and its offset in the file. 352 * 353 * @throws IOException if an I/O error occurs while reading the file. 354 * @throws SignatureNotFoundException if the EoCD could not be found. 355 */ getEocd(RandomAccessFile apk)356 static Pair<ByteBuffer, Long> getEocd(RandomAccessFile apk) 357 throws IOException, SignatureNotFoundException { 358 Pair<ByteBuffer, Long> eocdAndOffsetInFile = 359 ZipUtils.findZipEndOfCentralDirectoryRecord(apk); 360 if (eocdAndOffsetInFile == null) { 361 throw new SignatureNotFoundException( 362 "Not an APK file: ZIP End of Central Directory record not found"); 363 } 364 return eocdAndOffsetInFile; 365 } 366 getCentralDirOffset(ByteBuffer eocd, long eocdOffset)367 static long getCentralDirOffset(ByteBuffer eocd, long eocdOffset) 368 throws SignatureNotFoundException { 369 // Look up the offset of ZIP Central Directory. 370 long centralDirOffset = ZipUtils.getZipEocdCentralDirectoryOffset(eocd); 371 if (centralDirOffset > eocdOffset) { 372 throw new SignatureNotFoundException( 373 "ZIP Central Directory offset out of range: " + centralDirOffset 374 + ". ZIP End of Central Directory offset: " + eocdOffset); 375 } 376 long centralDirSize = ZipUtils.getZipEocdCentralDirectorySizeBytes(eocd); 377 if (centralDirOffset + centralDirSize != eocdOffset) { 378 throw new SignatureNotFoundException( 379 "ZIP Central Directory is not immediately followed by End of Central" 380 + " Directory"); 381 } 382 return centralDirOffset; 383 } 384 getChunkCount(long inputSizeBytes)385 private static long getChunkCount(long inputSizeBytes) { 386 return (inputSizeBytes + CHUNK_SIZE_BYTES - 1) / CHUNK_SIZE_BYTES; 387 } 388 389 private static final int CHUNK_SIZE_BYTES = 1024 * 1024; 390 391 static final int SIGNATURE_RSA_PSS_WITH_SHA256 = 0x0101; 392 static final int SIGNATURE_RSA_PSS_WITH_SHA512 = 0x0102; 393 static final int SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256 = 0x0103; 394 static final int SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512 = 0x0104; 395 static final int SIGNATURE_ECDSA_WITH_SHA256 = 0x0201; 396 static final int SIGNATURE_ECDSA_WITH_SHA512 = 0x0202; 397 static final int SIGNATURE_DSA_WITH_SHA256 = 0x0301; 398 static final int SIGNATURE_VERITY_RSA_PKCS1_V1_5_WITH_SHA256 = 0x0421; 399 static final int SIGNATURE_VERITY_ECDSA_WITH_SHA256 = 0x0423; 400 static final int SIGNATURE_VERITY_DSA_WITH_SHA256 = 0x0425; 401 402 static final int CONTENT_DIGEST_CHUNKED_SHA256 = 1; 403 static final int CONTENT_DIGEST_CHUNKED_SHA512 = 2; 404 static final int CONTENT_DIGEST_VERITY_CHUNKED_SHA256 = 3; 405 compareSignatureAlgorithm(int sigAlgorithm1, int sigAlgorithm2)406 static int compareSignatureAlgorithm(int sigAlgorithm1, int sigAlgorithm2) { 407 int digestAlgorithm1 = getSignatureAlgorithmContentDigestAlgorithm(sigAlgorithm1); 408 int digestAlgorithm2 = getSignatureAlgorithmContentDigestAlgorithm(sigAlgorithm2); 409 return compareContentDigestAlgorithm(digestAlgorithm1, digestAlgorithm2); 410 } 411 compareContentDigestAlgorithm(int digestAlgorithm1, int digestAlgorithm2)412 private static int compareContentDigestAlgorithm(int digestAlgorithm1, int digestAlgorithm2) { 413 switch (digestAlgorithm1) { 414 case CONTENT_DIGEST_CHUNKED_SHA256: 415 switch (digestAlgorithm2) { 416 case CONTENT_DIGEST_CHUNKED_SHA256: 417 return 0; 418 case CONTENT_DIGEST_CHUNKED_SHA512: 419 case CONTENT_DIGEST_VERITY_CHUNKED_SHA256: 420 return -1; 421 default: 422 throw new IllegalArgumentException( 423 "Unknown digestAlgorithm2: " + digestAlgorithm2); 424 } 425 case CONTENT_DIGEST_CHUNKED_SHA512: 426 switch (digestAlgorithm2) { 427 case CONTENT_DIGEST_CHUNKED_SHA256: 428 case CONTENT_DIGEST_VERITY_CHUNKED_SHA256: 429 return 1; 430 case CONTENT_DIGEST_CHUNKED_SHA512: 431 return 0; 432 default: 433 throw new IllegalArgumentException( 434 "Unknown digestAlgorithm2: " + digestAlgorithm2); 435 } 436 case CONTENT_DIGEST_VERITY_CHUNKED_SHA256: 437 switch (digestAlgorithm2) { 438 case CONTENT_DIGEST_CHUNKED_SHA512: 439 return -1; 440 case CONTENT_DIGEST_VERITY_CHUNKED_SHA256: 441 return 0; 442 case CONTENT_DIGEST_CHUNKED_SHA256: 443 return 1; 444 default: 445 throw new IllegalArgumentException( 446 "Unknown digestAlgorithm2: " + digestAlgorithm2); 447 } 448 default: 449 throw new IllegalArgumentException("Unknown digestAlgorithm1: " + digestAlgorithm1); 450 } 451 } 452 getSignatureAlgorithmContentDigestAlgorithm(int sigAlgorithm)453 static int getSignatureAlgorithmContentDigestAlgorithm(int sigAlgorithm) { 454 switch (sigAlgorithm) { 455 case SIGNATURE_RSA_PSS_WITH_SHA256: 456 case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256: 457 case SIGNATURE_ECDSA_WITH_SHA256: 458 case SIGNATURE_DSA_WITH_SHA256: 459 return CONTENT_DIGEST_CHUNKED_SHA256; 460 case SIGNATURE_RSA_PSS_WITH_SHA512: 461 case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512: 462 case SIGNATURE_ECDSA_WITH_SHA512: 463 return CONTENT_DIGEST_CHUNKED_SHA512; 464 case SIGNATURE_VERITY_RSA_PKCS1_V1_5_WITH_SHA256: 465 case SIGNATURE_VERITY_ECDSA_WITH_SHA256: 466 case SIGNATURE_VERITY_DSA_WITH_SHA256: 467 return CONTENT_DIGEST_VERITY_CHUNKED_SHA256; 468 default: 469 throw new IllegalArgumentException( 470 "Unknown signature algorithm: 0x" 471 + Long.toHexString(sigAlgorithm & 0xffffffff)); 472 } 473 } 474 getContentDigestAlgorithmJcaDigestAlgorithm(int digestAlgorithm)475 static String getContentDigestAlgorithmJcaDigestAlgorithm(int digestAlgorithm) { 476 switch (digestAlgorithm) { 477 case CONTENT_DIGEST_CHUNKED_SHA256: 478 case CONTENT_DIGEST_VERITY_CHUNKED_SHA256: 479 return "SHA-256"; 480 case CONTENT_DIGEST_CHUNKED_SHA512: 481 return "SHA-512"; 482 default: 483 throw new IllegalArgumentException( 484 "Unknown content digest algorthm: " + digestAlgorithm); 485 } 486 } 487 getContentDigestAlgorithmOutputSizeBytes(int digestAlgorithm)488 private static int getContentDigestAlgorithmOutputSizeBytes(int digestAlgorithm) { 489 switch (digestAlgorithm) { 490 case CONTENT_DIGEST_CHUNKED_SHA256: 491 case CONTENT_DIGEST_VERITY_CHUNKED_SHA256: 492 return 256 / 8; 493 case CONTENT_DIGEST_CHUNKED_SHA512: 494 return 512 / 8; 495 default: 496 throw new IllegalArgumentException( 497 "Unknown content digest algorthm: " + digestAlgorithm); 498 } 499 } 500 getSignatureAlgorithmJcaKeyAlgorithm(int sigAlgorithm)501 static String getSignatureAlgorithmJcaKeyAlgorithm(int sigAlgorithm) { 502 switch (sigAlgorithm) { 503 case SIGNATURE_RSA_PSS_WITH_SHA256: 504 case SIGNATURE_RSA_PSS_WITH_SHA512: 505 case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256: 506 case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512: 507 case SIGNATURE_VERITY_RSA_PKCS1_V1_5_WITH_SHA256: 508 return "RSA"; 509 case SIGNATURE_ECDSA_WITH_SHA256: 510 case SIGNATURE_ECDSA_WITH_SHA512: 511 case SIGNATURE_VERITY_ECDSA_WITH_SHA256: 512 return "EC"; 513 case SIGNATURE_DSA_WITH_SHA256: 514 case SIGNATURE_VERITY_DSA_WITH_SHA256: 515 return "DSA"; 516 default: 517 throw new IllegalArgumentException( 518 "Unknown signature algorithm: 0x" 519 + Long.toHexString(sigAlgorithm & 0xffffffff)); 520 } 521 } 522 523 static Pair<String, ? extends AlgorithmParameterSpec> getSignatureAlgorithmJcaSignatureAlgorithm(int sigAlgorithm)524 getSignatureAlgorithmJcaSignatureAlgorithm(int sigAlgorithm) { 525 switch (sigAlgorithm) { 526 case SIGNATURE_RSA_PSS_WITH_SHA256: 527 return Pair.create( 528 "SHA256withRSA/PSS", 529 new PSSParameterSpec( 530 "SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 256 / 8, 1)); 531 case SIGNATURE_RSA_PSS_WITH_SHA512: 532 return Pair.create( 533 "SHA512withRSA/PSS", 534 new PSSParameterSpec( 535 "SHA-512", "MGF1", MGF1ParameterSpec.SHA512, 512 / 8, 1)); 536 case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256: 537 case SIGNATURE_VERITY_RSA_PKCS1_V1_5_WITH_SHA256: 538 return Pair.create("SHA256withRSA", null); 539 case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512: 540 return Pair.create("SHA512withRSA", null); 541 case SIGNATURE_ECDSA_WITH_SHA256: 542 case SIGNATURE_VERITY_ECDSA_WITH_SHA256: 543 return Pair.create("SHA256withECDSA", null); 544 case SIGNATURE_ECDSA_WITH_SHA512: 545 return Pair.create("SHA512withECDSA", null); 546 case SIGNATURE_DSA_WITH_SHA256: 547 case SIGNATURE_VERITY_DSA_WITH_SHA256: 548 return Pair.create("SHA256withDSA", null); 549 default: 550 throw new IllegalArgumentException( 551 "Unknown signature algorithm: 0x" 552 + Long.toHexString(sigAlgorithm & 0xffffffff)); 553 } 554 } 555 556 /** 557 * Returns new byte buffer whose content is a shared subsequence of this buffer's content 558 * between the specified start (inclusive) and end (exclusive) positions. As opposed to 559 * {@link ByteBuffer#slice()}, the returned buffer's byte order is the same as the source 560 * buffer's byte order. 561 */ sliceFromTo(ByteBuffer source, int start, int end)562 static ByteBuffer sliceFromTo(ByteBuffer source, int start, int end) { 563 if (start < 0) { 564 throw new IllegalArgumentException("start: " + start); 565 } 566 if (end < start) { 567 throw new IllegalArgumentException("end < start: " + end + " < " + start); 568 } 569 int capacity = source.capacity(); 570 if (end > source.capacity()) { 571 throw new IllegalArgumentException("end > capacity: " + end + " > " + capacity); 572 } 573 int originalLimit = source.limit(); 574 int originalPosition = source.position(); 575 try { 576 source.position(0); 577 source.limit(end); 578 source.position(start); 579 ByteBuffer result = source.slice(); 580 result.order(source.order()); 581 return result; 582 } finally { 583 source.position(0); 584 source.limit(originalLimit); 585 source.position(originalPosition); 586 } 587 } 588 589 /** 590 * Relative <em>get</em> method for reading {@code size} number of bytes from the current 591 * position of this buffer. 592 * 593 * <p>This method reads the next {@code size} bytes at this buffer's current position, 594 * returning them as a {@code ByteBuffer} with start set to 0, limit and capacity set to 595 * {@code size}, byte order set to this buffer's byte order; and then increments the position by 596 * {@code size}. 597 */ getByteBuffer(ByteBuffer source, int size)598 static ByteBuffer getByteBuffer(ByteBuffer source, int size) 599 throws BufferUnderflowException { 600 if (size < 0) { 601 throw new IllegalArgumentException("size: " + size); 602 } 603 int originalLimit = source.limit(); 604 int position = source.position(); 605 int limit = position + size; 606 if ((limit < position) || (limit > originalLimit)) { 607 throw new BufferUnderflowException(); 608 } 609 source.limit(limit); 610 try { 611 ByteBuffer result = source.slice(); 612 result.order(source.order()); 613 source.position(limit); 614 return result; 615 } finally { 616 source.limit(originalLimit); 617 } 618 } 619 getLengthPrefixedSlice(ByteBuffer source)620 static ByteBuffer getLengthPrefixedSlice(ByteBuffer source) throws IOException { 621 if (source.remaining() < 4) { 622 throw new IOException( 623 "Remaining buffer too short to contain length of length-prefixed field." 624 + " Remaining: " + source.remaining()); 625 } 626 int len = source.getInt(); 627 if (len < 0) { 628 throw new IllegalArgumentException("Negative length"); 629 } else if (len > source.remaining()) { 630 throw new IOException("Length-prefixed field longer than remaining buffer." 631 + " Field length: " + len + ", remaining: " + source.remaining()); 632 } 633 return getByteBuffer(source, len); 634 } 635 readLengthPrefixedByteArray(ByteBuffer buf)636 static byte[] readLengthPrefixedByteArray(ByteBuffer buf) throws IOException { 637 int len = buf.getInt(); 638 if (len < 0) { 639 throw new IOException("Negative length"); 640 } else if (len > buf.remaining()) { 641 throw new IOException("Underflow while reading length-prefixed value. Length: " + len 642 + ", available: " + buf.remaining()); 643 } 644 byte[] result = new byte[len]; 645 buf.get(result); 646 return result; 647 } 648 setUnsignedInt32LittleEndian(int value, byte[] result, int offset)649 static void setUnsignedInt32LittleEndian(int value, byte[] result, int offset) { 650 result[offset] = (byte) (value & 0xff); 651 result[offset + 1] = (byte) ((value >>> 8) & 0xff); 652 result[offset + 2] = (byte) ((value >>> 16) & 0xff); 653 result[offset + 3] = (byte) ((value >>> 24) & 0xff); 654 } 655 656 private static final long APK_SIG_BLOCK_MAGIC_HI = 0x3234206b636f6c42L; 657 private static final long APK_SIG_BLOCK_MAGIC_LO = 0x20676953204b5041L; 658 private static final int APK_SIG_BLOCK_MIN_SIZE = 32; 659 findApkSigningBlock( RandomAccessFile apk, long centralDirOffset)660 static Pair<ByteBuffer, Long> findApkSigningBlock( 661 RandomAccessFile apk, long centralDirOffset) 662 throws IOException, SignatureNotFoundException { 663 // FORMAT: 664 // OFFSET DATA TYPE DESCRIPTION 665 // * @+0 bytes uint64: size in bytes (excluding this field) 666 // * @+8 bytes payload 667 // * @-24 bytes uint64: size in bytes (same as the one above) 668 // * @-16 bytes uint128: magic 669 670 if (centralDirOffset < APK_SIG_BLOCK_MIN_SIZE) { 671 throw new SignatureNotFoundException( 672 "APK too small for APK Signing Block. ZIP Central Directory offset: " 673 + centralDirOffset); 674 } 675 // Read the magic and offset in file from the footer section of the block: 676 // * uint64: size of block 677 // * 16 bytes: magic 678 ByteBuffer footer = ByteBuffer.allocate(24); 679 footer.order(ByteOrder.LITTLE_ENDIAN); 680 apk.seek(centralDirOffset - footer.capacity()); 681 apk.readFully(footer.array(), footer.arrayOffset(), footer.capacity()); 682 if ((footer.getLong(8) != APK_SIG_BLOCK_MAGIC_LO) 683 || (footer.getLong(16) != APK_SIG_BLOCK_MAGIC_HI)) { 684 throw new SignatureNotFoundException( 685 "No APK Signing Block before ZIP Central Directory"); 686 } 687 // Read and compare size fields 688 long apkSigBlockSizeInFooter = footer.getLong(0); 689 if ((apkSigBlockSizeInFooter < footer.capacity()) 690 || (apkSigBlockSizeInFooter > Integer.MAX_VALUE - 8)) { 691 throw new SignatureNotFoundException( 692 "APK Signing Block size out of range: " + apkSigBlockSizeInFooter); 693 } 694 int totalSize = (int) (apkSigBlockSizeInFooter + 8); 695 long apkSigBlockOffset = centralDirOffset - totalSize; 696 if (apkSigBlockOffset < 0) { 697 throw new SignatureNotFoundException( 698 "APK Signing Block offset out of range: " + apkSigBlockOffset); 699 } 700 ByteBuffer apkSigBlock = ByteBuffer.allocate(totalSize); 701 apkSigBlock.order(ByteOrder.LITTLE_ENDIAN); 702 apk.seek(apkSigBlockOffset); 703 apk.readFully(apkSigBlock.array(), apkSigBlock.arrayOffset(), apkSigBlock.capacity()); 704 long apkSigBlockSizeInHeader = apkSigBlock.getLong(0); 705 if (apkSigBlockSizeInHeader != apkSigBlockSizeInFooter) { 706 throw new SignatureNotFoundException( 707 "APK Signing Block sizes in header and footer do not match: " 708 + apkSigBlockSizeInHeader + " vs " + apkSigBlockSizeInFooter); 709 } 710 return Pair.create(apkSigBlock, apkSigBlockOffset); 711 } 712 findApkSignatureSchemeBlock(ByteBuffer apkSigningBlock, int blockId)713 static ByteBuffer findApkSignatureSchemeBlock(ByteBuffer apkSigningBlock, int blockId) 714 throws SignatureNotFoundException { 715 checkByteOrderLittleEndian(apkSigningBlock); 716 // FORMAT: 717 // OFFSET DATA TYPE DESCRIPTION 718 // * @+0 bytes uint64: size in bytes (excluding this field) 719 // * @+8 bytes pairs 720 // * @-24 bytes uint64: size in bytes (same as the one above) 721 // * @-16 bytes uint128: magic 722 ByteBuffer pairs = sliceFromTo(apkSigningBlock, 8, apkSigningBlock.capacity() - 24); 723 724 int entryCount = 0; 725 while (pairs.hasRemaining()) { 726 entryCount++; 727 if (pairs.remaining() < 8) { 728 throw new SignatureNotFoundException( 729 "Insufficient data to read size of APK Signing Block entry #" + entryCount); 730 } 731 long lenLong = pairs.getLong(); 732 if ((lenLong < 4) || (lenLong > Integer.MAX_VALUE)) { 733 throw new SignatureNotFoundException( 734 "APK Signing Block entry #" + entryCount 735 + " size out of range: " + lenLong); 736 } 737 int len = (int) lenLong; 738 int nextEntryPos = pairs.position() + len; 739 if (len > pairs.remaining()) { 740 throw new SignatureNotFoundException( 741 "APK Signing Block entry #" + entryCount + " size out of range: " + len 742 + ", available: " + pairs.remaining()); 743 } 744 int id = pairs.getInt(); 745 if (id == blockId) { 746 return getByteBuffer(pairs, len - 4); 747 } 748 pairs.position(nextEntryPos); 749 } 750 751 throw new SignatureNotFoundException( 752 "No block with ID " + blockId + " in APK Signing Block."); 753 } 754 checkByteOrderLittleEndian(ByteBuffer buffer)755 private static void checkByteOrderLittleEndian(ByteBuffer buffer) { 756 if (buffer.order() != ByteOrder.LITTLE_ENDIAN) { 757 throw new IllegalArgumentException("ByteBuffer byte order must be little endian"); 758 } 759 } 760 761 /** 762 * {@link DataDigester} that updates multiple {@link MessageDigest}s whenever data is fed. 763 */ 764 private static class MultipleDigestDataDigester implements DataDigester { 765 private final MessageDigest[] mMds; 766 MultipleDigestDataDigester(MessageDigest[] mds)767 MultipleDigestDataDigester(MessageDigest[] mds) { 768 mMds = mds; 769 } 770 771 @Override consume(ByteBuffer buffer)772 public void consume(ByteBuffer buffer) { 773 buffer = buffer.slice(); 774 for (MessageDigest md : mMds) { 775 buffer.position(0); 776 md.update(buffer); 777 } 778 } 779 } 780 781 } 782