1#! /usr/bin/env python3 2# 3# Copyright 2017-2020 Linaro Limited 4# Copyright 2019-2023 Arm Limited 5# 6# SPDX-License-Identifier: Apache-2.0 7# 8# Licensed under the Apache License, Version 2.0 (the "License"); 9# you may not use this file except in compliance with the License. 10# You may obtain a copy of the License at 11# 12# http://www.apache.org/licenses/LICENSE-2.0 13# 14# Unless required by applicable law or agreed to in writing, software 15# distributed under the License is distributed on an "AS IS" BASIS, 16# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17# See the License for the specific language governing permissions and 18# limitations under the License. 19 20import re 21import click 22import getpass 23import imgtool.keys as keys 24import sys 25import struct 26import os 27import lzma 28import hashlib 29import base64 30from imgtool import image, imgtool_version 31from imgtool.version import decode_version 32from imgtool.dumpinfo import dump_imginfo 33from .keys import ( 34 RSAUsageError, ECDSAUsageError, Ed25519UsageError, X25519UsageError) 35 36comp_default_dictsize=131072 37comp_default_pb=2 38comp_default_lc=3 39comp_default_lp=1 40comp_default_preset=9 41 42 43MIN_PYTHON_VERSION = (3, 6) 44if sys.version_info < MIN_PYTHON_VERSION: 45 sys.exit("Python %s.%s or newer is required by imgtool." 46 % MIN_PYTHON_VERSION) 47 48 49def gen_rsa2048(keyfile, passwd): 50 keys.RSA.generate().export_private(path=keyfile, passwd=passwd) 51 52 53def gen_rsa3072(keyfile, passwd): 54 keys.RSA.generate(key_size=3072).export_private(path=keyfile, 55 passwd=passwd) 56 57 58def gen_ecdsa_p256(keyfile, passwd): 59 keys.ECDSA256P1.generate().export_private(keyfile, passwd=passwd) 60 61 62def gen_ecdsa_p384(keyfile, passwd): 63 keys.ECDSA384P1.generate().export_private(keyfile, passwd=passwd) 64 65 66def gen_ed25519(keyfile, passwd): 67 keys.Ed25519.generate().export_private(path=keyfile, passwd=passwd) 68 69 70def gen_x25519(keyfile, passwd): 71 keys.X25519.generate().export_private(path=keyfile, passwd=passwd) 72 73 74valid_langs = ['c', 'rust'] 75valid_hash_encodings = ['lang-c', 'raw'] 76valid_encodings = ['lang-c', 'lang-rust', 'pem', 'raw'] 77keygens = { 78 'rsa-2048': gen_rsa2048, 79 'rsa-3072': gen_rsa3072, 80 'ecdsa-p256': gen_ecdsa_p256, 81 'ecdsa-p384': gen_ecdsa_p384, 82 'ed25519': gen_ed25519, 83 'x25519': gen_x25519, 84} 85valid_formats = ['openssl', 'pkcs8'] 86valid_sha = [ 'auto', '256', '384', '512' ] 87 88 89def load_signature(sigfile): 90 with open(sigfile, 'rb') as f: 91 signature = base64.b64decode(f.read()) 92 return signature 93 94 95def save_signature(sigfile, sig): 96 with open(sigfile, 'wb') as f: 97 signature = base64.b64encode(sig) 98 f.write(signature) 99 100 101def load_key(keyfile): 102 # TODO: better handling of invalid pass-phrase 103 key = keys.load(keyfile) 104 if key is not None: 105 return key 106 passwd = getpass.getpass("Enter key passphrase: ").encode('utf-8') 107 return keys.load(keyfile, passwd) 108 109 110def get_password(): 111 while True: 112 passwd = getpass.getpass("Enter key passphrase: ") 113 passwd2 = getpass.getpass("Reenter passphrase: ") 114 if passwd == passwd2: 115 break 116 print("Passwords do not match, try again") 117 118 # Password must be bytes, always use UTF-8 for consistent 119 # encoding. 120 return passwd.encode('utf-8') 121 122 123@click.option('-p', '--password', is_flag=True, 124 help='Prompt for password to protect key') 125@click.option('-t', '--type', metavar='type', required=True, 126 type=click.Choice(keygens.keys()), prompt=True, 127 help='{}'.format('One of: {}'.format(', '.join(keygens.keys())))) 128@click.option('-k', '--key', metavar='filename', required=True) 129@click.command(help='Generate pub/private keypair') 130def keygen(type, key, password): 131 password = get_password() if password else None 132 keygens[type](key, password) 133 134 135@click.option('-l', '--lang', metavar='lang', 136 type=click.Choice(valid_langs), 137 help='This option is deprecated. Please use the ' 138 '`--encoding` option. ' 139 'Valid langs: {}'.format(', '.join(valid_langs))) 140@click.option('-e', '--encoding', metavar='encoding', 141 type=click.Choice(valid_encodings), 142 help='Valid encodings: {}'.format(', '.join(valid_encodings))) 143@click.option('-k', '--key', metavar='filename', required=True) 144@click.option('-o', '--output', metavar='output', required=False, 145 help='Specify the output file\'s name. \ 146 The stdout is used if it is not provided.') 147@click.command(help='Dump public key from keypair') 148def getpub(key, encoding, lang, output): 149 if encoding and lang: 150 raise click.UsageError('Please use only one of `--encoding/-e` ' 151 'or `--lang/-l`') 152 elif not encoding and not lang: 153 # Preserve old behavior defaulting to `c`. If `lang` is removed, 154 # `default=valid_encodings[0]` should be added to `-e` param. 155 lang = valid_langs[0] 156 key = load_key(key) 157 158 if not output: 159 output = sys.stdout 160 if key is None: 161 print("Invalid passphrase") 162 elif lang == 'c' or encoding == 'lang-c': 163 key.emit_c_public(file=output) 164 elif lang == 'rust' or encoding == 'lang-rust': 165 key.emit_rust_public(file=output) 166 elif encoding == 'pem': 167 key.emit_public_pem(file=output) 168 elif encoding == 'raw': 169 key.emit_raw_public(file=output) 170 else: 171 raise click.UsageError() 172 173 174@click.option('-e', '--encoding', metavar='encoding', 175 type=click.Choice(valid_hash_encodings), 176 help='Valid encodings: {}. ' 177 'Default value is {}.' 178 .format(', '.join(valid_hash_encodings), 179 valid_hash_encodings[0])) 180@click.option('-k', '--key', metavar='filename', required=True) 181@click.option('-o', '--output', metavar='output', required=False, 182 help='Specify the output file\'s name. \ 183 The stdout is used if it is not provided.') 184@click.command(help='Dump the SHA256 hash of the public key') 185def getpubhash(key, output, encoding): 186 if not encoding: 187 encoding = valid_hash_encodings[0] 188 key = load_key(key) 189 190 if not output: 191 output = sys.stdout 192 if key is None: 193 print("Invalid passphrase") 194 elif encoding == 'lang-c': 195 key.emit_c_public_hash(file=output) 196 elif encoding == 'raw': 197 key.emit_raw_public_hash(file=output) 198 else: 199 raise click.UsageError() 200 201 202@click.option('--minimal', default=False, is_flag=True, 203 help='Reduce the size of the dumped private key to include only ' 204 'the minimum amount of data required to decrypt. This ' 205 'might require changes to the build config. Check the docs!' 206 ) 207@click.option('-k', '--key', metavar='filename', required=True) 208@click.option('-f', '--format', 209 type=click.Choice(valid_formats), 210 help='Valid formats: {}'.format(', '.join(valid_formats)) 211 ) 212@click.command(help='Dump private key from keypair') 213def getpriv(key, minimal, format): 214 key = load_key(key) 215 if key is None: 216 print("Invalid passphrase") 217 try: 218 key.emit_private(minimal, format) 219 except (RSAUsageError, ECDSAUsageError, Ed25519UsageError, 220 X25519UsageError) as e: 221 raise click.UsageError(e) 222 223 224@click.argument('imgfile') 225@click.option('-k', '--key', metavar='filename') 226@click.command(help="Check that signed image can be verified by given key") 227def verify(key, imgfile): 228 key = load_key(key) if key else None 229 ret, version, digest, signature = image.Image.verify(imgfile, key) 230 if ret == image.VerifyResult.OK: 231 print("Image was correctly validated") 232 print("Image version: {}.{}.{}+{}".format(*version)) 233 if digest: 234 print("Image digest: {}".format(digest.hex())) 235 if signature and digest is None: 236 print("Image signature over image: {}".format(signature.hex())) 237 return 238 elif ret == image.VerifyResult.INVALID_MAGIC: 239 print("Invalid image magic; is this an MCUboot image?") 240 elif ret == image.VerifyResult.INVALID_TLV_INFO_MAGIC: 241 print("Invalid TLV info magic; is this an MCUboot image?") 242 elif ret == image.VerifyResult.INVALID_HASH: 243 print("Image has an invalid hash") 244 elif ret == image.VerifyResult.INVALID_SIGNATURE: 245 print("No signature found for the given key") 246 elif ret == image.VerifyResult.KEY_MISMATCH: 247 print("Key type does not match TLV record") 248 else: 249 print("Unknown return code: {}".format(ret)) 250 sys.exit(1) 251 252 253@click.argument('imgfile') 254@click.option('-o', '--outfile', metavar='filename', required=False, 255 help='Save image information to outfile in YAML format') 256@click.option('-s', '--silent', default=False, is_flag=True, 257 help='Do not print image information to output') 258@click.command(help='Print header, TLV area and trailer information ' 259 'of a signed image') 260def dumpinfo(imgfile, outfile, silent): 261 dump_imginfo(imgfile, outfile, silent) 262 print("dumpinfo has run successfully") 263 264 265def validate_version(ctx, param, value): 266 try: 267 decode_version(value) 268 return value 269 except ValueError as e: 270 raise click.BadParameter("{}".format(e)) 271 272 273def validate_security_counter(ctx, param, value): 274 if value is not None: 275 if value.lower() == 'auto': 276 return 'auto' 277 else: 278 try: 279 return int(value, 0) 280 except ValueError: 281 raise click.BadParameter( 282 "{} is not a valid integer. Please use code literals " 283 "prefixed with 0b/0B, 0o/0O, or 0x/0X as necessary." 284 .format(value)) 285 286 287def validate_header_size(ctx, param, value): 288 min_hdr_size = image.IMAGE_HEADER_SIZE 289 if value < min_hdr_size: 290 raise click.BadParameter( 291 "Minimum value for -H/--header-size is {}".format(min_hdr_size)) 292 return value 293 294 295def get_dependencies(ctx, param, value): 296 if value is not None: 297 versions = [] 298 images = re.findall(r"\((\d+)", value) 299 if len(images) == 0: 300 raise click.BadParameter( 301 "Image dependency format is invalid: {}".format(value)) 302 raw_versions = re.findall(r",\s*([0-9.+]+)\)", value) 303 if len(images) != len(raw_versions): 304 raise click.BadParameter( 305 '''There's a mismatch between the number of dependency images 306 and versions in: {}'''.format(value)) 307 for raw_version in raw_versions: 308 try: 309 versions.append(decode_version(raw_version)) 310 except ValueError as e: 311 raise click.BadParameter("{}".format(e)) 312 dependencies = dict() 313 dependencies[image.DEP_IMAGES_KEY] = images 314 dependencies[image.DEP_VERSIONS_KEY] = versions 315 return dependencies 316 317def create_lzma2_header(dictsize, pb, lc, lp): 318 header = bytearray() 319 for i in range(0, 40): 320 if dictsize <= ((2 | ((i) & 1)) << int((i) / 2 + 11)): 321 header.append(i) 322 break 323 header.append( ( pb * 5 + lp) * 9 + lc) 324 return header 325 326class BasedIntParamType(click.ParamType): 327 name = 'integer' 328 329 def convert(self, value, param, ctx): 330 try: 331 return int(value, 0) 332 except ValueError: 333 self.fail('%s is not a valid integer. Please use code literals ' 334 'prefixed with 0b/0B, 0o/0O, or 0x/0X as necessary.' 335 % value, param, ctx) 336 337 338@click.argument('outfile') 339@click.argument('infile') 340@click.option('--non-bootable', default=False, is_flag=True, 341 help='Mark the image as non-bootable.') 342@click.option('--custom-tlv', required=False, nargs=2, default=[], 343 multiple=True, metavar='[tag] [value]', 344 help='Custom TLV that will be placed into protected area. ' 345 'Add "0x" prefix if the value should be interpreted as an ' 346 'integer, otherwise it will be interpreted as a string. ' 347 'Specify the option multiple times to add multiple TLVs.') 348@click.option('-R', '--erased-val', type=click.Choice(['0', '0xff']), 349 required=False, 350 help='The value that is read back from erased flash.') 351@click.option('-x', '--hex-addr', type=BasedIntParamType(), required=False, 352 help='Adjust address in hex output file.') 353@click.option('-L', '--load-addr', type=BasedIntParamType(), required=False, 354 help='Load address for image when it should run from RAM.') 355@click.option('-F', '--rom-fixed', type=BasedIntParamType(), required=False, 356 help='Set flash address the image is built for.') 357@click.option('--save-enctlv', default=False, is_flag=True, 358 help='When upgrading, save encrypted key TLVs instead of plain ' 359 'keys. Enable when BOOT_SWAP_SAVE_ENCTLV config option ' 360 'was set.') 361@click.option('-E', '--encrypt', metavar='filename', 362 help='Encrypt image using the provided public key. ' 363 '(Not supported in direct-xip or ram-load mode.)') 364@click.option('--encrypt-keylen', default='128', 365 type=click.Choice(['128', '256']), 366 help='When encrypting the image using AES, select a 128 bit or ' 367 '256 bit key len.') 368@click.option('--compression', default='disabled', 369 type=click.Choice(['disabled', 'lzma2', 'lzma2armthumb']), 370 help='Enable image compression using specified type. ' 371 'Will fall back without image compression automatically ' 372 'if the compression increases the image size.') 373@click.option('-c', '--clear', required=False, is_flag=True, default=False, 374 help='Output a non-encrypted image with encryption capabilities,' 375 'so it can be installed in the primary slot, and encrypted ' 376 'when swapped to the secondary.') 377@click.option('-e', '--endian', type=click.Choice(['little', 'big']), 378 default='little', help="Select little or big endian") 379@click.option('--overwrite-only', default=False, is_flag=True, 380 help='Use overwrite-only instead of swap upgrades') 381@click.option('--boot-record', metavar='sw_type', help='Create CBOR encoded ' 382 'boot record TLV. The sw_type represents the role of the ' 383 'software component (e.g. CoFM for coprocessor firmware). ' 384 '[max. 12 characters]') 385@click.option('-M', '--max-sectors', type=int, 386 help='When padding allow for this amount of sectors (defaults ' 387 'to 128)') 388@click.option('--confirm', default=False, is_flag=True, 389 help='When padding the image, mark it as confirmed (implies ' 390 '--pad)') 391@click.option('--pad', default=False, is_flag=True, 392 help='Pad image to --slot-size bytes, adding trailer magic') 393@click.option('-S', '--slot-size', type=BasedIntParamType(), required=True, 394 help='Size of the slot. If the slots have different sizes, use ' 395 'the size of the secondary slot.') 396@click.option('--pad-header', default=False, is_flag=True, 397 help='Add --header-size zeroed bytes at the beginning of the ' 398 'image') 399@click.option('-H', '--header-size', callback=validate_header_size, 400 type=BasedIntParamType(), required=True) 401@click.option('--pad-sig', default=False, is_flag=True, 402 help='Add 0-2 bytes of padding to ECDSA signature ' 403 '(for mcuboot <1.5)') 404@click.option('-d', '--dependencies', callback=get_dependencies, 405 required=False, help='''Add dependence on another image, format: 406 "(<image_ID>,<image_version>), ... "''') 407@click.option('-s', '--security-counter', callback=validate_security_counter, 408 help='Specify the value of security counter. Use the `auto` ' 409 'keyword to automatically generate it from the image version.') 410@click.option('-v', '--version', callback=validate_version, required=True) 411@click.option('--align', type=click.Choice(['1', '2', '4', '8', '16', '32']), 412 default='1', 413 required=False, 414 help='Alignment used by swap update modes.') 415@click.option('--max-align', type=click.Choice(['8', '16', '32']), 416 required=False, 417 help='Maximum flash alignment. Set if flash alignment of the ' 418 'primary and secondary slot differ and any of them is larger ' 419 'than 8.') 420@click.option('--public-key-format', type=click.Choice(['hash', 'full']), 421 default='hash', help='In what format to add the public key to ' 422 'the image manifest: full key or hash of the key.') 423@click.option('-k', '--key', metavar='filename') 424@click.option('--fix-sig', metavar='filename', 425 help='fixed signature for the image. It will be used instead of ' 426 'the signature calculated using the public key') 427@click.option('--fix-sig-pubkey', metavar='filename', 428 help='public key relevant to fixed signature') 429@click.option('--pure', 'is_pure', is_flag=True, default=False, show_default=True, 430 help='Expected Pure variant of signature; the Pure variant is ' 431 'expected to be signature done over an image rather than hash of ' 432 'that image.') 433@click.option('--sig-out', metavar='filename', 434 help='Path to the file to which signature will be written. ' 435 'The image signature will be encoded as base64 formatted string') 436@click.option('--sha', 'user_sha', type=click.Choice(valid_sha), default='auto', 437 help='selected sha algorithm to use; defaults to "auto" which is 256 if ' 438 'no cryptographic signature is used, or default for signature type') 439@click.option('--vector-to-sign', type=click.Choice(['payload', 'digest']), 440 help='send to OUTFILE the payload or payload''s digest instead ' 441 'of complied image. These data can be used for external image ' 442 'signing') 443@click.command(help='''Create a signed or unsigned image\n 444 INFILE and OUTFILE are parsed as Intel HEX if the params have 445 .hex extension, otherwise binary format is used''') 446def sign(key, public_key_format, align, version, pad_sig, header_size, 447 pad_header, slot_size, pad, confirm, max_sectors, overwrite_only, 448 endian, encrypt_keylen, encrypt, compression, infile, outfile, 449 dependencies, load_addr, hex_addr, erased_val, save_enctlv, 450 security_counter, boot_record, custom_tlv, rom_fixed, max_align, 451 clear, fix_sig, fix_sig_pubkey, sig_out, user_sha, is_pure, 452 vector_to_sign, non_bootable): 453 454 if confirm: 455 # Confirmed but non-padded images don't make much sense, because 456 # otherwise there's no trailer area for writing the confirmed status. 457 pad = True 458 img = image.Image(version=decode_version(version), header_size=header_size, 459 pad_header=pad_header, pad=pad, confirm=confirm, 460 align=int(align), slot_size=slot_size, 461 max_sectors=max_sectors, overwrite_only=overwrite_only, 462 endian=endian, load_addr=load_addr, rom_fixed=rom_fixed, 463 erased_val=erased_val, save_enctlv=save_enctlv, 464 security_counter=security_counter, max_align=max_align, 465 non_bootable=non_bootable) 466 compression_tlvs = {} 467 img.load(infile) 468 key = load_key(key) if key else None 469 enckey = load_key(encrypt) if encrypt else None 470 if enckey and key: 471 if ((isinstance(key, keys.ECDSA256P1) and 472 not isinstance(enckey, keys.ECDSA256P1Public)) 473 or (isinstance(key, keys.ECDSA384P1) and 474 not isinstance(enckey, keys.ECDSA384P1Public)) 475 or (isinstance(key, keys.RSA) and 476 not isinstance(enckey, keys.RSAPublic))): 477 # FIXME 478 raise click.UsageError("Signing and encryption must use the same " 479 "type of key") 480 481 if pad_sig and hasattr(key, 'pad_sig'): 482 key.pad_sig = True 483 484 # Get list of custom protected TLVs from the command-line 485 custom_tlvs = {} 486 for tlv in custom_tlv: 487 tag = int(tlv[0], 0) 488 if tag in custom_tlvs: 489 raise click.UsageError('Custom TLV %s already exists.' % hex(tag)) 490 if tag in image.TLV_VALUES.values(): 491 raise click.UsageError( 492 'Custom TLV %s conflicts with predefined TLV.' % hex(tag)) 493 494 value = tlv[1] 495 if value.startswith('0x'): 496 if len(value[2:]) % 2: 497 raise click.UsageError('Custom TLV length is odd.') 498 custom_tlvs[tag] = bytes.fromhex(value[2:]) 499 else: 500 custom_tlvs[tag] = value.encode('utf-8') 501 502 # Allow signature calculated externally. 503 raw_signature = load_signature(fix_sig) if fix_sig else None 504 505 baked_signature = None 506 pub_key = None 507 508 if raw_signature is not None: 509 if fix_sig_pubkey is None: 510 raise click.UsageError( 511 'public key of the fixed signature is not specified') 512 513 pub_key = load_key(fix_sig_pubkey) 514 515 baked_signature = { 516 'value': raw_signature 517 } 518 519 if is_pure and user_sha != 'auto': 520 raise click.UsageError( 521 'Pure signatures, currently, enforces preferred hash algorithm, ' 522 'and forbids sha selection by user.') 523 524 if compression in ["lzma2", "lzma2armthumb"]: 525 img.create(key, public_key_format, enckey, dependencies, boot_record, 526 custom_tlvs, compression_tlvs, None, int(encrypt_keylen), clear, 527 baked_signature, pub_key, vector_to_sign, user_sha=user_sha, 528 is_pure=is_pure, keep_comp_size=False, dont_encrypt=True) 529 compressed_img = image.Image(version=decode_version(version), 530 header_size=header_size, pad_header=pad_header, 531 pad=pad, confirm=confirm, align=int(align), 532 slot_size=slot_size, max_sectors=max_sectors, 533 overwrite_only=overwrite_only, endian=endian, 534 load_addr=load_addr, rom_fixed=rom_fixed, 535 erased_val=erased_val, save_enctlv=save_enctlv, 536 security_counter=security_counter, max_align=max_align) 537 compression_filters = [ 538 {"id": lzma.FILTER_LZMA2, "preset": comp_default_preset, 539 "dict_size": comp_default_dictsize, "lp": comp_default_lp, 540 "lc": comp_default_lc} 541 ] 542 if compression == "lzma2armthumb": 543 compression_filters.insert(0, {"id":lzma.FILTER_ARMTHUMB}) 544 compressed_data = lzma.compress(img.get_infile_data(),filters=compression_filters, 545 format=lzma.FORMAT_RAW) 546 uncompressed_size = len(img.get_infile_data()) 547 compressed_size = len(compressed_data) 548 print(f"compressed image size: {compressed_size} bytes") 549 print(f"original image size: {uncompressed_size} bytes") 550 compression_tlvs["DECOMP_SIZE"] = struct.pack( 551 img.get_struct_endian() + 'L', img.image_size) 552 compression_tlvs["DECOMP_SHA"] = img.image_hash 553 compression_tlvs_size = len(compression_tlvs["DECOMP_SIZE"]) 554 compression_tlvs_size += len(compression_tlvs["DECOMP_SHA"]) 555 if img.get_signature(): 556 compression_tlvs["DECOMP_SIGNATURE"] = img.get_signature() 557 compression_tlvs_size += len(compression_tlvs["DECOMP_SIGNATURE"]) 558 if (compressed_size + compression_tlvs_size) < uncompressed_size: 559 compression_header = create_lzma2_header( 560 dictsize = comp_default_dictsize, pb = comp_default_pb, 561 lc = comp_default_lc, lp = comp_default_lp) 562 compressed_img.load_compressed(compressed_data, compression_header) 563 compressed_img.base_addr = img.base_addr 564 keep_comp_size = False; 565 if enckey: 566 keep_comp_size = True 567 compressed_img.create(key, public_key_format, enckey, 568 dependencies, boot_record, custom_tlvs, compression_tlvs, 569 compression, int(encrypt_keylen), clear, baked_signature, 570 pub_key, vector_to_sign, user_sha=user_sha, 571 is_pure=is_pure, keep_comp_size=keep_comp_size) 572 img = compressed_img 573 else: 574 img.create(key, public_key_format, enckey, dependencies, boot_record, 575 custom_tlvs, compression_tlvs, None, int(encrypt_keylen), clear, 576 baked_signature, pub_key, vector_to_sign, user_sha=user_sha, 577 is_pure=is_pure) 578 img.save(outfile, hex_addr) 579 if sig_out is not None: 580 new_signature = img.get_signature() 581 save_signature(sig_out, new_signature) 582 583 584class AliasesGroup(click.Group): 585 586 _aliases = { 587 "create": "sign", 588 } 589 590 def list_commands(self, ctx): 591 cmds = [k for k in self.commands] 592 aliases = [k for k in self._aliases] 593 return sorted(cmds + aliases) 594 595 def get_command(self, ctx, cmd_name): 596 rv = click.Group.get_command(self, ctx, cmd_name) 597 if rv is not None: 598 return rv 599 if cmd_name in self._aliases: 600 return click.Group.get_command(self, ctx, self._aliases[cmd_name]) 601 return None 602 603 604@click.command(help='Print imgtool version information') 605def version(): 606 print(imgtool_version) 607 608 609@click.command(cls=AliasesGroup, 610 context_settings=dict(help_option_names=['-h', '--help'])) 611def imgtool(): 612 pass 613 614 615imgtool.add_command(keygen) 616imgtool.add_command(getpub) 617imgtool.add_command(getpubhash) 618imgtool.add_command(getpriv) 619imgtool.add_command(verify) 620imgtool.add_command(sign) 621imgtool.add_command(version) 622imgtool.add_command(dumpinfo) 623 624 625if __name__ == '__main__': 626 imgtool() 627