Merge "Support generating fs-verity descriptor"
This commit is contained in:
committed by
Android (Google) Code Review
commit
53ebe351c4
@@ -16004,7 +16004,8 @@ public class PackageManagerService extends IPackageManager.Stub
|
||||
}
|
||||
if (apkPath != null) {
|
||||
final VerityUtils.SetupResult result =
|
||||
VerityUtils.generateApkVeritySetupData(apkPath);
|
||||
VerityUtils.generateApkVeritySetupData(apkPath, null /* signaturePath */,
|
||||
true /* skipSigningBlock */);
|
||||
if (result.isOk()) {
|
||||
if (Build.IS_DEBUGGABLE) Slog.i(TAG, "Enabling apk verity to " + apkPath);
|
||||
FileDescriptor fd = result.getUnownedFileDescriptor();
|
||||
|
||||
@@ -26,42 +26,76 @@ import android.system.Os;
|
||||
import android.util.Pair;
|
||||
import android.util.Slog;
|
||||
import android.util.apk.ApkSignatureVerifier;
|
||||
import android.util.apk.ApkVerityBuilder;
|
||||
import android.util.apk.ByteBufferFactory;
|
||||
import android.util.apk.SignatureNotFoundException;
|
||||
|
||||
import libcore.util.HexEncoding;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.security.DigestException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import sun.security.pkcs.PKCS7;
|
||||
|
||||
/** Provides fsverity related operations. */
|
||||
abstract public class VerityUtils {
|
||||
private static final String TAG = "VerityUtils";
|
||||
|
||||
/** The maximum size of signature file. This is just to avoid potential abuse. */
|
||||
private static final int MAX_SIGNATURE_FILE_SIZE_BYTES = 8192;
|
||||
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
/**
|
||||
* Generates Merkle tree and fsverity metadata.
|
||||
* Generates Merkle tree and fs-verity metadata.
|
||||
*
|
||||
* @return {@code SetupResult} that contains the {@code EsetupResultCode}, and when success, the
|
||||
* @return {@code SetupResult} that contains the result code, and when success, the
|
||||
* {@code FileDescriptor} to read all the data from.
|
||||
*/
|
||||
public static SetupResult generateApkVeritySetupData(@NonNull String apkPath) {
|
||||
if (DEBUG) Slog.d(TAG, "Trying to install apk verity to " + apkPath);
|
||||
public static SetupResult generateApkVeritySetupData(@NonNull String apkPath,
|
||||
String signaturePath, boolean skipSigningBlock) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Trying to install apk verity to " + apkPath + " with signature file "
|
||||
+ signaturePath);
|
||||
}
|
||||
SharedMemory shm = null;
|
||||
try {
|
||||
byte[] signedRootHash = ApkSignatureVerifier.getVerityRootHash(apkPath);
|
||||
if (signedRootHash == null) {
|
||||
byte[] signedVerityHash;
|
||||
if (skipSigningBlock) {
|
||||
signedVerityHash = ApkSignatureVerifier.getVerityRootHash(apkPath);
|
||||
} else {
|
||||
Path path = Paths.get(signaturePath);
|
||||
if (Files.exists(path)) {
|
||||
// TODO(112037636): fail early if the signing key is not in .fs-verity keyring.
|
||||
PKCS7 pkcs7 = new PKCS7(Files.readAllBytes(path));
|
||||
signedVerityHash = pkcs7.getContentInfo().getContentBytes();
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "fs-verity measurement = " + bytesToString(signedVerityHash));
|
||||
}
|
||||
} else {
|
||||
signedVerityHash = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (signedVerityHash == null) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Skip verity tree generation since there is no root hash");
|
||||
Slog.d(TAG, "Skip verity tree generation since there is no signed root hash");
|
||||
}
|
||||
return SetupResult.skipped();
|
||||
}
|
||||
|
||||
Pair<SharedMemory, Integer> result = generateApkVerityIntoSharedMemory(apkPath,
|
||||
signedRootHash);
|
||||
Pair<SharedMemory, Integer> result = generateFsVerityIntoSharedMemory(apkPath,
|
||||
signaturePath, signedVerityHash, skipSigningBlock);
|
||||
shm = result.first;
|
||||
int contentSize = result.second;
|
||||
FileDescriptor rfd = shm.getFileDescriptor();
|
||||
@@ -96,23 +130,115 @@ abstract public class VerityUtils {
|
||||
return ApkSignatureVerifier.getVerityRootHash(apkPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates fs-verity metadata for {@code filePath} in the buffer created by {@code
|
||||
* trackedBufferFactory}. The metadata contains the Merkle tree, fs-verity descriptor and
|
||||
* extensions, including a PKCS#7 signature provided in {@code signaturePath}.
|
||||
*
|
||||
* <p>It is worthy to note that {@code trackedBufferFactory} generates a "tracked" {@code
|
||||
* ByteBuffer}. The data will be used outside this method via the factory itself.
|
||||
*
|
||||
* @return fs-verity measurement of {@code filePath}, which is a SHA-256 of fs-verity descriptor
|
||||
* and authenticated extensions.
|
||||
*/
|
||||
private static byte[] generateFsverityMetadata(String filePath, String signaturePath,
|
||||
@NonNull TrackedShmBufferFactory trackedBufferFactory)
|
||||
throws IOException, SignatureNotFoundException, SecurityException, DigestException,
|
||||
NoSuchAlgorithmException {
|
||||
try (RandomAccessFile file = new RandomAccessFile(filePath, "r")) {
|
||||
ApkVerityBuilder.ApkVerityResult result = ApkVerityBuilder.generateFsVerityTree(
|
||||
file, trackedBufferFactory);
|
||||
|
||||
ByteBuffer buffer = result.verityData;
|
||||
buffer.position(result.merkleTreeSize);
|
||||
return generateFsverityDescriptorAndMeasurement(file, result.rootHash, signaturePath,
|
||||
buffer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates fs-verity descriptor including the extensions to the {@code output} and returns the
|
||||
* fs-verity measurement.
|
||||
*
|
||||
* @return fs-verity measurement, which is a SHA-256 of fs-verity descriptor and authenticated
|
||||
* extensions.
|
||||
*/
|
||||
private static byte[] generateFsverityDescriptorAndMeasurement(
|
||||
@NonNull RandomAccessFile file, @NonNull byte[] rootHash,
|
||||
@NonNull String pkcs7SignaturePath, @NonNull ByteBuffer output)
|
||||
throws IOException, NoSuchAlgorithmException, DigestException {
|
||||
final short kRootHashExtensionId = 1;
|
||||
final short kPkcs7SignatureExtensionId = 3;
|
||||
final int origPosition = output.position();
|
||||
|
||||
// For generating fs-verity file measurement, which consists of the descriptor and
|
||||
// authenticated extensions (but not unauthenticated extensions and the footer).
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||
|
||||
// 1. Generate fs-verity descriptor.
|
||||
final byte[] desc = constructFsverityDescriptorNative(file.length());
|
||||
output.put(desc);
|
||||
md.update(desc);
|
||||
|
||||
// 2. Generate authenticated extensions.
|
||||
final byte[] authExt =
|
||||
constructFsverityExtensionNative(kRootHashExtensionId, rootHash.length);
|
||||
output.put(authExt);
|
||||
output.put(rootHash);
|
||||
md.update(authExt);
|
||||
md.update(rootHash);
|
||||
|
||||
// 3. Generate unauthenticated extensions.
|
||||
ByteBuffer header = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN);
|
||||
output.putShort((short) 1); // number of unauthenticated extensions below
|
||||
output.position(output.position() + 6);
|
||||
|
||||
// Generate PKCS#7 extension. NB: We do not verify agaist trusted certificate (should be
|
||||
// done by the caller if needed).
|
||||
Path path = Paths.get(pkcs7SignaturePath);
|
||||
if (Files.size(path) > MAX_SIGNATURE_FILE_SIZE_BYTES) {
|
||||
throw new IllegalArgumentException("Signature size is unexpectedly large: "
|
||||
+ pkcs7SignaturePath);
|
||||
}
|
||||
final byte[] pkcs7Signature = Files.readAllBytes(path);
|
||||
output.put(constructFsverityExtensionNative(kPkcs7SignatureExtensionId,
|
||||
pkcs7Signature.length));
|
||||
output.put(pkcs7Signature);
|
||||
|
||||
// 4. Generate the footer.
|
||||
output.put(constructFsverityFooterNative(output.position() - origPosition));
|
||||
|
||||
return md.digest();
|
||||
}
|
||||
|
||||
private static native byte[] constructFsverityDescriptorNative(long fileSize);
|
||||
private static native byte[] constructFsverityExtensionNative(short extensionId,
|
||||
int extensionDataSize);
|
||||
private static native byte[] constructFsverityFooterNative(int offsetToDescriptorHead);
|
||||
|
||||
/**
|
||||
* Returns a pair of {@code SharedMemory} and {@code Integer}. The {@code SharedMemory} contains
|
||||
* Merkle tree and fsverity headers for the given apk, in the form that can immediately be used
|
||||
* for fsverity setup. The data is aligned to the beginning of {@code SharedMemory}, and has
|
||||
* length equals to the returned {@code Integer}.
|
||||
*/
|
||||
private static Pair<SharedMemory, Integer> generateApkVerityIntoSharedMemory(
|
||||
String apkPath, byte[] expectedRootHash)
|
||||
private static Pair<SharedMemory, Integer> generateFsVerityIntoSharedMemory(
|
||||
String apkPath, String signaturePath, @NonNull byte[] expectedRootHash,
|
||||
boolean skipSigningBlock)
|
||||
throws IOException, SecurityException, DigestException, NoSuchAlgorithmException,
|
||||
SignatureNotFoundException {
|
||||
TrackedShmBufferFactory shmBufferFactory = new TrackedShmBufferFactory();
|
||||
byte[] generatedRootHash = ApkSignatureVerifier.generateApkVerity(apkPath,
|
||||
shmBufferFactory);
|
||||
byte[] generatedRootHash;
|
||||
if (skipSigningBlock) {
|
||||
generatedRootHash = ApkSignatureVerifier.generateApkVerity(apkPath, shmBufferFactory);
|
||||
} else {
|
||||
generatedRootHash = generateFsverityMetadata(apkPath, signaturePath, shmBufferFactory);
|
||||
}
|
||||
// We only generate Merkle tree once here, so it's important to make sure the root hash
|
||||
// matches the signed one in the apk.
|
||||
if (!Arrays.equals(expectedRootHash, generatedRootHash)) {
|
||||
throw new SecurityException("Locally generated verity root hash does not match");
|
||||
throw new SecurityException("verity hash mismatch: "
|
||||
+ bytesToString(generatedRootHash) + " != " + bytesToString(expectedRootHash));
|
||||
}
|
||||
|
||||
int contentSize = shmBufferFactory.getBufferLimit();
|
||||
@@ -126,11 +252,15 @@ abstract public class VerityUtils {
|
||||
return Pair.create(shm, contentSize);
|
||||
}
|
||||
|
||||
private static String bytesToString(byte[] bytes) {
|
||||
return HexEncoding.encodeToString(bytes);
|
||||
}
|
||||
|
||||
public static class SetupResult {
|
||||
/** Result code if verity is set up correctly. */
|
||||
private static final int RESULT_OK = 1;
|
||||
|
||||
/** Result code if the apk does not contain a verity root hash. */
|
||||
/** Result code if signature is not provided. */
|
||||
private static final int RESULT_SKIPPED = 2;
|
||||
|
||||
/** Result code if the setup failed. */
|
||||
|
||||
@@ -37,6 +37,7 @@ cc_library_static {
|
||||
"com_android_server_locksettings_SyntheticPasswordManager.cpp",
|
||||
"com_android_server_net_NetworkStatsService.cpp",
|
||||
"com_android_server_power_PowerManagerService.cpp",
|
||||
"com_android_server_security_VerityUtils.cpp",
|
||||
"com_android_server_SerialService.cpp",
|
||||
"com_android_server_storage_AppFuseBridge.cpp",
|
||||
"com_android_server_SystemServer.cpp",
|
||||
|
||||
137
services/core/jni/com_android_server_security_VerityUtils.cpp
Normal file
137
services/core/jni/com_android_server_security_VerityUtils.cpp
Normal file
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#define LOG_TAG "VerityUtils"
|
||||
|
||||
#include <nativehelper/JNIHelp.h>
|
||||
#include "jni.h"
|
||||
#include <utils/Log.h>
|
||||
|
||||
#include <string.h>
|
||||
|
||||
// TODO(112037636): Always include once fsverity.h is upstreamed and backported.
|
||||
#define HAS_FSVERITY 0
|
||||
|
||||
#if HAS_FSVERITY
|
||||
#include <linux/fsverity.h>
|
||||
#endif
|
||||
|
||||
namespace android {
|
||||
|
||||
namespace {
|
||||
|
||||
class JavaByteArrayHolder {
|
||||
public:
|
||||
static JavaByteArrayHolder* newArray(JNIEnv* env, jsize size) {
|
||||
return new JavaByteArrayHolder(env, size);
|
||||
}
|
||||
|
||||
jbyte* getRaw() {
|
||||
return mElements;
|
||||
}
|
||||
|
||||
jbyteArray release() {
|
||||
mEnv->ReleaseByteArrayElements(mBytes, mElements, 0);
|
||||
mElements = nullptr;
|
||||
return mBytes;
|
||||
}
|
||||
|
||||
private:
|
||||
JavaByteArrayHolder(JNIEnv* env, jsize size) {
|
||||
mEnv = env;
|
||||
mBytes = mEnv->NewByteArray(size);
|
||||
mElements = mEnv->GetByteArrayElements(mBytes, nullptr);
|
||||
memset(mElements, 0, size);
|
||||
}
|
||||
|
||||
virtual ~JavaByteArrayHolder() {
|
||||
LOG_ALWAYS_FATAL_IF(mElements == nullptr, "Elements are not released");
|
||||
}
|
||||
|
||||
JNIEnv* mEnv;
|
||||
jbyteArray mBytes;
|
||||
jbyte* mElements;
|
||||
};
|
||||
|
||||
jbyteArray constructFsverityDescriptor(JNIEnv* env, jobject /* clazz */, jlong fileSize) {
|
||||
#if HAS_FSVERITY
|
||||
auto raii = JavaByteArrayHolder::newArray(env, sizeof(fsverity_descriptor));
|
||||
fsverity_descriptor* desc = reinterpret_cast<fsverity_descriptor*>(raii->getRaw());
|
||||
|
||||
memcpy(desc->magic, FS_VERITY_MAGIC, sizeof(desc->magic));
|
||||
desc->major_version = 1;
|
||||
desc->minor_version = 0;
|
||||
desc->log_data_blocksize = 12;
|
||||
desc->log_tree_blocksize = 12;
|
||||
desc->data_algorithm = FS_VERITY_ALG_SHA256;
|
||||
desc->tree_algorithm = FS_VERITY_ALG_SHA256;
|
||||
desc->flags = 0;
|
||||
desc->orig_file_size = fileSize;
|
||||
desc->auth_ext_count = 1;
|
||||
|
||||
return raii->release();
|
||||
#else
|
||||
LOG_ALWAYS_FATAL("fs-verity is used while not enabled");
|
||||
return 0;
|
||||
#endif // HAS_FSVERITY
|
||||
}
|
||||
|
||||
jbyteArray constructFsverityExtension(JNIEnv* env, jobject /* clazz */, jshort extensionId,
|
||||
jint extensionDataSize) {
|
||||
#if HAS_FSVERITY
|
||||
auto raii = JavaByteArrayHolder::newArray(env, sizeof(fsverity_extension));
|
||||
fsverity_extension* ext = reinterpret_cast<fsverity_extension*>(raii->getRaw());
|
||||
|
||||
ext->length = sizeof(fsverity_extension) + extensionDataSize;
|
||||
ext->type = extensionId;
|
||||
|
||||
return raii->release();
|
||||
#else
|
||||
LOG_ALWAYS_FATAL("fs-verity is used while not enabled");
|
||||
return 0;
|
||||
#endif // HAS_FSVERITY
|
||||
}
|
||||
|
||||
jbyteArray constructFsverityFooter(JNIEnv* env, jobject /* clazz */,
|
||||
jint offsetToDescriptorHead) {
|
||||
#if HAS_FSVERITY
|
||||
auto raii = JavaByteArrayHolder::newArray(env, sizeof(fsverity_footer));
|
||||
fsverity_footer* footer = reinterpret_cast<fsverity_footer*>(raii->getRaw());
|
||||
|
||||
footer->desc_reverse_offset = offsetToDescriptorHead + sizeof(fsverity_footer);
|
||||
memcpy(footer->magic, FS_VERITY_MAGIC, sizeof(footer->magic));
|
||||
|
||||
return raii->release();
|
||||
#else
|
||||
LOG_ALWAYS_FATAL("fs-verity is used while not enabled");
|
||||
return 0;
|
||||
#endif // HAS_FSVERITY
|
||||
}
|
||||
|
||||
const JNINativeMethod sMethods[] = {
|
||||
{ "constructFsverityDescriptorNative", "(J)[B", (void *)constructFsverityDescriptor },
|
||||
{ "constructFsverityExtensionNative", "(SI)[B", (void *)constructFsverityExtension },
|
||||
{ "constructFsverityFooterNative", "(I)[B", (void *)constructFsverityFooter },
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
int register_android_server_security_VerityUtils(JNIEnv* env) {
|
||||
return jniRegisterNativeMethods(env,
|
||||
"com/android/server/security/VerityUtils", sMethods, NELEM(sMethods));
|
||||
}
|
||||
|
||||
} // namespace android
|
||||
@@ -54,6 +54,7 @@ int register_android_server_SyntheticPasswordManager(JNIEnv* env);
|
||||
int register_android_server_GraphicsStatsService(JNIEnv* env);
|
||||
int register_android_hardware_display_DisplayViewport(JNIEnv* env);
|
||||
int register_android_server_net_NetworkStatsService(JNIEnv* env);
|
||||
int register_android_server_security_VerityUtils(JNIEnv* env);
|
||||
};
|
||||
|
||||
using namespace android;
|
||||
@@ -101,5 +102,6 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
|
||||
register_android_server_GraphicsStatsService(env);
|
||||
register_android_hardware_display_DisplayViewport(env);
|
||||
register_android_server_net_NetworkStatsService(env);
|
||||
register_android_server_security_VerityUtils(env);
|
||||
return JNI_VERSION_1_4;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user