blob: 3a58755e47df4ffd1020248761ab40107fe1a73f [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:
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:
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:
Tao Bao46901fb2018-11-06 10:26:21 -0800138 logger.exception(error_message)
Tao Bao71197512018-10-11 14:08:45 -0700139 raise BuildVerityImageError(error_message)
140
141
Tao Bao7549e5e2018-10-03 14:23:59 -0700142def CreateVerityImageBuilder(prop_dict):
143 """Returns a verity image builder based on the given build properties.
Tao Bao71197512018-10-11 14:08:45 -0700144
145 Args:
Tao Bao7549e5e2018-10-03 14:23:59 -0700146 prop_dict: A dict that contains the build properties. In particular, it will
147 look for verity-related property values.
Tao Bao71197512018-10-11 14:08:45 -0700148
149 Returns:
Tao Bao7549e5e2018-10-03 14:23:59 -0700150 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.
Tao Bao71197512018-10-11 14:08:45 -0700152 """
Tao Bao7549e5e2018-10-03 14:23:59 -0700153 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)
Tao Bao71197512018-10-11 14:08:45 -0700157
Tao Bao7549e5e2018-10-03 14:23:59 -0700158 # 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
Tao Bao71197512018-10-11 14:08:45 -0700164 else:
Tao Bao7549e5e2018-10-03 14:23:59 -0700165 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)
Tao Bao71197512018-10-11 14:08:45 -0700174
Tao Bao7549e5e2018-10-03 14:23:59 -0700175 # 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"])
Tao Bao71197512018-10-11 14:08:45 -0700201
Tao Bao7549e5e2018-10-03 14:23:59 -0700202 return None
Tao Bao71197512018-10-11 14:08:45 -0700203
204
Tao Bao7549e5e2018-10-03 14:23:59 -0700205class VerityImageBuilder(object):
206 """A builder that generates an image with verity metadata for Verified Boot.
Tao Bao71197512018-10-11 14:08:45 -0700207
Tao Bao7549e5e2018-10-03 14:23:59 -0700208 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
Tao Bao71197512018-10-11 14:08:45 -0700215 """
Tao Bao71197512018-10-11 14:08:45 -0700216
Tao Bao7549e5e2018-10-03 14:23:59 -0700217 def CalculateMaxImageSize(self, partition_size):
218 """Calculates the filesystem image size for the given partition size."""
219 raise NotImplementedError
Tao Bao71197512018-10-11 14:08:45 -0700220
Tao Bao7549e5e2018-10-03 14:23:59 -0700221 def CalculateDynamicPartitionSize(self, image_size):
222 """Calculates and sets the partition size for a dynamic partition."""
223 raise NotImplementedError
Tao Bao71197512018-10-11 14:08:45 -0700224
Tao Bao7549e5e2018-10-03 14:23:59 -0700225 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
234class 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:
Tao Bao4a0d5132018-10-17 22:53:54 -0700304 out_file: the output image.
Tao Bao7549e5e2018-10-03 14:23:59 -0700305
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
353class 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))
Tao Bao71197512018-10-11 14:08:45 -0700501
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700502
503class HashtreeInfoGenerationError(Exception):
504 """An Exception raised during hashtree info generation."""
505
506 def __init__(self, message):
507 Exception.__init__(self, message)
508
509
510class 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
519def 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
531class 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
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700542class 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
Tao Bao7549e5e2018-10-03 14:23:59 -0700550 filesystem size, padding size, and verity size.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700551 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
Tao Bao7549e5e2018-10-03 14:23:59 -0700564 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
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700578 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
Tao Bao7549e5e2018-10-03 14:23:59 -0700594 adjusted_size = self.verity_image_builder.CalculateMaxImageSize()
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700595 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
Tao Bao7549e5e2018-10-03 14:23:59 -0700650 # Writes the filesystem section to a temp file; and calls the executable
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700651 # 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")
Tao Bao2f057462018-10-03 16:31:18 -0700657 root_hash, salt = BuildVerityTree(adjusted_partition, generated_verity_tree)
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700658
Tao Bao2f057462018-10-03 16:31:18 -0700659 # 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:
Tao Bao32fcdab2018-10-12 10:30:39 -0700665 logger.warning(
666 "Calculated root hash %s doesn't match the one in metadata %s",
667 root_hash, self.hashtree_info.root_hash)
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700668 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.
Tao Bao2f057462018-10-03 16:31:18 -0700681
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700682 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