blob: 8921590c4c47dd21f0dd86b704bea3299a574c53 [file] [log] [blame]
David Zeuthen21e95262016-07-27 17:58:40 -04001#!/usr/bin/env python
2
3# Copyright 2016, The Android Open Source Project
4#
David Zeuthenc612e2e2016-09-16 16:44:08 -04005# Permission is hereby granted, free of charge, to any person
6# obtaining a copy of this software and associated documentation
7# files (the "Software"), to deal in the Software without
8# restriction, including without limitation the rights to use, copy,
9# modify, merge, publish, distribute, sublicense, and/or sell copies
10# of the Software, and to permit persons to whom the Software is
11# furnished to do so, subject to the following conditions:
David Zeuthen21e95262016-07-27 17:58:40 -040012#
David Zeuthenc612e2e2016-09-16 16:44:08 -040013# The above copyright notice and this permission notice shall be
14# included in all copies or substantial portions of the Software.
David Zeuthen21e95262016-07-27 17:58:40 -040015#
David Zeuthenc612e2e2016-09-16 16:44:08 -040016# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
20# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
21# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
22# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23# SOFTWARE.
David Zeuthen21e95262016-07-27 17:58:40 -040024#
25"""Command-line tool for working with Brillo Verified Boot images."""
26
27import argparse
David Zeuthena4fee8b2016-08-22 15:20:43 -040028import bisect
David Zeuthen21e95262016-07-27 17:58:40 -040029import hashlib
30import os
31import struct
32import subprocess
33import sys
34
35import Crypto.PublicKey.RSA
36
37# Keep in sync with avb_vbmeta_header.h.
38AVB_VERSION_MAJOR = 1
39AVB_VERSION_MINOR = 0
40
41
42class AvbError(Exception):
43 """Application-specific errors.
44
45 These errors represent issues for which a stack-trace should not be
46 presented.
47
48 Attributes:
49 message: Error message.
50 """
51
52 def __init__(self, message):
53 Exception.__init__(self, message)
54
55
56class Algorithm(object):
57 """Contains details about an algorithm.
58
59 See the avb_vbmeta_header.h file for more details about
60 algorithms.
61
62 The constant |ALGORITHMS| is a dictionary from human-readable
63 names (e.g 'SHA256_RSA2048') to instances of this class.
64
65 Attributes:
66 algorithm_type: Integer code corresponding to |AvbAlgorithmType|.
67 hash_num_bytes: Number of bytes used to store the hash.
68 signature_num_bytes: Number of bytes used to store the signature.
69 public_key_num_bytes: Number of bytes used to store the public key.
70 padding: Padding used for signature, if any.
71 """
72
73 def __init__(self, algorithm_type, hash_num_bytes, signature_num_bytes,
74 public_key_num_bytes, padding):
75 self.algorithm_type = algorithm_type
76 self.hash_num_bytes = hash_num_bytes
77 self.signature_num_bytes = signature_num_bytes
78 self.public_key_num_bytes = public_key_num_bytes
79 self.padding = padding
80
81# This must be kept in sync with the avb_crypto.h file.
82#
83# The PKC1-v1.5 padding is a blob of binary DER of ASN.1 and is
84# obtained from section 5.2.2 of RFC 4880.
85ALGORITHMS = {
86 'NONE': Algorithm(
87 algorithm_type=0, # AVB_ALGORITHM_TYPE_NONE
88 hash_num_bytes=0,
89 signature_num_bytes=0,
90 public_key_num_bytes=0,
91 padding=[]),
92 'SHA256_RSA2048': Algorithm(
93 algorithm_type=1, # AVB_ALGORITHM_TYPE_SHA256_RSA2048
94 hash_num_bytes=32,
95 signature_num_bytes=256,
96 public_key_num_bytes=8 + 2*2048/8,
97 padding=[
98 # PKCS1-v1_5 padding
99 0x00, 0x01] + [0xff]*202 + [0x00] + [
100 # ASN.1 header
101 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
102 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05,
103 0x00, 0x04, 0x20,
104 ]),
105 'SHA256_RSA4096': Algorithm(
106 algorithm_type=2, # AVB_ALGORITHM_TYPE_SHA256_RSA4096
107 hash_num_bytes=32,
108 signature_num_bytes=512,
109 public_key_num_bytes=8 + 2*4096/8,
110 padding=[
111 # PKCS1-v1_5 padding
112 0x00, 0x01] + [0xff]*458 + [0x00] + [
113 # ASN.1 header
114 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
115 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05,
116 0x00, 0x04, 0x20,
117 ]),
118 'SHA256_RSA8192': Algorithm(
119 algorithm_type=3, # AVB_ALGORITHM_TYPE_SHA256_RSA8192
120 hash_num_bytes=32,
121 signature_num_bytes=1024,
122 public_key_num_bytes=8 + 2*8192/8,
123 padding=[
124 # PKCS1-v1_5 padding
125 0x00, 0x01] + [0xff]*970 + [0x00] + [
126 # ASN.1 header
127 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
128 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05,
129 0x00, 0x04, 0x20,
130 ]),
131 'SHA512_RSA2048': Algorithm(
132 algorithm_type=4, # AVB_ALGORITHM_TYPE_SHA512_RSA2048
133 hash_num_bytes=64,
134 signature_num_bytes=256,
135 public_key_num_bytes=8 + 2*2048/8,
136 padding=[
137 # PKCS1-v1_5 padding
138 0x00, 0x01] + [0xff]*170 + [0x00] + [
139 # ASN.1 header
140 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
141 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05,
142 0x00, 0x04, 0x40
143 ]),
144 'SHA512_RSA4096': Algorithm(
145 algorithm_type=5, # AVB_ALGORITHM_TYPE_SHA512_RSA4096
146 hash_num_bytes=64,
147 signature_num_bytes=512,
148 public_key_num_bytes=8 + 2*4096/8,
149 padding=[
150 # PKCS1-v1_5 padding
151 0x00, 0x01] + [0xff]*426 + [0x00] + [
152 # ASN.1 header
153 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
154 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05,
155 0x00, 0x04, 0x40
156 ]),
157 'SHA512_RSA8192': Algorithm(
158 algorithm_type=6, # AVB_ALGORITHM_TYPE_SHA512_RSA8192
159 hash_num_bytes=64,
160 signature_num_bytes=1024,
161 public_key_num_bytes=8 + 2*8192/8,
162 padding=[
163 # PKCS1-v1_5 padding
164 0x00, 0x01] + [0xff]*938 + [0x00] + [
165 # ASN.1 header
166 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
167 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05,
168 0x00, 0x04, 0x40
169 ]),
170}
171
172
173def round_to_multiple(number, size):
174 """Rounds a number up to nearest multiple of another number.
175
176 Args:
177 number: The number to round up.
178 size: The multiple to round up to.
179
180 Returns:
181 If |number| is a multiple of |size|, returns |number|, otherwise
182 returns |number| + |size|.
183 """
184 remainder = number % size
185 if remainder == 0:
186 return number
187 return number + size - remainder
188
189
190def round_to_pow2(number):
191 """Rounds a number up to the next power of 2.
192
193 Args:
194 number: The number to round up.
195
196 Returns:
197 If |number| is already a power of 2 then |number| is
198 returned. Otherwise the smallest power of 2 greater than |number|
199 is returned.
200 """
201 return 2**((number - 1).bit_length())
202
203
204def write_long(output, num_bits, value):
205 """Writes a long to an output stream using a given amount of bits.
206
207 This number is written big-endian, e.g. with the most significant
208 bit first.
209
210 Arguments:
211 output: The object to write the output to.
212 num_bits: The number of bits to write, e.g. 2048.
213 value: The value to write.
214 """
215 for bit_pos in range(num_bits, 0, -8):
216 octet = (value >> (bit_pos - 8)) & 0xff
217 output.write(struct.pack('!B', octet))
218
219
220def encode_long(num_bits, value):
221 """Encodes a long to a bytearray() using a given amount of bits.
222
223 This number is written big-endian, e.g. with the most significant
224 bit first.
225
226 Arguments:
227 num_bits: The number of bits to write, e.g. 2048.
228 value: The value to write.
229
230 Returns:
231 A bytearray() with the encoded long.
232 """
233 ret = bytearray()
234 for bit_pos in range(num_bits, 0, -8):
235 octet = (value >> (bit_pos - 8)) & 0xff
236 ret.extend(struct.pack('!B', octet))
237 return ret
238
239
240def egcd(a, b):
241 """Calculate greatest common divisor of two numbers.
242
243 This implementation uses a recursive version of the extended
244 Euclidian algorithm.
245
246 Arguments:
247 a: First number.
248 b: Second number.
249
250 Returns:
251 A tuple (gcd, x, y) that where |gcd| is the greatest common
252 divisor of |a| and |b| and |a|*|x| + |b|*|y| = |gcd|.
253 """
254 if a == 0:
255 return (b, 0, 1)
256 else:
257 g, y, x = egcd(b % a, a)
258 return (g, x - (b // a) * y, y)
259
260
261def modinv(a, m):
262 """Calculate modular multiplicative inverse of |a| modulo |m|.
263
264 This calculates the number |x| such that |a| * |x| == 1 (modulo
265 |m|). This number only exists if |a| and |m| are co-prime - |None|
266 is returned if this isn't true.
267
268 Arguments:
269 a: The number to calculate a modular inverse of.
270 m: The modulo to use.
271
272 Returns:
273 The modular multiplicative inverse of |a| and |m| or |None| if
274 these numbers are not co-prime.
275 """
276 gcd, x, _ = egcd(a, m)
277 if gcd != 1:
278 return None # modular inverse does not exist
279 else:
280 return x % m
281
282
283def parse_number(string):
284 """Parse a string as a number.
285
286 This is just a short-hand for int(string, 0) suitable for use in the
287 |type| parameter of |ArgumentParser|'s add_argument() function. An
288 improvement to just using type=int is that this function supports
289 numbers in other bases, e.g. "0x1234".
290
291 Arguments:
292 string: The string to parse.
293
294 Returns:
295 The parsed integer.
296
297 Raises:
298 ValueError: If the number could not be parsed.
299 """
300 return int(string, 0)
301
302
303def write_rsa_key(output, key):
304 """Writes a public RSA key in |AvbRSAPublicKeyHeader| format.
305
306 This writes the |AvbRSAPublicKeyHeader| as well as the two large
307 numbers (|key_num_bits| bits long) following it.
308
309 Arguments:
310 output: The object to write the output to.
311 key: A Crypto.PublicKey.RSA object.
312 """
313 # key.e is exponent
314 # key.n is modulus
315 key_num_bits = key.size() + 1
316 # Calculate n0inv = -1/n[0] (mod 2^32)
317 b = 2L**32
318 n0inv = b - modinv(key.n, b)
319 # Calculate rr = r^2 (mod N), where r = 2^(# of key bits)
320 r = 2L**key.n.bit_length()
321 rrmodn = r * r % key.n
322 output.write(struct.pack('!II', key_num_bits, n0inv))
323 write_long(output, key_num_bits, key.n)
324 write_long(output, key_num_bits, rrmodn)
325
326
327def encode_rsa_key(key):
328 """Encodes a public RSA key in |AvbRSAPublicKeyHeader| format.
329
330 This creates a |AvbRSAPublicKeyHeader| as well as the two large
331 numbers (|key_num_bits| bits long) following it.
332
333 Arguments:
334 key: A Crypto.PublicKey.RSA object.
335
336 Returns:
337 A bytearray() with the |AvbRSAPublicKeyHeader|.
338 """
339 ret = bytearray()
340 # key.e is exponent
341 # key.n is modulus
342 key_num_bits = key.size() + 1
343 # Calculate n0inv = -1/n[0] (mod 2^32)
344 b = 2L**32
345 n0inv = b - modinv(key.n, b)
346 # Calculate rr = r^2 (mod N), where r = 2^(# of key bits)
347 r = 2L**key.n.bit_length()
348 rrmodn = r * r % key.n
349 ret.extend(struct.pack('!II', key_num_bits, n0inv))
350 ret.extend(encode_long(key_num_bits, key.n))
351 ret.extend(encode_long(key_num_bits, rrmodn))
352 return ret
353
354
355def lookup_algorithm_by_type(alg_type):
356 """Looks up algorithm by type.
357
358 Arguments:
359 alg_type: The integer representing the type.
360
361 Returns:
362 A tuple with the algorithm name and an |Algorithm| instance.
363
364 Raises:
365 Exception: If the algorithm cannot be found
366 """
367 for alg_name in ALGORITHMS:
368 alg_data = ALGORITHMS[alg_name]
369 if alg_data.algorithm_type == alg_type:
370 return (alg_name, alg_data)
371 raise AvbError('Unknown algorithm type {}'.format(alg_type))
372
373
David Zeuthena4fee8b2016-08-22 15:20:43 -0400374class ImageChunk(object):
375 """Data structure used for representing chunks in Android sparse files.
376
377 Attributes:
378 chunk_type: One of TYPE_RAW, TYPE_FILL, or TYPE_DONT_CARE.
379 chunk_offset: Offset in the sparse file where this chunk begins.
380 output_offset: Offset in de-sparsified file where output begins.
381 output_size: Number of bytes in output.
382 input_offset: Offset in sparse file for data if TYPE_RAW otherwise None.
383 fill_data: Blob with data to fill if TYPE_FILL otherwise None.
384 """
385
386 FORMAT = '<2H2I'
387 TYPE_RAW = 0xcac1
388 TYPE_FILL = 0xcac2
389 TYPE_DONT_CARE = 0xcac3
390 TYPE_CRC32 = 0xcac4
391
392 def __init__(self, chunk_type, chunk_offset, output_offset, output_size,
393 input_offset, fill_data):
394 """Initializes an ImageChunk object.
395
396 Arguments:
397 chunk_type: One of TYPE_RAW, TYPE_FILL, or TYPE_DONT_CARE.
398 chunk_offset: Offset in the sparse file where this chunk begins.
399 output_offset: Offset in de-sparsified file.
400 output_size: Number of bytes in output.
401 input_offset: Offset in sparse file if TYPE_RAW otherwise None.
402 fill_data: Blob with data to fill if TYPE_FILL otherwise None.
403
404 Raises:
405 ValueError: If data is not well-formed.
406 """
407 self.chunk_type = chunk_type
408 self.chunk_offset = chunk_offset
409 self.output_offset = output_offset
410 self.output_size = output_size
411 self.input_offset = input_offset
412 self.fill_data = fill_data
413 # Check invariants.
414 if self.chunk_type == self.TYPE_RAW:
415 if self.fill_data is not None:
416 raise ValueError('RAW chunk cannot have fill_data set.')
417 if not self.input_offset:
418 raise ValueError('RAW chunk must have input_offset set.')
419 elif self.chunk_type == self.TYPE_FILL:
420 if self.fill_data is None:
421 raise ValueError('FILL chunk must have fill_data set.')
422 if self.input_offset:
423 raise ValueError('FILL chunk cannot have input_offset set.')
424 elif self.chunk_type == self.TYPE_DONT_CARE:
425 if self.fill_data is not None:
426 raise ValueError('DONT_CARE chunk cannot have fill_data set.')
427 if self.input_offset:
428 raise ValueError('DONT_CARE chunk cannot have input_offset set.')
429 else:
430 raise ValueError('Invalid chunk type')
431
432
433class ImageHandler(object):
434 """Abstraction for image I/O with support for Android sparse images.
435
436 This class provides an interface for working with image files that
437 may be using the Android Sparse Image format. When an instance is
438 constructed, we test whether it's an Android sparse file. If so,
439 operations will be on the sparse file by interpreting the sparse
440 format, otherwise they will be directly on the file. Either way the
441 operations do the same.
442
443 For reading, this interface mimics a file object - it has seek(),
444 tell(), and read() methods. For writing, only truncation
445 (truncate()) and appending is supported (append_raw() and
446 append_dont_care()). Additionally, data can only be written in units
447 of the block size.
448
449 Attributes:
450 is_sparse: Whether the file being operated on is sparse.
451 block_size: The block size, typically 4096.
452 image_size: The size of the unsparsified file.
453 care_size: Position in the unsparsified file where only
454 DONT_CARE data follows.
455 """
456 # See system/core/libsparse/sparse_format.h for details.
457 MAGIC = 0xed26ff3a
458 HEADER_FORMAT = '<I4H4I'
459
460 # These are formats and offset of just the |total_chunks| and
461 # |total_blocks| fields.
462 NUM_CHUNKS_AND_BLOCKS_FORMAT = '<II'
463 NUM_CHUNKS_AND_BLOCKS_OFFSET = 16
464
465 def __init__(self, image_filename):
466 """Initializes an image handler.
467
468 Arguments:
469 image_filename: The name of the file to operate on.
470
471 Raises:
472 ValueError: If data in the file is invalid.
473 """
474 self._image_filename = image_filename
475 self._read_header()
476
477 def _read_header(self):
478 """Initializes internal data structures used for reading file.
479
480 This may be called multiple times and is typically called after
481 modifying the file (e.g. appending, truncation).
482
483 Raises:
484 ValueError: If data in the file is invalid.
485 """
486 self.is_sparse = False
487 self.block_size = 4096
488 self._file_pos = 0
489 self._image = open(self._image_filename, 'r+b')
490 self._image.seek(0, os.SEEK_END)
491 self.care_size = self._image.tell()
492 self.image_size = self._image.tell()
493
494 self._image.seek(0, os.SEEK_SET)
495 header_bin = self._image.read(struct.calcsize(self.HEADER_FORMAT))
496 (magic, major_version, minor_version, file_hdr_sz, chunk_hdr_sz,
497 block_size, self._num_total_blocks, self._num_total_chunks,
498 _) = struct.unpack(self.HEADER_FORMAT, header_bin)
499 if magic != self.MAGIC:
500 # Not a sparse image, our job here is done.
501 return
502 if not (major_version == 1 and minor_version == 0):
503 raise ValueError('Encountered sparse image format version {}.{} but '
504 'only 1.0 is supported'.format(major_version,
505 minor_version))
506 if file_hdr_sz != struct.calcsize(self.HEADER_FORMAT):
507 raise ValueError('Unexpected file_hdr_sz value {}.'.
508 format(file_hdr_sz))
509 if chunk_hdr_sz != struct.calcsize(ImageChunk.FORMAT):
510 raise ValueError('Unexpected chunk_hdr_sz value {}.'.
511 format(chunk_hdr_sz))
512
513 self.block_size = block_size
514
515 # Build an list of chunks by parsing the file.
516 self._chunks = []
517
518 # Find the smallest offset where only "Don't care" chunks
519 # follow. This will be the size of the content in the sparse
520 # image.
521 offset = 0
522 output_offset = 0
523 last_dont_care_section_output_offset = None
524 last_section_was_dont_care = False
525 for _ in xrange(1, self._num_total_chunks + 1):
526 chunk_offset = self._image.tell()
527
528 header_bin = self._image.read(struct.calcsize(ImageChunk.FORMAT))
529 (chunk_type, _, chunk_sz, total_sz) = struct.unpack(ImageChunk.FORMAT,
530 header_bin)
531 data_sz = total_sz - struct.calcsize(ImageChunk.FORMAT)
532
533 last_section_was_dont_care = False
534
535 if chunk_type == ImageChunk.TYPE_RAW:
536 if data_sz != (chunk_sz * self.block_size):
537 raise ValueError('Raw chunk input size ({}) does not match output '
538 'size ({})'.
539 format(data_sz, chunk_sz*self.block_size))
540 self._chunks.append(ImageChunk(ImageChunk.TYPE_RAW,
541 chunk_offset,
542 output_offset,
543 chunk_sz*self.block_size,
544 self._image.tell(),
545 None))
546 self._image.read(data_sz)
547
548 elif chunk_type == ImageChunk.TYPE_FILL:
549 if data_sz != 4:
550 raise ValueError('Fill chunk should have 4 bytes of fill, but this '
551 'has {}'.format(data_sz))
552 fill_data = self._image.read(4)
553 self._chunks.append(ImageChunk(ImageChunk.TYPE_FILL,
554 chunk_offset,
555 output_offset,
556 chunk_sz*self.block_size,
557 None,
558 fill_data))
559 elif chunk_type == ImageChunk.TYPE_DONT_CARE:
560 if data_sz != 0:
561 raise ValueError('Don\'t care chunk input size is non-zero ({})'.
562 format(data_sz))
563 else:
564 if not last_section_was_dont_care:
565 last_dont_care_section_output_offset = output_offset
566 last_section_was_dont_care = True
567 self._chunks.append(ImageChunk(ImageChunk.TYPE_DONT_CARE,
568 chunk_offset,
569 output_offset,
570 chunk_sz*self.block_size,
571 None,
572 None))
573 elif chunk_type == ImageChunk.TYPE_CRC32:
574 if data_sz != 4:
575 raise ValueError('CRC32 chunk should have 4 bytes of CRC, but '
576 'this has {}'.format(data_sz))
577 self._image.read(4)
578 else:
579 raise ValueError('Unknown chunk type {}'.format(chunk_type))
580
581 offset += chunk_sz
582 output_offset += chunk_sz*self.block_size
583
584 # Record where sparse data end.
585 self._sparse_end = self._image.tell()
586
587 # Now that we've traversed all chunks, sanity check.
588 if self._num_total_blocks != offset:
589 raise ValueError('The header said we should have {} output blocks, '
590 'but we saw {}'.format(self._num_total_blocks, offset))
591 junk_len = len(self._image.read())
592 if junk_len > 0:
593 raise ValueError('There were {} bytes of extra data at the end of the '
594 'file.'.format(junk_len))
595
596 # Assign |image_size| and |care_size| attributes.
597 self.image_size = output_offset
598 if last_section_was_dont_care:
599 self.care_size = last_dont_care_section_output_offset
600 else:
601 self.care_size = output_offset
602
603 # This is used when bisecting in read() to find the initial slice.
604 self._chunk_output_offsets = [i.output_offset for i in self._chunks]
605
606 self.is_sparse = True
607
608 def _update_chunks_and_blocks(self):
609 """Helper function to update the image header.
610
611 The the |total_chunks| and |total_blocks| fields in the header
612 will be set to value of the |_num_total_blocks| and
613 |_num_total_chunks| attributes.
614
615 """
616 self._image.seek(self.NUM_CHUNKS_AND_BLOCKS_OFFSET, os.SEEK_SET)
617 self._image.write(struct.pack(self.NUM_CHUNKS_AND_BLOCKS_FORMAT,
618 self._num_total_blocks,
619 self._num_total_chunks))
620
621 def append_dont_care(self, num_bytes):
622 """Appends a DONT_CARE chunk to the sparse file.
623
624 The given number of bytes must be a multiple of the block size.
625
626 Arguments:
627 num_bytes: Size in number of bytes of the DONT_CARE chunk.
628 """
629 assert num_bytes % self.block_size == 0
630
631 if not self.is_sparse:
632 self._image.seek(0, os.SEEK_END)
633 # This is more efficient that writing NUL bytes since it'll add
634 # a hole on file systems that support sparse files (native
635 # sparse, not Android sparse).
636 self._image.truncate(self._image.tell() + num_bytes)
637 self._read_header()
638 return
639
640 self._num_total_chunks += 1
641 self._num_total_blocks += num_bytes / self.block_size
642 self._update_chunks_and_blocks()
643
644 self._image.seek(self._sparse_end, os.SEEK_SET)
645 self._image.write(struct.pack(ImageChunk.FORMAT,
646 ImageChunk.TYPE_DONT_CARE,
647 0, # Reserved
648 num_bytes / self.block_size,
649 struct.calcsize(ImageChunk.FORMAT)))
650 self._read_header()
651
652 def append_raw(self, data):
653 """Appends a RAW chunk to the sparse file.
654
655 The length of the given data must be a multiple of the block size.
656
657 Arguments:
658 data: Data to append.
659 """
660 assert len(data) % self.block_size == 0
661
662 if not self.is_sparse:
663 self._image.seek(0, os.SEEK_END)
664 self._image.write(data)
665 self._read_header()
666 return
667
668 self._num_total_chunks += 1
669 self._num_total_blocks += len(data) / self.block_size
670 self._update_chunks_and_blocks()
671
672 self._image.seek(self._sparse_end, os.SEEK_SET)
673 self._image.write(struct.pack(ImageChunk.FORMAT,
674 ImageChunk.TYPE_RAW,
675 0, # Reserved
676 len(data) / self.block_size,
677 len(data) +
678 struct.calcsize(ImageChunk.FORMAT)))
679 self._image.write(data)
680 self._read_header()
681
682 def append_fill(self, fill_data, size):
683 """Appends a fill chunk to the sparse file.
684
685 The total length of the fill data must be a multiple of the block size.
686
687 Arguments:
688 fill_data: Fill data to append - must be four bytes.
689 size: Number of chunk - must be a multiple of four and the block size.
690 """
691 assert len(fill_data) == 4
692 assert size % 4 == 0
693 assert size % self.block_size == 0
694
695 if not self.is_sparse:
696 self._image.seek(0, os.SEEK_END)
697 self._image.write(fill_data * (size/4))
698 self._read_header()
699 return
700
701 self._num_total_chunks += 1
702 self._num_total_blocks += size / self.block_size
703 self._update_chunks_and_blocks()
704
705 self._image.seek(self._sparse_end, os.SEEK_SET)
706 self._image.write(struct.pack(ImageChunk.FORMAT,
707 ImageChunk.TYPE_FILL,
708 0, # Reserved
709 size / self.block_size,
710 4 + struct.calcsize(ImageChunk.FORMAT)))
711 self._image.write(fill_data)
712 self._read_header()
713
714 def seek(self, offset):
715 """Sets the cursor position for reading from unsparsified file.
716
717 Arguments:
718 offset: Offset to seek to from the beginning of the file.
719 """
720 self._file_pos = offset
721
722 def read(self, size):
723 """Reads data from the unsparsified file.
724
725 This method may return fewer than |size| bytes of data if the end
726 of the file was encountered.
727
728 The file cursor for reading is advanced by the number of bytes
729 read.
730
731 Arguments:
732 size: Number of bytes to read.
733
734 Returns:
735 The data.
736
737 """
738 if not self.is_sparse:
739 self._image.seek(self._file_pos)
740 data = self._image.read(size)
741 self._file_pos += len(data)
742 return data
743
744 # Iterate over all chunks.
745 chunk_idx = bisect.bisect_right(self._chunk_output_offsets,
746 self._file_pos) - 1
747 data = bytearray()
748 to_go = size
749 while to_go > 0:
750 chunk = self._chunks[chunk_idx]
751 chunk_pos_offset = self._file_pos - chunk.output_offset
752 chunk_pos_to_go = min(chunk.output_size - chunk_pos_offset, to_go)
753
754 if chunk.chunk_type == ImageChunk.TYPE_RAW:
755 self._image.seek(chunk.input_offset + chunk_pos_offset)
756 data.extend(self._image.read(chunk_pos_to_go))
757 elif chunk.chunk_type == ImageChunk.TYPE_FILL:
758 all_data = chunk.fill_data*(chunk_pos_to_go/len(chunk.fill_data) + 2)
759 offset_mod = chunk_pos_offset % len(chunk.fill_data)
760 data.extend(all_data[offset_mod:(offset_mod + chunk_pos_to_go)])
761 else:
762 assert chunk.chunk_type == ImageChunk.TYPE_DONT_CARE
763 data.extend('\0' * chunk_pos_to_go)
764
765 to_go -= chunk_pos_to_go
766 self._file_pos += chunk_pos_to_go
767 chunk_idx += 1
768 # Generate partial read in case of EOF.
769 if chunk_idx >= len(self._chunks):
770 break
771
772 return data
773
774 def tell(self):
775 """Returns the file cursor position for reading from unsparsified file.
776
777 Returns:
778 The file cursor position for reading.
779 """
780 return self._file_pos
781
782 def truncate(self, size):
783 """Truncates the unsparsified file.
784
785 Arguments:
786 size: Desired size of unsparsified file.
787
788 Raises:
789 ValueError: If desired size isn't a multiple of the block size.
790 """
791 if not self.is_sparse:
792 self._image.truncate(size)
793 self._read_header()
794 return
795
796 if size % self.block_size != 0:
797 raise ValueError('Cannot truncate to a size which is not a multiple '
798 'of the block size')
799
800 if size == self.image_size:
801 # Trivial where there's nothing to do.
802 return
803 elif size < self.image_size:
804 chunk_idx = bisect.bisect_right(self._chunk_output_offsets, size) - 1
805 chunk = self._chunks[chunk_idx]
806 if chunk.output_offset != size:
807 # Truncation in the middle of a trunk - need to keep the chunk
808 # and modify it.
809 chunk_idx_for_update = chunk_idx + 1
810 num_to_keep = size - chunk.output_offset
811 assert num_to_keep % self.block_size == 0
812 if chunk.chunk_type == ImageChunk.TYPE_RAW:
813 truncate_at = (chunk.chunk_offset +
814 struct.calcsize(ImageChunk.FORMAT) + num_to_keep)
815 data_sz = num_to_keep
816 elif chunk.chunk_type == ImageChunk.TYPE_FILL:
817 truncate_at = (chunk.chunk_offset +
818 struct.calcsize(ImageChunk.FORMAT) + 4)
819 data_sz = 4
820 else:
821 assert chunk.chunk_type == ImageChunk.TYPE_DONT_CARE
822 truncate_at = chunk.chunk_offset + struct.calcsize(ImageChunk.FORMAT)
823 data_sz = 0
824 chunk_sz = num_to_keep/self.block_size
825 total_sz = data_sz + struct.calcsize(ImageChunk.FORMAT)
826 self._image.seek(chunk.chunk_offset)
827 self._image.write(struct.pack(ImageChunk.FORMAT,
828 chunk.chunk_type,
829 0, # Reserved
830 chunk_sz,
831 total_sz))
832 chunk.output_size = num_to_keep
833 else:
834 # Truncation at trunk boundary.
835 truncate_at = chunk.chunk_offset
836 chunk_idx_for_update = chunk_idx
837
838 self._num_total_chunks = chunk_idx_for_update
839 self._num_total_blocks = 0
840 for i in range(0, chunk_idx_for_update):
841 self._num_total_blocks += self._chunks[i].output_size / self.block_size
842 self._update_chunks_and_blocks()
843 self._image.truncate(truncate_at)
844
845 # We've modified the file so re-read all data.
846 self._read_header()
847 else:
848 # Truncating to grow - just add a DONT_CARE section.
849 self.append_dont_care(size - self.image_size)
850
851
David Zeuthen21e95262016-07-27 17:58:40 -0400852class AvbDescriptor(object):
853 """Class for AVB descriptor.
854
855 See the |AvbDescriptor| C struct for more information.
856
857 Attributes:
858 tag: The tag identifying what kind of descriptor this is.
859 data: The data in the descriptor.
860 """
861
862 SIZE = 16
863 FORMAT_STRING = ('!QQ') # tag, num_bytes_following (descriptor header)
864
865 def __init__(self, data):
866 """Initializes a new property descriptor.
867
868 Arguments:
869 data: If not None, must be a bytearray().
870
871 Raises:
872 LookupError: If the given descriptor is malformed.
873 """
874 assert struct.calcsize(self.FORMAT_STRING) == self.SIZE
875
876 if data:
877 (self.tag, num_bytes_following) = (
878 struct.unpack(self.FORMAT_STRING, data[0:self.SIZE]))
879 self.data = data[self.SIZE:self.SIZE + num_bytes_following]
880 else:
881 self.tag = None
882 self.data = None
883
884 def print_desc(self, o):
885 """Print the descriptor.
886
887 Arguments:
888 o: The object to write the output to.
889 """
890 o.write(' Unknown descriptor:\n')
891 o.write(' Tag: {}\n'.format(self.tag))
892 if len(self.data) < 256:
893 o.write(' Data: {} ({} bytes)\n'.format(
894 repr(str(self.data)), len(self.data)))
895 else:
896 o.write(' Data: {} bytes\n'.format(len(self.data)))
897
898 def encode(self):
899 """Serializes the descriptor.
900
901 Returns:
902 A bytearray() with the descriptor data.
903 """
904 num_bytes_following = len(self.data)
905 nbf_with_padding = round_to_multiple(num_bytes_following, 8)
906 padding_size = nbf_with_padding - num_bytes_following
907 desc = struct.pack(self.FORMAT_STRING, self.tag, nbf_with_padding)
908 padding = struct.pack(str(padding_size) + 'x')
909 ret = desc + self.data + padding
910 return bytearray(ret)
911
912
913class AvbPropertyDescriptor(AvbDescriptor):
914 """A class for property descriptors.
915
916 See the |AvbPropertyDescriptor| C struct for more information.
917
918 Attributes:
919 key: The key.
920 value: The key.
921 """
922
923 TAG = 0
924 SIZE = 32
925 FORMAT_STRING = ('!QQ' # tag, num_bytes_following (descriptor header)
926 'Q' # key size (bytes)
927 'Q') # value size (bytes)
928
929 def __init__(self, data=None):
930 """Initializes a new property descriptor.
931
932 Arguments:
933 data: If not None, must be a bytearray of size |SIZE|.
934
935 Raises:
936 LookupError: If the given descriptor is malformed.
937 """
938 AvbDescriptor.__init__(self, None)
939 assert struct.calcsize(self.FORMAT_STRING) == self.SIZE
940
941 if data:
942 (tag, num_bytes_following, key_size,
943 value_size) = struct.unpack(self.FORMAT_STRING, data[0:self.SIZE])
944 expected_size = round_to_multiple(
945 self.SIZE - 16 + key_size + 1 + value_size + 1, 8)
946 if tag != self.TAG or num_bytes_following != expected_size:
947 raise LookupError('Given data does not look like a property '
948 'descriptor.')
949 self.key = data[self.SIZE:(self.SIZE + key_size)]
950 self.value = data[(self.SIZE + key_size + 1):(self.SIZE + key_size + 1 +
951 value_size)]
952 else:
953 self.key = ''
954 self.value = ''
955
956 def print_desc(self, o):
957 """Print the descriptor.
958
959 Arguments:
960 o: The object to write the output to.
961 """
962 if len(self.value) < 256:
963 o.write(' Prop: {} -> {}\n'.format(self.key, repr(str(self.value))))
964 else:
965 o.write(' Prop: {} -> ({} bytes)\n'.format(self.key, len(self.value)))
966
967 def encode(self):
968 """Serializes the descriptor.
969
970 Returns:
971 A bytearray() with the descriptor data.
972 """
973 num_bytes_following = self.SIZE + len(self.key) + len(self.value) + 2 - 16
974 nbf_with_padding = round_to_multiple(num_bytes_following, 8)
975 padding_size = nbf_with_padding - num_bytes_following
976 desc = struct.pack(self.FORMAT_STRING, self.TAG, nbf_with_padding,
977 len(self.key), len(self.value))
978 padding = struct.pack(str(padding_size) + 'x')
979 ret = desc + self.key + '\0' + self.value + '\0' + padding
980 return bytearray(ret)
981
982
983class AvbHashtreeDescriptor(AvbDescriptor):
984 """A class for hashtree descriptors.
985
986 See the |AvbHashtreeDescriptor| C struct for more information.
987
988 Attributes:
989 dm_verity_version: dm-verity version used.
990 image_size: Size of the image, after rounding up to |block_size|.
991 tree_offset: Offset of the hash tree in the file.
992 tree_size: Size of the tree.
993 data_block_size: Data block size
994 hash_block_size: Hash block size
995 hash_algorithm: Hash algorithm used.
996 partition_name: Partition name.
997 salt: Salt used.
998 root_digest: Root digest.
999 """
1000
1001 TAG = 1
1002 SIZE = 96
1003 FORMAT_STRING = ('!QQ' # tag, num_bytes_following (descriptor header)
1004 'L' # dm-verity version used
1005 'Q' # image size (bytes)
1006 'Q' # tree offset (bytes)
1007 'Q' # tree size (bytes)
1008 'L' # data block size (bytes)
1009 'L' # hash block size (bytes)
1010 '32s' # hash algorithm used
1011 'L' # partition name (bytes)
1012 'L' # salt length (bytes)
1013 'L') # root digest length (bytes)
1014
1015 def __init__(self, data=None):
1016 """Initializes a new hashtree descriptor.
1017
1018 Arguments:
1019 data: If not None, must be a bytearray of size |SIZE|.
1020
1021 Raises:
1022 LookupError: If the given descriptor is malformed.
1023 """
1024 AvbDescriptor.__init__(self, None)
1025 assert struct.calcsize(self.FORMAT_STRING) == self.SIZE
1026
1027 if data:
1028 (tag, num_bytes_following, self.dm_verity_version, self.image_size,
1029 self.tree_offset, self.tree_size, self.data_block_size,
1030 self.hash_block_size, self.hash_algorithm, partition_name_len, salt_len,
1031 root_digest_len) = struct.unpack(self.FORMAT_STRING, data[0:self.SIZE])
1032 expected_size = round_to_multiple(
1033 self.SIZE - 16 + partition_name_len + salt_len + root_digest_len, 8)
1034 if tag != self.TAG or num_bytes_following != expected_size:
1035 raise LookupError('Given data does not look like a hashtree '
1036 'descriptor.')
1037 # Nuke NUL-bytes at the end.
1038 self.hash_algorithm = self.hash_algorithm.split('\0', 1)[0]
1039 o = 0
1040 self.partition_name = str(data[(self.SIZE + o):(self.SIZE + o +
1041 partition_name_len)])
1042 # Validate UTF-8 - decode() raises UnicodeDecodeError if not valid UTF-8.
1043 self.partition_name.decode('utf-8')
1044 o += partition_name_len
1045 self.salt = data[(self.SIZE + o):(self.SIZE + o + salt_len)]
1046 o += salt_len
1047 self.root_digest = data[(self.SIZE + o):(self.SIZE + o + root_digest_len)]
1048 if root_digest_len != len(hashlib.new(name=self.hash_algorithm).digest()):
1049 raise LookupError('root_digest_len doesn\'t match hash algorithm')
1050
1051 else:
1052 self.dm_verity_version = 0
1053 self.image_size = 0
1054 self.tree_offset = 0
1055 self.tree_size = 0
1056 self.data_block_size = 0
1057 self.hash_block_size = 0
1058 self.hash_algorithm = ''
1059 self.partition_name = ''
1060 self.salt = bytearray()
1061 self.root_digest = bytearray()
1062
1063 def print_desc(self, o):
1064 """Print the descriptor.
1065
1066 Arguments:
1067 o: The object to write the output to.
1068 """
1069 o.write(' Hashtree descriptor:\n')
1070 o.write(' Version of dm-verity: {}\n'.format(self.dm_verity_version))
1071 o.write(' Image Size: {} bytes\n'.format(self.image_size))
1072 o.write(' Tree Offset: {}\n'.format(self.tree_offset))
1073 o.write(' Tree Size: {} bytes\n'.format(self.tree_size))
1074 o.write(' Data Block Size: {} bytes\n'.format(
1075 self.data_block_size))
1076 o.write(' Hash Block Size: {} bytes\n'.format(
1077 self.hash_block_size))
1078 o.write(' Hash Algorithm: {}\n'.format(self.hash_algorithm))
1079 o.write(' Partition Name: {}\n'.format(self.partition_name))
1080 o.write(' Salt: {}\n'.format(str(self.salt).encode(
1081 'hex')))
1082 o.write(' Root Digest: {}\n'.format(str(
1083 self.root_digest).encode('hex')))
1084
1085 def encode(self):
1086 """Serializes the descriptor.
1087
1088 Returns:
1089 A bytearray() with the descriptor data.
1090 """
1091 encoded_name = self.partition_name.encode('utf-8')
1092 num_bytes_following = (self.SIZE + len(encoded_name) + len(self.salt) +
1093 len(self.root_digest) - 16)
1094 nbf_with_padding = round_to_multiple(num_bytes_following, 8)
1095 padding_size = nbf_with_padding - num_bytes_following
1096 desc = struct.pack(self.FORMAT_STRING, self.TAG, nbf_with_padding,
1097 self.dm_verity_version, self.image_size,
1098 self.tree_offset, self.tree_size, self.data_block_size,
1099 self.hash_block_size, self.hash_algorithm,
1100 len(encoded_name), len(self.salt), len(self.root_digest))
1101 padding = struct.pack(str(padding_size) + 'x')
1102 ret = desc + encoded_name + self.salt + self.root_digest + padding
1103 return bytearray(ret)
1104
1105
1106class AvbHashDescriptor(AvbDescriptor):
1107 """A class for hash descriptors.
1108
1109 See the |AvbHashDescriptor| C struct for more information.
1110
1111 Attributes:
1112 image_size: Image size, in bytes.
1113 hash_algorithm: Hash algorithm used.
1114 partition_name: Partition name.
1115 salt: Salt used.
1116 digest: The hash value of salt and data combined.
1117 """
1118
1119 TAG = 2
1120 SIZE = 68
1121 FORMAT_STRING = ('!QQ' # tag, num_bytes_following (descriptor header)
1122 'Q' # image size (bytes)
1123 '32s' # hash algorithm used
1124 'L' # partition name (bytes)
1125 'L' # salt length (bytes)
1126 'L') # digest length (bytes)
1127
1128 def __init__(self, data=None):
1129 """Initializes a new hash descriptor.
1130
1131 Arguments:
1132 data: If not None, must be a bytearray of size |SIZE|.
1133
1134 Raises:
1135 LookupError: If the given descriptor is malformed.
1136 """
1137 AvbDescriptor.__init__(self, None)
1138 assert struct.calcsize(self.FORMAT_STRING) == self.SIZE
1139
1140 if data:
1141 (tag, num_bytes_following, self.image_size, self.hash_algorithm,
1142 partition_name_len, salt_len,
1143 digest_len) = struct.unpack(self.FORMAT_STRING, data[0:self.SIZE])
1144 expected_size = round_to_multiple(
1145 self.SIZE - 16 + partition_name_len + salt_len + digest_len, 8)
1146 if tag != self.TAG or num_bytes_following != expected_size:
1147 raise LookupError('Given data does not look like a hash ' 'descriptor.')
1148 # Nuke NUL-bytes at the end.
1149 self.hash_algorithm = self.hash_algorithm.split('\0', 1)[0]
1150 o = 0
1151 self.partition_name = str(data[(self.SIZE + o):(self.SIZE + o +
1152 partition_name_len)])
1153 # Validate UTF-8 - decode() raises UnicodeDecodeError if not valid UTF-8.
1154 self.partition_name.decode('utf-8')
1155 o += partition_name_len
1156 self.salt = data[(self.SIZE + o):(self.SIZE + o + salt_len)]
1157 o += salt_len
1158 self.digest = data[(self.SIZE + o):(self.SIZE + o + digest_len)]
1159 if digest_len != len(hashlib.new(name=self.hash_algorithm).digest()):
1160 raise LookupError('digest_len doesn\'t match hash algorithm')
1161
1162 else:
1163 self.image_size = 0
1164 self.hash_algorithm = ''
1165 self.partition_name = ''
1166 self.salt = bytearray()
1167 self.digest = bytearray()
1168
1169 def print_desc(self, o):
1170 """Print the descriptor.
1171
1172 Arguments:
1173 o: The object to write the output to.
1174 """
1175 o.write(' Hash descriptor:\n')
1176 o.write(' Image Size: {} bytes\n'.format(self.image_size))
1177 o.write(' Hash Algorithm: {}\n'.format(self.hash_algorithm))
1178 o.write(' Partition Name: {}\n'.format(self.partition_name))
1179 o.write(' Salt: {}\n'.format(str(self.salt).encode(
1180 'hex')))
1181 o.write(' Digest: {}\n'.format(str(self.digest).encode(
1182 'hex')))
1183
1184 def encode(self):
1185 """Serializes the descriptor.
1186
1187 Returns:
1188 A bytearray() with the descriptor data.
1189 """
1190 encoded_name = self.partition_name.encode('utf-8')
1191 num_bytes_following = (
1192 self.SIZE + len(encoded_name) + len(self.salt) + len(self.digest) - 16)
1193 nbf_with_padding = round_to_multiple(num_bytes_following, 8)
1194 padding_size = nbf_with_padding - num_bytes_following
1195 desc = struct.pack(self.FORMAT_STRING, self.TAG, nbf_with_padding,
1196 self.image_size, self.hash_algorithm, len(encoded_name),
1197 len(self.salt), len(self.digest))
1198 padding = struct.pack(str(padding_size) + 'x')
1199 ret = desc + encoded_name + self.salt + self.digest + padding
1200 return bytearray(ret)
1201
1202
1203class AvbKernelCmdlineDescriptor(AvbDescriptor):
1204 """A class for kernel command-line descriptors.
1205
1206 See the |AvbKernelCmdlineDescriptor| C struct for more information.
1207
1208 Attributes:
1209 kernel_cmdline: The kernel command-line.
1210 """
1211
1212 TAG = 3
1213 SIZE = 20
1214 FORMAT_STRING = ('!QQ' # tag, num_bytes_following (descriptor header)
1215 'L') # cmdline length (bytes)
1216
1217 def __init__(self, data=None):
1218 """Initializes a new kernel cmdline descriptor.
1219
1220 Arguments:
1221 data: If not None, must be a bytearray of size |SIZE|.
1222
1223 Raises:
1224 LookupError: If the given descriptor is malformed.
1225 """
1226 AvbDescriptor.__init__(self, None)
1227 assert struct.calcsize(self.FORMAT_STRING) == self.SIZE
1228
1229 if data:
1230 (tag, num_bytes_following, kernel_cmdline_length) = (
1231 struct.unpack(self.FORMAT_STRING, data[0:self.SIZE]))
1232 expected_size = round_to_multiple(self.SIZE - 16 + kernel_cmdline_length,
1233 8)
1234 if tag != self.TAG or num_bytes_following != expected_size:
1235 raise LookupError('Given data does not look like a kernel cmdline '
1236 'descriptor.')
1237 # Nuke NUL-bytes at the end.
1238 self.kernel_cmdline = str(data[self.SIZE:(self.SIZE +
1239 kernel_cmdline_length)])
1240 # Validate UTF-8 - decode() raises UnicodeDecodeError if not valid UTF-8.
1241 self.kernel_cmdline.decode('utf-8')
1242 else:
1243 self.kernel_cmdline = ''
1244
1245 def print_desc(self, o):
1246 """Print the descriptor.
1247
1248 Arguments:
1249 o: The object to write the output to.
1250 """
1251 o.write(' Kernel Cmdline descriptor:\n')
1252 o.write(' Kernel Cmdline: {}\n'.format(repr(
1253 self.kernel_cmdline)))
1254
1255 def encode(self):
1256 """Serializes the descriptor.
1257
1258 Returns:
1259 A bytearray() with the descriptor data.
1260 """
1261 encoded_str = self.kernel_cmdline.encode('utf-8')
1262 num_bytes_following = (self.SIZE + len(encoded_str) - 16)
1263 nbf_with_padding = round_to_multiple(num_bytes_following, 8)
1264 padding_size = nbf_with_padding - num_bytes_following
1265 desc = struct.pack(self.FORMAT_STRING, self.TAG, nbf_with_padding,
1266 len(encoded_str))
1267 padding = struct.pack(str(padding_size) + 'x')
1268 ret = desc + encoded_str + padding
1269 return bytearray(ret)
1270
1271
1272class AvbChainPartitionDescriptor(AvbDescriptor):
1273 """A class for chained partition descriptors.
1274
1275 See the |AvbChainPartitionDescriptor| C struct for more information.
1276
1277 Attributes:
1278 rollback_index_slot: The rollback index slot to use.
1279 partition_name: Partition name.
1280 public_key: Bytes for the public key.
1281 """
1282
1283 TAG = 4
1284 SIZE = 28
1285 FORMAT_STRING = ('!QQ' # tag, num_bytes_following (descriptor header)
1286 'L' # rollback_index_slot
1287 'L' # partition_name_size (bytes)
1288 'L') # public_key_size (bytes)
1289
1290 def __init__(self, data=None):
1291 """Initializes a new chain partition descriptor.
1292
1293 Arguments:
1294 data: If not None, must be a bytearray of size |SIZE|.
1295
1296 Raises:
1297 LookupError: If the given descriptor is malformed.
1298 """
1299 AvbDescriptor.__init__(self, None)
1300 assert struct.calcsize(self.FORMAT_STRING) == self.SIZE
1301
1302 if data:
1303 (tag, num_bytes_following, self.rollback_index_slot, partition_name_len,
1304 public_key_len) = struct.unpack(self.FORMAT_STRING, data[0:self.SIZE])
1305 expected_size = round_to_multiple(
1306 self.SIZE - 16 + partition_name_len + public_key_len, 8)
1307 if tag != self.TAG or num_bytes_following != expected_size:
1308 raise LookupError('Given data does not look like a chain partition '
1309 'descriptor.')
1310 o = 0
1311 self.partition_name = str(data[(self.SIZE + o):(self.SIZE + o +
1312 partition_name_len)])
1313 # Validate UTF-8 - decode() raises UnicodeDecodeError if not valid UTF-8.
1314 self.partition_name.decode('utf-8')
1315 o += partition_name_len
1316 self.public_key = data[(self.SIZE + o):(self.SIZE + o + public_key_len)]
1317
1318 else:
1319 self.rollback_index_slot = 0
1320 self.partition_name = ''
1321 self.public_key = bytearray()
1322
1323 def print_desc(self, o):
1324 """Print the descriptor.
1325
1326 Arguments:
1327 o: The object to write the output to.
1328 """
1329 o.write(' Chain Partition descriptor:\n')
1330 o.write(' Partition Name: {}\n'.format(self.partition_name))
1331 o.write(' Rollback Index Slot: {}\n'.format(
1332 self.rollback_index_slot))
1333 # Just show the SHA1 of the key, for size reasons.
1334 hexdig = hashlib.sha1(self.public_key).hexdigest()
1335 o.write(' Public key (sha1): {}\n'.format(hexdig))
1336
1337 def encode(self):
1338 """Serializes the descriptor.
1339
1340 Returns:
1341 A bytearray() with the descriptor data.
1342 """
1343 encoded_name = self.partition_name.encode('utf-8')
1344 num_bytes_following = (
1345 self.SIZE + len(encoded_name) + len(self.public_key) - 16)
1346 nbf_with_padding = round_to_multiple(num_bytes_following, 8)
1347 padding_size = nbf_with_padding - num_bytes_following
1348 desc = struct.pack(self.FORMAT_STRING, self.TAG, nbf_with_padding,
1349 self.rollback_index_slot, len(encoded_name),
1350 len(self.public_key))
1351 padding = struct.pack(str(padding_size) + 'x')
1352 ret = desc + encoded_name + self.public_key + padding
1353 return bytearray(ret)
1354
1355
1356DESCRIPTOR_CLASSES = [
1357 AvbPropertyDescriptor, AvbHashtreeDescriptor, AvbHashDescriptor,
1358 AvbKernelCmdlineDescriptor, AvbChainPartitionDescriptor
1359]
1360
1361
1362def parse_descriptors(data):
1363 """Parses a blob of data into descriptors.
1364
1365 Arguments:
1366 data: A bytearray() with encoded descriptors.
1367
1368 Returns:
1369 A list of instances of objects derived from AvbDescriptor. For
1370 unknown descriptors, the class AvbDescriptor is used.
1371 """
1372 o = 0
1373 ret = []
1374 while o < len(data):
1375 tag, nb_following = struct.unpack('!2Q', data[o:o + 16])
1376 if tag < len(DESCRIPTOR_CLASSES):
1377 c = DESCRIPTOR_CLASSES[tag]
1378 else:
1379 c = AvbDescriptor
1380 ret.append(c(bytearray(data[o:o + 16 + nb_following])))
1381 o += 16 + nb_following
1382 return ret
1383
1384
1385class AvbFooter(object):
1386 """A class for parsing and writing footers.
1387
1388 Footers are stored at the end of partitions and point to where the
1389 AvbVBMeta blob is located. They also contain the original size of
1390 the image before AVB information was added.
1391
1392 Attributes:
1393 magic: Magic for identifying the footer, see |MAGIC|.
1394 version_major: The major version of avbtool that wrote the footer.
1395 version_minor: The minor version of avbtool that wrote the footer.
1396 original_image_size: Original image size.
1397 vbmeta_offset: Offset of where the AvbVBMeta blob is stored.
1398 vbmeta_size: Size of the AvbVBMeta blob.
1399 """
1400
1401 MAGIC = 'AVBf'
1402 SIZE = 64
1403 RESERVED = 28
1404 FORMAT_STRING = ('!4s2L' # magic, 2 x version.
1405 'Q' # Original image size.
1406 'Q' # Offset of VBMeta blob.
1407 'Q' + # Size of VBMeta blob.
1408 str(RESERVED) + 'x') # padding for reserved bytes
1409
1410 def __init__(self, data=None):
1411 """Initializes a new footer object.
1412
1413 Arguments:
1414 data: If not None, must be a bytearray of size 4096.
1415
1416 Raises:
1417 LookupError: If the given footer is malformed.
1418 struct.error: If the given data has no footer.
1419 """
1420 assert struct.calcsize(self.FORMAT_STRING) == self.SIZE
1421
1422 if data:
1423 (self.magic, self.version_major, self.version_minor,
1424 self.original_image_size, self.vbmeta_offset,
1425 self.vbmeta_size) = struct.unpack(self.FORMAT_STRING, data)
1426 if self.magic != self.MAGIC:
1427 raise LookupError('Given data does not look like a Brillo footer.')
1428 else:
1429 self.magic = self.MAGIC
1430 self.version_major = AVB_VERSION_MAJOR
1431 self.version_minor = AVB_VERSION_MINOR
1432 self.original_image_size = 0
1433 self.vbmeta_offset = 0
1434 self.vbmeta_size = 0
1435
David Zeuthena4fee8b2016-08-22 15:20:43 -04001436 def encode(self):
1437 """Gets a string representing the binary encoding of the footer.
David Zeuthen21e95262016-07-27 17:58:40 -04001438
David Zeuthena4fee8b2016-08-22 15:20:43 -04001439 Returns:
1440 A bytearray() with a binary representation of the footer.
David Zeuthen21e95262016-07-27 17:58:40 -04001441 """
David Zeuthena4fee8b2016-08-22 15:20:43 -04001442 return struct.pack(self.FORMAT_STRING, self.magic, self.version_major,
1443 self.version_minor, self.original_image_size,
1444 self.vbmeta_offset, self.vbmeta_size)
David Zeuthen21e95262016-07-27 17:58:40 -04001445
1446
1447class AvbVBMetaHeader(object):
1448 """A class for parsing and writing Brillo Verified Boot vbmeta images.
1449
1450 Attributes:
1451 The attributes correspond to the |AvbVBMetaHeader| struct
1452 defined in avb_vbmeta_header.h.
1453 """
1454
1455 SIZE = 256
1456
1457 # Keep in sync with |reserved| field of |AvbVBMetaImageHeader|.
1458 RESERVED = 152
1459
1460 # Keep in sync with |AvbVBMetaImageHeader|.
1461 FORMAT_STRING = ('!4s2L' # magic, 2 x version
1462 '2Q' # 2 x block size
1463 'L' # algorithm type
1464 '2Q' # offset, size (hash)
1465 '2Q' # offset, size (signature)
1466 '2Q' # offset, size (public key)
1467 '2Q' # offset, size (descriptors)
1468 'Q' + # rollback_index
1469 str(RESERVED) + 'x') # padding for reserved bytes
1470
1471 def __init__(self, data=None):
1472 """Initializes a new header object.
1473
1474 Arguments:
1475 data: If not None, must be a bytearray of size 8192.
1476
1477 Raises:
1478 Exception: If the given data is malformed.
1479 """
1480 assert struct.calcsize(self.FORMAT_STRING) == self.SIZE
1481
1482 if data:
1483 (self.magic, self.header_version_major, self.header_version_minor,
1484 self.authentication_data_block_size, self.auxiliary_data_block_size,
1485 self.algorithm_type, self.hash_offset, self.hash_size,
1486 self.signature_offset, self.signature_size, self.public_key_offset,
1487 self.public_key_size, self.descriptors_offset, self.descriptors_size,
1488 self.rollback_index) = struct.unpack(self.FORMAT_STRING, data)
1489 # Nuke NUL-bytes at the end of the string.
1490 if self.magic != 'AVB0':
1491 raise AvbError('Given image does not look like a Brillo boot image')
1492 else:
1493 self.magic = 'AVB0'
1494 self.header_version_major = AVB_VERSION_MAJOR
1495 self.header_version_minor = AVB_VERSION_MINOR
1496 self.authentication_data_block_size = 0
1497 self.auxiliary_data_block_size = 0
1498 self.algorithm_type = 0
1499 self.hash_offset = 0
1500 self.hash_size = 0
1501 self.signature_offset = 0
1502 self.signature_size = 0
1503 self.public_key_offset = 0
1504 self.public_key_size = 0
1505 self.descriptors_offset = 0
1506 self.descriptors_size = 0
1507 self.rollback_index = 0
1508
1509 def save(self, output):
1510 """Serializes the header (256 bytes) to disk.
1511
1512 Arguments:
1513 output: The object to write the output to.
1514 """
1515 output.write(struct.pack(
1516 self.FORMAT_STRING, self.magic, self.header_version_major,
1517 self.header_version_minor, self.authentication_data_block_size,
1518 self.auxiliary_data_block_size, self.algorithm_type, self.hash_offset,
1519 self.hash_size, self.signature_offset, self.signature_size,
1520 self.public_key_offset, self.public_key_size, self.descriptors_offset,
1521 self.descriptors_size, self.rollback_index))
1522
1523 def encode(self):
1524 """Serializes the header (256) to a bytearray().
1525
1526 Returns:
1527 A bytearray() with the encoded header.
1528 """
1529 return struct.pack(self.FORMAT_STRING, self.magic,
1530 self.header_version_major, self.header_version_minor,
1531 self.authentication_data_block_size,
1532 self.auxiliary_data_block_size, self.algorithm_type,
1533 self.hash_offset, self.hash_size, self.signature_offset,
1534 self.signature_size, self.public_key_offset,
1535 self.public_key_size, self.descriptors_offset,
1536 self.descriptors_size, self.rollback_index)
1537
1538
1539class Avb(object):
1540 """Business logic for avbtool command-line tool."""
1541
David Zeuthena4fee8b2016-08-22 15:20:43 -04001542 def erase_footer(self, image_filename, keep_hashtree):
David Zeuthen21e95262016-07-27 17:58:40 -04001543 """Implements the 'erase_footer' command.
1544
1545 Arguments:
David Zeuthena4fee8b2016-08-22 15:20:43 -04001546 image_filename: File to erase a footer from.
David Zeuthen21e95262016-07-27 17:58:40 -04001547 keep_hashtree: If True, keep the hashtree around.
1548
1549 Raises:
1550 AvbError: If there's no footer in the image.
1551 """
1552
David Zeuthena4fee8b2016-08-22 15:20:43 -04001553 image = ImageHandler(image_filename)
1554
David Zeuthen21e95262016-07-27 17:58:40 -04001555 (footer, _, descriptors, _) = self._parse_image(image)
1556
1557 if not footer:
1558 raise AvbError('Given image does not have a footer.')
1559
1560 new_image_size = None
1561 if not keep_hashtree:
1562 new_image_size = footer.original_image_size
1563 else:
1564 # If requested to keep the hashtree, search for a hashtree
1565 # descriptor to figure out the location and size of the hashtree.
1566 for desc in descriptors:
1567 if isinstance(desc, AvbHashtreeDescriptor):
1568 # The hashtree is always just following the main data so the
1569 # new size is easily derived.
1570 new_image_size = desc.tree_offset + desc.tree_size
1571 break
1572 if not new_image_size:
1573 raise AvbError('Requested to keep hashtree but no hashtree '
1574 'descriptor was found.')
1575
1576 # And cut...
1577 image.truncate(new_image_size)
1578
David Zeuthena4fee8b2016-08-22 15:20:43 -04001579 def info_image(self, image_filename, output):
David Zeuthen21e95262016-07-27 17:58:40 -04001580 """Implements the 'info_image' command.
1581
1582 Arguments:
David Zeuthena4fee8b2016-08-22 15:20:43 -04001583 image_filename: Image file to get information from (file object).
David Zeuthen21e95262016-07-27 17:58:40 -04001584 output: Output file to write human-readable information to (file object).
1585 """
1586
David Zeuthena4fee8b2016-08-22 15:20:43 -04001587 image = ImageHandler(image_filename)
1588
David Zeuthen21e95262016-07-27 17:58:40 -04001589 o = output
1590
1591 (footer, header, descriptors, image_size) = self._parse_image(image)
1592
1593 if footer:
1594 o.write('Footer version: {}.{}\n'.format(footer.version_major,
1595 footer.version_minor))
1596 o.write('Image size: {} bytes\n'.format(image_size))
1597 o.write('Original image size: {} bytes\n'.format(
1598 footer.original_image_size))
1599 o.write('VBMeta offset: {}\n'.format(footer.vbmeta_offset))
1600 o.write('VBMeta size: {} bytes\n'.format(footer.vbmeta_size))
1601 o.write('--\n')
1602
1603 (alg_name, _) = lookup_algorithm_by_type(header.algorithm_type)
1604
David Zeuthena4fee8b2016-08-22 15:20:43 -04001605 o.write('VBMeta image version: {}.{}{}\n'.format(
1606 header.header_version_major, header.header_version_minor,
1607 ' (Sparse)' if image.is_sparse else ''))
David Zeuthen21e95262016-07-27 17:58:40 -04001608 o.write('Header Block: {} bytes\n'.format(AvbVBMetaHeader.SIZE))
1609 o.write('Authentication Block: {} bytes\n'.format(
1610 header.authentication_data_block_size))
1611 o.write('Auxiliary Block: {} bytes\n'.format(
1612 header.auxiliary_data_block_size))
1613 o.write('Algorithm: {}\n'.format(alg_name))
1614 o.write('Rollback Index: {}\n'.format(header.rollback_index))
1615
1616 # Print descriptors.
1617 num_printed = 0
1618 o.write('Descriptors:\n')
1619 for desc in descriptors:
1620 desc.print_desc(o)
1621 num_printed += 1
1622 if num_printed == 0:
1623 o.write(' (none)\n')
1624
1625 def _parse_image(self, image):
1626 """Gets information about an image.
1627
1628 The image can either be a vbmeta or an image with a footer.
1629
1630 Arguments:
David Zeuthena4fee8b2016-08-22 15:20:43 -04001631 image: An ImageHandler (vbmeta or footer) with a hashtree descriptor.
David Zeuthen21e95262016-07-27 17:58:40 -04001632
1633 Returns:
1634 A tuple where the first argument is a AvbFooter (None if there
1635 is no footer on the image), the second argument is a
1636 AvbVBMetaHeader, the third argument is a list of
1637 AvbDescriptor-derived instances, and the fourth argument is the
1638 size of |image|.
1639 """
David Zeuthena4fee8b2016-08-22 15:20:43 -04001640 assert isinstance(image, ImageHandler)
David Zeuthen21e95262016-07-27 17:58:40 -04001641 footer = None
David Zeuthena4fee8b2016-08-22 15:20:43 -04001642 image_size = image.care_size
David Zeuthen21e95262016-07-27 17:58:40 -04001643 image.seek(image_size - AvbFooter.SIZE)
1644 try:
1645 footer = AvbFooter(image.read(AvbFooter.SIZE))
1646 except (LookupError, struct.error):
1647 # Nope, just seek back to the start.
1648 image.seek(0)
1649
1650 vbmeta_offset = 0
1651 if footer:
1652 vbmeta_offset = footer.vbmeta_offset
1653
1654 image.seek(vbmeta_offset)
1655 h = AvbVBMetaHeader(image.read(AvbVBMetaHeader.SIZE))
1656
1657 auth_block_offset = vbmeta_offset + AvbVBMetaHeader.SIZE
1658 aux_block_offset = auth_block_offset + h.authentication_data_block_size
1659 desc_start_offset = aux_block_offset + h.descriptors_offset
1660 image.seek(desc_start_offset)
1661 descriptors = parse_descriptors(image.read(h.descriptors_size))
1662
1663 return footer, h, descriptors, image_size
1664
1665 def _get_cmdline_descriptor_for_dm_verity(self, image):
1666 """Generate kernel cmdline descriptor for dm-verity.
1667
1668 Arguments:
David Zeuthena4fee8b2016-08-22 15:20:43 -04001669 image: An ImageHandler (vbmeta or footer) with a hashtree descriptor.
David Zeuthen21e95262016-07-27 17:58:40 -04001670
1671 Returns:
1672 A AvbKernelCmdlineDescriptor with dm-verity kernel cmdline
1673 instructions for the hashtree.
1674
1675 Raises:
1676 AvbError: If |image| doesn't have a hashtree descriptor.
1677
1678 """
1679
1680 (_, _, descriptors, _) = self._parse_image(image)
1681
1682 ht = None
1683 for desc in descriptors:
1684 if isinstance(desc, AvbHashtreeDescriptor):
1685 ht = desc
1686 break
1687
1688 if not ht:
1689 raise AvbError('No hashtree descriptor in given image')
1690
1691 c = 'dm="1 vroot none ro 1,'
1692 c += '0 ' # start
1693 c += '{} '.format((ht.image_size / 512)) # size (# sectors)
1694 c += 'verity {} '.format(ht.dm_verity_version) # type and version
1695 c += 'PARTUUID=$(ANDROID_SYSTEM_PARTUUID) ' # data_dev
1696 c += 'PARTUUID=$(ANDROID_SYSTEM_PARTUUID) ' # hash_dev
1697 c += '{} '.format(ht.data_block_size) # data_block
1698 c += '{} '.format(ht.hash_block_size) # hash_block
1699 c += '{} '.format(ht.image_size / ht.data_block_size) # #blocks
1700 c += '{} '.format(ht.image_size / ht.data_block_size) # hash_offset
1701 c += '{} '.format(ht.hash_algorithm) # hash_alg
1702 c += '{} '.format(str(ht.root_digest).encode('hex')) # root_digest
1703 c += '{}'.format(str(ht.salt).encode('hex')) # salt
1704 c += '"'
1705
1706 desc = AvbKernelCmdlineDescriptor()
1707 desc.kernel_cmdline = c
1708 return desc
1709
1710 def make_vbmeta_image(self, output, chain_partitions, algorithm_name,
1711 key_path, rollback_index, props, props_from_file,
1712 kernel_cmdlines,
1713 generate_dm_verity_cmdline_from_hashtree,
1714 include_descriptors_from_image):
1715 """Implements the 'make_vbmeta_image' command.
1716
1717 Arguments:
1718 output: File to write the image to.
1719 chain_partitions: List of partitions to chain.
1720 algorithm_name: Name of algorithm to use.
1721 key_path: Path to key to use or None.
1722 rollback_index: The rollback index to use.
1723 props: Properties to insert (list of strings of the form 'key:value').
1724 props_from_file: Properties to insert (list of strings 'key:<path>').
1725 kernel_cmdlines: Kernel cmdlines to insert (list of strings).
1726 generate_dm_verity_cmdline_from_hashtree: None or file to generate from.
1727 include_descriptors_from_image: List of file objects with descriptors.
1728
1729 Raises:
1730 AvbError: If a chained partition is malformed.
1731 """
1732
1733 descriptors = []
1734
1735 # Insert chained partition descriptors.
1736 if chain_partitions:
1737 for cp in chain_partitions:
1738 cp_tokens = cp.split(':')
1739 if len(cp_tokens) != 3:
1740 raise AvbError('Malformed chained partition "{}".'.format(cp))
1741 desc = AvbChainPartitionDescriptor()
1742 desc.partition_name = cp_tokens[0]
1743 desc.rollback_index_slot = int(cp_tokens[1])
1744 if desc.rollback_index_slot < 1:
1745 raise AvbError('Rollback index slot must be 1 or larger.')
1746 file_path = cp_tokens[2]
1747 desc.public_key = open(file_path, 'rb').read()
1748 descriptors.append(desc)
1749
1750 vbmeta_blob = self._generate_vbmeta_blob(
1751 algorithm_name, key_path, descriptors, rollback_index, props,
1752 props_from_file, kernel_cmdlines,
1753 generate_dm_verity_cmdline_from_hashtree,
1754 include_descriptors_from_image)
1755
1756 # Write entire vbmeta blob (header, authentication, auxiliary).
1757 output.seek(0)
1758 output.write(vbmeta_blob)
1759
1760 def _generate_vbmeta_blob(self, algorithm_name, key_path, descriptors,
1761 rollback_index, props, props_from_file,
1762 kernel_cmdlines,
1763 generate_dm_verity_cmdline_from_hashtree,
1764 include_descriptors_from_image):
1765 """Generates a VBMeta blob.
1766
1767 This blob contains the header (struct AvbVBMetaHeader), the
1768 authentication data block (which contains the hash and signature
1769 for the header and auxiliary block), and the auxiliary block
1770 (which contains descriptors, the public key used, and other data).
1771
1772 The |key| parameter can |None| only if the |algorithm_name| is
1773 'NONE'.
1774
1775 Arguments:
1776 algorithm_name: The algorithm name as per the ALGORITHMS dict.
1777 key_path: The path to the .pem file used to sign the blob.
1778 descriptors: A list of descriptors to insert or None.
1779 rollback_index: The rollback index to use.
1780 props: Properties to insert (List of strings of the form 'key:value').
1781 props_from_file: Properties to insert (List of strings 'key:<path>').
1782 kernel_cmdlines: Kernel cmdlines to insert (list of strings).
1783 generate_dm_verity_cmdline_from_hashtree: None or file to generate
1784 dm-verity kernel cmdline from.
1785 include_descriptors_from_image: List of file objects for which
1786 to insert descriptors from.
1787
1788 Returns:
1789 A bytearray() with the VBMeta blob.
1790
1791 Raises:
1792 Exception: If the |algorithm_name| is not found, if no key has
1793 been given and the given algorithm requires one, or the key is
1794 of the wrong size.
1795
1796 """
1797 try:
1798 alg = ALGORITHMS[algorithm_name]
1799 except KeyError:
1800 raise AvbError('Unknown algorithm with name {}'.format(algorithm_name))
1801
1802 # Descriptors.
1803 encoded_descriptors = bytearray()
1804 if descriptors:
1805 for desc in descriptors:
1806 encoded_descriptors.extend(desc.encode())
1807
1808 # Add properties.
1809 if props:
1810 for prop in props:
1811 idx = prop.find(':')
1812 if idx == -1:
1813 raise AvbError('Malformed property "{}".'.format(prop))
1814 desc = AvbPropertyDescriptor()
1815 desc.key = prop[0:idx]
1816 desc.value = prop[(idx + 1):]
1817 encoded_descriptors.extend(desc.encode())
1818 if props_from_file:
1819 for prop in props_from_file:
1820 idx = prop.find(':')
1821 if idx == -1:
1822 raise AvbError('Malformed property "{}".'.format(prop))
1823 desc = AvbPropertyDescriptor()
1824 desc.key = prop[0:idx]
1825 desc.value = prop[(idx + 1):]
1826 file_path = prop[(idx + 1):]
1827 desc.value = open(file_path, 'rb').read()
1828 encoded_descriptors.extend(desc.encode())
1829
1830 # Add AvbKernelCmdline descriptor for dm-verity, if requested.
1831 if generate_dm_verity_cmdline_from_hashtree:
David Zeuthena4fee8b2016-08-22 15:20:43 -04001832 image_handler = ImageHandler(
1833 generate_dm_verity_cmdline_from_hashtree.name)
David Zeuthen21e95262016-07-27 17:58:40 -04001834 encoded_descriptors.extend(self._get_cmdline_descriptor_for_dm_verity(
David Zeuthena4fee8b2016-08-22 15:20:43 -04001835 image_handler).encode())
David Zeuthen21e95262016-07-27 17:58:40 -04001836
1837 # Add kernel command-lines.
1838 if kernel_cmdlines:
1839 for i in kernel_cmdlines:
1840 desc = AvbKernelCmdlineDescriptor()
1841 desc.kernel_cmdline = i
1842 encoded_descriptors.extend(desc.encode())
1843
1844 # Add descriptors from other images.
1845 if include_descriptors_from_image:
1846 for image in include_descriptors_from_image:
David Zeuthena4fee8b2016-08-22 15:20:43 -04001847 image_handler = ImageHandler(image.name)
1848 (_, _, image_descriptors, _) = self._parse_image(image_handler)
David Zeuthen21e95262016-07-27 17:58:40 -04001849 for desc in image_descriptors:
1850 encoded_descriptors.extend(desc.encode())
1851
1852 key = None
1853 encoded_key = bytearray()
1854 if alg.public_key_num_bytes > 0:
1855 if not key_path:
1856 raise AvbError('Key is required for algorithm {}'.format(
1857 algorithm_name))
1858 key = Crypto.PublicKey.RSA.importKey(open(key_path).read())
1859 encoded_key = encode_rsa_key(key)
1860 if len(encoded_key) != alg.public_key_num_bytes:
1861 raise AvbError('Key is wrong size for algorithm {}'.format(
1862 algorithm_name))
1863
1864 h = AvbVBMetaHeader()
1865
1866 # For the Auxiliary data block, descriptors are stored at offset 0
1867 # and the public key is immediately after that.
1868 h.auxiliary_data_block_size = round_to_multiple(
1869 len(encoded_descriptors) + len(encoded_key), 64)
1870 h.descriptors_offset = 0
1871 h.descriptors_size = len(encoded_descriptors)
1872 h.public_key_offset = h.descriptors_size
1873 h.public_key_size = len(encoded_key)
1874
1875 # For the Authentication data block, the hash is first and then
1876 # the signature.
1877 h.authentication_data_block_size = round_to_multiple(
1878 alg.hash_num_bytes + alg.public_key_num_bytes, 64)
1879 h.algorithm_type = alg.algorithm_type
1880 h.hash_offset = 0
1881 h.hash_size = alg.hash_num_bytes
1882 # Signature offset and size - it's stored right after the hash
1883 # (in Authentication data block).
1884 h.signature_offset = alg.hash_num_bytes
1885 h.signature_size = alg.signature_num_bytes
1886
1887 h.rollback_index = rollback_index
1888
1889 # Generate Header data block.
1890 header_data_blob = h.encode()
1891
1892 # Generate Auxiliary data block.
1893 aux_data_blob = bytearray()
1894 aux_data_blob.extend(encoded_descriptors)
1895 aux_data_blob.extend(encoded_key)
1896 padding_bytes = h.auxiliary_data_block_size - len(aux_data_blob)
1897 aux_data_blob.extend('\0' * padding_bytes)
1898
1899 # Calculate the hash.
1900 binary_hash = bytearray()
1901 binary_signature = bytearray()
1902 if algorithm_name != 'NONE':
1903 if algorithm_name[0:6] == 'SHA256':
1904 ha = hashlib.sha256()
1905 elif algorithm_name[0:6] == 'SHA512':
1906 ha = hashlib.sha512()
1907 else:
1908 raise AvbError('Unsupported algorithm {}.'.format(algorithm_name))
1909 ha.update(header_data_blob)
1910 ha.update(aux_data_blob)
1911 binary_hash.extend(ha.digest())
1912
1913 # Calculate the signature.
1914 p = subprocess.Popen(
1915 ['openssl', 'rsautl', '-sign', '-inkey', key_path, '-raw'],
1916 stdin=subprocess.PIPE,
1917 stdout=subprocess.PIPE,
1918 stderr=subprocess.PIPE)
1919 padding_and_hash = str(bytearray(alg.padding)) + binary_hash
1920 (pout, perr) = p.communicate(padding_and_hash)
1921 retcode = p.wait()
1922 if retcode != 0:
1923 raise AvbError('Error signing: {}'.format(perr))
1924 binary_signature.extend(pout)
1925
1926 # Generate Authentication data block.
1927 auth_data_blob = bytearray()
1928 auth_data_blob.extend(binary_hash)
1929 auth_data_blob.extend(binary_signature)
1930 padding_bytes = h.authentication_data_block_size - len(auth_data_blob)
1931 auth_data_blob.extend('\0' * padding_bytes)
1932
1933 return header_data_blob + auth_data_blob + aux_data_blob
1934
1935 def extract_public_key(self, key_path, output):
1936 """Implements the 'extract_public_key' command.
1937
1938 Arguments:
1939 key_path: The path to a RSA private key file.
1940 output: The file to write to.
1941 """
1942 key = Crypto.PublicKey.RSA.importKey(open(key_path).read())
1943 write_rsa_key(output, key)
1944
David Zeuthena4fee8b2016-08-22 15:20:43 -04001945 def add_hash_footer(self, image_filename, partition_size, partition_name,
David Zeuthen21e95262016-07-27 17:58:40 -04001946 hash_algorithm, salt, algorithm_name, key_path,
1947 rollback_index, props, props_from_file, kernel_cmdlines,
1948 generate_dm_verity_cmdline_from_hashtree,
1949 include_descriptors_from_image):
David Zeuthena4fee8b2016-08-22 15:20:43 -04001950 """Implementation of the add_hash_footer on unsparse images.
David Zeuthen21e95262016-07-27 17:58:40 -04001951
1952 Arguments:
David Zeuthena4fee8b2016-08-22 15:20:43 -04001953 image_filename: File to add the footer to.
David Zeuthen21e95262016-07-27 17:58:40 -04001954 partition_size: Size of partition.
1955 partition_name: Name of partition (without A/B suffix).
1956 hash_algorithm: Hash algorithm to use.
1957 salt: Salt to use as a hexadecimal string or None to use /dev/urandom.
1958 algorithm_name: Name of algorithm to use.
1959 key_path: Path to key to use or None.
1960 rollback_index: Rollback index.
1961 props: Properties to insert (List of strings of the form 'key:value').
1962 props_from_file: Properties to insert (List of strings 'key:<path>').
1963 kernel_cmdlines: Kernel cmdlines to insert (list of strings).
1964 generate_dm_verity_cmdline_from_hashtree: None or file to generate
1965 dm-verity kernel cmdline from.
1966 include_descriptors_from_image: List of file objects for which
1967 to insert descriptors from.
David Zeuthena4fee8b2016-08-22 15:20:43 -04001968
1969 Raises:
1970 AvbError: If an argument is incorrect.
David Zeuthen21e95262016-07-27 17:58:40 -04001971 """
David Zeuthena4fee8b2016-08-22 15:20:43 -04001972 image = ImageHandler(image_filename)
1973
1974 if partition_size % image.block_size != 0:
1975 raise AvbError('Partition size of {} is not a multiple of the image '
1976 'block size {}.'.format(partition_size,
1977 image.block_size))
1978
David Zeuthen21e95262016-07-27 17:58:40 -04001979 # If there's already a footer, truncate the image to its original
1980 # size. This way 'avbtool add_hash_footer' is idempotent (modulo
1981 # salts).
David Zeuthena4fee8b2016-08-22 15:20:43 -04001982 image_size = image.care_size
David Zeuthen21e95262016-07-27 17:58:40 -04001983 image.seek(image_size - AvbFooter.SIZE)
1984 try:
1985 footer = AvbFooter(image.read(AvbFooter.SIZE))
1986 # Existing footer found. Just truncate.
1987 original_image_size = footer.original_image_size
1988 image_size = footer.original_image_size
1989 image.truncate(image_size)
1990 except (LookupError, struct.error):
1991 original_image_size = image_size
1992
1993 # If anything goes wrong from here-on, restore the image back to
1994 # its original size.
1995 try:
1996 digest_size = len(hashlib.new(name=hash_algorithm).digest())
1997 if salt:
1998 salt = salt.decode('hex')
1999 else:
2000 if salt is None:
2001 # If salt is not explicitly specified, choose a hash
2002 # that's the same size as the hash size.
2003 hash_size = digest_size
2004 salt = open('/dev/urandom').read(hash_size)
2005 else:
2006 salt = ''
2007
2008 hasher = hashlib.new(name=hash_algorithm, string=salt)
2009 # TODO(zeuthen): might want to read this in chunks to avoid
2010 # memory pressure, then again, this is only supposed to be used
2011 # on kernel/initramfs partitions. Possible optimization.
2012 image.seek(0)
2013 hasher.update(image.read(image_size))
2014 digest = hasher.digest()
2015
2016 h_desc = AvbHashDescriptor()
2017 h_desc.image_size = image_size
2018 h_desc.hash_algorithm = hash_algorithm
2019 h_desc.partition_name = partition_name
2020 h_desc.salt = salt
2021 h_desc.digest = digest
2022
2023 # Generate the VBMeta footer.
David Zeuthen21e95262016-07-27 17:58:40 -04002024 vbmeta_blob = self._generate_vbmeta_blob(
2025 algorithm_name, key_path, [h_desc], rollback_index, props,
2026 props_from_file, kernel_cmdlines,
2027 generate_dm_verity_cmdline_from_hashtree,
2028 include_descriptors_from_image)
2029
David Zeuthena4fee8b2016-08-22 15:20:43 -04002030 # We might have a DONT_CARE hole at the end (in which case
2031 # |image.care_size| < |image.image_size|) so truncate here.
2032 image.truncate(image.care_size)
David Zeuthen21e95262016-07-27 17:58:40 -04002033
David Zeuthena4fee8b2016-08-22 15:20:43 -04002034 # If the image isn't sparse, its size might not be a multiple of
2035 # the block size. This will screw up padding later so just grow it.
2036 if image.care_size % image.block_size != 0:
2037 assert not image.is_sparse
2038 padding_needed = image.block_size - (image.care_size%image.block_size)
2039 image.truncate(image.care_size + padding_needed)
David Zeuthen21e95262016-07-27 17:58:40 -04002040
David Zeuthena4fee8b2016-08-22 15:20:43 -04002041 # The append_raw() method requires content with size being a
2042 # multiple of |block_size| so add padding as needed. Also record
2043 # where this is written to since we'll need to put that in the
2044 # footer.
2045 vbmeta_offset = image.care_size
2046 padding_needed = (round_to_multiple(len(vbmeta_blob), image.block_size) -
2047 len(vbmeta_blob))
2048 vbmeta_blob_with_padding = vbmeta_blob + '\0'*padding_needed
2049 image.append_raw(vbmeta_blob_with_padding)
2050 vbmeta_end_offset = vbmeta_offset + len(vbmeta_blob_with_padding)
2051
2052 # Now insert a DONT_CARE chunk with enough bytes such that the
2053 # final Footer block is at the end of partition_size..
2054 image.append_dont_care(partition_size - vbmeta_end_offset -
2055 1*image.block_size)
2056
2057 # Generate the Footer that tells where the VBMeta footer
2058 # is. Also put enough padding in the front of the footer since
2059 # we'll write out an entire block.
David Zeuthen21e95262016-07-27 17:58:40 -04002060 footer = AvbFooter()
2061 footer.original_image_size = original_image_size
2062 footer.vbmeta_offset = vbmeta_offset
2063 footer.vbmeta_size = len(vbmeta_blob)
David Zeuthena4fee8b2016-08-22 15:20:43 -04002064 footer_blob = footer.encode()
2065 footer_blob_with_padding = ('\0'*(image.block_size - AvbFooter.SIZE) +
2066 footer_blob)
2067 image.append_raw(footer_blob_with_padding)
2068
David Zeuthen21e95262016-07-27 17:58:40 -04002069 except:
2070 # Truncate back to original size, then re-raise
2071 image.truncate(original_image_size)
2072 raise
2073
David Zeuthena4fee8b2016-08-22 15:20:43 -04002074 def add_hashtree_footer(self, image_filename, partition_size, partition_name,
David Zeuthen21e95262016-07-27 17:58:40 -04002075 hash_algorithm, block_size, salt, algorithm_name,
2076 key_path, rollback_index, props, props_from_file,
2077 kernel_cmdlines,
2078 generate_dm_verity_cmdline_from_hashtree,
2079 include_descriptors_from_image):
2080 """Implements the 'add_hashtree_footer' command.
2081
2082 See https://gitlab.com/cryptsetup/cryptsetup/wikis/DMVerity for
2083 more information about dm-verity and these hashes.
2084
2085 Arguments:
David Zeuthena4fee8b2016-08-22 15:20:43 -04002086 image_filename: File to add the footer to.
David Zeuthen21e95262016-07-27 17:58:40 -04002087 partition_size: Size of partition.
2088 partition_name: Name of partition (without A/B suffix).
2089 hash_algorithm: Hash algorithm to use.
2090 block_size: Block size to use.
2091 salt: Salt to use as a hexadecimal string or None to use /dev/urandom.
2092 algorithm_name: Name of algorithm to use.
2093 key_path: Path to key to use or None.
2094 rollback_index: Rollback index.
2095 props: Properties to insert (List of strings of the form 'key:value').
2096 props_from_file: Properties to insert (List of strings 'key:<path>').
2097 kernel_cmdlines: Kernel cmdlines to insert (list of strings).
2098 generate_dm_verity_cmdline_from_hashtree: None or file to generate
2099 dm-verity kernel cmdline from.
2100 include_descriptors_from_image: List of file objects for which
2101 to insert descriptors from.
David Zeuthena4fee8b2016-08-22 15:20:43 -04002102
2103 Raises:
2104 AvbError: If an argument is incorrect.
David Zeuthen21e95262016-07-27 17:58:40 -04002105 """
David Zeuthena4fee8b2016-08-22 15:20:43 -04002106 image = ImageHandler(image_filename)
2107
2108 if partition_size % image.block_size != 0:
2109 raise AvbError('Partition size of {} is not a multiple of the image '
2110 'block size {}.'.format(partition_size,
2111 image.block_size))
2112
David Zeuthen21e95262016-07-27 17:58:40 -04002113 # If there's already a footer, truncate the image to its original
2114 # size. This way 'avbtool add_hashtree_footer' is idempotent
2115 # (modulo salts).
David Zeuthena4fee8b2016-08-22 15:20:43 -04002116 image_size = image.care_size
David Zeuthen21e95262016-07-27 17:58:40 -04002117 image.seek(image_size - AvbFooter.SIZE)
2118 try:
2119 footer = AvbFooter(image.read(AvbFooter.SIZE))
2120 # Existing footer found. Just truncate.
2121 original_image_size = footer.original_image_size
2122 image_size = footer.original_image_size
2123 image.truncate(image_size)
2124 except (LookupError, struct.error):
2125 original_image_size = image_size
2126
2127 # If anything goes wrong from here-on, restore the image back to
2128 # its original size.
2129 try:
2130 # Ensure image is multiple of block_size.
2131 rounded_image_size = round_to_multiple(image_size, block_size)
2132 if rounded_image_size > image_size:
David Zeuthena4fee8b2016-08-22 15:20:43 -04002133 image.append_raw('\0' * (rounded_image_size - image_size))
David Zeuthen21e95262016-07-27 17:58:40 -04002134 image_size = rounded_image_size
2135
David Zeuthen21e95262016-07-27 17:58:40 -04002136 digest_size = len(hashlib.new(name=hash_algorithm).digest())
2137 digest_padding = round_to_pow2(digest_size) - digest_size
2138
2139 if salt:
2140 salt = salt.decode('hex')
2141 else:
2142 if salt is None:
2143 # If salt is not explicitly specified, choose a hash
2144 # that's the same size as the hash size.
2145 hash_size = digest_size
2146 salt = open('/dev/urandom').read(hash_size)
2147 else:
2148 salt = ''
2149
David Zeuthena4fee8b2016-08-22 15:20:43 -04002150 # Hashes are stored upside down so we need to calculate hash
David Zeuthen21e95262016-07-27 17:58:40 -04002151 # offsets in advance.
2152 (hash_level_offsets, tree_size) = calc_hash_level_offsets(
2153 image_size, block_size, digest_size + digest_padding)
2154
David Zeuthena4fee8b2016-08-22 15:20:43 -04002155 # We might have a DONT_CARE hole at the end (in which case
2156 # |image.care_size| < |image.image_size|) so truncate here.
2157 image.truncate(image.care_size)
David Zeuthen21e95262016-07-27 17:58:40 -04002158
David Zeuthena4fee8b2016-08-22 15:20:43 -04002159 # If the image isn't sparse, its size might not be a multiple of
2160 # the block size. This will screw up padding later so just grow it.
2161 if image.care_size % image.block_size != 0:
2162 assert not image.is_sparse
2163 padding_needed = image.block_size - (image.care_size%image.block_size)
2164 image.truncate(image.care_size + padding_needed)
David Zeuthen21e95262016-07-27 17:58:40 -04002165
David Zeuthena4fee8b2016-08-22 15:20:43 -04002166 # Generate the tree and add padding as needed.
2167 tree_offset = image.care_size
2168 root_digest, hash_tree = generate_hash_tree(image, image_size,
2169 block_size,
2170 hash_algorithm, salt,
2171 digest_padding,
2172 hash_level_offsets,
2173 tree_size)
2174 padding_needed = (round_to_multiple(len(hash_tree), image.block_size) -
2175 len(hash_tree))
2176 hash_tree_with_padding = hash_tree + '\0'*padding_needed
2177 image.append_raw(hash_tree_with_padding)
2178
2179 # Generate HashtreeDescriptor with details about the tree we
2180 # just generated.
David Zeuthen21e95262016-07-27 17:58:40 -04002181 ht_desc = AvbHashtreeDescriptor()
2182 ht_desc.dm_verity_version = 1
2183 ht_desc.image_size = image_size
2184 ht_desc.tree_offset = tree_offset
2185 ht_desc.tree_size = tree_size
2186 ht_desc.data_block_size = block_size
2187 ht_desc.hash_block_size = block_size
2188 ht_desc.hash_algorithm = hash_algorithm
2189 ht_desc.partition_name = partition_name
2190 ht_desc.salt = salt
2191 ht_desc.root_digest = root_digest
2192
David Zeuthena4fee8b2016-08-22 15:20:43 -04002193 # Generate the VBMeta footer and add padding as needed.
2194 vbmeta_offset = tree_offset + len(hash_tree_with_padding)
David Zeuthen21e95262016-07-27 17:58:40 -04002195 vbmeta_blob = self._generate_vbmeta_blob(
2196 algorithm_name, key_path, [ht_desc], rollback_index, props,
2197 props_from_file, kernel_cmdlines,
2198 generate_dm_verity_cmdline_from_hashtree,
2199 include_descriptors_from_image)
David Zeuthena4fee8b2016-08-22 15:20:43 -04002200 padding_needed = (round_to_multiple(len(vbmeta_blob), image.block_size) -
2201 len(vbmeta_blob))
2202 vbmeta_blob_with_padding = vbmeta_blob + '\0'*padding_needed
2203 image.append_raw(vbmeta_blob_with_padding)
David Zeuthen21e95262016-07-27 17:58:40 -04002204
David Zeuthena4fee8b2016-08-22 15:20:43 -04002205 # Now insert a DONT_CARE chunk with enough bytes such that the
2206 # final Footer block is at the end of partition_size..
2207 image.append_dont_care(partition_size - image.care_size -
2208 1*image.block_size)
David Zeuthen21e95262016-07-27 17:58:40 -04002209
David Zeuthena4fee8b2016-08-22 15:20:43 -04002210 # Generate the Footer that tells where the VBMeta footer
2211 # is. Also put enough padding in the front of the footer since
2212 # we'll write out an entire block.
David Zeuthen21e95262016-07-27 17:58:40 -04002213 footer = AvbFooter()
2214 footer.original_image_size = original_image_size
2215 footer.vbmeta_offset = vbmeta_offset
2216 footer.vbmeta_size = len(vbmeta_blob)
David Zeuthena4fee8b2016-08-22 15:20:43 -04002217 footer_blob = footer.encode()
2218 footer_blob_with_padding = ('\0'*(image.block_size - AvbFooter.SIZE) +
2219 footer_blob)
2220 image.append_raw(footer_blob_with_padding)
2221
David Zeuthen21e95262016-07-27 17:58:40 -04002222 except:
2223 # Truncate back to original size, then re-raise
2224 image.truncate(original_image_size)
2225 raise
2226
2227
2228def calc_hash_level_offsets(image_size, block_size, digest_size):
2229 """Calculate the offsets of all the hash-levels in a Merkle-tree.
2230
2231 Arguments:
2232 image_size: The size of the image to calculate a Merkle-tree for.
2233 block_size: The block size, e.g. 4096.
2234 digest_size: The size of each hash, e.g. 32 for SHA-256.
2235
2236 Returns:
2237 A tuple where the first argument is an array of offsets and the
2238 second is size of the tree, in bytes.
2239 """
2240 level_offsets = []
2241 level_sizes = []
2242 tree_size = 0
2243
2244 num_levels = 0
2245 size = image_size
2246 while size > block_size:
2247 num_blocks = (size + block_size - 1) / block_size
2248 level_size = round_to_multiple(num_blocks * digest_size, block_size)
2249
2250 level_sizes.append(level_size)
2251 tree_size += level_size
2252 num_levels += 1
2253
2254 size = level_size
2255
2256 for n in range(0, num_levels):
2257 offset = 0
2258 for m in range(n + 1, num_levels):
2259 offset += level_sizes[m]
2260 level_offsets.append(offset)
2261
David Zeuthena4fee8b2016-08-22 15:20:43 -04002262 return level_offsets, tree_size
David Zeuthen21e95262016-07-27 17:58:40 -04002263
2264
2265def generate_hash_tree(image, image_size, block_size, hash_alg_name, salt,
David Zeuthena4fee8b2016-08-22 15:20:43 -04002266 digest_padding, hash_level_offsets, tree_size):
David Zeuthen21e95262016-07-27 17:58:40 -04002267 """Generates a Merkle-tree for a file.
2268
2269 Args:
2270 image: The image, as a file.
2271 image_size: The size of the image.
2272 block_size: The block size, e.g. 4096.
2273 hash_alg_name: The hash algorithm, e.g. 'sha256' or 'sha1'.
2274 salt: The salt to use.
2275 digest_padding: The padding for each digest.
David Zeuthen21e95262016-07-27 17:58:40 -04002276 hash_level_offsets: The offsets from calc_hash_level_offsets().
David Zeuthena4fee8b2016-08-22 15:20:43 -04002277 tree_size: The size of the tree, in number of bytes.
David Zeuthen21e95262016-07-27 17:58:40 -04002278
2279 Returns:
David Zeuthena4fee8b2016-08-22 15:20:43 -04002280 A tuple where the first element is the top-level hash and the
2281 second element is the hash-tree.
David Zeuthen21e95262016-07-27 17:58:40 -04002282 """
David Zeuthena4fee8b2016-08-22 15:20:43 -04002283 hash_ret = bytearray(tree_size)
David Zeuthen21e95262016-07-27 17:58:40 -04002284 hash_src_offset = 0
2285 hash_src_size = image_size
2286 level_num = 0
2287 while hash_src_size > block_size:
2288 level_output = ''
David Zeuthen21e95262016-07-27 17:58:40 -04002289 remaining = hash_src_size
2290 while remaining > 0:
2291 hasher = hashlib.new(name=hash_alg_name, string=salt)
David Zeuthena4fee8b2016-08-22 15:20:43 -04002292 # Only read from the file for the first level - for subsequent
2293 # levels, access the array we're building.
2294 if level_num == 0:
2295 image.seek(hash_src_offset + hash_src_size - remaining)
2296 data = image.read(min(remaining, block_size))
2297 else:
2298 offset = hash_level_offsets[level_num - 1] + hash_src_size - remaining
2299 data = hash_ret[offset:offset + block_size]
David Zeuthen21e95262016-07-27 17:58:40 -04002300 hasher.update(data)
David Zeuthena4fee8b2016-08-22 15:20:43 -04002301
2302 remaining -= len(data)
David Zeuthen21e95262016-07-27 17:58:40 -04002303 if len(data) < block_size:
2304 hasher.update('\0' * (block_size - len(data)))
2305 level_output += hasher.digest()
2306 if digest_padding > 0:
2307 level_output += '\0' * digest_padding
2308
2309 padding_needed = (round_to_multiple(
2310 len(level_output), block_size) - len(level_output))
2311 level_output += '\0' * padding_needed
2312
David Zeuthena4fee8b2016-08-22 15:20:43 -04002313 # Copy level-output into resulting tree.
2314 offset = hash_level_offsets[level_num]
2315 hash_ret[offset:offset + len(level_output)] = level_output
David Zeuthen21e95262016-07-27 17:58:40 -04002316
David Zeuthena4fee8b2016-08-22 15:20:43 -04002317 # Continue on to the next level.
David Zeuthen21e95262016-07-27 17:58:40 -04002318 hash_src_size = len(level_output)
David Zeuthen21e95262016-07-27 17:58:40 -04002319 level_num += 1
2320
2321 hasher = hashlib.new(name=hash_alg_name, string=salt)
2322 hasher.update(level_output)
David Zeuthena4fee8b2016-08-22 15:20:43 -04002323 return hasher.digest(), hash_ret
David Zeuthen21e95262016-07-27 17:58:40 -04002324
2325
2326class AvbTool(object):
2327 """Object for avbtool command-line tool."""
2328
2329 def __init__(self):
2330 """Initializer method."""
2331 self.avb = Avb()
2332
2333 def _add_common_args(self, sub_parser):
2334 """Adds arguments used by several sub-commands.
2335
2336 Arguments:
2337 sub_parser: The parser to add arguments to.
2338 """
2339 sub_parser.add_argument('--algorithm',
2340 help='Algorithm to use (default: NONE)',
2341 metavar='ALGORITHM',
2342 default='NONE')
2343 sub_parser.add_argument('--key',
2344 help='Path to RSA private key file',
2345 metavar='KEY',
2346 required=False)
2347 sub_parser.add_argument('--rollback_index',
2348 help='Rollback Index',
2349 type=parse_number,
2350 default=0)
2351 sub_parser.add_argument('--prop',
2352 help='Add property',
2353 metavar='KEY:VALUE',
2354 action='append')
2355 sub_parser.add_argument('--prop_from_file',
2356 help='Add property from file',
2357 metavar='KEY:PATH',
2358 action='append')
2359 sub_parser.add_argument('--kernel_cmdline',
2360 help='Add kernel cmdline',
2361 metavar='CMDLINE',
2362 action='append')
2363 sub_parser.add_argument('--generate_dm_verity_cmdline_from_hashtree',
2364 metavar='IMAGE',
2365 help='Generate kernel cmdline for dm-verity',
2366 type=argparse.FileType('rb'))
2367 sub_parser.add_argument('--include_descriptors_from_image',
2368 help='Include descriptors from image',
2369 metavar='IMAGE',
2370 action='append',
2371 type=argparse.FileType('rb'))
2372
2373 def run(self, argv):
2374 """Command-line processor.
2375
2376 Arguments:
2377 argv: Pass sys.argv from main.
2378 """
2379 parser = argparse.ArgumentParser()
2380 subparsers = parser.add_subparsers(title='subcommands')
2381
2382 sub_parser = subparsers.add_parser('version',
2383 help='Prints version of avbtool.')
2384 sub_parser.set_defaults(func=self.version)
2385
2386 sub_parser = subparsers.add_parser('extract_public_key',
2387 help='Extract public key.')
2388 sub_parser.add_argument('--key',
2389 help='Path to RSA private key file',
2390 required=True)
2391 sub_parser.add_argument('--output',
2392 help='Output file name',
2393 type=argparse.FileType('wb'),
2394 required=True)
2395 sub_parser.set_defaults(func=self.extract_public_key)
2396
2397 sub_parser = subparsers.add_parser('make_vbmeta_image',
2398 help='Makes a vbmeta image.')
2399 sub_parser.add_argument('--output',
2400 help='Output file name',
2401 type=argparse.FileType('wb'),
2402 required=True)
2403 self._add_common_args(sub_parser)
2404 sub_parser.add_argument('--chain_partition',
2405 help='Allow signed integrity-data for partition',
2406 metavar='PART_NAME:ROLLBACK_SLOT:KEY_PATH',
2407 action='append')
2408 sub_parser.set_defaults(func=self.make_vbmeta_image)
2409
2410 sub_parser = subparsers.add_parser('add_hash_footer',
2411 help='Add hashes and footer to image.')
2412 sub_parser.add_argument('--image',
2413 help='Brillo boot image to add hashes to',
2414 type=argparse.FileType('rab+'))
2415 sub_parser.add_argument('--partition_size',
2416 help='Partition size',
2417 type=parse_number,
2418 required=True)
2419 sub_parser.add_argument('--partition_name',
2420 help='Partition name',
2421 required=True)
2422 sub_parser.add_argument('--hash_algorithm',
2423 help='Hash algorithm to use (default: sha256)',
2424 default='sha256')
2425 sub_parser.add_argument('--salt',
2426 help='Salt in hex (default: /dev/urandom)')
2427 self._add_common_args(sub_parser)
2428 sub_parser.set_defaults(func=self.add_hash_footer)
2429
2430 sub_parser = subparsers.add_parser('add_hashtree_footer',
2431 help='Add hashtree and footer to image.')
2432 sub_parser.add_argument('--image',
2433 help='Brillo boot image to add hashes to',
2434 type=argparse.FileType('rab+'))
2435 sub_parser.add_argument('--partition_size',
2436 help='Partition size',
2437 type=parse_number,
2438 required=True)
2439 sub_parser.add_argument('--partition_name',
2440 help='Partition name',
2441 required=True)
2442 sub_parser.add_argument('--hash_algorithm',
2443 help='Hash algorithm to use (default: sha1)',
2444 default='sha1')
2445 sub_parser.add_argument('--salt',
2446 help='Salt in hex (default: /dev/urandom)')
2447 sub_parser.add_argument('--block_size',
2448 help='Block size (default: 4096)',
2449 type=parse_number,
2450 default=4096)
2451 self._add_common_args(sub_parser)
2452 sub_parser.set_defaults(func=self.add_hashtree_footer)
2453
2454 sub_parser = subparsers.add_parser('erase_footer',
2455 help='Erase footer from an image.')
2456 sub_parser.add_argument('--image',
2457 help='Brillo image with a footer',
2458 type=argparse.FileType('rwb+'),
2459 required=True)
2460 sub_parser.add_argument('--keep_hashtree',
2461 help='Keep the hashtree in the image',
2462 action='store_true')
2463 sub_parser.set_defaults(func=self.erase_footer)
2464
2465 sub_parser = subparsers.add_parser(
2466 'info_image',
2467 help='Show information about vbmeta or footer.')
2468 sub_parser.add_argument('--image',
2469 help='Brillo boot image to use',
2470 type=argparse.FileType('rb'),
2471 required=True)
2472 sub_parser.add_argument('--output',
2473 help='Write info to file',
2474 type=argparse.FileType('wt'),
2475 default=sys.stdout)
2476 sub_parser.set_defaults(func=self.info_image)
2477
2478 args = parser.parse_args(argv[1:])
2479 try:
2480 args.func(args)
2481 except AvbError as e:
David Zeuthena4fee8b2016-08-22 15:20:43 -04002482 sys.stderr.write('{}: {}\n'.format(argv[0], e.message))
David Zeuthen21e95262016-07-27 17:58:40 -04002483 sys.exit(1)
2484
2485 def version(self, _):
2486 """Implements the 'version' sub-command."""
2487 print '{}.{}'.format(AVB_VERSION_MAJOR, AVB_VERSION_MINOR)
2488
2489 def extract_public_key(self, args):
2490 """Implements the 'extract_public_key' sub-command."""
2491 self.avb.extract_public_key(args.key, args.output)
2492
2493 def make_vbmeta_image(self, args):
2494 """Implements the 'make_vbmeta_image' sub-command."""
2495 self.avb.make_vbmeta_image(args.output, args.chain_partition,
2496 args.algorithm, args.key, args.rollback_index,
2497 args.prop, args.prop_from_file,
2498 args.kernel_cmdline,
2499 args.generate_dm_verity_cmdline_from_hashtree,
2500 args.include_descriptors_from_image)
2501
2502 def add_hash_footer(self, args):
2503 """Implements the 'add_hash_footer' sub-command."""
David Zeuthena4fee8b2016-08-22 15:20:43 -04002504 self.avb.add_hash_footer(args.image.name, args.partition_size,
David Zeuthen21e95262016-07-27 17:58:40 -04002505 args.partition_name, args.hash_algorithm,
2506 args.salt, args.algorithm, args.key,
2507 args.rollback_index, args.prop,
2508 args.prop_from_file, args.kernel_cmdline,
2509 args.generate_dm_verity_cmdline_from_hashtree,
2510 args.include_descriptors_from_image)
2511
2512 def add_hashtree_footer(self, args):
2513 """Implements the 'add_hashtree_footer' sub-command."""
David Zeuthena4fee8b2016-08-22 15:20:43 -04002514 self.avb.add_hashtree_footer(args.image.name, args.partition_size,
David Zeuthen21e95262016-07-27 17:58:40 -04002515 args.partition_name, args.hash_algorithm,
2516 args.block_size, args.salt, args.algorithm,
2517 args.key, args.rollback_index, args.prop,
2518 args.prop_from_file, args.kernel_cmdline,
2519 args.generate_dm_verity_cmdline_from_hashtree,
2520 args.include_descriptors_from_image)
2521
2522 def erase_footer(self, args):
2523 """Implements the 'erase_footer' sub-command."""
David Zeuthena4fee8b2016-08-22 15:20:43 -04002524 self.avb.erase_footer(args.image.name, args.keep_hashtree)
David Zeuthen21e95262016-07-27 17:58:40 -04002525
2526 def info_image(self, args):
2527 """Implements the 'info_image' sub-command."""
David Zeuthena4fee8b2016-08-22 15:20:43 -04002528 self.avb.info_image(args.image.name, args.output)
David Zeuthen21e95262016-07-27 17:58:40 -04002529
2530
2531if __name__ == '__main__':
2532 tool = AvbTool()
2533 tool.run(sys.argv)