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