• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2# -*- coding: UTF-8 -*-
3'''
4 * Copyright (c) 2022-2023 Huawei Device Co., Ltd.
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16'''
17
18import binascii
19import hashlib
20import os
21import re
22import stat
23import struct
24import subprocess
25import sys
26import bisect
27import shutil
28
29
30_params = {'partition':         None,     \
31          'partition_size':    None,      \
32          'image':             None,         \
33          'verity_type':       'hash',       \
34          'algorithm':         'SHA256_RSA3072',     \
35          'rollback_location': None,   \
36          'rollback_index':    None,   \
37          'salt':              None,           \
38          'pubkey':            None,           \
39          'privkey':           None,           \
40          'hash_algo':         'SHA256',  \
41          'block_size':        4096,  \
42          'fec_num_roots':     0,    \
43          'padding_size':      None,    \
44          'chain_partition':   [],  \
45          'output':            None}
46
47
48VERITY_TYPE = {'make_hash_footer':      'hash',   \
49               'make_hashtree_footer':  'hashtree'}
50
51
52class Algorithm(object):
53    def __init__(self, sig_algo, hash_algo, bit_length, sig_bytes, hash_bytes, pubkey_bytes):
54        self.sig_algo = sig_algo
55        self.hash_algo = hash_algo
56        self.bit_length = bit_length
57        self.sig_bytes = sig_bytes
58        self.hash_bytes = hash_bytes
59        self.pubkey_bytes = pubkey_bytes
60
61
62def round_to_multiple(num, size):
63    remainder = num % size
64    if remainder == 0:
65        return num
66    return num + size - remainder
67
68
69class HvbFooter(object):
70    FOOTERMAGIC = b'HVB' + b'\0' * 5
71    FOOTER_FORMAT = ('8s'  # Magic
72                     '4Q'  # Cert offset, Cert size, Image_size, Partition_size
73                     '64s'  # Reserved
74                     )
75
76    def __init__(self, footer=None):
77        self.foot = footer
78        if self.foot:
79            (self.magic, self.certoffset, self.certsize, self.imagesize,
80             self.partition_size, _) = struct.unpack(self.FOOTER_FORMAT, footer)
81            if self.magic != self.FOOTERMAGIC:
82                raise HvbError('Given footer does not look like a HVB footer.')
83        else:
84            raise HvbError('Given footer is None.')
85
86    def info_footer(self):
87        msg = "[HVB footer]: \n"
88        if self.foot:
89            msg += "\tMaigc:                   {}\n".format((self.magic).decode())
90            msg += "\tCert offset:             {} bytes\n".format(hex(self.certoffset))
91            msg += "\tCert size:               {} bytes\n".format(self.certsize)
92            msg += "\tImage size:              {} bytes\n".format(self.imagesize)
93            msg += "\tPartition size:          {} bytes\n\n".format(self.partition_size)
94        else:
95            msg += "There is no footer. \n\n"
96        print(msg)
97
98
99class HvbCert(object):
100    CERTMAGIC = b'HVB\0'
101    ALGORITHM_TYPES = {0 : 'SHA256_RSA3072',
102                       1 : 'SHA256_RSA4096',
103                       2 : 'SHA256_RSA2048'
104                    }
105
106    HASH_ALGORITHMS = {0 : 'SHA256',
107                       1 : 'SHA128',
108                       2 : 'SHA512'
109                       }
110
111    def __init__(self, cert=None):
112        self.cert = cert
113
114        flags = os.O_WRONLY | os.O_CREAT
115        modes = stat.S_IWUSR | stat.S_IRUSR
116        with os.fdopen(os.open('cert.bin', flags, modes), 'wb') as cert_fd:
117            cert_fd.write(self.cert)
118
119        if self.cert:
120            self.mgc, self.major, self.minor = struct.unpack('4s2I', self.cert[0:12])
121            self.version = '{}.{}'.format(self.major, self.minor)
122            if self.mgc != self.CERTMAGIC:
123                raise HvbError('Given cert does not look like a HVB cert.')
124            self.img_org_len, self.img_len, self.partition = struct.unpack('2Q64s', self.cert[48:128])
125            self.rollback_location, self.rollback_index = struct.unpack('2Q', self.cert[128:144])
126
127            verity, self.hash_algo = struct.unpack('2I', self.cert[144:152])
128            self.verity_type = 'hash' if verity == 1 else 'hashtree'
129            self.salt_offset, self.salt_size = struct.unpack('2Q', self.cert[152:168])
130
131            self.digest_offset, self.digest_size, self.hashtree_offset, self.hashtree_size, \
132                        self.data_block_size, self.hash_block_size, self.fec_num_roots, \
133                        self.fec_offset, self.fec_size = struct.unpack('9Q', self.cert[168:240])
134            self.salt = struct.unpack('{}s'.format(self.salt_size), self.cert[240:240 + self.salt_size])
135            self.digest = struct.unpack('{}s'.format(self.digest_size), \
136                                        self.cert[240 + self.salt_size : 240 + self.salt_size + self.digest_size])
137            hash_payload_size = self.salt_size + self.digest_size
138            self.algo, self.flags, self.key_offset, self.key_len = struct.unpack('2I2Q', \
139                                    self.cert[240 + hash_payload_size + 8 : 240 + hash_payload_size + 8 + 24])
140            self.key = self.cert[240 + hash_payload_size + 112 : 240 + hash_payload_size + 112 + self.key_len]
141
142        else:
143            raise HvbError('Given cert is None.')
144
145    def info_cert(self):
146        msg = "[HVB cert]: \n"
147        if self.cert:
148            msg += "\tHVB tool version:           hvb tool {}\n".format(self.version)
149            msg += "\tOriginal Image length:      {} bytes\n".format(self.img_org_len)
150            msg += "\tImage length:               {} bytes (4K alignment)\n".format(self.img_len)
151            msg += "\tPartition name:             {}\n".format(self.partition.decode())
152            msg += "\tverity type(hash/hashtree): {}\n".format(self.verity_type)
153            msg += "\tsalt size:                  {} bytes\n".format(self.salt_size)
154            if self.hash_algo not in self.HASH_ALGORITHMS:
155                raise HvbError("Unknown hash algorithm: {}".format(self.hash_algo))
156            msg += "\tHash algorithm:             {}\n".format(self.HASH_ALGORITHMS[self.hash_algo])
157            msg += "\tdigest size:                {} bytes\n".format(self.digest_size)
158            msg += "\thashtree size:              {}\n".format(self.hashtree_size)
159            msg += "\tfec size:                   {}\n".format(self.fec_size)
160            msg += "\thashpayload:\n"
161            msg += "\t\tsalt:               {}\n".format((binascii.b2a_hex(self.salt[0]).decode()))
162            msg += "\t\tdigest:             {}\n".format((binascii.b2a_hex(self.digest[0]).decode()))
163            if self.hashtree_size != 0:
164                msg += "\t\thashtree offset: 0x{:x}\n".format(self.hashtree_offset)
165            if self.fec_size != 0:
166                msg += "\t\tfec offset:      0x{:x}\n".format(self.fec_offset)
167            if self.algo not in self.ALGORITHM_TYPES:
168                raise HvbError("Unknown algorithm type: {}".format(self.algo))
169            msg += "\tAlgorithm:                  {}\n".format(self.ALGORITHM_TYPES[self.algo])
170            msg += "\tPublic key (sha256):        {}\n\n".format(hashlib.sha256(self.key).hexdigest())
171        else:
172            msg += 'There is no certificate.\n\n'
173        print(msg)
174
175
176class RSAPublicKey(object):
177    MODULUS_PREFIX = b'modulus='
178    BIT_LENGTH_KEYWORD = b'RSA Public-Key:'
179
180    def __init__(self, pubkey):
181        self.pubkey = pubkey
182        self.modulus_bits = self.get_bit_length(self.pubkey)
183        cmds = ['openssl', 'rsa', '-in', pubkey, '-modulus', '-noout', '-pubin']
184        process = subprocess.Popen(cmds, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
185        try:
186            (out, err) = process.communicate(timeout=10)
187        except subprocess.TimeoutExpired:
188            process.kill()
189            raise HvbError("Get public key timeout!")
190        if process.wait() != 0:
191            raise HvbError("Failed to get public key: {}".format(err))
192
193        if not out.lower().startswith(self.MODULUS_PREFIX):
194            raise HvbError('Invalid modulus')
195
196        self.modulus = out[len(self.MODULUS_PREFIX):].strip()
197        self.modulusdata = int(self.modulus, 16)
198        self.exponent = 65537
199
200    def get_bit_length(self, pubkey):
201        bitlen = 0
202        cmd = ['openssl', 'rsa',  '-inform', 'PEM',  '-in', pubkey,  '-pubin', '-text']
203        child = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
204        lines = child.stdout.read().split(b'\n')
205        for line in lines:
206            if self.BIT_LENGTH_KEYWORD in line:
207                bitlen = int(re.findall(b'\d+', line.split(self.BIT_LENGTH_KEYWORD)[-1])[0])
208                break
209        return bitlen
210
211    def calc_egcd(self, num1, num2):
212        if num1 == 0:
213            return (num2, 0, 1)
214        egcd_g, egcd_y, egcd_x = self.calc_egcd(num2 % num1, num1)
215        return (egcd_g, egcd_x - (num2 // num1) * egcd_y, egcd_y)
216
217    def calc_modinv(self, num1, modulo):
218        modinv_gcd, modinv_x, _ = self.calc_egcd(num1, modulo)
219        if modinv_gcd != 1:
220            raise HvbError("modular inverse does not exist.")
221        return modinv_x % modulo
222
223    def incode_long(self, num_bits, value):
224        ret = bytearray()
225        for bit_pos in range(num_bits, 0, -8):
226            octet = (value >> (bit_pos - 8)) & 0xff
227            ret.extend(struct.pack('!B', octet))
228        return ret
229
230    def get_public_key(self):
231        pkey_n0 = 2 ** 64 - self.calc_modinv(self.modulusdata, 2 ** 64)
232        pkey_r = 2 ** self.modulusdata.bit_length()
233        pkey_prr = bytes(self.incode_long(self.modulus_bits, pkey_r * pkey_r % self.modulusdata))
234        modulus = bytes(self.incode_long(self.modulus_bits, self.modulusdata))
235
236        return struct.pack('!QQ', self.modulus_bits, pkey_n0) + modulus + pkey_prr
237
238
239class HvbError(Exception):
240    def __init__(self, message):
241        print("[HvbError]: " + message)
242        Exception.__init__(self, message)
243
244
245class ImageChunk(object):
246    CHUNK_HEADER_FORMAT = '<2H2I'
247    CHUNK_HEADER_SIZE = struct.calcsize(CHUNK_HEADER_FORMAT)
248    CHUNK_TYPE_RAW = 0xcac1
249    CHUNK_TYPE_FILL = 0xcac2
250    CHUNK_TYPE_DONT_CARE = 0xcac3
251    CHUNK_TYPE_CRC32 = 0xcac4
252
253    def __init__(self, chunk_type, chunk_offset, nsparsed_output_offset,
254                 nsparsed_output_size, input_offset, fill_data):
255        """Initializes an ImageChunk object.
256
257            Arguments:
258              chunk_type: One of TYPE_RAW, TYPE_FILL, or TYPE_DONT_CARE.
259              chunk_offset: Offset in the sparse file where this chunk begins.
260              output_offset: Offset in de-sparsified file.
261              output_size: Number of bytes in output.
262              input_offset: Offset in sparse file where the chunk data begins if TYPE_RAW otherwise None.
263              fill_data: Blob as bytes with data to fill if TYPE_FILL otherwise None.
264            """
265
266        self.chunk_type = chunk_type
267        self.chunk_offset = chunk_offset
268        self.nsparsed_chunk_offset = nsparsed_output_offset
269        self.nsparsed_output_size = nsparsed_output_size
270        self.sparsed_input_offset = input_offset
271        self.fill_data = fill_data
272        # Check invariants.
273        if self.chunk_type == self.CHUNK_TYPE_RAW:
274            if self.fill_data is not None:
275                raise HvbError('RAW chunk cannot have fill_data set.')
276            if not self.sparsed_input_offset:
277                raise HvbError('RAW chunk must have input_offset set.')
278        elif self.chunk_type == self.CHUNK_TYPE_FILL:
279            if self.fill_data is None:
280                raise HvbError('FILL chunk must have fill_data set.')
281            if self.sparsed_input_offset:
282                raise HvbError('FILL chunk cannot have input_offset set.')
283        elif self.chunk_type == self.CHUNK_TYPE_DONT_CARE:
284            if self.fill_data is not None:
285                raise HvbError('DONT_CARE chunk cannot have fill_data set.')
286            if self.sparsed_input_offset:
287                raise HvbError('DONT_CARE chunk cannot have input_offset set.')
288        else:
289            raise HvbError('Invalid chunk type')
290
291
292class ImageHandle(object):
293    #Descriptions of sparse image
294    SIMAGE_MAGIC = 0xed26ff3a
295    SIMAGE_HEADER_FORMAT = '<I4H4I'
296    SIMAGE_HEADER_SIZE = struct.calcsize(SIMAGE_HEADER_FORMAT)
297
298    def __init__(self, file_name):
299        self.image_file = file_name
300        self.is_sparse = False
301        self.block_size = 4096    # A block size is 4096 bytes.
302        self.total_blks = 0
303        self.total_chunks = 0
304        self.header_analyze()
305
306    def header_analyze(self):
307        self.img_handler = open(self.image_file, 'r+b')
308        self.img_handler.seek(0, os.SEEK_END)
309        self.img_size = self.img_handler.tell()
310        print("Initial image length: ", self.img_size)
311
312        self.img_handler.seek(0, os.SEEK_SET)
313        header = self.img_handler.read(self.SIMAGE_HEADER_SIZE)
314
315        """ Sparse header
316            magic                  0xed26ff3a
317            major_version          (0x1) - reject images with higher major versions
318            minor_version          (0x0) - allow images with higer minor versions
319            file_hdr_sz            28 bytes for first revision of the file format
320            chunk_hdr_sz           12 bytes for first revision of the file format
321            blk_sz                 block size in bytes, must be a multiple of 4 (4096)
322            total_blks             total blocks in the non-sparse output image
323            total_chunks           total chunks in the sparse input image
324            image_checksum         CRC32 checksum of the original data, counting "don't care
325                                   as 0. Standard 802.3 polynomial, use a Public Domain
326                                   table implementation
327        """
328        (magic, major_version, minor_version, file_hdr_sz,
329            chunk_hdr_sz, block_size, self.total_blks, self.total_chunks,
330            img_checksum) = struct.unpack(self.SIMAGE_HEADER_FORMAT, header)
331        if magic != self.SIMAGE_MAGIC:
332            return
333
334        self.block_size = block_size
335        print("It's a sparse image.")
336        if self.SIMAGE_HEADER_SIZE != file_hdr_sz:
337            raise HvbError("Incorrect sparse image header size: {}".format(file_hdr_sz))
338        if ImageChunk.CHUNK_HEADER_SIZE != chunk_hdr_sz:
339            raise HvbError("Incorrect chunk header size: {}".format(chunk_hdr_sz))
340
341        self.chunks = list()
342        nsparsed_output_offset, nsparsed_chunk_nums = self.chunk_analyze()
343        self.sparse_end = self.img_handler.tell()
344
345        if self.total_blks != nsparsed_chunk_nums:
346            raise HvbError("The header said we should have {} output blocks, "
347                               'but we saw {}'.format(self.total_blks, nsparsed_chunk_nums))
348        if self.sparse_end != self.img_size:
349            raise HvbError("There were {} bytes of extra data at the end of the "
350                               "file.".format(self.img_size - self.sparse_end))
351
352        self.nsparsed_chunk_offset_list = [c.nsparsed_chunk_offset for c in self.chunks]
353        self.is_sparse = True
354        self.img_size = nsparsed_output_offset
355
356    def chunk_analyze(self):
357        nsparsed_output_offset = 0
358        nsparsed_chunk_nums = 0
359
360        for i in range(self.total_chunks):
361            chunk_offset = self.img_handler.tell()
362
363            """ chunk header
364                chunk_type             Type of this chunk (Raw, Fill, Dont care, CRC32)
365                chunk_sz               Size of the chunk before the sparse(The unit is blk_sz, that is, 4096)
366                total_sz               The size of the chunk after sparse, includes chunk_header and chunk data.
367            """
368            chunk_header = self.img_handler.read(ImageChunk.CHUNK_HEADER_SIZE)
369            (chunk_type, _, chunk_sz, total_sz) = struct.unpack(ImageChunk.CHUNK_HEADER_FORMAT, chunk_header)
370            chunk_data_size = total_sz - ImageChunk.CHUNK_HEADER_SIZE
371
372            if chunk_type == ImageChunk.CHUNK_TYPE_RAW:
373                if chunk_data_size != (chunk_sz * self.block_size):
374                    raise HvbError("Raw chunk size ({}) does not match output "
375                                    "size ({})".format(chunk_data_size, (chunk_sz * self.block_size)))
376                self.chunks.append(ImageChunk(ImageChunk.CHUNK_TYPE_RAW, chunk_offset,
377                                                  nsparsed_output_offset, chunk_sz * self.block_size,
378                                                  self.img_handler.tell(), None))
379                self.img_handler.seek(chunk_data_size, os.SEEK_CUR)
380            elif chunk_type == ImageChunk.CHUNK_TYPE_FILL:
381                if chunk_data_size != 4:
382                    raise HvbError("Fill chunk should have 4 bytes of fill, but this "
383                                       "has {}".format(chunk_data_size))
384                fill_data = self.img_handler.read(4)
385                self.chunks.append(ImageChunk(ImageChunk.CHUNK_TYPE_FILL, chunk_offset,
386                                                  nsparsed_output_offset, chunk_sz * self.block_size,
387                                                  None, fill_data))
388            elif chunk_type == ImageChunk.CHUNK_TYPE_DONT_CARE:
389                if chunk_data_size != 0:
390                    raise HvbError("Don\'t care chunk input size is non-zero ({})".
391                                       format(chunk_data_size))
392                self.chunks.append(ImageChunk(ImageChunk.CHUNK_TYPE_DONT_CARE, chunk_offset,
393                                                  nsparsed_output_offset, chunk_sz * self.block_size,
394                                                  None, None))
395            elif chunk_type == ImageChunk.CHUNK_TYPE_CRC32:
396                if chunk_data_size != 4:
397                    raise HvbError("CRC32 chunk should have 4 bytes of CRC, but "
398                                       "this has {}".format(chunk_data_size))
399                self.img_handler.read(4)
400            else:
401                raise HvbError("Unknown chunk type: {}".format(chunk_type))
402
403            nsparsed_chunk_nums += chunk_sz
404            nsparsed_output_offset += chunk_sz * self.block_size
405
406        return nsparsed_output_offset, nsparsed_chunk_nums
407
408    def update_chunks_and_blocks(self):
409        """Helper function to update the image header.
410
411        The the |total_chunks| and |total_blocks| fields in the header
412        will be set to value of the |_num_total_blocks| and
413        |_num_total_chunks| attributes.
414
415        """
416        num_chunks_and_blocks_offset = 16
417        num_chunks_and_blocks_format = '<II'
418        self.img_handler.seek(num_chunks_and_blocks_offset, os.SEEK_SET)
419        self.img_handler.write(struct.pack(num_chunks_and_blocks_format,
420                                      self.total_blks, self.total_chunks))
421
422    def append_dont_care(self, num_bytes):
423        """Appends a DONT_CARE chunk to the sparse file.
424
425        The given number of bytes must be a multiple of the block size.
426
427        Arguments:
428          num_bytes: Size in number of bytes of the DONT_CARE chunk.
429
430        """
431        if num_bytes % self.block_size != 0:
432            raise HvbError("Given number of bytes must be a multiple of the block size.")
433
434        if not self.is_sparse:
435            self.img_handler.seek(0, os.SEEK_END)
436            # This is more efficient that writing NUL bytes since it'll add
437            # a hole on file systems that support sparse files (native
438            # sparse, not Android sparse).
439            self.img_handler.truncate(self.img_handler.tell() + num_bytes)
440            self.header_analyze()
441            return
442
443        self.total_chunks += 1
444        self.total_blks += num_bytes // self.block_size
445        self.update_chunks_and_blocks()
446
447        self.img_handler.seek(self.sparse_end, os.SEEK_SET)
448        self.img_handler.write(struct.pack(ImageChunk.CHUNK_HEADER_FORMAT,
449                                      ImageChunk.CHUNK_TYPE_DONT_CARE,
450                                      0,  # Reserved
451                                      num_bytes // self.block_size,
452                                      struct.calcsize(ImageChunk.CHUNK_HEADER_FORMAT)))
453        self.header_analyze()
454
455    def append_raw(self, data):
456        """Appends a RAW chunk to the sparse file.
457
458        The length of the given data must be a multiple of the block size.
459
460        Arguments:
461          data: Data to append as bytes.
462
463        """
464        print("SELF>BLOCK_SIZE: ", self.block_size)
465        if len(data) % self.block_size != 0:
466            raise HvbError("Given data must be a multiple of the block size.")
467
468        if not self.is_sparse:
469            self.img_handler.seek(0, os.SEEK_END)
470            self.img_handler.write(data)
471            self.header_analyze()
472            return
473
474        self.total_chunks += 1
475        self.total_blks += len(data) // self.block_size
476        self.update_chunks_and_blocks()
477
478        self.img_handler.seek(self.sparse_end, os.SEEK_SET)
479        self.img_handler.write(struct.pack(ImageChunk.CHUNK_HEADER_FORMAT,
480                                      ImageChunk.CHUNK_TYPE_RAW,
481                                      0,  # Reserved
482                                      len(data) // self.block_size,
483                                      len(data) +
484                                      struct.calcsize(ImageChunk.CHUNK_HEADER_FORMAT)))
485        self.img_handler.write(data)
486        self.header_analyze()
487
488    def append_fill(self, fill_data, size):
489        """Appends a fill chunk to the sparse file.
490
491        The total length of the fill data must be a multiple of the block size.
492
493        Arguments:
494          fill_data: Fill data to append - must be four bytes.
495          size: Number of chunk - must be a multiple of four and the block size.
496
497        """
498        if len(fill_data) != 4 or size % 4 != 0 or size % self.block_size != 0:
499            raise HvbError("The total length of the fill data must be a multiple of the block size.")
500
501        if not self.is_sparse:
502            self.img_handler.seek(0, os.SEEK_END)
503            self.img_handler.write(fill_data * (size // 4))
504            self.header_analyze()
505            return
506
507        self.total_chunks += 1
508        self.total_blks += size // self.block_size
509        self.update_chunks_and_blocks()
510
511        self.img_handler.seek(self.sparse_end, os.SEEK_SET)
512        self.img_handler.write(struct.pack(ImageChunk.CHUNK_HEADER_FORMAT,
513                                      ImageChunk.CHUNK_TYPE_FILL,
514                                      0,  # Reserved
515                                      size // self.block_size,
516                                      4 + struct.calcsize(ImageChunk.CHUNK_HEADER_FORMAT)))
517        self.img_handler.write(fill_data)
518        self.header_analyze()
519
520    def seek(self, offset):
521        """Sets the cursor position for reading from unsparsified file.
522
523        Arguments:
524          offset: Offset to seek to from the beginning of the file.
525
526        Raises:
527          RuntimeError: If the given offset is negative.
528        """
529        if offset < 0:
530            raise RuntimeError('Seeking with negative offset: {}'.format(offset))
531        self._file_pos = offset
532
533    def read(self, size):
534        """Reads data from the unsparsified file.
535
536        This method may return fewer than |size| bytes of data if the end
537        of the file was encountered.
538
539        The file cursor for reading is advanced by the number of bytes
540        read.
541
542        Arguments:
543          size: Number of bytes to read.
544
545        Returns:
546          The data as bytes.
547        """
548        if not self.is_sparse:
549            self.img_handler.seek(self._file_pos)
550            data = self.img_handler.read(size)
551            self._file_pos += len(data)
552            return data
553
554        # Iterate over all chunks.
555        chunk_idx = bisect.bisect_right(self.nsparsed_chunk_offset_list,
556                                        self._file_pos) - 1
557        data = bytearray()
558        to_go = size
559        while to_go > 0:
560            chunk = self.chunks[chunk_idx]
561            chunk_pos_offset = self._file_pos - chunk.nsparsed_chunk_offset
562            chunk_pos_to_go = min(chunk.nsparsed_output_size - chunk_pos_offset, to_go)
563
564            if chunk.chunk_type == ImageChunk.CHUNK_TYPE_RAW:
565                self.img_handler.seek(chunk.sparsed_input_offset + chunk_pos_offset)
566                data.extend(self.img_handler.read(chunk_pos_to_go))
567            elif chunk.chunk_type == ImageChunk.CHUNK_TYPE_FILL:
568                all_data = chunk.fill_data * (chunk_pos_to_go // len(chunk.fill_data) + 2)
569                offset_mod = chunk_pos_offset % len(chunk.fill_data)
570                data.extend(all_data[offset_mod:(offset_mod + chunk_pos_to_go)])
571            else:
572                if chunk.chunk_type != ImageChunk.CHUNK_TYPE_DONT_CARE:
573                    raise HvbError("Invalid chunk type!")
574                data.extend(b'\0' * chunk_pos_to_go)
575
576            to_go -= chunk_pos_to_go
577            self._file_pos += chunk_pos_to_go
578            chunk_idx += 1
579            # Generate partial read in case of EOF.
580            if chunk_idx >= len(self.nsparsed_chunk_offset_list):
581                break
582
583        return bytes(data)
584
585    def tell(self):
586        """Returns the file cursor position for reading from unsparsified file.
587
588        Returns:
589          The file cursor position for reading.
590        """
591        return self._file_pos
592
593    def calc_truncate_location(self):
594        pass
595
596    def truncate(self, size):
597        """Truncates the unsparsified file.
598
599        Arguments:
600          size: Desired size of unsparsified file.
601
602        Raises:
603          ValueError: If desired size isn't a multiple of the block size.
604        """
605
606        if not self.is_sparse:
607            self.img_handler.truncate(size)
608            self.header_analyze()
609            return
610
611        if size % self.block_size != 0:
612            raise ValueError('Cannot truncate to a size which is not a multiple '
613                             'of the block size')
614
615        if size == self.img_size:
616            # Trivial where there's nothing to do.
617            return
618
619        if size < self.img_size:
620            chunk_idx = bisect.bisect_right(self.nsparsed_chunk_offset_list, size) - 1
621            chunk = self.chunks[chunk_idx]
622            if chunk.nsparsed_chunk_offset != size:
623                # Truncation in the middle of a trunk - need to keep the chunk
624                # and modify it.
625                chunk_idx_for_update = chunk_idx + 1
626                num_to_keep = size - chunk.nsparsed_chunk_offset
627                if num_to_keep % self.block_size != 0:
628                    raise HvbError("Remainder size of bytes must be multiple of the block size.")
629
630                if chunk.chunk_type == ImageChunk.CHUNK_TYPE_RAW:
631                    truncate_at = (chunk.nsparsed_chunk_offset +
632                                   struct.calcsize(ImageChunk.CHUNK_HEADER_FORMAT) + num_to_keep)
633                    data_sz = num_to_keep
634                elif chunk.chunk_type == ImageChunk.CHUNK_TYPE_FILL:
635                    truncate_at = (chunk.nsparsed_chunk_offset +
636                                   struct.calcsize(ImageChunk.CHUNK_HEADER_FORMAT) + 4)
637                    data_sz = 4
638                elif chunk.chunk_type == ImageChunk.CHUNK_TYPE_DONT_CARE:
639                    truncate_at = chunk.nsparsed_chunk_offset + struct.calcsize(ImageChunk.CHUNK_HEADER_FORMAT)
640                    data_sz = 0
641                else:
642                    raise HvbError("Invalid chunk type.")
643                chunk_sz = num_to_keep // self.block_size
644                total_sz = data_sz + struct.calcsize(ImageChunk.CHUNK_HEADER_FORMAT)
645                self.img_handler.seek(chunk.nsparsed_chunk_offset)
646                self.img_handler.write(struct.pack(ImageChunk.CHUNK_HEADER_FORMAT,
647                                              chunk.chunk_type,
648                                              0,  # Reserved
649                                              chunk_sz, total_sz))
650                chunk.output_size = num_to_keep
651            else:
652                # Truncation at trunk boundary.
653                truncate_at = chunk.chunk_offset
654                chunk_idx_for_update = chunk_idx
655
656            self.total_chunks = chunk_idx_for_update
657            self.total_blks = 0
658            for i in range(0, chunk_idx_for_update):
659                self.total_blks += self.chunks[i].nsparsed_output_size // self.block_size
660            self.update_chunks_and_blocks()
661            self.img_handler.truncate(truncate_at)
662
663            # We've modified the file so re-read all data.
664            self.header_analyze()
665        else:
666            # Truncating to grow - just add a DONT_CARE section.
667            self.append_dont_care(size - self.img_size)
668
669
670class HvbTool(object):
671    MAGIC = b'HVB\0'
672    HVB_VERSION_MAJOR = 1
673    HVB_VERSION_MINOR = 0
674    FOOTER_SIZE = 104
675    VERITY_RESERVED = b'\0' * 36
676    RVT_MAGIC = b'rot\0'
677
678    ALGORITHMS = {'SHA256_RSA3072': Algorithm(
679        sig_algo=0,
680        hash_algo='sha256',
681        bit_length=3072,
682        sig_bytes=384,
683        hash_bytes=32,
684        pubkey_bytes=8 + 2 * 3072 // 8
685        ), \
686        'SHA256_RSA4096': Algorithm(
687            sig_algo=1,
688            hash_algo='sha256',
689            bit_length=4096,
690            sig_bytes=512,
691            hash_bytes=32,
692            pubkey_bytes=8 + 2 * 4096 // 8
693        ), \
694        'SHA256_RSA2048': Algorithm(
695            sig_algo=2,
696            hash_algo='sha256',
697            bit_length=2048,
698            sig_bytes=256,
699            hash_bytes=32,
700            pubkey_bytes=8 + 2 * 2048 // 8
701        ) \
702    }
703
704    def __init__(self):
705        self.img = _params['image']
706        self.partition = _params['partition']
707        self.partition_size = _params['partition_size']
708        self.vertype = _params['verity_type'].lower()  # verity type: hash - 1 or hashtree - 2 (fix)
709        self.algo = _params['algorithm']
710        self.pubkey = _params['pubkey']
711        self.privkey = _params['privkey']
712        self.hash_algo = _params['hash_algo']
713        self.block_size = _params['block_size']
714        self.padding_size = _params['padding_size']
715        self.signed_img = _params['output']
716        if  _params['salt'] is not None:
717            self.salt = binascii.a2b_hex(_params['salt'])
718
719        self.hashtree = b''
720        self.digest = b''
721        self.fec = b''
722        self.hvb_cert_content = b''
723
724        self.original_image_info()
725
726    def original_image_info(self):
727        if self.img is None:
728            return
729
730        self.image_handle = ImageHandle(self.img)
731        self.original_image_length = self.image_handle.img_size
732        # Image length algins to 4096 bytes
733        self.img_len_with_padding = round_to_multiple(self.original_image_length, self.block_size)
734        print("Image length(%s): %d bytes" % (self.img, self.original_image_length))
735
736    def hvb_header(self):
737        self.hvb_cert_content = self.MAGIC
738        self.hvb_cert_content += struct.pack('I', self.HVB_VERSION_MAJOR)
739        self.hvb_cert_content += struct.pack('I', self.HVB_VERSION_MINOR)
740        self.hvb_cert_content += self.VERITY_RESERVED
741
742    def hvb_image_info(self):
743        max_partition_name_len = 64
744        partition_name = (self.partition).encode('utf-8') + b'\0' * (max_partition_name_len - len(self.partition))
745
746        self.hvb_cert_content += struct.pack('Q', self.original_image_length)
747        self.hvb_cert_content += struct.pack('Q', self.img_len_with_padding)
748        self.hvb_cert_content += partition_name
749        self.hvb_cert_content += struct.pack('2Q', int(_params['rollback_location']), int(_params['rollback_index']))
750
751    def image_hash(self):
752        # 0: SHA256(verity_type为hash时的默认值)0:SHA256(verity_type为hashtree时的默认值)2:SHA512
753        if self.vertype == 'hash':
754            halgo = 0   # SHA256
755        elif self.vertype == 'hashtree':
756            halgo = 0   # SHA256
757        else:
758            halgo = 2   # SHA512
759
760        print("digest: ", binascii.b2a_hex(self.digest))
761        print("digest size: ", len(self.digest))
762
763        hash_algo = struct.pack('I', halgo)
764        salt_offset = struct.pack('Q', 240)       # 根据HVB证书格式,salt偏移位置固定,为240字节的偏移
765        salt_size = struct.pack('Q', len(self.salt))
766        digest_offset = struct.pack('Q', 240 + len(self.salt))
767        digest_size = struct.pack('Q', len(self.digest))
768        return hash_algo + salt_offset + salt_size + digest_offset + digest_size
769
770    def generate_hash_tree(self, hash_level_offsets, tree_size):
771        hash_ret = bytearray(tree_size)
772        hash_src_offset = 0
773        hash_src_size = self.image_handle.img_size
774        level_num = 0
775        print("hash_level_offsets: {}, tree_size: {}".format(hash_level_offsets, tree_size))
776
777        while hash_src_size > self.block_size:
778            print("hash_src_size: ", hash_src_size)
779            level_output_list = []
780            remaining = hash_src_size
781            while remaining > 0:
782                hasher = hashlib.new(self.hash_algo, self.salt)
783                if level_num == 0:
784                    begin = hash_src_offset + hash_src_size - remaining
785                    end = min(remaining, self.block_size)
786                    self.image_handle.seek(begin)
787                    data = self.image_handle.read(end)
788                else:
789                    offset = hash_level_offsets[level_num - 1] + hash_src_size - remaining
790                    data = hash_ret[offset : offset + self.block_size]
791                hasher.update(data)
792
793                remaining -= len(data)
794                if len(data) < self.block_size:
795                    hasher.update(b'\0' * (self.block_size - len(data)))
796                level_output_list.append(hasher.digest())
797
798            level_output = b''.join(level_output_list)
799            level_output_padding = round_to_multiple(len(level_output), self.block_size) - len(level_output)
800            level_output += b'\0' * level_output_padding
801
802            offset = hash_level_offsets[level_num]
803            hash_ret[offset : offset + len(level_output)] = level_output
804
805            hash_src_size = len(level_output)
806            level_num += 1
807
808        hasher = hashlib.new(self.hash_algo, self.salt)
809        hasher.update(level_output)
810
811        return hasher.digest(), bytes(hash_ret)
812
813    def create_hashtree(self, digest_size):
814        level_offsets = []
815        level_sizes = []
816
817        treesize = 0
818        levels = 0
819        size = self.image_handle.img_size
820
821        while size > self.block_size:
822            blocknum = size // self.block_size
823            level_size = round_to_multiple(blocknum * digest_size, self.block_size)
824            level_sizes.append(level_size)
825            treesize += level_size
826            levels += 1
827            size = level_size
828        print("level_sizes: ", level_sizes)
829        for i in range(levels):
830            offset = 0
831            for j in range(i + 1, levels):
832                offset += level_sizes[j]
833            level_offsets.append(offset)
834
835        rootdigest, self.hashtree = self.generate_hash_tree(level_offsets, treesize)
836        if len(self.hashtree) % self.block_size != 0:
837            self.hashtree += b'\0' * (self.block_size - len(self.hashtree) % self.block_size)
838        print("root digest: ", binascii.b2a_hex(rootdigest))
839        return rootdigest
840
841    def image_hash_tree(self):
842        image_hashtree = {"hashtree_offset": self.img_len_with_padding, \
843                          "hashtree_size": len(self.hashtree), "data_block_size": self.block_size, \
844                          "hash_block_size": self.block_size, "fec_num_roots": 0, \
845                          "fec_offset": 240 + len(self.salt) + len(self.digest), "fec_size": 0}
846        hashtree_struct = struct.Struct('Q' * len(image_hashtree))
847        dlist = []
848        for item in image_hashtree:
849            dlist.append(image_hashtree[item])
850        return hashtree_struct.pack(*dlist)
851
852    def hvb_hash_info(self):
853        verity_type = 0
854        if self.vertype == 'hash':
855            verity_type = 1    # hash: 1    hashtree: 2
856
857            self.image_handle.seek(0)
858            image_content = self.image_handle.read(self.image_handle.img_size)
859            hasher = hashlib.new(self.hash_algo, self.salt)
860            hasher.update(image_content)
861            self.digest = hasher.digest()
862        elif self.vertype == 'hashtree':
863            verity_type = 2    # hash: 1    hashtree: 2
864
865            hashtree_hasher = hashlib.new(self.hash_algo, self.salt)
866            digest_size = len(hashtree_hasher.digest())
867            digest_padding = 2 ** ((digest_size - 1).bit_length()) - digest_size
868            print("hash_algo: {}, salt: {}".format(self.hash_algo, self.salt))
869            print("digest_size: {}, digest_padding: {}".format(digest_size, digest_padding))
870            remainder = self.block_size - self.image_handle.img_size % self.block_size
871            if remainder != self.block_size:
872                self.image_handle.append_raw(b'\0' * remainder)
873            self.img_len_with_padding = self.image_handle.img_size
874            self.digest = self.create_hashtree(digest_size)
875        else:
876            print("[hvbtool][ERROR]: Invalid verity_type: %d", self.vertype)
877            sys.exit(1)
878
879        imghash = self.image_hash()
880        imghashtree = self.image_hash_tree()
881        hashpayload = self.salt + self.digest
882
883        hashinfo = imghash + imghashtree + hashpayload
884        self.hashinfo_size = len(hashinfo)
885        self.hvb_cert_content += struct.pack('I', verity_type) + hashinfo
886
887    def hvb_signature_info(self):
888        sig_content = 'sigcontent.bin'
889        # 签名算法  0:SHA256_RSA3072(默认值), 1:SHA256_RSA4096 , 2:SHA256_RSA2048
890        if self.algo not in self.ALGORITHMS:
891            raise HvbError("Unknown algorithm: {}".format(self.algo))
892        algo = self.ALGORITHMS[self.algo]
893        flags = 0       # 预留的flag标记,默认全为0
894        keyblock_offset = 144 + self.hashinfo_size + 112
895
896        try:
897            key = RSAPublicKey(self.pubkey)
898        except HvbError as err:
899            sys.exit(1)
900        keyblock = key.get_public_key()
901
902        signature_offset = keyblock_offset + len(keyblock)
903
904        sig_length = len(self.hvb_cert_content) + 112 + len(keyblock)
905
906        self.hvb_cert_content += struct.pack('Q', sig_length) + struct.pack('I', algo.sig_algo) \
907                        + struct.pack('I', flags) + struct.pack('Q', keyblock_offset) \
908                        + struct.pack('Q', len(keyblock)) + struct.pack('Q', signature_offset) \
909                        + struct.pack('Q', algo.sig_bytes) + b'\0' * 64 + keyblock
910
911        if os.path.exists(sig_content):
912            os.remove(sig_content)
913
914        flags = os.O_RDONLY | os.O_WRONLY | os.O_CREAT
915        modes = stat.S_IWUSR | stat.S_IRUSR
916        with os.fdopen(os.open('tmp.bin', flags, modes), 'wb') as tmp_fd:
917            tmp_fd.write(self.hvb_cert_content)
918
919        cmd = ['openssl', 'dgst', '-sign', self.privkey, '-sigopt',  'rsa_padding_mode:pss',
920                '-sigopt', 'rsa_pss_saltlen:{}'.format(len(self.salt)), '-sha256', '-out',
921                sig_content,  'tmp.bin']
922        ret = subprocess.call(cmd)
923        if ret != 0:
924            print("Failed to sign the image.")
925            sys.exit(1)
926
927        flags = os.O_RDONLY | os.O_EXCL
928        with os.fdopen(os.open(sig_content, flags, modes), 'rb') as sig_fd:
929            sigcontent = sig_fd.read()
930
931        self.hvb_cert_content += sigcontent
932
933    def hvb_footer_info(self):
934        self.footer = b''
935        footer_magic = self.MAGIC + b'\0' * 4
936        self.partition_size = int(self.partition_size)
937
938        cert_size = len(self.hvb_cert_content)
939        cert_offset = self.img_len_with_padding + len(self.hashtree)
940
941        if self.padding_size is not None:
942            cert_offset = self.partition_size - self.padding_size
943
944        print("cert_size: %x, cert_offset: %x, partition_size: %x" % (cert_size, cert_offset, self.partition_size))
945        self.footer += footer_magic \
946                       + struct.pack('4Q', cert_offset, cert_size, self.original_image_length, self.partition_size) \
947                       + b'\0' * 64
948
949    def hvb_make_rvt_image(self):
950        self.img = 'tmp_rvt.img'
951        rvtcontent = b''
952        rvtcontent += self.RVT_MAGIC
953        verity_num = len(_params['chain_partition'])
954        rvtcontent += struct.pack('I', verity_num) + b'\0' * 64    # rvt_reversed: 64 bytes
955        cur_sizes = len(rvtcontent)
956
957        for item in _params['chain_partition']:
958            chain_partition_data = item.split(':')
959            partition_name = chain_partition_data[0].strip()
960            pubkey = chain_partition_data[1].strip()
961
962            try:
963                key = RSAPublicKey(pubkey)
964            except HvbError as err:
965                sys.exit(1)
966            pubkey_payload = key.get_public_key()
967            pubkey_len = len(pubkey_payload)
968            pubkey_offset = cur_sizes + 80
969            rvtcontent += partition_name.encode() + b'\0' * (64 - len(partition_name))    # partition_name
970            rvtcontent += struct.pack('Q', pubkey_offset) # pubkey_offset
971            rvtcontent += struct.pack('Q', pubkey_len)    # pubkey_len
972            rvtcontent += pubkey_payload  # pubkey_payload
973            cur_sizes += 80 + pubkey_len
974
975        if os.path.exists(self.img):
976            os.remove(self.img)
977
978        flags = os.O_WRONLY | os.O_RDONLY | os.O_CREAT
979        modes = stat.S_IWUSR | stat.S_IRUSR
980        with os.fdopen(os.open(self.img, flags, modes), 'wb') as rvt_fd:
981            rvt_fd.write(rvtcontent)
982
983        self.original_image_info()
984        self.hvb_make_image()
985
986    def hvb_combine_image_info(self):
987        cert_and_footer = b''
988        hashtree_length = len(self.hashtree)
989        hvb_cert_length = len(self.hvb_cert_content)
990        image = os.path.abspath(self.img)
991        signed_image = os.path.abspath(self.signed_img)
992
993        shutil.copy(image, signed_image)
994        image = ImageHandle(signed_image)
995
996        padding = round_to_multiple(image.img_size, self.block_size)
997        if padding > image.img_size:
998            if image.is_sparse is True:     # Original image size must be multiple of block_size
999                raise HvbError("The sparse image size is not multiple of the block size.")
1000            image.truncate(padding)
1001
1002        padding = round_to_multiple((hvb_cert_length + self.FOOTER_SIZE), self.block_size)
1003        if padding > (hvb_cert_length + self.FOOTER_SIZE):
1004            cert_and_footer = self.hvb_cert_content + b'\0' * (padding - (self.FOOTER_SIZE + hvb_cert_length)) + \
1005                self.footer
1006        else:
1007            cert_and_footer = self.hvb_cert_content + self.footer
1008
1009        if self.partition_size < image.img_size + hashtree_length + len(cert_and_footer):
1010            raise HvbError("[hvbtool][ERROR]: Partition size is too small!")
1011
1012        cert_and_footer = self.hvb_cert_content + b'\0' * (self.partition_size - image.img_size - \
1013            hashtree_length - hvb_cert_length - self.FOOTER_SIZE) + self.footer
1014        image.append_raw(self.hashtree)
1015        image.append_raw(cert_and_footer)
1016
1017    def parse_rvt_image(self, handle):
1018        handle.seek(0)
1019        msg = ''
1020
1021        header = handle.read(8)
1022        magic, verity_num = struct.unpack('4sI', header)
1023
1024        msg += "[rvt info]: \n"
1025        if magic != self.RVT_MAGIC:
1026            raise HvbError("It is not a valid rvt image.")
1027
1028        handle.seek(72)
1029        for i in range(verity_num):
1030            #The size of pubkey_des excludes pubkey_payload is 80 bytes
1031            data = handle.read(80)
1032            name, pubkey_offset, pubkey_len = struct.unpack('64s2Q', data)
1033            msg += '\tChain Partition descriptor: \n'
1034            msg += '\t\tPartition Name:      {}\n'.format(name.decode())
1035            pubkey = handle.read(pubkey_len)
1036            msg += "\t\tPublic key (sha256):   {}\n\n".format(hashlib.sha256(pubkey).hexdigest())
1037
1038        print(msg)
1039
1040    def hvb_make_image(self):
1041        self.hvb_header()
1042        self.hvb_image_info()
1043        self.hvb_hash_info()
1044        self.hvb_signature_info()
1045        self.hvb_footer_info()
1046        self.hvb_combine_image_info()
1047
1048    def hvb_parse_image(self):
1049        try:
1050            image = ImageHandle(self.img)
1051            image.seek(self.original_image_length - self.FOOTER_SIZE)
1052            footer_bin = image.read(self.FOOTER_SIZE)
1053            footer = HvbFooter(footer_bin)
1054            footer.info_footer()
1055
1056            image.seek(footer.certoffset)
1057            cert_bin = image.read(footer.certsize)
1058            cert = HvbCert(cert_bin)
1059            cert.info_cert()
1060
1061            if 'rvt' in cert.partition.decode():
1062                self.parse_rvt_image(image)
1063        except (HvbError, struct.error):
1064            raise HvbError("Failed to parse the image!")
1065
1066    def hvb_erase_image(self):
1067        try:
1068            image = ImageHandle(self.img)
1069            image.seek(self.original_image_length - self.FOOTER_SIZE)
1070            footer_bin = image.read(self.FOOTER_SIZE)
1071            footer = HvbFooter(footer_bin)
1072            image.seek(0)
1073            image.truncate(footer.imagesize)
1074        except (HvbError, struct.error):
1075            raise HvbError("Failed to erase image.")
1076
1077
1078def print_help():
1079    print("usage: hvbtool.py [-h]")
1080    print("\t\t{make_hash_footer, make_hashtree_footer, make_rvt_image, parse_image}")
1081
1082
1083def parse_arguments(argvs):
1084    global _params
1085    length = len(argvs)
1086    i = 0
1087    args = list()
1088
1089    if length % 2 != 0:
1090        print_help()
1091        print("[hvbtool][ERROR]: invalid argument format!")
1092        sys.exit(1)
1093
1094    while (i < length):
1095        args.append([argvs[i], argvs[i + 1]])
1096        i = i + 2
1097
1098    act = args[0][1]
1099    if act.strip() in VERITY_TYPE.keys():
1100        _params['verity_type'] = VERITY_TYPE[act]
1101        print(_params['verity_type'])
1102    else:
1103        _params['verity_type'] = 'hash'
1104
1105    for item in args[1:]:
1106        itemkey = item[0].strip()[2:]
1107        if itemkey in _params.keys():
1108            if itemkey == "chain_partition":
1109                _params[itemkey].append(item[1].strip())
1110            else:
1111                _params[itemkey] = item[1].strip()
1112        else:
1113            print("[hvbtool][ERROR]: Unknown argument: %s" % item[0])
1114            sys.exit(1)
1115    return act
1116
1117
1118def necessary_arguments_check(check_list):
1119    for item in check_list:
1120        if _params[item] is None or len(_params[item]) == 0:
1121            print("[hvbtool][ERROR]: The argument '{}' is necessary.".format(item))
1122            return False
1123    return True
1124
1125
1126def check_arguments(act):
1127    make_image_arguments_list = ['image', 'salt', 'pubkey', 'rollback_index', 'rollback_location']
1128    make_rvt_image_arguments_list = ['salt', 'pubkey', 'rollback_index', 'rollback_location', 'chain_partition']
1129    parse_erase_image_arguments_list = ['image']
1130    ret = False
1131
1132    if act == 'make_hash_footer' or act == 'make_hashtree_footer':
1133        ret = necessary_arguments_check(make_image_arguments_list)
1134    elif act == 'make_rvt_image':
1135        ret = necessary_arguments_check(make_rvt_image_arguments_list)
1136    elif act == 'parse_image' or act == 'erase_image':
1137        ret = necessary_arguments_check(parse_erase_image_arguments_list)
1138    else:
1139        print("[hvbtool][ERROR]: Unkown action: {}".format(act))
1140
1141    if ret is False:
1142        sys.exit(1)
1143
1144
1145def main(obj, act):
1146    if act == 'make_hash_footer' or act == 'make_hashtree_footer':
1147        obj.hvb_make_image()
1148    elif act == 'parse_image':
1149        obj.hvb_parse_image()
1150    elif act == 'make_rvt_image':
1151        obj.hvb_make_rvt_image()
1152    elif act == 'erase_image':
1153        obj.hvb_erase_image()
1154    else:
1155        raise HvbError("Unknown action: {}".format(act))
1156
1157if __name__ == '__main__':
1158    action = parse_arguments(sys.argv)
1159    check_arguments(action)
1160    tool = HvbTool()
1161    try:
1162        main(tool, action)
1163    except (HvbError, struct.error):
1164        print("HVB COMMAND FAILED!")
1165        sys.exit(1)
1166    sys.exit(0)
1167