|
| 1 | +# Copyright (c) 2018 Foundries.io |
| 2 | +# |
| 3 | +# SPDX-License-Identifier: Apache-2.0 |
| 4 | + |
| 5 | +import abc |
| 6 | +import argparse |
| 7 | +import os |
| 8 | +import subprocess |
| 9 | + |
| 10 | +from west import cmake |
| 11 | +from west import log |
| 12 | +from west.build import is_zephyr_build |
| 13 | +from west.util import quote_sh_list |
| 14 | + |
| 15 | +from runners.core import BuildConfiguration |
| 16 | + |
| 17 | +from zephyr_ext_common import find_build_dir, Forceable, \ |
| 18 | + BUILD_DIR_DESCRIPTION, cached_runner_config |
| 19 | + |
| 20 | +SIGN_DESCRIPTION = '''\ |
| 21 | +This command automates some of the drudgery of creating signed Zephyr |
| 22 | +binaries for chain-loading by a bootloader. |
| 23 | +
|
| 24 | +In the simplest usage, run this from your build directory: |
| 25 | +
|
| 26 | + west sign -t your_tool -- ARGS_FOR_YOUR_TOOL |
| 27 | +
|
| 28 | +Assuming your binary was properly built for processing and handling by |
| 29 | +tool "your_tool", this creates zephyr.signed.bin and zephyr.signed.hex |
| 30 | +files (if supported by "your_tool") which are ready for use by your |
| 31 | +bootloader. The "ARGS_FOR_YOUR_TOOL" value can be any additional |
| 32 | +arguments you want to pass to the tool, such as the location of a |
| 33 | +signing key, a version identifier, etc. |
| 34 | +
|
| 35 | +See tool-specific help below for details.''' |
| 36 | + |
| 37 | +SIGN_EPILOG = '''\ |
| 38 | +imgtool |
| 39 | +------- |
| 40 | +
|
| 41 | +Currently, MCUboot's 'imgtool' tool is supported. To build a signed |
| 42 | +binary you can load with MCUboot using imgtool, run this from your |
| 43 | +build directory: |
| 44 | +
|
| 45 | + west sign -t imgtool -- --key YOUR_SIGNING_KEY.pem |
| 46 | +
|
| 47 | +The image header size, alignment, and slot sizes are determined from |
| 48 | +the build directory using board information and the device tree. A |
| 49 | +default version number of 0.0.0+0 is used (which can be overridden by |
| 50 | +passing "--version x.y.z+w" after "--key"). As shown above, extra |
| 51 | +arguments after a '--' are passed to imgtool directly.''' |
| 52 | + |
| 53 | + |
| 54 | +class ToggleAction(argparse.Action): |
| 55 | + |
| 56 | + def __call__(self, parser, args, ignored, option): |
| 57 | + setattr(args, self.dest, not option.startswith('--no-')) |
| 58 | + |
| 59 | + |
| 60 | +class Sign(Forceable): |
| 61 | + def __init__(self): |
| 62 | + super(Sign, self).__init__( |
| 63 | + 'sign', |
| 64 | + # Keep this in sync with the string in west-commands.yml. |
| 65 | + 'sign a Zephyr binary for bootloader chain-loading', |
| 66 | + SIGN_DESCRIPTION, |
| 67 | + accepts_unknown_args=False) |
| 68 | + |
| 69 | + def do_add_parser(self, parser_adder): |
| 70 | + parser = parser_adder.add_parser( |
| 71 | + self.name, |
| 72 | + epilog=SIGN_EPILOG, |
| 73 | + help=self.help, |
| 74 | + formatter_class=argparse.RawDescriptionHelpFormatter, |
| 75 | + description=self.description) |
| 76 | + |
| 77 | + parser.add_argument('-d', '--build-dir', help=BUILD_DIR_DESCRIPTION) |
| 78 | + self.add_force_arg(parser) |
| 79 | + |
| 80 | + # general options |
| 81 | + group = parser.add_argument_group('tool control options') |
| 82 | + group.add_argument('-t', '--tool', choices=['imgtool'], |
| 83 | + help='image signing tool name') |
| 84 | + group.add_argument('-p', '--tool-path', default='imgtool', |
| 85 | + help='''path to the tool itself, if needed''') |
| 86 | + group.add_argument('tool_args', nargs='*', metavar='tool_opt', |
| 87 | + help='extra option(s) to pass to the signing tool') |
| 88 | + |
| 89 | + # bin file options |
| 90 | + group = parser.add_argument_group('binary (.bin) file options') |
| 91 | + group.add_argument('--bin', '--no-bin', dest='gen_bin', nargs=0, |
| 92 | + action=ToggleAction, |
| 93 | + help='''produce a signed .bin file? |
| 94 | + (default: yes, if supported)''') |
| 95 | + group.add_argument('-B', '--sbin', metavar='BIN', |
| 96 | + default='zephyr.signed.bin', |
| 97 | + help='''signed .bin file name |
| 98 | + (default: zephyr.signed.bin)''') |
| 99 | + |
| 100 | + # hex file options |
| 101 | + group = parser.add_argument_group('Intel HEX (.hex) file options') |
| 102 | + group.add_argument('--hex', '--no-hex', dest='gen_hex', nargs=0, |
| 103 | + action=ToggleAction, |
| 104 | + help='''produce a signed .hex file? |
| 105 | + (default: yes, if supported)''') |
| 106 | + group.add_argument('-H', '--shex', metavar='HEX', |
| 107 | + default='zephyr.signed.hex', |
| 108 | + help='''signed .hex file name |
| 109 | + (default: zephyr.signed.hex)''') |
| 110 | + |
| 111 | + # defaults for hex/bin generation |
| 112 | + parser.set_defaults(gen_bin=True, gen_hex=True) |
| 113 | + |
| 114 | + return parser |
| 115 | + |
| 116 | + def do_run(self, args, ignored): |
| 117 | + if not (args.gen_bin or args.gen_hex): |
| 118 | + return |
| 119 | + |
| 120 | + self.check_force(os.path.isdir(args.build_dir), |
| 121 | + 'no such build directory {}'.format(args.build_dir)) |
| 122 | + self.check_force(is_zephyr_build(args.build_dir), |
| 123 | + "build directory {} doesn't look like a Zephyr build " |
| 124 | + 'directory'.format(args.build_dir)) |
| 125 | + |
| 126 | + if args.tool == 'imgtool': |
| 127 | + signer = ImgtoolSigner() |
| 128 | + # (Add support for other signers here in elif blocks) |
| 129 | + else: |
| 130 | + raise RuntimeError("can't happen") |
| 131 | + |
| 132 | + # Provide the build directory if not given, and defer to the signer. |
| 133 | + args.build_dir = find_build_dir(args.build_dir) |
| 134 | + signer.sign(args) |
| 135 | + |
| 136 | + |
| 137 | +class Signer(abc.ABC): |
| 138 | + '''Common abstract superclass for signers. |
| 139 | +
|
| 140 | + To add support for a new tool, subclass this and add support for |
| 141 | + it in the Sign.do_run() method.''' |
| 142 | + |
| 143 | + @abc.abstractmethod |
| 144 | + def sign(self, args): |
| 145 | + '''Abstract method to perform a signature; subclasses must implement. |
| 146 | +
|
| 147 | + :param args: parsed arguments from Sign command |
| 148 | + ''' |
| 149 | + |
| 150 | + |
| 151 | +class ImgtoolSigner(Signer): |
| 152 | + |
| 153 | + def sign(self, args): |
| 154 | + cache = cmake.CMakeCache.from_build_dir(args.build_dir) |
| 155 | + runner_config = cached_runner_config(args.build_dir, cache) |
| 156 | + bcfg = BuildConfiguration(args.build_dir) |
| 157 | + |
| 158 | + # Build a signed .bin |
| 159 | + if args.gen_bin and runner_config.bin_file: |
| 160 | + sign_bin = self.sign_cmd(args, bcfg, runner_config.bin_file, |
| 161 | + args.sbin) |
| 162 | + log.dbg(quote_sh_list(sign_bin)) |
| 163 | + subprocess.check_call(sign_bin) |
| 164 | + |
| 165 | + # Build a signed .hex |
| 166 | + if args.gen_hex and runner_config.hex_file: |
| 167 | + sign_hex = self.sign_cmd(args, bcfg, runner_config.hex_file, |
| 168 | + args.shex) |
| 169 | + log.dbg(quote_sh_list(sign_hex)) |
| 170 | + subprocess.check_call(sign_hex) |
| 171 | + |
| 172 | + def sign_cmd(self, args, bcfg, infile, outfile): |
| 173 | + align = str(bcfg['FLASH_WRITE_BLOCK_SIZE']) |
| 174 | + vtoff = str(bcfg['CONFIG_TEXT_SECTION_OFFSET']) |
| 175 | + slot_size = str(bcfg['FLASH_AREA_IMAGE_0_SIZE']) |
| 176 | + |
| 177 | + sign_command = [args.tool_path or 'imgtool', |
| 178 | + 'sign', |
| 179 | + '--align', align, |
| 180 | + '--header-size', vtoff, |
| 181 | + '--slot-size', slot_size, |
| 182 | + # We provide a default --version in case the |
| 183 | + # user is just messing around and doesn't want |
| 184 | + # to set one. It will be overridden if there is |
| 185 | + # a --version in args.tool_args. |
| 186 | + '--version', '0.0.0+0', |
| 187 | + infile, |
| 188 | + outfile] |
| 189 | + |
| 190 | + sign_command.extend(args.tool_args) |
| 191 | + |
| 192 | + return sign_command |
0 commit comments