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