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