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