blob: 755241d282829383836d6b1cf6a3e2e9769d0811 [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
144 self.key_path = key_path
Oleksiy Avramchenko166d8192022-01-20 22:10:52 +0100145 if key_path and not os.path.exists(key_path) and OPTIONS.search_path:
146 new_key_path = os.path.join(OPTIONS.search_path, key_path)
147 if os.path.exists(new_key_path):
148 self.key_path = new_key_path
149
Tao Bao7549e5e2018-10-03 14:23:59 -0700150 self.salt = salt
151 self.signing_args = signing_args
152 self.image_size = None
153
154 def CalculateMinPartitionSize(self, image_size, size_calculator=None):
155 """Calculates min partition size for a given image size.
156
157 This is used when determining the partition size for a dynamic partition,
158 which should be cover the given image size (for filesystem files) as well as
159 the verity metadata size.
160
161 Args:
162 image_size: The size of the image in question.
163 size_calculator: The function to calculate max image size
164 for a given partition size.
165
166 Returns:
167 The minimum partition size required to accommodate the image size.
168 """
169 if size_calculator is None:
170 size_calculator = self.CalculateMaxImageSize
171
172 # Use image size as partition size to approximate final partition size.
173 image_ratio = size_calculator(image_size) / float(image_size)
174
175 # Prepare a binary search for the optimal partition size.
176 lo = int(image_size / image_ratio) // BLOCK_SIZE * BLOCK_SIZE - BLOCK_SIZE
177
178 # Ensure lo is small enough: max_image_size should <= image_size.
179 delta = BLOCK_SIZE
180 max_image_size = size_calculator(lo)
181 while max_image_size > image_size:
182 image_ratio = max_image_size / float(lo)
183 lo = int(image_size / image_ratio) // BLOCK_SIZE * BLOCK_SIZE - delta
184 delta *= 2
185 max_image_size = size_calculator(lo)
186
187 hi = lo + BLOCK_SIZE
188
189 # Ensure hi is large enough: max_image_size should >= image_size.
190 delta = BLOCK_SIZE
191 max_image_size = size_calculator(hi)
192 while max_image_size < image_size:
193 image_ratio = max_image_size / float(hi)
194 hi = int(image_size / image_ratio) // BLOCK_SIZE * BLOCK_SIZE + delta
195 delta *= 2
196 max_image_size = size_calculator(hi)
197
198 partition_size = hi
199
200 # Start to binary search.
201 while lo < hi:
202 mid = ((lo + hi) // (2 * BLOCK_SIZE)) * BLOCK_SIZE
203 max_image_size = size_calculator(mid)
204 if max_image_size >= image_size: # if mid can accommodate image_size
205 if mid < partition_size: # if a smaller partition size is found
206 partition_size = mid
207 hi = mid
208 else:
209 lo = mid + BLOCK_SIZE
210
211 logger.info(
212 "CalculateMinPartitionSize(%d): partition_size %d.", image_size,
213 partition_size)
214
215 return partition_size
216
217 def CalculateDynamicPartitionSize(self, image_size):
218 self.partition_size = self.CalculateMinPartitionSize(image_size)
219 return self.partition_size
220
221 def CalculateMaxImageSize(self, partition_size=None):
222 """Calculates max image size for a given partition size.
223
224 Args:
225 partition_size: The partition size, which defaults to self.partition_size
226 if unspecified.
227
228 Returns:
229 The maximum image size.
230
231 Raises:
232 BuildVerityImageError: On error or getting invalid image size.
233 """
234 if partition_size is None:
235 partition_size = self.partition_size
236 assert partition_size > 0, \
237 "Invalid partition size: {}".format(partition_size)
238
239 add_footer = ("add_hash_footer" if self.footer_type == self.AVB_HASH_FOOTER
240 else "add_hashtree_footer")
241 cmd = [self.avbtool, add_footer, "--partition_size",
242 str(partition_size), "--calc_max_image_size"]
243 cmd.extend(shlex.split(self.signing_args))
244
245 proc = common.Run(cmd)
246 output, _ = proc.communicate()
247 if proc.returncode != 0:
248 raise BuildVerityImageError(
249 "Failed to calculate max image size:\n{}".format(output))
250 image_size = int(output)
251 if image_size <= 0:
252 raise BuildVerityImageError(
253 "Invalid max image size: {}".format(output))
254 self.image_size = image_size
255 return image_size
256
257 def PadSparseImage(self, out_file):
258 # No-op as the padding is taken care of by avbtool.
259 pass
260
261 def Build(self, out_file):
262 """Adds dm-verity hashtree and AVB metadata to an image.
263
264 Args:
265 out_file: Path to image to modify.
266 """
267 add_footer = ("add_hash_footer" if self.footer_type == self.AVB_HASH_FOOTER
268 else "add_hashtree_footer")
269 cmd = [self.avbtool, add_footer,
270 "--partition_size", str(self.partition_size),
271 "--partition_name", self.partition_name,
272 "--image", out_file]
273 if self.key_path and self.algorithm:
274 cmd.extend(["--key", self.key_path, "--algorithm", self.algorithm])
275 if self.salt:
276 cmd.extend(["--salt", self.salt])
277 cmd.extend(shlex.split(self.signing_args))
278
279 proc = common.Run(cmd)
280 output, _ = proc.communicate()
281 if proc.returncode != 0:
282 raise BuildVerityImageError("Failed to add AVB footer: {}".format(output))
Tao Bao71197512018-10-11 14:08:45 -0700283
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700284
Hongguang Chenf23364d2020-04-27 18:36:36 -0700285def CreateCustomImageBuilder(info_dict, partition_name, partition_size,
286 key_path, algorithm, signing_args):
287 builder = None
288 if info_dict.get("avb_enable") == "true":
289 builder = VerifiedBootVersion2VerityImageBuilder(
290 partition_name,
291 partition_size,
292 VerifiedBootVersion2VerityImageBuilder.AVB_HASHTREE_FOOTER,
293 info_dict.get("avb_avbtool"),
294 key_path,
295 algorithm,
296 # Salt is None because custom images have no fingerprint property to be
297 # used as the salt.
298 None,
299 signing_args)
300
301 return builder
Jiyong Parkb92b8f42021-03-15 23:13:42 +0900302
303
304def GetDiskUsage(path):
305 """Returns the number of bytes that "path" occupies on host.
306
307 Args:
308 path: The directory or file to calculate size on.
309
310 Returns:
311 The number of bytes based on a 1K block_size.
312 """
313 cmd = ["du", "-b", "-k", "-s", path]
314 output = common.RunAndCheckOutput(cmd, verbose=False)
315 return int(output.split()[0]) * 1024
316
317
Tianjiebbde59f2021-05-03 21:18:56 -0700318def CalculateVbmetaDigest(extracted_dir, avbtool):
319 """Calculates the vbmeta digest of the images in the extracted target_file"""
320
321 images_dir = common.MakeTempDir()
322 for name in ("PREBUILT_IMAGES", "RADIO", "IMAGES"):
323 path = os.path.join(extracted_dir, name)
324 if not os.path.exists(path):
325 continue
326
327 # Create symlink for image files under PREBUILT_IMAGES, RADIO and IMAGES,
328 # and put them into one directory.
329 for filename in os.listdir(path):
330 if not filename.endswith(".img"):
331 continue
332 symlink_path = os.path.join(images_dir, filename)
333 # The files in latter directory overwrite the existing links
334 common.RunAndCheckOutput(
335 ['ln', '-sf', os.path.join(path, filename), symlink_path])
336
337 cmd = [avbtool, "calculate_vbmeta_digest", "--image",
338 os.path.join(images_dir, 'vbmeta.img')]
339 return common.RunAndCheckOutput(cmd)
340
341
Jiyong Parkb92b8f42021-03-15 23:13:42 +0900342def main(argv):
343 if len(argv) != 2:
344 print(__doc__)
345 sys.exit(1)
346
347 common.InitLogging()
348
349 dict_file = argv[0]
350 out_file = argv[1]
351
352 prop_dict = {}
353 with open(dict_file, 'r') as f:
354 for line in f:
355 line = line.strip()
356 if not line or line.startswith("#"):
357 continue
358 k, v = line.split("=", 1)
359 prop_dict[k] = v
360
361 builder = CreateVerityImageBuilder(prop_dict)
362
363 if "partition_size" not in prop_dict:
364 image_size = GetDiskUsage(out_file)
365 # make sure that the image is big enough to hold vbmeta and footer
366 image_size = image_size + (MAX_VBMETA_SIZE + MAX_FOOTER_SIZE)
367 size = builder.CalculateDynamicPartitionSize(image_size)
368 prop_dict["partition_size"] = size
369
370 builder.Build(out_file)
371
372
373if __name__ == '__main__':
374 try:
375 main(sys.argv[1:])
376 finally:
377 common.Cleanup()