1// Copyright (C) 2021 The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package filesystem
16
17import (
18	"fmt"
19	"strconv"
20
21	"github.com/google/blueprint"
22	"github.com/google/blueprint/proptools"
23
24	"android/soong/android"
25)
26
27func init() {
28	android.RegisterModuleType("vbmeta", vbmetaFactory)
29}
30
31type vbmeta struct {
32	android.ModuleBase
33
34	properties vbmetaProperties
35
36	output     android.OutputPath
37	installDir android.InstallPath
38}
39
40type vbmetaProperties struct {
41	// Name of the partition stored in vbmeta desc. Defaults to the name of this module.
42	Partition_name *string
43
44	// Set the name of the output. Defaults to <module_name>.img.
45	Stem *string
46
47	// Path to the private key that avbtool will use to sign this vbmeta image.
48	Private_key *string `android:"path"`
49
50	// Algorithm that avbtool will use to sign this vbmeta image. Default is SHA256_RSA4096.
51	Algorithm *string
52
53	// File whose content will provide the rollback index. If unspecified, the rollback index
54	// is from PLATFORM_SECURITY_PATCH
55	Rollback_index_file *string `android:"path"`
56
57	// Rollback index location of this vbmeta image. Must be 0, 1, 2, etc. Default is 0.
58	Rollback_index_location *int64
59
60	// List of filesystem modules that this vbmeta has descriptors for. The filesystem modules
61	// have to be signed (use_avb: true).
62	Partitions []string
63
64	// List of chained partitions that this vbmeta deletages the verification.
65	Chained_partitions []chainedPartitionProperties
66}
67
68type chainedPartitionProperties struct {
69	// Name of the chained partition
70	Name *string
71
72	// Rollback index location of the chained partition. Must be 0, 1, 2, etc. Default is the
73	// index of this partition in the list + 1.
74	Rollback_index_location *int64
75
76	// Path to the public key that the chained partition is signed with. If this is specified,
77	// private_key is ignored.
78	Public_key *string `android:"path"`
79
80	// Path to the private key that the chained partition is signed with. If this is specified,
81	// and public_key is not specified, a public key is extracted from this private key and
82	// the extracted public key is embedded in the vbmeta image.
83	Private_key *string `android:"path"`
84}
85
86// vbmeta is the partition image that has the verification information for other partitions.
87func vbmetaFactory() android.Module {
88	module := &vbmeta{}
89	module.AddProperties(&module.properties)
90	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibFirst)
91	return module
92}
93
94type vbmetaDep struct {
95	blueprint.BaseDependencyTag
96	kind string
97}
98
99var vbmetaPartitionDep = vbmetaDep{kind: "partition"}
100
101func (v *vbmeta) DepsMutator(ctx android.BottomUpMutatorContext) {
102	ctx.AddDependency(ctx.Module(), vbmetaPartitionDep, v.properties.Partitions...)
103}
104
105func (v *vbmeta) installFileName() string {
106	return proptools.StringDefault(v.properties.Stem, v.BaseModuleName()+".img")
107}
108
109func (v *vbmeta) partitionName() string {
110	return proptools.StringDefault(v.properties.Partition_name, v.BaseModuleName())
111}
112
113// See external/avb/libavb/avb_slot_verify.c#VBMETA_MAX_SIZE
114const vbmetaMaxSize = 64 * 1024
115
116func (v *vbmeta) GenerateAndroidBuildActions(ctx android.ModuleContext) {
117	extractedPublicKeys := v.extractPublicKeys(ctx)
118
119	v.output = android.PathForModuleOut(ctx, v.installFileName()).OutputPath
120
121	builder := android.NewRuleBuilder(pctx, ctx)
122	cmd := builder.Command().BuiltTool("avbtool").Text("make_vbmeta_image")
123
124	key := android.PathForModuleSrc(ctx, proptools.String(v.properties.Private_key))
125	cmd.FlagWithInput("--key ", key)
126
127	algorithm := proptools.StringDefault(v.properties.Algorithm, "SHA256_RSA4096")
128	cmd.FlagWithArg("--algorithm ", algorithm)
129
130	cmd.FlagWithArg("--rollback_index ", v.rollbackIndexCommand(ctx))
131	ril := proptools.IntDefault(v.properties.Rollback_index_location, 0)
132	if ril < 0 {
133		ctx.PropertyErrorf("rollback_index_location", "must be 0, 1, 2, ...")
134		return
135	}
136	cmd.FlagWithArg("--rollback_index_location ", strconv.Itoa(ril))
137
138	for _, p := range ctx.GetDirectDepsWithTag(vbmetaPartitionDep) {
139		f, ok := p.(Filesystem)
140		if !ok {
141			ctx.PropertyErrorf("partitions", "%q(type: %s) is not supported",
142				p.Name(), ctx.OtherModuleType(p))
143			continue
144		}
145		signedImage := f.SignedOutputPath()
146		if signedImage == nil {
147			ctx.PropertyErrorf("partitions", "%q(type: %s) is not signed. Use `use_avb: true`",
148				p.Name(), ctx.OtherModuleType(p))
149			continue
150		}
151		cmd.FlagWithInput("--include_descriptors_from_image ", signedImage)
152	}
153
154	for i, cp := range v.properties.Chained_partitions {
155		name := proptools.String(cp.Name)
156		if name == "" {
157			ctx.PropertyErrorf("chained_partitions", "name must be specified")
158			continue
159		}
160
161		ril := proptools.IntDefault(cp.Rollback_index_location, i+1)
162		if ril < 0 {
163			ctx.PropertyErrorf("chained_partitions", "must be 0, 1, 2, ...")
164			continue
165		}
166
167		var publicKey android.Path
168		if cp.Public_key != nil {
169			publicKey = android.PathForModuleSrc(ctx, proptools.String(cp.Public_key))
170		} else {
171			publicKey = extractedPublicKeys[name]
172		}
173		cmd.FlagWithArg("--chain_partition ", fmt.Sprintf("%s:%d:%s", name, ril, publicKey.String()))
174		cmd.Implicit(publicKey)
175	}
176
177	cmd.FlagWithOutput("--output ", v.output)
178
179	// libavb expects to be able to read the maximum vbmeta size, so we must provide a partition
180	// which matches this or the read will fail.
181	builder.Command().Text("truncate").
182		FlagWithArg("-s ", strconv.Itoa(vbmetaMaxSize)).
183		Output(v.output)
184
185	builder.Build("vbmeta", fmt.Sprintf("vbmeta %s", ctx.ModuleName()))
186
187	v.installDir = android.PathForModuleInstall(ctx, "etc")
188	ctx.InstallFile(v.installDir, v.installFileName(), v.output)
189}
190
191// Returns the embedded shell command that prints the rollback index
192func (v *vbmeta) rollbackIndexCommand(ctx android.ModuleContext) string {
193	var cmd string
194	if v.properties.Rollback_index_file != nil {
195		f := android.PathForModuleSrc(ctx, proptools.String(v.properties.Rollback_index_file))
196		cmd = "cat " + f.String()
197	} else {
198		cmd = "date -d 'TZ=\"GMT\" " + ctx.Config().PlatformSecurityPatch() + "' +%s"
199	}
200	// Take the first line and remove the newline char
201	return "$(" + cmd + " | head -1 | tr -d '\n'" + ")"
202}
203
204// Extract public keys from chained_partitions.private_key. The keys are indexed with the partition
205// name.
206func (v *vbmeta) extractPublicKeys(ctx android.ModuleContext) map[string]android.OutputPath {
207	result := make(map[string]android.OutputPath)
208
209	builder := android.NewRuleBuilder(pctx, ctx)
210	for _, cp := range v.properties.Chained_partitions {
211		if cp.Private_key == nil {
212			continue
213		}
214
215		name := proptools.String(cp.Name)
216		if name == "" {
217			ctx.PropertyErrorf("chained_partitions", "name must be specified")
218			continue
219		}
220
221		if _, ok := result[name]; ok {
222			ctx.PropertyErrorf("chained_partitions", "name %q is duplicated", name)
223			continue
224		}
225
226		privateKeyFile := android.PathForModuleSrc(ctx, proptools.String(cp.Private_key))
227		publicKeyFile := android.PathForModuleOut(ctx, name+".avbpubkey").OutputPath
228
229		builder.Command().
230			BuiltTool("avbtool").
231			Text("extract_public_key").
232			FlagWithInput("--key ", privateKeyFile).
233			FlagWithOutput("--output ", publicKeyFile)
234
235		result[name] = publicKeyFile
236	}
237	builder.Build("vbmeta_extract_public_key", fmt.Sprintf("Extract public keys for %s", ctx.ModuleName()))
238	return result
239}
240
241var _ android.AndroidMkEntriesProvider = (*vbmeta)(nil)
242
243// Implements android.AndroidMkEntriesProvider
244func (v *vbmeta) AndroidMkEntries() []android.AndroidMkEntries {
245	return []android.AndroidMkEntries{android.AndroidMkEntries{
246		Class:      "ETC",
247		OutputFile: android.OptionalPathForPath(v.output),
248		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
249			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
250				entries.SetString("LOCAL_MODULE_PATH", v.installDir.ToMakePath().String())
251				entries.SetString("LOCAL_INSTALLED_MODULE_STEM", v.installFileName())
252			},
253		},
254	}}
255}
256
257var _ Filesystem = (*vbmeta)(nil)
258
259func (v *vbmeta) OutputPath() android.Path {
260	return v.output
261}
262
263func (v *vbmeta) SignedOutputPath() android.Path {
264	return v.OutputPath() // vbmeta is always signed
265}
266
267var _ android.OutputFileProducer = (*vbmeta)(nil)
268
269// Implements android.OutputFileProducer
270func (v *vbmeta) OutputFiles(tag string) (android.Paths, error) {
271	if tag == "" {
272		return []android.Path{v.output}, nil
273	}
274	return nil, fmt.Errorf("unsupported module reference tag %q", tag)
275}
276