Add vbmeta module type
The module type is to create vbmeta image out of other partitions.
Bug: 180676957
Test: m microdroid_vbmeta microdroid_vbmeta_system
Inspect the built image using `avbtool info_image --image <image>`
Change-Id: Iac92e9ab1640dcd488af69842e09850a91262bf1
diff --git a/filesystem/vbmeta.go b/filesystem/vbmeta.go
new file mode 100644
index 0000000..f823387
--- /dev/null
+++ b/filesystem/vbmeta.go
@@ -0,0 +1,265 @@
+// Copyright (C) 2021 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 (
+ "fmt"
+ "strconv"
+
+ "github.com/google/blueprint"
+ "github.com/google/blueprint/proptools"
+
+ "android/soong/android"
+)
+
+func init() {
+ android.RegisterModuleType("vbmeta", vbmetaFactory)
+}
+
+type vbmeta struct {
+ android.ModuleBase
+
+ properties vbmetaProperties
+
+ output android.OutputPath
+ installDir android.InstallPath
+}
+
+type vbmetaProperties struct {
+ // Name of the partition stored in vbmeta desc. Defaults to the name of this module.
+ Partition_name *string
+
+ // Set the name of the output. Defaults to <module_name>.img.
+ Stem *string
+
+ // Path to the private key that avbtool will use to sign this vbmeta image.
+ Private_key *string `android:"path"`
+
+ // Algorithm that avbtool will use to sign this vbmeta image. Default is SHA256_RSA4096.
+ Algorithm *string
+
+ // File whose content will provide the rollback index. If unspecified, the rollback index
+ // is from PLATFORM_SECURITY_PATCH
+ Rollback_index_file *string `android:"path"`
+
+ // Rollback index location of this vbmeta image. Must be 0, 1, 2, etc. Default is 0.
+ Rollback_index_location *int64
+
+ // List of filesystem modules that this vbmeta has descriptors for. The filesystem modules
+ // have to be signed (use_avb: true).
+ Partitions []string
+
+ // List of chained partitions that this vbmeta deletages the verification.
+ Chained_partitions []chainedPartitionProperties
+}
+
+type chainedPartitionProperties struct {
+ // Name of the chained partition
+ Name *string
+
+ // Rollback index location of the chained partition. Must be 0, 1, 2, etc. Default is the
+ // index of this partition in the list + 1.
+ Rollback_index_location *int64
+
+ // Path to the public key that the chained partition is signed with. If this is specified,
+ // private_key is ignored.
+ Public_key *string `android:"path"`
+
+ // Path to the private key that the chained partition is signed with. If this is specified,
+ // and public_key is not specified, a public key is extracted from this private key and
+ // the extracted public key is embedded in the vbmeta image.
+ Private_key *string `android:"path"`
+}
+
+// vbmeta is the partition image that has the verification information for other partitions.
+func vbmetaFactory() android.Module {
+ module := &vbmeta{}
+ module.AddProperties(&module.properties)
+ android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibFirst)
+ return module
+}
+
+type vbmetaDep struct {
+ blueprint.BaseDependencyTag
+ kind string
+}
+
+var vbmetaPartitionDep = vbmetaDep{kind: "partition"}
+
+func (v *vbmeta) DepsMutator(ctx android.BottomUpMutatorContext) {
+ ctx.AddDependency(ctx.Module(), vbmetaPartitionDep, v.properties.Partitions...)
+}
+
+func (v *vbmeta) installFileName() string {
+ return proptools.StringDefault(v.properties.Stem, v.BaseModuleName()+".img")
+}
+
+func (v *vbmeta) partitionName() string {
+ return proptools.StringDefault(v.properties.Partition_name, v.BaseModuleName())
+}
+
+func (v *vbmeta) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+ extractedPublicKeys := v.extractPublicKeys(ctx)
+
+ v.output = android.PathForModuleOut(ctx, v.installFileName()).OutputPath
+
+ builder := android.NewRuleBuilder(pctx, ctx)
+ cmd := builder.Command().BuiltTool("avbtool").Text("make_vbmeta_image")
+
+ key := android.PathForModuleSrc(ctx, proptools.String(v.properties.Private_key))
+ cmd.FlagWithInput("--key ", key)
+
+ algorithm := proptools.StringDefault(v.properties.Algorithm, "SHA256_RSA4096")
+ cmd.FlagWithArg("--algorithm ", algorithm)
+
+ cmd.FlagWithArg("--rollback_index ", v.rollbackIndexCommand(ctx))
+ ril := proptools.IntDefault(v.properties.Rollback_index_location, 0)
+ if ril < 0 {
+ ctx.PropertyErrorf("rollback_index_location", "must be 0, 1, 2, ...")
+ return
+ }
+ cmd.FlagWithArg("--rollback_index_location ", strconv.Itoa(ril))
+
+ for _, p := range ctx.GetDirectDepsWithTag(vbmetaPartitionDep) {
+ f, ok := p.(Filesystem)
+ if !ok {
+ ctx.PropertyErrorf("partitions", "%q(type: %s) is not supported",
+ p.Name(), ctx.OtherModuleType(p))
+ continue
+ }
+ signedImage := f.SignedOutputPath()
+ if signedImage == nil {
+ ctx.PropertyErrorf("partitions", "%q(type: %s) is not signed. Use `use_avb: true`",
+ p.Name(), ctx.OtherModuleType(p))
+ continue
+ }
+ cmd.FlagWithInput("--include_descriptors_from_image ", signedImage)
+ }
+
+ for i, cp := range v.properties.Chained_partitions {
+ name := proptools.String(cp.Name)
+ if name == "" {
+ ctx.PropertyErrorf("chained_partitions", "name must be specified")
+ continue
+ }
+
+ ril := proptools.IntDefault(cp.Rollback_index_location, i+1)
+ if ril < 0 {
+ ctx.PropertyErrorf("chained_partitions", "must be 0, 1, 2, ...")
+ continue
+ }
+
+ var publicKey android.Path
+ if cp.Public_key != nil {
+ publicKey = android.PathForModuleSrc(ctx, proptools.String(cp.Public_key))
+ } else {
+ publicKey = extractedPublicKeys[name]
+ }
+ cmd.FlagWithArg("--chain_partition ", fmt.Sprintf("%s:%d:%s", name, ril, publicKey.String()))
+ cmd.Implicit(publicKey)
+ }
+
+ cmd.FlagWithOutput("--output ", v.output)
+ builder.Build("vbmeta", fmt.Sprintf("vbmeta %s", ctx.ModuleName()))
+
+ v.installDir = android.PathForModuleInstall(ctx, "etc")
+ ctx.InstallFile(v.installDir, v.installFileName(), v.output)
+}
+
+// Returns the embedded shell command that prints the rollback index
+func (v *vbmeta) rollbackIndexCommand(ctx android.ModuleContext) string {
+ var cmd string
+ if v.properties.Rollback_index_file != nil {
+ f := android.PathForModuleSrc(ctx, proptools.String(v.properties.Rollback_index_file))
+ cmd = "cat " + f.String()
+ } else {
+ cmd = "date -d 'TZ=\"GMT\" " + ctx.Config().PlatformSecurityPatch() + "' +%s"
+ }
+ // Take the first line and remove the newline char
+ return "$(" + cmd + " | head -1 | tr -d '\n'" + ")"
+}
+
+// Extract public keys from chained_partitions.private_key. The keys are indexed with the partition
+// name.
+func (v *vbmeta) extractPublicKeys(ctx android.ModuleContext) map[string]android.OutputPath {
+ result := make(map[string]android.OutputPath)
+
+ builder := android.NewRuleBuilder(pctx, ctx)
+ for _, cp := range v.properties.Chained_partitions {
+ if cp.Private_key == nil {
+ continue
+ }
+
+ name := proptools.String(cp.Name)
+ if name == "" {
+ ctx.PropertyErrorf("chained_partitions", "name must be specified")
+ continue
+ }
+
+ if _, ok := result[name]; ok {
+ ctx.PropertyErrorf("chained_partitions", "name %q is duplicated", name)
+ continue
+ }
+
+ privateKeyFile := android.PathForModuleSrc(ctx, proptools.String(cp.Private_key))
+ publicKeyFile := android.PathForModuleOut(ctx, name+".avbpubkey").OutputPath
+
+ builder.Command().
+ BuiltTool("avbtool").
+ Text("extract_public_key").
+ FlagWithInput("--key ", privateKeyFile).
+ FlagWithOutput("--output ", publicKeyFile)
+
+ result[name] = publicKeyFile
+ }
+ builder.Build("vbmeta_extract_public_key", fmt.Sprintf("Extract public keys for %s", ctx.ModuleName()))
+ return result
+}
+
+var _ android.AndroidMkEntriesProvider = (*vbmeta)(nil)
+
+// Implements android.AndroidMkEntriesProvider
+func (v *vbmeta) AndroidMkEntries() []android.AndroidMkEntries {
+ return []android.AndroidMkEntries{android.AndroidMkEntries{
+ Class: "ETC",
+ OutputFile: android.OptionalPathForPath(v.output),
+ ExtraEntries: []android.AndroidMkExtraEntriesFunc{
+ func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
+ entries.SetString("LOCAL_MODULE_PATH", v.installDir.ToMakePath().String())
+ entries.SetString("LOCAL_INSTALLED_MODULE_STEM", v.installFileName())
+ },
+ },
+ }}
+}
+
+var _ Filesystem = (*vbmeta)(nil)
+
+func (v *vbmeta) OutputPath() android.Path {
+ return v.output
+}
+
+func (v *vbmeta) SignedOutputPath() android.Path {
+ return v.OutputPath() // vbmeta is always signed
+}
+
+var _ android.OutputFileProducer = (*vbmeta)(nil)
+
+// Implements android.OutputFileProducer
+func (v *vbmeta) OutputFiles(tag string) (android.Paths, error) {
+ if tag == "" {
+ return []android.Path{v.output}, nil
+ }
+ return nil, fmt.Errorf("unsupported module reference tag %q", tag)
+}