diff options
6 files changed, 89 insertions, 117 deletions
diff --git a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java index 533d72590f0a..1203541756e8 100644 --- a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java +++ b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java @@ -410,11 +410,11 @@ public class ApkSignatureSchemeV2Verifier { NoSuchAlgorithmException { try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) { SignatureInfo signatureInfo = findSignature(apk); - return ApkSigningBlockUtils.generateApkVerity(apkPath, bufferFactory, signatureInfo); + return ApkVerityBuilder.generateApkVerity(apkPath, bufferFactory, signatureInfo); } } - static byte[] generateFsverityRootHash(String apkPath) + static byte[] generateApkVerityRootHash(String apkPath) throws IOException, SignatureNotFoundException, DigestException, NoSuchAlgorithmException { try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) { @@ -423,7 +423,7 @@ public class ApkSignatureSchemeV2Verifier { if (vSigner.verityRootHash == null) { return null; } - return ApkVerityBuilder.generateFsverityRootHash( + return ApkVerityBuilder.generateApkVerityRootHash( apk, ByteBuffer.wrap(vSigner.verityRootHash), signatureInfo); } } diff --git a/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java index 758cd2b877f2..939522dcd57f 100644 --- a/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java +++ b/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java @@ -534,11 +534,11 @@ public class ApkSignatureSchemeV3Verifier { NoSuchAlgorithmException { try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) { SignatureInfo signatureInfo = findSignature(apk); - return ApkSigningBlockUtils.generateApkVerity(apkPath, bufferFactory, signatureInfo); + return ApkVerityBuilder.generateApkVerity(apkPath, bufferFactory, signatureInfo); } } - static byte[] generateFsverityRootHash(String apkPath) + static byte[] generateApkVerityRootHash(String apkPath) throws NoSuchAlgorithmException, DigestException, IOException, SignatureNotFoundException { try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) { @@ -547,7 +547,7 @@ public class ApkSignatureSchemeV3Verifier { if (vSigner.verityRootHash == null) { return null; } - return ApkVerityBuilder.generateFsverityRootHash( + return ApkVerityBuilder.generateApkVerityRootHash( apk, ByteBuffer.wrap(vSigner.verityRootHash), signatureInfo); } } diff --git a/core/java/android/util/apk/ApkSignatureVerifier.java b/core/java/android/util/apk/ApkSignatureVerifier.java index de9f55b09200..a299b11c2b25 100644 --- a/core/java/android/util/apk/ApkSignatureVerifier.java +++ b/core/java/android/util/apk/ApkSignatureVerifier.java @@ -432,16 +432,16 @@ public class ApkSignatureVerifier { * * @return FSverity root hash */ - public static byte[] generateFsverityRootHash(String apkPath) + public static byte[] generateApkVerityRootHash(String apkPath) throws NoSuchAlgorithmException, DigestException, IOException { // first try v3 try { - return ApkSignatureSchemeV3Verifier.generateFsverityRootHash(apkPath); + return ApkSignatureSchemeV3Verifier.generateApkVerityRootHash(apkPath); } catch (SignatureNotFoundException e) { // try older version } try { - return ApkSignatureSchemeV2Verifier.generateFsverityRootHash(apkPath); + return ApkSignatureSchemeV2Verifier.generateApkVerityRootHash(apkPath); } catch (SignatureNotFoundException e) { return null; } diff --git a/core/java/android/util/apk/ApkSigningBlockUtils.java b/core/java/android/util/apk/ApkSigningBlockUtils.java index e247c87fdb4c..081033ae84e9 100644 --- a/core/java/android/util/apk/ApkSigningBlockUtils.java +++ b/core/java/android/util/apk/ApkSigningBlockUtils.java @@ -332,7 +332,7 @@ final class ApkSigningBlockUtils { try { byte[] expectedRootHash = parseVerityDigestAndVerifySourceLength(expectedDigest, apk.length(), signatureInfo); - ApkVerityBuilder.ApkVerityResult verity = ApkVerityBuilder.generateApkVerity(apk, + ApkVerityBuilder.ApkVerityResult verity = ApkVerityBuilder.generateApkVerityTree(apk, signatureInfo, new ByteBufferFactory() { @Override public ByteBuffer create(int capacity) { @@ -348,26 +348,6 @@ final class ApkSigningBlockUtils { } /** - * Generates the fsverity header and hash tree to be used by kernel for the given apk. This - * method does not check whether the root hash exists in the Signing Block or not. - * - * <p>The output is stored in the {@link ByteBuffer} created by the given {@link - * ByteBufferFactory}. - * - * @return the root hash of the generated hash tree. - */ - public static byte[] generateApkVerity(String apkPath, ByteBufferFactory bufferFactory, - SignatureInfo signatureInfo) - throws IOException, SignatureNotFoundException, SecurityException, DigestException, - NoSuchAlgorithmException { - try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) { - ApkVerityBuilder.ApkVerityResult result = ApkVerityBuilder.generateApkVerity(apk, - signatureInfo, bufferFactory); - return result.rootHash; - } - } - - /** * Returns the ZIP End of Central Directory (EoCD) and its offset in the file. * * @throws IOException if an I/O error occurs while reading the file. diff --git a/core/java/android/util/apk/ApkVerityBuilder.java b/core/java/android/util/apk/ApkVerityBuilder.java index 2dd0117d583d..553511d73670 100644 --- a/core/java/android/util/apk/ApkVerityBuilder.java +++ b/core/java/android/util/apk/ApkVerityBuilder.java @@ -16,6 +16,9 @@ package android.util.apk; +import android.annotation.NonNull; +import android.annotation.Nullable; + import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; @@ -26,8 +29,10 @@ import java.security.NoSuchAlgorithmException; import java.util.ArrayList; /** - * ApkVerityBuilder builds the APK verity tree and the verity header, which will be used by the - * kernel to verity the APK content on access. + * ApkVerityBuilder builds the APK verity tree and the verity header. The generated tree format can + * be stored on disk for apk-verity setup and used by kernel. Note that since the current + * implementation is different from the upstream, we call this implementation apk-verity instead of + * fs-verity. * * <p>Unlike a regular Merkle tree, APK verity tree does not cover the content fully. Due to * the existing APK format, it has to skip APK Signing Block and also has some special treatment for @@ -47,26 +52,28 @@ abstract class ApkVerityBuilder { private static final byte[] DEFAULT_SALT = new byte[8]; static class ApkVerityResult { - public final ByteBuffer fsverityData; + public final ByteBuffer verityData; + public final int merkleTreeSize; public final byte[] rootHash; - ApkVerityResult(ByteBuffer fsverityData, byte[] rootHash) { - this.fsverityData = fsverityData; + ApkVerityResult(ByteBuffer verityData, int merkleTreeSize, byte[] rootHash) { + this.verityData = verityData; + this.merkleTreeSize = merkleTreeSize; this.rootHash = rootHash; } } /** - * Generates fsverity metadata and the Merkle tree into the {@link ByteBuffer} created by the - * {@link ByteBufferFactory}. The bytes layout in the buffer will be used by the kernel and is - * ready to be appended to the target file to set up fsverity. For fsverity to work, this data - * must be placed at the next page boundary, and the caller must add additional padding in that - * case. + * Generates the 4k, SHA-256 based Merkle tree for the given APK and stores in the {@link + * ByteBuffer} created by the {@link ByteBufferFactory}. The Merkle tree is suitable to be used + * as the on-disk format for apk-verity. * - * @return ApkVerityResult containing the fsverity data and the root hash of the Merkle tree. + * @return ApkVerityResult containing a buffer with the generated Merkle tree stored at the + * front, the tree size, and the calculated root hash. */ - static ApkVerityResult generateApkVerity(RandomAccessFile apk, - SignatureInfo signatureInfo, ByteBufferFactory bufferFactory) + @NonNull + static ApkVerityResult generateApkVerityTree(@NonNull RandomAccessFile apk, + @Nullable SignatureInfo signatureInfo, @NonNull ByteBufferFactory bufferFactory) throws IOException, SecurityException, NoSuchAlgorithmException, DigestException { long signingBlockSize = signatureInfo.centralDirOffset - signatureInfo.apkSigningBlockOffset; @@ -76,86 +83,69 @@ abstract class ApkVerityBuilder { ByteBuffer output = bufferFactory.create( merkleTreeSize - + CHUNK_SIZE_BYTES); // maximum size of fsverity metadata + + CHUNK_SIZE_BYTES); // maximum size of apk-verity metadata output.order(ByteOrder.LITTLE_ENDIAN); ByteBuffer tree = slice(output, 0, merkleTreeSize); - ByteBuffer header = slice(output, merkleTreeSize, - merkleTreeSize + FSVERITY_HEADER_SIZE_BYTES); - ByteBuffer extensions = slice(output, merkleTreeSize + FSVERITY_HEADER_SIZE_BYTES, - merkleTreeSize + CHUNK_SIZE_BYTES); - byte[] apkDigestBytes = new byte[DIGEST_SIZE_BYTES]; - ByteBuffer apkDigest = ByteBuffer.wrap(apkDigestBytes); - apkDigest.order(ByteOrder.LITTLE_ENDIAN); - - // NB: Buffer limit is set inside once finished. - calculateFsveritySignatureInternal(apk, signatureInfo, tree, apkDigest, header, extensions); - - // Put the reverse offset to fs-verity header at the end. - output.position(merkleTreeSize + FSVERITY_HEADER_SIZE_BYTES + extensions.limit()); - output.putInt(FSVERITY_HEADER_SIZE_BYTES + extensions.limit() - + 4); // size of this integer right before EOF - output.flip(); - - return new ApkVerityResult(output, apkDigestBytes); + byte[] apkRootHash = generateApkVerityTreeInternal(apk, signatureInfo, DEFAULT_SALT, + levelOffset, tree); + return new ApkVerityResult(output, merkleTreeSize, apkRootHash); + } + + static void generateApkVerityFooter(@NonNull RandomAccessFile apk, + @NonNull SignatureInfo signatureInfo, @NonNull ByteBuffer footerOutput) + throws IOException { + footerOutput.order(ByteOrder.LITTLE_ENDIAN); + generateApkVerityHeader(footerOutput, apk.length(), DEFAULT_SALT); + long signingBlockSize = + signatureInfo.centralDirOffset - signatureInfo.apkSigningBlockOffset; + generateApkVerityExtensions(footerOutput, signatureInfo.apkSigningBlockOffset, + signingBlockSize, signatureInfo.eocdOffset); } /** - * Calculates the fsverity root hash for integrity measurement. This needs to be consistent to - * what kernel returns. + * Calculates the apk-verity root hash for integrity measurement. This needs to be consistent + * to what kernel returns. */ - static byte[] generateFsverityRootHash(RandomAccessFile apk, ByteBuffer apkDigest, - SignatureInfo signatureInfo) + @NonNull + static byte[] generateApkVerityRootHash(@NonNull RandomAccessFile apk, + @NonNull ByteBuffer apkDigest, @NonNull SignatureInfo signatureInfo) throws NoSuchAlgorithmException, DigestException, IOException { - ByteBuffer verityBlock = ByteBuffer.allocate(CHUNK_SIZE_BYTES) - .order(ByteOrder.LITTLE_ENDIAN); - ByteBuffer header = slice(verityBlock, 0, FSVERITY_HEADER_SIZE_BYTES); - ByteBuffer extensions = slice(verityBlock, FSVERITY_HEADER_SIZE_BYTES, - CHUNK_SIZE_BYTES - FSVERITY_HEADER_SIZE_BYTES); + assertSigningBlockAlignedAndHasFullPages(signatureInfo); - calculateFsveritySignatureInternal(apk, signatureInfo, null, null, header, extensions); + ByteBuffer footer = ByteBuffer.allocate(CHUNK_SIZE_BYTES).order(ByteOrder.LITTLE_ENDIAN); + generateApkVerityFooter(apk, signatureInfo, footer); + footer.flip(); MessageDigest md = MessageDigest.getInstance(JCA_DIGEST_ALGORITHM); - md.update(header); - md.update(extensions); + md.update(footer); md.update(apkDigest); return md.digest(); } /** - * Internal method to generate various parts of FSVerity constructs, including the header, - * extensions, Merkle tree, and the tree's root hash. The output buffer is flipped to the - * generated data size and is readey for consuming. + * Generates the apk-verity header and hash tree to be used by kernel for the given apk. This + * method does not check whether the root hash exists in the Signing Block or not. + * + * <p>The output is stored in the {@link ByteBuffer} created by the given {@link + * ByteBufferFactory}. + * + * @return the root hash of the generated hash tree. */ - private static void calculateFsveritySignatureInternal( - RandomAccessFile apk, SignatureInfo signatureInfo, ByteBuffer treeOutput, - ByteBuffer rootHashOutput, ByteBuffer headerOutput, ByteBuffer extensionsOutput) - throws IOException, NoSuchAlgorithmException, DigestException { - assertSigningBlockAlignedAndHasFullPages(signatureInfo); - long signingBlockSize = - signatureInfo.centralDirOffset - signatureInfo.apkSigningBlockOffset; - long dataSize = apk.length() - signingBlockSize; - int[] levelOffset = calculateVerityLevelOffset(dataSize); - - if (treeOutput != null) { - byte[] apkRootHash = generateApkVerityTree(apk, signatureInfo, DEFAULT_SALT, - levelOffset, treeOutput); - if (rootHashOutput != null) { - rootHashOutput.put(apkRootHash); - rootHashOutput.flip(); - } - } - - if (headerOutput != null) { - headerOutput.order(ByteOrder.LITTLE_ENDIAN); - generateFsverityHeader(headerOutput, apk.length(), levelOffset.length - 1, - DEFAULT_SALT); - } - - if (extensionsOutput != null) { - extensionsOutput.order(ByteOrder.LITTLE_ENDIAN); - generateFsverityExtensions(extensionsOutput, signatureInfo.apkSigningBlockOffset, - signingBlockSize, signatureInfo.eocdOffset); + @NonNull + static byte[] generateApkVerity(@NonNull String apkPath, + @NonNull ByteBufferFactory bufferFactory, @NonNull SignatureInfo signatureInfo) + throws IOException, SignatureNotFoundException, SecurityException, DigestException, + NoSuchAlgorithmException { + try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) { + ApkVerityResult result = generateApkVerityTree(apk, signatureInfo, bufferFactory); + ByteBuffer footer = slice(result.verityData, result.merkleTreeSize, + result.verityData.limit()); + generateApkVerityFooter(apk, signatureInfo, footer); + // Put the reverse offset to apk-verity header at the end. + footer.putInt(footer.position() + 4); + result.verityData.limit(result.merkleTreeSize + footer.position()); + return result.rootHash; } } @@ -297,9 +287,13 @@ abstract class ApkVerityBuilder { digester.fillUpLastOutputChunk(); } - private static byte[] generateApkVerityTree(RandomAccessFile apk, SignatureInfo signatureInfo, - byte[] salt, int[] levelOffset, ByteBuffer output) + @NonNull + private static byte[] generateApkVerityTreeInternal(@NonNull RandomAccessFile apk, + @Nullable SignatureInfo signatureInfo, @NonNull byte[] salt, + @NonNull int[] levelOffset, @NonNull ByteBuffer output) throws IOException, NoSuchAlgorithmException, DigestException { + assertSigningBlockAlignedAndHasFullPages(signatureInfo); + // 1. Digest the apk to generate the leaf level hashes. generateApkVerityDigestAtLeafLevel(apk, signatureInfo, salt, slice(output, levelOffset[levelOffset.length - 2], levelOffset[levelOffset.length - 1])); @@ -324,7 +318,7 @@ abstract class ApkVerityBuilder { return rootHash; } - private static ByteBuffer generateFsverityHeader(ByteBuffer buffer, long fileSize, int depth, + private static ByteBuffer generateApkVerityHeader(ByteBuffer buffer, long fileSize, byte[] salt) { if (salt.length != 8) { throw new IllegalArgumentException("salt is not 8 bytes long"); @@ -351,13 +345,12 @@ abstract class ApkVerityBuilder { buffer.put(salt); // salt (8 bytes) skip(buffer, 22); // reserved - buffer.flip(); return buffer; } - private static ByteBuffer generateFsverityExtensions(ByteBuffer buffer, long signingBlockOffset, - long signingBlockSize, long eocdOffset) { - // Snapshot of the FSVerity structs (subject to change once upstreamed). + private static ByteBuffer generateApkVerityExtensions(ByteBuffer buffer, + long signingBlockOffset, long signingBlockSize, long eocdOffset) { + // Snapshot of the experimental fs-verity structs (different from upstream). // // struct fsverity_extension_elide { // __le64 offset; @@ -409,7 +402,6 @@ abstract class ApkVerityBuilder { skip(buffer, kPadding); // padding } - buffer.flip(); return buffer; } diff --git a/services/core/java/com/android/server/security/VerityUtils.java b/services/core/java/com/android/server/security/VerityUtils.java index 180f34355c94..9f69702911c9 100644 --- a/services/core/java/com/android/server/security/VerityUtils.java +++ b/services/core/java/com/android/server/security/VerityUtils.java @@ -23,11 +23,11 @@ import android.annotation.NonNull; import android.os.SharedMemory; import android.system.ErrnoException; import android.system.Os; +import android.util.Pair; +import android.util.Slog; import android.util.apk.ApkSignatureVerifier; import android.util.apk.ByteBufferFactory; import android.util.apk.SignatureNotFoundException; -import android.util.Pair; -import android.util.Slog; import java.io.FileDescriptor; import java.io.IOException; @@ -85,7 +85,7 @@ abstract public class VerityUtils { */ public static byte[] generateFsverityRootHash(@NonNull String apkPath) throws NoSuchAlgorithmException, DigestException, IOException { - return ApkSignatureVerifier.generateFsverityRootHash(apkPath); + return ApkSignatureVerifier.generateApkVerityRootHash(apkPath); } /** |