Marti Bolivar | d1780aa | 2019-01-30 20:30:42 -0700 | [diff] [blame^] | 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 |