1#!/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
17from __future__ import print_function
18
19import logging
20import os.path
21import shlex
22import struct
23
24import common
25import sparse_img
26from rangelib import RangeSet
27
28logger = logging.getLogger(__name__)
29
30OPTIONS = common.OPTIONS
31BLOCK_SIZE = common.BLOCK_SIZE
32FIXED_SALT = "aee087a5be3b982978c923f566a94613496b417f2af592639bc80d141e34dfe7"
33
34
35class BuildVerityImageError(Exception):
36  """An Exception raised during verity image building."""
37
38  def __init__(self, message):
39    Exception.__init__(self, message)
40
41
42def GetVerityFECSize(image_size):
43  cmd = ["fec", "-s", str(image_size)]
44  output = common.RunAndCheckOutput(cmd, verbose=False)
45  return int(output)
46
47
48def GetVerityTreeSize(image_size):
49  cmd = ["build_verity_tree", "-s", str(image_size)]
50  output = common.RunAndCheckOutput(cmd, verbose=False)
51  return int(output)
52
53
54def GetVerityMetadataSize(image_size):
55  cmd = ["build_verity_metadata", "size", str(image_size)]
56  output = common.RunAndCheckOutput(cmd, verbose=False)
57  return int(output)
58
59
60def GetVeritySize(image_size, fec_supported):
61  verity_tree_size = GetVerityTreeSize(image_size)
62  verity_metadata_size = GetVerityMetadataSize(image_size)
63  verity_size = verity_tree_size + verity_metadata_size
64  if fec_supported:
65    fec_size = GetVerityFECSize(image_size + verity_size)
66    return verity_size + fec_size
67  return verity_size
68
69
70def GetSimgSize(image_file):
71  simg = sparse_img.SparseImage(image_file, build_map=False)
72  return simg.blocksize * simg.total_blocks
73
74
75def ZeroPadSimg(image_file, pad_size):
76  blocks = pad_size // BLOCK_SIZE
77  logger.info("Padding %d blocks (%d bytes)", blocks, pad_size)
78  simg = sparse_img.SparseImage(image_file, mode="r+b", build_map=False)
79  simg.AppendFillChunk(0, blocks)
80
81
82def BuildVerityFEC(sparse_image_path, verity_path, verity_fec_path,
83                   padding_size):
84  cmd = ["fec", "-e", "-p", str(padding_size), sparse_image_path,
85         verity_path, verity_fec_path]
86  common.RunAndCheckOutput(cmd)
87
88
89def BuildVerityTree(sparse_image_path, verity_image_path):
90  cmd = ["build_verity_tree", "-A", FIXED_SALT, sparse_image_path,
91         verity_image_path]
92  output = common.RunAndCheckOutput(cmd)
93  root, salt = output.split()
94  return root, salt
95
96
97def BuildVerityMetadata(image_size, verity_metadata_path, root_hash, salt,
98                        block_device, signer_path, key, signer_args,
99                        verity_disable):
100  cmd = ["build_verity_metadata", "build", str(image_size),
101         verity_metadata_path, root_hash, salt, block_device, signer_path, key]
102  if signer_args:
103    cmd.append("--signer_args=\"%s\"" % (' '.join(signer_args),))
104  if verity_disable:
105    cmd.append("--verity_disable")
106  common.RunAndCheckOutput(cmd)
107
108
109def Append2Simg(sparse_image_path, unsparse_image_path, error_message):
110  """Appends the unsparse image to the given sparse image.
111
112  Args:
113    sparse_image_path: the path to the (sparse) image
114    unsparse_image_path: the path to the (unsparse) image
115
116  Raises:
117    BuildVerityImageError: On error.
118  """
119  cmd = ["append2simg", sparse_image_path, unsparse_image_path]
120  try:
121    common.RunAndCheckOutput(cmd)
122  except:
123    logger.exception(error_message)
124    raise BuildVerityImageError(error_message)
125
126
127def Append(target, file_to_append, error_message):
128  """Appends file_to_append to target.
129
130  Raises:
131    BuildVerityImageError: On error.
132  """
133  try:
134    with open(target, 'ab') as out_file, \
135        open(file_to_append, 'rb') as input_file:
136      for line in input_file:
137        out_file.write(line)
138  except IOError:
139    logger.exception(error_message)
140    raise BuildVerityImageError(error_message)
141
142
143def CreateVerityImageBuilder(prop_dict):
144  """Returns a verity image builder based on the given build properties.
145
146  Args:
147    prop_dict: A dict that contains the build properties. In particular, it will
148        look for verity-related property values.
149
150  Returns:
151    A VerityImageBuilder instance for Verified Boot 1.0 or Verified Boot 2.0; or
152        None if the given build doesn't support Verified Boot.
153  """
154  partition_size = prop_dict.get("partition_size")
155  # partition_size could be None at this point, if using dynamic partitions.
156  if partition_size:
157    partition_size = int(partition_size)
158
159  # Verified Boot 1.0
160  verity_supported = prop_dict.get("verity") == "true"
161  is_verity_partition = "verity_block_device" in prop_dict
162  if verity_supported and is_verity_partition:
163    if OPTIONS.verity_signer_path is not None:
164      signer_path = OPTIONS.verity_signer_path
165    else:
166      signer_path = prop_dict["verity_signer_cmd"]
167    return Version1VerityImageBuilder(
168        partition_size,
169        prop_dict["verity_block_device"],
170        prop_dict.get("verity_fec") == "true",
171        signer_path,
172        prop_dict["verity_key"] + ".pk8",
173        OPTIONS.verity_signer_args,
174        "verity_disable" in prop_dict)
175
176  # Verified Boot 2.0
177  if (prop_dict.get("avb_hash_enable") == "true" or
178      prop_dict.get("avb_hashtree_enable") == "true"):
179    # key_path and algorithm are only available when chain partition is used.
180    key_path = prop_dict.get("avb_key_path")
181    algorithm = prop_dict.get("avb_algorithm")
182
183    # Image uses hash footer.
184    if prop_dict.get("avb_hash_enable") == "true":
185      return VerifiedBootVersion2VerityImageBuilder(
186          prop_dict["partition_name"],
187          partition_size,
188          VerifiedBootVersion2VerityImageBuilder.AVB_HASH_FOOTER,
189          prop_dict["avb_avbtool"],
190          key_path,
191          algorithm,
192          prop_dict.get("avb_salt"),
193          prop_dict["avb_add_hash_footer_args"])
194
195    # Image uses hashtree footer.
196    return VerifiedBootVersion2VerityImageBuilder(
197        prop_dict["partition_name"],
198        partition_size,
199        VerifiedBootVersion2VerityImageBuilder.AVB_HASHTREE_FOOTER,
200        prop_dict["avb_avbtool"],
201        key_path,
202        algorithm,
203        prop_dict.get("avb_salt"),
204        prop_dict["avb_add_hashtree_footer_args"])
205
206  return None
207
208
209class VerityImageBuilder(object):
210  """A builder that generates an image with verity metadata for Verified Boot.
211
212  A VerityImageBuilder instance handles the works for building an image with
213  verity metadata for supporting Android Verified Boot. This class defines the
214  common interface between Verified Boot 1.0 and Verified Boot 2.0. A matching
215  builder will be returned based on the given build properties.
216
217  More info on the verity image generation can be found at the following link.
218  https://source.android.com/security/verifiedboot/dm-verity#implementation
219  """
220
221  def CalculateMaxImageSize(self, partition_size):
222    """Calculates the filesystem image size for the given partition size."""
223    raise NotImplementedError
224
225  def CalculateDynamicPartitionSize(self, image_size):
226    """Calculates and sets the partition size for a dynamic partition."""
227    raise NotImplementedError
228
229  def PadSparseImage(self, out_file):
230    """Adds padding to the generated sparse image."""
231    raise NotImplementedError
232
233  def Build(self, out_file):
234    """Builds the verity image and writes it to the given file."""
235    raise NotImplementedError
236
237
238class Version1VerityImageBuilder(VerityImageBuilder):
239  """A VerityImageBuilder for Verified Boot 1.0."""
240
241  def __init__(self, partition_size, block_dev, fec_supported, signer_path,
242               signer_key, signer_args, verity_disable):
243    self.version = 1
244    self.partition_size = partition_size
245    self.block_device = block_dev
246    self.fec_supported = fec_supported
247    self.signer_path = signer_path
248    self.signer_key = signer_key
249    self.signer_args = signer_args
250    self.verity_disable = verity_disable
251    self.image_size = None
252    self.verity_size = None
253
254  def CalculateDynamicPartitionSize(self, image_size):
255    # This needs to be implemented. Note that returning the given image size as
256    # the partition size doesn't make sense, as it will fail later.
257    raise NotImplementedError
258
259  def CalculateMaxImageSize(self, partition_size=None):
260    """Calculates the max image size by accounting for the verity metadata.
261
262    Args:
263      partition_size: The partition size, which defaults to self.partition_size
264          if unspecified.
265
266    Returns:
267      The size of the image adjusted for verity metadata.
268    """
269    if partition_size is None:
270      partition_size = self.partition_size
271    assert partition_size > 0, \
272        "Invalid partition size: {}".format(partition_size)
273
274    hi = partition_size
275    if hi % BLOCK_SIZE != 0:
276      hi = (hi // BLOCK_SIZE) * BLOCK_SIZE
277
278    # verity tree and fec sizes depend on the partition size, which
279    # means this estimate is always going to be unnecessarily small
280    verity_size = GetVeritySize(hi, self.fec_supported)
281    lo = partition_size - verity_size
282    result = lo
283
284    # do a binary search for the optimal size
285    while lo < hi:
286      i = ((lo + hi) // (2 * BLOCK_SIZE)) * BLOCK_SIZE
287      v = GetVeritySize(i, self.fec_supported)
288      if i + v <= partition_size:
289        if result < i:
290          result = i
291          verity_size = v
292        lo = i + BLOCK_SIZE
293      else:
294        hi = i
295
296    self.image_size = result
297    self.verity_size = verity_size
298
299    logger.info(
300        "Calculated image size for verity: partition_size %d, image_size %d, "
301        "verity_size %d", partition_size, result, verity_size)
302    return result
303
304  def Build(self, out_file):
305    """Creates an image that is verifiable using dm-verity.
306
307    Args:
308      out_file: the output image.
309
310    Returns:
311      AssertionError: On invalid partition sizes.
312      BuildVerityImageError: On other errors.
313    """
314    image_size = int(self.image_size)
315    tempdir_name = common.MakeTempDir(suffix="_verity_images")
316
317    # Get partial image paths.
318    verity_image_path = os.path.join(tempdir_name, "verity.img")
319    verity_metadata_path = os.path.join(tempdir_name, "verity_metadata.img")
320
321    # Build the verity tree and get the root hash and salt.
322    root_hash, salt = BuildVerityTree(out_file, verity_image_path)
323
324    # Build the metadata blocks.
325    BuildVerityMetadata(
326        image_size, verity_metadata_path, root_hash, salt, self.block_device,
327        self.signer_path, self.signer_key, self.signer_args,
328        self.verity_disable)
329
330    padding_size = self.partition_size - self.image_size - self.verity_size
331    assert padding_size >= 0
332
333    # Build the full verified image.
334    Append(
335        verity_image_path, verity_metadata_path,
336        "Failed to append verity metadata")
337
338    if self.fec_supported:
339      # Build FEC for the entire partition, including metadata.
340      verity_fec_path = os.path.join(tempdir_name, "verity_fec.img")
341      BuildVerityFEC(
342          out_file, verity_image_path, verity_fec_path, padding_size)
343      Append(verity_image_path, verity_fec_path, "Failed to append FEC")
344
345    Append2Simg(
346        out_file, verity_image_path, "Failed to append verity data")
347
348  def PadSparseImage(self, out_file):
349    sparse_image_size = GetSimgSize(out_file)
350    if sparse_image_size > self.image_size:
351      raise BuildVerityImageError(
352          "Error: image size of {} is larger than partition size of "
353          "{}".format(sparse_image_size, self.image_size))
354    ZeroPadSimg(out_file, self.image_size - sparse_image_size)
355
356
357class VerifiedBootVersion2VerityImageBuilder(VerityImageBuilder):
358  """A VerityImageBuilder for Verified Boot 2.0."""
359
360  AVB_HASH_FOOTER = 1
361  AVB_HASHTREE_FOOTER = 2
362
363  def __init__(self, partition_name, partition_size, footer_type, avbtool,
364               key_path, algorithm, salt, signing_args):
365    self.version = 2
366    self.partition_name = partition_name
367    self.partition_size = partition_size
368    self.footer_type = footer_type
369    self.avbtool = avbtool
370    self.algorithm = algorithm
371    self.key_path = key_path
372    self.salt = salt
373    self.signing_args = signing_args
374    self.image_size = None
375
376  def CalculateMinPartitionSize(self, image_size, size_calculator=None):
377    """Calculates min partition size for a given image size.
378
379    This is used when determining the partition size for a dynamic partition,
380    which should be cover the given image size (for filesystem files) as well as
381    the verity metadata size.
382
383    Args:
384      image_size: The size of the image in question.
385      size_calculator: The function to calculate max image size
386          for a given partition size.
387
388    Returns:
389      The minimum partition size required to accommodate the image size.
390    """
391    if size_calculator is None:
392      size_calculator = self.CalculateMaxImageSize
393
394    # Use image size as partition size to approximate final partition size.
395    image_ratio = size_calculator(image_size) / float(image_size)
396
397    # Prepare a binary search for the optimal partition size.
398    lo = int(image_size / image_ratio) // BLOCK_SIZE * BLOCK_SIZE - BLOCK_SIZE
399
400    # Ensure lo is small enough: max_image_size should <= image_size.
401    delta = BLOCK_SIZE
402    max_image_size = size_calculator(lo)
403    while max_image_size > image_size:
404      image_ratio = max_image_size / float(lo)
405      lo = int(image_size / image_ratio) // BLOCK_SIZE * BLOCK_SIZE - delta
406      delta *= 2
407      max_image_size = size_calculator(lo)
408
409    hi = lo + BLOCK_SIZE
410
411    # Ensure hi is large enough: max_image_size should >= image_size.
412    delta = BLOCK_SIZE
413    max_image_size = size_calculator(hi)
414    while max_image_size < image_size:
415      image_ratio = max_image_size / float(hi)
416      hi = int(image_size / image_ratio) // BLOCK_SIZE * BLOCK_SIZE + delta
417      delta *= 2
418      max_image_size = size_calculator(hi)
419
420    partition_size = hi
421
422    # Start to binary search.
423    while lo < hi:
424      mid = ((lo + hi) // (2 * BLOCK_SIZE)) * BLOCK_SIZE
425      max_image_size = size_calculator(mid)
426      if max_image_size >= image_size:  # if mid can accommodate image_size
427        if mid < partition_size:  # if a smaller partition size is found
428          partition_size = mid
429        hi = mid
430      else:
431        lo = mid + BLOCK_SIZE
432
433    logger.info(
434        "CalculateMinPartitionSize(%d): partition_size %d.", image_size,
435        partition_size)
436
437    return partition_size
438
439  def CalculateDynamicPartitionSize(self, image_size):
440    self.partition_size = self.CalculateMinPartitionSize(image_size)
441    return self.partition_size
442
443  def CalculateMaxImageSize(self, partition_size=None):
444    """Calculates max image size for a given partition size.
445
446    Args:
447      partition_size: The partition size, which defaults to self.partition_size
448          if unspecified.
449
450    Returns:
451      The maximum image size.
452
453    Raises:
454      BuildVerityImageError: On error or getting invalid image size.
455    """
456    if partition_size is None:
457      partition_size = self.partition_size
458    assert partition_size > 0, \
459        "Invalid partition size: {}".format(partition_size)
460
461    add_footer = ("add_hash_footer" if self.footer_type == self.AVB_HASH_FOOTER
462                  else "add_hashtree_footer")
463    cmd = [self.avbtool, add_footer, "--partition_size",
464           str(partition_size), "--calc_max_image_size"]
465    cmd.extend(shlex.split(self.signing_args))
466
467    proc = common.Run(cmd)
468    output, _ = proc.communicate()
469    if proc.returncode != 0:
470      raise BuildVerityImageError(
471          "Failed to calculate max image size:\n{}".format(output))
472    image_size = int(output)
473    if image_size <= 0:
474      raise BuildVerityImageError(
475          "Invalid max image size: {}".format(output))
476    self.image_size = image_size
477    return image_size
478
479  def PadSparseImage(self, out_file):
480    # No-op as the padding is taken care of by avbtool.
481    pass
482
483  def Build(self, out_file):
484    """Adds dm-verity hashtree and AVB metadata to an image.
485
486    Args:
487      out_file: Path to image to modify.
488    """
489    add_footer = ("add_hash_footer" if self.footer_type == self.AVB_HASH_FOOTER
490                  else "add_hashtree_footer")
491    cmd = [self.avbtool, add_footer,
492           "--partition_size", str(self.partition_size),
493           "--partition_name", self.partition_name,
494           "--image", out_file]
495    if self.key_path and self.algorithm:
496      cmd.extend(["--key", self.key_path, "--algorithm", self.algorithm])
497    if self.salt:
498      cmd.extend(["--salt", self.salt])
499    cmd.extend(shlex.split(self.signing_args))
500
501    proc = common.Run(cmd)
502    output, _ = proc.communicate()
503    if proc.returncode != 0:
504      raise BuildVerityImageError("Failed to add AVB footer: {}".format(output))
505
506
507class HashtreeInfoGenerationError(Exception):
508  """An Exception raised during hashtree info generation."""
509
510  def __init__(self, message):
511    Exception.__init__(self, message)
512
513
514class HashtreeInfo(object):
515  def __init__(self):
516    self.hashtree_range = None
517    self.filesystem_range = None
518    self.hash_algorithm = None
519    self.salt = None
520    self.root_hash = None
521
522
523def CreateHashtreeInfoGenerator(partition_name, block_size, info_dict):
524  generator = None
525  if (info_dict.get("verity") == "true" and
526      info_dict.get("{}_verity_block_device".format(partition_name))):
527    partition_size = info_dict["{}_size".format(partition_name)]
528    fec_supported = info_dict.get("verity_fec") == "true"
529    generator = VerifiedBootVersion1HashtreeInfoGenerator(
530        partition_size, block_size, fec_supported)
531
532  return generator
533
534
535class HashtreeInfoGenerator(object):
536  def Generate(self, image):
537    raise NotImplementedError
538
539  def DecomposeSparseImage(self, image):
540    raise NotImplementedError
541
542  def ValidateHashtree(self):
543    raise NotImplementedError
544
545
546class VerifiedBootVersion1HashtreeInfoGenerator(HashtreeInfoGenerator):
547  """A class that parses the metadata of hashtree for a given partition."""
548
549  def __init__(self, partition_size, block_size, fec_supported):
550    """Initialize VerityTreeInfo with the sparse image and input property.
551
552    Arguments:
553      partition_size: The whole size in bytes of a partition, including the
554          filesystem size, padding size, and verity size.
555      block_size: Expected size in bytes of each block for the sparse image.
556      fec_supported: True if the verity section contains fec data.
557    """
558
559    self.block_size = block_size
560    self.partition_size = partition_size
561    self.fec_supported = fec_supported
562
563    self.image = None
564    self.filesystem_size = None
565    self.hashtree_size = None
566    self.metadata_size = None
567
568    prop_dict = {
569        'partition_size': str(partition_size),
570        'verity': 'true',
571        'verity_fec': 'true' if fec_supported else None,
572        # 'verity_block_device' needs to be present to indicate a verity-enabled
573        # partition.
574        'verity_block_device': '',
575        # We don't need the following properties that are needed for signing the
576        # verity metadata.
577        'verity_key': '',
578        'verity_signer_cmd': None,
579    }
580    self.verity_image_builder = CreateVerityImageBuilder(prop_dict)
581
582    self.hashtree_info = HashtreeInfo()
583
584  def DecomposeSparseImage(self, image):
585    """Calculate the verity size based on the size of the input image.
586
587    Since we already know the structure of a verity enabled image to be:
588    [filesystem, verity_hashtree, verity_metadata, fec_data]. We can then
589    calculate the size and offset of each section.
590    """
591
592    self.image = image
593    assert self.block_size == image.blocksize
594    assert self.partition_size == image.total_blocks * self.block_size, \
595        "partition size {} doesn't match with the calculated image size." \
596        " total_blocks: {}".format(self.partition_size, image.total_blocks)
597
598    adjusted_size = self.verity_image_builder.CalculateMaxImageSize()
599    assert adjusted_size % self.block_size == 0
600
601    verity_tree_size = GetVerityTreeSize(adjusted_size)
602    assert verity_tree_size % self.block_size == 0
603
604    metadata_size = GetVerityMetadataSize(adjusted_size)
605    assert metadata_size % self.block_size == 0
606
607    self.filesystem_size = adjusted_size
608    self.hashtree_size = verity_tree_size
609    self.metadata_size = metadata_size
610
611    self.hashtree_info.filesystem_range = RangeSet(
612        data=[0, adjusted_size // self.block_size])
613    self.hashtree_info.hashtree_range = RangeSet(
614        data=[adjusted_size // self.block_size,
615              (adjusted_size + verity_tree_size) // self.block_size])
616
617  def _ParseHashtreeMetadata(self):
618    """Parses the hash_algorithm, root_hash, salt from the metadata block."""
619
620    metadata_start = self.filesystem_size + self.hashtree_size
621    metadata_range = RangeSet(
622        data=[metadata_start // self.block_size,
623              (metadata_start + self.metadata_size) // self.block_size])
624    meta_data = b''.join(self.image.ReadRangeSet(metadata_range))
625
626    # More info about the metadata structure available in:
627    # system/extras/verity/build_verity_metadata.py
628    META_HEADER_SIZE = 268
629    header_bin = meta_data[0:META_HEADER_SIZE]
630    header = struct.unpack("II256sI", header_bin)
631
632    # header: magic_number, version, signature, table_len
633    assert header[0] == 0xb001b001, header[0]
634    table_len = header[3]
635    verity_table = meta_data[META_HEADER_SIZE: META_HEADER_SIZE + table_len]
636    table_entries = verity_table.rstrip().split()
637
638    # Expected verity table format: "1 block_device block_device block_size
639    # block_size data_blocks data_blocks hash_algorithm root_hash salt"
640    assert len(table_entries) == 10, "Unexpected verity table size {}".format(
641        len(table_entries))
642    assert (int(table_entries[3]) == self.block_size and
643            int(table_entries[4]) == self.block_size)
644    assert (int(table_entries[5]) * self.block_size == self.filesystem_size and
645            int(table_entries[6]) * self.block_size == self.filesystem_size)
646
647    self.hashtree_info.hash_algorithm = table_entries[7].decode()
648    self.hashtree_info.root_hash = table_entries[8].decode()
649    self.hashtree_info.salt = table_entries[9].decode()
650
651  def ValidateHashtree(self):
652    """Checks that we can reconstruct the verity hash tree."""
653
654    # Writes the filesystem section to a temp file; and calls the executable
655    # build_verity_tree to construct the hash tree.
656    adjusted_partition = common.MakeTempFile(prefix="adjusted_partition")
657    with open(adjusted_partition, "wb") as fd:
658      self.image.WriteRangeDataToFd(self.hashtree_info.filesystem_range, fd)
659
660    generated_verity_tree = common.MakeTempFile(prefix="verity")
661    root_hash, salt = BuildVerityTree(adjusted_partition, generated_verity_tree)
662
663    # The salt should be always identical, as we use fixed value.
664    assert salt == self.hashtree_info.salt, \
665        "Calculated salt {} doesn't match the one in metadata {}".format(
666            salt, self.hashtree_info.salt)
667
668    if root_hash != self.hashtree_info.root_hash:
669      logger.warning(
670          "Calculated root hash %s doesn't match the one in metadata %s",
671          root_hash, self.hashtree_info.root_hash)
672      return False
673
674    # Reads the generated hash tree and checks if it has the exact same bytes
675    # as the one in the sparse image.
676    with open(generated_verity_tree, 'rb') as fd:
677      return fd.read() == b''.join(self.image.ReadRangeSet(
678          self.hashtree_info.hashtree_range))
679
680  def Generate(self, image):
681    """Parses and validates the hashtree info in a sparse image.
682
683    Returns:
684      hashtree_info: The information needed to reconstruct the hashtree.
685
686    Raises:
687      HashtreeInfoGenerationError: If we fail to generate the exact bytes of
688          the hashtree.
689    """
690
691    self.DecomposeSparseImage(image)
692    self._ParseHashtreeMetadata()
693
694    if not self.ValidateHashtree():
695      raise HashtreeInfoGenerationError("Failed to reconstruct the verity tree")
696
697    return self.hashtree_info
698
699
700def CreateCustomImageBuilder(info_dict, partition_name, partition_size,
701                            key_path, algorithm, signing_args):
702  builder = None
703  if info_dict.get("avb_enable") == "true":
704    builder = VerifiedBootVersion2VerityImageBuilder(
705        partition_name,
706        partition_size,
707        VerifiedBootVersion2VerityImageBuilder.AVB_HASHTREE_FOOTER,
708        info_dict.get("avb_avbtool"),
709        key_path,
710        algorithm,
711        # Salt is None because custom images have no fingerprint property to be
712        # used as the salt.
713        None,
714        signing_args)
715
716  return builder
717