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 |
Roland Mikhel | 3d92a6c | 2023-02-23 15:35:44 +0100 | [diff] [blame] | 4 | # Copyright 2019-2023 Arm Limited |
Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 5 | # |
David Brown | 79c4fcf | 2021-01-26 15:04:05 -0700 | [diff] [blame] | 6 | # SPDX-License-Identifier: Apache-2.0 |
| 7 | # |
Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 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 | |
David Vincze | da8c919 | 2019-03-26 17:17:41 +0100 | [diff] [blame] | 20 | import re |
Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 21 | import click |
| 22 | import getpass |
| 23 | import imgtool.keys as keys |
Fabio Utzig | 4a5477a | 2019-05-27 15:45:08 -0300 | [diff] [blame] | 24 | import sys |
Mateusz Michalek | 63fa7e4 | 2024-08-09 12:16:40 +0200 | [diff] [blame] | 25 | import struct |
| 26 | import os |
| 27 | import lzma |
| 28 | import hashlib |
Andrzej Puzdrowski | 160303c | 2022-03-15 15:41:14 +0100 | [diff] [blame] | 29 | import base64 |
Fabio Utzig | 25c6a15 | 2019-09-10 12:52:26 -0300 | [diff] [blame] | 30 | from imgtool import image, imgtool_version |
Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 31 | from imgtool.version import decode_version |
David Vincze | ca56135 | 2023-01-27 15:39:12 +0100 | [diff] [blame] | 32 | from imgtool.dumpinfo import dump_imginfo |
Fabio Utzig | 4facd1b | 2020-04-02 13:17:38 -0300 | [diff] [blame] | 33 | from .keys import ( |
| 34 | RSAUsageError, ECDSAUsageError, Ed25519UsageError, X25519UsageError) |
Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 35 | |
Mateusz Michalek | 63fa7e4 | 2024-08-09 12:16:40 +0200 | [diff] [blame] | 36 | comp_default_dictsize=131072 |
| 37 | comp_default_pb=2 |
| 38 | comp_default_lc=3 |
| 39 | comp_default_lp=1 |
| 40 | comp_default_preset=9 |
| 41 | |
| 42 | |
David Vincze | 71b8f98 | 2020-03-17 19:08:12 +0100 | [diff] [blame] | 43 | MIN_PYTHON_VERSION = (3, 6) |
| 44 | if sys.version_info < MIN_PYTHON_VERSION: |
| 45 | sys.exit("Python %s.%s or newer is required by imgtool." |
| 46 | % MIN_PYTHON_VERSION) |
| 47 | |
Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 48 | |
| 49 | def gen_rsa2048(keyfile, passwd): |
Fabio Utzig | 19fd79a | 2019-05-08 18:20:39 -0300 | [diff] [blame] | 50 | keys.RSA.generate().export_private(path=keyfile, passwd=passwd) |
| 51 | |
| 52 | |
| 53 | def gen_rsa3072(keyfile, passwd): |
| 54 | keys.RSA.generate(key_size=3072).export_private(path=keyfile, |
| 55 | passwd=passwd) |
Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 56 | |
| 57 | |
| 58 | def gen_ecdsa_p256(keyfile, passwd): |
| 59 | keys.ECDSA256P1.generate().export_private(keyfile, passwd=passwd) |
| 60 | |
| 61 | |
Roland Mikhel | 5704174 | 2023-02-03 14:43:13 +0100 | [diff] [blame] | 62 | def gen_ecdsa_p384(keyfile, passwd): |
| 63 | keys.ECDSA384P1.generate().export_private(keyfile, passwd=passwd) |
| 64 | |
| 65 | |
Fabio Utzig | 8101d1f | 2019-05-09 15:03:22 -0300 | [diff] [blame] | 66 | def gen_ed25519(keyfile, passwd): |
Fabio Utzig | 4bd4c7c | 2019-06-27 08:23:21 -0300 | [diff] [blame] | 67 | keys.Ed25519.generate().export_private(path=keyfile, passwd=passwd) |
Fabio Utzig | 8101d1f | 2019-05-09 15:03:22 -0300 | [diff] [blame] | 68 | |
| 69 | |
Fabio Utzig | 4facd1b | 2020-04-02 13:17:38 -0300 | [diff] [blame] | 70 | def gen_x25519(keyfile, passwd): |
| 71 | keys.X25519.generate().export_private(path=keyfile, passwd=passwd) |
| 72 | |
| 73 | |
Fabio Utzig | 4e2cdfe | 2022-09-28 17:44:01 -0300 | [diff] [blame] | 74 | valid_langs = ['c', 'rust'] |
Bence Balogh | 97a20f1 | 2023-07-18 15:59:33 +0200 | [diff] [blame] | 75 | valid_hash_encodings = ['lang-c', 'raw'] |
Bence Balogh | ed8d68a | 2023-07-18 15:57:52 +0200 | [diff] [blame] | 76 | valid_encodings = ['lang-c', 'lang-rust', 'pem', 'raw'] |
Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 77 | keygens = { |
| 78 | 'rsa-2048': gen_rsa2048, |
Fabio Utzig | 19fd79a | 2019-05-08 18:20:39 -0300 | [diff] [blame] | 79 | 'rsa-3072': gen_rsa3072, |
Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 80 | 'ecdsa-p256': gen_ecdsa_p256, |
Roland Mikhel | 5704174 | 2023-02-03 14:43:13 +0100 | [diff] [blame] | 81 | 'ecdsa-p384': gen_ecdsa_p384, |
Fabio Utzig | 4facd1b | 2020-04-02 13:17:38 -0300 | [diff] [blame] | 82 | 'ed25519': gen_ed25519, |
| 83 | 'x25519': gen_x25519, |
Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 84 | } |
Antonio de Angelis | c6e7e9b | 2022-11-15 15:06:40 +0000 | [diff] [blame] | 85 | valid_formats = ['openssl', 'pkcs8'] |
Dominik Ermel | c894d04 | 2024-08-22 14:58:15 +0000 | [diff] [blame] | 86 | valid_sha = [ 'auto', '256', '384', '512' ] |
Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 87 | |
Antonio de Angelis | 7ba01c0 | 2022-11-15 15:10:41 +0000 | [diff] [blame] | 88 | |
Andrzej Puzdrowski | 160303c | 2022-03-15 15:41:14 +0100 | [diff] [blame] | 89 | def load_signature(sigfile): |
| 90 | with open(sigfile, 'rb') as f: |
| 91 | signature = base64.b64decode(f.read()) |
| 92 | return signature |
Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 93 | |
Antonio de Angelis | 7ba01c0 | 2022-11-15 15:10:41 +0000 | [diff] [blame] | 94 | |
Andrzej Puzdrowski | f72e374 | 2022-03-17 11:34:38 +0100 | [diff] [blame] | 95 | def save_signature(sigfile, sig): |
| 96 | with open(sigfile, 'wb') as f: |
| 97 | signature = base64.b64encode(sig) |
| 98 | f.write(signature) |
| 99 | |
Antonio de Angelis | 7ba01c0 | 2022-11-15 15:10:41 +0000 | [diff] [blame] | 100 | |
Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 101 | def 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 | |
| 110 | def 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, |
Fabio Utzig | 7ca2855 | 2019-12-13 11:24:20 -0300 | [diff] [blame] | 126 | type=click.Choice(keygens.keys()), prompt=True, |
| 127 | help='{}'.format('One of: {}'.format(', '.join(keygens.keys())))) |
Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 128 | @click.option('-k', '--key', metavar='filename', required=True) |
| 129 | @click.command(help='Generate pub/private keypair') |
| 130 | def keygen(type, key, password): |
| 131 | password = get_password() if password else None |
| 132 | keygens[type](key, password) |
| 133 | |
| 134 | |
Fabio Utzig | 4e2cdfe | 2022-09-28 17:44:01 -0300 | [diff] [blame] | 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))) |
Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 143 | @click.option('-k', '--key', metavar='filename', required=True) |
Bence Balogh | 367aefb | 2023-07-18 15:51:54 +0200 | [diff] [blame] | 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.') |
Ioannis Konstantelias | 78e57c7 | 2019-11-28 16:06:12 +0200 | [diff] [blame] | 147 | @click.command(help='Dump public key from keypair') |
Bence Balogh | 367aefb | 2023-07-18 15:51:54 +0200 | [diff] [blame] | 148 | def getpub(key, encoding, lang, output): |
Fabio Utzig | 4e2cdfe | 2022-09-28 17:44:01 -0300 | [diff] [blame] | 149 | if encoding and lang: |
Antonio de Angelis | 7ba01c0 | 2022-11-15 15:10:41 +0000 | [diff] [blame] | 150 | raise click.UsageError('Please use only one of `--encoding/-e` ' |
| 151 | 'or `--lang/-l`') |
Fabio Utzig | 4e2cdfe | 2022-09-28 17:44:01 -0300 | [diff] [blame] | 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] |
Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 156 | key = load_key(key) |
Bence Balogh | 367aefb | 2023-07-18 15:51:54 +0200 | [diff] [blame] | 157 | |
| 158 | if not output: |
| 159 | output = sys.stdout |
Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 160 | if key is None: |
| 161 | print("Invalid passphrase") |
Fabio Utzig | 4e2cdfe | 2022-09-28 17:44:01 -0300 | [diff] [blame] | 162 | elif lang == 'c' or encoding == 'lang-c': |
Bence Balogh | 367aefb | 2023-07-18 15:51:54 +0200 | [diff] [blame] | 163 | key.emit_c_public(file=output) |
Fabio Utzig | 4e2cdfe | 2022-09-28 17:44:01 -0300 | [diff] [blame] | 164 | elif lang == 'rust' or encoding == 'lang-rust': |
Bence Balogh | 367aefb | 2023-07-18 15:51:54 +0200 | [diff] [blame] | 165 | key.emit_rust_public(file=output) |
Fabio Utzig | 4e2cdfe | 2022-09-28 17:44:01 -0300 | [diff] [blame] | 166 | elif encoding == 'pem': |
Bence Balogh | 367aefb | 2023-07-18 15:51:54 +0200 | [diff] [blame] | 167 | key.emit_public_pem(file=output) |
Bence Balogh | ed8d68a | 2023-07-18 15:57:52 +0200 | [diff] [blame] | 168 | elif encoding == 'raw': |
| 169 | key.emit_raw_public(file=output) |
Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 170 | else: |
Fabio Utzig | 4e2cdfe | 2022-09-28 17:44:01 -0300 | [diff] [blame] | 171 | raise click.UsageError() |
Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 172 | |
| 173 | |
Bence Balogh | 97a20f1 | 2023-07-18 15:59:33 +0200 | [diff] [blame] | 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') |
| 185 | def 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 | |
Ioannis Konstantelias | 78e57c7 | 2019-11-28 16:06:12 +0200 | [diff] [blame] | 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) |
Antonio de Angelis | c6e7e9b | 2022-11-15 15:06:40 +0000 | [diff] [blame] | 208 | @click.option('-f', '--format', |
| 209 | type=click.Choice(valid_formats), |
Fabio Utzig | 8f289ba | 2023-01-09 21:01:55 -0300 | [diff] [blame] | 210 | help='Valid formats: {}'.format(', '.join(valid_formats)) |
| 211 | ) |
Ioannis Konstantelias | 78e57c7 | 2019-11-28 16:06:12 +0200 | [diff] [blame] | 212 | @click.command(help='Dump private key from keypair') |
Antonio de Angelis | c6e7e9b | 2022-11-15 15:06:40 +0000 | [diff] [blame] | 213 | def getpriv(key, minimal, format): |
Ioannis Konstantelias | 78e57c7 | 2019-11-28 16:06:12 +0200 | [diff] [blame] | 214 | key = load_key(key) |
| 215 | if key is None: |
| 216 | print("Invalid passphrase") |
Fabio Utzig | 1f50892 | 2020-01-15 11:37:51 -0300 | [diff] [blame] | 217 | try: |
Antonio de Angelis | c6e7e9b | 2022-11-15 15:06:40 +0000 | [diff] [blame] | 218 | key.emit_private(minimal, format) |
Fabio Utzig | 4facd1b | 2020-04-02 13:17:38 -0300 | [diff] [blame] | 219 | except (RSAUsageError, ECDSAUsageError, Ed25519UsageError, |
| 220 | X25519UsageError) as e: |
Fabio Utzig | 1f50892 | 2020-01-15 11:37:51 -0300 | [diff] [blame] | 221 | raise click.UsageError(e) |
Ioannis Konstantelias | 78e57c7 | 2019-11-28 16:06:12 +0200 | [diff] [blame] | 222 | |
| 223 | |
Fabio Utzig | 4a5477a | 2019-05-27 15:45:08 -0300 | [diff] [blame] | 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") |
| 227 | def verify(key, imgfile): |
| 228 | key = load_key(key) if key else None |
Dominik Ermel | 1c04eac | 2024-09-12 19:37:40 +0000 | [diff] [blame] | 229 | ret, version, digest, signature = image.Image.verify(imgfile, key) |
Fabio Utzig | 4a5477a | 2019-05-27 15:45:08 -0300 | [diff] [blame] | 230 | if ret == image.VerifyResult.OK: |
| 231 | print("Image was correctly validated") |
Marek Pieta | e955510 | 2019-08-08 16:08:16 +0200 | [diff] [blame] | 232 | print("Image version: {}.{}.{}+{}".format(*version)) |
Dominik Ermel | 1c04eac | 2024-09-12 19:37:40 +0000 | [diff] [blame] | 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())) |
Fabio Utzig | 4a5477a | 2019-05-27 15:45:08 -0300 | [diff] [blame] | 237 | return |
| 238 | elif ret == image.VerifyResult.INVALID_MAGIC: |
| 239 | print("Invalid image magic; is this an MCUboot image?") |
Christian Skubich | f13db12 | 2019-07-31 11:34:15 +0200 | [diff] [blame] | 240 | elif ret == image.VerifyResult.INVALID_TLV_INFO_MAGIC: |
Fabio Utzig | 4a5477a | 2019-05-27 15:45:08 -0300 | [diff] [blame] | 241 | print("Invalid TLV info magic; is this an MCUboot image?") |
| 242 | elif ret == image.VerifyResult.INVALID_HASH: |
Roland Mikhel | 5704174 | 2023-02-03 14:43:13 +0100 | [diff] [blame] | 243 | print("Image has an invalid hash") |
Fabio Utzig | 4a5477a | 2019-05-27 15:45:08 -0300 | [diff] [blame] | 244 | elif ret == image.VerifyResult.INVALID_SIGNATURE: |
| 245 | print("No signature found for the given key") |
Rustam Ismayilov | 36f8bf3 | 2023-11-27 21:44:54 +0100 | [diff] [blame] | 246 | elif ret == image.VerifyResult.KEY_MISMATCH: |
| 247 | print("Key type does not match TLV record") |
Christian Skubich | f13db12 | 2019-07-31 11:34:15 +0200 | [diff] [blame] | 248 | else: |
| 249 | print("Unknown return code: {}".format(ret)) |
Fabio Utzig | 4a5477a | 2019-05-27 15:45:08 -0300 | [diff] [blame] | 250 | sys.exit(1) |
| 251 | |
| 252 | |
David Vincze | ca56135 | 2023-01-27 15:39:12 +0100 | [diff] [blame] | 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') |
| 260 | def dumpinfo(imgfile, outfile, silent): |
| 261 | dump_imginfo(imgfile, outfile, silent) |
Guillaume G. | 6678c37 | 2025-03-17 13:28:22 +0100 | [diff] [blame] | 262 | if not silent: |
| 263 | print("dumpinfo has run successfully") |
David Vincze | ca56135 | 2023-01-27 15:39:12 +0100 | [diff] [blame] | 264 | |
| 265 | |
Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 266 | def validate_version(ctx, param, value): |
| 267 | try: |
| 268 | decode_version(value) |
| 269 | return value |
| 270 | except ValueError as e: |
| 271 | raise click.BadParameter("{}".format(e)) |
| 272 | |
| 273 | |
David Vincze | 1a7a690 | 2020-02-18 15:05:16 +0100 | [diff] [blame] | 274 | def validate_security_counter(ctx, param, value): |
| 275 | if value is not None: |
| 276 | if value.lower() == 'auto': |
| 277 | return 'auto' |
| 278 | else: |
| 279 | try: |
| 280 | return int(value, 0) |
| 281 | except ValueError: |
| 282 | raise click.BadParameter( |
| 283 | "{} is not a valid integer. Please use code literals " |
| 284 | "prefixed with 0b/0B, 0o/0O, or 0x/0X as necessary." |
| 285 | .format(value)) |
| 286 | |
| 287 | |
Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 288 | def validate_header_size(ctx, param, value): |
| 289 | min_hdr_size = image.IMAGE_HEADER_SIZE |
| 290 | if value < min_hdr_size: |
| 291 | raise click.BadParameter( |
| 292 | "Minimum value for -H/--header-size is {}".format(min_hdr_size)) |
| 293 | return value |
| 294 | |
| 295 | |
David Vincze | da8c919 | 2019-03-26 17:17:41 +0100 | [diff] [blame] | 296 | def get_dependencies(ctx, param, value): |
| 297 | if value is not None: |
| 298 | versions = [] |
| 299 | images = re.findall(r"\((\d+)", value) |
| 300 | if len(images) == 0: |
| 301 | raise click.BadParameter( |
| 302 | "Image dependency format is invalid: {}".format(value)) |
| 303 | raw_versions = re.findall(r",\s*([0-9.+]+)\)", value) |
| 304 | if len(images) != len(raw_versions): |
| 305 | raise click.BadParameter( |
| 306 | '''There's a mismatch between the number of dependency images |
| 307 | and versions in: {}'''.format(value)) |
| 308 | for raw_version in raw_versions: |
| 309 | try: |
| 310 | versions.append(decode_version(raw_version)) |
| 311 | except ValueError as e: |
| 312 | raise click.BadParameter("{}".format(e)) |
| 313 | dependencies = dict() |
| 314 | dependencies[image.DEP_IMAGES_KEY] = images |
| 315 | dependencies[image.DEP_VERSIONS_KEY] = versions |
| 316 | return dependencies |
| 317 | |
Mateusz Michalek | 63fa7e4 | 2024-08-09 12:16:40 +0200 | [diff] [blame] | 318 | def create_lzma2_header(dictsize, pb, lc, lp): |
| 319 | header = bytearray() |
| 320 | for i in range(0, 40): |
| 321 | if dictsize <= ((2 | ((i) & 1)) << int((i) / 2 + 11)): |
| 322 | header.append(i) |
| 323 | break |
| 324 | header.append( ( pb * 5 + lp) * 9 + lc) |
| 325 | return header |
David Vincze | da8c919 | 2019-03-26 17:17:41 +0100 | [diff] [blame] | 326 | |
Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 327 | class BasedIntParamType(click.ParamType): |
| 328 | name = 'integer' |
| 329 | |
| 330 | def convert(self, value, param, ctx): |
| 331 | try: |
David Vincze | 1a7a690 | 2020-02-18 15:05:16 +0100 | [diff] [blame] | 332 | return int(value, 0) |
Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 333 | except ValueError: |
David Vincze | 1a7a690 | 2020-02-18 15:05:16 +0100 | [diff] [blame] | 334 | self.fail('%s is not a valid integer. Please use code literals ' |
| 335 | 'prefixed with 0b/0B, 0o/0O, or 0x/0X as necessary.' |
| 336 | % value, param, ctx) |
Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 337 | |
| 338 | |
| 339 | @click.argument('outfile') |
| 340 | @click.argument('infile') |
Mateusz Wielgos | dc03055 | 2024-02-26 15:02:45 -0600 | [diff] [blame] | 341 | @click.option('--non-bootable', default=False, is_flag=True, |
| 342 | help='Mark the image as non-bootable.') |
Ihor Slabkyy | 24d9373 | 2020-03-10 15:33:57 +0200 | [diff] [blame] | 343 | @click.option('--custom-tlv', required=False, nargs=2, default=[], |
| 344 | multiple=True, metavar='[tag] [value]', |
| 345 | help='Custom TLV that will be placed into protected area. ' |
| 346 | 'Add "0x" prefix if the value should be interpreted as an ' |
| 347 | 'integer, otherwise it will be interpreted as a string. ' |
| 348 | 'Specify the option multiple times to add multiple TLVs.') |
Fabio Utzig | 9117fde | 2019-10-17 11:11:46 -0300 | [diff] [blame] | 349 | @click.option('-R', '--erased-val', type=click.Choice(['0', '0xff']), |
| 350 | required=False, |
| 351 | help='The value that is read back from erased flash.') |
Fabio Utzig | edbabcf | 2019-10-11 13:03:37 -0300 | [diff] [blame] | 352 | @click.option('-x', '--hex-addr', type=BasedIntParamType(), required=False, |
| 353 | help='Adjust address in hex output file.') |
Håkon Øye Amundsen | df8c891 | 2019-08-26 12:15:28 +0000 | [diff] [blame] | 354 | @click.option('-L', '--load-addr', type=BasedIntParamType(), required=False, |
David Vincze | 1e0c544 | 2020-04-07 14:12:33 +0200 | [diff] [blame] | 355 | help='Load address for image when it should run from RAM.') |
Dominik Ermel | 50820b1 | 2020-12-14 13:16:46 +0000 | [diff] [blame] | 356 | @click.option('-F', '--rom-fixed', type=BasedIntParamType(), required=False, |
| 357 | help='Set flash address the image is built for.') |
Fabio Utzig | 9a492d5 | 2020-01-15 11:31:52 -0300 | [diff] [blame] | 358 | @click.option('--save-enctlv', default=False, is_flag=True, |
| 359 | help='When upgrading, save encrypted key TLVs instead of plain ' |
| 360 | 'keys. Enable when BOOT_SWAP_SAVE_ENCTLV config option ' |
| 361 | 'was set.') |
Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 362 | @click.option('-E', '--encrypt', metavar='filename', |
David Vincze | e574f2d | 2020-07-10 11:42:03 +0200 | [diff] [blame] | 363 | help='Encrypt image using the provided public key. ' |
Tamas Ban | fe03109 | 2020-09-10 17:32:39 +0200 | [diff] [blame] | 364 | '(Not supported in direct-xip or ram-load mode.)') |
Salome Thirot | 0f64197 | 2021-05-14 11:19:55 +0100 | [diff] [blame] | 365 | @click.option('--encrypt-keylen', default='128', |
Antonio de Angelis | 7ba01c0 | 2022-11-15 15:10:41 +0000 | [diff] [blame] | 366 | type=click.Choice(['128', '256']), |
Salome Thirot | 0f64197 | 2021-05-14 11:19:55 +0100 | [diff] [blame] | 367 | help='When encrypting the image using AES, select a 128 bit or ' |
| 368 | '256 bit key len.') |
Mateusz Michalek | 63fa7e4 | 2024-08-09 12:16:40 +0200 | [diff] [blame] | 369 | @click.option('--compression', default='disabled', |
Mateusz Michalek | d69933c | 2024-10-04 13:36:52 +0200 | [diff] [blame] | 370 | type=click.Choice(['disabled', 'lzma2', 'lzma2armthumb']), |
Mateusz Michalek | 63fa7e4 | 2024-08-09 12:16:40 +0200 | [diff] [blame] | 371 | help='Enable image compression using specified type. ' |
| 372 | 'Will fall back without image compression automatically ' |
| 373 | 'if the compression increases the image size.') |
Michel Jaouen | d09aa6b | 2022-01-07 16:48:58 +0100 | [diff] [blame] | 374 | @click.option('-c', '--clear', required=False, is_flag=True, default=False, |
| 375 | help='Output a non-encrypted image with encryption capabilities,' |
| 376 | 'so it can be installed in the primary slot, and encrypted ' |
| 377 | 'when swapped to the secondary.') |
Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 378 | @click.option('-e', '--endian', type=click.Choice(['little', 'big']), |
| 379 | default='little', help="Select little or big endian") |
| 380 | @click.option('--overwrite-only', default=False, is_flag=True, |
| 381 | help='Use overwrite-only instead of swap upgrades') |
David Vincze | 71b8f98 | 2020-03-17 19:08:12 +0100 | [diff] [blame] | 382 | @click.option('--boot-record', metavar='sw_type', help='Create CBOR encoded ' |
| 383 | 'boot record TLV. The sw_type represents the role of the ' |
| 384 | 'software component (e.g. CoFM for coprocessor firmware). ' |
| 385 | '[max. 12 characters]') |
Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 386 | @click.option('-M', '--max-sectors', type=int, |
Fabio Utzig | 9a492d5 | 2020-01-15 11:31:52 -0300 | [diff] [blame] | 387 | help='When padding allow for this amount of sectors (defaults ' |
| 388 | 'to 128)') |
Henrik Brix Andersen | 0ce958e | 2020-03-11 14:04:11 +0100 | [diff] [blame] | 389 | @click.option('--confirm', default=False, is_flag=True, |
Martà BolÃvar | 009a150 | 2020-09-04 14:23:39 -0700 | [diff] [blame] | 390 | help='When padding the image, mark it as confirmed (implies ' |
| 391 | '--pad)') |
Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 392 | @click.option('--pad', default=False, is_flag=True, |
| 393 | help='Pad image to --slot-size bytes, adding trailer magic') |
| 394 | @click.option('-S', '--slot-size', type=BasedIntParamType(), required=True, |
Fabio Utzig | 826abf4 | 2020-07-13 20:56:35 -0300 | [diff] [blame] | 395 | help='Size of the slot. If the slots have different sizes, use ' |
| 396 | 'the size of the secondary slot.') |
Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 397 | @click.option('--pad-header', default=False, is_flag=True, |
Fabio Utzig | 9a492d5 | 2020-01-15 11:31:52 -0300 | [diff] [blame] | 398 | help='Add --header-size zeroed bytes at the beginning of the ' |
| 399 | 'image') |
Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 400 | @click.option('-H', '--header-size', callback=validate_header_size, |
| 401 | type=BasedIntParamType(), required=True) |
David Brown | 4878c27 | 2020-03-10 16:23:56 -0600 | [diff] [blame] | 402 | @click.option('--pad-sig', default=False, is_flag=True, |
| 403 | help='Add 0-2 bytes of padding to ECDSA signature ' |
| 404 | '(for mcuboot <1.5)') |
David Vincze | da8c919 | 2019-03-26 17:17:41 +0100 | [diff] [blame] | 405 | @click.option('-d', '--dependencies', callback=get_dependencies, |
| 406 | required=False, help='''Add dependence on another image, format: |
| 407 | "(<image_ID>,<image_version>), ... "''') |
David Vincze | 1a7a690 | 2020-02-18 15:05:16 +0100 | [diff] [blame] | 408 | @click.option('-s', '--security-counter', callback=validate_security_counter, |
| 409 | help='Specify the value of security counter. Use the `auto` ' |
| 410 | 'keyword to automatically generate it from the image version.') |
Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 411 | @click.option('-v', '--version', callback=validate_version, required=True) |
Kristine Jassmann | 73c38c6 | 2021-02-03 16:56:14 +0000 | [diff] [blame] | 412 | @click.option('--align', type=click.Choice(['1', '2', '4', '8', '16', '32']), |
Andrej Butok | 06bc548 | 2023-12-19 10:36:08 +0100 | [diff] [blame] | 413 | default='1', |
Andrej Butok | 6c4f7b4 | 2023-11-06 15:01:19 +0100 | [diff] [blame] | 414 | required=False, |
| 415 | help='Alignment used by swap update modes.') |
Kristine Jassmann | 73c38c6 | 2021-02-03 16:56:14 +0000 | [diff] [blame] | 416 | @click.option('--max-align', type=click.Choice(['8', '16', '32']), |
Piotr Mienkowski | b6d5cf3 | 2022-01-31 01:01:11 +0100 | [diff] [blame] | 417 | required=False, |
| 418 | help='Maximum flash alignment. Set if flash alignment of the ' |
| 419 | 'primary and secondary slot differ and any of them is larger ' |
| 420 | 'than 8.') |
David Vincze | dde178d | 2020-03-26 20:06:01 +0100 | [diff] [blame] | 421 | @click.option('--public-key-format', type=click.Choice(['hash', 'full']), |
| 422 | default='hash', help='In what format to add the public key to ' |
| 423 | 'the image manifest: full key or hash of the key.') |
Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 424 | @click.option('-k', '--key', metavar='filename') |
Andrzej Puzdrowski | 160303c | 2022-03-15 15:41:14 +0100 | [diff] [blame] | 425 | @click.option('--fix-sig', metavar='filename', |
iysheng | 6093cbb | 2022-05-28 17:00:40 +0800 | [diff] [blame] | 426 | help='fixed signature for the image. It will be used instead of ' |
Andrzej Puzdrowski | 160303c | 2022-03-15 15:41:14 +0100 | [diff] [blame] | 427 | 'the signature calculated using the public key') |
| 428 | @click.option('--fix-sig-pubkey', metavar='filename', |
| 429 | help='public key relevant to fixed signature') |
Dominik Ermel | 1c04eac | 2024-09-12 19:37:40 +0000 | [diff] [blame] | 430 | @click.option('--pure', 'is_pure', is_flag=True, default=False, show_default=True, |
| 431 | help='Expected Pure variant of signature; the Pure variant is ' |
| 432 | 'expected to be signature done over an image rather than hash of ' |
| 433 | 'that image.') |
Andrzej Puzdrowski | f72e374 | 2022-03-17 11:34:38 +0100 | [diff] [blame] | 434 | @click.option('--sig-out', metavar='filename', |
iysheng | 6093cbb | 2022-05-28 17:00:40 +0800 | [diff] [blame] | 435 | help='Path to the file to which signature will be written. ' |
Andrzej Puzdrowski | f72e374 | 2022-03-17 11:34:38 +0100 | [diff] [blame] | 436 | 'The image signature will be encoded as base64 formatted string') |
Dominik Ermel | c894d04 | 2024-08-22 14:58:15 +0000 | [diff] [blame] | 437 | @click.option('--sha', 'user_sha', type=click.Choice(valid_sha), default='auto', |
| 438 | help='selected sha algorithm to use; defaults to "auto" which is 256 if ' |
| 439 | 'no cryptographic signature is used, or default for signature type') |
Andrzej Puzdrowski | dfce0be | 2022-03-28 09:34:15 +0200 | [diff] [blame] | 440 | @click.option('--vector-to-sign', type=click.Choice(['payload', 'digest']), |
Antonio de Angelis | 7ba01c0 | 2022-11-15 15:10:41 +0000 | [diff] [blame] | 441 | help='send to OUTFILE the payload or payload''s digest instead ' |
| 442 | 'of complied image. These data can be used for external image ' |
Andrzej Puzdrowski | dfce0be | 2022-03-28 09:34:15 +0200 | [diff] [blame] | 443 | 'signing') |
Fabio Utzig | 7c00acd | 2019-01-07 09:54:20 -0200 | [diff] [blame] | 444 | @click.command(help='''Create a signed or unsigned image\n |
| 445 | 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] | 446 | .hex extension, otherwise binary format is used''') |
David Vincze | dde178d | 2020-03-26 20:06:01 +0100 | [diff] [blame] | 447 | def sign(key, public_key_format, align, version, pad_sig, header_size, |
| 448 | pad_header, slot_size, pad, confirm, max_sectors, overwrite_only, |
Mateusz Michalek | 63fa7e4 | 2024-08-09 12:16:40 +0200 | [diff] [blame] | 449 | endian, encrypt_keylen, encrypt, compression, infile, outfile, |
| 450 | dependencies, load_addr, hex_addr, erased_val, save_enctlv, |
| 451 | security_counter, boot_record, custom_tlv, rom_fixed, max_align, |
Dominik Ermel | 1c04eac | 2024-09-12 19:37:40 +0000 | [diff] [blame] | 452 | clear, fix_sig, fix_sig_pubkey, sig_out, user_sha, is_pure, |
| 453 | vector_to_sign, non_bootable): |
Martà BolÃvar | 009a150 | 2020-09-04 14:23:39 -0700 | [diff] [blame] | 454 | |
| 455 | if confirm: |
| 456 | # Confirmed but non-padded images don't make much sense, because |
| 457 | # otherwise there's no trailer area for writing the confirmed status. |
| 458 | pad = True |
Fabio Utzig | 7c00acd | 2019-01-07 09:54:20 -0200 | [diff] [blame] | 459 | img = image.Image(version=decode_version(version), header_size=header_size, |
Henrik Brix Andersen | 0ce958e | 2020-03-11 14:04:11 +0100 | [diff] [blame] | 460 | pad_header=pad_header, pad=pad, confirm=confirm, |
| 461 | align=int(align), slot_size=slot_size, |
| 462 | max_sectors=max_sectors, overwrite_only=overwrite_only, |
Dominik Ermel | 50820b1 | 2020-12-14 13:16:46 +0000 | [diff] [blame] | 463 | endian=endian, load_addr=load_addr, rom_fixed=rom_fixed, |
| 464 | erased_val=erased_val, save_enctlv=save_enctlv, |
Mateusz Wielgos | dc03055 | 2024-02-26 15:02:45 -0600 | [diff] [blame] | 465 | security_counter=security_counter, max_align=max_align, |
| 466 | non_bootable=non_bootable) |
Mateusz Michalek | 63fa7e4 | 2024-08-09 12:16:40 +0200 | [diff] [blame] | 467 | compression_tlvs = {} |
Fabio Utzig | 7c00acd | 2019-01-07 09:54:20 -0200 | [diff] [blame] | 468 | img.load(infile) |
Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 469 | key = load_key(key) if key else None |
| 470 | enckey = load_key(encrypt) if encrypt else None |
Fabio Utzig | 7a3b260 | 2019-10-22 09:56:44 -0300 | [diff] [blame] | 471 | if enckey and key: |
| 472 | if ((isinstance(key, keys.ECDSA256P1) and |
| 473 | not isinstance(enckey, keys.ECDSA256P1Public)) |
Roland Mikhel | 5704174 | 2023-02-03 14:43:13 +0100 | [diff] [blame] | 474 | or (isinstance(key, keys.ECDSA384P1) and |
| 475 | not isinstance(enckey, keys.ECDSA384P1Public)) |
Fabio Utzig | 7a3b260 | 2019-10-22 09:56:44 -0300 | [diff] [blame] | 476 | or (isinstance(key, keys.RSA) and |
| 477 | not isinstance(enckey, keys.RSAPublic))): |
| 478 | # FIXME |
Fabio Utzig | 1f50892 | 2020-01-15 11:37:51 -0300 | [diff] [blame] | 479 | raise click.UsageError("Signing and encryption must use the same " |
| 480 | "type of key") |
David Brown | 4878c27 | 2020-03-10 16:23:56 -0600 | [diff] [blame] | 481 | |
| 482 | if pad_sig and hasattr(key, 'pad_sig'): |
| 483 | key.pad_sig = True |
| 484 | |
Ihor Slabkyy | 24d9373 | 2020-03-10 15:33:57 +0200 | [diff] [blame] | 485 | # Get list of custom protected TLVs from the command-line |
| 486 | custom_tlvs = {} |
| 487 | for tlv in custom_tlv: |
| 488 | tag = int(tlv[0], 0) |
| 489 | if tag in custom_tlvs: |
| 490 | raise click.UsageError('Custom TLV %s already exists.' % hex(tag)) |
| 491 | if tag in image.TLV_VALUES.values(): |
| 492 | raise click.UsageError( |
| 493 | 'Custom TLV %s conflicts with predefined TLV.' % hex(tag)) |
| 494 | |
| 495 | value = tlv[1] |
| 496 | if value.startswith('0x'): |
| 497 | if len(value[2:]) % 2: |
| 498 | raise click.UsageError('Custom TLV length is odd.') |
| 499 | custom_tlvs[tag] = bytes.fromhex(value[2:]) |
| 500 | else: |
| 501 | custom_tlvs[tag] = value.encode('utf-8') |
| 502 | |
Andrzej Puzdrowski | 160303c | 2022-03-15 15:41:14 +0100 | [diff] [blame] | 503 | # Allow signature calculated externally. |
| 504 | raw_signature = load_signature(fix_sig) if fix_sig else None |
| 505 | |
| 506 | baked_signature = None |
| 507 | pub_key = None |
| 508 | |
| 509 | if raw_signature is not None: |
| 510 | if fix_sig_pubkey is None: |
Antonio de Angelis | 7ba01c0 | 2022-11-15 15:10:41 +0000 | [diff] [blame] | 511 | raise click.UsageError( |
Andrzej Puzdrowski | 160303c | 2022-03-15 15:41:14 +0100 | [diff] [blame] | 512 | 'public key of the fixed signature is not specified') |
| 513 | |
| 514 | pub_key = load_key(fix_sig_pubkey) |
| 515 | |
| 516 | baked_signature = { |
Antonio de Angelis | 7ba01c0 | 2022-11-15 15:10:41 +0000 | [diff] [blame] | 517 | 'value': raw_signature |
Andrzej Puzdrowski | 160303c | 2022-03-15 15:41:14 +0100 | [diff] [blame] | 518 | } |
| 519 | |
Dominik Ermel | 1c04eac | 2024-09-12 19:37:40 +0000 | [diff] [blame] | 520 | if is_pure and user_sha != 'auto': |
| 521 | raise click.UsageError( |
| 522 | 'Pure signatures, currently, enforces preferred hash algorithm, ' |
| 523 | 'and forbids sha selection by user.') |
| 524 | |
Mateusz Michalek | 1da18e9 | 2025-01-02 14:48:28 +0100 | [diff] [blame] | 525 | if compression in ["lzma2", "lzma2armthumb"]: |
| 526 | img.create(key, public_key_format, enckey, dependencies, boot_record, |
Mateusz Michalek | 33de65c | 2024-10-15 16:01:18 +0200 | [diff] [blame] | 527 | custom_tlvs, compression_tlvs, None, int(encrypt_keylen), clear, |
Dominik Ermel | 1c04eac | 2024-09-12 19:37:40 +0000 | [diff] [blame] | 528 | baked_signature, pub_key, vector_to_sign, user_sha=user_sha, |
Mateusz Michalek | 1da18e9 | 2025-01-02 14:48:28 +0100 | [diff] [blame] | 529 | is_pure=is_pure, keep_comp_size=False, dont_encrypt=True) |
Mateusz Michalek | 63fa7e4 | 2024-08-09 12:16:40 +0200 | [diff] [blame] | 530 | compressed_img = image.Image(version=decode_version(version), |
| 531 | header_size=header_size, pad_header=pad_header, |
| 532 | pad=pad, confirm=confirm, align=int(align), |
| 533 | slot_size=slot_size, max_sectors=max_sectors, |
| 534 | overwrite_only=overwrite_only, endian=endian, |
| 535 | load_addr=load_addr, rom_fixed=rom_fixed, |
| 536 | erased_val=erased_val, save_enctlv=save_enctlv, |
| 537 | security_counter=security_counter, max_align=max_align) |
| 538 | compression_filters = [ |
| 539 | {"id": lzma.FILTER_LZMA2, "preset": comp_default_preset, |
| 540 | "dict_size": comp_default_dictsize, "lp": comp_default_lp, |
| 541 | "lc": comp_default_lc} |
| 542 | ] |
Mateusz Michalek | d69933c | 2024-10-04 13:36:52 +0200 | [diff] [blame] | 543 | if compression == "lzma2armthumb": |
| 544 | compression_filters.insert(0, {"id":lzma.FILTER_ARMTHUMB}) |
Mateusz Michalek | 63fa7e4 | 2024-08-09 12:16:40 +0200 | [diff] [blame] | 545 | compressed_data = lzma.compress(img.get_infile_data(),filters=compression_filters, |
| 546 | format=lzma.FORMAT_RAW) |
| 547 | uncompressed_size = len(img.get_infile_data()) |
| 548 | compressed_size = len(compressed_data) |
| 549 | print(f"compressed image size: {compressed_size} bytes") |
| 550 | print(f"original image size: {uncompressed_size} bytes") |
| 551 | compression_tlvs["DECOMP_SIZE"] = struct.pack( |
| 552 | img.get_struct_endian() + 'L', img.image_size) |
| 553 | compression_tlvs["DECOMP_SHA"] = img.image_hash |
| 554 | compression_tlvs_size = len(compression_tlvs["DECOMP_SIZE"]) |
| 555 | compression_tlvs_size += len(compression_tlvs["DECOMP_SHA"]) |
| 556 | if img.get_signature(): |
| 557 | compression_tlvs["DECOMP_SIGNATURE"] = img.get_signature() |
| 558 | compression_tlvs_size += len(compression_tlvs["DECOMP_SIGNATURE"]) |
| 559 | if (compressed_size + compression_tlvs_size) < uncompressed_size: |
| 560 | compression_header = create_lzma2_header( |
| 561 | dictsize = comp_default_dictsize, pb = comp_default_pb, |
| 562 | lc = comp_default_lc, lp = comp_default_lp) |
| 563 | compressed_img.load_compressed(compressed_data, compression_header) |
| 564 | compressed_img.base_addr = img.base_addr |
Mateusz Michalek | 1da18e9 | 2025-01-02 14:48:28 +0100 | [diff] [blame] | 565 | keep_comp_size = False; |
| 566 | if enckey: |
| 567 | keep_comp_size = True |
Mateusz Michalek | 63fa7e4 | 2024-08-09 12:16:40 +0200 | [diff] [blame] | 568 | compressed_img.create(key, public_key_format, enckey, |
| 569 | dependencies, boot_record, custom_tlvs, compression_tlvs, |
| 570 | compression, int(encrypt_keylen), clear, baked_signature, |
Dominik Ermel | 1c04eac | 2024-09-12 19:37:40 +0000 | [diff] [blame] | 571 | pub_key, vector_to_sign, user_sha=user_sha, |
Mateusz Michalek | 1da18e9 | 2025-01-02 14:48:28 +0100 | [diff] [blame] | 572 | is_pure=is_pure, keep_comp_size=keep_comp_size) |
Mateusz Michalek | 63fa7e4 | 2024-08-09 12:16:40 +0200 | [diff] [blame] | 573 | img = compressed_img |
Mateusz Michalek | 1da18e9 | 2025-01-02 14:48:28 +0100 | [diff] [blame] | 574 | else: |
| 575 | img.create(key, public_key_format, enckey, dependencies, boot_record, |
| 576 | custom_tlvs, compression_tlvs, None, int(encrypt_keylen), clear, |
| 577 | baked_signature, pub_key, vector_to_sign, user_sha=user_sha, |
| 578 | is_pure=is_pure) |
Mateusz Michalek | 63fa7e4 | 2024-08-09 12:16:40 +0200 | [diff] [blame] | 579 | img.save(outfile, hex_addr) |
Andrzej Puzdrowski | f72e374 | 2022-03-17 11:34:38 +0100 | [diff] [blame] | 580 | if sig_out is not None: |
| 581 | new_signature = img.get_signature() |
| 582 | save_signature(sig_out, new_signature) |
| 583 | |
Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 584 | |
| 585 | class AliasesGroup(click.Group): |
| 586 | |
| 587 | _aliases = { |
| 588 | "create": "sign", |
| 589 | } |
| 590 | |
| 591 | def list_commands(self, ctx): |
| 592 | cmds = [k for k in self.commands] |
| 593 | aliases = [k for k in self._aliases] |
| 594 | return sorted(cmds + aliases) |
| 595 | |
| 596 | def get_command(self, ctx, cmd_name): |
| 597 | rv = click.Group.get_command(self, ctx, cmd_name) |
| 598 | if rv is not None: |
| 599 | return rv |
| 600 | if cmd_name in self._aliases: |
| 601 | return click.Group.get_command(self, ctx, self._aliases[cmd_name]) |
| 602 | return None |
| 603 | |
| 604 | |
Fabio Utzig | 25c6a15 | 2019-09-10 12:52:26 -0300 | [diff] [blame] | 605 | @click.command(help='Print imgtool version information') |
| 606 | def version(): |
| 607 | print(imgtool_version) |
| 608 | |
| 609 | |
Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 610 | @click.command(cls=AliasesGroup, |
| 611 | context_settings=dict(help_option_names=['-h', '--help'])) |
| 612 | def imgtool(): |
| 613 | pass |
| 614 | |
| 615 | |
| 616 | imgtool.add_command(keygen) |
| 617 | imgtool.add_command(getpub) |
Bence Balogh | 97a20f1 | 2023-07-18 15:59:33 +0200 | [diff] [blame] | 618 | imgtool.add_command(getpubhash) |
Ioannis Konstantelias | 78e57c7 | 2019-11-28 16:06:12 +0200 | [diff] [blame] | 619 | imgtool.add_command(getpriv) |
Fabio Utzig | 4a5477a | 2019-05-27 15:45:08 -0300 | [diff] [blame] | 620 | imgtool.add_command(verify) |
Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 621 | imgtool.add_command(sign) |
Fabio Utzig | 25c6a15 | 2019-09-10 12:52:26 -0300 | [diff] [blame] | 622 | imgtool.add_command(version) |
David Vincze | ca56135 | 2023-01-27 15:39:12 +0100 | [diff] [blame] | 623 | imgtool.add_command(dumpinfo) |
Fabio Utzig | e89841d | 2018-12-21 11:19:06 -0200 | [diff] [blame] | 624 | |
| 625 | |
| 626 | if __name__ == '__main__': |
| 627 | imgtool() |