blob: dddb7f4448668491ff9caebc14b3ea99cf06e201 [file] [log] [blame]
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001#!/usr/bin/env python
2#
3# Copyright (C) 2018 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
Jiyong Parkb92b8f42021-03-15 23:13:42 +090017"""
18Signs a given image using avbtool
19
20Usage: verity_utils properties_file output_image
21"""
22
Tianjie Xu67c7cbb2018-08-30 00:32:07 -070023from __future__ import print_function
24
Tao Bao32fcdab2018-10-12 10:30:39 -070025import logging
Tao Bao71197512018-10-11 14:08:45 -070026import os.path
27import shlex
Tianjie Xu67c7cbb2018-08-30 00:32:07 -070028import struct
Tianjiebbde59f2021-05-03 21:18:56 -070029import sys
Tianjie Xu67c7cbb2018-08-30 00:32:07 -070030
31import common
Tao Bao71197512018-10-11 14:08:45 -070032import sparse_img
Tianjie Xu67c7cbb2018-08-30 00:32:07 -070033from rangelib import RangeSet
34
Tao Bao32fcdab2018-10-12 10:30:39 -070035logger = logging.getLogger(__name__)
36
Tao Bao71197512018-10-11 14:08:45 -070037OPTIONS = common.OPTIONS
38BLOCK_SIZE = common.BLOCK_SIZE
39FIXED_SALT = "aee087a5be3b982978c923f566a94613496b417f2af592639bc80d141e34dfe7"
40
Jiyong Parkb92b8f42021-03-15 23:13:42 +090041# From external/avb/avbtool.py
42MAX_VBMETA_SIZE = 64 * 1024
43MAX_FOOTER_SIZE = 4096
Tao Bao71197512018-10-11 14:08:45 -070044
45class BuildVerityImageError(Exception):
46 """An Exception raised during verity image building."""
47
48 def __init__(self, message):
49 Exception.__init__(self, message)
50
51
Tao Bao7549e5e2018-10-03 14:23:59 -070052def CreateVerityImageBuilder(prop_dict):
53 """Returns a verity image builder based on the given build properties.
Tao Bao71197512018-10-11 14:08:45 -070054
55 Args:
Tao Bao7549e5e2018-10-03 14:23:59 -070056 prop_dict: A dict that contains the build properties. In particular, it will
57 look for verity-related property values.
Tao Bao71197512018-10-11 14:08:45 -070058
59 Returns:
Tao Bao7549e5e2018-10-03 14:23:59 -070060 A VerityImageBuilder instance for Verified Boot 1.0 or Verified Boot 2.0; or
61 None if the given build doesn't support Verified Boot.
Tao Bao71197512018-10-11 14:08:45 -070062 """
Tao Bao7549e5e2018-10-03 14:23:59 -070063 partition_size = prop_dict.get("partition_size")
64 # partition_size could be None at this point, if using dynamic partitions.
65 if partition_size:
66 partition_size = int(partition_size)
Tao Bao71197512018-10-11 14:08:45 -070067
Tao Bao7549e5e2018-10-03 14:23:59 -070068 # Verified Boot 2.0
69 if (prop_dict.get("avb_hash_enable") == "true" or
70 prop_dict.get("avb_hashtree_enable") == "true"):
71 # key_path and algorithm are only available when chain partition is used.
72 key_path = prop_dict.get("avb_key_path")
73 algorithm = prop_dict.get("avb_algorithm")
Tao Bao9e893c32019-06-20 16:14:55 -070074
75 # Image uses hash footer.
Tao Bao7549e5e2018-10-03 14:23:59 -070076 if prop_dict.get("avb_hash_enable") == "true":
77 return VerifiedBootVersion2VerityImageBuilder(
78 prop_dict["partition_name"],
79 partition_size,
80 VerifiedBootVersion2VerityImageBuilder.AVB_HASH_FOOTER,
81 prop_dict["avb_avbtool"],
82 key_path,
83 algorithm,
84 prop_dict.get("avb_salt"),
85 prop_dict["avb_add_hash_footer_args"])
Tao Bao9e893c32019-06-20 16:14:55 -070086
87 # Image uses hashtree footer.
88 return VerifiedBootVersion2VerityImageBuilder(
89 prop_dict["partition_name"],
90 partition_size,
91 VerifiedBootVersion2VerityImageBuilder.AVB_HASHTREE_FOOTER,
92 prop_dict["avb_avbtool"],
93 key_path,
94 algorithm,
95 prop_dict.get("avb_salt"),
96 prop_dict["avb_add_hashtree_footer_args"])
Tao Bao71197512018-10-11 14:08:45 -070097
Tao Bao7549e5e2018-10-03 14:23:59 -070098 return None
Tao Bao71197512018-10-11 14:08:45 -070099
100
Tao Bao7549e5e2018-10-03 14:23:59 -0700101class VerityImageBuilder(object):
102 """A builder that generates an image with verity metadata for Verified Boot.
Tao Bao71197512018-10-11 14:08:45 -0700103
Tao Bao7549e5e2018-10-03 14:23:59 -0700104 A VerityImageBuilder instance handles the works for building an image with
105 verity metadata for supporting Android Verified Boot. This class defines the
106 common interface between Verified Boot 1.0 and Verified Boot 2.0. A matching
107 builder will be returned based on the given build properties.
108
109 More info on the verity image generation can be found at the following link.
110 https://source.android.com/security/verifiedboot/dm-verity#implementation
Tao Bao71197512018-10-11 14:08:45 -0700111 """
Tao Bao71197512018-10-11 14:08:45 -0700112
Tao Bao7549e5e2018-10-03 14:23:59 -0700113 def CalculateMaxImageSize(self, partition_size):
114 """Calculates the filesystem image size for the given partition size."""
115 raise NotImplementedError
Tao Bao71197512018-10-11 14:08:45 -0700116
Tao Bao7549e5e2018-10-03 14:23:59 -0700117 def CalculateDynamicPartitionSize(self, image_size):
118 """Calculates and sets the partition size for a dynamic partition."""
119 raise NotImplementedError
Tao Bao71197512018-10-11 14:08:45 -0700120
Tao Bao7549e5e2018-10-03 14:23:59 -0700121 def PadSparseImage(self, out_file):
122 """Adds padding to the generated sparse image."""
123 raise NotImplementedError
124
125 def Build(self, out_file):
126 """Builds the verity image and writes it to the given file."""
127 raise NotImplementedError
128
129
Tao Bao7549e5e2018-10-03 14:23:59 -0700130class VerifiedBootVersion2VerityImageBuilder(VerityImageBuilder):
131 """A VerityImageBuilder for Verified Boot 2.0."""
132
133 AVB_HASH_FOOTER = 1
134 AVB_HASHTREE_FOOTER = 2
135
136 def __init__(self, partition_name, partition_size, footer_type, avbtool,
137 key_path, algorithm, salt, signing_args):
138 self.version = 2
139 self.partition_name = partition_name
140 self.partition_size = partition_size
141 self.footer_type = footer_type
142 self.avbtool = avbtool
143 self.algorithm = algorithm
zhangyongpeng70756972023-04-12 15:31:33 +0800144 self.key_path = common.ResolveAVBSigningPathArgs(key_path)
Oleksiy Avramchenko166d8192022-01-20 22:10:52 +0100145
Tao Bao7549e5e2018-10-03 14:23:59 -0700146 self.salt = salt
147 self.signing_args = signing_args
148 self.image_size = None
149
150 def CalculateMinPartitionSize(self, image_size, size_calculator=None):
151 """Calculates min partition size for a given image size.
152
153 This is used when determining the partition size for a dynamic partition,
154 which should be cover the given image size (for filesystem files) as well as
155 the verity metadata size.
156
157 Args:
158 image_size: The size of the image in question.
159 size_calculator: The function to calculate max image size
160 for a given partition size.
161
162 Returns:
163 The minimum partition size required to accommodate the image size.
164 """
165 if size_calculator is None:
166 size_calculator = self.CalculateMaxImageSize
167
168 # Use image size as partition size to approximate final partition size.
169 image_ratio = size_calculator(image_size) / float(image_size)
170
171 # Prepare a binary search for the optimal partition size.
172 lo = int(image_size / image_ratio) // BLOCK_SIZE * BLOCK_SIZE - BLOCK_SIZE
173
174 # Ensure lo is small enough: max_image_size should <= image_size.
175 delta = BLOCK_SIZE
176 max_image_size = size_calculator(lo)
177 while max_image_size > image_size:
178 image_ratio = max_image_size / float(lo)
179 lo = int(image_size / image_ratio) // BLOCK_SIZE * BLOCK_SIZE - delta
180 delta *= 2
181 max_image_size = size_calculator(lo)
182
183 hi = lo + BLOCK_SIZE
184
185 # Ensure hi is large enough: max_image_size should >= image_size.
186 delta = BLOCK_SIZE
187 max_image_size = size_calculator(hi)
188 while max_image_size < image_size:
189 image_ratio = max_image_size / float(hi)
190 hi = int(image_size / image_ratio) // BLOCK_SIZE * BLOCK_SIZE + delta
191 delta *= 2
192 max_image_size = size_calculator(hi)
193
194 partition_size = hi
195
196 # Start to binary search.
197 while lo < hi:
198 mid = ((lo + hi) // (2 * BLOCK_SIZE)) * BLOCK_SIZE
199 max_image_size = size_calculator(mid)
200 if max_image_size >= image_size: # if mid can accommodate image_size
201 if mid < partition_size: # if a smaller partition size is found
202 partition_size = mid
203 hi = mid
204 else:
205 lo = mid + BLOCK_SIZE
206
207 logger.info(
208 "CalculateMinPartitionSize(%d): partition_size %d.", image_size,
209 partition_size)
210
211 return partition_size
212
213 def CalculateDynamicPartitionSize(self, image_size):
214 self.partition_size = self.CalculateMinPartitionSize(image_size)
215 return self.partition_size
216
217 def CalculateMaxImageSize(self, partition_size=None):
218 """Calculates max image size for a given partition size.
219
220 Args:
221 partition_size: The partition size, which defaults to self.partition_size
222 if unspecified.
223
224 Returns:
225 The maximum image size.
226
227 Raises:
228 BuildVerityImageError: On error or getting invalid image size.
229 """
230 if partition_size is None:
231 partition_size = self.partition_size
232 assert partition_size > 0, \
233 "Invalid partition size: {}".format(partition_size)
234
235 add_footer = ("add_hash_footer" if self.footer_type == self.AVB_HASH_FOOTER
236 else "add_hashtree_footer")
237 cmd = [self.avbtool, add_footer, "--partition_size",
238 str(partition_size), "--calc_max_image_size"]
239 cmd.extend(shlex.split(self.signing_args))
240
241 proc = common.Run(cmd)
242 output, _ = proc.communicate()
243 if proc.returncode != 0:
244 raise BuildVerityImageError(
245 "Failed to calculate max image size:\n{}".format(output))
246 image_size = int(output)
247 if image_size <= 0:
248 raise BuildVerityImageError(
249 "Invalid max image size: {}".format(output))
250 self.image_size = image_size
251 return image_size
252
253 def PadSparseImage(self, out_file):
254 # No-op as the padding is taken care of by avbtool.
255 pass
256
257 def Build(self, out_file):
258 """Adds dm-verity hashtree and AVB metadata to an image.
259
260 Args:
261 out_file: Path to image to modify.
262 """
263 add_footer = ("add_hash_footer" if self.footer_type == self.AVB_HASH_FOOTER
264 else "add_hashtree_footer")
265 cmd = [self.avbtool, add_footer,
266 "--partition_size", str(self.partition_size),
267 "--partition_name", self.partition_name,
268 "--image", out_file]
269 if self.key_path and self.algorithm:
270 cmd.extend(["--key", self.key_path, "--algorithm", self.algorithm])
271 if self.salt:
272 cmd.extend(["--salt", self.salt])
273 cmd.extend(shlex.split(self.signing_args))
274
275 proc = common.Run(cmd)
276 output, _ = proc.communicate()
277 if proc.returncode != 0:
278 raise BuildVerityImageError("Failed to add AVB footer: {}".format(output))
Tao Bao71197512018-10-11 14:08:45 -0700279
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700280
Hongguang Chenf23364d2020-04-27 18:36:36 -0700281def CreateCustomImageBuilder(info_dict, partition_name, partition_size,
282 key_path, algorithm, signing_args):
283 builder = None
284 if info_dict.get("avb_enable") == "true":
285 builder = VerifiedBootVersion2VerityImageBuilder(
286 partition_name,
287 partition_size,
288 VerifiedBootVersion2VerityImageBuilder.AVB_HASHTREE_FOOTER,
289 info_dict.get("avb_avbtool"),
290 key_path,
291 algorithm,
292 # Salt is None because custom images have no fingerprint property to be
293 # used as the salt.
294 None,
295 signing_args)
296
297 return builder
Jiyong Parkb92b8f42021-03-15 23:13:42 +0900298
299
300def GetDiskUsage(path):
301 """Returns the number of bytes that "path" occupies on host.
302
303 Args:
304 path: The directory or file to calculate size on.
305
306 Returns:
307 The number of bytes based on a 1K block_size.
308 """
309 cmd = ["du", "-b", "-k", "-s", path]
310 output = common.RunAndCheckOutput(cmd, verbose=False)
311 return int(output.split()[0]) * 1024
312
313
Tianjiebbde59f2021-05-03 21:18:56 -0700314def CalculateVbmetaDigest(extracted_dir, avbtool):
315 """Calculates the vbmeta digest of the images in the extracted target_file"""
316
317 images_dir = common.MakeTempDir()
318 for name in ("PREBUILT_IMAGES", "RADIO", "IMAGES"):
319 path = os.path.join(extracted_dir, name)
320 if not os.path.exists(path):
321 continue
322
323 # Create symlink for image files under PREBUILT_IMAGES, RADIO and IMAGES,
324 # and put them into one directory.
325 for filename in os.listdir(path):
326 if not filename.endswith(".img"):
327 continue
328 symlink_path = os.path.join(images_dir, filename)
329 # The files in latter directory overwrite the existing links
330 common.RunAndCheckOutput(
331 ['ln', '-sf', os.path.join(path, filename), symlink_path])
332
333 cmd = [avbtool, "calculate_vbmeta_digest", "--image",
334 os.path.join(images_dir, 'vbmeta.img')]
335 return common.RunAndCheckOutput(cmd)
336
337
Jiyong Parkb92b8f42021-03-15 23:13:42 +0900338def main(argv):
339 if len(argv) != 2:
340 print(__doc__)
341 sys.exit(1)
342
343 common.InitLogging()
344
345 dict_file = argv[0]
346 out_file = argv[1]
347
348 prop_dict = {}
349 with open(dict_file, 'r') as f:
350 for line in f:
351 line = line.strip()
352 if not line or line.startswith("#"):
353 continue
354 k, v = line.split("=", 1)
355 prop_dict[k] = v
356
357 builder = CreateVerityImageBuilder(prop_dict)
358
359 if "partition_size" not in prop_dict:
360 image_size = GetDiskUsage(out_file)
361 # make sure that the image is big enough to hold vbmeta and footer
362 image_size = image_size + (MAX_VBMETA_SIZE + MAX_FOOTER_SIZE)
363 size = builder.CalculateDynamicPartitionSize(image_size)
364 prop_dict["partition_size"] = size
365
366 builder.Build(out_file)
367
368
369if __name__ == '__main__':
370 try:
371 main(sys.argv[1:])
372 finally:
373 common.Cleanup()