blob: 8faa2d1fc009652f109b238825f5b53b28580613 [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
29
30import common
Tao Bao71197512018-10-11 14:08:45 -070031import sparse_img
Tianjie Xu67c7cbb2018-08-30 00:32:07 -070032from rangelib import RangeSet
33
Tao Bao32fcdab2018-10-12 10:30:39 -070034logger = logging.getLogger(__name__)
35
Tao Bao71197512018-10-11 14:08:45 -070036OPTIONS = common.OPTIONS
37BLOCK_SIZE = common.BLOCK_SIZE
38FIXED_SALT = "aee087a5be3b982978c923f566a94613496b417f2af592639bc80d141e34dfe7"
39
Jiyong Parkb92b8f42021-03-15 23:13:42 +090040# From external/avb/avbtool.py
41MAX_VBMETA_SIZE = 64 * 1024
42MAX_FOOTER_SIZE = 4096
Tao Bao71197512018-10-11 14:08:45 -070043
44class BuildVerityImageError(Exception):
45 """An Exception raised during verity image building."""
46
47 def __init__(self, message):
48 Exception.__init__(self, message)
49
50
Tao Bao7549e5e2018-10-03 14:23:59 -070051def GetVerityFECSize(image_size):
52 cmd = ["fec", "-s", str(image_size)]
Tao Bao71197512018-10-11 14:08:45 -070053 output = common.RunAndCheckOutput(cmd, verbose=False)
54 return int(output)
55
56
Tao Bao7549e5e2018-10-03 14:23:59 -070057def GetVerityTreeSize(image_size):
58 cmd = ["build_verity_tree", "-s", str(image_size)]
Tao Bao71197512018-10-11 14:08:45 -070059 output = common.RunAndCheckOutput(cmd, verbose=False)
60 return int(output)
61
62
Tao Bao7549e5e2018-10-03 14:23:59 -070063def GetVerityMetadataSize(image_size):
Tao Baod5ab10e2019-05-02 18:17:18 -070064 cmd = ["build_verity_metadata", "size", str(image_size)]
Tao Bao71197512018-10-11 14:08:45 -070065 output = common.RunAndCheckOutput(cmd, verbose=False)
66 return int(output)
67
68
Tao Bao7549e5e2018-10-03 14:23:59 -070069def GetVeritySize(image_size, fec_supported):
70 verity_tree_size = GetVerityTreeSize(image_size)
71 verity_metadata_size = GetVerityMetadataSize(image_size)
Tao Bao71197512018-10-11 14:08:45 -070072 verity_size = verity_tree_size + verity_metadata_size
73 if fec_supported:
Tao Bao7549e5e2018-10-03 14:23:59 -070074 fec_size = GetVerityFECSize(image_size + verity_size)
Tao Bao71197512018-10-11 14:08:45 -070075 return verity_size + fec_size
76 return verity_size
77
78
79def GetSimgSize(image_file):
80 simg = sparse_img.SparseImage(image_file, build_map=False)
81 return simg.blocksize * simg.total_blocks
82
83
84def ZeroPadSimg(image_file, pad_size):
85 blocks = pad_size // BLOCK_SIZE
Tao Bao32fcdab2018-10-12 10:30:39 -070086 logger.info("Padding %d blocks (%d bytes)", blocks, pad_size)
Tao Bao71197512018-10-11 14:08:45 -070087 simg = sparse_img.SparseImage(image_file, mode="r+b", build_map=False)
88 simg.AppendFillChunk(0, blocks)
89
90
Tao Bao71197512018-10-11 14:08:45 -070091def BuildVerityFEC(sparse_image_path, verity_path, verity_fec_path,
92 padding_size):
93 cmd = ["fec", "-e", "-p", str(padding_size), sparse_image_path,
94 verity_path, verity_fec_path]
95 common.RunAndCheckOutput(cmd)
96
97
98def BuildVerityTree(sparse_image_path, verity_image_path):
99 cmd = ["build_verity_tree", "-A", FIXED_SALT, sparse_image_path,
100 verity_image_path]
101 output = common.RunAndCheckOutput(cmd)
102 root, salt = output.split()
103 return root, salt
104
105
106def BuildVerityMetadata(image_size, verity_metadata_path, root_hash, salt,
107 block_device, signer_path, key, signer_args,
108 verity_disable):
Tao Baod5ab10e2019-05-02 18:17:18 -0700109 cmd = ["build_verity_metadata", "build", str(image_size),
Tao Bao71197512018-10-11 14:08:45 -0700110 verity_metadata_path, root_hash, salt, block_device, signer_path, key]
111 if signer_args:
112 cmd.append("--signer_args=\"%s\"" % (' '.join(signer_args),))
113 if verity_disable:
114 cmd.append("--verity_disable")
115 common.RunAndCheckOutput(cmd)
116
117
118def Append2Simg(sparse_image_path, unsparse_image_path, error_message):
119 """Appends the unsparse image to the given sparse image.
120
121 Args:
122 sparse_image_path: the path to the (sparse) image
123 unsparse_image_path: the path to the (unsparse) image
124
125 Raises:
126 BuildVerityImageError: On error.
127 """
128 cmd = ["append2simg", sparse_image_path, unsparse_image_path]
129 try:
130 common.RunAndCheckOutput(cmd)
131 except:
Tao Bao46901fb2018-11-06 10:26:21 -0800132 logger.exception(error_message)
Tao Bao71197512018-10-11 14:08:45 -0700133 raise BuildVerityImageError(error_message)
134
135
136def Append(target, file_to_append, error_message):
137 """Appends file_to_append to target.
138
139 Raises:
140 BuildVerityImageError: On error.
141 """
142 try:
Tao Bao9e893c32019-06-20 16:14:55 -0700143 with open(target, 'ab') as out_file, \
144 open(file_to_append, 'rb') as input_file:
Tao Bao71197512018-10-11 14:08:45 -0700145 for line in input_file:
146 out_file.write(line)
147 except IOError:
Tao Bao46901fb2018-11-06 10:26:21 -0800148 logger.exception(error_message)
Tao Bao71197512018-10-11 14:08:45 -0700149 raise BuildVerityImageError(error_message)
150
151
Tao Bao7549e5e2018-10-03 14:23:59 -0700152def CreateVerityImageBuilder(prop_dict):
153 """Returns a verity image builder based on the given build properties.
Tao Bao71197512018-10-11 14:08:45 -0700154
155 Args:
Tao Bao7549e5e2018-10-03 14:23:59 -0700156 prop_dict: A dict that contains the build properties. In particular, it will
157 look for verity-related property values.
Tao Bao71197512018-10-11 14:08:45 -0700158
159 Returns:
Tao Bao7549e5e2018-10-03 14:23:59 -0700160 A VerityImageBuilder instance for Verified Boot 1.0 or Verified Boot 2.0; or
161 None if the given build doesn't support Verified Boot.
Tao Bao71197512018-10-11 14:08:45 -0700162 """
Tao Bao7549e5e2018-10-03 14:23:59 -0700163 partition_size = prop_dict.get("partition_size")
164 # partition_size could be None at this point, if using dynamic partitions.
165 if partition_size:
166 partition_size = int(partition_size)
Tao Bao71197512018-10-11 14:08:45 -0700167
Tao Bao7549e5e2018-10-03 14:23:59 -0700168 # Verified Boot 1.0
169 verity_supported = prop_dict.get("verity") == "true"
170 is_verity_partition = "verity_block_device" in prop_dict
171 if verity_supported and is_verity_partition:
172 if OPTIONS.verity_signer_path is not None:
173 signer_path = OPTIONS.verity_signer_path
Tao Bao71197512018-10-11 14:08:45 -0700174 else:
Tao Bao7549e5e2018-10-03 14:23:59 -0700175 signer_path = prop_dict["verity_signer_cmd"]
176 return Version1VerityImageBuilder(
177 partition_size,
178 prop_dict["verity_block_device"],
179 prop_dict.get("verity_fec") == "true",
180 signer_path,
181 prop_dict["verity_key"] + ".pk8",
182 OPTIONS.verity_signer_args,
183 "verity_disable" in prop_dict)
Tao Bao71197512018-10-11 14:08:45 -0700184
Tao Bao7549e5e2018-10-03 14:23:59 -0700185 # Verified Boot 2.0
186 if (prop_dict.get("avb_hash_enable") == "true" or
187 prop_dict.get("avb_hashtree_enable") == "true"):
188 # key_path and algorithm are only available when chain partition is used.
189 key_path = prop_dict.get("avb_key_path")
190 algorithm = prop_dict.get("avb_algorithm")
Tao Bao9e893c32019-06-20 16:14:55 -0700191
192 # Image uses hash footer.
Tao Bao7549e5e2018-10-03 14:23:59 -0700193 if prop_dict.get("avb_hash_enable") == "true":
194 return VerifiedBootVersion2VerityImageBuilder(
195 prop_dict["partition_name"],
196 partition_size,
197 VerifiedBootVersion2VerityImageBuilder.AVB_HASH_FOOTER,
198 prop_dict["avb_avbtool"],
199 key_path,
200 algorithm,
201 prop_dict.get("avb_salt"),
202 prop_dict["avb_add_hash_footer_args"])
Tao Bao9e893c32019-06-20 16:14:55 -0700203
204 # Image uses hashtree footer.
205 return VerifiedBootVersion2VerityImageBuilder(
206 prop_dict["partition_name"],
207 partition_size,
208 VerifiedBootVersion2VerityImageBuilder.AVB_HASHTREE_FOOTER,
209 prop_dict["avb_avbtool"],
210 key_path,
211 algorithm,
212 prop_dict.get("avb_salt"),
213 prop_dict["avb_add_hashtree_footer_args"])
Tao Bao71197512018-10-11 14:08:45 -0700214
Tao Bao7549e5e2018-10-03 14:23:59 -0700215 return None
Tao Bao71197512018-10-11 14:08:45 -0700216
217
Tao Bao7549e5e2018-10-03 14:23:59 -0700218class VerityImageBuilder(object):
219 """A builder that generates an image with verity metadata for Verified Boot.
Tao Bao71197512018-10-11 14:08:45 -0700220
Tao Bao7549e5e2018-10-03 14:23:59 -0700221 A VerityImageBuilder instance handles the works for building an image with
222 verity metadata for supporting Android Verified Boot. This class defines the
223 common interface between Verified Boot 1.0 and Verified Boot 2.0. A matching
224 builder will be returned based on the given build properties.
225
226 More info on the verity image generation can be found at the following link.
227 https://source.android.com/security/verifiedboot/dm-verity#implementation
Tao Bao71197512018-10-11 14:08:45 -0700228 """
Tao Bao71197512018-10-11 14:08:45 -0700229
Tao Bao7549e5e2018-10-03 14:23:59 -0700230 def CalculateMaxImageSize(self, partition_size):
231 """Calculates the filesystem image size for the given partition size."""
232 raise NotImplementedError
Tao Bao71197512018-10-11 14:08:45 -0700233
Tao Bao7549e5e2018-10-03 14:23:59 -0700234 def CalculateDynamicPartitionSize(self, image_size):
235 """Calculates and sets the partition size for a dynamic partition."""
236 raise NotImplementedError
Tao Bao71197512018-10-11 14:08:45 -0700237
Tao Bao7549e5e2018-10-03 14:23:59 -0700238 def PadSparseImage(self, out_file):
239 """Adds padding to the generated sparse image."""
240 raise NotImplementedError
241
242 def Build(self, out_file):
243 """Builds the verity image and writes it to the given file."""
244 raise NotImplementedError
245
246
247class Version1VerityImageBuilder(VerityImageBuilder):
248 """A VerityImageBuilder for Verified Boot 1.0."""
249
250 def __init__(self, partition_size, block_dev, fec_supported, signer_path,
251 signer_key, signer_args, verity_disable):
252 self.version = 1
253 self.partition_size = partition_size
254 self.block_device = block_dev
255 self.fec_supported = fec_supported
256 self.signer_path = signer_path
257 self.signer_key = signer_key
258 self.signer_args = signer_args
259 self.verity_disable = verity_disable
260 self.image_size = None
261 self.verity_size = None
262
263 def CalculateDynamicPartitionSize(self, image_size):
264 # This needs to be implemented. Note that returning the given image size as
265 # the partition size doesn't make sense, as it will fail later.
266 raise NotImplementedError
267
268 def CalculateMaxImageSize(self, partition_size=None):
269 """Calculates the max image size by accounting for the verity metadata.
270
271 Args:
272 partition_size: The partition size, which defaults to self.partition_size
273 if unspecified.
274
275 Returns:
276 The size of the image adjusted for verity metadata.
277 """
278 if partition_size is None:
279 partition_size = self.partition_size
280 assert partition_size > 0, \
281 "Invalid partition size: {}".format(partition_size)
282
283 hi = partition_size
284 if hi % BLOCK_SIZE != 0:
285 hi = (hi // BLOCK_SIZE) * BLOCK_SIZE
286
287 # verity tree and fec sizes depend on the partition size, which
288 # means this estimate is always going to be unnecessarily small
289 verity_size = GetVeritySize(hi, self.fec_supported)
290 lo = partition_size - verity_size
291 result = lo
292
293 # do a binary search for the optimal size
294 while lo < hi:
295 i = ((lo + hi) // (2 * BLOCK_SIZE)) * BLOCK_SIZE
296 v = GetVeritySize(i, self.fec_supported)
297 if i + v <= partition_size:
298 if result < i:
299 result = i
300 verity_size = v
301 lo = i + BLOCK_SIZE
302 else:
303 hi = i
304
305 self.image_size = result
306 self.verity_size = verity_size
307
308 logger.info(
309 "Calculated image size for verity: partition_size %d, image_size %d, "
310 "verity_size %d", partition_size, result, verity_size)
311 return result
312
313 def Build(self, out_file):
314 """Creates an image that is verifiable using dm-verity.
315
316 Args:
Tao Bao4a0d5132018-10-17 22:53:54 -0700317 out_file: the output image.
Tao Bao7549e5e2018-10-03 14:23:59 -0700318
319 Returns:
320 AssertionError: On invalid partition sizes.
321 BuildVerityImageError: On other errors.
322 """
323 image_size = int(self.image_size)
324 tempdir_name = common.MakeTempDir(suffix="_verity_images")
325
326 # Get partial image paths.
327 verity_image_path = os.path.join(tempdir_name, "verity.img")
328 verity_metadata_path = os.path.join(tempdir_name, "verity_metadata.img")
329
330 # Build the verity tree and get the root hash and salt.
331 root_hash, salt = BuildVerityTree(out_file, verity_image_path)
332
333 # Build the metadata blocks.
334 BuildVerityMetadata(
335 image_size, verity_metadata_path, root_hash, salt, self.block_device,
336 self.signer_path, self.signer_key, self.signer_args,
337 self.verity_disable)
338
339 padding_size = self.partition_size - self.image_size - self.verity_size
340 assert padding_size >= 0
341
342 # Build the full verified image.
343 Append(
344 verity_image_path, verity_metadata_path,
345 "Failed to append verity metadata")
346
347 if self.fec_supported:
348 # Build FEC for the entire partition, including metadata.
349 verity_fec_path = os.path.join(tempdir_name, "verity_fec.img")
350 BuildVerityFEC(
351 out_file, verity_image_path, verity_fec_path, padding_size)
352 Append(verity_image_path, verity_fec_path, "Failed to append FEC")
353
354 Append2Simg(
355 out_file, verity_image_path, "Failed to append verity data")
356
357 def PadSparseImage(self, out_file):
358 sparse_image_size = GetSimgSize(out_file)
359 if sparse_image_size > self.image_size:
360 raise BuildVerityImageError(
361 "Error: image size of {} is larger than partition size of "
362 "{}".format(sparse_image_size, self.image_size))
363 ZeroPadSimg(out_file, self.image_size - sparse_image_size)
364
365
366class VerifiedBootVersion2VerityImageBuilder(VerityImageBuilder):
367 """A VerityImageBuilder for Verified Boot 2.0."""
368
369 AVB_HASH_FOOTER = 1
370 AVB_HASHTREE_FOOTER = 2
371
372 def __init__(self, partition_name, partition_size, footer_type, avbtool,
373 key_path, algorithm, salt, signing_args):
374 self.version = 2
375 self.partition_name = partition_name
376 self.partition_size = partition_size
377 self.footer_type = footer_type
378 self.avbtool = avbtool
379 self.algorithm = algorithm
380 self.key_path = key_path
381 self.salt = salt
382 self.signing_args = signing_args
383 self.image_size = None
384
385 def CalculateMinPartitionSize(self, image_size, size_calculator=None):
386 """Calculates min partition size for a given image size.
387
388 This is used when determining the partition size for a dynamic partition,
389 which should be cover the given image size (for filesystem files) as well as
390 the verity metadata size.
391
392 Args:
393 image_size: The size of the image in question.
394 size_calculator: The function to calculate max image size
395 for a given partition size.
396
397 Returns:
398 The minimum partition size required to accommodate the image size.
399 """
400 if size_calculator is None:
401 size_calculator = self.CalculateMaxImageSize
402
403 # Use image size as partition size to approximate final partition size.
404 image_ratio = size_calculator(image_size) / float(image_size)
405
406 # Prepare a binary search for the optimal partition size.
407 lo = int(image_size / image_ratio) // BLOCK_SIZE * BLOCK_SIZE - BLOCK_SIZE
408
409 # Ensure lo is small enough: max_image_size should <= image_size.
410 delta = BLOCK_SIZE
411 max_image_size = size_calculator(lo)
412 while max_image_size > image_size:
413 image_ratio = max_image_size / float(lo)
414 lo = int(image_size / image_ratio) // BLOCK_SIZE * BLOCK_SIZE - delta
415 delta *= 2
416 max_image_size = size_calculator(lo)
417
418 hi = lo + BLOCK_SIZE
419
420 # Ensure hi is large enough: max_image_size should >= image_size.
421 delta = BLOCK_SIZE
422 max_image_size = size_calculator(hi)
423 while max_image_size < image_size:
424 image_ratio = max_image_size / float(hi)
425 hi = int(image_size / image_ratio) // BLOCK_SIZE * BLOCK_SIZE + delta
426 delta *= 2
427 max_image_size = size_calculator(hi)
428
429 partition_size = hi
430
431 # Start to binary search.
432 while lo < hi:
433 mid = ((lo + hi) // (2 * BLOCK_SIZE)) * BLOCK_SIZE
434 max_image_size = size_calculator(mid)
435 if max_image_size >= image_size: # if mid can accommodate image_size
436 if mid < partition_size: # if a smaller partition size is found
437 partition_size = mid
438 hi = mid
439 else:
440 lo = mid + BLOCK_SIZE
441
442 logger.info(
443 "CalculateMinPartitionSize(%d): partition_size %d.", image_size,
444 partition_size)
445
446 return partition_size
447
448 def CalculateDynamicPartitionSize(self, image_size):
449 self.partition_size = self.CalculateMinPartitionSize(image_size)
450 return self.partition_size
451
452 def CalculateMaxImageSize(self, partition_size=None):
453 """Calculates max image size for a given partition size.
454
455 Args:
456 partition_size: The partition size, which defaults to self.partition_size
457 if unspecified.
458
459 Returns:
460 The maximum image size.
461
462 Raises:
463 BuildVerityImageError: On error or getting invalid image size.
464 """
465 if partition_size is None:
466 partition_size = self.partition_size
467 assert partition_size > 0, \
468 "Invalid partition size: {}".format(partition_size)
469
470 add_footer = ("add_hash_footer" if self.footer_type == self.AVB_HASH_FOOTER
471 else "add_hashtree_footer")
472 cmd = [self.avbtool, add_footer, "--partition_size",
473 str(partition_size), "--calc_max_image_size"]
474 cmd.extend(shlex.split(self.signing_args))
475
476 proc = common.Run(cmd)
477 output, _ = proc.communicate()
478 if proc.returncode != 0:
479 raise BuildVerityImageError(
480 "Failed to calculate max image size:\n{}".format(output))
481 image_size = int(output)
482 if image_size <= 0:
483 raise BuildVerityImageError(
484 "Invalid max image size: {}".format(output))
485 self.image_size = image_size
486 return image_size
487
488 def PadSparseImage(self, out_file):
489 # No-op as the padding is taken care of by avbtool.
490 pass
491
492 def Build(self, out_file):
493 """Adds dm-verity hashtree and AVB metadata to an image.
494
495 Args:
496 out_file: Path to image to modify.
497 """
498 add_footer = ("add_hash_footer" if self.footer_type == self.AVB_HASH_FOOTER
499 else "add_hashtree_footer")
500 cmd = [self.avbtool, add_footer,
501 "--partition_size", str(self.partition_size),
502 "--partition_name", self.partition_name,
503 "--image", out_file]
504 if self.key_path and self.algorithm:
505 cmd.extend(["--key", self.key_path, "--algorithm", self.algorithm])
506 if self.salt:
507 cmd.extend(["--salt", self.salt])
508 cmd.extend(shlex.split(self.signing_args))
509
510 proc = common.Run(cmd)
511 output, _ = proc.communicate()
512 if proc.returncode != 0:
513 raise BuildVerityImageError("Failed to add AVB footer: {}".format(output))
Tao Bao71197512018-10-11 14:08:45 -0700514
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700515
516class HashtreeInfoGenerationError(Exception):
517 """An Exception raised during hashtree info generation."""
518
519 def __init__(self, message):
520 Exception.__init__(self, message)
521
522
523class HashtreeInfo(object):
524 def __init__(self):
525 self.hashtree_range = None
526 self.filesystem_range = None
527 self.hash_algorithm = None
528 self.salt = None
529 self.root_hash = None
530
531
532def CreateHashtreeInfoGenerator(partition_name, block_size, info_dict):
533 generator = None
534 if (info_dict.get("verity") == "true" and
535 info_dict.get("{}_verity_block_device".format(partition_name))):
536 partition_size = info_dict["{}_size".format(partition_name)]
537 fec_supported = info_dict.get("verity_fec") == "true"
538 generator = VerifiedBootVersion1HashtreeInfoGenerator(
539 partition_size, block_size, fec_supported)
540
541 return generator
542
543
544class HashtreeInfoGenerator(object):
545 def Generate(self, image):
546 raise NotImplementedError
547
548 def DecomposeSparseImage(self, image):
549 raise NotImplementedError
550
551 def ValidateHashtree(self):
552 raise NotImplementedError
553
554
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700555class VerifiedBootVersion1HashtreeInfoGenerator(HashtreeInfoGenerator):
556 """A class that parses the metadata of hashtree for a given partition."""
557
558 def __init__(self, partition_size, block_size, fec_supported):
559 """Initialize VerityTreeInfo with the sparse image and input property.
560
561 Arguments:
562 partition_size: The whole size in bytes of a partition, including the
Tao Bao7549e5e2018-10-03 14:23:59 -0700563 filesystem size, padding size, and verity size.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700564 block_size: Expected size in bytes of each block for the sparse image.
565 fec_supported: True if the verity section contains fec data.
566 """
567
568 self.block_size = block_size
569 self.partition_size = partition_size
570 self.fec_supported = fec_supported
571
572 self.image = None
573 self.filesystem_size = None
574 self.hashtree_size = None
575 self.metadata_size = None
576
Tao Bao7549e5e2018-10-03 14:23:59 -0700577 prop_dict = {
578 'partition_size': str(partition_size),
579 'verity': 'true',
580 'verity_fec': 'true' if fec_supported else None,
581 # 'verity_block_device' needs to be present to indicate a verity-enabled
582 # partition.
583 'verity_block_device': '',
584 # We don't need the following properties that are needed for signing the
585 # verity metadata.
586 'verity_key': '',
587 'verity_signer_cmd': None,
588 }
589 self.verity_image_builder = CreateVerityImageBuilder(prop_dict)
590
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700591 self.hashtree_info = HashtreeInfo()
592
593 def DecomposeSparseImage(self, image):
594 """Calculate the verity size based on the size of the input image.
595
596 Since we already know the structure of a verity enabled image to be:
597 [filesystem, verity_hashtree, verity_metadata, fec_data]. We can then
598 calculate the size and offset of each section.
599 """
600
601 self.image = image
602 assert self.block_size == image.blocksize
603 assert self.partition_size == image.total_blocks * self.block_size, \
604 "partition size {} doesn't match with the calculated image size." \
605 " total_blocks: {}".format(self.partition_size, image.total_blocks)
606
Tao Bao7549e5e2018-10-03 14:23:59 -0700607 adjusted_size = self.verity_image_builder.CalculateMaxImageSize()
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700608 assert adjusted_size % self.block_size == 0
609
610 verity_tree_size = GetVerityTreeSize(adjusted_size)
611 assert verity_tree_size % self.block_size == 0
612
613 metadata_size = GetVerityMetadataSize(adjusted_size)
614 assert metadata_size % self.block_size == 0
615
616 self.filesystem_size = adjusted_size
617 self.hashtree_size = verity_tree_size
618 self.metadata_size = metadata_size
619
620 self.hashtree_info.filesystem_range = RangeSet(
Tao Bao9e893c32019-06-20 16:14:55 -0700621 data=[0, adjusted_size // self.block_size])
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700622 self.hashtree_info.hashtree_range = RangeSet(
Tao Bao9e893c32019-06-20 16:14:55 -0700623 data=[adjusted_size // self.block_size,
624 (adjusted_size + verity_tree_size) // self.block_size])
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700625
626 def _ParseHashtreeMetadata(self):
627 """Parses the hash_algorithm, root_hash, salt from the metadata block."""
628
629 metadata_start = self.filesystem_size + self.hashtree_size
630 metadata_range = RangeSet(
Tao Bao9e893c32019-06-20 16:14:55 -0700631 data=[metadata_start // self.block_size,
632 (metadata_start + self.metadata_size) // self.block_size])
633 meta_data = b''.join(self.image.ReadRangeSet(metadata_range))
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700634
635 # More info about the metadata structure available in:
636 # system/extras/verity/build_verity_metadata.py
637 META_HEADER_SIZE = 268
638 header_bin = meta_data[0:META_HEADER_SIZE]
639 header = struct.unpack("II256sI", header_bin)
640
641 # header: magic_number, version, signature, table_len
642 assert header[0] == 0xb001b001, header[0]
643 table_len = header[3]
644 verity_table = meta_data[META_HEADER_SIZE: META_HEADER_SIZE + table_len]
645 table_entries = verity_table.rstrip().split()
646
647 # Expected verity table format: "1 block_device block_device block_size
648 # block_size data_blocks data_blocks hash_algorithm root_hash salt"
649 assert len(table_entries) == 10, "Unexpected verity table size {}".format(
650 len(table_entries))
651 assert (int(table_entries[3]) == self.block_size and
652 int(table_entries[4]) == self.block_size)
653 assert (int(table_entries[5]) * self.block_size == self.filesystem_size and
654 int(table_entries[6]) * self.block_size == self.filesystem_size)
655
Tao Bao9e893c32019-06-20 16:14:55 -0700656 self.hashtree_info.hash_algorithm = table_entries[7].decode()
657 self.hashtree_info.root_hash = table_entries[8].decode()
658 self.hashtree_info.salt = table_entries[9].decode()
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700659
660 def ValidateHashtree(self):
661 """Checks that we can reconstruct the verity hash tree."""
662
Tao Bao7549e5e2018-10-03 14:23:59 -0700663 # Writes the filesystem section to a temp file; and calls the executable
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700664 # build_verity_tree to construct the hash tree.
665 adjusted_partition = common.MakeTempFile(prefix="adjusted_partition")
666 with open(adjusted_partition, "wb") as fd:
667 self.image.WriteRangeDataToFd(self.hashtree_info.filesystem_range, fd)
668
669 generated_verity_tree = common.MakeTempFile(prefix="verity")
Tao Bao2f057462018-10-03 16:31:18 -0700670 root_hash, salt = BuildVerityTree(adjusted_partition, generated_verity_tree)
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700671
Tao Bao2f057462018-10-03 16:31:18 -0700672 # The salt should be always identical, as we use fixed value.
673 assert salt == self.hashtree_info.salt, \
674 "Calculated salt {} doesn't match the one in metadata {}".format(
675 salt, self.hashtree_info.salt)
676
677 if root_hash != self.hashtree_info.root_hash:
Tao Bao32fcdab2018-10-12 10:30:39 -0700678 logger.warning(
679 "Calculated root hash %s doesn't match the one in metadata %s",
680 root_hash, self.hashtree_info.root_hash)
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700681 return False
682
683 # Reads the generated hash tree and checks if it has the exact same bytes
684 # as the one in the sparse image.
Tao Bao9e893c32019-06-20 16:14:55 -0700685 with open(generated_verity_tree, 'rb') as fd:
686 return fd.read() == b''.join(self.image.ReadRangeSet(
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700687 self.hashtree_info.hashtree_range))
688
689 def Generate(self, image):
690 """Parses and validates the hashtree info in a sparse image.
691
692 Returns:
693 hashtree_info: The information needed to reconstruct the hashtree.
Tao Bao2f057462018-10-03 16:31:18 -0700694
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700695 Raises:
696 HashtreeInfoGenerationError: If we fail to generate the exact bytes of
697 the hashtree.
698 """
699
700 self.DecomposeSparseImage(image)
701 self._ParseHashtreeMetadata()
702
703 if not self.ValidateHashtree():
704 raise HashtreeInfoGenerationError("Failed to reconstruct the verity tree")
705
706 return self.hashtree_info
Hongguang Chenf23364d2020-04-27 18:36:36 -0700707
708
709def CreateCustomImageBuilder(info_dict, partition_name, partition_size,
710 key_path, algorithm, signing_args):
711 builder = None
712 if info_dict.get("avb_enable") == "true":
713 builder = VerifiedBootVersion2VerityImageBuilder(
714 partition_name,
715 partition_size,
716 VerifiedBootVersion2VerityImageBuilder.AVB_HASHTREE_FOOTER,
717 info_dict.get("avb_avbtool"),
718 key_path,
719 algorithm,
720 # Salt is None because custom images have no fingerprint property to be
721 # used as the salt.
722 None,
723 signing_args)
724
725 return builder
Jiyong Parkb92b8f42021-03-15 23:13:42 +0900726
727
728def GetDiskUsage(path):
729 """Returns the number of bytes that "path" occupies on host.
730
731 Args:
732 path: The directory or file to calculate size on.
733
734 Returns:
735 The number of bytes based on a 1K block_size.
736 """
737 cmd = ["du", "-b", "-k", "-s", path]
738 output = common.RunAndCheckOutput(cmd, verbose=False)
739 return int(output.split()[0]) * 1024
740
741
742def main(argv):
743 if len(argv) != 2:
744 print(__doc__)
745 sys.exit(1)
746
747 common.InitLogging()
748
749 dict_file = argv[0]
750 out_file = argv[1]
751
752 prop_dict = {}
753 with open(dict_file, 'r') as f:
754 for line in f:
755 line = line.strip()
756 if not line or line.startswith("#"):
757 continue
758 k, v = line.split("=", 1)
759 prop_dict[k] = v
760
761 builder = CreateVerityImageBuilder(prop_dict)
762
763 if "partition_size" not in prop_dict:
764 image_size = GetDiskUsage(out_file)
765 # make sure that the image is big enough to hold vbmeta and footer
766 image_size = image_size + (MAX_VBMETA_SIZE + MAX_FOOTER_SIZE)
767 size = builder.CalculateDynamicPartitionSize(image_size)
768 prop_dict["partition_size"] = size
769
770 builder.Build(out_file)
771
772
773if __name__ == '__main__':
774 try:
775 main(sys.argv[1:])
776 finally:
777 common.Cleanup()