Add support to generate fsverity metadata

By setting fsverity.inputs, android_filesystem can now generate
.fsv_meta and BuildManifest.apk. It has been done by Makefile because
Makefile is the only one knowing all installed files. But now
android_filesystem is aware of all artifacts, so we can move fsverity
metadata generation into Soong.

Bug: 330282551
Test: m aosp_cf_system_x86_64 and see output
Change-Id: Iae4dd83eaede960c263bfba537211df4ff4b36bd
diff --git a/filesystem/Android.bp b/filesystem/Android.bp
index cead5fc..18dd553 100644
--- a/filesystem/Android.bp
+++ b/filesystem/Android.bp
@@ -18,6 +18,7 @@
         "avb_gen_vbmeta_image.go",
         "bootimg.go",
         "filesystem.go",
+        "fsverity_metadata.go",
         "logical_partition.go",
         "raw_binary.go",
         "system_image.go",
diff --git a/filesystem/filesystem.go b/filesystem/filesystem.go
index efc889c..5ec7efe 100644
--- a/filesystem/filesystem.go
+++ b/filesystem/filesystem.go
@@ -57,7 +57,7 @@
 	output     android.OutputPath
 	installDir android.InstallPath
 
-	// For testing. Keeps the result of CopyDepsToZip()
+	// For testing. Keeps the result of CopySpecsToDir()
 	entries []string
 }
 
@@ -120,6 +120,8 @@
 	// modules would be installed to the same location as a make module, they will overwrite
 	// the make version.
 	Include_make_built_files string
+
+	Fsverity fsverityProperties
 }
 
 // android_filesystem packages a set of modules and their transitive dependencies into a filesystem
@@ -176,6 +178,10 @@
 	return f.BaseModuleName() + ".img"
 }
 
