blob: e7f84f56419e2954766e30dd5bb6f018d2c34c05 [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
17from __future__ import print_function
18
Tao Bao32fcdab2018-10-12 10:30:39 -070019import logging
Tao Bao71197512018-10-11 14:08:45 -070020import os.path
21import shlex
Tianjie Xu67c7cbb2018-08-30 00:32:07 -070022import struct
23
24import common
Tao Bao71197512018-10-11 14:08:45 -070025import sparse_img
Tianjie Xu67c7cbb2018-08-30 00:32:07 -070026from rangelib import RangeSet
27
Tao Bao32fcdab2018-10-12 10:30:39 -070028logger = logging.getLogger(__name__)
29
Tao Bao71197512018-10-11 14:08:45 -070030OPTIONS = 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
Tao Bao7549e5e2018-10-03 14:23:59 -070042def GetVerityFECSize(image_size):
43 cmd = ["fec", "-s", str(image_size)]
Tao Bao71197512018-10-11 14:08:45 -070044 output = common.RunAndCheckOutput(cmd, verbose=False)
45 return int(output)
46
47
Tao Bao7549e5e2018-10-03 14:23:59 -070048def GetVerityTreeSize(image_size):
49 cmd = ["build_verity_tree", "-s", str(image_size)]
Tao Bao71197512018-10-11 14:08:45 -070050 output = common.RunAndCheckOutput(cmd, verbose=False)
51 return int(output)
52
53
Tao Bao7549e5e2018-10-03 14:23:59 -070054def GetVerityMetadataSize(image_size):
Tao Baod5ab10e2019-05-02 18:17:18 -070055 cmd = ["build_verity_metadata", "size", str(image_size)]
Tao Bao71197512018-10-11 14:08:45 -070056 output = common.RunAndCheckOutput(cmd, verbose=False)
57 return int(output)
58
59
Tao Bao7549e5e2018-10-03 14:23:59 -070060def GetVeritySize(image_size, fec_supported):
61 verity_tree_size = GetVerityTreeSize(image_size)
62 verity_metadata_size = GetVerityMetadataSize(image_size)
Tao Bao71197512018-10-11 14:08:45 -070063 verity_size = verity_tree_size + verity_metadata_size
64 if fec_supported:
Tao Bao7549e5e2018-10-03 14:23:59 -070065 fec_size = GetVerityFECSize(image_size + verity_size)
Tao Bao71197512018-10-11 14:08:45 -070066 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
Tao Bao32fcdab2018-10-12 10:30:39 -070077 logger.info("Padding %d blocks (%d bytes)", blocks, pad_size)
Tao Bao71197512018-10-11 14:08:45 -070078 simg = sparse_img.SparseImage(image_file, mode="r+b", build_map=False)
79 simg.AppendFillChunk(0, blocks)
80
81
Tao Bao71197512018-10-11 14:08:45 -070082def 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):
Tao Baod5ab10e2019-05-02 18:17:18 -0700100 cmd = ["build_verity_metadata", "build", str(image_size),
Tao Bao71197512018-10-11 14:08:45 -0700101 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:
Tao Bao46901fb2018-11-06 10:26:21 -0800123 logger.exception(error_message)
Tao Bao71197512018-10-11 14:08:45 -0700124 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:
Tao Bao9e893c32019-06-20 16:14:55 -0700134 with open(target, 'ab') as out_file, \
135 open(file_to_append, 'rb') as input_file:
Tao Bao71197512018-10-11 14:08:45 -0700136 for line in input_file:
137 out_file.write(line)
138 except IOError:
Tao Bao46901fb2018-11-06 10:26:21 -0800139 logger.exception(error_message)
Tao Bao71197512018-10-11 14:08:45 -0700140 raise BuildVerityImageError(error_message)
141
142
Tao Bao7549e5e2018-10-03 14:23:59 -0700143def CreateVerityImageBuilder(prop_dict):
144 """Returns a verity image builder based on the given build properties.
Tao Bao71197512018-10-11 14:08:45 -0700145
146 Args:
Tao Bao7549e5e2018-10-03 14:23:59 -0700147 prop_dict: A dict that contains the build properties. In particular, it will
148 look for verity-related property values.
Tao Bao71197512018-10-11 14:08:45 -0700149
150 Returns:
Tao Bao7549e5e2018-10-03 14:23:59 -0700151 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.
Tao Bao71197512018-10-11 14:08:45 -0700153 """
Tao Bao7549e5e2018-10-03 14:23:59 -0700154 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)
Tao Bao71197512018-10-11 14:08:45 -0700158
Tao Bao7549e5e2018-10-03 14:23:59 -0700159 # 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
Tao Bao71197512018-10-11 14:08:45 -0700165 else:
Tao Bao7549e5e2018-10-03 14:23:59 -0700166 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)
Tao Bao71197512018-10-11 14:08:45 -0700175
Tao Bao7549e5e2018-10-03 14:23:59 -0700176 # 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")
Tao Bao9e893c32019-06-20 16:14:55 -0700182
183 # Image uses hash footer.
Tao Bao7549e5e2018-10-03 14:23:59 -0700184 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"])
Tao Bao9e893c32019-06-20 16:14:55 -0700194
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"])
Tao Bao71197512018-10-11 14:08:45 -0700205
Tao Bao7549e5e2018-10-03 14:23:59 -0700206 return None
Tao Bao71197512018-10-11 14:08:45 -0700207
208
Tao Bao7549e5e2018-10-03 14:23:59 -0700209class VerityImageBuilder(object):
210 """A builder that generates an image with verity metadata for Verified Boot.
Tao Bao71197512018-10-11 14:08:45 -0700211
Tao Bao7549e5e2018-10-03 14:23:59 -0700212 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
Tao Bao71197512018-10-11 14:08:45 -0700219 """
Tao Bao71197512018-10-11 14:08:45 -0700220
Tao Bao7549e5e2018-10-03 14:23:59 -0700221 def CalculateMaxImageSize(self, partition_size):
222 """Calculates the filesystem image size for the given partition size."""
223 raise NotImplementedError
Tao Bao71197512018-10-11 14:08:45 -0700224
Tao Bao7549e5e2018-10-03 14:23:59 -0700225 def CalculateDynamicPartitionSize(self, image_size):
226 """Calculates and sets the partition size for a dynamic partition."""
227 raise NotImplementedError
Tao Bao71197512018-10-11 14:08:45 -0700228
Tao Bao7549e5e2018-10-03 14:23:59 -0700229 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:
Tao Bao4a0d5132018-10-17 22:53:54 -0700308 out_file: the output image.
Tao Bao7549e5e2018-10-03 14:23:59 -0700309
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))
Tao Bao71197512018-10-11 14:08:45 -0700505
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700506
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
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700546class 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
Tao Bao7549e5e2018-10-03 14:23:59 -0700554 filesystem size, padding size, and verity size.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700555 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
Tao Bao7549e5e2018-10-03 14:23:59 -0700568 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
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700582 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
Tao Bao7549e5e2018-10-03 14:23:59 -0700598 adjusted_size = self.verity_image_builder.CalculateMaxImageSize()
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700599 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(
Tao Bao9e893c32019-06-20 16:14:55 -0700612 data=[0, adjusted_size // self.block_size])
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700613 self.hashtree_info.hashtree_range = RangeSet(
Tao Bao9e893c32019-06-20 16:14:55 -0700614 data=[adjusted_size // self.block_size,
615 (adjusted_size + verity_tree_size) // self.block_size])
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700616
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(
Tao Bao9e893c32019-06-20 16:14:55 -0700622 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))
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700625
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
Tao Bao9e893c32019-06-20 16:14:55 -0700647 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()
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700650
651 def ValidateHashtree(self):
652 """Checks that we can reconstruct the verity hash tree."""
653
Tao Bao7549e5e2018-10-03 14:23:59 -0700654 # Writes the filesystem section to a temp file; and calls the executable
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700655 # 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")
Tao Bao2f057462018-10-03 16:31:18 -0700661 root_hash, salt = BuildVerityTree(adjusted_partition, generated_verity_tree)
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700662
Tao Bao2f057462018-10-03 16:31:18 -0700663 # 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:
Tao Bao32fcdab2018-10-12 10:30:39 -0700669 logger.warning(
670 "Calculated root hash %s doesn't match the one in metadata %s",
671 root_hash, self.hashtree_info.root_hash)
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700672 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.
Tao Bao9e893c32019-06-20 16:14:55 -0700676 with open(generated_verity_tree, 'rb') as fd:
677 return fd.read() == b''.join(self.image.ReadRangeSet(
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700678 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.
Tao Bao2f057462018-10-03 16:31:18 -0700685
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700686 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