Support generating fs-verity descriptor

Note that since fsverity.h is not yet in bionic, most of them JNI code
is not compiled since HAS_FSVERITY is 0 by default.  The plan is to
remove it once the file is in.

Test: Manually copy linux/fsverity.h to bionic/libc/kernel/uapi, build
      with ENABLE_FSVERITY={0,1}.
Test: atest PtsApkVerityTestCases
Test: With other changes, observe the hash matches what other tool
      generates.
Bug: 112037636

Change-Id: I5e0ef0625a88326203ddfb3cbb73e12f2111b4b8
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index e10827b..10980b7 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -16004,7 +16004,8 @@
             }
             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();
diff --git a/services/core/java/com/android/server/security/VerityUtils.java b/services/core/java/com/android/server/security/VerityUtils.java
index 9f69702..3796610 100644
--- a/services/core/java/com/android/server/security/VerityUtils.java
+++ b/services/core/java/com/android/server/security/VerityUtils.java
@@ -26,42 +26,76 @@
 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();
@@ -97,22 +131,114 @@
     }
 
     /**
+     * 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 @@
         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. */
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index becde73..061f8e2 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -37,6 +37,7 @@
         "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",
diff --git a/services/core/jni/com_android_server_security_VerityUtils.cpp b/services/core/jni/com_android_server_security_VerityUtils.cpp
new file mode 100644
index 0000000..d0f173b
--- /dev/null
+++ b/services/core/jni/com_android_server_security_VerityUtils.cpp
@@ -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
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index bb6e684..918f57e 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -54,6 +54,7 @@
 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 @@
     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;
 }