• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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"""
18Signs a given image using avbtool
19
20Usage:  verity_utils properties_file output_image
21"""
22
23from __future__ import print_function
24
25import logging
26import os.path
27import shlex
28import struct
29import sys
30
31import common
32import sparse_img
33from rangelib import RangeSet
34
35logger = logging.getLogger(__name__)
36
37OPTIONS = common.OPTIONS
38BLOCK_SIZE = common.BLOCK_SIZE
39FIXED_SALT = "aee087a5be3b982978c923f566a94613496b417f2af592639bc80d141e34dfe7"
40
41# From external/avb/avbtool.py
42MAX_VBMETA_SIZE = 64 * 1024
43MAX_FOOTER_SIZE = 4096
44
45class BuildVerityImageError(Exception):
46  """An Exception raised during verity image building."""
47
48  def __init__(self, message):
49    Exception.__init__(self, message)
50
51
52def CreateVerityImageBuilder(prop_dict):
53  """Returns a verity image builder based on the given build properties.
54
55  Args:
56    prop_dict: A dict that contains the build properties. In particular, it will
57        look for verity-related property values.
58
59  Returns:
60    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.
62  """
63  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)
67
68  # 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")
74
75    # Image uses hash footer.
76    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"])
86
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"])
97
98  return None
99
100
101class VerityImageBuilder(object):
102  """A builder that generates an image with verity metadata for Verified Boot.
103
104  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
111  """
112
113  def CalculateMaxImageSize(self, partition_size):
114    """Calculates the filesystem image size for the given partition size."""
115    raise NotImplementedError
116
117  def CalculateDynamicPartitionSize(self, image_size):
118    """Calculates and sets the partition size for a dynamic partition."""
119    raise NotImplementedError
120
121  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
130class 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 = common.ResolveAVBSigningPathArgs(key_path)
145
146    self.salt = salt
147    self.signing_args = signing_args
148    self.image_size = None
149
150  def CalculateMinPartitionSize(self, image_size, size_calculator=None):
151    """Calculates min partition size for a given image size.
152
153    This is used when determining the partition size for a dynamic partition,
154    which should be cover the given image size (for filesystem files) as well as
155    the verity metadata size.
156
157    Args:
158      image_size: The size of the image in question.
159      size_calculator: The function to calculate max image size
160          for a given partition size.
161
162    Returns:
163      The minimum partition size required to accommodate the image size.
164    """
165    if size_calculator is None:
166      size_calculator = self.CalculateMaxImageSize
167
168    # Use image size as partition size to approximate final partition size.
169    image_ratio = size_calculator(image_size) / float(image_size)
170
171    # Prepare a binary search for the optimal partition size.
172    lo = int(image_size / image_ratio) // BLOCK_SIZE * BLOCK_SIZE - BLOCK_SIZE
173
174    # Ensure lo is small enough: max_image_size should <= image_size.
175    delta = BLOCK_SIZE
176    max_image_size = size_calculator(lo)
177    while max_image_size > image_size:
178      image_ratio = max_image_size / float(lo)
179      lo = int(image_size / image_ratio) // BLOCK_SIZE * BLOCK_SIZE - delta
180      delta *= 2
181      max_image_size = size_calculator(lo)
182
183    hi = lo + BLOCK_SIZE
184
185    # Ensure hi is large enough: max_image_size should >= image_size.
186    delta = BLOCK_SIZE
187    max_image_size = size_calculator(hi)
188    while max_image_size < image_size:
189      image_ratio = max_image_size / float(hi)
190      hi = int(image_size / image_ratio) // BLOCK_SIZE * BLOCK_SIZE + delta
191      delta *= 2
192      max_image_size = size_calculator(hi)
193
194    partition_size = hi
195
196    # Start to binary search.
197    while lo < hi:
198      mid = ((lo + hi) // (2 * BLOCK_SIZE)) * BLOCK_SIZE
199      max_image_size = size_calculator(mid)
200      if max_image_size >= image_size:  # if mid can accommodate image_size
201        if mid < partition_size:  # if a smaller partition size is found
202          partition_size = mid
203        hi = mid
204      else:
205        lo = mid + BLOCK_SIZE
206
207    logger.info(
208        "CalculateMinPartitionSize(%d): partition_size %d.", image_size,
209        partition_size)
210
211    return partition_size
212
213  def CalculateDynamicPartitionSize(self, image_size):
214    self.partition_size = self.CalculateMinPartitionSize(image_size)
215    return self.partition_size
216
217  def CalculateMaxImageSize(self, partition_size=None):
218    """Calculates max image size for a given partition size.
219
220    Args:
221      partition_size: The partition size, which defaults to self.partition_size
222          if unspecified.
223
224    Returns:
225      The maximum image size.
226
227    Raises:
228      BuildVerityImageError: On error or getting invalid image size.
229    """
230    if partition_size is None:
231      partition_size = self.partition_size
232    assert partition_size > 0, \
233        "Invalid partition size: {}".format(partition_size)
234
235    add_footer = ("add_hash_footer" if self.footer_type == self.AVB_HASH_FOOTER
236                  else "add_hashtree_footer")
237    cmd = [self.avbtool, add_footer, "--partition_size",
238           str(partition_size), "--calc_max_image_size"]
239    cmd.extend(shlex.split(self.signing_args))
240
241    proc = common.Run(cmd)
242    output, _ = proc.communicate()
243    if proc.returncode != 0:
244      raise BuildVerityImageError(
245          "Failed to calculate max image size:\n{}".format(output))
246    image_size = int(output)
247    if image_size <= 0:
248      raise BuildVerityImageError(
249          "Invalid max image size: {}".format(output))
250    self.image_size = image_size
251    return image_size
252
253  def PadSparseImage(self, out_file):
254    # No-op as the padding is taken care of by avbtool.
255    pass
256
257  def Build(self, out_file):
258    """Adds dm-verity hashtree and AVB metadata to an image.
259
260    Args:
261      out_file: Path to image to modify.
262    """
263    add_footer = ("add_hash_footer" if self.footer_type == self.AVB_HASH_FOOTER
264                  else "add_hashtree_footer")
265    cmd = [self.avbtool, add_footer,
266           "--partition_size", str(self.partition_size),
267           "--partition_name", self.partition_name,
268           "--image", out_file]
269    if self.key_path and self.algorithm:
270      cmd.extend(["--key", self.key_path, "--algorithm", self.algorithm])
271    if self.salt:
272      cmd.extend(["--salt", self.salt])
273    cmd.extend(shlex.split(self.signing_args))
274
275    proc = common.Run(cmd)
276    output, _ = proc.communicate()
277    if proc.returncode != 0:
278      raise BuildVerityImageError("Failed to add AVB footer: {}".format(output))
279
280
281def CreateCustomImageBuilder(info_dict, partition_name, partition_size,
282                            key_path, algorithm, signing_args):
283  builder = None
284  if info_dict.get("avb_enable") == "true":
285    builder = VerifiedBootVersion2VerityImageBuilder(
286        partition_name,
287        partition_size,
288        VerifiedBootVersion2VerityImageBuilder.AVB_HASHTREE_FOOTER,
289        info_dict.get("avb_avbtool"),
290        key_path,
291        algorithm,
292        # Salt is None because custom images have no fingerprint property to be
293        # used as the salt.
294        None,
295        signing_args)
296
297  return builder
298
299
300def GetDiskUsage(path):
301  """Returns the number of bytes that "path" occupies on host.
302
303  Args:
304    path: The directory or file to calculate size on.
305
306  Returns:
307    The number of bytes based on a 1K block_size.
308  """
309  cmd = ["du", "-b", "-k", "-s", path]
310  output = common.RunAndCheckOutput(cmd, verbose=False)
311  return int(output.split()[0]) * 1024
312
313
314def CalculateVbmetaDigest(extracted_dir, avbtool):
315  """Calculates the vbmeta digest of the images in the extracted target_file"""
316
317  images_dir = common.MakeTempDir()
318  for name in ("PREBUILT_IMAGES", "RADIO", "IMAGES"):
319    path = os.path.join(extracted_dir, name)
320    if not os.path.exists(path):
321      continue
322
323    # Create symlink for image files under PREBUILT_IMAGES, RADIO and IMAGES,
324    # and put them into one directory.
325    for filename in os.listdir(path):
326      if not filename.endswith(".img"):
327        continue
328      symlink_path = os.path.join(images_dir, filename)
329      # The files in latter directory overwrite the existing links
330      common.RunAndCheckOutput(
331        ['ln', '-sf', os.path.join(path, filename), symlink_path])
332
333  cmd = [avbtool, "calculate_vbmeta_digest", "--image",
334         os.path.join(images_dir, 'vbmeta.img')]
335  return common.RunAndCheckOutput(cmd)
336
337
338def main(argv):
339  if len(argv) != 2:
340    print(__doc__)
341    sys.exit(1)
342
343  common.InitLogging()
344
345  dict_file = argv[0]
346  out_file = argv[1]
347
348  prop_dict = {}
349  with open(dict_file, 'r') as f:
350    for line in f:
351      line = line.strip()
352      if not line or line.startswith("#"):
353        continue
354      k, v = line.split("=", 1)
355      prop_dict[k] = v
356
357  builder = CreateVerityImageBuilder(prop_dict)
358
359  if "partition_size" not in prop_dict:
360    image_size = GetDiskUsage(out_file)
361    # make sure that the image is big enough to hold vbmeta and footer
362    image_size = image_size + (MAX_VBMETA_SIZE + MAX_FOOTER_SIZE)
363    size = builder.CalculateDynamicPartitionSize(image_size)
364    prop_dict["partition_size"] = size
365
366  builder.Build(out_file)
367
368
369if __name__ == '__main__':
370  try:
371    main(sys.argv[1:])
372  finally:
373    common.Cleanup()
374