blob: 6a47859331b4e2aaa19d1bcd4a4e02414d9aae8d [file] [log] [blame]
// 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)
pctx.HostBinToolVariable("avbtool", "avbtool")
}
var (
extractPublicKeyRule = pctx.AndroidStaticRule("avb_extract_public_key",
blueprint.RuleParams{
Command: `${avbtool} extract_public_key --key $in --output $out`,
CommandDeps: []string{
"${avbtool}",
},
})
)
type vbmeta struct {
android.ModuleBase
properties VbmetaProperties
output android.Path
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
// The rollback index. If unspecified, the rollback index is from PLATFORM_SECURITY_PATCH
Rollback_index *int64
// 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 proptools.Configurable[[]string]
// Metadata about the chained partitions that this vbmeta delegates the verification.
// This is an alternative to chained_partitions, using chained_partitions instead is simpler
// in most cases. However, this property allows building this vbmeta partition without
// its chained partitions existing in this build.
Chained_partition_metadata []ChainedPartitionProperties
// List of chained partitions that this vbmeta delegates the verification. They are the
// names of other vbmeta modules.
Chained_partitions []string
// List of key-value pair of avb properties
Avb_properties []avbProperty
}
type avbProperty struct {
// Key of given avb property
Key *string
// Value of given avb property
Value *string
}
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"`
}
type vbmetaPartitionInfo struct {
// Name of the partition
Name string
// Rollback index location, non-negative int
RollbackIndexLocation int
// The path to the public key of the private key used to sign this partition. Derived from
// the private key.
PublicKey android.Path
}
var vbmetaPartitionProvider = blueprint.NewProvider[vbmetaPartitionInfo]()
// 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.MultilibCommon)
return module
}
type vbmetaDep struct {
blueprint.BaseDependencyTag
}
type chainedPartitionDep struct {
blueprint.BaseDependencyTag
}
var vbmetaPartitionDep = vbmetaDep{}
var vbmetaChainedPartitionDep = chainedPartitionDep{}
func (v *vbmeta) DepsMutator(ctx android.BottomUpMutatorContext) {
ctx.AddDependency(ctx.Module(), vbmetaPartitionDep, v.properties.Partitions.GetOrDefault(ctx, nil)...)
ctx.AddDependency(ctx.Module(), vbmetaChainedPartitionDep, v.properties.Chained_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())
}
// See external/avb/libavb/avb_slot_verify.c#VBMETA_MAX_SIZE
const vbmetaMaxSize = 64 * 1024
func (v *vbmeta) GenerateAndroidBuildActions(ctx android.ModuleContext) {
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 _, avb_prop := range v.properties.Avb_properties {
key := proptools.String(avb_prop.Key)
if key == "" {
ctx.PropertyErrorf("avb_properties", "key must be specified")
continue
}
value := proptools.String(avb_prop.Value)
if value == "" {
ctx.PropertyErrorf("avb_properties", "value must be specified")
continue
}
cmd.FlagWithArg("--prop ", key+":"+value)
}
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)
}
seenRils := make(map[int]bool)
for _, cp := range ctx.GetDirectDepsWithTag(vbmetaChainedPartitionDep) {
info, ok := android.OtherModuleProvider(ctx, cp, vbmetaPartitionProvider)
if !ok {
ctx.PropertyErrorf("chained_partitions", "Expected all modules in chained_partitions to provide vbmetaPartitionProvider, but %s did not", cp.Name())
continue
}
if info.Name == "" {
ctx.PropertyErrorf("chained_partitions", "name must be specified")
continue
}
ril := info.RollbackIndexLocation
if ril < 0 {
ctx.PropertyErrorf("chained_partitions", "rollback index location must be 0, 1, 2, ...")
continue
} else if seenRils[ril] {
ctx.PropertyErrorf("chained_partitions", "Multiple chained partitions with the same rollback index location %d", ril)
continue
}
seenRils[ril] = true
publicKey := info.PublicKey
cmd.FlagWithArg("--chain_partition ", fmt.Sprintf("%s:%d:%s", info.Name, ril, publicKey.String()))
cmd.Implicit(publicKey)
}
for _, cpm := range v.properties.Chained_partition_metadata {
name := proptools.String(cpm.Name)
if name == "" {
ctx.PropertyErrorf("chained_partitions", "name must be specified")
continue
}
ril := proptools.IntDefault(cpm.Rollback_index_location, -1)
if ril < 0 {
ctx.PropertyErrorf("chained_partition_metadata", "rollback index location must be 0, 1, 2, ...")
continue
} else if seenRils[ril] {
ctx.PropertyErrorf("chained_partition_metadata", "Multiple chained partitions with the same rollback index location %d", ril)
continue
}
seenRils[ril] = true
var publicKey android.Path
if cpm.Public_key != nil {
publicKey = android.PathForModuleSrc(ctx, *cpm.Public_key)
} else if cpm.Private_key != nil {
privateKey := android.PathForModuleSrc(ctx, *cpm.Private_key)
extractedPublicKey := android.PathForModuleOut(ctx, "chained_metadata", name+".avbpubkey")
ctx.Build(pctx, android.BuildParams{
Rule: extractPublicKeyRule,
Input: privateKey,
Output: extractedPublicKey,
})
publicKey = extractedPublicKey
} else {
ctx.PropertyErrorf("public_key", "Either public_key or private_key must be specified")
continue
}
cmd.FlagWithArg("--chain_partition ", fmt.Sprintf("%s:%d:%s", name, ril, publicKey.String()))
cmd.Implicit(publicKey)
}
output := android.PathForModuleOut(ctx, v.installFileName())
cmd.FlagWithOutput("--output ", output)
// libavb expects to be able to read the maximum vbmeta size, so we must provide a partition
// which matches this or the read will fail.
builder.Command().Text("truncate").
FlagWithArg("-s ", strconv.Itoa(vbmetaMaxSize)).
Output(output)
builder.Build("vbmeta", fmt.Sprintf("vbmeta %s", ctx.ModuleName()))
v.installDir = android.PathForModuleInstall(ctx, "etc")
ctx.InstallFile(v.installDir, v.installFileName(), output)
extractedPublicKey := android.PathForModuleOut(ctx, v.partitionName()+".avbpubkey")
ctx.Build(pctx, android.BuildParams{
Rule: extractPublicKeyRule,
Input: key,
Output: extractedPublicKey,
})
android.SetProvider(ctx, vbmetaPartitionProvider, vbmetaPartitionInfo{
Name: v.partitionName(),
RollbackIndexLocation: ril,
PublicKey: extractedPublicKey,
})
ctx.SetOutputFiles([]android.Path{output}, "")
v.output = output
}
// Returns the embedded shell command that prints the rollback index
func (v *vbmeta) rollbackIndexCommand(ctx android.ModuleContext) string {
if v.properties.Rollback_index != nil {
return fmt.Sprintf("%d", *v.properties.Rollback_index)
} else {
// Take the first line and remove the newline char
return "$(date -d 'TZ=\"GMT\" " + ctx.Config().PlatformSecurityPatch() + "' +%s | head -1 | tr -d '\n'" + ")"
}
}
var _ android.AndroidMkProviderInfoProducer = (*vbmeta)(nil)
func (v *vbmeta) PrepareAndroidMKProviderInfo(config android.Config) *android.AndroidMkProviderInfo {
providerData := android.AndroidMkProviderInfo{
PrimaryInfo: android.AndroidMkInfo{
Class: "ETC",
OutputFile: android.OptionalPathForPath(v.output),
EntryMap: make(map[string][]string),
},
}
providerData.PrimaryInfo.SetString("LOCAL_MODULE_PATH", v.installDir.String())
providerData.PrimaryInfo.SetString("LOCAL_INSTALLED_MODULE_STEM", v.installFileName())
return &providerData
}
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
}