Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 53ebe351 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Support generating fs-verity descriptor"

parents a226524a 38c55039
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -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();
+145 −15
Original line number Diff line number Diff line
@@ -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. */
+1 −0
Original line number Diff line number Diff line
@@ -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 −0
Original line number Diff line number Diff line
/*
 * 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
+2 −0
Original line number Diff line number Diff line
@@ -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;
}