1 /* 2 * Copyright (C) 2017 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.annotation.NonNull; 20 import android.annotation.Nullable; 21 22 import java.io.IOException; 23 import java.io.RandomAccessFile; 24 import java.nio.ByteBuffer; 25 import java.nio.ByteOrder; 26 import java.security.DigestException; 27 import java.security.MessageDigest; 28 import java.security.NoSuchAlgorithmException; 29 import java.util.ArrayList; 30 31 /** 32 * VerityBuilder builds the verity Merkle tree and other metadata. The generated tree format can 33 * be stored on disk for fs-verity setup and used by kernel. The builder support standard 34 * fs-verity, and Android specific apk-verity that requires additional kernel patches. 35 * 36 * <p>Unlike a regular Merkle tree of fs-verity, the apk-verity tree does not cover the file content 37 * fully, and has to skip APK Signing Block with some special treatment for the "Central Directory 38 * offset" field of ZIP End of Central Directory. 39 * 40 * @hide 41 */ 42 public abstract class VerityBuilder { VerityBuilder()43 private VerityBuilder() {} 44 45 private static final int CHUNK_SIZE_BYTES = 4096; // Typical Linux block size 46 private static final int DIGEST_SIZE_BYTES = 32; // SHA-256 size 47 private static final int FSVERITY_HEADER_SIZE_BYTES = 64; 48 private static final int ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_SIZE = 4; 49 private static final int ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_OFFSET = 16; 50 private static final String JCA_DIGEST_ALGORITHM = "SHA-256"; 51 private static final byte[] DEFAULT_SALT = new byte[8]; 52 53 /** Result generated by the builder. */ 54 public static class VerityResult { 55 /** Raw fs-verity metadata and Merkle tree ready to be deployed on disk. */ 56 public final ByteBuffer verityData; 57 58 /** Size of the Merkle tree in {@code verityData}. */ 59 public final int merkleTreeSize; 60 61 /** Root hash of the Merkle tree. */ 62 public final byte[] rootHash; 63 VerityResult(ByteBuffer verityData, int merkleTreeSize, byte[] rootHash)64 private VerityResult(ByteBuffer verityData, int merkleTreeSize, byte[] rootHash) { 65 this.verityData = verityData; 66 this.merkleTreeSize = merkleTreeSize; 67 this.rootHash = rootHash; 68 } 69 } 70 71 /** 72 * Generates the 4k, SHA-256 based Merkle tree for the given APK and stores in the {@link 73 * ByteBuffer} created by the {@link ByteBufferFactory}. The Merkle tree does not cover Signing 74 * Block specificed in {@code signatureInfo}. The output is suitable to be used as the on-disk 75 * format for fs-verity to use (with elide and patch extensions). 76 * 77 * @return VerityResult containing a buffer with the generated Merkle tree stored at the 78 * front, the tree size, and the calculated root hash. 79 */ 80 @NonNull generateApkVerityTree(@onNull RandomAccessFile apk, @Nullable SignatureInfo signatureInfo, @NonNull ByteBufferFactory bufferFactory)81 public static VerityResult generateApkVerityTree(@NonNull RandomAccessFile apk, 82 @Nullable SignatureInfo signatureInfo, @NonNull ByteBufferFactory bufferFactory) 83 throws IOException, SecurityException, NoSuchAlgorithmException, DigestException { 84 return generateVerityTreeInternal(apk, bufferFactory, signatureInfo); 85 } 86 87 @NonNull generateVerityTreeInternal(@onNull RandomAccessFile apk, @NonNull ByteBufferFactory bufferFactory, @Nullable SignatureInfo signatureInfo)88 private static VerityResult generateVerityTreeInternal(@NonNull RandomAccessFile apk, 89 @NonNull ByteBufferFactory bufferFactory, @Nullable SignatureInfo signatureInfo) 90 throws IOException, SecurityException, NoSuchAlgorithmException, DigestException { 91 long signingBlockSize = 92 signatureInfo.centralDirOffset - signatureInfo.apkSigningBlockOffset; 93 long dataSize = apk.getChannel().size() - signingBlockSize; 94 int[] levelOffset = calculateVerityLevelOffset(dataSize); 95 int merkleTreeSize = levelOffset[levelOffset.length - 1]; 96 97 ByteBuffer output = bufferFactory.create( 98 merkleTreeSize 99 + CHUNK_SIZE_BYTES); // maximum size of apk-verity metadata 100 output.order(ByteOrder.LITTLE_ENDIAN); 101 ByteBuffer tree = slice(output, 0, merkleTreeSize); 102 byte[] apkRootHash = generateVerityTreeInternal(apk, signatureInfo, DEFAULT_SALT, 103 levelOffset, tree); 104 return new VerityResult(output, merkleTreeSize, apkRootHash); 105 } 106 generateApkVerityFooter(@onNull RandomAccessFile apk, @NonNull SignatureInfo signatureInfo, @NonNull ByteBuffer footerOutput)107 static void generateApkVerityFooter(@NonNull RandomAccessFile apk, 108 @NonNull SignatureInfo signatureInfo, @NonNull ByteBuffer footerOutput) 109 throws IOException { 110 footerOutput.order(ByteOrder.LITTLE_ENDIAN); 111 generateApkVerityHeader(footerOutput, apk.getChannel().size(), DEFAULT_SALT); 112 long signingBlockSize = 113 signatureInfo.centralDirOffset - signatureInfo.apkSigningBlockOffset; 114 generateApkVerityExtensions(footerOutput, signatureInfo.apkSigningBlockOffset, 115 signingBlockSize, signatureInfo.eocdOffset); 116 } 117 118 /** 119 * Generates the fs-verity hash tree. It is the actual verity tree format on disk, as is 120 * re-generated on device. 121 * 122 * The tree is built bottom up. The bottom level has 256-bit digest for each 4 KB block in the 123 * input file. If the total size is larger than 4 KB, take this level as input and repeat the 124 * same procedure, until the level is within 4 KB. If salt is given, it will apply to each 125 * digestion before the actual data. 126 * 127 * The returned root hash is calculated from the last level of 4 KB chunk, similarly with salt. 128 * 129 * @return the root hash of the generated hash tree. 130 */ generateFsVerityRootHash(@onNull String apkPath, byte[] salt, @NonNull ByteBufferFactory bufferFactory)131 public static byte[] generateFsVerityRootHash(@NonNull String apkPath, byte[] salt, 132 @NonNull ByteBufferFactory bufferFactory) 133 throws IOException, NoSuchAlgorithmException, DigestException { 134 try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) { 135 int[] levelOffset = calculateVerityLevelOffset(apk.length()); 136 int merkleTreeSize = levelOffset[levelOffset.length - 1]; 137 138 ByteBuffer output = bufferFactory.create( 139 merkleTreeSize 140 + CHUNK_SIZE_BYTES); // maximum size of apk-verity metadata 141 output.order(ByteOrder.LITTLE_ENDIAN); 142 ByteBuffer tree = slice(output, 0, merkleTreeSize); 143 return generateFsVerityTreeInternal(apk, salt, levelOffset, tree); 144 } 145 } 146 147 /** 148 * Generates the apk-verity header and hash tree to be used by kernel for the given apk. This 149 * method does not check whether the root hash exists in the Signing Block or not. 150 * 151 * <p>The output is stored in the {@link ByteBuffer} created by the given {@link 152 * ByteBufferFactory}. 153 * 154 * @return the root hash of the generated hash tree. 155 */ 156 @NonNull generateApkVerity(@onNull String apkPath, @NonNull ByteBufferFactory bufferFactory, @NonNull SignatureInfo signatureInfo)157 static byte[] generateApkVerity(@NonNull String apkPath, 158 @NonNull ByteBufferFactory bufferFactory, @NonNull SignatureInfo signatureInfo) 159 throws IOException, SignatureNotFoundException, SecurityException, DigestException, 160 NoSuchAlgorithmException { 161 try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) { 162 VerityResult result = generateVerityTreeInternal(apk, bufferFactory, signatureInfo); 163 ByteBuffer footer = slice(result.verityData, result.merkleTreeSize, 164 result.verityData.limit()); 165 generateApkVerityFooter(apk, signatureInfo, footer); 166 // Put the reverse offset to apk-verity header at the end. 167 footer.putInt(footer.position() + 4); 168 result.verityData.limit(result.merkleTreeSize + footer.position()); 169 return result.rootHash; 170 } 171 } 172 173 /** 174 * A helper class to consume and digest data by block continuously, and write into a buffer. 175 */ 176 private static class BufferedDigester implements DataDigester { 177 /** Amount of the data to digest in each cycle before writting out the digest. */ 178 private static final int BUFFER_SIZE = CHUNK_SIZE_BYTES; 179 180 /** 181 * Amount of data the {@link MessageDigest} has consumed since the last reset. This must be 182 * always less than BUFFER_SIZE since {@link MessageDigest} is reset whenever it has 183 * consumed BUFFER_SIZE of data. 184 */ 185 private int mBytesDigestedSinceReset; 186 187 /** The final output {@link ByteBuffer} to write the digest to sequentially. */ 188 private final ByteBuffer mOutput; 189 190 private final MessageDigest mMd; 191 private final byte[] mDigestBuffer = new byte[DIGEST_SIZE_BYTES]; 192 private final byte[] mSalt; 193 BufferedDigester(@ullable byte[] salt, @NonNull ByteBuffer output)194 private BufferedDigester(@Nullable byte[] salt, @NonNull ByteBuffer output) 195 throws NoSuchAlgorithmException { 196 mSalt = salt; 197 mOutput = output.slice(); 198 mMd = MessageDigest.getInstance(JCA_DIGEST_ALGORITHM); 199 if (mSalt != null) { 200 mMd.update(mSalt); 201 } 202 mBytesDigestedSinceReset = 0; 203 } 204 205 /** 206 * Consumes and digests data up to BUFFER_SIZE (may continue from the previous remaining), 207 * then writes the final digest to the output buffer. Repeat until all data are consumed. 208 * If the last consumption is not enough for BUFFER_SIZE, the state will stay and future 209 * consumption will continuous from there. 210 */ 211 @Override consume(ByteBuffer buffer)212 public void consume(ByteBuffer buffer) throws DigestException { 213 int offset = buffer.position(); 214 int remaining = buffer.remaining(); 215 while (remaining > 0) { 216 int allowance = (int) Math.min(remaining, BUFFER_SIZE - mBytesDigestedSinceReset); 217 // Optimization: set the buffer limit to avoid allocating a new ByteBuffer object. 218 buffer.limit(buffer.position() + allowance); 219 mMd.update(buffer); 220 offset += allowance; 221 remaining -= allowance; 222 mBytesDigestedSinceReset += allowance; 223 224 if (mBytesDigestedSinceReset == BUFFER_SIZE) { 225 mMd.digest(mDigestBuffer, 0, mDigestBuffer.length); 226 mOutput.put(mDigestBuffer); 227 // After digest, MessageDigest resets automatically, so no need to reset again. 228 if (mSalt != null) { 229 mMd.update(mSalt); 230 } 231 mBytesDigestedSinceReset = 0; 232 } 233 } 234 } 235 assertEmptyBuffer()236 public void assertEmptyBuffer() throws DigestException { 237 if (mBytesDigestedSinceReset != 0) { 238 throw new IllegalStateException("Buffer is not empty: " + mBytesDigestedSinceReset); 239 } 240 } 241 fillUpLastOutputChunk()242 private void fillUpLastOutputChunk() { 243 int lastBlockSize = (int) (mOutput.position() % BUFFER_SIZE); 244 if (lastBlockSize == 0) { 245 return; 246 } 247 mOutput.put(ByteBuffer.allocate(BUFFER_SIZE - lastBlockSize)); 248 } 249 } 250 251 /** 252 * Digest the source by chunk in the given range. If the last chunk is not a full chunk, 253 * digest the remaining. 254 */ consumeByChunk(DataDigester digester, DataSource source, int chunkSize)255 private static void consumeByChunk(DataDigester digester, DataSource source, int chunkSize) 256 throws IOException, DigestException { 257 long inputRemaining = source.size(); 258 long inputOffset = 0; 259 while (inputRemaining > 0) { 260 int size = (int) Math.min(inputRemaining, chunkSize); 261 source.feedIntoDataDigester(digester, inputOffset, size); 262 inputOffset += size; 263 inputRemaining -= size; 264 } 265 } 266 267 // Rationale: 1) 1 MB should fit in memory space on all devices. 2) It is not too granular 268 // thus the syscall overhead is not too big. 269 private static final int MMAP_REGION_SIZE_BYTES = 1024 * 1024; 270 generateFsVerityDigestAtLeafLevel(RandomAccessFile file, @Nullable byte[] salt, ByteBuffer output)271 private static void generateFsVerityDigestAtLeafLevel(RandomAccessFile file, 272 @Nullable byte[] salt, ByteBuffer output) 273 throws IOException, NoSuchAlgorithmException, DigestException { 274 BufferedDigester digester = new BufferedDigester(salt, output); 275 276 // 1. Digest the whole file by chunks. 277 consumeByChunk(digester, 278 DataSource.create(file.getFD(), 0, file.length()), 279 MMAP_REGION_SIZE_BYTES); 280 281 // 2. Pad 0s up to the nearest 4096-byte block before hashing. 282 int lastIncompleteChunkSize = (int) (file.length() % CHUNK_SIZE_BYTES); 283 if (lastIncompleteChunkSize != 0) { 284 digester.consume(ByteBuffer.allocate(CHUNK_SIZE_BYTES - lastIncompleteChunkSize)); 285 } 286 digester.assertEmptyBuffer(); 287 288 // 3. Fill up the rest of buffer with 0s. 289 digester.fillUpLastOutputChunk(); 290 } 291 generateApkVerityDigestAtLeafLevel(RandomAccessFile apk, SignatureInfo signatureInfo, byte[] salt, ByteBuffer output)292 private static void generateApkVerityDigestAtLeafLevel(RandomAccessFile apk, 293 SignatureInfo signatureInfo, byte[] salt, ByteBuffer output) 294 throws IOException, NoSuchAlgorithmException, DigestException { 295 BufferedDigester digester = new BufferedDigester(salt, output); 296 297 // 1. Digest from the beginning of the file, until APK Signing Block is reached. 298 consumeByChunk(digester, 299 DataSource.create(apk.getFD(), 0, signatureInfo.apkSigningBlockOffset), 300 MMAP_REGION_SIZE_BYTES); 301 302 // 2. Skip APK Signing Block and continue digesting, until the Central Directory offset 303 // field in EoCD is reached. 304 long eocdCdOffsetFieldPosition = 305 signatureInfo.eocdOffset + ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_OFFSET; 306 consumeByChunk(digester, 307 DataSource.create(apk.getFD(), signatureInfo.centralDirOffset, 308 eocdCdOffsetFieldPosition - signatureInfo.centralDirOffset), 309 MMAP_REGION_SIZE_BYTES); 310 311 // 3. Consume offset of Signing Block as an alternative EoCD. 312 ByteBuffer alternativeCentralDirOffset = ByteBuffer.allocate( 313 ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_SIZE).order(ByteOrder.LITTLE_ENDIAN); 314 alternativeCentralDirOffset.putInt(Math.toIntExact(signatureInfo.apkSigningBlockOffset)); 315 alternativeCentralDirOffset.flip(); 316 digester.consume(alternativeCentralDirOffset); 317 318 // 4. Read from end of the Central Directory offset field in EoCD to the end of the file. 319 long offsetAfterEocdCdOffsetField = 320 eocdCdOffsetFieldPosition + ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_SIZE; 321 consumeByChunk(digester, 322 DataSource.create(apk.getFD(), offsetAfterEocdCdOffsetField, 323 apk.getChannel().size() - offsetAfterEocdCdOffsetField), 324 MMAP_REGION_SIZE_BYTES); 325 326 // 5. Pad 0s up to the nearest 4096-byte block before hashing. 327 int lastIncompleteChunkSize = (int) (apk.getChannel().size() % CHUNK_SIZE_BYTES); 328 if (lastIncompleteChunkSize != 0) { 329 digester.consume(ByteBuffer.allocate(CHUNK_SIZE_BYTES - lastIncompleteChunkSize)); 330 } 331 digester.assertEmptyBuffer(); 332 333 // 6. Fill up the rest of buffer with 0s. 334 digester.fillUpLastOutputChunk(); 335 } 336 337 @NonNull generateFsVerityTreeInternal(@onNull RandomAccessFile apk, @Nullable byte[] salt, @NonNull int[] levelOffset, @NonNull ByteBuffer output)338 private static byte[] generateFsVerityTreeInternal(@NonNull RandomAccessFile apk, 339 @Nullable byte[] salt, @NonNull int[] levelOffset, @NonNull ByteBuffer output) 340 throws IOException, NoSuchAlgorithmException, DigestException { 341 // 1. Digest the apk to generate the leaf level hashes. 342 generateFsVerityDigestAtLeafLevel(apk, salt, 343 slice(output, levelOffset[levelOffset.length - 2], 344 levelOffset[levelOffset.length - 1])); 345 346 // 2. Digest the lower level hashes bottom up. 347 for (int level = levelOffset.length - 3; level >= 0; level--) { 348 ByteBuffer inputBuffer = slice(output, levelOffset[level + 1], levelOffset[level + 2]); 349 ByteBuffer outputBuffer = slice(output, levelOffset[level], levelOffset[level + 1]); 350 351 DataSource source = new ByteBufferDataSource(inputBuffer); 352 BufferedDigester digester = new BufferedDigester(salt, outputBuffer); 353 consumeByChunk(digester, source, CHUNK_SIZE_BYTES); 354 digester.assertEmptyBuffer(); 355 digester.fillUpLastOutputChunk(); 356 } 357 358 // 3. Digest the first block (i.e. first level) to generate the root hash. 359 byte[] rootHash = new byte[DIGEST_SIZE_BYTES]; 360 BufferedDigester digester = new BufferedDigester(salt, ByteBuffer.wrap(rootHash)); 361 digester.consume(slice(output, 0, CHUNK_SIZE_BYTES)); 362 digester.assertEmptyBuffer(); 363 return rootHash; 364 } 365 366 @NonNull generateVerityTreeInternal(@onNull RandomAccessFile apk, @Nullable SignatureInfo signatureInfo, @Nullable byte[] salt, @NonNull int[] levelOffset, @NonNull ByteBuffer output)367 private static byte[] generateVerityTreeInternal(@NonNull RandomAccessFile apk, 368 @Nullable SignatureInfo signatureInfo, @Nullable byte[] salt, 369 @NonNull int[] levelOffset, @NonNull ByteBuffer output) 370 throws IOException, NoSuchAlgorithmException, DigestException { 371 // 1. Digest the apk to generate the leaf level hashes. 372 assertSigningBlockAlignedAndHasFullPages(signatureInfo); 373 generateApkVerityDigestAtLeafLevel(apk, signatureInfo, salt, slice(output, 374 levelOffset[levelOffset.length - 2], levelOffset[levelOffset.length - 1])); 375 376 // 2. Digest the lower level hashes bottom up. 377 for (int level = levelOffset.length - 3; level >= 0; level--) { 378 ByteBuffer inputBuffer = slice(output, levelOffset[level + 1], levelOffset[level + 2]); 379 ByteBuffer outputBuffer = slice(output, levelOffset[level], levelOffset[level + 1]); 380 381 DataSource source = new ByteBufferDataSource(inputBuffer); 382 BufferedDigester digester = new BufferedDigester(salt, outputBuffer); 383 consumeByChunk(digester, source, CHUNK_SIZE_BYTES); 384 digester.assertEmptyBuffer(); 385 digester.fillUpLastOutputChunk(); 386 } 387 388 // 3. Digest the first block (i.e. first level) to generate the root hash. 389 byte[] rootHash = new byte[DIGEST_SIZE_BYTES]; 390 BufferedDigester digester = new BufferedDigester(salt, ByteBuffer.wrap(rootHash)); 391 digester.consume(slice(output, 0, CHUNK_SIZE_BYTES)); 392 digester.assertEmptyBuffer(); 393 return rootHash; 394 } 395 generateApkVerityHeader(ByteBuffer buffer, long fileSize, byte[] salt)396 private static ByteBuffer generateApkVerityHeader(ByteBuffer buffer, long fileSize, 397 byte[] salt) { 398 if (salt.length != 8) { 399 throw new IllegalArgumentException("salt is not 8 bytes long"); 400 } 401 402 // TODO(b/30972906): update the reference when there is a better one in public. 403 buffer.put("TrueBrew".getBytes()); // magic 404 405 buffer.put((byte) 1); // major version 406 buffer.put((byte) 0); // minor version 407 buffer.put((byte) 12); // log2(block-size): log2(4096) 408 buffer.put((byte) 7); // log2(leaves-per-node): log2(4096 / 32) 409 410 buffer.putShort((short) 1); // meta algorithm, SHA256 == 1 411 buffer.putShort((short) 1); // data algorithm, SHA256 == 1 412 413 buffer.putInt(0); // flags 414 buffer.putInt(0); // reserved 415 416 buffer.putLong(fileSize); // original file size 417 418 buffer.put((byte) 2); // authenticated extension count 419 buffer.put((byte) 0); // unauthenticated extension count 420 buffer.put(salt); // salt (8 bytes) 421 skip(buffer, 22); // reserved 422 423 return buffer; 424 } 425 generateApkVerityExtensions(ByteBuffer buffer, long signingBlockOffset, long signingBlockSize, long eocdOffset)426 private static ByteBuffer generateApkVerityExtensions(ByteBuffer buffer, 427 long signingBlockOffset, long signingBlockSize, long eocdOffset) { 428 // Snapshot of the experimental fs-verity structs (different from upstream). 429 // 430 // struct fsverity_extension_elide { 431 // __le64 offset; 432 // __le64 length; 433 // } 434 // 435 // struct fsverity_extension_patch { 436 // __le64 offset; 437 // u8 databytes[]; 438 // }; 439 440 final int kSizeOfFsverityExtensionHeader = 8; 441 final int kExtensionSizeAlignment = 8; 442 443 { 444 // struct fsverity_extension #1 445 final int kSizeOfFsverityElidedExtension = 16; 446 447 // First field is total size of extension, padded to 64-bit alignment 448 buffer.putInt(kSizeOfFsverityExtensionHeader + kSizeOfFsverityElidedExtension); 449 buffer.putShort((short) 1); // ID of elide extension 450 skip(buffer, 2); // reserved 451 452 // struct fsverity_extension_elide 453 buffer.putLong(signingBlockOffset); 454 buffer.putLong(signingBlockSize); 455 } 456 457 { 458 // struct fsverity_extension #2 459 final int kTotalSize = kSizeOfFsverityExtensionHeader 460 + 8 // offset size 461 + ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_SIZE; 462 463 buffer.putInt(kTotalSize); // Total size of extension, padded to 64-bit alignment 464 buffer.putShort((short) 2); // ID of patch extension 465 skip(buffer, 2); // reserved 466 467 // struct fsverity_extension_patch 468 buffer.putLong(eocdOffset + ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_OFFSET); // offset 469 buffer.putInt(Math.toIntExact(signingBlockOffset)); // databytes 470 471 // The extension needs to be 0-padded at the end, since the length may not be multiple 472 // of 8. 473 int kPadding = kExtensionSizeAlignment - kTotalSize % kExtensionSizeAlignment; 474 if (kPadding == kExtensionSizeAlignment) { 475 kPadding = 0; 476 } 477 skip(buffer, kPadding); // padding 478 } 479 480 return buffer; 481 } 482 483 /** 484 * Returns an array of summed area table of level size in the verity tree. In other words, the 485 * returned array is offset of each level in the verity tree file format, plus an additional 486 * offset of the next non-existing level (i.e. end of the last level + 1). Thus the array size 487 * is level + 1. Thus, the returned array is guarantee to have at least 2 elements. 488 */ calculateVerityLevelOffset(long fileSize)489 private static int[] calculateVerityLevelOffset(long fileSize) { 490 ArrayList<Long> levelSize = new ArrayList<>(); 491 while (true) { 492 long levelDigestSize = divideRoundup(fileSize, CHUNK_SIZE_BYTES) * DIGEST_SIZE_BYTES; 493 long chunksSize = CHUNK_SIZE_BYTES * divideRoundup(levelDigestSize, CHUNK_SIZE_BYTES); 494 levelSize.add(chunksSize); 495 if (levelDigestSize <= CHUNK_SIZE_BYTES) { 496 break; 497 } 498 fileSize = levelDigestSize; 499 } 500 501 // Reverse and convert to summed area table. 502 int[] levelOffset = new int[levelSize.size() + 1]; 503 levelOffset[0] = 0; 504 for (int i = 0; i < levelSize.size(); i++) { 505 // We don't support verity tree if it is larger then Integer.MAX_VALUE. 506 levelOffset[i + 1] = levelOffset[i] 507 + Math.toIntExact(levelSize.get(levelSize.size() - i - 1)); 508 } 509 return levelOffset; 510 } 511 assertSigningBlockAlignedAndHasFullPages( @onNull SignatureInfo signatureInfo)512 private static void assertSigningBlockAlignedAndHasFullPages( 513 @NonNull SignatureInfo signatureInfo) { 514 if (signatureInfo.apkSigningBlockOffset % CHUNK_SIZE_BYTES != 0) { 515 throw new IllegalArgumentException( 516 "APK Signing Block does not start at the page boundary: " 517 + signatureInfo.apkSigningBlockOffset); 518 } 519 520 if ((signatureInfo.centralDirOffset - signatureInfo.apkSigningBlockOffset) 521 % CHUNK_SIZE_BYTES != 0) { 522 throw new IllegalArgumentException( 523 "Size of APK Signing Block is not a multiple of 4096: " 524 + (signatureInfo.centralDirOffset - signatureInfo.apkSigningBlockOffset)); 525 } 526 } 527 528 /** Returns a slice of the buffer which shares content with the provided buffer. */ slice(ByteBuffer buffer, int begin, int end)529 private static ByteBuffer slice(ByteBuffer buffer, int begin, int end) { 530 ByteBuffer b = buffer.duplicate(); 531 b.position(0); // to ensure position <= limit invariant. 532 b.limit(end); 533 b.position(begin); 534 return b.slice(); 535 } 536 537 /** Skip the {@code ByteBuffer} position by {@code bytes}. */ skip(ByteBuffer buffer, int bytes)538 private static void skip(ByteBuffer buffer, int bytes) { 539 buffer.position(buffer.position() + bytes); 540 } 541 542 /** Divides a number and round up to the closest integer. */ divideRoundup(long dividend, long divisor)543 private static long divideRoundup(long dividend, long divisor) { 544 return (dividend + divisor - 1) / divisor; 545 } 546 } 547