Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 1 | #! /usr/bin/env python3 |
| 2 | # |
David Vincze | 71b8f98 | 2020-03-17 19:08:12 +0100 | [diff] [blame] | 3 | # Copyright 2017-2020 Linaro Limited |
David Vincze | 1a7a690 | 2020-02-18 15:05:16 +0100 | [diff] [blame] | 4 | # Copyright 2019-2020 Arm Limited |
Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 5 | # |
| 6 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| 7 | # you may not use this file except in compliance with the License. |
| 8 | # You may obtain a copy of the License at |
| 9 | # |
| 10 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 11 | # |
| 12 | # Unless required by applicable law or agreed to in writing, software |
| 13 | # distributed under the License is distributed on an "AS IS" BASIS, |
| 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 15 | # See the License for the specific language governing permissions and |
| 16 | # limitations under the License. |
| 17 | |
David Vincze | da8c919 | 2019-03-26 17:17:41 +0100 | [diff] [blame] | 18 | import re |
Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 19 | import click |
| 20 | import getpass |
| 21 | import imgtool.keys as keys |
Fabio Utzig | 4a5477a | 2019-05-27 15:45:08 -0300 | [diff] [blame] | 22 | import sys |
Fabio Utzig | 25c6a15 | 2019-09-10 12:52:26 -0300 | [diff] [blame] | 23 | from imgtool import image, imgtool_version |
Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 24 | from imgtool.version import decode_version |
Fabio Utzig | 4facd1b | 2020-04-02 13:17:38 -0300 | [diff] [blame] | 25 | from .keys import ( |
| 26 | RSAUsageError, ECDSAUsageError, Ed25519UsageError, X25519UsageError) |
Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 27 | |
David Vincze | 71b8f98 | 2020-03-17 19:08:12 +0100 | [diff] [blame] | 28 | MIN_PYTHON_VERSION = (3, 6) |
| 29 | if sys.version_info < MIN_PYTHON_VERSION: |
| 30 | sys.exit("Python %s.%s or newer is required by imgtool." |
| 31 | % MIN_PYTHON_VERSION) |
| 32 | |
Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 33 | |
| 34 | def gen_rsa2048(keyfile, passwd): |
Fabio Utzig | 19fd79a | 2019-05-08 18:20:39 -0300 | [diff] [blame] | 35 | keys.RSA.generate().export_private(path=keyfile, passwd=passwd) |
| 36 | |
| 37 | |
| 38 | def gen_rsa3072(keyfile, passwd): |
| 39 | keys.RSA.generate(key_size=3072).export_private(path=keyfile, |
| 40 | passwd=passwd) |
Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 41 | |
| 42 | |
| 43 | def gen_ecdsa_p256(keyfile, passwd): |
| 44 | keys.ECDSA256P1.generate().export_private(keyfile, passwd=passwd) |
| 45 | |
| 46 | |
| 47 | def gen_ecdsa_p224(keyfile, passwd): |
| 48 | print("TODO: p-224 not yet implemented") |
| 49 | |
| 50 | |
Fabio Utzig | 8101d1f | 2019-05-09 15:03:22 -0300 | [diff] [blame] | 51 | def gen_ed25519(keyfile, passwd): |
Fabio Utzig | 4bd4c7c | 2019-06-27 08:23:21 -0300 | [diff] [blame] | 52 | keys.Ed25519.generate().export_private(path=keyfile, passwd=passwd) |
Fabio Utzig | 8101d1f | 2019-05-09 15:03:22 -0300 | [diff] [blame] | 53 | |
| 54 | |
Fabio Utzig | 4facd1b | 2020-04-02 13:17:38 -0300 | [diff] [blame] | 55 | def gen_x25519(keyfile, passwd): |
| 56 | keys.X25519.generate().export_private(path=keyfile, passwd=passwd) |
| 57 | |
| 58 | |
Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 59 | valid_langs = ['c', 'rust'] |
| 60 | keygens = { |
| 61 | 'rsa-2048': gen_rsa2048, |
Fabio Utzig | 19fd79a | 2019-05-08 18:20:39 -0300 | [diff] [blame] | 62 | 'rsa-3072': gen_rsa3072, |
Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 63 | 'ecdsa-p256': gen_ecdsa_p256, |
| 64 | 'ecdsa-p224': gen_ecdsa_p224, |
Fabio Utzig | 4facd1b | 2020-04-02 13:17:38 -0300 | [diff] [blame] | 65 | 'ed25519': gen_ed25519, |
| 66 | 'x25519': gen_x25519, |
Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 67 | } |
| 68 | |
| 69 | |
| 70 | def load_key(keyfile): |
| 71 | # TODO: better handling of invalid pass-phrase |
| 72 | key = keys.load(keyfile) |
| 73 | if key is not None: |
| 74 | return key |
| 75 | passwd = getpass.getpass("Enter key passphrase: ").encode('utf-8') |
| 76 | return keys.load(keyfile, passwd) |
| 77 | |
| 78 | |
| 79 | def get_password(): |
| 80 | while True: |
| 81 | passwd = getpass.getpass("Enter key passphrase: ") |
| 82 | passwd2 = getpass.getpass("Reenter passphrase: ") |
| 83 | if passwd == passwd2: |
| 84 | break |
| 85 | print("Passwords do not match, try again") |
| 86 | |
| 87 | # Password must be bytes, always use UTF-8 for consistent |
| 88 | # encoding. |
| 89 | return passwd.encode('utf-8') |
| 90 | |
| 91 | |
| 92 | @click.option('-p', '--password', is_flag=True, |
| 93 | help='Prompt for password to protect key') |
| 94 | @click.option('-t', '--type', metavar='type', required=True, |
Fabio Utzig | 7ca2855 | 2019-12-13 11:24:20 -0300 | [diff] [blame] | 95 | type=click.Choice(keygens.keys()), prompt=True, |
| 96 | help='{}'.format('One of: {}'.format(', '.join(keygens.keys())))) |
Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 97 | @click.option('-k', '--key', metavar='filename', required=True) |
| 98 | @click.command(help='Generate pub/private keypair') |
| 99 | def keygen(type, key, password): |
| 100 | password = get_password() if password else None |
| 101 | keygens[type](key, password) |
| 102 | |
| 103 | |
| 104 | @click.option('-l', '--lang', metavar='lang', default=valid_langs[0], |
| 105 | type=click.Choice(valid_langs)) |
| 106 | @click.option('-k', '--key', metavar='filename', required=True) |
Ioannis Konstantelias | 78e57c7 | 2019-11-28 16:06:12 +0200 | [diff] [blame] | 107 | @click.command(help='Dump public key from keypair') |
Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 108 | def getpub(key, lang): |
| 109 | key = load_key(key) |
| 110 | if key is None: |
| 111 | print("Invalid passphrase") |
| 112 | elif lang == 'c': |
Ioannis Konstantelias | 78e57c7 | 2019-11-28 16:06:12 +0200 | [diff] [blame] | 113 | key.emit_c_public() |
Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 114 | elif lang == 'rust': |
Ioannis Konstantelias | 78e57c7 | 2019-11-28 16:06:12 +0200 | [diff] [blame] | 115 | key.emit_rust_public() |
Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 116 | else: |
| 117 | raise ValueError("BUG: should never get here!") |
| 118 | |
| 119 | |
Ioannis Konstantelias | 78e57c7 | 2019-11-28 16:06:12 +0200 | [diff] [blame] | 120 | @click.option('--minimal', default=False, is_flag=True, |
| 121 | help='Reduce the size of the dumped private key to include only ' |
| 122 | 'the minimum amount of data required to decrypt. This ' |
| 123 | 'might require changes to the build config. Check the docs!' |
| 124 | ) |
| 125 | @click.option('-k', '--key', metavar='filename', required=True) |
| 126 | @click.command(help='Dump private key from keypair') |
| 127 | def getpriv(key, minimal): |
| 128 | key = load_key(key) |
| 129 | if key is None: |
| 130 | print("Invalid passphrase") |
Fabio Utzig | 1f50892 | 2020-01-15 11:37:51 -0300 | [diff] [blame] | 131 | try: |
| 132 | key.emit_private(minimal) |
Fabio Utzig | 4facd1b | 2020-04-02 13:17:38 -0300 | [diff] [blame] | 133 | except (RSAUsageError, ECDSAUsageError, Ed25519UsageError, |
| 134 | X25519UsageError) as e: |
Fabio Utzig | 1f50892 | 2020-01-15 11:37:51 -0300 | [diff] [blame] | 135 | raise click.UsageError(e) |
Ioannis Konstantelias | 78e57c7 | 2019-11-28 16:06:12 +0200 | [diff] [blame] | 136 | |
| 137 | |
Fabio Utzig | 4a5477a | 2019-05-27 15:45:08 -0300 | [diff] [blame] | 138 | @click.argument('imgfile') |
| 139 | @click.option('-k', '--key', metavar='filename') |
| 140 | @click.command(help="Check that signed image can be verified by given key") |
| 141 | def verify(key, imgfile): |
| 142 | key = load_key(key) if key else None |
Casper Meijn | 2a01f3f | 2020-08-22 13:51:40 +0200 | [diff] [blame] | 143 | ret, version, digest = image.Image.verify(imgfile, key) |
Fabio Utzig | 4a5477a | 2019-05-27 15:45:08 -0300 | [diff] [blame] | 144 | if ret == image.VerifyResult.OK: |
| 145 | print("Image was correctly validated") |
Marek Pieta | e955510 | 2019-08-08 16:08:16 +0200 | [diff] [blame] | 146 | print("Image version: {}.{}.{}+{}".format(*version)) |
Casper Meijn | 2a01f3f | 2020-08-22 13:51:40 +0200 | [diff] [blame] | 147 | print("Image digest: {}".format(digest.hex())) |
Fabio Utzig | 4a5477a | 2019-05-27 15:45:08 -0300 | [diff] [blame] | 148 | return |
| 149 | elif ret == image.VerifyResult.INVALID_MAGIC: |
| 150 | print("Invalid image magic; is this an MCUboot image?") |
Christian Skubich | f13db12 | 2019-07-31 11:34:15 +0200 | [diff] [blame] | 151 | elif ret == image.VerifyResult.INVALID_TLV_INFO_MAGIC: |
Fabio Utzig | 4a5477a | 2019-05-27 15:45:08 -0300 | [diff] [blame] | 152 | print("Invalid TLV info magic; is this an MCUboot image?") |
| 153 | elif ret == image.VerifyResult.INVALID_HASH: |
| 154 | print("Image has an invalid sha256 digest") |
| 155 | elif ret == image.VerifyResult.INVALID_SIGNATURE: |
| 156 | print("No signature found for the given key") |
Christian Skubich | f13db12 | 2019-07-31 11:34:15 +0200 | [diff] [blame] | 157 | else: |
| 158 | print("Unknown return code: {}".format(ret)) |
Fabio Utzig | 4a5477a | 2019-05-27 15:45:08 -0300 | [diff] [blame] | 159 | sys.exit(1) |
| 160 | |
| 161 | |
Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 162 | def validate_version(ctx, param, value): |
| 163 | try: |
| 164 | decode_version(value) |
| 165 | return value |
| 166 | except ValueError as e: |
| 167 | raise click.BadParameter("{}".format(e)) |
| 168 | |
| 169 | |
David Vincze | 1a7a690 | 2020-02-18 15:05:16 +0100 | [diff] [blame] | 170 | def validate_security_counter(ctx, param, value): |
| 171 | if value is not None: |
| 172 | if value.lower() == 'auto': |
| 173 | return 'auto' |
| 174 | else: |
| 175 | try: |
| 176 | return int(value, 0) |
| 177 | except ValueError: |
| 178 | raise click.BadParameter( |
| 179 | "{} is not a valid integer. Please use code literals " |
| 180 | "prefixed with 0b/0B, 0o/0O, or 0x/0X as necessary." |
| 181 | .format(value)) |
| 182 | |
| 183 | |
Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 184 | def validate_header_size(ctx, param, value): |
| 185 | min_hdr_size = image.IMAGE_HEADER_SIZE |
| 186 | if value < min_hdr_size: |
| 187 | raise click.BadParameter( |
| 188 | "Minimum value for -H/--header-size is {}".format(min_hdr_size)) |
| 189 | return value |
| 190 | |
| 191 | |
David Vincze | da8c919 | 2019-03-26 17:17:41 +0100 | [diff] [blame] | 192 | def get_dependencies(ctx, param, value): |
| 193 | if value is not None: |
| 194 | versions = [] |
| 195 | images = re.findall(r"\((\d+)", value) |
| 196 | if len(images) == 0: |
| 197 | raise click.BadParameter( |
| 198 | "Image dependency format is invalid: {}".format(value)) |
| 199 | raw_versions = re.findall(r",\s*([0-9.+]+)\)", value) |
| 200 | if len(images) != len(raw_versions): |
| 201 | raise click.BadParameter( |
| 202 | '''There's a mismatch between the number of dependency images |
| 203 | and versions in: {}'''.format(value)) |
| 204 | for raw_version in raw_versions: |
| 205 | try: |
| 206 | versions.append(decode_version(raw_version)) |
| 207 | except ValueError as e: |
| 208 | raise click.BadParameter("{}".format(e)) |
| 209 | dependencies = dict() |
| 210 | dependencies[image.DEP_IMAGES_KEY] = images |
| 211 | dependencies[image.DEP_VERSIONS_KEY] = versions |
| 212 | return dependencies |
| 213 | |
| 214 | |
Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 215 | class BasedIntParamType(click.ParamType): |
| 216 | name = 'integer' |
| 217 | |
| 218 | def convert(self, value, param, ctx): |
| 219 | try: |
David Vincze | 1a7a690 | 2020-02-18 15:05:16 +0100 | [diff] [blame] | 220 | return int(value, 0) |
Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 221 | except ValueError: |
David Vincze | 1a7a690 | 2020-02-18 15:05:16 +0100 | [diff] [blame] | 222 | self.fail('%s is not a valid integer. Please use code literals ' |
| 223 | 'prefixed with 0b/0B, 0o/0O, or 0x/0X as necessary.' |
| 224 | % value, param, ctx) |
Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 225 | |
| 226 | |
| 227 | @click.argument('outfile') |
| 228 | @click.argument('infile') |
Ihor Slabkyy | 24d9373 | 2020-03-10 15:33:57 +0200 | [diff] [blame] | 229 | @click.option('--custom-tlv', required=False, nargs=2, default=[], |
| 230 | multiple=True, metavar='[tag] [value]', |
| 231 | help='Custom TLV that will be placed into protected area. ' |
| 232 | 'Add "0x" prefix if the value should be interpreted as an ' |
| 233 | 'integer, otherwise it will be interpreted as a string. ' |
| 234 | 'Specify the option multiple times to add multiple TLVs.') |
Fabio Utzig | 9117fde | 2019-10-17 11:11:46 -0300 | [diff] [blame] | 235 | @click.option('-R', '--erased-val', type=click.Choice(['0', '0xff']), |
| 236 | required=False, |
| 237 | help='The value that is read back from erased flash.') |
Fabio Utzig | edbabcf | 2019-10-11 13:03:37 -0300 | [diff] [blame] | 238 | @click.option('-x', '--hex-addr', type=BasedIntParamType(), required=False, |
| 239 | help='Adjust address in hex output file.') |
Håkon Øye Amundsen | df8c891 | 2019-08-26 12:15:28 +0000 | [diff] [blame] | 240 | @click.option('-L', '--load-addr', type=BasedIntParamType(), required=False, |
David Vincze | 1e0c544 | 2020-04-07 14:12:33 +0200 | [diff] [blame] | 241 | help='Load address for image when it should run from RAM.') |
Fabio Utzig | 9a492d5 | 2020-01-15 11:31:52 -0300 | [diff] [blame] | 242 | @click.option('--save-enctlv', default=False, is_flag=True, |
| 243 | help='When upgrading, save encrypted key TLVs instead of plain ' |
| 244 | 'keys. Enable when BOOT_SWAP_SAVE_ENCTLV config option ' |
| 245 | 'was set.') |
Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 246 | @click.option('-E', '--encrypt', metavar='filename', |
David Vincze | e574f2d | 2020-07-10 11:42:03 +0200 | [diff] [blame] | 247 | help='Encrypt image using the provided public key. ' |
| 248 | '(Not supported in direct-xip mode.)') |
Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 249 | @click.option('-e', '--endian', type=click.Choice(['little', 'big']), |
| 250 | default='little', help="Select little or big endian") |
| 251 | @click.option('--overwrite-only', default=False, is_flag=True, |
| 252 | help='Use overwrite-only instead of swap upgrades') |
David Vincze | 71b8f98 | 2020-03-17 19:08:12 +0100 | [diff] [blame] | 253 | @click.option('--boot-record', metavar='sw_type', help='Create CBOR encoded ' |
| 254 | 'boot record TLV. The sw_type represents the role of the ' |
| 255 | 'software component (e.g. CoFM for coprocessor firmware). ' |
| 256 | '[max. 12 characters]') |
Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 257 | @click.option('-M', '--max-sectors', type=int, |
Fabio Utzig | 9a492d5 | 2020-01-15 11:31:52 -0300 | [diff] [blame] | 258 | help='When padding allow for this amount of sectors (defaults ' |
| 259 | 'to 128)') |
Henrik Brix Andersen | 0ce958e | 2020-03-11 14:04:11 +0100 | [diff] [blame] | 260 | @click.option('--confirm', default=False, is_flag=True, |
Martí Bolívar | 009a150 | 2020-09-04 14:23:39 -0700 | [diff] [blame] | 261 | help='When padding the image, mark it as confirmed (implies ' |
| 262 | '--pad)') |
Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 263 | @click.option('--pad', default=False, is_flag=True, |
| 264 | help='Pad image to --slot-size bytes, adding trailer magic') |
| 265 | @click.option('-S', '--slot-size', type=BasedIntParamType(), required=True, |
Fabio Utzig | 826abf4 | 2020-07-13 20:56:35 -0300 | [diff] [blame] | 266 | help='Size of the slot. If the slots have different sizes, use ' |
| 267 | 'the size of the secondary slot.') |
Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 268 | @click.option('--pad-header', default=False, is_flag=True, |
Fabio Utzig | 9a492d5 | 2020-01-15 11:31:52 -0300 | [diff] [blame] | 269 | help='Add --header-size zeroed bytes at the beginning of the ' |
| 270 | 'image') |
Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 271 | @click.option('-H', '--header-size', callback=validate_header_size, |
| 272 | type=BasedIntParamType(), required=True) |
David Brown | 4878c27 | 2020-03-10 16:23:56 -0600 | [diff] [blame] | 273 | @click.option('--pad-sig', default=False, is_flag=True, |
| 274 | help='Add 0-2 bytes of padding to ECDSA signature ' |
| 275 | '(for mcuboot <1.5)') |
David Vincze | da8c919 | 2019-03-26 17:17:41 +0100 | [diff] [blame] | 276 | @click.option('-d', '--dependencies', callback=get_dependencies, |
| 277 | required=False, help='''Add dependence on another image, format: |
| 278 | "(<image_ID>,<image_version>), ... "''') |
David Vincze | 1a7a690 | 2020-02-18 15:05:16 +0100 | [diff] [blame] | 279 | @click.option('-s', '--security-counter', callback=validate_security_counter, |
| 280 | help='Specify the value of security counter. Use the `auto` ' |
| 281 | 'keyword to automatically generate it from the image version.') |
Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 282 | @click.option('-v', '--version', callback=validate_version, required=True) |
| 283 | @click.option('--align', type=click.Choice(['1', '2', '4', '8']), |
| 284 | required=True) |
David Vincze | dde178d | 2020-03-26 20:06:01 +0100 | [diff] [blame] | 285 | @click.option('--public-key-format', type=click.Choice(['hash', 'full']), |
| 286 | default='hash', help='In what format to add the public key to ' |
| 287 | 'the image manifest: full key or hash of the key.') |
Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 288 | @click.option('-k', '--key', metavar='filename') |
Fabio Utzig | 7c00acd | 2019-01-07 09:54:20 -0200 | [diff] [blame] | 289 | @click.command(help='''Create a signed or unsigned image\n |
| 290 | INFILE and OUTFILE are parsed as Intel HEX if the params have |
Håkon Øye Amundsen | df8c891 | 2019-08-26 12:15:28 +0000 | [diff] [blame] | 291 | .hex extension, otherwise binary format is used''') |
David Vincze | dde178d | 2020-03-26 20:06:01 +0100 | [diff] [blame] | 292 | def sign(key, public_key_format, align, version, pad_sig, header_size, |
| 293 | pad_header, slot_size, pad, confirm, max_sectors, overwrite_only, |
| 294 | endian, encrypt, infile, outfile, dependencies, load_addr, hex_addr, |
Ihor Slabkyy | 24d9373 | 2020-03-10 15:33:57 +0200 | [diff] [blame] | 295 | erased_val, save_enctlv, security_counter, boot_record, custom_tlv): |
Martí Bolívar | 009a150 | 2020-09-04 14:23:39 -0700 | [diff] [blame] | 296 | |
| 297 | if confirm: |
| 298 | # Confirmed but non-padded images don't make much sense, because |
| 299 | # otherwise there's no trailer area for writing the confirmed status. |
| 300 | pad = True |
Fabio Utzig | 7c00acd | 2019-01-07 09:54:20 -0200 | [diff] [blame] | 301 | img = image.Image(version=decode_version(version), header_size=header_size, |
Henrik Brix Andersen | 0ce958e | 2020-03-11 14:04:11 +0100 | [diff] [blame] | 302 | pad_header=pad_header, pad=pad, confirm=confirm, |
| 303 | align=int(align), slot_size=slot_size, |
| 304 | max_sectors=max_sectors, overwrite_only=overwrite_only, |
| 305 | endian=endian, load_addr=load_addr, erased_val=erased_val, |
David Vincze | 1a7a690 | 2020-02-18 15:05:16 +0100 | [diff] [blame] | 306 | save_enctlv=save_enctlv, |
| 307 | security_counter=security_counter) |
Fabio Utzig | 7c00acd | 2019-01-07 09:54:20 -0200 | [diff] [blame] | 308 | img.load(infile) |
Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 309 | key = load_key(key) if key else None |
| 310 | enckey = load_key(encrypt) if encrypt else None |
Fabio Utzig | 7a3b260 | 2019-10-22 09:56:44 -0300 | [diff] [blame] | 311 | if enckey and key: |
| 312 | if ((isinstance(key, keys.ECDSA256P1) and |
| 313 | not isinstance(enckey, keys.ECDSA256P1Public)) |
| 314 | or (isinstance(key, keys.RSA) and |
| 315 | not isinstance(enckey, keys.RSAPublic))): |
| 316 | # FIXME |
Fabio Utzig | 1f50892 | 2020-01-15 11:37:51 -0300 | [diff] [blame] | 317 | raise click.UsageError("Signing and encryption must use the same " |
| 318 | "type of key") |
David Brown | 4878c27 | 2020-03-10 16:23:56 -0600 | [diff] [blame] | 319 | |
| 320 | if pad_sig and hasattr(key, 'pad_sig'): |
| 321 | key.pad_sig = True |
| 322 | |
Ihor Slabkyy | 24d9373 | 2020-03-10 15:33:57 +0200 | [diff] [blame] | 323 | # Get list of custom protected TLVs from the command-line |
| 324 | custom_tlvs = {} |
| 325 | for tlv in custom_tlv: |
| 326 | tag = int(tlv[0], 0) |
| 327 | if tag in custom_tlvs: |
| 328 | raise click.UsageError('Custom TLV %s already exists.' % hex(tag)) |
| 329 | if tag in image.TLV_VALUES.values(): |
| 330 | raise click.UsageError( |
| 331 | 'Custom TLV %s conflicts with predefined TLV.' % hex(tag)) |
| 332 | |
| 333 | value = tlv[1] |
| 334 | if value.startswith('0x'): |
| 335 | if len(value[2:]) % 2: |
| 336 | raise click.UsageError('Custom TLV length is odd.') |
| 337 | custom_tlvs[tag] = bytes.fromhex(value[2:]) |
| 338 | else: |
| 339 | custom_tlvs[tag] = value.encode('utf-8') |
| 340 | |
| 341 | img.create(key, public_key_format, enckey, dependencies, boot_record, |
| 342 | custom_tlvs) |
Fabio Utzig | edbabcf | 2019-10-11 13:03:37 -0300 | [diff] [blame] | 343 | img.save(outfile, hex_addr) |
Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 344 | |
| 345 | |
| 346 | class AliasesGroup(click.Group): |
| 347 | |
| 348 | _aliases = { |
| 349 | "create": "sign", |
| 350 | } |
| 351 | |
| 352 | def list_commands(self, ctx): |
| 353 | cmds = [k for k in self.commands] |
| 354 | aliases = [k for k in self._aliases] |
| 355 | return sorted(cmds + aliases) |
| 356 | |
| 357 | def get_command(self, ctx, cmd_name): |
| 358 | rv = click.Group.get_command(self, ctx, cmd_name) |
| 359 | if rv is not None: |
| 360 | return rv |
| 361 | if cmd_name in self._aliases: |
| 362 | return click.Group.get_command(self, ctx, self._aliases[cmd_name]) |
| 363 | return None |
| 364 | |
| 365 | |
Fabio Utzig | 25c6a15 | 2019-09-10 12:52:26 -0300 | [diff] [blame] | 366 | @click.command(help='Print imgtool version information') |
| 367 | def version(): |
| 368 | print(imgtool_version) |
| 369 | |
| 370 | |
Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 371 | @click.command(cls=AliasesGroup, |
| 372 | context_settings=dict(help_option_names=['-h', '--help'])) |
| 373 | def imgtool(): |
| 374 | pass |
| 375 | |
| 376 | |
| 377 | imgtool.add_command(keygen) |
| 378 | imgtool.add_command(getpub) |
Ioannis Konstantelias | 78e57c7 | 2019-11-28 16:06:12 +0200 | [diff] [blame] | 379 | imgtool.add_command(getpriv) |
Fabio Utzig | 4a5477a | 2019-05-27 15:45:08 -0300 | [diff] [blame] | 380 | imgtool.add_command(verify) |
Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 381 | imgtool.add_command(sign) |
Fabio Utzig | 25c6a15 | 2019-09-10 12:52:26 -0300 | [diff] [blame] | 382 | imgtool.add_command(version) |
Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 383 | |
| 384 | |
| 385 | if __name__ == '__main__': |
| 386 | imgtool() |