Merge "Refactor: break fs-verity data gen into 2 parts"
This commit is contained in:
committed by
Android (Google) Code Review
commit
7ba1d9bbd1
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
@@ -347,26 +347,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.
|
||||
*
|
||||
|
||||
@@ -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);
|
||||
byte[] apkRootHash = generateApkVerityTreeInternal(apk, signatureInfo, DEFAULT_SALT,
|
||||
levelOffset, tree);
|
||||
return new ApkVerityResult(output, merkleTreeSize, apkRootHash);
|
||||
}
|
||||
|
||||
// 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);
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user