+func (f *filesystem) partitionName() string {
+	return proptools.StringDefault(f.properties.Partition_name, f.Name())
+}
+
 var pctx = android.NewPackageContext("android/soong/filesystem")
 
 func (f *filesystem) GenerateAndroidBuildActions(ctx android.ModuleContext) {
@@ -255,10 +261,12 @@
 	builder := android.NewRuleBuilder(pctx, ctx)
 	// Wipe the root dir to get rid of leftover files from prior builds
 	builder.Command().Textf("rm -rf %s && mkdir -p %s", rootDir, rootDir)
-	f.entries = f.CopySpecsToDir(ctx, builder, f.gatherFilteredPackagingSpecs(ctx), rebasedDir)
+	specs := f.gatherFilteredPackagingSpecs(ctx)
+	f.entries = f.CopySpecsToDir(ctx, builder, specs, rebasedDir)
 
 	f.buildNonDepsFiles(ctx, builder, rootDir)
 	f.addMakeBuiltFiles(ctx, builder, rootDir)
+	f.buildFsverityMetadataFiles(ctx, builder, specs, rootDir, rebasedDir)
 
 	// run host_init_verifier
 	// Ideally we should have a concept of pluggable linters that verify the generated image.
@@ -338,13 +346,12 @@
 		addStr("avb_algorithm", algorithm)
 		key := android.PathForModuleSrc(ctx, proptools.String(f.properties.Avb_private_key))
 		addPath("avb_key_path", key)
-		partitionName := proptools.StringDefault(f.properties.Partition_name, f.Name())
-		addStr("partition_name", partitionName)
+		addStr("partition_name", f.partitionName())
 		avb_add_hashtree_footer_args := "--do_not_generate_fec"
 		if hashAlgorithm := proptools.String(f.properties.Avb_hash_algorithm); hashAlgorithm != "" {
 			avb_add_hashtree_footer_args += " --hash_algorithm " + hashAlgorithm
 		}
-		securityPatchKey := "com.android.build." + partitionName + ".security_patch"
+		securityPatchKey := "com.android.build." + f.partitionName() + ".security_patch"
 		securityPatchValue := ctx.Config().PlatformSecurityPatch()
 		avb_add_hashtree_footer_args += " --prop " + securityPatchKey + ":" + securityPatchValue
 		addStr("avb_add_hashtree_footer_args", avb_add_hashtree_footer_args)
@@ -388,9 +395,11 @@
 	builder := android.NewRuleBuilder(pctx, ctx)
 	// Wipe the root dir to get rid of leftover files from prior builds
 	builder.Command().Textf("rm -rf %s && mkdir -p %s", rootDir, rootDir)
-	f.entries = f.CopySpecsToDir(ctx, builder, f.gatherFilteredPackagingSpecs(ctx), rebasedDir)
+	specs := f.gatherFilteredPackagingSpecs(ctx)
+	f.entries = f.CopySpecsToDir(ctx, builder, specs, rebasedDir)
 
 	f.buildNonDepsFiles(ctx, builder, rootDir)
+	f.buildFsverityMetadataFiles(ctx, builder, specs, rootDir, rebasedDir)
 
 	output := android.PathForModuleOut(ctx, f.installFileName()).OutputPath
 	cmd := builder.Command().
diff --git a/filesystem/fsverity_metadata.go b/filesystem/fsverity_metadata.go
new file mode 100644
index 0000000..70f94e0
--- /dev/null
+++ b/filesystem/fsverity_metadata.go
@@ -0,0 +1,166 @@
+// Copyright (C) 2024 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.
+
+package filesystem
+
+import (
+	"path/filepath"
+	"strings"
+
+	"android/soong/android"
+)
+
+type fsverityProperties struct {
+	// Patterns of files for fsverity metadata generation.  For each matched file, a .fsv_meta file
+	// will be generated and included to the filesystem image.
+	// etc/security/fsverity/BuildManifest.apk will also be generated which contains information
+	// about generated .fsv_meta files.
+	Inputs []string
+}
+
+func (f *filesystem) writeManifestGeneratorListFile(ctx android.ModuleContext, outputPath android.OutputPath, matchedSpecs []android.PackagingSpec, rebasedDir android.OutputPath) {
+	var buf strings.Builder
+	for _, spec := range matchedSpecs {
+		buf.WriteString(rebasedDir.Join(ctx, spec.RelPathInPackage()).String())
+		buf.WriteRune('\n')
+	}
+	android.WriteFileRuleVerbatim(ctx, outputPath, buf.String())
+}
+
+func (f *filesystem) buildFsverityMetadataFiles(ctx android.ModuleContext, builder *android.RuleBuilder, specs map[string]android.PackagingSpec, rootDir android.OutputPath, rebasedDir android.OutputPath) {
+	match := func(path string) bool {
+		for _, pattern := range f.properties.Fsverity.Inputs {
+			if matched, err := filepath.Match(pattern, path); matched {
+				return true
+			} else if err != nil {
+				ctx.PropertyErrorf("fsverity.inputs", "bad pattern %q", pattern)
+				return false
+			}
+		}
+		return false
+	}
+
+	var matchedSpecs []android.PackagingSpec
+	for _, relPath := range android.SortedKeys(specs) {
+		if match(relPath) {
+			matchedSpecs = append(matchedSpecs, specs[relPath])
+		}
+	}
+
+	if len(matchedSpecs) == 0 {
+		return
+	}
+
+	fsverityBuilderPath := android.PathForModuleOut(ctx, "fsverity_builder.sh")
+	metadataGeneratorPath := ctx.Config().HostToolPath(ctx, "fsverity_metadata_generator")
+	fsverityPath := ctx.Config().HostToolPath(ctx, "fsverity")
+
+	cmd := builder.Command().Tool(fsverityBuilderPath)
+
+	// STEP 1: generate .fsv_meta
+	var sb strings.Builder
+	sb.WriteString("set -e\n")
+	cmd.Implicit(metadataGeneratorPath).Implicit(fsverityPath)
+	for _, spec := range matchedSpecs {
+		// srcPath is copied by CopySpecsToDir()
+		srcPath := rebasedDir.Join(ctx, spec.RelPathInPackage())
+		destPath := rebasedDir.Join(ctx, spec.RelPathInPackage()+".fsv_meta")
+		sb.WriteString(metadataGeneratorPath.String())
+		sb.WriteString(" --fsverity-path ")
+		sb.WriteString(fsverityPath.String())
+		sb.WriteString(" --signature none --hash-alg sha256 --output ")
+		sb.WriteString(destPath.String())
+		sb.WriteRune(' ')
+		sb.WriteString(srcPath.String())
+		sb.WriteRune('\n')
+	}
+
+	// STEP 2: generate signed BuildManifest.apk
+	// STEP 2-1: generate build_manifest.pb
+	assetsPath := android.PathForModuleOut(ctx, "fsverity_manifest/assets")
+	manifestPbPath := assetsPath.Join(ctx, "build_manifest.pb")
+	manifestGeneratorPath := ctx.Config().HostToolPath(ctx, "fsverity_manifest_generator")
+	cmd.Implicit(manifestGeneratorPath)
+	sb.WriteString("rm -rf ")
+	sb.WriteString(assetsPath.String())
+	sb.WriteString(" && mkdir -p ")
+	sb.WriteString(assetsPath.String())
+	sb.WriteRune('\n')
+	sb.WriteString(manifestGeneratorPath.String())
+	sb.WriteString(" --fsverity-path ")
+	sb.WriteString(fsverityPath.String())
+	sb.WriteString(" --base-dir ")
+	sb.WriteString(rootDir.String())
+	sb.WriteString(" --output ")
+	sb.WriteString(manifestPbPath.String())
+	sb.WriteRune(' ')
+
+	manifestGeneratorListPath := android.PathForModuleOut(ctx, "fsverity_manifest.list")
+	f.writeManifestGeneratorListFile(ctx, manifestGeneratorListPath.OutputPath, matchedSpecs, rebasedDir)
+	sb.WriteRune('@')
+	sb.WriteString(manifestGeneratorListPath.String())
+	sb.WriteRune('\n')
+	cmd.Implicit(manifestGeneratorListPath)
+
+	// STEP 2-2: generate BuildManifest.apk (unsigned)
+	aapt2Path := ctx.Config().HostToolPath(ctx, "aapt2")
+	apkPath := rebasedDir.Join(ctx, "etc", "security", "fsverity", "BuildManifest.apk")
+	manifestTemplatePath := android.PathForSource(ctx, "system/security/fsverity/AndroidManifest.xml")
+	// package-export is currently generated by Makefile.
+	// TODO(b/330282551): fully migrate into Soong
+	frameworkResPath := android.PathForArbitraryOutput(ctx, "target/common/obj/APPS/framework-res_intermediates/package-export.apk")
+	cmd.Implicit(aapt2Path)
+	cmd.Implicit(manifestTemplatePath)
+	cmd.Implicit(frameworkResPath)
+
+	sb.WriteString(aapt2Path.String())
+	sb.WriteString(" link -o ")
+	sb.WriteString(apkPath.String())
+	sb.WriteString(" -A ")
+	sb.WriteString(assetsPath.String())
+	sb.WriteString(" -I ")
+	sb.WriteString(frameworkResPath.String())
+	minSdkVersion := ctx.Config().PlatformSdkCodename()
+	if minSdkVersion == "REL" {
+		minSdkVersion = ctx.Config().PlatformSdkVersion().String()
+	}
+	sb.WriteString(" --min-sdk-version ")
+	sb.WriteString(minSdkVersion)
+	sb.WriteString(" --version-code ")
+	sb.WriteString(ctx.Config().PlatformSdkVersion().String())
+	sb.WriteString(" --version-name ")
+	sb.WriteString(ctx.Config().AppsDefaultVersionName())
+	sb.WriteString(" --manifest ")
+	sb.WriteString(manifestTemplatePath.String())
+	sb.WriteString(" --rename-manifest-package com.android.security.fsverity_metadata.")
+	sb.WriteString(f.partitionName())
+	sb.WriteRune('\n')
+
+	// STEP 2-3: sign BuildManifest.apk
+	apksignerPath := ctx.Config().HostToolPath(ctx, "apksigner")
+	pemPath, keyPath := ctx.Config().DefaultAppCertificate(ctx)
+	cmd.Implicit(apksignerPath)
+	cmd.Implicit(pemPath)
+	cmd.Implicit(keyPath)
+	sb.WriteString(apksignerPath.String())
+	sb.WriteString(" sign --in ")
+	sb.WriteString(apkPath.String())
+	sb.WriteString(" --cert ")
+	sb.WriteString(pemPath.String())
+	sb.WriteString(" --key ")
+	sb.WriteString(keyPath.String())
+	sb.WriteRune('\n')
+
+	android.WriteExecutableFileRuleVerbatim(ctx, fsverityBuilderPath, sb.String())
+}