• 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 sparse).
438            self.img_handler.truncate(self.img_handler.tell() + num_bytes)
439            self.header_analyze()
440            return
441
442        self.total_chunks += 1
443        self.total_blks += num_bytes // self.block_size
444        self.update_chunks_and_blocks()
445
446        self.img_handler.seek(self.sparse_end, os.SEEK_SET)
447        self.img_handler.write(struct.pack(ImageChunk.CHUNK_HEADER_FORMAT,
448                                      ImageChunk.CHUNK_TYPE_DONT_CARE,
449                                      0,  # Reserved
450                                      num_bytes // self.block_size,
451                                      struct.calcsize(ImageChunk.CHUNK_HEADER_FORMAT)))
452        self.header_analyze()
453
454    def append_raw(self, data):
455        """Appends a RAW chunk to the sparse file.
456
457        The length of the given data must be a multiple of the block size.
458
459        Arguments:
460          data: Data to append as bytes.
461
462        """
463        print("SELF>BLOCK_SIZE: ", self.block_size)
464        if len(data) % self.block_size != 0:
465            raise HvbError("Given data must be a multiple of the block size.")
466
467        if not self.is_sparse:
468            self.img_handler.seek(0, os.SEEK_END)
469            self.img_handler.write(data)
470            self.header_analyze()
471            return
472
473        self.total_chunks += 1
474        self.total_blks += len(data) // self.block_size
475        self.update_chunks_and_blocks()
476
477        self.img_handler.seek(self.sparse_end, os.SEEK_SET)
478        self.img_handler.write(struct.pack(ImageChunk.CHUNK_HEADER_FORMAT,
479                                      ImageChunk.CHUNK_TYPE_RAW,
480                                      0,  # Reserved
481                                      len(data) // self.block_size,
482                                      len(data) +
483                                      struct.calcsize(ImageChunk.CHUNK_HEADER_FORMAT)))
484        self.img_handler.write(data)
485        self.header_analyze()
486
487    def append_fill(self, fill_data, size):
488        """Appends a fill chunk to the sparse file.
489
490        The total length of the fill data must be a multiple of the block size.
491
492        Arguments:
493          fill_data: Fill data to append - must be four bytes.
494          size: Number of chunk - must be a multiple of four and the block size.
495
496        """
497        if len(fill_data) != 4 or size % 4 != 0 or size % self.block_size != 0:
498            raise HvbError("The total length of the fill data must be a multiple of the block size.")
499
500        if not self.is_sparse:
501            self.img_handler.seek(0, os.SEEK_END)
502            self.img_handler.write(fill_data * (size // 4))
503            self.header_analyze()
504            return
505
506        self.total_chunks += 1
507        self.total_blks += size // self.block_size
508        self.update_chunks_and_blocks()
509
510        self.img_handler.seek(self.sparse_end, os.SEEK_SET)
511        self.img_handler.write(struct.pack(ImageChunk.CHUNK_HEADER_FORMAT,
512                                      ImageChunk.CHUNK_TYPE_FILL,
513                                      0,  # Reserved
514                                      size // self.block_size,
515                                      4 + struct.calcsize(ImageChunk.CHUNK_HEADER_FORMAT)))
516        self.img_handler.write(fill_data)
517        self.header_analyze()
518
519    def seek(self, offset):
520        """Sets the cursor position for reading from unsparsified file.
521
522        Arguments:
523          offset: Offset to seek to from the beginning of the file.
524
525        Raises:
526          RuntimeError: If the given offset is negative.
527        """
528        if offset < 0:
529            raise RuntimeError('Seeking with negative offset: {}'.format(offset))
530        self._file_pos = offset
531
532    def read(self, size):
533        """Reads data from the unsparsified file.
534
535        This method may return fewer than |size| bytes of data if the end
536        of the file was encountered.
537
538        The file cursor for reading is advanced by the number of bytes
539        read.
540
541        Arguments:
542          size: Number of bytes to read.
543
544        Returns:
545          The data as bytes.
546        """
547        if not self.is_sparse:
548            self.img_handler.seek(self._file_pos)
549            data = self.img_handler.read(size)
550            self._file_pos += len(data)
551            return data
552
553        # Iterate over all chunks.
554        chunk_idx = bisect.bisect_right(self.nsparsed_chunk_offset_list,
555                                        self._file_pos) - 1
556        data = bytearray()
557        to_go = size
558        while to_go > 0:
559            chunk = self.chunks[chunk_idx]
560            chunk_pos_offset = self._file_pos - chunk.nsparsed_chunk_offset
561            chunk_pos_to_go = min(chunk.nsparsed_output_size - chunk_pos_offset, to_go)
562
563            if chunk.chunk_type == ImageChunk.CHUNK_TYPE_RAW:
564                self.img_handler.seek(chunk.sparsed_input_offset + chunk_pos_offset)
565                data.extend(self.img_handler.read(chunk_pos_to_go))
566            elif chunk.chunk_type == ImageChunk.CHUNK_TYPE_FILL:
567                all_data = chunk.fill_data * (chunk_pos_to_go // len(chunk.fill_data) + 2)
568                offset_mod = chunk_pos_offset % len(chunk.fill_data)
569                data.extend(all_data[offset_mod:(offset_mod + chunk_pos_to_go)])
570            else:
571                if chunk.chunk_type != ImageChunk.CHUNK_TYPE_DONT_CARE:
572                    raise HvbError("Invalid chunk type!")
573                data.extend(b'\0' * chunk_pos_to_go)
574
575            to_go -= chunk_pos_to_go
576            self._file_pos += chunk_pos_to_go
577            chunk_idx += 1
578            # Generate partial read in case of EOF.
579            if chunk_idx >= len(self.nsparsed_chunk_offset_list):
580                break
581
582        return bytes(data)
583
584    def tell(self):
585        """Returns the file cursor position for reading from unsparsified file.
586
587        Returns:
588          The file cursor position for reading.
589        """
590        return self._file_pos
591
592    def calc_truncate_location(self):
593        pass
594
595    def truncate(self, size):
596        """Truncates the unsparsified file.
597
598        Arguments:
599          size: Desired size of unsparsified file.
600
601        Raises:
602          ValueError: If desired size isn't a multiple of the block size.
603        """
604
605        if not self.is_sparse:
606            self.img_handler.truncate(size)
607            self.header_analyze()
608            return
609
610        if size % self.block_size != 0:
611            raise ValueError('Cannot truncate to a size which is not a multiple '
612                             'of the block size')
613
614        if size == self.img_size:
615            # Trivial where there's nothing to do.
616            return
617
618        if size < self.img_size:
619            chunk_idx = bisect.bisect_right(self.nsparsed_chunk_offset_list, size) - 1
620            chunk = self.chunks[chunk_idx]
621            if chunk.nsparsed_chunk_offset != size:
622                # Truncation in the middle of a trunk - need to keep the chunk
623                # and modify it.
624                chunk_idx_for_update = chunk_idx + 1
625                num_to_keep = size - chunk.nsparsed_chunk_offset
626                if num_to_keep % self.block_size != 0:
627                    raise HvbError("Remainder size of bytes must be multiple of the block size.")
628
629                if chunk.chunk_type == ImageChunk.CHUNK_TYPE_RAW:
630                    truncate_at = (chunk.nsparsed_chunk_offset +
631                                   struct.calcsize(ImageChunk.CHUNK_HEADER_FORMAT) + num_to_keep)
632                    data_sz = num_to_keep
633                elif chunk.chunk_type == ImageChunk.CHUNK_TYPE_FILL:
634                    truncate_at = (chunk.nsparsed_chunk_offset +
635                                   struct.calcsize(ImageChunk.CHUNK_HEADER_FORMAT) + 4)
636                    data_sz = 4
637                elif chunk.chunk_type == ImageChunk.CHUNK_TYPE_DONT_CARE:
638                    truncate_at = chunk.nsparsed_chunk_offset + struct.calcsize(ImageChunk.CHUNK_HEADER_FORMAT)
639                    data_sz = 0
640                else:
641                    raise HvbError("Invalid chunk type.")
642                chunk_sz = num_to_keep // self.block_size
643                total_sz = data_sz + struct.calcsize(ImageChunk.CHUNK_HEADER_FORMAT)
644                self.img_handler.seek(chunk.nsparsed_chunk_offset)
645                self.img_handler.write(struct.pack(ImageChunk.CHUNK_HEADER_FORMAT,
646                                              chunk.chunk_type,
647                                              0,  # Reserved
648                                              chunk_sz, total_sz))
649                chunk.output_size = num_to_keep
650            else:
651                # Truncation at trunk boundary.
652                truncate_at = chunk.chunk_offset
653                chunk_idx_for_update = chunk_idx
654
655            self.total_chunks = chunk_idx_for_update
656            self.total_blks = 0
657            for i in range(0, chunk_idx_for_update):
658                self.total_blks += self.chunks[i].nsparsed_output_size // self.block_size
659            self.update_chunks_and_blocks()
660            self.img_handler.truncate(truncate_at)
661
662            # We've modified the file so re-read all data.
663            self.header_analyze()
664        else:
665            # Truncating to grow - just add a DONT_CARE section.
666            self.append_dont_care(size - self.img_size)
667
668
669class HvbTool(object):
670    MAGIC = b'HVB\0'
671    HVB_VERSION_MAJOR = 1
672    HVB_VERSION_MINOR = 1
673    FOOTER_SIZE = 104
674    VERITY_RESERVED = b'\0' * 36
675    RVT_MAGIC = b'rot\0'
676
677    ALGORITHMS = {'SHA256_RSA3072': Algorithm(
678        sig_algo=0,
679        hash_algo='sha256',
680        bit_length=3072,
681        sig_bytes=384,
682        hash_bytes=32,
683        pubkey_bytes=8 + 2 * 3072 // 8
684        ), \
685        'SHA256_RSA4096': Algorithm(
686            sig_algo=1,
687            hash_algo='sha256',
688            bit_length=4096,
689            sig_bytes=512,
690            hash_bytes=32,
691            pubkey_bytes=8 + 2 * 4096 // 8
692        ), \
693        'SHA256_RSA2048': Algorithm(
694            sig_algo=2,
695            hash_algo='sha256',
696            bit_length=2048,
697            sig_bytes=256,
698            hash_bytes=32,
699            pubkey_bytes=8 + 2 * 2048 // 8
700        ) \
701    }
702
703    def __init__(self):
704        self.img = _params['image']
705        self.partition = _params['partition']
706        self.partition_size = _params['partition_size']
707        self.vertype = _params['verity_type'].lower()  # verity type: hash - 1 or hashtree - 2 (fix)
708        self.algo = _params['algorithm']
709        self.pubkey = _params['pubkey']
710        self.privkey = _params['privkey']
711        self.hash_algo = _params['hash_algo']
712        self.block_size = _params['block_size']
713        self.padding_size = _params['padding_size']
714        self.signed_img = _params['output']
715        if  _params['salt'] is not None:
716            self.salt = binascii.a2b_hex(_params['salt'])
717
718        self.hashtree = b''
719        self.digest = b''
720        self.fec = b''
721        self.hvb_cert_content = b''
722
723        self.original_image_info()
724
725    def original_image_info(self):
726        if self.img is None:
727            return
728
729        self.image_handle = ImageHandle(self.img)
730        self.original_image_length = self.image_handle.img_size
731        # Image length algins to 4096 bytes
732        self.img_len_with_padding = round_to_multiple(self.original_image_length, self.block_size)
733        print("Image length(%s): %d bytes" % (self.img, self.original_image_length))
734
735    def hvb_header(self):
736        self.hvb_cert_content = self.MAGIC
737        self.hvb_cert_content += struct.pack('I', self.HVB_VERSION_MAJOR)
738        self.hvb_cert_content += struct.pack('I', self.HVB_VERSION_MINOR)
739        self.hvb_cert_content += self.VERITY_RESERVED
740
741    def hvb_image_info(self):
742        max_partition_name_len = 64
743        partition_name = (self.partition).encode('utf-8') + b'\0' * (max_partition_name_len - len(self.partition))
744
745        self.hvb_cert_content += struct.pack('Q', self.original_image_length)
746        self.hvb_cert_content += struct.pack('Q', self.img_len_with_padding)
747        self.hvb_cert_content += partition_name
748        self.hvb_cert_content += struct.pack('2Q', int(_params['rollback_location']), int(_params['rollback_index']))
749
750    def image_hash(self):
751        # 0: SHA256(verity_type为hash时的默认值)0:SHA256(verity_type为hashtree时的默认值)2:SHA512
752        if self.vertype == 'hash':
753            halgo = 0   # SHA256
754        elif self.vertype == 'hashtree':
755            halgo = 0   # SHA256
756        else:
757            halgo = 2   # SHA512
758
759        print("digest: ", binascii.b2a_hex(self.digest))
760        print("digest size: ", len(self.digest))
761
762        hash_algo = struct.pack('I', halgo)
763        salt_offset = struct.pack('Q', 240)       # 根据HVB证书格式,salt偏移位置固定,为240字节的偏移
764        salt_size = struct.pack('Q', len(self.salt))
765        digest_offset = struct.pack('Q', 240 + len(self.salt))
766        digest_size = struct.pack('Q', len(self.digest))
767        return hash_algo + salt_offset + salt_size + digest_offset + digest_size
768
769    def generate_hash_tree(self, hash_level_offsets, tree_size):
770        hash_ret = bytearray(tree_size)
771        hash_src_offset = 0
772        hash_src_size = self.image_handle.img_size
773        level_num = 0
774        print("hash_level_offsets: {}, tree_size: {}".format(hash_level_offsets, tree_size))
775
776        while hash_src_size > self.block_size:
777            print("hash_src_size: ", hash_src_size)
778            level_output_list = []
779            remaining = hash_src_size
780            while remaining > 0:
781                hasher = hashlib.new(self.hash_algo, self.salt)
782                if level_num == 0:
783                    begin = hash_src_offset + hash_src_size - remaining
784                    end = min(remaining, self.block_size)
785                    self.image_handle.seek(begin)
786                    data = self.image_handle.read(end)
787                else:
788                    offset = hash_level_offsets[level_num - 1] + hash_src_size - remaining
789                    data = hash_ret[offset : offset + self.block_size]
790                hasher.update(data)
791
792                remaining -= len(data)
793                if len(data) < self.block_size:
794                    hasher.update(b'\0' * (self.block_size - len(data)))
795                level_output_list.append(hasher.digest())
796
797            level_output = b''.join(level_output_list)
798            level_output_padding = round_to_multiple(len(level_output), self.block_size) - len(level_output)
799            level_output += b'\0' * level_output_padding
800
801            offset = hash_level_offsets[level_num]
802            hash_ret[offset : offset + len(level_output)] = level_output
803
804            hash_src_size = len(level_output)
805            level_num += 1
806
807        hasher = hashlib.new(self.hash_algo, self.salt)
808        hasher.update(level_output)
809
810        return hasher.digest(), bytes(hash_ret)
811
812    def create_hashtree(self, digest_size):
813        level_offsets = []
814        level_sizes = []
815
816        treesize = 0
817        levels = 0
818        size = self.image_handle.img_size
819
820        while size > self.block_size:
821            blocknum = size // self.block_size
822            level_size = round_to_multiple(blocknum * digest_size, self.block_size)
823            level_sizes.append(level_size)
824            treesize += level_size
825            levels += 1
826            size = level_size
827        print("level_sizes: ", level_sizes)
828        for i in range(levels):
829            offset = 0
830            for j in range(i + 1, levels):
831                offset += level_sizes[j]
832            level_offsets.append(offset)
833
834        rootdigest, self.hashtree = self.generate_hash_tree(level_offsets, treesize)
835        if len(self.hashtree) % self.block_size != 0:
836            self.hashtree += b'\0' * (self.block_size - len(self.hashtree) % self.block_size)
837        print("root digest: ", binascii.b2a_hex(rootdigest))
838        return rootdigest
839
840    def image_hash_tree(self):
841        image_hashtree = {"hashtree_offset": self.img_len_with_padding, \
842                          "hashtree_size": len(self.hashtree), "data_block_size": self.block_size, \
843                          "hash_block_size": self.block_size, "fec_num_roots": 0, \
844                          "fec_offset": 240 + len(self.salt) + len(self.digest), "fec_size": 0}
845        hashtree_struct = struct.Struct('Q' * len(image_hashtree))
846        dlist = []
847        for item in image_hashtree:
848            dlist.append(image_hashtree[item])
849        return hashtree_struct.pack(*dlist)
850
851    def hvb_hash_info(self):
852        verity_type = 0
853        if self.vertype == 'hash':
854            verity_type = 1    # hash: 1    hashtree: 2
855
856            self.image_handle.seek(0)
857            image_content = self.image_handle.read(self.image_handle.img_size)
858            hasher = hashlib.new(self.hash_algo, self.salt)
859            hasher.update(image_content)
860            self.digest = hasher.digest()
861        elif self.vertype == 'hashtree':
862            verity_type = 2    # hash: 1    hashtree: 2
863
864            hashtree_hasher = hashlib.new(self.hash_algo, self.salt)
865            digest_size = len(hashtree_hasher.digest())
866            digest_padding = 2 ** ((digest_size - 1).bit_length()) - digest_size
867            print("hash_algo: {}, salt: {}".format(self.hash_algo, self.salt))
868            print("digest_size: {}, digest_padding: {}".format(digest_size, digest_padding))
869            remainder = self.block_size - self.image_handle.img_size % self.block_size
870            if remainder != self.block_size:
871                self.image_handle.append_raw(b'\0' * remainder)
872            self.img_len_with_padding = self.image_handle.img_size
873            self.digest = self.create_hashtree(digest_size)
874        else:
875            print("[hvbtool][ERROR]: Invalid verity_type: %d", self.vertype)
876            sys.exit(1)
877
878        imghash = self.image_hash()
879        imghashtree = self.image_hash_tree()
880        hashpayload = self.salt + self.digest
881
882        hashinfo = imghash + imghashtree + hashpayload
883        self.hashinfo_size = len(hashinfo)
884        self.hvb_cert_content += struct.pack('I', verity_type) + hashinfo
885
886    def hvb_signature_info(self):
887        sig_content = 'sigcontent.bin'
888        # 签名算法  0:SHA256_RSA3072(默认值), 1:SHA256_RSA4096 , 2:SHA256_RSA2048
889        if self.algo not in self.ALGORITHMS:
890            raise HvbError("Unknown algorithm: {}".format(self.algo))
891        algo = self.ALGORITHMS[self.algo]
892        flags = 0       # 预留的flag标记,默认全为0
893        keyblock_offset = 144 + self.hashinfo_size + 112
894
895        try:
896            key = RSAPublicKey(self.pubkey)
897        except HvbError as err:
898            sys.exit(1)
899        keyblock = key.get_public_key()
900
901        signature_offset = keyblock_offset + len(keyblock)
902
903        sig_length = len(self.hvb_cert_content) + 112 + len(keyblock)
904
905        self.hvb_cert_content += struct.pack('Q', sig_length) + struct.pack('I', algo.sig_algo) \
906                        + struct.pack('I', flags) + struct.pack('Q', keyblock_offset) \
907                        + struct.pack('Q', len(keyblock)) + struct.pack('Q', signature_offset) \
908                        + struct.pack('Q', algo.sig_bytes) + b'\0' * 64 + keyblock
909
910        if os.path.exists(sig_content):
911            os.remove(sig_content)
912
913        flags = os.O_RDONLY | os.O_WRONLY | os.O_CREAT
914        modes = stat.S_IWUSR | stat.S_IRUSR
915        with os.fdopen(os.open('tmp.bin', flags, modes), 'wb') as tmp_fd:
916            tmp_fd.write(self.hvb_cert_content)
917
918        cmd = ['openssl', 'dgst', '-sign', self.privkey, '-sigopt',  'rsa_padding_mode:pss',
919                '-sigopt', 'rsa_pss_saltlen:{}'.format(len(self.salt)), '-sha256', '-out',
920                sig_content,  'tmp.bin']
921        ret = subprocess.call(cmd)
922        if ret != 0:
923            print("Failed to sign the image.")
924            sys.exit(1)
925
926        flags = os.O_RDONLY | os.O_EXCL
927        with os.fdopen(os.open(sig_content, flags, modes), 'rb') as sig_fd:
928            sigcontent = sig_fd.read()
929
930        self.hvb_cert_content += sigcontent
931
932    def hvb_footer_info(self):
933        self.footer = b''
934        footer_magic = self.MAGIC + b'\0' * 4
935        self.partition_size = int(self.partition_size)
936
937        cert_size = len(self.hvb_cert_content)
938        cert_offset = self.img_len_with_padding + len(self.hashtree)
939
940        if self.padding_size is not None:
941            cert_offset = self.partition_size - self.padding_size
942
943        print("cert_size: %x, cert_offset: %x, partition_size: %x" % (cert_size, cert_offset, self.partition_size))
944        self.footer += footer_magic \
945                       + struct.pack('4Q', cert_offset, cert_size, self.original_image_length, self.partition_size) \
946                       + b'\0' * 64
947
948    def hvb_make_rvt_image(self):
949        self.img = 'tmp_rvt.img'
950        rvtcontent = b''
951        rvtcontent += self.RVT_MAGIC
952        verity_num = len(_params['chain_partition'])
953        rvtcontent += struct.pack('I', verity_num) + b'\0' * 64    # rvt_reversed: 64 bytes
954        cur_sizes = len(rvtcontent)
955
956        for item in _params['chain_partition']:
957            chain_partition_data = item.split(':')
958            partition_name = chain_partition_data[0].strip()
959            pubkey = chain_partition_data[1].strip()
960
961            try:
962                key = RSAPublicKey(pubkey)
963            except HvbError as err:
964                sys.exit(1)
965            pubkey_payload = key.get_public_key()
966            pubkey_len = len(pubkey_payload)
967            pubkey_offset = cur_sizes + 80
968            rvtcontent += partition_name.encode() + b'\0' * (64 - len(partition_name))    # partition_name
969            rvtcontent += struct.pack('Q', pubkey_offset) # pubkey_offset
970            rvtcontent += struct.pack('Q', pubkey_len)    # pubkey_len
971            rvtcontent += pubkey_payload  # pubkey_payload
972            cur_sizes += 80 + pubkey_len
973
974        if os.path.exists(self.img):
975            os.remove(self.img)
976
977        flags = os.O_WRONLY | os.O_RDONLY | os.O_CREAT
978        modes = stat.S_IWUSR | stat.S_IRUSR
979        with os.fdopen(os.open(self.img, flags, modes), 'wb') as rvt_fd:
980            rvt_fd.write(rvtcontent)
981
982        self.original_image_info()
983        self.hvb_make_image()
984
985    def hvb_combine_image_info(self):
986        cert_and_footer = b''
987        hashtree_length = len(self.hashtree)
988        hvb_cert_length = len(self.hvb_cert_content)
989        image = os.path.abspath(self.img)
990        signed_image = os.path.abspath(self.signed_img)
991
992        shutil.copy(image, signed_image)
993        image = ImageHandle(signed_image)
994
995        padding = round_to_multiple(image.img_size, self.block_size)
996        if padding > image.img_size:
997            if image.is_sparse is True:     # Original image size must be multiple of block_size
998                raise HvbError("The sparse image size is not multiple of the block size.")
999            image.truncate(padding)
1000
1001        padding = round_to_multiple((hvb_cert_length + self.FOOTER_SIZE), self.block_size)
1002        if padding > (hvb_cert_length + self.FOOTER_SIZE):
1003            cert_and_footer = self.hvb_cert_content + b'\0' * (padding - (self.FOOTER_SIZE + hvb_cert_length)) + \
1004                self.footer
1005        else:
1006            cert_and_footer = self.hvb_cert_content + self.footer
1007
1008        if self.partition_size < image.img_size + hashtree_length + len(cert_and_footer):
1009            raise HvbError("[hvbtool][ERROR]: Partition size is too small!")
1010
1011        cert_and_footer = self.hvb_cert_content + b'\0' * (self.partition_size - image.img_size - \
1012            hashtree_length - hvb_cert_length - self.FOOTER_SIZE) + self.footer
1013        image.append_raw(self.hashtree)
1014        image.append_raw(cert_and_footer)
1015
1016    def parse_rvt_image(self, handle):
1017        handle.seek(0)
1018        msg = ''
1019
1020        header = handle.read(8)
1021        magic, verity_num = struct.unpack('4sI', header)
1022
1023        msg += "[rvt info]: \n"
1024        if magic != self.RVT_MAGIC:
1025            raise HvbError("It is not a valid rvt image.")
1026
1027        handle.seek(72)
1028        for i in range(verity_num):
1029            #The size of pubkey_des excludes pubkey_payload is 80 bytes
1030            data = handle.read(80)
1031            name, pubkey_offset, pubkey_len = struct.unpack('64s2Q', data)
1032            msg += '\tChain Partition descriptor: \n'
1033            msg += '\t\tPartition Name:      {}\n'.format(name.decode())
1034            pubkey = handle.read(pubkey_len)
1035            msg += "\t\tPublic key (sha256):   {}\n\n".format(hashlib.sha256(pubkey).hexdigest())
1036
1037        print(msg)
1038
1039    def hvb_make_image(self):
1040        self.hvb_header()
1041        self.hvb_image_info()
1042        self.hvb_hash_info()
1043        self.hvb_signature_info()
1044        self.hvb_footer_info()
1045        self.hvb_combine_image_info()
1046
1047    def hvb_parse_image(self):
1048        try:
1049            image = ImageHandle(self.img)
1050            image.seek(self.original_image_length - self.FOOTER_SIZE)
1051            footer_bin = image.read(self.FOOTER_SIZE)
1052            footer = HvbFooter(footer_bin)
1053            footer.info_footer()
1054
1055            image.seek(footer.certoffset)
1056            cert_bin = image.read(footer.certsize)
1057            cert = HvbCert(cert_bin)
1058            cert.info_cert()
1059
1060            if 'rvt' in cert.partition.decode():
1061                self.parse_rvt_image(image)
1062        except (HvbError, struct.error):
1063            raise HvbError("Failed to parse the image!")
1064
1065    def hvb_erase_image(self):
1066        try:
1067            image = ImageHandle(self.img)
1068            image.seek(self.original_image_length - self.FOOTER_SIZE)
1069            footer_bin = image.read(self.FOOTER_SIZE)
1070            footer = HvbFooter(footer_bin)
1071            image.seek(0)
1072            image.truncate(footer.imagesize)
1073        except (HvbError, struct.error):
1074            raise HvbError("Failed to erase image.")
1075
1076
1077def print_help():
1078    print("usage: hvbtool.py [-h]")
1079    print("\t\t{make_hash_footer, make_hashtree_footer, make_rvt_image, parse_image}")
1080
1081
1082def parse_arguments(argvs):
1083    global _params
1084    length = len(argvs)
1085    i = 0
1086    args = list()
1087
1088    if length % 2 != 0:
1089        print_help()
1090        print("[hvbtool][ERROR]: invalid argument format!")
1091        sys.exit(1)
1092
1093    while (i < length):
1094        args.append([argvs[i], argvs[i + 1]])
1095        i = i + 2
1096
1097    act = args[0][1]
1098    if act.strip() in VERITY_TYPE.keys():
1099        _params['verity_type'] = VERITY_TYPE[act]
1100        print(_params['verity_type'])
1101    else:
1102        _params['verity_type'] = 'hash'
1103
1104    for item in args[1:]:
1105        itemkey = item[0].strip()[2:]
1106        if itemkey in _params.keys():
1107            if itemkey == "chain_partition":
1108                _params[itemkey].append(item[1].strip())
1109            else:
1110                _params[itemkey] = item[1].strip()
1111        else:
1112            print("[hvbtool][ERROR]: Unknown argument: %s" % item[0])
1113            sys.exit(1)
1114    return act
1115
1116
1117def necessary_arguments_check(check_list):
1118    for item in check_list:
1119        if _params[item] is None or len(_params[item]) == 0:
1120            print("[hvbtool][ERROR]: The argument '{}' is necessary.".format(item))
1121            return False
1122    return True
1123
1124
1125def check_arguments(act):
1126    make_image_arguments_list = ['image', 'salt', 'pubkey', 'rollback_index', 'rollback_location']
1127    make_rvt_image_arguments_list = ['salt', 'pubkey', 'rollback_index', 'rollback_location', 'chain_partition']
1128    parse_erase_image_arguments_list = ['image']
1129    ret = False
1130
1131    if act == 'make_hash_footer' or act == 'make_hashtree_footer':
1132        ret = necessary_arguments_check(make_image_arguments_list)
1133    elif act == 'make_rvt_image':
1134        ret = necessary_arguments_check(make_rvt_image_arguments_list)
1135    elif act == 'parse_image' or act == 'erase_image':
1136        ret = necessary_arguments_check(parse_erase_image_arguments_list)
1137    else:
1138        print("[hvbtool][ERROR]: Unkown action: {}".format(act))
1139
1140    if ret is False:
1141        sys.exit(1)
1142
1143
1144def main(obj, act):
1145    if act == 'make_hash_footer' or act == 'make_hashtree_footer':
1146        obj.hvb_make_image()
1147    elif act == 'parse_image':
1148        obj.hvb_parse_image()
1149    elif act == 'make_rvt_image':
1150        obj.hvb_make_rvt_image()
1151    elif act == 'erase_image':
1152        obj.hvb_erase_image()
1153    else:
1154        raise HvbError("Unknown action: {}".format(act))
1155
1156if __name__ == '__main__':
1157    action = parse_arguments(sys.argv)
1158    check_arguments(action)
1159    tool = HvbTool()
1160    try:
1161        main(tool, action)
1162    except (HvbError, struct.error):
1163        print("HVB COMMAND FAILED!")
1164        sys.exit(1)
1165    sys.exit(0)
1166