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